1use std::{fmt, ptr};
2
3use ffmpeg_next as ffmpeg;
4use str0m::format::{Codec, CodecSpec};
5
6use crate::error::Result;
7
8use super::command::WebRtcError;
9
10#[derive(Clone, PartialEq, Eq)]
18pub struct WebRtcStreamInfo {
19 codec_spec: CodecSpec,
20 h264: Option<H264Config>,
21}
22
23impl fmt::Debug for WebRtcStreamInfo {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 f.debug_struct("WebRtcStreamInfo")
26 .field("codec_spec", &self.codec_spec)
27 .field("video_dimensions", &self.video_dimensions())
28 .field("codec_parameters_ready", &self.codec_parameters_ready())
29 .finish()
30 }
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
34struct H264Config {
35 sps: Vec<u8>,
36 pps: Vec<u8>,
37 width: u32,
38 height: u32,
39}
40
41impl WebRtcStreamInfo {
42 pub fn codec_spec(&self) -> CodecSpec {
44 self.codec_spec
45 }
46
47 pub fn codec(&self) -> Codec {
49 self.codec_spec.codec
50 }
51
52 pub fn video_dimensions(&self) -> Option<(u32, u32)> {
55 self.h264.as_ref().map(|h264| (h264.width, h264.height))
56 }
57
58 pub fn time_base(&self) -> Result<ffmpeg::Rational> {
60 let clock_rate = clock_rate_i32(self.codec_spec)?;
61 Ok(ffmpeg::Rational::new(1, clock_rate))
62 }
63
64 pub fn codec_parameters(&self) -> Result<ffmpeg::codec::Parameters> {
72 let (medium, id) = ffmpeg_codec(self.codec_spec.codec).ok_or(
73 WebRtcError::UnsupportedCodecParameters(self.codec_spec.codec),
74 )?;
75 let mut parameters = base_parameters(self.codec_spec, medium, id)?;
76 match self.codec_spec.codec {
77 Codec::H264 => {
78 let h264 = self
79 .h264
80 .as_ref()
81 .ok_or(WebRtcError::H264ParameterSetsNotReceived)?;
82 set_h264_parameters(&mut parameters, h264)?;
83 }
84 Codec::Opus => {
85 let channels = audio_channels(self.codec_spec)?;
86 set_extradata(&mut parameters, &opus_head(channels))?;
87 }
88 _ => {}
89 }
90 Ok(parameters)
91 }
92
93 fn codec_parameters_ready(&self) -> bool {
94 match self.codec_spec.codec {
95 Codec::H264 => self.h264.is_some(),
96 Codec::Opus => self
97 .codec_spec
98 .channels
99 .is_some_and(|channels| (1..=2).contains(&channels)),
100 codec => ffmpeg_codec(codec).is_some(),
101 }
102 }
103}
104
105impl From<CodecSpec> for WebRtcStreamInfo {
106 fn from(codec_spec: CodecSpec) -> Self {
107 Self {
108 codec_spec,
109 h264: None,
110 }
111 }
112}
113
114pub(super) struct StreamInfoProbe {
116 codec_spec: Option<CodecSpec>,
117 h264_sps: Option<Vec<u8>>,
118 h264_pps: Option<Vec<u8>>,
119}
120
121impl StreamInfoProbe {
122 pub(super) fn new() -> Self {
123 Self {
124 codec_spec: None,
125 h264_sps: None,
126 h264_pps: None,
127 }
128 }
129
130 pub(super) fn observe(
133 &mut self,
134 codec_spec: CodecSpec,
135 payload: &[u8],
136 ) -> Option<WebRtcStreamInfo> {
137 if self.codec_spec != Some(codec_spec) {
138 self.codec_spec = Some(codec_spec);
139 self.h264_sps = None;
140 self.h264_pps = None;
141 }
142 if codec_spec.codec != Codec::H264 {
143 return Some(codec_spec.into());
144 }
145
146 for nalu in annex_b_nalus(payload) {
147 match nalu.first().map(|byte| byte & 0x1f) {
148 Some(7) => self.h264_sps = Some(nalu.to_vec()),
149 Some(8) => self.h264_pps = Some(nalu.to_vec()),
150 _ => {}
151 }
152 }
153 let (Some(sps), Some(pps)) = (&self.h264_sps, &self.h264_pps) else {
154 return None;
155 };
156 let (width, height) = parse_h264_dimensions(sps)?;
157 Some(WebRtcStreamInfo {
158 codec_spec,
159 h264: Some(H264Config {
160 sps: sps.clone(),
161 pps: pps.clone(),
162 width,
163 height,
164 }),
165 })
166 }
167}
168
169fn base_parameters(
170 codec_spec: CodecSpec,
171 medium: ffmpeg::media::Type,
172 id: ffmpeg::codec::Id,
173) -> Result<ffmpeg::codec::Parameters> {
174 let audio = medium == ffmpeg::media::Type::Audio;
175 let sample_rate = audio.then(|| clock_rate_i32(codec_spec)).transpose()?;
176 let channels = if audio {
177 match codec_spec.channels {
178 Some(0) => return Err(invalid_channel_count(codec_spec).into()),
179 channels => channels,
180 }
181 } else {
182 None
183 };
184 let mut parameters = ffmpeg::codec::Parameters::new();
185
186 unsafe {
189 let raw = parameters.as_mut_ptr();
190 (*raw).codec_type = medium.into();
191 (*raw).codec_id = id.into();
192 if let Some(sample_rate) = sample_rate {
193 (*raw).sample_rate = sample_rate;
194 }
195 if let Some(channels) = channels {
196 ffmpeg::ffi::av_channel_layout_default(&mut (*raw).ch_layout, i32::from(channels));
197 }
198 }
199 Ok(parameters)
200}
201
202fn set_h264_parameters(
203 parameters: &mut ffmpeg::codec::Parameters,
204 h264: &H264Config,
205) -> Result<()> {
206 let extradata = annex_b_h264_extradata(&h264.sps, &h264.pps)?;
211 unsafe {
214 let raw = parameters.as_mut_ptr();
215 (*raw).width = h264.width as i32;
216 (*raw).height = h264.height as i32;
217 (*raw).profile = i32::from(h264.sps[1]);
218 (*raw).level = i32::from(h264.sps[3]);
219 }
220 set_extradata(parameters, &extradata)
221}
222
223fn set_extradata(parameters: &mut ffmpeg::codec::Parameters, bytes: &[u8]) -> Result<()> {
224 let size = i32::try_from(bytes.len())
225 .map_err(|_| WebRtcError::CodecConfigurationTooLarge { size: bytes.len() })?;
226 let padded = bytes
227 .len()
228 .checked_add(ffmpeg::ffi::AV_INPUT_BUFFER_PADDING_SIZE as usize)
229 .ok_or(WebRtcError::CodecConfigurationTooLarge { size: bytes.len() })?;
230
231 unsafe {
234 let allocation = ffmpeg::ffi::av_mallocz(padded) as *mut u8;
235 if allocation.is_null() {
236 return Err(WebRtcError::CodecParametersAllocationFailed { size: padded }.into());
237 }
238 ptr::copy_nonoverlapping(bytes.as_ptr(), allocation, bytes.len());
239 let raw = parameters.as_mut_ptr();
240 (*raw).extradata = allocation;
241 (*raw).extradata_size = size;
242 }
243 Ok(())
244}
245
246fn annex_b_h264_extradata(sps: &[u8], pps: &[u8]) -> Result<Vec<u8>> {
247 if sps.len() < 4 || sps.first().map(|byte| byte & 0x1f) != Some(7) {
248 return Err(WebRtcError::InvalidH264ParameterSet("SPS").into());
249 }
250 if pps.is_empty() || pps.first().map(|byte| byte & 0x1f) != Some(8) {
251 return Err(WebRtcError::InvalidH264ParameterSet("PPS").into());
252 }
253 let mut result = Vec::with_capacity(8 + sps.len() + pps.len());
254 result.extend_from_slice(&[0, 0, 0, 1]);
255 result.extend_from_slice(sps);
256 result.extend_from_slice(&[0, 0, 0, 1]);
257 result.extend_from_slice(pps);
258 Ok(result)
259}
260
261fn opus_head(channels: u8) -> Vec<u8> {
262 let mut result = Vec::with_capacity(19);
263 result.extend_from_slice(b"OpusHead");
264 result.push(1);
265 result.push(channels);
266 result.extend_from_slice(&0u16.to_le_bytes());
267 result.extend_from_slice(&48_000u32.to_le_bytes());
268 result.extend_from_slice(&0i16.to_le_bytes());
269 result.push(0);
270 result
271}
272
273fn clock_rate_i32(codec_spec: CodecSpec) -> std::result::Result<i32, WebRtcError> {
274 let clock_rate = codec_spec.clock_rate.get();
275 i32::try_from(clock_rate).map_err(|_| WebRtcError::InvalidStreamClockRate {
276 codec: codec_spec.codec,
277 clock_rate,
278 })
279}
280
281fn audio_channels(codec_spec: CodecSpec) -> std::result::Result<u8, WebRtcError> {
282 match codec_spec.channels {
283 Some(channels @ 1..=2) => Ok(channels),
286 _ => Err(invalid_channel_count(codec_spec)),
287 }
288}
289
290fn invalid_channel_count(codec_spec: CodecSpec) -> WebRtcError {
291 WebRtcError::InvalidStreamChannelCount {
292 codec: codec_spec.codec,
293 channels: codec_spec.channels.unwrap_or(0),
294 }
295}
296
297fn ffmpeg_codec(codec: Codec) -> Option<(ffmpeg::media::Type, ffmpeg::codec::Id)> {
298 use ffmpeg::{codec::Id, media::Type};
299 match codec {
300 Codec::Opus => Some((Type::Audio, Id::OPUS)),
301 Codec::PCMU => Some((Type::Audio, Id::PCM_MULAW)),
302 Codec::PCMA => Some((Type::Audio, Id::PCM_ALAW)),
303 Codec::H264 => Some((Type::Video, Id::H264)),
304 Codec::H265 => Some((Type::Video, Id::HEVC)),
305 Codec::H266 => Some((Type::Video, Id::VVC)),
306 Codec::Vp8 => Some((Type::Video, Id::VP8)),
307 Codec::Vp9 => Some((Type::Video, Id::VP9)),
308 Codec::Av1 => Some((Type::Video, Id::AV1)),
309 _ => None,
310 }
311}
312
313pub(super) fn str0m_codec(id: ffmpeg::codec::Id) -> Option<Codec> {
317 use ffmpeg::codec::Id;
318 match id {
319 Id::OPUS => Some(Codec::Opus),
320 Id::PCM_MULAW => Some(Codec::PCMU),
321 Id::PCM_ALAW => Some(Codec::PCMA),
322 Id::H264 => Some(Codec::H264),
323 Id::HEVC => Some(Codec::H265),
324 Id::VVC => Some(Codec::H266),
325 Id::VP8 => Some(Codec::Vp8),
326 Id::VP9 => Some(Codec::Vp9),
327 Id::AV1 => Some(Codec::Av1),
328 _ => None,
329 }
330}
331
332pub(super) fn annex_b_nalus(data: &[u8]) -> Vec<&[u8]> {
333 let mut starts = Vec::new();
334 let mut offset = 0;
335 while offset + 3 <= data.len() {
336 let length = if data[offset..].starts_with(&[0, 0, 0, 1]) {
337 4
338 } else if data[offset..].starts_with(&[0, 0, 1]) {
339 3
340 } else {
341 offset += 1;
342 continue;
343 };
344 starts.push((offset, length));
345 offset += length;
346 }
347 starts
348 .iter()
349 .enumerate()
350 .filter_map(|(index, (start, length))| {
351 let nalu_start = start + length;
352 let nalu_end = starts
353 .get(index + 1)
354 .map(|(next, _)| *next)
355 .unwrap_or(data.len());
356 (nalu_start < nalu_end).then_some(&data[nalu_start..nalu_end])
357 })
358 .collect()
359}
360
361fn parse_h264_dimensions(sps: &[u8]) -> Option<(u32, u32)> {
362 if sps.len() < 4 || sps[0] & 0x1f != 7 {
363 return None;
364 }
365 let mut rbsp = Vec::with_capacity(sps.len() - 1);
366 let mut zeros = 0;
367 for &byte in &sps[1..] {
368 if zeros >= 2 && byte == 3 {
369 zeros = 0;
370 continue;
371 }
372 rbsp.push(byte);
373 zeros = if byte == 0 { zeros + 1 } else { 0 };
374 }
375
376 let mut bits = BitReader::new(&rbsp);
377 let profile_idc = bits.read_bits(8)? as u8;
378 bits.read_bits(8)?;
379 bits.read_bits(8)?;
380 bits.read_ue()?;
381
382 let mut chroma_format_idc = 1;
383 let mut separate_colour_plane = false;
384 if matches!(
385 profile_idc,
386 44 | 83 | 86 | 100 | 110 | 118 | 122 | 128 | 134 | 135 | 138 | 139 | 244
387 ) {
388 chroma_format_idc = bits.read_ue()?;
389 if chroma_format_idc > 3 {
390 return None;
391 }
392 if chroma_format_idc == 3 {
393 separate_colour_plane = bits.read_bit()?;
394 }
395 bits.read_ue()?;
396 bits.read_ue()?;
397 bits.read_bit()?;
398 if bits.read_bit()? {
399 let count = if chroma_format_idc == 3 { 12 } else { 8 };
400 for index in 0..count {
401 if bits.read_bit()? {
402 skip_scaling_list(&mut bits, if index < 6 { 16 } else { 64 })?;
403 }
404 }
405 }
406 }
407
408 bits.read_ue()?;
409 match bits.read_ue()? {
410 0 => {
411 bits.read_ue()?;
412 }
413 1 => {
414 bits.read_bit()?;
415 bits.read_se()?;
416 bits.read_se()?;
417 for _ in 0..bits.read_ue()? {
418 bits.read_se()?;
419 }
420 }
421 2 => {}
422 _ => return None,
423 }
424 bits.read_ue()?;
425 bits.read_bit()?;
426 let width_in_mbs = bits.read_ue()?.checked_add(1)?;
427 let height_in_map_units = bits.read_ue()?.checked_add(1)?;
428 let frame_mbs_only = bits.read_bit()?;
429 if !frame_mbs_only {
430 bits.read_bit()?;
431 }
432 bits.read_bit()?;
433 let (crop_left, crop_right, crop_top, crop_bottom) = if bits.read_bit()? {
434 (
435 bits.read_ue()?,
436 bits.read_ue()?,
437 bits.read_ue()?,
438 bits.read_ue()?,
439 )
440 } else {
441 (0, 0, 0, 0)
442 };
443
444 let frame_factor = if frame_mbs_only { 1 } else { 2 };
445 let chroma_array_type = if separate_colour_plane {
446 0
447 } else {
448 chroma_format_idc
449 };
450 let (sub_width_c, sub_height_c) = match chroma_array_type {
451 0 => (1, 1),
452 1 => (2, 2),
453 2 => (2, 1),
454 3 => (1, 1),
455 _ => return None,
456 };
457 let crop_unit_x = if chroma_array_type == 0 {
458 1
459 } else {
460 sub_width_c
461 };
462 let crop_unit_y = if chroma_array_type == 0 {
463 frame_factor
464 } else {
465 sub_height_c * frame_factor
466 };
467 let width = width_in_mbs
468 .checked_mul(16)?
469 .checked_sub((crop_left + crop_right).checked_mul(crop_unit_x)?)?;
470 let height = height_in_map_units
471 .checked_mul(16)?
472 .checked_mul(frame_factor)?
473 .checked_sub((crop_top + crop_bottom).checked_mul(crop_unit_y)?)?;
474 (width > 0 && height > 0 && width <= i32::MAX as u32 && height <= i32::MAX as u32)
475 .then_some((width, height))
476}
477
478fn skip_scaling_list(bits: &mut BitReader<'_>, size: usize) -> Option<()> {
479 let mut last_scale = 8i32;
480 let mut next_scale = 8i32;
481 for _ in 0..size {
482 if next_scale != 0 {
483 next_scale = (last_scale + bits.read_se()? + 256) % 256;
484 }
485 if next_scale != 0 {
486 last_scale = next_scale;
487 }
488 }
489 Some(())
490}
491
492struct BitReader<'a> {
493 data: &'a [u8],
494 bit: usize,
495}
496
497impl<'a> BitReader<'a> {
498 fn new(data: &'a [u8]) -> Self {
499 Self { data, bit: 0 }
500 }
501
502 fn read_bit(&mut self) -> Option<bool> {
503 Some(self.read_bits(1)? != 0)
504 }
505
506 fn read_bits(&mut self, count: usize) -> Option<u32> {
507 if count > 32 || self.bit.checked_add(count)? > self.data.len().checked_mul(8)? {
508 return None;
509 }
510 let mut value = 0u32;
511 for _ in 0..count {
512 let byte = self.data[self.bit / 8];
513 value = (value << 1) | u32::from((byte >> (7 - self.bit % 8)) & 1);
514 self.bit += 1;
515 }
516 Some(value)
517 }
518
519 fn read_ue(&mut self) -> Option<u32> {
520 let mut leading_zeroes = 0usize;
521 while !self.read_bit()? {
522 leading_zeroes += 1;
523 if leading_zeroes > 31 {
524 return None;
525 }
526 }
527 let suffix = self.read_bits(leading_zeroes)?;
528 ((1u32 << leading_zeroes) - 1).checked_add(suffix)
529 }
530
531 fn read_se(&mut self) -> Option<i32> {
532 let code_num = self.read_ue()?;
533 let magnitude = i32::try_from(code_num.checked_add(1)? / 2).ok()?;
534 Some(if code_num % 2 == 0 {
535 -magnitude
536 } else {
537 magnitude
538 })
539 }
540}
541
542#[cfg(test)]
543mod tests {
544 use ffmpeg_next as ffmpeg;
545 use str0m::{
546 format::{Codec, CodecSpec, FormatParams},
547 media::Frequency,
548 };
549
550 use super::{StreamInfoProbe, WebRtcStreamInfo};
551 use crate::elements::WebRtcError;
552
553 const SPS: &[u8] = &[
554 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, 0x40, 0x3c, 0x22, 0x11, 0xa8,
555 ];
556 const PPS: &[u8] = &[0x68, 0x1a, 0x34, 0xe3, 0xc8];
557
558 fn spec(codec: Codec, clock_rate: Frequency, channels: Option<u8>) -> CodecSpec {
559 CodecSpec {
560 codec,
561 clock_rate,
562 channels,
563 format: FormatParams::default(),
564 }
565 }
566
567 #[test]
568 fn h264_waits_for_both_parameter_sets_and_derives_codec_parameters() {
569 let codec = spec(Codec::H264, Frequency::NINETY_KHZ, None);
570 let mut probe = StreamInfoProbe::new();
571 let mut sps_payload = vec![0, 0, 0, 1];
572 sps_payload.extend_from_slice(SPS);
573 assert!(probe.observe(codec, &sps_payload).is_none());
574
575 let mut pps_payload = vec![0, 0, 1];
576 pps_payload.extend_from_slice(PPS);
577 let info = probe
578 .observe(codec, &pps_payload)
579 .expect("PPS completes H.264 stream info");
580 let parameters = info.codec_parameters().expect("H.264 codec parameters");
581
582 assert_eq!(info.video_dimensions(), Some((640, 480)));
583 assert_eq!(info.time_base().unwrap(), ffmpeg::Rational::new(1, 90_000));
584 assert_eq!(parameters.id(), ffmpeg::codec::Id::H264);
585 unsafe {
587 assert_eq!((*parameters.as_ptr()).width, 640);
588 assert_eq!((*parameters.as_ptr()).height, 480);
589 assert!((*parameters.as_ptr()).extradata_size > 0);
590 }
591 }
592
593 #[test]
594 fn opus_derives_complete_codec_parameters() {
595 let info = WebRtcStreamInfo::from(spec(Codec::Opus, Frequency::FORTY_EIGHT_KHZ, Some(2)));
596 let parameters = info.codec_parameters().expect("Opus codec parameters");
597 assert_eq!(parameters.id(), ffmpeg::codec::Id::OPUS);
598 unsafe {
600 assert_eq!((*parameters.as_ptr()).sample_rate, 48_000);
601 assert_eq!((*parameters.as_ptr()).ch_layout.nb_channels, 2);
602 let extra = std::slice::from_raw_parts(
603 (*parameters.as_ptr()).extradata,
604 (*parameters.as_ptr()).extradata_size as usize,
605 );
606 assert_eq!(&extra[..8], b"OpusHead");
607 }
608 }
609
610 #[test]
611 fn vp8_derives_codec_parameters_without_container_policy() {
612 let info = WebRtcStreamInfo::from(spec(Codec::Vp8, Frequency::NINETY_KHZ, None));
613 let parameters = info.codec_parameters().expect("VP8 codec parameters");
614
615 assert_eq!(parameters.id(), ffmpeg::codec::Id::VP8);
616 assert_eq!(info.time_base().unwrap(), ffmpeg::Rational::new(1, 90_000));
617 }
618
619 #[test]
620 fn rtx_cannot_form_codec_parameters() {
621 let info = WebRtcStreamInfo::from(spec(Codec::Rtx, Frequency::NINETY_KHZ, None));
622 assert!(matches!(
623 info.codec_parameters()
624 .err()
625 .expect("RTX codec-parameter rejection"),
626 crate::Error::WebRtcError(WebRtcError::UnsupportedCodecParameters(Codec::Rtx))
627 ));
628 }
629
630 #[test]
631 fn invalid_clock_rate_and_channels_are_typed_errors() {
632 let clock_rate = Frequency::new(u32::MAX).unwrap();
633 let info = WebRtcStreamInfo::from(spec(Codec::Opus, clock_rate, Some(2)));
634 assert!(matches!(
635 info.time_base().unwrap_err(),
636 crate::Error::WebRtcError(WebRtcError::InvalidStreamClockRate {
637 codec: Codec::Opus,
638 clock_rate: u32::MAX,
639 })
640 ));
641
642 let no_channels =
643 WebRtcStreamInfo::from(spec(Codec::Opus, Frequency::FORTY_EIGHT_KHZ, None));
644 assert!(matches!(
645 no_channels
646 .codec_parameters()
647 .err()
648 .expect("complete Opus parameters require a channel count"),
649 crate::Error::WebRtcError(WebRtcError::InvalidStreamChannelCount {
650 codec: Codec::Opus,
651 channels: 0,
652 })
653 ));
654
655 let zero_channels =
656 WebRtcStreamInfo::from(spec(Codec::Opus, Frequency::FORTY_EIGHT_KHZ, Some(0)));
657 assert!(matches!(
658 zero_channels
659 .codec_parameters()
660 .err()
661 .expect("zero channels are invalid"),
662 crate::Error::WebRtcError(WebRtcError::InvalidStreamChannelCount {
663 codec: Codec::Opus,
664 channels: 0,
665 })
666 ));
667 }
668
669 #[test]
681 fn malformed_h264_payloads_never_confirm_and_never_panic() {
682 let codec = spec(Codec::H264, Frequency::NINETY_KHZ, None);
683 let annex_b = |nalu: &[u8]| {
684 let mut payload = vec![0, 0, 0, 1];
685 payload.extend_from_slice(nalu);
686 payload
687 };
688
689 for payload in [vec![], vec![0, 0, 0, 1], vec![0, 0, 1]] {
691 let mut probe = StreamInfoProbe::new();
692 assert!(
693 probe.observe(codec, &payload).is_none(),
694 "an empty payload cannot confirm a stream"
695 );
696 }
697
698 for nal_type in [1u8, 5, 6, 9, 31] {
701 let mut probe = StreamInfoProbe::new();
702 let payload = annex_b(&[0x60 | nal_type, 0x42, 0xc0, 0x1f]);
703 assert!(
704 probe.observe(codec, &payload).is_none(),
705 "NAL type {nal_type} must not be mistaken for a parameter set"
706 );
707 }
708
709 for length in 0..SPS.len() {
722 let mut probe = StreamInfoProbe::new();
723 let _ = probe.observe(codec, &annex_b(&SPS[..length]));
724 let confirmed = probe.observe(codec, &annex_b(PPS));
725 if length < 4 {
726 assert!(
727 confirmed.is_none(),
728 "a {length}-byte SPS is too short to describe anything"
729 );
730 }
731 if let Some(info) = confirmed {
732 info.codec_parameters().unwrap_or_else(|error| {
733 panic!("a {length}-byte SPS confirmed but then failed to build: {error}")
734 });
735 }
736 }
737
738 let mut sps = vec![0x67, 0x42, 0xc0, 0x1f];
742 sps.extend(std::iter::repeat_n(0x00, 16));
743 sps.push(0x01);
744 let mut probe = StreamInfoProbe::new();
745 let _ = probe.observe(codec, &annex_b(&sps));
746 assert!(
747 probe.observe(codec, &annex_b(PPS)).is_none(),
748 "an over-long Exp-Golomb code must fail the parse, not the process"
749 );
750 }
751}