1#![forbid(unsafe_code)]
8
9use crate::lacing::{self, Lacing};
10use crate::types::{Bytes, CuePoint, Frame, Rational, SeekEntry, TrackInfo};
11use crate::{Error, INLINE_INDEX, INLINE_STACK, INLINE_TRACKS, ids, vint};
12use smallvec::SmallVec;
13use std::collections::VecDeque;
14
15const DEFAULT_TIMECODE_SCALE: u64 = 1_000_000;
17const DEFAULT_SAMPLE_RATE_HZ: f64 = 8000.0;
19const DEFAULT_CHANNELS: u32 = 1;
21
22#[derive(Debug, Clone, Copy)]
23struct OpenElement {
24 id: u32,
25 end: Option<usize>,
27}
28
29#[derive(Debug, Default)]
30struct TrackScratch {
31 track_number: Option<u64>,
32 track_type: Option<u8>,
33 codec_id: Option<String>,
34 codec_private: Option<Bytes>,
35 width: u32,
36 height: u32,
37 sample_rate: Option<f64>,
38 channels: Option<u32>,
39}
40
41impl TrackScratch {
42 fn finish(self) -> Option<TrackInfo> {
45 Some(TrackInfo {
46 track_number: self.track_number?,
47 track_type: self.track_type.unwrap_or(0),
48 codec_id: self.codec_id?,
49 codec_private: self.codec_private,
50 width: self.width,
51 height: self.height,
52 sample_rate: self.sample_rate.unwrap_or(DEFAULT_SAMPLE_RATE_HZ),
53 channels: self.channels.unwrap_or(DEFAULT_CHANNELS),
54 })
55 }
56}
57
58#[derive(Debug)]
66struct ParsedBlock {
67 track_number: u64,
68 timecode: i64,
69 flags: u8,
72 payloads: SmallVec<[Bytes; 8]>,
73}
74
75#[derive(Debug, Default)]
76struct BlockGroupScratch {
77 block: Option<ParsedBlock>,
78 has_reference_block: bool,
79 duration_ticks: Option<u64>,
80}
81
82#[derive(Debug, Default)]
83struct CuePointScratch {
84 time_ticks: Option<u64>,
85 cluster_position: Option<u64>,
86}
87
88#[derive(Debug, Default)]
89struct SeekScratch {
90 id: Option<u32>,
91 position: Option<u64>,
92}
93
94#[derive(Debug)]
96pub struct Demuxer {
97 buffer: Vec<u8>,
98 read_pos: usize,
99 stack: SmallVec<[OpenElement; INLINE_STACK]>,
100 tracks: SmallVec<[TrackInfo; INLINE_TRACKS]>,
101 building_track: Option<TrackScratch>,
102 building_block_group: Option<BlockGroupScratch>,
103 building_cue_point: Option<CuePointScratch>,
104 building_seek: Option<SeekScratch>,
105 cues: SmallVec<[CuePoint; INLINE_INDEX]>,
106 seek_head: SmallVec<[SeekEntry; INLINE_INDEX]>,
107 timecode_scale: u64,
108 cluster_timecode: i64,
109 frames: VecDeque<Frame>,
110 halted: bool,
114}
115
116impl Default for Demuxer {
117 fn default() -> Self {
118 Self {
119 buffer: Vec::new(),
120 read_pos: 0,
121 stack: SmallVec::new(),
122 tracks: SmallVec::new(),
123 building_track: None,
124 building_block_group: None,
125 building_cue_point: None,
126 building_seek: None,
127 cues: SmallVec::new(),
128 seek_head: SmallVec::new(),
129 timecode_scale: DEFAULT_TIMECODE_SCALE,
130 cluster_timecode: 0,
131 frames: VecDeque::new(),
132 halted: false,
133 }
134 }
135}
136
137impl Demuxer {
138 #[must_use]
140 pub fn new() -> Self {
141 Self::default()
142 }
143
144 pub fn push_bytes(&mut self, chunk: &[u8]) {
146 self.buffer.extend_from_slice(chunk);
147 self.pump();
148 self.compact();
149 }
150
151 #[must_use]
153 pub fn streams(&self) -> &[TrackInfo] {
154 &self.tracks
155 }
156
157 pub fn poll_frame(&mut self) -> Option<Frame> {
159 self.frames.pop_front()
160 }
161
162 #[must_use]
165 pub fn cues(&self) -> &[CuePoint] {
166 &self.cues
167 }
168
169 #[must_use]
171 pub fn seek_head(&self) -> &[SeekEntry] {
172 &self.seek_head
173 }
174
175 #[must_use]
178 pub const fn time_base(&self) -> Rational {
179 Rational::new(self.timecode_scale, 1_000_000_000)
180 }
181
182 fn pump(&mut self) {
183 if self.halted {
184 return;
185 }
186 loop {
187 self.close_finished_contexts();
188 if !self.step() {
189 break;
190 }
191 }
192 }
193
194 fn close_finished_contexts(&mut self) {
195 loop {
196 let Some(top) = self.stack.last() else {
197 return;
198 };
199 let done = matches!(top.end, Some(end) if self.read_pos >= end);
200 if !done {
201 return;
202 }
203 if let Some(closed) = self.stack.pop() {
204 self.on_close(closed.id);
205 }
206 }
207 }
208
209 fn step(&mut self) -> bool {
212 let remaining = &self.buffer[self.read_pos..];
213 let (id, id_len) = match vint::decode_id(remaining) {
214 Ok(v) => v,
215 Err(Error::Incomplete) => return false,
216 Err(Error::ReservedVint | Error::Unsupported(_)) => {
217 self.halted = true;
218 return false;
219 }
220 };
221 let (vs, size_len) = match vint::decode_size(&remaining[id_len..]) {
222 Ok(v) => v,
223 Err(Error::Incomplete) => return false,
224 Err(Error::ReservedVint | Error::Unsupported(_)) => {
225 self.halted = true;
226 return false;
227 }
228 };
229 let header_len = id_len + size_len;
230 let content_start = self.read_pos + header_len;
231 let content_end = if vs.unknown {
232 None
233 } else {
234 Some(content_start + vs.value as usize)
235 };
236
237 if ids::is_descend_master(id) {
238 self.on_open(id);
239 self.stack.push(OpenElement {
240 id,
241 end: content_end,
242 });
243 self.read_pos = content_start;
244 return true;
245 }
246
247 let Some(end) = content_end else {
248 self.halted = true;
251 return false;
252 };
253 if end > self.buffer.len() {
254 return false; }
256 self.handle_leaf(id, content_start, end);
257 self.read_pos = end;
258 true
259 }
260
261 fn on_open(&mut self, id: u32) {
262 match id {
263 ids::TRACK_ENTRY => self.building_track = Some(TrackScratch::default()),
264 ids::CLUSTER => self.cluster_timecode = 0,
265 ids::BLOCK_GROUP => self.building_block_group = Some(BlockGroupScratch::default()),
266 ids::CUE_POINT => self.building_cue_point = Some(CuePointScratch::default()),
267 ids::SEEK => self.building_seek = Some(SeekScratch::default()),
268 _ => {}
269 }
270 }
271
272 fn on_close(&mut self, id: u32) {
273 match id {
274 ids::TRACK_ENTRY => {
275 if let Some(scratch) = self.building_track.take() {
276 if let Some(track) = scratch.finish() {
277 self.tracks.push(track);
278 }
279 }
280 }
281 ids::BLOCK_GROUP => self.finish_block_group(),
282 ids::CUE_POINT => {
283 if let Some(scratch) = self.building_cue_point.take() {
284 if let (Some(time_ticks), Some(cluster_position)) =
285 (scratch.time_ticks, scratch.cluster_position)
286 {
287 self.cues.push(CuePoint {
288 time_ticks,
289 cluster_position,
290 });
291 }
292 }
293 }
294 ids::SEEK => {
295 if let Some(scratch) = self.building_seek.take() {
296 if let (Some(seek_id), Some(position)) = (scratch.id, scratch.position) {
297 self.seek_head.push(SeekEntry {
298 id: seek_id,
299 position,
300 });
301 }
302 }
303 }
304 _ => {}
305 }
306 }
307
308 fn finish_block_group(&mut self) {
309 let Some(scratch) = self.building_block_group.take() else {
310 return;
311 };
312 let Some(block) = scratch.block else {
313 return;
314 };
315 let is_keyframe = !scratch.has_reference_block;
316 for payload in block.payloads {
317 self.frames.push_back(Frame {
318 track_number: block.track_number,
319 timecode: block.timecode,
320 is_keyframe,
321 duration_ticks: scratch.duration_ticks,
322 payload,
323 });
324 }
325 }
326
327 fn top_is(&self, id: u32) -> bool {
328 matches!(self.stack.last(), Some(top) if top.id == id)
329 }
330
331 fn handle_leaf(&mut self, id: u32, start: usize, end: usize) {
332 match id {
333 ids::TIMECODE_SCALE => {
334 if let Some(v) = read_uint(&self.buffer[start..end]) {
335 self.timecode_scale = v.max(1);
336 }
337 }
338 ids::TRACK_NUMBER => {
339 let v = read_uint(&self.buffer[start..end]);
340 if let (Some(v), Some(scratch)) = (v, self.building_track.as_mut()) {
341 scratch.track_number = Some(v);
342 }
343 }
344 ids::TRACK_TYPE => {
345 let v = read_uint(&self.buffer[start..end]);
346 if let (Some(v), Some(scratch)) = (v, self.building_track.as_mut()) {
347 scratch.track_type = Some(v as u8);
348 }
349 }
350 ids::CODEC_ID => {
351 let codec_id = String::from_utf8(self.buffer[start..end].to_vec()).ok();
353 if let Some(scratch) = self.building_track.as_mut() {
354 scratch.codec_id = codec_id;
355 }
356 }
357 ids::CODEC_PRIVATE => {
358 let cp = Bytes::copy_from_slice(&self.buffer[start..end]);
360 if let Some(scratch) = self.building_track.as_mut() {
361 scratch.codec_private = Some(cp);
362 }
363 }
364 ids::PIXEL_WIDTH if self.top_is(ids::VIDEO) => {
365 let v = read_uint(&self.buffer[start..end]);
366 if let (Some(v), Some(scratch)) = (v, self.building_track.as_mut()) {
367 scratch.width = v as u32;
368 }
369 }
370 ids::PIXEL_HEIGHT if self.top_is(ids::VIDEO) => {
371 let v = read_uint(&self.buffer[start..end]);
372 if let (Some(v), Some(scratch)) = (v, self.building_track.as_mut()) {
373 scratch.height = v as u32;
374 }
375 }
376 ids::SAMPLING_FREQUENCY if self.top_is(ids::AUDIO) => {
377 let v = read_float(&self.buffer[start..end]);
378 if let (Some(v), Some(scratch)) = (v, self.building_track.as_mut()) {
379 scratch.sample_rate = Some(v);
380 }
381 }
382 ids::CHANNELS if self.top_is(ids::AUDIO) => {
383 let v = read_uint(&self.buffer[start..end]);
384 if let (Some(v), Some(scratch)) = (v, self.building_track.as_mut()) {
385 scratch.channels = Some(v as u32);
386 }
387 }
388 ids::TIMECODE => {
389 if let Some(v) = read_uint(&self.buffer[start..end]) {
390 self.cluster_timecode = v as i64;
391 }
392 }
393 ids::SIMPLE_BLOCK => self.handle_simple_block(start, end),
394 ids::BLOCK if self.building_block_group.is_some() => self.handle_block(start, end),
395 ids::BLOCK_DURATION if self.building_block_group.is_some() => {
396 let v = read_uint(&self.buffer[start..end]);
397 if let Some(scratch) = self.building_block_group.as_mut() {
398 scratch.duration_ticks = v;
399 }
400 }
401 ids::REFERENCE_BLOCK if self.building_block_group.is_some() => {
402 if let Some(scratch) = self.building_block_group.as_mut() {
403 scratch.has_reference_block = true;
404 }
405 }
406 ids::CUE_TIME if self.top_is(ids::CUE_POINT) => {
407 let v = read_uint(&self.buffer[start..end]);
408 if let Some(scratch) = self.building_cue_point.as_mut() {
409 scratch.time_ticks = v;
410 }
411 }
412 ids::CUE_CLUSTER_POSITION if self.top_is(ids::CUE_TRACK_POSITIONS) => {
413 let v = read_uint(&self.buffer[start..end]);
414 if let Some(scratch) = self.building_cue_point.as_mut() {
415 scratch.cluster_position = v;
416 }
417 }
418 ids::SEEK_ID if self.top_is(ids::SEEK) => {
419 let v = read_uint(&self.buffer[start..end]);
420 if let (Some(v), Some(scratch)) = (v, self.building_seek.as_mut()) {
421 scratch.id = Some(v as u32);
422 }
423 }
424 ids::SEEK_POSITION if self.top_is(ids::SEEK) => {
425 let v = read_uint(&self.buffer[start..end]);
426 if let Some(scratch) = self.building_seek.as_mut() {
427 scratch.position = v;
428 }
429 }
430 _ => {}
431 }
432 }
433
434 fn parse_block_common(&self, start: usize, end: usize) -> Option<ParsedBlock> {
438 let body = &self.buffer[start..end];
439 let (track_number, tn_len) = vint::decode_size(body).ok()?;
440 if body.len() < tn_len + 3 {
441 return None;
442 }
443 let rel_tc = i16::from_be_bytes([body[tn_len], body[tn_len + 1]]);
444 let flags = body[tn_len + 2];
445 let lacing = Lacing::from_flags(flags);
446 let ranges = lacing::split(body, tn_len + 3, lacing)?;
447 let timecode = self.cluster_timecode.saturating_add(i64::from(rel_tc));
448 let payloads = ranges
452 .into_iter()
453 .map(|(s, e)| Bytes::copy_from_slice(&self.buffer[start + s..start + e]))
454 .collect();
455 Some(ParsedBlock {
456 track_number: track_number.value,
457 timecode,
458 flags,
459 payloads,
460 })
461 }
462
463 fn handle_simple_block(&mut self, start: usize, end: usize) {
464 let Some(block) = self.parse_block_common(start, end) else {
465 return;
466 };
467 let is_keyframe = block.flags & 0x80 != 0;
468 for payload in block.payloads {
469 self.frames.push_back(Frame {
470 track_number: block.track_number,
471 timecode: block.timecode,
472 is_keyframe,
473 duration_ticks: None,
474 payload,
475 });
476 }
477 }
478
479 fn handle_block(&mut self, start: usize, end: usize) {
480 let Some(block) = self.parse_block_common(start, end) else {
481 return;
482 };
483 if let Some(scratch) = self.building_block_group.as_mut() {
484 scratch.block = Some(block);
485 }
486 }
487
488 fn compact(&mut self) {
489 let drained = self.read_pos;
490 if drained == 0 {
491 return;
492 }
493 if drained < 64 * 1024 && drained * 2 < self.buffer.len() {
494 return;
495 }
496 self.buffer.drain(..drained);
497 for open in &mut self.stack {
498 if let Some(end) = open.end.as_mut() {
499 *end -= drained;
500 }
501 }
502 self.read_pos = 0;
503 }
504}
505
506fn read_uint(body: &[u8]) -> Option<u64> {
508 if body.len() > 8 {
509 return None;
510 }
511 let mut v = 0u64;
512 for &b in body {
513 v = (v << 8) | u64::from(b);
514 }
515 Some(v)
516}
517
518fn read_float(body: &[u8]) -> Option<f64> {
520 match body.len() {
521 4 => Some(f64::from(f32::from_be_bytes(body.try_into().ok()?))),
522 8 => Some(f64::from_be_bytes(body.try_into().ok()?)),
523 _ => None,
524 }
525}
526
527#[cfg(test)]
528#[path = "demux_tests.rs"]
529mod tests;