1pub mod h264;
12pub mod opus;
13pub mod vp8;
14pub mod vp9;
15
16use bytes::Bytes;
17use hang::catalog::VideoConfig;
18use str0m::format::Codec;
19
20use crate::Result;
21
22#[derive(Clone, Debug)]
28pub struct Frame {
29 pub timestamp_us: u64,
30 pub payload: Bytes,
31}
32
33pub trait Bridge: Send {
41 fn push(&mut self, frame: Frame) -> Result<()>;
42}
43
44#[derive(Clone, Debug)]
50pub struct PacketizedFrame {
51 pub timestamp_us: u64,
52 pub payload: Bytes,
53}
54
55pub struct Track {
62 consumer: moq_mux::container::Consumer<moq_mux::catalog::hang::Container>,
63 codec: Codec,
64 convert: TrackConvert,
65}
66
67enum TrackConvert {
69 Passthrough,
73 LengthPrefixed { length_size: usize, keyframe_prefix: Bytes },
79}
80
81impl Track {
82 pub async fn opus(broadcast: &moq_net::BroadcastConsumer, name: &str) -> Result<Self> {
84 let container = moq_mux::catalog::hang::Container::Legacy;
85 let track = broadcast.subscribe_track(&moq_net::Track::new(name))?;
89 let consumer = moq_mux::container::Consumer::new(track, container);
90 Ok(Self {
91 consumer,
92 codec: Codec::Opus,
93 convert: TrackConvert::Passthrough,
94 })
95 }
96
97 pub async fn video(broadcast: &moq_net::BroadcastConsumer, name: &str, config: &VideoConfig) -> Result<Self> {
101 let container: moq_mux::catalog::hang::Container = (&config.container).try_into()?;
102 let track = broadcast.subscribe_track(&moq_net::Track::new(name))?;
105 let consumer = moq_mux::container::Consumer::new(track, container);
106
107 let (codec, convert) = match &config.codec {
108 hang::catalog::VideoCodec::VP8 => (Codec::Vp8, TrackConvert::Passthrough),
109 hang::catalog::VideoCodec::VP9(_) => (Codec::Vp9, TrackConvert::Passthrough),
110 hang::catalog::VideoCodec::AV1(_) => (Codec::Av1, TrackConvert::Passthrough),
111 hang::catalog::VideoCodec::H264(_) => (Codec::H264, h264_convert(config)?),
112 hang::catalog::VideoCodec::H265(_) => (Codec::H265, h265_convert(config)?),
113 other => return Err(crate::Error::UnsupportedCodec(format!("{other:?}"))),
114 };
115
116 Ok(Self {
117 consumer,
118 codec,
119 convert,
120 })
121 }
122
123 pub fn codec(&self) -> Codec {
124 self.codec
125 }
126
127 pub async fn next(&mut self) -> Result<Option<PacketizedFrame>> {
129 loop {
130 let Some(frame) = self.consumer.read().await? else {
131 return Ok(None);
132 };
133 let payload = match &self.convert {
134 TrackConvert::Passthrough => frame.payload,
135 TrackConvert::LengthPrefixed {
136 length_size,
137 keyframe_prefix,
138 } => {
139 let prefix = frame.keyframe.then(|| keyframe_prefix.as_ref());
140 moq_mux::codec::annexb::from_length_prefixed(&frame.payload, *length_size, prefix)
141 .map_err(|err| crate::Error::Other(anyhow::anyhow!("annexb: {err}")))?
142 }
143 };
144 if payload.is_empty() {
145 continue;
146 }
147 return Ok(Some(PacketizedFrame {
148 timestamp_us: frame.timestamp.as_micros() as u64,
149 payload,
150 }));
151 }
152 }
153}
154
155fn h264_convert(config: &VideoConfig) -> Result<TrackConvert> {
161 let Some(avcc) = config.description.as_ref().filter(|d| !d.is_empty()) else {
162 return Ok(TrackConvert::Passthrough);
163 };
164 let params = moq_mux::codec::h264::Avcc::parse(avcc)
165 .map_err(|err| crate::Error::Other(anyhow::anyhow!("avcc parse: {err}")))?;
166 if params.sps.is_empty() || params.pps.is_empty() {
170 return Err(crate::Error::Other(anyhow::anyhow!(
171 "avc1 avcC is missing parameter sets (sps={}, pps={})",
172 params.sps.len(),
173 params.pps.len()
174 )));
175 }
176 let keyframe_prefix = moq_mux::codec::annexb::build_prefix(params.sps.iter().chain(params.pps.iter()));
177 Ok(TrackConvert::LengthPrefixed {
178 length_size: params.length_size,
179 keyframe_prefix,
180 })
181}
182
183fn h265_convert(config: &VideoConfig) -> Result<TrackConvert> {
189 let Some(hvcc) = config.description.as_ref().filter(|d| !d.is_empty()) else {
190 return Ok(TrackConvert::Passthrough);
191 };
192 let params = moq_mux::codec::h265::Hvcc::parse(hvcc)
193 .map_err(|err| crate::Error::Other(anyhow::anyhow!("hvcc parse: {err}")))?;
194 if params.vps.is_empty() || params.sps.is_empty() || params.pps.is_empty() {
197 return Err(crate::Error::Other(anyhow::anyhow!(
198 "hvc1 hvcC is missing parameter sets (vps={}, sps={}, pps={})",
199 params.vps.len(),
200 params.sps.len(),
201 params.pps.len()
202 )));
203 }
204 let keyframe_prefix =
205 moq_mux::codec::annexb::build_prefix(params.vps.iter().chain(params.sps.iter()).chain(params.pps.iter()));
206 Ok(TrackConvert::LengthPrefixed {
207 length_size: params.length_size,
208 keyframe_prefix,
209 })
210}
211
212#[cfg(test)]
213mod tests {
214 use hang::catalog::{H264, H265, VideoConfig};
215
216 use super::*;
217
218 fn config(codec: impl Into<hang::catalog::VideoCodec>, description: Option<Bytes>) -> VideoConfig {
219 let mut config = VideoConfig::new(codec);
220 config.description = description;
221 config
222 }
223
224 fn h264(inline: bool) -> H264 {
225 H264 {
226 inline,
227 profile: 0x42,
228 constraints: 0,
229 level: 0x1f,
230 }
231 }
232
233 fn h265(in_band: bool) -> H265 {
234 H265 {
235 in_band,
236 profile_space: 0,
237 profile_idc: 1,
238 profile_compatibility_flags: [0; 4],
239 tier_flag: false,
240 level_idc: 0x5d,
241 constraint_flags: [0; 6],
242 }
243 }
244
245 fn build_avcc(sps: &[u8], pps: &[u8]) -> Bytes {
247 let mut v = vec![1, sps[1], sps[2], sps[3], 0xff, 0xe1];
248 v.extend_from_slice(&(sps.len() as u16).to_be_bytes());
249 v.extend_from_slice(sps);
250 v.push(1);
251 v.extend_from_slice(&(pps.len() as u16).to_be_bytes());
252 v.extend_from_slice(pps);
253 Bytes::from(v)
254 }
255
256 fn build_hvcc(vps: &[u8], sps: &[u8], pps: &[u8]) -> Bytes {
259 let mut v = vec![0u8; 21];
260 v.push(0xff); v.push(3); for (nal_type, nal) in [(32u8, vps), (33, sps), (34, pps)] {
263 v.push(nal_type); v.extend_from_slice(&1u16.to_be_bytes()); v.extend_from_slice(&(nal.len() as u16).to_be_bytes());
266 v.extend_from_slice(nal);
267 }
268 Bytes::from(v)
269 }
270
271 #[test]
272 fn h264_avc3_passthrough() {
273 let cfg = config(h264(true), None);
274 assert!(matches!(h264_convert(&cfg).unwrap(), TrackConvert::Passthrough));
275 }
276
277 #[test]
278 fn h264_avc1_length_prefixed() {
279 let sps: &[u8] = &[0x67, 0x42, 0xc0, 0x1f, 0xde];
280 let pps: &[u8] = &[0x68, 0xce, 0x3c, 0x80];
281 let cfg = config(h264(false), Some(build_avcc(sps, pps)));
282
283 let TrackConvert::LengthPrefixed {
284 length_size,
285 keyframe_prefix,
286 } = h264_convert(&cfg).unwrap()
287 else {
288 panic!("expected LengthPrefixed");
289 };
290 assert_eq!(length_size, 4);
291 assert!(keyframe_prefix.starts_with(&[0, 0, 0, 1]), "Annex-B start code");
292 assert!(keyframe_prefix.windows(sps.len()).any(|w| w == sps), "SPS in prefix");
293 assert!(keyframe_prefix.windows(pps.len()).any(|w| w == pps), "PPS in prefix");
294 }
295
296 #[test]
297 fn h265_hev1_passthrough() {
298 let cfg = config(h265(true), None);
299 assert!(matches!(h265_convert(&cfg).unwrap(), TrackConvert::Passthrough));
300 }
301
302 #[test]
303 fn h265_hvc1_length_prefixed() {
304 let vps: &[u8] = &[0x40, 0x01, 0x0c, 0x01];
305 let sps: &[u8] = &[0x42, 0x01, 0x01, 0x01];
306 let pps: &[u8] = &[0x44, 0x01, 0xc0, 0xf7];
307 let cfg = config(h265(false), Some(build_hvcc(vps, sps, pps)));
308
309 let TrackConvert::LengthPrefixed {
310 length_size,
311 keyframe_prefix,
312 } = h265_convert(&cfg).unwrap()
313 else {
314 panic!("expected LengthPrefixed");
315 };
316 assert_eq!(length_size, 4);
317 let v = keyframe_prefix.windows(vps.len()).position(|w| w == vps).expect("VPS");
319 let s = keyframe_prefix.windows(sps.len()).position(|w| w == sps).expect("SPS");
320 let p = keyframe_prefix.windows(pps.len()).position(|w| w == pps).expect("PPS");
321 assert!(v < s && s < p, "VPS < SPS < PPS order in prefix");
322 }
323
324 #[test]
327 fn h264_avc1_missing_param_sets_errors() {
328 let avcc = Bytes::from(vec![1, 0x42, 0, 0x1f, 0xff, 0xe0, 0x00]);
330 let cfg = config(h264(false), Some(avcc));
331 assert!(h264_convert(&cfg).is_err(), "missing SPS/PPS must error");
332 }
333}