RTX support, TCC support (not working correctly yet)
This commit is contained in:
parent
35fb24071e
commit
d5842ee79f
|
@ -101,7 +101,9 @@ async fn main_inner() -> Result<()> {
|
||||||
|
|
||||||
init_gstreamer();
|
init_gstreamer();
|
||||||
|
|
||||||
let parsed_bin = opt
|
// Parse pipeline early so that we don't bother connecting to the conference if it's invalid.
|
||||||
|
|
||||||
|
let send_pipeline = opt
|
||||||
.send_pipeline
|
.send_pipeline
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|pipeline| gstreamer::parse_bin_from_description(pipeline, false))
|
.map(|pipeline| gstreamer::parse_bin_from_description(pipeline, false))
|
||||||
|
@ -176,7 +178,7 @@ async fn main_inner() -> Result<()> {
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(bin) = parsed_bin {
|
if let Some(bin) = send_pipeline {
|
||||||
conference.add_bin(&bin).await?;
|
conference.add_bin(&bin).await?;
|
||||||
|
|
||||||
if let Some(audio) = bin.by_name("audio") {
|
if let Some(audio) = bin.by_name("audio") {
|
||||||
|
|
|
@ -37,7 +37,7 @@ pub enum ColibriMessage {
|
||||||
packet_loss: PacketLoss,
|
packet_loss: PacketLoss,
|
||||||
connection_quality: f32,
|
connection_quality: f32,
|
||||||
#[serde(rename = "jvbRTT")]
|
#[serde(rename = "jvbRTT")]
|
||||||
jvb_rtt: u16,
|
jvb_rtt: Option<u16>,
|
||||||
server_region: String,
|
server_region: String,
|
||||||
max_enabled_resolution: u16,
|
max_enabled_resolution: u16,
|
||||||
},
|
},
|
||||||
|
@ -139,7 +139,7 @@ impl ColibriChannel {
|
||||||
while let Some(msg) = colibri_stream.try_next().await? {
|
while let Some(msg) = colibri_stream.try_next().await? {
|
||||||
match msg {
|
match msg {
|
||||||
Message::Text(text) => {
|
Message::Text(text) => {
|
||||||
debug!("colibri: {}", text);
|
debug!("Colibri <<< {}", text);
|
||||||
match serde_json::from_str::<ColibriMessage>(&text) {
|
match serde_json::from_str::<ColibriMessage>(&text) {
|
||||||
Ok(colibri_msg) => {
|
Ok(colibri_msg) => {
|
||||||
let mut txs = recv_tx.lock().await;
|
let mut txs = recv_tx.lock().await;
|
||||||
|
@ -173,6 +173,7 @@ impl ColibriChannel {
|
||||||
while let Some(colibri_msg) = stream.next().await {
|
while let Some(colibri_msg) = stream.next().await {
|
||||||
match serde_json::to_string(&colibri_msg) {
|
match serde_json::to_string(&colibri_msg) {
|
||||||
Ok(json) => {
|
Ok(json) => {
|
||||||
|
debug!("Colibri >>> {}", json);
|
||||||
let msg = Message::Text(json);
|
let msg = Message::Text(json);
|
||||||
colibri_sink.send(msg).await?;
|
colibri_sink.send(msg).await?;
|
||||||
},
|
},
|
||||||
|
|
|
@ -28,31 +28,33 @@ use crate::{
|
||||||
xmpp,
|
xmpp,
|
||||||
};
|
};
|
||||||
|
|
||||||
static DISCO_INFO: Lazy<DiscoInfoResult> = Lazy::new(|| DiscoInfoResult {
|
static DISCO_INFO: Lazy<DiscoInfoResult> = Lazy::new(|| {
|
||||||
node: None,
|
let mut features = vec![
|
||||||
identities: vec![],
|
|
||||||
features: vec![
|
|
||||||
Feature::new(ns::JINGLE_RTP_AUDIO),
|
Feature::new(ns::JINGLE_RTP_AUDIO),
|
||||||
Feature::new(ns::JINGLE_RTP_VIDEO),
|
Feature::new(ns::JINGLE_RTP_VIDEO),
|
||||||
Feature::new(ns::JINGLE_ICE_UDP),
|
Feature::new(ns::JINGLE_ICE_UDP),
|
||||||
Feature::new(ns::JINGLE_DTLS),
|
Feature::new(ns::JINGLE_DTLS),
|
||||||
// not supported yet: rtx
|
Feature::new("urn:ietf:rfc:5888"), // BUNDLE
|
||||||
// Feature::new("urn:ietf:rfc:4588"),
|
Feature::new("urn:ietf:rfc:5761"), // RTCP-MUX
|
||||||
|
Feature::new("urn:ietf:rfc:4588"), // RTX
|
||||||
|
|
||||||
// not supported yet: rtcp remb
|
];
|
||||||
// Feature::new("http://jitsi.org/remb"),
|
let gst_version = gstreamer::version();
|
||||||
|
if gst_version.0 >= 1 && gst_version.1 >= 19 {
|
||||||
// not supported yet: transport-cc
|
// RTP header extensions are supported on GStreamer 1.19+
|
||||||
// Feature::new("http://jitsi.org/tcc"),
|
features.push(Feature::new("http://jitsi.org/tcc"));
|
||||||
|
}
|
||||||
// rtcp-mux
|
else {
|
||||||
Feature::new("urn:ietf:rfc:5761"),
|
warn!("Upgrade GStreamer to 1.19 or later to enable RTP header extensions");
|
||||||
// rtp-bundle
|
}
|
||||||
Feature::new("urn:ietf:rfc:5888"),
|
// Not supported yet:
|
||||||
// opus red
|
// Feature::new("http://jitsi.org/opus-red")
|
||||||
Feature::new("http://jitsi.org/opus-red"),
|
DiscoInfoResult {
|
||||||
],
|
node: None,
|
||||||
extensions: vec![],
|
identities: vec![],
|
||||||
|
features,
|
||||||
|
extensions: vec![],
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::{collections::HashMap, fmt, net::SocketAddr};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Context, Result};
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
use glib::{Cast, ObjectExt, ToValue};
|
use glib::{ObjectExt, ToValue};
|
||||||
use gstreamer::prelude::{ElementExt, GObjectExtManualGst, GstBinExt, PadExt};
|
use gstreamer::prelude::{ElementExt, GObjectExtManualGst, GstBinExt, PadExt};
|
||||||
use nice_gst_meet as nice;
|
use nice_gst_meet as nice;
|
||||||
use pem::Pem;
|
use pem::Pem;
|
||||||
|
@ -23,6 +23,7 @@ use xmpp_parsers::{
|
||||||
jingle_dtls_srtp::{Fingerprint, Setup},
|
jingle_dtls_srtp::{Fingerprint, Setup},
|
||||||
jingle_ice_udp::{self, Transport as IceUdpTransport},
|
jingle_ice_udp::{self, Transport as IceUdpTransport},
|
||||||
jingle_rtp::{Description as RtpDescription, PayloadType, RtcpMux},
|
jingle_rtp::{Description as RtpDescription, PayloadType, RtcpMux},
|
||||||
|
jingle_rtp_hdrext::RtpHdrext,
|
||||||
jingle_ssma::{self, Parameter},
|
jingle_ssma::{self, Parameter},
|
||||||
Jid,
|
Jid,
|
||||||
};
|
};
|
||||||
|
@ -34,6 +35,14 @@ use crate::{
|
||||||
util::generate_id,
|
util::generate_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const RTP_HDREXT_SSRC_AUDIO_LEVEL: &str = "urn:ietf:params:rtp-hdrext:ssrc-audio-level";
|
||||||
|
const RTP_HDREXT_ABS_SEND_TIME: &str = "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time";
|
||||||
|
const RTP_HDREXT_TRANSPORT_CC: &str = "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01";
|
||||||
|
|
||||||
|
const RTX_PAYLOAD_TYPE_VP8: u8 = 96;
|
||||||
|
const RTX_PAYLOAD_TYPE_VP9: u8 = 97;
|
||||||
|
const RTX_PAYLOAD_TYPE_H264: u8 = 99;
|
||||||
|
|
||||||
const DEFAULT_STUN_PORT: u16 = 3478;
|
const DEFAULT_STUN_PORT: u16 = 3478;
|
||||||
const DEFAULT_TURNS_PORT: u16 = 5349;
|
const DEFAULT_TURNS_PORT: u16 = 5349;
|
||||||
|
|
||||||
|
@ -42,9 +51,7 @@ pub(crate) struct JingleSession {
|
||||||
audio_sink_element: gstreamer::Element,
|
audio_sink_element: gstreamer::Element,
|
||||||
video_sink_element: gstreamer::Element,
|
video_sink_element: gstreamer::Element,
|
||||||
remote_ssrc_map: HashMap<u32, Source>,
|
remote_ssrc_map: HashMap<u32, Source>,
|
||||||
ice_agent: nice::Agent,
|
_ice_agent: nice::Agent,
|
||||||
ice_stream_id: u32,
|
|
||||||
ice_component_id: u32,
|
|
||||||
pub(crate) accept_iq_id: Option<String>,
|
pub(crate) accept_iq_id: Option<String>,
|
||||||
pub(crate) colibri_url: Option<String>,
|
pub(crate) colibri_url: Option<String>,
|
||||||
pub(crate) colibri_channel: Option<ColibriChannel>,
|
pub(crate) colibri_channel: Option<ColibriChannel>,
|
||||||
|
@ -107,6 +114,10 @@ impl JingleSession {
|
||||||
let mut h264_payload_type = None;
|
let mut h264_payload_type = None;
|
||||||
let mut vp8_payload_type = None;
|
let mut vp8_payload_type = None;
|
||||||
let mut vp9_payload_type = None;
|
let mut vp9_payload_type = None;
|
||||||
|
let mut audio_hdrext_ssrc_audio_level = None;
|
||||||
|
let mut audio_hdrext_transport_cc = None;
|
||||||
|
let mut video_hdrext_abs_send_time = None;
|
||||||
|
let mut video_hdrext_transport_cc = None;
|
||||||
let mut colibri_url = None;
|
let mut colibri_url = None;
|
||||||
|
|
||||||
let mut remote_ssrc_map = HashMap::new();
|
let mut remote_ssrc_map = HashMap::new();
|
||||||
|
@ -119,6 +130,18 @@ impl JingleSession {
|
||||||
.iter()
|
.iter()
|
||||||
.find(|pt| pt.name.as_deref() == Some("opus"))
|
.find(|pt| pt.name.as_deref() == Some("opus"))
|
||||||
.map(|pt| pt.id);
|
.map(|pt| pt.id);
|
||||||
|
audio_hdrext_ssrc_audio_level = description
|
||||||
|
.hdrexts
|
||||||
|
.iter()
|
||||||
|
.find(|hdrext| hdrext.uri == RTP_HDREXT_SSRC_AUDIO_LEVEL)
|
||||||
|
.map(|hdrext| hdrext.id.parse::<u8>())
|
||||||
|
.transpose()?;
|
||||||
|
audio_hdrext_transport_cc = description
|
||||||
|
.hdrexts
|
||||||
|
.iter()
|
||||||
|
.find(|hdrext| hdrext.uri == RTP_HDREXT_TRANSPORT_CC)
|
||||||
|
.map(|hdrext| hdrext.id.parse::<u8>())
|
||||||
|
.transpose()?;
|
||||||
}
|
}
|
||||||
else if description.media == "video" {
|
else if description.media == "video" {
|
||||||
h264_payload_type = description
|
h264_payload_type = description
|
||||||
|
@ -136,6 +159,18 @@ impl JingleSession {
|
||||||
.iter()
|
.iter()
|
||||||
.find(|pt| pt.name.as_deref() == Some("VP9"))
|
.find(|pt| pt.name.as_deref() == Some("VP9"))
|
||||||
.map(|pt| pt.id);
|
.map(|pt| pt.id);
|
||||||
|
video_hdrext_abs_send_time = description
|
||||||
|
.hdrexts
|
||||||
|
.iter()
|
||||||
|
.find(|hdrext| hdrext.uri == RTP_HDREXT_ABS_SEND_TIME)
|
||||||
|
.map(|hdrext| hdrext.id.parse::<u8>())
|
||||||
|
.transpose()?;
|
||||||
|
video_hdrext_transport_cc = description
|
||||||
|
.hdrexts
|
||||||
|
.iter()
|
||||||
|
.find(|hdrext| hdrext.uri == RTP_HDREXT_TRANSPORT_CC)
|
||||||
|
.map(|hdrext| hdrext.id.parse::<u8>())
|
||||||
|
.transpose()?;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
continue;
|
continue;
|
||||||
|
@ -348,83 +383,111 @@ impl JingleSession {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let pipeline_spec = format!(
|
debug!("building gstreamer pipeline");
|
||||||
r#"
|
|
||||||
rtpbin rtp-profile=savpf name=rtpbin
|
|
||||||
|
|
||||||
nicesrc stream={0} component={1} name=nicesrc ! dtlssrtpdec name=dtlssrtpdec connection-id=gst-meet
|
let pipeline = gstreamer::Pipeline::new(None);
|
||||||
dtlssrtpenc name=dtlssrtpenc connection-id=gst-meet is-client=true ! nicesink stream={0} component={1} name=nicesink
|
|
||||||
|
|
||||||
rtpbin.send_rtp_src_0 ! dtlssrtpenc.rtp_sink_0
|
let rtpbin = gstreamer::ElementFactory::make("rtpbin", Some("rtpbin"))?;
|
||||||
rtpbin.send_rtcp_src_0 ! dtlssrtpenc.rtcp_sink_0
|
rtpbin.set_property_from_str("rtp-profile", "savpf");
|
||||||
rtpbin.send_rtp_src_1 ! dtlssrtpenc.rtp_sink_1
|
rtpbin.set_property("do-retransmission", true)?;
|
||||||
rtpbin.send_rtcp_src_1 ! dtlssrtpenc.rtcp_sink_1
|
rtpbin.set_property("autoremove", true)?;
|
||||||
|
pipeline.add(&rtpbin)?;
|
||||||
|
|
||||||
dtlssrtpdec.rtp_src ! rtpbin.recv_rtp_sink_0
|
let nicesrc = gstreamer::ElementFactory::make("nicesrc", None)?;
|
||||||
dtlssrtpdec.rtcp_src ! rtpbin.recv_rtcp_sink_0
|
nicesrc.set_property("stream", ice_stream_id)?;
|
||||||
"#,
|
nicesrc.set_property("component", ice_component_id)?;
|
||||||
ice_stream_id, ice_component_id,
|
nicesrc.set_property("agent", &ice_agent)?;
|
||||||
);
|
pipeline.add(&nicesrc)?;
|
||||||
|
|
||||||
debug!("building gstreamer pipeline:\n{}", pipeline_spec);
|
let nicesink = gstreamer::ElementFactory::make("nicesink", None)?;
|
||||||
|
nicesink.set_property("stream", ice_stream_id)?;
|
||||||
|
nicesink.set_property("component", ice_component_id)?;
|
||||||
|
nicesink.set_property("agent", &ice_agent)?;
|
||||||
|
pipeline.add(&nicesink)?;
|
||||||
|
|
||||||
let pipeline = gstreamer::parse_launch(&pipeline_spec)?
|
let dtls_srtp_connection_id = "gst-meet";
|
||||||
.downcast::<gstreamer::Pipeline>()
|
|
||||||
.map_err(|_| anyhow!("pipeline did not parse as a pipeline"))?;
|
|
||||||
|
|
||||||
let rtpbin = pipeline
|
let dtlssrtpdec = gstreamer::ElementFactory::make("dtlssrtpdec", None)?;
|
||||||
.by_name("rtpbin")
|
dtlssrtpdec.set_property("connection-id", dtls_srtp_connection_id)?;
|
||||||
.context("no rtpbin in pipeline")?;
|
dtlssrtpdec.set_property(
|
||||||
|
"pem",
|
||||||
|
format!("{}\n{}", dtls_cert_pem, dtls_private_key_pem),
|
||||||
|
)?;
|
||||||
|
pipeline.add(&dtlssrtpdec)?;
|
||||||
|
|
||||||
|
let dtlssrtpenc = gstreamer::ElementFactory::make("dtlssrtpenc", None)?;
|
||||||
|
dtlssrtpenc.set_property("connection-id", dtls_srtp_connection_id)?;
|
||||||
|
dtlssrtpenc.set_property("is-client", true)?;
|
||||||
|
pipeline.add(&dtlssrtpenc)?;
|
||||||
|
|
||||||
rtpbin.connect("request-pt-map", false, move |values| {
|
rtpbin.connect("request-pt-map", false, move |values| {
|
||||||
let f = || {
|
let f = || {
|
||||||
debug!("rtpbin request-pt-map {:?}", values);
|
debug!("rtpbin request-pt-map {:?}", values);
|
||||||
let pt = values[2].get::<u32>()? as u8;
|
let pt = values[2].get::<u32>()? as u8;
|
||||||
let maybe_caps = if Some(pt) == opus_payload_type {
|
let mut caps = gstreamer::Caps::builder("application/x-rtp");
|
||||||
Some(gstreamer::Caps::new_simple(
|
if Some(pt) == opus_payload_type {
|
||||||
"application/x-rtp",
|
caps = caps
|
||||||
&[
|
.field("media", "audio")
|
||||||
("media", &"audio"),
|
.field("encoding-name", "OPUS")
|
||||||
("encoding-name", &"OPUS"),
|
.field("clock-rate", 48000);
|
||||||
("clock-rate", &48000),
|
if let Some(hdrext) = audio_hdrext_ssrc_audio_level {
|
||||||
],
|
caps = caps.field(&format!("extmap-{}", hdrext), RTP_HDREXT_SSRC_AUDIO_LEVEL);
|
||||||
))
|
}
|
||||||
|
if let Some(hdrext) = audio_hdrext_transport_cc {
|
||||||
|
caps = caps.field(&format!("extmap-{}", hdrext), &RTP_HDREXT_TRANSPORT_CC);
|
||||||
|
}
|
||||||
|
Ok::<_, anyhow::Error>(Some(caps.build()))
|
||||||
}
|
}
|
||||||
else if Some(pt) == h264_payload_type {
|
else if Some(pt) == h264_payload_type || Some(pt) == vp8_payload_type || Some(pt) == vp9_payload_type {
|
||||||
Some(gstreamer::Caps::new_simple(
|
caps = caps
|
||||||
"application/x-rtp",
|
.field("media", "video")
|
||||||
&[
|
.field("clock-rate", 90000)
|
||||||
("media", &"video"),
|
.field("encoding-name", if Some(pt) == h264_payload_type {
|
||||||
("encoding-name", &"H264"),
|
"H264"
|
||||||
("clock-rate", &90000),
|
}
|
||||||
],
|
else if Some(pt) == vp8_payload_type {
|
||||||
))
|
"VP8"
|
||||||
|
}
|
||||||
|
else if Some(pt) == vp9_payload_type {
|
||||||
|
"VP9"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
});
|
||||||
|
if let Some(hdrext) = video_hdrext_abs_send_time {
|
||||||
|
caps = caps.field(&format!("extmap-{}", hdrext), &RTP_HDREXT_ABS_SEND_TIME);
|
||||||
|
}
|
||||||
|
if let Some(hdrext) = video_hdrext_transport_cc {
|
||||||
|
caps = caps.field(&format!("extmap-{}", hdrext), &RTP_HDREXT_TRANSPORT_CC);
|
||||||
|
}
|
||||||
|
Ok(Some(caps.build()))
|
||||||
}
|
}
|
||||||
else if Some(pt) == vp8_payload_type {
|
else if pt == RTX_PAYLOAD_TYPE_VP8 || pt == RTX_PAYLOAD_TYPE_VP9 || pt == RTX_PAYLOAD_TYPE_H264 {
|
||||||
Some(gstreamer::Caps::new_simple(
|
caps = caps
|
||||||
"application/x-rtp",
|
.field("media", "video")
|
||||||
&[
|
.field("clock-rate", 90000)
|
||||||
("media", &"video"),
|
.field("encoding-name", "RTX")
|
||||||
("encoding-name", &"VP8"),
|
.field("apt", if pt == RTX_PAYLOAD_TYPE_VP8 {
|
||||||
("clock-rate", &90000),
|
vp8_payload_type
|
||||||
],
|
.context("missing VP8 payload type")?
|
||||||
))
|
}
|
||||||
}
|
else if pt == RTX_PAYLOAD_TYPE_VP9 {
|
||||||
else if Some(pt) == vp9_payload_type {
|
vp9_payload_type
|
||||||
Some(gstreamer::Caps::new_simple(
|
.context("missing VP9 payload type")?
|
||||||
"application/x-rtp",
|
}
|
||||||
&[
|
else if pt == RTX_PAYLOAD_TYPE_H264 {
|
||||||
("media", &"video"),
|
h264_payload_type
|
||||||
("encoding-name", &"VP9"),
|
.context("missing H264 payload type")?
|
||||||
("clock-rate", &90000),
|
}
|
||||||
],
|
else {
|
||||||
))
|
unreachable!()
|
||||||
|
});
|
||||||
|
Ok(Some(caps.build()))
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
warn!("unknown payload type: {}", pt);
|
warn!("unknown payload type: {}", pt);
|
||||||
None
|
Ok(None)
|
||||||
};
|
}
|
||||||
Ok::<_, anyhow::Error>(maybe_caps)
|
|
||||||
};
|
};
|
||||||
match f() {
|
match f() {
|
||||||
Ok(Some(caps)) => {
|
Ok(Some(caps)) => {
|
||||||
|
@ -439,6 +502,94 @@ impl JingleSession {
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
rtpbin.connect("request-aux-sender", false, move |values| {
|
||||||
|
let f = move || {
|
||||||
|
let session: u32 = values[1].get()?;
|
||||||
|
debug!("creating RTX sender for session {}", session);
|
||||||
|
let mut pt_map = gstreamer::Structure::builder("application/x-rtp-pt-map");
|
||||||
|
if let Some(pt) = vp8_payload_type {
|
||||||
|
pt_map = pt_map.field(&pt.to_string(), &(RTX_PAYLOAD_TYPE_VP8 as u32));
|
||||||
|
}
|
||||||
|
if let Some(pt) = vp9_payload_type {
|
||||||
|
pt_map = pt_map.field(&pt.to_string(), &(RTX_PAYLOAD_TYPE_VP9 as u32));
|
||||||
|
}
|
||||||
|
if let Some(pt) = h264_payload_type {
|
||||||
|
pt_map = pt_map.field(&pt.to_string(), &(RTX_PAYLOAD_TYPE_H264 as u32));
|
||||||
|
}
|
||||||
|
let bin = gstreamer::Bin::new(None);
|
||||||
|
let rtx_sender = gstreamer::ElementFactory::make("rtprtxsend", None)?;
|
||||||
|
rtx_sender.set_property("payload-type-map", pt_map.build())?;
|
||||||
|
bin.add(&rtx_sender)?;
|
||||||
|
bin.add_pad(
|
||||||
|
&gstreamer::GhostPad::with_target(
|
||||||
|
Some(&format!("src_{}", session)),
|
||||||
|
&rtx_sender
|
||||||
|
.static_pad("src")
|
||||||
|
.context("rtprtxsend has no src pad")?,
|
||||||
|
)?,
|
||||||
|
)?;
|
||||||
|
bin.add_pad(
|
||||||
|
&gstreamer::GhostPad::with_target(
|
||||||
|
Some(&format!("sink_{}", session)),
|
||||||
|
&rtx_sender
|
||||||
|
.static_pad("sink")
|
||||||
|
.context("rtprtxsend has no sink pad")?,
|
||||||
|
)?,
|
||||||
|
)?;
|
||||||
|
Ok::<_, anyhow::Error>(Some(bin.to_value()))
|
||||||
|
};
|
||||||
|
match f() {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(e) => {
|
||||||
|
warn!("request-aux-sender: {:?}", e);
|
||||||
|
None
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
rtpbin.connect("request-aux-receiver", false, move |values| {
|
||||||
|
let f = move || {
|
||||||
|
let session: u32 = values[1].get()?;
|
||||||
|
debug!("creating RTX receiver for session {}", session);
|
||||||
|
let mut pt_map = gstreamer::Structure::builder("application/x-rtp-pt-map");
|
||||||
|
if let Some(pt) = vp8_payload_type {
|
||||||
|
pt_map = pt_map.field(&pt.to_string(), RTX_PAYLOAD_TYPE_VP8 as u32);
|
||||||
|
}
|
||||||
|
if let Some(pt) = vp9_payload_type {
|
||||||
|
pt_map = pt_map.field(&pt.to_string(), RTX_PAYLOAD_TYPE_VP9 as u32);
|
||||||
|
}
|
||||||
|
if let Some(pt) = h264_payload_type {
|
||||||
|
pt_map = pt_map.field(&pt.to_string(), RTX_PAYLOAD_TYPE_H264 as u32);
|
||||||
|
}
|
||||||
|
let bin = gstreamer::Bin::new(None);
|
||||||
|
let rtx_receiver = gstreamer::ElementFactory::make("rtprtxreceive", None)?;
|
||||||
|
rtx_receiver.set_property("payload-type-map", pt_map.build())?;
|
||||||
|
bin.add(&rtx_receiver)?;
|
||||||
|
bin.add_pad(
|
||||||
|
&gstreamer::GhostPad::with_target(
|
||||||
|
Some(&format!("src_{}", session)),
|
||||||
|
&rtx_receiver.static_pad("src")
|
||||||
|
.context("rtprtxreceive has no src pad")?,
|
||||||
|
)?,
|
||||||
|
)?;
|
||||||
|
bin.add_pad(
|
||||||
|
&gstreamer::GhostPad::with_target(
|
||||||
|
Some(&format!("sink_{}", session)),
|
||||||
|
&rtx_receiver.static_pad("sink")
|
||||||
|
.context("rtprtxreceive has no sink pad")?,
|
||||||
|
)?,
|
||||||
|
)?;
|
||||||
|
Ok::<_, anyhow::Error>(Some(bin.to_value()))
|
||||||
|
};
|
||||||
|
match f() {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(e) => {
|
||||||
|
warn!("request-aux-receiver: {:?}", e);
|
||||||
|
None
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
let handle = Handle::current();
|
let handle = Handle::current();
|
||||||
let inner_ = conference.inner.clone();
|
let inner_ = conference.inner.clone();
|
||||||
let pipeline_ = pipeline.clone();
|
let pipeline_ = pipeline.clone();
|
||||||
|
@ -499,6 +650,12 @@ impl JingleSession {
|
||||||
};
|
};
|
||||||
|
|
||||||
let source_element = gstreamer::ElementFactory::make(element_name, None)?;
|
let source_element = gstreamer::ElementFactory::make(element_name, None)?;
|
||||||
|
if source_element.has_property("auto-header-extension", None) {
|
||||||
|
source_element.set_property("auto-header-extension", true)?;
|
||||||
|
}
|
||||||
|
if source_element.has_property("request-keyframe", None) {
|
||||||
|
source_element.set_property("request-keyframe", true)?;
|
||||||
|
}
|
||||||
pipeline_
|
pipeline_
|
||||||
.add(&source_element)
|
.add(&source_element)
|
||||||
.context(format!("failed to add {} to pipeline", element_name))?;
|
.context(format!("failed to add {} to pipeline", element_name))?;
|
||||||
|
@ -571,8 +728,14 @@ impl JingleSession {
|
||||||
)?;
|
)?;
|
||||||
audio_sink_element.set_property("min-ptime", 10i64 * 1000 * 1000)?;
|
audio_sink_element.set_property("min-ptime", 10i64 * 1000 * 1000)?;
|
||||||
audio_sink_element.set_property("ssrc", audio_ssrc)?;
|
audio_sink_element.set_property("ssrc", audio_ssrc)?;
|
||||||
|
let audio_hdrext_supported = if audio_sink_element.has_property("auto-header-extension", None) {
|
||||||
|
audio_sink_element.set_property("auto-header-extension", true)?;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
false
|
||||||
|
};
|
||||||
pipeline.add(&audio_sink_element)?;
|
pipeline.add(&audio_sink_element)?;
|
||||||
audio_sink_element.link_pads(None, &rtpbin, Some("send_rtp_sink_0"))?;
|
|
||||||
|
|
||||||
let video_sink_element = match conference.config.video_codec.as_str() {
|
let video_sink_element = match conference.config.video_codec.as_str() {
|
||||||
"h264" => {
|
"h264" => {
|
||||||
|
@ -605,26 +768,59 @@ impl JingleSession {
|
||||||
other => bail!("unsupported video codec: {}", other),
|
other => bail!("unsupported video codec: {}", other),
|
||||||
};
|
};
|
||||||
video_sink_element.set_property("ssrc", video_ssrc)?;
|
video_sink_element.set_property("ssrc", video_ssrc)?;
|
||||||
|
let video_hdrext_supported = if video_sink_element.has_property("auto-header-extension", None) {
|
||||||
|
video_sink_element.set_property("auto-header-extension", true)?;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
false
|
||||||
|
};
|
||||||
pipeline.add(&video_sink_element)?;
|
pipeline.add(&video_sink_element)?;
|
||||||
video_sink_element.link_pads(None, &rtpbin, Some("send_rtp_sink_1"))?;
|
|
||||||
|
|
||||||
let dtlssrtpdec = pipeline
|
let mut audio_caps = gstreamer::Caps::builder("application/x-rtp");
|
||||||
.by_name("dtlssrtpdec")
|
// TODO: fails to negotiate
|
||||||
.context("no dtlssrtpdec in pipeline")?;
|
// if let Some(hdrext) = audio_hdrext_ssrc_audio_level {
|
||||||
dtlssrtpdec.set_property(
|
// audio_caps = audio_caps.field(&format!("extmap-{}", hdrext), RTP_HDREXT_SSRC_AUDIO_LEVEL);
|
||||||
"pem",
|
// }
|
||||||
format!("{}\n{}", dtls_cert_pem, dtls_private_key_pem),
|
if let Some(hdrext) = audio_hdrext_transport_cc {
|
||||||
)?;
|
audio_caps = audio_caps.field(&format!("extmap-{}", hdrext), RTP_HDREXT_TRANSPORT_CC);
|
||||||
|
}
|
||||||
|
let audio_capsfilter = gstreamer::ElementFactory::make("capsfilter", None)?;
|
||||||
|
audio_capsfilter.set_property("caps", audio_caps.build())?;
|
||||||
|
pipeline.add(&audio_capsfilter)?;
|
||||||
|
|
||||||
let nicesrc = pipeline
|
let mut video_caps = gstreamer::Caps::builder("application/x-rtp");
|
||||||
.by_name("nicesrc")
|
if let Some(hdrext) = video_hdrext_abs_send_time {
|
||||||
.context("no nicesrc in pipeline")?;
|
video_caps = video_caps.field(&format!("extmap-{}", hdrext), &RTP_HDREXT_ABS_SEND_TIME);
|
||||||
nicesrc.set_property("agent", &ice_agent)?;
|
}
|
||||||
|
if let Some(hdrext) = video_hdrext_transport_cc {
|
||||||
|
video_caps = video_caps.field(&format!("extmap-{}", hdrext), &RTP_HDREXT_TRANSPORT_CC);
|
||||||
|
}
|
||||||
|
let video_capsfilter = gstreamer::ElementFactory::make("capsfilter", None)?;
|
||||||
|
video_capsfilter.set_property("caps", video_caps.build())?;
|
||||||
|
pipeline.add(&video_capsfilter)?;
|
||||||
|
|
||||||
let nicesink = pipeline
|
debug!("linking audio payloader -> rtpbin");
|
||||||
.by_name("nicesink")
|
audio_sink_element.link(&audio_capsfilter)?;
|
||||||
.context("no nicesink in pipeline")?;
|
audio_capsfilter.link_pads(None, &rtpbin, Some("send_rtp_sink_0"))?;
|
||||||
nicesink.set_property("agent", &ice_agent)?;
|
|
||||||
|
debug!("linking video payloader -> rtpbin");
|
||||||
|
video_sink_element.link(&video_capsfilter)?;
|
||||||
|
video_capsfilter.link_pads(None, &rtpbin, Some("send_rtp_sink_1"))?;
|
||||||
|
|
||||||
|
debug!("linking ICE <-> DTLS-SRTP");
|
||||||
|
nicesrc.link(&dtlssrtpdec)?;
|
||||||
|
dtlssrtpenc.link(&nicesink)?;
|
||||||
|
|
||||||
|
debug!("linking rtpbin -> DTLS-SRTP encoder");
|
||||||
|
rtpbin.link_pads(Some("send_rtp_src_0"), &dtlssrtpenc, Some("rtp_sink_0"))?;
|
||||||
|
rtpbin.link_pads(Some("send_rtcp_src_0"), &dtlssrtpenc, Some("rtcp_sink_0"))?;
|
||||||
|
rtpbin.link_pads(Some("send_rtp_src_1"), &dtlssrtpenc, Some("rtp_sink_1"))?;
|
||||||
|
rtpbin.link_pads(Some("send_rtcp_src_1"), &dtlssrtpenc, Some("rtcp_sink_1"))?;
|
||||||
|
|
||||||
|
debug!("linking DTLS-SRTP decoder -> rtpbin");
|
||||||
|
dtlssrtpdec.link_pads(Some("rtp_src"), &rtpbin, Some("recv_rtp_sink_0"))?;
|
||||||
|
dtlssrtpdec.link_pads(Some("rtcp_src"), &rtpbin, Some("recv_rtcp_sink_0"))?;
|
||||||
|
|
||||||
let bus = pipeline.bus().context("failed to get pipeline bus")?;
|
let bus = pipeline.bus().context("failed to get pipeline bus")?;
|
||||||
|
|
||||||
|
@ -716,29 +912,69 @@ impl JingleSession {
|
||||||
let label = Uuid::new_v4().to_string();
|
let label = Uuid::new_v4().to_string();
|
||||||
let cname = Uuid::new_v4().to_string();
|
let cname = Uuid::new_v4().to_string();
|
||||||
|
|
||||||
let mut ssrc = jingle_ssma::Source::new(if initiate_content.name.0 == "audio" {
|
description.ssrcs = if initiate_content.name.0 == "audio" {
|
||||||
audio_ssrc.to_string()
|
vec![jingle_ssma::Source::new(audio_ssrc.to_string())]
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
video_ssrc.to_string()
|
vec![jingle_ssma::Source::new(video_ssrc.to_string())]
|
||||||
});
|
};
|
||||||
ssrc.parameters.push(Parameter {
|
|
||||||
name: "cname".to_owned(),
|
for ssrc in description.ssrcs.iter_mut() {
|
||||||
value: Some(cname),
|
ssrc.parameters.push(Parameter {
|
||||||
});
|
name: "cname".to_owned(),
|
||||||
ssrc.parameters.push(Parameter {
|
value: Some(cname.clone()),
|
||||||
name: "msid".to_owned(),
|
});
|
||||||
value: Some(format!("{} {}", mslabel, label)),
|
ssrc.parameters.push(Parameter {
|
||||||
});
|
name: "msid".to_owned(),
|
||||||
ssrc.parameters.push(Parameter {
|
value: Some(format!("{} {}", mslabel, label)),
|
||||||
name: "mslabel".to_owned(),
|
});
|
||||||
value: Some(mslabel),
|
ssrc.parameters.push(Parameter {
|
||||||
});
|
name: "mslabel".to_owned(),
|
||||||
ssrc.parameters.push(Parameter {
|
value: Some(mslabel.clone()),
|
||||||
name: "label".to_owned(),
|
});
|
||||||
value: Some(label),
|
ssrc.parameters.push(Parameter {
|
||||||
});
|
name: "label".to_owned(),
|
||||||
description.ssrcs = vec![ssrc];
|
value: Some(label.clone()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if initiate_content.name.0 == "audio" {
|
||||||
|
// TODO: fails to negotiate
|
||||||
|
// if let Some(hdrext) = audio_hdrext_ssrc_audio_level {
|
||||||
|
// if audio_hdrext_supported {
|
||||||
|
// description.hdrexts.push(RtpHdrext::new(hdrext.to_string(), RTP_HDREXT_SSRC_AUDIO_LEVEL.to_owned()));
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// debug!("ssrc-audio-level hdrext requested but not supported");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
if let Some(hdrext) = audio_hdrext_transport_cc {
|
||||||
|
if audio_hdrext_supported {
|
||||||
|
description.hdrexts.push(RtpHdrext::new(hdrext.to_string(), RTP_HDREXT_TRANSPORT_CC.to_owned()));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
debug!("transport-cc hdrext requested, but hdrext not supported by audio payloader");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if initiate_content.name.0 == "video" {
|
||||||
|
if let Some(hdrext) = video_hdrext_abs_send_time {
|
||||||
|
if video_hdrext_supported {
|
||||||
|
description.hdrexts.push(RtpHdrext::new(hdrext.to_string(), RTP_HDREXT_ABS_SEND_TIME.to_owned()));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
debug!("abs-send-time hdrext requested, but hdrext not supported by video payloader");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(hdrext) = video_hdrext_transport_cc {
|
||||||
|
if video_hdrext_supported {
|
||||||
|
description.hdrexts.push(RtpHdrext::new(hdrext.to_string(), RTP_HDREXT_TRANSPORT_CC.to_owned()));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
debug!("transport-cc hdrext requested, but hdrext not supported by video payloader");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut transport = IceUdpTransport::new().with_fingerprint(Fingerprint {
|
let mut transport = IceUdpTransport::new().with_fingerprint(Fingerprint {
|
||||||
hash: Algo::Sha_256,
|
hash: Algo::Sha_256,
|
||||||
|
@ -793,9 +1029,7 @@ impl JingleSession {
|
||||||
audio_sink_element,
|
audio_sink_element,
|
||||||
video_sink_element,
|
video_sink_element,
|
||||||
remote_ssrc_map,
|
remote_ssrc_map,
|
||||||
ice_agent,
|
_ice_agent: ice_agent,
|
||||||
ice_stream_id,
|
|
||||||
ice_component_id,
|
|
||||||
accept_iq_id: Some(accept_iq_id),
|
accept_iq_id: Some(accept_iq_id),
|
||||||
colibri_url,
|
colibri_url,
|
||||||
colibri_channel: None,
|
colibri_channel: None,
|
||||||
|
|
58
shell.nix
58
shell.nix
|
@ -1,4 +1,52 @@
|
||||||
with import <nixpkgs> {};
|
with import <nixpkgs> {};
|
||||||
|
let
|
||||||
|
gstreamer = gst_all_1.gstreamer.overrideAttrs(old: rec {
|
||||||
|
version = "1.19.9999";
|
||||||
|
src = fetchGit {
|
||||||
|
url = "https://gitlab.freedesktop.org/gstreamer/gstreamer.git";
|
||||||
|
rev = "637b0d8dc25b660d3b05370e60a95249a5228a39";
|
||||||
|
};
|
||||||
|
patches = [];
|
||||||
|
});
|
||||||
|
gst-plugins-base = (gst_all_1.gst-plugins-base.override {
|
||||||
|
gstreamer = gstreamer;
|
||||||
|
}).overrideAttrs(old: rec {
|
||||||
|
version = "1.19.9999";
|
||||||
|
src = fetchGit {
|
||||||
|
url = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-base.git";
|
||||||
|
rev = "f5a79ce05f62ad98134435955ed3d10d22f17cb9";
|
||||||
|
};
|
||||||
|
patches = [];
|
||||||
|
});
|
||||||
|
gst-plugins-good = (gst_all_1.gst-plugins-good.override {
|
||||||
|
gst-plugins-base = gst-plugins-base;
|
||||||
|
}).overrideAttrs(old: rec {
|
||||||
|
version = "1.19.9999";
|
||||||
|
src = fetchGit {
|
||||||
|
url = "https://gitlab.freedesktop.org/hgr/gst-plugins-good.git";
|
||||||
|
ref = "hgr/twcc-fixes";
|
||||||
|
rev = "3cff164ef4fab1a74ecfe5fd247edb723c9a41a1";
|
||||||
|
};
|
||||||
|
patches = [];
|
||||||
|
});
|
||||||
|
gst-plugins-bad = (gst_all_1.gst-plugins-bad.override {
|
||||||
|
gst-plugins-base = gst-plugins-base;
|
||||||
|
}).overrideAttrs(old: rec {
|
||||||
|
version = "1.19.9999";
|
||||||
|
src = fetchGit {
|
||||||
|
url = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad.git";
|
||||||
|
rev = "4eb22b769559ef2696a78a03b30de215bd677d47";
|
||||||
|
};
|
||||||
|
patches = [];
|
||||||
|
mesonFlags = old.mesonFlags ++ ["-Dgs=disabled" "-Disac=disabled" "-Dldac=disabled" "-Donnx=disabled" "-Dopenaptx=disabled" "-Dqroverlay=disabled"];
|
||||||
|
});
|
||||||
|
libnice-patched = libnice.override {
|
||||||
|
gst_all_1 = {
|
||||||
|
gstreamer = gstreamer;
|
||||||
|
gst-plugins-base = gst-plugins-base;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
mkShell {
|
mkShell {
|
||||||
name = "gst-meet";
|
name = "gst-meet";
|
||||||
buildInputs = [
|
buildInputs = [
|
||||||
|
@ -6,11 +54,11 @@ mkShell {
|
||||||
pkg-config
|
pkg-config
|
||||||
glib
|
glib
|
||||||
glib-networking
|
glib-networking
|
||||||
gst_all_1.gstreamer
|
gstreamer
|
||||||
gst_all_1.gst-plugins-base
|
gst-plugins-base
|
||||||
gst_all_1.gst-plugins-good
|
gst-plugins-good
|
||||||
gst_all_1.gst-plugins-bad
|
gst-plugins-bad
|
||||||
libnice
|
libnice-patched
|
||||||
] ++ (if stdenv.isDarwin then [
|
] ++ (if stdenv.isDarwin then [
|
||||||
darwin.apple_sdk.frameworks.AppKit
|
darwin.apple_sdk.frameworks.AppKit
|
||||||
darwin.apple_sdk.frameworks.Security
|
darwin.apple_sdk.frameworks.Security
|
||||||
|
|
Loading…
Reference in New Issue