Skip to main content

ffmpeg_the_third/util/channel_layout/
layout.rs

1use std::borrow::Borrow;
2use std::borrow::Cow;
3use std::ffi::CString;
4
5use crate::ffi::*;
6#[cfg(feature = "ffmpeg_7_0")]
7use crate::Error;
8use libc::{c_int, c_uint};
9
10use super::Channel;
11use super::ChannelCustom;
12use super::ChannelLayoutIter;
13use super::ChannelLayoutMask;
14use super::ChannelOrder;
15
16#[derive(Debug, Clone, PartialEq)]
17pub struct ChannelLayout<'a>(Cow<'a, AVChannelLayout>);
18
19impl<'a> ChannelLayout<'a> {
20    /// Get a new channel layout with an unspecified channel ordering.
21    pub fn unspecified(channels: u32) -> Self {
22        let mut layout = AVChannelLayout::empty();
23        layout.order = AVChannelOrder::UNSPEC;
24        layout.nb_channels = channels as c_int;
25
26        Self(Cow::Owned(layout))
27    }
28
29    pub fn custom(channels: Vec<ChannelCustom>) -> Self {
30        #[cold]
31        fn alloc_failed(channels: usize) -> ! {
32            use std::alloc::{handle_alloc_error, Layout};
33
34            let alloc_size = channels * size_of::<AVChannelCustom>();
35            let layout =
36                Layout::from_size_align(alloc_size, align_of::<AVChannelCustom>()).unwrap();
37            handle_alloc_error(layout)
38        }
39
40        let mut layout = AVChannelLayout::empty();
41        layout.order = AVChannelOrder::CUSTOM;
42        layout.nb_channels = channels.len() as c_int;
43        unsafe {
44            layout.u.map = av_malloc_array(channels.len(), size_of::<AVChannelCustom>()) as _;
45            if layout.u.map.is_null() {
46                alloc_failed(channels.len());
47            }
48
49            for (i, ch) in channels.into_iter().enumerate() {
50                std::ptr::write(layout.u.map.add(i), AVChannelCustom::from(ch));
51            }
52        }
53
54        Self(Cow::Owned(layout))
55    }
56
57    /// Get the default channel layout for a given number of channels.
58    ///
59    /// If no default layout exists for the given number of channels,
60    /// an unspecified layout will be returned.
61    pub fn default_for_channels(channels: u32) -> Self {
62        let mut layout = AVChannelLayout::empty();
63
64        unsafe {
65            av_channel_layout_default(&mut layout as _, channels as c_int);
66        }
67
68        Self(Cow::Owned(layout))
69    }
70
71    /// Get an iterator over all standard channel layouts.
72    pub fn standard_layouts() -> ChannelLayoutIter {
73        ChannelLayoutIter::new()
74    }
75
76    /// Initialize a native channel layout from a bitmask indicating which
77    /// channels are present.
78    ///
79    /// This will return [None] for invalid bitmask values.
80    pub fn from_mask(layout_mask: ChannelLayoutMask) -> Option<Self> {
81        let mut layout = AVChannelLayout::empty();
82        let ret = unsafe { av_channel_layout_from_mask(&mut layout as _, layout_mask.bits()) };
83
84        match ret {
85            0 => Some(Self(Cow::Owned(layout))),
86            // This should only ever return 0 or AVERROR(EINVAL)
87            _ => None,
88        }
89    }
90
91    /// Initialize a channel layout from a given string description.
92    ///
93    /// This can be
94    /// - the formal channel layout name (as returned by [`description`][ChannelLayout::description]),
95    /// - one or more channel names concatenated with "+", each optionally containing a
96    ///   custom name after an "@", e.g. "FL@Left+FR@Right+LFE",
97    /// - a decimal or hexadecimal value of a native channel layout (e.g. "4" or "0x4"),
98    /// - the number of channels with the default layout (e.g. "4c"),
99    /// - the number of unordered channels (e.g. "4C" or "4 channels") or
100    /// - the ambisonic order followed by optional non-diegetic channels (e.g. "ambisonic 2+stereo")
101    pub fn from_string<S: AsRef<str>>(description: S) -> Option<Self> {
102        let mut layout = AVChannelLayout::empty();
103        let cstr = CString::new(description.as_ref()).expect("no nul byte in description");
104        let ret = unsafe { av_channel_layout_from_string(&mut layout as _, cstr.as_ptr()) };
105
106        match ret {
107            0 => Some(Self(Cow::Owned(layout))),
108            // This should only ever return 0 or AVERROR_INVALIDDATA
109            _ => None,
110        }
111    }
112
113    /// The [`ChannelOrder`][super::ChannelOrder] used in this layout.
114    pub fn order(&self) -> ChannelOrder {
115        ChannelOrder::from(self.0.order)
116    }
117
118    /// The number of channels in this layout.
119    pub fn channels(&self) -> u32 {
120        self.0.nb_channels as u32
121    }
122
123    /// If [`order`][ChannelLayout::order] is [`Native`][ChannelOrder::Native]:
124    /// A [`ChannelLayoutMask`] containing the channels of this layout.
125    ///
126    /// If [`order`][ChannelLayout::order] is [`Ambisonic`][ChannelOrder::Ambisonic]:
127    /// A [`ChannelLayoutMask`] containing the non-diegetic channels of this layout.
128    ///
129    /// Otherwise: [`None`].
130    pub fn mask(&self) -> Option<ChannelLayoutMask> {
131        match self.order() {
132            ChannelOrder::Unspecified | ChannelOrder::Custom => None,
133            ChannelOrder::Native | ChannelOrder::Ambisonic => unsafe {
134                Some(ChannelLayoutMask::from_bits_truncate(self.0.u.mask))
135            },
136        }
137    }
138
139    /// Returns the custom channel map for this layout.
140    ///
141    /// None if [`order`][ChannelLayout::order] is not [`Custom`][ChannelOrder::Custom].
142    pub fn map(&self) -> Option<&[ChannelCustom]> {
143        if self.order() != ChannelOrder::Custom {
144            return None;
145        }
146
147        unsafe {
148            // SAFETY: ChannelCustom is repr(transparent) around AVChannelCustom
149            Some(std::slice::from_raw_parts(
150                self.0.u.map as _,
151                self.0.nb_channels as usize,
152            ))
153        }
154    }
155
156    /// Extracts the owned `AVChannelLayout`.
157    ///
158    /// Clones it if not already owned.
159    pub fn into_owned(self) -> AVChannelLayout {
160        self.0.into_owned()
161    }
162
163    /// Exposes a pointer to the contained `AVChannelLayout` for FFI purposes.
164    ///
165    /// This is guaranteed to be a non-null pointer.
166    pub fn as_ptr(&self) -> *const AVChannelLayout {
167        self.0.as_ref() as _
168    }
169
170    /// Get a human-readable [`String`] describing the channel layout properties.
171    ///
172    /// The returned string will be in the same format that is accepted by [`from_string`][ChannelLayout::from_string],
173    /// allowing to rebuild the same channel layout (excluding opaque pointers).
174    pub fn description(&self) -> String {
175        let mut buf = vec![0u8; 256];
176
177        unsafe {
178            let ret_val =
179                av_channel_layout_describe(self.as_ptr(), buf.as_mut_ptr() as _, buf.len());
180
181            match usize::try_from(ret_val) {
182                Ok(out_len) if out_len > 0 => {
183                    #[cfg(feature = "ffmpeg_6_1")]
184                    // 6.1 changed out_len to include the NUL byte, which we don't want
185                    let out_len = out_len - 1;
186
187                    buf.truncate(out_len);
188                    String::from_utf8_unchecked(buf)
189                }
190                // `av_channel_layout_describe` returned an error, or 0 bytes written.
191                _ => String::new(),
192            }
193        }
194    }
195
196    /// Get the channel with the given index in a channel layout.
197    ///
198    /// Returns [`Channel::None`] when the index is invalid or the channel order is unspecified.
199    pub fn channel_from_index(&self, idx: u32) -> Channel {
200        Channel::from(unsafe { av_channel_layout_channel_from_index(self.as_ptr(), idx as c_uint) })
201    }
202
203    /// Get the index of a given channel in a channel layout.
204    pub fn index_from_channel(&self, channel: Channel) -> Option<u32> {
205        unsafe {
206            u32::try_from(av_channel_layout_index_from_channel(
207                self.as_ptr(),
208                AVChannel::from(channel),
209            ))
210            .ok()
211        }
212    }
213
214    /// Get the index in a channel layout of a channel described by the given string.
215    ///
216    /// Returns the first match. Accepts channel names in the same format as [`from_string`][ChannelLayout::from_string].
217    pub fn index_from_string<S: AsRef<str>>(&self, name: S) -> Option<u32> {
218        let cstr = CString::new(name.as_ref()).expect("no nul byte in name");
219        let ret = unsafe { av_channel_layout_index_from_string(self.as_ptr(), cstr.as_ptr()) };
220
221        u32::try_from(ret).ok()
222    }
223
224    /// Get a channel described by the given string.
225    ///
226    /// Accepts channel names in the same format as [`from_string`][ChannelLayout::from_string].
227    ///
228    /// Returns [`Channel::None`] when the string is invalid or the channel order is unspecified.
229    pub fn channel_from_string<S: AsRef<str>>(&self, name: S) -> Channel {
230        let cstr = CString::new(name.as_ref()).expect("no nul byte in name");
231
232        Channel::from(unsafe {
233            av_channel_layout_channel_from_string(self.as_ptr(), cstr.as_ptr())
234        })
235    }
236
237    /// Find out what channels from a given set are present in this layout, without regard for their positions.
238    pub fn subset(&self, mask: ChannelLayoutMask) -> ChannelLayoutMask {
239        ChannelLayoutMask::from_bits_truncate(unsafe {
240            av_channel_layout_subset(self.as_ptr(), mask.bits())
241        })
242    }
243
244    /// Check whether this layout is valid (i.e. can describe audio data).
245    #[doc(alias = "check")]
246    pub fn is_valid(&self) -> bool {
247        unsafe { av_channel_layout_check(self.as_ptr()) != 0 }
248    }
249
250    /// Change the [`ChannelOrder`] of this channel layout. If the current layout is borrowed,
251    /// calling this function will clone the contained [`AVChannelLayout`].
252    ///
253    /// This change can be lossless or lossy:
254    /// - A lossless conversion keeps all [`Channel`] designations and names intact.
255    /// - A lossy conversion might lose [`Channel`] designations and names depending on the targeted
256    ///   channel order.
257    ///
258    /// # Supported conversions
259    /// - Any -> Custom: Always possible, always lossless.
260    /// - Any -> Unspecified: Always possible, only lossless if every channel is designated
261    ///   [`Unknown`][Channel#variant.Unknown] and no channel names are used.
262    /// - Custom -> Ambisonic: Possible if it contains ambisonic channels with optional non-diegetic
263    ///   channels in the end. Lossless only if no channels have custom names.
264    /// - Custom -> Native: Possible if it contains native channels in native order. Lossless only
265    ///   if no channels have custom names.
266    ///
267    /// # Returns
268    /// - [`Ok`] if the conversion succeeded. The contained [`ChannelRetypeKind`] indicates
269    ///   whether the conversion was lossless or not.
270    /// - [`Err`] if the conversion failed. The original layout is untouched in this case.
271    #[cfg(feature = "ffmpeg_7_0")]
272    pub fn retype(&mut self, target: ChannelRetypeTarget) -> Result<ChannelRetypeKind, Error> {
273        use std::cmp::Ordering;
274        use ChannelRetypeTarget as Target;
275
276        let (channel_order, flags) = match target {
277            Target::Lossy(order) => (order, 0),
278            Target::Lossless(order) => (order, AV_CHANNEL_LAYOUT_RETYPE_FLAG_LOSSLESS),
279            Target::Canonical => (
280                ChannelOrder::Unspecified,
281                AV_CHANNEL_LAYOUT_RETYPE_FLAG_CANONICAL,
282            ),
283        };
284
285        let ret = unsafe { av_channel_layout_retype(self.0.to_mut(), channel_order.into(), flags) };
286
287        match ret.cmp(&0) {
288            Ordering::Greater => Ok(ChannelRetypeKind::Lossy),
289            Ordering::Equal => Ok(ChannelRetypeKind::Lossless),
290            Ordering::Less => Err(Error::from(ret)),
291        }
292    }
293}
294
295/// Whether the retyping was lossless or not.
296#[cfg(feature = "ffmpeg_7_0")]
297#[derive(Debug, Clone, Copy, PartialEq, Eq)]
298pub enum ChannelRetypeKind {
299    Lossless,
300    Lossy,
301}
302
303/// The possible targets for retyping channel layouts. See [`ChannelLayout::retype`]
304/// for more information.
305#[cfg(feature = "ffmpeg_7_0")]
306#[non_exhaustive]
307#[derive(Debug, Clone, Copy, PartialEq, Eq)]
308pub enum ChannelRetypeTarget {
309    /// Target a specific channel order, allowing lossy retyping.
310    Lossy(ChannelOrder),
311    /// Target a specific channel order, only allowing lossless retyping.
312    Lossless(ChannelOrder),
313    /// Automatically select the simplest channel order which allows lossless retyping.
314    Canonical,
315}
316
317impl<'a> From<AVChannelLayout> for ChannelLayout<'a> {
318    fn from(value: AVChannelLayout) -> Self {
319        Self(Cow::Owned(value))
320    }
321}
322
323impl<'a> From<&'a AVChannelLayout> for ChannelLayout<'a> {
324    fn from(value: &'a AVChannelLayout) -> Self {
325        Self(Cow::Borrowed(value))
326    }
327}
328
329impl<'a> Borrow<AVChannelLayout> for ChannelLayout<'a> {
330    fn borrow(&self) -> &AVChannelLayout {
331        &self.0
332    }
333}
334
335// Type alias to reduce line length below
336type Scl = ChannelLayout<'static>;
337
338// Constants
339impl<'a> ChannelLayout<'a> {
340    pub const MONO: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_MONO));
341    pub const STEREO: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_STEREO));
342    pub const _2POINT1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_2POINT1));
343    pub const _2_1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_2_1));
344    pub const SURROUND: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_SURROUND));
345    pub const _3POINT1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_3POINT1));
346    pub const _4POINT0: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_4POINT0));
347    pub const _4POINT1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_4POINT1));
348    pub const _2_2: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_2_2));
349    pub const QUAD: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_QUAD));
350    pub const _5POINT0: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_5POINT0));
351    pub const _5POINT1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_5POINT1));
352    pub const _5POINT0_BACK: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_5POINT0_BACK));
353    pub const _5POINT1_BACK: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_5POINT1_BACK));
354    pub const _6POINT0: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_6POINT0));
355    pub const _6POINT0_FRONT: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_6POINT0_FRONT));
356    pub const _3POINT1POINT2: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_3POINT1POINT2));
357    pub const HEXAGONAL: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_HEXAGONAL));
358    pub const _6POINT1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_6POINT1));
359    pub const _6POINT1_BACK: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_6POINT1_BACK));
360    pub const _6POINT1_FRONT: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_6POINT1_FRONT));
361    pub const _7POINT0: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT0));
362    pub const _7POINT0_FRONT: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT0_FRONT));
363    pub const _7POINT1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT1));
364    pub const _7POINT1_WIDE: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT1_WIDE));
365    pub const _7POINT1_WIDE_BACK: Scl =
366        ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT1_WIDE_BACK));
367    pub const _5POINT1POINT2_BACK: Scl =
368        ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_5POINT1POINT2_BACK));
369    pub const OCTAGONAL: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_OCTAGONAL));
370    pub const CUBE: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_CUBE));
371    pub const _5POINT1POINT4_BACK: Scl =
372        ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_5POINT1POINT4_BACK));
373    pub const _7POINT1POINT2: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT1POINT2));
374    pub const _7POINT1POINT4_BACK: Scl =
375        ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT1POINT4_BACK));
376    #[cfg(feature = "ffmpeg_7_0")]
377    pub const _7POINT2POINT3: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT2POINT3));
378    #[cfg(feature = "ffmpeg_7_0")]
379    pub const _9POINT1POINT4_BACK: Scl =
380        ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_9POINT1POINT4_BACK));
381    pub const HEXADECAGONAL: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_HEXADECAGONAL));
382    pub const STEREO_DOWNMIX: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_STEREO_DOWNMIX));
383    pub const _22POINT2: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_22POINT2));
384}
385
386#[cfg(test)]
387mod test {
388    use super::*;
389
390    #[test]
391    fn unspecified() {
392        let empty = ChannelLayout::unspecified(0);
393        assert_eq!(empty.order(), ChannelOrder::Unspecified);
394        assert_eq!(empty.channels(), 0);
395        assert!(!empty.is_valid());
396
397        let unspec = ChannelLayout::unspecified(42);
398        assert_eq!(unspec.order(), ChannelOrder::Unspecified);
399        assert_eq!(unspec.channels(), 42);
400        assert!(unspec.is_valid());
401    }
402
403    #[test]
404    fn custom() {
405        let channels = vec![
406            ChannelCustom::new(Channel::FrontLeft),
407            ChannelCustom::new(Channel::FrontRight),
408            ChannelCustom::named(Channel::LowFrequency, "bass"),
409            ChannelCustom::named(Channel::BackLeft, "back left"),
410            // name too long on purpose -> should truncate to 15 chars + NUL
411            ChannelCustom::named(Channel::BottomFrontCenter, "bottom front center"),
412        ];
413
414        let custom = ChannelLayout::custom(channels.clone());
415        assert!(custom.is_valid());
416        assert_eq!(custom.channels(), 5);
417        assert_eq!(custom.order(), ChannelOrder::Custom);
418        assert_eq!(custom.map().unwrap(), &channels);
419    }
420
421    #[test]
422    fn defaults() {
423        let unspec = ChannelLayout::default_for_channels(0);
424        assert!(unspec.order() == ChannelOrder::Unspecified);
425        assert!(!unspec.is_valid());
426
427        for i in 1..12 {
428            let layout = ChannelLayout::default_for_channels(i);
429            assert_eq!(layout.channels(), i);
430            assert!(layout.is_valid(), "default layout invalid for {i} channels");
431            assert!(!layout.description().is_empty());
432        }
433    }
434
435    #[test]
436    fn from_mask() {
437        use ChannelLayout as Layout;
438        use ChannelLayoutMask as Mask;
439
440        assert_eq!(Layout::from_mask(Mask::empty()), None);
441
442        let tests = [
443            (Mask::MONO, Layout::MONO),
444            (Mask::STEREO, Layout::STEREO),
445            (Mask::_2POINT1, Layout::_2POINT1),
446            (Mask::_2_1, Layout::_2_1),
447            (Mask::SURROUND, Layout::SURROUND),
448            (Mask::_3POINT1, Layout::_3POINT1),
449            (Mask::_4POINT0, Layout::_4POINT0),
450            (Mask::_4POINT1, Layout::_4POINT1),
451            (Mask::_2_2, Layout::_2_2),
452            (Mask::QUAD, Layout::QUAD),
453            (Mask::_5POINT0, Layout::_5POINT0),
454            (Mask::_5POINT1, Layout::_5POINT1),
455            (Mask::_5POINT0_BACK, Layout::_5POINT0_BACK),
456            (Mask::_5POINT1_BACK, Layout::_5POINT1_BACK),
457            (Mask::_6POINT0, Layout::_6POINT0),
458            (Mask::_6POINT0_FRONT, Layout::_6POINT0_FRONT),
459            (Mask::HEXAGONAL, Layout::HEXAGONAL),
460            (Mask::_3POINT1POINT2, Layout::_3POINT1POINT2),
461            (Mask::_6POINT1, Layout::_6POINT1),
462            (Mask::_6POINT1_BACK, Layout::_6POINT1_BACK),
463            (Mask::_6POINT1_FRONT, Layout::_6POINT1_FRONT),
464            (Mask::_7POINT0, Layout::_7POINT0),
465            (Mask::_7POINT0_FRONT, Layout::_7POINT0_FRONT),
466            (Mask::_7POINT1, Layout::_7POINT1),
467            (Mask::_7POINT1_WIDE, Layout::_7POINT1_WIDE),
468            (Mask::_7POINT1_WIDE_BACK, Layout::_7POINT1_WIDE_BACK),
469            (Mask::_5POINT1POINT2_BACK, Layout::_5POINT1POINT2_BACK),
470            (Mask::OCTAGONAL, Layout::OCTAGONAL),
471            (Mask::CUBE, Layout::CUBE),
472            (Mask::_5POINT1POINT4_BACK, Layout::_5POINT1POINT4_BACK),
473            (Mask::_7POINT1POINT2, Layout::_7POINT1POINT2),
474            (Mask::_7POINT1POINT4_BACK, Layout::_7POINT1POINT4_BACK),
475            (Mask::HEXADECAGONAL, Layout::HEXADECAGONAL),
476            (Mask::STEREO_DOWNMIX, Layout::STEREO_DOWNMIX),
477            (Mask::_22POINT2, Layout::_22POINT2),
478        ];
479
480        for (mask, expected) in tests {
481            let result = Layout::from_mask(mask).expect("can find layout for bitmask");
482            assert_eq!(
483                result.order(),
484                ChannelOrder::Native,
485                "layout from mask must use native order"
486            );
487            assert_eq!(result.mask(), Some(mask));
488            assert_eq!(result, expected);
489        }
490    }
491
492    #[test]
493    fn from_string() {
494        let test_strings = [
495            ("1 channels (FRC)", ChannelOrder::Native, 1),
496            ("FL@Left+FR@Right+LFE", ChannelOrder::Custom, 3),
497            ("0x4", ChannelOrder::Native, 1),
498            ("4c", ChannelOrder::Native, 4),
499            ("7 channels", ChannelOrder::Unspecified, 7),
500            ("ambisonic 2+stereo", ChannelOrder::Ambisonic, 11),
501        ];
502
503        for (s, order, channels) in test_strings {
504            let result = ChannelLayout::from_string(s).expect("can find layout for description");
505            assert!(result.is_valid());
506            assert_eq!(result.order(), order);
507            assert_eq!(result.channels(), channels);
508        }
509    }
510
511    #[test]
512    fn describe() {
513        use ChannelLayout as Layout;
514        use ChannelLayoutMask as Mask;
515
516        let tests = [
517            (Layout::MONO, "mono"),
518            (Layout::STEREO, "stereo"),
519            (Layout::_5POINT1, "5.1(side)"),
520            (
521                Layout::from_string("FL@Left+FR@Right+LFE").unwrap(),
522                "3 channels (FL@Left+FR@Right+LFE)",
523            ),
524            (
525                Layout::from_mask(Mask::FRONT_RIGHT_OF_CENTER).unwrap(),
526                "1 channels (FRC)",
527            ),
528            #[cfg(feature = "ffmpeg_6_1")]
529            (Layout::_7POINT1POINT4_BACK, "7.1.4"),
530            #[cfg(not(feature = "ffmpeg_6_1"))]
531            (
532                Layout::_7POINT1POINT4_BACK,
533                "12 channels (FL+FR+FC+LFE+BL+BR+SL+SR+TFL+TFR+TBL+TBR)",
534            ),
535        ];
536
537        for (layout, expected) in tests {
538            assert!(layout.is_valid());
539
540            let desc = layout.description();
541            assert!(!desc.is_empty());
542            assert_eq!(desc, expected);
543        }
544    }
545
546    #[cfg(feature = "ffmpeg_7_0")]
547    #[test]
548    fn retype() {
549        use ChannelLayout as Layout;
550        use ChannelOrder as Order;
551        use ChannelRetypeKind as Kind;
552        use ChannelRetypeTarget as Target;
553
554        let tests = [
555            (
556                // Ok(Lossless) if target order == current order
557                Layout::_7POINT1POINT4_BACK,
558                Target::Lossless(Order::Native),
559                Ok(Kind::Lossless),
560                Layout::_7POINT1POINT4_BACK,
561                true,
562            ),
563            (
564                // any -> custom always lossless
565                Layout::STEREO,
566                Target::Lossless(Order::Custom),
567                Ok(Kind::Lossless),
568                Layout::custom(vec![
569                    ChannelCustom::new(Channel::FrontLeft),
570                    ChannelCustom::new(Channel::FrontRight),
571                ]),
572                true,
573            ),
574            (
575                // any -> custom also works if lossy requested
576                Layout::STEREO,
577                Target::Lossy(Order::Custom),
578                Ok(Kind::Lossless),
579                Layout::custom(vec![
580                    ChannelCustom::new(Channel::FrontLeft),
581                    ChannelCustom::new(Channel::FrontRight),
582                ]),
583                true,
584            ),
585            (
586                // any -> unspecified lossy unless all channels are Channel::Unknown and unnamed
587                Layout::OCTAGONAL,
588                Target::Lossy(Order::Unspecified),
589                Ok(Kind::Lossy),
590                Layout::unspecified(8),
591                true,
592            ),
593            (
594                // AVERROR(ENOSYS) if lossless requested, but only lossy possible
595                Layout::OCTAGONAL,
596                Target::Lossless(Order::Unspecified),
597                Err(Error::Other {
598                    errno: libc::ENOSYS,
599                }),
600                Layout::OCTAGONAL,
601                true,
602            ),
603            (
604                // custom -> native lossless without names
605                Layout::custom(vec![
606                    ChannelCustom::new(Channel::FrontLeft),
607                    ChannelCustom::new(Channel::FrontRight),
608                    ChannelCustom::new(Channel::FrontCenter),
609                    ChannelCustom::new(Channel::LowFrequency),
610                ]),
611                Target::Lossy(Order::Native),
612                Ok(Kind::Lossless),
613                Layout::_3POINT1,
614                true,
615            ),
616            (
617                // custom -> native lossy with name
618                Layout::custom(vec![
619                    ChannelCustom::new(Channel::FrontLeft),
620                    ChannelCustom::new(Channel::FrontRight),
621                    ChannelCustom::named(Channel::FrontCenter, "front center"),
622                    ChannelCustom::new(Channel::LowFrequency),
623                ]),
624                Target::Lossy(Order::Native),
625                Ok(Kind::Lossy),
626                Layout::_3POINT1,
627                true,
628            ),
629            (
630                // AVERROR(EINVAL) if !layout.is_valid()
631                Layout::unspecified(0),
632                Target::Lossy(ChannelOrder::Custom),
633                Err(Error::Other {
634                    errno: libc::EINVAL,
635                }),
636                Layout::unspecified(0),
637                false,
638            ),
639        ];
640
641        for (layout, target, expected_result, expected_layout, expected_valid) in tests {
642            let mut layout = layout.clone();
643            let actual_result = layout.retype(target);
644
645            assert_eq!(
646                layout.is_valid(),
647                expected_valid,
648                "is_valid should return {expected_valid} for {layout:?}, but did not."
649            );
650            assert_eq!(
651                actual_result,
652                expected_result,
653                "retype should return {expected_result:?} for {layout:?}, but returned {actual_result:?}"
654            );
655            assert_eq!(layout, expected_layout);
656        }
657    }
658}