use std::ffi::CString;
use std::ptr::NonNull;
use crate::codec_context::classify_receive;
use crate::{
AVBSFContext, AVRational, AvError, CodecParameters, Packet, ReceiveOutcome,
av_bsf_free as ffi_av_bsf_free, av_bsf_get_null_filter as ffi_av_bsf_get_null_filter,
av_bsf_init as ffi_av_bsf_init, av_bsf_list_parse_str as ffi_av_bsf_list_parse_str,
av_bsf_receive_packet as ffi_av_bsf_receive_packet,
av_bsf_send_packet as ffi_av_bsf_send_packet, avcodec_parameters_copy,
};
#[derive(Debug)]
pub struct BsfContext {
ptr: NonNull<AVBSFContext>,
}
impl BsfContext {
pub fn open(
spec: &str,
par_in: Option<CodecParameters<'_>>,
time_base_in: AVRational,
) -> Result<Self, AvError> {
crate::ensure_initialized();
let c_spec = CString::new(spec).map_err(|_| AvError::new(crate::error_codes::EINVAL))?;
let mut raw: *mut AVBSFContext = std::ptr::null_mut();
let ret = unsafe { ffi_av_bsf_list_parse_str(c_spec.as_ptr(), &raw mut raw) };
if ret < 0 {
return Err(AvError::new(ret));
}
Self::finish(raw, par_in, time_base_in)
}
pub fn passthrough(
par_in: Option<CodecParameters<'_>>,
time_base_in: AVRational,
) -> Result<Self, AvError> {
crate::ensure_initialized();
let mut raw: *mut AVBSFContext = std::ptr::null_mut();
let ret = unsafe { ffi_av_bsf_get_null_filter(&raw mut raw) };
if ret < 0 {
return Err(AvError::new(ret));
}
Self::finish(raw, par_in, time_base_in)
}
fn finish(
raw: *mut AVBSFContext,
par_in: Option<CodecParameters<'_>>,
time_base_in: AVRational,
) -> Result<Self, AvError> {
let Some(ptr) = NonNull::new(raw) else {
return Err(AvError::new(crate::error_codes::ENOMEM));
};
let ctx = Self { ptr };
if let Some(par) = par_in {
let ret = unsafe { avcodec_parameters_copy((*ctx.ptr.as_ptr()).par_in, par.as_raw()) };
if ret < 0 {
return Err(AvError::new(ret));
}
}
unsafe { (*ctx.ptr.as_ptr()).time_base_in = time_base_in };
let ret = unsafe { ffi_av_bsf_init(ctx.ptr.as_ptr()) };
if ret < 0 {
return Err(AvError::new(ret));
}
Ok(ctx)
}
#[must_use]
pub fn output_params(&self) -> CodecParameters<'_> {
unsafe {
let par = (*self.ptr.as_ptr()).par_out;
CodecParameters::from_raw(NonNull::new_unchecked(par))
}
}
#[must_use]
pub fn output_time_base(&self) -> AVRational {
unsafe { (*self.ptr.as_ptr()).time_base_out }
}
pub fn send_packet(&mut self, pkt: &mut Packet) -> Result<(), AvError> {
let ret = unsafe { ffi_av_bsf_send_packet(self.ptr.as_ptr(), pkt.as_mut_ptr()) };
if ret < 0 {
Err(AvError::new(ret))
} else {
Ok(())
}
}
pub fn send_eof(&mut self) -> Result<(), AvError> {
let ret = unsafe { ffi_av_bsf_send_packet(self.ptr.as_ptr(), std::ptr::null_mut()) };
if ret < 0 {
Err(AvError::new(ret))
} else {
Ok(())
}
}
pub fn receive_packet(&mut self, pkt: &mut Packet) -> Result<ReceiveOutcome, AvError> {
let ret = unsafe { ffi_av_bsf_receive_packet(self.ptr.as_ptr(), pkt.as_mut_ptr()) };
classify_receive(if ret < 0 { Err(ret) } else { Ok(()) })
}
}
impl Drop for BsfContext {
fn drop(&mut self) {
let mut raw = self.ptr.as_ptr();
unsafe { ffi_av_bsf_free(&raw mut raw) };
}
}
unsafe impl Send for BsfContext {}
#[cfg(test)]
mod tests {
use super::*;
const TB: AVRational = AVRational { num: 1, den: 1000 };
#[test]
fn bsf_context_should_reject_an_unknown_filter_name() {
let err = BsfContext::open("no_such_bitstream_filter", None, TB)
.expect_err("an unregistered filter name must not open");
assert_eq!(err.code(), crate::error_codes::BSF_NOT_FOUND);
}
#[test]
fn bsf_context_should_reject_an_empty_spec() {
let err = BsfContext::open("", None, TB).expect_err("an empty spec must not open");
assert_eq!(err.code(), crate::error_codes::EINVAL);
}
#[test]
fn passthrough_should_drain_from_need_input_to_drained() {
let mut bsf = BsfContext::passthrough(None, TB).expect("the null filter must open");
let out_tb = bsf.output_time_base();
assert_eq!(
(out_tb.num, out_tb.den),
(TB.num, TB.den),
"the null filter must pass the time base through"
);
let mut pkt = Packet::new().expect("packet");
assert!(
matches!(bsf.receive_packet(&mut pkt), Ok(ReceiveOutcome::NeedInput)),
"a fresh filter must ask for input"
);
bsf.send_eof().expect("send_eof");
assert!(
matches!(bsf.receive_packet(&mut pkt), Ok(ReceiveOutcome::Drained)),
"after end of stream the filter must report drained"
);
}
}