use std::ffi::{CStr, CString};
use std::path::Path;
use std::ptr::{self, NonNull};
use crate::avio::ReadAvio;
use crate::error::{Error, FFmpegError};
use crate::sys;
use crate::util::{Frame, Packet};
use crate::{AudioBuffer, AudioInfo, ChannelLayout, LoadOptions, SampleData};
const INITIAL_CAPACITY: usize = 4096;
struct FormatCtx<'a>(
NonNull<sys::AVFormatContext>,
) alive until the
#[allow(dead_code)] Option<ReadAvio<'a>>,
);
impl<'a> FormatCtx<'a> {
fn finish(ptr: *mut sys::AVFormatContext, avio: Option<ReadAvio<'a>>) -> Result<Self, Error> {
unsafe {
let ctx = FormatCtx(NonNull::new_unchecked(ptr), avio);
let ret = sys::avformat_find_stream_info(ctx.0.as_ptr(), ptr::null_mut());
if ret < 0 {
return Err(Error::FindStreamInfo(FFmpegError(ret)));
}
Ok(ctx)
}
}
fn open_path(path: &CStr) -> Result<Self, Error> {
unsafe {
let mut ptr: *mut sys::AVFormatContext = ptr::null_mut();
let ret =
sys::avformat_open_input(&mut ptr, path.as_ptr(), ptr::null(), ptr::null_mut());
if ret < 0 {
return Err(Error::OpenInput(FFmpegError(ret)));
}
Self::finish(ptr, None)
}
}
fn open_bytes(data: &'a [u8]) -> Result<Self, Error> {
let avio = ReadAvio::new(data)?;
unsafe {
let ctx = sys::avformat_alloc_context();
if ctx.is_null() {
return Err(Error::OutOfMemory);
}
(*ctx).pb = avio.as_ptr();
(*ctx).flags |= sys::AVFMT_FLAG_CUSTOM_IO as i32;
let mut ptr = ctx;
let ret = sys::avformat_open_input(&mut ptr, ptr::null(), ptr::null(), ptr::null_mut());
if ret < 0 {
return Err(Error::OpenInput(FFmpegError(ret)));
}
Self::finish(ptr, Some(avio))
}
}
}
impl Drop for FormatCtx<'_> {
fn drop(&mut self) {
unsafe {
let mut p = self.0.as_ptr();
sys::avformat_close_input(&mut p);
}
}
}
struct CodecCtx(NonNull<sys::AVCodecContext>);
#[derive(Clone, Copy)]
struct StreamTimeBase {
num: i32,
den: i32,
}
impl StreamTimeBase {
fn as_av(self) -> sys::AVRational {
sys::AVRational {
num: self.num,
den: self.den,
}
}
}
impl CodecCtx {
fn open_audio(fmt: &FormatCtx<'_>) -> Result<(i32, Self, StreamTimeBase), Error> {
unsafe {
let idx = sys::av_find_best_stream(
fmt.0.as_ptr(),
sys::AVMEDIA_TYPE_AUDIO,
-1,
-1,
ptr::null_mut(),
0,
);
if idx < 0 {
return Err(Error::NoAudioStream);
}
let stream = *(*fmt.0.as_ptr()).streams.add(idx as usize);
let codecpar = (*stream).codecpar;
let codec = sys::avcodec_find_decoder((*codecpar).codec_id);
if codec.is_null() {
return Err(Error::DecoderNotFound((*codecpar).codec_id as u32));
}
let ptr =
NonNull::new(sys::avcodec_alloc_context3(codec)).ok_or(Error::AllocCodecContext)?;
let ctx = CodecCtx(ptr);
let ret = sys::avcodec_parameters_to_context(ctx.0.as_ptr(), codecpar);
if ret < 0 {
return Err(Error::CodecParameters(FFmpegError(ret)));
}
let stb = StreamTimeBase {
num: (*stream).time_base.num,
den: (*stream).time_base.den,
};
(*ctx.0.as_ptr()).pkt_timebase = stb.as_av();
let ret = sys::avcodec_open2(ctx.0.as_ptr(), codec, ptr::null_mut());
if ret < 0 {
return Err(Error::OpenCodec(FFmpegError(ret)));
}
Ok((idx, ctx, stb))
}
}
fn channels(&self) -> i32 {
unsafe { (*self.0.as_ptr()).ch_layout.nb_channels }
}
fn sample_rate(&self) -> i32 {
unsafe { (*self.0.as_ptr()).sample_rate }
}
fn sample_fmt(&self) -> i32 {
unsafe { (*self.0.as_ptr()).sample_fmt }
}
}
impl Drop for CodecCtx {
fn drop(&mut self) {
unsafe {
let mut p = self.0.as_ptr();
sys::avcodec_free_context(&mut p);
}
}
}
struct SwrCtx(NonNull<sys::SwrContext>);
impl SwrCtx {
fn new(
input: &CodecCtx,
out_channels: i32,
out_rate: i32,
out_fmt: i32,
) -> Result<Self, Error> {
unsafe {
let mut out_layout = std::mem::zeroed::<sys::AVChannelLayout>();
sys::av_channel_layout_default(&mut out_layout, out_channels);
let mut ptr: *mut sys::SwrContext = ptr::null_mut();
let ret = sys::swr_alloc_set_opts2(
&mut ptr,
&out_layout,
out_fmt as _,
out_rate,
&(*input.0.as_ptr()).ch_layout,
(*input.0.as_ptr()).sample_fmt,
(*input.0.as_ptr()).sample_rate,
0,
ptr::null_mut(),
);
sys::av_channel_layout_uninit(&mut out_layout);
if ret < 0 {
return Err(Error::SwrAlloc(FFmpegError(ret)));
}
let ctx = SwrCtx(NonNull::new_unchecked(ptr));
let ret = sys::swr_init(ctx.0.as_ptr());
if ret < 0 {
return Err(Error::SwrInit(FFmpegError(ret)));
}
Ok(ctx)
}
}
}
impl Drop for SwrCtx {
fn drop(&mut self) {
unsafe {
let mut p = self.0.as_ptr();
sys::swr_free(&mut p);
}
}
}
pub fn info(path: &Path) -> Result<AudioInfo, Error> {
let cpath = CString::new(path.as_os_str().to_string_lossy().as_bytes())?;
info_from_ctx(&FormatCtx::open_path(&cpath)?)
}
pub fn info_bytes(data: &[u8]) -> Result<AudioInfo, Error> {
info_from_ctx(&FormatCtx::open_bytes(data)?)
}
fn info_from_ctx(fmt: &FormatCtx<'_>) -> Result<AudioInfo, Error> {
unsafe {
let idx = sys::av_find_best_stream(
fmt.0.as_ptr(),
sys::AVMEDIA_TYPE_AUDIO,
-1,
-1,
ptr::null_mut(),
0,
);
if idx < 0 {
return Err(Error::NoAudioStream);
}
let stream = *(*fmt.0.as_ptr()).streams.add(idx as usize);
let codecpar = (*stream).codecpar;
let sample_rate = (*codecpar).sample_rate;
let channels = (*codecpar).ch_layout.nb_channels;
if sample_rate <= 0 || channels <= 0 {
return Err(Error::InvalidStreamParameters {
channels,
sample_rate,
});
}
let dur = (*stream).duration;
let frames = if dur > 0 && dur != sys::AV_NOPTS_VALUE {
let rescaled = sys::av_rescale_q(
dur,
sys::AVRational {
num: (*stream).time_base.num,
den: (*stream).time_base.den,
},
sys::AVRational {
num: 1,
den: sample_rate,
},
);
if rescaled > 0 {
Some(rescaled as u64)
} else {
None
}
} else {
None
};
let bps_raw = (*codecpar).bits_per_raw_sample;
let bps_coded = (*codecpar).bits_per_coded_sample;
let bits_per_sample = if bps_raw > 0 {
Some(bps_raw as u32)
} else if bps_coded > 0 {
Some(bps_coded as u32)
} else {
let bytes = sys::av_get_bytes_per_sample((*codecpar).format as _);
if bytes > 0 {
Some((bytes * 8) as u32)
} else {
None
}
};
let name_ptr = sys::avcodec_get_name((*codecpar).codec_id);
let codec = if name_ptr.is_null() {
None
} else {
Some(CStr::from_ptr(name_ptr).to_string_lossy().into_owned())
};
Ok(AudioInfo {
sample_rate: sample_rate as u32,
channels: channels as u32,
frames,
bits_per_sample,
codec,
})
}
}
fn pick_out_fmt(in_fmt: i32, normalize: bool, channels_first: bool) -> i32 {
let packed = if normalize {
sys::AV_SAMPLE_FMT_FLT
} else {
let p = unsafe { sys::av_get_packed_sample_fmt(in_fmt as _) };
if p == sys::AV_SAMPLE_FMT_U8
|| p == sys::AV_SAMPLE_FMT_S16
|| p == sys::AV_SAMPLE_FMT_S32
|| p == sys::AV_SAMPLE_FMT_S64
|| p == sys::AV_SAMPLE_FMT_FLT
|| p == sys::AV_SAMPLE_FMT_DBL
{
p
} else {
sys::AV_SAMPLE_FMT_FLT
}
};
if channels_first {
unsafe { sys::av_get_planar_sample_fmt(packed) as i32 }
} else {
packed as i32
}
}
trait Sample: Copy + Default + 'static {
fn into_planar(buf: Vec<Vec<Self>>) -> SampleData;
fn into_packed(buf: Vec<Self>) -> SampleData;
}
impl Sample for f64 {
fn into_planar(buf: Vec<Vec<f64>>) -> SampleData {
SampleData::F64(flatten_planes(buf))
}
fn into_packed(buf: Vec<f64>) -> SampleData {
SampleData::F64(buf)
}
}
impl Sample for f32 {
fn into_planar(buf: Vec<Vec<f32>>) -> SampleData {
SampleData::F32(flatten_planes(buf))
}
fn into_packed(buf: Vec<f32>) -> SampleData {
SampleData::F32(buf)
}
}
impl Sample for i64 {
fn into_planar(buf: Vec<Vec<i64>>) -> SampleData {
SampleData::I64(flatten_planes(buf))
}
fn into_packed(buf: Vec<i64>) -> SampleData {
SampleData::I64(buf)
}
}
impl Sample for i32 {
fn into_planar(buf: Vec<Vec<i32>>) -> SampleData {
SampleData::I32(flatten_planes(buf))
}
fn into_packed(buf: Vec<i32>) -> SampleData {
SampleData::I32(buf)
}
}
impl Sample for i16 {
fn into_planar(buf: Vec<Vec<i16>>) -> SampleData {
SampleData::I16(flatten_planes(buf))
}
fn into_packed(buf: Vec<i16>) -> SampleData {
SampleData::I16(buf)
}
}
impl Sample for u8 {
fn into_planar(buf: Vec<Vec<u8>>) -> SampleData {
SampleData::U8(flatten_planes(buf))
}
fn into_packed(buf: Vec<u8>) -> SampleData {
SampleData::U8(buf)
}
}
fn flatten_planes<T: Copy>(planes: Vec<Vec<T>>) -> Vec<T> {
let total: usize = planes.iter().map(|p| p.len()).sum();
let mut out = Vec::with_capacity(total);
for plane in &planes {
out.extend_from_slice(plane);
}
out
}
pub fn load(path: &Path, opts: &LoadOptions) -> Result<AudioBuffer, Error> {
let cpath = CString::new(path.as_os_str().to_string_lossy().as_bytes())?;
load_from_ctx(FormatCtx::open_path(&cpath)?, opts)
}
pub fn load_bytes(data: &[u8], opts: &LoadOptions) -> Result<AudioBuffer, Error> {
load_from_ctx(FormatCtx::open_bytes(data)?, opts)
}
fn load_from_ctx(fmt: FormatCtx<'_>, opts: &LoadOptions) -> Result<AudioBuffer, Error> {
let (audio_idx, codec_ctx, stream_tb) = CodecCtx::open_audio(&fmt)?;
let in_channels = codec_ctx.channels();
let in_rate = codec_ctx.sample_rate();
let in_fmt = codec_ctx.sample_fmt();
if in_channels <= 0 || in_rate <= 0 {
return Err(Error::InvalidStreamParameters {
channels: in_channels,
sample_rate: in_rate,
});
}
let out_channels = if opts.mono { 1 } else { in_channels };
let out_rate = opts.sample_rate.map(|r| r as i32).unwrap_or(in_rate);
let out_fmt = pick_out_fmt(in_fmt, opts.normalize, opts.channels_first);
let swr = SwrCtx::new(&codec_ctx, out_channels, out_rate, out_fmt)?;
let pkt = Packet::new()?;
let frame = Frame::new()?;
if opts.frame_offset > 0 {
unsafe {
let target_pts = sys::av_rescale_q(
opts.frame_offset as i64,
sys::AVRational {
num: 1,
den: in_rate,
},
stream_tb.as_av(),
);
let ret = sys::av_seek_frame(
fmt.0.as_ptr(),
audio_idx,
target_pts,
sys::AVSEEK_FLAG_BACKWARD,
);
if ret < 0 {
return Err(Error::Seek(FFmpegError(ret)));
}
sys::avcodec_flush_buffers(codec_ctx.0.as_ptr());
}
}
let in_planar = unsafe { sys::av_sample_fmt_is_planar(in_fmt as _) != 0 };
let in_bps = unsafe { sys::av_get_bytes_per_sample(in_fmt as _) } as usize;
if in_bps == 0 {
return Err(Error::InvalidArg(
"decoder produced an unknown sample format",
));
}
let packed_out = unsafe { sys::av_get_packed_sample_fmt(out_fmt as _) };
let ctx = DecodeCtx {
fmt: fmt.0,
codec_ctx: &codec_ctx,
swr: &swr,
pkt: &pkt,
frame: &frame,
audio_idx,
in_channels,
in_rate,
out_channels,
out_rate,
in_planar,
in_bps,
stream_tb,
opts,
};
if packed_out == sys::AV_SAMPLE_FMT_U8 {
decode::<u8>(ctx)
} else if packed_out == sys::AV_SAMPLE_FMT_S16 {
decode::<i16>(ctx)
} else if packed_out == sys::AV_SAMPLE_FMT_S32 {
decode::<i32>(ctx)
} else if packed_out == sys::AV_SAMPLE_FMT_S64 {
decode::<i64>(ctx)
} else if packed_out == sys::AV_SAMPLE_FMT_FLT {
decode::<f32>(ctx)
} else if packed_out == sys::AV_SAMPLE_FMT_DBL {
decode::<f64>(ctx)
} else {
Err(Error::InvalidArg("unsupported output sample format"))
}
}
struct DecodeCtx<'a> {
fmt: NonNull<sys::AVFormatContext>,
codec_ctx: &'a CodecCtx,
swr: &'a SwrCtx,
pkt: &'a Packet,
frame: &'a Frame,
audio_idx: i32,
in_channels: i32,
in_rate: i32,
out_channels: i32,
out_rate: i32,
in_planar: bool,
in_bps: usize,
stream_tb: StreamTimeBase,
opts: &'a LoadOptions,
}
fn decode<T: Sample>(ctx: DecodeCtx<'_>) -> Result<AudioBuffer, Error> {
let DecodeCtx {
fmt,
codec_ctx,
swr,
pkt,
frame,
audio_idx,
in_channels,
in_rate,
out_channels,
out_rate,
in_planar,
in_bps,
stream_tb,
opts,
} = ctx;
let n_out = out_channels as usize;
let channels_first = opts.channels_first;
let n_in_planes = if in_planar { in_channels as usize } else { 1 };
let mut chbuf_planar: Vec<Vec<T>> = if channels_first {
(0..n_out)
.map(|_| Vec::with_capacity(INITIAL_CAPACITY))
.collect()
} else {
Vec::new()
};
let mut chbuf_packed: Vec<T> = if channels_first {
Vec::new()
} else {
Vec::with_capacity(INITIAL_CAPACITY * n_out)
};
let mut total_frames: usize = 0;
let mut decoded_src_pos: i64 = 0;
let mut consumed_in: u64 = 0;
let target_start = opts.frame_offset as i64;
let target_end = match opts.num_frames {
Some(n) => target_start.saturating_add(n as i64),
None => i64::MAX,
};
let mut eof_sent = false;
let mut eof_seen = false;
unsafe {
'outer: while !eof_seen {
if !eof_sent {
let ret = sys::av_read_frame(fmt.as_ptr(), pkt.0.as_ptr());
if ret == sys::AVERROR_EOF {
sys::avcodec_send_packet(codec_ctx.0.as_ptr(), ptr::null());
eof_sent = true;
} else if ret < 0 {
return Err(Error::ReadFrame(FFmpegError(ret)));
} else if (*pkt.0.as_ptr()).stream_index != audio_idx {
sys::av_packet_unref(pkt.0.as_ptr());
continue;
} else {
let send = sys::avcodec_send_packet(codec_ctx.0.as_ptr(), pkt.0.as_ptr());
sys::av_packet_unref(pkt.0.as_ptr());
if send < 0 && send != sys::AVERROR(libc::EAGAIN) {
return Err(Error::SendPacket(FFmpegError(send)));
}
}
}
loop {
let recv = sys::avcodec_receive_frame(codec_ctx.0.as_ptr(), frame.0.as_ptr());
if recv == sys::AVERROR(libc::EAGAIN) {
break;
}
if recv == sys::AVERROR_EOF {
eof_seen = true;
break;
}
if recv < 0 {
return Err(Error::ReceiveFrame(FFmpegError(recv)));
}
let frame_samples = (*frame.0.as_ptr()).nb_samples as i64;
let pts = (*frame.0.as_ptr()).pts;
let pts_offset = if pts == sys::AV_NOPTS_VALUE {
decoded_src_pos
} else {
sys::av_rescale_q(
pts,
stream_tb.as_av(),
sys::AVRational {
num: 1,
den: in_rate,
},
)
};
decoded_src_pos = pts_offset + frame_samples;
let frame_end = pts_offset + frame_samples;
let in_start = pts_offset.max(target_start);
let in_end = frame_end.min(target_end);
if in_start >= in_end {
sys::av_frame_unref(frame.0.as_ptr());
if pts_offset >= target_end {
break 'outer;
}
continue;
}
let skip_in_frame = (in_start - pts_offset) as i32;
let take_samples = (in_end - in_start) as i32;
let stride_bytes = if in_planar {
in_bps
} else {
in_bps * in_channels as usize
};
let mut in_planes: [*const u8; sys::AV_NUM_DATA_POINTERS as usize] =
[ptr::null(); sys::AV_NUM_DATA_POINTERS as usize];
for (c, plane) in in_planes.iter_mut().take(n_in_planes).enumerate() {
let base = *(*frame.0.as_ptr()).extended_data.add(c);
*plane = base.add(skip_in_frame as usize * stride_bytes);
}
let max_out = sys::swr_get_out_samples(swr.0.as_ptr(), take_samples);
if max_out < 0 {
sys::av_frame_unref(frame.0.as_ptr());
return Err(Error::SwrConvert(FFmpegError(max_out)));
}
let max_out = max_out as usize;
grow_storage(
channels_first,
n_out,
total_frames,
max_out,
&mut chbuf_planar,
&mut chbuf_packed,
);
let mut out_planes: [*mut u8; sys::AV_NUM_DATA_POINTERS as usize] =
[ptr::null_mut(); sys::AV_NUM_DATA_POINTERS as usize];
fill_out_planes(
channels_first,
n_out,
total_frames,
&mut chbuf_planar,
&mut chbuf_packed,
&mut out_planes,
);
let converted = sys::swr_convert(
swr.0.as_ptr(),
out_planes.as_mut_ptr(),
max_out as i32,
in_planes.as_mut_ptr(),
take_samples,
);
sys::av_frame_unref(frame.0.as_ptr());
if converted < 0 {
return Err(Error::SwrConvert(FFmpegError(converted)));
}
total_frames += converted as usize;
consumed_in += take_samples as u64;
truncate_storage(
channels_first,
n_out,
total_frames,
&mut chbuf_planar,
&mut chbuf_packed,
);
if matches!(opts.num_frames, Some(n) if consumed_in >= n) {
break 'outer;
}
}
}
loop {
let remaining = sys::swr_get_out_samples(swr.0.as_ptr(), 0);
if remaining <= 0 {
break;
}
let remaining = remaining as usize;
grow_storage(
channels_first,
n_out,
total_frames,
remaining,
&mut chbuf_planar,
&mut chbuf_packed,
);
let mut out_planes: [*mut u8; sys::AV_NUM_DATA_POINTERS as usize] =
[ptr::null_mut(); sys::AV_NUM_DATA_POINTERS as usize];
fill_out_planes(
channels_first,
n_out,
total_frames,
&mut chbuf_planar,
&mut chbuf_packed,
&mut out_planes,
);
let converted = sys::swr_convert(
swr.0.as_ptr(),
out_planes.as_mut_ptr(),
remaining as i32,
ptr::null_mut(),
0,
);
if converted <= 0 {
truncate_storage(
channels_first,
n_out,
total_frames,
&mut chbuf_planar,
&mut chbuf_packed,
);
break;
}
total_frames += converted as usize;
truncate_storage(
channels_first,
n_out,
total_frames,
&mut chbuf_planar,
&mut chbuf_packed,
);
}
}
let samples = if channels_first {
T::into_planar(chbuf_planar)
} else {
T::into_packed(chbuf_packed)
};
Ok(AudioBuffer {
samples,
channels: out_channels as u32,
frames: total_frames as u64,
sample_rate: out_rate as u32,
layout: if channels_first {
ChannelLayout::Planar
} else {
ChannelLayout::Interleaved
},
})
}
fn grow_storage<T: Default + Clone>(
channels_first: bool,
n_out: usize,
total_frames: usize,
extra: usize,
planar: &mut [Vec<T>],
packed: &mut Vec<T>,
) {
if channels_first {
for buf in planar.iter_mut() {
buf.resize(total_frames + extra, T::default());
}
} else {
packed.resize((total_frames + extra) * n_out, T::default());
}
}
fn truncate_storage<T>(
channels_first: bool,
n_out: usize,
total_frames: usize,
planar: &mut [Vec<T>],
packed: &mut Vec<T>,
) {
if channels_first {
for buf in planar.iter_mut() {
buf.truncate(total_frames);
}
} else {
packed.truncate(total_frames * n_out);
}
}
unsafe fn fill_out_planes<T>(
channels_first: bool,
n_out: usize,
total_frames: usize,
planar: &mut [Vec<T>],
packed: &mut [T],
out_planes: &mut [*mut u8; sys::AV_NUM_DATA_POINTERS as usize],
) {
if channels_first {
for c in 0..n_out {
out_planes[c] = unsafe { planar[c].as_mut_ptr().add(total_frames) } as *mut u8;
}
} else {
out_planes[0] = unsafe { packed.as_mut_ptr().add(total_frames * n_out) } as *mut u8;
}
}