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, 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, AVFormatContext, AVPacket, AVRational, AVERROR,
AVERROR_EOF, AVFMT_NOTIMESTAMPS, AVFMT_TS_NONSTRICT, 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());
mux_task_start(
mux_idx,
out_fmt_ctx,
mux.take_queue(),
mux.start_time_us,
mux.recording_time_us,
mux.stream_count(),
mux.format_opts.clone(),
mux.bsf_chains.clone(),
mux.take_src_pre_recvs(),
mux.mux_start_gate(),
mux.enc_handle_receiver(),
packet_pool,
input_controller,
mux_stream_nodes,
sq_mux_plan,
scheduler_status,
thread_sync,
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 = mux.take_queue();
let src_pre_recvs = mux.take_src_pre_recvs();
let mux_start_gate = mux.mux_start_gate();
let enc_handle_receiver = mux.enc_handle_receiver();
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 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 out_fmt_ctx = Some(out_fmt_ctx);
loop {
let result = receiver.recv_timeout(Duration::from_millis(100));
if is_stopping(wait_until_not_paused(&scheduler_status)) {
thread_sync.thread_done_with(|| {
scheduler_status.store(STATUS_END, Ordering::Release);
});
info!("Init muxer receiver end command, finishing.");
break;
}
if let Err(e) = result {
if e == RecvTimeoutError::Disconnected {
thread_sync.thread_done_with(|| {
scheduler_status.store(STATUS_END, Ordering::Release);
});
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 {
let out_fmt_ctx = out_fmt_ctx
.take()
.expect("mux waiter reached all-ready without a context");
if let Err(e) = mux_task_start(
mux_idx,
out_fmt_ctx,
queue,
start_time_us,
recording_time_us,
stream_count,
format_opts,
bsf_chains,
src_pre_recvs,
mux_start_gate,
enc_handle_receiver,
packet_pool,
input_controller,
mux_stream_nodes,
sq_mux_plan,
scheduler_status,
thread_sync,
scheduler_result,
mux_done,
) {
debug!("Muxer init failed: {e}");
}
break;
}
}
});
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,
out_fmt_ctx: FormatContext,
queue: Option<(Sender<PacketBox>, Receiver<PacketBox>)>,
start_time_us: Option<i64>,
recording_time_us: Option<i64>,
stream_count: usize,
format_opts: Option<HashMap<CString, CString>>,
bsf_chains: StreamBsfChains,
src_pre_receivers: Vec<PreMuxQueueReceiver>,
mux_start_gate: Arc<crate::core::context::MuxStartGate>,
enc_handle_receiver: Receiver<std::thread::JoinHandle<()>>,
packet_pool: ObjPool<Packet>,
input_controller: Arc<InputController>,
mux_stream_nodes: Vec<Arc<SchNode>>,
sq_mux_plan: Option<SqMuxPlan>,
scheduler_status: Arc<AtomicUsize>,
thread_sync: ThreadSynchronizer,
scheduler_result: Arc<Mutex<Option<crate::error::Result<()>>>>,
mux_done: MuxDoneGuard,
) -> crate::error::Result<()> {
if queue.is_none() {
release_mux_slot(&scheduler_status, &thread_sync);
return Ok(());
}
let (queue_sender, queue_receiver) = queue.unwrap();
_mux_init(
mux_idx,
out_fmt_ctx,
queue_receiver,
start_time_us,
recording_time_us,
stream_count,
format_opts,
bsf_chains,
enc_handle_receiver,
packet_pool,
input_controller,
mux_stream_nodes,
sq_mux_plan,
scheduler_status,
thread_sync,
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,
out_fmt_ctx: FormatContext,
pkt_receiver: Receiver<PacketBox>,
start_time_us: Option<i64>,
recording_time_us: Option<i64>,
stream_count: usize,
format_opts: Option<HashMap<CString, CString>>,
bsf_chains: StreamBsfChains,
enc_handle_receiver: Receiver<std::thread::JoinHandle<()>>,
packet_pool: ObjPool<Packet>,
input_controller: Arc<InputController>,
mux_stream_nodes: Vec<Arc<SchNode>>,
sq_mux_plan: Option<SqMuxPlan>,
scheduler_status: Arc<AtomicUsize>,
thread_sync: ThreadSynchronizer,
scheduler_result: Arc<Mutex<Option<crate::error::Result<()>>>>,
mux_done: MuxDoneGuard,
) -> crate::error::Result<()> {
let out_fmt_ctx_ptr = unsafe { out_fmt_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)
);
set_scheduler_error(
&scheduler_status,
&scheduler_result,
Muxing(MuxingOperationError::BitstreamFilterInit(
name.clone(),
MuxingError::from(bsf_ret),
)),
);
thread_sync.thread_done_with(|| {
scheduler_status.store(STATUS_END, Ordering::Release);
});
return Err(Muxing(MuxingOperationError::BitstreamFilterInit(
name,
MuxingError::from(bsf_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,
&thread_sync,
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 thread_sync_spawn = thread_sync.clone();
let scheduler_result_spawn = scheduler_result.clone();
let result = std::thread::Builder::new().name(format!("muxer{mux_idx}:{format_name}")).spawn(move || {
let out_fmt_ctx = out_fmt_ctx;
let mux_done = mux_done;
let mut slot_guard = MuxSlotGuard::armed(thread_sync.clone(), scheduler_status.clone());
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];
ret = streamcopy_rescale(
packet_box.packet.as_mut_ptr(),
packet_data,
&start_time_us,
&recording_time_us,
started,
);
if ret == AVERROR(EAGAIN) {
packet_pool.release(packet_box.packet);
continue;
} else 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 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);
while let Ok(handle) = enc_handle_receiver.try_recv() {
let _ = handle.join();
}
thread_sync.thread_done_with(|| {
scheduler_status.store(STATUS_END, Ordering::Release);
});
slot_guard.disarm();
});
if let Err(e) = result {
error!("Muxer thread exited with error: {e}");
fail_mux_init(
&scheduler_status_spawn,
&scheduler_result_spawn,
&thread_sync_spawn,
Muxing(MuxingOperationError::ThreadExited),
);
return Err(MuxingOperationError::ThreadExited.into());
}
Ok(())
}
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_init(
scheduler_status: &Arc<AtomicUsize>,
scheduler_result: &Arc<Mutex<Option<crate::error::Result<()>>>>,
thread_sync: &ThreadSynchronizer,
error: crate::error::Error,
) {
set_scheduler_error(scheduler_status, scheduler_result, error);
release_mux_slot(scheduler_status, thread_sync);
}
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;
}
}
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);
});
}
}
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 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,
&thread_sync,
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 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 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]);
}
}