Skip to content

Commit

Permalink
Migrate to FFmpeg new channel layout API (#130)
Browse files Browse the repository at this point in the history
* Add `AVChannelLayout` and related methods

* Improve error type

* Bump rusty_ffmpeg to 0.13.3

* Add ch_layout getter for AVCodecParameters and AVCodecContext

* Rename `AVFilterContext::set_property` to `opt_set_bin`

* Add `AVFilterContext::opt_set`

* Migrate all code to new channel layout api

* Use rusty_ffmpeg with ffmpeg6

* Disable FFmpeg 6.0 tests due to FFmpeg bug
  • Loading branch information
ldm0 authored Jan 3, 2024
1 parent 13a4716 commit 44f8397
Show file tree
Hide file tree
Showing 11 changed files with 323 additions and 57 deletions.
10 changes: 7 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
10 changes: 8 additions & 2 deletions src/avcodec/codec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down
14 changes: 13 additions & 1 deletion src/avcodec/codec_par.rs
Original file line number Diff line number Diff line change
@@ -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);
Expand Down Expand Up @@ -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 {
Expand Down
17 changes: 16 additions & 1 deletion src/avfilter/avfilter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ wrap_mut!(AVFilterContext: ffi::AVFilterContext);

impl AVFilterContext {
/// Set property of a [`AVFilterContext`].
pub fn set_property<U>(&mut self, key: &CStr, value: &U) -> Result<()> {
pub fn opt_set_bin<U>(&mut self, key: &CStr, value: &U) -> Result<()> {
unsafe {
ffi::av_opt_set_bin(
self.as_mut_ptr().cast(),
Expand All @@ -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,
Expand Down
226 changes: 220 additions & 6 deletions src/avutil/channel_layout.rs
Original file line number Diff line number Diff line change
@@ -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::<ffi::AVChannelLayout>::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<Self> {
let mut layout = MaybeUninit::<ffi::AVChannelLayout>::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<Self> {
let mut layout = MaybeUninit::<ffi::AVChannelLayout>::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::<ffi::AVChannelLayout>::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<CString> {
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<ffi::AVChannel> {
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<u32> {
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<u32> {
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<ffi::AVChannel> {
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<bool> {
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<Self::Item> {
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");
}
}
Loading

0 comments on commit 44f8397

Please sign in to comment.