use crate::error::AllocFrameError;
use ffmpeg_sys_next::AVMediaType::{
AVMEDIA_TYPE_ATTACHMENT, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_DATA, AVMEDIA_TYPE_SUBTITLE,
AVMEDIA_TYPE_VIDEO,
};
#[cfg(ffmpeg_8_0)]
use ffmpeg_sys_next::av_frame_side_data_clone;
#[cfg(not(docsrs))]
use ffmpeg_sys_next::av_frame_side_data_free;
use ffmpeg_sys_next::{
av_freep, av_gettime_relative, avcodec_free_context, avformat_close_input,
avformat_free_context, avio_closep, avio_context_free, AVCodecContext, AVFormatContext,
AVFrameSideData, AVIOContext, AVMediaType, AVRational, AVStream, AVFMT_NOFILE,
};
use std::ffi::c_void;
use std::ptr::null_mut;
use std::sync::atomic::{AtomicI64, AtomicUsize, Ordering};
use std::sync::Arc;
const OUTPUT_END_GRACE_US: i64 = 500_000;
pub(crate) struct InterruptState {
scheduler_status: Arc<AtomicUsize>,
end_grace_start_us: AtomicI64,
}
impl InterruptState {
pub(crate) fn new(scheduler_status: Arc<AtomicUsize>) -> Self {
Self {
scheduler_status,
end_grace_start_us: AtomicI64::new(0),
}
}
fn should_interrupt_input(&self) -> bool {
crate::core::scheduler::ffmpeg_scheduler::is_stopping(
self.scheduler_status.load(Ordering::Acquire),
)
}
fn should_interrupt_output(&self) -> bool {
let status = self.scheduler_status.load(Ordering::Acquire);
if status == crate::core::scheduler::ffmpeg_scheduler::STATUS_ABORT {
return true;
}
if status != crate::core::scheduler::ffmpeg_scheduler::STATUS_END {
return false;
}
let now = unsafe { av_gettime_relative() };
let start = match self.end_grace_start_us.compare_exchange(
0,
now,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => now,
Err(previous) => previous,
};
now - start > OUTPUT_END_GRACE_US
}
}
pub(crate) unsafe extern "C" fn input_interrupt_cb(opaque: *mut c_void) -> libc::c_int {
let state = &*(opaque as *const InterruptState);
state.should_interrupt_input() as libc::c_int
}
pub(crate) unsafe extern "C" fn output_interrupt_cb(opaque: *mut c_void) -> libc::c_int {
let state = &*(opaque as *const InterruptState);
state.should_interrupt_output() as libc::c_int
}
pub(crate) struct MuxStartGate {
started: std::sync::atomic::AtomicBool,
lock: std::sync::Mutex<()>,
}
pub(crate) enum PreSendOutcome {
Sent,
Started(PacketBox),
Full(PacketBox),
Disconnected(PacketBox),
}
impl MuxStartGate {
pub(crate) fn new() -> Self {
Self {
started: std::sync::atomic::AtomicBool::new(false),
lock: std::sync::Mutex::new(()),
}
}
pub(crate) fn is_started(&self) -> bool {
self.started.load(Ordering::Acquire)
}
pub(crate) fn start_with(&self, drain: impl FnOnce()) {
let _guard = self.lock.lock().unwrap();
drain();
self.started.store(true, Ordering::Release);
}
pub(crate) fn send_pre(
&self,
pre_sender: &crossbeam_channel::Sender<PacketBox>,
packet_box: PacketBox,
) -> PreSendOutcome {
let _guard = self.lock.lock().unwrap();
if self.started.load(Ordering::Acquire) {
return PreSendOutcome::Started(packet_box);
}
match pre_sender.try_send(packet_box) {
Ok(()) => PreSendOutcome::Sent,
Err(crossbeam_channel::TrySendError::Full(pb)) => PreSendOutcome::Full(pb),
Err(crossbeam_channel::TrySendError::Disconnected(pb)) => {
PreSendOutcome::Disconnected(pb)
}
}
}
}
use ffmpeg_context::{InputOpaque, OutputOpaque};
pub mod ffmpeg_context;
pub mod ffmpeg_context_builder;
pub mod input;
pub mod output;
pub mod filter_complex;
pub(super) mod decoder_stream;
pub(super) mod demuxer;
pub(super) mod encoder_stream;
pub(super) mod filter_graph;
pub(super) mod input_filter;
pub(super) mod muxer;
pub(super) mod obj_pool;
pub(super) mod output_filter;
pub mod null_output;
pub(crate) struct CodecContext {
inner: *mut AVCodecContext,
}
unsafe impl Send for CodecContext {}
impl CodecContext {
pub(crate) fn new(avcodec_context: *mut AVCodecContext) -> Self {
Self {
inner: avcodec_context,
}
}
pub(crate) fn null() -> Self {
Self { inner: null_mut() }
}
pub(crate) fn as_mut_ptr(&self) -> *mut AVCodecContext {
self.inner
}
pub(crate) fn as_ptr(&self) -> *const AVCodecContext {
self.inner as *const AVCodecContext
}
}
impl Drop for CodecContext {
fn drop(&mut self) {
unsafe {
avcodec_free_context(&mut self.inner);
}
}
}
#[derive(Copy, Clone)]
pub(crate) struct Stream {
pub(crate) inner: *mut AVStream,
}
unsafe impl Send for Stream {}
pub(crate) struct FrameBox {
pub(crate) frame: ffmpeg_next::Frame,
pub(crate) frame_data: FrameData,
}
unsafe impl Send for FrameBox {}
pub fn frame_alloc() -> crate::error::Result<ffmpeg_next::Frame> {
unsafe {
let frame = ffmpeg_next::Frame::empty();
if frame.as_ptr().is_null() {
return Err(AllocFrameError::OutOfMemory.into());
}
Ok(frame)
}
}
pub fn null_frame() -> ffmpeg_next::Frame {
unsafe { ffmpeg_next::Frame::wrap(null_mut()) }
}
pub(crate) struct SideDataList {
entries: *mut *mut AVFrameSideData,
count: i32,
}
impl SideDataList {
pub(crate) fn new() -> Self {
Self {
entries: null_mut(),
count: 0,
}
}
#[cfg(ffmpeg_8_0)]
pub(crate) fn push_clone(&mut self, sd: *const AVFrameSideData, flags: u32) -> i32 {
unsafe { av_frame_side_data_clone(&mut self.entries, &mut self.count, sd, flags) }
}
pub(crate) fn clear(&mut self) {
#[cfg(not(docsrs))]
unsafe {
av_frame_side_data_free(&mut self.entries, &mut self.count)
}
}
#[cfg(ffmpeg_8_0)]
pub(crate) fn len(&self) -> i32 {
self.count
}
#[cfg(ffmpeg_8_0)]
pub(crate) fn as_mut_ptr(&mut self) -> *mut *mut AVFrameSideData {
self.entries
}
#[cfg(ffmpeg_8_0)]
pub(crate) fn iter(&self) -> impl Iterator<Item = *const AVFrameSideData> + '_ {
(0..self.count).map(|i| unsafe { *self.entries.offset(i as isize) as *const AVFrameSideData })
}
}
impl Drop for SideDataList {
fn drop(&mut self) {
self.clear();
}
}
unsafe impl Send for SideDataList {}
unsafe impl Sync for SideDataList {}
#[derive(Clone)]
pub(crate) struct FrameData {
pub(crate) framerate: Option<AVRational>,
pub(crate) bits_per_raw_sample: i32,
pub(crate) input_stream_width: i32,
pub(crate) input_stream_height: i32,
pub(crate) subtitle_header: Option<Arc<[u8]>>,
pub(crate) fg_input_index: usize,
#[cfg_attr(not(ffmpeg_8_0), allow(dead_code))]
pub(crate) side_data: Option<Arc<SideDataList>>,
}
pub(crate) struct PacketBox {
pub(crate) packet: ffmpeg_next::Packet,
pub(crate) packet_data: PacketData,
}
unsafe impl Send for PacketBox {}
#[derive(Clone)]
pub(crate) struct PacketData {
pub(crate) dts_est: i64,
pub(crate) codec_type: AVMediaType,
pub(crate) output_stream_index: i32,
pub(crate) is_copy: bool,
}
pub(crate) fn out_fmt_ctx_free(out_fmt_ctx: *mut AVFormatContext, is_set_write_callback: bool) {
if out_fmt_ctx.is_null() {
return;
}
unsafe {
if is_set_write_callback {
free_output_opaque((*out_fmt_ctx).pb);
} else if (*out_fmt_ctx).flags & AVFMT_NOFILE == 0 {
let mut pb = (*out_fmt_ctx).pb;
if !pb.is_null() {
avio_closep(&mut pb);
}
}
avformat_free_context(out_fmt_ctx);
}
}
pub(crate) unsafe fn free_output_opaque(mut avio_ctx: *mut AVIOContext) {
if avio_ctx.is_null() {
return;
}
if !(*avio_ctx).buffer.is_null() {
av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
}
let opaque_ptr = (*avio_ctx).opaque as *mut OutputOpaque;
if !opaque_ptr.is_null() {
let _ = Box::from_raw(opaque_ptr);
}
avio_context_free(&mut avio_ctx);
}
pub(crate) fn in_fmt_ctx_free(mut in_fmt_ctx: *mut AVFormatContext, is_set_read_callback: bool) {
if in_fmt_ctx.is_null() {
return;
}
unsafe {
let avio_ctx = if is_set_read_callback {
(*in_fmt_ctx).pb
} else {
null_mut()
};
avformat_close_input(&mut in_fmt_ctx);
free_input_opaque(avio_ctx);
}
}
pub(crate) unsafe fn free_input_opaque(mut avio_ctx: *mut AVIOContext) {
if !avio_ctx.is_null() {
let opaque_ptr = (*avio_ctx).opaque as *mut InputOpaque;
if !opaque_ptr.is_null() {
let _ = Box::from_raw(opaque_ptr);
}
av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
avio_context_free(&mut avio_ctx);
}
}
pub(crate) struct FmtCtxGuard {
ctx: *mut AVFormatContext,
mode: crate::raw::Mode,
}
impl FmtCtxGuard {
pub(crate) fn disarmed() -> Self {
Self {
ctx: null_mut(),
mode: crate::raw::Mode::Input,
}
}
pub(crate) fn arm(&mut self, ctx: *mut AVFormatContext, mode: crate::raw::Mode) {
self.ctx = ctx;
self.mode = mode;
}
pub(crate) fn release(&mut self) -> *mut AVFormatContext {
let ctx = self.ctx;
self.ctx = null_mut();
ctx
}
}
impl Drop for FmtCtxGuard {
fn drop(&mut self) {
if self.ctx.is_null() {
return;
}
match self.mode {
crate::raw::Mode::Input => in_fmt_ctx_free(self.ctx, false),
crate::raw::Mode::InputCustomIo => in_fmt_ctx_free(self.ctx, true),
crate::raw::Mode::Output => out_fmt_ctx_free(self.ctx, false),
crate::raw::Mode::OutputCustomIo => out_fmt_ctx_free(self.ctx, true),
}
}
}
#[allow(dead_code)]
pub(crate) fn type_to_linklabel(media_type: AVMediaType, index: usize) -> Option<String> {
match media_type {
AVMediaType::AVMEDIA_TYPE_UNKNOWN => None,
AVMEDIA_TYPE_VIDEO => Some(format!("{index}:v")),
AVMEDIA_TYPE_AUDIO => Some(format!("{index}:a")),
AVMEDIA_TYPE_DATA => Some(format!("{index}:d")),
AVMEDIA_TYPE_SUBTITLE => Some(format!("{index}:s")),
AVMEDIA_TYPE_ATTACHMENT => Some(format!("{index}:t")),
AVMediaType::AVMEDIA_TYPE_NB => None,
}
}