use std::ffi::CStr;
use ff_format::channel::ChannelLayout;
use ff_format::stream::AudioStreamInfo;
use super::mapping::{map_audio_codec, map_sample_format};
use super::video::{extract_codec_name, extract_stream_bitrate, extract_stream_duration};
pub(super) unsafe fn extract_audio_streams(
ctx: *mut ff_sys::AVFormatContext,
) -> Vec<AudioStreamInfo> {
unsafe {
let nb_streams = (*ctx).nb_streams;
let streams_ptr = (*ctx).streams;
if streams_ptr.is_null() || nb_streams == 0 {
return Vec::new();
}
let mut audio_streams = Vec::new();
for i in 0..nb_streams {
let stream = *streams_ptr.add(i as usize);
if stream.is_null() {
continue;
}
let codecpar = (*stream).codecpar;
if codecpar.is_null() {
continue;
}
if (*codecpar).codec_type != ff_sys::AVMediaType_AVMEDIA_TYPE_AUDIO {
continue;
}
let stream_info = extract_single_audio_stream(stream, codecpar, i);
audio_streams.push(stream_info);
}
audio_streams
}
}
unsafe fn extract_single_audio_stream(
stream: *mut ff_sys::AVStream,
codecpar: *mut ff_sys::AVCodecParameters,
index: u32,
) -> AudioStreamInfo {
unsafe {
let codec_id = (*codecpar).codec_id;
let codec = map_audio_codec(codec_id);
let codec_name = extract_codec_name(codec_id);
#[expect(clippy::cast_sign_loss, reason = "sample_rate is always positive")]
let sample_rate = (*codecpar).sample_rate as u32;
let channels = extract_channel_count(codecpar);
let channel_layout = extract_channel_layout(codecpar, channels);
let sample_format = map_sample_format((*codecpar).format);
let bitrate = extract_stream_bitrate(codecpar);
let duration = extract_stream_duration(stream);
let language = extract_language(stream);
let mut builder = AudioStreamInfo::builder()
.index(index)
.codec(codec)
.codec_name(codec_name)
.sample_rate(sample_rate)
.channels(channels)
.channel_layout(channel_layout)
.sample_format(sample_format);
if let Some(d) = duration {
builder = builder.duration(d);
}
if let Some(b) = bitrate {
builder = builder.bitrate(b);
}
if let Some(lang) = language {
builder = builder.language(lang);
}
builder.build()
}
}
unsafe fn extract_channel_count(codecpar: *mut ff_sys::AVCodecParameters) -> u32 {
#[expect(clippy::cast_sign_loss, reason = "channel count is always positive")]
let channels = unsafe { (*codecpar).ch_layout.nb_channels as u32 };
if channels > 0 {
channels
} else {
log::warn!(
"channel_count is 0 (uninitialized), falling back to mono \
fallback=1"
);
1
}
}
unsafe fn extract_channel_layout(
codecpar: *mut ff_sys::AVCodecParameters,
channels: u32,
) -> ChannelLayout {
let ch_layout = unsafe { &(*codecpar).ch_layout };
if ch_layout.order == ff_sys::AVChannelOrder_AV_CHANNEL_ORDER_NATIVE {
let mask = unsafe { ch_layout.u.mask };
match mask {
0x4 => ChannelLayout::Mono,
0x3 => ChannelLayout::Stereo,
0x103 => ChannelLayout::Stereo2_1,
0x7 => ChannelLayout::Surround3_0,
0x33 => ChannelLayout::Quad,
0x37 => ChannelLayout::Surround5_0,
0x3F => ChannelLayout::Surround5_1,
0x13F => ChannelLayout::Surround6_1,
0x63F => ChannelLayout::Surround7_1,
_ => {
log::warn!(
"channel_layout mask has no mapping, deriving from channel count \
mask={mask} channels={channels}"
);
ChannelLayout::from_channels(channels)
}
}
} else {
log::warn!(
"channel_layout order is not NATIVE, deriving from channel count \
order={order} channels={channels}",
order = ch_layout.order
);
ChannelLayout::from_channels(channels)
}
}
pub(super) unsafe fn extract_language(stream: *mut ff_sys::AVStream) -> Option<String> {
unsafe {
let metadata = (*stream).metadata;
if metadata.is_null() {
return None;
}
let key = c"language";
let entry = ff_sys::av_dict_get(metadata, key.as_ptr(), std::ptr::null(), 0);
if entry.is_null() {
return None;
}
let value_ptr = (*entry).value;
if value_ptr.is_null() {
return None;
}
Some(CStr::from_ptr(value_ptr).to_string_lossy().into_owned())
}
}