This commit is contained in:
Jasper Hugo 2022-03-09 13:35:00 +07:00 committed by Jasper
parent ea73eec7ee
commit 476bc21adf
6 changed files with 255 additions and 112 deletions

View File

@ -28,7 +28,7 @@ struct Opt {
#[structopt( #[structopt(
long, long,
help = "If not specified, assumed to be the host part of <web-socket-url>", help = "If not specified, assumed to be the host part of <web-socket-url>"
)] )]
xmpp_domain: Option<String>, xmpp_domain: Option<String>,
@ -37,20 +37,20 @@ struct Opt {
#[structopt( #[structopt(
long, long,
help = "If not specified, assumed to be conference.<xmpp-domain>", help = "If not specified, assumed to be conference.<xmpp-domain>"
)] )]
muc_domain: Option<String>, muc_domain: Option<String>,
#[structopt( #[structopt(
long, long,
help = "If not specified, assumed to be focus@auth.<xmpp-domain>/focus", help = "If not specified, assumed to be focus@auth.<xmpp-domain>/focus"
)] )]
focus_jid: Option<String>, focus_jid: Option<String>,
#[structopt( #[structopt(
long, long,
default_value = "vp9", default_value = "vp9",
help = "The video codec to negotiate support for. One of: vp9, vp8, h264", help = "The video codec to negotiate support for. One of: vp9, vp8, h264"
)] )]
video_codec: String, video_codec: String,
@ -77,13 +77,13 @@ struct Opt {
#[structopt( #[structopt(
long, long,
help = "Comma-separated endpoint IDs to select (prioritise receiving of)", help = "Comma-separated endpoint IDs to select (prioritise receiving of)"
)] )]
select_endpoints: Option<String>, select_endpoints: Option<String>,
#[structopt( #[structopt(
long, long,
help = "The maximum number of video streams we would like to receive", help = "The maximum number of video streams we would like to receive"
)] )]
last_n: Option<u16>, last_n: Option<u16>,
@ -103,21 +103,21 @@ struct Opt {
#[structopt( #[structopt(
long, long,
default_value = "1280", default_value = "1280",
help = "The width to scale received video to before passing it to the recv-pipeline.", help = "The width to scale received video to before passing it to the recv-pipeline."
)] )]
recv_video_scale_width: u16, recv_video_scale_width: u16,
#[structopt( #[structopt(
long, long,
default_value = "720", default_value = "720",
help = "The height to scale received video to before passing it to the recv-pipeline. This will also be signalled as the maximum height that JVB should send video to us at.", help = "The height to scale received video to before passing it to the recv-pipeline. This will also be signalled as the maximum height that JVB should send video to us at."
)] )]
recv_video_scale_height: u16, recv_video_scale_height: u16,
#[structopt( #[structopt(
long, long,
default_value = "200", default_value = "200",
help = "The size of the jitter buffers in milliseconds. Larger values are more resilient to packet loss and jitter, smaller values give lower latency.", help = "The size of the jitter buffers in milliseconds. Larger values are more resilient to packet loss and jitter, smaller values give lower latency."
)] )]
buffer_size: u32, buffer_size: u32,
@ -138,17 +138,11 @@ struct Opt {
tls_insecure: bool, tls_insecure: bool,
#[cfg(feature = "log-rtp")] #[cfg(feature = "log-rtp")]
#[structopt( #[structopt(long, help = "Log all RTP packets at DEBUG level (extremely verbose)")]
long,
help = "Log all RTP packets at DEBUG level (extremely verbose)"
)]
log_rtp: bool, log_rtp: bool,
#[cfg(feature = "log-rtp")] #[cfg(feature = "log-rtp")]
#[structopt( #[structopt(long, help = "Log all RTCP packets at DEBUG level")]
long,
help = "Log all RTCP packets at DEBUG level"
)]
log_rtcp: bool, log_rtcp: bool,
} }
@ -210,14 +204,14 @@ async fn main_inner() -> Result<()> {
.map(|pipeline| gstreamer::parse_bin_from_description(pipeline, false)) .map(|pipeline| gstreamer::parse_bin_from_description(pipeline, false))
.transpose() .transpose()
.context("failed to parse send pipeline")?; .context("failed to parse send pipeline")?;
let recv_pipeline = opt let recv_pipeline = opt
.recv_pipeline .recv_pipeline
.as_ref() .as_ref()
.map(|pipeline| gstreamer::parse_bin_from_description(pipeline, false)) .map(|pipeline| gstreamer::parse_bin_from_description(pipeline, false))
.transpose() .transpose()
.context("failed to parse recv pipeline")?; .context("failed to parse recv pipeline")?;
let web_socket_url: Uri = opt.web_socket_url.parse()?; let web_socket_url: Uri = opt.web_socket_url.parse()?;
let xmpp_domain = opt let xmpp_domain = opt
@ -297,8 +291,10 @@ async fn main_inner() -> Result<()> {
let conference = JitsiConference::join(connection, main_loop.context(), config) let conference = JitsiConference::join(connection, main_loop.context(), config)
.await .await
.context("failed to join conference")?; .context("failed to join conference")?;
conference.set_send_resolution(send_video_height.into()).await; conference
.set_send_resolution(send_video_height.into())
.await;
conference conference
.send_colibri_message(ColibriMessage::ReceiverVideoConstraints { .send_colibri_message(ColibriMessage::ReceiverVideoConstraints {
@ -357,13 +353,21 @@ async fn main_inner() -> Result<()> {
conference.add_bin(&bin).await?; conference.add_bin(&bin).await?;
if let Some(audio_element) = bin.by_name("audio") { if let Some(audio_element) = bin.by_name("audio") {
info!("recv pipeline has an audio element, a sink pad will be requested from it for each participant"); info!(
conference.set_remote_participant_audio_sink_element(Some(audio_element)).await; "recv pipeline has an audio element, a sink pad will be requested from it for each participant"
);
conference
.set_remote_participant_audio_sink_element(Some(audio_element))
.await;
} }
if let Some(video_element) = bin.by_name("video") { if let Some(video_element) = bin.by_name("video") {
info!("recv pipeline has a video element, a sink pad will be requested from it for each participant"); info!(
conference.set_remote_participant_video_sink_element(Some(video_element)).await; "recv pipeline has a video element, a sink pad will be requested from it for each participant"
);
conference
.set_remote_participant_video_sink_element(Some(video_element))
.await;
} }
} }

View File

@ -1,4 +1,6 @@
use std::{collections::HashMap, convert::TryFrom, fmt, future::Future, pin::Pin, sync::Arc, time::Duration}; use std::{
collections::HashMap, convert::TryFrom, fmt, future::Future, pin::Pin, sync::Arc, time::Duration,
};
use anyhow::{anyhow, bail, Context, Result}; use anyhow::{anyhow, bail, Context, Result};
use async_trait::async_trait; use async_trait::async_trait;
@ -10,7 +12,10 @@ use jitsi_xmpp_parsers::jingle::{Action, Jingle};
use maplit::hashmap; use maplit::hashmap;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use serde::Serialize; use serde::Serialize;
use tokio::{sync::{mpsc, oneshot, Mutex}, time}; use tokio::{
sync::{mpsc, oneshot, Mutex},
time,
};
use tokio_stream::wrappers::ReceiverStream; use tokio_stream::wrappers::ReceiverStream;
use tracing::{debug, error, info, trace, warn}; use tracing::{debug, error, info, trace, warn};
use uuid::Uuid; use uuid::Uuid;
@ -78,7 +83,7 @@ pub struct JitsiConferenceConfig {
pub region: Option<String>, pub region: Option<String>,
pub video_codec: String, pub video_codec: String,
pub extra_muc_features: Vec<String>, pub extra_muc_features: Vec<String>,
pub start_bitrate: u32, pub start_bitrate: u32,
pub stereo: bool, pub stereo: bool,
@ -377,10 +382,10 @@ impl JitsiConference {
} }
/// Set the max resolution that we are currently sending. /// Set the max resolution that we are currently sending.
/// ///
/// Setting this is required for browser clients in the same conference to display /// Setting this is required for browser clients in the same conference to display
/// the stats that we broadcast. /// the stats that we broadcast.
/// ///
/// Note that lib-gst-meet does not encode video (that is the responsibility of your /// Note that lib-gst-meet does not encode video (that is the responsibility of your
/// GStreamer pipeline), so this is purely informational. /// GStreamer pipeline), so this is purely informational.
pub async fn set_send_resolution(&self, height: i32) { pub async fn set_send_resolution(&self, height: i32) {
@ -658,18 +663,35 @@ impl StanzaFilter for JitsiConference {
jingle_session.stats_handler_task = Some(tokio::spawn(async move { jingle_session.stats_handler_task = Some(tokio::spawn(async move {
let mut interval = time::interval(SEND_STATS_INTERVAL); let mut interval = time::interval(SEND_STATS_INTERVAL);
loop { loop {
let maybe_remote_ssrc_map = self_.jingle_session.lock().await.as_ref().map(|sess| sess.remote_ssrc_map.clone()); let maybe_remote_ssrc_map = self_
.jingle_session
.lock()
.await
.as_ref()
.map(|sess| sess.remote_ssrc_map.clone());
let maybe_source_stats: Option<Vec<gstreamer::Structure>> = self_ let maybe_source_stats: Option<Vec<gstreamer::Structure>> = self_
.pipeline() .pipeline()
.await .await
.ok() .ok()
.and_then(|pipeline| pipeline.by_name("rtpbin")) .and_then(|pipeline| pipeline.by_name("rtpbin"))
.and_then(|rtpbin| rtpbin.try_emit_by_name("get-session", &[&0u32]).ok()) .and_then(|rtpbin| {
.and_then(|rtpsession: gstreamer::Element| rtpsession.try_property("stats").ok()) rtpbin.try_emit_by_name("get-session", &[&0u32]).ok()
})
.and_then(|rtpsession: gstreamer::Element| {
rtpsession.try_property("stats").ok()
})
.and_then(|stats: gstreamer::Structure| stats.get("source-stats").ok()) .and_then(|stats: gstreamer::Structure| stats.get("source-stats").ok())
.and_then(|stats: glib::ValueArray| stats.into_iter().map(|v| v.get()).collect::<Result<_, _>>().ok()); .and_then(|stats: glib::ValueArray| {
stats
if let (Some(remote_ssrc_map), Some(source_stats)) = (maybe_remote_ssrc_map, maybe_source_stats) { .into_iter()
.map(|v| v.get())
.collect::<Result<_, _>>()
.ok()
});
if let (Some(remote_ssrc_map), Some(source_stats)) =
(maybe_remote_ssrc_map, maybe_source_stats)
{
debug!("source stats: {:#?}", source_stats); debug!("source stats: {:#?}", source_stats);
let audio_recv_bitrate: u64 = source_stats let audio_recv_bitrate: u64 = source_stats
@ -679,7 +701,14 @@ impl StanzaFilter for JitsiConference {
.get("ssrc") .get("ssrc")
.ok() .ok()
.and_then(|ssrc: u32| remote_ssrc_map.get(&ssrc)) .and_then(|ssrc: u32| remote_ssrc_map.get(&ssrc))
.map(|source| source.media_type == MediaType::Audio && source.participant_id.as_ref().map(|id| id != &my_endpoint_id).unwrap_or_default()) .map(|source| {
source.media_type == MediaType::Audio
&& source
.participant_id
.as_ref()
.map(|id| id != &my_endpoint_id)
.unwrap_or_default()
})
.unwrap_or_default() .unwrap_or_default()
}) })
.filter_map(|stat| stat.get::<u64>("bitrate").ok()) .filter_map(|stat| stat.get::<u64>("bitrate").ok())
@ -692,7 +721,14 @@ impl StanzaFilter for JitsiConference {
.get("ssrc") .get("ssrc")
.ok() .ok()
.and_then(|ssrc: u32| remote_ssrc_map.get(&ssrc)) .and_then(|ssrc: u32| remote_ssrc_map.get(&ssrc))
.map(|source| source.media_type == MediaType::Video && source.participant_id.as_ref().map(|id| id != &my_endpoint_id).unwrap_or_default()) .map(|source| {
source.media_type == MediaType::Video
&& source
.participant_id
.as_ref()
.map(|id| id != &my_endpoint_id)
.unwrap_or_default()
})
.unwrap_or_default() .unwrap_or_default()
}) })
.filter_map(|stat| stat.get::<u64>("bitrate").ok()) .filter_map(|stat| stat.get::<u64>("bitrate").ok())
@ -705,7 +741,14 @@ impl StanzaFilter for JitsiConference {
.get("ssrc") .get("ssrc")
.ok() .ok()
.and_then(|ssrc: u32| remote_ssrc_map.get(&ssrc)) .and_then(|ssrc: u32| remote_ssrc_map.get(&ssrc))
.map(|source| source.media_type == MediaType::Audio && source.participant_id.as_ref().map(|id| id == &my_endpoint_id).unwrap_or_default()) .map(|source| {
source.media_type == MediaType::Audio
&& source
.participant_id
.as_ref()
.map(|id| id == &my_endpoint_id)
.unwrap_or_default()
})
.unwrap_or_default() .unwrap_or_default()
}) })
.and_then(|stat| stat.get("bitrate").ok()) .and_then(|stat| stat.get("bitrate").ok())
@ -717,7 +760,14 @@ impl StanzaFilter for JitsiConference {
.get("ssrc") .get("ssrc")
.ok() .ok()
.and_then(|ssrc: u32| remote_ssrc_map.get(&ssrc)) .and_then(|ssrc: u32| remote_ssrc_map.get(&ssrc))
.map(|source| source.media_type == MediaType::Video && source.participant_id.as_ref().map(|id| id == &my_endpoint_id).unwrap_or_default()) .map(|source| {
source.media_type == MediaType::Video
&& source
.participant_id
.as_ref()
.map(|id| id == &my_endpoint_id)
.unwrap_or_default()
})
.unwrap_or_default() .unwrap_or_default()
}) })
.and_then(|stat| stat.get("bitrate").ok()) .and_then(|stat| stat.get("bitrate").ok())
@ -730,7 +780,13 @@ impl StanzaFilter for JitsiConference {
.get("ssrc") .get("ssrc")
.ok() .ok()
.and_then(|ssrc: u32| remote_ssrc_map.get(&ssrc)) .and_then(|ssrc: u32| remote_ssrc_map.get(&ssrc))
.map(|source| source.participant_id.as_ref().map(|id| id != &my_endpoint_id).unwrap_or_default()) .map(|source| {
source
.participant_id
.as_ref()
.map(|id| id != &my_endpoint_id)
.unwrap_or_default()
})
.unwrap_or_default() .unwrap_or_default()
}) })
.filter_map(|stat| stat.get::<u64>("packets-received").ok()) .filter_map(|stat| stat.get::<u64>("packets-received").ok())
@ -750,7 +806,8 @@ impl StanzaFilter for JitsiConference {
// Loss can be negative because of duplicate packets. Clamp it to zero. // Loss can be negative because of duplicate packets. Clamp it to zero.
.try_into() .try_into()
.unwrap_or_default(); .unwrap_or_default();
let recv_loss = recv_lost as f64 / (recv_packets as f64 + recv_lost as f64); let recv_loss =
recv_lost as f64 / (recv_packets as f64 + recv_lost as f64);
let stats = ColibriMessage::EndpointStats { let stats = ColibriMessage::EndpointStats {
from: None, from: None,
@ -771,10 +828,10 @@ impl StanzaFilter for JitsiConference {
packet_loss: colibri::PacketLoss { packet_loss: colibri::PacketLoss {
total: (recv_loss * 100.) as u64, total: (recv_loss * 100.) as u64,
download: (recv_loss * 100.) as u64, download: (recv_loss * 100.) as u64,
upload: 0, // TODO upload: 0, // TODO
}, },
connection_quality: 100.0, connection_quality: 100.0,
jvb_rtt: Some(0), // TODO jvb_rtt: Some(0), // TODO
server_region: self_.config.region.clone(), server_region: self_.config.region.clone(),
max_enabled_resolution: self_.inner.lock().await.send_resolution, max_enabled_resolution: self_.inner.lock().await.send_resolution,
}; };
@ -786,7 +843,7 @@ impl StanzaFilter for JitsiConference {
warn!("unable to get stats from pipeline"); warn!("unable to get stats from pipeline");
} }
interval.tick().await; interval.tick().await;
} }
})); }));
} }
@ -797,14 +854,24 @@ impl StanzaFilter for JitsiConference {
while let Some(msg) = stream.next().await { while let Some(msg) = stream.next().await {
// Some message types are handled internally rather than passed to the on_colibri_message handler. // Some message types are handled internally rather than passed to the on_colibri_message handler.
let handled = match &msg { let handled = match &msg {
ColibriMessage::EndpointMessage { to: Some(to), from, msg_payload } if to == &my_endpoint_id => { ColibriMessage::EndpointMessage {
to: Some(to),
from,
msg_payload,
} if to == &my_endpoint_id => {
match serde_json::from_value::<JsonMessage>(msg_payload.clone()) { match serde_json::from_value::<JsonMessage>(msg_payload.clone()) {
Ok(JsonMessage::E2ePingRequest { id }) => { Ok(JsonMessage::E2ePingRequest { id }) => {
if let Err(e) = colibri_channel.send(ColibriMessage::EndpointMessage { if let Err(e) = colibri_channel
from: None, .send(ColibriMessage::EndpointMessage {
to: from.clone(), from: None,
msg_payload: serde_json::to_value(JsonMessage::E2ePingResponse { id }).unwrap(), to: from.clone(),
}).await { msg_payload: serde_json::to_value(
JsonMessage::E2ePingResponse { id },
)
.unwrap(),
})
.await
{
warn!("failed to send e2e ping response: {:?}", e); warn!("failed to send e2e ping response: {:?}", e);
} }
true true

View File

@ -3,10 +3,13 @@ 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::{Cast, ObjectExt, ToValue};
use gstreamer::{Bin, Element, GhostPad, prelude::{ElementExt, ElementExtManual, GObjectExtManualGst, GstBinExt, GstObjectExt, PadExt}}; use gstreamer::{
use gstreamer_rtp::{prelude::RTPHeaderExtensionExt, RTPHeaderExtension}; prelude::{ElementExt, ElementExtManual, GObjectExtManualGst, GstBinExt, GstObjectExt, PadExt},
Bin, Element, GhostPad,
};
#[cfg(feature = "log-rtp")] #[cfg(feature = "log-rtp")]
use gstreamer_rtp::RTPBuffer; use gstreamer_rtp::RTPBuffer;
use gstreamer_rtp::{prelude::RTPHeaderExtensionExt, RTPHeaderExtension};
use itertools::Itertools; use itertools::Itertools;
use jitsi_xmpp_parsers::{ use jitsi_xmpp_parsers::{
jingle::{Action, Content, Description, Jingle, Transport}, jingle::{Action, Content, Description, Jingle, Transport},
@ -594,8 +597,7 @@ impl JingleSession {
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 mut caps = gstreamer::Caps::builder("application/x-rtp") let mut caps = gstreamer::Caps::builder("application/x-rtp").field("payload", pt as i32);
.field("payload", pt as i32);
for codec in codecs.iter() { for codec in codecs.iter() {
if codec.is(pt) { if codec.is(pt) {
if codec.is_audio() { if codec.is_audio() {
@ -861,7 +863,7 @@ impl JingleSession {
pipeline pipeline
.add(&depayloader) .add(&depayloader)
.context("failed to add depayloader to pipeline")?; .context("failed to add depayloader to pipeline")?;
depayloader.sync_state_with_parent()?; depayloader.sync_state_with_parent()?;
debug!("created depayloader"); debug!("created depayloader");
rtpbin rtpbin
.link_pads(Some(&pad_name), &depayloader, None) .link_pads(Some(&pad_name), &depayloader, None)
@ -872,9 +874,13 @@ impl JingleSession {
debug!("rtpbin pads:\n{}", dump_pads(&rtpbin)); debug!("rtpbin pads:\n{}", dump_pads(&rtpbin));
let queue = gstreamer::ElementFactory::make("queue", None)?; let queue = gstreamer::ElementFactory::make("queue", None)?;
pipeline.add(&queue).context("failed to add queue to pipeline")?; pipeline
.add(&queue)
.context("failed to add queue to pipeline")?;
queue.sync_state_with_parent()?; queue.sync_state_with_parent()?;
depayloader.link(&queue).context("failed to link depayloader to queue")?; depayloader
.link(&queue)
.context("failed to link depayloader to queue")?;
let decoder = match source.media_type { let decoder = match source.media_type {
MediaType::Audio => { MediaType::Audio => {
@ -898,7 +904,10 @@ impl JingleSession {
if let Some(codec) = codec { if let Some(codec) = codec {
let decoder = gstreamer::ElementFactory::make(codec.decoder_name(), None)?; let decoder = gstreamer::ElementFactory::make(codec.decoder_name(), None)?;
decoder.set_property("automatic-request-sync-points", true); decoder.set_property("automatic-request-sync-points", true);
decoder.set_property_from_str("automatic-request-sync-point-flags", "GST_VIDEO_DECODER_REQUEST_SYNC_POINT_CORRUPT_OUTPUT"); decoder.set_property_from_str(
"automatic-request-sync-point-flags",
"GST_VIDEO_DECODER_REQUEST_SYNC_POINT_CORRUPT_OUTPUT",
);
decoder decoder
} }
else { else {
@ -907,9 +916,13 @@ impl JingleSession {
}, },
}; };
pipeline.add(&decoder).context("failed to add decoder to pipeline")?; pipeline
.add(&decoder)
.context("failed to add decoder to pipeline")?;
decoder.sync_state_with_parent()?; decoder.sync_state_with_parent()?;
queue.link(&decoder).context("failed to link queue to decoder")?; queue
.link(&decoder)
.context("failed to link queue to decoder")?;
let src_pad = match source.media_type { let src_pad = match source.media_type {
MediaType::Audio => decoder MediaType::Audio => decoder
@ -917,41 +930,81 @@ impl JingleSession {
.context("decoder has no src pad")?, .context("decoder has no src pad")?,
MediaType::Video => { MediaType::Video => {
let videoscale = gstreamer::ElementFactory::make("videoscale", None)?; let videoscale = gstreamer::ElementFactory::make("videoscale", None)?;
pipeline.add(&videoscale).context("failed to add videoscale to pipeline")?; pipeline
.add(&videoscale)
.context("failed to add videoscale to pipeline")?;
videoscale.sync_state_with_parent()?; videoscale.sync_state_with_parent()?;
decoder.link(&videoscale).context("failed to link decoder to videoscale")?; decoder
.link(&videoscale)
.context("failed to link decoder to videoscale")?;
let capsfilter = gstreamer::ElementFactory::make("capsfilter", None)?; let capsfilter = gstreamer::ElementFactory::make("capsfilter", None)?;
capsfilter.set_property_from_str("caps", &format!("video/x-raw, width={}, height={}", conference.config.recv_video_scale_width, conference.config.recv_video_scale_height)); capsfilter.set_property_from_str(
pipeline.add(&capsfilter).context("failed to add capsfilter to pipeline")?; "caps",
&format!(
"video/x-raw, width={}, height={}",
conference.config.recv_video_scale_width,
conference.config.recv_video_scale_height
),
);
pipeline
.add(&capsfilter)
.context("failed to add capsfilter to pipeline")?;
capsfilter.sync_state_with_parent()?; capsfilter.sync_state_with_parent()?;
videoscale.link(&capsfilter).context("failed to link videoscale to capsfilter")?; videoscale
.link(&capsfilter)
.context("failed to link videoscale to capsfilter")?;
let videoconvert = gstreamer::ElementFactory::make("videoconvert", None)?; let videoconvert = gstreamer::ElementFactory::make("videoconvert", None)?;
pipeline.add(&videoconvert).context("failed to add videoconvert to pipeline")?; pipeline
.add(&videoconvert)
.context("failed to add videoconvert to pipeline")?;
videoconvert.sync_state_with_parent()?; videoconvert.sync_state_with_parent()?;
capsfilter.link(&videoconvert).context("failed to link capsfilter to videoconvert")?; capsfilter
.link(&videoconvert)
.context("failed to link capsfilter to videoconvert")?;
videoconvert.static_pad("src").context("videoconvert has no src pad")? videoconvert
.static_pad("src")
.context("videoconvert has no src pad")?
}, },
}; };
if let Some(participant_id) = source.participant_id { if let Some(participant_id) = source.participant_id {
handle.block_on(conference.ensure_participant(&participant_id))?; handle.block_on(conference.ensure_participant(&participant_id))?;
let maybe_sink_element = match source.media_type { let maybe_sink_element = match source.media_type {
MediaType::Audio => handle.block_on(conference.remote_participant_audio_sink_element()), MediaType::Audio => {
MediaType::Video => handle.block_on(conference.remote_participant_video_sink_element()), handle.block_on(conference.remote_participant_audio_sink_element())
},
MediaType::Video => {
handle.block_on(conference.remote_participant_video_sink_element())
},
}; };
if let Some(sink_element) = maybe_sink_element { if let Some(sink_element) = maybe_sink_element {
let sink_pad = sink_element let sink_pad = sink_element
.request_pad_simple("sink_%u") .request_pad_simple("sink_%u")
.context("no suitable sink pad provided by sink element in recv pipeline")?; .context("no suitable sink pad provided by sink element in recv pipeline")?;
let ghost_pad = GhostPad::with_target(Some(&format!("participant_{}_{:?}", participant_id, source.media_type)), &sink_pad)?; let ghost_pad = GhostPad::with_target(
let bin: Bin = sink_element.parent().context("sink element has no parent")?.downcast().map_err(|_| anyhow!("sink element's parent is not a bin"))?; Some(&format!(
"participant_{}_{:?}",
participant_id, source.media_type
)),
&sink_pad,
)?;
let bin: Bin = sink_element
.parent()
.context("sink element has no parent")?
.downcast()
.map_err(|_| anyhow!("sink element's parent is not a bin"))?;
bin.add_pad(&ghost_pad)?; bin.add_pad(&ghost_pad)?;
src_pad.link(&ghost_pad).context("failed to link decode chain to participant bin from recv pipeline")?; src_pad
info!("linked {}/{:?} to new pad in recv pipeline", participant_id, source.media_type); .link(&ghost_pad)
.context("failed to link decode chain to participant bin from recv pipeline")?;
info!(
"linked {}/{:?} to new pad in recv pipeline",
participant_id, source.media_type
);
} }
else if let Some(participant_bin) = else if let Some(participant_bin) =
pipeline.by_name(&format!("participant_{}", participant_id)) pipeline.by_name(&format!("participant_{}", participant_id))
@ -961,8 +1014,13 @@ impl JingleSession {
MediaType::Video => "video", MediaType::Video => "video",
}; };
if let Some(sink_pad) = participant_bin.static_pad(sink_pad_name) { if let Some(sink_pad) = participant_bin.static_pad(sink_pad_name) {
src_pad.link(&sink_pad).context("failed to link decode chain to participant bin from recv participant pipeline")?; src_pad.link(&sink_pad).context(
info!("linked {}/{:?} to recv participant pipeline", participant_id, source.media_type); "failed to link decode chain to participant bin from recv participant pipeline",
)?;
info!(
"linked {}/{:?} to recv participant pipeline",
participant_id, source.media_type
);
} }
else { else {
warn!( warn!(
@ -984,7 +1042,9 @@ impl JingleSession {
let fakesink = gstreamer::ElementFactory::make("fakesink", None)?; let fakesink = gstreamer::ElementFactory::make("fakesink", None)?;
pipeline.add(&fakesink)?; pipeline.add(&fakesink)?;
fakesink.sync_state_with_parent()?; fakesink.sync_state_with_parent()?;
let sink_pad = fakesink.static_pad("sink").context("fakesink has no sink pad")?; let sink_pad = fakesink
.static_pad("sink")
.context("fakesink has no sink pad")?;
src_pad.link(&sink_pad)?; src_pad.link(&sink_pad)?;
} }
@ -1125,13 +1185,13 @@ impl JingleSession {
let buffer: gstreamer::Buffer = values[1].get()?; let buffer: gstreamer::Buffer = values[1].get()?;
let rtp_buffer = RTPBuffer::from_buffer_readable(&buffer)?; let rtp_buffer = RTPBuffer::from_buffer_readable(&buffer)?;
debug!( debug!(
ssrc=rtp_buffer.ssrc(), ssrc = rtp_buffer.ssrc(),
pt=rtp_buffer.payload_type(), pt = rtp_buffer.payload_type(),
seq=rtp_buffer.seq(), seq = rtp_buffer.seq(),
ts=rtp_buffer.timestamp(), ts = rtp_buffer.timestamp(),
marker=rtp_buffer.is_marker(), marker = rtp_buffer.is_marker(),
extension=rtp_buffer.is_extension(), extension = rtp_buffer.is_extension(),
payload_size=rtp_buffer.payload_size(), payload_size = rtp_buffer.payload_size(),
"RTP {}", "RTP {}",
direction, direction,
); );
@ -1156,14 +1216,11 @@ impl JingleSession {
let f = || { let f = || {
let buffer: gstreamer::Buffer = values[1].get()?; let buffer: gstreamer::Buffer = values[1].get()?;
let mut buf = [0u8; 1500]; let mut buf = [0u8; 1500];
buffer.copy_to_slice(0, &mut buf[..buffer.size()]).map_err(|_| anyhow!("invalid RTCP packet size"))?; buffer
.copy_to_slice(0, &mut buf[..buffer.size()])
.map_err(|_| anyhow!("invalid RTCP packet size"))?;
let decoded = rtcp::packet::unmarshal(&mut &buf[..buffer.size()])?; let decoded = rtcp::packet::unmarshal(&mut &buf[..buffer.size()])?;
debug!( debug!("RTCP {} size={}\n{:#?}", direction, buffer.size(), decoded,);
"RTCP {} size={}\n{:#?}",
direction,
buffer.size(),
decoded,
);
Ok::<_, anyhow::Error>(()) Ok::<_, anyhow::Error>(())
}; };
if let Err(e) = f() { if let Err(e) = f() {
@ -1177,7 +1234,7 @@ impl JingleSession {
rtcp_send_identity.connect("handoff", false, make_rtcp_logger("SEND")); rtcp_send_identity.connect("handoff", false, make_rtcp_logger("SEND"));
} }
} }
debug!("link dtlssrtpdec -> rtpbin"); debug!("link dtlssrtpdec -> rtpbin");
dtlssrtpdec.link_pads(Some("rtp_src"), &rtp_recv_identity, None)?; dtlssrtpdec.link_pads(Some("rtp_src"), &rtp_recv_identity, None)?;
rtp_recv_identity.link_pads(None, &rtpbin, Some("recv_rtp_sink_0"))?; rtp_recv_identity.link_pads(None, &rtpbin, Some("recv_rtp_sink_0"))?;
@ -1472,12 +1529,21 @@ fn dump_pads(element: &Element) -> String {
element element
.pads() .pads()
.into_iter() .into_iter()
.map(|pad| format!( .map(|pad| {
" {}, peer={}.{}, caps=\"{}\"", format!(
pad.name(), " {}, peer={}.{}, caps=\"{}\"",
pad.peer().and_then(|peer| peer.parent_element()).map(|element| element.name().to_string()).unwrap_or_default(), pad.name(),
pad.peer().map(|peer| peer.name().to_string()).unwrap_or_default(), pad
pad.caps().map(|caps| caps.to_string()).unwrap_or_default(), .peer()
)) .and_then(|peer| peer.parent_element())
.map(|element| element.name().to_string())
.unwrap_or_default(),
pad
.peer()
.map(|peer| peer.name().to_string())
.unwrap_or_default(),
pad.caps().map(|caps| caps.to_string()).unwrap_or_default(),
)
})
.join("\n") .join("\n")
} }

View File

@ -4,7 +4,7 @@ use anyhow::{anyhow, Result};
use async_trait::async_trait; use async_trait::async_trait;
use tokio::{sync::mpsc, task::JoinHandle, time}; use tokio::{sync::mpsc, task::JoinHandle, time};
use tracing::warn; use tracing::warn;
use xmpp_parsers::{iq::Iq, Element, FullJid, Jid, ping::Ping}; use xmpp_parsers::{iq::Iq, ping::Ping, Element, FullJid, Jid};
use crate::{stanza_filter::StanzaFilter, util::generate_id}; use crate::{stanza_filter::StanzaFilter, util::generate_id};
@ -29,11 +29,7 @@ impl Pinger {
} }
} }
}); });
Pinger { Pinger { jid, tx, ping_task }
jid,
tx,
ping_task,
}
} }
} }

View File

@ -12,8 +12,12 @@ use tokio_tungstenite::Connector;
#[cfg(feature = "tls-rustls-native-roots")] #[cfg(feature = "tls-rustls-native-roots")]
pub(crate) fn wss_connector(insecure: bool) -> Result<tokio_tungstenite::Connector> { pub(crate) fn wss_connector(insecure: bool) -> Result<tokio_tungstenite::Connector> {
let mut roots = rustls::RootCertStore::empty(); let mut roots = rustls::RootCertStore::empty();
for cert in rustls_native_certs::load_native_certs().context("failed to load native root certs")? { for cert in
roots.add(&rustls::Certificate(cert.0)).context("failed to add native root certs")?; rustls_native_certs::load_native_certs().context("failed to load native root certs")?
{
roots
.add(&rustls::Certificate(cert.0))
.context("failed to add native root certs")?;
} }
let mut config = rustls::ClientConfig::builder() let mut config = rustls::ClientConfig::builder()
@ -80,7 +84,11 @@ pub(crate) fn wss_connector(insecure: bool) -> Result<tokio_tungstenite::Connect
"Insecure TLS mode can only be enabled if the tls-insecure feature was enabled at compile time." "Insecure TLS mode can only be enabled if the tls-insecure feature was enabled at compile time."
) )
} }
Ok(Connector::NativeTls(builder.build().context("failed to build native TLS connector")?)) Ok(Connector::NativeTls(
builder
.build()
.context("failed to build native TLS connector")?,
))
} }
#[cfg(all( #[cfg(all(

View File

@ -269,7 +269,9 @@ impl Connection {
info!("My JID: {}", jid); info!("My JID: {}", jid);
locked_inner.jid = Some(jid.clone()); locked_inner.jid = Some(jid.clone());
locked_inner.stanza_filters.push(Box::new(Pinger::new(jid.clone(), tx.clone()))); locked_inner
.stanza_filters
.push(Box::new(Pinger::new(jid.clone(), tx.clone())));
let iq = Iq::from_get(generate_id(), DiscoInfoQuery { node: None }) let iq = Iq::from_get(generate_id(), DiscoInfoQuery { node: None })
.with_from(Jid::Full(jid.clone())) .with_from(Jid::Full(jid.clone()))