Skip to main content

mediadecode_ffmpeg/
boundary.rs

1//! Boundary conversions between FFmpeg's bindgen integers and the
2//! unified [`mediadecode`] vocabulary.
3//!
4//! Centralised so the rest of the crate never compares raw
5//! `AVPixelFormat` integers against literals or transmutes back into
6//! the bindgen enum (UB hazard when the value isn't in the enum's
7//! discriminant set).
8
9use core::{
10  ffi::c_int,
11  ptr::{addr_of, read_unaligned},
12};
13
14use derive_more::{IsVariant, TryUnwrap, Unwrap};
15use ffmpeg_next::{Packet, ffi::AVPixelFormat};
16use mediadecode::{
17  PixelFormat, Timestamp,
18  demuxer::{AttachmentPacket, DataPacket},
19  frame::{AudioFrame, Dimensions, Plane, SubtitleFrame, VideoFrame},
20  packet::{AudioPacket, PacketFlags as MdPacketFlags, SubtitlePacket, VideoPacket},
21  subtitle::{SubtitlePayload, Text as SubtitleText},
22};
23use mediaframe::audio::ChannelLayoutDescription;
24
25use crate::{
26  // `buffer::SideDataAlloc` is aliased: this module also defines its own
27  // `PacketBuildError::SideDataAlloc` payload (the write-side failure —
28  // FFmpeg refusing an allocation while rebuilding an `AVPacket`'s side
29  // data), which keeps the bare name since it is native to this file.
30  // `BufferSideDataAlloc` is `PacketBufferError`'s read-side counterpart
31  // (out of memory copying a side-data entry *out of* an `AVPacket`) —
32  // same short name, different struct, different direction.
33  buffer::{
34    FfmpegBytes, PacketBufferError, SideDataAlloc as BufferSideDataAlloc, SideDataArray,
35    SideDataBytes, SideDataEntries, SideDataPayload, UnrepresentableFlags,
36  },
37  carrier::BodyRoute,
38  convert::{SIDE_DATA_MAX_ENTRIES, SIDE_DATA_MAX_TOTAL_BYTES},
39  extras::{
40    AttachmentPacketExtra, AudioFrameExtra, AudioPacketExtra, DataPacketExtra, SideDataEntry,
41    SubtitleFrameExtra, SubtitlePacketExtra, VideoFrameExtra, VideoPacketExtra,
42  },
43  limits::PacketLimits,
44  sample_format::SampleFormat,
45};
46
47/// Maps a raw `AVFrame.format` integer (i.e. the value of an
48/// `AVPixelFormat` enum variant) onto [`mediadecode::PixelFormat`].
49///
50/// Returns [`PixelFormat::None`] for raw integers we don't have a
51/// mapping for — including `AV_PIX_FMT_NONE` itself and the
52/// hardware-frame markers (`AV_PIX_FMT_VIDEOTOOLBOX` / `_VAAPI` /
53/// `_CUDA` / `_D3D11` / …), since those never describe CPU-side pixel
54/// data and the unified enum intentionally doesn't carry them. Use
55/// [`is_hardware_pix_fmt`] to identify HW frames before transferring
56/// to a CPU format.
57///
58/// mediaframe 0.3 struck `PixelFormat::Unknown(u32)`, so the raw
59/// integer no longer rides along in the returned value; the caller's
60/// own `raw` is the place it survives. Every consumer in this crate
61/// already treats the fall-through as "not a deliverable CPU format"
62/// (`pixdesc::to_av_pixel_format`, `is_supported_cpu_pix_fmt` and the
63/// geometry tables all reject it), so the rejection is unchanged.
64///
65/// The match never constructs an `AVPixelFormat` from a runtime
66/// value; it compares the input against `AVPixelFormat::AV_PIX_FMT_X
67/// as i32` constants. Sound regardless of which discriminant set the
68/// linked FFmpeg version exposes.
69pub const fn from_av_pixel_format(raw: i32) -> PixelFormat {
70  // Mirrors `crate::pixdesc::to_av_pixel_format` arm-for-arm (its
71  // inverse). Every deliverable CPU format plus the non-deliverable
72  // formats `to_av` still resolves a constant for (monochrome / PAL /
73  // sub-byte-packed RGB / Bayer) is mapped here, so a frame's raw
74  // `format` integer always lands on the same `PixelFormat` the round
75  // trip would produce. Deliverability (HWACCEL / BAYER / PAL /
76  // BITSTREAM rejection) is enforced separately by
77  // `pixdesc::is_deliverable` / the convert layer — this boundary is
78  // identity-only.
79  //
80  // BE-tagged formats map to mediadecode's distinct `*Be` variants
81  // (never folded onto the LE canonical). Folding BE onto LE silently
82  // corrupted pixel data: each >8-bit sample is byte-swapped between
83  // BE and LE, and the convert path exports the AVBufferRef bytes
84  // verbatim with no endian conversion, so a consumer reading a
85  // BE-tagged frame's planes as LE samples would see every sample
86  // byte-reversed. Mapping to the `*Be` variant keeps the format
87  // distinct so the convert layer can handle (or reject) it correctly.
88  //
89  // The match never constructs an `AVPixelFormat` from a runtime
90  // value; it compares the input against `AVPixelFormat::AV_PIX_FMT_X
91  // as i32` constants. Sound regardless of which discriminant set the
92  // linked FFmpeg version exposes.
93  match raw {
94    // Planar YUV 8-bit.
95    x if x == AVPixelFormat::AV_PIX_FMT_YUV420P as i32 => PixelFormat::Yuv420p,
96    x if x == AVPixelFormat::AV_PIX_FMT_YUV422P as i32 => PixelFormat::Yuv422p,
97    x if x == AVPixelFormat::AV_PIX_FMT_YUV440P as i32 => PixelFormat::Yuv440p,
98    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P as i32 => PixelFormat::Yuv444p,
99    x if x == AVPixelFormat::AV_PIX_FMT_YUV411P as i32 => PixelFormat::Yuv411p,
100    x if x == AVPixelFormat::AV_PIX_FMT_YUV410P as i32 => PixelFormat::Yuv410p,
101    // Deprecated JPEG-range planar YUV (yuvj-family).
102    x if x == AVPixelFormat::AV_PIX_FMT_YUVJ411P as i32 => PixelFormat::Yuvj411p,
103    x if x == AVPixelFormat::AV_PIX_FMT_YUVJ420P as i32 => PixelFormat::Yuvj420p,
104    x if x == AVPixelFormat::AV_PIX_FMT_YUVJ422P as i32 => PixelFormat::Yuvj422p,
105    x if x == AVPixelFormat::AV_PIX_FMT_YUVJ440P as i32 => PixelFormat::Yuvj440p,
106    x if x == AVPixelFormat::AV_PIX_FMT_YUVJ444P as i32 => PixelFormat::Yuvj444p,
107    // Planar YUV 4:2:0 high-bit.
108    x if x == AVPixelFormat::AV_PIX_FMT_YUV420P9LE as i32 => PixelFormat::Yuv420p9Le,
109    x if x == AVPixelFormat::AV_PIX_FMT_YUV420P9BE as i32 => PixelFormat::Yuv420p9Be,
110    x if x == AVPixelFormat::AV_PIX_FMT_YUV420P10LE as i32 => PixelFormat::Yuv420p10Le,
111    x if x == AVPixelFormat::AV_PIX_FMT_YUV420P10BE as i32 => PixelFormat::Yuv420p10Be,
112    x if x == AVPixelFormat::AV_PIX_FMT_YUV420P12LE as i32 => PixelFormat::Yuv420p12Le,
113    x if x == AVPixelFormat::AV_PIX_FMT_YUV420P12BE as i32 => PixelFormat::Yuv420p12Be,
114    x if x == AVPixelFormat::AV_PIX_FMT_YUV420P14LE as i32 => PixelFormat::Yuv420p14Le,
115    x if x == AVPixelFormat::AV_PIX_FMT_YUV420P14BE as i32 => PixelFormat::Yuv420p14Be,
116    x if x == AVPixelFormat::AV_PIX_FMT_YUV420P16LE as i32 => PixelFormat::Yuv420p16Le,
117    x if x == AVPixelFormat::AV_PIX_FMT_YUV420P16BE as i32 => PixelFormat::Yuv420p16Be,
118    // Planar YUV 4:2:2 high-bit.
119    x if x == AVPixelFormat::AV_PIX_FMT_YUV422P9LE as i32 => PixelFormat::Yuv422p9Le,
120    x if x == AVPixelFormat::AV_PIX_FMT_YUV422P9BE as i32 => PixelFormat::Yuv422p9Be,
121    x if x == AVPixelFormat::AV_PIX_FMT_YUV422P10LE as i32 => PixelFormat::Yuv422p10Le,
122    x if x == AVPixelFormat::AV_PIX_FMT_YUV422P10BE as i32 => PixelFormat::Yuv422p10Be,
123    x if x == AVPixelFormat::AV_PIX_FMT_YUV422P12LE as i32 => PixelFormat::Yuv422p12Le,
124    x if x == AVPixelFormat::AV_PIX_FMT_YUV422P12BE as i32 => PixelFormat::Yuv422p12Be,
125    x if x == AVPixelFormat::AV_PIX_FMT_YUV422P14LE as i32 => PixelFormat::Yuv422p14Le,
126    x if x == AVPixelFormat::AV_PIX_FMT_YUV422P14BE as i32 => PixelFormat::Yuv422p14Be,
127    x if x == AVPixelFormat::AV_PIX_FMT_YUV422P16LE as i32 => PixelFormat::Yuv422p16Le,
128    x if x == AVPixelFormat::AV_PIX_FMT_YUV422P16BE as i32 => PixelFormat::Yuv422p16Be,
129    // Planar YUV 4:4:0 high-bit.
130    x if x == AVPixelFormat::AV_PIX_FMT_YUV440P10LE as i32 => PixelFormat::Yuv440p10Le,
131    x if x == AVPixelFormat::AV_PIX_FMT_YUV440P10BE as i32 => PixelFormat::Yuv440p10Be,
132    x if x == AVPixelFormat::AV_PIX_FMT_YUV440P12LE as i32 => PixelFormat::Yuv440p12Le,
133    x if x == AVPixelFormat::AV_PIX_FMT_YUV440P12BE as i32 => PixelFormat::Yuv440p12Be,
134    // Planar YUV 4:4:4 high-bit.
135    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P9LE as i32 => PixelFormat::Yuv444p9Le,
136    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P9BE as i32 => PixelFormat::Yuv444p9Be,
137    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P10LE as i32 => PixelFormat::Yuv444p10Le,
138    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P10BE as i32 => PixelFormat::Yuv444p10Be,
139    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P12LE as i32 => PixelFormat::Yuv444p12Le,
140    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P12BE as i32 => PixelFormat::Yuv444p12Be,
141    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P14LE as i32 => PixelFormat::Yuv444p14Le,
142    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P14BE as i32 => PixelFormat::Yuv444p14Be,
143    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P16LE as i32 => PixelFormat::Yuv444p16Le,
144    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P16BE as i32 => PixelFormat::Yuv444p16Be,
145    // MSB-packed YUV 4:4:4.
146    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P10MSBLE as i32 => PixelFormat::Yuv444p10MsbLe,
147    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P10MSBBE as i32 => PixelFormat::Yuv444p10MsbBe,
148    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P12MSBLE as i32 => PixelFormat::Yuv444p12MsbLe,
149    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P12MSBBE as i32 => PixelFormat::Yuv444p12MsbBe,
150    // Planar YUVA.
151    x if x == AVPixelFormat::AV_PIX_FMT_YUVA420P as i32 => PixelFormat::Yuva420p,
152    x if x == AVPixelFormat::AV_PIX_FMT_YUVA422P as i32 => PixelFormat::Yuva422p,
153    x if x == AVPixelFormat::AV_PIX_FMT_YUVA444P as i32 => PixelFormat::Yuva444p,
154    x if x == AVPixelFormat::AV_PIX_FMT_YUVA420P9LE as i32 => PixelFormat::Yuva420p9Le,
155    x if x == AVPixelFormat::AV_PIX_FMT_YUVA420P9BE as i32 => PixelFormat::Yuva420p9Be,
156    x if x == AVPixelFormat::AV_PIX_FMT_YUVA422P9LE as i32 => PixelFormat::Yuva422p9Le,
157    x if x == AVPixelFormat::AV_PIX_FMT_YUVA422P9BE as i32 => PixelFormat::Yuva422p9Be,
158    x if x == AVPixelFormat::AV_PIX_FMT_YUVA444P9LE as i32 => PixelFormat::Yuva444p9Le,
159    x if x == AVPixelFormat::AV_PIX_FMT_YUVA444P9BE as i32 => PixelFormat::Yuva444p9Be,
160    x if x == AVPixelFormat::AV_PIX_FMT_YUVA420P10LE as i32 => PixelFormat::Yuva420p10Le,
161    x if x == AVPixelFormat::AV_PIX_FMT_YUVA420P10BE as i32 => PixelFormat::Yuva420p10Be,
162    x if x == AVPixelFormat::AV_PIX_FMT_YUVA422P10LE as i32 => PixelFormat::Yuva422p10Le,
163    x if x == AVPixelFormat::AV_PIX_FMT_YUVA422P10BE as i32 => PixelFormat::Yuva422p10Be,
164    x if x == AVPixelFormat::AV_PIX_FMT_YUVA444P10LE as i32 => PixelFormat::Yuva444p10Le,
165    x if x == AVPixelFormat::AV_PIX_FMT_YUVA444P10BE as i32 => PixelFormat::Yuva444p10Be,
166    x if x == AVPixelFormat::AV_PIX_FMT_YUVA422P12LE as i32 => PixelFormat::Yuva422p12Le,
167    x if x == AVPixelFormat::AV_PIX_FMT_YUVA422P12BE as i32 => PixelFormat::Yuva422p12Be,
168    x if x == AVPixelFormat::AV_PIX_FMT_YUVA444P12LE as i32 => PixelFormat::Yuva444p12Le,
169    x if x == AVPixelFormat::AV_PIX_FMT_YUVA444P12BE as i32 => PixelFormat::Yuva444p12Be,
170    x if x == AVPixelFormat::AV_PIX_FMT_YUVA420P16LE as i32 => PixelFormat::Yuva420p16Le,
171    x if x == AVPixelFormat::AV_PIX_FMT_YUVA420P16BE as i32 => PixelFormat::Yuva420p16Be,
172    x if x == AVPixelFormat::AV_PIX_FMT_YUVA422P16LE as i32 => PixelFormat::Yuva422p16Le,
173    x if x == AVPixelFormat::AV_PIX_FMT_YUVA422P16BE as i32 => PixelFormat::Yuva422p16Be,
174    x if x == AVPixelFormat::AV_PIX_FMT_YUVA444P16LE as i32 => PixelFormat::Yuva444p16Le,
175    x if x == AVPixelFormat::AV_PIX_FMT_YUVA444P16BE as i32 => PixelFormat::Yuva444p16Be,
176    // Semi-planar YUV 8-bit.
177    x if x == AVPixelFormat::AV_PIX_FMT_NV12 as i32 => PixelFormat::Nv12,
178    x if x == AVPixelFormat::AV_PIX_FMT_NV21 as i32 => PixelFormat::Nv21,
179    x if x == AVPixelFormat::AV_PIX_FMT_NV16 as i32 => PixelFormat::Nv16,
180    x if x == AVPixelFormat::AV_PIX_FMT_NV24 as i32 => PixelFormat::Nv24,
181    x if x == AVPixelFormat::AV_PIX_FMT_NV42 as i32 => PixelFormat::Nv42,
182    x if x == AVPixelFormat::AV_PIX_FMT_NV20LE as i32 => PixelFormat::Nv20Le,
183    x if x == AVPixelFormat::AV_PIX_FMT_NV20BE as i32 => PixelFormat::Nv20Be,
184    // Semi-planar YUV high-bit.
185    x if x == AVPixelFormat::AV_PIX_FMT_P010LE as i32 => PixelFormat::P010Le,
186    x if x == AVPixelFormat::AV_PIX_FMT_P010BE as i32 => PixelFormat::P010Be,
187    x if x == AVPixelFormat::AV_PIX_FMT_P012LE as i32 => PixelFormat::P012Le,
188    x if x == AVPixelFormat::AV_PIX_FMT_P012BE as i32 => PixelFormat::P012Be,
189    x if x == AVPixelFormat::AV_PIX_FMT_P016LE as i32 => PixelFormat::P016Le,
190    x if x == AVPixelFormat::AV_PIX_FMT_P016BE as i32 => PixelFormat::P016Be,
191    x if x == AVPixelFormat::AV_PIX_FMT_P210LE as i32 => PixelFormat::P210Le,
192    x if x == AVPixelFormat::AV_PIX_FMT_P210BE as i32 => PixelFormat::P210Be,
193    x if x == AVPixelFormat::AV_PIX_FMT_P212LE as i32 => PixelFormat::P212Le,
194    x if x == AVPixelFormat::AV_PIX_FMT_P212BE as i32 => PixelFormat::P212Be,
195    x if x == AVPixelFormat::AV_PIX_FMT_P216LE as i32 => PixelFormat::P216Le,
196    x if x == AVPixelFormat::AV_PIX_FMT_P216BE as i32 => PixelFormat::P216Be,
197    x if x == AVPixelFormat::AV_PIX_FMT_P410LE as i32 => PixelFormat::P410Le,
198    x if x == AVPixelFormat::AV_PIX_FMT_P410BE as i32 => PixelFormat::P410Be,
199    x if x == AVPixelFormat::AV_PIX_FMT_P412LE as i32 => PixelFormat::P412Le,
200    x if x == AVPixelFormat::AV_PIX_FMT_P412BE as i32 => PixelFormat::P412Be,
201    x if x == AVPixelFormat::AV_PIX_FMT_P416LE as i32 => PixelFormat::P416Le,
202    x if x == AVPixelFormat::AV_PIX_FMT_P416BE as i32 => PixelFormat::P416Be,
203    // Packed YUV 8-bit.
204    x if x == AVPixelFormat::AV_PIX_FMT_YUYV422 as i32 => PixelFormat::Yuyv422,
205    x if x == AVPixelFormat::AV_PIX_FMT_UYVY422 as i32 => PixelFormat::Uyvy422,
206    x if x == AVPixelFormat::AV_PIX_FMT_YVYU422 as i32 => PixelFormat::Yvyu422,
207    x if x == AVPixelFormat::AV_PIX_FMT_UYYVYY411 as i32 => PixelFormat::Uyyvyy411,
208    // Packed YUV high-bit.
209    x if x == AVPixelFormat::AV_PIX_FMT_Y210LE as i32 => PixelFormat::Y210Le,
210    x if x == AVPixelFormat::AV_PIX_FMT_Y210BE as i32 => PixelFormat::Y210Be,
211    x if x == AVPixelFormat::AV_PIX_FMT_Y212LE as i32 => PixelFormat::Y212Le,
212    x if x == AVPixelFormat::AV_PIX_FMT_Y212BE as i32 => PixelFormat::Y212Be,
213    x if x == AVPixelFormat::AV_PIX_FMT_Y216LE as i32 => PixelFormat::Y216Le,
214    x if x == AVPixelFormat::AV_PIX_FMT_Y216BE as i32 => PixelFormat::Y216Be,
215    x if x == AVPixelFormat::AV_PIX_FMT_XV30LE as i32 => PixelFormat::Xv30Le,
216    x if x == AVPixelFormat::AV_PIX_FMT_XV30BE as i32 => PixelFormat::Xv30Be,
217    x if x == AVPixelFormat::AV_PIX_FMT_V30XLE as i32 => PixelFormat::V30xLe,
218    x if x == AVPixelFormat::AV_PIX_FMT_V30XBE as i32 => PixelFormat::V30xBe,
219    x if x == AVPixelFormat::AV_PIX_FMT_XV36LE as i32 => PixelFormat::Xv36Le,
220    x if x == AVPixelFormat::AV_PIX_FMT_XV36BE as i32 => PixelFormat::Xv36Be,
221    x if x == AVPixelFormat::AV_PIX_FMT_XV48LE as i32 => PixelFormat::Xv48Le,
222    x if x == AVPixelFormat::AV_PIX_FMT_XV48BE as i32 => PixelFormat::Xv48Be,
223    x if x == AVPixelFormat::AV_PIX_FMT_VUYA as i32 => PixelFormat::Vuya,
224    x if x == AVPixelFormat::AV_PIX_FMT_VUYX as i32 => PixelFormat::Vuyx,
225    x if x == AVPixelFormat::AV_PIX_FMT_AYUV as i32 => PixelFormat::Ayuv,
226    x if x == AVPixelFormat::AV_PIX_FMT_AYUV64LE as i32 => PixelFormat::Ayuv64Le,
227    x if x == AVPixelFormat::AV_PIX_FMT_AYUV64BE as i32 => PixelFormat::Ayuv64Be,
228    x if x == AVPixelFormat::AV_PIX_FMT_UYVA as i32 => PixelFormat::Uyva,
229    x if x == AVPixelFormat::AV_PIX_FMT_VYU444 as i32 => PixelFormat::Vyu444,
230    // XYZ.
231    x if x == AVPixelFormat::AV_PIX_FMT_XYZ12LE as i32 => PixelFormat::Xyz12Le,
232    x if x == AVPixelFormat::AV_PIX_FMT_XYZ12BE as i32 => PixelFormat::Xyz12Be,
233    // Packed RGB 8-bit.
234    x if x == AVPixelFormat::AV_PIX_FMT_RGB24 as i32 => PixelFormat::Rgb24,
235    x if x == AVPixelFormat::AV_PIX_FMT_BGR24 as i32 => PixelFormat::Bgr24,
236    x if x == AVPixelFormat::AV_PIX_FMT_RGBA as i32 => PixelFormat::Rgba,
237    x if x == AVPixelFormat::AV_PIX_FMT_BGRA as i32 => PixelFormat::Bgra,
238    x if x == AVPixelFormat::AV_PIX_FMT_ARGB as i32 => PixelFormat::Argb,
239    x if x == AVPixelFormat::AV_PIX_FMT_ABGR as i32 => PixelFormat::Abgr,
240    x if x == AVPixelFormat::AV_PIX_FMT_RGB0 as i32 => PixelFormat::Rgbx,
241    x if x == AVPixelFormat::AV_PIX_FMT_BGR0 as i32 => PixelFormat::Bgrx,
242    x if x == AVPixelFormat::AV_PIX_FMT_0RGB as i32 => PixelFormat::Xrgb,
243    x if x == AVPixelFormat::AV_PIX_FMT_0BGR as i32 => PixelFormat::Xbgr,
244    x if x == AVPixelFormat::AV_PIX_FMT_X2RGB10LE as i32 => PixelFormat::X2Rgb10Le,
245    x if x == AVPixelFormat::AV_PIX_FMT_X2RGB10BE as i32 => PixelFormat::X2Rgb10Be,
246    x if x == AVPixelFormat::AV_PIX_FMT_X2BGR10LE as i32 => PixelFormat::X2Bgr10Le,
247    x if x == AVPixelFormat::AV_PIX_FMT_X2BGR10BE as i32 => PixelFormat::X2Bgr10Be,
248    // Gbr24p shares AV_PIX_FMT_GBRP's discriminant; mapped to Gbrp above.
249    // Packed RGB high-bit.
250    x if x == AVPixelFormat::AV_PIX_FMT_RGB48LE as i32 => PixelFormat::Rgb48Le,
251    x if x == AVPixelFormat::AV_PIX_FMT_RGB48BE as i32 => PixelFormat::Rgb48Be,
252    x if x == AVPixelFormat::AV_PIX_FMT_BGR48LE as i32 => PixelFormat::Bgr48Le,
253    x if x == AVPixelFormat::AV_PIX_FMT_BGR48BE as i32 => PixelFormat::Bgr48Be,
254    x if x == AVPixelFormat::AV_PIX_FMT_RGBA64LE as i32 => PixelFormat::Rgba64Le,
255    x if x == AVPixelFormat::AV_PIX_FMT_RGBA64BE as i32 => PixelFormat::Rgba64Be,
256    x if x == AVPixelFormat::AV_PIX_FMT_BGRA64LE as i32 => PixelFormat::Bgra64Le,
257    x if x == AVPixelFormat::AV_PIX_FMT_BGRA64BE as i32 => PixelFormat::Bgra64Be,
258    x if x == AVPixelFormat::AV_PIX_FMT_RGB96LE as i32 => PixelFormat::Rgb96Le,
259    x if x == AVPixelFormat::AV_PIX_FMT_RGB96BE as i32 => PixelFormat::Rgb96Be,
260    x if x == AVPixelFormat::AV_PIX_FMT_RGBA128LE as i32 => PixelFormat::Rgba128Le,
261    x if x == AVPixelFormat::AV_PIX_FMT_RGBA128BE as i32 => PixelFormat::Rgba128Be,
262    // Packed RGB float / half-float.
263    x if x == AVPixelFormat::AV_PIX_FMT_RGBF16LE as i32 => PixelFormat::Rgbf16Le,
264    x if x == AVPixelFormat::AV_PIX_FMT_RGBF16BE as i32 => PixelFormat::Rgbf16Be,
265    x if x == AVPixelFormat::AV_PIX_FMT_RGBF32LE as i32 => PixelFormat::Rgbf32Le,
266    x if x == AVPixelFormat::AV_PIX_FMT_RGBF32BE as i32 => PixelFormat::Rgbf32Be,
267    x if x == AVPixelFormat::AV_PIX_FMT_RGBAF16LE as i32 => PixelFormat::Rgbaf16Le,
268    x if x == AVPixelFormat::AV_PIX_FMT_RGBAF16BE as i32 => PixelFormat::Rgbaf16Be,
269    x if x == AVPixelFormat::AV_PIX_FMT_RGBAF32LE as i32 => PixelFormat::Rgbaf32Le,
270    x if x == AVPixelFormat::AV_PIX_FMT_RGBAF32BE as i32 => PixelFormat::Rgbaf32Be,
271    // Planar GBR.
272    x if x == AVPixelFormat::AV_PIX_FMT_GBRP as i32 => PixelFormat::Gbrp,
273    x if x == AVPixelFormat::AV_PIX_FMT_GBRP9LE as i32 => PixelFormat::Gbrp9Le,
274    x if x == AVPixelFormat::AV_PIX_FMT_GBRP9BE as i32 => PixelFormat::Gbrp9Be,
275    x if x == AVPixelFormat::AV_PIX_FMT_GBRP10LE as i32 => PixelFormat::Gbrp10Le,
276    x if x == AVPixelFormat::AV_PIX_FMT_GBRP10BE as i32 => PixelFormat::Gbrp10Be,
277    x if x == AVPixelFormat::AV_PIX_FMT_GBRP10MSBLE as i32 => PixelFormat::Gbrp10MsbLe,
278    x if x == AVPixelFormat::AV_PIX_FMT_GBRP10MSBBE as i32 => PixelFormat::Gbrp10MsbBe,
279    x if x == AVPixelFormat::AV_PIX_FMT_GBRP12LE as i32 => PixelFormat::Gbrp12Le,
280    x if x == AVPixelFormat::AV_PIX_FMT_GBRP12BE as i32 => PixelFormat::Gbrp12Be,
281    x if x == AVPixelFormat::AV_PIX_FMT_GBRP12MSBLE as i32 => PixelFormat::Gbrp12MsbLe,
282    x if x == AVPixelFormat::AV_PIX_FMT_GBRP12MSBBE as i32 => PixelFormat::Gbrp12MsbBe,
283    x if x == AVPixelFormat::AV_PIX_FMT_GBRP14LE as i32 => PixelFormat::Gbrp14Le,
284    x if x == AVPixelFormat::AV_PIX_FMT_GBRP14BE as i32 => PixelFormat::Gbrp14Be,
285    x if x == AVPixelFormat::AV_PIX_FMT_GBRP16LE as i32 => PixelFormat::Gbrp16Le,
286    x if x == AVPixelFormat::AV_PIX_FMT_GBRP16BE as i32 => PixelFormat::Gbrp16Be,
287    x if x == AVPixelFormat::AV_PIX_FMT_GBRPF16LE as i32 => PixelFormat::Gbrpf16Le,
288    x if x == AVPixelFormat::AV_PIX_FMT_GBRPF16BE as i32 => PixelFormat::Gbrpf16Be,
289    x if x == AVPixelFormat::AV_PIX_FMT_GBRPF32LE as i32 => PixelFormat::Gbrpf32Le,
290    x if x == AVPixelFormat::AV_PIX_FMT_GBRPF32BE as i32 => PixelFormat::Gbrpf32Be,
291    // Planar GBRA.
292    x if x == AVPixelFormat::AV_PIX_FMT_GBRAP as i32 => PixelFormat::Gbrap,
293    x if x == AVPixelFormat::AV_PIX_FMT_GBRAP10LE as i32 => PixelFormat::Gbrap10Le,
294    x if x == AVPixelFormat::AV_PIX_FMT_GBRAP10BE as i32 => PixelFormat::Gbrap10Be,
295    x if x == AVPixelFormat::AV_PIX_FMT_GBRAP12LE as i32 => PixelFormat::Gbrap12Le,
296    x if x == AVPixelFormat::AV_PIX_FMT_GBRAP12BE as i32 => PixelFormat::Gbrap12Be,
297    x if x == AVPixelFormat::AV_PIX_FMT_GBRAP14LE as i32 => PixelFormat::Gbrap14Le,
298    x if x == AVPixelFormat::AV_PIX_FMT_GBRAP14BE as i32 => PixelFormat::Gbrap14Be,
299    x if x == AVPixelFormat::AV_PIX_FMT_GBRAP16LE as i32 => PixelFormat::Gbrap16Le,
300    x if x == AVPixelFormat::AV_PIX_FMT_GBRAP16BE as i32 => PixelFormat::Gbrap16Be,
301    x if x == AVPixelFormat::AV_PIX_FMT_GBRAP32LE as i32 => PixelFormat::Gbrap32Le,
302    x if x == AVPixelFormat::AV_PIX_FMT_GBRAP32BE as i32 => PixelFormat::Gbrap32Be,
303    x if x == AVPixelFormat::AV_PIX_FMT_GBRAPF16LE as i32 => PixelFormat::Gbrapf16Le,
304    x if x == AVPixelFormat::AV_PIX_FMT_GBRAPF16BE as i32 => PixelFormat::Gbrapf16Be,
305    x if x == AVPixelFormat::AV_PIX_FMT_GBRAPF32LE as i32 => PixelFormat::Gbrapf32Le,
306    x if x == AVPixelFormat::AV_PIX_FMT_GBRAPF32BE as i32 => PixelFormat::Gbrapf32Be,
307    // Greyscale.
308    x if x == AVPixelFormat::AV_PIX_FMT_GRAY8 as i32 => PixelFormat::Gray8,
309    x if x == AVPixelFormat::AV_PIX_FMT_GRAY9LE as i32 => PixelFormat::Gray9Le,
310    x if x == AVPixelFormat::AV_PIX_FMT_GRAY9BE as i32 => PixelFormat::Gray9Be,
311    x if x == AVPixelFormat::AV_PIX_FMT_GRAY10LE as i32 => PixelFormat::Gray10Le,
312    x if x == AVPixelFormat::AV_PIX_FMT_GRAY10BE as i32 => PixelFormat::Gray10Be,
313    x if x == AVPixelFormat::AV_PIX_FMT_GRAY12LE as i32 => PixelFormat::Gray12Le,
314    x if x == AVPixelFormat::AV_PIX_FMT_GRAY12BE as i32 => PixelFormat::Gray12Be,
315    x if x == AVPixelFormat::AV_PIX_FMT_GRAY14LE as i32 => PixelFormat::Gray14Le,
316    x if x == AVPixelFormat::AV_PIX_FMT_GRAY14BE as i32 => PixelFormat::Gray14Be,
317    x if x == AVPixelFormat::AV_PIX_FMT_GRAY16LE as i32 => PixelFormat::Gray16Le,
318    x if x == AVPixelFormat::AV_PIX_FMT_GRAY16BE as i32 => PixelFormat::Gray16Be,
319    x if x == AVPixelFormat::AV_PIX_FMT_GRAY32LE as i32 => PixelFormat::Gray32Le,
320    x if x == AVPixelFormat::AV_PIX_FMT_GRAY32BE as i32 => PixelFormat::Gray32Be,
321    x if x == AVPixelFormat::AV_PIX_FMT_GRAYF16LE as i32 => PixelFormat::Grayf16Le,
322    x if x == AVPixelFormat::AV_PIX_FMT_GRAYF16BE as i32 => PixelFormat::Grayf16Be,
323    x if x == AVPixelFormat::AV_PIX_FMT_GRAYF32LE as i32 => PixelFormat::Grayf32Le,
324    x if x == AVPixelFormat::AV_PIX_FMT_GRAYF32BE as i32 => PixelFormat::Grayf32Be,
325    x if x == AVPixelFormat::AV_PIX_FMT_YA8 as i32 => PixelFormat::Ya8,
326    x if x == AVPixelFormat::AV_PIX_FMT_YA16LE as i32 => PixelFormat::Ya16Le,
327    x if x == AVPixelFormat::AV_PIX_FMT_YA16BE as i32 => PixelFormat::Ya16Be,
328    x if x == AVPixelFormat::AV_PIX_FMT_YAF16LE as i32 => PixelFormat::Yaf16Le,
329    x if x == AVPixelFormat::AV_PIX_FMT_YAF16BE as i32 => PixelFormat::Yaf16Be,
330    x if x == AVPixelFormat::AV_PIX_FMT_YAF32LE as i32 => PixelFormat::Yaf32Le,
331    x if x == AVPixelFormat::AV_PIX_FMT_YAF32BE as i32 => PixelFormat::Yaf32Be,
332    x if x == AVPixelFormat::AV_PIX_FMT_MONOWHITE as i32 => PixelFormat::Monowhite,
333    x if x == AVPixelFormat::AV_PIX_FMT_MONOBLACK as i32 => PixelFormat::Monoblack,
334    x if x == AVPixelFormat::AV_PIX_FMT_PAL8 as i32 => PixelFormat::Pal8,
335    x if x == AVPixelFormat::AV_PIX_FMT_RGB4 as i32 => PixelFormat::Rgb4,
336    x if x == AVPixelFormat::AV_PIX_FMT_RGB4_BYTE as i32 => PixelFormat::Rgb4Byte,
337    x if x == AVPixelFormat::AV_PIX_FMT_RGB8 as i32 => PixelFormat::Rgb8,
338    x if x == AVPixelFormat::AV_PIX_FMT_BGR4 as i32 => PixelFormat::Bgr4,
339    x if x == AVPixelFormat::AV_PIX_FMT_BGR4_BYTE as i32 => PixelFormat::Bgr4Byte,
340    x if x == AVPixelFormat::AV_PIX_FMT_BGR8 as i32 => PixelFormat::Bgr8,
341    x if x == AVPixelFormat::AV_PIX_FMT_RGB444LE as i32 => PixelFormat::Rgb444Le,
342    x if x == AVPixelFormat::AV_PIX_FMT_RGB444BE as i32 => PixelFormat::Rgb444Be,
343    x if x == AVPixelFormat::AV_PIX_FMT_BGR444LE as i32 => PixelFormat::Bgr444Le,
344    x if x == AVPixelFormat::AV_PIX_FMT_BGR444BE as i32 => PixelFormat::Bgr444Be,
345    x if x == AVPixelFormat::AV_PIX_FMT_RGB555LE as i32 => PixelFormat::Rgb555Le,
346    x if x == AVPixelFormat::AV_PIX_FMT_RGB555BE as i32 => PixelFormat::Rgb555Be,
347    x if x == AVPixelFormat::AV_PIX_FMT_BGR555LE as i32 => PixelFormat::Bgr555Le,
348    x if x == AVPixelFormat::AV_PIX_FMT_BGR555BE as i32 => PixelFormat::Bgr555Be,
349    x if x == AVPixelFormat::AV_PIX_FMT_RGB565LE as i32 => PixelFormat::Rgb565Le,
350    x if x == AVPixelFormat::AV_PIX_FMT_RGB565BE as i32 => PixelFormat::Rgb565Be,
351    x if x == AVPixelFormat::AV_PIX_FMT_BGR565LE as i32 => PixelFormat::Bgr565Le,
352    x if x == AVPixelFormat::AV_PIX_FMT_BGR565BE as i32 => PixelFormat::Bgr565Be,
353    x if x == AVPixelFormat::AV_PIX_FMT_BAYER_BGGR8 as i32 => PixelFormat::BayerBggr8,
354    x if x == AVPixelFormat::AV_PIX_FMT_BAYER_RGGB8 as i32 => PixelFormat::BayerRggb8,
355    x if x == AVPixelFormat::AV_PIX_FMT_BAYER_GBRG8 as i32 => PixelFormat::BayerGbrg8,
356    x if x == AVPixelFormat::AV_PIX_FMT_BAYER_GRBG8 as i32 => PixelFormat::BayerGrbg8,
357    x if x == AVPixelFormat::AV_PIX_FMT_BAYER_BGGR16LE as i32 => PixelFormat::BayerBggr16Le,
358    x if x == AVPixelFormat::AV_PIX_FMT_BAYER_BGGR16BE as i32 => PixelFormat::BayerBggr16Be,
359    x if x == AVPixelFormat::AV_PIX_FMT_BAYER_RGGB16LE as i32 => PixelFormat::BayerRggb16Le,
360    x if x == AVPixelFormat::AV_PIX_FMT_BAYER_RGGB16BE as i32 => PixelFormat::BayerRggb16Be,
361    x if x == AVPixelFormat::AV_PIX_FMT_BAYER_GBRG16LE as i32 => PixelFormat::BayerGbrg16Le,
362    x if x == AVPixelFormat::AV_PIX_FMT_BAYER_GBRG16BE as i32 => PixelFormat::BayerGbrg16Be,
363    x if x == AVPixelFormat::AV_PIX_FMT_BAYER_GRBG16LE as i32 => PixelFormat::BayerGrbg16Le,
364    x if x == AVPixelFormat::AV_PIX_FMT_BAYER_GRBG16BE as i32 => PixelFormat::BayerGrbg16Be,
365    _ => PixelFormat::None,
366  }
367}
368
369/// Returns `true` when `raw` is one of FFmpeg's hardware-frame markers
370/// (`AV_PIX_FMT_VIDEOTOOLBOX` / `_VAAPI` / `_CUDA` / `_D3D11` /
371/// `_DRM_PRIME` / `_MEDIACODEC` / `_VULKAN`). Used by the HW probe to
372/// identify GPU-resident frames before triggering
373/// `av_hwframe_transfer_data`.
374pub const fn is_hardware_pix_fmt(raw: i32) -> bool {
375  matches!(
376    raw,
377    x if x == AVPixelFormat::AV_PIX_FMT_VIDEOTOOLBOX as i32
378      || x == AVPixelFormat::AV_PIX_FMT_VAAPI as i32
379      || x == AVPixelFormat::AV_PIX_FMT_CUDA as i32
380      || x == AVPixelFormat::AV_PIX_FMT_D3D11 as i32
381      || x == AVPixelFormat::AV_PIX_FMT_DRM_PRIME as i32
382      || x == AVPixelFormat::AV_PIX_FMT_MEDIACODEC as i32
383      || x == AVPixelFormat::AV_PIX_FMT_VULKAN as i32
384  )
385}
386
387/// Fallible counterpart to ffmpeg-next's `Packet::copy`.
388///
389/// The upstream helper calls `Packet::new(size)` (which silently
390/// truncates `size` to `c_int` and ignores `av_new_packet`'s return
391/// code) and then panics via `data_mut().unwrap().write_all(...).unwrap()`
392/// if the allocation failed. From a safe public decoder API we want
393/// the OOM / oversized-payload paths to surface as
394/// `ffmpeg_next::Error` rather than aborting the process — every
395/// `send_packet` path goes through this helper.
396///
397/// Failure modes:
398/// * payload larger than `c_int::MAX` (would overflow `AVPacket.size`)
399///   → `ffmpeg_next::Error::Other { errno: libc::EINVAL }`.
400/// * `av_new_packet` allocation failure (signalled by `data_mut()`
401///   returning `None`) → `ffmpeg_next::Error::Other { errno:
402///   libc::ENOMEM }`.
403pub(crate) fn try_packet_copy(data: &[u8]) -> std::result::Result<Packet, ffmpeg_next::Error> {
404  // FFmpeg's `AVPacket.size` is `c_int`. A payload larger than that
405  // can't fit in a single packet — refuse rather than truncate via
406  // `as c_int` inside `Packet::new`.
407  if data.len() > c_int::MAX as usize {
408    return Err(ffmpeg_next::Error::Other {
409      errno: libc::EINVAL,
410    });
411  }
412  // `Packet::new(size)` calls `av_new_packet(&mut pkt, size as
413  // c_int)` and ignores the return code; on OOM it returns a
414  // `Packet` whose `.data` is null. We detect that via
415  // `data_mut()` (returns `None` on null) and copy via
416  // `copy_nonoverlapping` so we never go through `data_mut()
417  // .unwrap().write_all().unwrap()` — the upstream `Packet::copy`'s
418  // double panic.
419  let mut pkt = Packet::new(data.len());
420  match pkt.data_mut() {
421    Some(slot) if slot.len() == data.len() => {
422      // SAFETY: `slot` is a `&mut [u8]` of `data.len()` bytes;
423      // `data` is a `&[u8]` of the same length. Non-overlapping
424      // because `slot` is a fresh allocation.
425      if !data.is_empty() {
426        unsafe {
427          core::ptr::copy_nonoverlapping(data.as_ptr(), slot.as_mut_ptr(), data.len());
428        }
429      }
430      Ok(pkt)
431    }
432    _ => Err(ffmpeg_next::Error::Other {
433      errno: libc::ENOMEM,
434    }),
435  }
436}
437
438/// Writes every flag the portable packet carries onto the `AVPacket`
439/// being rebuilt.
440///
441/// The one place this crate writes packet flags into FFmpeg. The three
442/// stream families rebuild through it, and so does the one-shot image
443/// road — which for two releases forgot to, and sent every attachment
444/// to libavcodec with a zeroed `flags` field.
445///
446/// Not through `Packet::set_flags`, which takes `ffmpeg_next`'s `Flags`
447/// and so can only spell `KEY` and `CORRUPT`: the bits are written to
448/// `AVPacket.flags` directly, so `DISCARD` — and anything else the
449/// forward direction retained — reaches the decoder that has to obey
450/// it. `PacketFlags` is a `u8` bit set and the field is a `c_int`, so
451/// the widening is total and nothing needs deciding here.
452///
453/// # Safety
454///
455/// `packet` must own a live `AVPacket`.
456pub(crate) unsafe fn write_md_flags(packet: &mut Packet, flags: MdPacketFlags) {
457  use ffmpeg_next::packet::Mut;
458  unsafe {
459    (*packet.as_mut_ptr()).flags = c_int::from(flags.bits());
460  }
461}
462
463/// Payload for [`PacketBuildError::UnknownSideData`].
464///
465/// A side-data entry whose type this build of FFmpeg does not name.
466///
467/// Refused rather than dropped: this crate carries side-data types as
468/// the raw integers they are on the wire, and handing an unknown one
469/// to C would either form an invalid enum discriminant or attach a
470/// type nothing downstream can read. Everything the demuxer captured
471/// came from this same build and is in range, so this only answers a
472/// hand-built entry.
473#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
474#[error("side-data type {kind} is not one this FFmpeg build names (0..{limit})")]
475pub struct UnknownSideData {
476  kind: i32,
477  limit: i32,
478}
479
480impl UnknownSideData {
481  /// Constructs an `UnknownSideData` payload.
482  #[cfg_attr(not(tarpaulin), inline(always))]
483  pub const fn new(kind: i32, limit: i32) -> Self {
484    Self { kind, limit }
485  }
486  /// The type integer the entry carried.
487  #[cfg_attr(not(tarpaulin), inline(always))]
488  pub const fn kind(&self) -> i32 {
489    self.kind
490  }
491  /// How many side-data types this build names.
492  #[cfg_attr(not(tarpaulin), inline(always))]
493  pub const fn limit(&self) -> i32 {
494    self.limit
495  }
496}
497
498/// Payload for [`PacketBuildError::SideDataAlloc`].
499///
500/// FFmpeg refused the side-data allocation.
501#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
502#[error("out of memory attaching {size} bytes of side data of type {kind}")]
503pub struct SideDataAlloc {
504  kind: i32,
505  size: usize,
506}
507
508impl SideDataAlloc {
509  /// Constructs a `SideDataAlloc` payload.
510  #[cfg_attr(not(tarpaulin), inline(always))]
511  pub const fn new(kind: i32, size: usize) -> Self {
512    Self { kind, size }
513  }
514  /// The entry's type integer.
515  #[cfg_attr(not(tarpaulin), inline(always))]
516  pub const fn kind(&self) -> i32 {
517    self.kind
518  }
519  /// The entry's payload length.
520  #[cfg_attr(not(tarpaulin), inline(always))]
521  pub const fn size(&self) -> usize {
522    self.size
523  }
524}
525
526/// Payload for [`PacketBuildError::SendPayloadTooLarge`].
527///
528/// A compressed payload on its way **into** FFmpeg is larger than the
529/// session's budget allows.
530///
531/// Named for the direction: this is the send leg. Its outbound twin is
532/// [`PacketBufferError::PacketTooLarge`](crate::PacketBufferError),
533/// which judges the same quantity coming the other way, against the
534/// same seat.
535#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)]
536#[error("a {bytes}-byte packet payload exceeds the {limit}-byte budget on the way into FFmpeg")]
537pub struct SendPayloadTooLarge {
538  bytes: usize,
539  limit: usize,
540}
541
542impl SendPayloadTooLarge {
543  /// Constructs a `SendPayloadTooLarge` payload.
544  #[cfg_attr(not(tarpaulin), inline(always))]
545  pub const fn new(bytes: usize, limit: usize) -> Self {
546    Self { bytes, limit }
547  }
548  /// The payload length the caller handed over.
549  #[cfg_attr(not(tarpaulin), inline(always))]
550  pub const fn bytes(&self) -> usize {
551    self.bytes
552  }
553  /// The budget in force.
554  #[cfg_attr(not(tarpaulin), inline(always))]
555  pub const fn limit(&self) -> usize {
556    self.limit
557  }
558}
559
560/// Why a portable packet could not be rebuilt as an `AVPacket`.
561///
562/// The reverse direction is what feeds a decoder, so everything the
563/// forward direction captured has to survive it or the capture was
564/// theatre.
565#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error, IsVariant, Unwrap, TryUnwrap)]
566#[unwrap(ref, ref_mut)]
567#[try_unwrap(ref, ref_mut)]
568pub enum PacketBuildError {
569  /// The payload is larger than the session's budget allows. Refused
570  /// **before** the `AVPacket` is allocated — the into-FFmpeg leg of
571  /// the budget the outbound leg already kept.
572  #[error(transparent)]
573  SendPayloadTooLarge(#[from] SendPayloadTooLarge),
574
575  /// The packet body could not be allocated or is larger than
576  /// `AVPacket.size` can hold.
577  #[error(transparent)]
578  Ffmpeg(#[from] ffmpeg_next::Error),
579
580  /// A side-data entry whose type this build of FFmpeg does not name.
581  #[error(transparent)]
582  UnknownSideData(#[from] UnknownSideData),
583
584  /// FFmpeg refused the side-data allocation.
585  #[error(transparent)]
586  SideDataAlloc(#[from] SideDataAlloc),
587
588  /// The packet is marked `AV_PKT_FLAG_TRUSTED`. See
589  /// [`crate::buffer::TrustedPayload`] — the other leg of the same
590  /// refusal.
591  #[error(transparent)]
592  TrustedPayload(#[from] crate::buffer::TrustedPayload),
593
594  /// The packet's side-data list is over the entry or byte cap the read
595  /// side has always applied. See [`SendSideDataTooLarge`].
596  #[error(transparent)]
597  SendSideDataTooLarge(#[from] SendSideDataTooLarge),
598}
599
600/// Judges the **whole** side-data list before a byte of it is
601/// allocated.
602///
603/// # One seat, both directions
604///
605/// The read side has capped frame and packet side data at 64 entries
606/// and 256 KiB since it was written; the send side had no cap at all.
607/// The preflight checked `body.len()` and then `attach_side_data`
608/// allocated every entry the caller handed over, one
609/// `av_packet_new_side_data` at a time, with nothing bounding the count
610/// or the total — so bytes the demux boundary would have refused coming
611/// *out* of a container went straight into libavcodec going *in*. That
612/// is the same asymmetry `PacketLimits` was introduced to close for the
613/// packet body, one field along.
614///
615/// So the caps are the read side's, applied here, before anything is
616/// allocated — including the body, because a list that cannot be
617/// carried should not cost a packet allocation first.
618fn check_side_data_budget(entries: &[SideDataEntry]) -> Result<(), PacketBuildError> {
619  use crate::convert::{SIDE_DATA_MAX_ENTRIES, SIDE_DATA_MAX_TOTAL_BYTES};
620
621  if entries.len() > SIDE_DATA_MAX_ENTRIES {
622    return Err(PacketBuildError::SendSideDataTooLarge(
623      SendSideDataTooLarge::new_entries(entries.len(), SIDE_DATA_MAX_ENTRIES),
624    ));
625  }
626  let mut total: usize = 0;
627  for entry in entries {
628    total = total.saturating_add(entry.data().len());
629    if total > SIDE_DATA_MAX_TOTAL_BYTES {
630      return Err(PacketBuildError::SendSideDataTooLarge(
631        SendSideDataTooLarge::new_bytes(total, SIDE_DATA_MAX_TOTAL_BYTES),
632      ));
633    }
634  }
635  Ok(())
636}
637
638/// Payload for [`PacketBuildError::SendSideDataTooLarge`].
639///
640/// A side-data list too long or too large to carry into a decoder.
641/// Carries whichever of the two caps was reached, so a caller can tell
642/// "too many annotations" from "too much annotation".
643#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
644#[error("packet side data is over the {what} ceiling: {value} against {limit}")]
645pub struct SendSideDataTooLarge {
646  what: &'static str,
647  value: usize,
648  limit: usize,
649}
650
651impl SendSideDataTooLarge {
652  /// The entry-count cap was reached.
653  #[inline]
654  pub const fn new_entries(value: usize, limit: usize) -> Self {
655    Self {
656      what: "entry-count",
657      value,
658      limit,
659    }
660  }
661  /// The aggregate-byte cap was reached.
662  #[inline]
663  pub const fn new_bytes(value: usize, limit: usize) -> Self {
664    Self {
665      what: "byte",
666      value,
667      limit,
668    }
669  }
670  /// The count or byte total the list reached.
671  #[inline]
672  pub const fn value(&self) -> usize {
673    self.value
674  }
675  /// The cap in force.
676  #[inline]
677  pub const fn limit(&self) -> usize {
678    self.limit
679  }
680  /// Which cap: `"entry-count"` or `"byte"`.
681  #[inline]
682  pub const fn what(&self) -> &'static str {
683    self.what
684  }
685}
686
687/// Refuses a portable packet whose flags carry `AV_PKT_FLAG_TRUSTED`.
688///
689/// The rebuild leg of the refusal `payload_of` makes on the way out.
690/// Closing only the copy-out leg would leave the loop open: a flag that
691/// reached a portable packet by some other route (a caller composing
692/// one by hand, a future producer, a graph that round-trips flags it
693/// does not interpret) would be written back onto a fresh `AVPacket` by
694/// `write_md_flags` and handed to a decoder entitled to believe it.
695///
696/// See [`crate::buffer::TrustedPayload`] for why the flag makes the
697/// payload uncarriable rather than merely suspicious.
698fn refuse_trusted(flags: MdPacketFlags, len: usize) -> std::result::Result<(), PacketBuildError> {
699  if flags.bits() & crate::buffer::TRUSTED_BIT != 0 {
700    return Err(PacketBuildError::TrustedPayload(
701      crate::buffer::TrustedPayload::new(len),
702    ));
703  }
704  Ok(())
705}
706
707/// Copies `entries` onto an `AVPacket` under construction.
708///
709/// A decoder learns things only this way: `AV_PKT_DATA_NEW_EXTRADATA`
710/// replaces its parameters mid-stream, `AV_PKT_DATA_PARAM_CHANGE` moves
711/// its rate or layout, `AV_PKT_DATA_SKIP_SAMPLES` is the encoder-delay
712/// trim without which a gapless stream is not gapless. Rebuilding a
713/// packet without them hands the decoder a body and a lie.
714fn attach_side_data(out: &mut Packet, entries: &[SideDataEntry]) -> Result<(), PacketBuildError> {
715  use ffmpeg_next::packet::Mut;
716  for entry in entries {
717    let kind = entry.kind();
718    let size = entry.data().len();
719    // SAFETY: `out` owns a live `AVPacket`; `packet_new_side_data`
720    // validates the type against this build's own range before handing
721    // it to C and reports a failed allocation as `None`.
722    let slot = unsafe { crate::ffi::packet_new_side_data(out.as_mut_ptr(), kind, size) };
723    let Some(slot) = slot else {
724      let limit = crate::ffi::side_data_type_count();
725      return Err(if kind < 0 || kind >= limit {
726        PacketBuildError::UnknownSideData(UnknownSideData::new(kind, limit))
727      } else {
728        PacketBuildError::SideDataAlloc(SideDataAlloc::new(kind, size))
729      });
730    };
731    if size > 0 {
732      // SAFETY: FFmpeg just allocated `size` bytes at `slot` (plus its
733      // padding), and `entry.data()` is a `&[u8]` of exactly that
734      // length; the two regions belong to different allocations.
735      unsafe { core::ptr::copy_nonoverlapping(entry.data().as_ptr(), slot, size) };
736    }
737  }
738  Ok(())
739}
740
741/// Builds an `AVPacket` that **shares** a view carrier's buffer instead
742/// of copying it — the send half of the zero-copy chain.
743///
744/// # What the census found
745///
746/// 0.8 did not have this. Its reverse builders called the same
747/// `try_packet_copy` the owned lane uses, so a packet demuxed
748/// zero-copy was copied again on its way back into a decoder. The
749/// zero-copy chain 0.8 actually shipped was demux to consumer, and
750/// stopped there.
751///
752/// # The padding proof
753///
754/// libavcodec is entitled to read `AV_INPUT_BUFFER_PADDING_SIZE` bytes
755/// **past** a packet's payload — bitstream readers do it routinely, and
756/// libavformat allocates that slack behind every packet it produces. A
757/// carrier viewing such a packet inherits the slack; a carrier this
758/// crate minted from a slice through
759/// [`FfmpegCarrier::from_bytes`](crate::FfmpegCarrier::from_bytes) does
760/// not.
761///
762/// So sharing is offered only where the slack is **provable**, and
763/// proving it takes two facts, not one:
764///
765/// * **provenance** — the carrier must be a payload captured out of an
766///   `AVPacket`'s own buffer, which is the only place libavformat's
767///   padding contract applies. Trailing *capacity* is not padding: a
768///   video plane has more pixels after it and a resampler's output
769///   frame has more samples, and a bitstream reader running past the
770///   payload would eat either as though it were bitstream. Provenance
771///   is recorded at capture, where it is known, rather than inferred
772///   here from a size comparison that cannot tell padding from a
773///   neighbour;
774/// * **extent** — the view must still leave at least the padding
775///   between its end and the end of the buffer, because a payload can
776///   be narrowed after capture.
777///
778/// Where either fails, the packet is copied, which is what
779/// `try_packet_copy` allocates the padding for. Handing a decoder an
780/// unpadded buffer would be an out-of-bounds read inside somebody
781/// else's bitstream reader, found by nobody.
782pub(crate) fn share_or_copy(
783  body: &crate::FfmpegBuffer,
784) -> std::result::Result<Packet, ffmpeg_next::Error> {
785  use ffmpeg_next::packet::Mut;
786
787  const PADDING: usize = ffmpeg_next::ffi::AV_INPUT_BUFFER_PADDING_SIZE as usize;
788
789  // **Empty first, before anything is dereferenced.** The empty carrier
790  // is deliberately backed by no buffer at all — that is what makes a
791  // placeholder plane slot free — so `as_av_buffer_ref` answers null
792  // for it, and reading `size` through that would be undefined on the
793  // most ordinary packet there is: a side-data-only one.
794  if body.is_empty() {
795    return try_packet_copy(&[]);
796  }
797
798  // Provenance before extent: the cheaper question, and the one that
799  // decides whether the other is worth asking.
800  if body.origin() != crate::view::Origin::PacketPayload {
801    return try_packet_copy(body.as_ref());
802  }
803
804  let buf = body.as_av_buffer_ref();
805  if buf.is_null() {
806    return try_packet_copy(body.as_ref());
807  }
808  // SAFETY: the carrier holds a live reference, just checked non-null;
809  // `size` is a public field on the `AVBufferRef` it names.
810  let capacity = unsafe { (*buf).size };
811  let end = body.offset().saturating_add(body.len());
812  let padded = capacity
813    .checked_sub(end)
814    .is_some_and(|slack| slack >= PADDING);
815
816  if !padded {
817    // No provable slack behind the view — copy, which allocates the
818    // padding libavcodec expects.
819    return try_packet_copy(body.as_ref());
820  }
821
822  let mut out = Packet::empty();
823  // SAFETY: `out` owns a live, zeroed `AVPacket`. `av_buffer_ref`
824  // returns a new reference to the same allocation — the packet owns
825  // that one and releases it on drop, while `body` keeps its own — and
826  // `data`/`size` are set to the view, which was proved to lie inside
827  // the buffer when the carrier was constructed.
828  unsafe {
829    let raw = out.as_mut_ptr();
830    let shared = ffmpeg_next::ffi::av_buffer_ref(buf.cast_mut());
831    if shared.is_null() {
832      return Err(ffmpeg_next::Error::Other {
833        errno: libc::ENOMEM,
834      });
835    }
836    (*raw).buf = shared;
837    (*raw).data = (*shared).data.add(body.offset());
838    (*raw).size = c_int::try_from(body.len()).map_err(|_| ffmpeg_next::Error::Other {
839      errno: libc::EINVAL,
840    })?;
841  }
842  Ok(out)
843}
844
845/// Builds an `ffmpeg::Packet` from a [`mediadecode::VideoPacket`]
846/// parameterized by [`crate::extras::VideoPacketExtra`] and
847/// `FfmpegBytes`.
848///
849/// The compressed bytes are **copied** into a new packet allocation.
850/// The return leg copies for the same reason the outbound one does:
851/// the carrier is Rust-owned memory with no `AVBufferRef` behind it to
852/// hand back.
853/// PTS / DTS / duration / flags / stream_index are propagated.
854///
855/// Side data on the extras is reattached to the rebuilt packet — see
856/// [`attach_side_data`] for why that is not optional.
857///
858/// Returns [`PacketBuildError`] on:
859/// * payload larger than `c_int::MAX` (would overflow `AVPacket.size`);
860/// * `av_new_packet` allocation failure (OOM);
861/// * a side-data entry this build of FFmpeg cannot name, or one whose
862///   allocation failed.
863pub fn ffmpeg_packet_from_video_packet(
864  packet: &mediadecode::packet::VideoPacket<VideoPacketExtra, crate::FfmpegBuffer>,
865  limits: PacketLimits,
866) -> std::result::Result<Packet, PacketBuildError> {
867  build_video_packet::<crate::View>(packet, limits, BodyRoute::Copy)
868}
869
870/// [`ffmpeg_packet_from_video_packet`] on the owned lane.
871pub fn ffmpeg_packet_from_owned_video_packet(
872  packet: &mediadecode::packet::VideoPacket<VideoPacketExtra, FfmpegBytes>,
873  limits: PacketLimits,
874) -> std::result::Result<Packet, PacketBuildError> {
875  build_video_packet::<crate::Owned>(packet, limits, BodyRoute::Copy)
876}
877
878/// [`ffmpeg_packet_from_video_packet`] built for **submission**, and
879/// scoped so the packet cannot outlive the call.
880///
881/// This is the only road on which the view lane may hand a decoder
882/// its own buffer rather than a copy, and the scoping is why it may.
883/// An `ffmpeg_next::Packet` lends `&mut [u8]` through `data_mut`,
884/// while the carrier it was built from still lends `&[u8]` — so a
885/// shared body reachable from a value a caller *holds* is two live
886/// references to one allocation with one of them mutable, out of
887/// entirely safe code, and `!Sync` would not save it either. Here the
888/// packet is built, lent to `submit` as `&Packet`, and dropped before
889/// this function returns: no value that can produce a `&mut` into the
890/// shared bytes ever exists.
891///
892/// **Which is why the route is the caller's to choose.** "Dropped
893/// before this returns" is a claim about this function, and a decoder
894/// that *records* what it is sent makes it false: the video decoder's
895/// hardware probe `av_packet_ref`s every accepted packet into a rescue
896/// history, and `FallbackFailed::unconsumed_packets` hands those
897/// recordings to the caller as owned, **mutable** `Packet`s. A
898/// submission that could be recorded must therefore carry
899/// [`BodyRoute::Copy`], so that what enters the history is storage
900/// nobody else reads. Callers with no history — every software road,
901/// and the hardware road after commit — pass
902/// [`BodyRoute::Submission`] and keep the zero-copy send.
903pub(crate) fn with_ffmpeg_video_packet<C: crate::FfmpegCarrier + crate::CarrierOps, T>(
904  packet: &mediadecode::packet::VideoPacket<VideoPacketExtra, C::Buffer>,
905  limits: PacketLimits,
906  route: BodyRoute,
907  submit: impl FnOnce(&Packet) -> T,
908) -> std::result::Result<T, PacketBuildError> {
909  let av_packet = build_video_packet::<C>(packet, limits, route)?;
910  let out = submit(&av_packet);
911  drop(av_packet);
912  Ok(out)
913}
914
915fn build_video_packet<C: crate::FfmpegCarrier + crate::CarrierOps>(
916  packet: &mediadecode::packet::VideoPacket<VideoPacketExtra, C::Buffer>,
917  limits: PacketLimits,
918  route: BodyRoute,
919) -> std::result::Result<Packet, PacketBuildError> {
920  let body = packet.data().as_ref();
921  // Before the budget and before the allocation: an uncarriable
922  // payload is not made carriable by fitting.
923  refuse_trusted(packet.flags(), body.len())?;
924  // And the annotations, judged whole before the body is allocated —
925  // a list that cannot be carried should not cost a packet first.
926  check_side_data_budget(packet.extra().side_data())?;
927  // **The into-FFmpeg budget, before the allocation.** `try_packet_copy`
928  // duplicates these bytes into a fresh `AVPacket` and checks only that
929  // they fit `c_int`. Without this the configured ceiling was dead on
930  // the road a caller feeds a decoder directly: bytes the demux
931  // boundary would have refused went straight into libavcodec.
932  if body.len() > limits.max_packet_bytes() {
933    return Err(PacketBuildError::SendPayloadTooLarge(
934      SendPayloadTooLarge::new(body.len(), limits.max_packet_bytes()),
935    ));
936  }
937  let mut out = C::packet_body(packet.data(), route)?;
938  attach_side_data(&mut out, packet.extra().side_data())?;
939  if let Some(ts) = packet.pts() {
940    out.set_pts(Some(ts.pts()));
941  }
942  if let Some(ts) = packet.dts() {
943    out.set_dts(Some(ts.pts()));
944  }
945  if let Some(d) = packet.duration() {
946    out.set_duration(d.pts());
947  }
948  // SAFETY: `out` owns the `AVPacket` `try_packet_copy` just built.
949  unsafe { write_md_flags(&mut out, packet.flags()) };
950  out.set_stream(packet.extra().stream_index() as usize);
951  Ok(out)
952}
953
954/// Builds an `ffmpeg::Packet` from a [`mediadecode::AudioPacket`].
955/// Same shape as [`ffmpeg_packet_from_video_packet`] — bytes are
956/// copied; pts/dts/duration/flags/stream_index and side data are
957/// forwarded. Same failure modes.
958pub fn ffmpeg_packet_from_audio_packet(
959  packet: &mediadecode::packet::AudioPacket<AudioPacketExtra, crate::FfmpegBuffer>,
960  limits: PacketLimits,
961) -> std::result::Result<Packet, PacketBuildError> {
962  build_audio_packet::<crate::View>(packet, limits, BodyRoute::Copy)
963}
964
965/// [`ffmpeg_packet_from_audio_packet`] on the owned lane.
966pub fn ffmpeg_packet_from_owned_audio_packet(
967  packet: &mediadecode::packet::AudioPacket<AudioPacketExtra, FfmpegBytes>,
968  limits: PacketLimits,
969) -> std::result::Result<Packet, PacketBuildError> {
970  build_audio_packet::<crate::Owned>(packet, limits, BodyRoute::Copy)
971}
972
973/// [`ffmpeg_packet_from_audio_packet`] built for **submission**, and
974/// scoped so the packet cannot outlive the call.
975///
976/// This is the only road on which the view lane may hand a decoder
977/// its own buffer rather than a copy, and the scoping is why it may.
978/// An `ffmpeg_next::Packet` lends `&mut [u8]` through `data_mut`,
979/// while the carrier it was built from still lends `&[u8]` — so a
980/// shared body reachable from a value a caller *holds* is two live
981/// references to one allocation with one of them mutable, out of
982/// entirely safe code, and `!Sync` would not save it either. Here the
983/// packet is built, lent to `submit` as `&Packet`, and dropped before
984/// this function returns: no value that can produce a `&mut` into the
985/// shared bytes ever exists.
986pub(crate) fn with_ffmpeg_audio_packet<C: crate::FfmpegCarrier + crate::CarrierOps, T>(
987  packet: &mediadecode::packet::AudioPacket<AudioPacketExtra, C::Buffer>,
988  limits: PacketLimits,
989  route: BodyRoute,
990  submit: impl FnOnce(&Packet) -> T,
991) -> std::result::Result<T, PacketBuildError> {
992  let av_packet = build_audio_packet::<C>(packet, limits, route)?;
993  let out = submit(&av_packet);
994  drop(av_packet);
995  Ok(out)
996}
997
998fn build_audio_packet<C: crate::FfmpegCarrier + crate::CarrierOps>(
999  packet: &mediadecode::packet::AudioPacket<AudioPacketExtra, C::Buffer>,
1000  limits: PacketLimits,
1001  route: BodyRoute,
1002) -> std::result::Result<Packet, PacketBuildError> {
1003  let body = packet.data().as_ref();
1004  // Before the budget and before the allocation: an uncarriable
1005  // payload is not made carriable by fitting.
1006  refuse_trusted(packet.flags(), body.len())?;
1007  // And the annotations, judged whole before the body is allocated —
1008  // a list that cannot be carried should not cost a packet first.
1009  check_side_data_budget(packet.extra().side_data())?;
1010  // **The into-FFmpeg budget, before the allocation.** `try_packet_copy`
1011  // duplicates these bytes into a fresh `AVPacket` and checks only that
1012  // they fit `c_int`. Without this the configured ceiling was dead on
1013  // the road a caller feeds a decoder directly: bytes the demux
1014  // boundary would have refused went straight into libavcodec.
1015  if body.len() > limits.max_packet_bytes() {
1016    return Err(PacketBuildError::SendPayloadTooLarge(
1017      SendPayloadTooLarge::new(body.len(), limits.max_packet_bytes()),
1018    ));
1019  }
1020  let mut out = C::packet_body(packet.data(), route)?;
1021  attach_side_data(&mut out, packet.extra().side_data())?;
1022  if let Some(ts) = packet.pts() {
1023    out.set_pts(Some(ts.pts()));
1024  }
1025  if let Some(ts) = packet.dts() {
1026    out.set_dts(Some(ts.pts()));
1027  }
1028  if let Some(d) = packet.duration() {
1029    out.set_duration(d.pts());
1030  }
1031  // SAFETY: `out` owns the `AVPacket` `try_packet_copy` just built.
1032  unsafe { write_md_flags(&mut out, packet.flags()) };
1033  out.set_stream(packet.extra().stream_index() as usize);
1034  Ok(out)
1035}
1036
1037/// Builds an `ffmpeg::Packet` from a [`mediadecode::SubtitlePacket`].
1038/// Bytes copied; pts/duration/flags/stream_index and side data
1039/// forwarded. Subtitle packets have no `dts` in the mediadecode model.
1040/// Same failure modes as [`ffmpeg_packet_from_video_packet`].
1041pub fn ffmpeg_packet_from_subtitle_packet(
1042  packet: &mediadecode::packet::SubtitlePacket<SubtitlePacketExtra, crate::FfmpegBuffer>,
1043  limits: PacketLimits,
1044) -> std::result::Result<Packet, PacketBuildError> {
1045  build_subtitle_packet::<crate::View>(packet, limits, BodyRoute::Copy)
1046}
1047
1048/// [`ffmpeg_packet_from_subtitle_packet`] on the owned lane.
1049pub fn ffmpeg_packet_from_owned_subtitle_packet(
1050  packet: &mediadecode::packet::SubtitlePacket<SubtitlePacketExtra, FfmpegBytes>,
1051  limits: PacketLimits,
1052) -> std::result::Result<Packet, PacketBuildError> {
1053  build_subtitle_packet::<crate::Owned>(packet, limits, BodyRoute::Copy)
1054}
1055
1056/// [`ffmpeg_packet_from_subtitle_packet`] built for **submission**, and
1057/// scoped so the packet cannot outlive the call.
1058///
1059/// This is the only road on which the view lane may hand a decoder
1060/// its own buffer rather than a copy, and the scoping is why it may.
1061/// An `ffmpeg_next::Packet` lends `&mut [u8]` through `data_mut`,
1062/// while the carrier it was built from still lends `&[u8]` — so a
1063/// shared body reachable from a value a caller *holds* is two live
1064/// references to one allocation with one of them mutable, out of
1065/// entirely safe code, and `!Sync` would not save it either. Here the
1066/// packet is built, lent to `submit` as `&Packet`, and dropped before
1067/// this function returns: no value that can produce a `&mut` into the
1068/// shared bytes ever exists.
1069pub(crate) fn with_ffmpeg_subtitle_packet<C: crate::FfmpegCarrier + crate::CarrierOps, T>(
1070  packet: &mediadecode::packet::SubtitlePacket<SubtitlePacketExtra, C::Buffer>,
1071  limits: PacketLimits,
1072  route: BodyRoute,
1073  submit: impl FnOnce(&Packet) -> T,
1074) -> std::result::Result<T, PacketBuildError> {
1075  let av_packet = build_subtitle_packet::<C>(packet, limits, route)?;
1076  let out = submit(&av_packet);
1077  drop(av_packet);
1078  Ok(out)
1079}
1080
1081fn build_subtitle_packet<C: crate::FfmpegCarrier + crate::CarrierOps>(
1082  packet: &mediadecode::packet::SubtitlePacket<SubtitlePacketExtra, C::Buffer>,
1083  limits: PacketLimits,
1084  route: BodyRoute,
1085) -> std::result::Result<Packet, PacketBuildError> {
1086  let body = packet.data().as_ref();
1087  // Before the budget and before the allocation: an uncarriable
1088  // payload is not made carriable by fitting.
1089  refuse_trusted(packet.flags(), body.len())?;
1090  // And the annotations, judged whole before the body is allocated —
1091  // a list that cannot be carried should not cost a packet first.
1092  check_side_data_budget(packet.extra().side_data())?;
1093  // **The into-FFmpeg budget, before the allocation.** `try_packet_copy`
1094  // duplicates these bytes into a fresh `AVPacket` and checks only that
1095  // they fit `c_int`. Without this the configured ceiling was dead on
1096  // the road a caller feeds a decoder directly: bytes the demux
1097  // boundary would have refused went straight into libavcodec.
1098  if body.len() > limits.max_packet_bytes() {
1099    return Err(PacketBuildError::SendPayloadTooLarge(
1100      SendPayloadTooLarge::new(body.len(), limits.max_packet_bytes()),
1101    ));
1102  }
1103  let mut out = C::packet_body(packet.data(), route)?;
1104  attach_side_data(&mut out, packet.extra().side_data())?;
1105  if let Some(ts) = packet.pts() {
1106    out.set_pts(Some(ts.pts()));
1107  }
1108  if let Some(d) = packet.duration() {
1109    out.set_duration(d.pts());
1110  }
1111  // SAFETY: `out` owns the `AVPacket` `try_packet_copy` just built.
1112  unsafe { write_md_flags(&mut out, packet.flags()) };
1113  out.set_stream(packet.extra().stream_index() as usize);
1114  Ok(out)
1115}
1116
1117// ---------------------------------------------------------------------------
1118//  Safe wrappers — `&ffmpeg::Packet` → `mediadecode::*Packet`.
1119// ---------------------------------------------------------------------------
1120
1121/// Carries a borrowed [`ffmpeg::Packet`] out as a
1122/// [`mediadecode::packet::VideoPacket`] on the **view** lane: the
1123/// payload is a refcounted window onto the source `AVPacket`'s own
1124/// buffer, which is why the delivered packet's lifetime is answerable
1125/// to libavformat's — see [the two carrier lanes][lanes]. For a packet
1126/// that must outlive the demuxer, ask for the owned lane by name:
1127/// `video_packet_from_ffmpeg_as::<Owned>`, whose payload is copied and
1128/// which the [D-seat amputation contract][law] governs.
1129///
1130/// Timestamps, duration, key/corrupt flags, and the source stream index
1131/// are forwarded to the produced packet.
1132///
1133/// Uses the default [`PacketLimits`]; the `_in` sibling takes them
1134/// explicitly, alongside the stream's timebase.
1135///
1136/// [lanes]: mediadecode::adapter#the-two-carrier-lanes
1137///
1138/// Returns `Ok(None)` when the source packet has no payload at all
1139/// (an empty packet — typical after EOF), and [`PacketBufferError`]
1140/// when a payload that *is* there could not be carried — over budget,
1141/// or claiming bytes outside its own buffer. Those are never the same
1142/// answer. Caller can also fill in [`VideoPacketExtra::byte_pos`] /
1143/// `side_data` post-construction if they need those.
1144///
1145/// [law]: mediadecode::adapter#the-d-seat-amputation-contract
1146pub fn video_packet_from_ffmpeg(
1147  packet: &Packet,
1148) -> Result<Option<VideoPacket<VideoPacketExtra, FfmpegBytes>>, PacketBufferError> {
1149  video_packet_from_borrowed::<crate::Owned>(
1150    packet,
1151    mediadecode::Timebase::default(),
1152    PacketLimits::default(),
1153    crate::buffer::PayloadProvenance::CallerSupplied,
1154  )
1155}
1156
1157/// Carries a borrowed [`ffmpeg::Packet`] out as a
1158/// [`mediadecode::packet::AudioPacket`]. Same shape as
1159/// [`video_packet_from_ffmpeg`] — shared payload, forwarded metadata,
1160/// default budgets.
1161pub fn audio_packet_from_ffmpeg(
1162  packet: &Packet,
1163) -> Result<Option<AudioPacket<AudioPacketExtra, FfmpegBytes>>, PacketBufferError> {
1164  audio_packet_from_borrowed::<crate::Owned>(
1165    packet,
1166    mediadecode::Timebase::default(),
1167    PacketLimits::default(),
1168    crate::buffer::PayloadProvenance::CallerSupplied,
1169  )
1170}
1171
1172/// Carries a borrowed [`ffmpeg::Packet`] out as a
1173/// [`mediadecode::packet::SubtitlePacket`]. Subtitle packets have no
1174/// `dts` in the mediadecode model; everything else mirrors
1175/// [`video_packet_from_ffmpeg`], shared payload included.
1176pub fn subtitle_packet_from_ffmpeg(
1177  packet: &Packet,
1178) -> Result<Option<SubtitlePacket<SubtitlePacketExtra, FfmpegBytes>>, PacketBufferError> {
1179  subtitle_packet_from_borrowed::<crate::Owned>(
1180    packet,
1181    mediadecode::Timebase::default(),
1182    PacketLimits::default(),
1183    crate::buffer::PayloadProvenance::CallerSupplied,
1184  )
1185}
1186
1187/// The most side-data entries this crate will walk on one packet.
1188///
1189/// The floor is [`SIDE_DATA_MAX_ENTRIES`], the same bound the
1190/// frame-side collector uses; it rises with `AV_PKT_DATA_NB` so a
1191/// future FFmpeg that names more side-data types than the floor cannot
1192/// turn a legitimate packet into a refusal. Measured: FFmpeg's own
1193/// packet API cannot exceed one entry per named type — both
1194/// `av_packet_new_side_data` and `av_packet_add_side_data` replace an
1195/// existing entry of the same type — so a packet over this cap is one
1196/// no FFmpeg call produced.
1197fn side_data_entry_cap() -> usize {
1198  SIDE_DATA_MAX_ENTRIES.max(crate::ffi::side_data_type_count().max(0) as usize)
1199}
1200
1201/// The side-data entries an `AVPacket` carries, copied into owned
1202/// values — **all of them, or none and an error**.
1203///
1204/// The packet twin of `convert::collect_side_data`, and bounded the
1205/// same way: at most [`side_data_entry_cap`] entries and
1206/// [`SIDE_DATA_MAX_TOTAL_BYTES`] bytes per packet, allocated through
1207/// `try_reserve_exact`. What is *not* the same is what happens when a
1208/// bound is reached. The frame collector truncates and warns, which it
1209/// can afford to — frame side data is descriptive, the frame is
1210/// delivered either way, and nothing downstream acts on it. Packet side
1211/// data is the opposite: `NEW_EXTRADATA` replaces a decoder's
1212/// parameters, `PARAM_CHANGE` moves its rate, `SKIP_SAMPLES` trims the
1213/// stream, and the codec acts on every one. A truncated copy is a
1214/// decoder quietly running on stale parameters, and a truncated copy of
1215/// a side-data-only packet is `Ok(None)` — the packet vanishing
1216/// entirely, which is the very defect this seam was built to close. So
1217/// every bound here is an error, and a caller either gets a packet with
1218/// all of its side data or a `DemuxError` naming what stopped it.
1219///
1220/// `AVPacket.side_data` is a flat array of `AVPacketSideData` (not the
1221/// array of pointers an `AVFrame` keeps), and its `type_` is read as
1222/// the integer it is on the wire — a discriminant this build has no
1223/// name for would be undefined behaviour the moment it existed as an
1224/// `AVPacketSideDataType`.
1225fn packet_side_data(packet: &Packet) -> Result<Vec<SideDataEntry>, PacketBufferError> {
1226  use ffmpeg_next::packet::Ref;
1227  // SAFETY: `packet` keeps the `AVPacket` live; `side_data` and
1228  // `side_data_elems` are public fields.
1229  let count_raw = unsafe { (*packet.as_ptr()).side_data_elems };
1230  let entries = unsafe { (*packet.as_ptr()).side_data };
1231  // Zero entries is the only shape that means "no side data". Every
1232  // other reading of that answer — a malformed count, a missing array —
1233  // is judged, and judged *before* the pointer: a count this crate
1234  // cannot walk stays an error whether or not the array happens to be
1235  // null, and a null array with entries to read is malformed rather
1236  // than empty. Both used to leave here as `Ok(vec![])`, which is the
1237  // silent loss the caps taught us to name, reached through the
1238  // pointer instead of the budget.
1239  if count_raw == 0 {
1240    return Ok(Vec::new());
1241  }
1242  let cap = side_data_entry_cap();
1243  if count_raw < 0 || count_raw as usize > cap {
1244    return Err(PacketBufferError::SideDataEntries(SideDataEntries::new(
1245      count_raw, cap,
1246    )));
1247  }
1248  if entries.is_null() {
1249    return Err(PacketBufferError::SideDataArray(SideDataArray::new(
1250      count_raw,
1251    )));
1252  }
1253  let count = count_raw as usize;
1254  let mut out: Vec<SideDataEntry> = Vec::new();
1255  if out.try_reserve_exact(count).is_err() {
1256    return Err(PacketBufferError::SideDataAlloc(BufferSideDataAlloc::new(
1257      count * core::mem::size_of::<SideDataEntry>(),
1258    )));
1259  }
1260  let mut total_bytes: usize = 0;
1261  for index in 0..count {
1262    // SAFETY: `entries` is valid for `count_raw` contiguous
1263    // `AVPacketSideData` values per FFmpeg's contract, and `index` is
1264    // below that count.
1265    let entry = unsafe { entries.add(index) };
1266    let kind = unsafe { read_unaligned(addr_of!((*entry).type_).cast::<i32>()) };
1267    let size = unsafe { (*entry).size };
1268    let data_ptr = unsafe { (*entry).data };
1269    let data = if size == 0 {
1270      // A marker entry: a type and no bytes. FFmpeg emits these, and
1271      // there is nothing to carry or to charge the budget for.
1272      FfmpegBytes::empty()
1273    } else if data_ptr.is_null() {
1274      // Bytes declared and not carried. Reading this as an empty entry
1275      // delivered a packet whose side data was a lie, and charged the
1276      // budget nothing for it.
1277      return Err(PacketBufferError::SideDataPayload(SideDataPayload::new(
1278        index, size,
1279      )));
1280    } else {
1281      total_bytes = total_bytes.saturating_add(size);
1282      if total_bytes > SIDE_DATA_MAX_TOTAL_BYTES {
1283        return Err(PacketBufferError::SideDataBytes(SideDataBytes::new(
1284          total_bytes,
1285          SIDE_DATA_MAX_TOTAL_BYTES,
1286        )));
1287      }
1288      let mut buf: Vec<u8> = Vec::new();
1289      if buf.try_reserve_exact(size).is_err() {
1290        return Err(PacketBufferError::SideDataAlloc(BufferSideDataAlloc::new(
1291          size,
1292        )));
1293      }
1294      // SAFETY: `data` is valid for `size` bytes per FFmpeg's
1295      // `AVPacketSideData` contract.
1296      buf.extend_from_slice(unsafe { core::slice::from_raw_parts(data_ptr, size) });
1297      // Staged through the `Vec` so `try_reserve_exact` keeps *one* of
1298      // the two payload-sized allocations a named refusal rather than
1299      // an abort. The carrier copy that follows is a second full
1300      // allocation of the same size — not a header — and it is
1301      // infallible, so what the staging really buys is that the first
1302      // and larger risk is reportable and the second is asked for a
1303      // size the allocator has just proved it has. The doubling is
1304      // affordable only because side data is capped at
1305      // [`SIDE_DATA_MAX_TOTAL_BYTES`]; the plane paths, which are not
1306      // small, use the one-allocation road instead.
1307      FfmpegBytes::copy_from_slice(&buf)
1308    };
1309    out.push(SideDataEntry::new(kind, data));
1310  }
1311  Ok(out)
1312}
1313
1314/// The buffer a timed packet is delivered with, or `None` when there is
1315/// no packet to deliver at all.
1316///
1317/// **`size == 0` is not the same as "nothing".** A packet with no body
1318/// and one or more side-data entries is a real packet: FFmpeg uses that
1319/// shape for `AV_PKT_DATA_NEW_EXTRADATA` and for a parameter change,
1320/// and a decoder that never sees it keeps decoding on parameters the
1321/// container has already replaced. Such a packet is delivered with an
1322/// owned empty buffer — zero bytes, but a buffer, so the packet exists
1323/// and its side data rides the extras.
1324///
1325/// `Ok(None)` therefore means the packet carried neither a payload nor
1326/// side data: the empty marker, and the only thing a pull loop may skip.
1327fn delivered_payload<C: crate::FfmpegCarrier + crate::CarrierOps>(
1328  packet: &Packet,
1329  side_data: &[SideDataEntry],
1330  limits: PacketLimits,
1331  provenance: crate::buffer::PayloadProvenance,
1332) -> Result<Option<C::Buffer>, PacketBufferError> {
1333  use ffmpeg_next::packet::Ref;
1334  // SAFETY: `packet` keeps the AVPacket live for the duration of this
1335  // call, which is all `payload_of` requires.
1336  if let Some(bytes) = unsafe {
1337    crate::buffer::payload_of::<C>(packet.as_ptr(), limits.max_packet_bytes(), provenance)
1338  }? {
1339    return Ok(Some(bytes));
1340  }
1341  if side_data.is_empty() {
1342    return Ok(None);
1343  }
1344  Ok(Some(C::empty()))
1345}
1346
1347// ---------------------------------------------------------------------------
1348//  Timebase-carrying variants.
1349//
1350//  An `AVPacket`'s timestamps are integers in its *stream's* timebase,
1351//  which the packet does not carry — the four functions above therefore
1352//  stamp `Timebase::default()` (1/1), leaving the caller to know what
1353//  the ticks meant. A demuxer knows: it holds the track table. These
1354//  variants take that timebase, so the produced `Timestamp` is a
1355//  complete, self-describing value.
1356// ---------------------------------------------------------------------------
1357
1358/// [`video_packet_from_ffmpeg`], with the stream's timebase stamped
1359/// onto every timestamp instead of the 1/1 placeholder.
1360pub(crate) fn video_packet_from_ffmpeg_as<C: crate::FfmpegCarrier + crate::CarrierOps>(
1361  source: Packet,
1362  time_base: mediadecode::Timebase,
1363  limits: PacketLimits,
1364  provenance: crate::buffer::PayloadProvenance,
1365) -> Result<Option<VideoPacket<VideoPacketExtra, C::Buffer>>, PacketBufferError> {
1366  // **The source is consumed.** A borrowed `AVPacket` cannot be
1367  // safely viewed: `ffmpeg_next::Packet` lends `&mut [u8]` through
1368  // `data_mut` and shares its buffer by refcount with no
1369  // copy-on-write, so a caller who kept the packet would hold a
1370  // mutable alias of every byte the returned carrier reads — and
1371  // both sides are `Send`. Taking it by value is what makes that
1372  // unconstructible; the packet is released when this returns, and
1373  // the view lane's carrier keeps the buffer alive by its own
1374  // reference. See the borrowed siblings for the owned lane, which
1375  // copies and so may borrow.
1376  video_packet_from_borrowed::<C>(&source, time_base, limits, provenance)
1377}
1378
1379/// The shared implementation of the video road.
1380///
1381/// Crate-private, and borrowed: the two public doors differ only in
1382/// what they owe the borrow checker. The consuming one may be asked
1383/// for either lane; the borrowing one is the owned lane, where the
1384/// bytes are copied and the source is nobody's concern afterwards.
1385pub(crate) fn video_packet_from_borrowed<C: crate::FfmpegCarrier + crate::CarrierOps>(
1386  packet: &Packet,
1387  time_base: mediadecode::Timebase,
1388  limits: PacketLimits,
1389  provenance: crate::buffer::PayloadProvenance,
1390) -> Result<Option<VideoPacket<VideoPacketExtra, C::Buffer>>, PacketBufferError> {
1391  let side_data = packet_side_data(packet)?;
1392  let Some(buf) = delivered_payload::<C>(packet, &side_data, limits, provenance)? else {
1393    return Ok(None);
1394  };
1395  let extra = VideoPacketExtra::new(packet.stream() as i32).with_side_data(side_data);
1396  let mut out = VideoPacket::new(buf, extra)
1397    .with_flags(md_flags_from_packet(packet)?)
1398    .with_pts(packet.pts().map(|p| Timestamp::new(p, time_base)))
1399    .with_dts(packet.dts().map(|d| Timestamp::new(d, time_base)));
1400  let dur = packet.duration();
1401  if dur > 0 {
1402    out = out.with_duration(Some(Timestamp::new(dur, time_base)));
1403  }
1404  Ok(Some(out))
1405}
1406
1407/// [`audio_packet_from_ffmpeg`], with the stream's timebase stamped
1408/// onto every timestamp instead of the 1/1 placeholder.
1409pub(crate) fn audio_packet_from_ffmpeg_as<C: crate::FfmpegCarrier + crate::CarrierOps>(
1410  source: Packet,
1411  time_base: mediadecode::Timebase,
1412  limits: PacketLimits,
1413  provenance: crate::buffer::PayloadProvenance,
1414) -> Result<Option<AudioPacket<AudioPacketExtra, C::Buffer>>, PacketBufferError> {
1415  // **The source is consumed.** A borrowed `AVPacket` cannot be
1416  // safely viewed: `ffmpeg_next::Packet` lends `&mut [u8]` through
1417  // `data_mut` and shares its buffer by refcount with no
1418  // copy-on-write, so a caller who kept the packet would hold a
1419  // mutable alias of every byte the returned carrier reads — and
1420  // both sides are `Send`. Taking it by value is what makes that
1421  // unconstructible; the packet is released when this returns, and
1422  // the view lane's carrier keeps the buffer alive by its own
1423  // reference. See the borrowed siblings for the owned lane, which
1424  // copies and so may borrow.
1425  audio_packet_from_borrowed::<C>(&source, time_base, limits, provenance)
1426}
1427
1428/// The shared implementation of the audio road.
1429///
1430/// Crate-private, and borrowed: the two public doors differ only in
1431/// what they owe the borrow checker. The consuming one may be asked
1432/// for either lane; the borrowing one is the owned lane, where the
1433/// bytes are copied and the source is nobody's concern afterwards.
1434pub(crate) fn audio_packet_from_borrowed<C: crate::FfmpegCarrier + crate::CarrierOps>(
1435  packet: &Packet,
1436  time_base: mediadecode::Timebase,
1437  limits: PacketLimits,
1438  provenance: crate::buffer::PayloadProvenance,
1439) -> Result<Option<AudioPacket<AudioPacketExtra, C::Buffer>>, PacketBufferError> {
1440  let side_data = packet_side_data(packet)?;
1441  let Some(buf) = delivered_payload::<C>(packet, &side_data, limits, provenance)? else {
1442    return Ok(None);
1443  };
1444  let extra = AudioPacketExtra::new(packet.stream() as i32).with_side_data(side_data);
1445  let mut out = AudioPacket::new(buf, extra)
1446    .with_flags(md_flags_from_packet(packet)?)
1447    .with_pts(packet.pts().map(|p| Timestamp::new(p, time_base)))
1448    .with_dts(packet.dts().map(|d| Timestamp::new(d, time_base)));
1449  let dur = packet.duration();
1450  if dur > 0 {
1451    out = out.with_duration(Some(Timestamp::new(dur, time_base)));
1452  }
1453  Ok(Some(out))
1454}
1455
1456/// [`subtitle_packet_from_ffmpeg`], with the stream's timebase stamped
1457/// onto every timestamp instead of the 1/1 placeholder.
1458pub(crate) fn subtitle_packet_from_ffmpeg_as<C: crate::FfmpegCarrier + crate::CarrierOps>(
1459  source: Packet,
1460  time_base: mediadecode::Timebase,
1461  limits: PacketLimits,
1462  provenance: crate::buffer::PayloadProvenance,
1463) -> Result<Option<SubtitlePacket<SubtitlePacketExtra, C::Buffer>>, PacketBufferError> {
1464  // **The source is consumed.** A borrowed `AVPacket` cannot be
1465  // safely viewed: `ffmpeg_next::Packet` lends `&mut [u8]` through
1466  // `data_mut` and shares its buffer by refcount with no
1467  // copy-on-write, so a caller who kept the packet would hold a
1468  // mutable alias of every byte the returned carrier reads — and
1469  // both sides are `Send`. Taking it by value is what makes that
1470  // unconstructible; the packet is released when this returns, and
1471  // the view lane's carrier keeps the buffer alive by its own
1472  // reference. See the borrowed siblings for the owned lane, which
1473  // copies and so may borrow.
1474  subtitle_packet_from_borrowed::<C>(&source, time_base, limits, provenance)
1475}
1476
1477/// The shared implementation of the subtitle road.
1478///
1479/// Crate-private, and borrowed: the two public doors differ only in
1480/// what they owe the borrow checker. The consuming one may be asked
1481/// for either lane; the borrowing one is the owned lane, where the
1482/// bytes are copied and the source is nobody's concern afterwards.
1483pub(crate) fn subtitle_packet_from_borrowed<C: crate::FfmpegCarrier + crate::CarrierOps>(
1484  packet: &Packet,
1485  time_base: mediadecode::Timebase,
1486  limits: PacketLimits,
1487  provenance: crate::buffer::PayloadProvenance,
1488) -> Result<Option<SubtitlePacket<SubtitlePacketExtra, C::Buffer>>, PacketBufferError> {
1489  let side_data = packet_side_data(packet)?;
1490  let Some(buf) = delivered_payload::<C>(packet, &side_data, limits, provenance)? else {
1491    return Ok(None);
1492  };
1493  let extra = SubtitlePacketExtra::new(packet.stream() as i32).with_side_data(side_data);
1494  let mut out = SubtitlePacket::new(buf, extra)
1495    .with_flags(md_flags_from_packet(packet)?)
1496    .with_pts(packet.pts().map(|p| Timestamp::new(p, time_base)));
1497  let dur = packet.duration();
1498  if dur > 0 {
1499    out = out.with_duration(Some(Timestamp::new(dur, time_base)));
1500  }
1501  Ok(Some(out))
1502}
1503
1504/// Wraps a borrowed [`ffmpeg::Packet`] from a **data** track — timecode,
1505/// KLV, timed ID3 — as a [`mediadecode::demuxer::DataPacket`], with the
1506/// stream's timebase stamped onto every timestamp.
1507///
1508/// Data packets are never reordered, so the mediadecode model gives
1509/// them no `dts` seat; everything else mirrors
1510/// [`video_packet_from_ffmpeg_in`]. `byte_pos` is forwarded from
1511/// `AVPacket.pos`, which data consumers use to correlate a payload with
1512/// its position in the file.
1513pub(crate) fn data_packet_from_ffmpeg_as<C: crate::FfmpegCarrier + crate::CarrierOps>(
1514  source: Packet,
1515  time_base: mediadecode::Timebase,
1516  limits: PacketLimits,
1517  provenance: crate::buffer::PayloadProvenance,
1518) -> Result<Option<DataPacket<DataPacketExtra, C::Buffer>>, PacketBufferError> {
1519  // **The source is consumed.** A borrowed `AVPacket` cannot be
1520  // safely viewed: `ffmpeg_next::Packet` lends `&mut [u8]` through
1521  // `data_mut` and shares its buffer by refcount with no
1522  // copy-on-write, so a caller who kept the packet would hold a
1523  // mutable alias of every byte the returned carrier reads — and
1524  // both sides are `Send`. Taking it by value is what makes that
1525  // unconstructible; the packet is released when this returns, and
1526  // the view lane's carrier keeps the buffer alive by its own
1527  // reference. See the borrowed siblings for the owned lane, which
1528  // copies and so may borrow.
1529  data_packet_from_borrowed::<C>(&source, time_base, limits, provenance)
1530}
1531
1532/// The shared implementation of the data road.
1533///
1534/// Crate-private, and borrowed: the two public doors differ only in
1535/// what they owe the borrow checker. The consuming one may be asked
1536/// for either lane; the borrowing one is the owned lane, where the
1537/// bytes are copied and the source is nobody's concern afterwards.
1538pub(crate) fn data_packet_from_borrowed<C: crate::FfmpegCarrier + crate::CarrierOps>(
1539  packet: &Packet,
1540  time_base: mediadecode::Timebase,
1541  limits: PacketLimits,
1542  provenance: crate::buffer::PayloadProvenance,
1543) -> Result<Option<DataPacket<DataPacketExtra, C::Buffer>>, PacketBufferError> {
1544  let side_data = packet_side_data(packet)?;
1545  let Some(buf) = delivered_payload::<C>(packet, &side_data, limits, provenance)? else {
1546    return Ok(None);
1547  };
1548  let pos = packet.position();
1549  let extra = DataPacketExtra::new(packet.stream() as i32)
1550    .with_byte_pos((pos >= 0).then_some(pos as i64))
1551    .with_side_data(side_data);
1552  let mut out = DataPacket::new(buf, extra)
1553    .with_flags(md_flags_from_packet(packet)?)
1554    .with_pts(packet.pts().map(|p| Timestamp::new(p, time_base)));
1555  let dur = packet.duration();
1556  if dur > 0 {
1557    out = out.with_duration(Some(Timestamp::new(dur, time_base)));
1558  }
1559  Ok(Some(out))
1560}
1561
1562/// Wraps a borrowed [`ffmpeg::Packet`] from an **attachment** track —
1563/// cover art that the container really does store as a packet — as a
1564/// [`mediadecode::demuxer::AttachmentPacket`].
1565///
1566/// No timestamps are forwarded, and there is nowhere to put them: an
1567/// attachment is not on the timeline. `synthesized` is `false`, because
1568/// this payload came from a real packet; the demuxer sets it `true` for
1569/// the packets it builds out of codec extradata.
1570///
1571/// **The one arm where a payload-less packet really is nothing.** The
1572/// four timed conversions deliver a side-data-only packet, because
1573/// codec-control side data is content a decoder must see. An attachment
1574/// is not decoded and is not on a timeline: it *is* its bytes, so a
1575/// packet carrying none carries no attachment, and this answers
1576/// `Ok(None)`. `AttachmentPacketExtra` accordingly has no side-data
1577/// seat — a deliberate absence, not an oversight.
1578pub(crate) fn attachment_packet_from_ffmpeg_as<C: crate::FfmpegCarrier + crate::CarrierOps>(
1579  source: Packet,
1580  limits: PacketLimits,
1581  provenance: crate::buffer::PayloadProvenance,
1582) -> Result<Option<AttachmentPacket<AttachmentPacketExtra, C::Buffer>>, PacketBufferError> {
1583  // **The source is consumed.** A borrowed `AVPacket` cannot be
1584  // safely viewed: `ffmpeg_next::Packet` lends `&mut [u8]` through
1585  // `data_mut` and shares its buffer by refcount with no
1586  // copy-on-write, so a caller who kept the packet would hold a
1587  // mutable alias of every byte the returned carrier reads — and
1588  // both sides are `Send`. Taking it by value is what makes that
1589  // unconstructible; the packet is released when this returns, and
1590  // the view lane's carrier keeps the buffer alive by its own
1591  // reference. See the borrowed siblings for the owned lane, which
1592  // copies and so may borrow.
1593  attachment_packet_from_borrowed::<C>(&source, limits, provenance)
1594}
1595
1596/// The shared implementation of the attachment road.
1597///
1598/// Crate-private, and borrowed: the two public doors differ only in
1599/// what they owe the borrow checker. The consuming one may be asked
1600/// for either lane; the borrowing one is the owned lane, where the
1601/// bytes are copied and the source is nobody's concern afterwards.
1602pub(crate) fn attachment_packet_from_borrowed<C: crate::FfmpegCarrier + crate::CarrierOps>(
1603  packet: &Packet,
1604  limits: PacketLimits,
1605  provenance: crate::buffer::PayloadProvenance,
1606) -> Result<Option<AttachmentPacket<AttachmentPacketExtra, C::Buffer>>, PacketBufferError> {
1607  use ffmpeg_next::packet::Ref;
1608  // SAFETY: `packet` keeps the AVPacket live for the duration of this
1609  // call, which is all `payload_of` requires.
1610  let Some(buf) = (unsafe {
1611    crate::buffer::payload_of::<C>(packet.as_ptr(), limits.max_packet_bytes(), provenance)
1612  })?
1613  else {
1614    return Ok(None);
1615  };
1616  Ok(Some(
1617    AttachmentPacket::new(buf, AttachmentPacketExtra::new(packet.stream() as i32))
1618      .with_flags(md_flags_from_packet(packet)?),
1619  ))
1620}
1621
1622/// Every flag the packet really carries.
1623///
1624/// Read from `AVPacket.flags` as the raw integer rather than through
1625/// `ffmpeg_next`'s `Packet::flags()`, whose `Flags` bit set names only
1626/// `KEY` and `CORRUPT` and drops the rest in `from_bits_truncate`.
1627/// `AV_PKT_FLAG_DISCARD` is among the dropped: it tells a consumer that
1628/// a packet must be fed to the decoder and its output thrown away, and
1629/// losing it makes preroll output look like something to keep.
1630///
1631/// `PacketFlags` is a bit set whose documented lossless door is
1632/// `from_bits_retain`, and every packet flag FFmpeg names lives inside
1633/// the byte it carries — including the three bits nothing names yet.
1634/// A bit outside that byte cannot be carried at all, and is refused
1635/// rather than dropped; the assertion below states the fact that keeps
1636/// the refusal unreachable against this build.
1637fn md_flags_from_packet(packet: &Packet) -> Result<MdPacketFlags, PacketBufferError> {
1638  use ffmpeg_next::packet::Ref;
1639  // SAFETY: `packet` keeps the `AVPacket` live for the call, which is
1640  // all the raw reader asks.
1641  unsafe { md_flags_from_av_packet(packet.as_ptr()) }
1642}
1643
1644/// [`md_flags_from_packet`] for an `AVPacket` that has no safe wrapper
1645/// — the one libavformat embeds in an `AVStream` for cover art.
1646///
1647/// The demuxer hoists that packet by hand, and hoisting it *without*
1648/// its flags was how an attached picture arrived with none: FFmpeg
1649/// marks it `AV_PKT_FLAG_KEY`, which is the one thing a still image is
1650/// certain to be. One reader, so a second construction site cannot
1651/// quietly disagree with the five that go through the boundary.
1652///
1653/// # Safety
1654///
1655/// `pkt` must be a live `*const AVPacket` for the duration of this
1656/// call.
1657pub(crate) unsafe fn md_flags_from_av_packet(
1658  pkt: *const ffmpeg_next::ffi::AVPacket,
1659) -> Result<MdPacketFlags, PacketBufferError> {
1660  // SAFETY: `pkt` is live per the contract above; `flags` is a public
1661  // field and a plain `c_int`.
1662  let raw = unsafe { (*pkt).flags };
1663  let carried = i32::from(u8::MAX);
1664  if raw & !carried != 0 {
1665    return Err(PacketBufferError::UnrepresentableFlags(
1666      UnrepresentableFlags::new(raw),
1667    ));
1668  }
1669  Ok(MdPacketFlags::from_bits_retain(raw as u8))
1670}
1671
1672/// What a track is, folded from `AVCodecParameters.codec_type` read as
1673/// the integer it is on the wire.
1674///
1675/// The dependency-API half of this crate's open-C-enum discipline.
1676/// `ffmpeg_next::codec::Parameters::medium()` materialises an
1677/// `AVMediaType` out of FFmpeg memory to answer the same question, and
1678/// a value outside this build's discriminant set is undefined behaviour
1679/// the moment it exists — before any `match` on it can run. That the
1680/// set is small and has been stable for years is a reason it has not
1681/// bitten, not a reason it cannot.
1682///
1683/// So the read is raw and the fold is total: anything this build does
1684/// not name becomes [`Unknown`](Self::Unknown), which the callers
1685/// already had to handle.
1686#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, IsVariant)]
1687pub enum MediaKind {
1688  /// `AVMEDIA_TYPE_VIDEO`.
1689  Video,
1690  /// `AVMEDIA_TYPE_AUDIO`.
1691  Audio,
1692  /// `AVMEDIA_TYPE_SUBTITLE`.
1693  Subtitle,
1694  /// `AVMEDIA_TYPE_DATA`.
1695  Data,
1696  /// `AVMEDIA_TYPE_ATTACHMENT`.
1697  Attachment,
1698  /// Anything this build of FFmpeg does not name, `AVMEDIA_TYPE_UNKNOWN`
1699  /// included.
1700  Unknown,
1701}
1702
1703/// Folds a raw `AVMediaType` integer into [`MediaKind`].
1704pub(crate) fn media_kind_from_raw(raw: i32) -> MediaKind {
1705  use ffmpeg_next::ffi::AVMediaType::*;
1706  match raw {
1707    x if x == AVMEDIA_TYPE_VIDEO as i32 => MediaKind::Video,
1708    x if x == AVMEDIA_TYPE_AUDIO as i32 => MediaKind::Audio,
1709    x if x == AVMEDIA_TYPE_SUBTITLE as i32 => MediaKind::Subtitle,
1710    x if x == AVMEDIA_TYPE_DATA as i32 => MediaKind::Data,
1711    x if x == AVMEDIA_TYPE_ATTACHMENT as i32 => MediaKind::Attachment,
1712    _ => MediaKind::Unknown,
1713  }
1714}
1715
1716/// The medium a set of codec parameters declares, without forming an
1717/// `AVMediaType`. Replaces every `Parameters::medium()` call in this
1718/// crate — see [`MediaKind`].
1719pub(crate) fn media_kind_of(parameters: &ffmpeg_next::codec::Parameters) -> MediaKind {
1720  // SAFETY: `as_ptr` is `unsafe` only because the pointer must not
1721  // outlive `parameters`; it is used and discarded inside this call.
1722  let ptr = unsafe { parameters.as_ptr() };
1723  if ptr.is_null() {
1724    return MediaKind::Unknown;
1725  }
1726  // SAFETY: `ptr` is a live `*const AVCodecParameters`; `addr_of!`
1727  // computes the field address without forming a reference, and reading
1728  // as `i32` matches the bindgen enum's `c_int` storage.
1729  let raw = unsafe { read_unaligned(addr_of!((*ptr).codec_type) as *const i32) };
1730  media_kind_from_raw(raw)
1731}
1732
1733/// Every packet flag this build of FFmpeg names fits the byte
1734/// `PacketFlags` carries. If that stops being true, this fails the
1735/// build rather than letting a flag go missing at run time.
1736const _: () = {
1737  assert!(
1738    (ffmpeg_next::ffi::AV_PKT_FLAG_KEY
1739      | ffmpeg_next::ffi::AV_PKT_FLAG_CORRUPT
1740      | ffmpeg_next::ffi::AV_PKT_FLAG_DISCARD
1741      | ffmpeg_next::ffi::AV_PKT_FLAG_TRUSTED
1742      | ffmpeg_next::ffi::AV_PKT_FLAG_DISPOSABLE)
1743      <= u8::MAX as c_int,
1744    "FFmpeg names a packet flag outside the byte `PacketFlags` carries",
1745  );
1746};
1747
1748// ---------------------------------------------------------------------------
1749//  Empty-frame placeholders for `receive_frame` destinations.
1750// ---------------------------------------------------------------------------
1751
1752/// Constructs an empty [`mediadecode::frame::VideoFrame`] suitable as
1753/// the destination argument to
1754/// [`mediadecode::decoder::VideoStreamDecoder::receive_frame`]. The
1755/// decoder overwrites the frame on success; this just provides a
1756/// well-formed slot.
1757///
1758/// All four plane slots hold the shared empty carrier (the array shape
1759/// requires a buffer in every slot, but `plane_count = 0` reports them
1760/// as inactive).
1761///
1762/// **Infallible on both lanes, and the `try_` sibling is gone.**
1763/// Through 0.8 each slot was its own one-byte `AVBufferRef`, so
1764/// building a placeholder was four FFmpeg allocations that could fail —
1765/// hence a `try_empty_video_frame` returning `Option` and an
1766/// `empty_video_frame` that panicked on `None`. Neither lane allocates
1767/// here now: the owned lane's empty carrier is made once for the
1768/// process and cloned by refcount, and the view lane's is a null-backed
1769/// zero-length view, which is what lets the *view* lane keep the same
1770/// infallible constructor rather than reintroducing 0.8's failure mode
1771/// under a new name. A constructor with no failure mode does not get to
1772/// keep a `Result`-shaped door.
1773pub(crate) fn empty_video_frame_as<C: crate::FfmpegCarrier + crate::CarrierOps>()
1774-> VideoFrame<PixelFormat, VideoFrameExtra, C::Buffer> {
1775  VideoFrame::new(
1776    Dimensions::new(0, 0),
1777    // mediaframe 0.3's named "no format yet" member, and its
1778    // `Default` — the state a descriptor is in before a decoder has
1779    // said what it produces, which is exactly this placeholder.
1780    PixelFormat::None,
1781    core::array::from_fn(|_| Plane::new(C::empty(), 0)),
1782    0,
1783    VideoFrameExtra::default(),
1784  )
1785}
1786
1787/// Constructs an empty [`mediadecode::frame::AudioFrame`] suitable as
1788/// the destination argument to
1789/// [`mediadecode::decoder::AudioStreamDecoder::receive_frame`]. Same
1790/// behaviour as [`empty_video_frame`] — eight shared empty plane
1791/// carriers, `plane_count = 0`, and no way to fail.
1792pub(crate) fn empty_audio_frame_as<C: crate::FfmpegCarrier + crate::CarrierOps>()
1793-> AudioFrame<SampleFormat, ChannelLayoutDescription, AudioFrameExtra, C::Buffer> {
1794  AudioFrame::new(
1795    0,
1796    0,
1797    0,
1798    SampleFormat::NONE,
1799    ChannelLayoutDescription::default(),
1800    core::array::from_fn(|_| Plane::new(C::empty(), 0)),
1801    0,
1802    AudioFrameExtra::default(),
1803  )
1804}
1805
1806/// Constructs an empty [`mediadecode::frame::SubtitleFrame`] suitable
1807/// as the destination argument to
1808/// [`mediadecode::decoder::SubtitleDecoder::receive_frame`]. The
1809/// payload is an empty `Text` placeholder; the decoder overwrites it
1810/// on success. Infallible, as its two siblings are.
1811pub(crate) fn empty_subtitle_frame_as<C: crate::FfmpegCarrier + crate::CarrierOps>()
1812-> SubtitleFrame<SubtitleFrameExtra, C::Buffer> {
1813  SubtitleFrame::new(
1814    SubtitlePayload::Text(SubtitleText::new(C::empty(), None)),
1815    SubtitleFrameExtra::default(),
1816  )
1817}
1818
1819// --- The bare names, on the view lane --------------------------------
1820//
1821// **The signature says the lane, and the compiler enforces it.** A
1822// conversion that *borrows* its `AVPacket` can only copy: the packet
1823// type lends `&mut [u8]` and shares its buffer by refcount with no
1824// copy-on-write, so a caller who still holds the packet holds a mutable
1825// alias of anything a view would read. A conversion that *consumes* its
1826// packet can share, because after it returns there is no other handle.
1827//
1828// So: **borrow in, owned lane out; move in, either lane out.** The
1829// `_in` names below take the packet by value and answer with the view
1830// lane — the ordinary road, where a direct consumer reads a packet in
1831// place and drops it — while the lane-generic `_as` workers, also
1832// by value, let a caller ask for either. The borrowing doors are the
1833// bare `*_packet_from_ffmpeg` names, and they are the owned lane.
1834//
1835// A generic function has no default parameter to fall back on (defaults
1836// are used when a type is *written*, not inferred from a call), so
1837// these are monomorphic wrappers rather than a default that would never
1838// apply.
1839
1840/// [`video_packet_from_ffmpeg_as`] on the view lane, with the
1841/// stream's timebase stamped onto every timestamp.
1842///
1843/// **Takes the packet by value**, and that is the safety property, not
1844/// a style choice. The program below is the alias this signature
1845/// forbids — it was accepted when the parameter was a reference, and
1846/// `data_mut` handed out a `&mut [u8]` over bytes the returned carrier
1847/// was still lending as `&[u8]`:
1848///
1849/// ```compile_fail,E0382
1850/// use ffmpeg_next::packet::Mut;
1851/// use mediadecode::Timebase;
1852/// use mediadecode_ffmpeg::{PacketLimits, video_packet_from_ffmpeg_in};
1853///
1854/// let mut packet = ffmpeg_next::Packet::copy(&[0u8; 64]);
1855/// let viewed = video_packet_from_ffmpeg_in(packet, Timebase::default(), PacketLimits::default());
1856/// // The source is gone, so this cannot be written.
1857/// let _aliased = packet.data_mut();
1858/// ```
1859///
1860/// A caller who wants to keep the packet wants the owned lane, which
1861/// copies and may therefore borrow:
1862/// [`owned_video_packet_from_ffmpeg_in`].
1863pub fn video_packet_from_ffmpeg_in(
1864  packet: Packet,
1865  time_base: mediadecode::Timebase,
1866  limits: PacketLimits,
1867) -> Result<Option<VideoPacket<VideoPacketExtra, crate::FfmpegBuffer>>, PacketBufferError> {
1868  video_packet_from_ffmpeg_as::<crate::View>(
1869    packet,
1870    time_base,
1871    limits,
1872    crate::buffer::PayloadProvenance::CallerSupplied,
1873  )
1874}
1875
1876/// [`audio_packet_from_ffmpeg_as`] on the view lane.
1877pub fn audio_packet_from_ffmpeg_in(
1878  packet: Packet,
1879  time_base: mediadecode::Timebase,
1880  limits: PacketLimits,
1881) -> Result<Option<AudioPacket<AudioPacketExtra, crate::FfmpegBuffer>>, PacketBufferError> {
1882  audio_packet_from_ffmpeg_as::<crate::View>(
1883    packet,
1884    time_base,
1885    limits,
1886    crate::buffer::PayloadProvenance::CallerSupplied,
1887  )
1888}
1889
1890/// [`subtitle_packet_from_ffmpeg_as`] on the view lane.
1891pub fn subtitle_packet_from_ffmpeg_in(
1892  packet: Packet,
1893  time_base: mediadecode::Timebase,
1894  limits: PacketLimits,
1895) -> Result<Option<SubtitlePacket<SubtitlePacketExtra, crate::FfmpegBuffer>>, PacketBufferError> {
1896  subtitle_packet_from_ffmpeg_as::<crate::View>(
1897    packet,
1898    time_base,
1899    limits,
1900    crate::buffer::PayloadProvenance::CallerSupplied,
1901  )
1902}
1903
1904/// [`data_packet_from_ffmpeg_as`] on the view lane.
1905pub fn data_packet_from_ffmpeg_in(
1906  packet: Packet,
1907  time_base: mediadecode::Timebase,
1908  limits: PacketLimits,
1909) -> Result<Option<DataPacket<DataPacketExtra, crate::FfmpegBuffer>>, PacketBufferError> {
1910  data_packet_from_ffmpeg_as::<crate::View>(
1911    packet,
1912    time_base,
1913    limits,
1914    crate::buffer::PayloadProvenance::CallerSupplied,
1915  )
1916}
1917
1918// --- The borrowing doors, on the owned lane --------------------------
1919//
1920// The owned lane may borrow because it copies: nothing it returns
1921// points at the source, so the source's fate is its own business.
1922// These are the shapes the view lane cannot offer, and the reason it
1923// cannot is the whole of the split — see the note above the `_in`
1924// family.
1925
1926/// [`video_packet_from_ffmpeg`] with the stream's timebase and explicit
1927/// budgets.
1928pub fn owned_video_packet_from_ffmpeg_in(
1929  packet: &Packet,
1930  time_base: mediadecode::Timebase,
1931  limits: PacketLimits,
1932) -> Result<Option<VideoPacket<VideoPacketExtra, FfmpegBytes>>, PacketBufferError> {
1933  video_packet_from_borrowed::<crate::Owned>(
1934    packet,
1935    time_base,
1936    limits,
1937    crate::buffer::PayloadProvenance::CallerSupplied,
1938  )
1939}
1940
1941/// [`audio_packet_from_ffmpeg`] with the stream's timebase and explicit
1942/// budgets.
1943pub fn owned_audio_packet_from_ffmpeg_in(
1944  packet: &Packet,
1945  time_base: mediadecode::Timebase,
1946  limits: PacketLimits,
1947) -> Result<Option<AudioPacket<AudioPacketExtra, FfmpegBytes>>, PacketBufferError> {
1948  audio_packet_from_borrowed::<crate::Owned>(
1949    packet,
1950    time_base,
1951    limits,
1952    crate::buffer::PayloadProvenance::CallerSupplied,
1953  )
1954}
1955
1956/// [`subtitle_packet_from_ffmpeg`] with the stream's timebase and
1957/// explicit budgets.
1958pub fn owned_subtitle_packet_from_ffmpeg_in(
1959  packet: &Packet,
1960  time_base: mediadecode::Timebase,
1961  limits: PacketLimits,
1962) -> Result<Option<SubtitlePacket<SubtitlePacketExtra, FfmpegBytes>>, PacketBufferError> {
1963  subtitle_packet_from_borrowed::<crate::Owned>(
1964    packet,
1965    time_base,
1966    limits,
1967    crate::buffer::PayloadProvenance::CallerSupplied,
1968  )
1969}
1970
1971/// [`data_packet_from_ffmpeg_in`] on the owned lane, borrowing its
1972/// source.
1973pub fn owned_data_packet_from_ffmpeg_in(
1974  packet: &Packet,
1975  time_base: mediadecode::Timebase,
1976  limits: PacketLimits,
1977) -> Result<Option<DataPacket<DataPacketExtra, FfmpegBytes>>, PacketBufferError> {
1978  data_packet_from_borrowed::<crate::Owned>(
1979    packet,
1980    time_base,
1981    limits,
1982    crate::buffer::PayloadProvenance::CallerSupplied,
1983  )
1984}
1985
1986/// [`attachment_packet_from_ffmpeg`] on the owned lane, borrowing its
1987/// source.
1988pub fn owned_attachment_packet_from_ffmpeg(
1989  packet: &Packet,
1990  limits: PacketLimits,
1991) -> Result<Option<AttachmentPacket<AttachmentPacketExtra, FfmpegBytes>>, PacketBufferError> {
1992  attachment_packet_from_borrowed::<crate::Owned>(
1993    packet,
1994    limits,
1995    crate::buffer::PayloadProvenance::CallerSupplied,
1996  )
1997}
1998
1999/// [`empty_video_frame_as`] on the view lane — the destination a
2000/// [`FfmpegVideoStreamDecoder`](crate::FfmpegVideoStreamDecoder) fills.
2001#[must_use]
2002pub fn empty_video_frame() -> VideoFrame<PixelFormat, VideoFrameExtra, crate::FfmpegBuffer> {
2003  empty_video_frame_as::<crate::View>()
2004}
2005
2006/// [`empty_video_frame_as`] on the owned lane.
2007#[must_use]
2008pub fn empty_owned_video_frame() -> VideoFrame<PixelFormat, VideoFrameExtra, FfmpegBytes> {
2009  empty_video_frame_as::<crate::Owned>()
2010}
2011
2012/// [`empty_audio_frame_as`] on the view lane.
2013#[must_use]
2014pub fn empty_audio_frame()
2015-> AudioFrame<SampleFormat, ChannelLayoutDescription, AudioFrameExtra, crate::FfmpegBuffer> {
2016  empty_audio_frame_as::<crate::View>()
2017}
2018
2019/// [`empty_audio_frame_as`] on the owned lane.
2020#[must_use]
2021pub fn empty_owned_audio_frame()
2022-> AudioFrame<SampleFormat, ChannelLayoutDescription, AudioFrameExtra, FfmpegBytes> {
2023  empty_audio_frame_as::<crate::Owned>()
2024}
2025
2026/// [`empty_subtitle_frame_as`] on the view lane.
2027#[must_use]
2028pub fn empty_subtitle_frame() -> SubtitleFrame<SubtitleFrameExtra, crate::FfmpegBuffer> {
2029  empty_subtitle_frame_as::<crate::View>()
2030}
2031
2032/// [`empty_subtitle_frame_as`] on the owned lane.
2033#[must_use]
2034pub fn empty_owned_subtitle_frame() -> SubtitleFrame<SubtitleFrameExtra, FfmpegBytes> {
2035  empty_subtitle_frame_as::<crate::Owned>()
2036}
2037
2038/// [`attachment_packet_from_ffmpeg_as`] on the view lane.
2039pub fn attachment_packet_from_ffmpeg(
2040  packet: Packet,
2041  limits: PacketLimits,
2042) -> Result<Option<AttachmentPacket<AttachmentPacketExtra, crate::FfmpegBuffer>>, PacketBufferError>
2043{
2044  attachment_packet_from_ffmpeg_as::<crate::View>(
2045    packet,
2046    limits,
2047    crate::buffer::PayloadProvenance::CallerSupplied,
2048  )
2049}
2050
2051#[cfg(test)]
2052mod tests {
2053  use super::*;
2054
2055  /// A refcounted packet whose header claims a payload larger than the
2056  /// buffer behind it — the shape a malformed container, or an
2057  /// `av_packet_split_side_data` gone wrong, produces. Its payload is
2058  /// *there* and cannot be wrapped, which is exactly the case that must
2059  /// not read as "empty".
2060  fn out_of_bounds_packet() -> Packet {
2061    use ffmpeg_next::packet::Mut;
2062    let mut packet = Packet::copy(&[1u8, 2, 3, 4]);
2063    // SAFETY: `packet` owns a live `AVPacket` with a refcounted buffer;
2064    // `size` is a public field.
2065    unsafe {
2066      (*packet.as_mut_ptr()).size = 1 << 20;
2067    }
2068    packet
2069  }
2070
2071  #[test]
2072  fn a_payload_that_cannot_be_wrapped_is_an_error_on_every_arm() {
2073    // All five delivery arms plus the attachment path go through the
2074    // same wrapper. If one of them still folded the failure into
2075    // `None`, the demuxer would drop that kind of packet in silence.
2076    let forged = out_of_bounds_packet();
2077    let tb = mediadecode::Timebase::default();
2078    assert!(matches!(
2079      video_packet_from_borrowed::<crate::Owned>(
2080        &forged,
2081        tb,
2082        PacketLimits::default(),
2083        crate::buffer::PayloadProvenance::CallerSupplied
2084      ),
2085      Err(PacketBufferError::Bounds(_)),
2086    ));
2087    assert!(matches!(
2088      audio_packet_from_borrowed::<crate::Owned>(
2089        &forged,
2090        tb,
2091        PacketLimits::default(),
2092        crate::buffer::PayloadProvenance::CallerSupplied
2093      ),
2094      Err(PacketBufferError::Bounds(_)),
2095    ));
2096    assert!(matches!(
2097      subtitle_packet_from_borrowed::<crate::Owned>(
2098        &forged,
2099        tb,
2100        PacketLimits::default(),
2101        crate::buffer::PayloadProvenance::CallerSupplied
2102      ),
2103      Err(PacketBufferError::Bounds(_)),
2104    ));
2105    assert!(matches!(
2106      data_packet_from_borrowed::<crate::Owned>(
2107        &forged,
2108        tb,
2109        PacketLimits::default(),
2110        crate::buffer::PayloadProvenance::CallerSupplied
2111      ),
2112      Err(PacketBufferError::Bounds(_)),
2113    ));
2114    assert!(matches!(
2115      attachment_packet_from_borrowed::<crate::Owned>(
2116        &forged,
2117        PacketLimits::default(),
2118        crate::buffer::PayloadProvenance::CallerSupplied
2119      ),
2120      Err(PacketBufferError::Bounds(_)),
2121    ));
2122  }
2123
2124  /// A packet with **no body** and one side-data entry — the shape
2125  /// FFmpeg uses to hand a decoder new extradata or a parameter change.
2126  /// `size` is 0 and `buf` is null, which is exactly what used to read
2127  /// as "empty, skip it".
2128  fn side_data_only_packet() -> Packet {
2129    use ffmpeg_next::{
2130      ffi::{AVPacketSideDataType, av_packet_new_side_data},
2131      packet::Mut,
2132    };
2133    let mut packet = Packet::empty();
2134    // SAFETY: `packet` owns a live `AVPacket`; the side-data type is a
2135    // compile-time constant of this build, so no invalid discriminant
2136    // is formed. The returned pointer is valid for the four bytes just
2137    // allocated.
2138    unsafe {
2139      let ptr = av_packet_new_side_data(
2140        packet.as_mut_ptr(),
2141        AVPacketSideDataType::AV_PKT_DATA_NEW_EXTRADATA,
2142        4,
2143      );
2144      assert!(!ptr.is_null(), "av_packet_new_side_data");
2145      core::ptr::copy_nonoverlapping([1u8, 2, 3, 4].as_ptr(), ptr, 4);
2146    }
2147    packet
2148  }
2149
2150  const NEW_EXTRADATA: i32 =
2151    ffmpeg_next::ffi::AVPacketSideDataType::AV_PKT_DATA_NEW_EXTRADATA as i32;
2152
2153  use ffmpeg_next::ffi::{AVPacketSideData, AVPacketSideDataType, av_malloc};
2154
2155  /// A packet carrying one side-data entry of exactly `size` bytes,
2156  /// with a body when `body` is set.
2157  fn packet_with_side_data(size: usize, body: bool) -> Packet {
2158    use ffmpeg_next::{
2159      ffi::{AVPacketSideDataType, av_packet_new_side_data},
2160      packet::Mut,
2161    };
2162    let mut packet = if body {
2163      Packet::copy(&[1u8, 2, 3])
2164    } else {
2165      Packet::empty()
2166    };
2167    // SAFETY: `packet` owns a live `AVPacket` and the type is a
2168    // compile-time constant of this build.
2169    let ptr = unsafe {
2170      av_packet_new_side_data(
2171        packet.as_mut_ptr(),
2172        AVPacketSideDataType::AV_PKT_DATA_NEW_EXTRADATA,
2173        size,
2174      )
2175    };
2176    assert!(!ptr.is_null(), "av_packet_new_side_data({size})");
2177    packet
2178  }
2179
2180  /// A packet whose side-data array has been forged into a shape
2181  /// FFmpeg's own API cannot produce, and cannot free either: `Drop`
2182  /// puts the header back into a state `av_packet_free_side_data` can
2183  /// walk, so a fixture for a malformed packet cannot take the test
2184  /// process down with it.
2185  ///
2186  /// Both `av_packet_new_side_data` and `av_packet_add_side_data`
2187  /// replace an existing entry of the same type (measured: seventy
2188  /// calls leave one entry), so every shape below — an over-cap count, a
2189  /// negative count, a missing array, an entry that declares bytes it
2190  /// does not carry — is one no FFmpeg call produces. That is exactly
2191  /// what the validation is for.
2192  struct Forged {
2193    packet: Packet,
2194    freeable: i32,
2195  }
2196
2197  impl Drop for Forged {
2198    fn drop(&mut self) {
2199      use ffmpeg_next::packet::Mut;
2200      // SAFETY: `packet` owns a live `AVPacket`; putting the count back
2201      // to what the array really holds is what makes the free sound.
2202      unsafe { (*self.packet.as_mut_ptr()).side_data_elems = self.freeable };
2203    }
2204  }
2205
2206  impl Forged {
2207    /// `count` real entries of four bytes each.
2208    fn entries(count: usize, body: bool) -> Self {
2209      let mut packet = Self::carrier(body);
2210      // SAFETY: the array and every entry payload come from FFmpeg's
2211      // own allocator and are handed to the packet, which frees them in
2212      // `av_packet_free_side_data`. The carrier has no side data of its
2213      // own, so the overwrite leaks nothing.
2214      unsafe {
2215        let array = Self::array(count);
2216        for index in 0..count {
2217          let data = av_malloc(4) as *mut u8;
2218          assert!(!data.is_null(), "av_malloc");
2219          core::ptr::write_bytes(data, 7, 4);
2220          (*array.add(index)).data = data;
2221          (*array.add(index)).size = 4;
2222          (*array.add(index)).type_ = AVPacketSideDataType::AV_PKT_DATA_NEW_EXTRADATA;
2223        }
2224        Self::attach(&mut packet, array, count as i32);
2225      }
2226      Self {
2227        packet,
2228        freeable: count as i32,
2229      }
2230    }
2231
2232    /// A count with no array behind it at all.
2233    fn null_array(count: i32, body: bool) -> Self {
2234      let mut packet = Self::carrier(body);
2235      use ffmpeg_next::packet::Mut;
2236      // SAFETY: the packet's side data is null already; only the count
2237      // is forged, and `Drop` puts it back to zero before the free.
2238      unsafe { (*packet.as_mut_ptr()).side_data_elems = count };
2239      Self {
2240        packet,
2241        freeable: 0,
2242      }
2243    }
2244
2245    /// One entry declaring `size` bytes it does not carry.
2246    fn null_entry_data(size: usize, body: bool) -> Self {
2247      let mut packet = Self::carrier(body);
2248      // SAFETY: as in `entries`, except the payload pointer is left
2249      // null — `av_freep(&NULL)` is a no-op, so the free stays sound.
2250      unsafe {
2251        let array = Self::array(1);
2252        (*array).data = core::ptr::null_mut();
2253        (*array).size = size;
2254        (*array).type_ = AVPacketSideDataType::AV_PKT_DATA_NEW_EXTRADATA;
2255        Self::attach(&mut packet, array, 1);
2256      }
2257      Self {
2258        packet,
2259        freeable: 1,
2260      }
2261    }
2262
2263    /// Overrides the declared count, keeping the array intact.
2264    fn with_declared_count(mut self, count: i32) -> Self {
2265      use ffmpeg_next::packet::Mut;
2266      // SAFETY: `Drop` restores `freeable`, which still describes the
2267      // array really attached.
2268      unsafe { (*self.packet.as_mut_ptr()).side_data_elems = count };
2269      self
2270    }
2271
2272    fn carrier(body: bool) -> Packet {
2273      if body {
2274        Packet::copy(&[1u8, 2, 3])
2275      } else {
2276        Packet::empty()
2277      }
2278    }
2279
2280    /// # Safety
2281    /// The returned array is FFmpeg-allocated and uninitialised.
2282    unsafe fn array(count: usize) -> *mut AVPacketSideData {
2283      let array = unsafe { av_malloc(count * core::mem::size_of::<AVPacketSideData>()) }
2284        as *mut AVPacketSideData;
2285      assert!(!array.is_null(), "av_malloc");
2286      array
2287    }
2288
2289    /// # Safety
2290    /// `array` must hold `count` initialised entries owned by FFmpeg.
2291    unsafe fn attach(packet: &mut Packet, array: *mut AVPacketSideData, count: i32) {
2292      use ffmpeg_next::packet::Mut;
2293      unsafe {
2294        (*packet.as_mut_ptr()).side_data = array;
2295        (*packet.as_mut_ptr()).side_data_elems = count;
2296      }
2297    }
2298  }
2299
2300  /// Runs one packet through all four timed conversions, returning what
2301  /// each answered — the arms share a collector, and a fix that misses
2302  /// one of them is a fix that misses.
2303  fn every_timed_arm(packet: &Packet) -> [(&'static str, Result<bool, PacketBufferError>); 4] {
2304    let tb = mediadecode::Timebase::default();
2305    [
2306      (
2307        "video",
2308        video_packet_from_borrowed::<crate::Owned>(
2309          packet,
2310          tb,
2311          PacketLimits::default(),
2312          crate::buffer::PayloadProvenance::CallerSupplied,
2313        )
2314        .map(|p| p.is_some()),
2315      ),
2316      (
2317        "audio",
2318        audio_packet_from_borrowed::<crate::Owned>(
2319          packet,
2320          tb,
2321          PacketLimits::default(),
2322          crate::buffer::PayloadProvenance::CallerSupplied,
2323        )
2324        .map(|p| p.is_some()),
2325      ),
2326      (
2327        "subtitle",
2328        subtitle_packet_from_borrowed::<crate::Owned>(
2329          packet,
2330          tb,
2331          PacketLimits::default(),
2332          crate::buffer::PayloadProvenance::CallerSupplied,
2333        )
2334        .map(|p| p.is_some()),
2335      ),
2336      (
2337        "data",
2338        data_packet_from_borrowed::<crate::Owned>(
2339          packet,
2340          tb,
2341          PacketLimits::default(),
2342          crate::buffer::PayloadProvenance::CallerSupplied,
2343        )
2344        .map(|p| p.is_some()),
2345      ),
2346    ]
2347  }
2348
2349  #[test]
2350  fn side_data_a_packet_declares_and_does_not_carry_refuses_it() {
2351    // Two pointer paths walked straight past the all-or-error rule: a
2352    // count with no array behind it returned "no side data", and an
2353    // entry declaring bytes it did not carry became an empty entry —
2354    // charged nothing, delivered as though the packet had said so. Both
2355    // are how a body-bearing packet reached a decoder stripped of its
2356    // control data, and how a side-data-only packet became `None` and
2357    // was skipped.
2358    for body in [true, false] {
2359      let forged = Forged::null_array(3, body);
2360      for (arm, result) in every_timed_arm(&forged.packet) {
2361        match result {
2362          Err(PacketBufferError::SideDataArray(p)) => assert_eq!(p.count(), 3, "{arm}"),
2363          other => panic!("{arm} (body={body}): expected SideDataArray, got {other:?}"),
2364        }
2365      }
2366
2367      let forged = Forged::null_entry_data(64, body);
2368      for (arm, result) in every_timed_arm(&forged.packet) {
2369        match result {
2370          Err(PacketBufferError::SideDataPayload(p)) => {
2371            assert_eq!((p.index(), p.size()), (0, 64), "{arm}");
2372          }
2373          other => panic!("{arm} (body={body}): expected SideDataPayload, got {other:?}"),
2374        }
2375      }
2376
2377      // And the malformed count keeps being refused when the array is
2378      // missing too — the count is judged on its own, before the
2379      // pointer, so one cannot excuse the other.
2380      let forged = Forged::null_array(-3, body);
2381      for (arm, result) in every_timed_arm(&forged.packet) {
2382        assert!(
2383          matches!(
2384            result,
2385            Err(PacketBufferError::SideDataEntries(p)) if p.count() == -3
2386          ),
2387          "{arm} (body={body}): {result:?}",
2388        );
2389      }
2390      let forged = Forged::null_array(i32::MAX, body);
2391      for (arm, result) in every_timed_arm(&forged.packet) {
2392        assert!(
2393          matches!(result, Err(PacketBufferError::SideDataEntries(_))),
2394          "{arm} (body={body}): {result:?}",
2395        );
2396      }
2397    }
2398
2399    // A zero-size entry is a marker, not a lie: a type with no bytes is
2400    // exactly what FFmpeg emits for some side data, and it stays
2401    // welcome.
2402    let marker = Forged::null_entry_data(0, true);
2403    let packet = video_packet_from_borrowed::<crate::Owned>(
2404      &marker.packet,
2405      mediadecode::Timebase::default(),
2406      PacketLimits::default(),
2407      crate::buffer::PayloadProvenance::CallerSupplied,
2408    )
2409    .expect("a marker entry is carriable")
2410    .expect("present");
2411    assert_eq!(packet.extra().side_data().len(), 1);
2412    assert!(packet.extra().side_data()[0].data().is_empty());
2413  }
2414
2415  #[test]
2416  fn side_data_that_cannot_be_carried_whole_refuses_the_packet() {
2417    let tb = mediadecode::Timebase::default();
2418
2419    // A body-bearing packet whose side data is over the byte cap. The
2420    // caps used to truncate and warn, which handed the codec a packet
2421    // that looked complete and was not: `NEW_EXTRADATA` gone, a decoder
2422    // left on parameters the container had already replaced.
2423    let oversized = packet_with_side_data(SIDE_DATA_MAX_TOTAL_BYTES + 1, true);
2424    for (arm, result) in [
2425      (
2426        "video",
2427        video_packet_from_borrowed::<crate::Owned>(
2428          &oversized,
2429          tb,
2430          PacketLimits::default(),
2431          crate::buffer::PayloadProvenance::CallerSupplied,
2432        )
2433        .map(|p| p.is_some()),
2434      ),
2435      (
2436        "audio",
2437        audio_packet_from_borrowed::<crate::Owned>(
2438          &oversized,
2439          tb,
2440          PacketLimits::default(),
2441          crate::buffer::PayloadProvenance::CallerSupplied,
2442        )
2443        .map(|p| p.is_some()),
2444      ),
2445      (
2446        "subtitle",
2447        subtitle_packet_from_borrowed::<crate::Owned>(
2448          &oversized,
2449          tb,
2450          PacketLimits::default(),
2451          crate::buffer::PayloadProvenance::CallerSupplied,
2452        )
2453        .map(|p| p.is_some()),
2454      ),
2455      (
2456        "data",
2457        data_packet_from_borrowed::<crate::Owned>(
2458          &oversized,
2459          tb,
2460          PacketLimits::default(),
2461          crate::buffer::PayloadProvenance::CallerSupplied,
2462        )
2463        .map(|p| p.is_some()),
2464      ),
2465    ] {
2466      match result {
2467        Err(PacketBufferError::SideDataBytes(p)) => {
2468          assert_eq!(p.cap(), SIDE_DATA_MAX_TOTAL_BYTES);
2469          assert!(p.bytes() > p.cap(), "{arm}");
2470        }
2471        other => panic!("{arm}: expected SideDataBytes, got {other:?}"),
2472      }
2473    }
2474
2475    // And the same packet with no body at all. This one used to
2476    // collapse twice over: the side data was dropped, the packet
2477    // therefore looked empty, and the demuxer skipped it in silence.
2478    let oversized = packet_with_side_data(SIDE_DATA_MAX_TOTAL_BYTES + 1, false);
2479    assert!(matches!(
2480      video_packet_from_borrowed::<crate::Owned>(
2481        &oversized,
2482        tb,
2483        PacketLimits::default(),
2484        crate::buffer::PayloadProvenance::CallerSupplied
2485      )
2486      .map(|p| p.is_some()),
2487      Err(PacketBufferError::SideDataBytes(_)),
2488    ));
2489  }
2490
2491  #[test]
2492  fn the_side_data_caps_are_refusals_at_the_boundary_not_before_it() {
2493    let tb = mediadecode::Timebase::default();
2494
2495    // Exactly at the byte cap: carried, whole.
2496    let at_cap = packet_with_side_data(SIDE_DATA_MAX_TOTAL_BYTES, true);
2497    let packet = video_packet_from_borrowed::<crate::Owned>(
2498      &at_cap,
2499      tb,
2500      PacketLimits::default(),
2501      crate::buffer::PayloadProvenance::CallerSupplied,
2502    )
2503    .expect("exactly at the cap is not over it")
2504    .expect("present");
2505    assert_eq!(packet.extra().side_data().len(), 1);
2506    assert_eq!(
2507      packet.extra().side_data()[0].data().len(),
2508      SIDE_DATA_MAX_TOTAL_BYTES,
2509    );
2510
2511    // One byte past: refused.
2512    let past = packet_with_side_data(SIDE_DATA_MAX_TOTAL_BYTES + 1, true);
2513    assert!(matches!(
2514      video_packet_from_borrowed::<crate::Owned>(
2515        &past,
2516        tb,
2517        PacketLimits::default(),
2518        crate::buffer::PayloadProvenance::CallerSupplied
2519      )
2520      .map(|p| p.is_some()),
2521      Err(PacketBufferError::SideDataBytes(_)),
2522    ));
2523
2524    // The entry cap, both sides of it. FFmpeg names fewer types than
2525    // the floor today, so the effective cap is the floor; if a future
2526    // build names more, this assertion moves the boundary rather than
2527    // letting the lane quietly test nothing.
2528    let cap = side_data_entry_cap();
2529    assert_eq!(cap, SIDE_DATA_MAX_ENTRIES, "the cap this lane straddles");
2530
2531    let at_cap = Forged::entries(cap, true);
2532    let packet = video_packet_from_borrowed::<crate::Owned>(
2533      &at_cap.packet,
2534      tb,
2535      PacketLimits::default(),
2536      crate::buffer::PayloadProvenance::CallerSupplied,
2537    )
2538    .expect("exactly at the cap is not over it")
2539    .expect("present");
2540    assert_eq!(packet.extra().side_data().len(), cap);
2541
2542    let past = Forged::entries(cap + 1, true);
2543    match video_packet_from_borrowed::<crate::Owned>(
2544      &past.packet,
2545      tb,
2546      PacketLimits::default(),
2547      crate::buffer::PayloadProvenance::CallerSupplied,
2548    )
2549    .map(|p| p.is_some())
2550    {
2551      Err(PacketBufferError::SideDataEntries(p)) => {
2552        assert_eq!(p.count() as usize, cap + 1);
2553        assert_eq!(p.cap(), cap);
2554      }
2555      other => panic!("expected SideDataEntries, got {other:?}"),
2556    }
2557
2558    // A negative count is malformed, not empty — reading it as "no side
2559    // data" would be the same silent loss by another route.
2560    let corrupt = Forged::entries(1, true).with_declared_count(-3);
2561    assert!(matches!(
2562      video_packet_from_borrowed::<crate::Owned>(&corrupt.packet, tb, PacketLimits::default(), crate::buffer::PayloadProvenance::CallerSupplied).map(|p| p.is_some()),
2563      Err(PacketBufferError::SideDataEntries(p)) if p.count() == -3,
2564    ));
2565  }
2566
2567  #[test]
2568  fn a_trusted_packet_is_refused_on_both_legs() {
2569    use ffmpeg_next::packet::Mut;
2570    let tb = mediadecode::Timebase::default();
2571
2572    // Copy-out: the leg such a packet would enter the graph on.
2573    let mut packet = Packet::copy(&[1u8, 2, 3]);
2574    // SAFETY: `packet` owns a live `AVPacket`; `flags` is a public field
2575    // and this bit has no `ffmpeg_next::Flags` spelling.
2576    unsafe {
2577      (*packet.as_mut_ptr()).flags =
2578        ffmpeg_next::ffi::AV_PKT_FLAG_KEY | ffmpeg_next::ffi::AV_PKT_FLAG_TRUSTED;
2579    };
2580    for (arm, taken) in [
2581      (
2582        "video",
2583        video_packet_from_borrowed::<crate::Owned>(
2584          &packet,
2585          tb,
2586          PacketLimits::default(),
2587          crate::buffer::PayloadProvenance::CallerSupplied,
2588        )
2589        .map(|p| p.is_some()),
2590      ),
2591      (
2592        "audio",
2593        audio_packet_from_borrowed::<crate::Owned>(
2594          &packet,
2595          tb,
2596          PacketLimits::default(),
2597          crate::buffer::PayloadProvenance::CallerSupplied,
2598        )
2599        .map(|p| p.is_some()),
2600      ),
2601      (
2602        "subtitle",
2603        subtitle_packet_from_borrowed::<crate::Owned>(
2604          &packet,
2605          tb,
2606          PacketLimits::default(),
2607          crate::buffer::PayloadProvenance::CallerSupplied,
2608        )
2609        .map(|p| p.is_some()),
2610      ),
2611      (
2612        "data",
2613        data_packet_from_borrowed::<crate::Owned>(
2614          &packet,
2615          tb,
2616          PacketLimits::default(),
2617          crate::buffer::PayloadProvenance::CallerSupplied,
2618        )
2619        .map(|p| p.is_some()),
2620      ),
2621      (
2622        "attachment",
2623        attachment_packet_from_borrowed::<crate::Owned>(
2624          &packet,
2625          PacketLimits::default(),
2626          crate::buffer::PayloadProvenance::CallerSupplied,
2627        )
2628        .map(|p| p.is_some()),
2629      ),
2630    ] {
2631      match taken {
2632        Err(PacketBufferError::TrustedPayload(p)) => assert_eq!(p.len(), 3, "{arm}"),
2633        other => panic!("{arm}: a TRUSTED payload must not be copied out, got {other:?}"),
2634      }
2635    }
2636
2637    // Rebuild: the leg a flag that arrived by some other route would be
2638    // handed back to a decoder on. Built by hand, because the copy-out
2639    // leg above will no longer produce one.
2640    let clean = Packet::copy(&[1u8, 2, 3]);
2641    let video = video_packet_from_borrowed::<crate::Owned>(
2642      &clean,
2643      tb,
2644      PacketLimits::default(),
2645      crate::buffer::PayloadProvenance::CallerSupplied,
2646    )
2647    .expect("a clean packet is carriable")
2648    .expect("present");
2649    let trusted = video
2650      .clone()
2651      .with_flags(MdPacketFlags::from_bits_retain(crate::buffer::TRUSTED_BIT));
2652    assert!(
2653      matches!(
2654        ffmpeg_packet_from_owned_video_packet(&trusted, PacketLimits::default()),
2655        Err(PacketBuildError::TrustedPayload(_)),
2656      ),
2657      "a TRUSTED flag must not be written back onto an AVPacket",
2658    );
2659  }
2660
2661  #[test]
2662  fn every_flag_bit_survives_both_directions() {
2663    // `PacketFlags` is a bit set whose documented lossless door is
2664    // `from_bits_retain`, and both directions used to squeeze it
2665    // through `ffmpeg_next`'s `Flags`, which names `KEY` and `CORRUPT`
2666    // and truncates the rest away. `AV_PKT_FLAG_DISCARD` is the one
2667    // that matters most: it tells a consumer to decode a packet and
2668    // throw its output away, and without it preroll output looks like
2669    // something to keep.
2670    use ffmpeg_next::packet::{Mut, Ref};
2671    const DISCARD: i32 = ffmpeg_next::ffi::AV_PKT_FLAG_DISCARD;
2672    const TRUSTED: i32 = ffmpeg_next::ffi::AV_PKT_FLAG_TRUSTED;
2673    const DISPOSABLE: i32 = ffmpeg_next::ffi::AV_PKT_FLAG_DISPOSABLE;
2674    const UNNAMED: i32 = 0b0010_0000; // nothing names this bit yet
2675    // `TRUSTED` is deliberately **not** in this set, and the constant is
2676    // kept only to say so. It used to be — this lane asserted it made
2677    // the round trip — and that assertion was the bug: a `TRUSTED`
2678    // payload may be a structure of pointers into other live objects,
2679    // so copying it mints an owned-looking carrier that dangles when
2680    // its source drops. It is now refused on both legs, which
2681    // `a_trusted_packet_is_refused_on_both_legs` pins.
2682    let _ = TRUSTED;
2683    let raw = ffmpeg_next::ffi::AV_PKT_FLAG_KEY | DISCARD | DISPOSABLE | UNNAMED;
2684
2685    let mut packet = Packet::copy(&[1u8, 2, 3]);
2686    // SAFETY: `packet` owns a live `AVPacket`; `flags` is a public
2687    // field and this is the only way to set a bit `ffmpeg_next`'s
2688    // `Flags` cannot spell.
2689    unsafe { (*packet.as_mut_ptr()).flags = raw };
2690    assert_ne!(
2691      packet.flags().bits(),
2692      raw,
2693      "the wrapper's own accessor is what loses them",
2694    );
2695
2696    let tb = mediadecode::Timebase::default();
2697    let expected = MdPacketFlags::from_bits_retain(raw as u8);
2698
2699    let video = video_packet_from_borrowed::<crate::Owned>(
2700      &packet,
2701      tb,
2702      PacketLimits::default(),
2703      crate::buffer::PayloadProvenance::CallerSupplied,
2704    )
2705    .expect("wrappable")
2706    .expect("present");
2707    assert_eq!(video.flags(), expected);
2708    assert!(video.flags().contains(MdPacketFlags::DISCARD));
2709    let audio = audio_packet_from_borrowed::<crate::Owned>(
2710      &packet,
2711      tb,
2712      PacketLimits::default(),
2713      crate::buffer::PayloadProvenance::CallerSupplied,
2714    )
2715    .expect("wrappable")
2716    .expect("present");
2717    assert_eq!(audio.flags(), expected);
2718    let subtitle = subtitle_packet_from_borrowed::<crate::Owned>(
2719      &packet,
2720      tb,
2721      PacketLimits::default(),
2722      crate::buffer::PayloadProvenance::CallerSupplied,
2723    )
2724    .expect("wrappable")
2725    .expect("present");
2726    assert_eq!(subtitle.flags(), expected);
2727    let data = data_packet_from_borrowed::<crate::Owned>(
2728      &packet,
2729      tb,
2730      PacketLimits::default(),
2731      crate::buffer::PayloadProvenance::CallerSupplied,
2732    )
2733    .expect("wrappable")
2734    .expect("present");
2735    assert_eq!(data.flags(), expected);
2736    let attachment = attachment_packet_from_borrowed::<crate::Owned>(
2737      &packet,
2738      PacketLimits::default(),
2739      crate::buffer::PayloadProvenance::CallerSupplied,
2740    )
2741    .expect("wrappable")
2742    .expect("present");
2743    assert_eq!(attachment.flags(), expected);
2744
2745    // And back out again, on the three paths a decoder is fed from.
2746    for (arm, rebuilt) in [
2747      (
2748        "video",
2749        ffmpeg_packet_from_owned_video_packet(&video, PacketLimits::default()).expect("rebuilt"),
2750      ),
2751      (
2752        "audio",
2753        ffmpeg_packet_from_owned_audio_packet(&audio, PacketLimits::default()).expect("rebuilt"),
2754      ),
2755      (
2756        "subtitle",
2757        ffmpeg_packet_from_owned_subtitle_packet(&subtitle, PacketLimits::default())
2758          .expect("rebuilt"),
2759      ),
2760    ] {
2761      // SAFETY: `rebuilt` owns a live `AVPacket`.
2762      let carried = unsafe { (*rebuilt.as_ptr()).flags };
2763      assert_eq!(carried, raw, "{arm} rebuilt {carried:#x} from {raw:#x}");
2764    }
2765  }
2766
2767  #[test]
2768  fn a_flag_bit_the_vocabulary_cannot_hold_refuses_the_packet() {
2769    // Unreachable against this build — every flag FFmpeg names lives in
2770    // the byte `PacketFlags` carries, and a compile-time assertion says
2771    // so. It is here because the day that stops being true, a packet
2772    // must be refused rather than delivered with a bit missing.
2773    use ffmpeg_next::packet::Mut;
2774    let mut packet = Packet::copy(&[1u8]);
2775    // SAFETY: `packet` owns a live `AVPacket`.
2776    unsafe { (*packet.as_mut_ptr()).flags = 0x1_00 };
2777    let tb = mediadecode::Timebase::default();
2778    for (arm, result) in every_timed_arm(&packet) {
2779      assert!(
2780        matches!(
2781          result,
2782          Err(PacketBufferError::UnrepresentableFlags(p)) if p.raw() == 0x1_00
2783        ),
2784        "{arm}: {result:?}",
2785      );
2786    }
2787    assert!(matches!(
2788      attachment_packet_from_borrowed::<crate::Owned>(
2789        &packet,
2790        PacketLimits::default(),
2791        crate::buffer::PayloadProvenance::CallerSupplied
2792      )
2793      .map(|p| p.is_some()),
2794      Err(PacketBufferError::UnrepresentableFlags(_)),
2795    ));
2796    let _ = tb;
2797  }
2798
2799  /// A live `AVBufferRef` of `len` bytes, released when the guard drops.
2800  struct TestBuffer(*mut ffmpeg_next::ffi::AVBufferRef);
2801
2802  impl TestBuffer {
2803    fn new(len: usize) -> Self {
2804      // SAFETY: a plain allocation, checked for null, written through
2805      // its own `data` pointer.
2806      unsafe {
2807        let raw = ffmpeg_next::ffi::av_buffer_alloc(len as _);
2808        assert!(!raw.is_null(), "av_buffer_alloc");
2809        core::ptr::write_bytes((*raw).data, 0xAB, len);
2810        Self(raw)
2811      }
2812    }
2813  }
2814
2815  impl Drop for TestBuffer {
2816    fn drop(&mut self) {
2817      // SAFETY: the guard owns exactly one reference, released once.
2818      unsafe { ffmpeg_next::ffi::av_buffer_unref(&mut self.0) };
2819    }
2820  }
2821
2822  /// The payload pointer a packet's buffer currently holds.
2823  fn payload_address(packet: &Packet) -> usize {
2824    use ffmpeg_next::packet::Ref;
2825    // SAFETY: the packet is live; `data` is a public field.
2826    unsafe { (*packet.as_ptr()).data as usize }
2827  }
2828
2829  /// The refcount of a packet's payload buffer.
2830  fn payload_references(packet: &Packet) -> i32 {
2831    use ffmpeg_next::packet::Ref;
2832    // SAFETY: the packet is live and refcounted; `buf` is a public
2833    // field and `av_buffer_get_ref_count` only reads the atomic.
2834    unsafe {
2835      let buf = (*packet.as_ptr()).buf;
2836      assert!(!buf.is_null(), "the fixture must be refcounted");
2837      ffmpeg_next::ffi::av_buffer_get_ref_count(buf)
2838    }
2839  }
2840
2841  #[test]
2842  fn a_shared_packet_buffer_is_refused_without_being_read() {
2843    // **The consumption argument has a hole a dependency can open, and
2844    // the obvious patch had a worse one.** Taking the packet by value
2845    // proves no *other* handle survives only if the packet's buffer was
2846    // the caller's alone to give. An earlier round answered a shared
2847    // buffer with a silent copy — but a copy needs a read, and a
2848    // refcount above one is exactly the state in which somebody else
2849    // may be writing those bytes, from safe code, on another thread.
2850    // The read *is* the race. So the answer is a refusal that touches
2851    // nothing.
2852    let mut original = Packet::copy(&[7u8; 4096]);
2853    let mut shared = Packet::empty();
2854    // SAFETY: both packets are live; `av_packet_ref` takes a reference
2855    // to `original`'s buffer without copying it.
2856    unsafe {
2857      use ffmpeg_next::packet::{Mut, Ref};
2858      assert_eq!(
2859        ffmpeg_next::ffi::av_packet_ref(shared.as_mut_ptr(), original.as_ptr()),
2860        0,
2861      );
2862    }
2863    assert_eq!(
2864      payload_address(&shared),
2865      payload_address(&original),
2866      "the premise: the two packets share one allocation",
2867    );
2868    assert_eq!(payload_references(&original), 2);
2869
2870    // The refusal is lane-independent, because the hazard is: both
2871    // lanes read the payload, one to view it and one to copy it.
2872    match video_packet_from_ffmpeg_as::<crate::View>(
2873      shared,
2874      mediadecode::Timebase::default(),
2875      PacketLimits::default(),
2876      crate::buffer::PayloadProvenance::CallerSupplied,
2877    ) {
2878      Err(PacketBufferError::SharedPayload(refused)) => {
2879        assert_eq!(refused.references(), 2);
2880      }
2881      other => panic!("a shared payload must be refused by name, got {other:?}"),
2882    }
2883
2884    // Nothing was read and nothing was carried: the packet the caller
2885    // kept is still theirs, still writable, and now sole owner again.
2886    {
2887      let slot = original.data_mut().expect("a writable payload");
2888      slot[0] = 0xEE;
2889    }
2890    assert_eq!(
2891      payload_references(&original),
2892      1,
2893      "the refusal must not have taken a reference of its own",
2894    );
2895
2896    // And the owned lane answers the same way, for the same reason: its
2897    // copy is a read too.
2898    let mut shared_again = Packet::empty();
2899    // SAFETY: as above.
2900    unsafe {
2901      use ffmpeg_next::packet::{Mut, Ref};
2902      assert_eq!(
2903        ffmpeg_next::ffi::av_packet_ref(shared_again.as_mut_ptr(), original.as_ptr()),
2904        0,
2905      );
2906    }
2907    assert!(matches!(
2908      owned_video_packet_from_ffmpeg_in(
2909        &shared_again,
2910        mediadecode::Timebase::default(),
2911        PacketLimits::default(),
2912      ),
2913      Err(PacketBufferError::SharedPayload(_)),
2914    ));
2915  }
2916
2917  #[test]
2918  fn a_clone_that_silently_shared_is_refused_by_name() {
2919    // The exact path: `ffmpeg_next::Packet::clone` calls
2920    // `av_packet_ref` then `av_packet_make_writable` and **ignores both
2921    // return codes**. Under allocation failure the second one leaves
2922    // the "clone" sharing the source's buffer, and nothing says so.
2923    //
2924    // The cap is process-global, so the manufacture runs alone. It is
2925    // lifted before the conversion, because what is being tested is the
2926    // conversion's answer to a shared buffer — not its behaviour under
2927    // memory pressure, which the other lanes cover.
2928    crate::fault_subprocess::in_subprocess(
2929      "boundary::tests::a_clone_that_silently_shared_is_refused_by_name",
2930      || {
2931        let mut original = Packet::copy(&[3u8; 8192]);
2932
2933        // Small allocations still succeed, so `av_packet_ref` gets its
2934        // `AVBufferRef`; a payload-sized one does not, so
2935        // `av_packet_make_writable` fails and is ignored.
2936        crate::fault_subprocess::cap_ffmpeg_allocations(512);
2937        let clone = original.clone();
2938        crate::fault_subprocess::uncap_ffmpeg_allocations();
2939
2940        assert_eq!(
2941          payload_address(&clone),
2942          payload_address(&original),
2943          "the premise this test exists for: a failed `make_writable` \
2944           leaves the clone sharing, silently",
2945        );
2946        assert_eq!(payload_references(&original), 2);
2947
2948        match video_packet_from_ffmpeg_as::<crate::View>(
2949          clone,
2950          mediadecode::Timebase::default(),
2951          PacketLimits::default(),
2952          crate::buffer::PayloadProvenance::CallerSupplied,
2953        ) {
2954          Err(PacketBufferError::SharedPayload(refused)) => {
2955            assert_eq!(refused.references(), 2);
2956          }
2957          other => panic!("expected a named refusal, got {other:?}"),
2958        }
2959
2960        // The handle the caller kept is untouched and unaliased.
2961        {
2962          let slot = original.data_mut().expect("a writable payload");
2963          slot[0] = 0x5A;
2964        }
2965        assert_eq!(payload_references(&original), 1);
2966      },
2967    );
2968  }
2969
2970  #[test]
2971  fn only_a_packet_payload_with_provable_padding_is_shared_into_a_decoder() {
2972    use crate::view::Origin;
2973    use ffmpeg_next::packet::Ref;
2974
2975    const PADDING: usize = ffmpeg_next::ffi::AV_INPUT_BUFFER_PADDING_SIZE as usize;
2976    let src = TestBuffer::new(512);
2977    let body_len = 512 - PADDING;
2978
2979    // The one shape that may share: a payload captured out of an
2980    // `AVPacket`'s own buffer, with libavformat's padding behind it.
2981    // SAFETY: `src` is live for the test and the extent is inside it.
2982    let payload =
2983      unsafe { crate::FfmpegBuffer::view_of(src.0, 0, body_len, Origin::PacketPayload) }
2984        .expect("a view");
2985    let shared = share_or_copy(&payload).expect("built");
2986    // SAFETY: both are live; `data` is a public field.
2987    assert_eq!(
2988      unsafe { (*shared.as_ptr()).data as usize },
2989      payload.as_ref().as_ptr() as usize,
2990      "a packet payload with provable padding must be shared",
2991    );
2992
2993    // **The same bytes, the same slack, no provenance.** This is the
2994    // frame-origin case: a decoded plane or a resampler's output reused
2995    // as a packet body. What follows it is more pixels or more samples,
2996    // and a bitstream reader running past the payload would eat them.
2997    // SAFETY: as above.
2998    let plane =
2999      unsafe { crate::FfmpegBuffer::view_of(src.0, 0, body_len, Origin::Foreign) }.expect("a view");
3000    let copied = share_or_copy(&plane).expect("built");
3001    // SAFETY: both are live.
3002    assert_ne!(
3003      unsafe { (*copied.as_ptr()).data as usize },
3004      plane.as_ref().as_ptr() as usize,
3005      "trailing capacity is not padding provenance — this must be copied",
3006    );
3007    assert_eq!(copied.data().unwrap_or(&[]), plane.as_ref());
3008
3009    // And a payload whose slack is short of the padding is copied too,
3010    // provenance or not: the extent has to hold as well.
3011    // SAFETY: as above.
3012    let tight = unsafe { crate::FfmpegBuffer::view_of(src.0, 0, 511, Origin::PacketPayload) }
3013      .expect("a view");
3014    let copied = share_or_copy(&tight).expect("built");
3015    // SAFETY: both are live.
3016    assert_ne!(
3017      unsafe { (*copied.as_ptr()).data as usize },
3018      tight.as_ref().as_ptr() as usize,
3019      "a payload with less than the padding behind it must be copied",
3020    );
3021
3022    // A narrowed payload has lost its claim, and is copied.
3023    let mut narrowed = payload.clone();
3024    narrowed.shrink_to(16);
3025    let copied = share_or_copy(&narrowed).expect("built");
3026    // SAFETY: both are live.
3027    assert_ne!(
3028      unsafe { (*copied.as_ptr()).data as usize },
3029      narrowed.as_ref().as_ptr() as usize,
3030    );
3031  }
3032
3033  #[test]
3034  fn an_empty_view_carrier_builds_a_payload_less_packet() {
3035    // The empty carrier is backed by **no buffer at all** — that is
3036    // what makes a placeholder plane slot free — so anything that
3037    // reads its `AVBufferRef` before asking whether it is empty
3038    // dereferences null. A side-data-only packet is the ordinary way
3039    // to get here, not an exotic one.
3040    let empty = crate::FfmpegBuffer::empty();
3041    assert!(empty.as_av_buffer_ref().is_null(), "the premise");
3042    let built = share_or_copy(&empty).expect("a payload-less packet");
3043    assert_eq!(built.size(), 0);
3044    // And it is a packet side data can be attached to.
3045    let mut built = built;
3046    attach_side_data(
3047      &mut built,
3048      &[SideDataEntry::new(
3049        NEW_EXTRADATA,
3050        FfmpegBytes::copy_from_slice(&[1u8, 2, 3, 4]),
3051      )],
3052    )
3053    .expect("side data attaches to a payload-less packet");
3054  }
3055
3056  #[test]
3057  fn a_side_data_only_packet_round_trips_on_every_view_decoder_family() {
3058    // Every family, both roads: the public builder a caller can call,
3059    // and the scoped submission a decoder goes through. Neither may
3060    // touch the empty carrier's absent buffer.
3061    let tb = mediadecode::Timebase::default();
3062    let source = side_data_only_packet();
3063    let limits = PacketLimits::default();
3064
3065    let video = video_packet_from_ffmpeg_as::<crate::View>(
3066      source.clone(),
3067      tb,
3068      limits,
3069      crate::buffer::PayloadProvenance::CallerSupplied,
3070    )
3071    .expect("carried")
3072    .expect("a side-data-only packet is a packet");
3073    assert!(video.data().as_ref().is_empty(), "the premise: no payload");
3074    assert_eq!(
3075      ffmpeg_packet_from_video_packet(&video, limits)
3076        .expect("built")
3077        .size(),
3078      0,
3079    );
3080    with_ffmpeg_video_packet::<crate::View, _>(&video, limits, BodyRoute::Submission, |av| {
3081      assert_eq!(av.size(), 0);
3082    })
3083    .expect("submitted");
3084
3085    let audio = audio_packet_from_ffmpeg_as::<crate::View>(
3086      source.clone(),
3087      tb,
3088      limits,
3089      crate::buffer::PayloadProvenance::CallerSupplied,
3090    )
3091    .expect("carried")
3092    .expect("a side-data-only packet is a packet");
3093    assert!(audio.data().as_ref().is_empty());
3094    assert_eq!(
3095      ffmpeg_packet_from_audio_packet(&audio, limits)
3096        .expect("built")
3097        .size(),
3098      0,
3099    );
3100    with_ffmpeg_audio_packet::<crate::View, _>(&audio, limits, BodyRoute::Submission, |av| {
3101      assert_eq!(av.size(), 0);
3102    })
3103    .expect("submitted");
3104
3105    let subtitle = subtitle_packet_from_ffmpeg_as::<crate::View>(
3106      source.clone(),
3107      tb,
3108      limits,
3109      crate::buffer::PayloadProvenance::CallerSupplied,
3110    )
3111    .expect("carried")
3112    .expect("a side-data-only packet is a packet");
3113    assert!(subtitle.data().as_ref().is_empty());
3114    assert_eq!(
3115      ffmpeg_packet_from_subtitle_packet(&subtitle, limits)
3116        .expect("built")
3117        .size(),
3118      0,
3119    );
3120    with_ffmpeg_subtitle_packet::<crate::View, _>(&subtitle, limits, BodyRoute::Submission, |av| {
3121      assert_eq!(av.size(), 0);
3122    })
3123    .expect("submitted");
3124  }
3125
3126  #[test]
3127  fn side_data_survives_the_round_trip_on_every_decoder_bound_arm() {
3128    // The forward direction captures side data; the reverse direction
3129    // is what a decoder is actually handed. Capturing without
3130    // reattaching is theatre: `NEW_EXTRADATA` never reaches the codec,
3131    // `SKIP_SAMPLES` never trims, and nothing says so.
3132    let tb = mediadecode::Timebase::default();
3133    let mut with_body = Packet::copy(&[4u8, 5, 6]);
3134    {
3135      use ffmpeg_next::{
3136        ffi::{AVPacketSideDataType, av_packet_new_side_data},
3137        packet::Mut,
3138      };
3139      unsafe {
3140        let ptr = av_packet_new_side_data(
3141          with_body.as_mut_ptr(),
3142          AVPacketSideDataType::AV_PKT_DATA_SKIP_SAMPLES,
3143          3,
3144        );
3145        assert!(!ptr.is_null());
3146        core::ptr::copy_nonoverlapping([1u8, 2, 3].as_ptr(), ptr, 3);
3147      }
3148    }
3149    const SKIP_SAMPLES: i32 =
3150      ffmpeg_next::ffi::AVPacketSideDataType::AV_PKT_DATA_SKIP_SAMPLES as i32;
3151
3152    for (name, source) in [
3153      ("body plus side data", &with_body),
3154      ("side data only", &side_data_only_packet()),
3155    ] {
3156      let expected_kind = if name == "side data only" {
3157        NEW_EXTRADATA
3158      } else {
3159        SKIP_SAMPLES
3160      };
3161
3162      let video = video_packet_from_borrowed::<crate::Owned>(
3163        source,
3164        tb,
3165        PacketLimits::default(),
3166        crate::buffer::PayloadProvenance::CallerSupplied,
3167      )
3168      .expect("wrappable")
3169      .expect("present");
3170      let rebuilt =
3171        ffmpeg_packet_from_owned_video_packet(&video, PacketLimits::default()).expect("rebuilt");
3172      let carried = packet_side_data(&rebuilt).expect("readable");
3173      assert_eq!(carried.len(), 1, "{name}: video lost its side data");
3174      assert_eq!(carried[0].kind(), expected_kind, "{name}: video");
3175      assert_eq!(carried[0].data(), video.extra().side_data()[0].data());
3176
3177      let audio = audio_packet_from_borrowed::<crate::Owned>(
3178        source,
3179        tb,
3180        PacketLimits::default(),
3181        crate::buffer::PayloadProvenance::CallerSupplied,
3182      )
3183      .expect("wrappable")
3184      .expect("present");
3185      let rebuilt =
3186        ffmpeg_packet_from_owned_audio_packet(&audio, PacketLimits::default()).expect("rebuilt");
3187      let carried = packet_side_data(&rebuilt).expect("readable");
3188      assert_eq!(carried.len(), 1, "{name}: audio lost its side data");
3189      assert_eq!(carried[0].data(), audio.extra().side_data()[0].data());
3190
3191      let subtitle = subtitle_packet_from_borrowed::<crate::Owned>(
3192        source,
3193        tb,
3194        PacketLimits::default(),
3195        crate::buffer::PayloadProvenance::CallerSupplied,
3196      )
3197      .expect("wrappable")
3198      .expect("present");
3199      let rebuilt = ffmpeg_packet_from_owned_subtitle_packet(&subtitle, PacketLimits::default())
3200        .expect("rebuilt");
3201      let carried = packet_side_data(&rebuilt).expect("readable");
3202      assert_eq!(carried.len(), 1, "{name}: subtitle lost its side data");
3203      assert_eq!(carried[0].data(), subtitle.extra().side_data()[0].data());
3204    }
3205  }
3206
3207  #[test]
3208  fn a_side_data_type_this_build_cannot_name_is_refused_not_dropped() {
3209    // This crate carries side-data types as the raw integers they are
3210    // on the wire, so a hand-built entry can name anything. Handing an
3211    // unknown one to C would either form an invalid discriminant or
3212    // attach a type nothing downstream reads — and dropping it quietly
3213    // is the very defect this whole seam exists to close.
3214    let limit = crate::ffi::side_data_type_count();
3215    let packet = mediadecode::packet::VideoPacket::new(
3216      FfmpegBytes::copy_from_slice(&[1u8]),
3217      VideoPacketExtra::new(0).with_side_data(vec![SideDataEntry::new(
3218        limit,
3219        FfmpegBytes::copy_from_slice(&[9u8]),
3220      )]),
3221    );
3222    match ffmpeg_packet_from_owned_video_packet(&packet, PacketLimits::default()).map(|_| ()) {
3223      Err(PacketBuildError::UnknownSideData(p)) => {
3224        assert_eq!(p.kind(), limit);
3225        assert_eq!(p.limit(), limit);
3226      }
3227      other => panic!("expected UnknownSideData, got {other:?}"),
3228    }
3229    assert!(matches!(
3230      ffmpeg_packet_from_owned_video_packet(&mediadecode::packet::VideoPacket::new(
3231        FfmpegBytes::copy_from_slice(&[1u8]),
3232        VideoPacketExtra::new(0).with_side_data(vec![SideDataEntry::new(-1, FfmpegBytes::copy_from_slice(&[9u8]))]),
3233      ), PacketLimits::default())
3234      .map(|_| ()),
3235      Err(PacketBuildError::UnknownSideData(p)) if p.kind() == -1,
3236    ));
3237  }
3238
3239  #[test]
3240  fn a_side_data_only_packet_is_delivered_on_every_timed_arm() {
3241    // Codec-control data with no body is still a packet. Dropped as an
3242    // "empty marker", it leaves a decoder running on parameters the
3243    // container has already replaced — and says nothing about it.
3244    let packet = side_data_only_packet();
3245    let tb = mediadecode::Timebase::default();
3246
3247    let video = video_packet_from_borrowed::<crate::Owned>(
3248      &packet,
3249      tb,
3250      PacketLimits::default(),
3251      crate::buffer::PayloadProvenance::CallerSupplied,
3252    )
3253    .expect("wrappable")
3254    .expect("a side-data-only packet is a packet");
3255    assert!(video.data().as_ref().is_empty(), "no body, but a buffer");
3256    assert_eq!(video.extra().side_data().len(), 1);
3257    assert_eq!(video.extra().side_data()[0].kind(), NEW_EXTRADATA);
3258    assert_eq!(video.extra().side_data()[0].data(), &[1, 2, 3, 4]);
3259
3260    let audio = audio_packet_from_borrowed::<crate::Owned>(
3261      &packet,
3262      tb,
3263      PacketLimits::default(),
3264      crate::buffer::PayloadProvenance::CallerSupplied,
3265    )
3266    .expect("wrappable")
3267    .expect("present");
3268    assert!(audio.data().as_ref().is_empty());
3269    assert_eq!(audio.extra().side_data()[0].data(), &[1, 2, 3, 4]);
3270
3271    let subtitle = subtitle_packet_from_borrowed::<crate::Owned>(
3272      &packet,
3273      tb,
3274      PacketLimits::default(),
3275      crate::buffer::PayloadProvenance::CallerSupplied,
3276    )
3277    .expect("wrappable")
3278    .expect("present");
3279    assert!(subtitle.data().as_ref().is_empty());
3280    assert_eq!(subtitle.extra().side_data()[0].data(), &[1, 2, 3, 4]);
3281
3282    let data = data_packet_from_borrowed::<crate::Owned>(
3283      &packet,
3284      tb,
3285      PacketLimits::default(),
3286      crate::buffer::PayloadProvenance::CallerSupplied,
3287    )
3288    .expect("wrappable")
3289    .expect("present");
3290    assert!(data.data().as_ref().is_empty());
3291    assert_eq!(data.extra().side_data()[0].data(), &[1, 2, 3, 4]);
3292
3293    // The one arm where a payload-less packet really is nothing: an
3294    // attachment is its bytes, and there are none.
3295    assert!(
3296      attachment_packet_from_borrowed::<crate::Owned>(
3297        &packet,
3298        PacketLimits::default(),
3299        crate::buffer::PayloadProvenance::CallerSupplied
3300      )
3301      .expect("wrappable")
3302      .is_none(),
3303      "an attachment with no bytes is no attachment",
3304    );
3305  }
3306
3307  #[test]
3308  fn side_data_rides_a_packet_that_has_a_body_too() {
3309    // The seat's documented promise — "the raw side-data entries from
3310    // `AVPacket.side_data`" — was never kept by the conversion before.
3311    use ffmpeg_next::{
3312      ffi::{AVPacketSideDataType, av_packet_new_side_data},
3313      packet::Mut,
3314    };
3315    let mut packet = Packet::copy(&[7u8, 7, 7]);
3316    unsafe {
3317      let ptr = av_packet_new_side_data(
3318        packet.as_mut_ptr(),
3319        AVPacketSideDataType::AV_PKT_DATA_NEW_EXTRADATA,
3320        2,
3321      );
3322      assert!(!ptr.is_null());
3323      core::ptr::copy_nonoverlapping([9u8, 9].as_ptr(), ptr, 2);
3324    }
3325    let video = video_packet_from_borrowed::<crate::Owned>(
3326      &packet,
3327      mediadecode::Timebase::default(),
3328      PacketLimits::default(),
3329      crate::buffer::PayloadProvenance::CallerSupplied,
3330    )
3331    .expect("wrappable")
3332    .expect("present");
3333    assert_eq!(video.data().as_ref(), &[7, 7, 7]);
3334    assert_eq!(video.extra().side_data()[0].data(), &[9, 9]);
3335  }
3336
3337  #[test]
3338  fn an_empty_packet_is_absent_and_a_real_one_is_present() {
3339    let tb = mediadecode::Timebase::default();
3340    // No payload at all: the marker some demuxers emit. Absent, not an
3341    // error — this is the only thing a pull loop may skip.
3342    let empty = Packet::empty();
3343    assert!(
3344      video_packet_from_borrowed::<crate::Owned>(
3345        &empty,
3346        tb,
3347        PacketLimits::default(),
3348        crate::buffer::PayloadProvenance::CallerSupplied
3349      )
3350      .expect("not a failure")
3351      .is_none()
3352    );
3353    assert!(
3354      attachment_packet_from_borrowed::<crate::Owned>(
3355        &empty,
3356        PacketLimits::default(),
3357        crate::buffer::PayloadProvenance::CallerSupplied
3358      )
3359      .expect("not a failure")
3360      .is_none()
3361    );
3362
3363    let real = Packet::copy(&[9u8, 8, 7]);
3364    let wrapped = video_packet_from_borrowed::<crate::Owned>(
3365      &real,
3366      tb,
3367      PacketLimits::default(),
3368      crate::buffer::PayloadProvenance::CallerSupplied,
3369    )
3370    .expect("wrappable")
3371    .expect("present");
3372    assert_eq!(wrapped.data().as_ref(), &[9, 8, 7]);
3373  }
3374
3375  #[test]
3376  fn nv12_round_trips() {
3377    assert_eq!(
3378      from_av_pixel_format(AVPixelFormat::AV_PIX_FMT_NV12 as i32),
3379      PixelFormat::Nv12,
3380    );
3381  }
3382
3383  #[test]
3384  fn p010be_maps_to_p010be() {
3385    // BE must map to the BE variant — the previous "fold to LE"
3386    // mapping silently corrupted P010BE pixel data via the safe
3387    // export path. The unsupported-format gate in `convert::av_frame_to_video_frame`
3388    // is the right place to reject BE today.
3389    assert_eq!(
3390      from_av_pixel_format(AVPixelFormat::AV_PIX_FMT_P010BE as i32),
3391      PixelFormat::P010Be,
3392    );
3393  }
3394
3395  #[test]
3396  fn unnamed_raw_maps_to_none() {
3397    assert_eq!(from_av_pixel_format(-99_999), PixelFormat::None);
3398  }
3399
3400  #[test]
3401  fn av_pix_fmt_none_maps_to_none() {
3402    assert_eq!(
3403      from_av_pixel_format(AVPixelFormat::AV_PIX_FMT_NONE as i32),
3404      PixelFormat::None,
3405    );
3406  }
3407
3408  #[test]
3409  fn hw_formats_detected() {
3410    assert!(is_hardware_pix_fmt(
3411      AVPixelFormat::AV_PIX_FMT_VIDEOTOOLBOX as i32
3412    ));
3413    assert!(is_hardware_pix_fmt(AVPixelFormat::AV_PIX_FMT_VAAPI as i32));
3414    assert!(is_hardware_pix_fmt(AVPixelFormat::AV_PIX_FMT_CUDA as i32));
3415    assert!(is_hardware_pix_fmt(AVPixelFormat::AV_PIX_FMT_D3D11 as i32));
3416  }
3417
3418  #[test]
3419  fn cpu_formats_not_detected_as_hw() {
3420    assert!(!is_hardware_pix_fmt(AVPixelFormat::AV_PIX_FMT_NV12 as i32));
3421    assert!(!is_hardware_pix_fmt(
3422      AVPixelFormat::AV_PIX_FMT_YUV420P as i32
3423    ));
3424    assert!(!is_hardware_pix_fmt(AVPixelFormat::AV_PIX_FMT_NONE as i32));
3425  }
3426
3427  #[test]
3428  fn hw_formats_map_to_none_in_pixel_format() {
3429    // HW sentinels intentionally don't have a mediadecode::PixelFormat
3430    // representation — they're not CPU pixel data.
3431    assert_eq!(
3432      from_av_pixel_format(AVPixelFormat::AV_PIX_FMT_VIDEOTOOLBOX as i32),
3433      PixelFormat::None,
3434    );
3435    assert_eq!(
3436      from_av_pixel_format(AVPixelFormat::AV_PIX_FMT_VAAPI as i32),
3437      PixelFormat::None,
3438    );
3439  }
3440}