ffmpeg_the_third/codec/
config.rs

1use crate::iters::TerminatedPtrIter;
2
3#[cfg(feature = "ffmpeg_7_1")]
4use std::ptr::NonNull;
5
6#[cfg(feature = "ffmpeg_7_1")]
7use crate::codec::Context;
8#[cfg(feature = "ffmpeg_7_1")]
9use crate::ffi::*;
10#[cfg(feature = "ffmpeg_7_1")]
11use crate::Codec;
12#[cfg(feature = "ffmpeg_7_1")]
13use crate::Error;
14
15#[cfg(feature = "ffmpeg_7_1")]
16#[derive(Debug, Clone)]
17pub enum Supported<I> {
18    All,
19    Specific(I),
20}
21
22#[cfg(feature = "ffmpeg_7_1")]
23impl<T, I> Supported<I>
24where
25    T: PartialEq,
26    I: Iterator<Item = T>,
27{
28    /// Check if all possible configuration values are supported.
29    ///
30    /// # Example
31    ///
32    /// ```
33    /// use ffmpeg_the_third::codec::{encoder, Id};
34    ///
35    /// let codec = encoder::find(Id::VP9)
36    ///     .expect("Can find a VP9 encoder")
37    ///     .video()
38    ///     .unwrap();
39    ///
40    /// let supported = codec.supported_rates();
41    /// assert!(supported.all())
42    /// ```
43    pub fn all(&self) -> bool {
44        matches!(self, Supported::All)
45    }
46
47    /// Check if a specific configuration value is supported.
48    ///
49    /// # Example
50    ///
51    /// ```
52    /// use ffmpeg_the_third::codec::{decoder, Id};
53    /// use ffmpeg_the_third::format::sample::{Sample, Type};
54    ///
55    /// let codec = decoder::find(Id::MP3)
56    ///     .expect("Can find an MP3 decoder")
57    ///     .audio()
58    ///     .unwrap();
59    ///
60    /// let supported = codec.supported_formats();
61    /// assert!(supported.supports(Sample::F32(Type::Planar)));
62    /// ```
63    pub fn supports(self, t: T) -> bool {
64        match self {
65            Supported::All => true,
66            Supported::Specific(mut iter) => iter.any(|elem| elem == t),
67        }
68    }
69}
70
71#[cfg(feature = "ffmpeg_7_1")]
72fn supported<WrapperType, AVType, CodecType, I>(
73    codec: Codec<CodecType>,
74    ctx: Option<&Context>,
75    cfg: AVCodecConfig,
76) -> Result<Supported<I>, Error>
77where
78    I: TerminatedPtrIter<AVType, WrapperType>,
79    AVType: Into<WrapperType>,
80{
81    let mut out_ptr: *const libc::c_void = std::ptr::null();
82
83    unsafe {
84        let avctx = ctx.map_or(std::ptr::null(), |ctx| ctx.as_ptr());
85
86        let ret = avcodec_get_supported_config(
87            avctx,
88            codec.as_ptr(),
89            cfg,
90            0, // flags: unused as of 7.1, set to zero
91            &mut out_ptr,
92            std::ptr::null_mut(), // out_num_configs: optional, we don't support it currently
93        );
94
95        if ret < 0 {
96            return Err(Error::from(ret));
97        }
98
99        match NonNull::new(out_ptr as *mut _) {
100            // non-nullptr -> Specific list of values is supported.
101            Some(ptr) => Ok(Supported::Specific(I::from_ptr(ptr))),
102            // nullptr -> Everything is supported
103            None => Ok(Supported::All),
104        }
105    }
106}
107
108macro_rules! impl_config_iter {
109    (
110        $fn_name:ident,
111        $codec_cfg:expr,
112        $iter:ident,
113        $ty:ty,
114        $av_ty:ty,
115        $terminator:expr
116    ) => {
117        impl_config_iter_fn!($fn_name, $iter, $codec_cfg);
118        impl_config_iter_struct!($iter, $av_ty);
119        impl_config_iter_traits!($iter, $ty, $av_ty, $terminator);
120    };
121}
122
123macro_rules! impl_config_iter_struct {
124    ($iter:ident, $av_ty:ty) => {
125        #[derive(Debug, Clone)]
126        pub struct $iter<'a> {
127            next: std::ptr::NonNull<$av_ty>,
128            _marker: std::marker::PhantomData<&'a $av_ty>,
129        }
130    };
131}
132
133macro_rules! impl_config_iter_fn {
134    ($fn_name:ident, $iter:ident, $codec_cfg:expr) => {
135        /// Low-level function interacting with the FFmpeg API via
136        /// `avcodec_get_supported_config()`. Consider using one of the convenience methods
137        /// on the codecs or codec contexts instead.
138        #[cfg(feature = "ffmpeg_7_1")]
139        pub fn $fn_name<T>(
140            codec: Codec<T>,
141            ctx: Option<&Context>,
142        ) -> Result<Supported<$iter<'_>>, Error> {
143            supported(codec, ctx, $codec_cfg)
144        }
145    };
146}
147
148macro_rules! impl_config_iter_traits {
149    ($iter:ident, $ty:ty, $av_ty:ty, $terminator:expr) => {
150        impl<'a> TerminatedPtrIter<$av_ty, $ty> for $iter<'a> {
151            unsafe fn from_ptr(ptr: std::ptr::NonNull<$av_ty>) -> Self {
152                Self {
153                    next: ptr,
154                    _marker: std::marker::PhantomData,
155                }
156            }
157        }
158
159        // We make sure that this is true by not incrementing self.ptr after the
160        // terminator has been reached.
161        impl<'a> std::iter::FusedIterator for $iter<'a> {}
162
163        // TODO: Maybe add ExactSizeIterator? This would require using the out_num_configs
164        //       parameter and storing it inside $iter. Not sure it's too important unless
165        //       many people want to use .collect() or something else that benefits from
166        //       ExactSizeIterator.
167
168        impl<'a> Iterator for $iter<'a> {
169            type Item = $ty;
170
171            fn next(&mut self) -> Option<Self::Item> {
172                // SAFETY: The FFmpeg API guarantees that the pointer is safe to deref and
173                //         increment until the terminator is reached.
174                unsafe {
175                    let curr = self.next.as_ptr();
176                    if *curr == $terminator {
177                        return None;
178                    }
179
180                    // TODO: Replace with the following if MSRV >= 1.80:
181                    // self.next = NonNull::from(self.next).add(1).as_ref();
182                    self.next = std::ptr::NonNull::new_unchecked(curr.add(1));
183
184                    Some((*curr).into())
185                }
186            }
187        }
188    };
189}
190
191impl_config_iter!(
192    supported_pixel_formats,
193    crate::ffi::AVCodecConfig::AV_CODEC_CONFIG_PIX_FORMAT,
194    PixelFormatIter,
195    crate::format::Pixel,
196    crate::ffi::AVPixelFormat,
197    crate::ffi::AVPixelFormat::AV_PIX_FMT_NONE
198);
199
200impl_config_iter!(
201    supported_frame_rates,
202    crate::ffi::AVCodecConfig::AV_CODEC_CONFIG_FRAME_RATE,
203    FrameRateIter,
204    crate::Rational,
205    crate::ffi::AVRational,
206    crate::ffi::AVRational { num: 0, den: 0 }
207);
208
209impl_config_iter!(
210    supported_sample_rates,
211    crate::ffi::AVCodecConfig::AV_CODEC_CONFIG_SAMPLE_RATE,
212    SampleRateIter,
213    libc::c_int,
214    libc::c_int,
215    0 as libc::c_int
216);
217
218impl_config_iter!(
219    supported_sample_formats,
220    crate::ffi::AVCodecConfig::AV_CODEC_CONFIG_SAMPLE_FORMAT,
221    SampleFormatIter,
222    crate::format::Sample,
223    crate::ffi::AVSampleFormat,
224    crate::ffi::AVSampleFormat::AV_SAMPLE_FMT_NONE
225);
226
227#[cfg(feature = "ffmpeg_7_1")]
228impl_config_iter!(
229    supported_color_ranges,
230    crate::ffi::AVCodecConfig::AV_CODEC_CONFIG_COLOR_RANGE,
231    ColorRangeIter,
232    crate::color::Range,
233    crate::ffi::AVColorRange,
234    crate::ffi::AVColorRange::AVCOL_RANGE_UNSPECIFIED
235);
236
237#[cfg(feature = "ffmpeg_7_1")]
238impl_config_iter!(
239    supported_color_spaces,
240    crate::ffi::AVCodecConfig::AV_CODEC_CONFIG_COLOR_SPACE,
241    ColorSpaceIter,
242    crate::color::Space,
243    crate::ffi::AVColorSpace,
244    crate::ffi::AVColorSpace::AVCOL_SPC_UNSPECIFIED
245);
246
247#[cfg(test)]
248#[cfg(feature = "ffmpeg_7_1")]
249mod test {
250    use super::*;
251
252    use crate::codec::{decoder, encoder, Compliance, Id};
253    use crate::color::Range;
254    use crate::format::Pixel;
255    use crate::Rational;
256
257    // These tests can fail if the FFmpeg build does not contain the required de/encoder.
258    // TODO: Check if tests can be hidden behind feature flags.
259
260    #[test]
261    fn audio_decoder() {
262        let codec = decoder::find(Id::MP3).expect("can find mp3 decoder");
263
264        // Audio decoder does not have color ranges
265        assert!(supported_color_ranges(codec, None).is_err());
266
267        let format_iter = match supported_sample_formats(codec, None) {
268            Ok(Supported::Specific(f)) => f,
269            sup => panic!("Should be Supported::Specific, got {sup:#?}"),
270        };
271
272        for format in format_iter {
273            println!("format: {format:#?}");
274        }
275    }
276
277    #[test]
278    fn audio_encoder() {
279        let codec = encoder::find(Id::OPUS).expect("can find opus encoder");
280
281        // looks like every codec returns Supported::All for color space.
282        // might change in a future FFmpeg release
283        assert!(matches!(
284            supported_color_spaces(codec, None),
285            Ok(Supported::All)
286        ));
287        let format_iter = match supported_sample_formats(codec, None) {
288            Ok(Supported::Specific(f)) => f,
289            sup => panic!("Should be Supported::Specific, got {sup:#?}"),
290        };
291
292        for format in format_iter {
293            println!("format: {format:#?}");
294        }
295    }
296
297    #[test]
298    fn video_decoder() {
299        let codec = decoder::find(Id::H264).expect("can find H264 decoder");
300
301        assert!(supported_sample_rates(codec, None).is_err());
302        assert!(matches!(
303            supported_color_spaces(codec, None),
304            Ok(Supported::All)
305        ));
306    }
307
308    #[test]
309    fn video_encoder() {
310        let codec = encoder::find(Id::VP9).expect("can find VP9 encoder");
311
312        let color_ranges = match supported_color_ranges(codec, None) {
313            Ok(Supported::Specific(c)) => c,
314            sup => panic!("Should be Supported::Specific, got {sup:#?}"),
315        };
316
317        for range in color_ranges {
318            println!("{range:#?}");
319        }
320
321        assert!(matches!(
322            supported_pixel_formats(codec, None),
323            Ok(Supported::Specific(_))
324        ));
325
326        assert!(matches!(
327            supported_frame_rates(codec, None),
328            Ok(Supported::All)
329        ));
330    }
331
332    #[test]
333    fn supports() {
334        let codec = encoder::find(Id::FFV1).expect("can find FFV1 encoder");
335
336        assert!(supported_color_ranges(codec, None)
337            .expect("can check color range support")
338            .supports(Range::MPEG));
339
340        assert!(!supported_pixel_formats(codec, None)
341            .expect("can check color range support")
342            .supports(Pixel::GRAY16));
343
344        assert!(supported_frame_rates(codec, None)
345            .expect("can check frame rate support")
346            .supports(Rational(123, 456)));
347
348        supported_sample_formats(codec, None)
349            .expect_err("can NOT check sample format support (video codec)");
350    }
351
352    #[test]
353    fn with_context() {
354        let codec = encoder::find(Id::MJPEG).expect("can find MJPEG encoder");
355
356        let mut ctx = unsafe {
357            let avctx = crate::ffi::avcodec_alloc_context3(codec.as_ptr());
358            crate::codec::Context::wrap(avctx, None)
359        };
360
361        ctx.compliance(Compliance::Strict);
362
363        assert!(!supported_color_ranges(ctx.codec().unwrap(), Some(&ctx))
364            .expect("can check color range support")
365            .supports(Range::MPEG));
366
367        ctx.compliance(Compliance::Unofficial);
368
369        // Note that we check for NOT supported above, and YES supported here
370        // MJPEG encoder only supports MPEG color range if compliance is
371        // Unofficial or lower (less strict)
372        assert!(supported_color_ranges(ctx.codec().unwrap(), Some(&ctx))
373            .expect("can check color range support")
374            .supports(Range::MPEG));
375    }
376}