mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2025-06-08 00:18:56 +00:00
rtp: opus: fix payloader caps query handling and add tests
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1571>
This commit is contained in:
parent
61523baa7b
commit
10e0294d5a
2 changed files with 165 additions and 5 deletions
|
@ -369,22 +369,28 @@ impl RtpBasePay2Impl for RtpOpusPay {
|
||||||
|
|
||||||
if s.get::<i32>("channel-mapping-family") == Ok(0) {
|
if s.get::<i32>("channel-mapping-family") == Ok(0) {
|
||||||
let peer_s = peer_caps.structure(0).unwrap();
|
let peer_s = peer_caps.structure(0).unwrap();
|
||||||
let pref_chans = peer_s.get::<&str>("sprop-stereo")
|
|
||||||
|
gst::trace!(CAT, imp: self, "Peer preference structure: {peer_s}");
|
||||||
|
|
||||||
|
let pref_chans = peer_s.get::<&str>("stereo")
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|params| params.trim().parse::<i32>().ok())
|
.and_then(|params| params.trim().parse::<i32>().ok())
|
||||||
.map(|v| match v {
|
.map(|v| match v {
|
||||||
0 => 1, // mono
|
0 => 1, // mono
|
||||||
1 => 2, // stereo
|
1 => 2, // stereo
|
||||||
_ => {
|
_ => {
|
||||||
gst::warning!(CAT, imp: self, "Unexpected sprop-stereo value {v} in input caps {s}");
|
gst::warning!(CAT, imp: self, "Unexpected stereo value {v} in peer caps {s}");
|
||||||
2 // default is stereo
|
2 // default is stereo
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(pref_chans) = pref_chans {
|
if let Some(pref_chans) = pref_chans {
|
||||||
let mut pref_s = peer_s.to_owned();
|
gst::trace!(CAT, imp: self, "Peer preference: channels={pref_chans}");
|
||||||
pref_s.set("channels", pref_chans);
|
|
||||||
let mut pref_caps = gst::Caps::builder_full().structure(pref_s).build();
|
let mut pref_caps = gst::Caps::builder("audio/x-opus")
|
||||||
|
.field("channel-mapping-family", 0i32)
|
||||||
|
.field("channels", pref_chans)
|
||||||
|
.build();
|
||||||
pref_caps.merge(ret_caps);
|
pref_caps.merge(ret_caps);
|
||||||
ret_caps = pref_caps;
|
ret_caps = pref_caps;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use crate::tests::{run_test_pipeline, ExpectedBuffer, ExpectedPacket, Source};
|
use crate::tests::{run_test_pipeline, ExpectedBuffer, ExpectedPacket, Source};
|
||||||
|
use gst::prelude::*;
|
||||||
use gst_check::Harness;
|
use gst_check::Harness;
|
||||||
|
|
||||||
fn init() {
|
fn init() {
|
||||||
|
@ -276,3 +277,156 @@ fn test_opus_depay_pay() {
|
||||||
|
|
||||||
let _output_buffer = h.pull().expect("Didn't get output buffer");
|
let _output_buffer = h.pull().expect("Didn't get output buffer");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// test_opus_payloader_get_caps
|
||||||
|
//
|
||||||
|
// Check that a caps query on payloader sink pad reflects downstream RTP caps requirements.
|
||||||
|
//
|
||||||
|
#[test]
|
||||||
|
fn test_opus_payloader_get_caps() {
|
||||||
|
init();
|
||||||
|
|
||||||
|
fn get_allowed_opus_caps_for_rtp_caps_string(recv_caps_str: &str) -> gst::Caps {
|
||||||
|
let src = gst::ElementFactory::make("appsrc").build().unwrap();
|
||||||
|
let pay = gst::ElementFactory::make("rtpopuspay2").build().unwrap();
|
||||||
|
let sink = gst::ElementFactory::make("appsink").build().unwrap();
|
||||||
|
|
||||||
|
sink.set_property_from_str("caps", recv_caps_str);
|
||||||
|
|
||||||
|
gst::Element::link_many([&src, &pay, &sink]).unwrap();
|
||||||
|
|
||||||
|
let pad = src.static_pad("src").unwrap();
|
||||||
|
|
||||||
|
pad.allowed_caps().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_allowed_opus_caps_for_rtp_caps(flavour: &str, recv_props: &str) -> gst::Caps {
|
||||||
|
get_allowed_opus_caps_for_rtp_caps_string(&format!(
|
||||||
|
"application/x-rtp, encoding-name={flavour}, {recv_props}"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
let stereo_caps = gst::Caps::builder("audio/x-opus")
|
||||||
|
.field("channels", 2i32)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let mono_caps = gst::Caps::builder("audio/x-opus")
|
||||||
|
.field("channels", 1i32)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// "stereo" is just a hint from receiver that sender can use to avoid unnecessary processing,
|
||||||
|
// but it doesn't signal a hard requirement. So both mono and stereo should always be allowed
|
||||||
|
// as input, but the channel preference should be relayed in the first caps structure.
|
||||||
|
{
|
||||||
|
let allowed_opus_caps = get_allowed_opus_caps_for_rtp_caps("OPUS", "stereo=(string)0");
|
||||||
|
|
||||||
|
eprintln!("OPUS stereo=0 => {:?}", allowed_opus_caps);
|
||||||
|
|
||||||
|
assert_eq!(allowed_opus_caps.size(), 2);
|
||||||
|
|
||||||
|
// First caps structure should have channels=1 for receiver preference stereo=0
|
||||||
|
let s = allowed_opus_caps.structure(0).unwrap();
|
||||||
|
assert_eq!(s.get::<i32>("channels"), Ok(1));
|
||||||
|
|
||||||
|
// ... but stereo should still be allowed
|
||||||
|
assert!(allowed_opus_caps.can_intersect(&stereo_caps));
|
||||||
|
|
||||||
|
// Make sure it's channel-mapping-family=0
|
||||||
|
for i in 0..2 {
|
||||||
|
let s = allowed_opus_caps.structure(i).unwrap();
|
||||||
|
assert!(s.has_name("audio/x-opus"));
|
||||||
|
assert_eq!(s.get::<i32>("channel-mapping-family"), Ok(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let allowed_opus_caps = get_allowed_opus_caps_for_rtp_caps("OPUS", "stereo=(string)1");
|
||||||
|
|
||||||
|
eprintln!("OPUS stereo=1 => {:?}", allowed_opus_caps);
|
||||||
|
|
||||||
|
assert_eq!(allowed_opus_caps.size(), 2);
|
||||||
|
|
||||||
|
// First caps structure should have channels=2 for receiver preference stereo=1
|
||||||
|
let s = allowed_opus_caps.structure(0).unwrap();
|
||||||
|
assert_eq!(s.get::<i32>("channels"), Ok(2));
|
||||||
|
|
||||||
|
// ... but mono should still be allowed
|
||||||
|
assert!(allowed_opus_caps.can_intersect(&mono_caps));
|
||||||
|
|
||||||
|
// Make sure it's channel-mapping-family=0
|
||||||
|
for i in 0..2 {
|
||||||
|
let s = allowed_opus_caps.structure(i).unwrap();
|
||||||
|
assert!(s.has_name("audio/x-opus"));
|
||||||
|
assert_eq!(s.get::<i32>("channel-mapping-family"), Ok(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure that with MULTIOPUS flavour the stereo hint is ignored entirely
|
||||||
|
for stereo_str in &[
|
||||||
|
&"stereo=(string)0",
|
||||||
|
&"stereo=(string)1",
|
||||||
|
&"description=none", // no stereo attribute present
|
||||||
|
] {
|
||||||
|
let allowed_opus_caps = get_allowed_opus_caps_for_rtp_caps("MULTIOPUS", stereo_str);
|
||||||
|
|
||||||
|
eprintln!("MULTIOPUS {stereo_str} => {allowed_opus_caps:?}");
|
||||||
|
|
||||||
|
assert_eq!(allowed_opus_caps.size(), 1);
|
||||||
|
|
||||||
|
// Neither mono nor stereo should be allowed for MULTIOPUS
|
||||||
|
let mono_stereo_caps = gst::Caps::builder("audio/x-opus")
|
||||||
|
.field("channels", gst::IntRange::new(1, 2))
|
||||||
|
.build();
|
||||||
|
assert!(!allowed_opus_caps.can_intersect(&mono_stereo_caps));
|
||||||
|
|
||||||
|
// Make sure it's channel-mapping-family=1
|
||||||
|
let s = allowed_opus_caps.structure(0).unwrap();
|
||||||
|
assert!(s.has_name("audio/x-opus"));
|
||||||
|
assert_eq!(s.get::<i32>("channel-mapping-family"), Ok(1));
|
||||||
|
assert_eq!(
|
||||||
|
s.get::<gst::IntRange::<i32>>("channels"),
|
||||||
|
Ok(gst::IntRange::new(3, 255))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If receiver supports both OPUS and MULTIOPUS ..
|
||||||
|
{
|
||||||
|
let allowed_opus_caps = get_allowed_opus_caps_for_rtp_caps_string(
|
||||||
|
"\
|
||||||
|
application/x-rtp, encoding-name=OPUS, stereo=(string)0; \
|
||||||
|
application/x-rtp, encoding-name=MULTIOPUS",
|
||||||
|
);
|
||||||
|
|
||||||
|
eprintln!("OPUS,stereo=0; MULTIOPUS => {allowed_opus_caps:?}");
|
||||||
|
|
||||||
|
// Mono should be in there of course
|
||||||
|
assert!(allowed_opus_caps.can_intersect(&mono_caps));
|
||||||
|
|
||||||
|
// Make sure mono is the first structure to indicate preference as per receiver caps
|
||||||
|
let s = allowed_opus_caps.structure(0).unwrap();
|
||||||
|
assert_eq!(s.get::<i32>("channels"), Ok(1));
|
||||||
|
|
||||||
|
// Stereo should still be allowed, since stereo=0 is just a hint
|
||||||
|
assert!(allowed_opus_caps.can_intersect(&stereo_caps));
|
||||||
|
|
||||||
|
// Multichannel of course
|
||||||
|
let multich_caps = gst::Caps::builder("audio/x-opus")
|
||||||
|
.field("channel-mapping-family", 1i32)
|
||||||
|
.field("channels", gst::IntRange::new(3, 255))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert!(allowed_opus_caps.can_intersect(&multich_caps));
|
||||||
|
|
||||||
|
// Make sure it's channel-mapping-family=0 in the first two structs
|
||||||
|
for i in 0..3 {
|
||||||
|
let s = allowed_opus_caps.structure(i).unwrap();
|
||||||
|
assert!(s.has_name("audio/x-opus"));
|
||||||
|
assert_eq!(
|
||||||
|
s.get::<i32>("channel-mapping-family"),
|
||||||
|
if i < 2 { Ok(0) } else { Ok(1) }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not testing MULTIOPUS, OPUS preference order, doesn't seem very interesting in practice.
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue