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::ffi::c_int;
10
11use ffmpeg_next::{Packet, ffi::AVPixelFormat};
12use mediadecode::{
13  PixelFormat, Timestamp,
14  channel::AudioChannelLayout,
15  frame::{AudioFrame, Dimensions, Plane, SubtitleFrame, VideoFrame},
16  packet::{AudioPacket, PacketFlags as MdPacketFlags, SubtitlePacket, VideoPacket},
17  subtitle::SubtitlePayload,
18};
19
20use crate::{
21  FfmpegBuffer,
22  extras::{
23    AudioFrameExtra, AudioPacketExtra, SubtitleFrameExtra, SubtitlePacketExtra, VideoFrameExtra,
24    VideoPacketExtra,
25  },
26  sample_format::SampleFormat,
27};
28
29/// Maps a raw `AVFrame.format` integer (i.e. the value of an
30/// `AVPixelFormat` enum variant) onto [`mediadecode::PixelFormat`].
31///
32/// Returns [`PixelFormat::Unknown`] for raw integers we don't have a
33/// mapping for — including hardware-frame markers
34/// (`AV_PIX_FMT_VIDEOTOOLBOX` / `_VAAPI` / `_CUDA` / `_D3D11` / …)
35/// since those never describe CPU-side pixel data and the unified
36/// enum intentionally doesn't carry them. Use [`is_hardware_pix_fmt`]
37/// to identify HW frames before transferring to a CPU format.
38///
39/// The match never constructs an `AVPixelFormat` from a runtime
40/// value; it compares the input against `AVPixelFormat::AV_PIX_FMT_X
41/// as i32` constants. Sound regardless of which discriminant set the
42/// linked FFmpeg version exposes.
43pub const fn from_av_pixel_format(raw: i32) -> PixelFormat {
44  // Mirrors `crate::pixdesc::to_av_pixel_format` arm-for-arm (its
45  // inverse). Every deliverable CPU format plus the non-deliverable
46  // formats `to_av` still resolves a constant for (monochrome / PAL /
47  // sub-byte-packed RGB / Bayer) is mapped here, so a frame's raw
48  // `format` integer always lands on the same `PixelFormat` the round
49  // trip would produce. Deliverability (HWACCEL / BAYER / PAL /
50  // BITSTREAM rejection) is enforced separately by
51  // `pixdesc::is_deliverable` / the convert layer — this boundary is
52  // identity-only.
53  //
54  // BE-tagged formats map to mediadecode's distinct `*Be` variants
55  // (never folded onto the LE canonical). Folding BE onto LE silently
56  // corrupted pixel data: each >8-bit sample is byte-swapped between
57  // BE and LE, and the convert path exports the AVBufferRef bytes
58  // verbatim with no endian conversion, so a consumer reading a
59  // BE-tagged frame's planes as LE samples would see every sample
60  // byte-reversed. Mapping to the `*Be` variant keeps the format
61  // distinct so the convert layer can handle (or reject) it correctly.
62  //
63  // The match never constructs an `AVPixelFormat` from a runtime
64  // value; it compares the input against `AVPixelFormat::AV_PIX_FMT_X
65  // as i32` constants. Sound regardless of which discriminant set the
66  // linked FFmpeg version exposes.
67  match raw {
68    // Planar YUV 8-bit.
69    x if x == AVPixelFormat::AV_PIX_FMT_YUV420P as i32 => PixelFormat::Yuv420p,
70    x if x == AVPixelFormat::AV_PIX_FMT_YUV422P as i32 => PixelFormat::Yuv422p,
71    x if x == AVPixelFormat::AV_PIX_FMT_YUV440P as i32 => PixelFormat::Yuv440p,
72    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P as i32 => PixelFormat::Yuv444p,
73    x if x == AVPixelFormat::AV_PIX_FMT_YUV411P as i32 => PixelFormat::Yuv411p,
74    x if x == AVPixelFormat::AV_PIX_FMT_YUV410P as i32 => PixelFormat::Yuv410p,
75    // Deprecated JPEG-range planar YUV (yuvj-family).
76    x if x == AVPixelFormat::AV_PIX_FMT_YUVJ411P as i32 => PixelFormat::Yuvj411p,
77    x if x == AVPixelFormat::AV_PIX_FMT_YUVJ420P as i32 => PixelFormat::Yuvj420p,
78    x if x == AVPixelFormat::AV_PIX_FMT_YUVJ422P as i32 => PixelFormat::Yuvj422p,
79    x if x == AVPixelFormat::AV_PIX_FMT_YUVJ440P as i32 => PixelFormat::Yuvj440p,
80    x if x == AVPixelFormat::AV_PIX_FMT_YUVJ444P as i32 => PixelFormat::Yuvj444p,
81    // Planar YUV 4:2:0 high-bit.
82    x if x == AVPixelFormat::AV_PIX_FMT_YUV420P9LE as i32 => PixelFormat::Yuv420p9Le,
83    x if x == AVPixelFormat::AV_PIX_FMT_YUV420P9BE as i32 => PixelFormat::Yuv420p9Be,
84    x if x == AVPixelFormat::AV_PIX_FMT_YUV420P10LE as i32 => PixelFormat::Yuv420p10Le,
85    x if x == AVPixelFormat::AV_PIX_FMT_YUV420P10BE as i32 => PixelFormat::Yuv420p10Be,
86    x if x == AVPixelFormat::AV_PIX_FMT_YUV420P12LE as i32 => PixelFormat::Yuv420p12Le,
87    x if x == AVPixelFormat::AV_PIX_FMT_YUV420P12BE as i32 => PixelFormat::Yuv420p12Be,
88    x if x == AVPixelFormat::AV_PIX_FMT_YUV420P14LE as i32 => PixelFormat::Yuv420p14Le,
89    x if x == AVPixelFormat::AV_PIX_FMT_YUV420P14BE as i32 => PixelFormat::Yuv420p14Be,
90    x if x == AVPixelFormat::AV_PIX_FMT_YUV420P16LE as i32 => PixelFormat::Yuv420p16Le,
91    x if x == AVPixelFormat::AV_PIX_FMT_YUV420P16BE as i32 => PixelFormat::Yuv420p16Be,
92    // Planar YUV 4:2:2 high-bit.
93    x if x == AVPixelFormat::AV_PIX_FMT_YUV422P9LE as i32 => PixelFormat::Yuv422p9Le,
94    x if x == AVPixelFormat::AV_PIX_FMT_YUV422P9BE as i32 => PixelFormat::Yuv422p9Be,
95    x if x == AVPixelFormat::AV_PIX_FMT_YUV422P10LE as i32 => PixelFormat::Yuv422p10Le,
96    x if x == AVPixelFormat::AV_PIX_FMT_YUV422P10BE as i32 => PixelFormat::Yuv422p10Be,
97    x if x == AVPixelFormat::AV_PIX_FMT_YUV422P12LE as i32 => PixelFormat::Yuv422p12Le,
98    x if x == AVPixelFormat::AV_PIX_FMT_YUV422P12BE as i32 => PixelFormat::Yuv422p12Be,
99    x if x == AVPixelFormat::AV_PIX_FMT_YUV422P14LE as i32 => PixelFormat::Yuv422p14Le,
100    x if x == AVPixelFormat::AV_PIX_FMT_YUV422P14BE as i32 => PixelFormat::Yuv422p14Be,
101    x if x == AVPixelFormat::AV_PIX_FMT_YUV422P16LE as i32 => PixelFormat::Yuv422p16Le,
102    x if x == AVPixelFormat::AV_PIX_FMT_YUV422P16BE as i32 => PixelFormat::Yuv422p16Be,
103    // Planar YUV 4:4:0 high-bit.
104    x if x == AVPixelFormat::AV_PIX_FMT_YUV440P10LE as i32 => PixelFormat::Yuv440p10Le,
105    x if x == AVPixelFormat::AV_PIX_FMT_YUV440P10BE as i32 => PixelFormat::Yuv440p10Be,
106    x if x == AVPixelFormat::AV_PIX_FMT_YUV440P12LE as i32 => PixelFormat::Yuv440p12Le,
107    x if x == AVPixelFormat::AV_PIX_FMT_YUV440P12BE as i32 => PixelFormat::Yuv440p12Be,
108    // Planar YUV 4:4:4 high-bit.
109    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P9LE as i32 => PixelFormat::Yuv444p9Le,
110    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P9BE as i32 => PixelFormat::Yuv444p9Be,
111    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P10LE as i32 => PixelFormat::Yuv444p10Le,
112    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P10BE as i32 => PixelFormat::Yuv444p10Be,
113    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P12LE as i32 => PixelFormat::Yuv444p12Le,
114    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P12BE as i32 => PixelFormat::Yuv444p12Be,
115    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P14LE as i32 => PixelFormat::Yuv444p14Le,
116    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P14BE as i32 => PixelFormat::Yuv444p14Be,
117    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P16LE as i32 => PixelFormat::Yuv444p16Le,
118    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P16BE as i32 => PixelFormat::Yuv444p16Be,
119    // MSB-packed YUV 4:4:4.
120    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P10MSBLE as i32 => PixelFormat::Yuv444p10MsbLe,
121    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P10MSBBE as i32 => PixelFormat::Yuv444p10MsbBe,
122    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P12MSBLE as i32 => PixelFormat::Yuv444p12MsbLe,
123    x if x == AVPixelFormat::AV_PIX_FMT_YUV444P12MSBBE as i32 => PixelFormat::Yuv444p12MsbBe,
124    // Planar YUVA.
125    x if x == AVPixelFormat::AV_PIX_FMT_YUVA420P as i32 => PixelFormat::Yuva420p,
126    x if x == AVPixelFormat::AV_PIX_FMT_YUVA422P as i32 => PixelFormat::Yuva422p,
127    x if x == AVPixelFormat::AV_PIX_FMT_YUVA444P as i32 => PixelFormat::Yuva444p,
128    x if x == AVPixelFormat::AV_PIX_FMT_YUVA420P9LE as i32 => PixelFormat::Yuva420p9Le,
129    x if x == AVPixelFormat::AV_PIX_FMT_YUVA420P9BE as i32 => PixelFormat::Yuva420p9Be,
130    x if x == AVPixelFormat::AV_PIX_FMT_YUVA422P9LE as i32 => PixelFormat::Yuva422p9Le,
131    x if x == AVPixelFormat::AV_PIX_FMT_YUVA422P9BE as i32 => PixelFormat::Yuva422p9Be,
132    x if x == AVPixelFormat::AV_PIX_FMT_YUVA444P9LE as i32 => PixelFormat::Yuva444p9Le,
133    x if x == AVPixelFormat::AV_PIX_FMT_YUVA444P9BE as i32 => PixelFormat::Yuva444p9Be,
134    x if x == AVPixelFormat::AV_PIX_FMT_YUVA420P10LE as i32 => PixelFormat::Yuva420p10Le,
135    x if x == AVPixelFormat::AV_PIX_FMT_YUVA420P10BE as i32 => PixelFormat::Yuva420p10Be,
136    x if x == AVPixelFormat::AV_PIX_FMT_YUVA422P10LE as i32 => PixelFormat::Yuva422p10Le,
137    x if x == AVPixelFormat::AV_PIX_FMT_YUVA422P10BE as i32 => PixelFormat::Yuva422p10Be,
138    x if x == AVPixelFormat::AV_PIX_FMT_YUVA444P10LE as i32 => PixelFormat::Yuva444p10Le,
139    x if x == AVPixelFormat::AV_PIX_FMT_YUVA444P10BE as i32 => PixelFormat::Yuva444p10Be,
140    x if x == AVPixelFormat::AV_PIX_FMT_YUVA422P12LE as i32 => PixelFormat::Yuva422p12Le,
141    x if x == AVPixelFormat::AV_PIX_FMT_YUVA422P12BE as i32 => PixelFormat::Yuva422p12Be,
142    x if x == AVPixelFormat::AV_PIX_FMT_YUVA444P12LE as i32 => PixelFormat::Yuva444p12Le,
143    x if x == AVPixelFormat::AV_PIX_FMT_YUVA444P12BE as i32 => PixelFormat::Yuva444p12Be,
144    x if x == AVPixelFormat::AV_PIX_FMT_YUVA420P16LE as i32 => PixelFormat::Yuva420p16Le,
145    x if x == AVPixelFormat::AV_PIX_FMT_YUVA420P16BE as i32 => PixelFormat::Yuva420p16Be,
146    x if x == AVPixelFormat::AV_PIX_FMT_YUVA422P16LE as i32 => PixelFormat::Yuva422p16Le,
147    x if x == AVPixelFormat::AV_PIX_FMT_YUVA422P16BE as i32 => PixelFormat::Yuva422p16Be,
148    x if x == AVPixelFormat::AV_PIX_FMT_YUVA444P16LE as i32 => PixelFormat::Yuva444p16Le,
149    x if x == AVPixelFormat::AV_PIX_FMT_YUVA444P16BE as i32 => PixelFormat::Yuva444p16Be,
150    // Semi-planar YUV 8-bit.
151    x if x == AVPixelFormat::AV_PIX_FMT_NV12 as i32 => PixelFormat::Nv12,
152    x if x == AVPixelFormat::AV_PIX_FMT_NV21 as i32 => PixelFormat::Nv21,
153    x if x == AVPixelFormat::AV_PIX_FMT_NV16 as i32 => PixelFormat::Nv16,
154    x if x == AVPixelFormat::AV_PIX_FMT_NV24 as i32 => PixelFormat::Nv24,
155    x if x == AVPixelFormat::AV_PIX_FMT_NV42 as i32 => PixelFormat::Nv42,
156    x if x == AVPixelFormat::AV_PIX_FMT_NV20LE as i32 => PixelFormat::Nv20Le,
157    x if x == AVPixelFormat::AV_PIX_FMT_NV20BE as i32 => PixelFormat::Nv20Be,
158    // Semi-planar YUV high-bit.
159    x if x == AVPixelFormat::AV_PIX_FMT_P010LE as i32 => PixelFormat::P010Le,
160    x if x == AVPixelFormat::AV_PIX_FMT_P010BE as i32 => PixelFormat::P010Be,
161    x if x == AVPixelFormat::AV_PIX_FMT_P012LE as i32 => PixelFormat::P012Le,
162    x if x == AVPixelFormat::AV_PIX_FMT_P012BE as i32 => PixelFormat::P012Be,
163    x if x == AVPixelFormat::AV_PIX_FMT_P016LE as i32 => PixelFormat::P016Le,
164    x if x == AVPixelFormat::AV_PIX_FMT_P016BE as i32 => PixelFormat::P016Be,
165    x if x == AVPixelFormat::AV_PIX_FMT_P210LE as i32 => PixelFormat::P210Le,
166    x if x == AVPixelFormat::AV_PIX_FMT_P210BE as i32 => PixelFormat::P210Be,
167    x if x == AVPixelFormat::AV_PIX_FMT_P212LE as i32 => PixelFormat::P212Le,
168    x if x == AVPixelFormat::AV_PIX_FMT_P212BE as i32 => PixelFormat::P212Be,
169    x if x == AVPixelFormat::AV_PIX_FMT_P216LE as i32 => PixelFormat::P216Le,
170    x if x == AVPixelFormat::AV_PIX_FMT_P216BE as i32 => PixelFormat::P216Be,
171    x if x == AVPixelFormat::AV_PIX_FMT_P410LE as i32 => PixelFormat::P410Le,
172    x if x == AVPixelFormat::AV_PIX_FMT_P410BE as i32 => PixelFormat::P410Be,
173    x if x == AVPixelFormat::AV_PIX_FMT_P412LE as i32 => PixelFormat::P412Le,
174    x if x == AVPixelFormat::AV_PIX_FMT_P412BE as i32 => PixelFormat::P412Be,
175    x if x == AVPixelFormat::AV_PIX_FMT_P416LE as i32 => PixelFormat::P416Le,
176    x if x == AVPixelFormat::AV_PIX_FMT_P416BE as i32 => PixelFormat::P416Be,
177    // Packed YUV 8-bit.
178    x if x == AVPixelFormat::AV_PIX_FMT_YUYV422 as i32 => PixelFormat::Yuyv422,
179    x if x == AVPixelFormat::AV_PIX_FMT_UYVY422 as i32 => PixelFormat::Uyvy422,
180    x if x == AVPixelFormat::AV_PIX_FMT_YVYU422 as i32 => PixelFormat::Yvyu422,
181    x if x == AVPixelFormat::AV_PIX_FMT_UYYVYY411 as i32 => PixelFormat::Uyyvyy411,
182    // Packed YUV high-bit.
183    x if x == AVPixelFormat::AV_PIX_FMT_Y210LE as i32 => PixelFormat::Y210Le,
184    x if x == AVPixelFormat::AV_PIX_FMT_Y210BE as i32 => PixelFormat::Y210Be,
185    x if x == AVPixelFormat::AV_PIX_FMT_Y212LE as i32 => PixelFormat::Y212Le,
186    x if x == AVPixelFormat::AV_PIX_FMT_Y212BE as i32 => PixelFormat::Y212Be,
187    x if x == AVPixelFormat::AV_PIX_FMT_Y216LE as i32 => PixelFormat::Y216Le,
188    x if x == AVPixelFormat::AV_PIX_FMT_Y216BE as i32 => PixelFormat::Y216Be,
189    x if x == AVPixelFormat::AV_PIX_FMT_XV30LE as i32 => PixelFormat::Xv30Le,
190    x if x == AVPixelFormat::AV_PIX_FMT_XV30BE as i32 => PixelFormat::Xv30Be,
191    x if x == AVPixelFormat::AV_PIX_FMT_V30XLE as i32 => PixelFormat::V30xLe,
192    x if x == AVPixelFormat::AV_PIX_FMT_V30XBE as i32 => PixelFormat::V30xBe,
193    x if x == AVPixelFormat::AV_PIX_FMT_XV36LE as i32 => PixelFormat::Xv36Le,
194    x if x == AVPixelFormat::AV_PIX_FMT_XV36BE as i32 => PixelFormat::Xv36Be,
195    x if x == AVPixelFormat::AV_PIX_FMT_XV48LE as i32 => PixelFormat::Xv48Le,
196    x if x == AVPixelFormat::AV_PIX_FMT_XV48BE as i32 => PixelFormat::Xv48Be,
197    x if x == AVPixelFormat::AV_PIX_FMT_VUYA as i32 => PixelFormat::Vuya,
198    x if x == AVPixelFormat::AV_PIX_FMT_VUYX as i32 => PixelFormat::Vuyx,
199    x if x == AVPixelFormat::AV_PIX_FMT_AYUV as i32 => PixelFormat::Ayuv,
200    x if x == AVPixelFormat::AV_PIX_FMT_AYUV64LE as i32 => PixelFormat::Ayuv64Le,
201    x if x == AVPixelFormat::AV_PIX_FMT_AYUV64BE as i32 => PixelFormat::Ayuv64Be,
202    x if x == AVPixelFormat::AV_PIX_FMT_UYVA as i32 => PixelFormat::Uyva,
203    x if x == AVPixelFormat::AV_PIX_FMT_VYU444 as i32 => PixelFormat::Vyu444,
204    // XYZ.
205    x if x == AVPixelFormat::AV_PIX_FMT_XYZ12LE as i32 => PixelFormat::Xyz12Le,
206    x if x == AVPixelFormat::AV_PIX_FMT_XYZ12BE as i32 => PixelFormat::Xyz12Be,
207    // Packed RGB 8-bit.
208    x if x == AVPixelFormat::AV_PIX_FMT_RGB24 as i32 => PixelFormat::Rgb24,
209    x if x == AVPixelFormat::AV_PIX_FMT_BGR24 as i32 => PixelFormat::Bgr24,
210    x if x == AVPixelFormat::AV_PIX_FMT_RGBA as i32 => PixelFormat::Rgba,
211    x if x == AVPixelFormat::AV_PIX_FMT_BGRA as i32 => PixelFormat::Bgra,
212    x if x == AVPixelFormat::AV_PIX_FMT_ARGB as i32 => PixelFormat::Argb,
213    x if x == AVPixelFormat::AV_PIX_FMT_ABGR as i32 => PixelFormat::Abgr,
214    x if x == AVPixelFormat::AV_PIX_FMT_RGB0 as i32 => PixelFormat::Rgbx,
215    x if x == AVPixelFormat::AV_PIX_FMT_BGR0 as i32 => PixelFormat::Bgrx,
216    x if x == AVPixelFormat::AV_PIX_FMT_0RGB as i32 => PixelFormat::Xrgb,
217    x if x == AVPixelFormat::AV_PIX_FMT_0BGR as i32 => PixelFormat::Xbgr,
218    x if x == AVPixelFormat::AV_PIX_FMT_X2RGB10LE as i32 => PixelFormat::X2Rgb10Le,
219    x if x == AVPixelFormat::AV_PIX_FMT_X2RGB10BE as i32 => PixelFormat::X2Rgb10Be,
220    x if x == AVPixelFormat::AV_PIX_FMT_X2BGR10LE as i32 => PixelFormat::X2Bgr10Le,
221    x if x == AVPixelFormat::AV_PIX_FMT_X2BGR10BE as i32 => PixelFormat::X2Bgr10Be,
222    // Gbr24p shares AV_PIX_FMT_GBRP's discriminant; mapped to Gbrp above.
223    // Packed RGB high-bit.
224    x if x == AVPixelFormat::AV_PIX_FMT_RGB48LE as i32 => PixelFormat::Rgb48Le,
225    x if x == AVPixelFormat::AV_PIX_FMT_RGB48BE as i32 => PixelFormat::Rgb48Be,
226    x if x == AVPixelFormat::AV_PIX_FMT_BGR48LE as i32 => PixelFormat::Bgr48Le,
227    x if x == AVPixelFormat::AV_PIX_FMT_BGR48BE as i32 => PixelFormat::Bgr48Be,
228    x if x == AVPixelFormat::AV_PIX_FMT_RGBA64LE as i32 => PixelFormat::Rgba64Le,
229    x if x == AVPixelFormat::AV_PIX_FMT_RGBA64BE as i32 => PixelFormat::Rgba64Be,
230    x if x == AVPixelFormat::AV_PIX_FMT_BGRA64LE as i32 => PixelFormat::Bgra64Le,
231    x if x == AVPixelFormat::AV_PIX_FMT_BGRA64BE as i32 => PixelFormat::Bgra64Be,
232    x if x == AVPixelFormat::AV_PIX_FMT_RGB96LE as i32 => PixelFormat::Rgb96Le,
233    x if x == AVPixelFormat::AV_PIX_FMT_RGB96BE as i32 => PixelFormat::Rgb96Be,
234    x if x == AVPixelFormat::AV_PIX_FMT_RGBA128LE as i32 => PixelFormat::Rgba128Le,
235    x if x == AVPixelFormat::AV_PIX_FMT_RGBA128BE as i32 => PixelFormat::Rgba128Be,
236    // Packed RGB float / half-float.
237    x if x == AVPixelFormat::AV_PIX_FMT_RGBF16LE as i32 => PixelFormat::Rgbf16Le,
238    x if x == AVPixelFormat::AV_PIX_FMT_RGBF16BE as i32 => PixelFormat::Rgbf16Be,
239    x if x == AVPixelFormat::AV_PIX_FMT_RGBF32LE as i32 => PixelFormat::Rgbf32Le,
240    x if x == AVPixelFormat::AV_PIX_FMT_RGBF32BE as i32 => PixelFormat::Rgbf32Be,
241    x if x == AVPixelFormat::AV_PIX_FMT_RGBAF16LE as i32 => PixelFormat::Rgbaf16Le,
242    x if x == AVPixelFormat::AV_PIX_FMT_RGBAF16BE as i32 => PixelFormat::Rgbaf16Be,
243    x if x == AVPixelFormat::AV_PIX_FMT_RGBAF32LE as i32 => PixelFormat::Rgbaf32Le,
244    x if x == AVPixelFormat::AV_PIX_FMT_RGBAF32BE as i32 => PixelFormat::Rgbaf32Be,
245    // Planar GBR.
246    x if x == AVPixelFormat::AV_PIX_FMT_GBRP as i32 => PixelFormat::Gbrp,
247    x if x == AVPixelFormat::AV_PIX_FMT_GBRP9LE as i32 => PixelFormat::Gbrp9Le,
248    x if x == AVPixelFormat::AV_PIX_FMT_GBRP9BE as i32 => PixelFormat::Gbrp9Be,
249    x if x == AVPixelFormat::AV_PIX_FMT_GBRP10LE as i32 => PixelFormat::Gbrp10Le,
250    x if x == AVPixelFormat::AV_PIX_FMT_GBRP10BE as i32 => PixelFormat::Gbrp10Be,
251    x if x == AVPixelFormat::AV_PIX_FMT_GBRP10MSBLE as i32 => PixelFormat::Gbrp10MsbLe,
252    x if x == AVPixelFormat::AV_PIX_FMT_GBRP10MSBBE as i32 => PixelFormat::Gbrp10MsbBe,
253    x if x == AVPixelFormat::AV_PIX_FMT_GBRP12LE as i32 => PixelFormat::Gbrp12Le,
254    x if x == AVPixelFormat::AV_PIX_FMT_GBRP12BE as i32 => PixelFormat::Gbrp12Be,
255    x if x == AVPixelFormat::AV_PIX_FMT_GBRP12MSBLE as i32 => PixelFormat::Gbrp12MsbLe,
256    x if x == AVPixelFormat::AV_PIX_FMT_GBRP12MSBBE as i32 => PixelFormat::Gbrp12MsbBe,
257    x if x == AVPixelFormat::AV_PIX_FMT_GBRP14LE as i32 => PixelFormat::Gbrp14Le,
258    x if x == AVPixelFormat::AV_PIX_FMT_GBRP14BE as i32 => PixelFormat::Gbrp14Be,
259    x if x == AVPixelFormat::AV_PIX_FMT_GBRP16LE as i32 => PixelFormat::Gbrp16Le,
260    x if x == AVPixelFormat::AV_PIX_FMT_GBRP16BE as i32 => PixelFormat::Gbrp16Be,
261    x if x == AVPixelFormat::AV_PIX_FMT_GBRPF16LE as i32 => PixelFormat::Gbrpf16Le,
262    x if x == AVPixelFormat::AV_PIX_FMT_GBRPF16BE as i32 => PixelFormat::Gbrpf16Be,
263    x if x == AVPixelFormat::AV_PIX_FMT_GBRPF32LE as i32 => PixelFormat::Gbrpf32Le,
264    x if x == AVPixelFormat::AV_PIX_FMT_GBRPF32BE as i32 => PixelFormat::Gbrpf32Be,
265    // Planar GBRA.
266    x if x == AVPixelFormat::AV_PIX_FMT_GBRAP as i32 => PixelFormat::Gbrap,
267    x if x == AVPixelFormat::AV_PIX_FMT_GBRAP10LE as i32 => PixelFormat::Gbrap10Le,
268    x if x == AVPixelFormat::AV_PIX_FMT_GBRAP10BE as i32 => PixelFormat::Gbrap10Be,
269    x if x == AVPixelFormat::AV_PIX_FMT_GBRAP12LE as i32 => PixelFormat::Gbrap12Le,
270    x if x == AVPixelFormat::AV_PIX_FMT_GBRAP12BE as i32 => PixelFormat::Gbrap12Be,
271    x if x == AVPixelFormat::AV_PIX_FMT_GBRAP14LE as i32 => PixelFormat::Gbrap14Le,
272    x if x == AVPixelFormat::AV_PIX_FMT_GBRAP14BE as i32 => PixelFormat::Gbrap14Be,
273    x if x == AVPixelFormat::AV_PIX_FMT_GBRAP16LE as i32 => PixelFormat::Gbrap16Le,
274    x if x == AVPixelFormat::AV_PIX_FMT_GBRAP16BE as i32 => PixelFormat::Gbrap16Be,
275    x if x == AVPixelFormat::AV_PIX_FMT_GBRAP32LE as i32 => PixelFormat::Gbrap32Le,
276    x if x == AVPixelFormat::AV_PIX_FMT_GBRAP32BE as i32 => PixelFormat::Gbrap32Be,
277    x if x == AVPixelFormat::AV_PIX_FMT_GBRAPF16LE as i32 => PixelFormat::Gbrapf16Le,
278    x if x == AVPixelFormat::AV_PIX_FMT_GBRAPF16BE as i32 => PixelFormat::Gbrapf16Be,
279    x if x == AVPixelFormat::AV_PIX_FMT_GBRAPF32LE as i32 => PixelFormat::Gbrapf32Le,
280    x if x == AVPixelFormat::AV_PIX_FMT_GBRAPF32BE as i32 => PixelFormat::Gbrapf32Be,
281    // Greyscale.
282    x if x == AVPixelFormat::AV_PIX_FMT_GRAY8 as i32 => PixelFormat::Gray8,
283    x if x == AVPixelFormat::AV_PIX_FMT_GRAY9LE as i32 => PixelFormat::Gray9Le,
284    x if x == AVPixelFormat::AV_PIX_FMT_GRAY9BE as i32 => PixelFormat::Gray9Be,
285    x if x == AVPixelFormat::AV_PIX_FMT_GRAY10LE as i32 => PixelFormat::Gray10Le,
286    x if x == AVPixelFormat::AV_PIX_FMT_GRAY10BE as i32 => PixelFormat::Gray10Be,
287    x if x == AVPixelFormat::AV_PIX_FMT_GRAY12LE as i32 => PixelFormat::Gray12Le,
288    x if x == AVPixelFormat::AV_PIX_FMT_GRAY12BE as i32 => PixelFormat::Gray12Be,
289    x if x == AVPixelFormat::AV_PIX_FMT_GRAY14LE as i32 => PixelFormat::Gray14Le,
290    x if x == AVPixelFormat::AV_PIX_FMT_GRAY14BE as i32 => PixelFormat::Gray14Be,
291    x if x == AVPixelFormat::AV_PIX_FMT_GRAY16LE as i32 => PixelFormat::Gray16Le,
292    x if x == AVPixelFormat::AV_PIX_FMT_GRAY16BE as i32 => PixelFormat::Gray16Be,
293    x if x == AVPixelFormat::AV_PIX_FMT_GRAY32LE as i32 => PixelFormat::Gray32Le,
294    x if x == AVPixelFormat::AV_PIX_FMT_GRAY32BE as i32 => PixelFormat::Gray32Be,
295    x if x == AVPixelFormat::AV_PIX_FMT_GRAYF16LE as i32 => PixelFormat::Grayf16Le,
296    x if x == AVPixelFormat::AV_PIX_FMT_GRAYF16BE as i32 => PixelFormat::Grayf16Be,
297    x if x == AVPixelFormat::AV_PIX_FMT_GRAYF32LE as i32 => PixelFormat::Grayf32Le,
298    x if x == AVPixelFormat::AV_PIX_FMT_GRAYF32BE as i32 => PixelFormat::Grayf32Be,
299    x if x == AVPixelFormat::AV_PIX_FMT_YA8 as i32 => PixelFormat::Ya8,
300    x if x == AVPixelFormat::AV_PIX_FMT_YA16LE as i32 => PixelFormat::Ya16Le,
301    x if x == AVPixelFormat::AV_PIX_FMT_YA16BE as i32 => PixelFormat::Ya16Be,
302    x if x == AVPixelFormat::AV_PIX_FMT_YAF16LE as i32 => PixelFormat::Yaf16Le,
303    x if x == AVPixelFormat::AV_PIX_FMT_YAF16BE as i32 => PixelFormat::Yaf16Be,
304    x if x == AVPixelFormat::AV_PIX_FMT_YAF32LE as i32 => PixelFormat::Yaf32Le,
305    x if x == AVPixelFormat::AV_PIX_FMT_YAF32BE as i32 => PixelFormat::Yaf32Be,
306    x if x == AVPixelFormat::AV_PIX_FMT_MONOWHITE as i32 => PixelFormat::Monowhite,
307    x if x == AVPixelFormat::AV_PIX_FMT_MONOBLACK as i32 => PixelFormat::Monoblack,
308    x if x == AVPixelFormat::AV_PIX_FMT_PAL8 as i32 => PixelFormat::Pal8,
309    x if x == AVPixelFormat::AV_PIX_FMT_RGB4 as i32 => PixelFormat::Rgb4,
310    x if x == AVPixelFormat::AV_PIX_FMT_RGB4_BYTE as i32 => PixelFormat::Rgb4Byte,
311    x if x == AVPixelFormat::AV_PIX_FMT_RGB8 as i32 => PixelFormat::Rgb8,
312    x if x == AVPixelFormat::AV_PIX_FMT_BGR4 as i32 => PixelFormat::Bgr4,
313    x if x == AVPixelFormat::AV_PIX_FMT_BGR4_BYTE as i32 => PixelFormat::Bgr4Byte,
314    x if x == AVPixelFormat::AV_PIX_FMT_BGR8 as i32 => PixelFormat::Bgr8,
315    x if x == AVPixelFormat::AV_PIX_FMT_RGB444LE as i32 => PixelFormat::Rgb444Le,
316    x if x == AVPixelFormat::AV_PIX_FMT_RGB444BE as i32 => PixelFormat::Rgb444Be,
317    x if x == AVPixelFormat::AV_PIX_FMT_BGR444LE as i32 => PixelFormat::Bgr444Le,
318    x if x == AVPixelFormat::AV_PIX_FMT_BGR444BE as i32 => PixelFormat::Bgr444Be,
319    x if x == AVPixelFormat::AV_PIX_FMT_RGB555LE as i32 => PixelFormat::Rgb555Le,
320    x if x == AVPixelFormat::AV_PIX_FMT_RGB555BE as i32 => PixelFormat::Rgb555Be,
321    x if x == AVPixelFormat::AV_PIX_FMT_BGR555LE as i32 => PixelFormat::Bgr555Le,
322    x if x == AVPixelFormat::AV_PIX_FMT_BGR555BE as i32 => PixelFormat::Bgr555Be,
323    x if x == AVPixelFormat::AV_PIX_FMT_RGB565LE as i32 => PixelFormat::Rgb565Le,
324    x if x == AVPixelFormat::AV_PIX_FMT_RGB565BE as i32 => PixelFormat::Rgb565Be,
325    x if x == AVPixelFormat::AV_PIX_FMT_BGR565LE as i32 => PixelFormat::Bgr565Le,
326    x if x == AVPixelFormat::AV_PIX_FMT_BGR565BE as i32 => PixelFormat::Bgr565Be,
327    x if x == AVPixelFormat::AV_PIX_FMT_BAYER_BGGR8 as i32 => PixelFormat::BayerBggr8,
328    x if x == AVPixelFormat::AV_PIX_FMT_BAYER_RGGB8 as i32 => PixelFormat::BayerRggb8,
329    x if x == AVPixelFormat::AV_PIX_FMT_BAYER_GBRG8 as i32 => PixelFormat::BayerGbrg8,
330    x if x == AVPixelFormat::AV_PIX_FMT_BAYER_GRBG8 as i32 => PixelFormat::BayerGrbg8,
331    x if x == AVPixelFormat::AV_PIX_FMT_BAYER_BGGR16LE as i32 => PixelFormat::BayerBggr16Le,
332    x if x == AVPixelFormat::AV_PIX_FMT_BAYER_BGGR16BE as i32 => PixelFormat::BayerBggr16Be,
333    x if x == AVPixelFormat::AV_PIX_FMT_BAYER_RGGB16LE as i32 => PixelFormat::BayerRggb16Le,
334    x if x == AVPixelFormat::AV_PIX_FMT_BAYER_RGGB16BE as i32 => PixelFormat::BayerRggb16Be,
335    x if x == AVPixelFormat::AV_PIX_FMT_BAYER_GBRG16LE as i32 => PixelFormat::BayerGbrg16Le,
336    x if x == AVPixelFormat::AV_PIX_FMT_BAYER_GBRG16BE as i32 => PixelFormat::BayerGbrg16Be,
337    x if x == AVPixelFormat::AV_PIX_FMT_BAYER_GRBG16LE as i32 => PixelFormat::BayerGrbg16Le,
338    x if x == AVPixelFormat::AV_PIX_FMT_BAYER_GRBG16BE as i32 => PixelFormat::BayerGrbg16Be,
339    _ => PixelFormat::Unknown(raw as u32),
340  }
341}
342
343/// Returns `true` when `raw` is one of FFmpeg's hardware-frame markers
344/// (`AV_PIX_FMT_VIDEOTOOLBOX` / `_VAAPI` / `_CUDA` / `_D3D11` /
345/// `_DRM_PRIME` / `_MEDIACODEC` / `_VULKAN`). Used by the HW probe to
346/// identify GPU-resident frames before triggering
347/// `av_hwframe_transfer_data`.
348pub const fn is_hardware_pix_fmt(raw: i32) -> bool {
349  matches!(
350    raw,
351    x if x == AVPixelFormat::AV_PIX_FMT_VIDEOTOOLBOX as i32
352      || x == AVPixelFormat::AV_PIX_FMT_VAAPI as i32
353      || x == AVPixelFormat::AV_PIX_FMT_CUDA as i32
354      || x == AVPixelFormat::AV_PIX_FMT_D3D11 as i32
355      || x == AVPixelFormat::AV_PIX_FMT_DRM_PRIME as i32
356      || x == AVPixelFormat::AV_PIX_FMT_MEDIACODEC as i32
357      || x == AVPixelFormat::AV_PIX_FMT_VULKAN as i32
358  )
359}
360
361/// Fallible counterpart to ffmpeg-next's `Packet::copy`.
362///
363/// The upstream helper calls `Packet::new(size)` (which silently
364/// truncates `size` to `c_int` and ignores `av_new_packet`'s return
365/// code) and then panics via `data_mut().unwrap().write_all(...).unwrap()`
366/// if the allocation failed. From a safe public decoder API we want
367/// the OOM / oversized-payload paths to surface as
368/// `ffmpeg_next::Error` rather than aborting the process — every
369/// `send_packet` path goes through this helper.
370///
371/// Failure modes:
372/// * payload larger than `c_int::MAX` (would overflow `AVPacket.size`)
373///   → `ffmpeg_next::Error::Other { errno: libc::EINVAL }`.
374/// * `av_new_packet` allocation failure (signalled by `data_mut()`
375///   returning `None`) → `ffmpeg_next::Error::Other { errno:
376///   libc::ENOMEM }`.
377fn try_packet_copy(data: &[u8]) -> std::result::Result<Packet, ffmpeg_next::Error> {
378  // FFmpeg's `AVPacket.size` is `c_int`. A payload larger than that
379  // can't fit in a single packet — refuse rather than truncate via
380  // `as c_int` inside `Packet::new`.
381  if data.len() > c_int::MAX as usize {
382    return Err(ffmpeg_next::Error::Other {
383      errno: libc::EINVAL,
384    });
385  }
386  // `Packet::new(size)` calls `av_new_packet(&mut pkt, size as
387  // c_int)` and ignores the return code; on OOM it returns a
388  // `Packet` whose `.data` is null. We detect that via
389  // `data_mut()` (returns `None` on null) and copy via
390  // `copy_nonoverlapping` so we never go through `data_mut()
391  // .unwrap().write_all().unwrap()` — the upstream `Packet::copy`'s
392  // double panic.
393  let mut pkt = Packet::new(data.len());
394  match pkt.data_mut() {
395    Some(slot) if slot.len() == data.len() => {
396      // SAFETY: `slot` is a `&mut [u8]` of `data.len()` bytes;
397      // `data` is a `&[u8]` of the same length. Non-overlapping
398      // because `slot` is a fresh allocation.
399      if !data.is_empty() {
400        unsafe {
401          core::ptr::copy_nonoverlapping(data.as_ptr(), slot.as_mut_ptr(), data.len());
402        }
403      }
404      Ok(pkt)
405    }
406    _ => Err(ffmpeg_next::Error::Other {
407      errno: libc::ENOMEM,
408    }),
409  }
410}
411
412/// Centralised mediadecode→AV packet flag mapping so the three
413/// packet-conversion helpers stay aligned.
414fn map_md_flags_to_av(flags: MdPacketFlags) -> ffmpeg_next::packet::Flags {
415  let mut av_flags = ffmpeg_next::packet::Flags::empty();
416  if flags.contains(MdPacketFlags::KEY) {
417    av_flags |= ffmpeg_next::packet::Flags::KEY;
418  }
419  if flags.contains(MdPacketFlags::CORRUPT) {
420    av_flags |= ffmpeg_next::packet::Flags::CORRUPT;
421  }
422  // ffmpeg-next 8.x doesn't expose a DISCARD flag constant on
423  // `packet::Flags`; the upstream `AV_PKT_FLAG_DISCARD` bit is
424  // documented as a demuxer hint and rarely set on packets passed
425  // to a decoder. We forward KEY and CORRUPT (the meaningful subset)
426  // and silently drop DISCARD until ffmpeg-next adds it.
427  av_flags
428}
429
430/// Builds an `ffmpeg::Packet` from a [`mediadecode::VideoPacket`]
431/// parameterized by [`crate::extras::VideoPacketExtra`] and
432/// [`crate::FfmpegBuffer`].
433///
434/// The compressed bytes are **copied** into a new packet allocation —
435/// zero-copy passthrough of the FfmpegBuffer's underlying AVBufferRef
436/// is a future optimization (would need to wire an `AVBufferRef` into
437/// `AVPacket.buf` directly via `av_packet_alloc` + manual buffer set).
438/// PTS / DTS / duration / flags / stream_index are propagated.
439///
440/// Returns `Err(ffmpeg_next::Error)` on:
441/// * payload larger than `c_int::MAX` (would overflow `AVPacket.size`);
442/// * `av_new_packet` allocation failure (OOM).
443pub fn ffmpeg_packet_from_video_packet(
444  packet: &mediadecode::packet::VideoPacket<VideoPacketExtra, FfmpegBuffer>,
445) -> std::result::Result<Packet, ffmpeg_next::Error> {
446  let mut out = try_packet_copy(packet.data().as_ref())?;
447  if let Some(ts) = packet.pts() {
448    out.set_pts(Some(ts.pts()));
449  }
450  if let Some(ts) = packet.dts() {
451    out.set_dts(Some(ts.pts()));
452  }
453  if let Some(d) = packet.duration() {
454    out.set_duration(d.pts());
455  }
456  out.set_flags(map_md_flags_to_av(packet.flags()));
457  out.set_stream(packet.extra().stream_index() as usize);
458  Ok(out)
459}
460
461/// Builds an `ffmpeg::Packet` from a [`mediadecode::AudioPacket`].
462/// Same shape as [`ffmpeg_packet_from_video_packet`] — bytes are
463/// copied; pts/dts/duration/flags/stream_index are forwarded. Same
464/// failure modes.
465pub fn ffmpeg_packet_from_audio_packet(
466  packet: &mediadecode::packet::AudioPacket<AudioPacketExtra, FfmpegBuffer>,
467) -> std::result::Result<Packet, ffmpeg_next::Error> {
468  let mut out = try_packet_copy(packet.data().as_ref())?;
469  if let Some(ts) = packet.pts() {
470    out.set_pts(Some(ts.pts()));
471  }
472  if let Some(ts) = packet.dts() {
473    out.set_dts(Some(ts.pts()));
474  }
475  if let Some(d) = packet.duration() {
476    out.set_duration(d.pts());
477  }
478  out.set_flags(map_md_flags_to_av(packet.flags()));
479  out.set_stream(packet.extra().stream_index() as usize);
480  Ok(out)
481}
482
483/// Builds an `ffmpeg::Packet` from a [`mediadecode::SubtitlePacket`].
484/// Bytes copied; pts/duration/flags/stream_index forwarded. Subtitle
485/// packets have no `dts` in the mediadecode model. Same failure
486/// modes as [`ffmpeg_packet_from_video_packet`].
487pub fn ffmpeg_packet_from_subtitle_packet(
488  packet: &mediadecode::packet::SubtitlePacket<SubtitlePacketExtra, FfmpegBuffer>,
489) -> std::result::Result<Packet, ffmpeg_next::Error> {
490  let mut out = try_packet_copy(packet.data().as_ref())?;
491  if let Some(ts) = packet.pts() {
492    out.set_pts(Some(ts.pts()));
493  }
494  if let Some(d) = packet.duration() {
495    out.set_duration(d.pts());
496  }
497  out.set_flags(map_md_flags_to_av(packet.flags()));
498  out.set_stream(packet.extra().stream_index() as usize);
499  Ok(out)
500}
501
502// ---------------------------------------------------------------------------
503//  Safe wrappers — `&ffmpeg::Packet` → `mediadecode::*Packet`.
504// ---------------------------------------------------------------------------
505
506/// Wraps a borrowed [`ffmpeg::Packet`] as a
507/// [`mediadecode::packet::VideoPacket`]. The compressed payload is
508/// shared with the source `AVPacket` via refcount bump (no copy).
509/// Timestamps, duration, key/corrupt flags, and the source stream
510/// index are forwarded to the produced packet.
511///
512/// Returns `None` when the source packet has no buffer attached
513/// (empty packet — typical after EOF). Caller can also fill in
514/// [`VideoPacketExtra::byte_pos`] / `side_data` post-construction
515/// if they need those.
516pub fn video_packet_from_ffmpeg(
517  packet: &Packet,
518) -> Option<VideoPacket<VideoPacketExtra, FfmpegBuffer>> {
519  let buf = FfmpegBuffer::from_packet(packet)?;
520  let mut out = VideoPacket::new(buf, VideoPacketExtra::new(packet.stream() as i32))
521    .with_flags(md_flags_from_av(packet.flags()));
522  if let Some(p) = packet.pts() {
523    out = out.with_pts(Some(Timestamp::new(p, mediadecode::Timebase::default())));
524  }
525  if let Some(d) = packet.dts() {
526    out = out.with_dts(Some(Timestamp::new(d, mediadecode::Timebase::default())));
527  }
528  let dur = packet.duration();
529  if dur > 0 {
530    out = out.with_duration(Some(Timestamp::new(dur, mediadecode::Timebase::default())));
531  }
532  Some(out)
533}
534
535/// Wraps a borrowed [`ffmpeg::Packet`] as a
536/// [`mediadecode::packet::AudioPacket`]. Same shape as
537/// [`video_packet_from_ffmpeg`] — refcounted payload, forwarded
538/// metadata.
539pub fn audio_packet_from_ffmpeg(
540  packet: &Packet,
541) -> Option<AudioPacket<AudioPacketExtra, FfmpegBuffer>> {
542  let buf = FfmpegBuffer::from_packet(packet)?;
543  let mut out = AudioPacket::new(buf, AudioPacketExtra::new(packet.stream() as i32))
544    .with_flags(md_flags_from_av(packet.flags()));
545  if let Some(p) = packet.pts() {
546    out = out.with_pts(Some(Timestamp::new(p, mediadecode::Timebase::default())));
547  }
548  if let Some(d) = packet.dts() {
549    out = out.with_dts(Some(Timestamp::new(d, mediadecode::Timebase::default())));
550  }
551  let dur = packet.duration();
552  if dur > 0 {
553    out = out.with_duration(Some(Timestamp::new(dur, mediadecode::Timebase::default())));
554  }
555  Some(out)
556}
557
558/// Wraps a borrowed [`ffmpeg::Packet`] as a
559/// [`mediadecode::packet::SubtitlePacket`]. Subtitle packets have no
560/// `dts` in the mediadecode model; everything else mirrors
561/// [`video_packet_from_ffmpeg`].
562pub fn subtitle_packet_from_ffmpeg(
563  packet: &Packet,
564) -> Option<SubtitlePacket<SubtitlePacketExtra, FfmpegBuffer>> {
565  let buf = FfmpegBuffer::from_packet(packet)?;
566  let mut out = SubtitlePacket::new(buf, SubtitlePacketExtra::new(packet.stream() as i32))
567    .with_flags(md_flags_from_av(packet.flags()));
568  if let Some(p) = packet.pts() {
569    out = out.with_pts(Some(Timestamp::new(p, mediadecode::Timebase::default())));
570  }
571  let dur = packet.duration();
572  if dur > 0 {
573    out = out.with_duration(Some(Timestamp::new(dur, mediadecode::Timebase::default())));
574  }
575  Some(out)
576}
577
578fn md_flags_from_av(flags: ffmpeg_next::packet::Flags) -> MdPacketFlags {
579  let mut out = MdPacketFlags::empty();
580  if flags.contains(ffmpeg_next::packet::Flags::KEY) {
581    out |= MdPacketFlags::KEY;
582  }
583  if flags.contains(ffmpeg_next::packet::Flags::CORRUPT) {
584    out |= MdPacketFlags::CORRUPT;
585  }
586  out
587}
588
589// ---------------------------------------------------------------------------
590//  Empty-frame placeholders for `receive_frame` destinations.
591// ---------------------------------------------------------------------------
592
593/// Constructs an empty [`mediadecode::frame::VideoFrame`] suitable as
594/// the destination argument to
595/// [`mediadecode::decoder::VideoStreamDecoder::receive_frame`]. The
596/// decoder overwrites the frame on success; this just provides a
597/// well-formed slot.
598///
599/// All four plane slots get a 1-byte `FfmpegBuffer` placeholder
600/// (the array shape requires a buffer in every slot, but
601/// `plane_count = 0` reports them as inactive).
602///
603/// # Panics
604///
605/// Panics on FFmpeg-side OOM (the per-plane 1-byte allocation
606/// failed). Callers who need to recover from OOM should use
607/// [`try_empty_video_frame`].
608pub fn empty_video_frame() -> VideoFrame<PixelFormat, VideoFrameExtra, FfmpegBuffer> {
609  try_empty_video_frame().expect("empty_video_frame: av_buffer_alloc returned null (OOM)")
610}
611
612/// Fallible counterpart to [`empty_video_frame`]. Returns `None` if
613/// any of the four placeholder allocations fails.
614pub fn try_empty_video_frame() -> Option<VideoFrame<PixelFormat, VideoFrameExtra, FfmpegBuffer>> {
615  let planes = [
616    Plane::new(FfmpegBuffer::try_empty()?, 0),
617    Plane::new(FfmpegBuffer::try_empty()?, 0),
618    Plane::new(FfmpegBuffer::try_empty()?, 0),
619    Plane::new(FfmpegBuffer::try_empty()?, 0),
620  ];
621  Some(VideoFrame::new(
622    Dimensions::new(0, 0),
623    PixelFormat::Unknown(0),
624    planes,
625    0,
626    VideoFrameExtra::default(),
627  ))
628}
629
630/// Constructs an empty [`mediadecode::frame::AudioFrame`] suitable as
631/// the destination argument to
632/// [`mediadecode::decoder::AudioStreamDecoder::receive_frame`]. Same
633/// behaviour as [`empty_video_frame`] — eight 1-byte plane
634/// placeholders, `plane_count = 0`.
635///
636/// # Panics
637///
638/// Panics on FFmpeg-side OOM. See [`try_empty_audio_frame`] for the
639/// fallible variant.
640pub fn empty_audio_frame()
641-> AudioFrame<SampleFormat, AudioChannelLayout, AudioFrameExtra, FfmpegBuffer> {
642  try_empty_audio_frame().expect("empty_audio_frame: av_buffer_alloc returned null (OOM)")
643}
644
645/// Fallible counterpart to [`empty_audio_frame`]. Returns `None` if
646/// any of the eight placeholder allocations fails.
647pub fn try_empty_audio_frame()
648-> Option<AudioFrame<SampleFormat, AudioChannelLayout, AudioFrameExtra, FfmpegBuffer>> {
649  let planes = [
650    Plane::new(FfmpegBuffer::try_empty()?, 0),
651    Plane::new(FfmpegBuffer::try_empty()?, 0),
652    Plane::new(FfmpegBuffer::try_empty()?, 0),
653    Plane::new(FfmpegBuffer::try_empty()?, 0),
654    Plane::new(FfmpegBuffer::try_empty()?, 0),
655    Plane::new(FfmpegBuffer::try_empty()?, 0),
656    Plane::new(FfmpegBuffer::try_empty()?, 0),
657    Plane::new(FfmpegBuffer::try_empty()?, 0),
658  ];
659  Some(AudioFrame::new(
660    0,
661    0,
662    0,
663    SampleFormat::NONE,
664    AudioChannelLayout::default(),
665    planes,
666    0,
667    AudioFrameExtra::default(),
668  ))
669}
670
671/// Constructs an empty [`mediadecode::frame::SubtitleFrame`] suitable
672/// as the destination argument to
673/// [`mediadecode::decoder::SubtitleDecoder::receive_frame`]. The
674/// payload is an empty `Text` placeholder; the decoder overwrites
675/// it on success.
676///
677/// # Panics
678///
679/// Panics on FFmpeg-side OOM. See [`try_empty_subtitle_frame`] for
680/// the fallible variant.
681pub fn empty_subtitle_frame() -> SubtitleFrame<SubtitleFrameExtra, FfmpegBuffer> {
682  try_empty_subtitle_frame().expect("empty_subtitle_frame: av_buffer_alloc returned null (OOM)")
683}
684
685/// Fallible counterpart to [`empty_subtitle_frame`]. Returns `None`
686/// if the placeholder allocation fails.
687pub fn try_empty_subtitle_frame() -> Option<SubtitleFrame<SubtitleFrameExtra, FfmpegBuffer>> {
688  let buf = FfmpegBuffer::copy_from_slice(&[]).or_else(FfmpegBuffer::try_empty)?;
689  Some(SubtitleFrame::new(
690    SubtitlePayload::Text {
691      text: buf,
692      language: None,
693    },
694    SubtitleFrameExtra::default(),
695  ))
696}
697
698#[cfg(test)]
699mod tests {
700  use super::*;
701
702  #[test]
703  fn nv12_round_trips() {
704    assert_eq!(
705      from_av_pixel_format(AVPixelFormat::AV_PIX_FMT_NV12 as i32),
706      PixelFormat::Nv12,
707    );
708  }
709
710  #[test]
711  fn p010be_maps_to_p010be() {
712    // BE must map to the BE variant — the previous "fold to LE"
713    // mapping silently corrupted P010BE pixel data via the safe
714    // export path. The unsupported-format gate in `convert::av_frame_to_video_frame`
715    // is the right place to reject BE today.
716    assert_eq!(
717      from_av_pixel_format(AVPixelFormat::AV_PIX_FMT_P010BE as i32),
718      PixelFormat::P010Be,
719    );
720  }
721
722  #[test]
723  fn unknown_for_garbage_value() {
724    assert!(matches!(
725      from_av_pixel_format(-99_999),
726      PixelFormat::Unknown(_)
727    ));
728  }
729
730  #[test]
731  fn hw_formats_detected() {
732    assert!(is_hardware_pix_fmt(
733      AVPixelFormat::AV_PIX_FMT_VIDEOTOOLBOX as i32
734    ));
735    assert!(is_hardware_pix_fmt(AVPixelFormat::AV_PIX_FMT_VAAPI as i32));
736    assert!(is_hardware_pix_fmt(AVPixelFormat::AV_PIX_FMT_CUDA as i32));
737    assert!(is_hardware_pix_fmt(AVPixelFormat::AV_PIX_FMT_D3D11 as i32));
738  }
739
740  #[test]
741  fn cpu_formats_not_detected_as_hw() {
742    assert!(!is_hardware_pix_fmt(AVPixelFormat::AV_PIX_FMT_NV12 as i32));
743    assert!(!is_hardware_pix_fmt(
744      AVPixelFormat::AV_PIX_FMT_YUV420P as i32
745    ));
746    assert!(!is_hardware_pix_fmt(AVPixelFormat::AV_PIX_FMT_NONE as i32));
747  }
748
749  #[test]
750  fn hw_formats_map_to_unknown_in_pixel_format() {
751    // HW sentinels intentionally don't have a mediadecode::PixelFormat
752    // representation — they're not CPU pixel data.
753    assert!(matches!(
754      from_av_pixel_format(AVPixelFormat::AV_PIX_FMT_VIDEOTOOLBOX as i32),
755      PixelFormat::Unknown(_)
756    ));
757    assert!(matches!(
758      from_av_pixel_format(AVPixelFormat::AV_PIX_FMT_VAAPI as i32),
759      PixelFormat::Unknown(_)
760    ));
761  }
762}