Skip to main content

mediadecode/
packet.rs

1//! Compressed `Packet` types and `PacketFlags`.
2//!
3//! The Packet types proper land in later tasks; this module starts
4//! with `PacketFlags` so dependent types can use it.
5
6use bitflags::bitflags;
7
8bitflags! {
9  /// Per-packet flags.
10  ///
11  /// Bit values are the public API:
12  /// - `KEY = 0b001` — packet starts a keyframe (FFmpeg `AV_PKT_FLAG_KEY`,
13  ///   WebCodecs `'key'`, ProRes RAW absence of
14  ///   `kCMSampleAttachmentKey_NotSync`).
15  /// - `CORRUPT = 0b010` — packet is known-corrupt (FFmpeg
16  ///   `AV_PKT_FLAG_CORRUPT`).
17  /// - `DISCARD = 0b100` — packet should be skipped during reconstruction
18  ///   (FFmpeg `AV_PKT_FLAG_DISCARD`).
19  ///
20  /// # Text form
21  ///
22  /// This type deliberately has **no** `Display` / `FromStr`, and its
23  /// serde shape is the raw [`bits`](Self::bits) as a number. A
24  /// vocabulary of *names* takes a text form; a bit *set* takes a
25  /// number. A flag-set grammar (`"key|discard"`) would need two shapes
26  /// rather than one, because a bit this build has no constant for can
27  /// only be printed as a bare literal — and there are such bits today:
28  /// FFmpeg carries `AV_PKT_FLAG_TRUSTED` (`0b0_1000`) and
29  /// `AV_PKT_FLAG_DISPOSABLE` (`0b1_0000`), which this set does not
30  /// name. Human-readable names live in `Debug` and in whatever
31  /// consumer surface wants them. This is `mediaframe::TrackDisposition`'s
32  /// stance, for the same reason.
33  #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
34  pub struct PacketFlags: u8 {
35    /// Keyframe / sync sample.
36    const KEY     = 0b001;
37    /// Bitstream-level corruption known.
38    const CORRUPT = 0b010;
39    /// Demuxer hint: skip this packet.
40    const DISCARD = 0b100;
41  }
42}
43
44use crate::Timestamp;
45
46/// A compressed video packet.
47///
48/// Generic over the [`VideoAdapter`] (which contributes
49/// `A::PacketExtra`) and the buffer type `B: AsRef<[u8]>`.
50///
51/// `pts` / `dts` / `duration` are `Option<Timestamp>` because not
52/// every backend supplies all three (WebCodecs `EncodedVideoChunk`
53/// has no DTS; vendor RAW SDKs that produce packets at all derive
54/// timestamps from frame index × fps).
55///
56/// `Clone` and `Debug` derive directly: every field is either a
57/// concrete `Copy` type or one of `E` / `D` themselves, so the
58/// derive's per-parameter bound (`E: Clone, D: Clone`) is exactly
59/// what cloning the fields requires — no associated-type indirection
60/// to route around.
61#[derive(Clone, Debug)]
62pub struct VideoPacket<E, D> {
63  pts: Option<Timestamp>,
64  dts: Option<Timestamp>,
65  duration: Option<Timestamp>,
66  flags: PacketFlags,
67  data: D,
68  extra: E,
69}
70
71impl<E, D> VideoPacket<E, D> {
72  /// Constructs a `VideoPacket` from `data` and `extra`. All
73  /// timestamps default to `None` and flags to empty.
74  #[cfg_attr(not(tarpaulin), inline(always))]
75  pub const fn new(data: D, extra: E) -> Self {
76    Self {
77      pts: None,
78      dts: None,
79      duration: None,
80      flags: PacketFlags::empty(),
81      data,
82      extra,
83    }
84  }
85
86  /// Returns the presentation timestamp.
87  #[cfg_attr(not(tarpaulin), inline(always))]
88  pub const fn pts(&self) -> Option<Timestamp> {
89    self.pts
90  }
91  /// Returns the decompression timestamp.
92  #[cfg_attr(not(tarpaulin), inline(always))]
93  pub const fn dts(&self) -> Option<Timestamp> {
94    self.dts
95  }
96  /// Returns the packet duration.
97  #[cfg_attr(not(tarpaulin), inline(always))]
98  pub const fn duration(&self) -> Option<Timestamp> {
99    self.duration
100  }
101  /// Returns the packet flags.
102  #[cfg_attr(not(tarpaulin), inline(always))]
103  pub const fn flags(&self) -> PacketFlags {
104    self.flags
105  }
106  /// Returns the compressed data buffer.
107  #[cfg_attr(not(tarpaulin), inline(always))]
108  pub const fn data(&self) -> &D {
109    &self.data
110  }
111  /// Returns the backend-specific extras.
112  #[cfg_attr(not(tarpaulin), inline(always))]
113  pub const fn extra(&self) -> &E {
114    &self.extra
115  }
116  /// Returns a mutable reference to the backend-specific extras.
117  #[cfg_attr(not(tarpaulin), inline(always))]
118  pub fn extra_mut(&mut self) -> &mut E {
119    &mut self.extra
120  }
121  /// Consumes the packet and returns the buffer.
122  #[cfg_attr(not(tarpaulin), inline(always))]
123  pub fn into_data(self) -> D {
124    self.data
125  }
126  /// Consumes the packet and returns `(buffer, extras)`.
127  #[cfg_attr(not(tarpaulin), inline(always))]
128  pub fn into_parts(self) -> (D, E) {
129    (self.data, self.extra)
130  }
131
132  /// Sets the PTS (consuming builder).
133  #[cfg_attr(not(tarpaulin), inline(always))]
134  #[must_use]
135  pub const fn with_pts(mut self, v: Option<Timestamp>) -> Self {
136    self.pts = v;
137    self
138  }
139  /// Sets the DTS (consuming builder).
140  #[cfg_attr(not(tarpaulin), inline(always))]
141  #[must_use]
142  pub const fn with_dts(mut self, v: Option<Timestamp>) -> Self {
143    self.dts = v;
144    self
145  }
146  /// Sets the duration (consuming builder).
147  #[cfg_attr(not(tarpaulin), inline(always))]
148  #[must_use]
149  pub const fn with_duration(mut self, v: Option<Timestamp>) -> Self {
150    self.duration = v;
151    self
152  }
153  /// Sets the flags (consuming builder).
154  #[cfg_attr(not(tarpaulin), inline(always))]
155  #[must_use]
156  pub const fn with_flags(mut self, v: PacketFlags) -> Self {
157    self.flags = v;
158    self
159  }
160
161  /// Sets the PTS in place.
162  #[cfg_attr(not(tarpaulin), inline(always))]
163  pub const fn set_pts(&mut self, v: Option<Timestamp>) -> &mut Self {
164    self.pts = v;
165    self
166  }
167  /// Sets the DTS in place.
168  #[cfg_attr(not(tarpaulin), inline(always))]
169  pub const fn set_dts(&mut self, v: Option<Timestamp>) -> &mut Self {
170    self.dts = v;
171    self
172  }
173  /// Sets the duration in place.
174  #[cfg_attr(not(tarpaulin), inline(always))]
175  pub const fn set_duration(&mut self, v: Option<Timestamp>) -> &mut Self {
176    self.duration = v;
177    self
178  }
179  /// Sets the flags in place.
180  #[cfg_attr(not(tarpaulin), inline(always))]
181  pub const fn set_flags(&mut self, v: PacketFlags) -> &mut Self {
182    self.flags = v;
183    self
184  }
185}
186
187/// A compressed audio packet.
188#[derive(Clone, Debug)]
189pub struct AudioPacket<E, D> {
190  pts: Option<Timestamp>,
191  dts: Option<Timestamp>,
192  duration: Option<Timestamp>,
193  flags: PacketFlags,
194  data: D,
195  extra: E,
196}
197
198impl<E, D> AudioPacket<E, D> {
199  /// Constructs an `AudioPacket` from `data` and `extra`.
200  #[cfg_attr(not(tarpaulin), inline(always))]
201  pub const fn new(data: D, extra: E) -> Self {
202    Self {
203      pts: None,
204      dts: None,
205      duration: None,
206      flags: PacketFlags::empty(),
207      data,
208      extra,
209    }
210  }
211
212  /// Returns the presentation timestamp.
213  #[cfg_attr(not(tarpaulin), inline(always))]
214  pub const fn pts(&self) -> Option<Timestamp> {
215    self.pts
216  }
217  /// Returns the decompression timestamp.
218  #[cfg_attr(not(tarpaulin), inline(always))]
219  pub const fn dts(&self) -> Option<Timestamp> {
220    self.dts
221  }
222  /// Returns the duration.
223  #[cfg_attr(not(tarpaulin), inline(always))]
224  pub const fn duration(&self) -> Option<Timestamp> {
225    self.duration
226  }
227  /// Returns the flags.
228  #[cfg_attr(not(tarpaulin), inline(always))]
229  pub const fn flags(&self) -> PacketFlags {
230    self.flags
231  }
232  /// Returns the compressed audio data.
233  #[cfg_attr(not(tarpaulin), inline(always))]
234  pub const fn data(&self) -> &D {
235    &self.data
236  }
237  /// Returns the backend extras.
238  #[cfg_attr(not(tarpaulin), inline(always))]
239  pub const fn extra(&self) -> &E {
240    &self.extra
241  }
242  /// Returns a mutable reference to the backend extras.
243  #[cfg_attr(not(tarpaulin), inline(always))]
244  pub fn extra_mut(&mut self) -> &mut E {
245    &mut self.extra
246  }
247  /// Consumes the packet and returns the buffer.
248  #[cfg_attr(not(tarpaulin), inline(always))]
249  pub fn into_data(self) -> D {
250    self.data
251  }
252  /// Consumes the packet and returns `(buffer, extras)`.
253  #[cfg_attr(not(tarpaulin), inline(always))]
254  pub fn into_parts(self) -> (D, E) {
255    (self.data, self.extra)
256  }
257
258  /// Sets the PTS (consuming builder).
259  #[cfg_attr(not(tarpaulin), inline(always))]
260  #[must_use]
261  pub const fn with_pts(mut self, v: Option<Timestamp>) -> Self {
262    self.pts = v;
263    self
264  }
265  /// Sets the DTS (consuming builder).
266  #[cfg_attr(not(tarpaulin), inline(always))]
267  #[must_use]
268  pub const fn with_dts(mut self, v: Option<Timestamp>) -> Self {
269    self.dts = v;
270    self
271  }
272  /// Sets the duration (consuming builder).
273  #[cfg_attr(not(tarpaulin), inline(always))]
274  #[must_use]
275  pub const fn with_duration(mut self, v: Option<Timestamp>) -> Self {
276    self.duration = v;
277    self
278  }
279  /// Sets the flags (consuming builder).
280  #[cfg_attr(not(tarpaulin), inline(always))]
281  #[must_use]
282  pub const fn with_flags(mut self, v: PacketFlags) -> Self {
283    self.flags = v;
284    self
285  }
286
287  /// Sets the PTS in place.
288  #[cfg_attr(not(tarpaulin), inline(always))]
289  pub const fn set_pts(&mut self, v: Option<Timestamp>) -> &mut Self {
290    self.pts = v;
291    self
292  }
293  /// Sets the DTS in place.
294  #[cfg_attr(not(tarpaulin), inline(always))]
295  pub const fn set_dts(&mut self, v: Option<Timestamp>) -> &mut Self {
296    self.dts = v;
297    self
298  }
299  /// Sets the duration in place.
300  #[cfg_attr(not(tarpaulin), inline(always))]
301  pub const fn set_duration(&mut self, v: Option<Timestamp>) -> &mut Self {
302    self.duration = v;
303    self
304  }
305  /// Sets the flags in place.
306  #[cfg_attr(not(tarpaulin), inline(always))]
307  pub const fn set_flags(&mut self, v: PacketFlags) -> &mut Self {
308    self.flags = v;
309    self
310  }
311}
312
313/// A compressed subtitle packet.
314#[derive(Clone, Debug)]
315pub struct SubtitlePacket<E, D> {
316  pts: Option<Timestamp>,
317  duration: Option<Timestamp>,
318  flags: PacketFlags,
319  data: D,
320  extra: E,
321}
322
323impl<E, D> SubtitlePacket<E, D> {
324  /// Constructs a `SubtitlePacket` from `data` and `extra`.
325  #[cfg_attr(not(tarpaulin), inline(always))]
326  pub const fn new(data: D, extra: E) -> Self {
327    Self {
328      pts: None,
329      duration: None,
330      flags: PacketFlags::empty(),
331      data,
332      extra,
333    }
334  }
335
336  /// Returns the presentation timestamp.
337  #[cfg_attr(not(tarpaulin), inline(always))]
338  pub const fn pts(&self) -> Option<Timestamp> {
339    self.pts
340  }
341  /// Returns the duration.
342  #[cfg_attr(not(tarpaulin), inline(always))]
343  pub const fn duration(&self) -> Option<Timestamp> {
344    self.duration
345  }
346  /// Returns the flags.
347  #[cfg_attr(not(tarpaulin), inline(always))]
348  pub const fn flags(&self) -> PacketFlags {
349    self.flags
350  }
351  /// Returns the compressed subtitle data.
352  #[cfg_attr(not(tarpaulin), inline(always))]
353  pub const fn data(&self) -> &D {
354    &self.data
355  }
356  /// Returns the backend extras.
357  #[cfg_attr(not(tarpaulin), inline(always))]
358  pub const fn extra(&self) -> &E {
359    &self.extra
360  }
361  /// Returns a mutable reference to the backend extras.
362  #[cfg_attr(not(tarpaulin), inline(always))]
363  pub fn extra_mut(&mut self) -> &mut E {
364    &mut self.extra
365  }
366  /// Consumes the packet and returns the buffer.
367  #[cfg_attr(not(tarpaulin), inline(always))]
368  pub fn into_data(self) -> D {
369    self.data
370  }
371  /// Consumes the packet and returns `(buffer, extras)`.
372  #[cfg_attr(not(tarpaulin), inline(always))]
373  pub fn into_parts(self) -> (D, E) {
374    (self.data, self.extra)
375  }
376
377  /// Sets the PTS (consuming builder).
378  #[cfg_attr(not(tarpaulin), inline(always))]
379  #[must_use]
380  pub const fn with_pts(mut self, v: Option<Timestamp>) -> Self {
381    self.pts = v;
382    self
383  }
384  /// Sets the duration (consuming builder).
385  #[cfg_attr(not(tarpaulin), inline(always))]
386  #[must_use]
387  pub const fn with_duration(mut self, v: Option<Timestamp>) -> Self {
388    self.duration = v;
389    self
390  }
391  /// Sets the flags (consuming builder).
392  #[cfg_attr(not(tarpaulin), inline(always))]
393  #[must_use]
394  pub const fn with_flags(mut self, v: PacketFlags) -> Self {
395    self.flags = v;
396    self
397  }
398
399  /// Sets the PTS in place.
400  #[cfg_attr(not(tarpaulin), inline(always))]
401  pub const fn set_pts(&mut self, v: Option<Timestamp>) -> &mut Self {
402    self.pts = v;
403    self
404  }
405  /// Sets the duration in place.
406  #[cfg_attr(not(tarpaulin), inline(always))]
407  pub const fn set_duration(&mut self, v: Option<Timestamp>) -> &mut Self {
408    self.duration = v;
409    self
410  }
411  /// Sets the flags in place.
412  #[cfg_attr(not(tarpaulin), inline(always))]
413  pub const fn set_flags(&mut self, v: PacketFlags) -> &mut Self {
414    self.flags = v;
415    self
416  }
417}
418
419// ---------------------------------------------------------------------------
420//  Optional trait matrices (`serde` / `arbitrary` / `quickcheck`) for
421//  `PacketFlags`. The packet types themselves are generic over a caller's
422//  buffer and extras and are not a wire vocabulary; the flag set is.
423// ---------------------------------------------------------------------------
424
425#[cfg(feature = "serde")]
426#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
427mod serde_impls {
428  //! The bit set travels as its number.
429  //!
430  //! This is the opposite of the choice `channel`'s two vocabularies
431  //! make, and for the opposite reason. There, a `u32` wire would let an
432  //! unrecognised code decode to `Unknown` — inventing a value — so the
433  //! name is the only faithful shape. Here every bit pattern *is* a
434  //! value, including the ones this build has no constant for
435  //! (`AV_PKT_FLAG_TRUSTED`, `AV_PKT_FLAG_DISPOSABLE`), so the number is
436  //! the only shape that carries them all. `from_bits_retain` is what
437  //! keeps that round trip lossless; `from_bits` would reject the very
438  //! bits the wire exists to preserve.
439  //!
440  //! `mediaframe::TrackDisposition` sits on this same wire, so a
441  //! consumer that stores both sees one convention.
442
443  use serde::{Deserialize, Deserializer, Serialize, Serializer};
444
445  use super::PacketFlags;
446
447  impl Serialize for PacketFlags {
448    #[cfg_attr(not(tarpaulin), inline(always))]
449    fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
450      ser.serialize_u8(self.bits())
451    }
452  }
453
454  impl<'de> Deserialize<'de> for PacketFlags {
455    #[cfg_attr(not(tarpaulin), inline(always))]
456    fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
457      u8::deserialize(de).map(Self::from_bits_retain)
458    }
459  }
460}
461
462#[cfg(feature = "arbitrary")]
463#[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))]
464mod arbitrary_impls {
465  //! Uniform over `u8`, decoded with `from_bits_retain`.
466  //!
467  //! Again the opposite of `channel`'s roster draw, and again because a
468  //! bit set has no fallback variant to collapse into: every one of the
469  //! 256 patterns is a distinct value, each named bit is set in half of
470  //! them, and the unnamed bits — the ones a real FFmpeg packet does
471  //! carry — appear at the same rate. Choosing from a roster of the
472  //! three named flags would generate exactly the inputs that cannot go
473  //! wrong.
474
475  use arbitrary::{Arbitrary, Result, Unstructured};
476
477  use super::PacketFlags;
478
479  impl<'a> Arbitrary<'a> for PacketFlags {
480    fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
481      Ok(Self::from_bits_retain(u8::arbitrary(u)?))
482    }
483  }
484}
485
486#[cfg(feature = "quickcheck")]
487#[cfg_attr(docsrs, doc(cfg(feature = "quickcheck")))]
488mod quickcheck_impls {
489  //! The `quickcheck` half of what `arbitrary_impls` gives, drawn the
490  //! same way and for the same reason.
491
492  use quickcheck::{Arbitrary, Gen};
493
494  use super::PacketFlags;
495
496  impl Arbitrary for PacketFlags {
497    fn arbitrary(g: &mut Gen) -> Self {
498      Self::from_bits_retain(u8::arbitrary(g))
499    }
500  }
501}
502
503#[cfg(test)]
504mod tests {
505  use super::*;
506
507  // `format!` needs an allocator, and this module carries no
508  // file-level `extern crate alloc;` (production `packet.rs` is
509  // core-only — its buffer type `B` is caller-supplied, never an
510  // owned `Vec` this crate allocates). Scoped to the test module that
511  // actually needs it; the enclosing `#[cfg(test)]` mod is not itself
512  // feature-gated, so this import (and each of its three call sites,
513  // below) carries its own gate — mirrors `serde_tests`' own
514  // `extern crate alloc;` / `use alloc::string::ToString;` a little
515  // further down, which needs the same bridge for the same reason.
516  #[cfg(any(feature = "std", feature = "alloc"))]
517  extern crate alloc;
518  #[cfg(any(feature = "std", feature = "alloc"))]
519  use alloc::format;
520
521  #[test]
522  fn flag_bits_are_stable() {
523    assert_eq!(PacketFlags::KEY.bits(), 0b001);
524    assert_eq!(PacketFlags::CORRUPT.bits(), 0b010);
525    assert_eq!(PacketFlags::DISCARD.bits(), 0b100);
526  }
527
528  #[test]
529  fn flags_combine() {
530    let f = PacketFlags::KEY | PacketFlags::CORRUPT;
531    assert!(f.contains(PacketFlags::KEY));
532    assert!(f.contains(PacketFlags::CORRUPT));
533    assert!(!f.contains(PacketFlags::DISCARD));
534  }
535
536  #[test]
537  fn empty_default() {
538    assert_eq!(PacketFlags::default(), PacketFlags::empty());
539  }
540
541  use crate::Timebase;
542  use core::num::NonZeroI32;
543
544  fn ms_tb() -> Timebase {
545    Timebase::new(1, NonZeroI32::new(1000).unwrap())
546  }
547
548  #[test]
549  fn video_packet_construct_and_access() {
550    let data: &[u8] = &[1, 2, 3];
551    let p: VideoPacket<_, &[u8]> = VideoPacket::new(data, ());
552    assert_eq!(p.pts(), None);
553    assert_eq!(p.flags(), PacketFlags::empty());
554    assert_eq!(*p.data(), data);
555  }
556
557  #[test]
558  fn video_packet_builders_chain() {
559    let pts = crate::Timestamp::new(1500, ms_tb());
560    let p: VideoPacket<_, &[u8]> = VideoPacket::new(&[][..], ())
561      .with_pts(Some(pts))
562      .with_flags(PacketFlags::KEY);
563    assert_eq!(p.pts(), Some(pts));
564    assert!(p.flags().contains(PacketFlags::KEY));
565  }
566
567  #[test]
568  fn video_packet_into_parts() {
569    let p: VideoPacket<_, &[u8]> = VideoPacket::new(&[1u8, 2][..], ());
570    let (data, _extra) = p.into_parts();
571    assert_eq!(data, &[1, 2]);
572  }
573
574  // `format!` needs an allocator; see the import note above.
575  #[cfg(any(feature = "std", feature = "alloc"))]
576  #[test]
577  fn video_packet_clone_matches_the_original() {
578    let pts = crate::Timestamp::new(1500, ms_tb());
579    let original: VideoPacket<_, &[u8]> = VideoPacket::new(&[1u8, 2, 3][..], ())
580      .with_pts(Some(pts))
581      .with_dts(Some(pts))
582      .with_flags(PacketFlags::KEY);
583    let cloned = original.clone();
584    assert_eq!(cloned.pts(), original.pts());
585    assert_eq!(cloned.dts(), original.dts());
586    assert_eq!(cloned.duration(), original.duration());
587    assert_eq!(cloned.flags(), original.flags());
588    assert_eq!(cloned.data(), original.data());
589    assert!(format!("{cloned:?}").contains("VideoPacket"));
590  }
591
592  #[test]
593  fn audio_packet_round_trip() {
594    let data: &[u8] = &[7, 8, 9];
595    let p: AudioPacket<_, &[u8]> = AudioPacket::new(data, ()).with_flags(PacketFlags::KEY);
596    assert_eq!(*p.data(), data);
597    assert!(p.flags().contains(PacketFlags::KEY));
598    let (recovered, _) = p.into_parts();
599    assert_eq!(recovered, data);
600  }
601
602  // `format!` needs an allocator; see the import note above.
603  #[cfg(any(feature = "std", feature = "alloc"))]
604  #[test]
605  fn audio_packet_clone_matches_the_original() {
606    let data: &[u8] = &[7, 8, 9];
607    let original: AudioPacket<_, &[u8]> = AudioPacket::new(data, ()).with_flags(PacketFlags::KEY);
608    let cloned = original.clone();
609    assert_eq!(cloned.flags(), original.flags());
610    assert_eq!(cloned.data(), original.data());
611    assert!(format!("{cloned:?}").contains("AudioPacket"));
612  }
613
614  #[test]
615  fn subtitle_packet_round_trip() {
616    let data: &[u8] = b"hi";
617    let p: SubtitlePacket<_, &[u8]> = SubtitlePacket::new(data, ());
618    assert_eq!(*p.data(), data);
619  }
620
621  // `format!` needs an allocator; see the import note above.
622  #[cfg(any(feature = "std", feature = "alloc"))]
623  #[test]
624  fn subtitle_packet_clone_matches_the_original() {
625    let data: &[u8] = b"hi";
626    let original: SubtitlePacket<_, &[u8]> = SubtitlePacket::new(data, ());
627    let cloned = original.clone();
628    assert_eq!(cloned.data(), original.data());
629    assert!(format!("{cloned:?}").contains("SubtitlePacket"));
630  }
631
632  // -------------------------------------------------------------------
633  //  Optional matrices (`serde` / `arbitrary` / `quickcheck`)
634  // -------------------------------------------------------------------
635
636  // The wire assertions need a real self-describing format, which needs
637  // an allocator; the impls themselves compile at every tier.
638  #[cfg(all(feature = "serde", any(feature = "alloc", feature = "std")))]
639  mod serde_tests {
640    // The crate root links `alloc` under the name `std` (the workspace's
641    // alloc-as-std alias), so `alloc` has to be named here to reach
642    // `ToString` — which the std prelude would otherwise have supplied.
643    // Both this and the `use` are valid under `std` too, hence no `cfg`:
644    // the enclosing module is already gated on `alloc` or `std`.
645    extern crate alloc;
646
647    use alloc::string::ToString;
648
649    use super::*;
650
651    #[test]
652    fn the_wire_is_a_number_not_a_flag_grammar() {
653      // The ruling this pins: a bit set reaches the wire as its bits.
654      // `bitflags`' own serde would have written `"KEY | CORRUPT"` here
655      // for a human-readable format, which is why that sub-feature is
656      // not the mechanism.
657      let flags = PacketFlags::KEY | PacketFlags::CORRUPT;
658      assert_eq!(
659        serde_json::to_string(&flags).expect("flags always serialize"),
660        "3"
661      );
662      assert_eq!(
663        serde_json::to_string(&PacketFlags::empty()).expect("flags always serialize"),
664        "0"
665      );
666    }
667
668    #[test]
669    fn a_name_is_not_a_number() {
670      assert!(serde_json::from_str::<PacketFlags>(r#""KEY""#).is_err());
671      assert!(serde_json::from_str::<PacketFlags>(r#""key|corrupt""#).is_err());
672    }
673
674    #[test]
675    fn every_bit_pattern_round_trips_including_the_unnamed_ones() {
676      // 0b0000_1000 and 0b0001_0000 are FFmpeg's TRUSTED / DISPOSABLE,
677      // which this set does not name. They still have to survive, which
678      // is what `from_bits_retain` buys and what `from_bits` would lose.
679      for bits in 0..=u8::MAX {
680        let flags = PacketFlags::from_bits_retain(bits);
681        let json = serde_json::to_string(&flags).expect("flags always serialize");
682        assert_eq!(json, bits.to_string());
683        assert_eq!(
684          serde_json::from_str::<PacketFlags>(&json).expect("its own output parses"),
685          flags,
686          "round-trip failed for {bits:#010b}"
687        );
688      }
689    }
690
691    #[test]
692    fn a_value_no_u8_can_hold_is_refused() {
693      assert!(serde_json::from_str::<PacketFlags>("256").is_err());
694      assert!(serde_json::from_str::<PacketFlags>("-1").is_err());
695    }
696  }
697
698  // -------------------------------------------------------------------
699  //  `serde` feature forwarding pin (`mediaframe/serde`)
700  // -------------------------------------------------------------------
701
702  // `PixelFormat` and `BayerPattern` carry no `Serialize` / `Deserialize`
703  // impl in this crate — they are mediaframe vocabulary types the facade
704  // only re-exports. They compile here solely because mediadecode's
705  // `serde` feature also turns on `mediaframe/serde`, which is where
706  // mediaframe writes those impls. If that forwarding entry in
707  // `Cargo.toml` is ever dropped, this module stops compiling under
708  // `--features serde` instead of silently losing coverage.
709  #[cfg(all(feature = "serde", any(feature = "alloc", feature = "std")))]
710  mod mediaframe_serde_forwarding_tests {
711    #[test]
712    fn pixel_format_round_trips_through_json() {
713      let value = crate::PixelFormat::Yuv420p;
714      let json = serde_json::to_string(&value).expect("mediaframe/serde forwards PixelFormat");
715      assert_eq!(json, "\"yuv420p\"");
716      assert_eq!(
717        serde_json::from_str::<crate::PixelFormat>(&json).expect("its own output parses"),
718        value
719      );
720    }
721
722    #[test]
723    fn bayer_pattern_round_trips_through_json() {
724      let value = crate::cfa::BayerPattern::Rggb;
725      let json = serde_json::to_string(&value).expect("mediaframe/serde forwards BayerPattern");
726      assert_eq!(json, "\"rggb\"");
727      assert_eq!(
728        serde_json::from_str::<crate::cfa::BayerPattern>(&json).expect("its own output parses"),
729        value
730      );
731    }
732  }
733
734  #[cfg(feature = "arbitrary")]
735  mod arbitrary_tests {
736    use arbitrary::{Arbitrary, Unstructured};
737
738    use super::*;
739
740    #[test]
741    fn every_bit_pattern_is_reachable() {
742      let mut seen = [false; 256];
743      for byte in 0..=u8::MAX {
744        let data = [byte];
745        let mut u = Unstructured::new(&data);
746        let flags = PacketFlags::arbitrary(&mut u).expect("the generator is total");
747        seen[flags.bits() as usize] = true;
748      }
749      assert!(
750        seen.iter().all(|&s| s),
751        "a bit pattern the generator never produces"
752      );
753    }
754  }
755
756  #[cfg(feature = "quickcheck")]
757  mod quickcheck_tests {
758    use quickcheck::{Arbitrary, Gen};
759
760    use super::*;
761
762    #[test]
763    fn the_named_flags_and_the_unnamed_bits_are_both_reachable() {
764      // 4000 draws over 256 patterns: missing a named flag here means
765      // the generator is skewed, not unlucky.
766      let mut g = Gen::new(16);
767      let mut union = PacketFlags::empty();
768      let mut saw_unnamed = false;
769      for _ in 0..4000 {
770        let flags = PacketFlags::arbitrary(&mut g);
771        union |= flags;
772        saw_unnamed |= !PacketFlags::all().contains(flags);
773      }
774      assert_eq!(
775        union,
776        PacketFlags::from_bits_retain(u8::MAX),
777        "a bit the generator never sets"
778      );
779      assert!(saw_unnamed, "an unnamed bit is never produced");
780    }
781  }
782}