use crate::core::context::muxer::{Muxer, SqMuxPlan, StreamBsfChains};
use crate::core::context::obj_pool::ObjPool;
use crate::core::context::pre_mux_queue::PreMuxQueueReceiver;
use crate::core::context::{PacketBox, PacketData};
use crate::core::scheduler::ffmpeg_scheduler::{
is_stopping, packet_is_null, set_scheduler_error, wait_until_not_paused, STATUS_ABORT,
STATUS_END,
};
use crate::core::scheduler::input_controller::{InputController, SchNode};
use crate::core::scheduler::sync_queue::SyncQueue;
use crate::error::Error::Muxing;
use crate::error::{MuxingError, MuxingOperationError, OpenOutputError, WriteHeaderError};
use crate::raw::{BitStreamFilter, FormatContext};
use crate::util::ffmpeg_utils::{av_err2str, hashmap_to_avdictionary, DictGuard};
use crate::util::thread_synchronizer::ThreadSynchronizer;
use crossbeam_channel::{Receiver, RecvTimeoutError, Sender};
use ffmpeg_next::packet::{Mut, Ref};
use ffmpeg_next::Packet;
use ffmpeg_sys_next::AVMediaType::{AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_SUBTITLE, AVMEDIA_TYPE_VIDEO};
use ffmpeg_sys_next::{
av_compare_ts, av_get_audio_frame_duration2, av_interleaved_write_frame, av_packet_move_ref,
av_packet_rescale_ts, av_rescale_delta, av_rescale_q, av_write_trailer,
avcodec_parameters_copy, avformat_write_header, avio_open2, AVFormatContext, AVPacket,
AVRational, AVERROR, AVERROR_EOF, AVFMT_NOFILE, AVFMT_NOTIMESTAMPS, AVFMT_TS_NONSTRICT,
AVIO_FLAG_WRITE, AV_LOG_DEBUG, AV_LOG_WARNING, AV_NOPTS_VALUE, AV_PKT_FLAG_KEY, AV_TIME_BASE_Q,
EAGAIN, ENOMEM,
};
use log::{debug, error, info, trace, warn};
use std::collections::HashMap;
use std::collections::VecDeque;
use std::ffi::{CStr, CString};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;
struct SqMux {
queue: SyncQueue<PacketBox>,
sq_idx: Vec<Option<usize>>,
ostream: Vec<usize>,
}
fn build_sq_mux(plan: SqMuxPlan, stream_count: usize) -> SqMux {
let mut queue = SyncQueue::<PacketBox>::new(plan.buf_size_us);
let mut sq_idx = vec![None; stream_count];
let mut ostream = Vec::with_capacity(plan.streams.len());
for (output_stream_index, limiting, frames_max) in plan.streams {
let idx = queue.add_stream(limiting);
if output_stream_index < stream_count {
sq_idx[output_stream_index] = Some(idx);
}
ostream.push(output_stream_index);
if let Some(max) = frames_max {
queue.sq_limit_frames(idx, max);
}
}
SqMux {
queue,
sq_idx,
ostream,
}
}
unsafe fn sq_pkt_end(pkt: *const AVPacket) -> (Option<i64>, AVRational, i32) {
let pts = (*pkt).pts;
let end = if pts == AV_NOPTS_VALUE {
None
} else {
Some(pts + (*pkt).duration)
};
(end, (*pkt).time_base, 0)
}
pub(crate) fn mux_init(
mux_idx: usize,
mux: &mut Muxer,
packet_pool: ObjPool<Packet>,
input_controller: Arc<InputController>,
mux_stream_nodes: Vec<Arc<SchNode>>,
scheduler_status: Arc<AtomicUsize>,
thread_sync: ThreadSynchronizer,
scheduler_result: Arc<Mutex<Option<crate::error::Result<()>>>>,
mux_done_remaining: Arc<AtomicUsize>,
) -> crate::error::Result<()> {
let sq_mux_plan = mux.sq_mux_plan();
let out_fmt_ctx = mux
.out_fmt_ctx
.take()
.expect("mux_init called without an output context");
let mux_done = MuxDoneGuard::new(mux_done_remaining, scheduler_status.clone());
let (queue_sender, pkt_receiver) = match mux.take_queue() {
Some((s, r)) => (Some(s), Some(r)),
None => (None, None),
};
let guard = MuxTeardownGuard::new(
pkt_receiver,
mux.take_src_pre_recvs(),
mux.enc_handle_receiver(),
out_fmt_ctx,
);
let slot_guard = MuxSlotGuard::armed(thread_sync.clone(), scheduler_status.clone());
mux_task_start(
mux_idx,
guard,
slot_guard,
queue_sender,
mux.start_time_us,
mux.recording_time_us,
mux.stream_count(),
mux.format_opts.clone(),
mux.bsf_chains.clone(),
mux.mux_start_gate(),
mux.interrupt_state.clone(),
packet_pool,
input_controller,
mux_stream_nodes,
sq_mux_plan,
scheduler_status,
scheduler_result,
mux_done,
)
}
pub(crate) fn ready_to_init_mux(
mux_idx: usize,
mux: &mut Muxer,
packet_pool: ObjPool<Packet>,
input_controller: Arc<InputController>,
scheduler_status: Arc<AtomicUsize>,
thread_sync: ThreadSynchronizer,
scheduler_result: Arc<Mutex<Option<crate::error::Result<()>>>>,
mux_done_remaining: Arc<AtomicUsize>,
) -> crate::error::Result<Option<crossbeam_channel::Sender<i32>>> {
if !mux.is_ready() {
let (sender, receiver) = crossbeam_channel::bounded(1);
let mux_done = MuxDoneGuard::new(mux_done_remaining, scheduler_status.clone());
let sq_mux_plan = mux.sq_mux_plan();
let out_fmt_ctx = mux
.out_fmt_ctx
.take()
.expect("ready_to_init_mux called without an output context");
let mux_stream_nodes = mux.mux_stream_nodes.clone();
let (queue_sender, pkt_receiver) = match mux.take_queue() {
Some((s, r)) => (Some(s), Some(r)),
None => (None, None),
};
let guard = MuxTeardownGuard::new(
pkt_receiver,
mux.take_src_pre_recvs(),
mux.enc_handle_receiver(),
out_fmt_ctx,
);
let mux_start_gate = mux.mux_start_gate();
let interrupt_state = mux.interrupt_state.clone();
let start_time_us = mux.start_time_us;
let recording_time_us = mux.recording_time_us;
let stream_count = mux.stream_count();
let nb_streams_ready = mux.nb_streams_ready.clone();
let enc_registered = mux.enc_registered.clone();
let format_opts = mux.format_opts.clone();
let bsf_chains = mux.bsf_chains.clone();
let result = std::thread::Builder::new().name(format!("ready-to-init-muxer{mux_idx}")).spawn(move || {
let mut slot_guard = Some(MuxSlotGuard::armed(
thread_sync.clone(),
scheduler_status.clone(),
));
let mut guard = Some(guard);
let _registration_barrier = MuxRegistrationBarrier {
enc_registered: enc_registered.clone(),
};
let _panic_status = MuxPanicStatusGuard {
scheduler_status: scheduler_status.clone(),
scheduler_result: scheduler_result.clone(),
};
let wait_enc_registered = || {
while !enc_registered.load(Ordering::Acquire) {
std::thread::sleep(Duration::from_millis(1));
}
};
let mut queue_sender = queue_sender;
loop {
let result = receiver.recv_timeout(Duration::from_millis(100));
if is_stopping(wait_until_not_paused(&scheduler_status)) {
info!("Init muxer receiver end command, finishing.");
break;
}
if let Err(e) = result {
if e == RecvTimeoutError::Disconnected {
warn!(
"mux init aborted: encoder(s) exited before all {stream_count} streams became ready ({} ready)",
nb_streams_ready.load(Ordering::Acquire)
);
break;
}
continue;
}
let stream_index = result.unwrap();
debug!("output_stream: {stream_index} is readied");
let nb_streams_ready = nb_streams_ready.fetch_add(1, Ordering::Release);
if nb_streams_ready + 1 == stream_count {
wait_enc_registered();
let guard = guard
.take()
.expect("mux waiter reached all-ready without a context");
let slot_guard = slot_guard
.take()
.expect("mux waiter reached all-ready without its slot guard");
if let Err(e) = mux_task_start(
mux_idx,
guard,
slot_guard,
queue_sender.take(),
start_time_us,
recording_time_us,
stream_count,
format_opts,
bsf_chains,
mux_start_gate,
interrupt_state,
packet_pool,
input_controller,
mux_stream_nodes,
sq_mux_plan,
scheduler_status.clone(),
scheduler_result,
mux_done,
) {
debug!("Muxer init failed: {e}");
}
break;
}
}
if let Some(guard) = guard.take() {
wait_enc_registered();
drop(guard);
match slot_guard.take() {
Some(slot_guard) => slot_guard.release(),
None => thread_sync.thread_done_with(|| {
scheduler_status.store(STATUS_END, Ordering::Release);
}),
}
}
});
if let Err(e) = result {
error!("Mux init thread exited with error: {e}");
return Err(MuxingOperationError::ThreadExited.into());
}
Ok(Some(sender))
} else {
Ok(None)
}
}
fn mux_task_start(
mux_idx: usize,
guard: MuxTeardownGuard,
slot_guard: MuxSlotGuard,
queue_sender: Option<Sender<PacketBox>>,
start_time_us: Option<i64>,
recording_time_us: Option<i64>,
stream_count: usize,
format_opts: Option<HashMap<CString, CString>>,
bsf_chains: StreamBsfChains,
mux_start_gate: Arc<crate::core::context::MuxStartGate>,
interrupt_state: Arc<crate::core::context::InterruptState>,
packet_pool: ObjPool<Packet>,
input_controller: Arc<InputController>,
mux_stream_nodes: Vec<Arc<SchNode>>,
sq_mux_plan: Option<SqMuxPlan>,
scheduler_status: Arc<AtomicUsize>,
scheduler_result: Arc<Mutex<Option<crate::error::Result<()>>>>,
mux_done: MuxDoneGuard,
) -> crate::error::Result<()> {
let Some(queue_sender) = queue_sender else {
if let Err(open_ret) = unsafe { open_muxer_output(guard.ctx().as_ptr()) } {
fail_mux_init(
&scheduler_status,
&scheduler_result,
guard,
slot_guard,
crate::error::Error::OpenOutput(OpenOutputError::from(open_ret)),
);
error!("Error opening output: {}", av_err2str(open_ret));
return Err(crate::error::Error::OpenOutput(OpenOutputError::from(
open_ret,
)));
}
drop(guard);
slot_guard.release();
return Ok(());
};
let src_pre_receivers = _mux_init(
mux_idx,
guard,
slot_guard,
start_time_us,
recording_time_us,
stream_count,
format_opts,
bsf_chains,
interrupt_state,
packet_pool,
input_controller,
mux_stream_nodes,
sq_mux_plan,
scheduler_status,
scheduler_result,
mux_done,
)?;
mux_start_gate.start_with(|| {
let mut queues: Vec<VecDeque<PacketBox>> = src_pre_receivers
.iter()
.map(|receiver| receiver.drain_all())
.collect();
loop {
let mut min_stream = None;
let mut min_ts: Option<(i64, AVRational)> = None;
for (i, queue) in queues.iter().enumerate() {
let Some(front) = queue.front() else { continue };
let (dts, tb) = unsafe {
let pkt = front.packet.as_ptr();
((*pkt).dts, (*pkt).time_base)
};
if dts == AV_NOPTS_VALUE || tb.num <= 0 || tb.den <= 0 {
min_stream = Some(i);
break;
}
match min_ts {
Some((min_dts, min_tb))
if unsafe { av_compare_ts(min_dts, min_tb, dts, tb) } <= 0 => {}
_ => {
min_stream = Some(i);
min_ts = Some((dts, tb));
}
}
}
let Some(i) = min_stream else { break };
let packet_box = queues[i].pop_front().unwrap();
let _ = queue_sender.send(packet_box);
}
});
Ok(())
}
fn _mux_init(
mux_idx: usize,
guard: MuxTeardownGuard,
slot_guard: MuxSlotGuard,
start_time_us: Option<i64>,
recording_time_us: Option<i64>,
stream_count: usize,
format_opts: Option<HashMap<CString, CString>>,
bsf_chains: StreamBsfChains,
interrupt_state: Arc<crate::core::context::InterruptState>,
packet_pool: ObjPool<Packet>,
input_controller: Arc<InputController>,
mux_stream_nodes: Vec<Arc<SchNode>>,
sq_mux_plan: Option<SqMuxPlan>,
scheduler_status: Arc<AtomicUsize>,
scheduler_result: Arc<Mutex<Option<crate::error::Result<()>>>>,
mux_done: MuxDoneGuard,
) -> crate::error::Result<Vec<PreMuxQueueReceiver>> {
let slot_guard = slot_guard;
let mut guard = guard;
let mux_done = mux_done;
let _panic_status = MuxPanicStatusGuard {
scheduler_status: scheduler_status.clone(),
scheduler_result: scheduler_result.clone(),
};
let out_fmt_ctx_ptr = unsafe { guard.ctx().as_ptr() };
let mut opts = DictGuard::new(hashmap_to_avdictionary(&format_opts));
let stream_bsfs =
match unsafe { init_bitstream_filters(out_fmt_ctx_ptr, &bsf_chains, stream_count) } {
Ok(bsfs) => bsfs,
Err((name, bsf_ret)) => {
error!(
"Could not initialize bitstream filter chain '{name}': {}",
av_err2str(bsf_ret)
);
fail_mux_init(
&scheduler_status,
&scheduler_result,
guard,
slot_guard,
Muxing(MuxingOperationError::BitstreamFilterInit(
name.clone(),
MuxingError::from(bsf_ret),
)),
);
return Err(Muxing(MuxingOperationError::BitstreamFilterInit(
name,
MuxingError::from(bsf_ret),
)));
}
};
if let Err(open_ret) = unsafe { open_muxer_output(out_fmt_ctx_ptr) } {
error!("Error opening output: {}", av_err2str(open_ret));
fail_mux_init(
&scheduler_status,
&scheduler_result,
guard,
slot_guard,
crate::error::Error::OpenOutput(OpenOutputError::from(open_ret)),
);
return Err(crate::error::Error::OpenOutput(OpenOutputError::from(
open_ret,
)));
}
let ret = unsafe { avformat_write_header(out_fmt_ctx_ptr, opts.as_double_ptr()) };
if ret < 0 {
error!(
"Could not write header (incorrect codec parameters ?): {}",
av_err2str(ret)
);
fail_mux_init(
&scheduler_status,
&scheduler_result,
guard,
slot_guard,
Muxing(MuxingOperationError::WriteHeader(WriteHeaderError::from(
ret,
))),
);
return Err(Muxing(MuxingOperationError::WriteHeader(
WriteHeaderError::from(ret),
)));
}
for key in opts.leftover_keys() {
warn!("Option '{key}' was not recognized by output {mux_idx}");
}
let oformat_flags = unsafe {
let oformat = (*out_fmt_ctx_ptr).oformat;
(*oformat).flags
};
let format_name = unsafe {
CStr::from_ptr((*(*out_fmt_ctx_ptr).oformat).name)
.to_str()
.unwrap_or("unknown")
};
let scheduler_status_spawn = scheduler_status.clone();
let scheduler_result_spawn = scheduler_result.clone();
let thread_name = format!("muxer{mux_idx}:{format_name}");
let src_pre_receivers = guard.take_pre_receivers();
let guard_slot = Arc::new(Mutex::new((Some(guard), Some(slot_guard))));
let worker_guard_slot = Arc::clone(&guard_slot);
let result = std::thread::Builder::new().name(thread_name).spawn(move || {
let slot_guard = worker_guard_slot
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.1
.take()
.expect("mux worker started without its slot guard");
let mut guard = worker_guard_slot
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.0
.take()
.expect("mux worker started without its teardown guard");
let mux_done = mux_done;
let _panic_status = MuxPanicStatusGuard {
scheduler_status: scheduler_status.clone(),
scheduler_result: scheduler_result.clone(),
};
let pkt_receiver = guard
.take_pkt_receiver()
.expect("mux worker without a packet queue");
let out_fmt_ctx: &FormatContext = guard.ctx();
let mut stream_bsfs = stream_bsfs;
let has_bsf = !stream_bsfs.is_empty();
let mut stream_pkt_templates: Vec<Option<PacketData>> = if has_bsf {
(0..stream_count).map(|_| None).collect()
} else {
Vec::new()
};
let mut stream_started: Vec<bool> = vec![false; stream_count];
let mut stream_eof: Vec<bool> = vec![false; stream_count];
let mut st_rescale_delta_last: Vec<i64> = vec![0; stream_count];
let mut st_last_dts: Vec<i64> = vec![AV_NOPTS_VALUE; stream_count];
let sq_mux = sq_mux_plan.map(|plan| build_sq_mux(plan, stream_count));
let mut nb_done = 0;
let cfg = MuxWriteCfg {
has_bsf,
oformat_flags,
stream_count,
out_fmt_ctx: &out_fmt_ctx,
packet_pool: &packet_pool,
mux_stream_nodes: &mux_stream_nodes,
input_controller: &input_controller,
scheduler_status: &scheduler_status,
};
let mut state = MuxWriteState {
stream_pkt_templates: &mut stream_pkt_templates,
st_rescale_delta_last: &mut st_rescale_delta_last,
st_last_dts: &mut st_last_dts,
stream_eof: &mut stream_eof,
nb_done: &mut nb_done,
};
let mut ret = 0;
if let Some(mut sq) = sq_mux {
let mut released: Vec<PacketBox> = Vec::new();
let mut nf: Vec<usize> = Vec::new();
let fin_tb = AVRational { num: 1, den: 1 };
for i in 0..stream_count {
if sq.sq_idx.get(i).copied().flatten().is_none() {
if let Err(e) = unsafe {
sq_finish_output_stream(
i,
&cfg,
&mut state,
&mut stream_bsfs,
)
} {
ret = e;
}
}
}
while *state.nb_done < stream_count && ret >= 0 {
let result = pkt_receiver.recv_timeout(Duration::from_millis(100));
if is_stopping(wait_until_not_paused(&scheduler_status)) {
info!("Muxer receiver end command, finishing.");
break;
}
let mut packet_box = match result {
Ok(pb) => pb,
Err(RecvTimeoutError::Disconnected) => {
debug!("Encoder thread exit.");
break;
}
Err(RecvTimeoutError::Timeout) => {
match sq_mux_pump(
&mut sq, &mut released, &mut nf, &cfg, &mut state, &mut stream_bsfs,
) {
Ok(true) => break,
Ok(false) => continue,
Err(e) => { ret = e; break; }
}
}
};
let pkt = packet_box.packet.as_ptr();
let raw_stream_index = unsafe { (*pkt).stream_index };
if raw_stream_index < 0 {
let eof_stream = packet_box.packet_data.output_stream_index;
packet_pool.release(packet_box.packet);
if eof_stream >= 0 {
if let Some(Some(sq_i)) =
sq.sq_idx.get(eof_stream as usize).copied()
{
sq.queue.send(sq_i, None, None, fin_tb, 0);
}
}
match sq_mux_pump(
&mut sq, &mut released, &mut nf, &cfg, &mut state, &mut stream_bsfs,
) {
Ok(true) => break,
Ok(false) => continue,
Err(e) => { ret = e; break; }
}
}
let stream_index = raw_stream_index as usize;
if stream_index >= mux_stream_nodes.len() {
error!("Invalid stream_index: {} >= {}", stream_index, mux_stream_nodes.len());
packet_pool.release(packet_box.packet);
continue;
}
let is_marker = unsafe {
let has_side_data = (*pkt).side_data_elems > 0;
packet_is_null(&packet_box.packet)
|| (packet_box.packet.is_empty() && !has_side_data)
};
if is_marker {
if scheduler_status.load(Ordering::Acquire) == STATUS_ABORT {
debug!("Muxer detected abort from stream {}, exiting without trailer", stream_index);
packet_pool.release(packet_box.packet);
break;
}
packet_pool.release(packet_box.packet);
if !state.stream_eof[stream_index] {
if let Some(Some(sq_i)) = sq.sq_idx.get(stream_index).copied() {
sq.queue.send(sq_i, None, None, fin_tb, 0);
}
}
match sq_mux_pump(
&mut sq, &mut released, &mut nf, &cfg, &mut state, &mut stream_bsfs,
) {
Ok(true) => break,
Ok(false) => continue,
Err(e) => { ret = e; break; }
}
}
unsafe {
update_last_dts(&mux_stream_nodes[stream_index], &input_controller, &scheduler_status, pkt);
}
if state.stream_eof[stream_index] {
packet_pool.release(packet_box.packet);
continue;
}
let sq_i = match sq.sq_idx.get(stream_index).copied().flatten() {
Some(i) => i,
None => {
let wret = unsafe {
mux_write_released(
&mut packet_box, &cfg, &mut state, &mut stream_bsfs,
)
};
packet_pool.release(packet_box.packet);
if wret == AVERROR_EOF { break; }
if wret < 0 { ret = wret; error!("Error muxing a packet: stream_index={stream_index}, ret={wret}"); break; }
continue;
}
};
if packet_box.packet_data.is_copy {
let started = &mut stream_started[stream_index];
let rret = unsafe {
streamcopy_rescale(
packet_box.packet.as_mut_ptr(),
&packet_box.packet_data,
&start_time_us,
&recording_time_us,
started,
)
};
if rret == AVERROR(EAGAIN) {
packet_pool.release(packet_box.packet);
continue;
} else if rret == AVERROR_EOF {
packet_pool.release(packet_box.packet);
sq.queue.send(sq_i, None, None, fin_tb, 0);
match sq_mux_pump(
&mut sq, &mut released, &mut nf, &cfg, &mut state, &mut stream_bsfs,
) {
Ok(true) => break,
Ok(false) => continue,
Err(e) => { ret = e; break; }
}
}
}
let (end_ts, tb, nb_samples) =
unsafe { sq_pkt_end(packet_box.packet.as_ptr()) };
sq.queue.send(sq_i, Some(packet_box), end_ts, tb, nb_samples);
match sq_mux_pump(
&mut sq, &mut released, &mut nf, &cfg, &mut state, &mut stream_bsfs,
) {
Ok(true) => break,
Ok(false) => {}
Err(e) => { ret = e; break; }
}
}
} else {
loop {
let result = pkt_receiver.recv_timeout(Duration::from_millis(100));
if is_stopping(wait_until_not_paused(&scheduler_status)) {
info!("Muxer receiver end command, finishing.");
break;
}
if let Err(e) = result {
if e == RecvTimeoutError::Disconnected {
debug!("Encoder thread exit.");
break;
}
continue;
}
let mut packet_box = result.unwrap();
let pkt = packet_box.packet.as_ptr();
let packet_data = &packet_box.packet_data;
let raw_stream_index = unsafe { (*pkt).stream_index };
if raw_stream_index < 0 {
let eof_stream = packet_box.packet_data.output_stream_index;
if eof_stream >= 0 {
let eof_idx = eof_stream as usize;
if eof_idx < stream_count && !state.stream_eof[eof_idx] {
if has_bsf {
let fret = unsafe {
flush_stream_bsf(
&cfg,
&mut state,
&mut stream_bsfs,
eof_idx,
)
};
if fret < 0 {
ret = fret;
error!("Error flushing bitstream filter at EOF: stream={eof_idx}, ret={fret}");
packet_pool.release(packet_box.packet);
break;
}
}
state.stream_eof[eof_idx] = true;
*state.nb_done += 1;
if eof_idx < mux_stream_nodes.len() {
let node = mux_stream_nodes[eof_idx].as_ref();
let SchNode::MuxStream { src: _, last_dts: _, source_finished } = node else { unreachable!() };
source_finished.store(true, Ordering::Release);
input_controller.update_locked(&scheduler_status);
}
}
}
packet_pool.release(packet_box.packet);
if *state.nb_done == stream_count {
trace!("All streams finished (demux EOF signal)");
break;
}
continue;
}
let stream_index = raw_stream_index as usize;
if stream_index >= mux_stream_nodes.len() {
error!("Invalid stream_index: {} >= {}", stream_index, mux_stream_nodes.len());
packet_pool.release(packet_box.packet);
continue;
}
let mux_stream_node = &mux_stream_nodes[stream_index];
unsafe {
let has_side_data = (*packet_box.packet.as_ptr()).side_data_elems > 0;
if packet_is_null(&packet_box.packet) || (packet_box.packet.is_empty() && !has_side_data) {
let current_status = scheduler_status.load(Ordering::Acquire);
if current_status == STATUS_ABORT {
debug!("Muxer detected abort from stream {}, exiting without trailer", stream_index);
packet_pool.release(packet_box.packet);
break;
}
if state.stream_eof[stream_index] {
packet_pool.release(packet_box.packet);
continue;
}
if has_bsf {
let fret = flush_stream_bsf(
&cfg,
&mut state,
&mut stream_bsfs,
stream_index,
);
if fret < 0 {
ret = fret;
error!("Error flushing bitstream filter at EOF: stream={stream_index}, ret={fret}");
packet_pool.release(packet_box.packet);
break;
}
}
*state.nb_done += 1;
packet_pool.release(packet_box.packet);
let mux_stream_node = mux_stream_node.as_ref();
let SchNode::MuxStream { src: _, last_dts: _, source_finished } = mux_stream_node else { unreachable!() };
source_finished.store(true, Ordering::Release);
input_controller.update_locked(&scheduler_status);
if *state.nb_done == stream_count {
trace!("All streams finished");
break;
} else {
continue;
}
}
update_last_dts(mux_stream_node, &input_controller, &scheduler_status, pkt);
if state.stream_eof[stream_index] {
packet_pool.release(packet_box.packet);
continue;
}
if !packet_is_null(&packet_box.packet) && packet_data.is_copy {
let started = &mut stream_started[stream_index];
let rret = streamcopy_rescale(
packet_box.packet.as_mut_ptr(),
packet_data,
&start_time_us,
&recording_time_us,
started,
);
if rret == AVERROR(EAGAIN) {
packet_pool.release(packet_box.packet);
continue;
}
ret = rret;
if ret == AVERROR_EOF {
if has_bsf {
let fret = flush_stream_bsf(
&cfg,
&mut state,
&mut stream_bsfs,
stream_index,
);
if fret < 0 {
ret = fret;
error!("Error flushing bitstream filter at EOF: stream={stream_index}, ret={fret}");
packet_pool.release(packet_box.packet);
break;
}
}
state.stream_eof[stream_index] = true;
packet_pool.release(packet_box.packet);
*state.nb_done += 1;
let mux_stream_node = mux_stream_node.as_ref();
let SchNode::MuxStream { src: _, last_dts: _, source_finished } = mux_stream_node else { unreachable!() };
source_finished.store(true, Ordering::Release);
input_controller.update_locked(&scheduler_status);
if *state.nb_done == stream_count {
trace!("All streams finished (recording_time)");
break;
}
continue;
}
}
if !packet_is_null(&packet_box.packet)
&& (*packet_box.packet.as_ptr()).stream_index >= 0
{
if has_bsf {
if stream_bsfs[stream_index].is_some() {
state.stream_pkt_templates[stream_index] =
Some(packet_box.packet_data);
}
ret = mux_filter_and_write_packet(
&cfg,
&mut state,
&mut packet_box,
stream_bsfs[stream_index].as_mut(),
);
} else {
ret = write_packet(
&cfg,
&mut state,
&mut packet_box,
);
}
packet_pool.release(packet_box.packet);
if ret == AVERROR_EOF {
trace!("Muxer returned EOF");
break;
} else if ret < 0 {
error!("Error muxing a packet: stream_index={stream_index}, ret={ret}");
break;
}
}
}
}
}
if ret < 0 && ret != AVERROR_EOF {
set_scheduler_error(
&scheduler_status,
&scheduler_result,
Muxing(MuxingOperationError::InterleavedWriteError(
MuxingError::from(ret),
)),
);
}
let _finalizing = (ret >= 0 || ret == AVERROR_EOF)
.then(|| interrupt_state.begin_output_finalize());
let final_status = scheduler_status.load(Ordering::Acquire);
if final_status != STATUS_ABORT {
unsafe {
let ret = av_write_trailer(out_fmt_ctx.as_ptr());
if ret < 0 {
error!("Error writing trailer: {}", av_err2str(ret));
set_scheduler_error(
&scheduler_status,
&scheduler_result,
Muxing(MuxingOperationError::TrailerWriteError(MuxingError::from(
ret,
))),
);
}
}
} else {
debug!("Muxer skipping trailer due to abort");
}
debug!("Muxer finished.");
drop(pkt_receiver);
for node in &mux_stream_nodes {
if let SchNode::MuxStream {
source_finished, ..
} = node.as_ref()
{
source_finished.store(true, Ordering::Release);
}
}
drop(mux_done);
input_controller.update_locked(&scheduler_status);
drop(guard);
slot_guard.release();
});
if let Err(e) = result {
fail_mux_worker_spawn(
&scheduler_status_spawn,
&scheduler_result_spawn,
src_pre_receivers,
&guard_slot,
);
error!("Muxer thread exited with error: {e}");
return Err(MuxingOperationError::ThreadExited.into());
}
Ok(src_pre_receivers)
}
pub(crate) fn release_mux_slot(
scheduler_status: &Arc<AtomicUsize>,
thread_sync: &ThreadSynchronizer,
) {
thread_sync.thread_done_with(|| {
scheduler_status.store(STATUS_END, Ordering::Release);
});
}
fn fail_mux_worker_spawn(
scheduler_status: &Arc<AtomicUsize>,
scheduler_result: &Arc<Mutex<Option<crate::error::Result<()>>>>,
src_pre_receivers: Vec<PreMuxQueueReceiver>,
guard_slot: &Arc<Mutex<(Option<MuxTeardownGuard>, Option<MuxSlotGuard>)>>,
) {
set_scheduler_error(
scheduler_status,
scheduler_result,
Muxing(MuxingOperationError::ThreadExited),
);
drop(src_pre_receivers);
let (guard, slot_guard) = {
let mut handoff = guard_slot
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
(
handoff
.0
.take()
.expect("mux worker spawn failed but the closure consumed the guard"),
handoff
.1
.take()
.expect("mux worker spawn failed but the closure consumed the slot guard"),
)
};
drop(guard);
slot_guard.release();
}
unsafe fn open_muxer_output(out_fmt_ctx: *mut AVFormatContext) -> std::result::Result<(), i32> {
if !(*out_fmt_ctx).pb.is_null() {
return Ok(());
}
if (*(*out_fmt_ctx).oformat).flags & AVFMT_NOFILE != 0 {
return Ok(());
}
let ret = avio_open2(
&mut (*out_fmt_ctx).pb,
(*out_fmt_ctx).url,
AVIO_FLAG_WRITE,
&(*out_fmt_ctx).interrupt_callback,
std::ptr::null_mut(),
);
if ret < 0 {
Err(ret)
} else {
Ok(())
}
}
fn fail_mux_init(
scheduler_status: &Arc<AtomicUsize>,
scheduler_result: &Arc<Mutex<Option<crate::error::Result<()>>>>,
guard: MuxTeardownGuard,
slot_guard: MuxSlotGuard,
error: crate::error::Error,
) {
set_scheduler_error(scheduler_status, scheduler_result, error);
drop(guard);
slot_guard.release();
}
struct MuxDoneGuard {
remaining: Arc<AtomicUsize>,
scheduler_status: Arc<AtomicUsize>,
}
impl MuxDoneGuard {
fn new(remaining: Arc<AtomicUsize>, scheduler_status: Arc<AtomicUsize>) -> Self {
Self {
remaining,
scheduler_status,
}
}
}
impl Drop for MuxDoneGuard {
fn drop(&mut self) {
if self.remaining.fetch_sub(1, Ordering::AcqRel) != 1 {
return;
}
let mut current = self.scheduler_status.load(Ordering::Acquire);
while !is_stopping(current) {
match self.scheduler_status.compare_exchange_weak(
current,
STATUS_END,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => break,
Err(actual) => current = actual,
}
}
}
}
struct MuxSlotGuard {
armed: bool,
thread_sync: ThreadSynchronizer,
scheduler_status: Arc<AtomicUsize>,
}
impl MuxSlotGuard {
fn armed(thread_sync: ThreadSynchronizer, scheduler_status: Arc<AtomicUsize>) -> Self {
Self {
armed: true,
thread_sync,
scheduler_status,
}
}
fn disarm(&mut self) {
self.armed = false;
}
fn release(mut self) {
self.disarm();
let status = self.scheduler_status.clone();
self.thread_sync.thread_done_with(move || {
status.store(STATUS_END, Ordering::Release);
});
}
}
impl Drop for MuxSlotGuard {
fn drop(&mut self) {
if !self.armed {
return;
}
let status = self.scheduler_status.clone();
self.thread_sync.thread_done_with(move || {
status.store(STATUS_END, Ordering::Release);
});
}
}
struct MuxPanicStatusGuard {
scheduler_status: Arc<AtomicUsize>,
scheduler_result: Arc<Mutex<Option<crate::error::Result<()>>>>,
}
impl Drop for MuxPanicStatusGuard {
fn drop(&mut self) {
if !std::thread::panicking() {
return;
}
crate::core::scheduler::ffmpeg_scheduler::set_scheduler_result_only(
&self.scheduler_result,
crate::error::Error::WorkerPanicked(
std::thread::current().name().unwrap_or("muxer").to_string(),
),
);
let mut current = self.scheduler_status.load(Ordering::Acquire);
while !is_stopping(current) {
match self.scheduler_status.compare_exchange_weak(
current,
STATUS_END,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => break,
Err(actual) => current = actual,
}
}
crate::core::scheduler::ffmpeg_scheduler::notify_pause_waiters();
}
}
struct MuxRegistrationBarrier {
enc_registered: Arc<std::sync::atomic::AtomicBool>,
}
impl Drop for MuxRegistrationBarrier {
fn drop(&mut self) {
while !self.enc_registered.load(Ordering::Acquire) {
std::thread::sleep(Duration::from_millis(1));
}
}
}
struct MuxTeardownGuard {
pkt_receiver: Option<Receiver<PacketBox>>,
pre_receivers: Vec<PreMuxQueueReceiver>,
enc_handle_receiver: Receiver<std::thread::JoinHandle<()>>,
out_fmt_ctx: Option<FormatContext>,
}
impl MuxTeardownGuard {
fn new(
pkt_receiver: Option<Receiver<PacketBox>>,
pre_receivers: Vec<PreMuxQueueReceiver>,
enc_handle_receiver: Receiver<std::thread::JoinHandle<()>>,
out_fmt_ctx: FormatContext,
) -> Self {
Self {
pkt_receiver,
pre_receivers,
enc_handle_receiver,
out_fmt_ctx: Some(out_fmt_ctx),
}
}
fn ctx(&self) -> &FormatContext {
self.out_fmt_ctx
.as_ref()
.expect("mux teardown guard lost its output context")
}
fn take_pkt_receiver(&mut self) -> Option<Receiver<PacketBox>> {
self.pkt_receiver.take()
}
fn take_pre_receivers(&mut self) -> Vec<PreMuxQueueReceiver> {
std::mem::take(&mut self.pre_receivers)
}
#[cfg(test)]
fn for_test() -> Self {
Self {
pkt_receiver: None,
pre_receivers: Vec::new(),
enc_handle_receiver: crossbeam_channel::unbounded().1,
out_fmt_ctx: None,
}
}
}
impl Drop for MuxTeardownGuard {
fn drop(&mut self) {
drop(self.pkt_receiver.take());
self.pre_receivers.clear();
while let Ok(handle) = self.enc_handle_receiver.try_recv() {
let _ = handle.join();
}
drop(self.out_fmt_ctx.take());
}
}
unsafe fn update_last_dts(
mux_stream_node: &Arc<SchNode>,
input_controller: &Arc<InputController>,
scheduler_status: &Arc<AtomicUsize>,
pkt: *const AVPacket,
) {
if (*pkt).dts != AV_NOPTS_VALUE {
let dts = av_rescale_q(
(*pkt).dts + (*pkt).duration,
(*pkt).time_base,
AV_TIME_BASE_Q,
);
let node = mux_stream_node.as_ref();
let SchNode::MuxStream {
src: _,
last_dts,
source_finished: _,
} = node
else {
unreachable!()
};
last_dts.store(dts, Ordering::Release);
input_controller.update_locked(scheduler_status);
}
}
unsafe fn streamcopy_rescale(
pkt: *mut AVPacket,
packet_data: &PacketData,
start_time_us: &Option<i64>,
recording_time_us: &Option<i64>,
started: &mut bool,
) -> i32 {
if !packet_data.is_copy {
return 0;
}
let dts = packet_data.dts_est;
let start_time = start_time_us.unwrap_or(0);
if let Some(recording_time_us) = recording_time_us {
if dts >= recording_time_us + start_time {
return AVERROR_EOF;
}
}
if !*started && (*pkt).flags & AV_PKT_FLAG_KEY == 0 {
return AVERROR(EAGAIN);
}
if !*started && start_time_us.is_some() {
let no_pts = (*pkt).pts == AV_NOPTS_VALUE;
let not_start = if no_pts {
dts < start_time
} else {
(*pkt).pts < av_rescale_q(start_time, AV_TIME_BASE_Q, (*pkt).time_base)
};
if not_start {
return AVERROR(EAGAIN);
}
}
let ts_offset = av_rescale_q(start_time, AV_TIME_BASE_Q, (*pkt).time_base);
if (*pkt).pts != AV_NOPTS_VALUE {
(*pkt).pts -= ts_offset;
}
if (*pkt).dts == AV_NOPTS_VALUE {
(*pkt).dts = av_rescale_q(dts, AV_TIME_BASE_Q, (*pkt).time_base);
} else if packet_data.codec_type == AVMEDIA_TYPE_AUDIO {
(*pkt).pts = (*pkt).dts - ts_offset;
}
(*pkt).dts -= ts_offset;
*started = true;
0
}
struct MuxWriteCfg<'a> {
has_bsf: bool,
oformat_flags: i32,
stream_count: usize,
out_fmt_ctx: &'a FormatContext,
packet_pool: &'a ObjPool<Packet>,
mux_stream_nodes: &'a [Arc<SchNode>],
input_controller: &'a Arc<InputController>,
scheduler_status: &'a Arc<AtomicUsize>,
}
struct MuxWriteState<'a> {
stream_pkt_templates: &'a mut [Option<PacketData>],
st_rescale_delta_last: &'a mut [i64],
st_last_dts: &'a mut [i64],
stream_eof: &'a mut [bool],
nb_done: &'a mut usize,
}
unsafe fn write_packet(
cfg: &MuxWriteCfg,
state: &mut MuxWriteState,
sq_packet_box: &mut PacketBox,
) -> i32 {
mux_fixup_ts(cfg, state, sq_packet_box);
(*sq_packet_box.packet.as_mut_ptr()).stream_index =
sq_packet_box.packet_data.output_stream_index;
av_interleaved_write_frame(cfg.out_fmt_ctx.as_ptr(), sq_packet_box.packet.as_mut_ptr())
}
unsafe fn mux_write_released(
packet_box: &mut PacketBox,
cfg: &MuxWriteCfg,
state: &mut MuxWriteState,
stream_bsfs: &mut [Option<BitStreamFilter>],
) -> i32 {
let stream_index = packet_box.packet_data.output_stream_index as usize;
if cfg.has_bsf {
if stream_bsfs.get(stream_index).is_some_and(|b| b.is_some()) {
state.stream_pkt_templates[stream_index] = Some(packet_box.packet_data);
}
mux_filter_and_write_packet(cfg, state, packet_box, stream_bsfs[stream_index].as_mut())
} else {
write_packet(cfg, state, packet_box)
}
}
unsafe fn sq_finish_output_stream(
ost: usize,
cfg: &MuxWriteCfg,
state: &mut MuxWriteState,
stream_bsfs: &mut [Option<BitStreamFilter>],
) -> Result<(), i32> {
if ost >= cfg.stream_count || state.stream_eof[ost] {
return Ok(());
}
if cfg.has_bsf {
let fret = flush_stream_bsf(cfg, state, stream_bsfs, ost);
if fret < 0 {
return Err(fret);
}
}
state.stream_eof[ost] = true;
*state.nb_done += 1;
if ost < cfg.mux_stream_nodes.len() {
if let SchNode::MuxStream {
source_finished, ..
} = cfg.mux_stream_nodes[ost].as_ref()
{
source_finished.store(true, Ordering::Release);
}
}
cfg.input_controller.update_locked(cfg.scheduler_status);
Ok(())
}
fn sq_mux_pump(
sq: &mut SqMux,
released: &mut Vec<PacketBox>,
nf: &mut Vec<usize>,
cfg: &MuxWriteCfg,
state: &mut MuxWriteState,
stream_bsfs: &mut [Option<BitStreamFilter>],
) -> Result<bool, i32> {
released.clear();
sq.queue.drain_all_releasable(released);
for mut pb in released.drain(..) {
let wret = unsafe { mux_write_released(&mut pb, cfg, state, stream_bsfs) };
cfg.packet_pool.release(pb.packet);
if wret == AVERROR_EOF {
return Ok(true);
} else if wret < 0 {
return Err(wret);
}
}
nf.clear();
sq.queue.newly_finished(nf);
for &sq_j in nf.iter() {
let ost = sq.ostream[sq_j];
unsafe {
sq_finish_output_stream(ost, cfg, state, stream_bsfs)?;
}
}
Ok(*state.nb_done == cfg.stream_count)
}
unsafe fn init_bitstream_filters(
out_fmt_ctx: *mut AVFormatContext,
bsf_chains: &StreamBsfChains,
stream_count: usize,
) -> Result<Vec<Option<BitStreamFilter>>, (String, i32)> {
if bsf_chains.is_empty() {
return Ok(Vec::new());
}
let mut stream_bsfs: Vec<Option<BitStreamFilter>> = (0..stream_count).map(|_| None).collect();
for i in 0..stream_count {
let st = *(*out_fmt_ctx).streams.add(i);
let codec_type = (*(*st).codecpar).codec_type;
let Some(chain) = bsf_chains.for_media_type(codec_type) else {
continue;
};
let name = || chain.to_string_lossy().into_owned();
let mut bsf = BitStreamFilter::parse(chain.as_c_str()).map_err(|ret| (name(), ret))?;
let ctx = bsf.as_ptr();
let ret = avcodec_parameters_copy((*ctx).par_in, (*st).codecpar);
if ret < 0 {
return Err((name(), ret));
}
(*ctx).time_base_in = (*st).time_base;
let ret = bsf.init();
if ret < 0 {
return Err((name(), ret));
}
let ret = avcodec_parameters_copy((*st).codecpar, (*ctx).par_out);
if ret < 0 {
return Err((name(), ret));
}
let old_tb = (*st).time_base;
let old_duration = (*st).duration;
(*st).time_base = bsf.time_base_out();
if old_duration != AV_NOPTS_VALUE && old_tb.num > 0 && old_tb.den > 0 {
(*st).duration = av_rescale_q(old_duration, old_tb, (*st).time_base);
}
stream_bsfs[i] = Some(bsf);
}
Ok(stream_bsfs)
}
unsafe fn mux_filter_and_write_packet(
cfg: &MuxWriteCfg,
state: &mut MuxWriteState,
packet_box: &mut PacketBox,
bsf: Option<&mut BitStreamFilter>,
) -> i32 {
let Some(bsf) = bsf else {
return write_packet(cfg, state, packet_box);
};
let pkt = packet_box.packet.as_mut_ptr();
av_packet_rescale_ts(pkt, (*pkt).time_base, bsf.time_base_in());
let ret = bsf.send_packet(pkt);
if ret < 0 {
return ret;
}
match drain_bsf_write(cfg, state, bsf, &packet_box.packet_data) {
BsfDrain::Exhausted => 0,
BsfDrain::Flushed => AVERROR_EOF,
BsfDrain::Err(ret) => ret,
}
}
enum BsfDrain {
Exhausted,
Flushed,
Err(i32),
}
unsafe fn drain_bsf_write(
cfg: &MuxWriteCfg,
state: &mut MuxWriteState,
bsf: &mut BitStreamFilter,
template: &PacketData,
) -> BsfDrain {
loop {
let ret = bsf.receive_packet();
if ret == AVERROR(EAGAIN) {
return BsfDrain::Exhausted;
} else if ret == AVERROR_EOF {
return BsfDrain::Flushed;
} else if ret < 0 {
error!("Error receiving a packet from a bitstream filter (skipping): ret={ret}");
return BsfDrain::Exhausted;
}
let mut out_pkt = match cfg.packet_pool.get() {
Ok(p) => p,
Err(_) => return BsfDrain::Err(AVERROR(ENOMEM)),
};
av_packet_move_ref(out_pkt.as_mut_ptr(), bsf.pkt_ptr());
(*out_pkt.as_mut_ptr()).time_base = bsf.time_base_out();
let mut out_box = PacketBox {
packet: out_pkt,
packet_data: *template,
};
let wret = write_packet(cfg, state, &mut out_box);
cfg.packet_pool.release(out_box.packet);
if wret < 0 {
return BsfDrain::Err(wret);
}
}
}
unsafe fn flush_stream_bsf(
cfg: &MuxWriteCfg,
state: &mut MuxWriteState,
stream_bsfs: &mut [Option<BitStreamFilter>],
stream_index: usize,
) -> i32 {
let Some(bsf) = stream_bsfs[stream_index].as_mut() else {
return 0;
};
let template = match &state.stream_pkt_templates[stream_index] {
Some(t) => *t,
None => {
let st = *(*cfg.out_fmt_ctx.as_ptr()).streams.add(stream_index);
PacketData {
dts_est: 0,
codec_type: (*(*st).codecpar).codec_type,
output_stream_index: stream_index as i32,
is_copy: false,
}
}
};
let ret = bsf.send_packet(std::ptr::null_mut());
if ret < 0 {
return ret;
}
match drain_bsf_write(cfg, state, bsf, &template) {
BsfDrain::Flushed | BsfDrain::Exhausted => 0,
BsfDrain::Err(ret) => ret,
}
}
unsafe fn mux_fixup_ts(cfg: &MuxWriteCfg, state: &mut MuxWriteState, packet_box: &mut PacketBox) {
let out_fmt_ctx = cfg.out_fmt_ctx.as_ptr();
let pkt = packet_box.packet.as_mut_ptr();
let packet_data = &packet_box.packet_data;
let stream_index = packet_data.output_stream_index;
if packet_data.codec_type == AVMEDIA_TYPE_AUDIO && packet_data.is_copy {
let codecpar = (**(*out_fmt_ctx).streams.add(stream_index as usize)).codecpar;
let mut duration = av_get_audio_frame_duration2(codecpar, (*pkt).size);
if duration == 0 {
duration = (*codecpar).frame_size;
}
let ts_rescale_delta_last = &mut state.st_rescale_delta_last[stream_index as usize];
(*pkt).dts = av_rescale_delta(
(*pkt).time_base,
(*pkt).dts,
AVRational {
num: 1,
den: (*codecpar).sample_rate,
},
duration,
ts_rescale_delta_last,
(**(*out_fmt_ctx).streams.add(stream_index as usize)).time_base,
);
(*pkt).pts = (*pkt).dts;
(*pkt).duration = av_rescale_q(
(*pkt).duration,
(*pkt).time_base,
(**(*out_fmt_ctx).streams.add(stream_index as usize)).time_base,
);
} else {
av_packet_rescale_ts(
pkt,
(*pkt).time_base,
(**(*out_fmt_ctx).streams.add(stream_index as usize)).time_base,
);
}
(*pkt).time_base = (**(*out_fmt_ctx).streams.add(stream_index as usize)).time_base;
let last_mux_dts = &mut state.st_last_dts[stream_index as usize];
if (cfg.oformat_flags & AVFMT_NOTIMESTAMPS) == 0 {
if (*pkt).dts != AV_NOPTS_VALUE && (*pkt).pts != AV_NOPTS_VALUE && (*pkt).dts > (*pkt).pts {
warn!(
"Invalid DTS: {} PTS: {}, replacing by guess",
(*pkt).dts,
(*pkt).pts
);
(*pkt).pts = (*pkt).pts + (*pkt).dts + *last_mux_dts + 1
- min3((*pkt).pts, (*pkt).dts, *last_mux_dts + 1)
- max3((*pkt).pts, (*pkt).dts, *last_mux_dts + 1);
(*pkt).dts = (*pkt).pts;
}
if (packet_data.codec_type == AVMEDIA_TYPE_AUDIO
|| packet_data.codec_type == AVMEDIA_TYPE_VIDEO
|| packet_data.codec_type == AVMEDIA_TYPE_SUBTITLE)
&& (*pkt).dts != AV_NOPTS_VALUE
&& *last_mux_dts != AV_NOPTS_VALUE
{
let max = *last_mux_dts + ((cfg.oformat_flags & AVFMT_TS_NONSTRICT) == 0) as i64;
if (*pkt).dts < max {
let loglevel =
if max - (*pkt).dts > 2 || packet_data.codec_type == AVMEDIA_TYPE_VIDEO {
AV_LOG_WARNING
} else {
AV_LOG_DEBUG
};
if loglevel == AV_LOG_WARNING {
warn!(
"Non-monotonic DTS; previous: {}, current: {}; ",
*last_mux_dts,
(*pkt).dts
);
warn!(
"changing to {}. This may result in incorrect timestamps in the output file.",
max
);
} else {
debug!(
"Non-monotonic DTS; previous: {}, current: {}; ",
*last_mux_dts,
(*pkt).dts
);
debug!(
"changing to {}. This may result in incorrect timestamps in the output file.",
max
);
}
if (*pkt).pts >= (*pkt).dts {
(*pkt).pts = std::cmp::max((*pkt).pts, max);
}
(*pkt).dts = max;
}
}
}
*last_mux_dts = (*pkt).dts;
}
fn min3(a: i64, b: i64, c: i64) -> i64 {
std::cmp::min(a, std::cmp::min(b, c))
}
fn max3(a: i64, b: i64, c: i64) -> i64 {
std::cmp::max(a, std::cmp::max(b, c))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::scheduler::ffmpeg_scheduler::{
is_stopping, STATUS_ABORT, STATUS_END, STATUS_RUN,
};
use std::sync::mpsc;
#[test]
fn registration_barrier_waits_for_enc_registered() {
let flag = Arc::new(std::sync::atomic::AtomicBool::new(false));
let barrier = MuxRegistrationBarrier {
enc_registered: flag.clone(),
};
let setter_flag = flag.clone();
let setter = std::thread::spawn(move || {
std::thread::sleep(Duration::from_millis(100));
setter_flag.store(true, Ordering::Release);
});
drop(barrier);
assert!(
flag.load(Ordering::Acquire),
"the barrier returned before enc_registered was published"
);
setter.join().unwrap();
}
#[test]
fn fail_mux_init_releases_slot_and_records_error() {
let thread_sync = ThreadSynchronizer::new();
let scheduler_status = Arc::new(AtomicUsize::new(STATUS_RUN));
let scheduler_result: Arc<Mutex<Option<crate::error::Result<()>>>> =
Arc::new(Mutex::new(None));
thread_sync.thread_start();
fail_mux_init(
&scheduler_status,
&scheduler_result,
MuxTeardownGuard::for_test(),
MuxSlotGuard::armed(thread_sync.clone(), scheduler_status.clone()),
Muxing(MuxingOperationError::ThreadExited),
);
let (done_tx, done_rx) = mpsc::channel();
let sync_clone = thread_sync.clone();
std::thread::spawn(move || {
sync_clone.wait_for_all_threads();
let _ = done_tx.send(());
});
assert!(
done_rx.recv_timeout(Duration::from_secs(5)).is_ok(),
"mux thread slot leaked: wait_for_all_threads did not return"
);
assert!(is_stopping(scheduler_status.load(Ordering::Acquire)));
assert!(matches!(&*scheduler_result.lock().unwrap(), Some(Err(_))));
}
#[test]
fn mux_worker_unwind_records_error_before_the_slot_drains() {
let thread_sync = ThreadSynchronizer::new();
let scheduler_status = Arc::new(AtomicUsize::new(STATUS_RUN));
let scheduler_result: Arc<Mutex<Option<crate::error::Result<()>>>> =
Arc::new(Mutex::new(None));
let mux_done_remaining = Arc::new(AtomicUsize::new(1));
thread_sync.thread_start();
let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _slot_guard = MuxSlotGuard::armed(thread_sync.clone(), scheduler_status.clone());
let _mux_done = MuxDoneGuard::new(mux_done_remaining.clone(), scheduler_status.clone());
let _panic_status = MuxPanicStatusGuard {
scheduler_status: scheduler_status.clone(),
scheduler_result: scheduler_result.clone(),
};
panic!("test-injected mux worker panic");
}));
assert!(outcome.is_err(), "the injected panic must unwind");
let (done_tx, done_rx) = mpsc::channel();
let sync_clone = thread_sync.clone();
std::thread::spawn(move || {
sync_clone.wait_for_all_threads();
let _ = done_tx.send(());
});
assert!(
done_rx.recv_timeout(Duration::from_secs(5)).is_ok(),
"mux worker slot leaked on the panic path"
);
assert!(
matches!(&*scheduler_result.lock().unwrap(), Some(Err(_))),
"the worker panic must be recorded as the job error"
);
assert!(is_stopping(scheduler_status.load(Ordering::Acquire)));
}
#[test]
fn release_mux_slot_unblocks_wait_without_error() {
let thread_sync = ThreadSynchronizer::new();
let scheduler_status = Arc::new(AtomicUsize::new(STATUS_RUN));
thread_sync.thread_start();
release_mux_slot(&scheduler_status, &thread_sync);
let (done_tx, done_rx) = mpsc::channel();
let sync_clone = thread_sync.clone();
std::thread::spawn(move || {
sync_clone.wait_for_all_threads();
let _ = done_tx.send(());
});
assert!(
done_rx.recv_timeout(Duration::from_secs(5)).is_ok(),
"zero-stream mux slot leaked: wait_for_all_threads did not return"
);
assert!(is_stopping(scheduler_status.load(Ordering::Acquire)));
}
#[test]
fn streamless_release_is_not_premature_with_pre_counted_slots() {
let thread_sync = ThreadSynchronizer::new();
let status = Arc::new(AtomicUsize::new(STATUS_RUN));
thread_sync.thread_start();
thread_sync.thread_start();
release_mux_slot(&status, &thread_sync);
assert_eq!(
status.load(Ordering::Acquire),
STATUS_RUN,
"releasing one of two pre-counted mux slots must not publish a terminal status"
);
release_mux_slot(&status, &thread_sync);
assert!(
is_stopping(status.load(Ordering::Acquire)),
"the last mux slot release publishes STATUS_END"
);
}
#[test]
fn mux_done_guard_publishes_end_only_after_last_muxer() {
let remaining = Arc::new(AtomicUsize::new(2));
let status = Arc::new(AtomicUsize::new(STATUS_RUN));
let g1 = MuxDoneGuard::new(remaining.clone(), status.clone());
let g2 = MuxDoneGuard::new(remaining.clone(), status.clone());
drop(g1);
assert_eq!(
status.load(Ordering::Acquire),
STATUS_RUN,
"one of two muxers finishing must not terminate the scheduler"
);
drop(g2);
assert_eq!(
status.load(Ordering::Acquire),
STATUS_END,
"the last muxer finishing must publish STATUS_END"
);
}
#[test]
fn mux_done_guard_never_downgrades_abort() {
let remaining = Arc::new(AtomicUsize::new(1));
let status = Arc::new(AtomicUsize::new(STATUS_ABORT));
drop(MuxDoneGuard::new(remaining, status.clone()));
assert_eq!(
status.load(Ordering::Acquire),
STATUS_ABORT,
"the last muxer must not overwrite an abort with STATUS_END"
);
}
#[test]
fn last_mux_done_releases_a_choked_demuxer() {
use crate::util::sch_waiter::SchWaiter;
let status = Arc::new(AtomicUsize::new(STATUS_RUN));
let waiter = Arc::new(SchWaiter::new());
waiter.set(true);
let (tx, rx) = mpsc::channel();
let w = Arc::clone(&waiter);
let st = Arc::clone(&status);
std::thread::spawn(move || {
w.wait_with_scheduler_status(&st, false);
let _ = tx.send(());
});
std::thread::sleep(Duration::from_millis(150));
assert!(
rx.try_recv().is_err(),
"the demuxer must stay parked until a terminal status is published"
);
let remaining = Arc::new(AtomicUsize::new(1));
drop(MuxDoneGuard::new(remaining, status.clone()));
rx.recv_timeout(Duration::from_secs(2))
.expect("the choked demuxer must be released when the last muxer finishes");
}
#[test]
fn mux_slot_guard_releases_slot_on_armed_drop() {
let thread_sync = ThreadSynchronizer::new();
let status = Arc::new(AtomicUsize::new(STATUS_RUN));
thread_sync.thread_start();
drop(MuxSlotGuard::armed(thread_sync.clone(), status.clone()));
let (tx, rx) = mpsc::channel();
let sync = thread_sync.clone();
std::thread::spawn(move || {
sync.wait_for_all_threads();
let _ = tx.send(());
});
assert!(
rx.recv_timeout(Duration::from_secs(2)).is_ok(),
"an armed MuxSlotGuard drop must release the slot (else wait() hangs)"
);
assert!(
is_stopping(status.load(Ordering::Acquire)),
"the armed drop also publishes a terminal status"
);
}
#[test]
fn mux_slot_guard_disarmed_drop_is_a_noop() {
let thread_sync = ThreadSynchronizer::new();
let status = Arc::new(AtomicUsize::new(STATUS_RUN));
thread_sync.thread_start();
let mut guard = MuxSlotGuard::armed(thread_sync.clone(), status.clone());
guard.disarm();
drop(guard);
let (tx, rx) = mpsc::channel();
let sync = thread_sync.clone();
std::thread::spawn(move || {
sync.wait_for_all_threads();
let _ = tx.send(());
});
assert!(
rx.recv_timeout(Duration::from_millis(200)).is_err(),
"a disarmed MuxSlotGuard must NOT release the slot (the manual path owns it)"
);
assert_eq!(
status.load(Ordering::Acquire),
STATUS_RUN,
"a disarmed guard must not publish a terminal status"
);
thread_sync.thread_done_with(|| {});
}
#[test]
fn teardown_guard_unparks_pre_mux_sender_and_joins_encoder() {
use crate::core::context::pre_mux_queue::{channel, PreMuxQueueConfig, PreQueueTryPush};
use crate::core::context::PacketData;
use ffmpeg_sys_next::AVMediaType::AVMEDIA_TYPE_VIDEO;
use std::sync::atomic::AtomicBool;
let (pre_sender, pre_receiver) = channel(PreMuxQueueConfig {
max_packets: 1,
data_threshold: 1,
});
let first = PacketBox {
packet: Packet::empty(),
packet_data: PacketData {
dts_est: 0,
codec_type: AVMEDIA_TYPE_VIDEO,
output_stream_index: 0,
is_copy: false,
},
};
assert!(
matches!(pre_sender.try_push(first), PreQueueTryPush::Sent),
"the first packet must be admitted (queue below max_packets)"
);
let parked = Arc::new(AtomicBool::new(false));
let joined_cleanly = Arc::new(AtomicBool::new(false));
let parked_probe = Arc::clone(&parked);
let joined_probe = Arc::clone(&joined_cleanly);
let handle = std::thread::spawn(move || {
parked_probe.store(true, Ordering::SeqCst);
pre_sender.wait_for_space(2, Duration::from_secs(30));
joined_probe.store(true, Ordering::SeqCst);
});
let (handle_tx, handle_rx) = crossbeam_channel::unbounded();
handle_tx.send(handle).unwrap();
let deadline = std::time::Instant::now() + Duration::from_secs(5);
while !parked.load(Ordering::SeqCst) {
assert!(std::time::Instant::now() < deadline, "encoder never parked");
std::thread::yield_now();
}
std::thread::sleep(Duration::from_millis(100));
assert!(
!joined_cleanly.load(Ordering::SeqCst),
"the encoder was supposed to be parked on the full queue, \
but it already ran to completion"
);
let guard = MuxTeardownGuard {
pkt_receiver: None,
pre_receivers: vec![pre_receiver],
enc_handle_receiver: handle_rx,
out_fmt_ctx: None,
};
let start = std::time::Instant::now();
drop(guard);
let elapsed = start.elapsed();
assert!(
joined_cleanly.load(Ordering::SeqCst),
"the guard's join completed without the encoder having run to completion"
);
assert!(
elapsed < Duration::from_secs(5),
"the guard's drop took {elapsed:?}: the parked sender was not woken by the \
pre-mux close (join waited out the 30s park timeout instead)"
);
}
#[test]
fn mux_panic_status_guard_records_and_publishes_on_unwind() {
let status = Arc::new(AtomicUsize::new(STATUS_RUN));
let result: Arc<Mutex<Option<crate::error::Result<()>>>> = Arc::new(Mutex::new(None));
let guard = MuxPanicStatusGuard {
scheduler_status: status.clone(),
scheduler_result: result.clone(),
};
let handle = std::thread::Builder::new()
.name("panicky-muxer".to_string())
.spawn(move || {
let _guard = guard;
panic!("test-injected mux panic");
})
.unwrap();
let _ = handle.join();
assert!(is_stopping(status.load(Ordering::Acquire)));
let recorded = result.lock().unwrap().take();
match recorded {
Some(Err(crate::error::Error::WorkerPanicked(name))) => {
assert_eq!(name, "panicky-muxer");
}
other => panic!("expected WorkerPanicked, got {other:?}"),
}
}
#[test]
fn mux_init_internal_panic_publishes_before_join_and_releases_slot_once() {
use std::sync::atomic::AtomicBool;
let status = Arc::new(AtomicUsize::new(STATUS_RUN));
let result: Arc<Mutex<Option<crate::error::Result<()>>>> = Arc::new(Mutex::new(None));
let thread_sync = ThreadSynchronizer::new();
thread_sync.thread_start();
let enc_status = status.clone();
let enc_joined = Arc::new(AtomicBool::new(false));
let enc_joined_probe = enc_joined.clone();
let enc_handle = std::thread::spawn(move || {
let deadline = std::time::Instant::now() + Duration::from_secs(10);
while !is_stopping(enc_status.load(Ordering::Acquire)) {
if std::time::Instant::now() >= deadline {
return;
}
std::thread::sleep(Duration::from_millis(2));
}
enc_joined_probe.store(true, Ordering::SeqCst);
});
let (handle_tx, handle_rx) = crossbeam_channel::unbounded();
handle_tx.send(enc_handle).unwrap();
let remaining = Arc::new(AtomicUsize::new(2));
let status_t = status.clone();
let result_t = result.clone();
let sync_t = thread_sync.clone();
let panicker = std::thread::Builder::new()
.name("mux-init-panicker".to_string())
.spawn(move || {
let slot_guard = MuxSlotGuard::armed(sync_t.clone(), status_t.clone());
let guard = MuxTeardownGuard {
pkt_receiver: None,
pre_receivers: Vec::new(),
enc_handle_receiver: handle_rx,
out_fmt_ctx: None,
};
let mux_done = MuxDoneGuard::new(remaining, status_t.clone());
let _panic_status = MuxPanicStatusGuard {
scheduler_status: status_t.clone(),
scheduler_result: result_t.clone(),
};
let _ = (&slot_guard, &guard, &mux_done);
panic!("test-injected panic inside _mux_init");
})
.unwrap();
let _ = panicker.join();
assert!(
enc_joined.load(Ordering::SeqCst),
"the ordered unwind must publish the terminal status before the encoder join"
);
assert!(is_stopping(status.load(Ordering::Acquire)));
match result.lock().unwrap().take() {
Some(Err(crate::error::Error::WorkerPanicked(name))) => {
assert_eq!(name, "mux-init-panicker");
}
other => panic!("expected WorkerPanicked, got {other:?}"),
}
let (tx, rx) = mpsc::channel();
let sync = thread_sync.clone();
std::thread::spawn(move || {
sync.wait_for_all_threads();
let _ = tx.send(());
});
assert!(
rx.recv_timeout(Duration::from_secs(5)).is_ok(),
"the mux slot must be released exactly once on the internal-panic unwind"
);
}
#[test]
fn mux_worker_spawn_failure_unparks_and_joins_before_releasing_slot() {
use crate::core::context::pre_mux_queue::{channel, PreMuxQueueConfig, PreQueueTryPush};
use crate::core::context::PacketData;
use ffmpeg_sys_next::AVMediaType::AVMEDIA_TYPE_VIDEO;
use std::sync::atomic::AtomicBool;
let thread_sync = ThreadSynchronizer::new();
let status = Arc::new(AtomicUsize::new(STATUS_RUN));
let result: Arc<Mutex<Option<crate::error::Result<()>>>> = Arc::new(Mutex::new(None));
thread_sync.thread_start();
let (pre_sender, pre_receiver) = channel(PreMuxQueueConfig {
max_packets: 1,
data_threshold: 1,
});
let first = PacketBox {
packet: Packet::empty(),
packet_data: PacketData {
dts_est: 0,
codec_type: AVMEDIA_TYPE_VIDEO,
output_stream_index: 0,
is_copy: false,
},
};
assert!(matches!(pre_sender.try_push(first), PreQueueTryPush::Sent));
let joined = Arc::new(AtomicBool::new(false));
let joined_probe = Arc::clone(&joined);
let enc = std::thread::spawn(move || {
pre_sender.wait_for_space(2, Duration::from_secs(30));
joined_probe.store(true, Ordering::SeqCst);
});
let (handle_tx, handle_rx) = crossbeam_channel::unbounded();
handle_tx.send(enc).unwrap();
let src_pre_receivers = vec![pre_receiver];
let guard = MuxTeardownGuard {
pkt_receiver: None,
pre_receivers: Vec::new(),
enc_handle_receiver: handle_rx,
out_fmt_ctx: None,
};
let guard_slot = Arc::new(Mutex::new((
Some(guard),
Some(MuxSlotGuard::armed(thread_sync.clone(), status.clone())),
)));
let status_c = status.clone();
let result_c = result.clone();
let (done_tx, done_rx) = mpsc::channel();
std::thread::spawn(move || {
fail_mux_worker_spawn(&status_c, &result_c, src_pre_receivers, &guard_slot);
let _ = done_tx.send(());
});
assert!(
done_rx.recv_timeout(Duration::from_secs(5)).is_ok(),
"fail_mux_worker_spawn hung: the pre-mux close did not unpark the encoder before the join"
);
assert!(
joined.load(Ordering::SeqCst),
"the encoder must be unparked (pre-mux close) before the guard's join"
);
assert!(is_stopping(status.load(Ordering::Acquire)));
assert!(matches!(&*result.lock().unwrap(), Some(Err(_))));
let (tx, rx) = mpsc::channel();
let sync = thread_sync.clone();
std::thread::spawn(move || {
sync.wait_for_all_threads();
let _ = tx.send(());
});
assert!(
rx.recv_timeout(Duration::from_secs(5)).is_ok(),
"the pre-counted slot must be released exactly once on spawn failure"
);
}
#[test]
fn mux_panic_status_guard_never_downgrades_abort() {
let status = Arc::new(AtomicUsize::new(STATUS_ABORT));
let result: Arc<Mutex<Option<crate::error::Result<()>>>> = Arc::new(Mutex::new(None));
let guard = MuxPanicStatusGuard {
scheduler_status: status.clone(),
scheduler_result: result.clone(),
};
let _ = std::thread::spawn(move || {
let _guard = guard;
panic!("test-injected mux panic under abort");
})
.join();
assert_eq!(
status.load(Ordering::Acquire),
STATUS_ABORT,
"the panic publish must not overwrite an abort"
);
assert!(
result.lock().unwrap().is_some(),
"the panic is still recorded"
);
}
#[test]
fn mux_panic_status_guard_is_a_noop_without_a_panic() {
let status = Arc::new(AtomicUsize::new(STATUS_RUN));
let result: Arc<Mutex<Option<crate::error::Result<()>>>> = Arc::new(Mutex::new(None));
drop(MuxPanicStatusGuard {
scheduler_status: status.clone(),
scheduler_result: result.clone(),
});
assert_eq!(status.load(Ordering::Acquire), STATUS_RUN);
assert!(result.lock().unwrap().is_none());
}
#[test]
fn build_sq_mux_maps_members_with_attachment_gap() {
let plan = SqMuxPlan {
buf_size_us: 5_000_000,
streams: vec![(0, true, None), (1, true, Some(7)), (3, true, None)],
};
let sq = build_sq_mux(plan, 4);
assert_eq!(sq.sq_idx, vec![Some(0), Some(1), None, Some(2)]);
assert_eq!(sq.ostream, vec![0, 1, 3]);
}
}