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