mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-12-23 02:26:35 +00:00
jitterbuffer: implement support for serialized events / queries
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1426>
This commit is contained in:
parent
74ec83a0ff
commit
327f563e80
2 changed files with 279 additions and 42 deletions
|
@ -158,7 +158,7 @@ impl JitterBufferStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl futures::stream::Stream for JitterBufferStream {
|
impl futures::stream::Stream for JitterBufferStream {
|
||||||
type Item = (gst::Buffer, mpsc::Sender<gst::Buffer>);
|
type Item = (JitterBufferItem, mpsc::Sender<JitterBufferItem>);
|
||||||
|
|
||||||
fn poll_next(
|
fn poll_next(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
|
@ -173,16 +173,19 @@ impl futures::stream::Stream for JitterBufferStream {
|
||||||
match jitterbuffer_store.jitterbuffer.poll(now) {
|
match jitterbuffer_store.jitterbuffer.poll(now) {
|
||||||
jitterbuffer::PollResult::Forward { id, discont } => {
|
jitterbuffer::PollResult::Forward { id, discont } => {
|
||||||
if let Some(ref tx) = pad.tx {
|
if let Some(ref tx) = pad.tx {
|
||||||
let mut packet = jitterbuffer_store
|
let mut item = jitterbuffer_store
|
||||||
.store
|
.store
|
||||||
.remove(&id)
|
.remove(&id)
|
||||||
.unwrap_or_else(|| panic!("Buffer with id {id} not in store!"));
|
.unwrap_or_else(|| panic!("Buffer with id {id} not in store!"));
|
||||||
if discont {
|
|
||||||
gst::debug!(CAT, obj: pad.pad, "Forwarding discont buffer");
|
if let JitterBufferItem::Packet(ref mut packet) = item {
|
||||||
let packet_mut = packet.make_mut();
|
if discont {
|
||||||
packet_mut.set_flags(gst::BufferFlags::DISCONT);
|
gst::debug!(CAT, obj: pad.pad, "Forwarding discont buffer");
|
||||||
|
let packet_mut = packet.make_mut();
|
||||||
|
packet_mut.set_flags(gst::BufferFlags::DISCONT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return Poll::Ready(Some((packet, tx.clone())));
|
return Poll::Ready(Some((item, tx.clone())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jitterbuffer::PollResult::Timeout(timeout) => {
|
jitterbuffer::PollResult::Timeout(timeout) => {
|
||||||
|
@ -211,9 +214,19 @@ impl futures::stream::Stream for JitterBufferStream {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum JitterBufferItem {
|
||||||
|
Packet(gst::Buffer),
|
||||||
|
Event(gst::Event),
|
||||||
|
Query(std::ptr::NonNull<gst::QueryRef>),
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: Need to be able to pass *mut gst::QueryRef
|
||||||
|
unsafe impl Send for JitterBufferItem {}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct JitterBufferStore {
|
struct JitterBufferStore {
|
||||||
store: BTreeMap<usize, gst::Buffer>,
|
store: BTreeMap<usize, JitterBufferItem>,
|
||||||
jitterbuffer: JitterBuffer,
|
jitterbuffer: JitterBuffer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,7 +235,7 @@ struct RtpRecvSrcPad {
|
||||||
pt: u8,
|
pt: u8,
|
||||||
ssrc: u32,
|
ssrc: u32,
|
||||||
pad: gst::Pad,
|
pad: gst::Pad,
|
||||||
tx: Option<mpsc::Sender<gst::Buffer>>,
|
tx: Option<mpsc::Sender<JitterBufferItem>>,
|
||||||
jitter_buffer_store: Arc<Mutex<JitterBufferStore>>,
|
jitter_buffer_store: Arc<Mutex<JitterBufferStore>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,6 +318,9 @@ struct BinSessionInner {
|
||||||
rtp_recv_sink_segment: Option<gst::FormattedSegment<gst::ClockTime>>,
|
rtp_recv_sink_segment: Option<gst::FormattedSegment<gst::ClockTime>>,
|
||||||
rtp_recv_sink_seqnum: Option<gst::Seqnum>,
|
rtp_recv_sink_seqnum: Option<gst::Seqnum>,
|
||||||
|
|
||||||
|
// For replying to synchronized queries on the sink pad
|
||||||
|
query_tx: Arc<Mutex<Option<std::sync::mpsc::Sender<bool>>>>,
|
||||||
|
|
||||||
caps_map: HashMap<u8, HashMap<u32, gst::Caps>>,
|
caps_map: HashMap<u8, HashMap<u32, gst::Caps>>,
|
||||||
recv_store: Vec<HeldRecvBuffer>,
|
recv_store: Vec<HeldRecvBuffer>,
|
||||||
jitterbuffer_task: Option<JitterBufferTask>,
|
jitterbuffer_task: Option<JitterBufferTask>,
|
||||||
|
@ -334,6 +350,8 @@ impl BinSessionInner {
|
||||||
rtp_recv_sink_segment: None,
|
rtp_recv_sink_segment: None,
|
||||||
rtp_recv_sink_seqnum: None,
|
rtp_recv_sink_seqnum: None,
|
||||||
|
|
||||||
|
query_tx: Arc::new(Mutex::new(None)),
|
||||||
|
|
||||||
caps_map: HashMap::default(),
|
caps_map: HashMap::default(),
|
||||||
recv_store: vec![],
|
recv_store: vec![],
|
||||||
jitterbuffer_task: None,
|
jitterbuffer_task: None,
|
||||||
|
@ -377,6 +395,7 @@ impl BinSessionInner {
|
||||||
let pad_weak = pad.downgrade();
|
let pad_weak = pad.downgrade();
|
||||||
|
|
||||||
let recv_flow_combiner = self.recv_flow_combiner.clone();
|
let recv_flow_combiner = self.recv_flow_combiner.clone();
|
||||||
|
let query_tx = Arc::downgrade(&self.query_tx);
|
||||||
// A task per received ssrc may be a bit excessive.
|
// A task per received ssrc may be a bit excessive.
|
||||||
// Other options are:
|
// Other options are:
|
||||||
// - Single task per received input stream rather than per output ssrc/pt
|
// - Single task per received input stream rather than per output ssrc/pt
|
||||||
|
@ -386,17 +405,33 @@ impl BinSessionInner {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(buffer) = rx.blocking_recv() else {
|
let Some(item) = rx.blocking_recv() else {
|
||||||
gst::debug!(CAT, obj: pad, "Pad channel was closed, pausing");
|
gst::debug!(CAT, obj: pad, "Pad channel was closed, pausing");
|
||||||
let _ = pad.pause_task();
|
let _ = pad.pause_task();
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut recv_flow_combiner = recv_flow_combiner.lock().unwrap();
|
match item {
|
||||||
let flow = pad.push(buffer);
|
JitterBufferItem::Packet(buffer) => {
|
||||||
gst::trace!(CAT, obj: pad, "Pushed buffer, flow ret {:?}", flow);
|
let mut recv_flow_combiner = recv_flow_combiner.lock().unwrap();
|
||||||
let _combined_flow = recv_flow_combiner.update_pad_flow(&pad, flow);
|
let flow = pad.push(buffer);
|
||||||
// TODO: store flow, return only on session pads?
|
gst::trace!(CAT, obj: pad, "Pushed buffer, flow ret {:?}", flow);
|
||||||
|
let _combined_flow = recv_flow_combiner.update_pad_flow(&pad, flow);
|
||||||
|
// TODO: store flow, return only on session pads?
|
||||||
|
}
|
||||||
|
JitterBufferItem::Event(event) => {
|
||||||
|
let res = pad.push_event(event);
|
||||||
|
gst::trace!(CAT, obj: pad, "Pushed serialized event, result: {}", res);
|
||||||
|
}
|
||||||
|
JitterBufferItem::Query(mut query) => {
|
||||||
|
// This is safe because the thread holding the original reference is waiting
|
||||||
|
// for us exclusively
|
||||||
|
let res = pad.query(unsafe { query.as_mut() });
|
||||||
|
if let Some(query_tx) = query_tx.upgrade() {
|
||||||
|
let _ = query_tx.lock().unwrap().as_mut().unwrap().send(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
gst::debug!(CAT, obj: pad, "Task started");
|
gst::debug!(CAT, obj: pad, "Task started");
|
||||||
|
@ -791,8 +826,8 @@ impl RtpBin2 {
|
||||||
|
|
||||||
async fn jitterbuffer_task(state: Arc<Mutex<BinSessionInner>>) {
|
async fn jitterbuffer_task(state: Arc<Mutex<BinSessionInner>>) {
|
||||||
let mut stream = JitterBufferStream::new(state);
|
let mut stream = JitterBufferStream::new(state);
|
||||||
while let Some((buffer, tx)) = stream.next().await {
|
while let Some((item, tx)) = stream.next().await {
|
||||||
let _ = tx.send(buffer).await;
|
let _ = tx.send(item).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1067,7 +1102,7 @@ impl RtpBin2 {
|
||||||
// is blocked
|
// is blocked
|
||||||
let mut jitterbuffer_store = held.pad.jitter_buffer_store.lock().unwrap();
|
let mut jitterbuffer_store = held.pad.jitter_buffer_store.lock().unwrap();
|
||||||
|
|
||||||
match jitterbuffer_store.jitterbuffer.queue(
|
match jitterbuffer_store.jitterbuffer.queue_packet(
|
||||||
&rtp,
|
&rtp,
|
||||||
held.buffer.pts().unwrap().nseconds(),
|
held.buffer.pts().unwrap().nseconds(),
|
||||||
now,
|
now,
|
||||||
|
@ -1075,7 +1110,9 @@ impl RtpBin2 {
|
||||||
jitterbuffer::QueueResult::Queued(id) => {
|
jitterbuffer::QueueResult::Queued(id) => {
|
||||||
drop(mapped);
|
drop(mapped);
|
||||||
|
|
||||||
jitterbuffer_store.store.insert(id, held.buffer);
|
jitterbuffer_store
|
||||||
|
.store
|
||||||
|
.insert(id, JitterBufferItem::Packet(held.buffer));
|
||||||
}
|
}
|
||||||
jitterbuffer::QueueResult::Late => {
|
jitterbuffer::QueueResult::Late => {
|
||||||
gst::warning!(CAT, "Late buffer was dropped");
|
gst::warning!(CAT, "Late buffer was dropped");
|
||||||
|
@ -1287,6 +1324,115 @@ impl RtpBin2 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn rtp_recv_sink_query(
|
||||||
|
&self,
|
||||||
|
pad: &gst::Pad,
|
||||||
|
query: &mut gst::QueryRef,
|
||||||
|
id: usize,
|
||||||
|
) -> bool {
|
||||||
|
gst::log!(CAT, obj: pad, "Handling query {query:?}");
|
||||||
|
|
||||||
|
if query.is_serialized() {
|
||||||
|
let state = self.state.lock().unwrap();
|
||||||
|
let mut ret = true;
|
||||||
|
|
||||||
|
if let Some(session) = state.session_by_id(id) {
|
||||||
|
let session = session.inner.lock().unwrap();
|
||||||
|
|
||||||
|
let (query_tx, query_rx) = std::sync::mpsc::channel();
|
||||||
|
|
||||||
|
assert!(session.query_tx.lock().unwrap().replace(query_tx).is_none());
|
||||||
|
|
||||||
|
let jb_stores: Vec<Arc<Mutex<JitterBufferStore>>> = session
|
||||||
|
.rtp_recv_srcpads
|
||||||
|
.iter()
|
||||||
|
.filter(|r| state.pads_session_id_map.contains_key(&r.pad))
|
||||||
|
.map(|p| p.jitter_buffer_store.clone())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
drop(session);
|
||||||
|
|
||||||
|
let query = std::ptr::NonNull::from(query);
|
||||||
|
|
||||||
|
// The idea here is to reproduce the default behavior of GstPad, where
|
||||||
|
// queries will run sequentially on each internally linked source pad
|
||||||
|
// until one succeeds.
|
||||||
|
//
|
||||||
|
// We however jump through hoops here in order to keep the query
|
||||||
|
// reasonably synchronized with the data flow.
|
||||||
|
//
|
||||||
|
// While the GstPad behavior makes complete sense for allocation
|
||||||
|
// queries (can't have it succeed for two downstream branches as they
|
||||||
|
// need to modify the query), we could in the future decide to have
|
||||||
|
// the drain query run on all relevant source pads no matter what.
|
||||||
|
//
|
||||||
|
// Also note that if there were no internally linked pads, GstPad's
|
||||||
|
// behavior is to return TRUE, we do this here too.
|
||||||
|
for jb_store in jb_stores {
|
||||||
|
let mut jitterbuffer_store = jb_store.lock().unwrap();
|
||||||
|
|
||||||
|
let jitterbuffer::QueueResult::Queued(id) =
|
||||||
|
jitterbuffer_store.jitterbuffer.queue_serialized_item()
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
jitterbuffer_store
|
||||||
|
.store
|
||||||
|
.insert(id, JitterBufferItem::Query(query));
|
||||||
|
|
||||||
|
// Now block until the jitterbuffer has processed the query
|
||||||
|
match query_rx.recv() {
|
||||||
|
Ok(res) => {
|
||||||
|
ret |= res;
|
||||||
|
if ret {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// The sender was closed because of a state change
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret
|
||||||
|
} else {
|
||||||
|
gst::Pad::query_default(pad, Some(pad), query)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialized events received on our sink pads have to navigate
|
||||||
|
// through the relevant jitterbuffers in order to remain (reasonably)
|
||||||
|
// consistently ordered with the RTP packets once output on our source
|
||||||
|
// pads
|
||||||
|
fn rtp_recv_sink_queue_serialized_event(&self, id: usize, event: gst::Event) -> bool {
|
||||||
|
let state = self.state.lock().unwrap();
|
||||||
|
if let Some(session) = state.session_by_id(id) {
|
||||||
|
let session = session.inner.lock().unwrap();
|
||||||
|
for srcpad in session
|
||||||
|
.rtp_recv_srcpads
|
||||||
|
.iter()
|
||||||
|
.filter(|r| state.pads_session_id_map.contains_key(&r.pad))
|
||||||
|
{
|
||||||
|
let mut jitterbuffer_store = srcpad.jitter_buffer_store.lock().unwrap();
|
||||||
|
|
||||||
|
let jitterbuffer::QueueResult::Queued(id) =
|
||||||
|
jitterbuffer_store.jitterbuffer.queue_serialized_item()
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
jitterbuffer_store
|
||||||
|
.store
|
||||||
|
.insert(id, JitterBufferItem::Event(event.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn rtp_recv_sink_event(&self, pad: &gst::Pad, mut event: gst::Event, id: usize) -> bool {
|
fn rtp_recv_sink_event(&self, pad: &gst::Pad, mut event: gst::Event, id: usize) -> bool {
|
||||||
match event.view() {
|
match event.view() {
|
||||||
gst::EventView::StreamStart(stream_start) => {
|
gst::EventView::StreamStart(stream_start) => {
|
||||||
|
@ -1346,7 +1492,7 @@ impl RtpBin2 {
|
||||||
|
|
||||||
drop(state);
|
drop(state);
|
||||||
|
|
||||||
gst::Pad::event_default(pad, Some(&*self.obj()), event)
|
self.rtp_recv_sink_queue_serialized_event(id, event)
|
||||||
}
|
}
|
||||||
gst::EventView::Eos(_eos) => {
|
gst::EventView::Eos(_eos) => {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
|
@ -1385,7 +1531,13 @@ impl RtpBin2 {
|
||||||
// FIXME: may need to delay sending eos under some circumstances
|
// FIXME: may need to delay sending eos under some circumstances
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
_ => gst::Pad::event_default(pad, Some(&*self.obj()), event),
|
_ => {
|
||||||
|
if event.is_serialized() {
|
||||||
|
self.rtp_recv_sink_queue_serialized_event(id, event)
|
||||||
|
} else {
|
||||||
|
gst::Pad::event_default(pad, Some(&*self.obj()), event)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1808,6 +1960,13 @@ impl ElementImpl for RtpBin2 {
|
||||||
|this| this.rtp_recv_sink_event(pad, event, id),
|
|this| this.rtp_recv_sink_event(pad, event, id),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
.query_function(move |pad, parent, query| {
|
||||||
|
RtpBin2::catch_panic_pad_function(
|
||||||
|
parent,
|
||||||
|
|| false,
|
||||||
|
|this| this.rtp_recv_sink_query(pad, query, id),
|
||||||
|
)
|
||||||
|
})
|
||||||
.name(format!("rtp_recv_sink_{}", id))
|
.name(format!("rtp_recv_sink_{}", id))
|
||||||
.build();
|
.build();
|
||||||
session.rtp_recv_sinkpad = Some(sinkpad.clone());
|
session.rtp_recv_sinkpad = Some(sinkpad.clone());
|
||||||
|
@ -2028,6 +2187,7 @@ impl ElementImpl for RtpBin2 {
|
||||||
session.rtp_recv_sink_group_id = None;
|
session.rtp_recv_sink_group_id = None;
|
||||||
|
|
||||||
session.caps_map.clear();
|
session.caps_map.clear();
|
||||||
|
session.query_tx.lock().unwrap().take();
|
||||||
|
|
||||||
if let Some(jitterbuffer_task) = session.jitterbuffer_task.take() {
|
if let Some(jitterbuffer_task) = session.jitterbuffer_task.take() {
|
||||||
jitterbuffer_task.abort_handle.abort();
|
jitterbuffer_task.abort_handle.abort();
|
||||||
|
|
|
@ -65,7 +65,13 @@ struct Item {
|
||||||
|
|
||||||
impl Ord for Item {
|
impl Ord for Item {
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
self.seqnum.cmp(&other.seqnum)
|
self.seqnum
|
||||||
|
.cmp(&other.seqnum)
|
||||||
|
.then(match (self.pts, other.pts) {
|
||||||
|
(None, Some(_)) => Ordering::Greater,
|
||||||
|
(Some(_), None) => Ordering::Less,
|
||||||
|
_ => Ordering::Equal,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +107,21 @@ impl JitterBuffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn queue(&mut self, rtp: &RtpPacket, mut pts: u64, now: Instant) -> QueueResult {
|
pub fn queue_serialized_item(&mut self) -> QueueResult {
|
||||||
|
let id = self.packet_counter;
|
||||||
|
self.packet_counter += 1;
|
||||||
|
let item = Item {
|
||||||
|
id,
|
||||||
|
pts: None,
|
||||||
|
seqnum: (*self.seqnums.last().unwrap_or(&0)),
|
||||||
|
};
|
||||||
|
self.items.insert(item);
|
||||||
|
trace!("Queued serialized item and assigned ID {id}");
|
||||||
|
|
||||||
|
QueueResult::Queued(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn queue_packet(&mut self, rtp: &RtpPacket, mut pts: u64, now: Instant) -> QueueResult {
|
||||||
// From this point on we always work with extended sequence numbers
|
// From this point on we always work with extended sequence numbers
|
||||||
let seqnum = self.extended_seqnum.next(rtp.sequence_number());
|
let seqnum = self.extended_seqnum.next(rtp.sequence_number());
|
||||||
|
|
||||||
|
@ -257,7 +277,7 @@ mod tests {
|
||||||
|
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
|
|
||||||
let QueueResult::Queued(id) = jb.queue(&packet, 0, now) else {
|
let QueueResult::Queued(id) = jb.queue_packet(&packet, 0, now) else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -273,7 +293,7 @@ mod tests {
|
||||||
|
|
||||||
let mut now = Instant::now();
|
let mut now = Instant::now();
|
||||||
|
|
||||||
let QueueResult::Queued(id) = jb.queue(&packet, 0, now) else {
|
let QueueResult::Queued(id) = jb.queue_packet(&packet, 0, now) else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -304,13 +324,13 @@ mod tests {
|
||||||
let rtp_data = generate_rtp_packet(0x12345678, 0, 0, 4);
|
let rtp_data = generate_rtp_packet(0x12345678, 0, 0, 4);
|
||||||
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
||||||
|
|
||||||
let QueueResult::Queued(id_first) = jb.queue(&packet, 0, now) else {
|
let QueueResult::Queued(id_first) = jb.queue_packet(&packet, 0, now) else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let rtp_data = generate_rtp_packet(0x12345678, 1, 0, 4);
|
let rtp_data = generate_rtp_packet(0x12345678, 1, 0, 4);
|
||||||
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
||||||
let QueueResult::Queued(id_second) = jb.queue(&packet, 0, now) else {
|
let QueueResult::Queued(id_second) = jb.queue_packet(&packet, 0, now) else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -338,13 +358,13 @@ mod tests {
|
||||||
|
|
||||||
let rtp_data = generate_rtp_packet(0x12345678, 0, 0, 4);
|
let rtp_data = generate_rtp_packet(0x12345678, 0, 0, 4);
|
||||||
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
||||||
let QueueResult::Queued(id_first) = jb.queue(&packet, 0, now) else {
|
let QueueResult::Queued(id_first) = jb.queue_packet(&packet, 0, now) else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let rtp_data = generate_rtp_packet(0x12345678, 2, 0, 4);
|
let rtp_data = generate_rtp_packet(0x12345678, 2, 0, 4);
|
||||||
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
||||||
let QueueResult::Queued(id_second) = jb.queue(&packet, 0, now) else {
|
let QueueResult::Queued(id_second) = jb.queue_packet(&packet, 0, now) else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -372,7 +392,7 @@ mod tests {
|
||||||
|
|
||||||
let rtp_data = generate_rtp_packet(0x12345678, 1, 0, 4);
|
let rtp_data = generate_rtp_packet(0x12345678, 1, 0, 4);
|
||||||
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
||||||
let QueueResult::Queued(id) = jb.queue(&packet, 0, now) else {
|
let QueueResult::Queued(id) = jb.queue_packet(&packet, 0, now) else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -380,17 +400,17 @@ mod tests {
|
||||||
|
|
||||||
let rtp_data = generate_rtp_packet(0x12345678, 0, 0, 4);
|
let rtp_data = generate_rtp_packet(0x12345678, 0, 0, 4);
|
||||||
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
||||||
assert_eq!(jb.queue(&packet, 0, now), QueueResult::Late);
|
assert_eq!(jb.queue_packet(&packet, 0, now), QueueResult::Late);
|
||||||
|
|
||||||
// Try and push a duplicate
|
// Try and push a duplicate
|
||||||
let rtp_data = generate_rtp_packet(0x12345678, 1, 0, 4);
|
let rtp_data = generate_rtp_packet(0x12345678, 1, 0, 4);
|
||||||
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
||||||
assert_eq!(jb.queue(&packet, 0, now), QueueResult::Duplicate);
|
assert_eq!(jb.queue_packet(&packet, 0, now), QueueResult::Duplicate);
|
||||||
|
|
||||||
// We do accept future sequence numbers up to a distance of at least std::i16::MAX
|
// We do accept future sequence numbers up to a distance of at least std::i16::MAX
|
||||||
let rtp_data = generate_rtp_packet(0x12345678, std::i16::MAX as u16 + 1, 0, 4);
|
let rtp_data = generate_rtp_packet(0x12345678, std::i16::MAX as u16 + 1, 0, 4);
|
||||||
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
||||||
let QueueResult::Queued(id) = jb.queue(&packet, 0, now) else {
|
let QueueResult::Queued(id) = jb.queue_packet(&packet, 0, now) else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -399,7 +419,7 @@ mod tests {
|
||||||
// But no further
|
// But no further
|
||||||
let rtp_data = generate_rtp_packet(0x12345678, 2, 0, 4);
|
let rtp_data = generate_rtp_packet(0x12345678, 2, 0, 4);
|
||||||
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
||||||
assert_eq!(jb.queue(&packet, 0, now), QueueResult::Late);
|
assert_eq!(jb.queue_packet(&packet, 0, now), QueueResult::Late);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -410,7 +430,7 @@ mod tests {
|
||||||
|
|
||||||
let rtp_data = generate_rtp_packet(0x12345678, 0, 0, 4);
|
let rtp_data = generate_rtp_packet(0x12345678, 0, 0, 4);
|
||||||
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
||||||
let QueueResult::Queued(id_first) = jb.queue(&packet, 0, now) else {
|
let QueueResult::Queued(id_first) = jb.queue_packet(&packet, 0, now) else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -421,7 +441,7 @@ mod tests {
|
||||||
|
|
||||||
let rtp_data = generate_rtp_packet(0x12345678, 1, 180000, 4);
|
let rtp_data = generate_rtp_packet(0x12345678, 1, 180000, 4);
|
||||||
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
||||||
let QueueResult::Queued(id_second) = jb.queue(&packet, 2_000_000_000, now) else {
|
let QueueResult::Queued(id_second) = jb.queue_packet(&packet, 2_000_000_000, now) else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -479,13 +499,13 @@ mod tests {
|
||||||
|
|
||||||
let rtp_data = generate_rtp_packet(0x12345678, 0, 0, 4);
|
let rtp_data = generate_rtp_packet(0x12345678, 0, 0, 4);
|
||||||
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
||||||
jb.queue(&packet, 0, now);
|
jb.queue_packet(&packet, 0, now);
|
||||||
|
|
||||||
assert_stats(&jb, 0, 0, 0, 0);
|
assert_stats(&jb, 0, 0, 0, 0);
|
||||||
|
|
||||||
// At this point pushing the same packet in before it gets output
|
// At this point pushing the same packet in before it gets output
|
||||||
// results in an increment of the duplicate stat
|
// results in an increment of the duplicate stat
|
||||||
jb.queue(&packet, 0, now);
|
jb.queue_packet(&packet, 0, now);
|
||||||
assert_stats(&jb, 0, 0, 1, 0);
|
assert_stats(&jb, 0, 0, 1, 0);
|
||||||
|
|
||||||
now += Duration::from_secs(1);
|
now += Duration::from_secs(1);
|
||||||
|
@ -495,14 +515,14 @@ mod tests {
|
||||||
|
|
||||||
// Pushing it after the first version got output also results in
|
// Pushing it after the first version got output also results in
|
||||||
// an increment of the duplicate stat
|
// an increment of the duplicate stat
|
||||||
jb.queue(&packet, 0, now);
|
jb.queue_packet(&packet, 0, now);
|
||||||
assert_stats(&jb, 0, 0, 2, 1);
|
assert_stats(&jb, 0, 0, 2, 1);
|
||||||
|
|
||||||
// Then after a packet with seqnum 2 goes through, the lost
|
// Then after a packet with seqnum 2 goes through, the lost
|
||||||
// stat must be incremented by 1 (as packet with seqnum 1 went missing)
|
// stat must be incremented by 1 (as packet with seqnum 1 went missing)
|
||||||
let rtp_data = generate_rtp_packet(0x12345678, 2, 9000, 4);
|
let rtp_data = generate_rtp_packet(0x12345678, 2, 9000, 4);
|
||||||
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
||||||
jb.queue(&packet, 100_000_000, now);
|
jb.queue_packet(&packet, 100_000_000, now);
|
||||||
|
|
||||||
now += Duration::from_millis(100);
|
now += Duration::from_millis(100);
|
||||||
let _ = jb.poll(now);
|
let _ = jb.poll(now);
|
||||||
|
@ -512,7 +532,7 @@ mod tests {
|
||||||
// considered both late and lost
|
// considered both late and lost
|
||||||
let rtp_data = generate_rtp_packet(0x12345678, 1, 4500, 4);
|
let rtp_data = generate_rtp_packet(0x12345678, 1, 4500, 4);
|
||||||
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
||||||
jb.queue(&packet, 50_000_000, now);
|
jb.queue_packet(&packet, 50_000_000, now);
|
||||||
|
|
||||||
let _ = jb.poll(now);
|
let _ = jb.poll(now);
|
||||||
assert_stats(&jb, 1, 1, 2, 2);
|
assert_stats(&jb, 1, 1, 2, 2);
|
||||||
|
@ -520,9 +540,66 @@ mod tests {
|
||||||
// Finally if it arrives again it should be considered a duplicate,
|
// Finally if it arrives again it should be considered a duplicate,
|
||||||
// and will have achieved the dubious honor of simultaneously being
|
// and will have achieved the dubious honor of simultaneously being
|
||||||
// lost, late and duplicated
|
// lost, late and duplicated
|
||||||
jb.queue(&packet, 50_000_000, now);
|
jb.queue_packet(&packet, 50_000_000, now);
|
||||||
|
|
||||||
let _ = jb.poll(now);
|
let _ = jb.poll(now);
|
||||||
assert_stats(&jb, 1, 1, 3, 2);
|
assert_stats(&jb, 1, 1, 3, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialized_items() {
|
||||||
|
let mut jb = JitterBuffer::new(Duration::from_secs(0));
|
||||||
|
|
||||||
|
let now = Instant::now();
|
||||||
|
|
||||||
|
let rtp_data = generate_rtp_packet(0x12345678, 0, 0, 4);
|
||||||
|
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
||||||
|
|
||||||
|
let QueueResult::Queued(id_first_serialized_item) = jb.queue_serialized_item() else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
let QueueResult::Queued(id_first) = jb.queue_packet(&packet, 0, now) else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
let QueueResult::Queued(id_second_serialized_item) = jb.queue_serialized_item() else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
let rtp_data = generate_rtp_packet(0x12345678, 1, 0, 4);
|
||||||
|
let packet = RtpPacket::parse(&rtp_data).unwrap();
|
||||||
|
let QueueResult::Queued(id_second) = jb.queue_packet(&packet, 0, now) else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
jb.poll(now),
|
||||||
|
PollResult::Forward {
|
||||||
|
id: id_first_serialized_item,
|
||||||
|
discont: false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
jb.poll(now),
|
||||||
|
PollResult::Forward {
|
||||||
|
id: id_first,
|
||||||
|
discont: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
jb.poll(now),
|
||||||
|
PollResult::Forward {
|
||||||
|
id: id_second_serialized_item,
|
||||||
|
discont: false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
jb.poll(now),
|
||||||
|
PollResult::Forward {
|
||||||
|
id: id_second,
|
||||||
|
discont: false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue