use std::collections::VecDeque;
use std::sync::{Arc, Condvar, Mutex, MutexGuard, PoisonError};
use std::time::Duration;
use ffmpeg_next::packet::Ref;
use super::PacketBox;
pub(crate) const DEFAULT_PRE_MUX_MAX_PACKETS: usize = 128;
pub(crate) const DEFAULT_PRE_MUX_DATA_THRESHOLD: usize = 50 * 1024 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct PreMuxQueueConfig {
pub(crate) max_packets: usize,
pub(crate) data_threshold: usize,
}
impl Default for PreMuxQueueConfig {
fn default() -> Self {
Self {
max_packets: DEFAULT_PRE_MUX_MAX_PACKETS,
data_threshold: DEFAULT_PRE_MUX_DATA_THRESHOLD,
}
}
}
struct PreMuxQueueState {
queue: VecDeque<PacketBox>,
byte_size: usize,
closed: bool,
}
struct PreMuxQueueInner {
state: Mutex<PreMuxQueueState>,
cv: Condvar,
config: PreMuxQueueConfig,
}
#[derive(Clone)]
pub(crate) struct PreMuxQueueSender {
inner: Arc<PreMuxQueueInner>,
}
pub(crate) struct PreMuxQueueReceiver {
inner: Arc<PreMuxQueueInner>,
}
pub(crate) enum PreQueueTryPush {
Sent,
Full(PacketBox),
Disconnected(PacketBox),
}
pub(crate) fn channel(config: PreMuxQueueConfig) -> (PreMuxQueueSender, PreMuxQueueReceiver) {
let inner = Arc::new(PreMuxQueueInner {
state: Mutex::new(PreMuxQueueState {
queue: VecDeque::new(),
byte_size: 0,
closed: false,
}),
cv: Condvar::new(),
config,
});
(
PreMuxQueueSender {
inner: inner.clone(),
},
PreMuxQueueReceiver { inner },
)
}
pub(crate) fn packet_payload_size(packet_box: &PacketBox) -> usize {
unsafe {
let pkt = packet_box.packet.as_ptr();
if pkt.is_null() || (*pkt).size <= 0 {
0
} else {
(*pkt).size as usize
}
}
}
fn has_space_for(state: &PreMuxQueueState, config: PreMuxQueueConfig, size: usize) -> bool {
state.queue.len() < config.max_packets
|| state.byte_size.saturating_add(size) <= config.data_threshold
}
fn lock_state(inner: &PreMuxQueueInner) -> MutexGuard<'_, PreMuxQueueState> {
inner.state.lock().unwrap_or_else(PoisonError::into_inner)
}
impl PreMuxQueueSender {
pub(crate) fn try_push(&self, packet_box: PacketBox) -> PreQueueTryPush {
let size = packet_payload_size(&packet_box);
let mut state = lock_state(&self.inner);
if state.closed {
return PreQueueTryPush::Disconnected(packet_box);
}
if !has_space_for(&state, self.inner.config, size) {
return PreQueueTryPush::Full(packet_box);
}
state.byte_size = state.byte_size.saturating_add(size);
state.queue.push_back(packet_box);
PreQueueTryPush::Sent
}
pub(crate) fn wait_for_space(&self, packet_size: usize, timeout: Duration) {
let state = lock_state(&self.inner);
if state.closed || has_space_for(&state, self.inner.config, packet_size) {
return;
}
let _ = self
.inner
.cv
.wait_timeout(state, timeout)
.unwrap_or_else(PoisonError::into_inner);
}
}
impl PreMuxQueueReceiver {
pub(crate) fn drain_all(&self) -> VecDeque<PacketBox> {
let mut state = lock_state(&self.inner);
state.byte_size = 0;
let drained = std::mem::take(&mut state.queue);
drop(state);
self.inner.cv.notify_all();
drained
}
}
impl Drop for PreMuxQueueReceiver {
fn drop(&mut self) {
let mut state = lock_state(&self.inner);
state.closed = true;
drop(state);
self.inner.cv.notify_all();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::context::{MuxStartGate, PacketData, PreSendOutcome};
use ffmpeg_sys_next::AVMediaType::AVMEDIA_TYPE_VIDEO;
use std::sync::mpsc;
use std::thread;
fn packet_box(payload: usize) -> PacketBox {
let packet = if payload == 0 {
ffmpeg_next::Packet::empty()
} else {
ffmpeg_next::Packet::new(payload)
};
PacketBox {
packet,
packet_data: PacketData {
dts_est: 0,
codec_type: AVMEDIA_TYPE_VIDEO,
output_stream_index: 0,
is_copy: false,
},
}
}
fn cfg(max_packets: usize, data_threshold: usize) -> PreMuxQueueConfig {
PreMuxQueueConfig {
max_packets,
data_threshold,
}
}
#[test]
fn byte_metering_and_threshold_semantics() {
let (tx, rx) = channel(cfg(2, 10));
assert!(matches!(tx.try_push(packet_box(6)), PreQueueTryPush::Sent));
assert!(matches!(tx.try_push(packet_box(4)), PreQueueTryPush::Sent));
assert!(matches!(
tx.try_push(packet_box(1)),
PreQueueTryPush::Full(_)
));
assert!(matches!(tx.try_push(packet_box(0)), PreQueueTryPush::Sent));
let drained = rx.drain_all();
let sizes: Vec<usize> = drained.iter().map(packet_payload_size).collect();
assert_eq!(sizes, vec![6, 4, 0], "FIFO order must be preserved");
assert!(matches!(tx.try_push(packet_box(1)), PreQueueTryPush::Sent));
}
#[test]
fn packet_cap_does_not_apply_below_byte_threshold() {
let (tx, _rx) = channel(cfg(1, 10));
assert!(matches!(tx.try_push(packet_box(2)), PreQueueTryPush::Sent));
assert!(matches!(tx.try_push(packet_box(2)), PreQueueTryPush::Sent));
assert!(matches!(tx.try_push(packet_box(2)), PreQueueTryPush::Sent));
assert!(matches!(
tx.try_push(packet_box(9)),
PreQueueTryPush::Full(_)
));
}
#[test]
fn full_sender_parks_and_drain_wakes_it() {
let (tx, rx) = channel(cfg(1, 4));
assert!(matches!(tx.try_push(packet_box(4)), PreQueueTryPush::Sent));
let (done_tx, done_rx) = mpsc::channel();
thread::spawn(move || {
let mut pb = packet_box(2);
loop {
match tx.try_push(pb) {
PreQueueTryPush::Sent => break,
PreQueueTryPush::Full(p) => {
tx.wait_for_space(2, Duration::from_secs(5));
pb = p;
}
PreQueueTryPush::Disconnected(_) => panic!("unexpected disconnect"),
}
}
let _ = done_tx.send(());
});
assert!(
done_rx.recv_timeout(Duration::from_millis(100)).is_err(),
"sender must park while the queue is full"
);
let drained = rx.drain_all();
assert_eq!(drained.len(), 1);
assert!(
done_rx.recv_timeout(Duration::from_secs(5)).is_ok(),
"drain must wake the parked sender"
);
}
#[test]
fn receiver_drop_unparks_disconnected() {
let (tx, rx) = channel(cfg(1, 4));
assert!(matches!(tx.try_push(packet_box(4)), PreQueueTryPush::Sent));
let (done_tx, done_rx) = mpsc::channel();
thread::spawn(move || {
let mut pb = packet_box(2);
loop {
match tx.try_push(pb) {
PreQueueTryPush::Sent => panic!("queue should stay full"),
PreQueueTryPush::Full(p) => {
tx.wait_for_space(2, Duration::from_secs(5));
pb = p;
}
PreQueueTryPush::Disconnected(_) => break,
}
}
let _ = done_tx.send(());
});
thread::sleep(Duration::from_millis(50));
drop(rx);
assert!(
done_rx.recv_timeout(Duration::from_secs(5)).is_ok(),
"receiver drop must unpark the sender with Disconnected"
);
}
#[test]
fn gate_start_resolves_started_for_parked_sender() {
let gate = Arc::new(MuxStartGate::new());
let (tx, rx) = channel(cfg(1, 4));
assert!(matches!(
gate.send_pre(&tx, packet_box(4)),
PreSendOutcome::Sent
));
let (done_tx, done_rx) = mpsc::channel();
let gate_clone = gate.clone();
thread::spawn(move || {
let mut pb = packet_box(2);
let outcome = loop {
match gate_clone.send_pre(&tx, pb) {
PreSendOutcome::Sent => break "sent",
PreSendOutcome::Started(_) => break "started",
PreSendOutcome::Disconnected(_) => break "disconnected",
PreSendOutcome::Full(p) => {
tx.wait_for_space(2, Duration::from_secs(5));
pb = p;
}
}
};
let _ = done_tx.send(outcome);
});
assert!(
done_rx.recv_timeout(Duration::from_millis(100)).is_err(),
"sender must park while the gate is closed and the queue full"
);
let mut drained_len = 0;
gate.start_with(|| {
drained_len = rx.drain_all().len();
});
assert_eq!(drained_len, 1);
assert_eq!(
done_rx.recv_timeout(Duration::from_secs(5)).unwrap(),
"started",
"a parked sender must observe the opened gate and divert to the live queue"
);
}
}