webrtc: Unify the Codec structure between sink and source

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1194>
This commit is contained in:
Thibault Saunier 2023-03-07 10:17:03 -03:00 committed by Mathieu Duponchelle
parent cf32d9d668
commit 059cdecf7d
3 changed files with 504 additions and 317 deletions

View file

@ -1,6 +1,20 @@
use std::collections::HashMap;
use std::{
collections::{BTreeMap, HashMap},
ops::Deref,
sync::atomic::{AtomicBool, Ordering},
};
use anyhow::{Context, Error};
use gst::{glib, prelude::*};
use once_cell::sync::Lazy;
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
gst::DebugCategory::new(
"webrtcutils",
gst::DebugColorFlags::empty(),
Some("WebRTC Utils"),
)
});
pub fn gvalue_to_json(val: &gst::glib::Value) -> Option<serde_json::Value> {
match val.type_() {
@ -98,3 +112,421 @@ pub fn make_element(element: &str, name: Option<&str>) -> Result<gst::Element, E
.build()
.with_context(|| format!("Failed to make element {element}"))
}
#[derive(Debug)]
struct DecodingInfo {
has_decoder: AtomicBool,
decoders: Vec<gst::ElementFactory>,
}
impl Clone for DecodingInfo {
fn clone(&self) -> Self {
Self {
has_decoder: AtomicBool::new(self.has_decoder.load(Ordering::SeqCst)),
decoders: self.decoders.clone(),
}
}
}
#[derive(Clone, Debug)]
struct EncodingInfo {
encoder: gst::ElementFactory,
payloader: gst::ElementFactory,
output_filter: Option<gst::Caps>,
}
#[derive(Clone, Debug)]
pub struct Codec {
pub name: String,
pub caps: gst::Caps,
pub stream_type: gst::StreamType,
payload_type: Option<i32>,
decoding_info: Option<DecodingInfo>,
encoding_info: Option<EncodingInfo>,
}
impl Codec {
pub fn new(
name: &str,
stream_type: gst::StreamType,
caps: &gst::Caps,
decoders: &glib::List<gst::ElementFactory>,
encoders: &glib::List<gst::ElementFactory>,
payloaders: &glib::List<gst::ElementFactory>,
) -> Self {
let has_decoder = Self::has_decoder_for_caps(caps, decoders);
let encoder = Self::get_encoder_for_caps(caps, encoders);
let payloader = Self::get_payloader_for_codec(name, payloaders);
let encoding_info = if encoder.is_some() && payloader.is_some() {
Some(EncodingInfo {
encoder: encoder.unwrap(),
payloader: payloader.unwrap(),
output_filter: None,
})
} else {
None
};
Self {
caps: caps.clone(),
stream_type,
name: name.into(),
payload_type: None,
decoding_info: Some(DecodingInfo {
has_decoder: AtomicBool::new(has_decoder),
decoders: decoders.iter().cloned().collect(),
}),
encoding_info,
}
}
pub fn can_encode(&self) -> bool {
self.encoding_info.is_some()
}
pub fn set_pt(&mut self, pt: i32) {
self.payload_type = Some(pt);
}
pub fn new_decoding(
name: &str,
stream_type: gst::StreamType,
caps: &gst::Caps,
decoders: &glib::List<gst::ElementFactory>,
) -> Self {
let has_decoder = Self::has_decoder_for_caps(caps, decoders);
Self {
caps: caps.clone(),
stream_type,
name: name.into(),
payload_type: None,
decoding_info: Some(DecodingInfo {
has_decoder: AtomicBool::new(has_decoder),
decoders: decoders.iter().cloned().collect(),
}),
encoding_info: None,
}
}
pub fn has_decoder(&self) -> bool {
if self.decoding_info.is_none() {
return false;
}
let decoder_info = self.decoding_info.as_ref().unwrap();
if decoder_info.has_decoder.load(Ordering::SeqCst) {
true
} else if Self::has_decoder_for_caps(
&self.caps,
// Replicating decodebin logic
&gst::ElementFactory::factories_with_type(
gst::ElementFactoryType::DECODER,
gst::Rank::Marginal,
),
) {
// Check if new decoders have been installed meanwhile
decoder_info.has_decoder.store(true, Ordering::SeqCst);
true
} else {
false
}
}
fn get_encoder_for_caps(
caps: &gst::Caps,
decoders: &glib::List<gst::ElementFactory>,
) -> Option<gst::ElementFactory> {
decoders
.iter()
.find(|factory| {
factory.static_pad_templates().iter().any(|template| {
let template_caps = template.caps();
template.direction() == gst::PadDirection::Src
&& !template_caps.is_any()
&& caps.can_intersect(&template_caps)
})
})
.cloned()
}
fn get_payloader_for_codec(
codec: &str,
payloaders: &glib::List<gst::ElementFactory>,
) -> Option<gst::ElementFactory> {
payloaders
.iter()
.find(|factory| {
factory.static_pad_templates().iter().any(|template| {
let template_caps = template.caps();
if template.direction() != gst::PadDirection::Src || template_caps.is_any() {
return false;
}
template_caps.iter().any(|s| {
s.has_field("encoding-name")
&& s.get::<gst::List>("encoding-name").map_or_else(
|_| {
if let Ok(encoding_name) = s.get::<&str>("encoding-name") {
encoding_name == codec
} else {
false
}
},
|encoding_names| {
encoding_names.iter().any(|v| {
v.get::<&str>()
.map_or(false, |encoding_name| encoding_name == codec)
})
},
)
})
})
})
.cloned()
}
fn has_decoder_for_caps(caps: &gst::Caps, decoders: &glib::List<gst::ElementFactory>) -> bool {
decoders.iter().any(|factory| {
factory.static_pad_templates().iter().any(|template| {
let template_caps = template.caps();
template.direction() == gst::PadDirection::Sink
&& !template_caps.is_any()
&& caps.can_intersect(&template_caps)
})
})
}
pub fn is_video(&self) -> bool {
matches!(self.stream_type, gst::StreamType::VIDEO)
}
pub fn payload(&self) -> Option<i32> {
self.payload_type
}
pub fn build_encoder(&self) -> Option<Result<gst::Element, Error>> {
self.encoding_info.as_ref().map(|info| {
info.encoder.create().build().with_context(|| {
format!(
"Creating payloader {}",
self.encoding_info.as_ref().unwrap().encoder.name()
)
})
})
}
pub fn build_payloader(&self) -> Option<Result<gst::Element, Error>> {
self.encoding_info.as_ref().map(|info| {
info.payloader.create().build().with_context(|| {
format!(
"Creating payloader {}",
self.encoding_info.as_ref().unwrap().payloader.name()
)
})
})
}
pub fn encoder_name(&self) -> Option<String> {
self.encoding_info
.as_ref()
.map(|info| info.encoder.name().to_string())
}
pub fn set_output_filter(&mut self, caps: gst::Caps) {
self.encoding_info
.as_mut()
.map(|info| info.output_filter = Some(caps));
}
pub fn output_filter(&self) -> Option<gst::Caps> {
self.encoding_info
.as_ref()
.map(|info| info.output_filter.clone())
.flatten()
}
}
pub static AUDIO_CAPS: Lazy<gst::Caps> = Lazy::new(|| gst::Caps::new_empty_simple("audio/x-raw"));
pub static OPUS_CAPS: Lazy<gst::Caps> = Lazy::new(|| gst::Caps::new_empty_simple("audio/x-opus"));
pub static VIDEO_CAPS: Lazy<gst::Caps> = Lazy::new(|| {
gst::Caps::builder_full_with_any_features()
.structure(gst::Structure::new_empty("video/x-raw"))
.build()
});
pub static VP8_CAPS: Lazy<gst::Caps> = Lazy::new(|| gst::Caps::new_empty_simple("video/x-vp8"));
pub static VP9_CAPS: Lazy<gst::Caps> = Lazy::new(|| gst::Caps::new_empty_simple("video/x-vp9"));
pub static H264_CAPS: Lazy<gst::Caps> = Lazy::new(|| gst::Caps::new_empty_simple("video/x-h264"));
pub static H265_CAPS: Lazy<gst::Caps> = Lazy::new(|| gst::Caps::new_empty_simple("video/x-h265"));
pub static RTP_CAPS: Lazy<gst::Caps> =
Lazy::new(|| gst::Caps::new_empty_simple("application/x-rtp"));
#[derive(Debug, Clone)]
pub struct Codecs(Vec<Codec>);
impl Deref for Codecs {
type Target = Vec<Codec>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Codecs {
pub fn to_map(self) -> BTreeMap<i32, Codec> {
self.0
.into_iter()
.map(|codec| (codec.payload().unwrap(), codec))
.collect()
}
}
static CODECS: Lazy<Codecs> = Lazy::new(|| {
let decoders = gst::ElementFactory::factories_with_type(
gst::ElementFactoryType::DECODER,
gst::Rank::Marginal,
);
let encoders = gst::ElementFactory::factories_with_type(
gst::ElementFactoryType::ENCODER,
gst::Rank::Marginal,
);
let payloaders = gst::ElementFactory::factories_with_type(
gst::ElementFactoryType::PAYLOADER,
gst::Rank::Marginal,
);
Codecs(vec![
Codec::new(
"OPUS",
gst::StreamType::AUDIO,
&OPUS_CAPS,
&decoders,
&encoders,
&payloaders,
),
Codec::new(
"VP8",
gst::StreamType::VIDEO,
&VP8_CAPS,
&decoders,
&encoders,
&payloaders,
),
Codec::new(
"H264",
gst::StreamType::VIDEO,
&H264_CAPS,
&decoders,
&encoders,
&payloaders,
),
Codec::new(
"VP9",
gst::StreamType::VIDEO,
&VP9_CAPS,
&decoders,
&encoders,
&payloaders,
),
Codec::new(
"H265",
gst::StreamType::VIDEO,
&H265_CAPS,
&decoders,
&encoders,
&payloaders,
),
])
});
impl Codecs {
pub fn find(encoding_name: &str) -> Option<Codec> {
CODECS
.iter()
.find(|codec| codec.name == encoding_name)
.map(|codec| codec.clone())
}
pub fn video_codecs() -> Vec<Codec> {
CODECS
.iter()
.filter(|codec| codec.stream_type == gst::StreamType::VIDEO)
.map(|codec| codec.clone())
.collect()
}
pub fn audio_codecs() -> Vec<Codec> {
CODECS
.iter()
.filter(|codec| codec.stream_type == gst::StreamType::AUDIO)
.map(|codec| codec.clone())
.collect()
}
pub fn video_codec_names() -> Vec<String> {
CODECS
.iter()
.filter(|codec| codec.stream_type == gst::StreamType::VIDEO)
.map(|codec| codec.name.clone())
.collect()
}
pub fn audio_codec_names() -> Vec<String> {
CODECS
.iter()
.filter(|codec| codec.stream_type == gst::StreamType::AUDIO)
.map(|codec| codec.name.clone())
.collect()
}
/// List all codecs that can be used for encoding the given caps and assign
/// a payload type to each of them. This is useful to initiate SDP negotiation.
pub fn list_encoders<'a>(caps: impl IntoIterator<Item = &'a gst::StructureRef>) -> Codecs {
let mut payload = 96..128;
Codecs(
caps.into_iter()
.filter_map(move |s| {
let caps = gst::Caps::builder_full().structure(s.to_owned()).build();
CODECS
.iter()
.find(|codec| {
codec
.encoding_info
.as_ref()
.map_or(false, |_| codec.caps.can_intersect(&caps))
})
.and_then(|codec| {
/* Assign a payload type to the codec */
if let Some(pt) = payload.next() {
let mut codec = codec.clone();
codec.payload_type = Some(pt);
Some(codec)
} else {
gst::warning!(
CAT,
"Too many formats for available payload type range, ignoring {}",
s
);
None
}
})
})
.collect()
)
}
}

View file

@ -1,5 +1,6 @@
// SPDX-License-Identifier: MPL-2.0
use crate::utils::*;
use anyhow::Context;
use gst::glib;
use gst::prelude::*;
@ -81,23 +82,6 @@ struct Settings {
signaller: Signallable,
}
/// Represents a codec we can offer
#[derive(Debug, Clone)]
struct Codec {
encoder: gst::ElementFactory,
payloader: gst::ElementFactory,
caps: gst::Caps,
payload: i32,
output_filter: Option<gst::Caps>,
}
impl Codec {
fn is_video(&self) -> bool {
self.encoder
.has_type(gst::ElementFactoryType::VIDEO_ENCODER)
}
}
/// Wrapper around our sink pads
#[derive(Debug, Clone)]
struct InputStream {
@ -281,13 +265,17 @@ impl Default for Settings {
let signaller = Signaller::new(WebRTCSignallerRole::Producer);
Self {
video_caps: ["video/x-vp8", "video/x-h264", "video/x-vp9", "video/x-h265"]
video_caps: Codecs::video_codecs()
.into_iter()
.map(|codec| codec.caps.iter().map(|s| s.to_owned()).collect::<Vec<_>>())
.flatten()
.into_iter()
.map(gst::Structure::new_empty)
.collect::<gst::Caps>(),
audio_caps: ["audio/x-opus"]
audio_caps: Codecs::audio_codecs()
.into_iter()
.map(|codec| codec.caps.iter().map(|s| s.to_owned()).collect::<Vec<_>>())
.flatten()
.into_iter()
.map(gst::Structure::new_empty)
.collect::<gst::Caps>(),
stun_server: DEFAULT_STUN_SERVER.map(String::from),
turn_servers: gst::Array::new(Vec::new() as Vec<glib::SendValue>),
@ -497,20 +485,16 @@ fn setup_encoding(
let conv_filter = make_element("capsfilter", None)?;
let enc = codec
.encoder
.create()
.build()
.with_context(|| format!("Creating encoder {}", codec.encoder.name()))?;
.build_encoder()
.expect("Encoders should always have been set in the CodecInfo we handle")?;
let parse_filter = make_element("capsfilter", None)?;
let pay = codec
.payloader
.create()
.build()
.with_context(|| format!("Creating payloader {}", codec.payloader.name()))?;
.build_payloader()
.expect("Payloaders should always have been set in the CodecInfo we handle")?;
let pay_filter = make_element("capsfilter", None)?;
pay.set_property("mtu", 1200_u32);
pay.set_property("pt", codec.payload as u32);
pay.set_property("pt", codec.payload().unwrap() as u32);
if let Some(ssrc) = ssrc {
pay.set_property("ssrc", ssrc);
@ -555,7 +539,7 @@ fn setup_encoding(
let mut structure_builder = gst::Structure::builder("video/x-raw")
.field("pixel-aspect-ratio", gst::Fraction::new(1, 1));
if codec.encoder.name() == "nvh264enc" {
if codec.encoder_name().unwrap().as_str() == "nvh264enc" {
// Quirk: nvh264enc can perform conversion from RGB formats, but
// doesn't advertise / negotiate colorimetry correctly, leading
// to incorrect color display in Chrome (but interestingly not in
@ -572,7 +556,7 @@ fn setup_encoding(
gst::Caps::builder("audio/x-raw").build()
};
match codec.encoder.name().as_str() {
match codec.encoder_name().unwrap().as_str() {
"vp8enc" | "vp9enc" => {
pay.set_property_from_str("picture-id-mode", "15-bit");
}
@ -931,7 +915,7 @@ impl Session {
self.pipeline.add(&pay_filter).unwrap();
let output_caps = codec
.output_filter
.output_filter()
.clone()
.unwrap_or_else(gst::Caps::new_any);
@ -1211,20 +1195,8 @@ impl BaseWebRTCSink {
let mut payloader_caps = match media {
Some(media) => {
let encoders = gst::ElementFactory::factories_with_type(
gst::ElementFactoryType::ENCODER,
gst::Rank::Marginal,
);
let payloaders = gst::ElementFactory::factories_with_type(
gst::ElementFactoryType::PAYLOADER,
gst::Rank::Marginal,
);
let codec = BaseWebRTCSink::select_codec(
element,
&encoders,
&payloaders,
media,
&stream.in_caps.as_ref().unwrap().clone(),
&stream.sink_pad.name(),
@ -1240,8 +1212,8 @@ impl BaseWebRTCSink {
"Selected {codec:?} for media {media_idx}"
);
codecs.insert(codec.payload, codec.clone());
codec.output_filter.unwrap()
codecs.insert(codec.payload().unwrap(), codec.clone());
codec.output_filter().unwrap()
}
None => {
gst::error!(CAT, obj: element, "No codec selected for media {media_idx}");
@ -1306,64 +1278,6 @@ impl BaseWebRTCSink {
}
}
/// Build an ordered map of Codecs, given user-provided audio / video caps */
fn lookup_codecs(&self) -> BTreeMap<i32, Codec> {
/* First gather all encoder and payloader factories */
let encoders = gst::ElementFactory::factories_with_type(
gst::ElementFactoryType::ENCODER,
gst::Rank::Marginal,
);
let payloaders = gst::ElementFactory::factories_with_type(
gst::ElementFactoryType::PAYLOADER,
gst::Rank::Marginal,
);
/* Now iterate user-provided codec preferences and determine
* whether we can fulfill these preferences */
let settings = self.settings.lock().unwrap();
let mut payload = 96..128;
settings
.video_caps
.iter()
.chain(settings.audio_caps.iter())
.filter_map(move |s| {
let caps = gst::Caps::builder_full().structure(s.to_owned()).build();
Option::zip(
encoders
.iter()
.find(|factory| factory.can_src_any_caps(&caps)),
payloaders
.iter()
.find(|factory| factory.can_sink_any_caps(&caps)),
)
.and_then(|(encoder, payloader)| {
/* Assign a payload type to the codec */
if let Some(pt) = payload.next() {
Some(Codec {
encoder: encoder.clone(),
payloader: payloader.clone(),
caps,
payload: pt,
output_filter: None,
})
} else {
gst::warning!(
CAT,
imp: self,
"Too many formats for available payload type range, ignoring {}",
s
);
None
}
})
})
.map(|codec| (codec.payload, codec))
.collect()
}
/// Prepare for accepting consumers, by setting
/// up StreamProducers for each of our sink pads
fn prepare(&self, element: &super::BaseWebRTCSink) -> Result<(), Error> {
@ -1666,44 +1580,8 @@ impl BaseWebRTCSink {
}
}
fn build_codec(
encoding_name: &str,
payload: i32,
encoders: &gst::glib::List<gst::ElementFactory>,
payloaders: &gst::glib::List<gst::ElementFactory>,
) -> Option<Codec> {
let caps = match encoding_name {
"VP8" => gst::Caps::builder("video/x-vp8").build(),
"VP9" => gst::Caps::builder("video/x-vp9").build(),
"H264" => gst::Caps::builder("video/x-h264").build(),
"H265" => gst::Caps::builder("video/x-h265").build(),
"OPUS" => gst::Caps::builder("audio/x-opus").build(),
_ => {
return None;
}
};
Option::zip(
encoders
.iter()
.find(|factory| factory.can_src_any_caps(&caps)),
payloaders
.iter()
.find(|factory| factory.can_sink_any_caps(&caps)),
)
.map(|(encoder, payloader)| Codec {
encoder: encoder.clone(),
payloader: payloader.clone(),
caps,
payload,
output_filter: None,
})
}
async fn select_codec(
element: &super::BaseWebRTCSink,
encoders: &gst::glib::List<gst::ElementFactory>,
payloaders: &gst::glib::List<gst::ElementFactory>,
media: &gst_sdp::SDPMediaRef,
in_caps: &gst::Caps,
stream_name: &str,
@ -1751,9 +1629,8 @@ impl BaseWebRTCSink {
let encoding_name = s.get::<String>("encoding-name").unwrap();
if let Some(codec) =
BaseWebRTCSink::build_codec(&encoding_name, payload, encoders, payloaders)
{
if let Some(mut codec) = Codecs::find(&encoding_name) {
codec.set_pt(payload);
for (user_caps, codecs_and_caps) in ordered_codecs_and_caps.iter_mut() {
if codec.caps.is_subset(user_caps) {
codecs_and_caps.push((codec, caps));
@ -1798,12 +1675,12 @@ impl BaseWebRTCSink {
caps,
twcc_idx,
)
.await
.map(|s| {
let mut codec = codec.clone();
codec.output_filter = Some([s].into_iter().collect());
codec
})
.await
.map(|s| {
let mut codec = codec.clone();
codec.set_output_filter([s].into_iter().collect());
codec
})
});
/* Run sequentially to avoid NVENC collisions */
@ -2850,7 +2727,7 @@ impl BaseWebRTCSink {
"sprop-parameter-sets",
"a-framerate",
]);
s.set("payload", codec.payload);
s.set("payload", codec.payload().unwrap());
gst::debug!(
CAT,
obj: element,
@ -2875,7 +2752,7 @@ impl BaseWebRTCSink {
name: String,
in_caps: gst::Caps,
output_caps: gst::Caps,
codecs: &BTreeMap<i32, Codec>,
codecs: &Codecs,
) -> (String, gst::Caps) {
let sink_caps = in_caps.as_ref().to_owned();
@ -2890,8 +2767,8 @@ impl BaseWebRTCSink {
let futs = codecs
.iter()
.filter(|(_, codec)| codec.is_video() == is_video)
.map(|(_, codec)| {
.filter(|codec| codec.is_video() == is_video)
.map(|codec| {
BaseWebRTCSink::run_discovery_pipeline(
element,
&name,
@ -2925,7 +2802,11 @@ impl BaseWebRTCSink {
}
async fn lookup_streams_caps(&self, element: &super::BaseWebRTCSink) -> Result<(), Error> {
let codecs = self.lookup_codecs();
let codecs = {
let settings = self.settings.lock().unwrap();
Codecs::list_encoders(settings.video_caps.iter().chain(settings.audio_caps.iter()))
};
gst::debug!(CAT, obj: element, "Looked up codecs {codecs:?}");
@ -2960,7 +2841,7 @@ impl BaseWebRTCSink {
}
}
state.codecs = codecs;
state.codecs = codecs.to_map();
Ok(())
}
@ -3496,7 +3377,7 @@ impl ElementImpl for BaseWebRTCSink {
"audio_%u",
gst::PadDirection::Sink,
gst::PadPresence::Request,
&caps,
&caps
)
.unwrap();

View file

@ -3,15 +3,14 @@
use gst::prelude::*;
use crate::signaller::{prelude::*, Signallable, Signaller};
use crate::utils::*;
use crate::webrtcsrc::WebRTCSrcPad;
use anyhow::{Context, Error};
use core::ops::Deref;
use gst::glib;
use gst::subclass::prelude::*;
use once_cell::sync::Lazy;
use std::collections::HashSet;
use std::str::FromStr;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicU16;
use std::sync::atomic::Ordering;
use std::sync::Mutex;
@ -27,112 +26,6 @@ static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
)
});
struct Codec {
name: String,
caps: gst::Caps,
has_decoder: AtomicBool,
stream_type: gst::StreamType,
}
impl Clone for Codec {
fn clone(&self) -> Self {
Self {
name: self.name.clone(),
caps: self.caps.clone(),
has_decoder: AtomicBool::new(self.has_decoder.load(Ordering::SeqCst)),
stream_type: self.stream_type,
}
}
}
impl Codec {
fn new(
name: &str,
stream_type: gst::StreamType,
caps: &gst::Caps,
decoders: &glib::List<gst::ElementFactory>,
) -> Self {
let has_decoder = Self::has_decoder_for_caps(caps, decoders);
Self {
caps: caps.clone(),
stream_type,
name: name.into(),
has_decoder: AtomicBool::new(has_decoder),
}
}
fn has_decoder(&self) -> bool {
if self.has_decoder.load(Ordering::SeqCst) {
true
} else if Self::has_decoder_for_caps(
&self.caps,
// Replicating decodebin logic
&gst::ElementFactory::factories_with_type(
gst::ElementFactoryType::DECODER,
gst::Rank::Marginal,
),
) {
// Check if new decoders have been installed meanwhile
self.has_decoder.store(true, Ordering::SeqCst);
true
} else {
false
}
}
fn has_decoder_for_caps(caps: &gst::Caps, decoders: &glib::List<gst::ElementFactory>) -> bool {
decoders.iter().any(|factory| {
factory.static_pad_templates().iter().any(|template| {
let template_caps = template.caps();
template.direction() == gst::PadDirection::Sink
&& !template_caps.is_any()
&& caps.can_intersect(&template_caps)
})
})
}
}
static AUDIO_CAPS: Lazy<gst::Caps> = Lazy::new(|| gst::Caps::new_empty_simple("audio/x-raw"));
static OPUS_CAPS: Lazy<gst::Caps> = Lazy::new(|| gst::Caps::new_empty_simple("audio/x-opus"));
static VIDEO_CAPS: Lazy<gst::Caps> = Lazy::new(|| {
gst::Caps::builder_full_with_any_features()
.structure(gst::Structure::new_empty("video/x-raw"))
.build()
});
static VP8_CAPS: Lazy<gst::Caps> = Lazy::new(|| gst::Caps::new_empty_simple("video/x-vp8"));
static VP9_CAPS: Lazy<gst::Caps> = Lazy::new(|| gst::Caps::new_empty_simple("video/x-vp9"));
static H264_CAPS: Lazy<gst::Caps> = Lazy::new(|| gst::Caps::new_empty_simple("video/x-h264"));
static H265_CAPS: Lazy<gst::Caps> = Lazy::new(|| gst::Caps::new_empty_simple("video/x-h265"));
static RTP_CAPS: Lazy<gst::Caps> = Lazy::new(|| gst::Caps::new_empty_simple("application/x-rtp"));
struct Codecs(Vec<Codec>);
impl Deref for Codecs {
type Target = Vec<Codec>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
static CODECS: Lazy<Codecs> = Lazy::new(|| {
let decoders = gst::ElementFactory::factories_with_type(
gst::ElementFactoryType::DECODER,
gst::Rank::Marginal,
);
Codecs(vec![
Codec::new("OPUS", gst::StreamType::AUDIO, &OPUS_CAPS, &decoders),
Codec::new("VP8", gst::StreamType::VIDEO, &VP8_CAPS, &decoders),
Codec::new("H264", gst::StreamType::VIDEO, &H264_CAPS, &decoders),
Codec::new("VP9", gst::StreamType::VIDEO, &VP9_CAPS, &decoders),
Codec::new("H265", gst::StreamType::VIDEO, &H265_CAPS, &decoders),
])
});
struct Settings {
stun_server: Option<String>,
signaller: Signallable,
@ -175,25 +68,15 @@ impl ObjectImpl for WebRTCSrc {
.build(),
gst::ParamSpecArray::builder("video-codecs")
.flags(glib::ParamFlags::READWRITE | gst::PARAM_FLAG_MUTABLE_READY)
.blurb(&format!("Names of video codecs to be be used during the SDP negotiation. Valid values: [{}]", CODECS.iter().filter_map(|c|
if matches!(c.stream_type, gst::StreamType::VIDEO) {
Some(c.name.to_owned())
} else {
None
}
).collect::<Vec<String>>().join(", ")
.blurb(&format!("Names of video codecs to be be used during the SDP negotiation. Valid values: [{}]",
Codecs::video_codec_names().into_iter().collect::<Vec<String>>().join(", ")
))
.element_spec(&glib::ParamSpecString::builder("video-codec-name").build())
.build(),
gst::ParamSpecArray::builder("audio-codecs")
.flags(glib::ParamFlags::READWRITE | gst::PARAM_FLAG_MUTABLE_READY)
.blurb(&format!("Names of audio codecs to be be used during the SDP negotiation. Valid values: [{}]", CODECS.iter().filter_map(|c|
if matches!(c.stream_type, gst::StreamType::AUDIO) {
Some(c.name.to_owned())
} else {
None
}
).collect::<Vec<String>>().join(", ")
.blurb(&format!("Names of audio codecs to be be used during the SDP negotiation. Valid values: [{}]",
Codecs::video_codec_names().into_iter().collect::<Vec<String>>().join(", ")
))
.element_spec(&glib::ParamSpecString::builder("audio-codec-name").build())
.build(),
@ -216,14 +99,7 @@ impl ObjectImpl for WebRTCSrc {
.as_slice()
.iter()
.filter_map(|codec_name| {
CODECS
.iter()
.find(|codec| {
codec.stream_type == gst::StreamType::VIDEO
&& codec.name
== codec_name.get::<&str>().expect("Type checked upstream")
})
.cloned()
Codecs::find(codec_name.get::<&str>().expect("Type checked upstream"))
})
.collect::<Vec<Codec>>()
}
@ -234,14 +110,7 @@ impl ObjectImpl for WebRTCSrc {
.as_slice()
.iter()
.filter_map(|codec_name| {
CODECS
.iter()
.find(|codec| {
codec.stream_type == gst::StreamType::AUDIO
&& codec.name
== codec_name.get::<&str>().expect("Type checked upstream")
})
.cloned()
Codecs::find(codec_name.get::<&str>().expect("Type checked upstream"))
})
.collect::<Vec<Codec>>()
}
@ -339,19 +208,13 @@ impl Default for Settings {
stun_server: DEFAULT_STUN_SERVER.map(|v| v.to_string()),
signaller: signaller.upcast(),
meta: Default::default(),
audio_codecs: CODECS
.iter()
.filter(|codec| {
matches!(codec.stream_type, gst::StreamType::AUDIO) && codec.has_decoder()
})
.cloned()
audio_codecs: Codecs::audio_codecs()
.into_iter()
.filter(|codec| codec.has_decoder())
.collect(),
video_codecs: CODECS
.iter()
.filter(|codec| {
matches!(codec.stream_type, gst::StreamType::VIDEO) && codec.has_decoder()
})
.cloned()
video_codecs: Codecs::video_codecs()
.into_iter()
.filter(|codec| codec.has_decoder())
.collect(),
}
}
@ -1002,15 +865,30 @@ impl ElementImpl for WebRTCSrc {
fn pad_templates() -> &'static [gst::PadTemplate] {
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
let mut video_caps_builder = gst::Caps::builder_full()
.structure_with_any_features(VIDEO_CAPS.structure(0).unwrap().to_owned())
.structure(RTP_CAPS.structure(0).unwrap().to_owned());
for codec in Codecs::video_codecs() {
video_caps_builder =
video_caps_builder.structure(codec.caps.structure(0).unwrap().to_owned());
}
let mut audio_caps_builder = gst::Caps::builder_full()
.structure_with_any_features(AUDIO_CAPS.structure(0).unwrap().to_owned())
.structure(RTP_CAPS.structure(0).unwrap().to_owned());
for codec in Codecs::audio_codecs() {
audio_caps_builder =
audio_caps_builder.structure(codec.caps.structure(0).unwrap().to_owned());
}
vec![
gst::PadTemplate::with_gtype(
"video_%u",
gst::PadDirection::Src,
gst::PadPresence::Sometimes,
&gst::Caps::builder_full()
.structure_with_any_features(VIDEO_CAPS.structure(0).unwrap().to_owned())
.structure(RTP_CAPS.structure(0).unwrap().to_owned())
.build(),
&video_caps_builder.build(),
WebRTCSrcPad::static_type(),
)
.unwrap(),
@ -1018,11 +896,7 @@ impl ElementImpl for WebRTCSrc {
"audio_%u",
gst::PadDirection::Src,
gst::PadPresence::Sometimes,
&gst::Caps::builder_full()
.structure_with_any_features(AUDIO_CAPS.structure(0).unwrap().to_owned())
.structure(OPUS_CAPS.structure(0).unwrap().to_owned())
.structure(RTP_CAPS.structure(0).unwrap().to_owned())
.build(),
&audio_caps_builder.build(),
WebRTCSrcPad::static_type(),
)
.unwrap(),