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