mirror of
https://github.com/royshil/obs-localvocal.git
synced 2026-01-09 12:28:05 -05:00
Add support for hevc and av1 to WebVTT-in-video-stream (#204)
* Squashed 'deps/c-webvtt-in-video-stream/' changes from 5579ca6..3d44cbd 3d44cbd Add h265 support 7f96bcf Extract various bits of h264 functionality to prepare for h265 support 698793a Prepare `h264::NalUnitWrite` for reuse with h265 git-subtree-dir: deps/c-webvtt-in-video-stream git-subtree-split: 3d44cbd5039e3ea247972f44f73a66e08cea6e71 * Add h265 support * Squashed 'deps/c-webvtt-in-video-stream/' changes from 3d44cbd5..d599f6f0 d599f6f0 Add av1 support 11693f6a Extract header writing semantics e7eb1894 Convert h264/h265 header writing to bitwriters git-subtree-dir: deps/c-webvtt-in-video-stream git-subtree-split: d599f6f0142e24ba9a7daeee252da9f055aa39c4 * Add av1 support * Fix subtitle track name lookup
This commit is contained in:
9
deps/c-webvtt-in-video-stream/Cargo.lock
generated
vendored
9
deps/c-webvtt-in-video-stream/Cargo.lock
generated
vendored
@@ -63,6 +63,12 @@ version = "1.10.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e445576659fd04a57b44cbd00aa37aaa815ebefa0aa3cb677a6b5e63d883074f"
|
checksum = "e445576659fd04a57b44cbd00aa37aaa815ebefa0aa3cb677a6b5e63d883074f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitstream-io"
|
||||||
|
version = "2.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@@ -172,7 +178,7 @@ version = "0.7.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bd118dcc322cc71cfc33254a19ebece92cfaaf6d4b4793fec3f7f44fbc4150df"
|
checksum = "bd118dcc322cc71cfc33254a19ebece92cfaaf6d4b4793fec3f7f44fbc4150df"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitstream-io",
|
"bitstream-io 1.10.0",
|
||||||
"hex-slice",
|
"hex-slice",
|
||||||
"log",
|
"log",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -484,6 +490,7 @@ checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
|
|||||||
name = "video-bytestream-tools"
|
name = "video-bytestream-tools"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bitstream-io 2.6.0",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"h264-reader",
|
"h264-reader",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
|||||||
51
deps/c-webvtt-in-video-stream/src/lib.rs
vendored
51
deps/c-webvtt-in-video-stream/src/lib.rs
vendored
@@ -5,7 +5,10 @@ use std::{
|
|||||||
};
|
};
|
||||||
use strum_macros::FromRepr;
|
use strum_macros::FromRepr;
|
||||||
use video_bytestream_tools::{
|
use video_bytestream_tools::{
|
||||||
h264::{self, H264ByteStreamWrite, NalHeader, NalUnitWrite, RbspWrite},
|
av1,
|
||||||
|
h264::{self, H264ByteStreamWrite, H264NalHeader},
|
||||||
|
h265::{self, H265ByteStreamWrite, H265NalHeader},
|
||||||
|
h26x::{NalUnitWrite, RbspWrite},
|
||||||
webvtt::WebvttWrite,
|
webvtt::WebvttWrite,
|
||||||
};
|
};
|
||||||
use webvtt_in_video_stream::{WebvttMuxer, WebvttMuxerBuilder, WebvttString};
|
use webvtt_in_video_stream::{WebvttMuxer, WebvttMuxerBuilder, WebvttString};
|
||||||
@@ -103,6 +106,8 @@ enum CodecFlavor {
|
|||||||
H264Avcc2,
|
H264Avcc2,
|
||||||
H264Avcc4,
|
H264Avcc4,
|
||||||
H264AnnexB,
|
H264AnnexB,
|
||||||
|
H265AnnexB,
|
||||||
|
AV1OBUs,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CodecFlavor {
|
impl CodecFlavor {
|
||||||
@@ -112,6 +117,8 @@ impl CodecFlavor {
|
|||||||
CodecFlavor::H264Avcc2 => CodecFlavorInternal::H264(CodecFlavorH264::Avcc(2)),
|
CodecFlavor::H264Avcc2 => CodecFlavorInternal::H264(CodecFlavorH264::Avcc(2)),
|
||||||
CodecFlavor::H264Avcc4 => CodecFlavorInternal::H264(CodecFlavorH264::Avcc(4)),
|
CodecFlavor::H264Avcc4 => CodecFlavorInternal::H264(CodecFlavorH264::Avcc(4)),
|
||||||
CodecFlavor::H264AnnexB => CodecFlavorInternal::H264(CodecFlavorH264::AnnexB),
|
CodecFlavor::H264AnnexB => CodecFlavorInternal::H264(CodecFlavorH264::AnnexB),
|
||||||
|
CodecFlavor::H265AnnexB => CodecFlavorInternal::H265(CodecFlavorH265::AnnexB),
|
||||||
|
CodecFlavor::AV1OBUs => CodecFlavorInternal::AV1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,8 +128,14 @@ enum CodecFlavorH264 {
|
|||||||
AnnexB,
|
AnnexB,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum CodecFlavorH265 {
|
||||||
|
AnnexB,
|
||||||
|
}
|
||||||
|
|
||||||
enum CodecFlavorInternal {
|
enum CodecFlavorInternal {
|
||||||
H264(CodecFlavorH264),
|
H264(CodecFlavorH264),
|
||||||
|
H265(CodecFlavorH265),
|
||||||
|
AV1,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WebvttBuffer(Vec<u8>);
|
pub struct WebvttBuffer(Vec<u8>);
|
||||||
@@ -150,8 +163,13 @@ pub extern "C" fn webvtt_muxer_try_mux_into_bytestream(
|
|||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_nal_header() -> NalHeader {
|
fn create_nal_header() -> H264NalHeader {
|
||||||
NalHeader::from_nal_unit_type_and_nal_ref_idc(h264_reader::nal::UnitType::SEI, 0).unwrap()
|
H264NalHeader::from_nal_unit_type_and_nal_ref_idc(h264_reader::nal::UnitType::SEI, 0)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_h265_nal_header() -> H265NalHeader {
|
||||||
|
H265NalHeader::from_nal_unit_type_and_nuh_ids(h265::UnitType::PrefixSeiNut, 0, 0).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inner(
|
fn inner(
|
||||||
@@ -197,6 +215,33 @@ pub extern "C" fn webvtt_muxer_try_mux_into_bytestream(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
.ok()?,
|
.ok()?,
|
||||||
|
|
||||||
|
CodecFlavorInternal::H265(CodecFlavorH265::AnnexB) => mux_into_bytestream(
|
||||||
|
muxer,
|
||||||
|
video_timestamp,
|
||||||
|
add_header,
|
||||||
|
&mut buffer,
|
||||||
|
|buffer| -> Result<h265::annex_b::AnnexBRbspWriter<_>, Box<dyn Error>> {
|
||||||
|
Ok(h265::annex_b::AnnexBWriter::new(buffer)
|
||||||
|
.start_write_nal_unit()?
|
||||||
|
.write_nal_header(create_h265_nal_header())?)
|
||||||
|
},
|
||||||
|
|write| {
|
||||||
|
write.finish_rbsp()?;
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.ok()?,
|
||||||
|
|
||||||
|
CodecFlavorInternal::AV1 => mux_into_bytestream(
|
||||||
|
muxer,
|
||||||
|
video_timestamp,
|
||||||
|
add_header,
|
||||||
|
&mut buffer,
|
||||||
|
|buffer| Ok(av1::OBUWriter::new(buffer)),
|
||||||
|
|_write| Ok(()),
|
||||||
|
)
|
||||||
|
.ok()?,
|
||||||
};
|
};
|
||||||
if !data_written {
|
if !data_written {
|
||||||
return None;
|
return None;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ name = "video-bytestream-tools"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bitstream-io = "2.6.0"
|
||||||
byteorder = "1.5.0"
|
byteorder = "1.5.0"
|
||||||
h264-reader = "0.7.0"
|
h264-reader = "0.7.0"
|
||||||
thiserror = "2.0.4"
|
thiserror = "2.0.4"
|
||||||
|
|||||||
337
deps/c-webvtt-in-video-stream/video-bytestream-tools/src/av1.rs
vendored
Normal file
337
deps/c-webvtt-in-video-stream/video-bytestream-tools/src/av1.rs
vendored
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
use crate::webvtt::{
|
||||||
|
write_webvtt_header, write_webvtt_payload, CountingSink, WebvttTrack, WebvttWrite,
|
||||||
|
};
|
||||||
|
use bitstream_io::{BigEndian, BitWrite, BitWriter};
|
||||||
|
use byteorder::WriteBytesExt;
|
||||||
|
use std::{
|
||||||
|
io::{Cursor, Write},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
type Result<T, E = std::io::Error> = std::result::Result<T, E>;
|
||||||
|
|
||||||
|
pub trait WriteLeb128Ext: BitWrite {
|
||||||
|
fn write_leb128(&mut self, mut val: u32) -> std::io::Result<()> {
|
||||||
|
loop {
|
||||||
|
let bits = u8::try_from(val & 0b111_1111).unwrap();
|
||||||
|
val >>= 7;
|
||||||
|
self.write_bit(val != 0)?;
|
||||||
|
self.write(7, bits)?;
|
||||||
|
if val == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: BitWrite + ?Sized> WriteLeb128Ext for W {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct OBUHeaderWithSize {
|
||||||
|
obu_type: OBUType,
|
||||||
|
obu_size: Option<u32>,
|
||||||
|
obu_extension_header: Option<OBUExtensionHeader>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum OBUType {
|
||||||
|
Reserved0,
|
||||||
|
SequenceHeader,
|
||||||
|
TemporalDelimiter,
|
||||||
|
FrameHeader,
|
||||||
|
TileGroup,
|
||||||
|
Metadata,
|
||||||
|
Frame,
|
||||||
|
RedundantFrameHeader,
|
||||||
|
TileList,
|
||||||
|
Reserved9,
|
||||||
|
Reserved10,
|
||||||
|
Reserved11,
|
||||||
|
Reserved12,
|
||||||
|
Reserved13,
|
||||||
|
Reserved14,
|
||||||
|
Padding,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum MetadataType {
|
||||||
|
ReservedForAOMUse,
|
||||||
|
HdrCll,
|
||||||
|
HdrMdcv,
|
||||||
|
Scalability,
|
||||||
|
ItutT35,
|
||||||
|
Timecode,
|
||||||
|
UnregisteredPrivate6,
|
||||||
|
UnregisteredPrivate7,
|
||||||
|
UnregisteredPrivate8,
|
||||||
|
UnregisteredPrivate9,
|
||||||
|
UnregisteredPrivate10,
|
||||||
|
UnregisteredPrivate11,
|
||||||
|
UnregisteredPrivate12,
|
||||||
|
UnregisteredPrivate13,
|
||||||
|
UnregisteredPrivate14,
|
||||||
|
UnregisteredPrivate15,
|
||||||
|
UnregisteredPrivate16,
|
||||||
|
UnregisteredPrivate17,
|
||||||
|
UnregisteredPrivate18,
|
||||||
|
UnregisteredPrivate19,
|
||||||
|
UnregisteredPrivate20,
|
||||||
|
UnregisteredPrivate21,
|
||||||
|
UnregisteredPrivate22,
|
||||||
|
UnregisteredPrivate23,
|
||||||
|
UnregisteredPrivate24,
|
||||||
|
UnregisteredPrivate25,
|
||||||
|
UnregisteredPrivate26,
|
||||||
|
UnregisteredPrivate27,
|
||||||
|
UnregisteredPrivate28,
|
||||||
|
UnregisteredPrivate29,
|
||||||
|
UnregisteredPrivate30,
|
||||||
|
UnregisteredPrivate31,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct OBUExtensionHeader {
|
||||||
|
temporal_id: u8,
|
||||||
|
spatial_id: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OBUHeaderWithSize {
|
||||||
|
pub fn new(
|
||||||
|
obu_type: OBUType,
|
||||||
|
obu_size: Option<u32>,
|
||||||
|
obu_extension_header: Option<OBUExtensionHeader>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
obu_type,
|
||||||
|
obu_size,
|
||||||
|
obu_extension_header,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_header_bytes(self, buffer: &mut [u8; 10]) -> Result<&[u8]> {
|
||||||
|
let mut cursor = Cursor::new(&mut buffer[..]);
|
||||||
|
let mut writer = BitWriter::endian(&mut cursor, BigEndian);
|
||||||
|
writer.write(1, 0)?;
|
||||||
|
writer.write(4, self.obu_type.id())?;
|
||||||
|
writer.write_bit(self.obu_extension_header.is_some())?;
|
||||||
|
writer.write_bit(self.obu_size.is_some())?;
|
||||||
|
writer.write(1, 0)?;
|
||||||
|
if let Some(extension_header) = self.obu_extension_header {
|
||||||
|
writer.write(3, extension_header.temporal_id)?;
|
||||||
|
writer.write(2, extension_header.spatial_id)?;
|
||||||
|
writer.write(3, 0)?;
|
||||||
|
}
|
||||||
|
if let Some(size) = self.obu_size {
|
||||||
|
writer.write_leb128(size)?;
|
||||||
|
}
|
||||||
|
assert!(writer.into_unwritten() == (0, 0));
|
||||||
|
let written = usize::try_from(cursor.position()).unwrap();
|
||||||
|
Ok(&buffer[..written])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OBUType {
|
||||||
|
pub fn id(self) -> u8 {
|
||||||
|
match self {
|
||||||
|
OBUType::Reserved0 => 0,
|
||||||
|
OBUType::SequenceHeader => 1,
|
||||||
|
OBUType::TemporalDelimiter => 2,
|
||||||
|
OBUType::FrameHeader => 3,
|
||||||
|
OBUType::TileGroup => 4,
|
||||||
|
OBUType::Metadata => 5,
|
||||||
|
OBUType::Frame => 6,
|
||||||
|
OBUType::RedundantFrameHeader => 7,
|
||||||
|
OBUType::TileList => 8,
|
||||||
|
OBUType::Reserved9 => 9,
|
||||||
|
OBUType::Reserved10 => 10,
|
||||||
|
OBUType::Reserved11 => 11,
|
||||||
|
OBUType::Reserved12 => 12,
|
||||||
|
OBUType::Reserved13 => 13,
|
||||||
|
OBUType::Reserved14 => 14,
|
||||||
|
OBUType::Padding => 15,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MetadataType {
|
||||||
|
fn id(self) -> u32 {
|
||||||
|
match self {
|
||||||
|
MetadataType::ReservedForAOMUse => 0,
|
||||||
|
MetadataType::HdrCll => 1,
|
||||||
|
MetadataType::HdrMdcv => 2,
|
||||||
|
MetadataType::Scalability => 3,
|
||||||
|
MetadataType::ItutT35 => 4,
|
||||||
|
MetadataType::Timecode => 5,
|
||||||
|
MetadataType::UnregisteredPrivate6 => 6,
|
||||||
|
MetadataType::UnregisteredPrivate7 => 7,
|
||||||
|
MetadataType::UnregisteredPrivate8 => 8,
|
||||||
|
MetadataType::UnregisteredPrivate9 => 9,
|
||||||
|
MetadataType::UnregisteredPrivate10 => 10,
|
||||||
|
MetadataType::UnregisteredPrivate11 => 11,
|
||||||
|
MetadataType::UnregisteredPrivate12 => 12,
|
||||||
|
MetadataType::UnregisteredPrivate13 => 13,
|
||||||
|
MetadataType::UnregisteredPrivate14 => 14,
|
||||||
|
MetadataType::UnregisteredPrivate15 => 15,
|
||||||
|
MetadataType::UnregisteredPrivate16 => 16,
|
||||||
|
MetadataType::UnregisteredPrivate17 => 17,
|
||||||
|
MetadataType::UnregisteredPrivate18 => 18,
|
||||||
|
MetadataType::UnregisteredPrivate19 => 19,
|
||||||
|
MetadataType::UnregisteredPrivate20 => 20,
|
||||||
|
MetadataType::UnregisteredPrivate21 => 21,
|
||||||
|
MetadataType::UnregisteredPrivate22 => 22,
|
||||||
|
MetadataType::UnregisteredPrivate23 => 23,
|
||||||
|
MetadataType::UnregisteredPrivate24 => 24,
|
||||||
|
MetadataType::UnregisteredPrivate25 => 25,
|
||||||
|
MetadataType::UnregisteredPrivate26 => 26,
|
||||||
|
MetadataType::UnregisteredPrivate27 => 27,
|
||||||
|
MetadataType::UnregisteredPrivate28 => 28,
|
||||||
|
MetadataType::UnregisteredPrivate29 => 29,
|
||||||
|
MetadataType::UnregisteredPrivate30 => 30,
|
||||||
|
MetadataType::UnregisteredPrivate31 => 31,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum OBUExtensionHeaderError {
|
||||||
|
TemporalIdOutOfRange(u8),
|
||||||
|
SpatialIdOutOfRange(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OBUExtensionHeader {
|
||||||
|
pub fn new(temporal_id: u8, spatial_id: u8) -> Result<Self, OBUExtensionHeaderError> {
|
||||||
|
if temporal_id > 0b111 {
|
||||||
|
return Err(OBUExtensionHeaderError::TemporalIdOutOfRange(temporal_id));
|
||||||
|
}
|
||||||
|
if spatial_id > 0b11 {
|
||||||
|
return Err(OBUExtensionHeaderError::SpatialIdOutOfRange(spatial_id));
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
temporal_id,
|
||||||
|
spatial_id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct OBUWriter<W: ?Sized + Write>(W);
|
||||||
|
|
||||||
|
impl<W: Write> OBUWriter<W> {
|
||||||
|
pub fn new(inner: W) -> Self {
|
||||||
|
Self(inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: ?Sized + Write> OBUWriter<W> {
|
||||||
|
fn write_obu_header(&mut self, obu_header: OBUHeaderWithSize) -> Result<()> {
|
||||||
|
let mut buffer = [0u8; 10];
|
||||||
|
let header_bytes = obu_header.as_header_bytes(&mut buffer)?;
|
||||||
|
self.0.write_all(header_bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish_payload(&mut self) -> Result<()> {
|
||||||
|
self.0.write_u8(0b1000_0000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Write + ?Sized> WebvttWrite for OBUWriter<W> {
|
||||||
|
fn write_webvtt_header(
|
||||||
|
&mut self,
|
||||||
|
max_latency_to_video: Duration,
|
||||||
|
send_frequency_hz: u8,
|
||||||
|
subtitle_tracks: &[WebvttTrack],
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
fn inner<W: ?Sized + Write>(
|
||||||
|
writer: &mut W,
|
||||||
|
max_latency_to_video: Duration,
|
||||||
|
send_frequency_hz: u8,
|
||||||
|
subtitle_tracks: &[WebvttTrack],
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
write_webvtt_header(
|
||||||
|
writer,
|
||||||
|
max_latency_to_video,
|
||||||
|
send_frequency_hz,
|
||||||
|
subtitle_tracks,
|
||||||
|
|write, _size| {
|
||||||
|
let mut writer = BitWriter::endian(write, BigEndian);
|
||||||
|
writer.write_leb128(MetadataType::UnregisteredPrivate6.id())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
let mut count = CountingSink::new();
|
||||||
|
inner(
|
||||||
|
&mut count,
|
||||||
|
max_latency_to_video,
|
||||||
|
send_frequency_hz,
|
||||||
|
subtitle_tracks,
|
||||||
|
)?;
|
||||||
|
let header = OBUHeaderWithSize::new(
|
||||||
|
OBUType::Metadata,
|
||||||
|
Some(u32::try_from(count.count()).unwrap() + 1),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
self.write_obu_header(header)?;
|
||||||
|
inner(
|
||||||
|
&mut self.0,
|
||||||
|
max_latency_to_video,
|
||||||
|
send_frequency_hz,
|
||||||
|
subtitle_tracks,
|
||||||
|
)?;
|
||||||
|
self.finish_payload()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_webvtt_payload(
|
||||||
|
&mut self,
|
||||||
|
track_index: u8,
|
||||||
|
chunk_number: u64,
|
||||||
|
chunk_version: u8,
|
||||||
|
video_offset: Duration,
|
||||||
|
webvtt_payload: &str, // TODO: replace with string type that checks for interior NULs
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
fn inner<W: ?Sized + Write>(
|
||||||
|
writer: &mut W,
|
||||||
|
track_index: u8,
|
||||||
|
chunk_number: u64,
|
||||||
|
chunk_version: u8,
|
||||||
|
video_offset: Duration,
|
||||||
|
webvtt_payload: &str,
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
write_webvtt_payload(
|
||||||
|
writer,
|
||||||
|
track_index,
|
||||||
|
chunk_number,
|
||||||
|
chunk_version,
|
||||||
|
video_offset,
|
||||||
|
webvtt_payload,
|
||||||
|
|write, _size| {
|
||||||
|
let mut writer = BitWriter::endian(write, BigEndian);
|
||||||
|
writer.write_leb128(MetadataType::UnregisteredPrivate6.id())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
let mut count = CountingSink::new();
|
||||||
|
inner(
|
||||||
|
&mut count,
|
||||||
|
track_index,
|
||||||
|
chunk_number,
|
||||||
|
chunk_version,
|
||||||
|
video_offset,
|
||||||
|
webvtt_payload,
|
||||||
|
)?;
|
||||||
|
let header = OBUHeaderWithSize::new(
|
||||||
|
OBUType::Metadata,
|
||||||
|
Some(u32::try_from(count.count()).unwrap() + 1),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
self.write_obu_header(header)?;
|
||||||
|
inner(
|
||||||
|
&mut self.0,
|
||||||
|
track_index,
|
||||||
|
chunk_number,
|
||||||
|
chunk_version,
|
||||||
|
video_offset,
|
||||||
|
webvtt_payload,
|
||||||
|
)?;
|
||||||
|
self.finish_payload()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,34 +1,24 @@
|
|||||||
use crate::webvtt::{write_webvtt_header, write_webvtt_payload, WebvttTrack, WebvttWrite};
|
use crate::{
|
||||||
use byteorder::WriteBytesExt;
|
h26x::{annex_b::WriteNalHeader, NalUnitWrite, NalUnitWriter, RbspWrite, RbspWriter},
|
||||||
|
webvtt::{WebvttTrack, WebvttWrite},
|
||||||
|
};
|
||||||
|
use bitstream_io::{BigEndian, BitWrite, BitWriter};
|
||||||
use h264_reader::nal::UnitType;
|
use h264_reader::nal::UnitType;
|
||||||
use std::{collections::VecDeque, io::Write, time::Duration};
|
use std::{io::Write, time::Duration};
|
||||||
|
|
||||||
type Result<T, E = std::io::Error> = std::result::Result<T, E>;
|
type Result<T, E = std::io::Error> = std::result::Result<T, E>;
|
||||||
|
|
||||||
pub mod annex_b;
|
pub mod annex_b;
|
||||||
pub mod avcc;
|
pub mod avcc;
|
||||||
|
|
||||||
pub trait H264ByteStreamWrite<W: ?Sized + Write> {
|
|
||||||
type Writer: NalUnitWrite<W>;
|
|
||||||
fn start_write_nal_unit(self) -> Result<Self::Writer>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: Write> H264ByteStreamWrite<W> for W {
|
|
||||||
type Writer = NalUnitWriter<W>;
|
|
||||||
|
|
||||||
fn start_write_nal_unit(self) -> Result<Self::Writer> {
|
|
||||||
Ok(NalUnitWriter::new(self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct NalHeader {
|
pub struct H264NalHeader {
|
||||||
nal_unit_type: UnitType,
|
nal_unit_type: UnitType,
|
||||||
nal_ref_idc: u8,
|
nal_ref_idc: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum NalHeaderError {
|
pub enum H264NalHeaderError {
|
||||||
NalRefIdcOutOfRange(u8),
|
NalRefIdcOutOfRange(u8),
|
||||||
InvalidNalRefIdcForNalUnitType {
|
InvalidNalRefIdcForNalUnitType {
|
||||||
nal_unit_type: UnitType,
|
nal_unit_type: UnitType,
|
||||||
@@ -37,24 +27,24 @@ pub enum NalHeaderError {
|
|||||||
NalUnitTypeOutOfRange(UnitType),
|
NalUnitTypeOutOfRange(UnitType),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NalHeader {
|
impl H264NalHeader {
|
||||||
pub fn from_nal_unit_type_and_nal_ref_idc(
|
pub fn from_nal_unit_type_and_nal_ref_idc(
|
||||||
nal_unit_type: UnitType,
|
nal_unit_type: UnitType,
|
||||||
nal_ref_idc: u8,
|
nal_ref_idc: u8,
|
||||||
) -> Result<NalHeader, NalHeaderError> {
|
) -> Result<Self, H264NalHeaderError> {
|
||||||
if nal_ref_idc >= 4 {
|
if nal_ref_idc >= 4 {
|
||||||
return Err(NalHeaderError::NalRefIdcOutOfRange(nal_ref_idc));
|
return Err(H264NalHeaderError::NalRefIdcOutOfRange(nal_ref_idc));
|
||||||
}
|
}
|
||||||
match nal_unit_type.id() {
|
match nal_unit_type.id() {
|
||||||
0 => Err(NalHeaderError::NalUnitTypeOutOfRange(nal_unit_type)),
|
0 => Err(H264NalHeaderError::NalUnitTypeOutOfRange(nal_unit_type)),
|
||||||
6 | 9 | 10 | 11 | 12 => {
|
6 | 9 | 10 | 11 | 12 => {
|
||||||
if nal_ref_idc == 0 {
|
if nal_ref_idc == 0 {
|
||||||
Ok(NalHeader {
|
Ok(Self {
|
||||||
nal_unit_type,
|
nal_unit_type,
|
||||||
nal_ref_idc,
|
nal_ref_idc,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(NalHeaderError::InvalidNalRefIdcForNalUnitType {
|
Err(H264NalHeaderError::InvalidNalRefIdcForNalUnitType {
|
||||||
nal_unit_type,
|
nal_unit_type,
|
||||||
nal_ref_idc,
|
nal_ref_idc,
|
||||||
})
|
})
|
||||||
@@ -62,164 +52,85 @@ impl NalHeader {
|
|||||||
}
|
}
|
||||||
5 => {
|
5 => {
|
||||||
if nal_ref_idc != 0 {
|
if nal_ref_idc != 0 {
|
||||||
Ok(NalHeader {
|
Ok(Self {
|
||||||
nal_unit_type,
|
nal_unit_type,
|
||||||
nal_ref_idc,
|
nal_ref_idc,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(NalHeaderError::InvalidNalRefIdcForNalUnitType {
|
Err(H264NalHeaderError::InvalidNalRefIdcForNalUnitType {
|
||||||
nal_unit_type,
|
nal_unit_type,
|
||||||
nal_ref_idc,
|
nal_ref_idc,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
32.. => Err(NalHeaderError::NalUnitTypeOutOfRange(nal_unit_type)),
|
32.. => Err(H264NalHeaderError::NalUnitTypeOutOfRange(nal_unit_type)),
|
||||||
_ => Ok(NalHeader {
|
_ => Ok(Self {
|
||||||
nal_unit_type,
|
nal_unit_type,
|
||||||
nal_ref_idc,
|
nal_ref_idc,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_header_byte(&self) -> u8 {
|
fn as_header_bytes(&self) -> Result<[u8; 1]> {
|
||||||
self.nal_ref_idc << 5 | self.nal_unit_type.id()
|
let mut output = [0u8];
|
||||||
|
let mut writer = BitWriter::endian(&mut output[..], BigEndian);
|
||||||
|
writer.write(1, 0)?;
|
||||||
|
writer.write(2, self.nal_ref_idc)?;
|
||||||
|
writer.write(5, self.nal_unit_type.id())?;
|
||||||
|
assert!(writer.into_unwritten() == (0, 0));
|
||||||
|
Ok(output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NalUnitWriter<W: ?Sized + Write> {
|
impl<W: ?Sized + Write> WriteNalHeader<W> for H264NalHeader {
|
||||||
inner: W,
|
fn write_to(self, writer: &mut W) -> crate::h26x::Result<()> {
|
||||||
}
|
writer.write_all(&self.as_header_bytes()?[..])
|
||||||
|
|
||||||
pub trait NalUnitWrite<W: ?Sized + Write> {
|
|
||||||
type Writer: RbspWrite<W>;
|
|
||||||
fn write_nal_header(self, nal_header: NalHeader) -> Result<Self::Writer>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: Write> NalUnitWriter<W> {
|
|
||||||
fn new(inner: W) -> Self {
|
|
||||||
Self { inner }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: Write> NalUnitWrite<W> for NalUnitWriter<W> {
|
pub trait H264ByteStreamWrite<W: ?Sized + Write> {
|
||||||
type Writer = RbspWriter<W>;
|
type Writer: NalUnitWrite<W>;
|
||||||
|
fn start_write_nal_unit(self) -> Result<Self::Writer>;
|
||||||
|
}
|
||||||
|
|
||||||
fn write_nal_header(mut self, nal_header: NalHeader) -> Result<RbspWriter<W>> {
|
impl<W: Write> H264ByteStreamWrite<W> for W {
|
||||||
self.inner.write_u8(nal_header.as_header_byte())?;
|
type Writer = H264NalUnitWriter<W>;
|
||||||
Ok(RbspWriter::new(self.inner))
|
|
||||||
|
fn start_write_nal_unit(self) -> Result<Self::Writer> {
|
||||||
|
Ok(H264NalUnitWriter(NalUnitWriter::new(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RbspWriter<W: ?Sized + Write> {
|
pub struct H264NalUnitWriter<W: ?Sized + Write>(NalUnitWriter<W>);
|
||||||
last_written: VecDeque<u8>,
|
pub struct H264RbspWriter<W: ?Sized + Write>(RbspWriter<W>);
|
||||||
inner: W,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait RbspWrite<W: ?Sized + Write> {
|
impl<W: Write> NalUnitWrite<W> for H264NalUnitWriter<W> {
|
||||||
type Writer: H264ByteStreamWrite<W>;
|
type Writer = H264RbspWriter<W>;
|
||||||
fn finish_rbsp(self) -> Result<Self::Writer>;
|
type NalHeader = H264NalHeader;
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: Write> RbspWriter<W> {
|
fn write_nal_header(mut self, nal_header: H264NalHeader) -> Result<H264RbspWriter<W>> {
|
||||||
pub fn new(inner: W) -> Self {
|
self.0.inner.write_all(&nal_header.as_header_bytes()?[..])?;
|
||||||
Self {
|
Ok(H264RbspWriter(RbspWriter::new(self.0.inner)))
|
||||||
last_written: VecDeque::with_capacity(3),
|
|
||||||
inner,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: Write> RbspWrite<W> for RbspWriter<W> {
|
impl<W: Write> RbspWrite<W> for H264RbspWriter<W> {
|
||||||
type Writer = W;
|
type Writer = W;
|
||||||
fn finish_rbsp(mut self) -> Result<W> {
|
|
||||||
self.write_u8(0x80)?;
|
fn finish_rbsp(self) -> crate::h26x::Result<Self::Writer> {
|
||||||
Ok(self.inner)
|
self.0.finish_rbsp()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: ?Sized + Write> Write for RbspWriter<W> {
|
impl<W: Write + ?Sized> WebvttWrite for H264RbspWriter<W> {
|
||||||
fn write(&mut self, buf: &[u8]) -> Result<usize> {
|
|
||||||
let mut written = 0;
|
|
||||||
for &byte in buf {
|
|
||||||
let mut last_written_iter = self.last_written.iter();
|
|
||||||
if last_written_iter.next() == Some(&0)
|
|
||||||
&& last_written_iter.next() == Some(&0)
|
|
||||||
&& (byte == 0 || byte == 1 || byte == 2 || byte == 3)
|
|
||||||
{
|
|
||||||
self.inner.write_u8(3)?;
|
|
||||||
self.last_written.clear();
|
|
||||||
}
|
|
||||||
self.inner.write_u8(byte)?;
|
|
||||||
written += 1;
|
|
||||||
self.last_written.push_back(byte);
|
|
||||||
if self.last_written.len() > 2 {
|
|
||||||
self.last_written.pop_front();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(written)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&mut self) -> Result<()> {
|
|
||||||
self.inner.flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct CountingSink {
|
|
||||||
count: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CountingSink {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self { count: 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn count(&self) -> usize {
|
|
||||||
self.count
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Write for CountingSink {
|
|
||||||
fn write(&mut self, buf: &[u8]) -> Result<usize> {
|
|
||||||
self.count += buf.len();
|
|
||||||
Ok(buf.len())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&mut self) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn write_sei_header<W: ?Sized + Write>(
|
|
||||||
writer: &mut W,
|
|
||||||
mut payload_type: usize,
|
|
||||||
mut payload_size: usize,
|
|
||||||
) -> std::io::Result<()> {
|
|
||||||
while payload_type >= 255 {
|
|
||||||
writer.write_u8(255)?;
|
|
||||||
payload_type -= 255;
|
|
||||||
}
|
|
||||||
writer.write_u8(payload_type.try_into().unwrap())?;
|
|
||||||
while payload_size >= 255 {
|
|
||||||
writer.write_u8(255)?;
|
|
||||||
payload_size -= 255;
|
|
||||||
}
|
|
||||||
writer.write_u8(payload_size.try_into().unwrap())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: Write + ?Sized> WebvttWrite for RbspWriter<W> {
|
|
||||||
fn write_webvtt_header(
|
fn write_webvtt_header(
|
||||||
&mut self,
|
&mut self,
|
||||||
max_latency_to_video: Duration,
|
max_latency_to_video: Duration,
|
||||||
send_frequency_hz: u8,
|
send_frequency_hz: u8,
|
||||||
subtitle_tracks: &[WebvttTrack],
|
subtitle_tracks: &[WebvttTrack],
|
||||||
) -> std::io::Result<()> {
|
) -> std::io::Result<()> {
|
||||||
write_webvtt_header(
|
self.0
|
||||||
self,
|
.write_webvtt_header(max_latency_to_video, send_frequency_hz, subtitle_tracks)
|
||||||
max_latency_to_video,
|
|
||||||
send_frequency_hz,
|
|
||||||
subtitle_tracks,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_webvtt_payload(
|
fn write_webvtt_payload(
|
||||||
@@ -230,8 +141,7 @@ impl<W: Write + ?Sized> WebvttWrite for RbspWriter<W> {
|
|||||||
video_offset: Duration,
|
video_offset: Duration,
|
||||||
webvtt_payload: &str, // TODO: replace with string type that checks for interior NULs
|
webvtt_payload: &str, // TODO: replace with string type that checks for interior NULs
|
||||||
) -> std::io::Result<()> {
|
) -> std::io::Result<()> {
|
||||||
write_webvtt_payload(
|
self.0.write_webvtt_payload(
|
||||||
self,
|
|
||||||
track_index,
|
track_index,
|
||||||
chunk_number,
|
chunk_number,
|
||||||
chunk_version,
|
chunk_version,
|
||||||
@@ -244,7 +154,8 @@ impl<W: Write + ?Sized> WebvttWrite for RbspWriter<W> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
h264::{NalHeader, NalUnitWrite, NalUnitWriter, RbspWrite},
|
h264::{H264NalHeader, H264NalUnitWriter},
|
||||||
|
h26x::{NalUnitWrite, NalUnitWriter, RbspWrite},
|
||||||
webvtt::{WebvttWrite, PAYLOAD_GUID, USER_DATA_UNREGISTERED},
|
webvtt::{WebvttWrite, PAYLOAD_GUID, USER_DATA_UNREGISTERED},
|
||||||
};
|
};
|
||||||
use byteorder::{BigEndian, ReadBytesExt};
|
use byteorder::{BigEndian, ReadBytesExt};
|
||||||
@@ -255,11 +166,11 @@ mod tests {
|
|||||||
fn check_webvtt_sei() {
|
fn check_webvtt_sei() {
|
||||||
let mut writer = vec![];
|
let mut writer = vec![];
|
||||||
|
|
||||||
let nalu_writer = NalUnitWriter::new(&mut writer);
|
let nalu_writer = H264NalUnitWriter(NalUnitWriter::new(&mut writer));
|
||||||
let nal_unit_type = h264_reader::nal::UnitType::SEI;
|
let nal_unit_type = h264_reader::nal::UnitType::SEI;
|
||||||
let nal_ref_idc = 0;
|
let nal_ref_idc = 0;
|
||||||
let nal_header =
|
let nal_header =
|
||||||
NalHeader::from_nal_unit_type_and_nal_ref_idc(nal_unit_type, nal_ref_idc).unwrap();
|
H264NalHeader::from_nal_unit_type_and_nal_ref_idc(nal_unit_type, nal_ref_idc).unwrap();
|
||||||
let mut payload_writer = nalu_writer.write_nal_header(nal_header).unwrap();
|
let mut payload_writer = nalu_writer.write_nal_header(nal_header).unwrap();
|
||||||
let track_index = 0;
|
let track_index = 0;
|
||||||
let chunk_number = 1;
|
let chunk_number = 1;
|
||||||
@@ -308,11 +219,11 @@ mod tests {
|
|||||||
fn check_webvtt_multi_sei() {
|
fn check_webvtt_multi_sei() {
|
||||||
let mut writer = vec![];
|
let mut writer = vec![];
|
||||||
|
|
||||||
let nalu_writer = NalUnitWriter::new(&mut writer);
|
let nalu_writer = H264NalUnitWriter(NalUnitWriter::new(&mut writer));
|
||||||
let nal_unit_type = h264_reader::nal::UnitType::SEI;
|
let nal_unit_type = h264_reader::nal::UnitType::SEI;
|
||||||
let nal_ref_idc = 0;
|
let nal_ref_idc = 0;
|
||||||
let nal_header =
|
let nal_header =
|
||||||
NalHeader::from_nal_unit_type_and_nal_ref_idc(nal_unit_type, nal_ref_idc).unwrap();
|
H264NalHeader::from_nal_unit_type_and_nal_ref_idc(nal_unit_type, nal_ref_idc).unwrap();
|
||||||
let mut payload_writer = nalu_writer.write_nal_header(nal_header).unwrap();
|
let mut payload_writer = nalu_writer.write_nal_header(nal_header).unwrap();
|
||||||
let track_index = 0;
|
let track_index = 0;
|
||||||
let chunk_number = 1;
|
let chunk_number = 1;
|
||||||
|
|||||||
@@ -1,72 +1,50 @@
|
|||||||
use super::{
|
use crate::{
|
||||||
H264ByteStreamWrite, NalHeader, NalUnitWrite, NalUnitWriter, RbspWrite, RbspWriter, Result,
|
h264::{H264ByteStreamWrite, H264NalHeader},
|
||||||
|
h26x::{
|
||||||
|
annex_b::{
|
||||||
|
AnnexBNalUnitWriter as AnnexBNalUnitWriterImpl,
|
||||||
|
AnnexBRbspWriter as AnnexBRbspWriterImpl, AnnexBWriter as AnnexBWriterImpl,
|
||||||
|
},
|
||||||
|
NalUnitWrite, RbspWrite, Result,
|
||||||
|
},
|
||||||
|
webvtt::{WebvttTrack, WebvttWrite},
|
||||||
};
|
};
|
||||||
use crate::webvtt::{WebvttTrack, WebvttWrite};
|
|
||||||
use byteorder::WriteBytesExt;
|
|
||||||
use std::{io::Write, time::Duration};
|
use std::{io::Write, time::Duration};
|
||||||
|
|
||||||
pub struct AnnexBWriter<W: ?Sized + Write> {
|
pub struct AnnexBWriter<W: ?Sized + Write>(AnnexBWriterImpl<W>);
|
||||||
leading_zero_8bits_written: bool,
|
|
||||||
inner: W,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: Write> AnnexBWriter<W> {
|
impl<W: Write> AnnexBWriter<W> {
|
||||||
pub fn new(inner: W) -> Self {
|
pub fn new(inner: W) -> Self {
|
||||||
Self {
|
Self(AnnexBWriterImpl::new(inner))
|
||||||
leading_zero_8bits_written: false,
|
|
||||||
inner,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: Write> H264ByteStreamWrite<W> for AnnexBWriter<W> {
|
impl<W: Write> H264ByteStreamWrite<W> for AnnexBWriter<W> {
|
||||||
type Writer = AnnexBNalUnitWriter<W>;
|
type Writer = AnnexBNalUnitWriter<W>;
|
||||||
|
|
||||||
fn start_write_nal_unit(mut self) -> Result<AnnexBNalUnitWriter<W>> {
|
fn start_write_nal_unit(self) -> Result<AnnexBNalUnitWriter<W>> {
|
||||||
if !self.leading_zero_8bits_written {
|
self.0.start_write_nal_unit().map(AnnexBNalUnitWriter)
|
||||||
self.inner.write_u8(0)?;
|
|
||||||
self.leading_zero_8bits_written = true;
|
|
||||||
}
|
|
||||||
self.inner.write_all(&[0, 0, 1])?;
|
|
||||||
Ok(AnnexBNalUnitWriter {
|
|
||||||
inner: NalUnitWriter::new(self.inner),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AnnexBNalUnitWriter<W: ?Sized + Write> {
|
pub struct AnnexBNalUnitWriter<W: ?Sized + Write>(AnnexBNalUnitWriterImpl<W>);
|
||||||
inner: NalUnitWriter<W>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: Write> AnnexBNalUnitWriter<W> {
|
|
||||||
fn _nal_unit_writer(&mut self) -> &mut NalUnitWriter<W> {
|
|
||||||
&mut self.inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: Write> NalUnitWrite<W> for AnnexBNalUnitWriter<W> {
|
impl<W: Write> NalUnitWrite<W> for AnnexBNalUnitWriter<W> {
|
||||||
type Writer = AnnexBRbspWriter<W>;
|
type Writer = AnnexBRbspWriter<W>;
|
||||||
|
type NalHeader = H264NalHeader;
|
||||||
|
|
||||||
fn write_nal_header(self, nal_header: NalHeader) -> Result<AnnexBRbspWriter<W>> {
|
fn write_nal_header(self, nal_header: Self::NalHeader) -> Result<AnnexBRbspWriter<W>> {
|
||||||
self.inner
|
self.0.write_nal_header(nal_header).map(AnnexBRbspWriter)
|
||||||
.write_nal_header(nal_header)
|
|
||||||
.map(|inner| AnnexBRbspWriter { inner })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AnnexBRbspWriter<W: ?Sized + Write> {
|
pub struct AnnexBRbspWriter<W: ?Sized + Write>(AnnexBRbspWriterImpl<W>);
|
||||||
inner: RbspWriter<W>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: ?Sized + Write> AnnexBRbspWriter<W> {}
|
|
||||||
|
|
||||||
impl<W: Write> RbspWrite<W> for AnnexBRbspWriter<W> {
|
impl<W: Write> RbspWrite<W> for AnnexBRbspWriter<W> {
|
||||||
type Writer = AnnexBWriter<W>;
|
type Writer = AnnexBWriter<W>;
|
||||||
|
|
||||||
fn finish_rbsp(self) -> Result<Self::Writer> {
|
fn finish_rbsp(self) -> Result<Self::Writer> {
|
||||||
self.inner
|
self.0.finish_rbsp().map(AnnexBWriter)
|
||||||
.finish_rbsp()
|
|
||||||
.map(|writer| AnnexBWriter::new(writer))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +55,7 @@ impl<W: Write + ?Sized> WebvttWrite for AnnexBRbspWriter<W> {
|
|||||||
send_frequency_hz: u8,
|
send_frequency_hz: u8,
|
||||||
subtitle_tracks: &[WebvttTrack],
|
subtitle_tracks: &[WebvttTrack],
|
||||||
) -> std::io::Result<()> {
|
) -> std::io::Result<()> {
|
||||||
self.inner
|
self.0
|
||||||
.write_webvtt_header(max_latency_to_video, send_frequency_hz, subtitle_tracks)
|
.write_webvtt_header(max_latency_to_video, send_frequency_hz, subtitle_tracks)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +67,7 @@ impl<W: Write + ?Sized> WebvttWrite for AnnexBRbspWriter<W> {
|
|||||||
video_offset: Duration,
|
video_offset: Duration,
|
||||||
webvtt_payload: &str, // TODO: replace with string type that checks for interior NULs
|
webvtt_payload: &str, // TODO: replace with string type that checks for interior NULs
|
||||||
) -> std::io::Result<()> {
|
) -> std::io::Result<()> {
|
||||||
self.inner.write_webvtt_payload(
|
self.0.write_webvtt_payload(
|
||||||
track_index,
|
track_index,
|
||||||
chunk_number,
|
chunk_number,
|
||||||
chunk_version,
|
chunk_version,
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
use super::{
|
use crate::{
|
||||||
H264ByteStreamWrite, NalHeader, NalUnitWrite, NalUnitWriter, RbspWrite, RbspWriter, Result,
|
h264::{H264ByteStreamWrite, H264NalHeader},
|
||||||
|
h26x::{NalUnitWrite, NalUnitWriter, RbspWrite, Result},
|
||||||
|
webvtt::{WebvttTrack, WebvttWrite},
|
||||||
};
|
};
|
||||||
use crate::webvtt::{WebvttTrack, WebvttWrite};
|
|
||||||
use byteorder::{BigEndian, WriteBytesExt};
|
use byteorder::{BigEndian, WriteBytesExt};
|
||||||
use std::{io::Write, time::Duration};
|
use std::{io::Write, time::Duration};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use super::{H264NalUnitWriter, H264RbspWriter};
|
||||||
|
|
||||||
const AVCC_MAX_LENGTH: [usize; 4] = [0xff, 0xff_ff, 0, 0xff_ff_ff_ff];
|
const AVCC_MAX_LENGTH: [usize; 4] = [0xff, 0xff_ff, 0, 0xff_ff_ff_ff];
|
||||||
|
|
||||||
pub struct AVCCWriter<W: ?Sized + Write> {
|
pub struct AVCCWriter<W: ?Sized + Write> {
|
||||||
@@ -38,7 +41,7 @@ impl<W: Write> H264ByteStreamWrite<W> for AVCCWriter<W> {
|
|||||||
|
|
||||||
fn start_write_nal_unit(self) -> Result<AVCCNalUnitWriter<AVCCWriterBuffer<W>>> {
|
fn start_write_nal_unit(self) -> Result<AVCCNalUnitWriter<AVCCWriterBuffer<W>>> {
|
||||||
Ok(AVCCNalUnitWriter {
|
Ok(AVCCNalUnitWriter {
|
||||||
inner: NalUnitWriter::new(AVCCWriterBuffer::new(self)),
|
inner: H264NalUnitWriter(NalUnitWriter::new(AVCCWriterBuffer::new(self))),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,37 +91,28 @@ impl<W: ?Sized + Write> Write for AVCCWriterBuffer<W> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct AVCCNalUnitWriter<W: ?Sized + Write> {
|
pub struct AVCCNalUnitWriter<W: ?Sized + Write> {
|
||||||
inner: NalUnitWriter<W>,
|
inner: H264NalUnitWriter<W>,
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: Write> AVCCNalUnitWriter<W> {
|
|
||||||
fn _nal_unit_writer(&mut self) -> &mut NalUnitWriter<W> {
|
|
||||||
&mut self.inner
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: Write> NalUnitWrite<W> for AVCCNalUnitWriter<AVCCWriterBuffer<W>> {
|
impl<W: Write> NalUnitWrite<W> for AVCCNalUnitWriter<AVCCWriterBuffer<W>> {
|
||||||
type Writer = AVCCRbspWriter<AVCCWriterBuffer<W>>;
|
type Writer = AVCCRbspWriter<AVCCWriterBuffer<W>>;
|
||||||
|
type NalHeader = H264NalHeader;
|
||||||
|
|
||||||
fn write_nal_header(
|
fn write_nal_header(
|
||||||
self,
|
self,
|
||||||
nal_header: NalHeader,
|
nal_header: Self::NalHeader,
|
||||||
) -> Result<AVCCRbspWriter<AVCCWriterBuffer<W>>> {
|
) -> Result<AVCCRbspWriter<AVCCWriterBuffer<W>>> {
|
||||||
self.inner
|
self.inner.write_nal_header(nal_header).map(AVCCRbspWriter)
|
||||||
.write_nal_header(nal_header)
|
|
||||||
.map(|inner| AVCCRbspWriter { inner })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AVCCRbspWriter<W: ?Sized + Write> {
|
pub struct AVCCRbspWriter<W: ?Sized + Write>(H264RbspWriter<W>);
|
||||||
inner: RbspWriter<W>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: Write> RbspWrite<W> for AVCCRbspWriter<AVCCWriterBuffer<W>> {
|
impl<W: Write> RbspWrite<W> for AVCCRbspWriter<AVCCWriterBuffer<W>> {
|
||||||
type Writer = AVCCWriter<W>;
|
type Writer = AVCCWriter<W>;
|
||||||
|
|
||||||
fn finish_rbsp(self) -> Result<Self::Writer> {
|
fn finish_rbsp(self) -> Result<Self::Writer> {
|
||||||
let buffer = self.inner.finish_rbsp()?;
|
let buffer = self.0.finish_rbsp()?;
|
||||||
buffer.finish()
|
buffer.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,7 +124,7 @@ impl<W: Write + ?Sized> WebvttWrite for AVCCRbspWriter<W> {
|
|||||||
send_frequency_hz: u8,
|
send_frequency_hz: u8,
|
||||||
subtitle_tracks: &[WebvttTrack],
|
subtitle_tracks: &[WebvttTrack],
|
||||||
) -> std::io::Result<()> {
|
) -> std::io::Result<()> {
|
||||||
self.inner
|
self.0
|
||||||
.write_webvtt_header(max_latency_to_video, send_frequency_hz, subtitle_tracks)
|
.write_webvtt_header(max_latency_to_video, send_frequency_hz, subtitle_tracks)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +136,7 @@ impl<W: Write + ?Sized> WebvttWrite for AVCCRbspWriter<W> {
|
|||||||
video_offset: Duration,
|
video_offset: Duration,
|
||||||
webvtt_payload: &str, // TODO: replace with string type that checks for interior NULs
|
webvtt_payload: &str, // TODO: replace with string type that checks for interior NULs
|
||||||
) -> std::io::Result<()> {
|
) -> std::io::Result<()> {
|
||||||
self.inner.write_webvtt_payload(
|
self.0.write_webvtt_payload(
|
||||||
track_index,
|
track_index,
|
||||||
chunk_number,
|
chunk_number,
|
||||||
chunk_version,
|
chunk_version,
|
||||||
|
|||||||
437
deps/c-webvtt-in-video-stream/video-bytestream-tools/src/h265.rs
vendored
Normal file
437
deps/c-webvtt-in-video-stream/video-bytestream-tools/src/h265.rs
vendored
Normal file
@@ -0,0 +1,437 @@
|
|||||||
|
use crate::{
|
||||||
|
h26x::{annex_b::WriteNalHeader, NalUnitWrite, NalUnitWriter, RbspWrite, RbspWriter},
|
||||||
|
webvtt::{WebvttTrack, WebvttWrite},
|
||||||
|
};
|
||||||
|
use bitstream_io::{BigEndian, BitWrite, BitWriter};
|
||||||
|
use std::{io::Write, time::Duration};
|
||||||
|
|
||||||
|
type Result<T, E = std::io::Error> = std::result::Result<T, E>;
|
||||||
|
|
||||||
|
pub mod annex_b;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum UnitType {
|
||||||
|
TrailN,
|
||||||
|
TrailR,
|
||||||
|
TsaN,
|
||||||
|
TsaR,
|
||||||
|
StsaN,
|
||||||
|
StsaR,
|
||||||
|
RadlN,
|
||||||
|
RadlR,
|
||||||
|
RaslN,
|
||||||
|
RaslR,
|
||||||
|
RsvVclN10,
|
||||||
|
RsvVclN12,
|
||||||
|
RsvVclN14,
|
||||||
|
RsvVclR11,
|
||||||
|
RsvVclR13,
|
||||||
|
RsvVclR15,
|
||||||
|
BlaWLp,
|
||||||
|
BlaWRadl,
|
||||||
|
BlaNLp,
|
||||||
|
IdrWRadl,
|
||||||
|
IdrNLp,
|
||||||
|
CraNut,
|
||||||
|
RsvIrapVcl22,
|
||||||
|
RsvIrapVcl23,
|
||||||
|
RsvVcl24,
|
||||||
|
RsvVcl25,
|
||||||
|
RsvVcl26,
|
||||||
|
RsvVcl27,
|
||||||
|
RsvVcl28,
|
||||||
|
RsvVcl29,
|
||||||
|
RsvVcl30,
|
||||||
|
RsvVcl31,
|
||||||
|
VpsNut,
|
||||||
|
SpsNut,
|
||||||
|
PpsNut,
|
||||||
|
AudNut,
|
||||||
|
EosNut,
|
||||||
|
EobNut,
|
||||||
|
FdNut,
|
||||||
|
PrefixSeiNut,
|
||||||
|
SuffixSeiNut,
|
||||||
|
RsvNvcl41,
|
||||||
|
RsvNvcl42,
|
||||||
|
RsvNvcl43,
|
||||||
|
RsvNvcl44,
|
||||||
|
RsvNvcl45,
|
||||||
|
RsvNvcl46,
|
||||||
|
RsvNvcl47,
|
||||||
|
Unspec48,
|
||||||
|
Unspec49,
|
||||||
|
Unspec50,
|
||||||
|
Unspec51,
|
||||||
|
Unspec52,
|
||||||
|
Unspec53,
|
||||||
|
Unspec54,
|
||||||
|
Unspec55,
|
||||||
|
Unspec56,
|
||||||
|
Unspec57,
|
||||||
|
Unspec58,
|
||||||
|
Unspec59,
|
||||||
|
Unspec60,
|
||||||
|
Unspec61,
|
||||||
|
Unspec62,
|
||||||
|
Unspec63,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnitType {
|
||||||
|
fn id(self) -> u8 {
|
||||||
|
match self {
|
||||||
|
UnitType::TrailN => 0,
|
||||||
|
UnitType::TrailR => 1,
|
||||||
|
UnitType::TsaN => 2,
|
||||||
|
UnitType::TsaR => 3,
|
||||||
|
UnitType::StsaN => 4,
|
||||||
|
UnitType::StsaR => 5,
|
||||||
|
UnitType::RadlN => 6,
|
||||||
|
UnitType::RadlR => 7,
|
||||||
|
UnitType::RaslN => 8,
|
||||||
|
UnitType::RaslR => 9,
|
||||||
|
UnitType::RsvVclN10 => 10,
|
||||||
|
UnitType::RsvVclN12 => 11,
|
||||||
|
UnitType::RsvVclN14 => 12,
|
||||||
|
UnitType::RsvVclR11 => 13,
|
||||||
|
UnitType::RsvVclR13 => 14,
|
||||||
|
UnitType::RsvVclR15 => 15,
|
||||||
|
UnitType::BlaWLp => 16,
|
||||||
|
UnitType::BlaWRadl => 17,
|
||||||
|
UnitType::BlaNLp => 18,
|
||||||
|
UnitType::IdrWRadl => 19,
|
||||||
|
UnitType::IdrNLp => 20,
|
||||||
|
UnitType::CraNut => 21,
|
||||||
|
UnitType::RsvIrapVcl22 => 22,
|
||||||
|
UnitType::RsvIrapVcl23 => 23,
|
||||||
|
UnitType::RsvVcl24 => 24,
|
||||||
|
UnitType::RsvVcl25 => 25,
|
||||||
|
UnitType::RsvVcl26 => 26,
|
||||||
|
UnitType::RsvVcl27 => 27,
|
||||||
|
UnitType::RsvVcl28 => 28,
|
||||||
|
UnitType::RsvVcl29 => 29,
|
||||||
|
UnitType::RsvVcl30 => 30,
|
||||||
|
UnitType::RsvVcl31 => 31,
|
||||||
|
UnitType::VpsNut => 32,
|
||||||
|
UnitType::SpsNut => 33,
|
||||||
|
UnitType::PpsNut => 34,
|
||||||
|
UnitType::AudNut => 35,
|
||||||
|
UnitType::EosNut => 36,
|
||||||
|
UnitType::EobNut => 37,
|
||||||
|
UnitType::FdNut => 38,
|
||||||
|
UnitType::PrefixSeiNut => 39,
|
||||||
|
UnitType::SuffixSeiNut => 40,
|
||||||
|
UnitType::RsvNvcl41 => 41,
|
||||||
|
UnitType::RsvNvcl42 => 42,
|
||||||
|
UnitType::RsvNvcl43 => 43,
|
||||||
|
UnitType::RsvNvcl44 => 44,
|
||||||
|
UnitType::RsvNvcl45 => 45,
|
||||||
|
UnitType::RsvNvcl46 => 46,
|
||||||
|
UnitType::RsvNvcl47 => 47,
|
||||||
|
UnitType::Unspec48 => 48,
|
||||||
|
UnitType::Unspec49 => 49,
|
||||||
|
UnitType::Unspec50 => 50,
|
||||||
|
UnitType::Unspec51 => 51,
|
||||||
|
UnitType::Unspec52 => 52,
|
||||||
|
UnitType::Unspec53 => 53,
|
||||||
|
UnitType::Unspec54 => 54,
|
||||||
|
UnitType::Unspec55 => 55,
|
||||||
|
UnitType::Unspec56 => 56,
|
||||||
|
UnitType::Unspec57 => 57,
|
||||||
|
UnitType::Unspec58 => 58,
|
||||||
|
UnitType::Unspec59 => 59,
|
||||||
|
UnitType::Unspec60 => 60,
|
||||||
|
UnitType::Unspec61 => 61,
|
||||||
|
UnitType::Unspec62 => 62,
|
||||||
|
UnitType::Unspec63 => 63,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct H265NalHeader {
|
||||||
|
nal_unit_type: UnitType,
|
||||||
|
nuh_layer_id: u8,
|
||||||
|
nuh_temporal_id: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum H265NalHeaderError {
|
||||||
|
NuhLayerIdOutOfRange(u8),
|
||||||
|
NuhTemporalIdOutOfRange(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl H265NalHeader {
|
||||||
|
pub fn from_nal_unit_type_and_nuh_ids(
|
||||||
|
nal_unit_type: UnitType,
|
||||||
|
nuh_layer_id: u8,
|
||||||
|
nuh_temporal_id: u8,
|
||||||
|
) -> Result<Self, H265NalHeaderError> {
|
||||||
|
if nuh_layer_id >= 0b100_0000 {
|
||||||
|
return Err(H265NalHeaderError::NuhLayerIdOutOfRange(nuh_layer_id));
|
||||||
|
}
|
||||||
|
if nuh_temporal_id >= (0b1000 - 1) {
|
||||||
|
return Err(H265NalHeaderError::NuhTemporalIdOutOfRange(nuh_temporal_id));
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
nal_unit_type,
|
||||||
|
nuh_layer_id,
|
||||||
|
nuh_temporal_id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_header_bytes(&self) -> Result<[u8; 2]> {
|
||||||
|
let mut output = [0u8; 2];
|
||||||
|
let mut writer = BitWriter::endian(&mut output[..], BigEndian);
|
||||||
|
writer.write(1, 0)?;
|
||||||
|
writer.write(6, self.nal_unit_type.id())?;
|
||||||
|
writer.write(6, self.nuh_layer_id)?;
|
||||||
|
writer.write(3, self.nuh_temporal_id + 1)?;
|
||||||
|
assert!(writer.into_unwritten() == (0, 0));
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: ?Sized + Write> WriteNalHeader<W> for H265NalHeader {
|
||||||
|
fn write_to(self, writer: &mut W) -> crate::h26x::Result<()> {
|
||||||
|
writer.write_all(&self.as_header_bytes()?[..])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait H265ByteStreamWrite<W: ?Sized + Write> {
|
||||||
|
type Writer: NalUnitWrite<W>;
|
||||||
|
fn start_write_nal_unit(self) -> Result<Self::Writer>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Write> H265ByteStreamWrite<W> for W {
|
||||||
|
type Writer = H265NalUnitWriter<W>;
|
||||||
|
|
||||||
|
fn start_write_nal_unit(self) -> Result<Self::Writer> {
|
||||||
|
Ok(H265NalUnitWriter(NalUnitWriter::new(self)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct H265NalUnitWriter<W: ?Sized + Write>(NalUnitWriter<W>);
|
||||||
|
pub struct H265RbspWriter<W: ?Sized + Write>(RbspWriter<W>);
|
||||||
|
|
||||||
|
impl<W: Write> NalUnitWrite<W> for H265NalUnitWriter<W> {
|
||||||
|
type Writer = H265RbspWriter<W>;
|
||||||
|
type NalHeader = H265NalHeader;
|
||||||
|
|
||||||
|
fn write_nal_header(mut self, nal_header: H265NalHeader) -> Result<H265RbspWriter<W>> {
|
||||||
|
self.0.inner.write_all(&nal_header.as_header_bytes()?[..])?;
|
||||||
|
Ok(H265RbspWriter(RbspWriter::new(self.0.inner)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Write> RbspWrite<W> for H265RbspWriter<W> {
|
||||||
|
type Writer = W;
|
||||||
|
|
||||||
|
fn finish_rbsp(self) -> crate::h26x::Result<Self::Writer> {
|
||||||
|
self.0.finish_rbsp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Write + ?Sized> WebvttWrite for H265RbspWriter<W> {
|
||||||
|
fn write_webvtt_header(
|
||||||
|
&mut self,
|
||||||
|
max_latency_to_video: Duration,
|
||||||
|
send_frequency_hz: u8,
|
||||||
|
subtitle_tracks: &[WebvttTrack],
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
self.0
|
||||||
|
.write_webvtt_header(max_latency_to_video, send_frequency_hz, subtitle_tracks)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_webvtt_payload(
|
||||||
|
&mut self,
|
||||||
|
track_index: u8,
|
||||||
|
chunk_number: u64,
|
||||||
|
chunk_version: u8,
|
||||||
|
video_offset: Duration,
|
||||||
|
webvtt_payload: &str, // TODO: replace with string type that checks for interior NULs
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
self.0.write_webvtt_payload(
|
||||||
|
track_index,
|
||||||
|
chunk_number,
|
||||||
|
chunk_version,
|
||||||
|
video_offset,
|
||||||
|
webvtt_payload,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{
|
||||||
|
h265::{H265NalHeader, H265NalUnitWriter, UnitType},
|
||||||
|
h26x::{NalUnitWrite, NalUnitWriter, RbspWrite},
|
||||||
|
webvtt::{WebvttWrite, PAYLOAD_GUID, USER_DATA_UNREGISTERED},
|
||||||
|
};
|
||||||
|
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
use std::{
|
||||||
|
collections::VecDeque,
|
||||||
|
io::{ErrorKind, Read},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct RbspReader<R: ?Sized + Read> {
|
||||||
|
last_read: VecDeque<u8>,
|
||||||
|
inner: R,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Read> RbspReader<R> {
|
||||||
|
pub fn new(inner: R) -> Self {
|
||||||
|
RbspReader {
|
||||||
|
last_read: VecDeque::new(),
|
||||||
|
inner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Read> Read for RbspReader<R> {
|
||||||
|
fn read(&mut self, mut buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
|
let mut read = 0;
|
||||||
|
while !buf.is_empty() {
|
||||||
|
let res = self.inner.read_u8();
|
||||||
|
let byte = match res {
|
||||||
|
Ok(byte) => byte,
|
||||||
|
Err(err) if err.kind() == ErrorKind::UnexpectedEof => return Ok(0),
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
};
|
||||||
|
let mut last_read_iter = self.last_read.iter();
|
||||||
|
if last_read_iter.next() == Some(&0)
|
||||||
|
&& last_read_iter.next() == Some(&0)
|
||||||
|
&& byte == 3
|
||||||
|
{
|
||||||
|
self.last_read.clear();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if self.last_read.len() > 1 {
|
||||||
|
self.last_read.pop_front();
|
||||||
|
}
|
||||||
|
read += 1;
|
||||||
|
self.last_read.push_back(byte);
|
||||||
|
buf.write_u8(byte).unwrap();
|
||||||
|
}
|
||||||
|
Ok(read)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_webvtt_sei() {
|
||||||
|
let mut writer = vec![];
|
||||||
|
|
||||||
|
let nalu_writer = H265NalUnitWriter(NalUnitWriter::new(&mut writer));
|
||||||
|
let nal_unit_type = UnitType::PrefixSeiNut;
|
||||||
|
let nuh_layer_id = 0;
|
||||||
|
let nuh_temporal_id = 0;
|
||||||
|
let nal_header = H265NalHeader::from_nal_unit_type_and_nuh_ids(
|
||||||
|
nal_unit_type,
|
||||||
|
nuh_layer_id,
|
||||||
|
nuh_temporal_id,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let mut payload_writer = nalu_writer.write_nal_header(nal_header).unwrap();
|
||||||
|
let track_index = 0;
|
||||||
|
let chunk_number = 1;
|
||||||
|
let chunk_version = 0;
|
||||||
|
let video_offset = Duration::from_millis(200);
|
||||||
|
let webvtt_payload = "Some unverified data";
|
||||||
|
payload_writer
|
||||||
|
.write_webvtt_payload(
|
||||||
|
track_index,
|
||||||
|
chunk_number,
|
||||||
|
chunk_version,
|
||||||
|
video_offset,
|
||||||
|
webvtt_payload,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
payload_writer.finish_rbsp().unwrap();
|
||||||
|
|
||||||
|
assert!(&writer[4..20] == PAYLOAD_GUID.as_bytes());
|
||||||
|
assert!(writer[0] == nal_unit_type.id() << 1);
|
||||||
|
|
||||||
|
let mut reader = RbspReader::new(&writer[2..]);
|
||||||
|
|
||||||
|
assert!(usize::from(reader.read_u8().unwrap()) == USER_DATA_UNREGISTERED);
|
||||||
|
let mut length = 0;
|
||||||
|
loop {
|
||||||
|
let byte = reader.read_u8().unwrap();
|
||||||
|
length += usize::from(byte);
|
||||||
|
if byte != 255 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(dbg!(length + 1) == dbg!(reader.clone().bytes().count()));
|
||||||
|
reader.read_u128::<BigEndian>().unwrap();
|
||||||
|
assert!(track_index == reader.read_u8().unwrap());
|
||||||
|
assert!(chunk_number == reader.read_u64::<BigEndian>().unwrap());
|
||||||
|
assert!(chunk_version == reader.read_u8().unwrap());
|
||||||
|
assert!(
|
||||||
|
u16::try_from(video_offset.as_millis()).unwrap()
|
||||||
|
== reader.read_u16::<BigEndian>().unwrap()
|
||||||
|
);
|
||||||
|
println!("{writer:02x?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_webvtt_multi_sei() {
|
||||||
|
let mut writer = vec![];
|
||||||
|
|
||||||
|
let nalu_writer = H265NalUnitWriter(NalUnitWriter::new(&mut writer));
|
||||||
|
let nal_unit_type = UnitType::PrefixSeiNut;
|
||||||
|
let nuh_layer_id = 0;
|
||||||
|
let nuh_temporal_id = 0;
|
||||||
|
let nal_header = H265NalHeader::from_nal_unit_type_and_nuh_ids(
|
||||||
|
nal_unit_type,
|
||||||
|
nuh_layer_id,
|
||||||
|
nuh_temporal_id,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let mut payload_writer = nalu_writer.write_nal_header(nal_header).unwrap();
|
||||||
|
let track_index = 0;
|
||||||
|
let chunk_number = 1;
|
||||||
|
let chunk_version = 0;
|
||||||
|
let video_offset = Duration::from_millis(200);
|
||||||
|
let webvtt_payload = "Some unverified data";
|
||||||
|
payload_writer
|
||||||
|
.write_webvtt_payload(
|
||||||
|
track_index,
|
||||||
|
chunk_number,
|
||||||
|
chunk_version,
|
||||||
|
video_offset,
|
||||||
|
webvtt_payload,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
payload_writer
|
||||||
|
.write_webvtt_payload(1, 1, 0, video_offset, "Something else")
|
||||||
|
.unwrap();
|
||||||
|
payload_writer.finish_rbsp().unwrap();
|
||||||
|
|
||||||
|
assert!(&writer[4..20] == PAYLOAD_GUID.as_bytes());
|
||||||
|
assert!(writer[0] == nal_unit_type.id() << 1);
|
||||||
|
|
||||||
|
let mut reader = RbspReader::new(&writer[2..]);
|
||||||
|
|
||||||
|
assert!(usize::from(reader.read_u8().unwrap()) == USER_DATA_UNREGISTERED);
|
||||||
|
let mut _length = 0;
|
||||||
|
loop {
|
||||||
|
let byte = reader.read_u8().unwrap();
|
||||||
|
_length += usize::from(byte);
|
||||||
|
if byte != 255 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.read_u128::<BigEndian>().unwrap();
|
||||||
|
assert!(track_index == reader.read_u8().unwrap());
|
||||||
|
assert!(chunk_number == reader.read_u64::<BigEndian>().unwrap());
|
||||||
|
assert!(chunk_version == reader.read_u8().unwrap());
|
||||||
|
assert!(
|
||||||
|
u16::try_from(video_offset.as_millis()).unwrap()
|
||||||
|
== reader.read_u16::<BigEndian>().unwrap()
|
||||||
|
);
|
||||||
|
println!("{writer:02x?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
78
deps/c-webvtt-in-video-stream/video-bytestream-tools/src/h265/annex_b.rs
vendored
Normal file
78
deps/c-webvtt-in-video-stream/video-bytestream-tools/src/h265/annex_b.rs
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
use crate::{
|
||||||
|
h265::{H265ByteStreamWrite, H265NalHeader},
|
||||||
|
h26x::{
|
||||||
|
annex_b::{
|
||||||
|
AnnexBNalUnitWriter as AnnexBNalUnitWriterImpl,
|
||||||
|
AnnexBRbspWriter as AnnexBRbspWriterImpl, AnnexBWriter as AnnexBWriterImpl,
|
||||||
|
},
|
||||||
|
NalUnitWrite, RbspWrite, Result,
|
||||||
|
},
|
||||||
|
webvtt::{WebvttTrack, WebvttWrite},
|
||||||
|
};
|
||||||
|
use std::{io::Write, time::Duration};
|
||||||
|
|
||||||
|
pub struct AnnexBWriter<W: ?Sized + Write>(AnnexBWriterImpl<W>);
|
||||||
|
|
||||||
|
impl<W: Write> AnnexBWriter<W> {
|
||||||
|
pub fn new(inner: W) -> Self {
|
||||||
|
Self(AnnexBWriterImpl::new(inner))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Write> H265ByteStreamWrite<W> for AnnexBWriter<W> {
|
||||||
|
type Writer = AnnexBNalUnitWriter<W>;
|
||||||
|
|
||||||
|
fn start_write_nal_unit(self) -> Result<AnnexBNalUnitWriter<W>> {
|
||||||
|
self.0.start_write_nal_unit().map(AnnexBNalUnitWriter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AnnexBNalUnitWriter<W: ?Sized + Write>(AnnexBNalUnitWriterImpl<W>);
|
||||||
|
|
||||||
|
impl<W: Write> NalUnitWrite<W> for AnnexBNalUnitWriter<W> {
|
||||||
|
type Writer = AnnexBRbspWriter<W>;
|
||||||
|
type NalHeader = H265NalHeader;
|
||||||
|
|
||||||
|
fn write_nal_header(self, nal_header: Self::NalHeader) -> Result<AnnexBRbspWriter<W>> {
|
||||||
|
self.0.write_nal_header(nal_header).map(AnnexBRbspWriter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AnnexBRbspWriter<W: ?Sized + Write>(AnnexBRbspWriterImpl<W>);
|
||||||
|
|
||||||
|
impl<W: Write> RbspWrite<W> for AnnexBRbspWriter<W> {
|
||||||
|
type Writer = AnnexBWriter<W>;
|
||||||
|
|
||||||
|
fn finish_rbsp(self) -> Result<Self::Writer> {
|
||||||
|
self.0.finish_rbsp().map(AnnexBWriter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Write + ?Sized> WebvttWrite for AnnexBRbspWriter<W> {
|
||||||
|
fn write_webvtt_header(
|
||||||
|
&mut self,
|
||||||
|
max_latency_to_video: Duration,
|
||||||
|
send_frequency_hz: u8,
|
||||||
|
subtitle_tracks: &[WebvttTrack],
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
self.0
|
||||||
|
.write_webvtt_header(max_latency_to_video, send_frequency_hz, subtitle_tracks)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_webvtt_payload(
|
||||||
|
&mut self,
|
||||||
|
track_index: u8,
|
||||||
|
chunk_number: u64,
|
||||||
|
chunk_version: u8,
|
||||||
|
video_offset: Duration,
|
||||||
|
webvtt_payload: &str, // TODO: replace with string type that checks for interior NULs
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
self.0.write_webvtt_payload(
|
||||||
|
track_index,
|
||||||
|
chunk_number,
|
||||||
|
chunk_version,
|
||||||
|
video_offset,
|
||||||
|
webvtt_payload,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
130
deps/c-webvtt-in-video-stream/video-bytestream-tools/src/h26x.rs
vendored
Normal file
130
deps/c-webvtt-in-video-stream/video-bytestream-tools/src/h26x.rs
vendored
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
use crate::webvtt::{
|
||||||
|
write_webvtt_header, write_webvtt_payload, WebvttTrack, WebvttWrite, USER_DATA_UNREGISTERED,
|
||||||
|
};
|
||||||
|
use byteorder::WriteBytesExt;
|
||||||
|
use std::{collections::VecDeque, io::Write, time::Duration};
|
||||||
|
|
||||||
|
pub(crate) mod annex_b;
|
||||||
|
|
||||||
|
pub(crate) type Result<T, E = std::io::Error> = std::result::Result<T, E>;
|
||||||
|
|
||||||
|
pub(crate) struct NalUnitWriter<W: ?Sized + Write> {
|
||||||
|
pub(crate) inner: W,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait NalUnitWrite<W: ?Sized + Write> {
|
||||||
|
type Writer: RbspWrite<W>;
|
||||||
|
type NalHeader;
|
||||||
|
fn write_nal_header(self, nal_header: Self::NalHeader) -> Result<Self::Writer>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Write> NalUnitWriter<W> {
|
||||||
|
pub(crate) fn new(inner: W) -> Self {
|
||||||
|
Self { inner }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct RbspWriter<W: ?Sized + Write> {
|
||||||
|
last_written: VecDeque<u8>,
|
||||||
|
inner: W,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait RbspWrite<W: ?Sized + Write> {
|
||||||
|
type Writer;
|
||||||
|
fn finish_rbsp(self) -> Result<Self::Writer>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Write> RbspWriter<W> {
|
||||||
|
pub fn new(inner: W) -> Self {
|
||||||
|
Self {
|
||||||
|
last_written: VecDeque::with_capacity(3),
|
||||||
|
inner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finish_rbsp(mut self) -> Result<W> {
|
||||||
|
self.write_u8(0x80)?;
|
||||||
|
Ok(self.inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: ?Sized + Write> Write for RbspWriter<W> {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> Result<usize> {
|
||||||
|
let mut written = 0;
|
||||||
|
for &byte in buf {
|
||||||
|
let mut last_written_iter = self.last_written.iter();
|
||||||
|
if last_written_iter.next() == Some(&0)
|
||||||
|
&& last_written_iter.next() == Some(&0)
|
||||||
|
&& (byte == 0 || byte == 1 || byte == 2 || byte == 3)
|
||||||
|
{
|
||||||
|
self.inner.write_u8(3)?;
|
||||||
|
self.last_written.clear();
|
||||||
|
}
|
||||||
|
self.inner.write_u8(byte)?;
|
||||||
|
written += 1;
|
||||||
|
self.last_written.push_back(byte);
|
||||||
|
if self.last_written.len() > 2 {
|
||||||
|
self.last_written.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(written)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> Result<()> {
|
||||||
|
self.inner.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn write_sei_header<W: ?Sized + Write>(
|
||||||
|
writer: &mut W,
|
||||||
|
mut payload_type: usize,
|
||||||
|
mut payload_size: usize,
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
while payload_type >= 255 {
|
||||||
|
writer.write_u8(255)?;
|
||||||
|
payload_type -= 255;
|
||||||
|
}
|
||||||
|
writer.write_u8(payload_type.try_into().unwrap())?;
|
||||||
|
while payload_size >= 255 {
|
||||||
|
writer.write_u8(255)?;
|
||||||
|
payload_size -= 255;
|
||||||
|
}
|
||||||
|
writer.write_u8(payload_size.try_into().unwrap())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Write + ?Sized> WebvttWrite for RbspWriter<W> {
|
||||||
|
fn write_webvtt_header(
|
||||||
|
&mut self,
|
||||||
|
max_latency_to_video: Duration,
|
||||||
|
send_frequency_hz: u8,
|
||||||
|
subtitle_tracks: &[WebvttTrack],
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
write_webvtt_header(
|
||||||
|
self,
|
||||||
|
max_latency_to_video,
|
||||||
|
send_frequency_hz,
|
||||||
|
subtitle_tracks,
|
||||||
|
|writer, size| write_sei_header(writer, USER_DATA_UNREGISTERED, size),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_webvtt_payload(
|
||||||
|
&mut self,
|
||||||
|
track_index: u8,
|
||||||
|
chunk_number: u64,
|
||||||
|
chunk_version: u8,
|
||||||
|
video_offset: Duration,
|
||||||
|
webvtt_payload: &str, // TODO: replace with string type that checks for interior NULs
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
write_webvtt_payload(
|
||||||
|
self,
|
||||||
|
track_index,
|
||||||
|
chunk_number,
|
||||||
|
chunk_version,
|
||||||
|
video_offset,
|
||||||
|
webvtt_payload,
|
||||||
|
|writer, size| write_sei_header(writer, USER_DATA_UNREGISTERED, size),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
92
deps/c-webvtt-in-video-stream/video-bytestream-tools/src/h26x/annex_b.rs
vendored
Normal file
92
deps/c-webvtt-in-video-stream/video-bytestream-tools/src/h26x/annex_b.rs
vendored
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
use crate::{
|
||||||
|
h26x::{NalUnitWriter, RbspWriter, Result},
|
||||||
|
webvtt::{WebvttTrack, WebvttWrite},
|
||||||
|
};
|
||||||
|
use byteorder::WriteBytesExt;
|
||||||
|
use std::{io::Write, time::Duration};
|
||||||
|
|
||||||
|
pub(crate) struct AnnexBWriter<W: ?Sized + Write> {
|
||||||
|
leading_zero_8bits_written: bool,
|
||||||
|
inner: W,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Write> AnnexBWriter<W> {
|
||||||
|
pub fn new(inner: W) -> Self {
|
||||||
|
Self {
|
||||||
|
leading_zero_8bits_written: false,
|
||||||
|
inner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_write_nal_unit(mut self) -> Result<AnnexBNalUnitWriter<W>> {
|
||||||
|
if !self.leading_zero_8bits_written {
|
||||||
|
self.inner.write_u8(0)?;
|
||||||
|
self.leading_zero_8bits_written = true;
|
||||||
|
}
|
||||||
|
self.inner.write_all(&[0, 0, 1])?;
|
||||||
|
Ok(AnnexBNalUnitWriter {
|
||||||
|
inner: NalUnitWriter::new(self.inner),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait WriteNalHeader<W: ?Sized + Write> {
|
||||||
|
fn write_to(self, writer: &mut W) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct AnnexBNalUnitWriter<W: ?Sized + Write> {
|
||||||
|
inner: NalUnitWriter<W>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Write> AnnexBNalUnitWriter<W> {
|
||||||
|
pub fn write_nal_header(
|
||||||
|
mut self,
|
||||||
|
header: impl WriteNalHeader<W>,
|
||||||
|
) -> Result<AnnexBRbspWriter<W>> {
|
||||||
|
header.write_to(&mut self.inner.inner)?;
|
||||||
|
Ok(AnnexBRbspWriter {
|
||||||
|
inner: RbspWriter::new(self.inner.inner),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct AnnexBRbspWriter<W: ?Sized + Write> {
|
||||||
|
inner: RbspWriter<W>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Write> AnnexBRbspWriter<W> {
|
||||||
|
pub fn finish_rbsp(self) -> Result<AnnexBWriter<W>> {
|
||||||
|
self.inner
|
||||||
|
.finish_rbsp()
|
||||||
|
.map(|writer| AnnexBWriter::new(writer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Write + ?Sized> WebvttWrite for AnnexBRbspWriter<W> {
|
||||||
|
fn write_webvtt_header(
|
||||||
|
&mut self,
|
||||||
|
max_latency_to_video: Duration,
|
||||||
|
send_frequency_hz: u8,
|
||||||
|
subtitle_tracks: &[WebvttTrack],
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
self.inner
|
||||||
|
.write_webvtt_header(max_latency_to_video, send_frequency_hz, subtitle_tracks)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_webvtt_payload(
|
||||||
|
&mut self,
|
||||||
|
track_index: u8,
|
||||||
|
chunk_number: u64,
|
||||||
|
chunk_version: u8,
|
||||||
|
video_offset: Duration,
|
||||||
|
webvtt_payload: &str, // TODO: replace with string type that checks for interior NULs
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
self.inner.write_webvtt_payload(
|
||||||
|
track_index,
|
||||||
|
chunk_number,
|
||||||
|
chunk_version,
|
||||||
|
video_offset,
|
||||||
|
webvtt_payload,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1,5 @@
|
|||||||
|
pub mod av1;
|
||||||
pub mod h264;
|
pub mod h264;
|
||||||
|
pub mod h265;
|
||||||
|
pub mod h26x;
|
||||||
pub mod webvtt;
|
pub mod webvtt;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::h264::{write_sei_header, CountingSink};
|
use crate::h26x::Result;
|
||||||
use byteorder::{BigEndian, WriteBytesExt};
|
use byteorder::{BigEndian, WriteBytesExt};
|
||||||
use std::{io::Write, time::Duration};
|
use std::{io::Write, time::Duration};
|
||||||
use uuid::{uuid, Uuid};
|
use uuid::{uuid, Uuid};
|
||||||
@@ -17,6 +17,31 @@ trait WriteCStrExt: Write {
|
|||||||
|
|
||||||
impl<W: Write + ?Sized> WriteCStrExt for W {}
|
impl<W: Write + ?Sized> WriteCStrExt for W {}
|
||||||
|
|
||||||
|
pub(crate) struct CountingSink {
|
||||||
|
count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CountingSink {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { count: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn count(&self) -> usize {
|
||||||
|
self.count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write for CountingSink {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> Result<usize> {
|
||||||
|
self.count += buf.len();
|
||||||
|
Ok(buf.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct WebvttTrack<'a> {
|
pub struct WebvttTrack<'a> {
|
||||||
pub default: bool,
|
pub default: bool,
|
||||||
pub autoselect: bool,
|
pub autoselect: bool,
|
||||||
@@ -32,6 +57,7 @@ pub(crate) fn write_webvtt_header<W: Write + ?Sized>(
|
|||||||
max_latency_to_video: Duration,
|
max_latency_to_video: Duration,
|
||||||
send_frequency_hz: u8,
|
send_frequency_hz: u8,
|
||||||
subtitle_tracks: &[WebvttTrack],
|
subtitle_tracks: &[WebvttTrack],
|
||||||
|
write_format_header: impl FnOnce(&mut W, usize) -> std::io::Result<()>,
|
||||||
) -> std::io::Result<()> {
|
) -> std::io::Result<()> {
|
||||||
fn inner<W: ?Sized + Write>(
|
fn inner<W: ?Sized + Write>(
|
||||||
writer: &mut W,
|
writer: &mut W,
|
||||||
@@ -82,7 +108,7 @@ pub(crate) fn write_webvtt_header<W: Write + ?Sized>(
|
|||||||
send_frequency_hz,
|
send_frequency_hz,
|
||||||
subtitle_tracks,
|
subtitle_tracks,
|
||||||
)?;
|
)?;
|
||||||
write_sei_header(writer, USER_DATA_UNREGISTERED, count.count())?;
|
write_format_header(writer, count.count())?;
|
||||||
inner(
|
inner(
|
||||||
writer,
|
writer,
|
||||||
max_latency_to_video,
|
max_latency_to_video,
|
||||||
@@ -98,6 +124,7 @@ pub(crate) fn write_webvtt_payload<W: Write + ?Sized>(
|
|||||||
chunk_version: u8,
|
chunk_version: u8,
|
||||||
video_offset: Duration,
|
video_offset: Duration,
|
||||||
webvtt_payload: &str, // TODO: replace with string type that checks for interior NULs
|
webvtt_payload: &str, // TODO: replace with string type that checks for interior NULs
|
||||||
|
write_format_header: impl FnOnce(&mut W, usize) -> std::io::Result<()>,
|
||||||
) -> std::io::Result<()> {
|
) -> std::io::Result<()> {
|
||||||
fn inner<W: ?Sized + Write>(
|
fn inner<W: ?Sized + Write>(
|
||||||
writer: &mut W,
|
writer: &mut W,
|
||||||
@@ -125,7 +152,7 @@ pub(crate) fn write_webvtt_payload<W: Write + ?Sized>(
|
|||||||
video_offset,
|
video_offset,
|
||||||
webvtt_payload,
|
webvtt_payload,
|
||||||
)?;
|
)?;
|
||||||
write_sei_header(writer, USER_DATA_UNREGISTERED, count.count())?;
|
write_format_header(writer, count.count())?;
|
||||||
inner(
|
inner(
|
||||||
writer,
|
writer,
|
||||||
track_index,
|
track_index,
|
||||||
|
|||||||
@@ -463,9 +463,9 @@ void output_packet_added_callback(obs_output_t *output, struct encoder_packet *p
|
|||||||
if (strcmp(obs_encoder_get_codec(encoder), "h264") == 0) {
|
if (strcmp(obs_encoder_get_codec(encoder), "h264") == 0) {
|
||||||
codec_flavor = H264AnnexB;
|
codec_flavor = H264AnnexB;
|
||||||
} else if (strcmp(obs_encoder_get_codec(encoder), "av1") == 0) {
|
} else if (strcmp(obs_encoder_get_codec(encoder), "av1") == 0) {
|
||||||
continue;
|
codec_flavor = AV1OBUs;
|
||||||
} else if (strcmp(obs_encoder_get_codec(encoder), "hevc") == 0) {
|
} else if (strcmp(obs_encoder_get_codec(encoder), "hevc") == 0) {
|
||||||
continue;
|
codec_flavor = H265AnnexB;
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -479,7 +479,7 @@ void output_packet_added_callback(obs_output_t *output, struct encoder_packet *p
|
|||||||
uint8_t track_index = 0;
|
uint8_t track_index = 0;
|
||||||
// FIXME: this may be too lazy, i.e. languages should probably be locked in the signal handler instead
|
// FIXME: this may be too lazy, i.e. languages should probably be locked in the signal handler instead
|
||||||
for (auto &lang : gf.active_languages) {
|
for (auto &lang : gf.active_languages) {
|
||||||
auto lang_it = whisper_available_lang_reverse.find(lang);
|
auto lang_it = whisper_available_lang.find(lang);
|
||||||
if (lang_it == whisper_available_lang.end()) {
|
if (lang_it == whisper_available_lang.end()) {
|
||||||
obs_log(LOG_WARNING,
|
obs_log(LOG_WARNING,
|
||||||
"requested language '%s' unknown, track not added",
|
"requested language '%s' unknown, track not added",
|
||||||
|
|||||||
Reference in New Issue
Block a user