use super::chunk::AudioChunk;
use crate::core::filter::frame_filter::{FrameFilter, FrameFilterError, RequestFrameMode};
use crate::core::filter::frame_filter_context::FrameFilterContext;
use crate::util::ffmpeg_utils::frame_is_eof_marker;
use crossbeam_channel::Sender;
use ffmpeg_next::Frame;
use ffmpeg_sys_next::{
av_channel_layout_describe, av_rescale_q, AVChannelLayout, AVFrame, AVMediaType,
AVMediaType::AVMEDIA_TYPE_AUDIO, AVRational, AVSampleFormat::AV_SAMPLE_FMT_FLT, AV_NOPTS_VALUE,
};
const US_PER_SEC: AVRational = AVRational {
num: 1,
den: 1_000_000,
};
pub(crate) struct SampleSink {
tx: Sender<AudioChunk>,
emitted: u64,
done: bool,
}
impl SampleSink {
pub(crate) fn new(tx: Sender<AudioChunk>) -> Self {
Self {
tx,
emitted: 0,
done: false,
}
}
unsafe fn frame_pts_us(p: *const AVFrame) -> Option<i64> {
let tb = (*p).time_base;
if tb.den == 0 {
return None;
}
let pts = (*p).pts;
if pts == AV_NOPTS_VALUE {
return None;
}
Some(av_rescale_q(pts, tb, US_PER_SEC))
}
}
impl FrameFilter for SampleSink {
fn media_type(&self) -> AVMediaType {
AVMEDIA_TYPE_AUDIO
}
fn request_frame_mode(&self) -> RequestFrameMode {
RequestFrameMode::Never
}
fn filter_frame(
&mut self,
frame: Frame,
_ctx: &mut FrameFilterContext,
) -> Result<Option<Frame>, FrameFilterError> {
if frame_is_eof_marker(&frame) {
return Ok(Some(frame));
}
let p = unsafe { frame.as_ptr() };
if p.is_null() {
return Ok(Some(frame));
}
if self.done {
return Ok(None);
}
let pts_us = unsafe { Self::frame_pts_us(p) };
let chunk = match unsafe { pack_chunk(p, pts_us, self.emitted)? } {
Some(c) => c,
None => return Ok(Some(frame)),
};
match self.tx.send(chunk) {
Ok(()) => {
self.emitted += 1;
Ok(Some(frame))
}
Err(_) => {
self.done = true;
Ok(Some(frame))
}
}
}
}
unsafe fn pack_chunk(
p: *const AVFrame,
pts_us: Option<i64>,
index: u64,
) -> Result<Option<AudioChunk>, FrameFilterError> {
let expected = AV_SAMPLE_FMT_FLT as i32;
if (*p).format != expected {
return Err(format!(
"sample export: expected packed f32 (AV_SAMPLE_FMT_FLT = {expected}), \
got sample format {}",
(*p).format
)
.into());
}
let nb_samples = (*p).nb_samples;
let channels = (*p).ch_layout.nb_channels;
if channels <= 0 {
return Err(format!("sample export: non-positive channel count {channels}").into());
}
if nb_samples <= 0 {
return Ok(None);
}
let sample_rate = (*p).sample_rate;
if sample_rate <= 0 {
return Err(format!("sample export: non-positive sample rate {sample_rate}").into());
}
let channel_layout = describe_channel_layout(&(*p).ch_layout);
let n = (nb_samples as usize)
.checked_mul(channels as usize)
.ok_or_else(|| -> FrameFilterError { "sample export: sample count overflow".into() })?;
let byte_len = n
.checked_mul(std::mem::size_of::<f32>())
.ok_or_else(|| -> FrameFilterError { "sample export: buffer size overflow".into() })?;
let base = (*p).data[0];
if base.is_null() {
return Err("sample export: sample plane pointer is null".into());
}
let linesize = (*p).linesize[0];
if linesize < 0 || (linesize as usize) < byte_len {
return Err(format!(
"sample export: linesize {linesize} smaller than {byte_len} packed sample bytes"
)
.into());
}
let mut out = vec![0f32; n];
std::ptr::copy_nonoverlapping(base, out.as_mut_ptr() as *mut u8, byte_len);
Ok(Some(AudioChunk::new(
pts_us,
index,
sample_rate as u32,
channels as u16,
channel_layout,
out,
)))
}
unsafe fn describe_channel_layout(layout: *const AVChannelLayout) -> String {
describe_channel_layout_with_capacity(layout, 128)
}
unsafe fn describe_channel_layout_with_capacity(
layout: *const AVChannelLayout,
capacity: usize,
) -> String {
let mut buf = vec![0u8; capacity];
let n = av_channel_layout_describe(layout, buf.as_mut_ptr() as *mut libc::c_char, buf.len());
if n < 0 {
return String::new();
}
if n as usize > buf.len() {
buf = vec![0u8; n as usize];
let retried =
av_channel_layout_describe(layout, buf.as_mut_ptr() as *mut libc::c_char, buf.len());
if retried < 0 || retried as usize > buf.len() {
return String::new();
}
}
match std::ffi::CStr::from_bytes_until_nul(&buf) {
Ok(c) => c.to_string_lossy().into_owned(),
Err(_) => String::new(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use ffmpeg_sys_next::{av_channel_layout_default, av_channel_layout_uninit};
#[test]
fn describe_channel_layout_with_capacity_recovers_the_full_description() {
let mut layout: AVChannelLayout = unsafe { std::mem::zeroed() };
unsafe { av_channel_layout_default(&mut layout, 2) };
let via_retry = unsafe { describe_channel_layout_with_capacity(&layout, 4) };
assert_eq!(via_retry, "stereo");
let via_first_attempt = unsafe { describe_channel_layout_with_capacity(&layout, 128) };
assert_eq!(via_first_attempt, "stereo");
unsafe { av_channel_layout_uninit(&mut layout) };
}
}