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/// **The one recorder keeps copies.** "Dropped before this returns" is
893/// a claim about this function, and a decoder that kept what it is sent
894/// by reference and handed it back would carry the shared body past it.
895/// The video decoder's hardware probe is that recorder: it keeps every
896/// accepted packet in a rescue history, which `AllBackendsFailed` — or
897/// a failed fallback's `FallbackFailed` — hands to the caller as owned,
898/// **mutable** `Packet`s. It records copies of its own
899/// (`decoder::try_clone_packet`), so nothing in that history addresses
900/// the carrier, and every video send passes [`BodyRoute::Submission`],
901/// in the probe window as after it. [`BodyRoute::Copy`] is for a packet
902/// handed to a caller — [`ffmpeg_packet_from_video_packet`] and its
903/// siblings.
904pub(crate) fn with_ffmpeg_video_packet<C: crate::FfmpegCarrier + crate::CarrierOps, T>(
905  packet: &mediadecode::packet::VideoPacket<VideoPacketExtra, C::Buffer>,
906  limits: PacketLimits,
907  route: BodyRoute,
908  submit: impl FnOnce(&Packet) -> T,
909) -> std::result::Result<T, PacketBuildError> {
910  let av_packet = build_video_packet::<C>(packet, limits, route)?;
911  let out = submit(&av_packet);
912  drop(av_packet);
913  Ok(out)
914}
915
916fn build_video_packet<C: crate::FfmpegCarrier + crate::CarrierOps>(
917  packet: &mediadecode::packet::VideoPacket<VideoPacketExtra, C::Buffer>,
918  limits: PacketLimits,
919  route: BodyRoute,
920) -> std::result::Result<Packet, PacketBuildError> {
921  let body = packet.data().as_ref();
922  // Before the budget and before the allocation: an uncarriable
923  // payload is not made carriable by fitting.
924  refuse_trusted(packet.flags(), body.len())?;
925  // And the annotations, judged whole before the body is allocated —
926  // a list that cannot be carried should not cost a packet first.
927  check_side_data_budget(packet.extra().side_data())?;
928  // **The into-FFmpeg budget, before the allocation.** `try_packet_copy`
929  // duplicates these bytes into a fresh `AVPacket` and checks only that
930  // they fit `c_int`. Without this the configured ceiling was dead on
931  // the road a caller feeds a decoder directly: bytes the demux
932  // boundary would have refused went straight into libavcodec.
933  if body.len() > limits.max_packet_bytes() {
934    return Err(PacketBuildError::SendPayloadTooLarge(
935      SendPayloadTooLarge::new(body.len(), limits.max_packet_bytes()),
936    ));
937  }
938  let mut out = C::packet_body(packet.data(), route)?;
939  attach_side_data(&mut out, packet.extra().side_data())?;
940  if let Some(ts) = packet.pts() {
941    out.set_pts(Some(ts.pts()));
942  }
943  if let Some(ts) = packet.dts() {
944    out.set_dts(Some(ts.pts()));
945  }
946  if let Some(d) = packet.duration() {
947    out.set_duration(d.pts());
948  }
949  // SAFETY: `out` owns the `AVPacket` `try_packet_copy` just built.
950  unsafe { write_md_flags(&mut out, packet.flags()) };
951  out.set_stream(packet.extra().stream_index() as usize);
952  Ok(out)
953}
954
955/// Builds an `ffmpeg::Packet` from a [`mediadecode::AudioPacket`].
956/// Same shape as [`ffmpeg_packet_from_video_packet`] — bytes are
957/// copied; pts/dts/duration/flags/stream_index and side data are
958/// forwarded. Same failure modes.
959pub fn ffmpeg_packet_from_audio_packet(
960  packet: &mediadecode::packet::AudioPacket<AudioPacketExtra, crate::FfmpegBuffer>,
961  limits: PacketLimits,
962) -> std::result::Result<Packet, PacketBuildError> {
963  build_audio_packet::<crate::View>(packet, limits, BodyRoute::Copy)
964}
965
966/// [`ffmpeg_packet_from_audio_packet`] on the owned lane.
967pub fn ffmpeg_packet_from_owned_audio_packet(
968  packet: &mediadecode::packet::AudioPacket<AudioPacketExtra, FfmpegBytes>,
969  limits: PacketLimits,
970) -> std::result::Result<Packet, PacketBuildError> {
971  build_audio_packet::<crate::Owned>(packet, limits, BodyRoute::Copy)
972}
973
974/// [`ffmpeg_packet_from_audio_packet`] built for **submission**, and
975/// scoped so the packet cannot outlive the call.
976///
977/// This is the only road on which the view lane may hand a decoder
978/// its own buffer rather than a copy, and the scoping is why it may.
979/// An `ffmpeg_next::Packet` lends `&mut [u8]` through `data_mut`,
980/// while the carrier it was built from still lends `&[u8]` — so a
981/// shared body reachable from a value a caller *holds* is two live
982/// references to one allocation with one of them mutable, out of
983/// entirely safe code, and `!Sync` would not save it either. Here the
984/// packet is built, lent to `submit` as `&Packet`, and dropped before
985/// this function returns: no value that can produce a `&mut` into the
986/// shared bytes ever exists.
987pub(crate) fn with_ffmpeg_audio_packet<C: crate::FfmpegCarrier + crate::CarrierOps, T>(
988  packet: &mediadecode::packet::AudioPacket<AudioPacketExtra, C::Buffer>,
989  limits: PacketLimits,
990  route: BodyRoute,
991  submit: impl FnOnce(&Packet) -> T,
992) -> std::result::Result<T, PacketBuildError> {
993  let av_packet = build_audio_packet::<C>(packet, limits, route)?;
994  let out = submit(&av_packet);
995  drop(av_packet);
996  Ok(out)
997}
998
999fn build_audio_packet<C: crate::FfmpegCarrier + crate::CarrierOps>(
1000  packet: &mediadecode::packet::AudioPacket<AudioPacketExtra, C::Buffer>,
1001  limits: PacketLimits,
1002  route: BodyRoute,
1003) -> std::result::Result<Packet, PacketBuildError> {
1004  let body = packet.data().as_ref();
1005  // Before the budget and before the allocation: an uncarriable
1006  // payload is not made carriable by fitting.
1007  refuse_trusted(packet.flags(), body.len())?;
1008  // And the annotations, judged whole before the body is allocated —
1009  // a list that cannot be carried should not cost a packet first.
1010  check_side_data_budget(packet.extra().side_data())?;
1011  // **The into-FFmpeg budget, before the allocation.** `try_packet_copy`
1012  // duplicates these bytes into a fresh `AVPacket` and checks only that
1013  // they fit `c_int`. Without this the configured ceiling was dead on
1014  // the road a caller feeds a decoder directly: bytes the demux
1015  // boundary would have refused went straight into libavcodec.
1016  if body.len() > limits.max_packet_bytes() {
1017    return Err(PacketBuildError::SendPayloadTooLarge(
1018      SendPayloadTooLarge::new(body.len(), limits.max_packet_bytes()),
1019    ));
1020  }
1021  let mut out = C::packet_body(packet.data(), route)?;
1022  attach_side_data(&mut out, packet.extra().side_data())?;
1023  if let Some(ts) = packet.pts() {
1024    out.set_pts(Some(ts.pts()));
1025  }
1026  if let Some(ts) = packet.dts() {
1027    out.set_dts(Some(ts.pts()));
1028  }
1029  if let Some(d) = packet.duration() {
1030    out.set_duration(d.pts());
1031  }
1032  // SAFETY: `out` owns the `AVPacket` `try_packet_copy` just built.
1033  unsafe { write_md_flags(&mut out, packet.flags()) };
1034  out.set_stream(packet.extra().stream_index() as usize);
1035  Ok(out)
1036}
1037
1038/// Builds an `ffmpeg::Packet` from a [`mediadecode::SubtitlePacket`].
1039/// Bytes copied; pts/duration/flags/stream_index and side data
1040/// forwarded. Subtitle packets have no `dts` in the mediadecode model.
1041/// Same failure modes as [`ffmpeg_packet_from_video_packet`].
1042pub fn ffmpeg_packet_from_subtitle_packet(
1043  packet: &mediadecode::packet::SubtitlePacket<SubtitlePacketExtra, crate::FfmpegBuffer>,
1044  limits: PacketLimits,
1045) -> std::result::Result<Packet, PacketBuildError> {
1046  build_subtitle_packet::<crate::View>(packet, limits, BodyRoute::Copy)
1047}
1048
1049/// [`ffmpeg_packet_from_subtitle_packet`] on the owned lane.
1050pub fn ffmpeg_packet_from_owned_subtitle_packet(
1051  packet: &mediadecode::packet::SubtitlePacket<SubtitlePacketExtra, FfmpegBytes>,
1052  limits: PacketLimits,
1053) -> std::result::Result<Packet, PacketBuildError> {
1054  build_subtitle_packet::<crate::Owned>(packet, limits, BodyRoute::Copy)
1055}
1056
1057/// [`ffmpeg_packet_from_subtitle_packet`] built for **submission**, and
1058/// scoped so the packet cannot outlive the call.
1059///
1060/// This is the only road on which the view lane may hand a decoder
1061/// its own buffer rather than a copy, and the scoping is why it may.
1062/// An `ffmpeg_next::Packet` lends `&mut [u8]` through `data_mut`,
1063/// while the carrier it was built from still lends `&[u8]` — so a
1064/// shared body reachable from a value a caller *holds* is two live
1065/// references to one allocation with one of them mutable, out of
1066/// entirely safe code, and `!Sync` would not save it either. Here the
1067/// packet is built, lent to `submit` as `&Packet`, and dropped before
1068/// this function returns: no value that can produce a `&mut` into the
1069/// shared bytes ever exists.
1070pub(crate) fn with_ffmpeg_subtitle_packet<C: crate::FfmpegCarrier + crate::CarrierOps, T>(
1071  packet: &mediadecode::packet::SubtitlePacket<SubtitlePacketExtra, C::Buffer>,
1072  limits: PacketLimits,
1073  route: BodyRoute,
1074  submit: impl FnOnce(&Packet) -> T,
1075) -> std::result::Result<T, PacketBuildError> {
1076  let av_packet = build_subtitle_packet::<C>(packet, limits, route)?;
1077  let out = submit(&av_packet);
1078  drop(av_packet);
1079  Ok(out)
1080}
1081
1082fn build_subtitle_packet<C: crate::FfmpegCarrier + crate::CarrierOps>(
1083  packet: &mediadecode::packet::SubtitlePacket<SubtitlePacketExtra, C::Buffer>,
1084  limits: PacketLimits,
1085  route: BodyRoute,
1086) -> std::result::Result<Packet, PacketBuildError> {
1087  let body = packet.data().as_ref();
1088  // Before the budget and before the allocation: an uncarriable
1089  // payload is not made carriable by fitting.
1090  refuse_trusted(packet.flags(), body.len())?;
1091  // And the annotations, judged whole before the body is allocated —
1092  // a list that cannot be carried should not cost a packet first.
1093  check_side_data_budget(packet.extra().side_data())?;
1094  // **The into-FFmpeg budget, before the allocation.** `try_packet_copy`
1095  // duplicates these bytes into a fresh `AVPacket` and checks only that
1096  // they fit `c_int`. Without this the configured ceiling was dead on
1097  // the road a caller feeds a decoder directly: bytes the demux
1098  // boundary would have refused went straight into libavcodec.
1099  if body.len() > limits.max_packet_bytes() {
1100    return Err(PacketBuildError::SendPayloadTooLarge(
1101      SendPayloadTooLarge::new(body.len(), limits.max_packet_bytes()),
1102    ));
1103  }
1104  let mut out = C::packet_body(packet.data(), route)?;
1105  attach_side_data(&mut out, packet.extra().side_data())?;
1106  if let Some(ts) = packet.pts() {
1107    out.set_pts(Some(ts.pts()));
1108  }
1109  if let Some(d) = packet.duration() {
1110    out.set_duration(d.pts());
1111  }
1112  // SAFETY: `out` owns the `AVPacket` `try_packet_copy` just built.
1113  unsafe { write_md_flags(&mut out, packet.flags()) };
1114  out.set_stream(packet.extra().stream_index() as usize);
1115  Ok(out)
1116}
1117
1118// ---------------------------------------------------------------------------
1119//  Safe wrappers — `&ffmpeg::Packet` → `mediadecode::*Packet`.
1120// ---------------------------------------------------------------------------
1121
1122/// Carries a borrowed [`ffmpeg::Packet`] out as a
1123/// [`mediadecode::packet::VideoPacket`] on the **view** lane: the
1124/// payload is a refcounted window onto the source `AVPacket`'s own
1125/// buffer, which is why the delivered packet's lifetime is answerable
1126/// to libavformat's — see [the two carrier lanes][lanes]. For a packet
1127/// that must outlive the demuxer, ask for the owned lane by name:
1128/// `video_packet_from_ffmpeg_as::<Owned>`, whose payload is copied and
1129/// which the [D-seat amputation contract][law] governs.
1130///
1131/// Timestamps, duration, key/corrupt flags, and the source stream index
1132/// are forwarded to the produced packet.
1133///
1134/// Uses the default [`PacketLimits`]; the `_in` sibling takes them
1135/// explicitly, alongside the stream's timebase.
1136///
1137/// [lanes]: mediadecode::adapter#the-two-carrier-lanes
1138///
1139/// Returns `Ok(None)` when the source packet has no payload at all
1140/// (an empty packet — typical after EOF), and [`PacketBufferError`]
1141/// when a payload that *is* there could not be carried — over budget,
1142/// or claiming bytes outside its own buffer. Those are never the same
1143/// answer. Caller can also fill in [`VideoPacketExtra::byte_pos`] /
1144/// `side_data` post-construction if they need those.
1145///
1146/// [law]: mediadecode::adapter#the-d-seat-amputation-contract
1147pub fn video_packet_from_ffmpeg(
1148  packet: &Packet,
1149  time_base: mediadecode::Timebase,
1150) -> Result<Option<VideoPacket<VideoPacketExtra, FfmpegBytes>>, PacketBufferError> {
1151  video_packet_from_borrowed::<crate::Owned>(
1152    packet,
1153    time_base,
1154    PacketLimits::default(),
1155    crate::buffer::PayloadProvenance::CallerSupplied,
1156  )
1157}
1158
1159/// Carries a borrowed [`ffmpeg::Packet`] out as a
1160/// [`mediadecode::packet::AudioPacket`]. Same shape as
1161/// [`video_packet_from_ffmpeg`] — shared payload, forwarded metadata,
1162/// default budgets.
1163pub fn audio_packet_from_ffmpeg(
1164  packet: &Packet,
1165  time_base: mediadecode::Timebase,
1166) -> Result<Option<AudioPacket<AudioPacketExtra, FfmpegBytes>>, PacketBufferError> {
1167  audio_packet_from_borrowed::<crate::Owned>(
1168    packet,
1169    time_base,
1170    PacketLimits::default(),
1171    crate::buffer::PayloadProvenance::CallerSupplied,
1172  )
1173}
1174
1175/// Carries a borrowed [`ffmpeg::Packet`] out as a
1176/// [`mediadecode::packet::SubtitlePacket`]. Subtitle packets have no
1177/// `dts` in the mediadecode model; everything else mirrors
1178/// [`video_packet_from_ffmpeg`], shared payload included.
1179pub fn subtitle_packet_from_ffmpeg(
1180  packet: &Packet,
1181  time_base: mediadecode::Timebase,
1182) -> Result<Option<SubtitlePacket<SubtitlePacketExtra, FfmpegBytes>>, PacketBufferError> {
1183  subtitle_packet_from_borrowed::<crate::Owned>(
1184    packet,
1185    time_base,
1186    PacketLimits::default(),
1187    crate::buffer::PayloadProvenance::CallerSupplied,
1188  )
1189}
1190
1191/// The most side-data entries this crate will walk on one packet.
1192///
1193/// The floor is [`SIDE_DATA_MAX_ENTRIES`], the same bound the
1194/// frame-side collector uses; it rises with `AV_PKT_DATA_NB` so a
1195/// future FFmpeg that names more side-data types than the floor cannot
1196/// turn a legitimate packet into a refusal. Measured: FFmpeg's own
1197/// packet API cannot exceed one entry per named type — both
1198/// `av_packet_new_side_data` and `av_packet_add_side_data` replace an
1199/// existing entry of the same type — so a packet over this cap is one
1200/// no FFmpeg call produced.
1201fn side_data_entry_cap() -> usize {
1202  SIDE_DATA_MAX_ENTRIES.max(crate::ffi::side_data_type_count().max(0) as usize)
1203}
1204
1205/// The side-data entries an `AVPacket` carries, copied into owned
1206/// values — **all of them, or none and an error**.
1207///
1208/// The packet twin of `convert::collect_side_data`, and bounded the
1209/// same way: at most [`side_data_entry_cap`] entries and
1210/// [`SIDE_DATA_MAX_TOTAL_BYTES`] bytes per packet, allocated through
1211/// `try_reserve_exact`. What is *not* the same is what happens when a
1212/// bound is reached. The frame collector truncates and warns, which it
1213/// can afford to — frame side data is descriptive, the frame is
1214/// delivered either way, and nothing downstream acts on it. Packet side
1215/// data is the opposite: `NEW_EXTRADATA` replaces a decoder's
1216/// parameters, `PARAM_CHANGE` moves its rate, `SKIP_SAMPLES` trims the
1217/// stream, and the codec acts on every one. A truncated copy is a
1218/// decoder quietly running on stale parameters, and a truncated copy of
1219/// a side-data-only packet is `Ok(None)` — the packet vanishing
1220/// entirely, which is the very defect this seam was built to close. So
1221/// every bound here is an error, and a caller either gets a packet with
1222/// all of its side data or a `DemuxError` naming what stopped it.
1223///
1224/// `AVPacket.side_data` is a flat array of `AVPacketSideData` (not the
1225/// array of pointers an `AVFrame` keeps), and its `type_` is read as
1226/// the integer it is on the wire — a discriminant this build has no
1227/// name for would be undefined behaviour the moment it existed as an
1228/// `AVPacketSideDataType`.
1229fn packet_side_data(packet: &Packet) -> Result<Vec<SideDataEntry>, PacketBufferError> {
1230  use ffmpeg_next::packet::Ref;
1231  // SAFETY: `packet` keeps the `AVPacket` live; `side_data` and
1232  // `side_data_elems` are public fields.
1233  let count_raw = unsafe { (*packet.as_ptr()).side_data_elems };
1234  let entries = unsafe { (*packet.as_ptr()).side_data };
1235  // Zero entries is the only shape that means "no side data". Every
1236  // other reading of that answer — a malformed count, a missing array —
1237  // is judged, and judged *before* the pointer: a count this crate
1238  // cannot walk stays an error whether or not the array happens to be
1239  // null, and a null array with entries to read is malformed rather
1240  // than empty. Both used to leave here as `Ok(vec![])`, which is the
1241  // silent loss the caps taught us to name, reached through the
1242  // pointer instead of the budget.
1243  if count_raw == 0 {
1244    return Ok(Vec::new());
1245  }
1246  let cap = side_data_entry_cap();
1247  if count_raw < 0 || count_raw as usize > cap {
1248    return Err(PacketBufferError::SideDataEntries(SideDataEntries::new(
1249      count_raw, cap,
1250    )));
1251  }
1252  if entries.is_null() {
1253    return Err(PacketBufferError::SideDataArray(SideDataArray::new(
1254      count_raw,
1255    )));
1256  }
1257  let count = count_raw as usize;
1258  let mut out: Vec<SideDataEntry> = Vec::new();
1259  if out.try_reserve_exact(count).is_err() {
1260    return Err(PacketBufferError::SideDataAlloc(BufferSideDataAlloc::new(
1261      count * core::mem::size_of::<SideDataEntry>(),
1262    )));
1263  }
1264  let mut total_bytes: usize = 0;
1265  for index in 0..count {
1266    // SAFETY: `entries` is valid for `count_raw` contiguous
1267    // `AVPacketSideData` values per FFmpeg's contract, and `index` is
1268    // below that count.
1269    let entry = unsafe { entries.add(index) };
1270    let kind = unsafe { read_unaligned(addr_of!((*entry).type_).cast::<i32>()) };
1271    let size = unsafe { (*entry).size };
1272    let data_ptr = unsafe { (*entry).data };
1273    let data = if size == 0 {
1274      // A marker entry: a type and no bytes. FFmpeg emits these, and
1275      // there is nothing to carry or to charge the budget for.
1276      FfmpegBytes::empty()
1277    } else if data_ptr.is_null() {
1278      // Bytes declared and not carried. Reading this as an empty entry
1279      // delivered a packet whose side data was a lie, and charged the
1280      // budget nothing for it.
1281      return Err(PacketBufferError::SideDataPayload(SideDataPayload::new(
1282        index, size,
1283      )));
1284    } else {
1285      total_bytes = total_bytes.saturating_add(size);
1286      if total_bytes > SIDE_DATA_MAX_TOTAL_BYTES {
1287        return Err(PacketBufferError::SideDataBytes(SideDataBytes::new(
1288          total_bytes,
1289          SIDE_DATA_MAX_TOTAL_BYTES,
1290        )));
1291      }
1292      // **One allocation, and a fallible one.** This used to stage
1293      // through a `try_reserve_exact`ed `Vec` and then copy again into
1294      // the carrier — two payload-sized allocations, the second
1295      // infallible — with a comment conceding the doubling was
1296      // affordable only because side data is capped. The carrier
1297      // allocates fallibly itself now, so the staging bought nothing
1298      // and is gone with it.
1299      //
1300      // SAFETY: `data` is valid for `size` bytes per FFmpeg's
1301      // `AVPacketSideData` contract.
1302      FfmpegBytes::try_copy_from_slice(unsafe { core::slice::from_raw_parts(data_ptr, size) })
1303        .ok_or(PacketBufferError::SideDataAlloc(BufferSideDataAlloc::new(
1304          size,
1305        )))?
1306    };
1307    out.push(SideDataEntry::new(kind, data));
1308  }
1309  Ok(out)
1310}
1311
1312/// The buffer a timed packet is delivered with, or `None` when there is
1313/// no packet to deliver at all.
1314///
1315/// **`size == 0` is not the same as "nothing".** A packet with no body
1316/// and one or more side-data entries is a real packet: FFmpeg uses that
1317/// shape for `AV_PKT_DATA_NEW_EXTRADATA` and for a parameter change,
1318/// and a decoder that never sees it keeps decoding on parameters the
1319/// container has already replaced. Such a packet is delivered with an
1320/// owned empty buffer — zero bytes, but a buffer, so the packet exists
1321/// and its side data rides the extras.
1322///
1323/// `Ok(None)` therefore means the packet carried neither a payload nor
1324/// side data: the empty marker, and the only thing a pull loop may skip.
1325fn delivered_payload<C: crate::FfmpegCarrier + crate::CarrierOps>(
1326  packet: &Packet,
1327  side_data: &[SideDataEntry],
1328  limits: PacketLimits,
1329  provenance: crate::buffer::PayloadProvenance,
1330) -> Result<Option<C::Buffer>, PacketBufferError> {
1331  use ffmpeg_next::packet::Ref;
1332  // SAFETY: `packet` keeps the AVPacket live for the duration of this
1333  // call, which is all `payload_of` requires.
1334  if let Some(bytes) = unsafe {
1335    crate::buffer::payload_of::<C>(packet.as_ptr(), limits.max_packet_bytes(), provenance)
1336  }? {
1337    return Ok(Some(bytes));
1338  }
1339  if side_data.is_empty() {
1340    return Ok(None);
1341  }
1342  Ok(Some(C::empty()))
1343}
1344
1345// ---------------------------------------------------------------------------
1346//  Timebase-carrying variants.
1347//
1348//  An `AVPacket`'s timestamps are integers in its *stream's* timebase,
1349//  which the packet does not carry. The functions above therefore ask
1350//  the caller for it — they used to stamp `Timebase::default()` (1/1)
1351//  instead, which turned PTS 90,000 from a 1/90,000 stream into a
1352//  self-describing timestamp reading 90,000 *seconds*, with nothing in
1353//  the value marking 1/1 as a placeholder rather than a reading. A
1354//  demuxer knows the real one: it holds the track table. The variants
1355//  below add explicit budgets on top of the same requirement.
1356// ---------------------------------------------------------------------------
1357
1358/// [`video_packet_from_ffmpeg`], with explicit carrier and
1359/// provenance seats on top of the same timebase requirement.
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 explicit carrier and
1408/// provenance seats on top of the same timebase requirement.
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 explicit carrier and
1457/// provenance seats on top of the same timebase requirement.
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/// `AV_PKT_FLAG_DISPOSABLE` is the same bit as `PacketFlags::DISPOSABLE`,
1635/// so a demuxer's "nothing references this packet" arrives under its
1636/// name, and a packet the demuxer did not mark arrives without it:
1637/// unknown, not referenced. In libavformat 9.0.1 the MP4/MOV demuxer is
1638/// the one that sets it, from `sdtp` (`mov.c` 11719–11723); the Matroska
1639/// demuxer does not map its discardable bit (`matroskadec.c` 4319–4320
1640/// reads the keyframe bit alone), so a Matroska packet arrives without it.
1641/// A bit outside that byte cannot be carried at all, and is refused
1642/// rather than dropped; the assertion below states the fact that keeps
1643/// the refusal unreachable against this build.
1644fn md_flags_from_packet(packet: &Packet) -> Result<MdPacketFlags, PacketBufferError> {
1645  use ffmpeg_next::packet::Ref;
1646  // SAFETY: `packet` keeps the `AVPacket` live for the call, which is
1647  // all the raw reader asks.
1648  unsafe { md_flags_from_av_packet(packet.as_ptr()) }
1649}
1650
1651/// [`md_flags_from_packet`] for an `AVPacket` that has no safe wrapper
1652/// — the one libavformat embeds in an `AVStream` for cover art.
1653///
1654/// The demuxer hoists that packet by hand, and hoisting it *without*
1655/// its flags was how an attached picture arrived with none: FFmpeg
1656/// marks it `AV_PKT_FLAG_KEY`, which is the one thing a still image is
1657/// certain to be. One reader, so a second construction site cannot
1658/// quietly disagree with the five that go through the boundary.
1659///
1660/// # Safety
1661///
1662/// `pkt` must be a live `*const AVPacket` for the duration of this
1663/// call.
1664pub(crate) unsafe fn md_flags_from_av_packet(
1665  pkt: *const ffmpeg_next::ffi::AVPacket,
1666) -> Result<MdPacketFlags, PacketBufferError> {
1667  // SAFETY: `pkt` is live per the contract above; `flags` is a public
1668  // field and a plain `c_int`.
1669  let raw = unsafe { (*pkt).flags };
1670  let carried = i32::from(u8::MAX);
1671  if raw & !carried != 0 {
1672    return Err(PacketBufferError::UnrepresentableFlags(
1673      UnrepresentableFlags::new(raw),
1674    ));
1675  }
1676  Ok(MdPacketFlags::from_bits_retain(raw as u8))
1677}
1678
1679/// What a track is, folded from `AVCodecParameters.codec_type` read as
1680/// the integer it is on the wire.
1681///
1682/// The dependency-API half of this crate's open-C-enum discipline.
1683/// `ffmpeg_next::codec::Parameters::medium()` materialises an
1684/// `AVMediaType` out of FFmpeg memory to answer the same question, and
1685/// a value outside this build's discriminant set is undefined behaviour
1686/// the moment it exists — before any `match` on it can run. That the
1687/// set is small and has been stable for years is a reason it has not
1688/// bitten, not a reason it cannot.
1689///
1690/// So the read is raw and the fold is total: anything this build does
1691/// not name becomes [`Unknown`](Self::Unknown), which the callers
1692/// already had to handle.
1693#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, IsVariant)]
1694pub enum MediaKind {
1695  /// `AVMEDIA_TYPE_VIDEO`.
1696  Video,
1697  /// `AVMEDIA_TYPE_AUDIO`.
1698  Audio,
1699  /// `AVMEDIA_TYPE_SUBTITLE`.
1700  Subtitle,
1701  /// `AVMEDIA_TYPE_DATA`.
1702  Data,
1703  /// `AVMEDIA_TYPE_ATTACHMENT`.
1704  Attachment,
1705  /// Anything this build of FFmpeg does not name, `AVMEDIA_TYPE_UNKNOWN`
1706  /// included.
1707  Unknown,
1708}
1709
1710/// Folds a raw `AVMediaType` integer into [`MediaKind`].
1711pub(crate) fn media_kind_from_raw(raw: i32) -> MediaKind {
1712  use ffmpeg_next::ffi::AVMediaType::*;
1713  match raw {
1714    x if x == AVMEDIA_TYPE_VIDEO as i32 => MediaKind::Video,
1715    x if x == AVMEDIA_TYPE_AUDIO as i32 => MediaKind::Audio,
1716    x if x == AVMEDIA_TYPE_SUBTITLE as i32 => MediaKind::Subtitle,
1717    x if x == AVMEDIA_TYPE_DATA as i32 => MediaKind::Data,
1718    x if x == AVMEDIA_TYPE_ATTACHMENT as i32 => MediaKind::Attachment,
1719    _ => MediaKind::Unknown,
1720  }
1721}
1722
1723/// The medium a set of codec parameters declares, without forming an
1724/// `AVMediaType`. Replaces every `Parameters::medium()` call in this
1725/// crate — see [`MediaKind`].
1726pub(crate) fn media_kind_of(parameters: &ffmpeg_next::codec::Parameters) -> MediaKind {
1727  // SAFETY: `as_ptr` is `unsafe` only because the pointer must not
1728  // outlive `parameters`; it is used and discarded inside this call.
1729  let ptr = unsafe { parameters.as_ptr() };
1730  if ptr.is_null() {
1731    return MediaKind::Unknown;
1732  }
1733  // SAFETY: `ptr` is a live `*const AVCodecParameters`; `addr_of!`
1734  // computes the field address without forming a reference, and reading
1735  // as `i32` matches the bindgen enum's `c_int` storage.
1736  let raw = unsafe { read_unaligned(addr_of!((*ptr).codec_type) as *const i32) };
1737  media_kind_from_raw(raw)
1738}
1739
1740/// Every packet flag this build of FFmpeg names fits the byte
1741/// `PacketFlags` carries. If that stops being true, this fails the
1742/// build rather than letting a flag go missing at run time.
1743const _: () = {
1744  assert!(
1745    (ffmpeg_next::ffi::AV_PKT_FLAG_KEY
1746      | ffmpeg_next::ffi::AV_PKT_FLAG_CORRUPT
1747      | ffmpeg_next::ffi::AV_PKT_FLAG_DISCARD
1748      | ffmpeg_next::ffi::AV_PKT_FLAG_TRUSTED
1749      | ffmpeg_next::ffi::AV_PKT_FLAG_DISPOSABLE)
1750      <= u8::MAX as c_int,
1751    "FFmpeg names a packet flag outside the byte `PacketFlags` carries",
1752  );
1753};
1754
1755/// `PacketFlags::DISPOSABLE` is FFmpeg's `AV_PKT_FLAG_DISPOSABLE` bit for
1756/// bit. The raw flags byte crosses the boundary unchanged on both legs,
1757/// so no mapping stands between the demuxer's hint and its name.
1758const _: () = assert!(
1759  MdPacketFlags::DISPOSABLE.bits() as c_int == ffmpeg_next::ffi::AV_PKT_FLAG_DISPOSABLE,
1760  "`PacketFlags::DISPOSABLE` is not FFmpeg's `AV_PKT_FLAG_DISPOSABLE`",
1761);
1762
1763// ---------------------------------------------------------------------------
1764//  Empty-frame placeholders for `receive_frame` destinations.
1765// ---------------------------------------------------------------------------
1766
1767/// Constructs an empty [`mediadecode::frame::VideoFrame`] suitable as
1768/// the destination argument to
1769/// [`mediadecode::decoder::VideoStreamDecoder::receive_frame`]. The
1770/// decoder overwrites the frame on success; this just provides a
1771/// well-formed slot.
1772///
1773/// All four plane slots hold the shared empty carrier (the array shape
1774/// requires a buffer in every slot, but `plane_count = 0` reports them
1775/// as inactive).
1776///
1777/// **Infallible on both lanes, and the `try_` sibling is gone.**
1778/// Through 0.8 each slot was its own one-byte `AVBufferRef`, so
1779/// building a placeholder was four FFmpeg allocations that could fail —
1780/// hence a `try_empty_video_frame` returning `Option` and an
1781/// `empty_video_frame` that panicked on `None`. Neither lane allocates
1782/// here now: the owned lane's empty carrier is made once for the
1783/// process and cloned by refcount, and the view lane's is a null-backed
1784/// zero-length view, which is what lets the *view* lane keep the same
1785/// infallible constructor rather than reintroducing 0.8's failure mode
1786/// under a new name. A constructor with no failure mode does not get to
1787/// keep a `Result`-shaped door.
1788pub(crate) fn empty_video_frame_as<C: crate::FfmpegCarrier + crate::CarrierOps>()
1789-> VideoFrame<PixelFormat, VideoFrameExtra, C::Buffer> {
1790  VideoFrame::new(
1791    Dimensions::new(0, 0),
1792    // mediaframe 0.3's named "no format yet" member, and its
1793    // `Default` — the state a descriptor is in before a decoder has
1794    // said what it produces, which is exactly this placeholder.
1795    PixelFormat::None,
1796    core::array::from_fn(|_| Plane::new(C::empty(), 0)),
1797    0,
1798    VideoFrameExtra::default(),
1799  )
1800}
1801
1802/// Constructs an empty [`mediadecode::frame::AudioFrame`] suitable as
1803/// the destination argument to
1804/// [`mediadecode::decoder::AudioStreamDecoder::receive_frame`]. Same
1805/// behaviour as [`empty_video_frame`] — eight shared empty plane
1806/// carriers, `plane_count = 0`, and no way to fail.
1807pub(crate) fn empty_audio_frame_as<C: crate::FfmpegCarrier + crate::CarrierOps>()
1808-> AudioFrame<SampleFormat, ChannelLayoutDescription, AudioFrameExtra, C::Buffer> {
1809  AudioFrame::new(
1810    0,
1811    0,
1812    0,
1813    SampleFormat::NONE,
1814    ChannelLayoutDescription::default(),
1815    core::array::from_fn(|_| Plane::new(C::empty(), 0)),
1816    0,
1817    AudioFrameExtra::default(),
1818  )
1819}
1820
1821/// Constructs an empty [`mediadecode::frame::SubtitleFrame`] suitable
1822/// as the destination argument to
1823/// [`mediadecode::decoder::SubtitleDecoder::receive_frame`]. The
1824/// payload is an empty `Text` placeholder; the decoder overwrites it
1825/// on success. Infallible, as its two siblings are.
1826pub(crate) fn empty_subtitle_frame_as<C: crate::FfmpegCarrier + crate::CarrierOps>()
1827-> SubtitleFrame<SubtitleFrameExtra, C::Buffer> {
1828  SubtitleFrame::new(
1829    SubtitlePayload::Text(SubtitleText::new(C::empty(), None)),
1830    SubtitleFrameExtra::default(),
1831  )
1832}
1833
1834// --- The bare names, on the view lane --------------------------------
1835//
1836// **The signature says the lane, and the compiler enforces it.** A
1837// conversion that *borrows* its `AVPacket` can only copy: the packet
1838// type lends `&mut [u8]` and shares its buffer by refcount with no
1839// copy-on-write, so a caller who still holds the packet holds a mutable
1840// alias of anything a view would read. A conversion that *consumes* its
1841// packet can share, because after it returns there is no other handle.
1842//
1843// So: **borrow in, owned lane out; move in, either lane out.** The
1844// `_in` names below take the packet by value and answer with the view
1845// lane — the ordinary road, where a direct consumer reads a packet in
1846// place and drops it — while the lane-generic `_as` workers, also
1847// by value, let a caller ask for either. The borrowing doors are the
1848// bare `*_packet_from_ffmpeg` names, and they are the owned lane.
1849//
1850// A generic function has no default parameter to fall back on (defaults
1851// are used when a type is *written*, not inferred from a call), so
1852// these are monomorphic wrappers rather than a default that would never
1853// apply.
1854
1855/// [`video_packet_from_ffmpeg_as`] on the view lane, with the
1856/// stream's timebase stamped onto every timestamp.
1857///
1858/// **Takes the packet by value**, and that is the safety property, not
1859/// a style choice. The program below is the alias this signature
1860/// forbids — it was accepted when the parameter was a reference, and
1861/// `data_mut` handed out a `&mut [u8]` over bytes the returned carrier
1862/// was still lending as `&[u8]`:
1863///
1864/// ```compile_fail,E0382
1865/// use ffmpeg_next::packet::Mut;
1866/// use mediadecode::Timebase;
1867/// use mediadecode_ffmpeg::{PacketLimits, video_packet_from_ffmpeg_in};
1868///
1869/// let mut packet = ffmpeg_next::Packet::copy(&[0u8; 64]);
1870/// let viewed = video_packet_from_ffmpeg_in(packet, Timebase::default(), PacketLimits::default());
1871/// // The source is gone, so this cannot be written.
1872/// let _aliased = packet.data_mut();
1873/// ```
1874///
1875/// A caller who wants to keep the packet wants the owned lane, which
1876/// copies and may therefore borrow:
1877/// [`owned_video_packet_from_ffmpeg_in`].
1878pub fn video_packet_from_ffmpeg_in(
1879  packet: Packet,
1880  time_base: mediadecode::Timebase,
1881  limits: PacketLimits,
1882) -> Result<Option<VideoPacket<VideoPacketExtra, crate::FfmpegBuffer>>, PacketBufferError> {
1883  video_packet_from_ffmpeg_as::<crate::View>(
1884    packet,
1885    time_base,
1886    limits,
1887    crate::buffer::PayloadProvenance::CallerSupplied,
1888  )
1889}
1890
1891/// [`audio_packet_from_ffmpeg_as`] on the view lane.
1892pub fn audio_packet_from_ffmpeg_in(
1893  packet: Packet,
1894  time_base: mediadecode::Timebase,
1895  limits: PacketLimits,
1896) -> Result<Option<AudioPacket<AudioPacketExtra, crate::FfmpegBuffer>>, PacketBufferError> {
1897  audio_packet_from_ffmpeg_as::<crate::View>(
1898    packet,
1899    time_base,
1900    limits,
1901    crate::buffer::PayloadProvenance::CallerSupplied,
1902  )
1903}
1904
1905/// [`subtitle_packet_from_ffmpeg_as`] on the view lane.
1906pub fn subtitle_packet_from_ffmpeg_in(
1907  packet: Packet,
1908  time_base: mediadecode::Timebase,
1909  limits: PacketLimits,
1910) -> Result<Option<SubtitlePacket<SubtitlePacketExtra, crate::FfmpegBuffer>>, PacketBufferError> {
1911  subtitle_packet_from_ffmpeg_as::<crate::View>(
1912    packet,
1913    time_base,
1914    limits,
1915    crate::buffer::PayloadProvenance::CallerSupplied,
1916  )
1917}
1918
1919/// [`data_packet_from_ffmpeg_as`] on the view lane.
1920pub fn data_packet_from_ffmpeg_in(
1921  packet: Packet,
1922  time_base: mediadecode::Timebase,
1923  limits: PacketLimits,
1924) -> Result<Option<DataPacket<DataPacketExtra, crate::FfmpegBuffer>>, PacketBufferError> {
1925  data_packet_from_ffmpeg_as::<crate::View>(
1926    packet,
1927    time_base,
1928    limits,
1929    crate::buffer::PayloadProvenance::CallerSupplied,
1930  )
1931}
1932
1933// --- The borrowing doors, on the owned lane --------------------------
1934//
1935// The owned lane may borrow because it copies: nothing it returns
1936// points at the source, so the source's fate is its own business.
1937// These are the shapes the view lane cannot offer, and the reason it
1938// cannot is the whole of the split — see the note above the `_in`
1939// family.
1940
1941/// [`video_packet_from_ffmpeg`] with the stream's timebase and explicit
1942/// budgets.
1943pub fn owned_video_packet_from_ffmpeg_in(
1944  packet: &Packet,
1945  time_base: mediadecode::Timebase,
1946  limits: PacketLimits,
1947) -> Result<Option<VideoPacket<VideoPacketExtra, FfmpegBytes>>, PacketBufferError> {
1948  video_packet_from_borrowed::<crate::Owned>(
1949    packet,
1950    time_base,
1951    limits,
1952    crate::buffer::PayloadProvenance::CallerSupplied,
1953  )
1954}
1955
1956/// [`audio_packet_from_ffmpeg`] with the stream's timebase and explicit
1957/// budgets.
1958pub fn owned_audio_packet_from_ffmpeg_in(
1959  packet: &Packet,
1960  time_base: mediadecode::Timebase,
1961  limits: PacketLimits,
1962) -> Result<Option<AudioPacket<AudioPacketExtra, FfmpegBytes>>, PacketBufferError> {
1963  audio_packet_from_borrowed::<crate::Owned>(
1964    packet,
1965    time_base,
1966    limits,
1967    crate::buffer::PayloadProvenance::CallerSupplied,
1968  )
1969}
1970
1971/// [`subtitle_packet_from_ffmpeg`] with the stream's timebase and
1972/// explicit budgets.
1973pub fn owned_subtitle_packet_from_ffmpeg_in(
1974  packet: &Packet,
1975  time_base: mediadecode::Timebase,
1976  limits: PacketLimits,
1977) -> Result<Option<SubtitlePacket<SubtitlePacketExtra, FfmpegBytes>>, PacketBufferError> {
1978  subtitle_packet_from_borrowed::<crate::Owned>(
1979    packet,
1980    time_base,
1981    limits,
1982    crate::buffer::PayloadProvenance::CallerSupplied,
1983  )
1984}
1985
1986/// [`data_packet_from_ffmpeg_in`] on the owned lane, borrowing its
1987/// source.
1988pub fn owned_data_packet_from_ffmpeg_in(
1989  packet: &Packet,
1990  time_base: mediadecode::Timebase,
1991  limits: PacketLimits,
1992) -> Result<Option<DataPacket<DataPacketExtra, FfmpegBytes>>, PacketBufferError> {
1993  data_packet_from_borrowed::<crate::Owned>(
1994    packet,
1995    time_base,
1996    limits,
1997    crate::buffer::PayloadProvenance::CallerSupplied,
1998  )
1999}
2000
2001/// [`attachment_packet_from_ffmpeg`] on the owned lane, borrowing its
2002/// source.
2003pub fn owned_attachment_packet_from_ffmpeg(
2004  packet: &Packet,
2005  limits: PacketLimits,
2006) -> Result<Option<AttachmentPacket<AttachmentPacketExtra, FfmpegBytes>>, PacketBufferError> {
2007  attachment_packet_from_borrowed::<crate::Owned>(
2008    packet,
2009    limits,
2010    crate::buffer::PayloadProvenance::CallerSupplied,
2011  )
2012}
2013
2014/// [`empty_video_frame_as`] on the view lane — the destination a
2015/// [`FfmpegVideoStreamDecoder`](crate::FfmpegVideoStreamDecoder) fills.
2016#[must_use]
2017pub fn empty_video_frame() -> VideoFrame<PixelFormat, VideoFrameExtra, crate::FfmpegBuffer> {
2018  empty_video_frame_as::<crate::View>()
2019}
2020
2021/// [`empty_video_frame_as`] on the owned lane.
2022#[must_use]
2023pub fn empty_owned_video_frame() -> VideoFrame<PixelFormat, VideoFrameExtra, FfmpegBytes> {
2024  empty_video_frame_as::<crate::Owned>()
2025}
2026
2027/// [`empty_audio_frame_as`] on the view lane.
2028#[must_use]
2029pub fn empty_audio_frame()
2030-> AudioFrame<SampleFormat, ChannelLayoutDescription, AudioFrameExtra, crate::FfmpegBuffer> {
2031  empty_audio_frame_as::<crate::View>()
2032}
2033
2034/// [`empty_audio_frame_as`] on the owned lane.
2035#[must_use]
2036pub fn empty_owned_audio_frame()
2037-> AudioFrame<SampleFormat, ChannelLayoutDescription, AudioFrameExtra, FfmpegBytes> {
2038  empty_audio_frame_as::<crate::Owned>()
2039}
2040
2041/// [`empty_subtitle_frame_as`] on the view lane.
2042#[must_use]
2043pub fn empty_subtitle_frame() -> SubtitleFrame<SubtitleFrameExtra, crate::FfmpegBuffer> {
2044  empty_subtitle_frame_as::<crate::View>()
2045}
2046
2047/// [`empty_subtitle_frame_as`] on the owned lane.
2048#[must_use]
2049pub fn empty_owned_subtitle_frame() -> SubtitleFrame<SubtitleFrameExtra, FfmpegBytes> {
2050  empty_subtitle_frame_as::<crate::Owned>()
2051}
2052
2053/// [`attachment_packet_from_ffmpeg_as`] on the view lane.
2054pub fn attachment_packet_from_ffmpeg(
2055  packet: Packet,
2056  limits: PacketLimits,
2057) -> Result<Option<AttachmentPacket<AttachmentPacketExtra, crate::FfmpegBuffer>>, PacketBufferError>
2058{
2059  attachment_packet_from_ffmpeg_as::<crate::View>(
2060    packet,
2061    limits,
2062    crate::buffer::PayloadProvenance::CallerSupplied,
2063  )
2064}
2065
2066#[cfg(test)]
2067mod tests {
2068  use super::*;
2069
2070  /// A refcounted packet whose header claims a payload larger than the
2071  /// buffer behind it — the shape a malformed container, or an
2072  /// `av_packet_split_side_data` gone wrong, produces. Its payload is
2073  /// *there* and cannot be wrapped, which is exactly the case that must
2074  /// not read as "empty".
2075  fn out_of_bounds_packet() -> Packet {
2076    use ffmpeg_next::packet::Mut;
2077    let mut packet = Packet::copy(&[1u8, 2, 3, 4]);
2078    // SAFETY: `packet` owns a live `AVPacket` with a refcounted buffer;
2079    // `size` is a public field.
2080    unsafe {
2081      (*packet.as_mut_ptr()).size = 1 << 20;
2082    }
2083    packet
2084  }
2085
2086  #[test]
2087  fn a_payload_that_cannot_be_wrapped_is_an_error_on_every_arm() {
2088    // All five delivery arms plus the attachment path go through the
2089    // same wrapper. If one of them still folded the failure into
2090    // `None`, the demuxer would drop that kind of packet in silence.
2091    let forged = out_of_bounds_packet();
2092    let tb = mediadecode::Timebase::default();
2093    assert!(matches!(
2094      video_packet_from_borrowed::<crate::Owned>(
2095        &forged,
2096        tb,
2097        PacketLimits::default(),
2098        crate::buffer::PayloadProvenance::CallerSupplied
2099      ),
2100      Err(PacketBufferError::Bounds(_)),
2101    ));
2102    assert!(matches!(
2103      audio_packet_from_borrowed::<crate::Owned>(
2104        &forged,
2105        tb,
2106        PacketLimits::default(),
2107        crate::buffer::PayloadProvenance::CallerSupplied
2108      ),
2109      Err(PacketBufferError::Bounds(_)),
2110    ));
2111    assert!(matches!(
2112      subtitle_packet_from_borrowed::<crate::Owned>(
2113        &forged,
2114        tb,
2115        PacketLimits::default(),
2116        crate::buffer::PayloadProvenance::CallerSupplied
2117      ),
2118      Err(PacketBufferError::Bounds(_)),
2119    ));
2120    assert!(matches!(
2121      data_packet_from_borrowed::<crate::Owned>(
2122        &forged,
2123        tb,
2124        PacketLimits::default(),
2125        crate::buffer::PayloadProvenance::CallerSupplied
2126      ),
2127      Err(PacketBufferError::Bounds(_)),
2128    ));
2129    assert!(matches!(
2130      attachment_packet_from_borrowed::<crate::Owned>(
2131        &forged,
2132        PacketLimits::default(),
2133        crate::buffer::PayloadProvenance::CallerSupplied
2134      ),
2135      Err(PacketBufferError::Bounds(_)),
2136    ));
2137  }
2138
2139  /// A packet with **no body** and one side-data entry — the shape
2140  /// FFmpeg uses to hand a decoder new extradata or a parameter change.
2141  /// `size` is 0 and `buf` is null, which is exactly what used to read
2142  /// as "empty, skip it".
2143  fn side_data_only_packet() -> Packet {
2144    use ffmpeg_next::{
2145      ffi::{AVPacketSideDataType, av_packet_new_side_data},
2146      packet::Mut,
2147    };
2148    let mut packet = Packet::empty();
2149    // SAFETY: `packet` owns a live `AVPacket`; the side-data type is a
2150    // compile-time constant of this build, so no invalid discriminant
2151    // is formed. The returned pointer is valid for the four bytes just
2152    // allocated.
2153    unsafe {
2154      let ptr = av_packet_new_side_data(
2155        packet.as_mut_ptr(),
2156        AVPacketSideDataType::AV_PKT_DATA_NEW_EXTRADATA,
2157        4,
2158      );
2159      assert!(!ptr.is_null(), "av_packet_new_side_data");
2160      core::ptr::copy_nonoverlapping([1u8, 2, 3, 4].as_ptr(), ptr, 4);
2161    }
2162    packet
2163  }
2164
2165  const NEW_EXTRADATA: i32 =
2166    ffmpeg_next::ffi::AVPacketSideDataType::AV_PKT_DATA_NEW_EXTRADATA as i32;
2167
2168  use ffmpeg_next::ffi::{AVPacketSideData, AVPacketSideDataType, av_malloc};
2169
2170  /// A packet carrying one side-data entry of exactly `size` bytes,
2171  /// with a body when `body` is set.
2172  fn packet_with_side_data(size: usize, body: bool) -> Packet {
2173    use ffmpeg_next::{
2174      ffi::{AVPacketSideDataType, av_packet_new_side_data},
2175      packet::Mut,
2176    };
2177    let mut packet = if body {
2178      Packet::copy(&[1u8, 2, 3])
2179    } else {
2180      Packet::empty()
2181    };
2182    // SAFETY: `packet` owns a live `AVPacket` and the type is a
2183    // compile-time constant of this build.
2184    let ptr = unsafe {
2185      av_packet_new_side_data(
2186        packet.as_mut_ptr(),
2187        AVPacketSideDataType::AV_PKT_DATA_NEW_EXTRADATA,
2188        size,
2189      )
2190    };
2191    assert!(!ptr.is_null(), "av_packet_new_side_data({size})");
2192    packet
2193  }
2194
2195  /// A packet whose side-data array has been forged into a shape
2196  /// FFmpeg's own API cannot produce, and cannot free either: `Drop`
2197  /// puts the header back into a state `av_packet_free_side_data` can
2198  /// walk, so a fixture for a malformed packet cannot take the test
2199  /// process down with it.
2200  ///
2201  /// Both `av_packet_new_side_data` and `av_packet_add_side_data`
2202  /// replace an existing entry of the same type (measured: seventy
2203  /// calls leave one entry), so every shape below — an over-cap count, a
2204  /// negative count, a missing array, an entry that declares bytes it
2205  /// does not carry — is one no FFmpeg call produces. That is exactly
2206  /// what the validation is for.
2207  struct Forged {
2208    packet: Packet,
2209    freeable: i32,
2210  }
2211
2212  impl Drop for Forged {
2213    fn drop(&mut self) {
2214      use ffmpeg_next::packet::Mut;
2215      // SAFETY: `packet` owns a live `AVPacket`; putting the count back
2216      // to what the array really holds is what makes the free sound.
2217      unsafe { (*self.packet.as_mut_ptr()).side_data_elems = self.freeable };
2218    }
2219  }
2220
2221  impl Forged {
2222    /// `count` real entries of four bytes each.
2223    fn entries(count: usize, body: bool) -> Self {
2224      let mut packet = Self::carrier(body);
2225      // SAFETY: the array and every entry payload come from FFmpeg's
2226      // own allocator and are handed to the packet, which frees them in
2227      // `av_packet_free_side_data`. The carrier has no side data of its
2228      // own, so the overwrite leaks nothing.
2229      unsafe {
2230        let array = Self::array(count);
2231        for index in 0..count {
2232          let data = av_malloc(4) as *mut u8;
2233          assert!(!data.is_null(), "av_malloc");
2234          core::ptr::write_bytes(data, 7, 4);
2235          (*array.add(index)).data = data;
2236          (*array.add(index)).size = 4;
2237          (*array.add(index)).type_ = AVPacketSideDataType::AV_PKT_DATA_NEW_EXTRADATA;
2238        }
2239        Self::attach(&mut packet, array, count as i32);
2240      }
2241      Self {
2242        packet,
2243        freeable: count as i32,
2244      }
2245    }
2246
2247    /// A count with no array behind it at all.
2248    fn null_array(count: i32, body: bool) -> Self {
2249      let mut packet = Self::carrier(body);
2250      use ffmpeg_next::packet::Mut;
2251      // SAFETY: the packet's side data is null already; only the count
2252      // is forged, and `Drop` puts it back to zero before the free.
2253      unsafe { (*packet.as_mut_ptr()).side_data_elems = count };
2254      Self {
2255        packet,
2256        freeable: 0,
2257      }
2258    }
2259
2260    /// One entry declaring `size` bytes it does not carry.
2261    fn null_entry_data(size: usize, body: bool) -> Self {
2262      let mut packet = Self::carrier(body);
2263      // SAFETY: as in `entries`, except the payload pointer is left
2264      // null — `av_freep(&NULL)` is a no-op, so the free stays sound.
2265      unsafe {
2266        let array = Self::array(1);
2267        (*array).data = core::ptr::null_mut();
2268        (*array).size = size;
2269        (*array).type_ = AVPacketSideDataType::AV_PKT_DATA_NEW_EXTRADATA;
2270        Self::attach(&mut packet, array, 1);
2271      }
2272      Self {
2273        packet,
2274        freeable: 1,
2275      }
2276    }
2277
2278    /// Overrides the declared count, keeping the array intact.
2279    fn with_declared_count(mut self, count: i32) -> Self {
2280      use ffmpeg_next::packet::Mut;
2281      // SAFETY: `Drop` restores `freeable`, which still describes the
2282      // array really attached.
2283      unsafe { (*self.packet.as_mut_ptr()).side_data_elems = count };
2284      self
2285    }
2286
2287    fn carrier(body: bool) -> Packet {
2288      if body {
2289        Packet::copy(&[1u8, 2, 3])
2290      } else {
2291        Packet::empty()
2292      }
2293    }
2294
2295    /// # Safety
2296    /// The returned array is FFmpeg-allocated and uninitialised.
2297    unsafe fn array(count: usize) -> *mut AVPacketSideData {
2298      let array = unsafe { av_malloc(count * core::mem::size_of::<AVPacketSideData>()) }
2299        as *mut AVPacketSideData;
2300      assert!(!array.is_null(), "av_malloc");
2301      array
2302    }
2303
2304    /// # Safety
2305    /// `array` must hold `count` initialised entries owned by FFmpeg.
2306    unsafe fn attach(packet: &mut Packet, array: *mut AVPacketSideData, count: i32) {
2307      use ffmpeg_next::packet::Mut;
2308      unsafe {
2309        (*packet.as_mut_ptr()).side_data = array;
2310        (*packet.as_mut_ptr()).side_data_elems = count;
2311      }
2312    }
2313  }
2314
2315  /// Runs one packet through all four timed conversions, returning what
2316  /// each answered — the arms share a collector, and a fix that misses
2317  /// one of them is a fix that misses.
2318  fn every_timed_arm(packet: &Packet) -> [(&'static str, Result<bool, PacketBufferError>); 4] {
2319    let tb = mediadecode::Timebase::default();
2320    [
2321      (
2322        "video",
2323        video_packet_from_borrowed::<crate::Owned>(
2324          packet,
2325          tb,
2326          PacketLimits::default(),
2327          crate::buffer::PayloadProvenance::CallerSupplied,
2328        )
2329        .map(|p| p.is_some()),
2330      ),
2331      (
2332        "audio",
2333        audio_packet_from_borrowed::<crate::Owned>(
2334          packet,
2335          tb,
2336          PacketLimits::default(),
2337          crate::buffer::PayloadProvenance::CallerSupplied,
2338        )
2339        .map(|p| p.is_some()),
2340      ),
2341      (
2342        "subtitle",
2343        subtitle_packet_from_borrowed::<crate::Owned>(
2344          packet,
2345          tb,
2346          PacketLimits::default(),
2347          crate::buffer::PayloadProvenance::CallerSupplied,
2348        )
2349        .map(|p| p.is_some()),
2350      ),
2351      (
2352        "data",
2353        data_packet_from_borrowed::<crate::Owned>(
2354          packet,
2355          tb,
2356          PacketLimits::default(),
2357          crate::buffer::PayloadProvenance::CallerSupplied,
2358        )
2359        .map(|p| p.is_some()),
2360      ),
2361    ]
2362  }
2363
2364  #[test]
2365  fn side_data_a_packet_declares_and_does_not_carry_refuses_it() {
2366    // Two pointer paths walked straight past the all-or-error rule: a
2367    // count with no array behind it returned "no side data", and an
2368    // entry declaring bytes it did not carry became an empty entry —
2369    // charged nothing, delivered as though the packet had said so. Both
2370    // are how a body-bearing packet reached a decoder stripped of its
2371    // control data, and how a side-data-only packet became `None` and
2372    // was skipped.
2373    for body in [true, false] {
2374      let forged = Forged::null_array(3, body);
2375      for (arm, result) in every_timed_arm(&forged.packet) {
2376        match result {
2377          Err(PacketBufferError::SideDataArray(p)) => assert_eq!(p.count(), 3, "{arm}"),
2378          other => panic!("{arm} (body={body}): expected SideDataArray, got {other:?}"),
2379        }
2380      }
2381
2382      let forged = Forged::null_entry_data(64, body);
2383      for (arm, result) in every_timed_arm(&forged.packet) {
2384        match result {
2385          Err(PacketBufferError::SideDataPayload(p)) => {
2386            assert_eq!((p.index(), p.size()), (0, 64), "{arm}");
2387          }
2388          other => panic!("{arm} (body={body}): expected SideDataPayload, got {other:?}"),
2389        }
2390      }
2391
2392      // And the malformed count keeps being refused when the array is
2393      // missing too — the count is judged on its own, before the
2394      // pointer, so one cannot excuse the other.
2395      let forged = Forged::null_array(-3, body);
2396      for (arm, result) in every_timed_arm(&forged.packet) {
2397        assert!(
2398          matches!(
2399            result,
2400            Err(PacketBufferError::SideDataEntries(p)) if p.count() == -3
2401          ),
2402          "{arm} (body={body}): {result:?}",
2403        );
2404      }
2405      let forged = Forged::null_array(i32::MAX, body);
2406      for (arm, result) in every_timed_arm(&forged.packet) {
2407        assert!(
2408          matches!(result, Err(PacketBufferError::SideDataEntries(_))),
2409          "{arm} (body={body}): {result:?}",
2410        );
2411      }
2412    }
2413
2414    // A zero-size entry is a marker, not a lie: a type with no bytes is
2415    // exactly what FFmpeg emits for some side data, and it stays
2416    // welcome.
2417    let marker = Forged::null_entry_data(0, true);
2418    let packet = video_packet_from_borrowed::<crate::Owned>(
2419      &marker.packet,
2420      mediadecode::Timebase::default(),
2421      PacketLimits::default(),
2422      crate::buffer::PayloadProvenance::CallerSupplied,
2423    )
2424    .expect("a marker entry is carriable")
2425    .expect("present");
2426    assert_eq!(packet.extra().side_data().len(), 1);
2427    assert!(packet.extra().side_data()[0].data().is_empty());
2428  }
2429
2430  #[test]
2431  fn side_data_that_cannot_be_carried_whole_refuses_the_packet() {
2432    let tb = mediadecode::Timebase::default();
2433
2434    // A body-bearing packet whose side data is over the byte cap. The
2435    // caps used to truncate and warn, which handed the codec a packet
2436    // that looked complete and was not: `NEW_EXTRADATA` gone, a decoder
2437    // left on parameters the container had already replaced.
2438    let oversized = packet_with_side_data(SIDE_DATA_MAX_TOTAL_BYTES + 1, true);
2439    for (arm, result) in [
2440      (
2441        "video",
2442        video_packet_from_borrowed::<crate::Owned>(
2443          &oversized,
2444          tb,
2445          PacketLimits::default(),
2446          crate::buffer::PayloadProvenance::CallerSupplied,
2447        )
2448        .map(|p| p.is_some()),
2449      ),
2450      (
2451        "audio",
2452        audio_packet_from_borrowed::<crate::Owned>(
2453          &oversized,
2454          tb,
2455          PacketLimits::default(),
2456          crate::buffer::PayloadProvenance::CallerSupplied,
2457        )
2458        .map(|p| p.is_some()),
2459      ),
2460      (
2461        "subtitle",
2462        subtitle_packet_from_borrowed::<crate::Owned>(
2463          &oversized,
2464          tb,
2465          PacketLimits::default(),
2466          crate::buffer::PayloadProvenance::CallerSupplied,
2467        )
2468        .map(|p| p.is_some()),
2469      ),
2470      (
2471        "data",
2472        data_packet_from_borrowed::<crate::Owned>(
2473          &oversized,
2474          tb,
2475          PacketLimits::default(),
2476          crate::buffer::PayloadProvenance::CallerSupplied,
2477        )
2478        .map(|p| p.is_some()),
2479      ),
2480    ] {
2481      match result {
2482        Err(PacketBufferError::SideDataBytes(p)) => {
2483          assert_eq!(p.cap(), SIDE_DATA_MAX_TOTAL_BYTES);
2484          assert!(p.bytes() > p.cap(), "{arm}");
2485        }
2486        other => panic!("{arm}: expected SideDataBytes, got {other:?}"),
2487      }
2488    }
2489
2490    // And the same packet with no body at all. This one used to
2491    // collapse twice over: the side data was dropped, the packet
2492    // therefore looked empty, and the demuxer skipped it in silence.
2493    let oversized = packet_with_side_data(SIDE_DATA_MAX_TOTAL_BYTES + 1, false);
2494    assert!(matches!(
2495      video_packet_from_borrowed::<crate::Owned>(
2496        &oversized,
2497        tb,
2498        PacketLimits::default(),
2499        crate::buffer::PayloadProvenance::CallerSupplied
2500      )
2501      .map(|p| p.is_some()),
2502      Err(PacketBufferError::SideDataBytes(_)),
2503    ));
2504  }
2505
2506  #[test]
2507  fn the_side_data_caps_are_refusals_at_the_boundary_not_before_it() {
2508    let tb = mediadecode::Timebase::default();
2509
2510    // Exactly at the byte cap: carried, whole.
2511    let at_cap = packet_with_side_data(SIDE_DATA_MAX_TOTAL_BYTES, true);
2512    let packet = video_packet_from_borrowed::<crate::Owned>(
2513      &at_cap,
2514      tb,
2515      PacketLimits::default(),
2516      crate::buffer::PayloadProvenance::CallerSupplied,
2517    )
2518    .expect("exactly at the cap is not over it")
2519    .expect("present");
2520    assert_eq!(packet.extra().side_data().len(), 1);
2521    assert_eq!(
2522      packet.extra().side_data()[0].data().len(),
2523      SIDE_DATA_MAX_TOTAL_BYTES,
2524    );
2525
2526    // One byte past: refused.
2527    let past = packet_with_side_data(SIDE_DATA_MAX_TOTAL_BYTES + 1, true);
2528    assert!(matches!(
2529      video_packet_from_borrowed::<crate::Owned>(
2530        &past,
2531        tb,
2532        PacketLimits::default(),
2533        crate::buffer::PayloadProvenance::CallerSupplied
2534      )
2535      .map(|p| p.is_some()),
2536      Err(PacketBufferError::SideDataBytes(_)),
2537    ));
2538
2539    // The entry cap, both sides of it. FFmpeg names fewer types than
2540    // the floor today, so the effective cap is the floor; if a future
2541    // build names more, this assertion moves the boundary rather than
2542    // letting the lane quietly test nothing.
2543    let cap = side_data_entry_cap();
2544    assert_eq!(cap, SIDE_DATA_MAX_ENTRIES, "the cap this lane straddles");
2545
2546    let at_cap = Forged::entries(cap, true);
2547    let packet = video_packet_from_borrowed::<crate::Owned>(
2548      &at_cap.packet,
2549      tb,
2550      PacketLimits::default(),
2551      crate::buffer::PayloadProvenance::CallerSupplied,
2552    )
2553    .expect("exactly at the cap is not over it")
2554    .expect("present");
2555    assert_eq!(packet.extra().side_data().len(), cap);
2556
2557    let past = Forged::entries(cap + 1, true);
2558    match video_packet_from_borrowed::<crate::Owned>(
2559      &past.packet,
2560      tb,
2561      PacketLimits::default(),
2562      crate::buffer::PayloadProvenance::CallerSupplied,
2563    )
2564    .map(|p| p.is_some())
2565    {
2566      Err(PacketBufferError::SideDataEntries(p)) => {
2567        assert_eq!(p.count() as usize, cap + 1);
2568        assert_eq!(p.cap(), cap);
2569      }
2570      other => panic!("expected SideDataEntries, got {other:?}"),
2571    }
2572
2573    // A negative count is malformed, not empty — reading it as "no side
2574    // data" would be the same silent loss by another route.
2575    let corrupt = Forged::entries(1, true).with_declared_count(-3);
2576    assert!(matches!(
2577      video_packet_from_borrowed::<crate::Owned>(&corrupt.packet, tb, PacketLimits::default(), crate::buffer::PayloadProvenance::CallerSupplied).map(|p| p.is_some()),
2578      Err(PacketBufferError::SideDataEntries(p)) if p.count() == -3,
2579    ));
2580  }
2581
2582  #[test]
2583  fn a_trusted_packet_is_refused_on_both_legs() {
2584    use ffmpeg_next::packet::Mut;
2585    let tb = mediadecode::Timebase::default();
2586
2587    // Copy-out: the leg such a packet would enter the graph on.
2588    let mut packet = Packet::copy(&[1u8, 2, 3]);
2589    // SAFETY: `packet` owns a live `AVPacket`; `flags` is a public field
2590    // and this bit has no `ffmpeg_next::Flags` spelling.
2591    unsafe {
2592      (*packet.as_mut_ptr()).flags =
2593        ffmpeg_next::ffi::AV_PKT_FLAG_KEY | ffmpeg_next::ffi::AV_PKT_FLAG_TRUSTED;
2594    };
2595    for (arm, taken) in [
2596      (
2597        "video",
2598        video_packet_from_borrowed::<crate::Owned>(
2599          &packet,
2600          tb,
2601          PacketLimits::default(),
2602          crate::buffer::PayloadProvenance::CallerSupplied,
2603        )
2604        .map(|p| p.is_some()),
2605      ),
2606      (
2607        "audio",
2608        audio_packet_from_borrowed::<crate::Owned>(
2609          &packet,
2610          tb,
2611          PacketLimits::default(),
2612          crate::buffer::PayloadProvenance::CallerSupplied,
2613        )
2614        .map(|p| p.is_some()),
2615      ),
2616      (
2617        "subtitle",
2618        subtitle_packet_from_borrowed::<crate::Owned>(
2619          &packet,
2620          tb,
2621          PacketLimits::default(),
2622          crate::buffer::PayloadProvenance::CallerSupplied,
2623        )
2624        .map(|p| p.is_some()),
2625      ),
2626      (
2627        "data",
2628        data_packet_from_borrowed::<crate::Owned>(
2629          &packet,
2630          tb,
2631          PacketLimits::default(),
2632          crate::buffer::PayloadProvenance::CallerSupplied,
2633        )
2634        .map(|p| p.is_some()),
2635      ),
2636      (
2637        "attachment",
2638        attachment_packet_from_borrowed::<crate::Owned>(
2639          &packet,
2640          PacketLimits::default(),
2641          crate::buffer::PayloadProvenance::CallerSupplied,
2642        )
2643        .map(|p| p.is_some()),
2644      ),
2645    ] {
2646      match taken {
2647        Err(PacketBufferError::TrustedPayload(p)) => assert_eq!(p.len(), 3, "{arm}"),
2648        other => panic!("{arm}: a TRUSTED payload must not be copied out, got {other:?}"),
2649      }
2650    }
2651
2652    // Rebuild: the leg a flag that arrived by some other route would be
2653    // handed back to a decoder on. Built by hand, because the copy-out
2654    // leg above will no longer produce one.
2655    let clean = Packet::copy(&[1u8, 2, 3]);
2656    let video = video_packet_from_borrowed::<crate::Owned>(
2657      &clean,
2658      tb,
2659      PacketLimits::default(),
2660      crate::buffer::PayloadProvenance::CallerSupplied,
2661    )
2662    .expect("a clean packet is carriable")
2663    .expect("present");
2664    let trusted = video
2665      .clone()
2666      .with_flags(MdPacketFlags::from_bits_retain(crate::buffer::TRUSTED_BIT));
2667    assert!(
2668      matches!(
2669        ffmpeg_packet_from_owned_video_packet(&trusted, PacketLimits::default()),
2670        Err(PacketBuildError::TrustedPayload(_)),
2671      ),
2672      "a TRUSTED flag must not be written back onto an AVPacket",
2673    );
2674  }
2675
2676  #[test]
2677  fn every_flag_bit_survives_both_directions() {
2678    // `PacketFlags` is a bit set whose documented lossless door is
2679    // `from_bits_retain`, and both directions used to squeeze it
2680    // through `ffmpeg_next`'s `Flags`, which names `KEY` and `CORRUPT`
2681    // and truncates the rest away. `AV_PKT_FLAG_DISCARD` is the one
2682    // that matters most: it tells a consumer to decode a packet and
2683    // throw its output away, and without it preroll output looks like
2684    // something to keep.
2685    use ffmpeg_next::packet::{Mut, Ref};
2686    const DISCARD: i32 = ffmpeg_next::ffi::AV_PKT_FLAG_DISCARD;
2687    const TRUSTED: i32 = ffmpeg_next::ffi::AV_PKT_FLAG_TRUSTED;
2688    const DISPOSABLE: i32 = ffmpeg_next::ffi::AV_PKT_FLAG_DISPOSABLE;
2689    const UNNAMED: i32 = 0b0010_0000; // nothing names this bit yet
2690    // `TRUSTED` is deliberately **not** in this set, and the constant is
2691    // kept only to say so. It used to be — this lane asserted it made
2692    // the round trip — and that assertion was the bug: a `TRUSTED`
2693    // payload may be a structure of pointers into other live objects,
2694    // so copying it mints an owned-looking carrier that dangles when
2695    // its source drops. It is now refused on both legs, which
2696    // `a_trusted_packet_is_refused_on_both_legs` pins.
2697    let _ = TRUSTED;
2698    let raw = ffmpeg_next::ffi::AV_PKT_FLAG_KEY | DISCARD | DISPOSABLE | UNNAMED;
2699
2700    let mut packet = Packet::copy(&[1u8, 2, 3]);
2701    // SAFETY: `packet` owns a live `AVPacket`; `flags` is a public
2702    // field and this is the only way to set a bit `ffmpeg_next`'s
2703    // `Flags` cannot spell.
2704    unsafe { (*packet.as_mut_ptr()).flags = raw };
2705    assert_ne!(
2706      packet.flags().bits(),
2707      raw,
2708      "the wrapper's own accessor is what loses them",
2709    );
2710
2711    let tb = mediadecode::Timebase::default();
2712    let expected = MdPacketFlags::from_bits_retain(raw as u8);
2713
2714    let video = video_packet_from_borrowed::<crate::Owned>(
2715      &packet,
2716      tb,
2717      PacketLimits::default(),
2718      crate::buffer::PayloadProvenance::CallerSupplied,
2719    )
2720    .expect("wrappable")
2721    .expect("present");
2722    assert_eq!(video.flags(), expected);
2723    assert!(video.flags().contains(MdPacketFlags::DISCARD));
2724    let audio = audio_packet_from_borrowed::<crate::Owned>(
2725      &packet,
2726      tb,
2727      PacketLimits::default(),
2728      crate::buffer::PayloadProvenance::CallerSupplied,
2729    )
2730    .expect("wrappable")
2731    .expect("present");
2732    assert_eq!(audio.flags(), expected);
2733    let subtitle = subtitle_packet_from_borrowed::<crate::Owned>(
2734      &packet,
2735      tb,
2736      PacketLimits::default(),
2737      crate::buffer::PayloadProvenance::CallerSupplied,
2738    )
2739    .expect("wrappable")
2740    .expect("present");
2741    assert_eq!(subtitle.flags(), expected);
2742    let data = data_packet_from_borrowed::<crate::Owned>(
2743      &packet,
2744      tb,
2745      PacketLimits::default(),
2746      crate::buffer::PayloadProvenance::CallerSupplied,
2747    )
2748    .expect("wrappable")
2749    .expect("present");
2750    assert_eq!(data.flags(), expected);
2751    let attachment = attachment_packet_from_borrowed::<crate::Owned>(
2752      &packet,
2753      PacketLimits::default(),
2754      crate::buffer::PayloadProvenance::CallerSupplied,
2755    )
2756    .expect("wrappable")
2757    .expect("present");
2758    assert_eq!(attachment.flags(), expected);
2759
2760    // And back out again, on the three paths a decoder is fed from.
2761    for (arm, rebuilt) in [
2762      (
2763        "video",
2764        ffmpeg_packet_from_owned_video_packet(&video, PacketLimits::default()).expect("rebuilt"),
2765      ),
2766      (
2767        "audio",
2768        ffmpeg_packet_from_owned_audio_packet(&audio, PacketLimits::default()).expect("rebuilt"),
2769      ),
2770      (
2771        "subtitle",
2772        ffmpeg_packet_from_owned_subtitle_packet(&subtitle, PacketLimits::default())
2773          .expect("rebuilt"),
2774      ),
2775    ] {
2776      // SAFETY: `rebuilt` owns a live `AVPacket`.
2777      let carried = unsafe { (*rebuilt.as_ptr()).flags };
2778      assert_eq!(carried, raw, "{arm} rebuilt {carried:#x} from {raw:#x}");
2779    }
2780  }
2781
2782  #[test]
2783  fn the_disposable_hint_crosses_under_its_name_and_its_absence_reads_unknown() {
2784    // libavformat 9.0.1's MP4/MOV demuxer marks a packet
2785    // `AV_PKT_FLAG_DISPOSABLE` from `sdtp` (`mov.c` 11719–11723); a
2786    // synthesized packet stands in for one. The Matroska demuxer never
2787    // marks it (`matroskadec.c` 4319–4320), so its packets are the
2788    // unmarked rows below; no Matroska fixture is at hand to demux one
2789    // here. The hint crosses on the demux road and on the copy a probe
2790    // or a replay makes (`try_clone_packet`), and goes back onto the
2791    // `AVPacket` a decoder is fed. A packet without it reads absent.
2792    use ffmpeg_next::packet::{Mut, Ref};
2793    const KEY: i32 = ffmpeg_next::ffi::AV_PKT_FLAG_KEY;
2794    const DISPOSABLE: i32 = ffmpeg_next::ffi::AV_PKT_FLAG_DISPOSABLE;
2795    let tb = mediadecode::Timebase::default();
2796    for (raw, marked) in [
2797      (DISPOSABLE, true),
2798      (KEY | DISPOSABLE, true),
2799      (KEY, false),
2800      (0, false),
2801    ] {
2802      let mut packet = Packet::copy(&[1u8, 2, 3]);
2803      // SAFETY: `packet` owns a live `AVPacket`; `flags` is a public
2804      // field.
2805      unsafe { (*packet.as_mut_ptr()).flags = raw };
2806      let copy = crate::decoder::try_clone_packet(&packet).expect("a packet clones");
2807      for (road, source) in [("demuxed", &packet), ("copied", &copy)] {
2808        let video = video_packet_from_borrowed::<crate::Owned>(
2809          source,
2810          tb,
2811          PacketLimits::default(),
2812          crate::buffer::PayloadProvenance::CallerSupplied,
2813        )
2814        .expect("wrappable")
2815        .expect("present");
2816        assert_eq!(
2817          video.flags().contains(MdPacketFlags::DISPOSABLE),
2818          marked,
2819          "{road} {raw:#x}"
2820        );
2821        let rebuilt =
2822          ffmpeg_packet_from_owned_video_packet(&video, PacketLimits::default()).expect("rebuilt");
2823        // SAFETY: `rebuilt` owns a live `AVPacket`.
2824        let carried = unsafe { (*rebuilt.as_ptr()).flags };
2825        assert_eq!(
2826          carried & DISPOSABLE != 0,
2827          marked,
2828          "{road} {raw:#x} rebuilt as {carried:#x}"
2829        );
2830      }
2831    }
2832  }
2833
2834  #[test]
2835  fn a_flag_bit_the_vocabulary_cannot_hold_refuses_the_packet() {
2836    // Unreachable against this build — every flag FFmpeg names lives in
2837    // the byte `PacketFlags` carries, and a compile-time assertion says
2838    // so. It is here because the day that stops being true, a packet
2839    // must be refused rather than delivered with a bit missing.
2840    use ffmpeg_next::packet::Mut;
2841    let mut packet = Packet::copy(&[1u8]);
2842    // SAFETY: `packet` owns a live `AVPacket`.
2843    unsafe { (*packet.as_mut_ptr()).flags = 0x1_00 };
2844    let tb = mediadecode::Timebase::default();
2845    for (arm, result) in every_timed_arm(&packet) {
2846      assert!(
2847        matches!(
2848          result,
2849          Err(PacketBufferError::UnrepresentableFlags(p)) if p.raw() == 0x1_00
2850        ),
2851        "{arm}: {result:?}",
2852      );
2853    }
2854    assert!(matches!(
2855      attachment_packet_from_borrowed::<crate::Owned>(
2856        &packet,
2857        PacketLimits::default(),
2858        crate::buffer::PayloadProvenance::CallerSupplied
2859      )
2860      .map(|p| p.is_some()),
2861      Err(PacketBufferError::UnrepresentableFlags(_)),
2862    ));
2863    let _ = tb;
2864  }
2865
2866  /// A live `AVBufferRef` of `len` bytes, released when the guard drops.
2867  struct TestBuffer(*mut ffmpeg_next::ffi::AVBufferRef);
2868
2869  impl TestBuffer {
2870    fn new(len: usize) -> Self {
2871      // SAFETY: a plain allocation, checked for null, written through
2872      // its own `data` pointer.
2873      unsafe {
2874        let raw = ffmpeg_next::ffi::av_buffer_alloc(len as _);
2875        assert!(!raw.is_null(), "av_buffer_alloc");
2876        core::ptr::write_bytes((*raw).data, 0xAB, len);
2877        Self(raw)
2878      }
2879    }
2880  }
2881
2882  impl Drop for TestBuffer {
2883    fn drop(&mut self) {
2884      // SAFETY: the guard owns exactly one reference, released once.
2885      unsafe { ffmpeg_next::ffi::av_buffer_unref(&mut self.0) };
2886    }
2887  }
2888
2889  /// The payload pointer a packet's buffer currently holds.
2890  fn payload_address(packet: &Packet) -> usize {
2891    use ffmpeg_next::packet::Ref;
2892    // SAFETY: the packet is live; `data` is a public field.
2893    unsafe { (*packet.as_ptr()).data as usize }
2894  }
2895
2896  /// The refcount of a packet's payload buffer.
2897  fn payload_references(packet: &Packet) -> i32 {
2898    use ffmpeg_next::packet::Ref;
2899    // SAFETY: the packet is live and refcounted; `buf` is a public
2900    // field and `av_buffer_get_ref_count` only reads the atomic.
2901    unsafe {
2902      let buf = (*packet.as_ptr()).buf;
2903      assert!(!buf.is_null(), "the fixture must be refcounted");
2904      ffmpeg_next::ffi::av_buffer_get_ref_count(buf)
2905    }
2906  }
2907
2908  #[test]
2909  fn a_shared_packet_buffer_is_refused_without_being_read() {
2910    // **The consumption argument has a hole a dependency can open, and
2911    // the obvious patch had a worse one.** Taking the packet by value
2912    // proves no *other* handle survives only if the packet's buffer was
2913    // the caller's alone to give. An earlier round answered a shared
2914    // buffer with a silent copy — but a copy needs a read, and a
2915    // refcount above one is exactly the state in which somebody else
2916    // may be writing those bytes, from safe code, on another thread.
2917    // The read *is* the race. So the answer is a refusal that touches
2918    // nothing.
2919    let mut original = Packet::copy(&[7u8; 4096]);
2920    let mut shared = Packet::empty();
2921    // SAFETY: both packets are live; `av_packet_ref` takes a reference
2922    // to `original`'s buffer without copying it.
2923    unsafe {
2924      use ffmpeg_next::packet::{Mut, Ref};
2925      assert_eq!(
2926        ffmpeg_next::ffi::av_packet_ref(shared.as_mut_ptr(), original.as_ptr()),
2927        0,
2928      );
2929    }
2930    assert_eq!(
2931      payload_address(&shared),
2932      payload_address(&original),
2933      "the premise: the two packets share one allocation",
2934    );
2935    assert_eq!(payload_references(&original), 2);
2936
2937    // The refusal is lane-independent, because the hazard is: both
2938    // lanes read the payload, one to view it and one to copy it.
2939    match video_packet_from_ffmpeg_as::<crate::View>(
2940      shared,
2941      mediadecode::Timebase::default(),
2942      PacketLimits::default(),
2943      crate::buffer::PayloadProvenance::CallerSupplied,
2944    ) {
2945      Err(PacketBufferError::SharedPayload(refused)) => {
2946        assert_eq!(refused.references(), 2);
2947      }
2948      other => panic!("a shared payload must be refused by name, got {other:?}"),
2949    }
2950
2951    // Nothing was read and nothing was carried: the packet the caller
2952    // kept is still theirs, still writable, and now sole owner again.
2953    {
2954      let slot = original.data_mut().expect("a writable payload");
2955      slot[0] = 0xEE;
2956    }
2957    assert_eq!(
2958      payload_references(&original),
2959      1,
2960      "the refusal must not have taken a reference of its own",
2961    );
2962
2963    // And the owned lane answers the same way, for the same reason: its
2964    // copy is a read too.
2965    let mut shared_again = Packet::empty();
2966    // SAFETY: as above.
2967    unsafe {
2968      use ffmpeg_next::packet::{Mut, Ref};
2969      assert_eq!(
2970        ffmpeg_next::ffi::av_packet_ref(shared_again.as_mut_ptr(), original.as_ptr()),
2971        0,
2972      );
2973    }
2974    assert!(matches!(
2975      owned_video_packet_from_ffmpeg_in(
2976        &shared_again,
2977        mediadecode::Timebase::default(),
2978        PacketLimits::default(),
2979      ),
2980      Err(PacketBufferError::SharedPayload(_)),
2981    ));
2982  }
2983
2984  #[test]
2985  fn a_clone_that_silently_shared_is_refused_by_name() {
2986    // The exact path: `ffmpeg_next::Packet::clone` calls
2987    // `av_packet_ref` then `av_packet_make_writable` and **ignores both
2988    // return codes**. Under allocation failure the second one leaves
2989    // the "clone" sharing the source's buffer, and nothing says so.
2990    //
2991    // The cap is process-global, so the manufacture runs alone. It is
2992    // lifted before the conversion, because what is being tested is the
2993    // conversion's answer to a shared buffer — not its behaviour under
2994    // memory pressure, which the other lanes cover.
2995    crate::fault_subprocess::in_subprocess(
2996      "boundary::tests::a_clone_that_silently_shared_is_refused_by_name",
2997      || {
2998        let mut original = Packet::copy(&[3u8; 8192]);
2999
3000        // Small allocations still succeed, so `av_packet_ref` gets its
3001        // `AVBufferRef`; a payload-sized one does not, so
3002        // `av_packet_make_writable` fails and is ignored.
3003        crate::fault_subprocess::cap_ffmpeg_allocations(512);
3004        let clone = original.clone();
3005        crate::fault_subprocess::uncap_ffmpeg_allocations();
3006
3007        assert_eq!(
3008          payload_address(&clone),
3009          payload_address(&original),
3010          "the premise this test exists for: a failed `make_writable` \
3011           leaves the clone sharing, silently",
3012        );
3013        assert_eq!(payload_references(&original), 2);
3014
3015        match video_packet_from_ffmpeg_as::<crate::View>(
3016          clone,
3017          mediadecode::Timebase::default(),
3018          PacketLimits::default(),
3019          crate::buffer::PayloadProvenance::CallerSupplied,
3020        ) {
3021          Err(PacketBufferError::SharedPayload(refused)) => {
3022            assert_eq!(refused.references(), 2);
3023          }
3024          other => panic!("expected a named refusal, got {other:?}"),
3025        }
3026
3027        // The handle the caller kept is untouched and unaliased.
3028        {
3029          let slot = original.data_mut().expect("a writable payload");
3030          slot[0] = 0x5A;
3031        }
3032        assert_eq!(payload_references(&original), 1);
3033      },
3034    );
3035  }
3036
3037  #[test]
3038  fn only_a_packet_payload_with_provable_padding_is_shared_into_a_decoder() {
3039    use crate::view::Origin;
3040    use ffmpeg_next::packet::Ref;
3041
3042    const PADDING: usize = ffmpeg_next::ffi::AV_INPUT_BUFFER_PADDING_SIZE as usize;
3043    let src = TestBuffer::new(512);
3044    let body_len = 512 - PADDING;
3045
3046    // The one shape that may share: a payload captured out of an
3047    // `AVPacket`'s own buffer, with libavformat's padding behind it.
3048    // SAFETY: `src` is live for the test and the extent is inside it.
3049    let payload =
3050      unsafe { crate::FfmpegBuffer::view_of(src.0, 0, body_len, Origin::PacketPayload) }
3051        .expect("a view");
3052    let shared = share_or_copy(&payload).expect("built");
3053    // SAFETY: both are live; `data` is a public field.
3054    assert_eq!(
3055      unsafe { (*shared.as_ptr()).data as usize },
3056      payload.as_ref().as_ptr() as usize,
3057      "a packet payload with provable padding must be shared",
3058    );
3059
3060    // **The same bytes, the same slack, no provenance.** This is the
3061    // frame-origin case: a decoded plane or a resampler's output reused
3062    // as a packet body. What follows it is more pixels or more samples,
3063    // and a bitstream reader running past the payload would eat them.
3064    // SAFETY: as above.
3065    let plane =
3066      unsafe { crate::FfmpegBuffer::view_of(src.0, 0, body_len, Origin::Foreign) }.expect("a view");
3067    let copied = share_or_copy(&plane).expect("built");
3068    // SAFETY: both are live.
3069    assert_ne!(
3070      unsafe { (*copied.as_ptr()).data as usize },
3071      plane.as_ref().as_ptr() as usize,
3072      "trailing capacity is not padding provenance — this must be copied",
3073    );
3074    assert_eq!(copied.data().unwrap_or(&[]), plane.as_ref());
3075
3076    // And a payload whose slack is short of the padding is copied too,
3077    // provenance or not: the extent has to hold as well.
3078    // SAFETY: as above.
3079    let tight = unsafe { crate::FfmpegBuffer::view_of(src.0, 0, 511, Origin::PacketPayload) }
3080      .expect("a view");
3081    let copied = share_or_copy(&tight).expect("built");
3082    // SAFETY: both are live.
3083    assert_ne!(
3084      unsafe { (*copied.as_ptr()).data as usize },
3085      tight.as_ref().as_ptr() as usize,
3086      "a payload with less than the padding behind it must be copied",
3087    );
3088
3089    // A narrowed payload has lost its claim, and is copied.
3090    let mut narrowed = payload.clone();
3091    narrowed.shrink_to(16);
3092    let copied = share_or_copy(&narrowed).expect("built");
3093    // SAFETY: both are live.
3094    assert_ne!(
3095      unsafe { (*copied.as_ptr()).data as usize },
3096      narrowed.as_ref().as_ptr() as usize,
3097    );
3098  }
3099
3100  #[test]
3101  fn an_empty_view_carrier_builds_a_payload_less_packet() {
3102    // The empty carrier is backed by **no buffer at all** — that is
3103    // what makes a placeholder plane slot free — so anything that
3104    // reads its `AVBufferRef` before asking whether it is empty
3105    // dereferences null. A side-data-only packet is the ordinary way
3106    // to get here, not an exotic one.
3107    let empty = crate::FfmpegBuffer::empty();
3108    assert!(empty.as_av_buffer_ref().is_null(), "the premise");
3109    let built = share_or_copy(&empty).expect("a payload-less packet");
3110    assert_eq!(built.size(), 0);
3111    // And it is a packet side data can be attached to.
3112    let mut built = built;
3113    attach_side_data(
3114      &mut built,
3115      &[SideDataEntry::new(
3116        NEW_EXTRADATA,
3117        FfmpegBytes::copy_from_slice(&[1u8, 2, 3, 4]),
3118      )],
3119    )
3120    .expect("side data attaches to a payload-less packet");
3121  }
3122
3123  #[test]
3124  fn a_side_data_only_packet_round_trips_on_every_view_decoder_family() {
3125    // Every family, both roads: the public builder a caller can call,
3126    // and the scoped submission a decoder goes through. Neither may
3127    // touch the empty carrier's absent buffer.
3128    let tb = mediadecode::Timebase::default();
3129    let source = side_data_only_packet();
3130    let limits = PacketLimits::default();
3131
3132    let video = video_packet_from_ffmpeg_as::<crate::View>(
3133      source.clone(),
3134      tb,
3135      limits,
3136      crate::buffer::PayloadProvenance::CallerSupplied,
3137    )
3138    .expect("carried")
3139    .expect("a side-data-only packet is a packet");
3140    assert!(video.data().as_ref().is_empty(), "the premise: no payload");
3141    assert_eq!(
3142      ffmpeg_packet_from_video_packet(&video, limits)
3143        .expect("built")
3144        .size(),
3145      0,
3146    );
3147    with_ffmpeg_video_packet::<crate::View, _>(&video, limits, BodyRoute::Submission, |av| {
3148      assert_eq!(av.size(), 0);
3149    })
3150    .expect("submitted");
3151
3152    let audio = audio_packet_from_ffmpeg_as::<crate::View>(
3153      source.clone(),
3154      tb,
3155      limits,
3156      crate::buffer::PayloadProvenance::CallerSupplied,
3157    )
3158    .expect("carried")
3159    .expect("a side-data-only packet is a packet");
3160    assert!(audio.data().as_ref().is_empty());
3161    assert_eq!(
3162      ffmpeg_packet_from_audio_packet(&audio, limits)
3163        .expect("built")
3164        .size(),
3165      0,
3166    );
3167    with_ffmpeg_audio_packet::<crate::View, _>(&audio, limits, BodyRoute::Submission, |av| {
3168      assert_eq!(av.size(), 0);
3169    })
3170    .expect("submitted");
3171
3172    let subtitle = subtitle_packet_from_ffmpeg_as::<crate::View>(
3173      source.clone(),
3174      tb,
3175      limits,
3176      crate::buffer::PayloadProvenance::CallerSupplied,
3177    )
3178    .expect("carried")
3179    .expect("a side-data-only packet is a packet");
3180    assert!(subtitle.data().as_ref().is_empty());
3181    assert_eq!(
3182      ffmpeg_packet_from_subtitle_packet(&subtitle, limits)
3183        .expect("built")
3184        .size(),
3185      0,
3186    );
3187    with_ffmpeg_subtitle_packet::<crate::View, _>(&subtitle, limits, BodyRoute::Submission, |av| {
3188      assert_eq!(av.size(), 0);
3189    })
3190    .expect("submitted");
3191  }
3192
3193  #[test]
3194  fn side_data_survives_the_round_trip_on_every_decoder_bound_arm() {
3195    // The forward direction captures side data; the reverse direction
3196    // is what a decoder is actually handed. Capturing without
3197    // reattaching is theatre: `NEW_EXTRADATA` never reaches the codec,
3198    // `SKIP_SAMPLES` never trims, and nothing says so.
3199    let tb = mediadecode::Timebase::default();
3200    let mut with_body = Packet::copy(&[4u8, 5, 6]);
3201    {
3202      use ffmpeg_next::{
3203        ffi::{AVPacketSideDataType, av_packet_new_side_data},
3204        packet::Mut,
3205      };
3206      unsafe {
3207        let ptr = av_packet_new_side_data(
3208          with_body.as_mut_ptr(),
3209          AVPacketSideDataType::AV_PKT_DATA_SKIP_SAMPLES,
3210          3,
3211        );
3212        assert!(!ptr.is_null());
3213        core::ptr::copy_nonoverlapping([1u8, 2, 3].as_ptr(), ptr, 3);
3214      }
3215    }
3216    const SKIP_SAMPLES: i32 =
3217      ffmpeg_next::ffi::AVPacketSideDataType::AV_PKT_DATA_SKIP_SAMPLES as i32;
3218
3219    for (name, source) in [
3220      ("body plus side data", &with_body),
3221      ("side data only", &side_data_only_packet()),
3222    ] {
3223      let expected_kind = if name == "side data only" {
3224        NEW_EXTRADATA
3225      } else {
3226        SKIP_SAMPLES
3227      };
3228
3229      let video = video_packet_from_borrowed::<crate::Owned>(
3230        source,
3231        tb,
3232        PacketLimits::default(),
3233        crate::buffer::PayloadProvenance::CallerSupplied,
3234      )
3235      .expect("wrappable")
3236      .expect("present");
3237      let rebuilt =
3238        ffmpeg_packet_from_owned_video_packet(&video, PacketLimits::default()).expect("rebuilt");
3239      let carried = packet_side_data(&rebuilt).expect("readable");
3240      assert_eq!(carried.len(), 1, "{name}: video lost its side data");
3241      assert_eq!(carried[0].kind(), expected_kind, "{name}: video");
3242      assert_eq!(carried[0].data(), video.extra().side_data()[0].data());
3243
3244      let audio = audio_packet_from_borrowed::<crate::Owned>(
3245        source,
3246        tb,
3247        PacketLimits::default(),
3248        crate::buffer::PayloadProvenance::CallerSupplied,
3249      )
3250      .expect("wrappable")
3251      .expect("present");
3252      let rebuilt =
3253        ffmpeg_packet_from_owned_audio_packet(&audio, PacketLimits::default()).expect("rebuilt");
3254      let carried = packet_side_data(&rebuilt).expect("readable");
3255      assert_eq!(carried.len(), 1, "{name}: audio lost its side data");
3256      assert_eq!(carried[0].data(), audio.extra().side_data()[0].data());
3257
3258      let subtitle = subtitle_packet_from_borrowed::<crate::Owned>(
3259        source,
3260        tb,
3261        PacketLimits::default(),
3262        crate::buffer::PayloadProvenance::CallerSupplied,
3263      )
3264      .expect("wrappable")
3265      .expect("present");
3266      let rebuilt = ffmpeg_packet_from_owned_subtitle_packet(&subtitle, PacketLimits::default())
3267        .expect("rebuilt");
3268      let carried = packet_side_data(&rebuilt).expect("readable");
3269      assert_eq!(carried.len(), 1, "{name}: subtitle lost its side data");
3270      assert_eq!(carried[0].data(), subtitle.extra().side_data()[0].data());
3271    }
3272  }
3273
3274  #[test]
3275  fn a_side_data_type_this_build_cannot_name_is_refused_not_dropped() {
3276    // This crate carries side-data types as the raw integers they are
3277    // on the wire, so a hand-built entry can name anything. Handing an
3278    // unknown one to C would either form an invalid discriminant or
3279    // attach a type nothing downstream reads — and dropping it quietly
3280    // is the very defect this whole seam exists to close.
3281    let limit = crate::ffi::side_data_type_count();
3282    let packet = mediadecode::packet::VideoPacket::new(
3283      FfmpegBytes::copy_from_slice(&[1u8]),
3284      VideoPacketExtra::new(0).with_side_data(vec![SideDataEntry::new(
3285        limit,
3286        FfmpegBytes::copy_from_slice(&[9u8]),
3287      )]),
3288    );
3289    match ffmpeg_packet_from_owned_video_packet(&packet, PacketLimits::default()).map(|_| ()) {
3290      Err(PacketBuildError::UnknownSideData(p)) => {
3291        assert_eq!(p.kind(), limit);
3292        assert_eq!(p.limit(), limit);
3293      }
3294      other => panic!("expected UnknownSideData, got {other:?}"),
3295    }
3296    assert!(matches!(
3297      ffmpeg_packet_from_owned_video_packet(&mediadecode::packet::VideoPacket::new(
3298        FfmpegBytes::copy_from_slice(&[1u8]),
3299        VideoPacketExtra::new(0).with_side_data(vec![SideDataEntry::new(-1, FfmpegBytes::copy_from_slice(&[9u8]))]),
3300      ), PacketLimits::default())
3301      .map(|_| ()),
3302      Err(PacketBuildError::UnknownSideData(p)) if p.kind() == -1,
3303    ));
3304  }
3305
3306  #[test]
3307  fn a_side_data_only_packet_is_delivered_on_every_timed_arm() {
3308    // Codec-control data with no body is still a packet. Dropped as an
3309    // "empty marker", it leaves a decoder running on parameters the
3310    // container has already replaced — and says nothing about it.
3311    let packet = side_data_only_packet();
3312    let tb = mediadecode::Timebase::default();
3313
3314    let video = video_packet_from_borrowed::<crate::Owned>(
3315      &packet,
3316      tb,
3317      PacketLimits::default(),
3318      crate::buffer::PayloadProvenance::CallerSupplied,
3319    )
3320    .expect("wrappable")
3321    .expect("a side-data-only packet is a packet");
3322    assert!(video.data().as_ref().is_empty(), "no body, but a buffer");
3323    assert_eq!(video.extra().side_data().len(), 1);
3324    assert_eq!(video.extra().side_data()[0].kind(), NEW_EXTRADATA);
3325    assert_eq!(video.extra().side_data()[0].data(), &[1, 2, 3, 4]);
3326
3327    let audio = audio_packet_from_borrowed::<crate::Owned>(
3328      &packet,
3329      tb,
3330      PacketLimits::default(),
3331      crate::buffer::PayloadProvenance::CallerSupplied,
3332    )
3333    .expect("wrappable")
3334    .expect("present");
3335    assert!(audio.data().as_ref().is_empty());
3336    assert_eq!(audio.extra().side_data()[0].data(), &[1, 2, 3, 4]);
3337
3338    let subtitle = subtitle_packet_from_borrowed::<crate::Owned>(
3339      &packet,
3340      tb,
3341      PacketLimits::default(),
3342      crate::buffer::PayloadProvenance::CallerSupplied,
3343    )
3344    .expect("wrappable")
3345    .expect("present");
3346    assert!(subtitle.data().as_ref().is_empty());
3347    assert_eq!(subtitle.extra().side_data()[0].data(), &[1, 2, 3, 4]);
3348
3349    let data = data_packet_from_borrowed::<crate::Owned>(
3350      &packet,
3351      tb,
3352      PacketLimits::default(),
3353      crate::buffer::PayloadProvenance::CallerSupplied,
3354    )
3355    .expect("wrappable")
3356    .expect("present");
3357    assert!(data.data().as_ref().is_empty());
3358    assert_eq!(data.extra().side_data()[0].data(), &[1, 2, 3, 4]);
3359
3360    // The one arm where a payload-less packet really is nothing: an
3361    // attachment is its bytes, and there are none.
3362    assert!(
3363      attachment_packet_from_borrowed::<crate::Owned>(
3364        &packet,
3365        PacketLimits::default(),
3366        crate::buffer::PayloadProvenance::CallerSupplied
3367      )
3368      .expect("wrappable")
3369      .is_none(),
3370      "an attachment with no bytes is no attachment",
3371    );
3372  }
3373
3374  #[test]
3375  fn side_data_rides_a_packet_that_has_a_body_too() {
3376    // The seat's documented promise — "the raw side-data entries from
3377    // `AVPacket.side_data`" — was never kept by the conversion before.
3378    use ffmpeg_next::{
3379      ffi::{AVPacketSideDataType, av_packet_new_side_data},
3380      packet::Mut,
3381    };
3382    let mut packet = Packet::copy(&[7u8, 7, 7]);
3383    unsafe {
3384      let ptr = av_packet_new_side_data(
3385        packet.as_mut_ptr(),
3386        AVPacketSideDataType::AV_PKT_DATA_NEW_EXTRADATA,
3387        2,
3388      );
3389      assert!(!ptr.is_null());
3390      core::ptr::copy_nonoverlapping([9u8, 9].as_ptr(), ptr, 2);
3391    }
3392    let video = video_packet_from_borrowed::<crate::Owned>(
3393      &packet,
3394      mediadecode::Timebase::default(),
3395      PacketLimits::default(),
3396      crate::buffer::PayloadProvenance::CallerSupplied,
3397    )
3398    .expect("wrappable")
3399    .expect("present");
3400    assert_eq!(video.data().as_ref(), &[7, 7, 7]);
3401    assert_eq!(video.extra().side_data()[0].data(), &[9, 9]);
3402  }
3403
3404  #[test]
3405  fn an_empty_packet_is_absent_and_a_real_one_is_present() {
3406    let tb = mediadecode::Timebase::default();
3407    // No payload at all: the marker some demuxers emit. Absent, not an
3408    // error — this is the only thing a pull loop may skip.
3409    let empty = Packet::empty();
3410    assert!(
3411      video_packet_from_borrowed::<crate::Owned>(
3412        &empty,
3413        tb,
3414        PacketLimits::default(),
3415        crate::buffer::PayloadProvenance::CallerSupplied
3416      )
3417      .expect("not a failure")
3418      .is_none()
3419    );
3420    assert!(
3421      attachment_packet_from_borrowed::<crate::Owned>(
3422        &empty,
3423        PacketLimits::default(),
3424        crate::buffer::PayloadProvenance::CallerSupplied
3425      )
3426      .expect("not a failure")
3427      .is_none()
3428    );
3429
3430    let real = Packet::copy(&[9u8, 8, 7]);
3431    let wrapped = video_packet_from_borrowed::<crate::Owned>(
3432      &real,
3433      tb,
3434      PacketLimits::default(),
3435      crate::buffer::PayloadProvenance::CallerSupplied,
3436    )
3437    .expect("wrappable")
3438    .expect("present");
3439    assert_eq!(wrapped.data().as_ref(), &[9, 8, 7]);
3440  }
3441
3442  #[test]
3443  fn nv12_round_trips() {
3444    assert_eq!(
3445      from_av_pixel_format(AVPixelFormat::AV_PIX_FMT_NV12 as i32),
3446      PixelFormat::Nv12,
3447    );
3448  }
3449
3450  #[test]
3451  fn p010be_maps_to_p010be() {
3452    // BE must map to the BE variant — the previous "fold to LE"
3453    // mapping silently corrupted P010BE pixel data via the safe
3454    // export path. The unsupported-format gate in `convert::av_frame_to_video_frame`
3455    // is the right place to reject BE today.
3456    assert_eq!(
3457      from_av_pixel_format(AVPixelFormat::AV_PIX_FMT_P010BE as i32),
3458      PixelFormat::P010Be,
3459    );
3460  }
3461
3462  #[test]
3463  fn unnamed_raw_maps_to_none() {
3464    assert_eq!(from_av_pixel_format(-99_999), PixelFormat::None);
3465  }
3466
3467  #[test]
3468  fn av_pix_fmt_none_maps_to_none() {
3469    assert_eq!(
3470      from_av_pixel_format(AVPixelFormat::AV_PIX_FMT_NONE as i32),
3471      PixelFormat::None,
3472    );
3473  }
3474
3475  #[test]
3476  fn hw_formats_detected() {
3477    assert!(is_hardware_pix_fmt(
3478      AVPixelFormat::AV_PIX_FMT_VIDEOTOOLBOX as i32
3479    ));
3480    assert!(is_hardware_pix_fmt(AVPixelFormat::AV_PIX_FMT_VAAPI as i32));
3481    assert!(is_hardware_pix_fmt(AVPixelFormat::AV_PIX_FMT_CUDA as i32));
3482    assert!(is_hardware_pix_fmt(AVPixelFormat::AV_PIX_FMT_D3D11 as i32));
3483  }
3484
3485  #[test]
3486  fn cpu_formats_not_detected_as_hw() {
3487    assert!(!is_hardware_pix_fmt(AVPixelFormat::AV_PIX_FMT_NV12 as i32));
3488    assert!(!is_hardware_pix_fmt(
3489      AVPixelFormat::AV_PIX_FMT_YUV420P as i32
3490    ));
3491    assert!(!is_hardware_pix_fmt(AVPixelFormat::AV_PIX_FMT_NONE as i32));
3492  }
3493
3494  #[test]
3495  fn hw_formats_map_to_none_in_pixel_format() {
3496    // HW sentinels intentionally don't have a mediadecode::PixelFormat
3497    // representation — they're not CPU pixel data.
3498    assert_eq!(
3499      from_av_pixel_format(AVPixelFormat::AV_PIX_FMT_VIDEOTOOLBOX as i32),
3500      PixelFormat::None,
3501    );
3502    assert_eq!(
3503      from_av_pixel_format(AVPixelFormat::AV_PIX_FMT_VAAPI as i32),
3504      PixelFormat::None,
3505    );
3506  }
3507}