use std::{
collections::VecDeque,
ptr::{addr_of, read_unaligned},
};
use derive_more::{IsVariant, TryUnwrap, Unwrap};
use ffmpeg_next::{
ChannelLayout,
codec::Parameters,
ffi::{
AV_NOPTS_VALUE, AVChannelOrder, AVMatrixEncoding, AVSampleFormat, av_channel_layout_from_mask,
av_frame_get_buffer, swr_build_matrix2,
},
format::Sample,
frame,
software::resampling,
};
use mediadecode::{
Timebase, Timestamp,
frame::{AudioFrame, Plane},
resampler::AudioResampler,
};
use mediaframe::audio::ChannelLayoutDescription;
use crate::{Error, Ffmpeg, FfmpegBuffer, extras::AudioFrameExtra, sample_format::SampleFormat};
type Frame = AudioFrame<SampleFormat, ChannelLayoutDescription, AudioFrameExtra, FfmpegBuffer>;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ResampleSpec {
rate: u32,
format: Sample,
layout: ChannelLayout,
}
impl ResampleSpec {
#[inline]
pub const fn new(rate: u32, format: Sample, layout: ChannelLayout) -> Self {
Self {
rate,
format,
layout,
}
}
pub fn from_parameters(parameters: &Parameters) -> Option<Self> {
if unsafe { parameters.as_ptr() }.is_null() {
return None;
}
if parameters.medium() != ffmpeg_next::media::Type::Audio {
return None;
}
let par = unsafe { parameters.as_ptr() };
let rate = unsafe { (*par).sample_rate }.max(0) as u32;
if rate == 0 {
return None;
}
let format = SampleFormat::from_raw(unsafe { (*par).format }).to_ffmpeg()?;
let layout = unsafe { layout_from_raw(addr_of!((*par).ch_layout)) }?;
Some(Self::new(rate, format, layout))
}
pub fn from_decoder(decoder: &ffmpeg_next::decoder::Audio) -> Option<Self> {
let ctx = unsafe { decoder.as_ptr() };
if ctx.is_null() {
return None;
}
let format =
SampleFormat::from_raw(unsafe { read_unaligned(addr_of!((*ctx).sample_fmt).cast::<i32>()) })
.to_ffmpeg()?;
let rate = unsafe { (*ctx).sample_rate }.max(0) as u32;
if rate == 0 {
return None;
}
let layout = unsafe { layout_from_raw(addr_of!((*ctx).ch_layout)) }?;
Some(Self::new(rate, format, layout))
}
#[inline]
pub fn unspecified_layout(channels: i32) -> ChannelLayout {
unsafe {
let mut layout: ffmpeg_next::ffi::AVChannelLayout = std::mem::zeroed();
layout.nb_channels = channels.max(0);
ChannelLayout(layout)
}
}
#[inline]
pub const fn rate(&self) -> u32 {
self.rate
}
#[inline]
pub const fn format(&self) -> Sample {
self.format
}
#[inline]
pub const fn layout(&self) -> ChannelLayout {
self.layout
}
#[inline]
pub fn channels(&self) -> i32 {
self.layout.channels()
}
fn timebase(&self) -> Timebase {
Timebase::new(
1,
std::num::NonZeroI32::new(self.rate.min(i32::MAX as u32) as i32).unwrap_or(
std::num::NonZeroI32::new(1).expect("1 is non-zero"),
),
)
}
}
pub struct FfmpegResampler {
ctx: resampling::Context,
source: ResampleSpec,
target: ResampleSpec,
source_format: SampleFormat,
source_layout: ChannelLayoutDescription,
target_format: SampleFormat,
target_layout: ChannelLayoutDescription,
staged_source_layout: ChannelLayout,
staged_target_layout: ChannelLayout,
target_timebase: Timebase,
ready: VecDeque<Frame>,
next_pts: Option<i64>,
eof: bool,
}
impl FfmpegResampler {
pub fn new(source: ResampleSpec, target: ResampleSpec) -> Result<Self, ResampleError> {
check_spec(&source, SpecEnd::Source)?;
check_spec(&target, SpecEnd::Target)?;
let staged_source_layout = initialized_layout(source.layout);
let staged_target_layout = initialized_layout(target.layout);
check_pair(&staged_source_layout, &staged_target_layout)?;
let ctx = open_context(&source, &target, staged_source_layout, staged_target_layout)?;
let source_format = SampleFormat::from_ffmpeg(source.format);
let target_format = SampleFormat::from_ffmpeg(target.format);
let target_layout =
crate::channel_layout::channel_layout_description_from_ffmpeg(&staged_target_layout);
let source_layout =
crate::channel_layout::channel_layout_description_from_ffmpeg(&source.layout);
let target_timebase = target.timebase();
Ok(Self {
ctx,
source,
target,
source_format,
source_layout,
target_format,
target_layout,
staged_source_layout,
staged_target_layout,
target_timebase,
ready: VecDeque::new(),
next_pts: None,
eof: false,
})
}
#[inline]
pub const fn source(&self) -> &ResampleSpec {
&self.source
}
#[inline]
pub const fn target(&self) -> &ResampleSpec {
&self.target
}
#[inline]
pub const fn inner(&self) -> &resampling::Context {
&self.ctx
}
#[inline]
pub fn delay(&self) -> i64 {
self.ctx.delay().map_or(0, |d| d.output.max(0))
}
fn check_source(&self, frame: &Frame) -> Result<(), ResampleError> {
if frame.sample_rate() != self.source.rate
|| *frame.sample_format() != self.source_format
|| *frame.channel_layout() != self.source_layout
{
return Err(ResampleError::SourceChanged(SourceChanged::new(
self.source.rate,
self.source_format,
frame.sample_rate(),
*frame.sample_format(),
)));
}
Ok(())
}
fn anchor_of(&self, frame: &Frame) -> Result<Option<i64>, ResampleError> {
let Some(timestamp) = frame.pts() else {
return Ok(None);
};
let ticks = timestamp.pts();
let out_of_range = || ResampleError::TimestampOutOfRange(TimestampOutOfRange::new(ticks));
if ticks == AV_NOPTS_VALUE {
return Err(out_of_range());
}
let rescaled = timestamp
.timebase()
.checked_rescale(ticks, self.target_timebase)
.ok_or_else(out_of_range)?;
if rescaled == AV_NOPTS_VALUE {
return Err(out_of_range());
}
Ok(Some(rescaled))
}
fn stage_input(&self, frame: &Frame) -> Result<frame::Audio, ResampleError> {
let samples = frame.nb_samples() as usize;
let channels = self.source.channels();
let planes = if self.source.format.is_planar() {
channels.max(0) as usize
} else {
1
};
let found = frame.plane_count() as usize;
if planes > found {
return Err(ResampleError::PlaneCount(PlaneCount::new(planes, found)));
}
let bytes = plane_bytes(self.source.format, samples, channels)
.ok_or(ResampleError::SampleCount(SampleCount::new(samples)))?;
for plane in frame.planes().iter().take(planes) {
let src = plane.data_ref().as_ref();
if src.len() < bytes {
return Err(ResampleError::PlaneCount(PlaneCount::new(bytes, src.len())));
}
}
let mut input = new_audio_frame(
self.source.format,
samples,
self.source.rate,
self.staged_source_layout,
)?;
let staged = input.planes();
if staged < planes {
return Err(ResampleError::PlaneCount(PlaneCount::new(planes, staged)));
}
for (index, plane) in frame.planes().iter().take(planes).enumerate() {
let src = plane.data_ref().as_ref();
let dst = input.data_mut(index);
if dst.len() < bytes {
return Err(ResampleError::PlaneCount(PlaneCount::new(bytes, dst.len())));
}
dst[..bytes].copy_from_slice(&src[..bytes]);
}
Ok(input)
}
fn output_capacity(&self, in_samples: i64) -> Result<usize, ResampleError> {
let delay_in = self.ctx.delay().map_or(0, |d| d.input.max(0));
let total = delay_in.saturating_add(in_samples).max(0) as i128;
let scaled = (total * i128::from(self.target.rate) + i128::from(self.source.rate) - 1)
/ i128::from(self.source.rate).max(1);
let samples = scaled + 1;
if samples > i128::from(i32::MAX) {
return Err(ResampleError::SampleCount(SampleCount::new(
usize::try_from(samples).unwrap_or(usize::MAX),
)));
}
Ok(samples.max(1) as usize)
}
fn check_timeline(&self, anchor: Option<i64>, capacity: usize) -> Result<(), ResampleError> {
let pts = self.next_pts.or(anchor).unwrap_or(0);
let samples = capacity as i64;
if pts.checked_add(samples).is_none() {
return Err(ResampleError::TimestampOverflow(TimestampOverflow::new(
pts, samples,
)));
}
Ok(())
}
fn prepare_output(&self, capacity: usize) -> Result<PreparedOutput, ResampleError> {
let frame = new_audio_frame(
self.target.format,
capacity,
self.target.rate,
self.staged_target_layout,
)?;
let channels = self.target.channels();
let plane_count = if self.target.format.is_planar() {
channels.max(0) as usize
} else {
1
};
let plane_len = plane_bytes(self.target.format, capacity, channels)
.ok_or(ResampleError::SampleCount(SampleCount::new(capacity)))?;
let per_sample = plane_bytes(self.target.format, 1, channels)
.ok_or(ResampleError::SampleCount(SampleCount::new(1)))?;
if frame.planes() < plane_count {
return Err(ResampleError::PlaneCount(PlaneCount::new(
plane_count,
frame.planes(),
)));
}
let mut buffers: [Option<FfmpegBuffer>; 8] = [const { None }; 8];
for (index, slot) in buffers.iter_mut().enumerate() {
*slot = Some(if index < plane_count {
let data_ptr = unsafe { (*frame.as_ptr()).data[index] };
if data_ptr.is_null() {
return Err(ResampleError::OutputBuffer(OutputBuffer::new(index)));
}
let buf =
unsafe { crate::convert::find_audio_backing_buffer(frame.as_ptr(), data_ptr, plane_len) }
.ok_or(ResampleError::OutputBuffer(OutputBuffer::new(index)))?;
let offset = unsafe { (data_ptr as usize).wrapping_sub((*buf).data as usize) };
unsafe { FfmpegBuffer::from_ref_view(buf, offset, plane_len) }
.ok_or(ResampleError::OutputBuffer(OutputBuffer::new(index)))?
} else {
FfmpegBuffer::try_empty().ok_or(ResampleError::OutputBuffer(OutputBuffer::new(index)))?
});
}
Ok(PreparedOutput {
frame,
buffers,
plane_count,
plane_len,
per_sample,
})
}
fn finish_output(&mut self, mut prepared: PreparedOutput) -> Option<Frame> {
let produced = prepared.frame.samples();
if produced == 0 {
return None;
}
let pts = self.next_pts.unwrap_or(0);
debug_assert!(
pts.checked_add(produced as i64).is_some(),
"the timeline was preflighted against a capacity >= produced",
);
self.next_pts = Some(pts.saturating_add(produced as i64));
let bytes = prepared
.per_sample
.saturating_mul(produced)
.min(prepared.plane_len);
let plane_count = prepared.plane_count;
let planes = std::array::from_fn(|index| {
let mut buffer = prepared.buffers[index]
.take()
.expect("prepare_output fills every slot");
if index < plane_count {
buffer.shrink_to(bytes);
Plane::new(buffer, bytes as u32)
} else {
Plane::new(buffer, 0)
}
});
Some(
AudioFrame::new(
self.target.rate,
produced as u32,
self.target.channels().clamp(0, 255) as u8,
self.target_format,
self.target_layout.clone(),
planes,
plane_count as u8,
AudioFrameExtra::default(),
)
.with_pts(Some(Timestamp::new(pts, self.target_timebase)))
.with_duration(Some(Timestamp::new(produced as i64, self.target_timebase))),
)
}
}
struct PreparedOutput {
frame: frame::Audio,
buffers: [Option<FfmpegBuffer>; 8],
plane_count: usize,
plane_len: usize,
per_sample: usize,
}
impl AudioResampler for FfmpegResampler {
type Adapter = Ffmpeg;
type Buffer = FfmpegBuffer;
type Error = ResampleError;
fn send_frame(&mut self, frame: &Frame) -> Result<(), ResampleError> {
if self.eof {
return Err(ResampleError::AfterEof);
}
self.check_source(frame)?;
if frame.nb_samples() == 0 {
return Ok(());
}
let anchor = self.anchor_of(frame)?;
let input = self.stage_input(frame)?;
let capacity = self.output_capacity(frame.nb_samples() as i64)?;
self.check_timeline(anchor, capacity)?;
let mut prepared = self.prepare_output(capacity)?;
self
.ready
.try_reserve(1)
.map_err(|_| ResampleError::QueueAlloc)?;
self
.ctx
.run(&input, &mut prepared.frame)
.map_err(|e| ResampleError::Resample(Error::Ffmpeg(e)))?;
if self.next_pts.is_none() {
self.next_pts = anchor;
}
if let Some(converted) = self.finish_output(prepared) {
self.ready.push_back(converted);
}
Ok(())
}
fn receive_frame(&mut self, dst: &mut Frame) -> Result<(), ResampleError> {
if let Some(frame) = self.ready.pop_front() {
*dst = frame;
return Ok(());
}
if !self.eof {
return Err(ResampleError::Again);
}
let remaining = self.delay();
if remaining <= 0 {
return Err(ResampleError::Again);
}
let capacity = remaining.min(i64::from(i32::MAX)) as usize;
self.check_timeline(None, capacity)?;
let mut prepared = self.prepare_output(capacity)?;
self
.ctx
.flush(&mut prepared.frame)
.map_err(|e| ResampleError::Resample(Error::Ffmpeg(e)))?;
match self.finish_output(prepared) {
Some(frame) => {
*dst = frame;
Ok(())
}
None => Err(ResampleError::Again),
}
}
fn send_eof(&mut self) -> Result<(), ResampleError> {
self.eof = true;
Ok(())
}
fn flush(&mut self) -> Result<(), ResampleError> {
let ctx = open_context(
&self.source,
&self.target,
self.staged_source_layout,
self.staged_target_layout,
)?;
self.ctx = ctx;
self.ready.clear();
self.next_pts = None;
self.eof = false;
debug_assert_eq!(self.delay(), 0, "a fresh swr context holds nothing");
Ok(())
}
}
#[derive(thiserror::Error, Debug, Clone)]
#[error(
"source format changed mid-stream: expected {expected_rate} Hz {expected_format:?}, \
got {found_rate} Hz {found_format:?}"
)]
pub struct SourceChanged {
expected_rate: u32,
expected_format: SampleFormat,
found_rate: u32,
found_format: SampleFormat,
}
impl SourceChanged {
#[inline]
pub const fn new(
expected_rate: u32,
expected_format: SampleFormat,
found_rate: u32,
found_format: SampleFormat,
) -> Self {
Self {
expected_rate,
expected_format,
found_rate,
found_format,
}
}
#[inline]
pub const fn expected_rate(&self) -> u32 {
self.expected_rate
}
#[inline]
pub const fn expected_format(&self) -> SampleFormat {
self.expected_format
}
#[inline]
pub const fn found_rate(&self) -> u32 {
self.found_rate
}
#[inline]
pub const fn found_format(&self) -> SampleFormat {
self.found_format
}
}
#[derive(thiserror::Error, Debug, Clone)]
#[error("frame plane geometry mismatch: expected {expected}, found {found}")]
pub struct PlaneCount {
expected: usize,
found: usize,
}
impl PlaneCount {
#[inline]
pub const fn new(expected: usize, found: usize) -> Self {
Self { expected, found }
}
#[inline]
pub const fn expected(&self) -> usize {
self.expected
}
#[inline]
pub const fn found(&self) -> usize {
self.found
}
}
#[derive(thiserror::Error, Debug, Clone)]
#[error("{requested} samples is not a frame size")]
pub struct SampleCount {
requested: usize,
}
impl SampleCount {
#[inline]
pub const fn new(requested: usize) -> Self {
Self { requested }
}
#[inline]
pub const fn requested(&self) -> usize {
self.requested
}
}
#[derive(thiserror::Error, Debug, Clone)]
#[error("the {end} rate {rate} is not a sample rate swr can use")]
pub struct UnsupportedRate {
end: SpecEnd,
rate: u32,
}
impl UnsupportedRate {
#[inline]
pub const fn new(end: SpecEnd, rate: u32) -> Self {
Self { end, rate }
}
#[inline]
pub const fn end(&self) -> SpecEnd {
self.end
}
#[inline]
pub const fn rate(&self) -> u32 {
self.rate
}
}
#[derive(thiserror::Error, Debug, Clone)]
#[error("the {end} spec names no sample format")]
pub struct UnsupportedFormat {
end: SpecEnd,
}
impl UnsupportedFormat {
#[inline]
pub const fn new(end: SpecEnd) -> Self {
Self { end }
}
#[inline]
pub const fn end(&self) -> SpecEnd {
self.end
}
}
#[derive(thiserror::Error, Debug, Clone)]
#[error("the {end} channel layout is not supported: order {order}, {channels} channels")]
pub struct UnsupportedLayout {
end: SpecEnd,
order: i32,
channels: i32,
}
impl UnsupportedLayout {
#[inline]
pub const fn new(end: SpecEnd, order: i32, channels: i32) -> Self {
Self {
end,
order,
channels,
}
}
#[inline]
pub const fn end(&self) -> SpecEnd {
self.end
}
#[inline]
pub const fn order(&self) -> i32 {
self.order
}
#[inline]
pub const fn channels(&self) -> i32 {
self.channels
}
}
#[derive(thiserror::Error, Debug, Clone)]
#[error("the {end} spec is planar with {channels} channels; a frame carries {limit} planes")]
pub struct TooManyPlanes {
end: SpecEnd,
channels: i32,
limit: i32,
}
impl TooManyPlanes {
#[inline]
pub const fn new(end: SpecEnd, channels: i32, limit: i32) -> Self {
Self {
end,
channels,
limit,
}
}
#[inline]
pub const fn end(&self) -> SpecEnd {
self.end
}
#[inline]
pub const fn channels(&self) -> i32 {
self.channels
}
#[inline]
pub const fn limit(&self) -> i32 {
self.limit
}
}
#[derive(thiserror::Error, Debug, Clone)]
#[error("the frame timestamp {pts} does not land on the output timeline")]
pub struct TimestampOutOfRange {
pts: i64,
}
impl TimestampOutOfRange {
#[inline]
pub const fn new(pts: i64) -> Self {
Self { pts }
}
#[inline]
pub const fn pts(&self) -> i64 {
self.pts
}
}
#[derive(thiserror::Error, Debug, Clone)]
#[error(
"converting {source_channels} channels to {target_channels} would drop source channel \
{channel}: FFmpeg's mixing matrix routes it to no output"
)]
pub struct ChannelDropped {
source_channels: i32,
target_channels: i32,
channel: i32,
}
impl ChannelDropped {
#[inline]
pub const fn new(source_channels: i32, target_channels: i32, channel: i32) -> Self {
Self {
source_channels,
target_channels,
channel,
}
}
#[inline]
pub const fn source_channels(&self) -> i32 {
self.source_channels
}
#[inline]
pub const fn target_channels(&self) -> i32 {
self.target_channels
}
#[inline]
pub const fn channel(&self) -> i32 {
self.channel
}
}
#[derive(thiserror::Error, Debug, Clone)]
#[error("FFmpeg builds no mixing matrix from {source_channels} channels to {target_channels}")]
pub struct RematrixUnsupported {
source_channels: i32,
target_channels: i32,
}
impl RematrixUnsupported {
#[inline]
pub const fn new(source_channels: i32, target_channels: i32) -> Self {
Self {
source_channels,
target_channels,
}
}
#[inline]
pub const fn source_channels(&self) -> i32 {
self.source_channels
}
#[inline]
pub const fn target_channels(&self) -> i32 {
self.target_channels
}
}
#[derive(thiserror::Error, Debug, Clone)]
#[error("the output timeline overflows: {pts} + {samples} samples")]
pub struct TimestampOverflow {
pts: i64,
samples: i64,
}
impl TimestampOverflow {
#[inline]
pub const fn new(pts: i64, samples: i64) -> Self {
Self { pts, samples }
}
#[inline]
pub const fn pts(&self) -> i64 {
self.pts
}
#[inline]
pub const fn samples(&self) -> i64 {
self.samples
}
}
#[derive(thiserror::Error, Debug, Clone)]
#[error("the output frame's plane {plane} could not be referenced")]
pub struct OutputBuffer {
plane: usize,
}
impl OutputBuffer {
#[inline]
pub const fn new(plane: usize) -> Self {
Self { plane }
}
#[inline]
pub const fn plane(&self) -> usize {
self.plane
}
}
#[derive(thiserror::Error, Debug, Clone, IsVariant, Unwrap, TryUnwrap)]
#[unwrap(ref, ref_mut)]
#[try_unwrap(ref, ref_mut)]
pub enum ResampleError {
#[error("no converted frame ready")]
Again,
#[error(transparent)]
SourceChanged(#[from] SourceChanged),
#[error("send_frame after send_eof; flush() first to start another stream")]
AfterEof,
#[error(transparent)]
PlaneCount(#[from] PlaneCount),
#[error(transparent)]
SampleCount(#[from] SampleCount),
#[error(transparent)]
UnsupportedRate(#[from] UnsupportedRate),
#[error(transparent)]
UnsupportedFormat(#[from] UnsupportedFormat),
#[error(transparent)]
UnsupportedLayout(#[from] UnsupportedLayout),
#[error(transparent)]
TooManyPlanes(#[from] TooManyPlanes),
#[error(transparent)]
TimestampOutOfRange(#[from] TimestampOutOfRange),
#[error(transparent)]
ChannelDropped(#[from] ChannelDropped),
#[error(transparent)]
RematrixUnsupported(#[from] RematrixUnsupported),
#[error(transparent)]
TimestampOverflow(#[from] TimestampOverflow),
#[error(transparent)]
Resample(#[from] Error),
#[error(transparent)]
OutputBuffer(#[from] OutputBuffer),
#[error("out of memory reserving room for a converted frame")]
QueueAlloc,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, IsVariant)]
pub enum SpecEnd {
Source,
Target,
}
impl core::fmt::Display for SpecEnd {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(match self {
Self::Source => "source",
Self::Target => "target",
})
}
}
const MAX_AUDIO_PLANES: i32 = 8;
fn check_spec(spec: &ResampleSpec, end: SpecEnd) -> Result<(), ResampleError> {
if spec.rate == 0 || spec.rate > i32::MAX as u32 {
return Err(ResampleError::UnsupportedRate(UnsupportedRate::new(
end, spec.rate,
)));
}
if spec.format == Sample::None {
return Err(ResampleError::UnsupportedFormat(UnsupportedFormat::new(
end,
)));
}
let order = layout_order(&spec.layout);
let channels = spec.layout.channels();
let carried = order == AVChannelOrder::AV_CHANNEL_ORDER_NATIVE as i32
|| order == AVChannelOrder::AV_CHANNEL_ORDER_UNSPEC as i32;
if !carried || channels <= 0 {
return Err(ResampleError::UnsupportedLayout(UnsupportedLayout::new(
end, order, channels,
)));
}
if spec.format.is_planar() && channels > MAX_AUDIO_PLANES {
return Err(ResampleError::TooManyPlanes(TooManyPlanes::new(
end,
channels,
MAX_AUDIO_PLANES,
)));
}
Ok(())
}
fn layout_order(layout: &ChannelLayout) -> i32 {
unsafe { read_unaligned(addr_of!(layout.0.order).cast::<i32>()) }
}
const SWR_CH_MAX: usize = 64;
fn check_pair(source: &ChannelLayout, target: &ChannelLayout) -> Result<(), ResampleError> {
let native = AVChannelOrder::AV_CHANNEL_ORDER_NATIVE as i32;
if layout_order(source) != native || layout_order(target) != native || source == target {
return Ok(());
}
let source_channels = source.channels();
let target_channels = target.channels();
let mut matrix = vec![0f64; SWR_CH_MAX * SWR_CH_MAX];
let rc = unsafe {
swr_build_matrix2(
&source.0,
&target.0,
core::f64::consts::FRAC_1_SQRT_2,
core::f64::consts::FRAC_1_SQRT_2,
1.0,
1.0,
1.0,
matrix.as_mut_ptr(),
SWR_CH_MAX as isize,
AVMatrixEncoding::AV_MATRIX_ENCODING_NONE,
core::ptr::null_mut(),
)
};
if rc < 0 {
return Err(ResampleError::RematrixUnsupported(
RematrixUnsupported::new(source_channels, target_channels),
));
}
for channel in 0..source_channels.min(SWR_CH_MAX as i32) {
let index = channel as usize;
if (0..target_channels.min(SWR_CH_MAX as i32) as usize)
.all(|out| matrix[index + SWR_CH_MAX * out] == 0.0)
{
return Err(ResampleError::ChannelDropped(ChannelDropped::new(
source_channels,
target_channels,
channel,
)));
}
}
Ok(())
}
fn open_context(
source: &ResampleSpec,
target: &ResampleSpec,
staged_source_layout: ChannelLayout,
staged_target_layout: ChannelLayout,
) -> Result<resampling::Context, ResampleError> {
resampling::Context::get(
source.format,
staged_source_layout,
source.rate,
target.format,
staged_target_layout,
target.rate,
)
.map_err(|e| ResampleError::Resample(Error::Ffmpeg(e)))
}
fn new_audio_frame(
format: Sample,
samples: usize,
rate: u32,
layout: ChannelLayout,
) -> Result<frame::Audio, ResampleError> {
if samples == 0 || samples > i32::MAX as usize {
return Err(ResampleError::SampleCount(SampleCount::new(samples)));
}
let mut out = crate::frame::alloc_av_audio_frame()?;
out.set_format(format);
out.set_samples(samples);
out.set_channel_layout(layout);
out.set_rate(rate);
let rc = unsafe { av_frame_get_buffer(out.as_mut_ptr(), 0) };
if rc < 0 {
return Err(ResampleError::Resample(Error::Ffmpeg(
ffmpeg_next::Error::from(rc),
)));
}
Ok(out)
}
fn plane_bytes(format: Sample, samples: usize, channels: i32) -> Option<usize> {
let bytes = samples.checked_mul(format.bytes())?;
if format.is_planar() {
Some(bytes)
} else {
bytes.checked_mul(channels.max(1) as usize)
}
}
fn layout_from_mask(mask: u64) -> ChannelLayout {
unsafe {
let mut layout = std::mem::zeroed();
if av_channel_layout_from_mask(&mut layout, mask) < 0 {
return ChannelLayout::default(mask.count_ones() as i32);
}
ChannelLayout(layout)
}
}
fn initialized_layout(layout: ChannelLayout) -> ChannelLayout {
if layout.is_empty() {
ChannelLayout::default(layout.channels())
} else {
layout
}
}
unsafe fn layout_from_raw(ptr: *const ffmpeg_next::ffi::AVChannelLayout) -> Option<ChannelLayout> {
let order = unsafe { read_unaligned(addr_of!((*ptr).order).cast::<i32>()) };
let channels = unsafe { (*ptr).nb_channels };
if channels <= 0 {
return None;
}
if order == AVChannelOrder::AV_CHANNEL_ORDER_NATIVE as i32 {
let mask = unsafe { (*ptr).u.mask };
if mask != 0 {
return Some(layout_from_mask(mask));
}
return Some(ResampleSpec::unspecified_layout(channels));
}
if order == AVChannelOrder::AV_CHANNEL_ORDER_UNSPEC as i32 {
return Some(ResampleSpec::unspecified_layout(channels));
}
None
}
const _: () = {
assert!(
SampleFormat::from_raw(AVSampleFormat::AV_SAMPLE_FMT_NONE as i32)
.to_ffmpeg()
.is_none()
);
};
#[cfg(test)]
mod tests {
use super::*;
use mediadecode::resampler::AudioResampler;
fn stereo_frame(samples: u32) -> Frame {
let plane = FfmpegBuffer::copy_from_slice(&vec![0u8; samples as usize * 2 * 2]).expect("plane");
let planes = std::array::from_fn(|index| {
Plane::new(
if index == 0 {
plane.clone()
} else {
FfmpegBuffer::empty()
},
0,
)
});
AudioFrame::new(
48_000,
samples,
2,
SampleFormat::S16,
crate::channel_layout::channel_layout_description_from_ffmpeg(&ChannelLayout::STEREO),
planes,
1,
AudioFrameExtra::default(),
)
.with_pts(Some(Timestamp::new(
0,
Timebase::new(1, std::num::NonZeroI32::new(48_000).expect("a real rate")),
)))
}
fn stereo_to_mono() -> FfmpegResampler {
FfmpegResampler::new(
ResampleSpec::new(
48_000,
Sample::I16(ffmpeg_next::format::sample::Type::Packed),
ChannelLayout::STEREO,
),
ResampleSpec::new(
16_000,
Sample::I16(ffmpeg_next::format::sample::Type::Packed),
ChannelLayout::MONO,
),
)
.expect("open resampler")
}
#[test]
fn resample_error_carries_the_derived_accessor_face() {
let err = ResampleError::OutputBuffer(OutputBuffer::new(2));
assert!(err.is_output_buffer());
assert!(!err.is_again());
assert_eq!(err.unwrap_output_buffer_ref().plane(), 2);
assert!(err.try_unwrap_again().is_err());
}
#[test]
fn an_allocation_fault_while_sending_leaves_the_session_untouched() {
crate::fault_subprocess::in_subprocess(
"resampler::tests::an_allocation_fault_while_sending_leaves_the_session_untouched",
|| {
let mut resampler = stereo_to_mono();
let frame = stereo_frame(4_800);
let mut dst = crate::boundary::empty_audio_frame();
resampler.send_frame(&frame).expect("a first frame");
while resampler.receive_frame(&mut dst).is_ok() {}
let delay = resampler.delay();
assert!(delay > 0, "the filter has to be holding something");
crate::fault_subprocess::cap_ffmpeg_allocations(1);
let refused = resampler.send_frame(&frame);
crate::fault_subprocess::uncap_ffmpeg_allocations();
assert!(
refused.is_err(),
"an allocator that refuses everything must not look like success",
);
assert_eq!(
resampler.delay(),
delay,
"the frame went into the filter anyway",
);
assert!(
resampler.receive_frame(&mut dst).unwrap_err().is_again(),
"a failed send left output ready",
);
resampler
.send_frame(&frame)
.expect("the failure cost nothing");
assert!(resampler.receive_frame(&mut dst).is_ok());
},
);
}
#[test]
fn an_allocation_fault_while_draining_keeps_the_tail() {
crate::fault_subprocess::in_subprocess(
"resampler::tests::an_allocation_fault_while_draining_keeps_the_tail",
|| {
let mut resampler = stereo_to_mono();
let frame = stereo_frame(4_800);
let mut dst = crate::boundary::empty_audio_frame();
for _ in 0..3 {
resampler.send_frame(&frame).expect("send_frame");
while resampler.receive_frame(&mut dst).is_ok() {}
}
resampler.send_eof().expect("eof");
let tail = resampler.delay();
assert!(tail > 0, "there has to be a tail to lose");
crate::fault_subprocess::cap_ffmpeg_allocations(1);
let refused = resampler.receive_frame(&mut dst);
crate::fault_subprocess::uncap_ffmpeg_allocations();
let refused = refused.expect_err("the drain cannot have succeeded");
assert!(
!refused.is_again(),
"an allocation failure is not `send me more input`: {refused:?}",
);
assert_eq!(
resampler.delay(),
tail,
"the tail was consumed by a drain that failed",
);
resampler
.receive_frame(&mut dst)
.expect("the tail survived the failure");
},
);
}
#[test]
fn the_sample_format_table_round_trips() {
for format in [
SampleFormat::U8,
SampleFormat::S16,
SampleFormat::S32,
SampleFormat::S64,
SampleFormat::FLT,
SampleFormat::DBL,
SampleFormat::U8P,
SampleFormat::S16P,
SampleFormat::S32P,
SampleFormat::S64P,
SampleFormat::FLTP,
SampleFormat::DBLP,
] {
let ffmpeg = format.to_ffmpeg().expect("a named format");
assert_eq!(
SampleFormat::from_ffmpeg(ffmpeg),
format,
"{format:?} does not survive the round trip",
);
assert_eq!(ffmpeg.is_planar(), format.is_planar());
}
assert!(SampleFormat::NONE.to_ffmpeg().is_none());
assert!(SampleFormat::from_raw(9999).to_ffmpeg().is_none());
}
#[test]
fn a_mask_rebuilds_the_layout_it_names() {
let stereo = layout_from_mask(ChannelLayout::STEREO.bits());
assert_eq!(stereo.channels(), 2);
assert_eq!(stereo.bits(), ChannelLayout::STEREO.bits());
let five_one = layout_from_mask(ChannelLayout::_5POINT1.bits());
assert_eq!(five_one.channels(), 6);
assert_eq!(
five_one.bits(),
ChannelLayout::_5POINT1.bits(),
"the side-vs-back distinction is exactly what a default layout would lose",
);
}
#[test]
fn plane_geometry_follows_packed_versus_planar() {
use ffmpeg_next::format::sample::Type;
assert_eq!(
plane_bytes(Sample::I16(Type::Packed), 1024, 2),
Some(1024 * 2 * 2)
);
assert_eq!(
plane_bytes(Sample::I16(Type::Planar), 1024, 2),
Some(1024 * 2)
);
assert_eq!(
plane_bytes(Sample::F32(Type::Planar), 1024, 6),
Some(1024 * 4)
);
assert_eq!(
plane_bytes(Sample::F32(Type::Packed), usize::MAX / 2, 8),
None,
"an overflowing plane size is refused, not wrapped",
);
}
#[test]
fn the_target_timebase_is_one_tick_per_output_sample() {
let spec = ResampleSpec::new(
16_000,
Sample::I16(ffmpeg_next::format::sample::Type::Packed),
ChannelLayout::MONO,
);
let tb = spec.timebase();
assert_eq!((tb.num(), tb.den().get()), (1, 16_000));
}
}