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) fn extract_audio_streams(ctx: &ff_sys::InputFormatContext) -> Vec<AudioStreamInfo> {
ctx.streams()
.filter(|s| s.codecpar().codec_type() == ff_sys::AVMediaType_AVMEDIA_TYPE_AUDIO)
.map(extract_single_audio_stream)
.collect()
}
fn extract_single_audio_stream(stream: ff_sys::StreamRef<'_>) -> AudioStreamInfo {
let codecpar = stream.codecpar();
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);
#[expect(clippy::cast_sign_loss, reason = "stream index is always non-negative")]
let index = stream.index() as u32;
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()
}
fn extract_channel_count(codecpar: ff_sys::CodecParameters<'_>) -> u32 {
#[expect(clippy::cast_sign_loss, reason = "channel count is always positive")]
let channels = 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
}
}
fn extract_channel_layout(codecpar: ff_sys::CodecParameters<'_>, channels: u32) -> ChannelLayout {
let ch_layout = 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) fn extract_language(stream: ff_sys::StreamRef<'_>) -> Option<String> {
stream.metadata().remove("language")
}