use core::{ffi::c_char, slice};
use ffmpeg_next::{ChannelLayout, ffi};
use mediadecode::channel::{
AudioChannelLayout, AudioChannelOrderKind, AudioChannelSpec, ChannelLayoutKind,
};
use smol_str::SmolStr;
use std::vec::Vec;
pub fn channel_layout_kind_from_ffmpeg(value: &ChannelLayout) -> ChannelLayoutKind {
match () {
() if value.eq(&ChannelLayout::MONO) => ChannelLayoutKind::Mono,
() if value.eq(&ChannelLayout::STEREO) => ChannelLayoutKind::Stereo,
() if value.eq(&ChannelLayout::STEREO_DOWNMIX) => ChannelLayoutKind::StereoDownmix,
() if value.eq(&ChannelLayout::SURROUND) => ChannelLayoutKind::Surround,
() if value.eq(&ChannelLayout::QUAD) => ChannelLayoutKind::Quad,
() if value.eq(&ChannelLayout::HEXAGONAL) => ChannelLayoutKind::Hexagonal,
() if value.eq(&ChannelLayout::OCTAGONAL) => ChannelLayoutKind::Octagonal,
() if value.eq(&ChannelLayout::HEXADECAGONAL) => ChannelLayoutKind::Hexadecagonal,
() if value.eq(&ChannelLayout::CUBE) => ChannelLayoutKind::Cube,
() if value.eq(&ChannelLayout::_2POINT1) => ChannelLayoutKind::Ch2_1,
() if value.eq(&ChannelLayout::_2_1) => ChannelLayoutKind::Ch2_1Alt,
() if value.eq(&ChannelLayout::_2_2) => ChannelLayoutKind::Ch2_2,
() if value.eq(&ChannelLayout::_3POINT1) => ChannelLayoutKind::Ch3_1,
() if value.eq(&ChannelLayout::_3POINT1POINT2) => ChannelLayoutKind::Ch3_1_2,
() if value.eq(&ChannelLayout::_4POINT0) => ChannelLayoutKind::Ch4_0,
() if value.eq(&ChannelLayout::_4POINT1) => ChannelLayoutKind::Ch4_1,
() if value.eq(&ChannelLayout::_5POINT0) => ChannelLayoutKind::Ch5_0,
() if value.eq(&ChannelLayout::_5POINT0_BACK) => ChannelLayoutKind::Ch5_0Back,
() if value.eq(&ChannelLayout::_5POINT1) => ChannelLayoutKind::Ch5_1,
() if value.eq(&ChannelLayout::_5POINT1_BACK) => ChannelLayoutKind::Ch5_1Back,
() if value.eq(&ChannelLayout::_5POINT1POINT2_BACK) => ChannelLayoutKind::Ch5_1_2Back,
() if value.eq(&ChannelLayout::_5POINT1POINT4_BACK) => ChannelLayoutKind::Ch5_1_4Back,
() if value.eq(&ChannelLayout::_6POINT0) => ChannelLayoutKind::Ch6_0,
() if value.eq(&ChannelLayout::_6POINT0_FRONT) => ChannelLayoutKind::Ch6_0Front,
() if value.eq(&ChannelLayout::_6POINT1) => ChannelLayoutKind::Ch6_1,
() if value.eq(&ChannelLayout::_6POINT1_BACK) => ChannelLayoutKind::Ch6_1Back,
() if value.eq(&ChannelLayout::_6POINT1_FRONT) => ChannelLayoutKind::Ch6_1Front,
() if value.eq(&ChannelLayout::_7POINT0) => ChannelLayoutKind::Ch7_0,
() if value.eq(&ChannelLayout::_7POINT0_FRONT) => ChannelLayoutKind::Ch7_0Front,
() if value.eq(&ChannelLayout::_7POINT1) => ChannelLayoutKind::Ch7_1,
() if value.eq(&ChannelLayout::_7POINT1_WIDE) => ChannelLayoutKind::Ch7_1Wide,
() if value.eq(&ChannelLayout::_7POINT1_WIDE_BACK) => ChannelLayoutKind::Ch7_1WideBack,
() if value.eq(&ChannelLayout::_7POINT1_TOP_BACK) => ChannelLayoutKind::Ch7_1TopBack,
() if value.eq(&ChannelLayout::_7POINT1POINT2) => ChannelLayoutKind::Ch7_1_2,
() if value.eq(&ChannelLayout::_7POINT1POINT4_BACK) => ChannelLayoutKind::Ch7_1_4Back,
() if value.eq(&ChannelLayout::_7POINT2POINT3) => ChannelLayoutKind::Ch7_2_3,
() if value.eq(&ChannelLayout::_9POINT1POINT4_BACK) => ChannelLayoutKind::Ch9_1_4Back,
() if value.eq(&ChannelLayout::_22POINT2) => ChannelLayoutKind::Ch22_2,
() => ChannelLayoutKind::Unknown,
}
}
pub fn audio_channel_order_kind_from_ffmpeg(value: ffi::AVChannelOrder) -> AudioChannelOrderKind {
audio_channel_order_kind_from_raw(value as i32)
}
pub fn audio_channel_order_kind_from_raw(raw: i32) -> AudioChannelOrderKind {
match raw {
x if x == ffi::AVChannelOrder::AV_CHANNEL_ORDER_NATIVE as i32 => AudioChannelOrderKind::Native,
x if x == ffi::AVChannelOrder::AV_CHANNEL_ORDER_CUSTOM as i32 => AudioChannelOrderKind::Custom,
x if x == ffi::AVChannelOrder::AV_CHANNEL_ORDER_AMBISONIC as i32 => {
AudioChannelOrderKind::Ambisonic
}
_ => AudioChannelOrderKind::Unspecified,
}
}
pub fn audio_channel_layout_from_ffmpeg(value: &ChannelLayout) -> AudioChannelLayout {
unsafe { audio_channel_layout_from_raw_ptr(&value.0 as *const ffi::AVChannelLayout) }
}
pub unsafe fn audio_channel_layout_from_raw_ptr(
ptr: *const ffi::AVChannelLayout,
) -> AudioChannelLayout {
use core::ptr::{addr_of, read_unaligned};
let order_raw = unsafe { read_unaligned(addr_of!((*ptr).order) as *const i32) };
let order = audio_channel_order_kind_from_raw(order_raw);
let nb_channels = unsafe { (*ptr).nb_channels };
let native_mask = match order {
AudioChannelOrderKind::Native | AudioChannelOrderKind::Ambisonic => {
let mask = unsafe { (*ptr).u.mask };
if mask != 0 { Some(mask) } else { None }
}
_ => None,
};
let known_kind = if matches!(order, AudioChannelOrderKind::Unspecified) {
ChannelLayoutKind::Unknown
} else {
let layout_ref = unsafe { &*(ptr as *const ChannelLayout) };
channel_layout_kind_from_ffmpeg(layout_ref)
};
let custom_channels_vec = unsafe { custom_channels_raw(ptr, order) };
let description = if matches!(order, AudioChannelOrderKind::Unspecified) {
SmolStr::default()
} else {
let layout_ref = unsafe { &*(ptr as *const ChannelLayout) };
describe_layout(layout_ref)
};
AudioChannelLayout::new(nb_channels.max(0) as u32)
.with_order(order)
.with_known_kind(known_kind)
.with_native_mask(native_mask)
.with_custom_channels(custom_channels_vec)
.with_description(description)
}
unsafe fn custom_channels_raw(
ptr: *const ffi::AVChannelLayout,
order: AudioChannelOrderKind,
) -> Vec<AudioChannelSpec> {
use core::ptr::{addr_of, read_unaligned};
if !matches!(order, AudioChannelOrderKind::Custom) {
return Vec::new();
}
let count = unsafe { (*ptr).nb_channels }.max(0) as usize;
if count == 0 {
return Vec::new();
}
let map_ptr = unsafe { (*ptr).u.map };
if map_ptr.is_null() {
return Vec::new();
}
let mut out = Vec::with_capacity(count);
for index in 0..count {
let entry_ptr: *const ffi::AVChannelCustom = unsafe { map_ptr.add(index) };
let raw_id = unsafe { read_unaligned(addr_of!((*entry_ptr).id) as *const i32) };
let label = unsafe { custom_channel_label_raw(entry_ptr) };
out.push(AudioChannelSpec::new(index as u32, raw_id as u32).with_label(label));
}
out
}
unsafe fn custom_channel_label_raw(entry_ptr: *const ffi::AVChannelCustom) -> SmolStr {
use core::ptr::addr_of;
let name_ptr = unsafe { addr_of!((*entry_ptr).name) } as *const u8;
let bytes = unsafe { slice::from_raw_parts(name_ptr, 16) };
let end = bytes
.iter()
.position(|byte| *byte == 0)
.unwrap_or(bytes.len());
if end == 0 {
return SmolStr::default();
}
SmolStr::new(std::string::String::from_utf8_lossy(&bytes[..end]))
}
#[allow(dead_code)]
fn custom_channels(layout: &ChannelLayout) -> Vec<AudioChannelSpec> {
use core::ptr::{addr_of, read_unaligned};
let order_raw = unsafe { read_unaligned(addr_of!(layout.0.order) as *const i32) };
if order_raw != ffi::AVChannelOrder::AV_CHANNEL_ORDER_CUSTOM as i32 {
return Vec::new();
}
let count = layout.0.nb_channels.max(0) as usize;
if count == 0 {
return Vec::new();
}
let ptr = unsafe { layout.0.u.map };
if ptr.is_null() {
return Vec::new();
}
let slice_ref = unsafe { slice::from_raw_parts(ptr, count) };
slice_ref
.iter()
.enumerate()
.map(|(index, channel)| {
let raw_id = unsafe { read_unaligned(addr_of!(channel.id) as *const i32) };
AudioChannelSpec::new(index as u32, raw_id as u32).with_label(custom_channel_label(channel))
})
.collect()
}
fn custom_channel_label(channel: &ffi::AVChannelCustom) -> SmolStr {
let bytes =
unsafe { slice::from_raw_parts(channel.name.as_ptr() as *const u8, channel.name.len()) };
let end = bytes
.iter()
.position(|byte| *byte == 0)
.unwrap_or(bytes.len());
if end == 0 {
return SmolStr::default();
}
SmolStr::new(std::string::String::from_utf8_lossy(&bytes[..end]))
}
fn describe_layout(layout: &ChannelLayout) -> SmolStr {
let mut buf = std::vec![0 as c_char; 128];
let mut needed =
unsafe { ffi::av_channel_layout_describe(&layout.0 as *const _, buf.as_mut_ptr(), buf.len()) };
if needed < 0 {
return SmolStr::default();
}
if needed as usize >= buf.len() {
buf.resize(needed as usize + 1, 0 as c_char);
needed = unsafe {
ffi::av_channel_layout_describe(&layout.0 as *const _, buf.as_mut_ptr(), buf.len())
};
if needed < 0 {
return SmolStr::default();
}
}
let bytes = unsafe { slice::from_raw_parts(buf.as_ptr() as *const u8, buf.len()) };
let end = bytes
.iter()
.position(|byte| *byte == 0)
.unwrap_or(needed as usize);
if end == 0 {
return SmolStr::default();
}
SmolStr::new(std::string::String::from_utf8_lossy(&bytes[..end]))
}