mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2025-02-19 22:26:20 +00:00
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:
parent
cf32d9d668
commit
059cdecf7d
3 changed files with 504 additions and 317 deletions
|
@ -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 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> {
|
pub fn gvalue_to_json(val: &gst::glib::Value) -> Option<serde_json::Value> {
|
||||||
match val.type_() {
|
match val.type_() {
|
||||||
|
@ -98,3 +112,421 @@ pub fn make_element(element: &str, name: Option<&str>) -> Result<gst::Element, E
|
||||||
.build()
|
.build()
|
||||||
.with_context(|| format!("Failed to make element {element}"))
|
.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()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use crate::utils::*;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
|
@ -81,23 +82,6 @@ struct Settings {
|
||||||
signaller: Signallable,
|
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
|
/// Wrapper around our sink pads
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct InputStream {
|
struct InputStream {
|
||||||
|
@ -281,13 +265,17 @@ impl Default for Settings {
|
||||||
let signaller = Signaller::new(WebRTCSignallerRole::Producer);
|
let signaller = Signaller::new(WebRTCSignallerRole::Producer);
|
||||||
|
|
||||||
Self {
|
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()
|
.into_iter()
|
||||||
.map(gst::Structure::new_empty)
|
|
||||||
.collect::<gst::Caps>(),
|
.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()
|
.into_iter()
|
||||||
.map(gst::Structure::new_empty)
|
|
||||||
.collect::<gst::Caps>(),
|
.collect::<gst::Caps>(),
|
||||||
stun_server: DEFAULT_STUN_SERVER.map(String::from),
|
stun_server: DEFAULT_STUN_SERVER.map(String::from),
|
||||||
turn_servers: gst::Array::new(Vec::new() as Vec<glib::SendValue>),
|
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 conv_filter = make_element("capsfilter", None)?;
|
||||||
|
|
||||||
let enc = codec
|
let enc = codec
|
||||||
.encoder
|
.build_encoder()
|
||||||
.create()
|
.expect("Encoders should always have been set in the CodecInfo we handle")?;
|
||||||
.build()
|
|
||||||
.with_context(|| format!("Creating encoder {}", codec.encoder.name()))?;
|
|
||||||
let parse_filter = make_element("capsfilter", None)?;
|
let parse_filter = make_element("capsfilter", None)?;
|
||||||
let pay = codec
|
let pay = codec
|
||||||
.payloader
|
.build_payloader()
|
||||||
.create()
|
.expect("Payloaders should always have been set in the CodecInfo we handle")?;
|
||||||
.build()
|
|
||||||
.with_context(|| format!("Creating payloader {}", codec.payloader.name()))?;
|
|
||||||
let pay_filter = make_element("capsfilter", None)?;
|
let pay_filter = make_element("capsfilter", None)?;
|
||||||
|
|
||||||
pay.set_property("mtu", 1200_u32);
|
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 {
|
if let Some(ssrc) = ssrc {
|
||||||
pay.set_property("ssrc", ssrc);
|
pay.set_property("ssrc", ssrc);
|
||||||
|
@ -555,7 +539,7 @@ fn setup_encoding(
|
||||||
let mut structure_builder = gst::Structure::builder("video/x-raw")
|
let mut structure_builder = gst::Structure::builder("video/x-raw")
|
||||||
.field("pixel-aspect-ratio", gst::Fraction::new(1, 1));
|
.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
|
// Quirk: nvh264enc can perform conversion from RGB formats, but
|
||||||
// doesn't advertise / negotiate colorimetry correctly, leading
|
// doesn't advertise / negotiate colorimetry correctly, leading
|
||||||
// to incorrect color display in Chrome (but interestingly not in
|
// to incorrect color display in Chrome (but interestingly not in
|
||||||
|
@ -572,7 +556,7 @@ fn setup_encoding(
|
||||||
gst::Caps::builder("audio/x-raw").build()
|
gst::Caps::builder("audio/x-raw").build()
|
||||||
};
|
};
|
||||||
|
|
||||||
match codec.encoder.name().as_str() {
|
match codec.encoder_name().unwrap().as_str() {
|
||||||
"vp8enc" | "vp9enc" => {
|
"vp8enc" | "vp9enc" => {
|
||||||
pay.set_property_from_str("picture-id-mode", "15-bit");
|
pay.set_property_from_str("picture-id-mode", "15-bit");
|
||||||
}
|
}
|
||||||
|
@ -931,7 +915,7 @@ impl Session {
|
||||||
self.pipeline.add(&pay_filter).unwrap();
|
self.pipeline.add(&pay_filter).unwrap();
|
||||||
|
|
||||||
let output_caps = codec
|
let output_caps = codec
|
||||||
.output_filter
|
.output_filter()
|
||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_else(gst::Caps::new_any);
|
.unwrap_or_else(gst::Caps::new_any);
|
||||||
|
|
||||||
|
@ -1211,20 +1195,8 @@ impl BaseWebRTCSink {
|
||||||
|
|
||||||
let mut payloader_caps = match media {
|
let mut payloader_caps = match media {
|
||||||
Some(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(
|
let codec = BaseWebRTCSink::select_codec(
|
||||||
element,
|
element,
|
||||||
&encoders,
|
|
||||||
&payloaders,
|
|
||||||
media,
|
media,
|
||||||
&stream.in_caps.as_ref().unwrap().clone(),
|
&stream.in_caps.as_ref().unwrap().clone(),
|
||||||
&stream.sink_pad.name(),
|
&stream.sink_pad.name(),
|
||||||
|
@ -1240,8 +1212,8 @@ impl BaseWebRTCSink {
|
||||||
"Selected {codec:?} for media {media_idx}"
|
"Selected {codec:?} for media {media_idx}"
|
||||||
);
|
);
|
||||||
|
|
||||||
codecs.insert(codec.payload, codec.clone());
|
codecs.insert(codec.payload().unwrap(), codec.clone());
|
||||||
codec.output_filter.unwrap()
|
codec.output_filter().unwrap()
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
gst::error!(CAT, obj: element, "No codec selected for media {media_idx}");
|
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
|
/// Prepare for accepting consumers, by setting
|
||||||
/// up StreamProducers for each of our sink pads
|
/// up StreamProducers for each of our sink pads
|
||||||
fn prepare(&self, element: &super::BaseWebRTCSink) -> Result<(), Error> {
|
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(
|
async fn select_codec(
|
||||||
element: &super::BaseWebRTCSink,
|
element: &super::BaseWebRTCSink,
|
||||||
encoders: &gst::glib::List<gst::ElementFactory>,
|
|
||||||
payloaders: &gst::glib::List<gst::ElementFactory>,
|
|
||||||
media: &gst_sdp::SDPMediaRef,
|
media: &gst_sdp::SDPMediaRef,
|
||||||
in_caps: &gst::Caps,
|
in_caps: &gst::Caps,
|
||||||
stream_name: &str,
|
stream_name: &str,
|
||||||
|
@ -1751,9 +1629,8 @@ impl BaseWebRTCSink {
|
||||||
|
|
||||||
let encoding_name = s.get::<String>("encoding-name").unwrap();
|
let encoding_name = s.get::<String>("encoding-name").unwrap();
|
||||||
|
|
||||||
if let Some(codec) =
|
if let Some(mut codec) = Codecs::find(&encoding_name) {
|
||||||
BaseWebRTCSink::build_codec(&encoding_name, payload, encoders, payloaders)
|
codec.set_pt(payload);
|
||||||
{
|
|
||||||
for (user_caps, codecs_and_caps) in ordered_codecs_and_caps.iter_mut() {
|
for (user_caps, codecs_and_caps) in ordered_codecs_and_caps.iter_mut() {
|
||||||
if codec.caps.is_subset(user_caps) {
|
if codec.caps.is_subset(user_caps) {
|
||||||
codecs_and_caps.push((codec, caps));
|
codecs_and_caps.push((codec, caps));
|
||||||
|
@ -1798,12 +1675,12 @@ impl BaseWebRTCSink {
|
||||||
caps,
|
caps,
|
||||||
twcc_idx,
|
twcc_idx,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|s| {
|
.map(|s| {
|
||||||
let mut codec = codec.clone();
|
let mut codec = codec.clone();
|
||||||
codec.output_filter = Some([s].into_iter().collect());
|
codec.set_output_filter([s].into_iter().collect());
|
||||||
codec
|
codec
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
/* Run sequentially to avoid NVENC collisions */
|
/* Run sequentially to avoid NVENC collisions */
|
||||||
|
@ -2850,7 +2727,7 @@ impl BaseWebRTCSink {
|
||||||
"sprop-parameter-sets",
|
"sprop-parameter-sets",
|
||||||
"a-framerate",
|
"a-framerate",
|
||||||
]);
|
]);
|
||||||
s.set("payload", codec.payload);
|
s.set("payload", codec.payload().unwrap());
|
||||||
gst::debug!(
|
gst::debug!(
|
||||||
CAT,
|
CAT,
|
||||||
obj: element,
|
obj: element,
|
||||||
|
@ -2875,7 +2752,7 @@ impl BaseWebRTCSink {
|
||||||
name: String,
|
name: String,
|
||||||
in_caps: gst::Caps,
|
in_caps: gst::Caps,
|
||||||
output_caps: gst::Caps,
|
output_caps: gst::Caps,
|
||||||
codecs: &BTreeMap<i32, Codec>,
|
codecs: &Codecs,
|
||||||
) -> (String, gst::Caps) {
|
) -> (String, gst::Caps) {
|
||||||
let sink_caps = in_caps.as_ref().to_owned();
|
let sink_caps = in_caps.as_ref().to_owned();
|
||||||
|
|
||||||
|
@ -2890,8 +2767,8 @@ impl BaseWebRTCSink {
|
||||||
|
|
||||||
let futs = codecs
|
let futs = codecs
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, codec)| codec.is_video() == is_video)
|
.filter(|codec| codec.is_video() == is_video)
|
||||||
.map(|(_, codec)| {
|
.map(|codec| {
|
||||||
BaseWebRTCSink::run_discovery_pipeline(
|
BaseWebRTCSink::run_discovery_pipeline(
|
||||||
element,
|
element,
|
||||||
&name,
|
&name,
|
||||||
|
@ -2925,7 +2802,11 @@ impl BaseWebRTCSink {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn lookup_streams_caps(&self, element: &super::BaseWebRTCSink) -> Result<(), Error> {
|
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:?}");
|
gst::debug!(CAT, obj: element, "Looked up codecs {codecs:?}");
|
||||||
|
|
||||||
|
@ -2960,7 +2841,7 @@ impl BaseWebRTCSink {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.codecs = codecs;
|
state.codecs = codecs.to_map();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -3496,7 +3377,7 @@ impl ElementImpl for BaseWebRTCSink {
|
||||||
"audio_%u",
|
"audio_%u",
|
||||||
gst::PadDirection::Sink,
|
gst::PadDirection::Sink,
|
||||||
gst::PadPresence::Request,
|
gst::PadPresence::Request,
|
||||||
&caps,
|
&caps
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -3,15 +3,14 @@
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
|
|
||||||
use crate::signaller::{prelude::*, Signallable, Signaller};
|
use crate::signaller::{prelude::*, Signallable, Signaller};
|
||||||
|
use crate::utils::*;
|
||||||
use crate::webrtcsrc::WebRTCSrcPad;
|
use crate::webrtcsrc::WebRTCSrcPad;
|
||||||
use anyhow::{Context, Error};
|
use anyhow::{Context, Error};
|
||||||
use core::ops::Deref;
|
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
use gst::subclass::prelude::*;
|
use gst::subclass::prelude::*;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::atomic::AtomicBool;
|
|
||||||
use std::sync::atomic::AtomicU16;
|
use std::sync::atomic::AtomicU16;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::sync::Mutex;
|
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 {
|
struct Settings {
|
||||||
stun_server: Option<String>,
|
stun_server: Option<String>,
|
||||||
signaller: Signallable,
|
signaller: Signallable,
|
||||||
|
@ -175,25 +68,15 @@ impl ObjectImpl for WebRTCSrc {
|
||||||
.build(),
|
.build(),
|
||||||
gst::ParamSpecArray::builder("video-codecs")
|
gst::ParamSpecArray::builder("video-codecs")
|
||||||
.flags(glib::ParamFlags::READWRITE | gst::PARAM_FLAG_MUTABLE_READY)
|
.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|
|
.blurb(&format!("Names of video codecs to be be used during the SDP negotiation. Valid values: [{}]",
|
||||||
if matches!(c.stream_type, gst::StreamType::VIDEO) {
|
Codecs::video_codec_names().into_iter().collect::<Vec<String>>().join(", ")
|
||||||
Some(c.name.to_owned())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
).collect::<Vec<String>>().join(", ")
|
|
||||||
))
|
))
|
||||||
.element_spec(&glib::ParamSpecString::builder("video-codec-name").build())
|
.element_spec(&glib::ParamSpecString::builder("video-codec-name").build())
|
||||||
.build(),
|
.build(),
|
||||||
gst::ParamSpecArray::builder("audio-codecs")
|
gst::ParamSpecArray::builder("audio-codecs")
|
||||||
.flags(glib::ParamFlags::READWRITE | gst::PARAM_FLAG_MUTABLE_READY)
|
.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|
|
.blurb(&format!("Names of audio codecs to be be used during the SDP negotiation. Valid values: [{}]",
|
||||||
if matches!(c.stream_type, gst::StreamType::AUDIO) {
|
Codecs::video_codec_names().into_iter().collect::<Vec<String>>().join(", ")
|
||||||
Some(c.name.to_owned())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
).collect::<Vec<String>>().join(", ")
|
|
||||||
))
|
))
|
||||||
.element_spec(&glib::ParamSpecString::builder("audio-codec-name").build())
|
.element_spec(&glib::ParamSpecString::builder("audio-codec-name").build())
|
||||||
.build(),
|
.build(),
|
||||||
|
@ -216,14 +99,7 @@ impl ObjectImpl for WebRTCSrc {
|
||||||
.as_slice()
|
.as_slice()
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|codec_name| {
|
.filter_map(|codec_name| {
|
||||||
CODECS
|
Codecs::find(codec_name.get::<&str>().expect("Type checked upstream"))
|
||||||
.iter()
|
|
||||||
.find(|codec| {
|
|
||||||
codec.stream_type == gst::StreamType::VIDEO
|
|
||||||
&& codec.name
|
|
||||||
== codec_name.get::<&str>().expect("Type checked upstream")
|
|
||||||
})
|
|
||||||
.cloned()
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<Codec>>()
|
.collect::<Vec<Codec>>()
|
||||||
}
|
}
|
||||||
|
@ -234,14 +110,7 @@ impl ObjectImpl for WebRTCSrc {
|
||||||
.as_slice()
|
.as_slice()
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|codec_name| {
|
.filter_map(|codec_name| {
|
||||||
CODECS
|
Codecs::find(codec_name.get::<&str>().expect("Type checked upstream"))
|
||||||
.iter()
|
|
||||||
.find(|codec| {
|
|
||||||
codec.stream_type == gst::StreamType::AUDIO
|
|
||||||
&& codec.name
|
|
||||||
== codec_name.get::<&str>().expect("Type checked upstream")
|
|
||||||
})
|
|
||||||
.cloned()
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<Codec>>()
|
.collect::<Vec<Codec>>()
|
||||||
}
|
}
|
||||||
|
@ -339,19 +208,13 @@ impl Default for Settings {
|
||||||
stun_server: DEFAULT_STUN_SERVER.map(|v| v.to_string()),
|
stun_server: DEFAULT_STUN_SERVER.map(|v| v.to_string()),
|
||||||
signaller: signaller.upcast(),
|
signaller: signaller.upcast(),
|
||||||
meta: Default::default(),
|
meta: Default::default(),
|
||||||
audio_codecs: CODECS
|
audio_codecs: Codecs::audio_codecs()
|
||||||
.iter()
|
.into_iter()
|
||||||
.filter(|codec| {
|
.filter(|codec| codec.has_decoder())
|
||||||
matches!(codec.stream_type, gst::StreamType::AUDIO) && codec.has_decoder()
|
|
||||||
})
|
|
||||||
.cloned()
|
|
||||||
.collect(),
|
.collect(),
|
||||||
video_codecs: CODECS
|
video_codecs: Codecs::video_codecs()
|
||||||
.iter()
|
.into_iter()
|
||||||
.filter(|codec| {
|
.filter(|codec| codec.has_decoder())
|
||||||
matches!(codec.stream_type, gst::StreamType::VIDEO) && codec.has_decoder()
|
|
||||||
})
|
|
||||||
.cloned()
|
|
||||||
.collect(),
|
.collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1002,15 +865,30 @@ impl ElementImpl for WebRTCSrc {
|
||||||
|
|
||||||
fn pad_templates() -> &'static [gst::PadTemplate] {
|
fn pad_templates() -> &'static [gst::PadTemplate] {
|
||||||
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
|
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![
|
vec![
|
||||||
gst::PadTemplate::with_gtype(
|
gst::PadTemplate::with_gtype(
|
||||||
"video_%u",
|
"video_%u",
|
||||||
gst::PadDirection::Src,
|
gst::PadDirection::Src,
|
||||||
gst::PadPresence::Sometimes,
|
gst::PadPresence::Sometimes,
|
||||||
&gst::Caps::builder_full()
|
&video_caps_builder.build(),
|
||||||
.structure_with_any_features(VIDEO_CAPS.structure(0).unwrap().to_owned())
|
|
||||||
.structure(RTP_CAPS.structure(0).unwrap().to_owned())
|
|
||||||
.build(),
|
|
||||||
WebRTCSrcPad::static_type(),
|
WebRTCSrcPad::static_type(),
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
@ -1018,11 +896,7 @@ impl ElementImpl for WebRTCSrc {
|
||||||
"audio_%u",
|
"audio_%u",
|
||||||
gst::PadDirection::Src,
|
gst::PadDirection::Src,
|
||||||
gst::PadPresence::Sometimes,
|
gst::PadPresence::Sometimes,
|
||||||
&gst::Caps::builder_full()
|
&audio_caps_builder.build(),
|
||||||
.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(),
|
|
||||||
WebRTCSrcPad::static_type(),
|
WebRTCSrcPad::static_type(),
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
|
Loading…
Reference in a new issue