1pub mod av1;
12pub mod h264;
13pub mod h265;
14pub mod opus;
15pub mod vp8;
16pub mod vp9;
17
18#[cfg(test)]
19mod bitstream_test;
20
21use bytes::Bytes;
22use hang::catalog::VideoConfig;
23
24use crate::Result;
25
26#[derive(Clone, Debug)]
32pub struct Frame {
33 pub timestamp_us: u64,
34 pub payload: Bytes,
35}
36
37pub trait Bridge: Send {
45 fn push(&mut self, frame: Frame) -> Result<()>;
46
47 fn abort(self: Box<Self>, err: moq_net::Error);
52}
53
54pub(crate) trait DeferredImport: Send + Sized {
56 fn create(track: moq_net::track::Producer, reserved: moq_mux::catalog::Reserved) -> moq_mux::Result<Self>;
58
59 fn decode(&mut self, frame: Bytes, pts: moq_net::Timestamp) -> moq_mux::Result<()>;
61
62 fn abort(self, err: moq_net::Error);
64}
65
66impl DeferredImport for moq_mux::codec::vp8::Import {
67 fn create(track: moq_net::track::Producer, reserved: moq_mux::catalog::Reserved) -> moq_mux::Result<Self> {
68 Self::new(track, reserved, Default::default())
69 }
70
71 fn decode(&mut self, frame: Bytes, pts: moq_net::Timestamp) -> moq_mux::Result<()> {
72 moq_mux::codec::vp8::Import::decode(self, frame, Some(pts))
73 }
74
75 fn abort(self, err: moq_net::Error) {
76 moq_mux::codec::vp8::Import::abort(self, err);
77 }
78}
79
80impl DeferredImport for moq_mux::codec::vp9::Import {
81 fn create(track: moq_net::track::Producer, reserved: moq_mux::catalog::Reserved) -> moq_mux::Result<Self> {
82 Self::new(track, reserved, Default::default())
83 }
84
85 fn decode(&mut self, frame: Bytes, pts: moq_net::Timestamp) -> moq_mux::Result<()> {
86 moq_mux::codec::vp9::Import::decode(self, frame, Some(pts))
87 }
88
89 fn abort(self, err: moq_net::Error) {
90 moq_mux::codec::vp9::Import::abort(self, err);
91 }
92}
93
94struct PendingVideo {
95 track: moq_net::track::Producer,
96 catalog: moq_mux::catalog::Producer,
97}
98
99enum DeferredState<I> {
100 Pending(Box<PendingVideo>),
101 Active(Box<I>),
102 Failed(Box<moq_net::track::Producer>),
103 Poisoned,
104}
105
106pub(crate) struct DeferredVideo<I> {
108 state: DeferredState<I>,
109}
110
111impl<I: DeferredImport> DeferredVideo<I> {
112 pub fn new(
114 mut broadcast: moq_net::broadcast::Producer,
115 catalog: moq_mux::catalog::Producer,
116 suffix: &str,
117 ) -> Result<Self> {
118 let track = broadcast.unique_track(suffix, catalog.track_info())?;
119 Ok(Self {
120 state: DeferredState::Pending(Box::new(PendingVideo { track, catalog })),
121 })
122 }
123
124 pub fn decode(&mut self, frame: Bytes, pts: moq_net::Timestamp) -> Result<()> {
126 if let DeferredState::Active(import) = &mut self.state {
127 return import.decode(frame, pts).map_err(Into::into);
128 }
129
130 let DeferredState::Pending(pending) = std::mem::replace(&mut self.state, DeferredState::Poisoned) else {
131 return Err(crate::Error::Other(anyhow::anyhow!(
132 "video bridge initialization already failed"
133 )));
134 };
135 let reserved = pending.catalog.reserve();
136 let abort = pending.track.clone();
137 let import = match I::create(pending.track, reserved) {
138 Ok(import) => import,
139 Err(err) => {
140 self.state = DeferredState::Failed(Box::new(abort));
141 return Err(err.into());
142 }
143 };
144 self.state = DeferredState::Active(Box::new(import));
145 let DeferredState::Active(import) = &mut self.state else {
146 unreachable!();
147 };
148 import.decode(frame, pts).map_err(Into::into)
149 }
150
151 pub fn abort(self, err: moq_net::Error) {
153 match self.state {
154 DeferredState::Pending(pending) => {
155 let _ = pending.track.abort(err);
156 }
157 DeferredState::Active(import) => import.abort(err),
158 DeferredState::Failed(track) => {
159 let _ = track.abort(err);
160 }
161 DeferredState::Poisoned => {}
162 }
163 }
164}
165
166#[derive(Clone, Debug)]
172pub struct PacketizedFrame {
173 pub timestamp_us: u64,
174 pub payload: Bytes,
175}
176
177pub struct Track {
184 consumer: moq_mux::container::Consumer<moq_mux::catalog::hang::Container>,
185 convert: TrackConvert,
186}
187
188enum TrackConvert {
190 Passthrough,
194 LengthPrefixed { length_size: usize, keyframe_prefix: Bytes },
200}
201
202impl Track {
203 pub fn opus(track: moq_net::track::Subscriber) -> Self {
205 let container = moq_mux::catalog::hang::Container::Legacy;
206 let consumer = moq_mux::container::Consumer::new(track, container);
207 Self {
208 consumer,
209 convert: TrackConvert::Passthrough,
210 }
211 }
212
213 pub fn video(track: moq_net::track::Subscriber, config: &VideoConfig) -> Result<Self> {
217 let container: moq_mux::catalog::hang::Container = (&config.container).try_into()?;
218 let consumer = moq_mux::container::Consumer::new(track, container);
219
220 let convert = match &config.codec {
221 hang::catalog::VideoCodec::VP8 => TrackConvert::Passthrough,
222 hang::catalog::VideoCodec::VP9(_) => TrackConvert::Passthrough,
223 hang::catalog::VideoCodec::AV1(_) => TrackConvert::Passthrough,
224 hang::catalog::VideoCodec::H264(_) => h264_convert(config)?,
225 hang::catalog::VideoCodec::H265(_) => h265_convert(config)?,
226 other => return Err(crate::Error::UnsupportedCodec(format!("{other:?}"))),
227 };
228
229 Ok(Self { consumer, convert })
230 }
231
232 pub async fn next(&mut self) -> Result<Option<PacketizedFrame>> {
234 loop {
235 let Some(frame) = self.consumer.read().await? else {
236 return Ok(None);
237 };
238 let payload = match &self.convert {
239 TrackConvert::Passthrough => frame.payload,
240 TrackConvert::LengthPrefixed {
241 length_size,
242 keyframe_prefix,
243 } => {
244 let prefix = frame.keyframe.then(|| keyframe_prefix.as_ref());
245 moq_mux::codec::annexb::from_length_prefixed(&frame.payload, *length_size, prefix)
246 .map_err(|err| crate::Error::Other(anyhow::anyhow!("annexb: {err}")))?
247 }
248 };
249 if payload.is_empty() {
250 continue;
251 }
252 return Ok(Some(PacketizedFrame {
253 timestamp_us: frame.timestamp.as_micros() as u64,
254 payload,
255 }));
256 }
257 }
258}
259
260fn h264_convert(config: &VideoConfig) -> Result<TrackConvert> {
266 let Some(avcc) = config.description.as_ref().filter(|d| !d.is_empty()) else {
267 return Ok(TrackConvert::Passthrough);
268 };
269 let params = moq_mux::codec::h264::Avcc::parse(avcc)
270 .map_err(|err| crate::Error::Other(anyhow::anyhow!("avcc parse: {err}")))?;
271 if params.sps.is_empty() || params.pps.is_empty() {
275 return Err(crate::Error::Other(anyhow::anyhow!(
276 "avc1 avcC is missing parameter sets (sps={}, pps={})",
277 params.sps.len(),
278 params.pps.len()
279 )));
280 }
281 let keyframe_prefix = moq_mux::codec::annexb::build_prefix(params.sps.iter().chain(params.pps.iter()));
282 Ok(TrackConvert::LengthPrefixed {
283 length_size: params.length_size,
284 keyframe_prefix,
285 })
286}
287
288fn h265_convert(config: &VideoConfig) -> Result<TrackConvert> {
294 let Some(hvcc) = config.description.as_ref().filter(|d| !d.is_empty()) else {
295 return Ok(TrackConvert::Passthrough);
296 };
297 let params = moq_mux::codec::h265::Hvcc::parse(hvcc)
298 .map_err(|err| crate::Error::Other(anyhow::anyhow!("hvcc parse: {err}")))?;
299 if params.vps.is_empty() || params.sps.is_empty() || params.pps.is_empty() {
302 return Err(crate::Error::Other(anyhow::anyhow!(
303 "hvc1 hvcC is missing parameter sets (vps={}, sps={}, pps={})",
304 params.vps.len(),
305 params.sps.len(),
306 params.pps.len()
307 )));
308 }
309 let keyframe_prefix =
310 moq_mux::codec::annexb::build_prefix(params.vps.iter().chain(params.sps.iter()).chain(params.pps.iter()));
311 Ok(TrackConvert::LengthPrefixed {
312 length_size: params.length_size,
313 keyframe_prefix,
314 })
315}
316
317#[cfg(test)]
318mod tests {
319 use hang::catalog::{H264, H265, VideoConfig};
320
321 use super::*;
322
323 fn config(codec: impl Into<hang::catalog::VideoCodec>, description: Option<Bytes>) -> VideoConfig {
324 let mut config = VideoConfig::new(codec);
325 config.description = description;
326 config
327 }
328
329 fn h264(inline: bool) -> H264 {
330 H264 {
331 inline,
332 profile: 0x42,
333 constraints: 0,
334 level: 0x1f,
335 }
336 }
337
338 fn h265(in_band: bool) -> H265 {
339 H265 {
340 in_band,
341 profile_space: 0,
342 profile_idc: 1,
343 profile_compatibility_flags: [0; 4],
344 tier_flag: false,
345 level_idc: 0x5d,
346 constraint_flags: [0; 6],
347 }
348 }
349
350 fn build_avcc(sps: &[u8], pps: &[u8]) -> Bytes {
352 let mut v = vec![1, sps[1], sps[2], sps[3], 0xff, 0xe1];
353 v.extend_from_slice(&(sps.len() as u16).to_be_bytes());
354 v.extend_from_slice(sps);
355 v.push(1);
356 v.extend_from_slice(&(pps.len() as u16).to_be_bytes());
357 v.extend_from_slice(pps);
358 Bytes::from(v)
359 }
360
361 fn build_hvcc(vps: &[u8], sps: &[u8], pps: &[u8]) -> Bytes {
364 let mut v = vec![0u8; 21];
365 v.push(0xff); v.push(3); for (nal_type, nal) in [(32u8, vps), (33, sps), (34, pps)] {
368 v.push(nal_type); v.extend_from_slice(&1u16.to_be_bytes()); v.extend_from_slice(&(nal.len() as u16).to_be_bytes());
371 v.extend_from_slice(nal);
372 }
373 Bytes::from(v)
374 }
375
376 #[test]
377 fn h264_avc3_passthrough() {
378 let cfg = config(h264(true), None);
379 assert!(matches!(h264_convert(&cfg).unwrap(), TrackConvert::Passthrough));
380 }
381
382 #[test]
383 fn h264_avc1_length_prefixed() {
384 let sps: &[u8] = &[0x67, 0x42, 0xc0, 0x1f, 0xde];
385 let pps: &[u8] = &[0x68, 0xce, 0x3c, 0x80];
386 let cfg = config(h264(false), Some(build_avcc(sps, pps)));
387
388 let TrackConvert::LengthPrefixed {
389 length_size,
390 keyframe_prefix,
391 } = h264_convert(&cfg).unwrap()
392 else {
393 panic!("expected LengthPrefixed");
394 };
395 assert_eq!(length_size, 4);
396 assert!(keyframe_prefix.starts_with(&[0, 0, 0, 1]), "Annex-B start code");
397 assert!(keyframe_prefix.windows(sps.len()).any(|w| w == sps), "SPS in prefix");
398 assert!(keyframe_prefix.windows(pps.len()).any(|w| w == pps), "PPS in prefix");
399 }
400
401 #[test]
402 fn h265_hev1_passthrough() {
403 let cfg = config(h265(true), None);
404 assert!(matches!(h265_convert(&cfg).unwrap(), TrackConvert::Passthrough));
405 }
406
407 #[test]
408 fn h265_hvc1_length_prefixed() {
409 let vps: &[u8] = &[0x40, 0x01, 0x0c, 0x01];
410 let sps: &[u8] = &[0x42, 0x01, 0x01, 0x01];
411 let pps: &[u8] = &[0x44, 0x01, 0xc0, 0xf7];
412 let cfg = config(h265(false), Some(build_hvcc(vps, sps, pps)));
413
414 let TrackConvert::LengthPrefixed {
415 length_size,
416 keyframe_prefix,
417 } = h265_convert(&cfg).unwrap()
418 else {
419 panic!("expected LengthPrefixed");
420 };
421 assert_eq!(length_size, 4);
422 let v = keyframe_prefix.windows(vps.len()).position(|w| w == vps).expect("VPS");
424 let s = keyframe_prefix.windows(sps.len()).position(|w| w == sps).expect("SPS");
425 let p = keyframe_prefix.windows(pps.len()).position(|w| w == pps).expect("PPS");
426 assert!(v < s && s < p, "VPS < SPS < PPS order in prefix");
427 }
428
429 #[test]
432 fn h264_avc1_missing_param_sets_errors() {
433 let avcc = Bytes::from(vec![1, 0x42, 0, 0x1f, 0xff, 0xe0, 0x00]);
435 let cfg = config(h264(false), Some(avcc));
436 assert!(h264_convert(&cfg).is_err(), "missing SPS/PPS must error");
437 }
438}