Skip to main content

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                    self.next = self.next.add(1);
181                    Some((*curr).into())
182                }
183            }
184        }
185    };
186}
187
188impl_config_iter!(
189    supported_pixel_formats,
190    crate::ffi::AVCodecConfig::PIX_FORMAT,
191    PixelFormatIter,
192    crate::format::Pixel,
193    crate::ffi::AVPixelFormat,
194    crate::ffi::AVPixelFormat::NONE
195);
196
197impl_config_iter!(
198    supported_frame_rates,
199    crate::ffi::AVCodecConfig::FRAME_RATE,
200    FrameRateIter,
201    crate::Rational,
202    crate::ffi::AVRational,
203    crate::ffi::AVRational { num: 0, den: 0 }
204);
205
206impl_config_iter!(
207    supported_sample_rates,
208    crate::ffi::AVCodecConfig::SAMPLE_RATE,
209    SampleRateIter,
210    libc::c_int,
211    libc::c_int,
212    0 as libc::c_int
213);
214
215impl_config_iter!(
216    supported_sample_formats,
217    crate::ffi::AVCodecConfig::SAMPLE_FORMAT,
218    SampleFormatIter,
219    crate::format::Sample,
220    crate::ffi::AVSampleFormat,
221    crate::ffi::AVSampleFormat::NONE
222);
223
224#[cfg(feature = "ffmpeg_7_1")]
225impl_config_iter!(
226    supported_color_ranges,
227    crate::ffi::AVCodecConfig::COLOR_RANGE,
228    ColorRangeIter,
229    crate::color::Range,
230    crate::ffi::AVColorRange,
231    crate::ffi::AVColorRange::UNSPECIFIED
232);
233
234#[cfg(feature = "ffmpeg_7_1")]
235impl_config_iter!(
236    supported_color_spaces,
237    crate::ffi::AVCodecConfig::COLOR_SPACE,
238    ColorSpaceIter,
239    crate::color::Space,
240    crate::ffi::AVColorSpace,
241    crate::ffi::AVColorSpace::UNSPECIFIED
242);
243
244#[cfg(feature = "ffmpeg_8_1")]
245impl_config_iter!(
246    supported_alpha_modes,
247    crate::ffi::AVCodecConfig::ALPHA_MODE,
248    AlphaModeIter,
249    crate::format::AlphaMode,
250    crate::ffi::AVAlphaMode,
251    crate::ffi::AVAlphaMode::UNSPECIFIED
252);
253
254#[cfg(test)]
255#[cfg(feature = "ffmpeg_7_1")]
256mod test {
257    use super::*;
258
259    use crate::codec::{decoder, encoder, Compliance, Id};
260    use crate::color::Range;
261    use crate::format::Pixel;
262    use crate::Rational;
263
264    // These tests can fail if the FFmpeg build does not contain the required de/encoder.
265    // TODO: Check if tests can be hidden behind feature flags.
266
267    #[test]
268    fn audio_decoder() {
269        let codec = decoder::find(Id::MP3).expect("can find mp3 decoder");
270
271        // Audio decoder does not have color ranges
272        assert!(supported_color_ranges(codec, None).is_err());
273
274        let format_iter = match supported_sample_formats(codec, None) {
275            Ok(Supported::Specific(f)) => f,
276            sup => panic!("Should be Supported::Specific, got {sup:#?}"),
277        };
278
279        for format in format_iter {
280            println!("format: {format:#?}");
281        }
282    }
283
284    #[test]
285    fn audio_encoder() {
286        let codec = encoder::find(Id::OPUS).expect("can find opus encoder");
287
288        // looks like every codec returns Supported::All for color space.
289        // might change in a future FFmpeg release
290        assert!(matches!(
291            supported_color_spaces(codec, None),
292            Ok(Supported::All)
293        ));
294        let format_iter = match supported_sample_formats(codec, None) {
295            Ok(Supported::Specific(f)) => f,
296            sup => panic!("Should be Supported::Specific, got {sup:#?}"),
297        };
298
299        for format in format_iter {
300            println!("format: {format:#?}");
301        }
302    }
303
304    #[test]
305    fn video_decoder() {
306        let codec = decoder::find(Id::H264).expect("can find H264 decoder");
307
308        assert!(supported_sample_rates(codec, None).is_err());
309        assert!(matches!(
310            supported_color_spaces(codec, None),
311            Ok(Supported::All)
312        ));
313    }
314
315    #[test]
316    fn video_encoder() {
317        let codec = encoder::find(Id::VP9).expect("can find VP9 encoder");
318
319        let color_ranges = match supported_color_ranges(codec, None) {
320            Ok(Supported::Specific(c)) => c,
321            sup => panic!("Should be Supported::Specific, got {sup:#?}"),
322        };
323
324        for range in color_ranges {
325            println!("{range:#?}");
326        }
327
328        assert!(matches!(
329            supported_pixel_formats(codec, None),
330            Ok(Supported::Specific(_))
331        ));
332
333        assert!(matches!(
334            supported_frame_rates(codec, None),
335            Ok(Supported::All)
336        ));
337    }
338
339    #[cfg(feature = "ffmpeg_8_1")]
340    #[test]
341    fn alpha_modes() {
342        let codec = encoder::find(Id::PNG).expect("can find PNG encoder");
343
344        let alpha_modes = match supported_alpha_modes(codec, None) {
345            Ok(Supported::Specific(c)) => c,
346            sup => panic!("Should be Supported::Specific, got {sup:#?}"),
347        };
348
349        for mode in alpha_modes {
350            println!("{mode:?}");
351        }
352    }
353
354    #[test]
355    fn supports() {
356        let codec = encoder::find(Id::FFV1).expect("can find FFV1 encoder");
357
358        assert!(supported_color_ranges(codec, None)
359            .expect("can check color range support")
360            .supports(Range::MPEG));
361
362        assert!(!supported_pixel_formats(codec, None)
363            .expect("can check color range support")
364            .supports(Pixel::GRAY16));
365
366        assert!(supported_frame_rates(codec, None)
367            .expect("can check frame rate support")
368            .supports(Rational(123, 456)));
369
370        supported_sample_formats(codec, None)
371            .expect_err("can NOT check sample format support (video codec)");
372    }
373
374    #[test]
375    fn with_context() {
376        let codec = encoder::find(Id::MJPEG).expect("can find MJPEG encoder");
377
378        let mut ctx = unsafe {
379            let avctx = crate::ffi::avcodec_alloc_context3(codec.as_ptr());
380            crate::codec::Context::wrap(avctx, None)
381        };
382
383        ctx.compliance(Compliance::Strict);
384
385        assert!(!supported_color_ranges(ctx.codec().unwrap(), Some(&ctx))
386            .expect("can check color range support")
387            .supports(Range::MPEG));
388
389        ctx.compliance(Compliance::Unofficial);
390
391        // Note that we check for NOT supported above, and YES supported here
392        // MJPEG encoder only supports MPEG color range if compliance is
393        // Unofficial or lower (less strict)
394        assert!(supported_color_ranges(ctx.codec().unwrap(), Some(&ctx))
395            .expect("can check color range support")
396            .supports(Range::MPEG));
397    }
398}