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_rescale_q, 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 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,
out,
)))
}