diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14d3383..42db95f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,7 +109,11 @@ jobs: rust: ["nightly", "1.70.0"] valgrind: ["valgrind", "no valgrind"] include: + # Only run tests on nightly rust + latest FFmpeg + # This is caused by specific bug of FFmpeg 6.0 + # https://github.com/FFmpeg/FFmpeg/commit/c4f35ba8084f254afe1fb05202abfdcfff63b854 - rust: "nightly" + ffmpeg-version: "release/6.1" should_test: "true" exclude: # Only run valgrind with latest FFmpeg and nightly rust to reduce resource consumption. @@ -174,12 +178,12 @@ jobs: export FFMPEG_PKG_CONFIG_PATH=${PWD}/tmp/ffmpeg_build/lib/pkgconfig if [ '${{ matrix.should_test }}' == 'true' ]; then if [ '${{ matrix.valgrind }}' == 'valgrind' ]; then - cargo valgrind test ${{ matrix.cargo-features }} -vv + cargo valgrind test -vv else - cargo test ${{ matrix.cargo-features }} -vv + cargo test -vv fi else - cargo test ${{ matrix.cargo-features }} --no-run -vv + cargo test --no-run -vv fi # Cross build on Ubuntu, then send it to Windows machine for CI. diff --git a/Cargo.toml b/Cargo.toml index 7c4b80a..26a86d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ exclude = ["tests/*", "examples/*", "utils/*"] rust-version = "1.70.0" [dependencies] -rusty_ffmpeg = "0.13.2" +rusty_ffmpeg = { version = "0.13.3", features = ["ffmpeg6"] } libc = "0.2" paste = "1.0" thiserror = "1.0" diff --git a/src/avcodec/codec.rs b/src/avcodec/codec.rs index 6789bc4..ff459c3 100644 --- a/src/avcodec/codec.rs +++ b/src/avcodec/codec.rs @@ -8,7 +8,7 @@ use std::{ use crate::{ avcodec::{AVCodecID, AVCodecParameters, AVPacket}, - avutil::{AVDictionary, AVFrame, AVPixelFormat, AVRational}, + avutil::{AVChannelLayoutRef, AVDictionary, AVFrame, AVPixelFormat, AVRational}, error::{Result, RsmpegError}, ffi, shared::*, @@ -121,7 +121,7 @@ impl Drop for AVCodec { wrap_ref!(AVCodecContext: ffi::AVCodecContext); settable!(AVCodecContext { framerate: AVRational, - channel_layout: u64, + ch_layout: ffi::AVChannelLayout, height: i32, width: i32, sample_aspect_ratio: AVRational, @@ -319,6 +319,12 @@ impl AVCodecContext { parameters } + /// Get channel layout + pub fn ch_layout(&self) -> AVChannelLayoutRef { + let inner = NonNull::new(&self.ch_layout as *const _ as *mut _).unwrap(); + unsafe { AVChannelLayoutRef::from_raw(inner) } + } + /// Is hardware accelaration enabled in this codec context. pub fn is_hwaccel(&self) -> bool { // We doesn't expose the `AVHWAccel` because the documentation states: diff --git a/src/avcodec/codec_par.rs b/src/avcodec/codec_par.rs index d53751c..dbbba94 100644 --- a/src/avcodec/codec_par.rs +++ b/src/avcodec/codec_par.rs @@ -1,9 +1,15 @@ -use crate::{avcodec::AVCodecContext, avutil::AVMediaType, ffi, shared::*}; +use crate::{ + avcodec::AVCodecContext, + avutil::{AVChannelLayoutRef, AVMediaType}, + ffi, + shared::*, +}; use std::{ clone::Clone, default::Default, fmt, ops::{Deref, Drop}, + ptr::NonNull, }; wrap_ref_mut!(AVCodecParameters: ffi::AVCodecParameters); @@ -44,6 +50,12 @@ impl AVCodecParameters { pub fn codec_type(&self) -> AVMediaType { AVMediaType(self.codec_type) } + + /// Get channel layout + pub fn ch_layout(&self) -> AVChannelLayoutRef { + let inner = NonNull::new(&self.ch_layout as *const _ as *mut _).unwrap(); + unsafe { AVChannelLayoutRef::from_raw(inner) } + } } impl fmt::Debug for AVCodecParameters { diff --git a/src/avfilter/avfilter.rs b/src/avfilter/avfilter.rs index edd136e..a66cdff 100644 --- a/src/avfilter/avfilter.rs +++ b/src/avfilter/avfilter.rs @@ -34,7 +34,7 @@ wrap_mut!(AVFilterContext: ffi::AVFilterContext); impl AVFilterContext { /// Set property of a [`AVFilterContext`]. - pub fn set_property(&mut self, key: &CStr, value: &U) -> Result<()> { + pub fn opt_set_bin(&mut self, key: &CStr, value: &U) -> Result<()> { unsafe { ffi::av_opt_set_bin( self.as_mut_ptr().cast(), @@ -49,6 +49,21 @@ impl AVFilterContext { Ok(()) } + /// Set property of a [`AVFilterContext`]. + pub fn opt_set(&mut self, key: &CStr, value: &CStr) -> Result<()> { + unsafe { + ffi::av_opt_set( + self.as_mut_ptr().cast(), + key.as_ptr(), + value.as_ptr(), + ffi::AV_OPT_SEARCH_CHILDREN as i32, + ) + } + .upgrade() + .map_err(RsmpegError::SetPropertyError)?; + Ok(()) + } + /// Add a frame to the buffer source. pub fn buffersrc_add_frame( &mut self, diff --git a/src/avutil/channel_layout.rs b/src/avutil/channel_layout.rs index 694d8c3..d5a483d 100644 --- a/src/avutil/channel_layout.rs +++ b/src/avutil/channel_layout.rs @@ -1,10 +1,224 @@ -use crate::ffi; +use crate::{ + error::Result, + ffi, + shared::{PointerUpgrade, RetUpgrade}, +}; +use libc::c_void; +use std::{ + ffi::{CStr, CString}, + mem::MaybeUninit, + ptr::NonNull, +}; -pub fn av_get_channel_layout_nb_channels(channel_layout: u64) -> i32 { - unsafe { ffi::av_get_channel_layout_nb_channels(channel_layout) } +wrap_ref!(AVChannelLayout: ffi::AVChannelLayout); + +impl Drop for AVChannelLayout { + fn drop(&mut self) { + let layout = self.as_mut_ptr(); + unsafe { ffi::av_channel_layout_uninit(layout) }; + let _ = unsafe { Box::from_raw(layout) }; + } +} + +impl Clone for AVChannelLayout { + fn clone(&self) -> Self { + let mut layout = MaybeUninit::::uninit(); + // unwrap: this function only fail on OOM. + unsafe { ffi::av_channel_layout_copy(layout.as_mut_ptr(), self.as_ptr()) } + .upgrade() + .unwrap(); + let layout = unsafe { layout.assume_init() }; + unsafe { Self::from_raw(NonNull::new(Box::into_raw(Box::new(layout))).unwrap()) } + } } -pub fn av_get_default_channel_layout(nb_channels: i32) -> u64 { - // From i64 to u64, safe. - unsafe { ffi::av_get_default_channel_layout(nb_channels) as u64 } +impl AVChannelLayout { + /// Convert self into [`ffi::AVChannelLayout`]`. + /// + /// Be careful when using it. Since this fucntion leaks the raw type, + /// you have to manually do `ffi::av_channel_layout_uninit``. + pub fn into_inner(mut self) -> ffi::AVChannelLayout { + let layout = self.as_mut_ptr(); + let layout = *unsafe { Box::from_raw(layout) }; + std::mem::forget(self); + layout + } + + /// Initialize a native channel layout from a bitmask indicating which channels are present. + pub fn from_mask(mask: u64) -> Option { + let mut layout = MaybeUninit::::uninit(); + if unsafe { ffi::av_channel_layout_from_mask(layout.as_mut_ptr(), mask) } == 0 { + let layout = unsafe { layout.assume_init() }; + Some(unsafe { Self::from_raw(NonNull::new(Box::into_raw(Box::new(layout))).unwrap()) }) + } else { + None + } + } + + /// Initialize a channel layout from a given string description. + /// The input string can be represented by: + /// - the formal channel layout name (returned by av_channel_layout_describe()) + /// - single or multiple channel names (returned by av_channel_name(), eg. "FL", + /// or concatenated with "+", each optionally containing a custom name after + /// a "@", eg. "FL@Left+FR@Right+LFE") + /// - a decimal or hexadecimal value of a native channel layout (eg. "4" or "0x4") + /// - the number of channels with default layout (eg. "4c") + /// - the number of unordered channels (eg. "4C" or "4 channels") + /// - the ambisonic order followed by optional non-diegetic channels (eg. + /// "ambisonic 2+stereo") + pub fn from_string(str: &CStr) -> Option { + let mut layout = MaybeUninit::::uninit(); + if unsafe { ffi::av_channel_layout_from_string(layout.as_mut_ptr(), str.as_ptr()) } == 0 { + let layout = unsafe { layout.assume_init() }; + Some(unsafe { Self::from_raw(NonNull::new(Box::into_raw(Box::new(layout))).unwrap()) }) + } else { + None + } + } + + /// Get the default channel layout for a given number of channels. + pub fn from_nb_channels(nb_channels: i32) -> Self { + let mut layout = MaybeUninit::::uninit(); + unsafe { ffi::av_channel_layout_default(layout.as_mut_ptr(), nb_channels) } + let layout = unsafe { layout.assume_init() }; + unsafe { Self::from_raw(NonNull::new(Box::into_raw(Box::new(layout))).unwrap()) } + } + + /// Make a copy of a channel layout. This differs from just assigning src to dst + /// in that it allocates and copies the map for AV_CHANNEL_ORDER_CUSTOM. + pub fn copy(&mut self, src: &Self) { + // unwrap: this function only fail on OOM. + unsafe { ffi::av_channel_layout_copy(self.as_mut_ptr(), src.as_ptr()) } + .upgrade() + .unwrap(); + } + + /// Get a human-readable string describing the channel layout properties. + /// The string will be in the same format that is accepted by + /// [`AVChannelLayout::from_string`], allowing to rebuild the same + /// channel layout, except for opaque pointers. + pub fn describe(&self) -> Result { + const BUF_SIZE: usize = 32; + let mut buf = vec![0u8; BUF_SIZE]; + + // # Safety: `as usize` after upgrading, len is assumed to be positive. + let len = unsafe { + ffi::av_channel_layout_describe(self.as_ptr(), buf.as_mut_ptr() as *mut i8, BUF_SIZE) + } + .upgrade()? as usize; + + let len = if len > BUF_SIZE { + buf.resize(len, 0); + unsafe { + ffi::av_channel_layout_describe(self.as_ptr(), buf.as_mut_ptr() as *mut i8, len) + } + .upgrade()? as usize + } else { + len + }; + Ok(CString::new(&buf[..len - 1]).unwrap()) + } + + /// Get the channel with the given index in a channel layout. + /// + /// Return `None` if idx is not valid or the channel order is unspecified + pub fn channel_from_index(&self, idx: u32) -> Option { + let channel = unsafe { ffi::av_channel_layout_channel_from_index(self.as_ptr(), idx) }; + (channel != ffi::AVChannel_AV_CHAN_NONE).then_some(channel) + } + + /// Get the index of a given channel in a channel layout. In case multiple + /// channels are found, only the first match will be returned. + /// + /// Return `None` when channel is not present in channel_layout + pub fn index_from_channel(&self, channel: ffi::AVChannel) -> Option { + unsafe { ffi::av_channel_layout_index_from_channel(self.as_ptr(), channel) } + .upgrade() + .ok() + .map(|x| x as u32) + } + + /// Get the index in a channel layout of a channel described by the given string. + /// In case multiple channels are found, only the first match will be returned. + pub fn index_from_string(&self, name: &CStr) -> Option { + unsafe { ffi::av_channel_layout_index_from_string(self.as_ptr(), name.as_ptr()) } + .upgrade() + .ok() + .map(|x| x as u32) + } + + /// Get a channel described by the given string. + pub fn channel_from_string(&self, name: &CStr) -> Option { + let channel = + unsafe { ffi::av_channel_layout_channel_from_string(self.as_ptr(), name.as_ptr()) }; + (channel != ffi::AVChannel_AV_CHAN_NONE).then_some(channel) + } + + /// Find out what channels from a given set are present in a channel layout, + /// without regard for their positions. + pub fn subset(&self, mask: u64) -> u64 { + unsafe { ffi::av_channel_layout_subset(self.as_ptr(), mask) } + } + + /// Check whether a channel layout is valid, i.e. can possibly describe audio data. + /// + /// Return `true` if channel_layout is valid, `false` otherwise. + pub fn check(&self) -> bool { + let ret = unsafe { ffi::av_channel_layout_check(self.as_ptr()) }; + ret == 1 + } + + /// Check whether two channel layouts are semantically the same, i.e. the same + /// channels are present on the same positions in both. + /// + /// If one of the channel layouts is AV_CHANNEL_ORDER_UNSPEC, while the other is + /// not, they are considered to be unequal. If both are AV_CHANNEL_ORDER_UNSPEC, + /// they are considered equal iff the channel counts are the same in both. + pub fn equal(&self, other: &Self) -> Result { + let ret = + unsafe { ffi::av_channel_layout_compare(self.as_ptr(), other.as_ptr()) }.upgrade()?; + Ok(ret == 0) + } +} + +/// Iterate over all standard channel layouts. +pub struct AVChannelLayoutIter { + opaque: *mut c_void, +} + +impl Default for AVChannelLayoutIter { + fn default() -> Self { + Self { + opaque: std::ptr::null_mut(), + } + } +} + +impl Iterator for AVChannelLayoutIter { + type Item = AVChannelLayoutRef<'static>; + + fn next(&mut self) -> Option { + unsafe { ffi::av_channel_layout_standard(&mut self.opaque) } + .upgrade() + .map(|ptr| unsafe { AVChannelLayoutRef::from_raw(ptr) }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn channel_layout_iterator_test() { + let mut iter = AVChannelLayoutIter::default(); + let item = iter.next().unwrap(); + assert_eq!(item.describe().unwrap().to_str().unwrap(), "mono"); + let mut item = iter.next().unwrap(); + assert_eq!(item.describe().unwrap().to_str().unwrap(), "stereo"); + for x in iter { + item = x; + assert!(!item.describe().unwrap().to_str().unwrap().is_empty()) + } + assert_eq!(item.describe().unwrap().to_str().unwrap(), "22.2"); + } } diff --git a/src/avutil/frame.rs b/src/avutil/frame.rs index db6b9c0..65eef38 100644 --- a/src/avutil/frame.rs +++ b/src/avutil/frame.rs @@ -1,11 +1,11 @@ use crate::{ - avutil::{av_image_fill_arrays, AVImage, AVMotionVector, AVPixelFormat}, + avutil::{av_image_fill_arrays, AVChannelLayoutRef, AVImage, AVMotionVector, AVPixelFormat}, error::*, ffi, shared::*, }; -use std::{fmt, mem::size_of, ops::Drop, slice}; +use std::{fmt, mem::size_of, ops::Drop, ptr::NonNull, slice}; wrap!(AVFrame: ffi::AVFrame); settable!(AVFrame { @@ -15,7 +15,7 @@ settable!(AVFrame { pict_type: ffi::AVPictureType, nb_samples: i32, format: i32, - channel_layout: u64, + ch_layout: ffi::AVChannelLayout, sample_rate: i32, }); @@ -28,7 +28,7 @@ impl fmt::Debug for AVFrame { .field("pict_type", &self.pict_type) .field("nb_samples", &self.nb_samples) .field("format", &self.format) - .field("channel_layout", &self.channel_layout) + .field("ch_layout", &self.ch_layout().describe()) .field("sample_rate", &self.sample_rate) .finish() } @@ -81,6 +81,12 @@ impl AVFrame { unsafe { &mut self.deref_mut().linesize } } + /// Get channel layout + pub fn ch_layout(&self) -> AVChannelLayoutRef { + let inner = NonNull::new(&self.ch_layout as *const _ as *mut _).unwrap(); + unsafe { AVChannelLayoutRef::from_raw(inner) } + } + /// Setup the data pointers and linesizes based on the specified image /// parameters and the provided array. /// @@ -238,14 +244,15 @@ impl<'frame> AVFrameSideDataRef<'frame> { #[cfg(test)] mod test { use super::*; - use crate::{avcodec::AVCodec, avutil::av_get_default_channel_layout}; + use crate::avcodec::AVCodec; + use crate::avutil::AVChannelLayout; #[test] fn test_get_buffer() { let encoder = AVCodec::find_encoder(ffi::AVCodecID_AV_CODEC_ID_AAC).unwrap(); let mut frame = AVFrame::new(); frame.set_nb_samples(2); - frame.set_channel_layout(av_get_default_channel_layout(2)); + frame.set_ch_layout(AVChannelLayout::from_nb_channels(2).into_inner()); frame.set_format(encoder.sample_fmts().unwrap()[0]); assert!(frame.alloc_buffer().is_ok()); } @@ -264,7 +271,7 @@ mod test { let encoder = AVCodec::find_encoder(ffi::AVCodecID_AV_CODEC_ID_AAC).unwrap(); let mut frame = AVFrame::new(); frame.set_nb_samples(2); - frame.set_channel_layout(av_get_default_channel_layout(2)); + frame.set_ch_layout(AVChannelLayout::from_nb_channels(2).into_inner()); frame.set_format(encoder.sample_fmts().unwrap()[0]); frame.alloc_buffer().unwrap(); assert!(matches!( diff --git a/src/error.rs b/src/error.rs index 02740ce..71b43f0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -182,7 +182,7 @@ impl RsmpegError { } /// Overall result of Rsmpeg functions -pub type Result = std::result::Result; +pub type Result = std::result::Result; /// A wrapper around c_int(return type of many ffmpeg inner libraries functions) pub type Ret = std::result::Result; diff --git a/src/swresample/swresample.rs b/src/swresample/swresample.rs index 2a9cdeb..5f5e6c2 100644 --- a/src/swresample/swresample.rs +++ b/src/swresample/swresample.rs @@ -4,7 +4,10 @@ use crate::{ ffi, shared::*, }; -use std::{ops::Drop, ptr}; +use std::{ + ops::Drop, + ptr::{self, NonNull}, +}; wrap!(SwrContext: ffi::SwrContext); @@ -30,29 +33,30 @@ impl SwrContext { /// /// Returns None on invalid parameters or insufficient parameters. pub fn new( - out_ch_layout: u64, + out_ch_layout: &ffi::AVChannelLayout, out_sample_fmt: ffi::AVSampleFormat, out_sample_rate: i32, - in_ch_layout: u64, + in_ch_layout: &ffi::AVChannelLayout, in_sample_fmt: ffi::AVSampleFormat, in_sample_rate: i32, - ) -> Option { + ) -> Result { + let mut context = ptr::null_mut(); unsafe { // u64 to i64, safe - ffi::swr_alloc_set_opts( - ptr::null_mut(), - out_ch_layout as i64, + ffi::swr_alloc_set_opts2( + &mut context, + out_ch_layout, out_sample_fmt, out_sample_rate, - in_ch_layout as i64, + in_ch_layout, in_sample_fmt, in_sample_rate, 0, ptr::null_mut(), ) } - .upgrade() - .map(|x| unsafe { Self::from_raw(x) }) + .upgrade()?; + Ok(unsafe { Self::from_raw(NonNull::new(context).unwrap()) }) } /// Initialize context after user parameters have been set. diff --git a/tests/transcode_aac.rs b/tests/transcode_aac.rs index 0791d4b..c903ffb 100644 --- a/tests/transcode_aac.rs +++ b/tests/transcode_aac.rs @@ -1,10 +1,11 @@ +// Due to usage of new channel layout api use anyhow::{bail, Context as AnyhowContext, Result}; use cstr::cstr; use once_cell::sync::Lazy as SyncLazy; use rsmpeg::{ avcodec::{AVCodec, AVCodecContext}, avformat::{AVFormatContextInput, AVFormatContextOutput}, - avutil::{av_get_default_channel_layout, ra, AVAudioFifo, AVFrame, AVSamples}, + avutil::{ra, AVAudioFifo, AVChannelLayout, AVFrame, AVSamples}, error::RsmpegError, ffi, swresample::SwrContext, @@ -49,7 +50,7 @@ fn open_output_file( // Set the basic encoder parameters. // The input file's sample rate is used to avoid a sample rate conversion. encode_context.set_channels(OUTPUT_CHANNELS); - encode_context.set_channel_layout(av_get_default_channel_layout(OUTPUT_CHANNELS)); + encode_context.set_ch_layout(AVChannelLayout::from_nb_channels(OUTPUT_CHANNELS).into_inner()); encode_context.set_sample_rate(decode_context.sample_rate); encode_context.set_sample_fmt(encode_codec.sample_fmts().unwrap()[0]); encode_context.set_bit_rate(OUTPUT_BIT_RATE); @@ -76,10 +77,10 @@ fn init_resampler( encode_context: &mut AVCodecContext, ) -> Result { let mut resample_context = SwrContext::new( - av_get_default_channel_layout(encode_context.channels), + &encode_context.ch_layout, encode_context.sample_fmt, encode_context.sample_rate, - av_get_default_channel_layout(decode_context.channels), + &decode_context.ch_layout, decode_context.sample_fmt, decode_context.sample_rate, ) @@ -102,13 +103,13 @@ fn add_samples_to_fifo( fn create_output_frame( nb_samples: i32, - channel_layout: u64, + ch_layout: ffi::AVChannelLayout, sample_fmt: i32, sample_rate: i32, ) -> AVFrame { let mut frame = AVFrame::new(); frame.set_nb_samples(nb_samples); - frame.set_channel_layout(channel_layout); + frame.set_ch_layout(ch_layout); frame.set_format(sample_fmt); frame.set_sample_rate(sample_rate); @@ -154,7 +155,7 @@ fn load_encode_and_write( let nb_samples = fifo.size().min(encode_context.frame_size); let mut frame = create_output_frame( nb_samples, - encode_context.channel_layout, + encode_context.ch_layout().clone().into_inner(), encode_context.sample_fmt, encode_context.sample_rate, ); diff --git a/tests/transcoding.rs b/tests/transcoding.rs index 79e385a..44cbfe7 100644 --- a/tests/transcoding.rs +++ b/tests/transcoding.rs @@ -5,10 +5,7 @@ use rsmpeg::{ avcodec::{AVCodec, AVCodecContext}, avfilter::{AVFilter, AVFilterContextMut, AVFilterGraph, AVFilterInOut}, avformat::{AVFormatContextInput, AVFormatContextOutput}, - avutil::{ - av_get_channel_layout_nb_channels, av_get_default_channel_layout, av_inv_q, av_mul_q, - get_sample_fmt_name, ra, AVDictionary, AVFrame, - }, + avutil::{av_inv_q, av_mul_q, get_sample_fmt_name, ra, AVChannelLayout, AVDictionary, AVFrame}, error::RsmpegError, ffi, }; @@ -106,10 +103,8 @@ fn open_output_file( ))); } else if decode_context.codec_type == ffi::AVMediaType_AVMEDIA_TYPE_AUDIO { new_encode_context.set_sample_rate(decode_context.sample_rate); - new_encode_context.set_channel_layout(decode_context.channel_layout); - new_encode_context.set_channels(av_get_channel_layout_nb_channels( - decode_context.channel_layout, - )); + new_encode_context.set_ch_layout(decode_context.ch_layout().clone().into_inner()); + new_encode_context.set_channels(decode_context.ch_layout.nb_channels); new_encode_context.set_sample_fmt(encoder.sample_fmts().unwrap()[0]); new_encode_context.set_time_base(ra(1, decode_context.sample_rate)); } else { @@ -181,20 +176,22 @@ fn init_filter<'graph>( let mut buffer_sink_context = filter_graph.create_filter_context(&buffer_sink, cstr!("out"), None)?; - buffer_sink_context.set_property(cstr!("pix_fmts"), &encode_context.pix_fmt)?; + buffer_sink_context.opt_set_bin(cstr!("pix_fmts"), &encode_context.pix_fmt)?; (buffer_src_context, buffer_sink_context) } else if decode_context.codec_type == ffi::AVMediaType_AVMEDIA_TYPE_AUDIO { let buffer_src = AVFilter::get_by_name(cstr!("abuffer")).unwrap(); let buffer_sink = AVFilter::get_by_name(cstr!("abuffersink")).unwrap(); - if decode_context.channel_layout == 0 { - let channel_layout = av_get_default_channel_layout(decode_context.channels); - decode_context.set_channel_layout(channel_layout); + if decode_context.ch_layout.order == ffi::AVChannelOrder_AV_CHANNEL_ORDER_UNSPEC { + decode_context.set_ch_layout( + AVChannelLayout::from_nb_channels(decode_context.ch_layout.nb_channels) + .into_inner(), + ); } let args = format!( - "time_base={}/{}:sample_rate={}:sample_fmt={}:channel_layout=0x{}", + "time_base={}/{}:sample_rate={}:sample_fmt={}:channel_layout={}", decode_context.time_base.num, decode_context.time_base.den, decode_context.sample_rate, @@ -203,7 +200,11 @@ fn init_filter<'graph>( get_sample_fmt_name(decode_context.sample_fmt) .unwrap() .to_string_lossy(), - decode_context.channel_layout, + decode_context + .ch_layout() + .describe() + .unwrap() + .to_string_lossy(), ); let args = &CString::new(args).unwrap(); @@ -212,10 +213,12 @@ fn init_filter<'graph>( let mut buffer_sink_context = filter_graph.create_filter_context(&buffer_sink, cstr!("out"), None)?; - buffer_sink_context.set_property(cstr!("sample_fmts"), &encode_context.sample_fmt)?; - buffer_sink_context - .set_property(cstr!("channel_layouts"), &encode_context.channel_layout)?; - buffer_sink_context.set_property(cstr!("sample_rates"), &encode_context.sample_rate)?; + buffer_sink_context.opt_set_bin(cstr!("sample_fmts"), &encode_context.sample_fmt)?; + buffer_sink_context.opt_set( + cstr!("ch_layouts"), + &encode_context.ch_layout().describe().unwrap(), + )?; + buffer_sink_context.opt_set_bin(cstr!("sample_rates"), &encode_context.sample_rate)?; (buffer_src_context, buffer_sink_context) } else {