use ffmpeg_sys_next::{av_compare_ts, av_rescale_q, AVRational, AV_NOPTS_VALUE, AV_TIME_BASE_Q};
use std::collections::VecDeque;
#[inline]
fn compare_ts(ts_a: i64, tb_a: AVRational, ts_b: i64, tb_b: AVRational) -> i32 {
unsafe { av_compare_ts(ts_a, tb_a, ts_b, tb_b) }
}
#[inline]
fn rescale_q(a: i64, from: AVRational, to: AVRational) -> i64 {
unsafe { av_rescale_q(a, from, to) }
}
struct QueuedItem<T> {
item: T,
end_ts: i64,
}
struct SqStream<T> {
fifo: VecDeque<QueuedItem<T>>,
tb: AVRational,
head_ts: Option<i64>,
limiting: bool,
finished: bool,
samples_queued: u64,
samples_sent: u64,
frames_sent: u64,
frames_max: u64,
frame_samples: i32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum SqSend {
Accepted,
StreamFinished,
}
pub(crate) struct SyncQueue<T> {
streams: Vec<SqStream<T>>,
finished: bool,
head_stream: Option<usize>,
head_finished_stream: Option<usize>,
buf_size_us: i64,
have_limiting: bool,
pending_finished: Vec<usize>,
}
impl<T> SyncQueue<T> {
pub(crate) fn new(buf_size_us: i64) -> Self {
SyncQueue {
streams: Vec::new(),
finished: false,
head_stream: None,
head_finished_stream: None,
buf_size_us,
have_limiting: false,
pending_finished: Vec::new(),
}
}
pub(crate) fn add_stream(&mut self, limiting: bool) -> usize {
let idx = self.streams.len();
self.streams.push(SqStream {
fifo: VecDeque::new(),
tb: AVRational { num: 1, den: 1 },
head_ts: None,
limiting,
finished: false,
samples_queued: 0,
samples_sent: 0,
frames_sent: 0,
frames_max: u64::MAX,
frame_samples: 0,
});
self.have_limiting |= limiting;
idx
}
pub(crate) fn send(
&mut self,
idx: usize,
item: Option<T>,
end_ts: Option<i64>,
tb: AVRational,
nb_samples: i32,
) -> SqSend {
match item {
None => self.finish_stream(idx),
Some(payload) => {
if self.streams[idx].finished {
return SqSend::StreamFinished;
}
self.tb_update(idx, tb);
let end = end_ts.unwrap_or(AV_NOPTS_VALUE);
self.streams[idx].fifo.push_back(QueuedItem {
item: payload,
end_ts: end,
});
self.stream_update_ts(idx, end);
let ns = nb_samples as u64;
let reached_max = {
let st = &mut self.streams[idx];
st.samples_queued += ns;
st.samples_sent += ns;
if st.frame_samples != 0 {
st.frames_sent = st.samples_sent / st.frame_samples as u64;
} else {
st.frames_sent += 1;
}
st.frames_sent >= st.frames_max
};
if reached_max {
self.finish_stream(idx);
}
}
}
if self.streams[idx].finished {
SqSend::StreamFinished
} else {
SqSend::Accepted
}
}
pub(crate) fn sq_limit_frames(&mut self, idx: usize, max: u64) {
self.streams[idx].frames_max = max;
if self.streams[idx].frames_sent >= self.streams[idx].frames_max {
self.finish_stream(idx);
}
}
pub(crate) fn drain_releasable_into(&mut self, idx: usize, out: &mut Vec<T>) {
while let Some(front_end) = self.streams[idx].fifo.front().map(|q| q.end_ts) {
if !self.front_releasable(idx, front_end) {
break;
}
let q = self.streams[idx].fifo.pop_front().expect("front present");
out.push(q.item);
}
}
pub(crate) fn drain_all_releasable(&mut self, out: &mut Vec<T>) {
loop {
let before = out.len();
for i in 0..self.streams.len() {
self.drain_releasable_into(i, out);
}
if out.len() == before {
if !self.heartbeat() {
break;
}
}
}
}
pub(crate) fn is_stream_drained(&self, idx: usize) -> bool {
self.finished || (self.streams[idx].finished && self.streams[idx].fifo.is_empty())
}
pub(crate) fn newly_finished(&mut self, out: &mut Vec<usize>) {
out.append(&mut self.pending_finished);
}
pub(crate) fn heartbeat(&mut self) -> bool {
let mut best: Option<usize> = None;
for i in 0..self.streams.len() {
let Some(hts) = self.streams[i].head_ts else {
continue;
};
match best {
None => best = Some(i),
Some(b) => {
let b_ts = self.streams[b].head_ts.expect("best has ts");
if compare_ts(b_ts, self.streams[b].tb, hts, self.streams[i].tb) < 0 {
best = Some(i);
}
}
}
}
let Some(sidx) = best else { return false };
let st_tb = self.streams[sidx].tb;
let st_head_ts = self.streams[sidx].head_ts.expect("best has ts");
let mut tail_ts = AV_NOPTS_VALUE;
for q in self.streams[sidx].fifo.iter() {
tail_ts = q.end_ts;
if tail_ts != AV_NOPTS_VALUE {
break;
}
}
if tail_ts == AV_NOPTS_VALUE
|| tail_ts >= st_head_ts
|| rescale_q(st_head_ts - tail_ts, st_tb, AV_TIME_BASE_Q) < self.buf_size_us
{
return false;
}
tail_ts += 1;
let mut advanced = false;
for i in 0..self.streams.len() {
if i == sidx || self.streams[i].finished {
continue;
}
if let Some(hts) = self.streams[i].head_ts {
if compare_ts(tail_ts, st_tb, hts, self.streams[i].tb) <= 0 {
continue;
}
}
let mut ts = rescale_q(tail_ts, st_tb, self.streams[i].tb);
if let Some(hts) = self.streams[i].head_ts {
ts = ts.max(hts + 1);
}
self.stream_update_ts(i, ts);
advanced = true;
}
advanced
}
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn is_finished(&self) -> bool {
self.finished
}
fn mark_finished(&mut self, idx: usize) {
if self.streams[idx].finished {
return;
}
self.streams[idx].finished = true;
self.pending_finished.push(idx);
}
fn front_releasable(&self, idx: usize, front_end: i64) -> bool {
if front_end == AV_NOPTS_VALUE {
return true;
}
if !self.have_limiting {
return true;
}
let Some(h) = self.head_stream else {
return false;
};
let Some(head_ts) = self.streams[h].head_ts else {
return false;
};
compare_ts(front_end, self.streams[idx].tb, head_ts, self.streams[h].tb) <= 0
}
fn tb_update(&mut self, idx: usize, tb: AVRational) {
let st = &mut self.streams[idx];
if tb.num == st.tb.num && tb.den == st.tb.den {
return;
}
debug_assert!(st.fifo.is_empty(), "sync queue timebase changed mid-stream");
if let Some(hts) = st.head_ts {
st.head_ts = Some(rescale_q(hts, st.tb, tb));
}
st.tb = tb;
}
fn stream_update_ts(&mut self, idx: usize, ts: i64) {
if ts == AV_NOPTS_VALUE {
return;
}
if let Some(hts) = self.streams[idx].head_ts {
if hts >= ts {
return;
}
}
self.streams[idx].head_ts = Some(ts);
if let Some(hf) = self.head_finished_stream {
let hf_ts = self.streams[hf].head_ts.expect("finished head has ts");
let hf_tb = self.streams[hf].tb;
let st_tb = self.streams[idx].tb;
if compare_ts(hf_ts, hf_tb, ts, st_tb) <= 0 {
self.finish_stream(idx);
}
}
if self.streams[idx].limiting
&& (self.head_stream.is_none() || self.head_stream == Some(idx))
{
self.queue_head_update();
}
}
fn queue_head_update(&mut self) {
debug_assert!(self.have_limiting);
if self.head_stream.is_none() {
let mut first_limiting: Option<usize> = None;
for i in 0..self.streams.len() {
if !self.streams[i].limiting {
continue;
}
if self.streams[i].head_ts.is_none() {
if self.streams[i].finished {
continue;
}
return;
}
if first_limiting.is_none() {
first_limiting = Some(i);
}
}
match first_limiting {
Some(fi) => self.head_stream = Some(fi),
None => return,
}
}
for i in 0..self.streams.len() {
if !self.streams[i].limiting {
continue;
}
let Some(other_ts) = self.streams[i].head_ts else {
continue;
};
let other_tb = self.streams[i].tb;
let h = self.head_stream.expect("head set above");
let head_ts = self.streams[h].head_ts.expect("head has ts");
let head_tb = self.streams[h].tb;
if compare_ts(other_ts, other_tb, head_ts, head_tb) < 0 {
self.head_stream = Some(i);
}
}
}
fn finish_stream(&mut self, idx: usize) {
self.mark_finished(idx);
let limiting = self.streams[idx].limiting;
let head_ts = self.streams[idx].head_ts;
if limiting {
if let Some(st_ts) = head_ts {
let st_tb = self.streams[idx].tb;
let take = match self.head_finished_stream {
None => true,
Some(hf) => {
let hf_ts = self.streams[hf].head_ts.expect("finished head has ts");
let hf_tb = self.streams[hf].tb;
compare_ts(st_ts, st_tb, hf_ts, hf_tb) < 0
}
};
if take {
self.head_finished_stream = Some(idx);
}
let hf = self.head_finished_stream.expect("just set");
let hf_ts = self.streams[hf].head_ts.expect("finished head has ts");
let hf_tb = self.streams[hf].tb;
for i in 0..self.streams.len() {
if i == hf {
continue;
}
if let Some(other_ts) = self.streams[i].head_ts {
let other_tb = self.streams[i].tb;
if compare_ts(hf_ts, hf_tb, other_ts, other_tb) <= 0 {
self.mark_finished(i);
}
}
}
}
}
if self.streams.iter().all(|s| s.finished) {
self.finished = true;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Id(i64);
fn tb(num: i32, den: i32) -> AVRational {
AVRational { num, den }
}
fn push(q: &mut SyncQueue<Id>, idx: usize, id: i64, end_ts: i64, t: AVRational) -> SqSend {
q.send(idx, Some(Id(id)), Some(end_ts), t, 0)
}
fn finish(q: &mut SyncQueue<Id>, idx: usize) -> SqSend {
q.send(idx, None, None, tb(1, 1), 0)
}
fn drain(q: &mut SyncQueue<Id>, idx: usize) -> Vec<i64> {
let mut out = Vec::new();
q.drain_releasable_into(idx, &mut out);
out.into_iter().map(|Id(v)| v).collect()
}
fn drain_all(q: &mut SyncQueue<Id>) -> Vec<i64> {
let mut out = Vec::new();
q.drain_all_releasable(&mut out);
out.into_iter().map(|Id(v)| v).collect()
}
#[test]
fn release_at_and_just_past_head() {
let ms = tb(1, 1000);
let mut q = SyncQueue::<Id>::new(10_000_000);
let a = q.add_stream(true);
let b = q.add_stream(true);
push(&mut q, a, 1, 1000, ms);
assert!(drain(&mut q, a).is_empty(), "no head yet -> hold");
push(&mut q, b, 100, 1000, ms);
assert_eq!(drain(&mut q, a), vec![1]);
push(&mut q, a, 2, 2000, ms);
assert!(drain(&mut q, a).is_empty(), "end just past head -> hold");
push(&mut q, b, 101, 2000, ms);
assert_eq!(drain(&mut q, a), vec![2]);
}
#[test]
fn finish_stream_finishes_overbound_peers() {
let ms = tb(1, 1000);
let mut q = SyncQueue::<Id>::new(10_000_000);
let a = q.add_stream(true);
let b = q.add_stream(true);
push(&mut q, a, 1, 3000, ms); for (i, end) in [1000, 2000, 3000, 4000, 5000].iter().enumerate() {
push(&mut q, b, 100 + i as i64, *end, ms); }
assert_eq!(finish(&mut q, a), SqSend::StreamFinished);
assert!(q.is_finished(), "both streams finished");
assert_eq!(drain(&mut q, b), vec![100, 101, 102]);
}
#[test]
fn heartbeat_releases_at_exactly_buf_size() {
let a_tb = tb(1, 1000); let b_tb = tb(1, 90000);
let build = |buf_us: i64| {
let mut q = SyncQueue::<Id>::new(buf_us);
let a = q.add_stream(true);
let b = q.add_stream(true);
push(&mut q, a, 1, 1000, a_tb); push(&mut q, b, 100, 0, b_tb); push(&mut q, a, 2, 3000, a_tb); (q, a, b)
};
let (mut q, a, _b) = build(2_000_001);
assert!(
drain(&mut q, a).is_empty(),
"A tail held behind stalled head"
);
assert!(!q.heartbeat(), "gap < buf_size_us -> no heartbeat");
assert!(drain(&mut q, a).is_empty());
let (mut q, a, _b) = build(2_000_000);
assert!(drain(&mut q, a).is_empty());
assert!(q.heartbeat(), "gap == buf_size_us -> heartbeat");
assert_eq!(
drain(&mut q, a),
vec![1],
"stuck tail released after heartbeat"
);
}
#[test]
fn untimestamped_finish_does_not_truncate_peers() {
let ms = tb(1, 1000);
let mut q = SyncQueue::<Id>::new(10_000_000);
let a = q.add_stream(true);
let b = q.add_stream(true);
for (i, end) in [1000, 2000, 3000, 4000, 5000].iter().enumerate() {
push(&mut q, b, 100 + i as i64, *end, ms);
}
assert_eq!(finish(&mut q, a), SqSend::StreamFinished);
assert!(
!q.is_finished(),
"untimestamped finish must not finish the queue"
);
assert!(drain(&mut q, b).is_empty());
}
#[test]
fn finished_stream_fake_advance_then_release() {
let ms = tb(1, 1000);
let mut q = SyncQueue::<Id>::new(1_000_000);
let a = q.add_stream(true);
let b = q.add_stream(true);
push(&mut q, a, 1, 1000, ms); push(&mut q, a, 2, 2000, ms); assert_eq!(finish(&mut q, a), SqSend::StreamFinished);
push(&mut q, b, 100, 500, ms);
assert!(drain(&mut q, a).is_empty());
assert!(q.heartbeat());
assert_eq!(drain(&mut q, a), vec![1], "finished stream's tail released");
}
#[test]
fn audio_and_packet_end_ts() {
let a_tb = tb(1, 48000); let sr = 48000;
let p_tb = tb(1, 90000);
let mut q = SyncQueue::<Id>::new(10_000_000);
let a = q.add_stream(true); let p = q.add_stream(true);
let nb_samples = 1600;
let mut pts = 0i64;
for i in 0..3 {
let end = pts + rescale_q(nb_samples, tb(1, sr), a_tb);
push(&mut q, a, i, end, a_tb);
pts = end;
}
assert_eq!(finish(&mut q, a), SqSend::StreamFinished);
let dur = 4500i64;
for i in 0..4 {
let ppts = i * dur;
push(&mut q, p, 100 + i, ppts + dur, p_tb);
}
assert!(q.is_finished());
assert_eq!(drain(&mut q, p), vec![100, 101]);
}
#[test]
fn drain_fifo_order_only_this_stream() {
let ms = tb(1, 1000);
let mut q = SyncQueue::<Id>::new(10_000_000);
let a = q.add_stream(true);
let b = q.add_stream(true);
push(&mut q, a, 10, 1000, ms);
push(&mut q, a, 11, 2000, ms);
push(&mut q, a, 12, 3000, ms); push(&mut q, b, 20, 1000, ms);
push(&mut q, b, 21, 2000, ms);
assert_eq!(drain(&mut q, a), vec![10, 11]);
assert_eq!(drain(&mut q, b), vec![20, 21]);
}
#[test]
fn two_stream_shortest() {
let ms = tb(1, 1000);
let mut q = SyncQueue::<Id>::new(10_000_000);
let a = q.add_stream(true);
let b = q.add_stream(true);
push(&mut q, a, 1, 1000, ms);
push(&mut q, b, 101, 1000, ms);
push(&mut q, a, 2, 2000, ms);
push(&mut q, b, 102, 2000, ms);
push(&mut q, a, 3, 3000, ms);
push(&mut q, b, 103, 3000, ms);
push(&mut q, b, 104, 4000, ms); push(&mut q, b, 105, 5000, ms);
assert_eq!(finish(&mut q, a), SqSend::StreamFinished); assert!(q.is_finished(), "cascade finished B at the 3s bound");
assert_eq!(drain(&mut q, a), vec![1, 2, 3]);
assert_eq!(drain(&mut q, b), vec![101, 102, 103]);
}
#[test]
fn is_stream_drained_predicate() {
let ms = tb(1, 1000);
let mut q = SyncQueue::<Id>::new(10_000_000);
let a = q.add_stream(true);
let b = q.add_stream(true);
let c = q.add_stream(true);
push(&mut q, a, 1, 3000, ms); for (i, end) in [1000, 2000, 3000, 4000, 5000].iter().enumerate() {
push(&mut q, b, 100 + i as i64, *end, ms); }
push(&mut q, c, 50, 1000, ms);
finish(&mut q, a);
assert!(!q.is_finished(), "C still live -> queue not finished");
assert!(
!q.is_stream_drained(b),
"finished stream with over-bound frames, queue live -> not drained"
);
assert!(!q.is_stream_drained(c));
finish(&mut q, c);
assert!(q.is_finished());
assert!(
q.is_stream_drained(b),
"whole queue finished -> drained despite over-bound frames still in fifo"
);
assert_eq!(q.streams[b].fifo.len(), 5);
}
#[test]
fn is_stream_drained_finished_empty_fifo() {
let mut q = SyncQueue::<Id>::new(10_000_000);
let a = q.add_stream(true);
let b = q.add_stream(true);
finish(&mut q, a); assert!(!q.is_finished(), "B still live");
assert!(q.is_stream_drained(a), "finished + empty fifo -> drained");
assert!(!q.is_stream_drained(b), "B not finished -> not drained");
}
#[test]
fn attack4_no_loss_when_peer_still_live() {
let ms = tb(1, 1000);
let mut q = SyncQueue::<Id>::new(10_000_000);
let a = q.add_stream(true);
let b = q.add_stream(true);
for s in 1..=10 {
push(&mut q, b, 100 + s, s * 1000, ms);
}
for s in 1..=5 {
push(&mut q, a, s, s * 1000, ms);
}
finish(&mut q, b);
assert!(
!q.is_finished(),
"A still live -> B's finish does not end the queue"
);
assert_eq!(drain(&mut q, b), vec![101, 102, 103, 104, 105]);
assert_eq!(
q.streams[b].fifo.len(),
5,
"(5s,10s] still buffered, nothing lost"
);
for s in 6..=10 {
push(&mut q, a, s, s * 1000, ms);
}
assert_eq!(
drain(&mut q, b),
vec![106, 107, 108, 109, 110],
"B's (5s,10s] released once A advances; nothing lost"
);
}
#[test]
fn attack4_truncation_abandons_overbound() {
let ms = tb(1, 1000);
let mut q = SyncQueue::<Id>::new(10_000_000);
let a = q.add_stream(true);
let b = q.add_stream(true);
for s in 1..=10 {
push(&mut q, b, 100 + s, s * 1000, ms); }
for s in 1..=7 {
push(&mut q, a, s, s * 1000, ms); }
finish(&mut q, a);
assert!(q.is_finished());
assert!(
q.is_stream_drained(b),
"queue finished -> B drained even though (7s,10s] are still buffered"
);
assert_eq!(drain(&mut q, b), vec![101, 102, 103, 104, 105, 106, 107]);
assert!(
drain(&mut q, b).is_empty(),
"over-bound (7s,10s] never release"
);
assert_eq!(
q.streams[b].fifo.len(),
3,
"over-bound (7s,10s] remain buffered"
);
}
#[test]
fn frames_max_finishes_after_limit() {
let ms = tb(1, 1000);
let mut q = SyncQueue::<Id>::new(10_000_000);
let a = q.add_stream(true);
let _b = q.add_stream(true);
q.sq_limit_frames(a, 1); assert!(!q.streams[a].finished);
assert_eq!(
q.send(a, Some(Id(1)), Some(1000), ms, 0),
SqSend::StreamFinished,
"reaching frames_max finishes the stream"
);
assert!(q.streams[a].finished);
assert_eq!(
q.streams[a].head_ts,
Some(1000),
"bound frozen at frame-1 end"
);
assert_eq!(q.streams[a].fifo.len(), 1, "frame 1 enqueued");
assert_eq!(
q.send(a, Some(Id(2)), Some(2000), ms, 0),
SqSend::StreamFinished,
"send to a finished stream is dropped"
);
assert_eq!(
q.streams[a].head_ts,
Some(1000),
"bound unchanged by the drop"
);
assert_eq!(q.streams[a].fifo.len(), 1, "frame 2 was not enqueued");
}
#[test]
fn frames_max_retroactive_finish() {
let ms = tb(1, 1000);
let mut q = SyncQueue::<Id>::new(10_000_000);
let a = q.add_stream(true);
let _b = q.add_stream(true);
push(&mut q, a, 1, 1000, ms);
push(&mut q, a, 2, 2000, ms); assert!(!q.streams[a].finished);
q.sq_limit_frames(a, 2); assert!(
q.streams[a].finished,
"frames_sent >= frames_max -> finished"
);
}
#[test]
fn drain_all_releasable_order() {
let ms = tb(1, 1000);
let mut q = SyncQueue::<Id>::new(10_000_000);
let a = q.add_stream(true);
let b = q.add_stream(true);
push(&mut q, a, 10, 1000, ms);
push(&mut q, a, 11, 2000, ms); push(&mut q, b, 20, 1000, ms);
push(&mut q, b, 21, 2000, ms);
assert_eq!(drain_all(&mut q), vec![10, 11, 20, 21]);
}
#[test]
fn drain_all_releasable_heartbeat_unblocks() {
let ms = tb(1, 1000);
let mut q = SyncQueue::<Id>::new(1_000_000); let a = q.add_stream(true);
let b = q.add_stream(true);
push(&mut q, a, 1, 1000, ms); push(&mut q, a, 2, 3000, ms); push(&mut q, b, 100, 500, ms);
assert_eq!(drain_all(&mut q), vec![100, 1]);
assert_eq!(q.streams[a].fifo.len(), 1, "A's 3s item still buffered");
}
#[test]
fn newly_finished_reports_each_once() {
let ms = tb(1, 1000);
let mut q = SyncQueue::<Id>::new(10_000_000);
let a = q.add_stream(true);
let b = q.add_stream(true);
let mut nf = Vec::new();
q.newly_finished(&mut nf);
assert!(nf.is_empty());
push(&mut q, a, 1, 3000, ms); for (i, end) in [1000, 2000, 3000, 4000, 5000].iter().enumerate() {
push(&mut q, b, 100 + i as i64, *end, ms); }
finish(&mut q, a);
let mut nf = Vec::new();
q.newly_finished(&mut nf);
assert_eq!(nf, vec![a, b], "self A then secondary-cascade B, each once");
let mut nf2 = Vec::new();
q.newly_finished(&mut nf2);
assert!(nf2.is_empty(), "no double-report across calls");
}
#[test]
fn untimestamped_finished_limiter_does_not_stall() {
let ms = tb(1, 1000);
let mut q = SyncQueue::<Id>::new(10_000_000);
let a = q.add_stream(true);
let b = q.add_stream(true);
finish(&mut q, a);
assert!(!q.is_finished(), "B still live");
push(&mut q, b, 20, 1000, ms);
push(&mut q, b, 21, 2000, ms);
assert_eq!(
drain(&mut q, b),
vec![20, 21],
"peer releases despite the finished untimestamped limiter"
);
}
}