mod context;
mod convert;
pub mod audio_fifo;
pub mod channel_layout;
pub mod sample_format;
pub use context::{alloc, alloc_set_opts2, free, init, is_initialized};
pub use convert::{convert, estimate_output_samples, get_delay};
#[cfg(test)]
mod tests {
use super::*;
fn get_test_audio_path() -> std::path::PathBuf {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
std::path::PathBuf::from(format!(
"{}/../../assets/audio/konekonoosanpo.mp3",
manifest_dir
))
}
struct AudioDecoder {
format_ctx: *mut crate::AVFormatContext,
codec_ctx: *mut crate::AVCodecContext,
stream_index: i32,
frame: *mut crate::AVFrame,
packet: *mut crate::AVPacket,
}
impl AudioDecoder {
unsafe fn open(path: &std::path::Path) -> Result<Self, i32> {
use crate::{
AVMediaType_AVMEDIA_TYPE_AUDIO, av_frame_alloc, av_packet_alloc, avcodec,
avformat::{close_input, find_stream_info, open_input},
};
let format_ctx = open_input(path)?;
find_stream_info(format_ctx)?;
let mut stream_index = -1;
let nb_streams = (*format_ctx).nb_streams;
for i in 0..nb_streams {
let stream = *(*format_ctx).streams.add(i as usize);
let codecpar = (*stream).codecpar;
if (*codecpar).codec_type == AVMediaType_AVMEDIA_TYPE_AUDIO {
stream_index = i as i32;
break;
}
}
if stream_index < 0 {
close_input(&mut (format_ctx as *mut _));
return Err(crate::error_codes::EINVAL);
}
let stream = *(*format_ctx).streams.add(stream_index as usize);
let codecpar = (*stream).codecpar;
let codec =
avcodec::find_decoder((*codecpar).codec_id).ok_or(crate::error_codes::EINVAL)?;
let codec_ctx = avcodec::alloc_context3(codec)?;
avcodec::parameters_to_context(codec_ctx, codecpar)?;
avcodec::open2(codec_ctx, codec, std::ptr::null_mut())?;
let frame = av_frame_alloc();
if frame.is_null() {
avcodec::free_context(&mut (codec_ctx as *mut _));
close_input(&mut (format_ctx as *mut _));
return Err(crate::error_codes::ENOMEM);
}
let packet = av_packet_alloc();
if packet.is_null() {
crate::av_frame_free(&mut (frame as *mut _));
avcodec::free_context(&mut (codec_ctx as *mut _));
close_input(&mut (format_ctx as *mut _));
return Err(crate::error_codes::ENOMEM);
}
Ok(Self {
format_ctx,
codec_ctx,
stream_index,
frame,
packet,
})
}
unsafe fn decode_frame(&mut self) -> Option<&crate::AVFrame> {
use crate::{avcodec, avformat, error_codes};
loop {
match avcodec::receive_frame(self.codec_ctx, self.frame) {
Ok(()) => return Some(&*self.frame),
Err(e) if e == error_codes::EAGAIN => {
}
Err(_) => return None,
}
loop {
match avformat::read_frame(self.format_ctx, self.packet) {
Ok(()) => {
if (*self.packet).stream_index == self.stream_index {
break;
}
crate::av_packet_unref(self.packet);
}
Err(_) => {
let _ = avcodec::send_packet(self.codec_ctx, std::ptr::null());
match avcodec::receive_frame(self.codec_ctx, self.frame) {
Ok(()) => return Some(&*self.frame),
Err(_) => return None,
}
}
}
}
let _ = avcodec::send_packet(self.codec_ctx, self.packet);
crate::av_packet_unref(self.packet);
}
}
unsafe fn sample_format(&self) -> crate::AVSampleFormat {
(*self.codec_ctx).sample_fmt
}
unsafe fn sample_rate(&self) -> i32 {
(*self.codec_ctx).sample_rate
}
unsafe fn channel_layout(&self) -> &crate::AVChannelLayout {
&(*self.codec_ctx).ch_layout
}
}
impl Drop for AudioDecoder {
fn drop(&mut self) {
unsafe {
if !self.packet.is_null() {
crate::av_packet_free(&mut self.packet);
}
if !self.frame.is_null() {
crate::av_frame_free(&mut self.frame);
}
if !self.codec_ctx.is_null() {
crate::avcodec::free_context(&mut self.codec_ctx);
}
if !self.format_ctx.is_null() {
crate::avformat::close_input(&mut self.format_ctx);
}
}
}
}
#[test]
fn test_integration_decode_and_resample_mp3() {
let audio_path = get_test_audio_path();
if !audio_path.exists() {
eprintln!("Skipping test: audio file not found at {:?}", audio_path);
return;
}
unsafe {
let mut decoder = AudioDecoder::open(&audio_path).expect("Failed to open audio file");
let in_sample_rate = decoder.sample_rate();
let in_sample_fmt = decoder.sample_format();
let out_layout = channel_layout::stereo();
let in_layout = decoder.channel_layout();
let mut swr_ctx = alloc_set_opts2(
&out_layout,
sample_format::FLTP,
48000,
in_layout,
in_sample_fmt,
in_sample_rate,
)
.expect("Failed to create resampler context");
init(swr_ctx).expect("Failed to initialize resampler");
let mut total_input_samples = 0;
let mut total_output_samples = 0;
let mut frames_processed = 0;
while let Some(frame) = decoder.decode_frame() {
let nb_samples = (*frame).nb_samples;
if nb_samples <= 0 {
continue;
}
total_input_samples += nb_samples as usize;
let out_count = estimate_output_samples(48000, in_sample_rate, nb_samples);
let mut out_left: Vec<f32> = vec![0.0; out_count as usize];
let mut out_right: Vec<f32> = vec![0.0; out_count as usize];
let mut out_ptrs: [*mut u8; 2] =
[out_left.as_mut_ptr().cast(), out_right.as_mut_ptr().cast()];
let result = convert(
swr_ctx,
out_ptrs.as_mut_ptr(),
out_count,
(*frame).extended_data.cast(),
nb_samples,
);
assert!(
result.is_ok(),
"Conversion failed at frame {}",
frames_processed
);
let samples_out = result.unwrap();
total_output_samples += samples_out as usize;
frames_processed += 1;
if frames_processed >= 100 {
break;
}
}
let out_count = 4096;
let mut out_left: Vec<f32> = vec![0.0; out_count as usize];
let mut out_right: Vec<f32> = vec![0.0; out_count as usize];
let mut out_ptrs: [*mut u8; 2] =
[out_left.as_mut_ptr().cast(), out_right.as_mut_ptr().cast()];
let flush_result = convert(
swr_ctx,
out_ptrs.as_mut_ptr(),
out_count,
std::ptr::null(),
0,
);
if let Ok(flushed) = flush_result {
total_output_samples += flushed as usize;
}
free(&mut swr_ctx);
assert!(frames_processed > 0, "Should process at least one frame");
assert!(total_input_samples > 0, "Should have input samples");
assert!(total_output_samples > 0, "Should have output samples");
println!(
"Integration test: processed {} frames, {} input samples -> {} output samples",
frames_processed, total_input_samples, total_output_samples
);
}
}
#[test]
fn test_integration_format_conversion_chain() {
let audio_path = get_test_audio_path();
if !audio_path.exists() {
eprintln!("Skipping test: audio file not found at {:?}", audio_path);
return;
}
unsafe {
let mut decoder = AudioDecoder::open(&audio_path).expect("Failed to open audio file");
let in_sample_rate = decoder.sample_rate();
let in_sample_fmt = decoder.sample_format();
let in_layout = decoder.channel_layout();
let mid_layout = channel_layout::stereo();
let mut swr_to_s16 = alloc_set_opts2(
&mid_layout,
sample_format::S16,
44100,
in_layout,
in_sample_fmt,
in_sample_rate,
)
.expect("Failed to create S16 resampler");
init(swr_to_s16).expect("Failed to init S16 resampler");
let out_layout = channel_layout::stereo();
let mut swr_to_fltp = alloc_set_opts2(
&out_layout,
sample_format::FLTP,
48000,
&mid_layout,
sample_format::S16,
44100,
)
.expect("Failed to create FLTP resampler");
init(swr_to_fltp).expect("Failed to init FLTP resampler");
let mut frames_processed = 0;
while let Some(frame) = decoder.decode_frame() {
let nb_samples = (*frame).nb_samples;
if nb_samples <= 0 {
continue;
}
let mid_count = estimate_output_samples(44100, in_sample_rate, nb_samples);
let mut mid_data: Vec<i16> = vec![0; mid_count as usize * 2];
let mut mid_ptr = mid_data.as_mut_ptr() as *mut u8;
let mut mid_ptrs: [*mut u8; 1] = [mid_ptr];
let mid_result = convert(
swr_to_s16,
mid_ptrs.as_mut_ptr(),
mid_count,
(*frame).extended_data.cast(),
nb_samples,
);
assert!(mid_result.is_ok(), "S16 conversion failed");
let mid_samples = mid_result.unwrap();
let out_count = estimate_output_samples(48000, 44100, mid_samples);
let mut out_left: Vec<f32> = vec![0.0; out_count as usize];
let mut out_right: Vec<f32> = vec![0.0; out_count as usize];
let mut out_ptrs: [*mut u8; 2] =
[out_left.as_mut_ptr().cast(), out_right.as_mut_ptr().cast()];
let mid_in_ptr = mid_data.as_ptr() as *const u8;
let mid_in_ptrs: [*const u8; 1] = [mid_in_ptr];
let out_result = convert(
swr_to_fltp,
out_ptrs.as_mut_ptr(),
out_count,
mid_in_ptrs.as_ptr(),
mid_samples,
);
assert!(out_result.is_ok(), "FLTP conversion failed");
frames_processed += 1;
if frames_processed >= 50 {
break;
}
}
free(&mut swr_to_s16);
free(&mut swr_to_fltp);
assert!(frames_processed > 0, "Should process frames through chain");
println!(
"Format chain test: processed {} frames through S16 -> FLTP pipeline",
frames_processed
);
}
}
#[test]
fn test_integration_mono_to_stereo_conversion() {
let audio_path = get_test_audio_path();
if !audio_path.exists() {
eprintln!("Skipping test: audio file not found at {:?}", audio_path);
return;
}
unsafe {
let mut decoder = AudioDecoder::open(&audio_path).expect("Failed to open audio file");
let in_sample_rate = decoder.sample_rate();
let in_sample_fmt = decoder.sample_format();
let in_layout = decoder.channel_layout();
let mono_layout = channel_layout::mono();
let mut swr_to_mono = alloc_set_opts2(
&mono_layout,
sample_format::FLTP,
in_sample_rate,
in_layout,
in_sample_fmt,
in_sample_rate,
)
.expect("Failed to create mono resampler");
init(swr_to_mono).expect("Failed to init mono resampler");
let stereo_layout = channel_layout::stereo();
let mut swr_to_stereo = alloc_set_opts2(
&stereo_layout,
sample_format::FLTP,
48000,
&mono_layout,
sample_format::FLTP,
in_sample_rate,
)
.expect("Failed to create stereo resampler");
init(swr_to_stereo).expect("Failed to init stereo resampler");
let mut frames_processed = 0;
while let Some(frame) = decoder.decode_frame() {
let nb_samples = (*frame).nb_samples;
if nb_samples <= 0 {
continue;
}
let mono_count = nb_samples + 256;
let mut mono_data: Vec<f32> = vec![0.0; mono_count as usize];
let mut mono_ptr = mono_data.as_mut_ptr() as *mut u8;
let mut mono_ptrs: [*mut u8; 1] = [mono_ptr];
let mono_result = convert(
swr_to_mono,
mono_ptrs.as_mut_ptr(),
mono_count,
(*frame).extended_data.cast(),
nb_samples,
);
assert!(mono_result.is_ok(), "Mono conversion failed");
let mono_samples = mono_result.unwrap();
let stereo_count = estimate_output_samples(48000, in_sample_rate, mono_samples);
let mut stereo_left: Vec<f32> = vec![0.0; stereo_count as usize];
let mut stereo_right: Vec<f32> = vec![0.0; stereo_count as usize];
let mut stereo_ptrs: [*mut u8; 2] = [
stereo_left.as_mut_ptr().cast(),
stereo_right.as_mut_ptr().cast(),
];
let mono_in_ptr = mono_data.as_ptr() as *const u8;
let mono_in_ptrs: [*const u8; 1] = [mono_in_ptr];
let stereo_result = convert(
swr_to_stereo,
stereo_ptrs.as_mut_ptr(),
stereo_count,
mono_in_ptrs.as_ptr(),
mono_samples,
);
assert!(stereo_result.is_ok(), "Stereo conversion failed");
let stereo_samples = stereo_result.unwrap() as usize;
if stereo_samples > 10 {
for i in 0..10 {
let diff = (stereo_left[i] - stereo_right[i]).abs();
assert!(
diff < 0.001,
"Mono->stereo should have identical channels, diff={}",
diff
);
}
}
frames_processed += 1;
if frames_processed >= 30 {
break;
}
}
free(&mut swr_to_mono);
free(&mut swr_to_stereo);
assert!(frames_processed > 0, "Should process frames");
println!(
"Mono/stereo test: processed {} frames through mono->stereo pipeline",
frames_processed
);
}
}
}