mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2025-02-16 21:05:15 +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 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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
Loading…
Reference in a new issue