From 7d367dafb7d541e1b63cc4e5381eaa7bd47ad5d4 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 6 Dec 2024 16:58:53 +1100 Subject: [PATCH 1/8] codec,util: Fix pre-6.0 ChannelLayout compatibility - Restore the channel layout implementation from before bdb9f76 - Some channel layout functions are kept, some newer ones currently disabled on older FFMPEG as need more work to implement. --- examples/codec-info.rs | 21 ++++--- examples/metadata.rs | 1 + examples/transcode-audio.rs | 36 ++++++++---- src/codec/audio.rs | 4 ++ src/codec/decoder/audio.rs | 2 + src/codec/encoder/audio.rs | 2 + src/codec/packet/packet.rs | 4 ++ src/software/resampling/extensions.rs | 2 + src/util/channel_layout_pre6.rs | 83 +++++++++++++++++++++++++++ src/util/frame/audio.rs | 14 ++++- src/util/mod.rs | 4 +- src/util/option/mod.rs | 4 ++ src/util/option/traits.rs | 17 +++++- 13 files changed, 169 insertions(+), 25 deletions(-) create mode 100644 src/util/channel_layout_pre6.rs diff --git a/examples/codec-info.rs b/examples/codec-info.rs index 0dbe8ea7..cbd9cb1c 100644 --- a/examples/codec-info.rs +++ b/examples/codec-info.rs @@ -46,15 +46,18 @@ fn print_codec(codec: Codec) { println!("\t formats: any"); } - if let Some(layouts) = audio.channel_layouts() { - println!("\t channel_layouts:"); - for layout in layouts { - println!("\t\t {}", layout.describe().unwrap()); - } - } - else { - println!("\t channel_layouts: any"); - } + #[cfg(feature = "ffmpeg_6_0")] + { + if let Some(layouts) = audio.channel_layouts() { + println!("\t channel_layouts:"); + for layout in layouts { + println!("\t\t {}", layout.describe().unwrap()); + } + } + else { + println!("\t channel_layouts: any"); + } + } } println!("\t max_lowres: {:?}", codec.max_lowres()); diff --git a/examples/metadata.rs b/examples/metadata.rs index 279e7c33..99dd45c7 100644 --- a/examples/metadata.rs +++ b/examples/metadata.rs @@ -85,6 +85,7 @@ fn main() { println!("\taudio.format: {:?}", audio.format()); println!("\taudio.frames: {}", audio.frames()); println!("\taudio.align: {}", audio.align()); + #[cfg(feature = "ffmpeg_6_0")] println!("\taudio.channel_layout: {:?}", audio.channel_layout()); } } diff --git a/examples/transcode-audio.rs b/examples/transcode-audio.rs index 9e578ce8..470135be 100644 --- a/examples/transcode-audio.rs +++ b/examples/transcode-audio.rs @@ -1,6 +1,8 @@ use std::{env, path::Path}; -use ffmpeg::{channel_layout::ChannelLayout, codec, filter, format, frame, media, rescale, Rescale}; +use ffmpeg::{codec, filter, format, frame, media, rescale, Rescale}; +#[cfg(feature = "ffmpeg_6_0")] +use ffmpeg::channel_layout::{self, ChannelLayout}; fn filter( spec: &str, @@ -9,12 +11,20 @@ fn filter( ) -> Result { let mut filter = filter::Graph::new(); + #[cfg(feature = "ffmpeg_6_0")] + let channel_layout_arg = format!(":channel_layout={}", + decoder.channel_layout().describe().unwrap()); + + // Not yet implemented for older versions + #[cfg(not(feature = "ffmpeg_6_0"))] + let channel_layout_arg = ""; + let args = format!( - "time_base={}:sample_rate={}:sample_fmt={}:channel_layout={}", + "time_base={}:sample_rate={}:sample_fmt={}{}", decoder.time_base().unwrap(), decoder.sample_rate(), decoder.format().name(), - decoder.channel_layout().describe().unwrap() + channel_layout_arg ); filter.add(&filter::find("abuffer").unwrap(), "in", &args)?; @@ -24,6 +34,7 @@ fn filter( let mut out = filter.get("out").unwrap(); out.set_sample_format(encoder.format()); + #[cfg(feature = "ffmpeg_6_0")] out.set_channel_layout(encoder.channel_layout()); out.set_sample_rate(encoder.sample_rate()); } @@ -78,22 +89,25 @@ fn transcoder>( let mut output = octx.add_stream()?; let mut encoder = codec::Encoder::new(codec)?.audio()?; - let channel_layout = codec - .channel_layouts() - .map(|cls| cls.best(decoder.channel_layout().channels())) - .unwrap_or(ChannelLayout::STEREO); - if global { encoder.set_flags(ffmpeg::codec::flag::Flags::GLOBAL_HEADER); } - encoder.set_sample_rate(decoder.sample_rate()); - encoder.set_channel_layout(channel_layout); - encoder.set_channels(channel_layout.channels()); encoder.set_format(codec.formats().expect("unknown supported formats").next().unwrap()); encoder.set_bit_rate(decoder.bit_rate()); encoder.set_max_bit_rate(decoder.max_bit_rate()); + #[cfg(feature = "ffmpeg_6_0")] + { + let channel_layout = codec + .channel_layouts() + .map(|cls| cls.best(decoder.channel_layout().channels())) + .unwrap_or(ChannelLayout::STEREO); + + encoder.set_channel_layout(channel_layout); + encoder.set_channels(channel_layout.channels()); + } + let enc_tb = (1, decoder.sample_rate() as i32); encoder.set_time_base(Some(enc_tb)); output.set_time_base(Some(enc_tb)); diff --git a/src/codec/audio.rs b/src/codec/audio.rs index ecda6572..bdf035ae 100644 --- a/src/codec/audio.rs +++ b/src/codec/audio.rs @@ -33,6 +33,7 @@ impl Audio { } } + #[cfg(feature = "ffmpeg_6_0")] pub fn channel_layouts(&self) -> Option { unsafe { (!(*self.codec.as_ptr()).ch_layouts.is_null()).then(|| ChannelLayoutIter { @@ -99,11 +100,13 @@ impl Iterator for FormatIter<'_> { } } +#[cfg(feature = "ffmpeg_6_0")] pub struct ChannelLayoutIter<'a> { inner: &'a Audio, next_idx: isize, } +#[cfg(feature = "ffmpeg_6_0")] impl ChannelLayoutIter<'_> { pub fn best(self, max: i32) -> ChannelLayout { self.fold(crate::channel_layout::ChannelLayout::MONO, |acc, cur| { @@ -117,6 +120,7 @@ impl ChannelLayoutIter<'_> { } } +#[cfg(feature = "ffmpeg_6_0")] impl Iterator for ChannelLayoutIter<'_> { type Item = ChannelLayout; diff --git a/src/codec/decoder/audio.rs b/src/codec/decoder/audio.rs index 0957ce76..e61cf7f7 100644 --- a/src/codec/decoder/audio.rs +++ b/src/codec/decoder/audio.rs @@ -32,10 +32,12 @@ impl Audio { unsafe { (*self.as_ptr()).block_align as usize } } + #[cfg(feature = "ffmpeg_6_0")] pub fn channel_layout(&self) -> ChannelLayout { unsafe { (*self.as_ptr()).ch_layout.into() } } + #[cfg(feature = "ffmpeg_6_0")] pub fn set_channel_layout(&mut self, value: ChannelLayout) { unsafe { (*self.as_mut_ptr()).ch_layout = value.into(); diff --git a/src/codec/encoder/audio.rs b/src/codec/encoder/audio.rs index bca30c43..11b4b16e 100644 --- a/src/codec/encoder/audio.rs +++ b/src/codec/encoder/audio.rs @@ -92,12 +92,14 @@ impl Audio { unsafe { format::Sample::from((*self.as_ptr()).sample_fmt) } } + #[cfg(feature = "ffmpeg_6_0")] pub fn set_channel_layout(&mut self, value: ChannelLayout) { unsafe { (*self.as_mut_ptr()).ch_layout = value.into(); } } + #[cfg(feature = "ffmpeg_6_0")] pub fn channel_layout(&self) -> ChannelLayout { unsafe { (*self.as_ptr()).ch_layout.into() } } diff --git a/src/codec/packet/packet.rs b/src/codec/packet/packet.rs index b3a9e3eb..17888b5b 100644 --- a/src/codec/packet/packet.rs +++ b/src/codec/packet/packet.rs @@ -107,11 +107,13 @@ impl Packet { } #[inline] + #[cfg(feature = "ffmpeg_5_0")] pub fn time_base(&self) -> Option { Rational::from(self.0.time_base).non_zero() } #[inline] + #[cfg(feature = "ffmpeg_5_0")] pub fn set_time_base(&mut self, time_base: Option>) { self.0.time_base = time_base.map(Into::into).unwrap_or(Rational::ZERO).into(); } @@ -255,11 +257,13 @@ impl Packet { } #[inline] + #[cfg(feature = "ffmpeg_5_0")] pub fn opaque(&self) -> *mut c_void { unsafe { (*self.as_ptr()).opaque } } #[inline] + #[cfg(feature = "ffmpeg_5_0")] pub fn set_opaque(&mut self, data: *mut c_void) { unsafe { (*self.as_mut_ptr()).opaque = data; diff --git a/src/software/resampling/extensions.rs b/src/software/resampling/extensions.rs index 63f09a52..0fd610c8 100644 --- a/src/software/resampling/extensions.rs +++ b/src/software/resampling/extensions.rs @@ -1,6 +1,7 @@ use super::Context; use crate::{decoder, frame, util::format, ChannelLayout, Error}; +#[cfg(feature = "ffmpeg_6_0")] impl frame::Audio { #[inline] pub fn resampler(&self, format: format::Sample, channel_layout: ChannelLayout, rate: u32) -> Result { @@ -15,6 +16,7 @@ impl frame::Audio { } } +#[cfg(feature = "ffmpeg_6_0")] impl decoder::Audio { #[inline] pub fn resampler(&self, format: format::Sample, channel_layout: ChannelLayout, rate: u32) -> Result { diff --git a/src/util/channel_layout_pre6.rs b/src/util/channel_layout_pre6.rs new file mode 100644 index 00000000..da80521b --- /dev/null +++ b/src/util/channel_layout_pre6.rs @@ -0,0 +1,83 @@ +use libc::c_ulonglong; + +use crate::ffi::*; + +bitflags! { + #[cfg_attr(feature = "serde", derive(serde_derive::Serialize, serde_derive::Deserialize))] + #[cfg_attr(feature = "serde", serde(crate = "serde_", rename_all = "kebab-case"))] + pub struct ChannelLayout: c_ulonglong { + const FRONT_LEFT = AV_CH_FRONT_LEFT; + const FRONT_RIGHT = AV_CH_FRONT_RIGHT; + const FRONT_CENTER = AV_CH_FRONT_CENTER; + const LOW_FREQUENCY = AV_CH_LOW_FREQUENCY; + const BACK_LEFT = AV_CH_BACK_LEFT; + const BACK_RIGHT = AV_CH_BACK_RIGHT; + const FRONT_LEFT_OF_CENTER = AV_CH_FRONT_LEFT_OF_CENTER; + const FRONT_RIGHT_OF_CENTER = AV_CH_FRONT_RIGHT_OF_CENTER; + const BACK_CENTER = AV_CH_BACK_CENTER; + const SIDE_LEFT = AV_CH_SIDE_LEFT; + const SIDE_RIGHT = AV_CH_SIDE_RIGHT; + const TOP_CENTER = AV_CH_TOP_CENTER; + const TOP_FRONT_LEFT = AV_CH_TOP_FRONT_LEFT; + const TOP_FRONT_CENTER = AV_CH_TOP_FRONT_CENTER; + const TOP_FRONT_RIGHT = AV_CH_TOP_FRONT_RIGHT; + const TOP_BACK_LEFT = AV_CH_TOP_BACK_LEFT; + const TOP_BACK_CENTER = AV_CH_TOP_BACK_CENTER; + const TOP_BACK_RIGHT = AV_CH_TOP_BACK_RIGHT; + const STEREO_LEFT = AV_CH_STEREO_LEFT; + const STEREO_RIGHT = AV_CH_STEREO_RIGHT; + const WIDE_LEFT = AV_CH_WIDE_LEFT; + const WIDE_RIGHT = AV_CH_WIDE_RIGHT; + const SURROUND_DIRECT_LEFT = AV_CH_SURROUND_DIRECT_LEFT; + const SURROUND_DIRECT_RIGHT = AV_CH_SURROUND_DIRECT_RIGHT; + const LOW_FREQUENCY_2 = AV_CH_LOW_FREQUENCY_2; + const TOP_SIDE_LEFT = AV_CH_TOP_SIDE_LEFT; + const TOP_SIDE_RIGHT = AV_CH_TOP_SIDE_RIGHT; + const BOTTOM_FRONT_CENTER = AV_CH_BOTTOM_FRONT_CENTER; + const BOTTOM_FRONT_LEFT = AV_CH_BOTTOM_FRONT_LEFT; + const BOTTOM_FRONT_RIGHT = AV_CH_BOTTOM_FRONT_RIGHT; + + const NATIVE = AV_CH_LAYOUT_NATIVE; + const MONO = AV_CH_LAYOUT_MONO; + const STEREO = AV_CH_LAYOUT_STEREO; + const _2POINT1 = AV_CH_LAYOUT_2POINT1; + const _2_1 = AV_CH_LAYOUT_2_1; + const SURROUND = AV_CH_LAYOUT_SURROUND; + const _3POINT1 = AV_CH_LAYOUT_3POINT1; + const _4POINT0 = AV_CH_LAYOUT_4POINT0; + const _4POINT1 = AV_CH_LAYOUT_4POINT1; + const _2_2 = AV_CH_LAYOUT_2_2; + const QUAD = AV_CH_LAYOUT_QUAD; + const _5POINT0 = AV_CH_LAYOUT_5POINT0; + const _5POINT1 = AV_CH_LAYOUT_5POINT1; + const _5POINT0_BACK = AV_CH_LAYOUT_5POINT0_BACK; + const _5POINT1_BACK = AV_CH_LAYOUT_5POINT1_BACK; + const _6POINT0 = AV_CH_LAYOUT_6POINT0; + const _6POINT0_FRONT = AV_CH_LAYOUT_6POINT0_FRONT; + const HEXAGONAL = AV_CH_LAYOUT_HEXAGONAL; + const _6POINT1 = AV_CH_LAYOUT_6POINT1; + const _6POINT1_BACK = AV_CH_LAYOUT_6POINT1_BACK; + const _6POINT1_FRONT = AV_CH_LAYOUT_6POINT1_FRONT; + const _7POINT0 = AV_CH_LAYOUT_7POINT0; + const _7POINT0_FRONT = AV_CH_LAYOUT_7POINT0_FRONT; + const _7POINT1 = AV_CH_LAYOUT_7POINT1; + const _7POINT1_WIDE = AV_CH_LAYOUT_7POINT1_WIDE; + const _7POINT1_WIDE_BACK = AV_CH_LAYOUT_7POINT1_WIDE_BACK; + const OCTAGONAL = AV_CH_LAYOUT_OCTAGONAL; + const HEXADECAGONAL = AV_CH_LAYOUT_HEXADECAGONAL; + const STEREO_DOWNMIX = AV_CH_LAYOUT_STEREO_DOWNMIX; + } +} + +pub type Channel = i32; + +impl ChannelLayout { + #[inline] + pub fn channels(&self) -> i32 { + unsafe { av_get_channel_layout_nb_channels(self.bits()) } + } + + pub fn default(number: i32) -> ChannelLayout { + unsafe { ChannelLayout::from_bits_truncate(av_get_default_channel_layout(number) as c_ulonglong) } + } +} diff --git a/src/util/frame/audio.rs b/src/util/frame/audio.rs index 9b92b944..a658abf9 100644 --- a/src/util/frame/audio.rs +++ b/src/util/frame/audio.rs @@ -4,7 +4,7 @@ use std::{ slice, }; -use libc::c_int; +use libc::{c_int, c_ulonglong}; use super::Frame; use crate::{ffi::*, util::format, ChannelLayout}; @@ -65,14 +65,24 @@ impl Audio { #[inline] pub fn channel_layout(&self) -> ChannelLayout { + #[cfg(feature = "ffmpeg_6_0")] unsafe { ChannelLayout::from((*self.as_ptr()).ch_layout) } + + #[cfg(not(feature = "ffmpeg_6_0"))] + unsafe { ChannelLayout::from_bits_truncate(av_frame_get_channel_layout(self.as_ptr()) as c_ulonglong) } } #[inline] pub fn set_channel_layout(&mut self, value: ChannelLayout) { + #[cfg(feature = "ffmpeg_6_0")] unsafe { (*self.as_mut_ptr()).ch_layout = value.into(); - } + } + + #[cfg(not(feature = "ffmpeg_6_0"))] + unsafe { + av_frame_set_channel_layout(self.as_mut_ptr(), value.bits() as i64); + } } #[inline] diff --git a/src/util/mod.rs b/src/util/mod.rs index 35c8ccea..6788f7bc 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,6 +1,5 @@ #[macro_use] pub mod dictionary; -pub mod channel_layout; pub mod chroma; pub mod color; pub mod error; @@ -16,6 +15,9 @@ pub mod range; pub mod rational; pub mod time; +#[cfg_attr(not(feature = "ffmpeg_6_0"), path="channel_layout_pre6.rs")] +pub mod channel_layout; + #[cfg(feature = "log")] pub mod log; diff --git a/src/util/option/mod.rs b/src/util/option/mod.rs index 2ef15f4a..fa6cd019 100644 --- a/src/util/option/mod.rs +++ b/src/util/option/mod.rs @@ -49,6 +49,7 @@ impl From for Type { AV_OPT_TYPE_DURATION => Type::Duration, AV_OPT_TYPE_COLOR => Type::Color, AV_OPT_TYPE_CHANNEL_LAYOUT => Type::ChannelLayout, + #[cfg(feature = "ffmpeg_6_0")] AV_OPT_TYPE_CHLAYOUT => Type::ChannelLayout, } } @@ -76,7 +77,10 @@ impl Into for Type { Type::VideoRate => AV_OPT_TYPE_VIDEO_RATE, Type::Duration => AV_OPT_TYPE_DURATION, Type::Color => AV_OPT_TYPE_COLOR, + #[cfg(feature = "ffmpeg_6_0")] Type::ChannelLayout => AV_OPT_TYPE_CHLAYOUT, + #[cfg(not(feature = "ffmpeg_6_0"))] + Type::ChannelLayout => AV_OPT_TYPE_CHANNEL_LAYOUT, } } } diff --git a/src/util/option/traits.rs b/src/util/option/traits.rs index c79190ff..453b0fcf 100644 --- a/src/util/option/traits.rs +++ b/src/util/option/traits.rs @@ -128,17 +128,30 @@ pub trait Settable: Target { } } + #[cfg(feature = "ffmpeg_6_0")] fn set_channel_layout(&mut self, name: &str, layout: ChannelLayout) -> Result<(), Error> { unsafe { let name = CString::new(name).unwrap(); - check!(av_opt_set_chlayout( self.as_mut_ptr(), name.as_ptr(), layout.as_ptr(), AV_OPT_SEARCH_CHILDREN )) - } + } + } + + #[cfg(not(feature = "ffmpeg_6_0"))] + fn set_channel_layout(&mut self, name: &str, layout: ChannelLayout) -> Result<(), Error> { + unsafe { + let name = CString::new(name).unwrap(); + check!(av_opt_set_channel_layout( + self.as_mut_ptr(), + name.as_ptr(), + layout.bits() as i64, + AV_OPT_SEARCH_CHILDREN + )) + } } } From 544df52a1e69d204ce46ae98c34c6458ce5c0684 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 6 Dec 2024 18:30:29 +1100 Subject: [PATCH 2/8] chapter: Add ChapterId type for pre-5.0 compatibility. The ChapterId argument changed from i32 to i64 in FFMPEG 5.0, so expose this with a ChapterId alias. --- src/format/chapter/chapter.rs | 8 +++++++- src/format/chapter/chapter_mut.rs | 4 ++-- src/format/chapter/mod.rs | 1 + src/format/context/output.rs | 4 ++-- src/lib.rs | 2 +- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/format/chapter/chapter.rs b/src/format/chapter/chapter.rs index 00f19cd9..6db3a050 100644 --- a/src/format/chapter/chapter.rs +++ b/src/format/chapter/chapter.rs @@ -1,5 +1,11 @@ use crate::{ffi::*, format::context::common::Context, DictionaryRef, Rational}; +#[cfg(feature = "ffmpeg_5_0")] +pub type ChapterId = i64; + +#[cfg(not(feature = "ffmpeg_5_0"))] +pub type ChapterId = i32; + // WARNING: index refers to the offset in the chapters array (starting from 0) // it is not necessarly equal to the id (which may start at 1) pub struct Chapter<'a> { @@ -22,7 +28,7 @@ impl<'a> Chapter<'a> { self.index } - pub fn id(&self) -> i64 { + pub fn id(&self) -> ChapterId { unsafe { (*self.as_ptr()).id } } diff --git a/src/format/chapter/chapter_mut.rs b/src/format/chapter/chapter_mut.rs index 481516d5..411f79f2 100644 --- a/src/format/chapter/chapter_mut.rs +++ b/src/format/chapter/chapter_mut.rs @@ -1,6 +1,6 @@ use std::{mem, ops::Deref}; -use super::Chapter; +use super::{ChapterId, Chapter}; use crate::{ffi::*, format::context::common::Context, Dictionary, DictionaryMut, Rational}; // WARNING: index refers to the offset in the chapters array (starting from 0) @@ -28,7 +28,7 @@ impl<'a> ChapterMut<'a> { } impl<'a> ChapterMut<'a> { - pub fn set_id(&mut self, value: i64) { + pub fn set_id(&mut self, value: ChapterId) { unsafe { (*self.as_mut_ptr()).id = value; } diff --git a/src/format/chapter/mod.rs b/src/format/chapter/mod.rs index 9136f080..f0e1caf7 100644 --- a/src/format/chapter/mod.rs +++ b/src/format/chapter/mod.rs @@ -1,5 +1,6 @@ mod chapter; pub use self::chapter::Chapter; +pub use self::chapter::ChapterId; mod chapter_mut; pub use self::chapter_mut::ChapterMut; diff --git a/src/format/context/output.rs b/src/format/context/output.rs index b3628a98..3c9e0638 100644 --- a/src/format/context/output.rs +++ b/src/format/context/output.rs @@ -11,7 +11,7 @@ use super::{common::Context, destructor}; use crate::{ ffi::*, format::{self, io::Io}, - ChapterMut, Dictionary, Error, Rational, StreamMut, + ChapterId, ChapterMut, Dictionary, Error, Rational, StreamMut, }; pub struct Output { @@ -116,7 +116,7 @@ impl Output { pub fn add_chapter, S: AsRef>( &mut self, - id: i64, + id: ChapterId, time_base: R, start: i64, end: i64, diff --git a/src/lib.rs b/src/lib.rs index 7d37de14..aa09fc90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,7 @@ pub type Result = ::std::result::Result; #[cfg(feature = "format")] pub mod format; #[cfg(feature = "format")] -pub use crate::format::chapter::{Chapter, ChapterMut}; +pub use crate::format::chapter::{Chapter, ChapterId, ChapterMut}; #[cfg(feature = "format")] pub use crate::format::format::Format; #[cfg(feature = "format")] From 831df396546a243277a572d50317b6a65956f573 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 6 Dec 2024 18:34:32 +1100 Subject: [PATCH 3/8] various: Workaround const-ified pointers in v5.0. A number of pointers were made 'const' in FFMPEG 5.0. This is a fairly clunky way to handle these, maybe there's a better way? --- src/device/input.rs | 8 ++++++++ src/device/output.rs | 8 ++++++++ src/format/context/common.rs | 7 ++++++- src/format/format/input.rs | 17 +++++++++++++++++ src/format/format/output.rs | 24 +++++++++++++++++++++++- src/format/io.rs | 4 ++-- src/format/mod.rs | 27 +++++++++++++++++---------- 7 files changed, 81 insertions(+), 14 deletions(-) diff --git a/src/device/input.rs b/src/device/input.rs index a1856018..3a84dc9a 100644 --- a/src/device/input.rs +++ b/src/device/input.rs @@ -9,8 +9,12 @@ impl Iterator for AudioIter { fn next(&mut self) -> Option<::Item> { unsafe { + #[cfg(feature = "ffmpeg_5_0")] let ptr = av_input_audio_device_next(self.0); + #[cfg(not(feature = "ffmpeg_5_0"))] + let ptr = av_input_audio_device_next(self.0.cast_mut()); + if ptr.is_null() && !self.0.is_null() { None } @@ -34,8 +38,12 @@ impl Iterator for VideoIter { fn next(&mut self) -> Option<::Item> { unsafe { + #[cfg(feature = "ffmpeg_5_0")] let ptr = av_input_video_device_next(self.0); + #[cfg(not(feature = "ffmpeg_5_0"))] + let ptr = av_input_video_device_next(self.0.cast_mut()); + if ptr.is_null() && !self.0.is_null() { None } diff --git a/src/device/output.rs b/src/device/output.rs index 63637a5c..fbfcba12 100644 --- a/src/device/output.rs +++ b/src/device/output.rs @@ -9,8 +9,12 @@ impl Iterator for AudioIter { fn next(&mut self) -> Option<::Item> { unsafe { + #[cfg(feature = "ffmpeg_5_0")] let ptr = av_output_audio_device_next(self.0); + #[cfg(not(feature = "ffmpeg_5_0"))] + let ptr = av_output_audio_device_next(self.0.cast_mut()); + if ptr.is_null() && !self.0.is_null() { None } @@ -34,8 +38,12 @@ impl Iterator for VideoIter { fn next(&mut self) -> Option<::Item> { unsafe { + #[cfg(feature = "ffmpeg_5_0")] let ptr = av_output_video_device_next(self.0); + #[cfg(not(feature = "ffmpeg_5_0"))] + let ptr = av_output_video_device_next(self.0.cast_mut()); + if ptr.is_null() && !self.0.is_null() { None } diff --git a/src/format/context/common.rs b/src/format/context/common.rs index b4807109..031ef722 100644 --- a/src/format/context/common.rs +++ b/src/format/context/common.rs @@ -172,13 +172,18 @@ impl<'a> Best<'a> { 'a: 'b, { unsafe { + #[cfg(feature = "ffmpeg_5_0")] let mut decoder = ptr::null::(); + + #[cfg(not(feature = "ffmpeg_5_0"))] + let mut decoder = ptr::null_mut::(); + let index = av_find_best_stream( self.context.ptr, kind.into(), self.wanted as c_int, self.related as c_int, - &mut decoder as *mut _, + &mut decoder as *mut _, 0, ); diff --git a/src/format/format/input.rs b/src/format/format/input.rs index a7639673..4f6a6abe 100644 --- a/src/format/format/input.rs +++ b/src/format/format/input.rs @@ -20,6 +20,23 @@ impl Input { pub unsafe fn as_ptr(&self) -> *const AVInputFormat { self.ptr } + + #[cfg(not(feature = "ffmpeg_5_0"))] + pub unsafe fn as_mut_ptr(&mut self) -> *mut AVInputFormat { + self.ptr.cast_mut() + } + + // Some APIs const-ified args in FFMPEG 5.0: + + #[cfg(not(feature = "ffmpeg_5_0"))] + pub(crate) unsafe fn as_api_ptr(&self) -> *mut AVInputFormat { + self.as_ptr() as *mut _ + } + + #[cfg(feature = "ffmpeg_5_0")] + pub(crate) unsafe fn as_api_ptr(&self) -> *const AVInputFormat { + self.as_ptr() + } } impl Input { diff --git a/src/format/format/output.rs b/src/format/format/output.rs index 0ed1951d..ebc272eb 100644 --- a/src/format/format/output.rs +++ b/src/format/format/output.rs @@ -26,6 +26,22 @@ impl Output { pub unsafe fn as_ptr(&self) -> *const AVOutputFormat { self.ptr } + + pub unsafe fn as_mut_ptr(&mut self) -> *mut AVOutputFormat { + self.ptr.cast_mut() + } + + // Some APIs const-ified args in FFMPEG 5.0: + + #[cfg(not(feature = "ffmpeg_5_0"))] + pub(crate) unsafe fn as_api_ptr(&self) -> *mut AVOutputFormat { + self.as_ptr() as *mut _ + } + + #[cfg(feature = "ffmpeg_5_0")] + pub(crate) unsafe fn as_api_ptr(&self) -> *const AVOutputFormat { + self.as_ptr() + } } impl Output { @@ -68,8 +84,14 @@ impl Output { let path = CString::new(path.as_ref().as_os_str().to_str().unwrap()).unwrap(); unsafe { + #[cfg(feature = "ffmpeg_5_0")] + let as_ptr = self.as_ptr(); + + #[cfg(not(feature = "ffmpeg_5_0"))] + let as_ptr = unsafe { self.as_ptr() as *mut _ }; + codec::Id::from(av_guess_codec( - self.as_ptr(), + as_ptr, ptr::null(), path.as_ptr(), ptr::null(), diff --git a/src/format/io.rs b/src/format/io.rs index c735628e..92e6812b 100644 --- a/src/format/io.rs +++ b/src/format/io.rs @@ -241,7 +241,7 @@ pub fn input_as(io: impl Read + Seek + 'static, format: format::Input) -> Result (*ps).flags |= AVFMT_FLAG_CUSTOM_IO; (*ps).pb = io.as_mut_ptr(); - match avformat_open_input(&mut ps, ptr::null_mut(), format.as_ptr(), ptr::null_mut()) { + match avformat_open_input(&mut ps, ptr::null_mut(), format.as_api_ptr(), ptr::null_mut()) { 0 => match avformat_find_stream_info(ps, ptr::null_mut()) { r if r >= 0 => Ok(context::Input::wrap_with(ps, io)), @@ -261,7 +261,7 @@ pub fn output(io: impl Write + 'static, format: format::Output) -> Result= 0 => { (*ps).flags |= AVFMT_FLAG_CUSTOM_IO; (*ps).pb = io.as_mut_ptr(); diff --git a/src/format/mod.rs b/src/format/mod.rs index 9059ebc5..f4197724 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -23,6 +23,13 @@ use std::{ use crate::{ffi::*, util::from_os_str, Dictionary, Error, Format}; +// FFMPEG 5.0 const-ified a bunch of string APIs +#[cfg(feature = "ffmpeg_5_0")] +use ptr::null as maybe_mut_ptr_null; + +#[cfg(not(feature = "ffmpeg_5_0"))] +use ptr::null_mut as maybe_mut_ptr_null; + pub fn version() -> u32 { unsafe { avformat_version() } } @@ -42,7 +49,7 @@ pub fn open(path_or_url: impl AsRef, format: &Format) -> Result { - match avformat_open_input(&mut ps, path.as_ptr(), format.as_ptr() as *mut _, ptr::null_mut()) { + match avformat_open_input(&mut ps, path.as_ptr(), format.as_api_ptr(), ptr::null_mut()) { 0 => match avformat_find_stream_info(ps, ptr::null_mut()) { r if r >= 0 => Ok(Context::Input(context::Input::wrap(ps))), e => Err(Error::from(e)), @@ -53,7 +60,7 @@ pub fn open(path_or_url: impl AsRef, format: &Format) -> Result { - match avformat_alloc_output_context2(&mut ps, format.as_ptr() as *mut _, ptr::null(), path.as_ptr()) { + match avformat_alloc_output_context2(&mut ps, format.as_api_ptr(), ptr::null(), path.as_ptr()) { 0 => { let output = context::Output::wrap(ps); if output.format().flags().contains(Flags::NO_FILE) { @@ -94,7 +101,7 @@ pub fn open_with(path_or_url: impl AsRef, format: &Format, options: Dicti } Format::Output(ref format) => { - match avformat_alloc_output_context2(&mut ps, format.as_ptr(), ptr::null(), path.as_ptr()) { + match avformat_alloc_output_context2(&mut ps, format.as_api_ptr(), maybe_mut_ptr_null(), path.as_ptr()) { 0 => { let output = context::Output::wrap(ps); if output.format().flags().contains(Flags::NO_FILE) { @@ -118,7 +125,7 @@ pub fn input(path_or_url: impl AsRef) -> Result { let mut ps = ptr::null_mut(); let path = from_os_str(path_or_url); - match avformat_open_input(&mut ps, path.as_ptr(), ptr::null(), ptr::null_mut()) { + match avformat_open_input(&mut ps, path.as_ptr(), maybe_mut_ptr_null(), ptr::null_mut()) { 0 => match avformat_find_stream_info(ps, ptr::null_mut()) { r if r >= 0 => Ok(context::Input::wrap(ps)), e => { @@ -137,7 +144,7 @@ pub fn input_with_dictionary(path_or_url: impl AsRef, options: Dictionary let mut ps = ptr::null_mut(); let path = from_os_str(path_or_url); let mut opts = options.disown(); - let res = avformat_open_input(&mut ps, path.as_ptr(), ptr::null(), &mut opts); + let res = avformat_open_input(&mut ps, path.as_ptr(), maybe_mut_ptr_null(), &mut opts); Dictionary::own(opts); @@ -164,7 +171,7 @@ pub fn input_with_interrupt>( let path = from_os_str(path_or_url); (*ps).interrupt_callback = interrupt::new(Box::new(closure)).interrupt; - match avformat_open_input(&mut ps, path.as_ptr(), ptr::null(), ptr::null_mut()) { + match avformat_open_input(&mut ps, path.as_ptr(), maybe_mut_ptr_null(), ptr::null_mut()) { 0 => match avformat_find_stream_info(ps, ptr::null_mut()) { r if r >= 0 => Ok(context::Input::wrap(ps)), e => { @@ -183,7 +190,7 @@ pub fn output(path_or_url: impl AsRef) -> Result let mut ps = ptr::null_mut(); let path = from_os_str(path_or_url); - match avformat_alloc_output_context2(&mut ps, ptr::null(), ptr::null(), path.as_ptr()) { + match avformat_alloc_output_context2(&mut ps, maybe_mut_ptr_null(), ptr::null(), path.as_ptr()) { 0 => { let output = context::Output::wrap(ps); if output.format().flags().contains(Flags::NO_FILE) { @@ -205,7 +212,7 @@ pub fn output_with(path_or_url: impl AsRef, options: Dictionary) -> Resul let mut ps = ptr::null_mut(); let path = from_os_str(path_or_url); - match avformat_alloc_output_context2(&mut ps, ptr::null(), ptr::null(), path.as_ptr()) { + match avformat_alloc_output_context2(&mut ps, maybe_mut_ptr_null(), ptr::null(), path.as_ptr()) { 0 => { let output = context::Output::wrap(ps); if output.format().flags().contains(Flags::NO_FILE) { @@ -232,7 +239,7 @@ pub fn output_as(path_or_url: impl AsRef, format: Output) -> Result { let output = context::Output::wrap(ps); if output.format().flags().contains(Flags::NO_FILE) { @@ -258,7 +265,7 @@ pub fn output_as_with( let mut ps = ptr::null_mut(); let path = from_os_str(path_or_url); - match avformat_alloc_output_context2(&mut ps, format.as_ptr(), ptr::null_mut(), path.as_ptr()) { + match avformat_alloc_output_context2(&mut ps, format.as_api_ptr(), ptr::null_mut(), path.as_ptr()) { 0 => { let output = context::Output::wrap(ps); if output.format().flags().contains(Flags::NO_FILE) { From 8d95c20ddb32426c7e0cb82cc7bc62f762146931 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 6 Dec 2024 18:35:17 +1100 Subject: [PATCH 4/8] filter: Fix inputs and outputs iterators pre-5.0 Before the nb_inputs/nb_outputs fields, the array is NULL-terminated and need to call a function to get the number of entries. --- src/filter/filter.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/filter/filter.rs b/src/filter/filter.rs index 2f1fe936..a34b7513 100644 --- a/src/filter/filter.rs +++ b/src/filter/filter.rs @@ -43,11 +43,17 @@ impl Filter { unsafe { let ptr = (*self.as_ptr()).inputs; + #[cfg(feature = "ffmpeg_5_0")] + let count = (*self.as_ptr()).nb_inputs; + + #[cfg(not(feature = "ffmpeg_5_0"))] + let count = avfilter_pad_count((*self.as_ptr()).inputs); + if ptr.is_null() { None } else { - Some(PadIter::new(ptr, (*self.as_ptr()).nb_inputs as usize)) + Some(PadIter::new(ptr, count as usize)) } } } @@ -56,11 +62,17 @@ impl Filter { unsafe { let ptr = (*self.as_ptr()).outputs; + #[cfg(feature = "ffmpeg_5_0")] + let count = (*self.as_ptr()).nb_outputs; + + #[cfg(not(feature = "ffmpeg_5_0"))] + let count = avfilter_pad_count((*self.as_ptr()).outputs); + if ptr.is_null() { None } else { - Some(PadIter::new(ptr, (*self.as_ptr()).nb_outputs as usize)) + Some(PadIter::new(ptr, count as usize)) } } } From 868ffe9e75316fac68c9ea8e3aea28a5add0a90e Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 6 Dec 2024 18:41:28 +1100 Subject: [PATCH 5/8] stream_mut: Workaround different pointer types in av_stream_get_side_data() --- src/format/stream/stream_mut.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/format/stream/stream_mut.rs b/src/format/stream/stream_mut.rs index f3bec76f..1edf12d0 100644 --- a/src/format/stream/stream_mut.rs +++ b/src/format/stream/stream_mut.rs @@ -75,20 +75,28 @@ impl<'a> StreamMut<'a> { const MATRIX_LEN: usize = 9 * size_of::(); let mut matrix = [0u8; MATRIX_LEN]; + #[cfg(feature = "ffmpeg_5_0")] + type DataSize = usize; + #[cfg(not(feature = "ffmpeg_5_0"))] + type DataSize = i32; + unsafe { av_display_rotation_set(matrix.as_mut_ptr() as *mut i32, angle); - let mut data_size: usize = 0; + let mut data_size: DataSize = 0; let mut side_data = av_stream_get_side_data( self.as_mut_ptr(), AVPacketSideDataType::AV_PKT_DATA_DISPLAYMATRIX, &mut data_size, ); + + let data_size = data_size as usize; + if side_data.is_null() || data_size != MATRIX_LEN { side_data = av_stream_new_side_data( self.as_mut_ptr(), AVPacketSideDataType::AV_PKT_DATA_DISPLAYMATRIX, - MATRIX_LEN, + MATRIX_LEN as DataSize, ); } side_data.copy_from(matrix.as_ptr(), MATRIX_LEN); From 5c84039d21cc3b7afd125ead41a74e3ea5e99756 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 6 Dec 2024 18:42:19 +1100 Subject: [PATCH 6/8] frame: Workaround missing or renamed fields pre-5.0 & 6.0 - time_base field missing - duration field available as pkt_duration I think (the newer versions deprecate pkt_duration for duration).. --- src/codec/packet/packet.rs | 8 ++++---- src/device/input.rs | 4 ++-- src/device/output.rs | 8 ++++---- src/util/frame/mod.rs | 26 +++++++++++++++++++------- 4 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/codec/packet/packet.rs b/src/codec/packet/packet.rs index 17888b5b..f392caf7 100644 --- a/src/codec/packet/packet.rs +++ b/src/codec/packet/packet.rs @@ -107,13 +107,13 @@ impl Packet { } #[inline] - #[cfg(feature = "ffmpeg_5_0")] + #[cfg(feature = "ffmpeg_5_0")] pub fn time_base(&self) -> Option { Rational::from(self.0.time_base).non_zero() } #[inline] - #[cfg(feature = "ffmpeg_5_0")] + #[cfg(feature = "ffmpeg_5_0")] pub fn set_time_base(&mut self, time_base: Option>) { self.0.time_base = time_base.map(Into::into).unwrap_or(Rational::ZERO).into(); } @@ -257,13 +257,13 @@ impl Packet { } #[inline] - #[cfg(feature = "ffmpeg_5_0")] + #[cfg(feature = "ffmpeg_5_0")] pub fn opaque(&self) -> *mut c_void { unsafe { (*self.as_ptr()).opaque } } #[inline] - #[cfg(feature = "ffmpeg_5_0")] + #[cfg(feature = "ffmpeg_5_0")] pub fn set_opaque(&mut self, data: *mut c_void) { unsafe { (*self.as_mut_ptr()).opaque = data; diff --git a/src/device/input.rs b/src/device/input.rs index 3a84dc9a..46a95dd4 100644 --- a/src/device/input.rs +++ b/src/device/input.rs @@ -9,10 +9,10 @@ impl Iterator for AudioIter { fn next(&mut self) -> Option<::Item> { unsafe { - #[cfg(feature = "ffmpeg_5_0")] + #[cfg(feature = "ffmpeg_5_0")] let ptr = av_input_audio_device_next(self.0); - #[cfg(not(feature = "ffmpeg_5_0"))] + #[cfg(not(feature = "ffmpeg_5_0"))] let ptr = av_input_audio_device_next(self.0.cast_mut()); if ptr.is_null() && !self.0.is_null() { diff --git a/src/device/output.rs b/src/device/output.rs index fbfcba12..238f1445 100644 --- a/src/device/output.rs +++ b/src/device/output.rs @@ -9,10 +9,10 @@ impl Iterator for AudioIter { fn next(&mut self) -> Option<::Item> { unsafe { - #[cfg(feature = "ffmpeg_5_0")] + #[cfg(feature = "ffmpeg_5_0")] let ptr = av_output_audio_device_next(self.0); - #[cfg(not(feature = "ffmpeg_5_0"))] + #[cfg(not(feature = "ffmpeg_5_0"))] let ptr = av_output_audio_device_next(self.0.cast_mut()); if ptr.is_null() && !self.0.is_null() { @@ -38,10 +38,10 @@ impl Iterator for VideoIter { fn next(&mut self) -> Option<::Item> { unsafe { - #[cfg(feature = "ffmpeg_5_0")] + #[cfg(feature = "ffmpeg_5_0")] let ptr = av_output_video_device_next(self.0); - #[cfg(not(feature = "ffmpeg_5_0"))] + #[cfg(not(feature = "ffmpeg_5_0"))] let ptr = av_output_video_device_next(self.0.cast_mut()); if ptr.is_null() && !self.0.is_null() { diff --git a/src/util/frame/mod.rs b/src/util/frame/mod.rs index 1d077393..1253166c 100644 --- a/src/util/frame/mod.rs +++ b/src/util/frame/mod.rs @@ -103,11 +103,13 @@ impl Frame { } #[inline] + #[cfg(feature = "ffmpeg_5_0")] pub fn time_base(&self) -> Option { unsafe { Rational::from((*self.as_ptr()).time_base).non_zero() } } - #[inline] + #[inline] + #[cfg(feature = "ffmpeg_5_0")] pub fn set_time_base(&mut self, time_base: Option>) { unsafe { (*self.as_mut_ptr()).time_base = time_base.map(Into::into).unwrap_or(Rational::ZERO).into(); @@ -143,16 +145,26 @@ impl Frame { #[inline] pub fn duration(&self) -> Option { - unsafe { - match (*self.as_ptr()).duration { - 0 => None, - d => Some(d), - } - } + #[cfg(not(feature = "ffmpeg_6_0"))] + let raw = unsafe { (*self.as_ptr()).pkt_duration }; + + #[cfg(feature = "ffmpeg_6_0")] + let raw = unsafe { (*self.as_ptr()).duration }; + + match raw { + 0 => None, + d => Some(d), + } } #[inline] pub fn set_duration(&mut self, value: Option) { + #[cfg(feature = "ffmpeg_6_0")] + unsafe { + (*self.as_mut_ptr()).pkt_duration = value.unwrap_or(0); + } + + #[cfg(feature = "ffmpeg_6_0")] unsafe { (*self.as_mut_ptr()).duration = value.unwrap_or(0); } From dad0cf789c1027b62e70a46348583f8cada2a3f9 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 6 Dec 2024 16:11:49 +1100 Subject: [PATCH 7/8] ci: Run on two FFMPEG versions - ffmpeg 4.4 on Ubuntu 22.04 (Jammy) - ffmpeg 6.1 on Ubuntu 24.04 (Noble) --- .github/workflows/tests.yml | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a5cede0b..ff9bfb18 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,11 +11,17 @@ env: jobs: build: - runs-on: ubuntu-latest - + strategy: + matrix: + include: + - os: ubuntu-24.04 # "noble", ffmpeg 6.1 + ffmpeg_feature: "ffmpeg_6_0" + - os: ubuntu-22.04 # "jammy", ffmpeg 4.4 + ffmpeg_feature: "ffmpeg_4_4" + runs-on: ${{ matrix.os }} steps: - - name: Install ffmpeg - run: sudo apt-get -y install ffmpeg pkg-config libavutil-dev libavformat-dev libavfilter-dev libavdevice-dev - - uses: actions/checkout@v4 - - name: Run tests - run: cargo test --all --verbose --features=ffmpeg_4_4 + - name: Install ffmpeg + run: sudo apt-get -y install ffmpeg pkg-config libavutil-dev libavformat-dev libavfilter-dev libavdevice-dev + - uses: actions/checkout@v4 + - name: Run tests + run: cargo test --all --verbose --features=${{ matrix.ffmpeg_feature }} From d8568cf20bfbac5b22c2f919dc4b5192743cac84 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 6 Dec 2024 18:45:01 +1100 Subject: [PATCH 8/8] ci: Run on all branches --- .github/workflows/tests.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ff9bfb18..8d1e6042 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,10 +1,8 @@ name: Tests on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] + - push + - pull_request env: CARGO_TERM_COLOR: always