mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-11-29 15:01:07 +00:00
rtp/send: support receiving buffer lists
Can reduce processing overhead if many buffers are pushed concurrently. Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1618>
This commit is contained in:
parent
2d1f556794
commit
df4a4fb2ef
2 changed files with 265 additions and 35 deletions
|
@ -307,17 +307,14 @@ impl RtpSend {
|
||||||
gst::Iterator::from_vec(vec![])
|
gst::Iterator::from_vec(vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rtp_send_sink_chain(
|
fn handle_buffer(
|
||||||
&self,
|
&self,
|
||||||
id: usize,
|
sinkpad: &gst::Pad,
|
||||||
|
srcpad: &gst::Pad,
|
||||||
|
internal_session: &SharedSession,
|
||||||
buffer: gst::Buffer,
|
buffer: gst::Buffer,
|
||||||
|
now: Instant,
|
||||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||||
let state = self.state.lock().unwrap();
|
|
||||||
let Some(session) = state.session_by_id(id) else {
|
|
||||||
gst::error!(CAT, "No session?");
|
|
||||||
return Err(gst::FlowError::Error);
|
|
||||||
};
|
|
||||||
|
|
||||||
let mapped = buffer.map_readable().map_err(|e| {
|
let mapped = buffer.map_readable().map_err(|e| {
|
||||||
gst::error!(CAT, imp: self, "Failed to map input buffer {e:?}");
|
gst::error!(CAT, imp: self, "Failed to map input buffer {e:?}");
|
||||||
gst::FlowError::Error
|
gst::FlowError::Error
|
||||||
|
@ -330,14 +327,9 @@ impl RtpSend {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let srcpad = session.rtp_send_srcpad.clone().unwrap();
|
let mut session_inner = internal_session.inner.lock().unwrap();
|
||||||
let sinkpad = session.rtp_send_sinkpad.clone().unwrap();
|
|
||||||
let session = session.internal_session.clone();
|
|
||||||
let mut session_inner = session.inner.lock().unwrap();
|
|
||||||
drop(state);
|
|
||||||
|
|
||||||
let now = Instant::now();
|
let mut ssrc_collision: smallvec::SmallVec<[u32; 4]> = smallvec::SmallVec::new();
|
||||||
let mut ssrc_collision = vec![];
|
|
||||||
loop {
|
loop {
|
||||||
match session_inner.session.handle_send(&rtp, now) {
|
match session_inner.session.handle_send(&rtp, now) {
|
||||||
SendReply::SsrcCollision(ssrc) => {
|
SendReply::SsrcCollision(ssrc) => {
|
||||||
|
@ -347,8 +339,10 @@ impl RtpSend {
|
||||||
}
|
}
|
||||||
SendReply::NewSsrc(ssrc, _pt) => {
|
SendReply::NewSsrc(ssrc, _pt) => {
|
||||||
drop(session_inner);
|
drop(session_inner);
|
||||||
session.config.emit_by_name::<()>("new-ssrc", &[&ssrc]);
|
internal_session
|
||||||
session_inner = session.inner.lock().unwrap();
|
.config
|
||||||
|
.emit_by_name::<()>("new-ssrc", &[&ssrc]);
|
||||||
|
session_inner = internal_session.inner.lock().unwrap();
|
||||||
}
|
}
|
||||||
SendReply::Passthrough => break,
|
SendReply::Passthrough => break,
|
||||||
SendReply::Drop => return Ok(gst::FlowSuccess::Ok),
|
SendReply::Drop => return Ok(gst::FlowSuccess::Ok),
|
||||||
|
@ -374,7 +368,50 @@ impl RtpSend {
|
||||||
srcpad.push(buffer)
|
srcpad.push(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rtp_send_sink_event(&self, pad: &gst::Pad, event: gst::Event, id: usize) -> bool {
|
fn rtp_sink_chain_list(
|
||||||
|
&self,
|
||||||
|
pad: &gst::Pad,
|
||||||
|
id: usize,
|
||||||
|
list: gst::BufferList,
|
||||||
|
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||||
|
let state = self.state.lock().unwrap();
|
||||||
|
let Some(session) = state.session_by_id(id) else {
|
||||||
|
gst::error!(CAT, "No session?");
|
||||||
|
return Err(gst::FlowError::Error);
|
||||||
|
};
|
||||||
|
|
||||||
|
let srcpad = session.rtp_send_srcpad.clone().unwrap();
|
||||||
|
let internal_session = session.internal_session.clone();
|
||||||
|
drop(state);
|
||||||
|
|
||||||
|
let now = Instant::now();
|
||||||
|
for buffer in list.iter_owned() {
|
||||||
|
self.handle_buffer(pad, &srcpad, &internal_session, buffer, now)?;
|
||||||
|
}
|
||||||
|
Ok(gst::FlowSuccess::Ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rtp_sink_chain(
|
||||||
|
&self,
|
||||||
|
pad: &gst::Pad,
|
||||||
|
id: usize,
|
||||||
|
buffer: gst::Buffer,
|
||||||
|
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||||
|
let state = self.state.lock().unwrap();
|
||||||
|
let Some(session) = state.session_by_id(id) else {
|
||||||
|
gst::error!(CAT, "No session?");
|
||||||
|
return Err(gst::FlowError::Error);
|
||||||
|
};
|
||||||
|
|
||||||
|
let srcpad = session.rtp_send_srcpad.clone().unwrap();
|
||||||
|
let internal_session = session.internal_session.clone();
|
||||||
|
drop(state);
|
||||||
|
|
||||||
|
let now = Instant::now();
|
||||||
|
self.handle_buffer(pad, &srcpad, &internal_session, buffer, now)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rtp_sink_event(&self, pad: &gst::Pad, event: gst::Event, id: usize) -> bool {
|
||||||
match event.view() {
|
match event.view() {
|
||||||
gst::EventView::Caps(caps) => {
|
gst::EventView::Caps(caps) => {
|
||||||
if let Some((pt, clock_rate)) = pt_clock_rate_from_caps(caps.caps()) {
|
if let Some((pt, clock_rate)) = pt_clock_rate_from_caps(caps.caps()) {
|
||||||
|
@ -651,11 +688,18 @@ impl ElementImpl for RtpSend {
|
||||||
Vec<gst::Event>,
|
Vec<gst::Event>,
|
||||||
)> {
|
)> {
|
||||||
let sinkpad = gst::Pad::builder_from_template(templ)
|
let sinkpad = gst::Pad::builder_from_template(templ)
|
||||||
.chain_function(move |_pad, parent, buffer| {
|
.chain_function(move |pad, parent, buffer| {
|
||||||
RtpSend::catch_panic_pad_function(
|
RtpSend::catch_panic_pad_function(
|
||||||
parent,
|
parent,
|
||||||
|| Err(gst::FlowError::Error),
|
|| Err(gst::FlowError::Error),
|
||||||
|this| this.rtp_send_sink_chain(id, buffer),
|
|this| this.rtp_sink_chain(pad, id, buffer),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.chain_list_function(move |pad, parent, list| {
|
||||||
|
RtpSend::catch_panic_pad_function(
|
||||||
|
parent,
|
||||||
|
|| Err(gst::FlowError::Error),
|
||||||
|
|this| this.rtp_sink_chain_list(pad, id, list),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.iterate_internal_links_function(|pad, parent| {
|
.iterate_internal_links_function(|pad, parent| {
|
||||||
|
@ -669,7 +713,7 @@ impl ElementImpl for RtpSend {
|
||||||
RtpSend::catch_panic_pad_function(
|
RtpSend::catch_panic_pad_function(
|
||||||
parent,
|
parent,
|
||||||
|| false,
|
|| false,
|
||||||
|this| this.rtp_send_sink_event(pad, event, id),
|
|this| this.rtp_sink_event(pad, event, id),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.flags(gst::PadFlags::PROXY_CAPS)
|
.flags(gst::PadFlags::PROXY_CAPS)
|
||||||
|
|
|
@ -47,9 +47,35 @@ fn generate_rtp_buffer(seqno: u16, rtpts: u32, payload_len: usize) -> gst::Buffe
|
||||||
gst::Buffer::from_mut_slice(data)
|
gst::Buffer::from_mut_slice(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[derive(Debug, Copy, Clone)]
|
||||||
fn test_send() {
|
struct PacketInfo {
|
||||||
|
seq_no: u16,
|
||||||
|
rtp_ts: u32,
|
||||||
|
payload_len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PacketInfo {
|
||||||
|
fn generate_buffer(&self) -> gst::Buffer {
|
||||||
|
generate_rtp_buffer(self.seq_no, self.rtp_ts, self.payload_len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static PACKETS_TEST_1: [PacketInfo; 2] = [
|
||||||
|
PacketInfo {
|
||||||
|
seq_no: 500,
|
||||||
|
rtp_ts: 20,
|
||||||
|
payload_len: 7,
|
||||||
|
},
|
||||||
|
PacketInfo {
|
||||||
|
seq_no: 501,
|
||||||
|
rtp_ts: 30,
|
||||||
|
payload_len: 23,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
fn send_init() -> gst_check::Harness {
|
||||||
init();
|
init();
|
||||||
|
|
||||||
let id = next_element_counter();
|
let id = next_element_counter();
|
||||||
|
|
||||||
let elem = gst::ElementFactory::make("rtpsend")
|
let elem = gst::ElementFactory::make("rtpsend")
|
||||||
|
@ -67,19 +93,56 @@ fn test_send() {
|
||||||
.build();
|
.build();
|
||||||
h.set_src_caps(caps);
|
h.set_src_caps(caps);
|
||||||
|
|
||||||
h.push(generate_rtp_buffer(500, 20, 9)).unwrap();
|
h
|
||||||
h.push(generate_rtp_buffer(501, 30, 11)).unwrap();
|
}
|
||||||
|
|
||||||
let buffer = h.pull().unwrap();
|
fn send_push<I>(h: &mut gst_check::Harness, packets: I, buffer_list: bool)
|
||||||
let mapped = buffer.map_readable().unwrap();
|
where
|
||||||
let rtp = rtp_types::RtpPacket::parse(&mapped).unwrap();
|
I: IntoIterator<Item = PacketInfo>,
|
||||||
assert_eq!(rtp.sequence_number(), 500);
|
{
|
||||||
|
if buffer_list {
|
||||||
|
let mut list = gst::BufferList::new();
|
||||||
|
let list_mut = list.make_mut();
|
||||||
|
for packet in packets {
|
||||||
|
list_mut.add(packet.generate_buffer());
|
||||||
|
}
|
||||||
|
let push_pad = h
|
||||||
|
.element()
|
||||||
|
.unwrap()
|
||||||
|
.static_pad("rtp_sink_0")
|
||||||
|
.unwrap()
|
||||||
|
.peer()
|
||||||
|
.unwrap();
|
||||||
|
push_pad.push_list(list).unwrap();
|
||||||
|
} else {
|
||||||
|
for packet in packets {
|
||||||
|
h.push(packet.generate_buffer()).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let buffer = h.pull().unwrap();
|
fn send_pull<I>(h: &mut gst_check::Harness, packets: I)
|
||||||
let mapped = buffer.map_readable().unwrap();
|
where
|
||||||
let rtp = rtp_types::RtpPacket::parse(&mapped).unwrap();
|
I: IntoIterator<Item = PacketInfo>,
|
||||||
assert_eq!(rtp.sequence_number(), 501);
|
{
|
||||||
|
for packet in packets {
|
||||||
|
let buffer = h.pull().unwrap();
|
||||||
|
let mapped = buffer.map_readable().unwrap();
|
||||||
|
let rtp = rtp_types::RtpPacket::parse(&mapped).unwrap();
|
||||||
|
assert_eq!(rtp.sequence_number(), packet.seq_no);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_check_stats<I>(h: &mut gst_check::Harness, packets: I)
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = PacketInfo>,
|
||||||
|
{
|
||||||
|
let mut n_packets = 0;
|
||||||
|
let mut n_bytes = 0;
|
||||||
|
for packet in packets {
|
||||||
|
n_packets += 1;
|
||||||
|
n_bytes += packet.payload_len;
|
||||||
|
}
|
||||||
let stats = h.element().unwrap().property::<gst::Structure>("stats");
|
let stats = h.element().unwrap().property::<gst::Structure>("stats");
|
||||||
|
|
||||||
let session_stats = stats.get::<gst::Structure>("0").unwrap();
|
let session_stats = stats.get::<gst::Structure>("0").unwrap();
|
||||||
|
@ -93,8 +156,131 @@ fn test_send() {
|
||||||
);
|
);
|
||||||
assert!(source_stats.get::<bool>("sender").unwrap());
|
assert!(source_stats.get::<bool>("sender").unwrap());
|
||||||
assert!(source_stats.get::<bool>("local").unwrap());
|
assert!(source_stats.get::<bool>("local").unwrap());
|
||||||
assert_eq!(source_stats.get::<u64>("packets-sent").unwrap(), 2);
|
assert_eq!(source_stats.get::<u64>("packets-sent").unwrap(), n_packets);
|
||||||
assert_eq!(source_stats.get::<u64>("octets-sent").unwrap(), 20);
|
assert_eq!(
|
||||||
|
source_stats.get::<u64>("octets-sent").unwrap(),
|
||||||
|
n_bytes as u64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_send() {
|
||||||
|
init();
|
||||||
|
|
||||||
|
let mut h = send_init();
|
||||||
|
|
||||||
|
send_push(&mut h, PACKETS_TEST_1, false);
|
||||||
|
send_pull(&mut h, PACKETS_TEST_1);
|
||||||
|
send_check_stats(&mut h, PACKETS_TEST_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_send_list() {
|
||||||
|
let mut h = send_init();
|
||||||
|
|
||||||
|
send_push(&mut h, PACKETS_TEST_1, true);
|
||||||
|
send_pull(&mut h, PACKETS_TEST_1);
|
||||||
|
send_check_stats(&mut h, PACKETS_TEST_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_send_benchmark() {
|
||||||
|
init();
|
||||||
|
let clock = gst::SystemClock::obtain();
|
||||||
|
const N_PACKETS: usize = 2 * 1024 * 1024;
|
||||||
|
let mut packets = Vec::with_capacity(N_PACKETS);
|
||||||
|
for i in 0..N_PACKETS {
|
||||||
|
packets.push(
|
||||||
|
PacketInfo {
|
||||||
|
seq_no: (i % u16::MAX as usize) as u16,
|
||||||
|
rtp_ts: i as u32,
|
||||||
|
payload_len: 8,
|
||||||
|
}
|
||||||
|
.generate_buffer(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut h = send_init();
|
||||||
|
|
||||||
|
let start = clock.time();
|
||||||
|
|
||||||
|
for packet in packets {
|
||||||
|
h.push(packet).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let pushed = clock.time();
|
||||||
|
|
||||||
|
for i in 0..N_PACKETS {
|
||||||
|
let buffer = h.pull().unwrap();
|
||||||
|
let mapped = buffer.map_readable().unwrap();
|
||||||
|
let rtp = rtp_types::RtpPacket::parse(&mapped).unwrap();
|
||||||
|
assert_eq!(rtp.sequence_number(), (i % u16::MAX as usize) as u16);
|
||||||
|
}
|
||||||
|
|
||||||
|
let end = clock.time();
|
||||||
|
|
||||||
|
let test_time = end.opt_sub(start);
|
||||||
|
let pull_time = end.opt_sub(pushed);
|
||||||
|
let push_time = pushed.opt_sub(start);
|
||||||
|
println!(
|
||||||
|
"test took {} (push {}, pull {})",
|
||||||
|
test_time.display(),
|
||||||
|
push_time.display(),
|
||||||
|
pull_time.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_send_list_benchmark() {
|
||||||
|
init();
|
||||||
|
let clock = gst::SystemClock::obtain();
|
||||||
|
const N_PACKETS: usize = 2 * 1024 * 1024;
|
||||||
|
let mut list = gst::BufferList::new();
|
||||||
|
let list_mut = list.make_mut();
|
||||||
|
for i in 0..N_PACKETS {
|
||||||
|
list_mut.add(
|
||||||
|
PacketInfo {
|
||||||
|
seq_no: (i % u16::MAX as usize) as u16,
|
||||||
|
rtp_ts: i as u32,
|
||||||
|
payload_len: 8,
|
||||||
|
}
|
||||||
|
.generate_buffer(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let mut h = send_init();
|
||||||
|
|
||||||
|
let push_pad = h
|
||||||
|
.element()
|
||||||
|
.unwrap()
|
||||||
|
.static_pad("rtp_sink_0")
|
||||||
|
.unwrap()
|
||||||
|
.peer()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let start = clock.time();
|
||||||
|
|
||||||
|
push_pad.push_list(list).unwrap();
|
||||||
|
|
||||||
|
let pushed = clock.time();
|
||||||
|
|
||||||
|
for i in 0..N_PACKETS {
|
||||||
|
let buffer = h.pull().unwrap();
|
||||||
|
let mapped = buffer.map_readable().unwrap();
|
||||||
|
let rtp = rtp_types::RtpPacket::parse(&mapped).unwrap();
|
||||||
|
assert_eq!(rtp.sequence_number(), (i % u16::MAX as usize) as u16);
|
||||||
|
}
|
||||||
|
|
||||||
|
let end = clock.time();
|
||||||
|
|
||||||
|
let test_time = end.opt_sub(start);
|
||||||
|
let pull_time = end.opt_sub(pushed);
|
||||||
|
let push_time = pushed.opt_sub(start);
|
||||||
|
println!(
|
||||||
|
"test took {} (push {}, pull {})",
|
||||||
|
test_time.display(),
|
||||||
|
push_time.display(),
|
||||||
|
pull_time.display()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Reference in a new issue