Add ChannelLayout API (#38)
* Create channel_layout directory * Rename ChannelLayout -> ChannelLayoutMask * Add enum Channel (<-> AVChannel) * Add struct ChannelCustom (<-> AVChannelCustom) * Add enum ChannelOrder (<-> AVChannelOrder) * Add struct ChannelLayout - Smart copy-on-write pointer to AVChannelLayout - idiomatic Rust API wrapping around FFmpeg APIs - no Ambisonic support (yet) - consts will need to be manually updated * Add ChannelLayoutIter (iterator over all standard layouts) * Add codec/Audio::ch_layouts * Add ch_layout API to Audio-related structs
This commit is contained in:
parent
3a9f4584a0
commit
3206eedcf0
@ -91,7 +91,7 @@ fn transcoder<P: AsRef<Path>>(
|
|||||||
let channel_layout = codec
|
let channel_layout = codec
|
||||||
.channel_layouts()
|
.channel_layouts()
|
||||||
.map(|cls| cls.best(decoder.channel_layout().channels()))
|
.map(|cls| cls.best(decoder.channel_layout().channels()))
|
||||||
.unwrap_or(ffmpeg::channel_layout::ChannelLayout::STEREO);
|
.unwrap_or(ffmpeg::channel_layout::ChannelLayoutMask::STEREO);
|
||||||
|
|
||||||
if global {
|
if global {
|
||||||
encoder.set_flags(ffmpeg::codec::flag::Flags::GLOBAL_HEADER);
|
encoder.set_flags(ffmpeg::codec::flag::Flags::GLOBAL_HEADER);
|
||||||
|
@ -2,7 +2,7 @@ use std::ops::Deref;
|
|||||||
|
|
||||||
use super::codec::Codec;
|
use super::codec::Codec;
|
||||||
use crate::ffi::*;
|
use crate::ffi::*;
|
||||||
use crate::{format, ChannelLayout};
|
use crate::{format, ChannelLayoutMask};
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||||
pub struct Audio {
|
pub struct Audio {
|
||||||
@ -36,17 +36,22 @@ impl Audio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn channel_layouts(&self) -> Option<ChannelLayoutIter> {
|
pub fn channel_layouts(&self) -> Option<ChannelLayoutMaskIter> {
|
||||||
unsafe {
|
unsafe {
|
||||||
if (*self.codec.as_ptr()).channel_layouts.is_null() {
|
if (*self.codec.as_ptr()).channel_layouts.is_null() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(ChannelLayoutIter::new(
|
Some(ChannelLayoutMaskIter::new(
|
||||||
(*self.codec.as_ptr()).channel_layouts,
|
(*self.codec.as_ptr()).channel_layouts,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ffmpeg_5_1")]
|
||||||
|
pub fn ch_layouts(&self) -> Option<ChannelLayoutIter> {
|
||||||
|
unsafe { ChannelLayoutIter::from_raw((*self.codec.as_ptr()).ch_layouts) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Audio {
|
impl Deref for Audio {
|
||||||
@ -111,17 +116,17 @@ impl Iterator for FormatIter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ChannelLayoutIter {
|
pub struct ChannelLayoutMaskIter {
|
||||||
ptr: *const u64,
|
ptr: *const u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChannelLayoutIter {
|
impl ChannelLayoutMaskIter {
|
||||||
pub fn new(ptr: *const u64) -> Self {
|
pub fn new(ptr: *const u64) -> Self {
|
||||||
ChannelLayoutIter { ptr }
|
ChannelLayoutMaskIter { ptr }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn best(self, max: i32) -> ChannelLayout {
|
pub fn best(self, max: i32) -> ChannelLayoutMask {
|
||||||
self.fold(ChannelLayout::MONO, |acc, cur| {
|
self.fold(ChannelLayoutMask::MONO, |acc, cur| {
|
||||||
if cur.channels() > acc.channels() && cur.channels() <= max {
|
if cur.channels() > acc.channels() && cur.channels() <= max {
|
||||||
cur
|
cur
|
||||||
} else {
|
} else {
|
||||||
@ -131,8 +136,8 @@ impl ChannelLayoutIter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Iterator for ChannelLayoutIter {
|
impl Iterator for ChannelLayoutMaskIter {
|
||||||
type Item = ChannelLayout;
|
type Item = ChannelLayoutMask;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
|
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -140,10 +145,52 @@ impl Iterator for ChannelLayoutIter {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let layout = ChannelLayout::from_bits_truncate(*self.ptr);
|
let layout = ChannelLayoutMask::from_bits_truncate(*self.ptr);
|
||||||
self.ptr = self.ptr.offset(1);
|
self.ptr = self.ptr.offset(1);
|
||||||
|
|
||||||
Some(layout)
|
Some(layout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ffmpeg_5_1")]
|
||||||
|
pub use ch_layout::ChannelLayoutIter;
|
||||||
|
|
||||||
|
#[cfg(feature = "ffmpeg_5_1")]
|
||||||
|
mod ch_layout {
|
||||||
|
use super::*;
|
||||||
|
use crate::ChannelLayout;
|
||||||
|
|
||||||
|
pub struct ChannelLayoutIter<'a> {
|
||||||
|
next: &'a AVChannelLayout,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ChannelLayoutIter<'a> {
|
||||||
|
pub unsafe fn from_raw(ptr: *const AVChannelLayout) -> Option<Self> {
|
||||||
|
ptr.as_ref().map(|next| Self { next })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for ChannelLayoutIter<'a> {
|
||||||
|
type Item = ChannelLayout<'a>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
unsafe {
|
||||||
|
let curr = self.next;
|
||||||
|
if *curr == zeroed_layout() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: We trust that there is always an initialized layout up until
|
||||||
|
// the zeroed-out AVChannelLayout, which signals the end of iteration.
|
||||||
|
self.next = (curr as *const AVChannelLayout).add(1).as_ref().unwrap();
|
||||||
|
Some(ChannelLayout::from(curr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove this with a const variable when zeroed() is const (1.75.0)
|
||||||
|
unsafe fn zeroed_layout() -> AVChannelLayout {
|
||||||
|
std::mem::zeroed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -12,7 +12,10 @@ use crate::frame;
|
|||||||
use crate::util::format;
|
use crate::util::format;
|
||||||
#[cfg(not(feature = "ffmpeg_5_0"))]
|
#[cfg(not(feature = "ffmpeg_5_0"))]
|
||||||
use crate::{packet, Error};
|
use crate::{packet, Error};
|
||||||
use crate::{AudioService, ChannelLayout};
|
use crate::{AudioService, ChannelLayoutMask};
|
||||||
|
|
||||||
|
#[cfg(feature = "ffmpeg_5_1")]
|
||||||
|
use crate::ChannelLayout;
|
||||||
|
|
||||||
pub struct Audio(pub Opened);
|
pub struct Audio(pub Opened);
|
||||||
|
|
||||||
@ -69,22 +72,34 @@ impl Audio {
|
|||||||
unsafe { (*self.as_ptr()).block_align as usize }
|
unsafe { (*self.as_ptr()).block_align as usize }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn channel_layout(&self) -> ChannelLayout {
|
pub fn channel_layout(&self) -> ChannelLayoutMask {
|
||||||
unsafe { ChannelLayout::from_bits_truncate((*self.as_ptr()).channel_layout) }
|
unsafe { ChannelLayoutMask::from_bits_truncate((*self.as_ptr()).channel_layout) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_channel_layout(&mut self, value: ChannelLayout) {
|
pub fn set_channel_layout(&mut self, value: ChannelLayoutMask) {
|
||||||
unsafe {
|
unsafe {
|
||||||
(*self.as_mut_ptr()).channel_layout = value.bits();
|
(*self.as_mut_ptr()).channel_layout = value.bits();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request_channel_layout(&mut self, value: ChannelLayout) {
|
pub fn request_channel_layout(&mut self, value: ChannelLayoutMask) {
|
||||||
unsafe {
|
unsafe {
|
||||||
(*self.as_mut_ptr()).request_channel_layout = value.bits();
|
(*self.as_mut_ptr()).request_channel_layout = value.bits();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ffmpeg_5_1")]
|
||||||
|
pub fn ch_layout(&self) -> ChannelLayout {
|
||||||
|
unsafe { ChannelLayout::from(&self.as_ptr().as_ref().unwrap().ch_layout) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ffmpeg_5_1")]
|
||||||
|
pub fn set_ch_layout(&mut self, value: ChannelLayout) {
|
||||||
|
unsafe {
|
||||||
|
self.as_mut_ptr().as_mut().unwrap().ch_layout = value.into_owned();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn audio_service(&mut self) -> AudioService {
|
pub fn audio_service(&mut self) -> AudioService {
|
||||||
unsafe { AudioService::from((*self.as_mut_ptr()).audio_service_type) }
|
unsafe { AudioService::from((*self.as_mut_ptr()).audio_service_type) }
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,10 @@ use crate::codec::{traits, Context};
|
|||||||
use crate::util::format;
|
use crate::util::format;
|
||||||
#[cfg(not(feature = "ffmpeg_5_0"))]
|
#[cfg(not(feature = "ffmpeg_5_0"))]
|
||||||
use crate::{frame, packet};
|
use crate::{frame, packet};
|
||||||
use crate::{ChannelLayout, Dictionary, Error};
|
use crate::{ChannelLayoutMask, Dictionary, Error};
|
||||||
|
|
||||||
|
#[cfg(feature = "ffmpeg_5_1")]
|
||||||
|
use crate::ChannelLayout;
|
||||||
|
|
||||||
pub struct Audio(pub Super);
|
pub struct Audio(pub Super);
|
||||||
|
|
||||||
@ -93,14 +96,14 @@ impl Audio {
|
|||||||
unsafe { format::Sample::from((*self.as_ptr()).sample_fmt) }
|
unsafe { format::Sample::from((*self.as_ptr()).sample_fmt) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_channel_layout(&mut self, value: ChannelLayout) {
|
pub fn set_channel_layout(&mut self, value: ChannelLayoutMask) {
|
||||||
unsafe {
|
unsafe {
|
||||||
(*self.as_mut_ptr()).channel_layout = value.bits();
|
(*self.as_mut_ptr()).channel_layout = value.bits();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn channel_layout(&self) -> ChannelLayout {
|
pub fn channel_layout(&self) -> ChannelLayoutMask {
|
||||||
unsafe { ChannelLayout::from_bits_truncate((*self.as_ptr()).channel_layout) }
|
unsafe { ChannelLayoutMask::from_bits_truncate((*self.as_ptr()).channel_layout) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_channels(&mut self, value: i32) {
|
pub fn set_channels(&mut self, value: i32) {
|
||||||
@ -112,6 +115,18 @@ impl Audio {
|
|||||||
pub fn channels(&self) -> u16 {
|
pub fn channels(&self) -> u16 {
|
||||||
unsafe { (*self.as_ptr()).channels as u16 }
|
unsafe { (*self.as_ptr()).channels as u16 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ffmpeg_5_1")]
|
||||||
|
pub fn ch_layout(&self) -> ChannelLayout {
|
||||||
|
unsafe { ChannelLayout::from(&self.as_ptr().as_ref().unwrap().ch_layout) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ffmpeg_5_1")]
|
||||||
|
pub fn set_ch_layout(&mut self, value: ChannelLayout) {
|
||||||
|
unsafe {
|
||||||
|
self.as_mut_ptr().as_mut().unwrap().ch_layout = value.into_owned();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Audio {
|
impl Deref for Audio {
|
||||||
|
@ -2,7 +2,7 @@ use std::marker::PhantomData;
|
|||||||
|
|
||||||
use super::{Sink, Source};
|
use super::{Sink, Source};
|
||||||
use crate::ffi::*;
|
use crate::ffi::*;
|
||||||
use crate::{format, option, ChannelLayout};
|
use crate::{format, option, ChannelLayoutMask};
|
||||||
use libc::c_void;
|
use libc::c_void;
|
||||||
|
|
||||||
pub struct Context<'a> {
|
pub struct Context<'a> {
|
||||||
@ -49,7 +49,7 @@ impl<'a> Context<'a> {
|
|||||||
let _ = option::Settable::set(self, "sample_rates", &i64::from(value));
|
let _ = option::Settable::set(self, "sample_rates", &i64::from(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_channel_layout(&mut self, value: ChannelLayout) {
|
pub fn set_channel_layout(&mut self, value: ChannelLayoutMask) {
|
||||||
let _ = option::Settable::set(self, "channel_layouts", &value.bits());
|
let _ = option::Settable::set(self, "channel_layouts", &value.bits());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,11 @@ pub use crate::sys as ffi;
|
|||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod util;
|
pub mod util;
|
||||||
pub use crate::util::channel_layout::{self, ChannelLayout};
|
pub use crate::util::channel_layout::{self, ChannelLayoutMask};
|
||||||
|
#[cfg(feature = "ffmpeg_5_1")]
|
||||||
|
pub use crate::util::channel_layout::{
|
||||||
|
Channel, ChannelCustom, ChannelLayout, ChannelLayoutIter, ChannelOrder,
|
||||||
|
};
|
||||||
pub use crate::util::chroma;
|
pub use crate::util::chroma;
|
||||||
pub use crate::util::color;
|
pub use crate::util::color;
|
||||||
pub use crate::util::dictionary;
|
pub use crate::util::dictionary;
|
||||||
|
@ -38,8 +38,8 @@ pub mod resampling;
|
|||||||
#[cfg(feature = "software-resampling")]
|
#[cfg(feature = "software-resampling")]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn resampler(
|
pub fn resampler(
|
||||||
(in_format, in_layout, in_rate): (crate::format::Sample, crate::ChannelLayout, u32),
|
(in_format, in_layout, in_rate): (crate::format::Sample, crate::ChannelLayoutMask, u32),
|
||||||
(out_format, out_layout, out_rate): (crate::format::Sample, crate::ChannelLayout, u32),
|
(out_format, out_layout, out_rate): (crate::format::Sample, crate::ChannelLayoutMask, u32),
|
||||||
) -> Result<resampling::Context, crate::Error> {
|
) -> Result<resampling::Context, crate::Error> {
|
||||||
resampling::Context::get(
|
resampling::Context::get(
|
||||||
in_format, in_layout, in_rate, out_format, out_layout, out_rate,
|
in_format, in_layout, in_rate, out_format, out_layout, out_rate,
|
||||||
|
@ -4,14 +4,14 @@ use super::Delay;
|
|||||||
use crate::ffi::*;
|
use crate::ffi::*;
|
||||||
use crate::util::format;
|
use crate::util::format;
|
||||||
use crate::Dictionary;
|
use crate::Dictionary;
|
||||||
use crate::{frame, ChannelLayout, Error};
|
use crate::{frame, ChannelLayoutMask, Error};
|
||||||
use libc::c_int;
|
use libc::c_int;
|
||||||
use std::ffi::c_void;
|
use std::ffi::c_void;
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Copy, Clone)]
|
#[derive(Eq, PartialEq, Copy, Clone)]
|
||||||
pub struct Definition {
|
pub struct Definition {
|
||||||
pub format: format::Sample,
|
pub format: format::Sample,
|
||||||
pub channel_layout: ChannelLayout,
|
pub channel_layout: ChannelLayoutMask,
|
||||||
pub rate: u32,
|
pub rate: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,10 +40,10 @@ impl Context {
|
|||||||
/// Create a resampler with the given definitions.
|
/// Create a resampler with the given definitions.
|
||||||
pub fn get(
|
pub fn get(
|
||||||
src_format: format::Sample,
|
src_format: format::Sample,
|
||||||
src_channel_layout: ChannelLayout,
|
src_channel_layout: ChannelLayoutMask,
|
||||||
src_rate: u32,
|
src_rate: u32,
|
||||||
dst_format: format::Sample,
|
dst_format: format::Sample,
|
||||||
dst_channel_layout: ChannelLayout,
|
dst_channel_layout: ChannelLayoutMask,
|
||||||
dst_rate: u32,
|
dst_rate: u32,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
Self::get_with(
|
Self::get_with(
|
||||||
@ -60,10 +60,10 @@ impl Context {
|
|||||||
/// Create a resampler with the given definitions and custom options dictionary.
|
/// Create a resampler with the given definitions and custom options dictionary.
|
||||||
pub fn get_with(
|
pub fn get_with(
|
||||||
src_format: format::Sample,
|
src_format: format::Sample,
|
||||||
src_channel_layout: ChannelLayout,
|
src_channel_layout: ChannelLayoutMask,
|
||||||
src_rate: u32,
|
src_rate: u32,
|
||||||
dst_format: format::Sample,
|
dst_format: format::Sample,
|
||||||
dst_channel_layout: ChannelLayout,
|
dst_channel_layout: ChannelLayoutMask,
|
||||||
dst_rate: u32,
|
dst_rate: u32,
|
||||||
options: Dictionary,
|
options: Dictionary,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
use super::Context;
|
use super::Context;
|
||||||
use crate::util::format;
|
use crate::util::format;
|
||||||
use crate::{decoder, frame, ChannelLayout, Error};
|
use crate::{decoder, frame, ChannelLayoutMask, Error};
|
||||||
|
|
||||||
impl frame::Audio {
|
impl frame::Audio {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn resampler(
|
pub fn resampler(
|
||||||
&self,
|
&self,
|
||||||
format: format::Sample,
|
format: format::Sample,
|
||||||
channel_layout: ChannelLayout,
|
channel_layout: ChannelLayoutMask,
|
||||||
rate: u32,
|
rate: u32,
|
||||||
) -> Result<Context, Error> {
|
) -> Result<Context, Error> {
|
||||||
Context::get(
|
Context::get(
|
||||||
@ -26,7 +26,7 @@ impl decoder::Audio {
|
|||||||
pub fn resampler(
|
pub fn resampler(
|
||||||
&self,
|
&self,
|
||||||
format: format::Sample,
|
format: format::Sample,
|
||||||
channel_layout: ChannelLayout,
|
channel_layout: ChannelLayoutMask,
|
||||||
rate: u32,
|
rate: u32,
|
||||||
) -> Result<Context, Error> {
|
) -> Result<Context, Error> {
|
||||||
Context::get(
|
Context::get(
|
||||||
|
270
src/util/channel_layout/channel.rs
Normal file
270
src/util/channel_layout/channel.rs
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
use crate::ffi::*;
|
||||||
|
|
||||||
|
use std::ffi::CString;
|
||||||
|
|
||||||
|
#[cfg(feature = "serialize")]
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
|
||||||
|
pub enum Channel {
|
||||||
|
None,
|
||||||
|
FrontLeft,
|
||||||
|
FrontRight,
|
||||||
|
FrontCenter,
|
||||||
|
LowFrequency,
|
||||||
|
BackLeft,
|
||||||
|
BackRight,
|
||||||
|
FrontLeftOfCenter,
|
||||||
|
FrontRightOfCenter,
|
||||||
|
BackCenter,
|
||||||
|
SideLeft,
|
||||||
|
SideRight,
|
||||||
|
TopCenter,
|
||||||
|
TopFrontLeft,
|
||||||
|
TopFrontCenter,
|
||||||
|
TopFrontRight,
|
||||||
|
TopBackLeft,
|
||||||
|
TopBackCenter,
|
||||||
|
TopBackRight,
|
||||||
|
StereoLeft,
|
||||||
|
StereoRight,
|
||||||
|
WideLeft,
|
||||||
|
WideRight,
|
||||||
|
SurroundDirectLeft,
|
||||||
|
SurroundDirectRight,
|
||||||
|
LowFrequency2,
|
||||||
|
TopSideLeft,
|
||||||
|
TopSideRight,
|
||||||
|
BottomFrontCenter,
|
||||||
|
BottomFrontLeft,
|
||||||
|
BottomFrontRight,
|
||||||
|
|
||||||
|
/// Channel is empty and can be safely skipped.
|
||||||
|
Unused,
|
||||||
|
|
||||||
|
/// Channel contains data, but its position is unknown.
|
||||||
|
Unknown,
|
||||||
|
|
||||||
|
/// Defines the start of channel IDs when using Ambisonic.
|
||||||
|
AmbisonicBase,
|
||||||
|
/// Defines the end of channel IDs when using Ambisonic.
|
||||||
|
AmbisonicEnd,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Channel {
|
||||||
|
/// Get an abbreviated, human-readable string describing this channel.
|
||||||
|
pub fn name(self) -> String {
|
||||||
|
let mut buf = vec![0u8; 32];
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let ret_val = av_channel_name(buf.as_mut_ptr() as _, buf.len(), AVChannel::from(self));
|
||||||
|
|
||||||
|
match usize::try_from(ret_val) {
|
||||||
|
Ok(out_len) if out_len > 0 => {
|
||||||
|
#[cfg(feature = "ffmpeg_6_1")]
|
||||||
|
// 6.1 changed out_len to include the NUL byte, which we don't want
|
||||||
|
let out_len = out_len - 1;
|
||||||
|
|
||||||
|
buf.truncate(out_len);
|
||||||
|
String::from_utf8_unchecked(buf)
|
||||||
|
}
|
||||||
|
// `av_channel_name` returned an error, or 0 bytes written.
|
||||||
|
_ => String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a human-readable string describing this channel.
|
||||||
|
pub fn description(self) -> String {
|
||||||
|
let mut buf = vec![0u8; 256];
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let ret_val =
|
||||||
|
av_channel_description(buf.as_mut_ptr() as _, buf.len(), AVChannel::from(self));
|
||||||
|
|
||||||
|
match usize::try_from(ret_val) {
|
||||||
|
Ok(out_len) if out_len > 0 => {
|
||||||
|
#[cfg(feature = "ffmpeg_6_1")]
|
||||||
|
// 6.1 changed out_len to include the NUL byte, which we don't want
|
||||||
|
let out_len = out_len - 1;
|
||||||
|
|
||||||
|
buf.truncate(out_len);
|
||||||
|
String::from_utf8_unchecked(buf)
|
||||||
|
}
|
||||||
|
// `av_channel_description` returned an error, or 0 bytes written.
|
||||||
|
_ => String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is the inverse function of [`name`][Channel::name].
|
||||||
|
pub fn from_string<S: AsRef<str>>(name: S) -> Self {
|
||||||
|
let cstr = CString::new(name.as_ref()).expect("no nul byte in name");
|
||||||
|
Self::from(unsafe { av_channel_from_string(cstr.as_ptr()) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AVChannel> for Channel {
|
||||||
|
fn from(value: AVChannel) -> Self {
|
||||||
|
use crate::ffi::AVChannel::*;
|
||||||
|
use Channel::*;
|
||||||
|
|
||||||
|
match value {
|
||||||
|
AV_CHAN_NONE => None,
|
||||||
|
AV_CHAN_FRONT_LEFT => FrontLeft,
|
||||||
|
AV_CHAN_FRONT_RIGHT => FrontRight,
|
||||||
|
AV_CHAN_FRONT_CENTER => FrontCenter,
|
||||||
|
AV_CHAN_LOW_FREQUENCY => LowFrequency,
|
||||||
|
AV_CHAN_BACK_LEFT => BackLeft,
|
||||||
|
AV_CHAN_BACK_RIGHT => BackRight,
|
||||||
|
AV_CHAN_FRONT_LEFT_OF_CENTER => FrontLeftOfCenter,
|
||||||
|
AV_CHAN_FRONT_RIGHT_OF_CENTER => FrontRightOfCenter,
|
||||||
|
AV_CHAN_BACK_CENTER => BackCenter,
|
||||||
|
AV_CHAN_SIDE_LEFT => SideLeft,
|
||||||
|
AV_CHAN_SIDE_RIGHT => SideRight,
|
||||||
|
AV_CHAN_TOP_CENTER => TopCenter,
|
||||||
|
AV_CHAN_TOP_FRONT_LEFT => TopFrontLeft,
|
||||||
|
AV_CHAN_TOP_FRONT_CENTER => TopFrontCenter,
|
||||||
|
AV_CHAN_TOP_FRONT_RIGHT => TopFrontRight,
|
||||||
|
AV_CHAN_TOP_BACK_LEFT => TopBackLeft,
|
||||||
|
AV_CHAN_TOP_BACK_CENTER => TopBackCenter,
|
||||||
|
AV_CHAN_TOP_BACK_RIGHT => TopBackRight,
|
||||||
|
AV_CHAN_STEREO_LEFT => StereoLeft,
|
||||||
|
AV_CHAN_STEREO_RIGHT => StereoRight,
|
||||||
|
AV_CHAN_WIDE_LEFT => WideLeft,
|
||||||
|
AV_CHAN_WIDE_RIGHT => WideRight,
|
||||||
|
AV_CHAN_SURROUND_DIRECT_LEFT => SurroundDirectLeft,
|
||||||
|
AV_CHAN_SURROUND_DIRECT_RIGHT => SurroundDirectRight,
|
||||||
|
AV_CHAN_LOW_FREQUENCY_2 => LowFrequency2,
|
||||||
|
AV_CHAN_TOP_SIDE_LEFT => TopSideLeft,
|
||||||
|
AV_CHAN_TOP_SIDE_RIGHT => TopSideRight,
|
||||||
|
AV_CHAN_BOTTOM_FRONT_CENTER => BottomFrontCenter,
|
||||||
|
AV_CHAN_BOTTOM_FRONT_LEFT => BottomFrontLeft,
|
||||||
|
AV_CHAN_BOTTOM_FRONT_RIGHT => BottomFrontRight,
|
||||||
|
AV_CHAN_UNUSED => Unused,
|
||||||
|
AV_CHAN_UNKNOWN => Unknown,
|
||||||
|
AV_CHAN_AMBISONIC_BASE => AmbisonicBase,
|
||||||
|
AV_CHAN_AMBISONIC_END => AmbisonicEnd,
|
||||||
|
|
||||||
|
#[cfg(feature = "non-exhaustive-enums")]
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Channel> for AVChannel {
|
||||||
|
fn from(value: Channel) -> Self {
|
||||||
|
use crate::ffi::AVChannel::*;
|
||||||
|
use Channel::*;
|
||||||
|
|
||||||
|
match value {
|
||||||
|
None => AV_CHAN_NONE,
|
||||||
|
FrontLeft => AV_CHAN_FRONT_LEFT,
|
||||||
|
FrontRight => AV_CHAN_FRONT_RIGHT,
|
||||||
|
FrontCenter => AV_CHAN_FRONT_CENTER,
|
||||||
|
LowFrequency => AV_CHAN_LOW_FREQUENCY,
|
||||||
|
BackLeft => AV_CHAN_BACK_LEFT,
|
||||||
|
BackRight => AV_CHAN_BACK_RIGHT,
|
||||||
|
FrontLeftOfCenter => AV_CHAN_FRONT_LEFT_OF_CENTER,
|
||||||
|
FrontRightOfCenter => AV_CHAN_FRONT_RIGHT_OF_CENTER,
|
||||||
|
BackCenter => AV_CHAN_BACK_CENTER,
|
||||||
|
SideLeft => AV_CHAN_SIDE_LEFT,
|
||||||
|
SideRight => AV_CHAN_SIDE_RIGHT,
|
||||||
|
TopCenter => AV_CHAN_TOP_CENTER,
|
||||||
|
TopFrontLeft => AV_CHAN_TOP_FRONT_LEFT,
|
||||||
|
TopFrontCenter => AV_CHAN_TOP_FRONT_CENTER,
|
||||||
|
TopFrontRight => AV_CHAN_TOP_FRONT_RIGHT,
|
||||||
|
TopBackLeft => AV_CHAN_TOP_BACK_LEFT,
|
||||||
|
TopBackCenter => AV_CHAN_TOP_BACK_CENTER,
|
||||||
|
TopBackRight => AV_CHAN_TOP_BACK_RIGHT,
|
||||||
|
StereoLeft => AV_CHAN_STEREO_LEFT,
|
||||||
|
StereoRight => AV_CHAN_STEREO_RIGHT,
|
||||||
|
WideLeft => AV_CHAN_WIDE_LEFT,
|
||||||
|
WideRight => AV_CHAN_WIDE_RIGHT,
|
||||||
|
SurroundDirectLeft => AV_CHAN_SURROUND_DIRECT_LEFT,
|
||||||
|
SurroundDirectRight => AV_CHAN_SURROUND_DIRECT_RIGHT,
|
||||||
|
LowFrequency2 => AV_CHAN_LOW_FREQUENCY_2,
|
||||||
|
TopSideLeft => AV_CHAN_TOP_SIDE_LEFT,
|
||||||
|
TopSideRight => AV_CHAN_TOP_SIDE_RIGHT,
|
||||||
|
BottomFrontCenter => AV_CHAN_BOTTOM_FRONT_CENTER,
|
||||||
|
BottomFrontLeft => AV_CHAN_BOTTOM_FRONT_LEFT,
|
||||||
|
BottomFrontRight => AV_CHAN_BOTTOM_FRONT_RIGHT,
|
||||||
|
Unused => AV_CHAN_UNUSED,
|
||||||
|
Unknown => AV_CHAN_UNKNOWN,
|
||||||
|
AmbisonicBase => AV_CHAN_AMBISONIC_BASE,
|
||||||
|
AmbisonicEnd => AV_CHAN_AMBISONIC_END,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// just test everything
|
||||||
|
const TEST_VALUES: &[Channel] = &[
|
||||||
|
Channel::None,
|
||||||
|
Channel::FrontLeft,
|
||||||
|
Channel::FrontRight,
|
||||||
|
Channel::FrontCenter,
|
||||||
|
Channel::LowFrequency,
|
||||||
|
Channel::BackLeft,
|
||||||
|
Channel::BackRight,
|
||||||
|
Channel::FrontLeftOfCenter,
|
||||||
|
Channel::FrontRightOfCenter,
|
||||||
|
Channel::BackCenter,
|
||||||
|
Channel::SideLeft,
|
||||||
|
Channel::SideRight,
|
||||||
|
Channel::TopCenter,
|
||||||
|
Channel::TopFrontLeft,
|
||||||
|
Channel::TopFrontCenter,
|
||||||
|
Channel::TopFrontRight,
|
||||||
|
Channel::TopBackLeft,
|
||||||
|
Channel::TopBackCenter,
|
||||||
|
Channel::TopBackRight,
|
||||||
|
Channel::StereoLeft,
|
||||||
|
Channel::StereoRight,
|
||||||
|
Channel::WideLeft,
|
||||||
|
Channel::WideRight,
|
||||||
|
Channel::SurroundDirectLeft,
|
||||||
|
Channel::SurroundDirectRight,
|
||||||
|
Channel::LowFrequency2,
|
||||||
|
Channel::TopSideLeft,
|
||||||
|
Channel::TopSideRight,
|
||||||
|
Channel::BottomFrontCenter,
|
||||||
|
Channel::BottomFrontLeft,
|
||||||
|
Channel::BottomFrontRight,
|
||||||
|
Channel::Unused,
|
||||||
|
Channel::Unknown,
|
||||||
|
Channel::AmbisonicBase,
|
||||||
|
Channel::AmbisonicEnd,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn name() {
|
||||||
|
for ch in TEST_VALUES {
|
||||||
|
let name = ch.name();
|
||||||
|
assert!(!name.is_empty());
|
||||||
|
println!("{name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn description() {
|
||||||
|
for ch in TEST_VALUES {
|
||||||
|
let desc = ch.description();
|
||||||
|
assert!(!desc.is_empty());
|
||||||
|
println!("{desc}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_string() {
|
||||||
|
for ch in TEST_VALUES {
|
||||||
|
let name = ch.name();
|
||||||
|
let found = Channel::from_string(name);
|
||||||
|
assert_eq!(found, *ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
90
src/util/channel_layout/channel_custom.rs
Normal file
90
src/util/channel_layout/channel_custom.rs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
use crate::ffi::{AVChannel, AVChannelCustom};
|
||||||
|
|
||||||
|
use super::Channel;
|
||||||
|
|
||||||
|
/// Wrapper around [`AVChannelCustom`][crate::ffi::AVChannelCustom].
|
||||||
|
///
|
||||||
|
/// This struct does not support reading or writing user data via the opaque pointer.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct ChannelCustom(AVChannelCustom);
|
||||||
|
|
||||||
|
impl ChannelCustom {
|
||||||
|
pub fn new(id: Channel) -> Self {
|
||||||
|
Self(AVChannelCustom {
|
||||||
|
id: AVChannel::from(id),
|
||||||
|
name: [0; 16],
|
||||||
|
opaque: std::ptr::null_mut(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn named<S: AsRef<str>>(id: Channel, name: S) -> Self {
|
||||||
|
let name = name.as_ref();
|
||||||
|
let name = to_char_array(name.as_bytes());
|
||||||
|
|
||||||
|
Self(AVChannelCustom {
|
||||||
|
id: AVChannel::from(id),
|
||||||
|
name,
|
||||||
|
opaque: std::ptr::null_mut(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_char_array(bytes: &[u8]) -> [i8; 16] {
|
||||||
|
let mut result = [0i8; 16];
|
||||||
|
|
||||||
|
// Only take the first 15 bytes, leaving at least one NUL byte
|
||||||
|
for (b, r) in bytes.iter().take(15).zip(&mut result) {
|
||||||
|
*r = *b as i8;
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AVChannelCustom> for ChannelCustom {
|
||||||
|
fn from(value: AVChannelCustom) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ChannelCustom> for AVChannelCustom {
|
||||||
|
fn from(value: ChannelCustom) -> Self {
|
||||||
|
value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use std::alloc::Layout;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_repr_transparent() {
|
||||||
|
// ChannelLayout::map relies on this being true.
|
||||||
|
assert_eq!(
|
||||||
|
Layout::new::<AVChannelCustom>(),
|
||||||
|
Layout::new::<ChannelCustom>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new() {
|
||||||
|
let custom = ChannelCustom::new(Channel::FrontRight);
|
||||||
|
assert_eq!(custom.0.id, AVChannel::AV_CHAN_FRONT_RIGHT);
|
||||||
|
assert_eq!(custom.0.name, [0; 16]);
|
||||||
|
assert!(custom.0.opaque.is_null());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn named() {
|
||||||
|
// "Bottom front ri\0"
|
||||||
|
let c_str_name = [
|
||||||
|
66, 111, 116, 116, 111, 109, 32, 102, 114, 111, 110, 116, 32, 114, 105, 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
let custom = ChannelCustom::named(Channel::BottomFrontRight, "Bottom front right");
|
||||||
|
assert_eq!(custom.0.id, AVChannel::AV_CHAN_BOTTOM_FRONT_RIGHT);
|
||||||
|
assert_eq!(custom.0.name, c_str_name);
|
||||||
|
assert!(custom.0.opaque.is_null());
|
||||||
|
}
|
||||||
|
}
|
44
src/util/channel_layout/iter.rs
Normal file
44
src/util/channel_layout/iter.rs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
use ffmpeg_sys_the_third::av_channel_layout_standard;
|
||||||
|
use libc::c_void;
|
||||||
|
|
||||||
|
use super::ChannelLayout;
|
||||||
|
|
||||||
|
pub struct ChannelLayoutIter {
|
||||||
|
opaque: *mut c_void,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChannelLayoutIter {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
opaque: std::ptr::null_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for ChannelLayoutIter {
|
||||||
|
type Item = ChannelLayout<'static>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
unsafe {
|
||||||
|
av_channel_layout_standard(&mut self.opaque as _)
|
||||||
|
.as_ref()
|
||||||
|
.map(ChannelLayout::from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn iter() {
|
||||||
|
let iter = ChannelLayoutIter::new();
|
||||||
|
let count = iter.count();
|
||||||
|
println!("{count}");
|
||||||
|
assert!(count > 0);
|
||||||
|
|
||||||
|
let mut iter = ChannelLayoutIter::new();
|
||||||
|
assert!(iter.all(|ch| ch.is_valid()));
|
||||||
|
}
|
||||||
|
}
|
473
src/util/channel_layout/layout.rs
Normal file
473
src/util/channel_layout/layout.rs
Normal file
@ -0,0 +1,473 @@
|
|||||||
|
use std::borrow::Borrow;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::ffi::CString;
|
||||||
|
use std::mem::{align_of, size_of};
|
||||||
|
|
||||||
|
use crate::ffi::*;
|
||||||
|
use libc::{c_int, c_uint};
|
||||||
|
|
||||||
|
use super::Channel;
|
||||||
|
use super::ChannelCustom;
|
||||||
|
use super::ChannelLayoutIter;
|
||||||
|
use super::ChannelLayoutMask;
|
||||||
|
use super::ChannelOrder;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct ChannelLayout<'a>(Cow<'a, AVChannelLayout>);
|
||||||
|
|
||||||
|
impl<'a> ChannelLayout<'a> {
|
||||||
|
/// Get a new channel layout with an unspecified channel ordering.
|
||||||
|
pub fn unspecified(channels: u32) -> Self {
|
||||||
|
let mut layout = AVChannelLayout::empty();
|
||||||
|
layout.order = AVChannelOrder::AV_CHANNEL_ORDER_UNSPEC;
|
||||||
|
layout.nb_channels = channels as c_int;
|
||||||
|
|
||||||
|
Self(Cow::Owned(layout))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn custom(channels: Vec<ChannelCustom>) -> Self {
|
||||||
|
#[cold]
|
||||||
|
fn alloc_failed(channels: usize) -> ! {
|
||||||
|
use std::alloc::{handle_alloc_error, Layout};
|
||||||
|
|
||||||
|
let alloc_size = channels * size_of::<AVChannelCustom>();
|
||||||
|
let layout =
|
||||||
|
Layout::from_size_align(alloc_size, align_of::<AVChannelCustom>()).unwrap();
|
||||||
|
handle_alloc_error(layout)
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut layout = AVChannelLayout::empty();
|
||||||
|
layout.order = AVChannelOrder::AV_CHANNEL_ORDER_CUSTOM;
|
||||||
|
layout.nb_channels = channels.len() as c_int;
|
||||||
|
unsafe {
|
||||||
|
layout.u.map = av_malloc_array(channels.len(), size_of::<AVChannelCustom>()) as _;
|
||||||
|
if layout.u.map.is_null() {
|
||||||
|
alloc_failed(channels.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, ch) in channels.into_iter().enumerate() {
|
||||||
|
std::ptr::write(layout.u.map.add(i), AVChannelCustom::from(ch));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self(Cow::Owned(layout))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the default channel layout for a given number of channels.
|
||||||
|
///
|
||||||
|
/// If no default layout exists for the given number of channels,
|
||||||
|
/// an unspecified layout will be returned.
|
||||||
|
pub fn default_for_channels(channels: u32) -> Self {
|
||||||
|
let mut layout = AVChannelLayout::empty();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
av_channel_layout_default(&mut layout as _, channels as c_int);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self(Cow::Owned(layout))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an iterator over all standard channel layouts.
|
||||||
|
pub fn standard_layouts() -> ChannelLayoutIter {
|
||||||
|
ChannelLayoutIter::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize a native channel layout from a bitmask indicating which
|
||||||
|
/// channels are present.
|
||||||
|
///
|
||||||
|
/// This will return [None] for invalid bitmask values.
|
||||||
|
pub fn from_mask(layout_mask: ChannelLayoutMask) -> Option<Self> {
|
||||||
|
let mut layout = AVChannelLayout::empty();
|
||||||
|
let ret = unsafe { av_channel_layout_from_mask(&mut layout as _, layout_mask.bits()) };
|
||||||
|
|
||||||
|
match ret {
|
||||||
|
0 => Some(Self(Cow::Owned(layout))),
|
||||||
|
// This should only ever return 0 or AVERROR(EINVAL)
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize a channel layout from a given string description.
|
||||||
|
///
|
||||||
|
/// This can be
|
||||||
|
/// - the formal channel layout name (as returned by [`description`][ChannelLayout::description]),
|
||||||
|
/// - one or more channel names concatenated with "+", each optionally containing a
|
||||||
|
/// custom name after an "@", e.g. "FL@Left+FR@Right+LFE",
|
||||||
|
/// - a decimal or hexadecimal value of a native channel layout (e.g. "4" or "0x4"),
|
||||||
|
/// - the number of channels with the default layout (e.g. "4c"),
|
||||||
|
/// - the number of unordered channels (e.g. "4C" or "4 channels") or
|
||||||
|
/// - the ambisonic order followed by optional non-diegetic channels (e.g. "ambisonic 2+stereo")
|
||||||
|
pub fn from_string<S: AsRef<str>>(description: S) -> Option<Self> {
|
||||||
|
let mut layout = AVChannelLayout::empty();
|
||||||
|
let cstr = CString::new(description.as_ref()).expect("no nul byte in description");
|
||||||
|
let ret = unsafe { av_channel_layout_from_string(&mut layout as _, cstr.as_ptr()) };
|
||||||
|
|
||||||
|
match ret {
|
||||||
|
0 => Some(Self(Cow::Owned(layout))),
|
||||||
|
// This should only ever return 0 or AVERROR_INVALIDDATA
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The [`ChannelOrder`][super::ChannelOrder] used in this layout.
|
||||||
|
pub fn order(&self) -> ChannelOrder {
|
||||||
|
ChannelOrder::from(self.0.order)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The number of channels in this layout.
|
||||||
|
pub fn channels(&self) -> u32 {
|
||||||
|
self.0.nb_channels as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If [`order`][ChannelLayout::order] is [`Native`][ChannelOrder::Native]:
|
||||||
|
/// A [`ChannelLayoutMask`] containing the channels of this layout.
|
||||||
|
///
|
||||||
|
/// If [`order`][ChannelLayout::order] is [`Ambisonic`][ChannelOrder::Ambisonic]:
|
||||||
|
/// A [`ChannelLayoutMask`] containing the non-diegetic channels of this layout.
|
||||||
|
///
|
||||||
|
/// Otherwise: [`None`].
|
||||||
|
pub fn mask(&self) -> Option<ChannelLayoutMask> {
|
||||||
|
match self.order() {
|
||||||
|
ChannelOrder::Unspecified | ChannelOrder::Custom => None,
|
||||||
|
ChannelOrder::Native | ChannelOrder::Ambisonic => unsafe {
|
||||||
|
Some(ChannelLayoutMask::from_bits_truncate(self.0.u.mask))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the custom channel map for this layout.
|
||||||
|
///
|
||||||
|
/// None if [`order`][ChannelLayout::order] is not [`Custom`][ChannelOrder::Custom].
|
||||||
|
pub fn map(&self) -> Option<&[ChannelCustom]> {
|
||||||
|
if self.order() != ChannelOrder::Custom {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
// SAFETY: ChannelCustom is repr(transparent) around AVChannelCustom
|
||||||
|
Some(std::slice::from_raw_parts(
|
||||||
|
self.0.u.map as _,
|
||||||
|
self.0.nb_channels as usize,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts the owned `AVChannelLayout`.
|
||||||
|
///
|
||||||
|
/// Clones it if not already owned.
|
||||||
|
pub fn into_owned(self) -> AVChannelLayout {
|
||||||
|
self.0.into_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exposes a pointer to the contained `AVChannelLayout` for FFI purposes.
|
||||||
|
///
|
||||||
|
/// This is guaranteed to be a non-null pointer.
|
||||||
|
pub fn as_ptr(&self) -> *const AVChannelLayout {
|
||||||
|
self.0.as_ref() as _
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a human-readable [`String`] describing the channel layout properties.
|
||||||
|
///
|
||||||
|
/// The returned string will be in the same format that is accepted by [`from_string`][ChannelLayout::from_string],
|
||||||
|
/// allowing to rebuild the same channel layout (excluding opaque pointers).
|
||||||
|
pub fn description(&self) -> String {
|
||||||
|
let mut buf = vec![0u8; 256];
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let ret_val =
|
||||||
|
av_channel_layout_describe(self.as_ptr(), buf.as_mut_ptr() as _, buf.len());
|
||||||
|
|
||||||
|
match usize::try_from(ret_val) {
|
||||||
|
Ok(out_len) if out_len > 0 => {
|
||||||
|
#[cfg(feature = "ffmpeg_6_1")]
|
||||||
|
// 6.1 changed out_len to include the NUL byte, which we don't want
|
||||||
|
let out_len = out_len - 1;
|
||||||
|
|
||||||
|
buf.truncate(out_len);
|
||||||
|
String::from_utf8_unchecked(buf)
|
||||||
|
}
|
||||||
|
// `av_channel_layout_describe` returned an error, or 0 bytes written.
|
||||||
|
_ => String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the channel with the given index in a channel layout.
|
||||||
|
///
|
||||||
|
/// Returns [`Channel::None`] when the index is invalid or the channel order is unspecified.
|
||||||
|
pub fn channel_from_index(&self, idx: u32) -> Channel {
|
||||||
|
Channel::from(unsafe { av_channel_layout_channel_from_index(self.as_ptr(), idx as c_uint) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the index of a given channel in a channel layout.
|
||||||
|
pub fn index_from_channel(&self, channel: Channel) -> Option<u32> {
|
||||||
|
unsafe {
|
||||||
|
u32::try_from(av_channel_layout_index_from_channel(
|
||||||
|
self.as_ptr(),
|
||||||
|
AVChannel::from(channel),
|
||||||
|
))
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the index in a channel layout of a channel described by the given string.
|
||||||
|
///
|
||||||
|
/// Returns the first match. Accepts channel names in the same format as [`from_string`][ChannelLayout::from_string].
|
||||||
|
pub fn index_from_string<S: AsRef<str>>(&self, name: S) -> Option<u32> {
|
||||||
|
let cstr = CString::new(name.as_ref()).expect("no nul byte in name");
|
||||||
|
let ret = unsafe { av_channel_layout_index_from_string(self.as_ptr(), cstr.as_ptr()) };
|
||||||
|
|
||||||
|
u32::try_from(ret).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a channel described by the given string.
|
||||||
|
///
|
||||||
|
/// Accepts channel names in the same format as [`from_string`][ChannelLayout::from_string].
|
||||||
|
///
|
||||||
|
/// Returns [`Channel::None`] when the string is invalid or the channel order is unspecified.
|
||||||
|
pub fn channel_from_string<S: AsRef<str>>(&self, name: S) -> Channel {
|
||||||
|
let cstr = CString::new(name.as_ref()).expect("no nul byte in name");
|
||||||
|
|
||||||
|
Channel::from(unsafe {
|
||||||
|
av_channel_layout_channel_from_string(self.as_ptr(), cstr.as_ptr())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find out what channels from a given set are present in this layout, without regard for their positions.
|
||||||
|
pub fn subset(&self, mask: ChannelLayoutMask) -> ChannelLayoutMask {
|
||||||
|
ChannelLayoutMask::from_bits_truncate(unsafe {
|
||||||
|
av_channel_layout_subset(self.as_ptr(), mask.bits())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether this layout is valid (i.e. can describe audio data).
|
||||||
|
#[doc(alias = "check")]
|
||||||
|
pub fn is_valid(&self) -> bool {
|
||||||
|
unsafe { av_channel_layout_check(self.as_ptr()) != 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<AVChannelLayout> for ChannelLayout<'a> {
|
||||||
|
fn from(value: AVChannelLayout) -> Self {
|
||||||
|
Self(Cow::Owned(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a AVChannelLayout> for ChannelLayout<'a> {
|
||||||
|
fn from(value: &'a AVChannelLayout) -> Self {
|
||||||
|
Self(Cow::Borrowed(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Borrow<AVChannelLayout> for ChannelLayout<'a> {
|
||||||
|
fn borrow(&self) -> &AVChannelLayout {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type alias to reduce line length below
|
||||||
|
type Scl = ChannelLayout<'static>;
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
impl<'a> ChannelLayout<'a> {
|
||||||
|
pub const MONO: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_MONO));
|
||||||
|
pub const STEREO: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_STEREO));
|
||||||
|
pub const _2POINT1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_2POINT1));
|
||||||
|
pub const _2_1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_2_1));
|
||||||
|
pub const SURROUND: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_SURROUND));
|
||||||
|
pub const _3POINT1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_3POINT1));
|
||||||
|
pub const _4POINT0: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_4POINT0));
|
||||||
|
pub const _4POINT1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_4POINT1));
|
||||||
|
pub const _2_2: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_2_2));
|
||||||
|
pub const QUAD: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_QUAD));
|
||||||
|
pub const _5POINT0: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_5POINT0));
|
||||||
|
pub const _5POINT1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_5POINT1));
|
||||||
|
pub const _5POINT0_BACK: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_5POINT0_BACK));
|
||||||
|
pub const _5POINT1_BACK: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_5POINT1_BACK));
|
||||||
|
pub const _6POINT0: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_6POINT0));
|
||||||
|
pub const _6POINT0_FRONT: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_6POINT0_FRONT));
|
||||||
|
pub const _3POINT1POINT2: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_3POINT1POINT2));
|
||||||
|
pub const HEXAGONAL: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_HEXAGONAL));
|
||||||
|
pub const _6POINT1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_6POINT1));
|
||||||
|
pub const _6POINT1_BACK: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_6POINT1_BACK));
|
||||||
|
pub const _6POINT1_FRONT: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_6POINT1_FRONT));
|
||||||
|
pub const _7POINT0: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT0));
|
||||||
|
pub const _7POINT0_FRONT: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT0_FRONT));
|
||||||
|
pub const _7POINT1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT1));
|
||||||
|
pub const _7POINT1_WIDE: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT1_WIDE));
|
||||||
|
pub const _7POINT1_WIDE_BACK: Scl =
|
||||||
|
ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT1_WIDE_BACK));
|
||||||
|
pub const _5POINT1POINT2_BACK: Scl =
|
||||||
|
ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_5POINT1POINT2_BACK));
|
||||||
|
pub const OCTAGONAL: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_OCTAGONAL));
|
||||||
|
pub const CUBE: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_CUBE));
|
||||||
|
pub const _5POINT1POINT4_BACK: Scl =
|
||||||
|
ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_5POINT1POINT4_BACK));
|
||||||
|
pub const _7POINT1POINT2: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT1POINT2));
|
||||||
|
pub const _7POINT1POINT4_BACK: Scl =
|
||||||
|
ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT1POINT4_BACK));
|
||||||
|
pub const HEXADECAGONAL: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_HEXADECAGONAL));
|
||||||
|
pub const STEREO_DOWNMIX: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_STEREO_DOWNMIX));
|
||||||
|
pub const _22POINT2: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_22POINT2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unspecified() {
|
||||||
|
let empty = ChannelLayout::unspecified(0);
|
||||||
|
assert_eq!(empty.order(), ChannelOrder::Unspecified);
|
||||||
|
assert_eq!(empty.channels(), 0);
|
||||||
|
assert!(!empty.is_valid());
|
||||||
|
|
||||||
|
let unspec = ChannelLayout::unspecified(42);
|
||||||
|
assert_eq!(unspec.order(), ChannelOrder::Unspecified);
|
||||||
|
assert_eq!(unspec.channels(), 42);
|
||||||
|
assert!(unspec.is_valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn custom() {
|
||||||
|
let channels = vec![
|
||||||
|
ChannelCustom::new(Channel::FrontLeft),
|
||||||
|
ChannelCustom::new(Channel::FrontRight),
|
||||||
|
ChannelCustom::named(Channel::LowFrequency, "bass"),
|
||||||
|
ChannelCustom::named(Channel::BackLeft, "back left"),
|
||||||
|
// name too long on purpose -> should truncate to 15 chars + NUL
|
||||||
|
ChannelCustom::named(Channel::BottomFrontCenter, "bottom front center"),
|
||||||
|
];
|
||||||
|
|
||||||
|
let custom = ChannelLayout::custom(channels.clone());
|
||||||
|
assert!(custom.is_valid());
|
||||||
|
assert_eq!(custom.channels(), 5);
|
||||||
|
assert_eq!(custom.order(), ChannelOrder::Custom);
|
||||||
|
assert_eq!(custom.map().unwrap(), &channels);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn defaults() {
|
||||||
|
let unspec = ChannelLayout::default_for_channels(0);
|
||||||
|
assert!(unspec.order() == ChannelOrder::Unspecified);
|
||||||
|
assert!(!unspec.is_valid());
|
||||||
|
|
||||||
|
for i in 1..12 {
|
||||||
|
let layout = ChannelLayout::default_for_channels(i);
|
||||||
|
assert_eq!(layout.channels(), i);
|
||||||
|
assert!(layout.is_valid(), "default layout invalid for {i} channels");
|
||||||
|
assert!(!layout.description().is_empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_mask() {
|
||||||
|
use ChannelLayout as Layout;
|
||||||
|
use ChannelLayoutMask as Mask;
|
||||||
|
|
||||||
|
assert_eq!(Layout::from_mask(Mask::empty()), None);
|
||||||
|
|
||||||
|
let tests = [
|
||||||
|
(Mask::MONO, Layout::MONO),
|
||||||
|
(Mask::STEREO, Layout::STEREO),
|
||||||
|
(Mask::_2POINT1, Layout::_2POINT1),
|
||||||
|
(Mask::_2_1, Layout::_2_1),
|
||||||
|
(Mask::SURROUND, Layout::SURROUND),
|
||||||
|
(Mask::_3POINT1, Layout::_3POINT1),
|
||||||
|
(Mask::_4POINT0, Layout::_4POINT0),
|
||||||
|
(Mask::_4POINT1, Layout::_4POINT1),
|
||||||
|
(Mask::_2_2, Layout::_2_2),
|
||||||
|
(Mask::QUAD, Layout::QUAD),
|
||||||
|
(Mask::_5POINT0, Layout::_5POINT0),
|
||||||
|
(Mask::_5POINT1, Layout::_5POINT1),
|
||||||
|
(Mask::_5POINT0_BACK, Layout::_5POINT0_BACK),
|
||||||
|
(Mask::_5POINT1_BACK, Layout::_5POINT1_BACK),
|
||||||
|
(Mask::_6POINT0, Layout::_6POINT0),
|
||||||
|
(Mask::_6POINT0_FRONT, Layout::_6POINT0_FRONT),
|
||||||
|
(Mask::HEXAGONAL, Layout::HEXAGONAL),
|
||||||
|
(Mask::_3POINT1POINT2, Layout::_3POINT1POINT2),
|
||||||
|
(Mask::_6POINT1, Layout::_6POINT1),
|
||||||
|
(Mask::_6POINT1_BACK, Layout::_6POINT1_BACK),
|
||||||
|
(Mask::_6POINT1_FRONT, Layout::_6POINT1_FRONT),
|
||||||
|
(Mask::_7POINT0, Layout::_7POINT0),
|
||||||
|
(Mask::_7POINT0_FRONT, Layout::_7POINT0_FRONT),
|
||||||
|
(Mask::_7POINT1, Layout::_7POINT1),
|
||||||
|
(Mask::_7POINT1_WIDE, Layout::_7POINT1_WIDE),
|
||||||
|
(Mask::_7POINT1_WIDE_BACK, Layout::_7POINT1_WIDE_BACK),
|
||||||
|
(Mask::_5POINT1POINT2_BACK, Layout::_5POINT1POINT2_BACK),
|
||||||
|
(Mask::OCTAGONAL, Layout::OCTAGONAL),
|
||||||
|
(Mask::CUBE, Layout::CUBE),
|
||||||
|
(Mask::_5POINT1POINT4_BACK, Layout::_5POINT1POINT4_BACK),
|
||||||
|
(Mask::_7POINT1POINT2, Layout::_7POINT1POINT2),
|
||||||
|
(Mask::_7POINT1POINT4_BACK, Layout::_7POINT1POINT4_BACK),
|
||||||
|
(Mask::HEXADECAGONAL, Layout::HEXADECAGONAL),
|
||||||
|
(Mask::STEREO_DOWNMIX, Layout::STEREO_DOWNMIX),
|
||||||
|
(Mask::_22POINT2, Layout::_22POINT2),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (mask, expected) in tests {
|
||||||
|
let result = Layout::from_mask(mask).expect("can find layout for bitmask");
|
||||||
|
assert_eq!(
|
||||||
|
result.order(),
|
||||||
|
ChannelOrder::Native,
|
||||||
|
"layout from mask must use native order"
|
||||||
|
);
|
||||||
|
assert_eq!(result.mask(), Some(mask));
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_string() {
|
||||||
|
let test_strings = [
|
||||||
|
("1 channels (FRC)", ChannelOrder::Native, 1),
|
||||||
|
("FL@Left+FR@Right+LFE", ChannelOrder::Custom, 3),
|
||||||
|
("0x4", ChannelOrder::Native, 1),
|
||||||
|
("4c", ChannelOrder::Native, 4),
|
||||||
|
("7 channels", ChannelOrder::Unspecified, 7),
|
||||||
|
("ambisonic 2+stereo", ChannelOrder::Ambisonic, 11),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (s, order, channels) in test_strings {
|
||||||
|
let result = ChannelLayout::from_string(s).expect("can find layout for description");
|
||||||
|
assert!(result.is_valid());
|
||||||
|
assert_eq!(result.order(), order);
|
||||||
|
assert_eq!(result.channels(), channels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn describe() {
|
||||||
|
use ChannelLayout as Layout;
|
||||||
|
use ChannelLayoutMask as Mask;
|
||||||
|
|
||||||
|
let tests = [
|
||||||
|
(Layout::MONO, "mono"),
|
||||||
|
(Layout::STEREO, "stereo"),
|
||||||
|
(Layout::_5POINT1, "5.1(side)"),
|
||||||
|
(
|
||||||
|
Layout::from_string("FL@Left+FR@Right+LFE").unwrap(),
|
||||||
|
"3 channels (FL@Left+FR@Right+LFE)",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Layout::from_mask(Mask::FRONT_RIGHT_OF_CENTER).unwrap(),
|
||||||
|
"1 channels (FRC)",
|
||||||
|
),
|
||||||
|
#[cfg(feature = "ffmpeg_6_1")]
|
||||||
|
(Layout::_7POINT1POINT4_BACK, "7.1.4"),
|
||||||
|
#[cfg(not(feature = "ffmpeg_6_1"))]
|
||||||
|
(
|
||||||
|
Layout::_7POINT1POINT4_BACK,
|
||||||
|
"12 channels (FL+FR+FC+LFE+BL+BR+SL+SR+TFL+TFR+TBL+TBR)",
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (layout, expected) in tests {
|
||||||
|
assert!(layout.is_valid());
|
||||||
|
|
||||||
|
let desc = layout.description();
|
||||||
|
assert!(!desc.is_empty());
|
||||||
|
assert_eq!(desc, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,7 @@ use crate::ffi::*;
|
|||||||
use libc::c_ulonglong;
|
use libc::c_ulonglong;
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
pub struct ChannelLayout: c_ulonglong {
|
pub struct ChannelLayoutMask: c_ulonglong {
|
||||||
const FRONT_LEFT = AV_CH_FRONT_LEFT;
|
const FRONT_LEFT = AV_CH_FRONT_LEFT;
|
||||||
const FRONT_RIGHT = AV_CH_FRONT_RIGHT;
|
const FRONT_RIGHT = AV_CH_FRONT_RIGHT;
|
||||||
const FRONT_CENTER = AV_CH_FRONT_CENTER;
|
const FRONT_CENTER = AV_CH_FRONT_CENTER;
|
||||||
@ -75,15 +75,17 @@ bitflags! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChannelLayout {
|
impl ChannelLayoutMask {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn channels(&self) -> i32 {
|
pub fn channels(&self) -> i32 {
|
||||||
unsafe { av_get_channel_layout_nb_channels(self.bits()) }
|
unsafe { av_get_channel_layout_nb_channels(self.bits()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn default(number: i32) -> ChannelLayout {
|
pub fn default(number: i32) -> ChannelLayoutMask {
|
||||||
unsafe {
|
unsafe {
|
||||||
ChannelLayout::from_bits_truncate(av_get_default_channel_layout(number) as c_ulonglong)
|
ChannelLayoutMask::from_bits_truncate(
|
||||||
|
av_get_default_channel_layout(number) as c_ulonglong
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
27
src/util/channel_layout/mod.rs
Normal file
27
src/util/channel_layout/mod.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
mod mask;
|
||||||
|
pub use mask::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "ffmpeg_5_1")]
|
||||||
|
mod channel_custom;
|
||||||
|
#[cfg(feature = "ffmpeg_5_1")]
|
||||||
|
pub use channel_custom::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "ffmpeg_5_1")]
|
||||||
|
mod channel;
|
||||||
|
#[cfg(feature = "ffmpeg_5_1")]
|
||||||
|
pub use channel::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "ffmpeg_5_1")]
|
||||||
|
mod iter;
|
||||||
|
#[cfg(feature = "ffmpeg_5_1")]
|
||||||
|
pub use iter::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "ffmpeg_5_1")]
|
||||||
|
mod layout;
|
||||||
|
#[cfg(feature = "ffmpeg_5_1")]
|
||||||
|
pub use layout::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "ffmpeg_5_1")]
|
||||||
|
mod order;
|
||||||
|
#[cfg(feature = "ffmpeg_5_1")]
|
||||||
|
pub use order::*;
|
47
src/util/channel_layout/order.rs
Normal file
47
src/util/channel_layout/order.rs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
use crate::ffi::AVChannelOrder;
|
||||||
|
|
||||||
|
use AVChannelOrder::*;
|
||||||
|
use ChannelOrder::*;
|
||||||
|
|
||||||
|
/// Specifies an order for audio channels.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum ChannelOrder {
|
||||||
|
/// No channel order. Only the channel count is specified.
|
||||||
|
Unspecified,
|
||||||
|
|
||||||
|
/// Native channel order, i.e. the channels are in the same order in which they
|
||||||
|
/// are defined in the [`Channel`][super::Channel] enum. This supports up to 63 channels.
|
||||||
|
Native,
|
||||||
|
|
||||||
|
/// The channel order does not correspond to any predefined order and is stored as an
|
||||||
|
/// explicit map. This can be used to support layouts with more than 64 channels or with
|
||||||
|
/// empty channels at arbitrary positions.
|
||||||
|
Custom,
|
||||||
|
|
||||||
|
/// The audio is represented as the decomposition of the sound field into spherical harmonics.
|
||||||
|
Ambisonic,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AVChannelOrder> for ChannelOrder {
|
||||||
|
fn from(value: AVChannelOrder) -> Self {
|
||||||
|
match value {
|
||||||
|
AV_CHANNEL_ORDER_UNSPEC => Unspecified,
|
||||||
|
AV_CHANNEL_ORDER_NATIVE => Native,
|
||||||
|
AV_CHANNEL_ORDER_CUSTOM => Custom,
|
||||||
|
AV_CHANNEL_ORDER_AMBISONIC => Ambisonic,
|
||||||
|
#[cfg(feature = "non-exhaustive-enums")]
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ChannelOrder> for AVChannelOrder {
|
||||||
|
fn from(value: ChannelOrder) -> Self {
|
||||||
|
match value {
|
||||||
|
Unspecified => AV_CHANNEL_ORDER_UNSPEC,
|
||||||
|
Native => AV_CHANNEL_ORDER_NATIVE,
|
||||||
|
Custom => AV_CHANNEL_ORDER_CUSTOM,
|
||||||
|
Ambisonic => AV_CHANNEL_ORDER_AMBISONIC,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,9 +5,12 @@ use std::slice;
|
|||||||
use super::Frame;
|
use super::Frame;
|
||||||
use crate::ffi::*;
|
use crate::ffi::*;
|
||||||
use crate::util::format;
|
use crate::util::format;
|
||||||
use crate::ChannelLayout;
|
use crate::ChannelLayoutMask;
|
||||||
use libc::{c_int, c_ulonglong};
|
use libc::{c_int, c_ulonglong};
|
||||||
|
|
||||||
|
#[cfg(feature = "ffmpeg_5_1")]
|
||||||
|
use crate::ChannelLayout;
|
||||||
|
|
||||||
#[derive(PartialEq, Eq)]
|
#[derive(PartialEq, Eq)]
|
||||||
pub struct Audio(Frame);
|
pub struct Audio(Frame);
|
||||||
|
|
||||||
@ -18,7 +21,12 @@ impl Audio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub unsafe fn alloc(&mut self, format: format::Sample, samples: usize, layout: ChannelLayout) {
|
pub unsafe fn alloc(
|
||||||
|
&mut self,
|
||||||
|
format: format::Sample,
|
||||||
|
samples: usize,
|
||||||
|
layout: ChannelLayoutMask,
|
||||||
|
) {
|
||||||
self.set_format(format);
|
self.set_format(format);
|
||||||
self.set_samples(samples);
|
self.set_samples(samples);
|
||||||
self.set_channel_layout(layout);
|
self.set_channel_layout(layout);
|
||||||
@ -34,7 +42,7 @@ impl Audio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(format: format::Sample, samples: usize, layout: ChannelLayout) -> Self {
|
pub fn new(format: format::Sample, samples: usize, layout: ChannelLayoutMask) -> Self {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut frame = Audio::empty();
|
let mut frame = Audio::empty();
|
||||||
frame.alloc(format, samples, layout);
|
frame.alloc(format, samples, layout);
|
||||||
@ -62,17 +70,33 @@ impl Audio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn channel_layout(&self) -> ChannelLayout {
|
pub fn channel_layout(&self) -> ChannelLayoutMask {
|
||||||
unsafe { ChannelLayout::from_bits_truncate((*self.as_ptr()).channel_layout as c_ulonglong) }
|
unsafe {
|
||||||
|
ChannelLayoutMask::from_bits_truncate((*self.as_ptr()).channel_layout as c_ulonglong)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_channel_layout(&mut self, value: ChannelLayout) {
|
pub fn set_channel_layout(&mut self, value: ChannelLayoutMask) {
|
||||||
unsafe {
|
unsafe {
|
||||||
(*self.as_mut_ptr()).channel_layout = value.bits() as u64;
|
(*self.as_mut_ptr()).channel_layout = value.bits() as u64;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ffmpeg_5_1")]
|
||||||
|
#[inline]
|
||||||
|
pub fn ch_layout(&self) -> ChannelLayout {
|
||||||
|
unsafe { ChannelLayout::from(&self.as_ref().unwrap().ch_layout) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ffmpeg_5_1")]
|
||||||
|
#[inline]
|
||||||
|
pub fn set_ch_layout(&mut self, value: ChannelLayout) {
|
||||||
|
unsafe {
|
||||||
|
self.as_mut().unwrap().ch_layout = value.into_owned();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn channels(&self) -> u16 {
|
pub fn channels(&self) -> u16 {
|
||||||
unsafe { (*self.as_ptr()).channels as u16 }
|
unsafe { (*self.as_ptr()).channels as u16 }
|
||||||
|
@ -58,6 +58,16 @@ impl Frame {
|
|||||||
self.ptr
|
self.ptr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn as_ref(&self) -> Option<&AVFrame> {
|
||||||
|
self.ptr.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn as_mut(&mut self) -> Option<&mut AVFrame> {
|
||||||
|
self.ptr.as_mut()
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub unsafe fn is_empty(&self) -> bool {
|
pub unsafe fn is_empty(&self) -> bool {
|
||||||
(*self.as_ptr()).data[0].is_null()
|
(*self.as_ptr()).data[0].is_null()
|
||||||
|
@ -5,7 +5,7 @@ use std::mem;
|
|||||||
|
|
||||||
use crate::ffi::*;
|
use crate::ffi::*;
|
||||||
use crate::util::format;
|
use crate::util::format;
|
||||||
use crate::{ChannelLayout, Error, Rational};
|
use crate::{ChannelLayoutMask, Error, Rational};
|
||||||
use libc::{c_int, c_void};
|
use libc::{c_int, c_void};
|
||||||
|
|
||||||
macro_rules! check {
|
macro_rules! check {
|
||||||
@ -130,7 +130,7 @@ pub trait Settable: Target {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_channel_layout(&mut self, name: &str, layout: ChannelLayout) -> Result<(), Error> {
|
fn set_channel_layout(&mut self, name: &str, layout: ChannelLayoutMask) -> Result<(), Error> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let name = CString::new(name).unwrap();
|
let name = CString::new(name).unwrap();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user