Skip to main content

mediadecode_ffmpeg/
extras.rs

1//! Backend-specific `*Extra` carriers used as the
2//! `mediadecode::*Adapter::*Extra` associated types.
3//!
4//! Fields are private; values are read through getters and set through
5//! `with_*` (consuming builders) / `set_*` (in-place mutators) — the
6//! crate-wide encapsulation convention. `const fn` is used wherever
7//! the field type permits (i.e. anything but `Vec`).
8
9use std::vec::Vec;
10
11use derive_more::IsVariant;
12use ffmpeg_next::{codec::Parameters, ffi::avcodec_parameters_copy};
13
14use crate::demuxer::{DemuxError, ParametersAlloc, ParametersCopy, ParametersMissing};
15
16/// Per-`VideoPacket` extras.
17#[derive(Clone, Debug, Default)]
18pub struct VideoPacketExtra {
19  stream_index: i32,
20  byte_pos: Option<i64>,
21  side_data: Vec<SideDataEntry>,
22}
23
24impl VideoPacketExtra {
25  /// Constructs a `VideoPacketExtra` with the given stream index.
26  /// `byte_pos` defaults to `None` and `side_data` to empty.
27  #[cfg_attr(not(tarpaulin), inline(always))]
28  pub const fn new(stream_index: i32) -> Self {
29    Self {
30      stream_index,
31      byte_pos: None,
32      side_data: Vec::new(),
33    }
34  }
35
36  /// Returns the source `AVStream.index`.
37  #[cfg_attr(not(tarpaulin), inline(always))]
38  pub const fn stream_index(&self) -> i32 {
39    self.stream_index
40  }
41
42  /// Returns the byte position of the packet in the input file, or
43  /// `None` if unknown.
44  #[cfg_attr(not(tarpaulin), inline(always))]
45  pub const fn byte_pos(&self) -> Option<i64> {
46    self.byte_pos
47  }
48
49  /// Returns the raw side-data entries from `AVPacket.side_data`.
50  #[cfg_attr(not(tarpaulin), inline(always))]
51  pub fn side_data(&self) -> &[SideDataEntry] {
52    self.side_data.as_slice()
53  }
54
55  /// Sets the stream index (consuming builder).
56  #[cfg_attr(not(tarpaulin), inline(always))]
57  #[must_use]
58  pub const fn with_stream_index(mut self, value: i32) -> Self {
59    self.stream_index = value;
60    self
61  }
62  /// Sets the byte position (consuming builder).
63  #[cfg_attr(not(tarpaulin), inline(always))]
64  #[must_use]
65  pub const fn with_byte_pos(mut self, value: Option<i64>) -> Self {
66    self.byte_pos = value;
67    self
68  }
69  /// Sets the side-data list (consuming builder).
70  #[cfg_attr(not(tarpaulin), inline(always))]
71  #[must_use]
72  pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
73    self.side_data = value;
74    self
75  }
76
77  /// Sets the stream index in place.
78  #[cfg_attr(not(tarpaulin), inline(always))]
79  pub const fn set_stream_index(&mut self, value: i32) -> &mut Self {
80    self.stream_index = value;
81    self
82  }
83  /// Sets the byte position in place.
84  #[cfg_attr(not(tarpaulin), inline(always))]
85  pub const fn set_byte_pos(&mut self, value: Option<i64>) -> &mut Self {
86    self.byte_pos = value;
87    self
88  }
89  /// Sets the side-data list in place.
90  #[cfg_attr(not(tarpaulin), inline(always))]
91  pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
92    self.side_data = value;
93    self
94  }
95}
96
97/// Per-`VideoFrame` extras carrying everything the unified
98/// `mediadecode::ColorInfo` doesn't already cover.
99#[derive(Clone, Debug, Default)]
100pub struct VideoFrameExtra {
101  sample_aspect_ratio: Option<(u32, u32)>,
102  picture_type: PictureType,
103  key_frame: bool,
104  interlaced: bool,
105  top_field_first: bool,
106  best_effort_timestamp: Option<i64>,
107  mastering_display: Option<MasteringDisplay>,
108  content_light_level: Option<ContentLightLevel>,
109  smpte_timecode: Vec<u32>,
110  side_data: Vec<SideDataEntry>,
111}
112
113impl VideoFrameExtra {
114  /// Constructs an empty `VideoFrameExtra` (all fields at default).
115  #[cfg_attr(not(tarpaulin), inline(always))]
116  pub const fn new() -> Self {
117    Self {
118      sample_aspect_ratio: None,
119      picture_type: PictureType::Unspecified,
120      key_frame: false,
121      interlaced: false,
122      top_field_first: false,
123      best_effort_timestamp: None,
124      mastering_display: None,
125      content_light_level: None,
126      smpte_timecode: Vec::new(),
127      side_data: Vec::new(),
128    }
129  }
130
131  /// Sample aspect ratio (par numerator / denominator), `None` if 1:1
132  /// or unspecified.
133  #[cfg_attr(not(tarpaulin), inline(always))]
134  pub const fn sample_aspect_ratio(&self) -> Option<(u32, u32)> {
135    self.sample_aspect_ratio
136  }
137  /// Frame picture type (I/P/B/etc.).
138  #[cfg_attr(not(tarpaulin), inline(always))]
139  pub const fn picture_type(&self) -> PictureType {
140    self.picture_type
141  }
142  /// `True` if this frame is a key frame.
143  #[cfg_attr(not(tarpaulin), inline(always))]
144  pub const fn key_frame(&self) -> bool {
145    self.key_frame
146  }
147  /// `True` if the frame is interlaced.
148  #[cfg_attr(not(tarpaulin), inline(always))]
149  pub const fn interlaced(&self) -> bool {
150    self.interlaced
151  }
152  /// `True` if the top field is first (only meaningful with `interlaced`).
153  #[cfg_attr(not(tarpaulin), inline(always))]
154  pub const fn top_field_first(&self) -> bool {
155    self.top_field_first
156  }
157  /// FFmpeg's heuristic best-effort PTS, or `None` if unknown.
158  #[cfg_attr(not(tarpaulin), inline(always))]
159  pub const fn best_effort_timestamp(&self) -> Option<i64> {
160    self.best_effort_timestamp
161  }
162  /// HDR10 mastering-display metadata, if present on the source frame.
163  #[cfg_attr(not(tarpaulin), inline(always))]
164  pub const fn mastering_display(&self) -> Option<MasteringDisplay> {
165    self.mastering_display
166  }
167  /// HDR10 content-light-level.
168  #[cfg_attr(not(tarpaulin), inline(always))]
169  pub const fn content_light_level(&self) -> Option<ContentLightLevel> {
170    self.content_light_level
171  }
172  /// SMPTE ST 12-M timecode entries (raw 32-bit BCD-packed values).
173  #[cfg_attr(not(tarpaulin), inline(always))]
174  pub fn smpte_timecode(&self) -> &[u32] {
175    self.smpte_timecode.as_slice()
176  }
177  /// Raw side-data entries from `AVFrame.side_data`.
178  #[cfg_attr(not(tarpaulin), inline(always))]
179  pub fn side_data(&self) -> &[SideDataEntry] {
180    self.side_data.as_slice()
181  }
182
183  /// Sets the sample aspect ratio (consuming builder).
184  #[cfg_attr(not(tarpaulin), inline(always))]
185  pub const fn with_sample_aspect_ratio(mut self, value: Option<(u32, u32)>) -> Self {
186    self.sample_aspect_ratio = value;
187    self
188  }
189  /// Sets the picture type (consuming builder).
190  #[cfg_attr(not(tarpaulin), inline(always))]
191  #[must_use]
192  pub const fn with_picture_type(mut self, value: PictureType) -> Self {
193    self.picture_type = value;
194    self
195  }
196  /// Sets the key-frame flag (consuming builder).
197  #[cfg_attr(not(tarpaulin), inline(always))]
198  #[must_use]
199  pub const fn with_key_frame(mut self, value: bool) -> Self {
200    self.key_frame = value;
201    self
202  }
203  /// Sets the interlaced flag (consuming builder).
204  #[cfg_attr(not(tarpaulin), inline(always))]
205  #[must_use]
206  pub const fn with_interlaced(mut self, value: bool) -> Self {
207    self.interlaced = value;
208    self
209  }
210  /// Sets the top-field-first flag (consuming builder).
211  #[cfg_attr(not(tarpaulin), inline(always))]
212  #[must_use]
213  pub const fn with_top_field_first(mut self, value: bool) -> Self {
214    self.top_field_first = value;
215    self
216  }
217  /// Sets the best-effort timestamp (consuming builder).
218  #[cfg_attr(not(tarpaulin), inline(always))]
219  #[must_use]
220  pub const fn with_best_effort_timestamp(mut self, value: Option<i64>) -> Self {
221    self.best_effort_timestamp = value;
222    self
223  }
224  /// Sets the mastering-display metadata (consuming builder).
225  #[cfg_attr(not(tarpaulin), inline(always))]
226  #[must_use]
227  pub const fn with_mastering_display(mut self, value: Option<MasteringDisplay>) -> Self {
228    self.mastering_display = value;
229    self
230  }
231  /// Sets the content-light-level metadata (consuming builder).
232  #[cfg_attr(not(tarpaulin), inline(always))]
233  #[must_use]
234  pub const fn with_content_light_level(mut self, value: Option<ContentLightLevel>) -> Self {
235    self.content_light_level = value;
236    self
237  }
238  /// Sets the SMPTE timecode list (consuming builder).
239  #[cfg_attr(not(tarpaulin), inline(always))]
240  #[must_use]
241  pub fn with_smpte_timecode(mut self, value: Vec<u32>) -> Self {
242    self.smpte_timecode = value;
243    self
244  }
245  /// Sets the side-data list (consuming builder).
246  #[cfg_attr(not(tarpaulin), inline(always))]
247  #[must_use]
248  pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
249    self.side_data = value;
250    self
251  }
252
253  /// Sets the sample aspect ratio in place.
254  #[cfg_attr(not(tarpaulin), inline(always))]
255  pub const fn set_sample_aspect_ratio(&mut self, value: Option<(u32, u32)>) -> &mut Self {
256    self.sample_aspect_ratio = value;
257    self
258  }
259  /// Sets the picture type in place.
260  #[cfg_attr(not(tarpaulin), inline(always))]
261  pub const fn set_picture_type(&mut self, value: PictureType) -> &mut Self {
262    self.picture_type = value;
263    self
264  }
265  /// Sets the key-frame flag in place.
266  #[cfg_attr(not(tarpaulin), inline(always))]
267  pub const fn set_key_frame(&mut self, value: bool) -> &mut Self {
268    self.key_frame = value;
269    self
270  }
271  /// Sets the interlaced flag in place.
272  #[cfg_attr(not(tarpaulin), inline(always))]
273  pub const fn set_interlaced(&mut self, value: bool) -> &mut Self {
274    self.interlaced = value;
275    self
276  }
277  /// Sets the top-field-first flag in place.
278  #[cfg_attr(not(tarpaulin), inline(always))]
279  pub const fn set_top_field_first(&mut self, value: bool) -> &mut Self {
280    self.top_field_first = value;
281    self
282  }
283  /// Sets the best-effort timestamp in place.
284  #[cfg_attr(not(tarpaulin), inline(always))]
285  pub const fn set_best_effort_timestamp(&mut self, value: Option<i64>) -> &mut Self {
286    self.best_effort_timestamp = value;
287    self
288  }
289  /// Sets the mastering-display metadata in place.
290  #[cfg_attr(not(tarpaulin), inline(always))]
291  pub const fn set_mastering_display(&mut self, value: Option<MasteringDisplay>) -> &mut Self {
292    self.mastering_display = value;
293    self
294  }
295  /// Sets the content-light-level metadata in place.
296  #[cfg_attr(not(tarpaulin), inline(always))]
297  pub const fn set_content_light_level(&mut self, value: Option<ContentLightLevel>) -> &mut Self {
298    self.content_light_level = value;
299    self
300  }
301  /// Sets the SMPTE timecode list in place.
302  #[cfg_attr(not(tarpaulin), inline(always))]
303  pub fn set_smpte_timecode(&mut self, value: Vec<u32>) -> &mut Self {
304    self.smpte_timecode = value;
305    self
306  }
307  /// Sets the side-data list in place.
308  #[cfg_attr(not(tarpaulin), inline(always))]
309  pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
310    self.side_data = value;
311    self
312  }
313}
314
315/// Per-`AudioPacket` extras.
316#[derive(Clone, Debug, Default)]
317pub struct AudioPacketExtra {
318  stream_index: i32,
319  byte_pos: Option<i64>,
320  side_data: Vec<SideDataEntry>,
321}
322
323impl AudioPacketExtra {
324  /// Constructs an `AudioPacketExtra` with the given stream index.
325  #[cfg_attr(not(tarpaulin), inline(always))]
326  pub const fn new(stream_index: i32) -> Self {
327    Self {
328      stream_index,
329      byte_pos: None,
330      side_data: Vec::new(),
331    }
332  }
333
334  /// Returns the source `AVStream.index`.
335  #[cfg_attr(not(tarpaulin), inline(always))]
336  pub const fn stream_index(&self) -> i32 {
337    self.stream_index
338  }
339  /// Returns the byte position, or `None` if unknown.
340  #[cfg_attr(not(tarpaulin), inline(always))]
341  pub const fn byte_pos(&self) -> Option<i64> {
342    self.byte_pos
343  }
344  /// Returns the raw side-data entries.
345  #[cfg_attr(not(tarpaulin), inline(always))]
346  pub fn side_data(&self) -> &[SideDataEntry] {
347    self.side_data.as_slice()
348  }
349
350  /// Sets the stream index (consuming builder).
351  #[cfg_attr(not(tarpaulin), inline(always))]
352  #[must_use]
353  pub const fn with_stream_index(mut self, value: i32) -> Self {
354    self.stream_index = value;
355    self
356  }
357  /// Sets the byte position (consuming builder).
358  #[cfg_attr(not(tarpaulin), inline(always))]
359  #[must_use]
360  pub const fn with_byte_pos(mut self, value: Option<i64>) -> Self {
361    self.byte_pos = value;
362    self
363  }
364  /// Sets the side-data list (consuming builder).
365  #[cfg_attr(not(tarpaulin), inline(always))]
366  #[must_use]
367  pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
368    self.side_data = value;
369    self
370  }
371
372  /// Sets the stream index in place.
373  #[cfg_attr(not(tarpaulin), inline(always))]
374  pub const fn set_stream_index(&mut self, value: i32) -> &mut Self {
375    self.stream_index = value;
376    self
377  }
378  /// Sets the byte position in place.
379  #[cfg_attr(not(tarpaulin), inline(always))]
380  pub const fn set_byte_pos(&mut self, value: Option<i64>) -> &mut Self {
381    self.byte_pos = value;
382    self
383  }
384  /// Sets the side-data list in place.
385  #[cfg_attr(not(tarpaulin), inline(always))]
386  pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
387    self.side_data = value;
388    self
389  }
390}
391
392/// Per-`AudioFrame` extras.
393#[derive(Clone, Debug, Default)]
394pub struct AudioFrameExtra {
395  best_effort_timestamp: Option<i64>,
396  side_data: Vec<SideDataEntry>,
397}
398
399impl AudioFrameExtra {
400  /// Constructs an empty `AudioFrameExtra`.
401  #[cfg_attr(not(tarpaulin), inline(always))]
402  pub const fn new() -> Self {
403    Self {
404      best_effort_timestamp: None,
405      side_data: Vec::new(),
406    }
407  }
408
409  /// FFmpeg's heuristic best-effort PTS, or `None` if unknown.
410  #[cfg_attr(not(tarpaulin), inline(always))]
411  pub const fn best_effort_timestamp(&self) -> Option<i64> {
412    self.best_effort_timestamp
413  }
414  /// Returns the raw side-data entries.
415  #[cfg_attr(not(tarpaulin), inline(always))]
416  pub fn side_data(&self) -> &[SideDataEntry] {
417    self.side_data.as_slice()
418  }
419
420  /// Sets the best-effort timestamp (consuming builder).
421  #[cfg_attr(not(tarpaulin), inline(always))]
422  #[must_use]
423  pub const fn with_best_effort_timestamp(mut self, value: Option<i64>) -> Self {
424    self.best_effort_timestamp = value;
425    self
426  }
427  /// Sets the side-data list (consuming builder).
428  #[cfg_attr(not(tarpaulin), inline(always))]
429  #[must_use]
430  pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
431    self.side_data = value;
432    self
433  }
434
435  /// Sets the best-effort timestamp in place.
436  #[cfg_attr(not(tarpaulin), inline(always))]
437  pub const fn set_best_effort_timestamp(&mut self, value: Option<i64>) -> &mut Self {
438    self.best_effort_timestamp = value;
439    self
440  }
441  /// Sets the side-data list in place.
442  #[cfg_attr(not(tarpaulin), inline(always))]
443  pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
444    self.side_data = value;
445    self
446  }
447}
448
449/// Per-`SubtitlePacket` extras.
450#[derive(Clone, Debug, Default)]
451pub struct SubtitlePacketExtra {
452  stream_index: i32,
453  language: Option<[u8; 3]>,
454  forced: bool,
455  side_data: Vec<SideDataEntry>,
456}
457
458impl SubtitlePacketExtra {
459  /// Constructs a `SubtitlePacketExtra` with the given stream index.
460  /// `side_data` defaults to empty.
461  #[cfg_attr(not(tarpaulin), inline(always))]
462  pub const fn new(stream_index: i32) -> Self {
463    Self {
464      stream_index,
465      language: None,
466      forced: false,
467      side_data: Vec::new(),
468    }
469  }
470
471  /// Returns the source `AVStream.index`.
472  #[cfg_attr(not(tarpaulin), inline(always))]
473  pub const fn stream_index(&self) -> i32 {
474    self.stream_index
475  }
476  /// Returns the ISO 639-2/T language tag, or `None` if unspecified.
477  #[cfg_attr(not(tarpaulin), inline(always))]
478  pub const fn language(&self) -> Option<[u8; 3]> {
479    self.language
480  }
481  /// Returns whether this subtitle stream is marked "forced".
482  #[cfg_attr(not(tarpaulin), inline(always))]
483  pub const fn forced(&self) -> bool {
484    self.forced
485  }
486  /// Returns the raw side-data entries from `AVPacket.side_data`.
487  ///
488  /// A subtitle packet's side data is rare but not absent — and a
489  /// packet that carries *nothing else* is exactly the case this seat
490  /// exists for: with no seat, a side-data-only packet has nowhere to
491  /// put its only content.
492  #[cfg_attr(not(tarpaulin), inline(always))]
493  pub fn side_data(&self) -> &[SideDataEntry] {
494    self.side_data.as_slice()
495  }
496
497  /// Sets the stream index (consuming builder).
498  #[cfg_attr(not(tarpaulin), inline(always))]
499  #[must_use]
500  pub const fn with_stream_index(mut self, value: i32) -> Self {
501    self.stream_index = value;
502    self
503  }
504  /// Sets the language tag (consuming builder).
505  #[cfg_attr(not(tarpaulin), inline(always))]
506  #[must_use]
507  pub const fn with_language(mut self, value: Option<[u8; 3]>) -> Self {
508    self.language = value;
509    self
510  }
511  /// Sets the side-data list (consuming builder).
512  #[cfg_attr(not(tarpaulin), inline(always))]
513  #[must_use]
514  pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
515    self.side_data = value;
516    self
517  }
518  /// Sets the side-data list in place.
519  #[cfg_attr(not(tarpaulin), inline(always))]
520  pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
521    self.side_data = value;
522    self
523  }
524  /// Sets the forced flag (consuming builder).
525  #[cfg_attr(not(tarpaulin), inline(always))]
526  #[must_use]
527  pub const fn with_forced(mut self, value: bool) -> Self {
528    self.forced = value;
529    self
530  }
531
532  /// Sets the stream index in place.
533  #[cfg_attr(not(tarpaulin), inline(always))]
534  pub const fn set_stream_index(&mut self, value: i32) -> &mut Self {
535    self.stream_index = value;
536    self
537  }
538  /// Sets the language tag in place.
539  #[cfg_attr(not(tarpaulin), inline(always))]
540  pub const fn set_language(&mut self, value: Option<[u8; 3]>) -> &mut Self {
541    self.language = value;
542    self
543  }
544  /// Sets the forced flag in place.
545  #[cfg_attr(not(tarpaulin), inline(always))]
546  pub const fn set_forced(&mut self, value: bool) -> &mut Self {
547    self.forced = value;
548    self
549  }
550}
551
552/// Per-`SubtitleFrame` extras.
553#[derive(Clone, Debug, Default)]
554pub struct SubtitleFrameExtra {
555  start_display_time: u32,
556  end_display_time: u32,
557}
558
559impl SubtitleFrameExtra {
560  /// Constructs a `SubtitleFrameExtra`.
561  #[cfg_attr(not(tarpaulin), inline(always))]
562  pub const fn new(start_display_time: u32, end_display_time: u32) -> Self {
563    Self {
564      start_display_time,
565      end_display_time,
566    }
567  }
568
569  /// `AVSubtitle.start_display_time` — milliseconds from `pts`.
570  #[cfg_attr(not(tarpaulin), inline(always))]
571  pub const fn start_display_time(&self) -> u32 {
572    self.start_display_time
573  }
574  /// `AVSubtitle.end_display_time` — milliseconds from `pts`.
575  #[cfg_attr(not(tarpaulin), inline(always))]
576  pub const fn end_display_time(&self) -> u32 {
577    self.end_display_time
578  }
579
580  /// Sets the start display time (consuming builder).
581  #[cfg_attr(not(tarpaulin), inline(always))]
582  #[must_use]
583  pub const fn with_start_display_time(mut self, value: u32) -> Self {
584    self.start_display_time = value;
585    self
586  }
587  /// Sets the end display time (consuming builder).
588  #[cfg_attr(not(tarpaulin), inline(always))]
589  #[must_use]
590  pub const fn with_end_display_time(mut self, value: u32) -> Self {
591    self.end_display_time = value;
592    self
593  }
594
595  /// Sets the start display time in place.
596  #[cfg_attr(not(tarpaulin), inline(always))]
597  pub const fn set_start_display_time(&mut self, value: u32) -> &mut Self {
598    self.start_display_time = value;
599    self
600  }
601  /// Sets the end display time in place.
602  #[cfg_attr(not(tarpaulin), inline(always))]
603  pub const fn set_end_display_time(&mut self, value: u32) -> &mut Self {
604    self.end_display_time = value;
605    self
606  }
607}
608
609/// Picture type per `AVFrame.pict_type`.
610#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash, IsVariant)]
611#[non_exhaustive]
612pub enum PictureType {
613  /// Unspecified / unset.
614  #[default]
615  Unspecified,
616  /// Intra (I-frame).
617  I,
618  /// Predicted (P-frame).
619  P,
620  /// Bi-directional predicted (B-frame).
621  B,
622  /// S(GMC)-VOP from MPEG-4.
623  S,
624  /// Switching Intra (H.264).
625  Si,
626  /// Switching Predicted (H.264).
627  Sp,
628  /// Bi-predicted intra (BI-frame).
629  Bi,
630}
631
632/// Raw side-data entry carrying the FFmpeg type id and the unparsed
633/// byte buffer. Type ids correspond to FFmpeg's
634/// `AV_FRAME_DATA_*` / `AV_PKT_DATA_*` constants — see
635/// `libavutil/frame.h` and `libavcodec/packet.h`.
636#[derive(Clone, Debug)]
637pub struct SideDataEntry {
638  kind: i32,
639  data: Vec<u8>,
640}
641
642impl SideDataEntry {
643  /// Constructs a `SideDataEntry`.
644  #[cfg_attr(not(tarpaulin), inline(always))]
645  pub const fn new(kind: i32, data: Vec<u8>) -> Self {
646    Self { kind, data }
647  }
648
649  /// FFmpeg side-data type id.
650  #[cfg_attr(not(tarpaulin), inline(always))]
651  pub const fn kind(&self) -> i32 {
652    self.kind
653  }
654  /// Side-data payload as raw bytes.
655  #[cfg_attr(not(tarpaulin), inline(always))]
656  pub fn data(&self) -> &[u8] {
657    self.data.as_slice()
658  }
659
660  /// Sets the type id (consuming builder).
661  #[cfg_attr(not(tarpaulin), inline(always))]
662  #[must_use]
663  pub const fn with_kind(mut self, value: i32) -> Self {
664    self.kind = value;
665    self
666  }
667  /// Sets the payload (consuming builder).
668  #[cfg_attr(not(tarpaulin), inline(always))]
669  #[must_use]
670  pub fn with_data(mut self, value: Vec<u8>) -> Self {
671    self.data = value;
672    self
673  }
674
675  /// Sets the type id in place.
676  #[cfg_attr(not(tarpaulin), inline(always))]
677  pub const fn set_kind(&mut self, value: i32) -> &mut Self {
678    self.kind = value;
679    self
680  }
681  /// Sets the payload in place.
682  #[cfg_attr(not(tarpaulin), inline(always))]
683  pub fn set_data(&mut self, value: Vec<u8>) -> &mut Self {
684    self.data = value;
685    self
686  }
687}
688
689/// HDR10 mastering display metadata.
690#[derive(Copy, Clone, Debug, PartialEq)]
691pub struct MasteringDisplay {
692  display_primaries: [(u32, u32); 3],
693  white_point: (u32, u32),
694  max_luminance: (u32, u32),
695  min_luminance: (u32, u32),
696}
697
698impl MasteringDisplay {
699  /// Constructs a `MasteringDisplay`.
700  #[cfg_attr(not(tarpaulin), inline(always))]
701  pub const fn new(
702    display_primaries: [(u32, u32); 3],
703    white_point: (u32, u32),
704    max_luminance: (u32, u32),
705    min_luminance: (u32, u32),
706  ) -> Self {
707    Self {
708      display_primaries,
709      white_point,
710      max_luminance,
711      min_luminance,
712    }
713  }
714
715  /// Display primary chromaticities `(x, y)` for R, G, B in CIE 1931
716  /// (each as `(num, den)` rational, with `den` non-zero).
717  #[cfg_attr(not(tarpaulin), inline(always))]
718  pub const fn display_primaries(&self) -> [(u32, u32); 3] {
719    self.display_primaries
720  }
721  /// White-point chromaticity `(x, y)` as rationals.
722  #[cfg_attr(not(tarpaulin), inline(always))]
723  pub const fn white_point(&self) -> (u32, u32) {
724    self.white_point
725  }
726  /// Maximum luminance in `0.0001 cd/m²` units (rational `(num, den)`).
727  #[cfg_attr(not(tarpaulin), inline(always))]
728  pub const fn max_luminance(&self) -> (u32, u32) {
729    self.max_luminance
730  }
731  /// Minimum luminance in `0.0001 cd/m²` units.
732  #[cfg_attr(not(tarpaulin), inline(always))]
733  pub const fn min_luminance(&self) -> (u32, u32) {
734    self.min_luminance
735  }
736
737  /// Sets the display primaries (consuming builder).
738  #[cfg_attr(not(tarpaulin), inline(always))]
739  pub const fn with_display_primaries(mut self, value: [(u32, u32); 3]) -> Self {
740    self.display_primaries = value;
741    self
742  }
743  /// Sets the white point (consuming builder).
744  #[cfg_attr(not(tarpaulin), inline(always))]
745  pub const fn with_white_point(mut self, value: (u32, u32)) -> Self {
746    self.white_point = value;
747    self
748  }
749  /// Sets the max luminance (consuming builder).
750  #[cfg_attr(not(tarpaulin), inline(always))]
751  pub const fn with_max_luminance(mut self, value: (u32, u32)) -> Self {
752    self.max_luminance = value;
753    self
754  }
755  /// Sets the min luminance (consuming builder).
756  #[cfg_attr(not(tarpaulin), inline(always))]
757  pub const fn with_min_luminance(mut self, value: (u32, u32)) -> Self {
758    self.min_luminance = value;
759    self
760  }
761
762  /// Sets the display primaries in place.
763  #[cfg_attr(not(tarpaulin), inline(always))]
764  pub const fn set_display_primaries(&mut self, value: [(u32, u32); 3]) -> &mut Self {
765    self.display_primaries = value;
766    self
767  }
768  /// Sets the white point in place.
769  #[cfg_attr(not(tarpaulin), inline(always))]
770  pub const fn set_white_point(&mut self, value: (u32, u32)) -> &mut Self {
771    self.white_point = value;
772    self
773  }
774  /// Sets the max luminance in place.
775  #[cfg_attr(not(tarpaulin), inline(always))]
776  pub const fn set_max_luminance(&mut self, value: (u32, u32)) -> &mut Self {
777    self.max_luminance = value;
778    self
779  }
780  /// Sets the min luminance in place.
781  #[cfg_attr(not(tarpaulin), inline(always))]
782  pub const fn set_min_luminance(&mut self, value: (u32, u32)) -> &mut Self {
783    self.min_luminance = value;
784    self
785  }
786}
787
788/// HDR10 content light level (`AV_FRAME_DATA_CONTENT_LIGHT_LEVEL`).
789#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)]
790pub struct ContentLightLevel {
791  max_cll: u32,
792  max_fall: u32,
793}
794
795impl ContentLightLevel {
796  /// Constructs a `ContentLightLevel`.
797  #[cfg_attr(not(tarpaulin), inline(always))]
798  pub const fn new(max_cll: u32, max_fall: u32) -> Self {
799    Self { max_cll, max_fall }
800  }
801
802  /// Maximum content light level (cd/m²).
803  #[cfg_attr(not(tarpaulin), inline(always))]
804  pub const fn max_cll(&self) -> u32 {
805    self.max_cll
806  }
807  /// Maximum frame-average light level (cd/m²).
808  #[cfg_attr(not(tarpaulin), inline(always))]
809  pub const fn max_fall(&self) -> u32 {
810    self.max_fall
811  }
812
813  /// Sets `max_cll` (consuming builder).
814  #[cfg_attr(not(tarpaulin), inline(always))]
815  #[must_use]
816  pub const fn with_max_cll(mut self, value: u32) -> Self {
817    self.max_cll = value;
818    self
819  }
820  /// Sets `max_fall` (consuming builder).
821  #[cfg_attr(not(tarpaulin), inline(always))]
822  #[must_use]
823  pub const fn with_max_fall(mut self, value: u32) -> Self {
824    self.max_fall = value;
825    self
826  }
827
828  /// Sets `max_cll` in place.
829  #[cfg_attr(not(tarpaulin), inline(always))]
830  pub const fn set_max_cll(&mut self, value: u32) -> &mut Self {
831    self.max_cll = value;
832    self
833  }
834  /// Sets `max_fall` in place.
835  #[cfg_attr(not(tarpaulin), inline(always))]
836  pub const fn set_max_fall(&mut self, value: u32) -> &mut Self {
837    self.max_fall = value;
838    self
839  }
840}
841
842// ---------------------------------------------------------------------------
843//  The demux tier's carriers.
844// ---------------------------------------------------------------------------
845
846/// Per-`DataPacket` extras — timecode, KLV, timed ID3.
847///
848/// The same three seats as [`VideoPacketExtra`]. The side-data list was
849/// left off at first — data demuxers carry their whole payload in the
850/// packet body — and then earned its place: a packet with no body and
851/// only side data is a real packet, and without this seat its only
852/// content would have nowhere to go.
853#[derive(Clone, Debug, Default)]
854pub struct DataPacketExtra {
855  stream_index: i32,
856  byte_pos: Option<i64>,
857  side_data: Vec<SideDataEntry>,
858}
859
860impl DataPacketExtra {
861  /// Constructs a `DataPacketExtra` with the given stream index.
862  /// `byte_pos` defaults to `None` and `side_data` to empty.
863  #[cfg_attr(not(tarpaulin), inline(always))]
864  pub const fn new(stream_index: i32) -> Self {
865    Self {
866      stream_index,
867      byte_pos: None,
868      side_data: Vec::new(),
869    }
870  }
871
872  /// Returns the source `AVStream.index`.
873  #[cfg_attr(not(tarpaulin), inline(always))]
874  pub const fn stream_index(&self) -> i32 {
875    self.stream_index
876  }
877  /// Returns the byte position of the packet in the input file, or
878  /// `None` if unknown.
879  #[cfg_attr(not(tarpaulin), inline(always))]
880  pub const fn byte_pos(&self) -> Option<i64> {
881    self.byte_pos
882  }
883  /// Returns the raw side-data entries from `AVPacket.side_data`.
884  #[cfg_attr(not(tarpaulin), inline(always))]
885  pub fn side_data(&self) -> &[SideDataEntry] {
886    self.side_data.as_slice()
887  }
888
889  /// Sets the stream index (consuming builder).
890  #[cfg_attr(not(tarpaulin), inline(always))]
891  #[must_use]
892  pub const fn with_stream_index(mut self, value: i32) -> Self {
893    self.stream_index = value;
894    self
895  }
896  /// Sets the byte position (consuming builder).
897  #[cfg_attr(not(tarpaulin), inline(always))]
898  #[must_use]
899  pub const fn with_byte_pos(mut self, value: Option<i64>) -> Self {
900    self.byte_pos = value;
901    self
902  }
903  /// Sets the side-data list (consuming builder).
904  #[cfg_attr(not(tarpaulin), inline(always))]
905  #[must_use]
906  pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
907    self.side_data = value;
908    self
909  }
910
911  /// Sets the stream index in place.
912  #[cfg_attr(not(tarpaulin), inline(always))]
913  pub const fn set_stream_index(&mut self, value: i32) -> &mut Self {
914    self.stream_index = value;
915    self
916  }
917  /// Sets the byte position in place.
918  #[cfg_attr(not(tarpaulin), inline(always))]
919  pub const fn set_byte_pos(&mut self, value: Option<i64>) -> &mut Self {
920    self.byte_pos = value;
921    self
922  }
923  /// Sets the side-data list in place.
924  #[cfg_attr(not(tarpaulin), inline(always))]
925  pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
926    self.side_data = value;
927    self
928  }
929}
930
931/// Per-`AttachmentPacket` extras — fonts, cover art.
932///
933/// `synthesized` records where the payload came from, which is not a
934/// detail: an attachment track's single packet is either a real packet
935/// the container stores (cover art, which libavformat parks in
936/// `AVStream.attached_pic`) or one this crate builds out of the
937/// track's codec extradata (fonts, whose bytes never appear in the
938/// packet stream at all). A consumer chasing a payload that looks
939/// wrong needs to know which.
940#[derive(Clone, Debug, Default)]
941pub struct AttachmentPacketExtra {
942  stream_index: i32,
943  synthesized: bool,
944}
945
946impl AttachmentPacketExtra {
947  /// Constructs an `AttachmentPacketExtra` with the given stream index.
948  /// `synthesized` defaults to `false`.
949  #[cfg_attr(not(tarpaulin), inline(always))]
950  pub const fn new(stream_index: i32) -> Self {
951    Self {
952      stream_index,
953      synthesized: false,
954    }
955  }
956
957  /// Returns the source `AVStream.index`.
958  #[cfg_attr(not(tarpaulin), inline(always))]
959  pub const fn stream_index(&self) -> i32 {
960    self.stream_index
961  }
962  /// `true` when the payload was built from the track's codec
963  /// extradata rather than taken from a packet the container stores.
964  #[cfg_attr(not(tarpaulin), inline(always))]
965  pub const fn synthesized(&self) -> bool {
966    self.synthesized
967  }
968
969  /// Sets the stream index (consuming builder).
970  #[cfg_attr(not(tarpaulin), inline(always))]
971  #[must_use]
972  pub const fn with_stream_index(mut self, value: i32) -> Self {
973    self.stream_index = value;
974    self
975  }
976  /// Sets the synthesized flag (consuming builder).
977  #[cfg_attr(not(tarpaulin), inline(always))]
978  #[must_use]
979  pub const fn with_synthesized(mut self, value: bool) -> Self {
980    self.synthesized = value;
981    self
982  }
983
984  /// Sets the stream index in place.
985  #[cfg_attr(not(tarpaulin), inline(always))]
986  pub const fn set_stream_index(&mut self, value: i32) -> &mut Self {
987    self.stream_index = value;
988    self
989  }
990  /// Sets the synthesized flag in place.
991  #[cfg_attr(not(tarpaulin), inline(always))]
992  pub const fn set_synthesized(&mut self, value: bool) -> &mut Self {
993    self.synthesized = value;
994    self
995  }
996}
997
998/// A deep copy of codec parameters, with both fallible steps checked.
999///
1000/// `ffmpeg_next`'s `Clone` for `Parameters` checks neither.
1001/// `Parameters::new` does not test `avcodec_parameters_alloc` for null
1002/// and the copy dereferences the result immediately — measured under a
1003/// capped allocator, that is a SIGSEGV — while
1004/// `avcodec_parameters_copy`'s return value is discarded, so a copy
1005/// that failed part way yields parameters that look complete and open a
1006/// decoder wrong.
1007///
1008/// A partial copy leaves with `out`'s own destructor:
1009/// `avcodec_parameters_copy` resets the destination before it starts,
1010/// so whatever it managed to allocate belongs to `out`.
1011pub(crate) fn clone_parameters(
1012  source: &Parameters,
1013  stream_index: usize,
1014) -> Result<Parameters, DemuxError> {
1015  // The *source* first. `Parameters::new()` and `Parameters::default()`
1016  // hand back a value whose pointer is null when
1017  // `avcodec_parameters_alloc` failed — safe code, no error, no way to
1018  // tell — and `avcodec_parameters_copy` dereferences its source. So a
1019  // copier that checks only what it allocates still crashes, one
1020  // allocator recovery later, on a `Parameters` that never allocated.
1021  // SAFETY: reading the pointer without dereferencing it.
1022  if unsafe { source.as_ptr() }.is_null() {
1023    return Err(DemuxError::ParametersMissing(ParametersMissing::new(
1024      stream_index,
1025    )));
1026  }
1027  let mut out = Parameters::new();
1028  // SAFETY: reading the pointer the constructor stored without
1029  // dereferencing it — which is exactly what the check is for.
1030  if unsafe { out.as_ptr() }.is_null() {
1031    return Err(DemuxError::ParametersAlloc(ParametersAlloc::new(
1032      stream_index,
1033    )));
1034  }
1035  // SAFETY: both pointers are live `AVCodecParameters` — the
1036  // destination freshly allocated and non-null, the source owned by its
1037  // holder for the duration of this call.
1038  let rc = unsafe { avcodec_parameters_copy(out.as_mut_ptr(), source.as_ptr()) };
1039  if rc < 0 {
1040    return Err(DemuxError::ParametersCopy(ParametersCopy::new(
1041      stream_index,
1042      ffmpeg_next::Error::from(rc),
1043    )));
1044  }
1045  Ok(out)
1046}
1047
1048/// Per-`TrackInfo` extras — the FFmpeg side of one track-table row.
1049///
1050/// Carries the stream's [`Parameters`], which is what opens a decoder
1051/// for the track — through [`Self::clone_parameters`], which is a deep
1052/// `avcodec_parameters_copy` with no tie back to the format context, so
1053/// a decoder outlives the demuxer that named it.
1054///
1055/// **No `Clone`, and no `Default`.** Both would have to go through
1056/// `ffmpeg_next`'s `Clone` / `Default` for [`Parameters`], which check
1057/// neither the allocation nor the copy: safe public code could
1058/// dereference a null destination or receive parameters that are
1059/// quietly incomplete. `Clone` cannot report either, so this type does
1060/// not implement it; [`Self::try_clone`] is the same copy with the
1061/// answer a caller can act on, and [`Self::clone_parameters`] is the
1062/// handoff a decoder actually needs. This crate shipped a derived
1063/// `Clone` over the unchecked path once, reachable from safe code
1064/// that just copied a track row, and closed it by removing the
1065/// derive (see
1066/// `demuxer::tests::the_public_track_extra_copies_are_checked_too`).
1067///
1068/// The message-carrier law is the second, independent reason `Clone`
1069/// stays off: messages may be `Clone`, but `Clone` is always a
1070/// refcount bump, never a deep copy, and `avcodec_parameters_copy` is
1071/// not that. This crate shipped a *hand-written*, checked `Clone`
1072/// here once too — through [`Self::try_clone`], to satisfy a channel
1073/// bound — and it came back out for the same reason: a consumer that
1074/// needs to share the [`TrackInfo`](mediadecode::demuxer::TrackInfo)
1075/// this type lives inside wraps it in `Arc` once, at the door,
1076/// instead of paying a deep copy per consumer. [`Self::try_clone`]
1077/// remains for the one caller that genuinely wants an owned duplicate
1078/// of the codec parameters, which sharing a message is not.
1079///
1080/// `disposition` is the raw `AV_DISPOSITION_*` bit set, not
1081/// `ffmpeg_next::format::stream::Disposition`. That type's
1082/// `from_bits_truncate` drops bits the linked build has no constant
1083/// for, and this crate's stance on bit sets is that every pattern is a
1084/// value — the same reason `PacketFlags` reaches the wire as a number.
1085pub struct TrackExtra {
1086  stream_index: i32,
1087  disposition: i32,
1088  start_time: Option<i64>,
1089  frame_count: Option<i64>,
1090  parameters: Parameters,
1091}
1092
1093impl TrackExtra {
1094  /// Constructs a `TrackExtra` from the stream index and its codec
1095  /// parameters. Everything else starts absent.
1096  ///
1097  /// **Fallible, and that is the point.** `Parameters::new()` and
1098  /// `Parameters::default()` are safe constructors that hand back a
1099  /// null-backed value when `avcodec_parameters_alloc` fails, saying
1100  /// nothing; accepting one here would store a landmine that goes off
1101  /// later, in a copy, on a thread that has forgotten the allocator
1102  /// ever failed. Refusing it at the door is what lets every other
1103  /// method on this type — and every reader of
1104  /// [`Self::parameters`] — rely on there being parameters at all.
1105  ///
1106  /// Not `const fn`: [`Parameters`] owns a heap allocation.
1107  pub fn new(stream_index: i32, parameters: Parameters) -> Result<Self, DemuxError> {
1108    // SAFETY: reading the pointer without dereferencing it.
1109    if unsafe { parameters.as_ptr() }.is_null() {
1110      return Err(DemuxError::ParametersMissing(ParametersMissing::new(
1111        stream_index.max(0) as usize,
1112      )));
1113    }
1114    Ok(Self {
1115      stream_index,
1116      disposition: 0,
1117      start_time: None,
1118      frame_count: None,
1119      parameters,
1120    })
1121  }
1122
1123  /// A deep copy of this row, with the codec-parameter copy checked.
1124  ///
1125  /// The fallible counterpart of the `Clone` this type deliberately
1126  /// does not implement — see the type's own documentation for why.
1127  pub fn try_clone(&self) -> Result<Self, DemuxError> {
1128    // No re-check: `self` cannot exist over null-backed parameters, and
1129    // `clone_parameters` never returns one.
1130    Ok(Self {
1131      stream_index: self.stream_index,
1132      disposition: self.disposition,
1133      start_time: self.start_time,
1134      frame_count: self.frame_count,
1135      parameters: self.clone_parameters()?,
1136    })
1137  }
1138
1139  /// An owned deep copy of the track's codec parameters — the handoff
1140  /// that opens a decoder for this track.
1141  ///
1142  /// `FfmpegAudioStreamDecoder::open(track.extra().clone_parameters()?,
1143  /// track.timebase())`. Fallible because the copy is: an allocation
1144  /// failure here is the difference between a decoder that is not
1145  /// opened and one opened on parameters that are not the file's.
1146  pub fn clone_parameters(&self) -> Result<Parameters, DemuxError> {
1147    clone_parameters(&self.parameters, self.stream_index.max(0) as usize)
1148  }
1149
1150  /// Returns the source `AVStream.index`.
1151  #[cfg_attr(not(tarpaulin), inline(always))]
1152  pub const fn stream_index(&self) -> i32 {
1153    self.stream_index
1154  }
1155  /// Returns the raw `AVStream.disposition` bit set.
1156  #[cfg_attr(not(tarpaulin), inline(always))]
1157  pub const fn disposition(&self) -> i32 {
1158    self.disposition
1159  }
1160  /// Returns the stream's start time in the track's timebase, or
1161  /// `None` when the container does not carry one.
1162  #[cfg_attr(not(tarpaulin), inline(always))]
1163  pub const fn start_time(&self) -> Option<i64> {
1164    self.start_time
1165  }
1166  /// Returns `AVStream.nb_frames` when the container carries it.
1167  #[cfg_attr(not(tarpaulin), inline(always))]
1168  pub const fn frame_count(&self) -> Option<i64> {
1169    self.frame_count
1170  }
1171  /// Returns the stream's codec parameters — the handle a decoder is
1172  /// opened from.
1173  #[cfg_attr(not(tarpaulin), inline(always))]
1174  pub const fn parameters(&self) -> &Parameters {
1175    &self.parameters
1176  }
1177
1178  /// Sets the disposition bits (consuming builder).
1179  #[cfg_attr(not(tarpaulin), inline(always))]
1180  #[must_use]
1181  pub const fn with_disposition(mut self, value: i32) -> Self {
1182    self.disposition = value;
1183    self
1184  }
1185  /// Sets the start time (consuming builder).
1186  #[cfg_attr(not(tarpaulin), inline(always))]
1187  #[must_use]
1188  pub const fn with_start_time(mut self, value: Option<i64>) -> Self {
1189    self.start_time = value;
1190    self
1191  }
1192  /// Sets the frame count (consuming builder).
1193  #[cfg_attr(not(tarpaulin), inline(always))]
1194  #[must_use]
1195  pub const fn with_frame_count(mut self, value: Option<i64>) -> Self {
1196    self.frame_count = value;
1197    self
1198  }
1199
1200  /// Sets the disposition bits in place.
1201  #[cfg_attr(not(tarpaulin), inline(always))]
1202  pub const fn set_disposition(&mut self, value: i32) -> &mut Self {
1203    self.disposition = value;
1204    self
1205  }
1206  /// Sets the start time in place.
1207  #[cfg_attr(not(tarpaulin), inline(always))]
1208  pub const fn set_start_time(&mut self, value: Option<i64>) -> &mut Self {
1209    self.start_time = value;
1210    self
1211  }
1212  /// Sets the frame count in place.
1213  #[cfg_attr(not(tarpaulin), inline(always))]
1214  pub const fn set_frame_count(&mut self, value: Option<i64>) -> &mut Self {
1215    self.frame_count = value;
1216    self
1217  }
1218}
1219
1220impl std::fmt::Debug for TrackExtra {
1221  /// Hand-written because [`Parameters`] does not derive `Debug`. The
1222  /// medium and codec id are the two fields worth printing; the rest of
1223  /// `AVCodecParameters` is per-kind detail the track row already
1224  /// carries in typed form.
1225  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1226    f.debug_struct("TrackExtra")
1227      .field("stream_index", &self.stream_index)
1228      .field("disposition", &format_args!("{:#x}", self.disposition))
1229      .field("start_time", &self.start_time)
1230      .field("frame_count", &self.frame_count)
1231      .field(
1232        "parameters",
1233        &format_args!("{:?}", self.parameters.medium()),
1234      )
1235      .finish()
1236  }
1237}
1238
1239#[cfg(test)]
1240mod tests {
1241  use super::*;
1242
1243  #[test]
1244  fn defaults_construct() {
1245    let v = VideoPacketExtra::default();
1246    assert_eq!(v.stream_index(), 0);
1247    assert!(v.side_data().is_empty());
1248
1249    let f = VideoFrameExtra::default();
1250    assert_eq!(f.picture_type(), PictureType::Unspecified);
1251    assert!(!f.key_frame());
1252    assert!(f.mastering_display().is_none());
1253
1254    let s = SubtitleFrameExtra::default();
1255    assert_eq!(s.start_display_time(), 0);
1256    assert_eq!(s.end_display_time(), 0);
1257  }
1258
1259  #[test]
1260  fn picture_type_default_is_unspecified() {
1261    assert_eq!(PictureType::default(), PictureType::Unspecified);
1262  }
1263
1264  #[test]
1265  fn side_data_entry_carries_bytes() {
1266    let entry = SideDataEntry::new(12345, vec![1, 2, 3, 4]);
1267    assert_eq!(entry.kind(), 12345);
1268    assert_eq!(entry.data(), &[1, 2, 3, 4]);
1269  }
1270
1271  #[test]
1272  fn content_light_level_default_is_zero() {
1273    let cll = ContentLightLevel::default();
1274    assert_eq!(cll.max_cll(), 0);
1275    assert_eq!(cll.max_fall(), 0);
1276  }
1277
1278  #[test]
1279  fn builders_chain() {
1280    let v = VideoPacketExtra::new(7)
1281      .with_byte_pos(Some(1234))
1282      .with_side_data(vec![SideDataEntry::new(1, vec![0xAB])]);
1283    assert_eq!(v.stream_index(), 7);
1284    assert_eq!(v.byte_pos(), Some(1234));
1285    assert_eq!(v.side_data().len(), 1);
1286  }
1287
1288  #[test]
1289  fn setters_chain() {
1290    let mut v = VideoPacketExtra::default();
1291    v.set_stream_index(3).set_byte_pos(Some(99));
1292    assert_eq!(v.stream_index(), 3);
1293    assert_eq!(v.byte_pos(), Some(99));
1294  }
1295}