use alloc::vec::Vec;
use core::marker::PhantomData;
use broadcast_common::{Parse, Unpackage};
use crate::avc_config::{AVCConfigurationBox, AVCDecoderConfigurationRecord};
use crate::error::{Error, Result};
use crate::hevc_config::{HEVCConfigurationBox, HEVCDecoderConfigurationRecord};
use crate::media::{Media, Track};
use crate::opus::OpusSpecificBox;
use crate::pipeline::{CodecConfig, Sample, TrackSpec};
use crate::rtp_sdp::aac_config_from_asc_bytes;
use crate::vp9::Vp9ConfigurationBox;
const EBML_HEADER: u32 = 0x1A45_DFA3;
const SEGMENT: u32 = 0x1853_8067;
const INFO: u32 = 0x1549_A966;
const TIMESTAMP_SCALE: u32 = 0x2A_D7_B1;
const TRACKS: u32 = 0x1654_AE6B;
const TRACK_ENTRY: u32 = 0xAE;
const TRACK_NUMBER: u32 = 0xD7;
const TRACK_TYPE: u32 = 0x83;
const CODEC_ID: u32 = 0x86;
const CODEC_PRIVATE: u32 = 0x63A2;
const DEFAULT_DURATION: u32 = 0x23_E3_83;
const VIDEO: u32 = 0xE0;
const PIXEL_WIDTH: u32 = 0xB0;
const PIXEL_HEIGHT: u32 = 0xBA;
const AUDIO: u32 = 0xE1;
const SAMPLING_FREQUENCY: u32 = 0xB5;
const CHANNELS: u32 = 0x9F;
const CLUSTER: u32 = 0x1F43_B675;
const CLUSTER_TIMESTAMP: u32 = 0xE7;
const SIMPLE_BLOCK: u32 = 0xA3;
const BLOCK_GROUP: u32 = 0xA0;
const BLOCK: u32 = 0xA1;
const REFERENCE_BLOCK: u32 = 0xFB;
const CODEC_V_VP9: &[u8] = b"V_VP9";
const CODEC_V_VP8: &[u8] = b"V_VP8";
const CODEC_V_AVC: &[u8] = b"V_MPEG4/ISO/AVC";
const CODEC_V_HEVC: &[u8] = b"V_MPEGH/ISO/HEVC";
const CODEC_A_OPUS: &[u8] = b"A_OPUS";
const CODEC_A_VORBIS: &[u8] = b"A_VORBIS";
const CODEC_A_AAC: &[u8] = b"A_AAC";
const TRACK_TYPE_VIDEO: u64 = 1;
const TRACK_TYPE_AUDIO: u64 = 2;
const BLOCK_FLAG_KEYFRAME: u8 = 0x80;
const BLOCK_FLAG_LACING_MASK: u8 = 0x06;
const DEFAULT_TIMESTAMP_SCALE_NS: u64 = 1_000_000;
pub const IR_TIMESCALE: u32 = 1000;
const NS_PER_SECOND: u64 = 1_000_000_000;
const OPUS_HEAD_MAGIC: &[u8; 8] = b"OpusHead";
const OPUS_HEAD_MIN_LEN: usize = 19;
const OPUS_OUTPUT_SAMPLE_RATE: u32 = 48_000;
const AUDIO_SAMPLE_SIZE: u16 = 16;
const VP8_FRAME_TAG_LEN: usize = 3;
const VP8_START_CODE: [u8; 3] = [0x9D, 0x01, 0x2A];
const VP8_KEYFRAME_TAG_BIT: u8 = 0x01;
const VP8_DIMENSION_MASK: u16 = 0x3FFF;
const VP8_KEYFRAME_HEADER_LEN: usize = VP8_FRAME_TAG_LEN + VP8_START_CODE.len() + 4;
const VORBIS_LACE_COUNT: u8 = 2;
const VORBIS_ID_HEADER_TYPE: u8 = 0x01;
const VORBIS_SIGNATURE: &[u8; 6] = b"vorbis";
const VORBIS_ID_CHANNELS_OFFSET: usize = 1 + 6 + 4;
const VORBIS_ID_SAMPLE_RATE_OFFSET: usize = VORBIS_ID_CHANNELS_OFFSET + 1;
const VORBIS_ID_MIN_LEN: usize = VORBIS_ID_SAMPLE_RATE_OFFSET + 4;
const VPCC_VERSION: u8 = 1;
const VP9_PROFILE_0: u8 = 0;
const VP9_LEVEL_UNSPECIFIED: u8 = 0;
const VP9_BIT_DEPTH_8: u8 = 8;
const VP9_CHROMA_420: u8 = 1;
const CICP_UNSPECIFIED: u8 = 2;
#[derive(Debug)]
struct RawBlock {
track_number: u64,
pts_ticks: i64,
is_sync: bool,
data: Vec<u8>,
}
#[derive(Default)]
struct TrackInfo {
track_number: u64,
track_type: u64,
codec_id: Vec<u8>,
codec_private: Vec<u8>,
default_duration_ns: u64,
pixel_width: u16,
pixel_height: u16,
channels: u16,
sampling_frequency: u32,
}
#[derive(Debug, Default, Clone)]
pub struct WebmDemux<'a> {
_marker: PhantomData<&'a [u8]>,
}
impl<'a> WebmDemux<'a> {
pub fn new() -> Self {
Self {
_marker: PhantomData,
}
}
pub fn demux(&mut self, input: &'a [u8]) -> Result<Media> {
let mut r = EbmlReader::new(input);
let mut timestamp_scale_ns = DEFAULT_TIMESTAMP_SCALE_NS;
let mut tracks: Vec<TrackInfo> = Vec::new();
let mut blocks: Vec<RawBlock> = Vec::new();
while let Some((id, body)) = r.next_element()? {
match id {
EBML_HEADER => {}
SEGMENT => {
Self::walk_segment(body, &mut timestamp_scale_ns, &mut tracks, &mut blocks)?;
}
_ => {}
}
}
build_media(timestamp_scale_ns, tracks, blocks)
}
fn walk_segment(
body: &[u8],
timestamp_scale_ns: &mut u64,
tracks: &mut Vec<TrackInfo>,
blocks: &mut Vec<RawBlock>,
) -> Result<()> {
let mut r = EbmlReader::new(body);
while let Some((id, child)) = r.next_element()? {
match id {
INFO => Self::walk_info(child, timestamp_scale_ns)?,
TRACKS => Self::walk_tracks(child, tracks)?,
CLUSTER => Self::walk_cluster(child, *timestamp_scale_ns, blocks)?,
_ => {}
}
}
Ok(())
}
fn walk_info(body: &[u8], timestamp_scale_ns: &mut u64) -> Result<()> {
let mut r = EbmlReader::new(body);
while let Some((id, child)) = r.next_element()? {
if id == TIMESTAMP_SCALE {
*timestamp_scale_ns = read_uint(child);
}
}
Ok(())
}
fn walk_tracks(body: &[u8], tracks: &mut Vec<TrackInfo>) -> Result<()> {
let mut r = EbmlReader::new(body);
while let Some((id, child)) = r.next_element()? {
if id == TRACK_ENTRY {
tracks.push(Self::parse_track_entry(child)?);
}
}
Ok(())
}
fn parse_track_entry(body: &[u8]) -> Result<TrackInfo> {
let mut info = TrackInfo::default();
let mut r = EbmlReader::new(body);
while let Some((id, child)) = r.next_element()? {
match id {
TRACK_NUMBER => info.track_number = read_uint(child),
TRACK_TYPE => info.track_type = read_uint(child),
CODEC_ID => info.codec_id = child.to_vec(),
CODEC_PRIVATE => info.codec_private = child.to_vec(),
DEFAULT_DURATION => info.default_duration_ns = read_uint(child),
VIDEO => Self::parse_video(child, &mut info)?,
AUDIO => Self::parse_audio(child, &mut info)?,
_ => {}
}
}
Ok(info)
}
fn parse_video(body: &[u8], info: &mut TrackInfo) -> Result<()> {
let mut r = EbmlReader::new(body);
while let Some((id, child)) = r.next_element()? {
match id {
PIXEL_WIDTH => info.pixel_width = read_uint(child) as u16,
PIXEL_HEIGHT => info.pixel_height = read_uint(child) as u16,
_ => {}
}
}
Ok(())
}
fn parse_audio(body: &[u8], info: &mut TrackInfo) -> Result<()> {
let mut r = EbmlReader::new(body);
while let Some((id, child)) = r.next_element()? {
match id {
CHANNELS => info.channels = read_uint(child) as u16,
SAMPLING_FREQUENCY => info.sampling_frequency = read_float(child) as u32,
_ => {}
}
}
Ok(())
}
fn walk_cluster(
body: &[u8],
timestamp_scale_ns: u64,
blocks: &mut Vec<RawBlock>,
) -> Result<()> {
let mut cluster_ts: i64 = 0;
let mut r = EbmlReader::new(body);
while let Some((id, child)) = r.next_element()? {
match id {
CLUSTER_TIMESTAMP => cluster_ts = read_uint(child) as i64,
SIMPLE_BLOCK => {
blocks.push(parse_block(child, cluster_ts, timestamp_scale_ns, true)?);
}
BLOCK_GROUP => {
let mut block_bytes: Option<&[u8]> = None;
let mut has_reference = false;
let mut g = EbmlReader::new(child);
while let Some((gid, gchild)) = g.next_element()? {
match gid {
BLOCK => block_bytes = Some(gchild),
REFERENCE_BLOCK => has_reference = true,
_ => {}
}
}
if let Some(b) = block_bytes {
let mut rb = parse_block(b, cluster_ts, timestamp_scale_ns, false)?;
rb.is_sync = !has_reference;
blocks.push(rb);
}
}
_ => {}
}
}
Ok(())
}
}
impl<'a> Unpackage for WebmDemux<'a> {
type Input = &'a [u8];
type Media = Media;
type Error = Error;
fn unpackage(&mut self, input: &'a [u8]) -> Result<Media> {
self.demux(input)
}
}
fn parse_block(
data: &[u8],
cluster_ts: i64,
timestamp_scale_ns: u64,
is_simple_block: bool,
) -> Result<RawBlock> {
let (track_number, mut off) = read_vint_value(data).ok_or(Error::InvalidInput(
"webm block: truncated track-number VINT",
))?;
if data.len() < off + 3 {
return Err(Error::BufferTooShort {
need: off + 3,
have: data.len(),
what: "webm block header (rel-ts + flags)",
});
}
let rel_ts = i16::from_be_bytes([data[off], data[off + 1]]) as i64;
let flags = data[off + 2];
off += 3;
if flags & BLOCK_FLAG_LACING_MASK != 0 {
return Err(Error::InvalidInput(
"webm block: lacing is not supported (expected one frame per block)",
));
}
let is_sync = if is_simple_block {
flags & BLOCK_FLAG_KEYFRAME != 0
} else {
false
};
let raw_ticks = cluster_ts + rel_ts;
let ns = raw_ticks.saturating_mul(timestamp_scale_ns as i64);
let ns_per_ir_tick = (NS_PER_SECOND / IR_TIMESCALE as u64) as i64;
let pts_ticks = ns / ns_per_ir_tick;
Ok(RawBlock {
track_number,
pts_ticks,
is_sync,
data: data[off..].to_vec(),
})
}
fn build_media(
timestamp_scale_ns: u64,
tracks: Vec<TrackInfo>,
blocks: Vec<RawBlock>,
) -> Result<Media> {
let _ = timestamp_scale_ns;
let mut out_tracks: Vec<Track> = Vec::new();
let mut track_id: u32 = 1;
for info in &tracks {
let mut samples: Vec<Sample> = Vec::new();
let mut pts: Vec<i64> = Vec::new();
let mut sync: Vec<bool> = Vec::new();
let mut payloads: Vec<Vec<u8>> = Vec::new();
for b in &blocks {
if b.track_number == info.track_number {
pts.push(b.pts_ticks);
sync.push(b.is_sync);
payloads.push(b.data.clone());
}
}
if payloads.is_empty() {
continue;
}
let first_sync = sync
.iter()
.position(|&s| s)
.map(|i| payloads[i].as_slice())
.unwrap_or(payloads[0].as_slice());
let Some(config) = codec_config_for(info, first_sync)? else {
continue;
};
let default_dur_ir = info.default_duration_ns / (NS_PER_SECOND / IR_TIMESCALE as u64);
let n = payloads.len();
for i in 0..n {
let duration = if i + 1 < n {
(pts[i + 1] - pts[i]).max(0) as u32
} else if n >= 2 {
(pts[i] - pts[i - 1]).max(0) as u32
} else {
default_dur_ir as u32
};
samples.push(Sample {
data: core::mem::take(&mut payloads[i]).into(),
dts: Some(pts[i]),
pts: Some(pts[i]),
duration: Some(duration),
flags: crate::ir::SampleFlags::new(sync[i]),
provenance: None,
});
}
let anchor = pts.first().map(|&p| p.max(0) as u64).unwrap_or(0);
out_tracks.push(Track::new_at(
TrackSpec::new(track_id, IR_TIMESCALE, config),
samples,
anchor,
));
track_id += 1;
}
Ok(Media::new(out_tracks, IR_TIMESCALE))
}
fn codec_config_for(info: &TrackInfo, first_frame: &[u8]) -> Result<Option<CodecConfig>> {
if info.track_type == TRACK_TYPE_VIDEO && info.codec_id == CODEC_V_VP9 {
Ok(Some(vp9_config(info)))
} else if info.track_type == TRACK_TYPE_VIDEO && info.codec_id == CODEC_V_VP8 {
Ok(Some(vp8_config(first_frame)?))
} else if info.track_type == TRACK_TYPE_VIDEO && info.codec_id == CODEC_V_AVC {
Ok(Some(avc_config(info)?))
} else if info.track_type == TRACK_TYPE_VIDEO && info.codec_id == CODEC_V_HEVC {
Ok(Some(hevc_config(info)?))
} else if info.track_type == TRACK_TYPE_AUDIO && info.codec_id == CODEC_A_OPUS {
Ok(Some(opus_config(info)?))
} else if info.track_type == TRACK_TYPE_AUDIO && info.codec_id == CODEC_A_VORBIS {
Ok(Some(vorbis_config(info)?))
} else if info.track_type == TRACK_TYPE_AUDIO && info.codec_id == CODEC_A_AAC {
Ok(Some(aac_config_from_asc_bytes(info.codec_private.clone())?))
} else {
Ok(None)
}
}
fn avc_config(info: &TrackInfo) -> Result<CodecConfig> {
let record = AVCDecoderConfigurationRecord::parse(&info.codec_private)?;
Ok(CodecConfig::Avc {
config: AVCConfigurationBox::new(record),
width: info.pixel_width,
height: info.pixel_height,
})
}
fn hevc_config(info: &TrackInfo) -> Result<CodecConfig> {
let record = HEVCDecoderConfigurationRecord::parse(&info.codec_private)?;
Ok(CodecConfig::Hevc {
config: HEVCConfigurationBox::new(record),
width: info.pixel_width,
height: info.pixel_height,
})
}
fn vp8_config(first_frame: &[u8]) -> Result<CodecConfig> {
if first_frame.len() < VP8_KEYFRAME_HEADER_LEN {
return Err(Error::BufferTooShort {
need: VP8_KEYFRAME_HEADER_LEN,
have: first_frame.len(),
what: "VP8 key-frame header",
});
}
if first_frame[0] & VP8_KEYFRAME_TAG_BIT != 0 {
return Err(Error::InvalidValue {
field: "VP8 key_frame",
value: (first_frame[0] & VP8_KEYFRAME_TAG_BIT) as u64,
reason: "first VP8 frame is not a key frame (key_frame bit != 0)",
});
}
let start = &first_frame[VP8_FRAME_TAG_LEN..VP8_FRAME_TAG_LEN + VP8_START_CODE.len()];
if start != VP8_START_CODE {
return Err(Error::InvalidValue {
field: "VP8 start code",
value: u32::from_be_bytes([0, start[0], start[1], start[2]]) as u64,
reason: "VP8 key-frame start code is not 0x9D012A",
});
}
let d = VP8_FRAME_TAG_LEN + VP8_START_CODE.len();
let width = u16::from_le_bytes([first_frame[d], first_frame[d + 1]]) & VP8_DIMENSION_MASK;
let height = u16::from_le_bytes([first_frame[d + 2], first_frame[d + 3]]) & VP8_DIMENSION_MASK;
Ok(CodecConfig::Vp8 { width, height })
}
fn vorbis_config(info: &TrackInfo) -> Result<CodecConfig> {
let cp = &info.codec_private;
let id = vorbis_id_header(cp)?;
if id.len() < VORBIS_ID_MIN_LEN {
return Err(Error::BufferTooShort {
need: VORBIS_ID_MIN_LEN,
have: id.len(),
what: "Vorbis identification header",
});
}
if id[0] != VORBIS_ID_HEADER_TYPE {
return Err(Error::InvalidValue {
field: "Vorbis header packet type",
value: id[0] as u64,
reason: "first Vorbis header is not the identification header (type 0x01)",
});
}
if &id[1..1 + VORBIS_SIGNATURE.len()] != VORBIS_SIGNATURE {
return Err(Error::InvalidInput(
"Vorbis identification header missing the \"vorbis\" signature",
));
}
let channels = id[VORBIS_ID_CHANNELS_OFFSET] as u16;
let sample_rate = u32::from_le_bytes([
id[VORBIS_ID_SAMPLE_RATE_OFFSET],
id[VORBIS_ID_SAMPLE_RATE_OFFSET + 1],
id[VORBIS_ID_SAMPLE_RATE_OFFSET + 2],
id[VORBIS_ID_SAMPLE_RATE_OFFSET + 3],
]);
Ok(CodecConfig::Vorbis {
codec_private: cp.clone(),
channels,
sample_rate,
})
}
fn vorbis_id_header(cp: &[u8]) -> Result<&[u8]> {
if cp.is_empty() {
return Err(Error::BufferTooShort {
need: 1,
have: 0,
what: "Vorbis CodecPrivate (Xiph lacing count)",
});
}
if cp[0] != VORBIS_LACE_COUNT {
return Err(Error::InvalidValue {
field: "Vorbis CodecPrivate lacing count",
value: cp[0] as u64,
reason: "expected numPackets-1 == 2 (three Xiph-laced Vorbis headers)",
});
}
let mut pos = 1usize;
let mut lengths = [0usize; 2];
for len in lengths.iter_mut() {
loop {
let b = *cp.get(pos).ok_or(Error::BufferTooShort {
need: pos + 1,
have: cp.len(),
what: "Vorbis CodecPrivate Xiph lacing length",
})?;
pos += 1;
*len += b as usize;
if b != 0xFF {
break;
}
}
}
let id_start = pos;
let id_end = id_start
.checked_add(lengths[0])
.filter(|&e| e <= cp.len())
.ok_or(Error::BufferTooShort {
need: id_start + lengths[0],
have: cp.len(),
what: "Vorbis identification header body",
})?;
Ok(&cp[id_start..id_end])
}
fn vp9_config(info: &TrackInfo) -> CodecConfig {
let config = Vp9ConfigurationBox {
version: VPCC_VERSION,
flags: 0,
profile: VP9_PROFILE_0,
level: VP9_LEVEL_UNSPECIFIED,
bit_depth: VP9_BIT_DEPTH_8,
chroma_subsampling: VP9_CHROMA_420,
video_full_range_flag: false,
colour_primaries: CICP_UNSPECIFIED,
transfer_characteristics: CICP_UNSPECIFIED,
matrix_coefficients: CICP_UNSPECIFIED,
codec_initialization_data: Vec::new(),
};
CodecConfig::Vp9 {
config,
width: info.pixel_width,
height: info.pixel_height,
}
}
fn opus_config(info: &TrackInfo) -> Result<CodecConfig> {
let cp = &info.codec_private;
if cp.len() < OPUS_HEAD_MIN_LEN {
return Err(Error::BufferTooShort {
need: OPUS_HEAD_MIN_LEN,
have: cp.len(),
what: "Opus CodecPrivate (OpusHead)",
});
}
if &cp[0..8] != OPUS_HEAD_MAGIC {
return Err(Error::InvalidValue {
field: "OpusHead magic",
value: u64::from_be_bytes([cp[0], cp[1], cp[2], cp[3], cp[4], cp[5], cp[6], cp[7]]),
reason: "Opus CodecPrivate does not start with the \"OpusHead\" signature",
});
}
let version = cp[8];
let output_channel_count = cp[9];
let pre_skip = u16::from_le_bytes([cp[10], cp[11]]);
let input_sample_rate = u32::from_le_bytes([cp[12], cp[13], cp[14], cp[15]]);
let output_gain = i16::from_le_bytes([cp[16], cp[17]]);
let channel_mapping_family = cp[18];
let channel_mapping = if channel_mapping_family != 0 {
let need = OPUS_HEAD_MIN_LEN + 2 + output_channel_count as usize;
if cp.len() < need {
return Err(Error::BufferTooShort {
need,
have: cp.len(),
what: "OpusHead channel-mapping table",
});
}
let stream_count = cp[19];
let coupled_count = cp[20];
let map_start = 21;
let map_end = map_start + output_channel_count as usize;
Some(crate::opus::ChannelMappingTable {
stream_count,
coupled_count,
channel_mapping: cp[map_start..map_end].to_vec(),
})
} else {
None
};
let dops = OpusSpecificBox {
version,
output_channel_count,
pre_skip,
input_sample_rate,
output_gain,
channel_mapping_family,
channel_mapping,
};
Ok(CodecConfig::Opus {
config: dops,
channel_count: output_channel_count as u16,
sample_rate: OPUS_OUTPUT_SAMPLE_RATE,
sample_size: AUDIO_SAMPLE_SIZE,
})
}
struct EbmlReader<'a> {
buf: &'a [u8],
pos: usize,
}
impl<'a> EbmlReader<'a> {
fn new(buf: &'a [u8]) -> Self {
Self { buf, pos: 0 }
}
fn next_element(&mut self) -> Result<Option<(u32, &'a [u8])>> {
if self.pos >= self.buf.len() {
return Ok(None);
}
let rest = &self.buf[self.pos..];
let (id, id_len) =
read_element_id(rest).ok_or(Error::InvalidInput("webm: truncated element ID"))?;
let after_id = &rest[id_len..];
let (size, size_len, unknown) = read_element_size(after_id)
.ok_or(Error::InvalidInput("webm: truncated element size"))?;
let body_start = self.pos + id_len + size_len;
let body_end = if unknown {
self.buf.len()
} else {
let end = body_start + size as usize;
if end > self.buf.len() {
return Err(Error::BufferTooShort {
need: end,
have: self.buf.len(),
what: "webm element body",
});
}
end
};
let body = &self.buf[body_start..body_end];
self.pos = body_end;
Ok(Some((id, body)))
}
}
fn read_element_id(buf: &[u8]) -> Option<(u32, usize)> {
let first = *buf.first()?;
if first == 0 {
return None; }
let len = first.leading_zeros() as usize + 1;
if len > 4 || buf.len() < len {
return None;
}
let mut id: u32 = 0;
for &b in &buf[..len] {
id = (id << 8) | b as u32;
}
Some((id, len))
}
fn read_element_size(buf: &[u8]) -> Option<(u64, usize, bool)> {
let (value, len, all_ones) = read_vint(buf)?;
Some((value, len, all_ones))
}
fn read_vint(buf: &[u8]) -> Option<(u64, usize, bool)> {
let first = *buf.first()?;
if first == 0 {
return None; }
let len = first.leading_zeros() as usize + 1;
if buf.len() < len {
return None;
}
let first_mask: u8 = if len >= 8 { 0 } else { 0xFF >> len };
let mut value = (first & first_mask) as u64;
for &b in &buf[1..len] {
value = (value << 8) | b as u64;
}
let data_bits = 7 * len; let max = if data_bits >= 64 {
u64::MAX
} else {
(1u64 << data_bits) - 1
};
Some((value, len, value == max))
}
fn read_vint_value(buf: &[u8]) -> Option<(u64, usize)> {
read_vint(buf).map(|(v, len, _)| (v, len))
}
fn read_uint(body: &[u8]) -> u64 {
let mut v: u64 = 0;
for &b in body.iter().take(8) {
v = (v << 8) | b as u64;
}
v
}
fn read_float(body: &[u8]) -> f64 {
match body.len() {
4 => f32::from_be_bytes([body[0], body[1], body[2], body[3]]) as f64,
8 => f64::from_be_bytes([
body[0], body[1], body[2], body[3], body[4], body[5], body[6], body[7],
]),
_ => 0.0,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn vint_one_byte() {
assert_eq!(read_vint(&[0x81]), Some((1, 1, false)));
assert_eq!(read_vint(&[0xFF]), Some((127, 1, true)));
}
#[test]
fn vint_two_byte() {
assert_eq!(read_vint(&[0x40, 0x02]), Some((2, 2, false)));
}
#[test]
fn element_id_segment() {
assert_eq!(
read_element_id(&[0x18, 0x53, 0x80, 0x67]),
Some((SEGMENT, 4))
);
}
#[test]
fn element_id_track_entry() {
assert_eq!(read_element_id(&[0xAE]), Some((TRACK_ENTRY, 1)));
}
#[test]
fn uint_be() {
assert_eq!(read_uint(&[0x0F, 0x42, 0x40]), 1_000_000);
}
#[test]
fn lacing_rejected() {
let block = [0x81u8, 0x00, 0x00, 0x02, 0xAA];
let err = parse_block(&block, 0, DEFAULT_TIMESTAMP_SCALE_NS, true).unwrap_err();
assert!(matches!(err, Error::InvalidInput(_)));
}
}