1use std::collections::VecDeque;
4
5use hang::catalog::VideoConfig;
6
7use super::decoder::{Config, Start};
8use super::sink::Sink;
9use crate::Error;
10use crate::Frame;
11
12pub struct Consumer {
17 decoder: Sink,
22 track: moq_mux::container::Consumer<moq_mux::catalog::hang::Container>,
23 pending: VecDeque<Frame>,
27 drained: bool,
29 discontinuity: u64,
31}
32
33impl Consumer {
34 pub async fn new(
37 broadcast: &moq_net::broadcast::Consumer,
38 catalog: &VideoConfig,
39 name: impl Into<String>,
40 config: Config,
41 ) -> Result<Self, Error> {
42 let decoder = Sink::open(catalog, &config).await?;
43
44 let name = name.into();
45 let track = broadcast.track(&name)?;
46 let mut subscriber = track
47 .subscribe(moq_net::track::Subscription::default().with_priority(hang::catalog::PRIORITY.video))
48 .await?;
49 if config.start == Start::Latest
64 && let Some(live_edge) = track.latest()
65 {
66 subscriber.start_at(live_edge);
67 }
68 let track = subscriber;
69 let container = moq_mux::catalog::hang::Container::try_from(&catalog.container)?;
73 let mut track = moq_mux::container::Consumer::new(track, container);
74 if let Some(latency) = config.latency_max {
75 track = track.with_latency(latency);
76 }
77
78 Ok(Self {
79 decoder,
80 track,
81 pending: VecDeque::new(),
82 drained: false,
83 discontinuity: 0,
84 })
85 }
86
87 pub fn name(&self) -> &str {
89 self.decoder.name()
90 }
91
92 pub async fn read(&mut self) -> Result<Option<Frame>, Error> {
95 loop {
96 if let Some(frame) = self.pending.pop_front() {
97 return Ok(Some(frame));
98 }
99 if self.drained {
100 return Ok(None);
101 }
102
103 let mux_frame = self.track.read().await?;
104 let discontinuity = self.track.discontinuity();
105 if discontinuity != self.discontinuity {
106 self.decoder.flush().await?;
109 self.pending.clear();
110 self.discontinuity = discontinuity;
111 }
112
113 let Some(mux_frame) = mux_frame else {
114 let tail = self.decoder.flush().await;
119 self.drained = true;
126 self.pending.extend(tail?);
127 continue;
128 };
129
130 self.pending.extend(
131 self.decoder
132 .decode(mux_frame.payload, mux_frame.timestamp, mux_frame.keyframe)
133 .await?,
134 );
135 }
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use bytes::Bytes;
142 use moq_net::Timestamp;
143
144 use super::*;
145 use crate::decode::Kind;
146 use crate::decode::backend::probe;
147 use crate::encode::{Config as EncodeConfig, Encoder, Kind as EncodeKind, Producer as EncodeProducer};
148
149 #[tokio::test]
150 async fn reads_cmaf_container_declared_by_catalog() {
151 let mut source_broadcast = moq_net::broadcast::Info::new().produce();
152 let source_subscriber = source_broadcast.consume();
153 let source_catalog = moq_mux::catalog::Producer::new(&mut source_broadcast).unwrap();
154 let config = EncodeConfig {
155 kind: EncodeKind::Software,
156 ..EncodeConfig::new(320, 240, 30)
157 };
158 let rendition = config.probe().await.unwrap();
159 let mut producer = EncodeProducer::new(source_broadcast, source_catalog, rendition).unwrap();
160 let mut encoder = Encoder::new(&config).unwrap();
161 let rgba = vec![0x80u8; 320 * 240 * 4];
162 for index in 0..2 {
163 encoder.keyframe();
164 let surface = crate::Surface::rgba(&rgba, crate::Size::new(320, 240)).unwrap();
165 let frame = crate::Frame::new(surface, moq_net::Timestamp::from_micros(index * 33_333).unwrap());
166 producer.publish(&encoder.encode(&frame).unwrap()).unwrap();
167 }
168
169 let origin = moq_net::Origin::random().produce();
170 let mut requests = origin.dynamic();
171 let served = source_subscriber.clone();
172 tokio::spawn(async move {
173 while let Ok(request) = requests.requested_broadcast().await {
174 request.accept(served.clone());
175 }
176 });
177 let catalog = moq_mux::catalog::Consumer::<()>::new(&source_subscriber, moq_mux::catalog::CatalogFormat::Hang)
178 .await
179 .unwrap();
180 let source = moq_mux::Source::new(origin.consume(), "test");
181 let mut export = moq_mux::container::fmp4::Export::new(source, catalog);
182 let init = export.next().await.unwrap().expect("CMAF init");
183 let fragment = export.next().await.unwrap().expect("CMAF fragment");
184
185 let mut broadcast = moq_net::broadcast::Info::new().produce();
186 let subscriber = broadcast.consume();
187 let catalog = moq_mux::catalog::Producer::new(&mut broadcast).unwrap();
188 let mut import = moq_mux::container::fmp4::Import::new(broadcast, catalog.reserve());
189 import.decode(&init).unwrap();
190 import.decode(&fragment).unwrap();
191
192 let snapshot = catalog.snapshot();
193 let (name, config) = snapshot.video.renditions.iter().next().expect("video rendition");
194 assert!(matches!(config.container, hang::catalog::Container::Cmaf { .. }));
195 let mut consumer = Consumer::new(
196 &subscriber,
197 config,
198 name,
199 Config {
200 kind: Kind::Software,
201 ..Config::new()
202 },
203 )
204 .await
205 .unwrap();
206
207 let frame = consumer.read().await.unwrap().expect("decoded frame");
208 assert_eq!(frame.size(), crate::Size::new(320, 240));
209 }
210
211 #[tokio::test]
219 async fn a_second_consumer_starts_at_the_live_edge() {
220 let mut broadcast = moq_net::broadcast::Info::new().produce();
221 let track = broadcast.create_track("video", hang::container::track_info()).unwrap();
222 let published = track.clone();
224 let subscriber = broadcast.consume();
225 let mut producer = moq_mux::container::Producer::new(track, moq_mux::catalog::hang::Container::Legacy);
226 for index in 0..3u64 {
228 producer
229 .write(moq_mux::container::Frame {
230 timestamp: Timestamp::from_micros(index * 1_000_000).unwrap(),
231 duration: None,
232 payload: Bytes::from_static(b"access unit"),
233 keyframe: true,
234 })
235 .unwrap();
236 }
237 producer.finish().unwrap();
238
239 let catalog = VideoConfig::new(hang::catalog::H264 {
240 inline: true,
241 profile: 0x42,
242 constraints: 0,
243 level: 30,
244 });
245 let mut consumer = Consumer::new(
246 &subscriber,
247 &catalog,
248 "video",
249 Config {
250 kind: Kind::Named(probe::BUFFERED_NAME.into()),
251 start: Start::Latest,
252 ..Config::new()
253 },
254 )
255 .await
256 .unwrap();
257
258 let frame = consumer.read().await.unwrap().expect("a decoded frame");
263 assert_eq!(
264 frame.timestamp,
265 Timestamp::from_micros(2_000_000).unwrap(),
266 "a fresh consumer replayed the groups an earlier reader still holds",
267 );
268
269 assert_eq!(
277 published.subscription().and_then(|sub| sub.group_start),
278 None,
279 "the publisher was asked to rewind the track",
280 );
281 }
282
283 #[tokio::test]
291 async fn the_default_reads_every_cached_group() {
292 let mut broadcast = moq_net::broadcast::Info::new().produce();
293 let track = broadcast.create_track("video", hang::container::track_info()).unwrap();
294 let subscriber = broadcast.consume();
295 let mut producer = moq_mux::container::Producer::new(track, moq_mux::catalog::hang::Container::Legacy);
296 for index in 0..3u64 {
297 producer
298 .write(moq_mux::container::Frame {
299 timestamp: Timestamp::from_micros(index * 1_000_000).unwrap(),
300 duration: None,
301 payload: Bytes::from_static(b"access unit"),
302 keyframe: true,
303 })
304 .unwrap();
305 }
306 producer.finish().unwrap();
307
308 let catalog = VideoConfig::new(hang::catalog::H264 {
309 inline: true,
310 profile: 0x42,
311 constraints: 0,
312 level: 30,
313 });
314 let mut consumer = Consumer::new(
315 &subscriber,
316 &catalog,
317 "video",
318 Config {
319 kind: Kind::Named(probe::BUFFERED_NAME.into()),
322 ..Config::new()
323 },
324 )
325 .await
326 .unwrap();
327
328 let mut seen = Vec::new();
329 while let Some(frame) = consumer.read().await.unwrap() {
330 seen.push(frame.timestamp);
331 }
332 assert_eq!(
333 seen,
334 vec![
335 Timestamp::from_micros(0).unwrap(),
336 Timestamp::from_micros(1_000_000).unwrap(),
337 Timestamp::from_micros(2_000_000).unwrap(),
338 ],
339 "the default dropped groups the caller never asked to drop",
340 );
341 }
342
343 #[tokio::test]
346 async fn track_end_drains_buffered_decoder() {
347 let mut broadcast = moq_net::broadcast::Info::new().produce();
348 let track = broadcast.create_track("video", hang::container::track_info()).unwrap();
349 let subscriber = broadcast.consume();
350 let mut producer = moq_mux::container::Producer::new(track, moq_mux::catalog::hang::Container::Legacy);
351 for index in 0..2u64 {
352 producer
353 .write(moq_mux::container::Frame {
354 timestamp: Timestamp::from_micros(index * 33_333).unwrap(),
355 duration: None,
356 payload: Bytes::from_static(b"access unit"),
357 keyframe: index == 0,
358 })
359 .unwrap();
360 }
361 producer.finish().unwrap();
362
363 let catalog = VideoConfig::new(hang::catalog::H264 {
364 inline: true,
365 profile: 0x42,
366 constraints: 0,
367 level: 30,
368 });
369 let mut consumer = Consumer::new(
370 &subscriber,
371 &catalog,
372 "video",
373 Config {
374 kind: Kind::Named(probe::BUFFERED_NAME.into()),
375 ..Config::new()
376 },
377 )
378 .await
379 .unwrap();
380
381 let mut timestamps = Vec::new();
382 while let Some(frame) = consumer.read().await.unwrap() {
383 timestamps.push(frame.timestamp.as_micros());
384 }
385 assert_eq!(timestamps, vec![0, 33_333]);
386 assert!(
387 consumer.read().await.unwrap().is_none(),
388 "the decoder was drained twice"
389 );
390 }
391
392 #[tokio::test]
396 async fn discontinuity_discards_buffered_tail() {
397 let mut broadcast = moq_net::broadcast::Info::new().produce();
398 let track = broadcast.create_track("video", hang::container::track_info()).unwrap();
399 let subscriber = broadcast.consume();
400 let mut producer = moq_mux::container::Producer::new(track, moq_mux::catalog::hang::Container::Legacy);
401 producer
402 .write(moq_mux::container::Frame {
403 timestamp: Timestamp::from_micros(100_000).unwrap(),
404 duration: None,
405 payload: Bytes::from_static(b"old access unit"),
406 keyframe: true,
407 })
408 .unwrap();
409 producer.discontinuity().unwrap();
410 producer
411 .write(moq_mux::container::Frame {
412 timestamp: Timestamp::ZERO,
413 duration: None,
414 payload: Bytes::from_static(b"new access unit"),
415 keyframe: true,
416 })
417 .unwrap();
418 producer.finish().unwrap();
419
420 let catalog = VideoConfig::new(hang::catalog::H264 {
421 inline: true,
422 profile: 0x42,
423 constraints: 0,
424 level: 30,
425 });
426 let mut consumer = Consumer::new(
427 &subscriber,
428 &catalog,
429 "video",
430 Config {
431 kind: Kind::Named(probe::BUFFERED_NAME.into()),
432 ..Config::new()
433 },
434 )
435 .await
436 .unwrap();
437
438 let mut timestamps = Vec::new();
439 while let Some(frame) = consumer.read().await.unwrap() {
440 timestamps.push(frame.timestamp.as_micros());
441 }
442 assert_eq!(timestamps, vec![0]);
443 }
444
445 #[cfg(not(target_os = "macos"))]
449 #[tokio::test]
450 async fn cancelled_track_end_flush_is_not_reported_as_drained() {
451 probe::prepare_blocking_flush();
452 let mut broadcast = moq_net::broadcast::Info::new().produce();
453 let track = broadcast.create_track("video", hang::container::track_info()).unwrap();
454 let subscriber = broadcast.consume();
455 let mut producer = moq_mux::container::Producer::new(track, moq_mux::catalog::hang::Container::Legacy);
456 producer.finish().unwrap();
457
458 let catalog = VideoConfig::new(hang::catalog::H264 {
459 inline: true,
460 profile: 0x42,
461 constraints: 0,
462 level: 30,
463 });
464 let mut consumer = Consumer::new(
465 &subscriber,
466 &catalog,
467 "video",
468 Config {
469 kind: Kind::Named(probe::BLOCKING_FLUSH_NAME.into()),
470 ..Config::new()
471 },
472 )
473 .await
474 .unwrap();
475
476 let mut read = Box::pin(consumer.read());
477 let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(1);
478 loop {
479 tokio::select! {
480 _result = &mut read => panic!("flush returned before cancellation"),
481 _ = tokio::time::sleep(std::time::Duration::from_millis(1)) => {
482 if probe::flush_entered() {
483 break;
484 }
485 if tokio::time::Instant::now() >= deadline {
486 probe::release_flush();
487 panic!("flush never reached the codec thread");
488 }
489 }
490 }
491 }
492 drop(read);
493 probe::release_flush();
494
495 let err = match consumer.read().await {
496 Err(err) => err,
497 Ok(_) => panic!("cancelled flush must poison the sink"),
498 };
499 assert!(err.to_string().contains("cancelled call"), "unexpected error: {err}");
500 assert!(matches!(err, crate::Error::CodecGone(_)));
501 assert!(consumer.read().await.unwrap().is_none());
502 }
503
504 #[cfg(all(target_os = "linux", feature = "vaapi"))]
506 #[tokio::test]
507 async fn the_track_ending_drains_the_decoder() {
508 const FRAMES: u64 = 5;
509 let config = EncodeConfig {
510 kind: EncodeKind::Software,
511 ..EncodeConfig::new(320, 240, 30)
512 };
513 let catalog = config.probe().await.expect("probe the software encoder");
514
515 let mut broadcast = moq_net::broadcast::Info::new().produce();
516 let track = broadcast.create_track("video", hang::container::track_info()).unwrap();
517 let subscriber = broadcast.consume();
518 let mut producer = moq_mux::container::Producer::new(track, moq_mux::catalog::hang::Container::Legacy);
519
520 let mut encoder = Encoder::new(&config).unwrap();
521 let rgba = vec![0x80u8; 320 * 240 * 4];
522 for index in 0..FRAMES {
523 if index == 0 {
524 encoder.keyframe();
525 }
526 let surface = crate::Surface::rgba(&rgba, crate::Size::new(320, 240)).unwrap();
527 let frame = crate::Frame::new(surface, moq_net::Timestamp::from_micros(index * 33_333).unwrap());
528 for encoded in encoder.encode(&frame).unwrap() {
529 producer
530 .write(moq_mux::container::Frame {
531 timestamp: encoded.timestamp,
532 duration: None,
533 payload: encoded.payload,
534 keyframe: index == 0,
535 })
536 .unwrap();
537 }
538 }
539 producer.finish().unwrap();
540
541 let decode = Config {
542 kind: Kind::Named("vaapi".into()),
543 ..Config::new()
544 };
545 let Ok(mut consumer) = Consumer::new(&subscriber, &catalog, "video", decode).await else {
548 return;
549 };
550
551 let mut timestamps = Vec::new();
552 while let Some(frame) = consumer.read().await.unwrap() {
553 timestamps.push(frame.timestamp.as_micros());
554 }
555 let expected: Vec<u128> = (0..FRAMES as u128).map(|index| index * 33_333).collect();
556 assert_eq!(timestamps, expected, "the track ended before the stream did");
557
558 assert!(consumer.read().await.unwrap().is_none());
561 }
562}