use crate::audio_decode::{AudioBuffer, AudioDecodeError};
use std::path::Path;
pub fn decode_with_ffmpeg(path: &Path) -> Result<AudioBuffer, AudioDecodeError> {
ffmpeg_next::init().map_err(|e| AudioDecodeError::DecodeFailed(format!("ffmpeg init: {}", e)))?;
let mut input = ffmpeg_next::format::input(&path)
.map_err(|e| AudioDecodeError::IoError(format!("{}: {}", path.display(), e)))?;
let audio_stream_index = input
.streams()
.best(ffmpeg_next::media::Type::Audio)
.ok_or(AudioDecodeError::NoAudioStream)?
.index();
let stream = input
.stream(audio_stream_index)
.ok_or(AudioDecodeError::NoAudioStream)?;
let codec_params = stream.parameters();
let sample_rate;
let channels;
let context = ffmpeg_next::codec::context::Context::from_parameters(codec_params)
.map_err(|e| AudioDecodeError::DecodeFailed(format!("codec context: {}", e)))?;
let mut decoder = context
.decoder()
.audio()
.map_err(|e| AudioDecodeError::DecodeFailed(format!("audio decoder: {}", e)))?;
sample_rate = decoder.rate();
channels = decoder.channels() as u16;
let mut resampler = ffmpeg_next::software::resampling::context::Context::get(
decoder.format(),
decoder.channel_layout(),
decoder.rate(),
ffmpeg_next::format::Sample::F32(ffmpeg_next::format::sample::Type::Packed),
ffmpeg_next::ChannelLayout::MONO,
decoder.rate(),
)
.map_err(|e| AudioDecodeError::DecodeFailed(format!("resampler: {}", e)))?;
let mut all_samples: Vec<f32> = Vec::new();
let mut decoded_frame = ffmpeg_next::frame::Audio::empty();
for (stream_idx, packet) in input.packets() {
if stream_idx.index() != audio_stream_index {
continue;
}
decoder
.send_packet(&packet)
.map_err(|e| AudioDecodeError::DecodeFailed(format!("send_packet: {}", e)))?;
while decoder.receive_frame(&mut decoded_frame).is_ok() {
let mut resampled = ffmpeg_next::frame::Audio::empty();
resampler
.run(&decoded_frame, &mut resampled)
.map_err(|e| AudioDecodeError::DecodeFailed(format!("resample: {}", e)))?;
let data = resampled.data(0);
let sample_count = resampled.samples();
let float_slice: &[f32] = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, sample_count) };
all_samples.extend_from_slice(float_slice);
}
}
decoder
.send_eof()
.map_err(|e| AudioDecodeError::DecodeFailed(format!("send_eof: {}", e)))?;
while decoder.receive_frame(&mut decoded_frame).is_ok() {
let mut resampled = ffmpeg_next::frame::Audio::empty();
resampler
.run(&decoded_frame, &mut resampled)
.map_err(|e| AudioDecodeError::DecodeFailed(format!("flush resample: {}", e)))?;
let data = resampled.data(0);
let sample_count = resampled.samples();
let float_slice: &[f32] = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, sample_count) };
all_samples.extend_from_slice(float_slice);
}
if all_samples.is_empty() {
return Err(AudioDecodeError::DecodeFailed("decoded zero samples".into()));
}
let format_name = path
.extension()
.and_then(|e| e.to_str())
.unwrap_or("unknown")
.to_lowercase();
log::info!(
"FFmpeg decode: {} ({} samples, {}Hz, {}ch → mono f32)",
path.display(),
all_samples.len(),
sample_rate,
channels
);
Ok(AudioBuffer {
samples: all_samples,
sample_rate,
original_channels: channels,
format: format_name,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decode_nonexistent_ffmpeg() {
let result = decode_with_ffmpeg(Path::new("nonexistent.mp3"));
assert!(result.is_err());
}
}