ffmpeg_the_third/util/channel_layout/
layout.rs

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