mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-11-28 22:41:02 +00:00
webrtc: add raw payload support
This commit adds support for raw payloads such as L24 audio to `webrtcsink` & `webrtcsrc`. Most changes take place within the `Codec` helper structure: * A `Codec` can now advertise a depayloader. This also ensures that a format not only can be decoded when necessary, but it can also be depayloaded in the first place. * It is possible to declare raw `Codec`s, meaning that their caps are compatible with a payloader and a depayloader without the need for an encoder and decoder. * Previous accessor `has_decoder` was renamed as `can_be_received` to account for codecs which can be handled by an available depayloader with or without the need for a decoder. * New codecs were added for the following formats: * L24, L16, L8 audio. * RAW video. The `webrtc-precise-sync` examples were updated to demonstrate streaming of raw audio or video. Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1501>
This commit is contained in:
parent
9c84748fc3
commit
34b791ff5e
9 changed files with 402 additions and 143 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -3051,6 +3051,7 @@ dependencies = [
|
||||||
"gstreamer-webrtc",
|
"gstreamer-webrtc",
|
||||||
"http 1.1.0",
|
"http 1.1.0",
|
||||||
"human_bytes",
|
"human_bytes",
|
||||||
|
"itertools 0.12.1",
|
||||||
"livekit-api",
|
"livekit-api",
|
||||||
"livekit-protocol",
|
"livekit-protocol",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
|
|
@ -10183,7 +10183,7 @@
|
||||||
"kind": "object",
|
"kind": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"audio-caps": {
|
"audio-caps": {
|
||||||
"blurb": "Governs what audio codecs will be proposed",
|
"blurb": "Governs what audio codecs will be proposed. Valid values: [audio/x-opus; audio/x-raw, format=(string)S24BE, layout=(string)interleaved, rate=(int)[ 1, 2147483647 ], channels=(int)[ 1, 2147483647 ]; audio/x-raw, format=(string)S16BE, layout=(string)interleaved, rate=(int)[ 1, 2147483647 ], channels=(int)[ 1, 2147483647 ]; audio/x-raw, format=(string)U8, layout=(string)interleaved, rate=(int)[ 1, 2147483647 ], channels=(int)[ 1, 2147483647 ]]",
|
||||||
"conditionally-available": false,
|
"conditionally-available": false,
|
||||||
"construct": false,
|
"construct": false,
|
||||||
"construct-only": false,
|
"construct-only": false,
|
||||||
|
@ -10366,7 +10366,7 @@
|
||||||
"writable": true
|
"writable": true
|
||||||
},
|
},
|
||||||
"video-caps": {
|
"video-caps": {
|
||||||
"blurb": "Governs what video codecs will be proposed",
|
"blurb": "Governs what video codecs will be proposed. Valid values: [video/x-vp8; video/x-h264; video/x-vp9; video/x-h265; video/x-av1; video/x-raw, format=(string){ RGB, RGBA, BGR, BGRA, AYUV, UYVY, I420, Y41B, UYVP }, width=(int)[ 1, 32767 ], height=(int)[ 1, 32767 ]]",
|
||||||
"conditionally-available": false,
|
"conditionally-available": false,
|
||||||
"construct": false,
|
"construct": false,
|
||||||
"construct-only": false,
|
"construct-only": false,
|
||||||
|
@ -10498,7 +10498,7 @@
|
||||||
"kind": "object",
|
"kind": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"audio-codecs": {
|
"audio-codecs": {
|
||||||
"blurb": "Names of audio codecs to be be used during the SDP negotiation. Valid values: [OPUS]",
|
"blurb": "Names of audio codecs to be be used during the SDP negotiation. Valid values: [OPUS, L24, L16, L8]",
|
||||||
"conditionally-available": false,
|
"conditionally-available": false,
|
||||||
"construct": false,
|
"construct": false,
|
||||||
"construct-only": false,
|
"construct-only": false,
|
||||||
|
@ -10578,7 +10578,7 @@
|
||||||
"writable": true
|
"writable": true
|
||||||
},
|
},
|
||||||
"video-codecs": {
|
"video-codecs": {
|
||||||
"blurb": "Names of video codecs to be be used during the SDP negotiation. Valid values: [VP8, H264, VP9, H265, AV1]",
|
"blurb": "Names of video codecs to be be used during the SDP negotiation. Valid values: [VP8, H264, VP9, H265, AV1, RAW]",
|
||||||
"conditionally-available": false,
|
"conditionally-available": false,
|
||||||
"construct": false,
|
"construct": false,
|
||||||
"construct-only": false,
|
"construct-only": false,
|
||||||
|
|
|
@ -25,6 +25,7 @@ anyhow = "1"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
|
itertools = "0.12"
|
||||||
tokio = { version = "1", features = ["fs", "macros", "rt-multi-thread", "time"] }
|
tokio = { version = "1", features = ["fs", "macros", "rt-multi-thread", "time"] }
|
||||||
tokio-native-tls = "0.3.0"
|
tokio-native-tls = "0.3.0"
|
||||||
tokio-stream = "0.1.11"
|
tokio-stream = "0.1.11"
|
||||||
|
|
|
@ -34,6 +34,13 @@ mode and an example based on RTSP instead of WebRTC.
|
||||||
The examples can also be used for [RFC 7273] NTP or PTP clock signalling and
|
The examples can also be used for [RFC 7273] NTP or PTP clock signalling and
|
||||||
synchronization.
|
synchronization.
|
||||||
|
|
||||||
|
Finally, raw payloads (e.g. L24 audio) can be negotiated.
|
||||||
|
|
||||||
|
Note: you can have your host act as an NTP server, which can help the examples
|
||||||
|
with clock synchronization. For `chrony`, this can be configure by editing
|
||||||
|
`/etc/chrony.conf` and uncommenting / editing the `allow` entry. The examples
|
||||||
|
can then be launched with `--ntp-server _ip_address_`.
|
||||||
|
|
||||||
[RFC 6051]: https://datatracker.ietf.org/doc/html/rfc6051
|
[RFC 6051]: https://datatracker.ietf.org/doc/html/rfc6051
|
||||||
[RFC 7273]: https://datatracker.ietf.org/doc/html/rfc7273
|
[RFC 7273]: https://datatracker.ietf.org/doc/html/rfc7273
|
||||||
[Instantaneous RTP synchronization...]: https://coaxion.net/blog/2022/05/instantaneous-rtp-synchronization-retrieval-of-absolute-sender-clock-times-with-gstreamer/
|
[Instantaneous RTP synchronization...]: https://coaxion.net/blog/2022/05/instantaneous-rtp-synchronization-retrieval-of-absolute-sender-clock-times-with-gstreamer/
|
||||||
|
@ -44,7 +51,7 @@ The example uses the default WebRTC signaller. Launch it using the following
|
||||||
command:
|
command:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo run --bin gst-webrtc-signalling-server
|
cargo run --bin gst-webrtc-signalling-server --no-default-features
|
||||||
```
|
```
|
||||||
|
|
||||||
### Receiver
|
### Receiver
|
||||||
|
@ -53,14 +60,14 @@ The receiver awaits for new audio & video stream publishers and render the
|
||||||
streams using auto sink elements. Launch it using the following command:
|
streams using auto sink elements. Launch it using the following command:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo r --example webrtc-precise-sync-recv
|
cargo r --example webrtc-precise-sync-recv --no-default-features
|
||||||
```
|
```
|
||||||
|
|
||||||
The default configuration should work for a local test. For a multi-host setup,
|
The default configuration should work for a local test. For a multi-host setup,
|
||||||
see the available options:
|
see the available options:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo r --example webrtc-precise-sync-recv -- --help
|
cargo r --example webrtc-precise-sync-recv --no-default-features -- --help
|
||||||
```
|
```
|
||||||
|
|
||||||
E.g.: the following will force `avdec_h264` over hardware decoders, activate
|
E.g.: the following will force `avdec_h264` over hardware decoders, activate
|
||||||
|
@ -70,7 +77,8 @@ specified address:
|
||||||
```shell
|
```shell
|
||||||
GST_PLUGIN_FEATURE_RANK=avdec_h264:MAX \
|
GST_PLUGIN_FEATURE_RANK=avdec_h264:MAX \
|
||||||
WEBRTC_PRECISE_SYNC_RECV_LOG=debug \
|
WEBRTC_PRECISE_SYNC_RECV_LOG=debug \
|
||||||
cargo r --example webrtc-precise-sync-recv -- --server 192.168.1.22
|
cargo r --example webrtc-precise-sync-recv --no-default-features -- \
|
||||||
|
--server 192.168.1.22
|
||||||
```
|
```
|
||||||
|
|
||||||
### Sender
|
### Sender
|
||||||
|
@ -79,7 +87,7 @@ The sender publishes audio & video test streams. Launch it using the following
|
||||||
command:
|
command:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo r --example webrtc-precise-sync-send
|
cargo r --example webrtc-precise-sync-send --no-default-features
|
||||||
```
|
```
|
||||||
|
|
||||||
The default configuration should work for a local test. For a multi-host setup,
|
The default configuration should work for a local test. For a multi-host setup,
|
||||||
|
@ -87,7 +95,7 @@ to set the number of audio / video streams, to enable rapid synchronization or
|
||||||
to force the video encoder, see the available options:
|
to force the video encoder, see the available options:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo r --example webrtc-precise-sync-send -- --help
|
cargo r --example webrtc-precise-sync-send --no-default-features -- --help
|
||||||
```
|
```
|
||||||
|
|
||||||
E.g.: the following will force H264 and `x264enc` over hardware encoders,
|
E.g.: the following will force H264 and `x264enc` over hardware encoders,
|
||||||
|
@ -97,7 +105,7 @@ specified address:
|
||||||
```shell
|
```shell
|
||||||
GST_PLUGIN_FEATURE_RANK=264enc:MAX \
|
GST_PLUGIN_FEATURE_RANK=264enc:MAX \
|
||||||
WEBRTC_PRECISE_SYNC_SEND_LOG=debug \
|
WEBRTC_PRECISE_SYNC_SEND_LOG=debug \
|
||||||
cargo r --example webrtc-precise-sync-send -- \
|
cargo r --example webrtc-precise-sync-send --no-default-features -- \
|
||||||
--server 192.168.1.22 --video-caps video/x-h264
|
--server 192.168.1.22 --video-caps video/x-h264
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -120,16 +128,49 @@ commands such as:
|
||||||
#### Receiver
|
#### Receiver
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo r --example webrtc-precise-sync-recv -- --expect-clock-signalling
|
cargo r --example webrtc-precise-sync-recv --no-default-features -- \
|
||||||
|
--expect-clock-signalling
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Sender
|
#### Sender
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo r --example webrtc-precise-sync-send -- --clock ntp --do-clock-signalling \
|
cargo r --example webrtc-precise-sync-send --no-default-features -- \
|
||||||
|
--clock ntp --do-clock-signalling \
|
||||||
--video-streams 0 --audio-streams 2
|
--video-streams 0 --audio-streams 2
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Raw payload
|
||||||
|
|
||||||
|
The sender can be instructed to send raw payloads. Note that raw payloads
|
||||||
|
are not activated by default and must be selected explicitly.
|
||||||
|
|
||||||
|
This command will stream two stereo L24 streams:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cargo r --example webrtc-precise-sync-send --no-default-features -- \
|
||||||
|
--video-streams 0 \
|
||||||
|
--audio-streams 2 --audio-codecs L24
|
||||||
|
```
|
||||||
|
|
||||||
|
Launch the receiver with:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cargo r --example webrtc-precise-sync-recv --no-default-features -- \
|
||||||
|
--audio-codecs L24
|
||||||
|
```
|
||||||
|
|
||||||
|
This can be used to stream multiple RAW video streams using specific CAPS for
|
||||||
|
the streams and allowing fallback to VP8 & OPUS if remote doesn't support raw
|
||||||
|
payloads:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cargo r --example webrtc-precise-sync-send --no-default-features -- \
|
||||||
|
--video-streams 2 --audio-streams 1 \
|
||||||
|
--video-codecs RAW --video-codecs VP8 --video-caps video/x-raw,format=I420,width=400 \
|
||||||
|
--audio-codecs L24 --audio-codecs OPUS --audio-caps audio/x-raw,rate=48000,channels=2
|
||||||
|
```
|
||||||
|
|
||||||
## Android
|
## Android
|
||||||
|
|
||||||
### `webrtcsrc` based Android application
|
### `webrtcsrc` based Android application
|
||||||
|
|
|
@ -43,6 +43,18 @@ struct Args {
|
||||||
#[clap(long, help = "RTP jitterbuffer latency (ms)", default_value = "40")]
|
#[clap(long, help = "RTP jitterbuffer latency (ms)", default_value = "40")]
|
||||||
pub rtp_latency: u32,
|
pub rtp_latency: u32,
|
||||||
|
|
||||||
|
#[clap(
|
||||||
|
long,
|
||||||
|
help = "Force accepted audio codecs. See 'webrtcsrc' 'audio-codecs' property (ex. 'OPUS'). Accepts several occurrences."
|
||||||
|
)]
|
||||||
|
pub audio_codecs: Vec<String>,
|
||||||
|
|
||||||
|
#[clap(
|
||||||
|
long,
|
||||||
|
help = "Force accepted video codecs. See 'webrtcsrc' 'video-codecs' property (ex. 'VP8'). Accepts several occurrences."
|
||||||
|
)]
|
||||||
|
pub video_codecs: Vec<String>,
|
||||||
|
|
||||||
#[clap(long, help = "Signalling server host", default_value = "localhost")]
|
#[clap(long, help = "Signalling server host", default_value = "localhost")]
|
||||||
pub server: String,
|
pub server: String,
|
||||||
|
|
||||||
|
@ -123,6 +135,14 @@ fn spawn_consumer(
|
||||||
webrtcsrc.set_property("do-retransmission", false);
|
webrtcsrc.set_property("do-retransmission", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !args.audio_codecs.is_empty() {
|
||||||
|
webrtcsrc.set_property("audio-codecs", gst::Array::new(&args.audio_codecs));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !args.video_codecs.is_empty() {
|
||||||
|
webrtcsrc.set_property("video-codecs", gst::Array::new(&args.video_codecs));
|
||||||
|
}
|
||||||
|
|
||||||
bin.add(&webrtcsrc).context("Adding webrtcsrc")?;
|
bin.add(&webrtcsrc).context("Adding webrtcsrc")?;
|
||||||
|
|
||||||
let signaller = webrtcsrc.property::<gst::glib::Object>("signaller");
|
let signaller = webrtcsrc.property::<gst::glib::Object>("signaller");
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
use anyhow::{bail, Context};
|
use anyhow::{anyhow, bail, Context};
|
||||||
use futures::prelude::*;
|
use futures::prelude::*;
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
use gst_rtp::prelude::*;
|
use gst_rtp::prelude::*;
|
||||||
|
use gstrswebrtc::utils::Codecs;
|
||||||
|
use itertools::Itertools;
|
||||||
use tracing::{debug, error, info};
|
use tracing::{debug, error, info};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
@ -45,7 +47,28 @@ struct Args {
|
||||||
)]
|
)]
|
||||||
pub video_streams: usize,
|
pub video_streams: usize,
|
||||||
|
|
||||||
#[clap(long, help = "Force video caps (ex. 'video/x-h264')")]
|
#[clap(
|
||||||
|
long,
|
||||||
|
help = "Audio codecs that will be proposed in the SDP (ex. 'L24', defaults to ['OPUS']). Accepts several occurrences."
|
||||||
|
)]
|
||||||
|
pub audio_codecs: Vec<String>,
|
||||||
|
|
||||||
|
#[clap(
|
||||||
|
long,
|
||||||
|
help = "Use specific audio caps (ex. 'audio/x-raw,rate=48000,channels=2')"
|
||||||
|
)]
|
||||||
|
pub audio_caps: Option<String>,
|
||||||
|
|
||||||
|
#[clap(
|
||||||
|
long,
|
||||||
|
help = "Video codecs that will be proposed in the SDP (ex. 'RAW', defaults to ['VP8', 'H264', 'VP9', 'H265']). Accepts several occurrences."
|
||||||
|
)]
|
||||||
|
pub video_codecs: Vec<String>,
|
||||||
|
|
||||||
|
#[clap(
|
||||||
|
long,
|
||||||
|
help = "Use specific video caps (ex. 'video/x-raw,format=I420,width=400')"
|
||||||
|
)]
|
||||||
pub video_caps: Option<String>,
|
pub video_caps: Option<String>,
|
||||||
|
|
||||||
#[clap(long, help = "Use RFC 6051 64-bit NTP timestamp RTP header extension.")]
|
#[clap(long, help = "Use RFC 6051 64-bit NTP timestamp RTP header extension.")]
|
||||||
|
@ -135,6 +158,8 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn prepare(&mut self) -> anyhow::Result<()> {
|
async fn prepare(&mut self) -> anyhow::Result<()> {
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
debug!("Preparing");
|
debug!("Preparing");
|
||||||
|
|
||||||
self.pipeline = Some(gst::Pipeline::new());
|
self.pipeline = Some(gst::Pipeline::new());
|
||||||
|
@ -217,6 +242,26 @@ impl App {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !self.args.audio_codecs.is_empty() {
|
||||||
|
let mut audio_caps = gst::Caps::new_empty();
|
||||||
|
for codec in self.args.audio_codecs.iter() {
|
||||||
|
audio_caps.merge(
|
||||||
|
Codecs::audio_codecs()
|
||||||
|
.find(|c| &c.name == codec)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow!(
|
||||||
|
"Unknown audio codec {codec}. Valid values are: {}",
|
||||||
|
Codecs::audio_codecs().map(|c| c.name.as_str()).join(", ")
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.caps
|
||||||
|
.clone(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
webrtcsink.set_property("audio-caps", audio_caps);
|
||||||
|
}
|
||||||
|
|
||||||
for idx in 0..self.args.audio_streams {
|
for idx in 0..self.args.audio_streams {
|
||||||
let audiosrc = gst::ElementFactory::make("audiotestsrc")
|
let audiosrc = gst::ElementFactory::make("audiotestsrc")
|
||||||
.property("is-live", true)
|
.property("is-live", true)
|
||||||
|
@ -226,11 +271,65 @@ impl App {
|
||||||
.context("Creating audiotestsrc")?;
|
.context("Creating audiotestsrc")?;
|
||||||
self.pipeline().add(&audiosrc).context("Adding audiosrc")?;
|
self.pipeline().add(&audiosrc).context("Adding audiosrc")?;
|
||||||
|
|
||||||
audiosrc
|
if let Some(ref caps) = self.args.audio_caps {
|
||||||
.link_pads(None, &webrtcsink, Some("audio_%u"))
|
audiosrc
|
||||||
.context("Linking audiosrc")?;
|
.link_pads_filtered(
|
||||||
|
None,
|
||||||
|
&webrtcsink,
|
||||||
|
Some("audio_%u"),
|
||||||
|
&gst::Caps::from_str(caps).context("Parsing audio caps")?,
|
||||||
|
)
|
||||||
|
.context("Linking audiosrc")?;
|
||||||
|
} else {
|
||||||
|
audiosrc
|
||||||
|
.link_pads(None, &webrtcsink, Some("audio_%u"))
|
||||||
|
.context("Linking audiosrc")?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !self.args.video_codecs.is_empty() {
|
||||||
|
let mut video_caps = gst::Caps::new_empty();
|
||||||
|
for codec in self.args.video_codecs.iter() {
|
||||||
|
video_caps.merge(
|
||||||
|
Codecs::video_codecs()
|
||||||
|
.find(|c| &c.name == codec)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow!(
|
||||||
|
"Unknown video codec {codec}. Valid values are: {}",
|
||||||
|
Codecs::video_codecs().map(|c| c.name.as_str()).join(", ")
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.caps
|
||||||
|
.clone(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
webrtcsink.set_property("video-caps", video_caps);
|
||||||
|
}
|
||||||
|
|
||||||
|
let raw_video_caps = {
|
||||||
|
let mut raw_video_caps = if let Some(ref video_caps) = self.args.video_caps {
|
||||||
|
gst::Caps::from_str(video_caps).context("Parsing video caps")?
|
||||||
|
} else {
|
||||||
|
gst::Caps::new_empty_simple("video/x-raw")
|
||||||
|
};
|
||||||
|
|
||||||
|
// If no width / height are specified, set something big enough
|
||||||
|
let caps_mut = raw_video_caps.make_mut();
|
||||||
|
let s = caps_mut.structure_mut(0).expect("created above");
|
||||||
|
match (s.get::<i32>("width").ok(), s.get::<i32>("height").ok()) {
|
||||||
|
(None, None) => {
|
||||||
|
s.set("width", 800i32);
|
||||||
|
s.set("height", 600i32);
|
||||||
|
}
|
||||||
|
(Some(width), None) => s.set("height", 3 * width / 4),
|
||||||
|
(None, Some(height)) => s.set("width", 4 * height / 3),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
raw_video_caps
|
||||||
|
};
|
||||||
|
|
||||||
for idx in 0..self.args.video_streams {
|
for idx in 0..self.args.video_streams {
|
||||||
let videosrc = gst::ElementFactory::make("videotestsrc")
|
let videosrc = gst::ElementFactory::make("videotestsrc")
|
||||||
.property("is-live", true)
|
.property("is-live", true)
|
||||||
|
@ -247,13 +346,7 @@ impl App {
|
||||||
.expect("adding video elements");
|
.expect("adding video elements");
|
||||||
|
|
||||||
videosrc
|
videosrc
|
||||||
.link_filtered(
|
.link_filtered(&video_overlay, &raw_video_caps)
|
||||||
&video_overlay,
|
|
||||||
&gst::Caps::builder("video/x-raw")
|
|
||||||
.field("width", 800i32)
|
|
||||||
.field("height", 600i32)
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
.context("Linking videosrc to timeoverlay")?;
|
.context("Linking videosrc to timeoverlay")?;
|
||||||
|
|
||||||
video_overlay
|
video_overlay
|
||||||
|
@ -261,10 +354,6 @@ impl App {
|
||||||
.context("Linking video overlay")?;
|
.context("Linking video overlay")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref video_caps) = self.args.video_caps {
|
|
||||||
webrtcsink.set_property("video-caps", &gst::Caps::builder(video_caps).build());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -427,7 +427,7 @@ impl Clone for DecodingInfo {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct EncodingInfo {
|
struct EncodingInfo {
|
||||||
encoder: gst::ElementFactory,
|
encoder: Option<gst::ElementFactory>,
|
||||||
payloader: gst::ElementFactory,
|
payloader: gst::ElementFactory,
|
||||||
output_filter: Option<gst::Caps>,
|
output_filter: Option<gst::Caps>,
|
||||||
}
|
}
|
||||||
|
@ -437,6 +437,7 @@ pub struct Codec {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub caps: gst::Caps,
|
pub caps: gst::Caps,
|
||||||
pub stream_type: gst::StreamType,
|
pub stream_type: gst::StreamType,
|
||||||
|
pub is_raw: bool,
|
||||||
|
|
||||||
payload_type: Option<i32>,
|
payload_type: Option<i32>,
|
||||||
decoding_info: Option<DecodingInfo>,
|
decoding_info: Option<DecodingInfo>,
|
||||||
|
@ -449,16 +450,27 @@ impl Codec {
|
||||||
stream_type: gst::StreamType,
|
stream_type: gst::StreamType,
|
||||||
caps: &gst::Caps,
|
caps: &gst::Caps,
|
||||||
decoders: &glib::List<gst::ElementFactory>,
|
decoders: &glib::List<gst::ElementFactory>,
|
||||||
|
depayloaders: &glib::List<gst::ElementFactory>,
|
||||||
encoders: &glib::List<gst::ElementFactory>,
|
encoders: &glib::List<gst::ElementFactory>,
|
||||||
payloaders: &glib::List<gst::ElementFactory>,
|
payloaders: &glib::List<gst::ElementFactory>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let has_decoder = Self::has_decoder_for_caps(caps, decoders);
|
let has_decoder = Self::has_decoder_for_caps(caps, decoders);
|
||||||
|
let has_depayloader = Self::has_depayloader_for_codec(name, depayloaders);
|
||||||
|
|
||||||
|
let decoding_info = if has_depayloader && has_decoder {
|
||||||
|
Some(DecodingInfo {
|
||||||
|
has_decoder: AtomicBool::new(has_decoder),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let encoder = Self::get_encoder_for_caps(caps, encoders);
|
let encoder = Self::get_encoder_for_caps(caps, encoders);
|
||||||
let payloader = Self::get_payloader_for_codec(name, payloaders);
|
let payloader = Self::get_payloader_for_codec(name, payloaders);
|
||||||
|
|
||||||
let encoding_info = if let (Some(encoder), Some(payloader)) = (encoder, payloader) {
|
let encoding_info = if let (Some(encoder), Some(payloader)) = (encoder, payloader) {
|
||||||
Some(EncodingInfo {
|
Some(EncodingInfo {
|
||||||
encoder,
|
encoder: Some(encoder),
|
||||||
payloader,
|
payloader,
|
||||||
output_filter: None,
|
output_filter: None,
|
||||||
})
|
})
|
||||||
|
@ -470,12 +482,52 @@ impl Codec {
|
||||||
caps: caps.clone(),
|
caps: caps.clone(),
|
||||||
stream_type,
|
stream_type,
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
|
is_raw: false,
|
||||||
payload_type: None,
|
payload_type: None,
|
||||||
|
decoding_info,
|
||||||
|
encoding_info,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
decoding_info: Some(DecodingInfo {
|
pub fn new_raw(
|
||||||
has_decoder: AtomicBool::new(has_decoder),
|
name: &str,
|
||||||
}),
|
stream_type: gst::StreamType,
|
||||||
|
depayloaders: &glib::List<gst::ElementFactory>,
|
||||||
|
payloaders: &glib::List<gst::ElementFactory>,
|
||||||
|
) -> Self {
|
||||||
|
let decoding_info = if Self::has_depayloader_for_codec(name, depayloaders) {
|
||||||
|
Some(DecodingInfo {
|
||||||
|
has_decoder: AtomicBool::new(false),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let payloader = Self::get_payloader_for_codec(name, payloaders);
|
||||||
|
let encoding_info = payloader.map(|payloader| EncodingInfo {
|
||||||
|
encoder: None,
|
||||||
|
payloader,
|
||||||
|
output_filter: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut caps = None;
|
||||||
|
if let Some(elem) = Codec::get_payloader_for_codec(name, payloaders) {
|
||||||
|
if let Some(tmpl) = elem
|
||||||
|
.static_pad_templates()
|
||||||
|
.iter()
|
||||||
|
.find(|template| template.direction() == gst::PadDirection::Sink)
|
||||||
|
{
|
||||||
|
caps = Some(tmpl.caps());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
caps: caps.unwrap_or_else(gst::Caps::new_empty),
|
||||||
|
stream_type,
|
||||||
|
name: name.into(),
|
||||||
|
is_raw: true,
|
||||||
|
payload_type: None,
|
||||||
|
decoding_info,
|
||||||
encoding_info,
|
encoding_info,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -488,33 +540,15 @@ impl Codec {
|
||||||
self.payload_type = Some(pt);
|
self.payload_type = Some(pt);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_decoding(
|
pub fn can_be_received(&self) -> bool {
|
||||||
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),
|
|
||||||
}),
|
|
||||||
|
|
||||||
encoding_info: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_decoder(&self) -> bool {
|
|
||||||
if self.decoding_info.is_none() {
|
if self.decoding_info.is_none() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.is_raw {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
let decoder_info = self.decoding_info.as_ref().unwrap();
|
let decoder_info = self.decoding_info.as_ref().unwrap();
|
||||||
if decoder_info.has_decoder.load(Ordering::SeqCst) {
|
if decoder_info.has_decoder.load(Ordering::SeqCst) {
|
||||||
true
|
true
|
||||||
|
@ -599,6 +633,40 @@ impl Codec {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn has_depayloader_for_codec(
|
||||||
|
codec: &str,
|
||||||
|
depayloaders: &glib::List<gst::ElementFactory>,
|
||||||
|
) -> bool {
|
||||||
|
depayloaders.iter().any(|factory| {
|
||||||
|
factory.static_pad_templates().iter().any(|template| {
|
||||||
|
let template_caps = template.caps();
|
||||||
|
|
||||||
|
if template.direction() != gst::PadDirection::Sink {
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_video(&self) -> bool {
|
pub fn is_video(&self) -> bool {
|
||||||
matches!(self.stream_type, gst::StreamType::VIDEO)
|
matches!(self.stream_type, gst::StreamType::VIDEO)
|
||||||
}
|
}
|
||||||
|
@ -608,11 +676,13 @@ impl Codec {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_encoder(&self) -> Option<Result<gst::Element, Error>> {
|
pub fn build_encoder(&self) -> Option<Result<gst::Element, Error>> {
|
||||||
self.encoding_info.as_ref().map(|info| {
|
self.encoding_info.as_ref().and_then(|info| {
|
||||||
info.encoder
|
info.encoder.as_ref().map(|encoder| {
|
||||||
.create()
|
encoder
|
||||||
.build()
|
.create()
|
||||||
.with_context(|| format!("Creating encoder {}", info.encoder.name()))
|
.build()
|
||||||
|
.with_context(|| format!("Creating encoder {}", encoder.name()))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -655,13 +725,17 @@ impl Codec {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn encoder_factory(&self) -> Option<gst::ElementFactory> {
|
pub fn encoder_factory(&self) -> Option<gst::ElementFactory> {
|
||||||
self.encoding_info.as_ref().map(|info| info.encoder.clone())
|
self.encoding_info
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|info| info.encoder.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn encoder_name(&self) -> Option<String> {
|
pub fn encoder_name(&self) -> Option<String> {
|
||||||
self.encoding_info
|
self.encoding_info.as_ref().and_then(|info| {
|
||||||
.as_ref()
|
info.encoder
|
||||||
.map(|info| info.encoder.name().to_string())
|
.as_ref()
|
||||||
|
.map(|encoder| encoder.name().to_string())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_output_filter(&mut self, caps: gst::Caps) {
|
pub fn set_output_filter(&mut self, caps: gst::Caps) {
|
||||||
|
@ -753,7 +827,7 @@ impl Codecs {
|
||||||
Self(codecs.values().cloned().collect())
|
Self(codecs.values().cloned().collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_for_encoded_caps(&self, caps: &gst::Caps) -> Option<Codec> {
|
pub fn find_for_payloadable_caps(&self, caps: &gst::Caps) -> Option<Codec> {
|
||||||
self.iter()
|
self.iter()
|
||||||
.find(|codec| codec.caps.can_intersect(caps) && codec.encoding_info.is_some())
|
.find(|codec| codec.caps.can_intersect(caps) && codec.encoding_info.is_some())
|
||||||
.cloned()
|
.cloned()
|
||||||
|
@ -766,6 +840,11 @@ static CODECS: Lazy<Codecs> = Lazy::new(|| {
|
||||||
gst::Rank::MARGINAL,
|
gst::Rank::MARGINAL,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let depayloaders = gst::ElementFactory::factories_with_type(
|
||||||
|
gst::ElementFactoryType::DEPAYLOADER,
|
||||||
|
gst::Rank::MARGINAL,
|
||||||
|
);
|
||||||
|
|
||||||
let encoders = gst::ElementFactory::factories_with_type(
|
let encoders = gst::ElementFactory::factories_with_type(
|
||||||
gst::ElementFactoryType::ENCODER,
|
gst::ElementFactoryType::ENCODER,
|
||||||
gst::Rank::MARGINAL,
|
gst::Rank::MARGINAL,
|
||||||
|
@ -782,14 +861,19 @@ static CODECS: Lazy<Codecs> = Lazy::new(|| {
|
||||||
gst::StreamType::AUDIO,
|
gst::StreamType::AUDIO,
|
||||||
&OPUS_CAPS,
|
&OPUS_CAPS,
|
||||||
&decoders,
|
&decoders,
|
||||||
|
&depayloaders,
|
||||||
&encoders,
|
&encoders,
|
||||||
&payloaders,
|
&payloaders,
|
||||||
),
|
),
|
||||||
|
Codec::new_raw("L24", gst::StreamType::AUDIO, &depayloaders, &payloaders),
|
||||||
|
Codec::new_raw("L16", gst::StreamType::AUDIO, &depayloaders, &payloaders),
|
||||||
|
Codec::new_raw("L8", gst::StreamType::AUDIO, &depayloaders, &payloaders),
|
||||||
Codec::new(
|
Codec::new(
|
||||||
"VP8",
|
"VP8",
|
||||||
gst::StreamType::VIDEO,
|
gst::StreamType::VIDEO,
|
||||||
&VP8_CAPS,
|
&VP8_CAPS,
|
||||||
&decoders,
|
&decoders,
|
||||||
|
&depayloaders,
|
||||||
&encoders,
|
&encoders,
|
||||||
&payloaders,
|
&payloaders,
|
||||||
),
|
),
|
||||||
|
@ -798,6 +882,7 @@ static CODECS: Lazy<Codecs> = Lazy::new(|| {
|
||||||
gst::StreamType::VIDEO,
|
gst::StreamType::VIDEO,
|
||||||
&H264_CAPS,
|
&H264_CAPS,
|
||||||
&decoders,
|
&decoders,
|
||||||
|
&depayloaders,
|
||||||
&encoders,
|
&encoders,
|
||||||
&payloaders,
|
&payloaders,
|
||||||
),
|
),
|
||||||
|
@ -806,6 +891,7 @@ static CODECS: Lazy<Codecs> = Lazy::new(|| {
|
||||||
gst::StreamType::VIDEO,
|
gst::StreamType::VIDEO,
|
||||||
&VP9_CAPS,
|
&VP9_CAPS,
|
||||||
&decoders,
|
&decoders,
|
||||||
|
&depayloaders,
|
||||||
&encoders,
|
&encoders,
|
||||||
&payloaders,
|
&payloaders,
|
||||||
),
|
),
|
||||||
|
@ -814,6 +900,7 @@ static CODECS: Lazy<Codecs> = Lazy::new(|| {
|
||||||
gst::StreamType::VIDEO,
|
gst::StreamType::VIDEO,
|
||||||
&H265_CAPS,
|
&H265_CAPS,
|
||||||
&decoders,
|
&decoders,
|
||||||
|
&depayloaders,
|
||||||
&encoders,
|
&encoders,
|
||||||
&payloaders,
|
&payloaders,
|
||||||
),
|
),
|
||||||
|
@ -822,9 +909,11 @@ static CODECS: Lazy<Codecs> = Lazy::new(|| {
|
||||||
gst::StreamType::VIDEO,
|
gst::StreamType::VIDEO,
|
||||||
&AV1_CAPS,
|
&AV1_CAPS,
|
||||||
&decoders,
|
&decoders,
|
||||||
|
&depayloaders,
|
||||||
&encoders,
|
&encoders,
|
||||||
&payloaders,
|
&payloaders,
|
||||||
),
|
),
|
||||||
|
Codec::new_raw("RAW", gst::StreamType::VIDEO, &depayloaders, &payloaders),
|
||||||
])
|
])
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -836,36 +925,16 @@ impl Codecs {
|
||||||
.cloned()
|
.cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn video_codecs() -> Vec<Codec> {
|
pub fn video_codecs<'a>() -> impl Iterator<Item = &'a Codec> {
|
||||||
CODECS
|
CODECS
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|codec| codec.stream_type == gst::StreamType::VIDEO)
|
.filter(|codec| codec.stream_type == gst::StreamType::VIDEO)
|
||||||
.cloned()
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn audio_codecs() -> Vec<Codec> {
|
pub fn audio_codecs<'a>() -> impl Iterator<Item = &'a Codec> {
|
||||||
CODECS
|
CODECS
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|codec| codec.stream_type == gst::StreamType::AUDIO)
|
.filter(|codec| codec.stream_type == gst::StreamType::AUDIO)
|
||||||
.cloned()
|
|
||||||
.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
|
/// List all codecs that can be used for encoding the given caps and assign
|
||||||
|
@ -909,9 +978,9 @@ impl Codecs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_raw_caps(caps: &gst::Caps) -> bool {
|
pub fn has_raw_caps(caps: &gst::Caps) -> bool {
|
||||||
assert!(caps.is_fixed());
|
caps.iter()
|
||||||
["video/x-raw", "audio/x-raw"].contains(&caps.structure(0).unwrap().name().as_str())
|
.any(|s| ["video/x-raw", "audio/x-raw"].contains(&s.name().as_str()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cleanup_codec_caps(mut caps: gst::Caps) -> gst::Caps {
|
pub fn cleanup_codec_caps(mut caps: gst::Caps) -> gst::Caps {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use crate::utils::{cleanup_codec_caps, is_raw_caps, make_element, Codec, Codecs, NavigationEvent};
|
use crate::utils::{
|
||||||
|
cleanup_codec_caps, has_raw_caps, make_element, Codec, Codecs, NavigationEvent,
|
||||||
|
};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
|
@ -13,6 +15,7 @@ use gst_webrtc::{WebRTCDataChannel, WebRTCICETransportPolicy};
|
||||||
use futures::prelude::*;
|
use futures::prelude::*;
|
||||||
|
|
||||||
use anyhow::{anyhow, Error};
|
use anyhow::{anyhow, Error};
|
||||||
|
use itertools::Itertools;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
@ -513,12 +516,12 @@ impl Default for Settings {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
video_caps: Codecs::video_codecs()
|
video_caps: Codecs::video_codecs()
|
||||||
.into_iter()
|
.filter(|codec| !codec.is_raw)
|
||||||
.flat_map(|codec| codec.caps.iter().map(|s| s.to_owned()).collect::<Vec<_>>())
|
.flat_map(|codec| codec.caps.iter().map(ToOwned::to_owned))
|
||||||
.collect::<gst::Caps>(),
|
.collect::<gst::Caps>(),
|
||||||
audio_caps: Codecs::audio_codecs()
|
audio_caps: Codecs::audio_codecs()
|
||||||
.into_iter()
|
.filter(|codec| !codec.is_raw)
|
||||||
.flat_map(|codec| codec.caps.iter().map(|s| s.to_owned()).collect::<Vec<_>>())
|
.flat_map(|codec| codec.caps.iter().map(ToOwned::to_owned))
|
||||||
.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>),
|
||||||
|
@ -884,7 +887,12 @@ impl PayloadChainBuilder {
|
||||||
codec = self.codec,
|
codec = self.codec,
|
||||||
);
|
);
|
||||||
|
|
||||||
let needs_encoding = is_raw_caps(&self.input_caps);
|
let needs_encoding = if self.codec.is_raw {
|
||||||
|
!self.codec.caps.can_intersect(&self.input_caps)
|
||||||
|
} else {
|
||||||
|
has_raw_caps(&self.input_caps)
|
||||||
|
};
|
||||||
|
|
||||||
let mut elements: Vec<gst::Element> = Vec::new();
|
let mut elements: Vec<gst::Element> = Vec::new();
|
||||||
|
|
||||||
let (raw_filter, encoder) = if needs_encoding {
|
let (raw_filter, encoder) = if needs_encoding {
|
||||||
|
@ -898,14 +906,20 @@ impl PayloadChainBuilder {
|
||||||
let raw_filter = self.codec.raw_converter_filter()?;
|
let raw_filter = self.codec.raw_converter_filter()?;
|
||||||
elements.push(raw_filter.clone());
|
elements.push(raw_filter.clone());
|
||||||
|
|
||||||
let encoder = self
|
let encoder = if self.codec.is_raw {
|
||||||
.codec
|
None
|
||||||
.build_encoder()
|
} else {
|
||||||
.expect("We should always have an encoder for negotiated codecs")?;
|
let encoder = self
|
||||||
elements.push(encoder.clone());
|
.codec
|
||||||
elements.push(make_element("capsfilter", None)?);
|
.build_encoder()
|
||||||
|
.expect("We should always have an encoder for negotiated codecs")?;
|
||||||
|
elements.push(encoder.clone());
|
||||||
|
elements.push(make_element("capsfilter", None)?);
|
||||||
|
|
||||||
(Some(raw_filter), Some(encoder))
|
Some(encoder)
|
||||||
|
};
|
||||||
|
|
||||||
|
(Some(raw_filter), encoder)
|
||||||
} else {
|
} else {
|
||||||
(None, None)
|
(None, None)
|
||||||
};
|
};
|
||||||
|
@ -3431,7 +3445,7 @@ impl BaseWebRTCSink {
|
||||||
) -> Result<gst::Structure, Error> {
|
) -> Result<gst::Structure, Error> {
|
||||||
let pipe = PipelineWrapper(gst::Pipeline::default());
|
let pipe = PipelineWrapper(gst::Pipeline::default());
|
||||||
|
|
||||||
let has_raw_input = is_raw_caps(&input_caps);
|
let has_raw_input = has_raw_caps(&input_caps);
|
||||||
let src = discovery_info.create_src();
|
let src = discovery_info.create_src();
|
||||||
let mut elements = vec![src.clone().upcast::<gst::Element>()];
|
let mut elements = vec![src.clone().upcast::<gst::Element>()];
|
||||||
let encoding_chain_src = if codec.is_video() && has_raw_input {
|
let encoding_chain_src = if codec.is_video() && has_raw_input {
|
||||||
|
@ -3616,29 +3630,7 @@ impl BaseWebRTCSink {
|
||||||
output_caps: gst::Caps,
|
output_caps: gst::Caps,
|
||||||
codecs: &Codecs,
|
codecs: &Codecs,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let futs = if let Some(codec) = codecs.find_for_encoded_caps(&discovery_info.caps) {
|
let futs = if has_raw_caps(&discovery_info.caps) {
|
||||||
let mut caps = discovery_info.caps.clone();
|
|
||||||
|
|
||||||
gst::info!(
|
|
||||||
CAT,
|
|
||||||
imp = self,
|
|
||||||
"Stream is already encoded with codec {}, still need to payload it",
|
|
||||||
codec.name
|
|
||||||
);
|
|
||||||
|
|
||||||
caps = cleanup_codec_caps(caps);
|
|
||||||
|
|
||||||
vec![self.run_discovery_pipeline(
|
|
||||||
&name,
|
|
||||||
&discovery_info,
|
|
||||||
codec,
|
|
||||||
caps,
|
|
||||||
&output_caps,
|
|
||||||
ExtensionConfigurationType::Auto,
|
|
||||||
)]
|
|
||||||
} else {
|
|
||||||
let sink_caps = discovery_info.caps.clone();
|
|
||||||
|
|
||||||
if codecs.is_empty() {
|
if codecs.is_empty() {
|
||||||
return Err(anyhow!(
|
return Err(anyhow!(
|
||||||
"No codec available for encoding stream {}, \
|
"No codec available for encoding stream {}, \
|
||||||
|
@ -3648,10 +3640,12 @@ impl BaseWebRTCSink {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let sink_caps = discovery_info.caps.clone();
|
||||||
|
|
||||||
let is_video = match sink_caps.structure(0).unwrap().name().as_str() {
|
let is_video = match sink_caps.structure(0).unwrap().name().as_str() {
|
||||||
"video/x-raw" => true,
|
"video/x-raw" => true,
|
||||||
"audio/x-raw" => false,
|
"audio/x-raw" => false,
|
||||||
_ => anyhow::bail!("Unsupported caps: {}", discovery_info.caps),
|
_ => anyhow::bail!("expected audio or video raw caps: {sink_caps}"),
|
||||||
};
|
};
|
||||||
|
|
||||||
codecs
|
codecs
|
||||||
|
@ -3668,6 +3662,28 @@ impl BaseWebRTCSink {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
} else if let Some(codec) = codecs.find_for_payloadable_caps(&discovery_info.caps) {
|
||||||
|
let mut caps = discovery_info.caps.clone();
|
||||||
|
|
||||||
|
gst::info!(
|
||||||
|
CAT,
|
||||||
|
imp = self,
|
||||||
|
"Stream is already in the {} format, we still need to payload it",
|
||||||
|
codec.name
|
||||||
|
);
|
||||||
|
|
||||||
|
caps = cleanup_codec_caps(caps);
|
||||||
|
|
||||||
|
vec![self.run_discovery_pipeline(
|
||||||
|
&name,
|
||||||
|
&discovery_info,
|
||||||
|
codec,
|
||||||
|
caps,
|
||||||
|
&output_caps,
|
||||||
|
ExtensionConfigurationType::Auto,
|
||||||
|
)]
|
||||||
|
} else {
|
||||||
|
anyhow::bail!("Unsupported caps: {}", discovery_info.caps);
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut payloader_caps = gst::Caps::new_empty();
|
let mut payloader_caps = gst::Caps::new_empty();
|
||||||
|
@ -3949,12 +3965,20 @@ impl ObjectImpl for BaseWebRTCSink {
|
||||||
vec![
|
vec![
|
||||||
glib::ParamSpecBoxed::builder::<gst::Caps>("video-caps")
|
glib::ParamSpecBoxed::builder::<gst::Caps>("video-caps")
|
||||||
.nick("Video encoder caps")
|
.nick("Video encoder caps")
|
||||||
.blurb("Governs what video codecs will be proposed")
|
.blurb(&format!("Governs what video codecs will be proposed. Valid values: [{}]",
|
||||||
|
Codecs::video_codecs()
|
||||||
|
.map(|c| c.caps.to_string())
|
||||||
|
.join("; ")
|
||||||
|
))
|
||||||
.mutable_ready()
|
.mutable_ready()
|
||||||
.build(),
|
.build(),
|
||||||
glib::ParamSpecBoxed::builder::<gst::Caps>("audio-caps")
|
glib::ParamSpecBoxed::builder::<gst::Caps>("audio-caps")
|
||||||
.nick("Audio encoder caps")
|
.nick("Audio encoder caps")
|
||||||
.blurb("Governs what audio codecs will be proposed")
|
.blurb(&format!("Governs what audio codecs will be proposed. Valid values: [{}]",
|
||||||
|
Codecs::audio_codecs()
|
||||||
|
.map(|c| c.caps.to_string())
|
||||||
|
.join("; ")
|
||||||
|
))
|
||||||
.mutable_ready()
|
.mutable_ready()
|
||||||
.build(),
|
.build(),
|
||||||
glib::ParamSpecString::builder("stun-server")
|
glib::ParamSpecString::builder("stun-server")
|
||||||
|
@ -4397,7 +4421,8 @@ impl ElementImpl for BaseWebRTCSink {
|
||||||
gst::CapsFeatures::new([D3D11_MEMORY_FEATURE]),
|
gst::CapsFeatures::new([D3D11_MEMORY_FEATURE]),
|
||||||
);
|
);
|
||||||
|
|
||||||
for codec in Codecs::video_codecs() {
|
// Ignore specific raw caps from Codecs: they are covered by video/x-raw & audio/x-raw
|
||||||
|
for codec in Codecs::video_codecs().filter(|codec| !codec.is_raw) {
|
||||||
caps_builder = caps_builder.structure(codec.caps.structure(0).unwrap().to_owned());
|
caps_builder = caps_builder.structure(codec.caps.structure(0).unwrap().to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4412,7 +4437,7 @@ impl ElementImpl for BaseWebRTCSink {
|
||||||
|
|
||||||
let mut caps_builder =
|
let mut caps_builder =
|
||||||
gst::Caps::builder_full().structure(gst::Structure::builder("audio/x-raw").build());
|
gst::Caps::builder_full().structure(gst::Structure::builder("audio/x-raw").build());
|
||||||
for codec in Codecs::audio_codecs() {
|
for codec in Codecs::audio_codecs().filter(|codec| !codec.is_raw) {
|
||||||
caps_builder = caps_builder.structure(codec.caps.structure(0).unwrap().to_owned());
|
caps_builder = caps_builder.structure(codec.caps.structure(0).unwrap().to_owned());
|
||||||
}
|
}
|
||||||
let audio_pad_template = gst::PadTemplate::with_gtype(
|
let audio_pad_template = gst::PadTemplate::with_gtype(
|
||||||
|
|
|
@ -9,6 +9,7 @@ use anyhow::{Context, Error};
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
use gst::subclass::prelude::*;
|
use gst::subclass::prelude::*;
|
||||||
use gst_webrtc::WebRTCDataChannel;
|
use gst_webrtc::WebRTCDataChannel;
|
||||||
|
use itertools::Itertools;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::borrow::BorrowMut;
|
use std::borrow::BorrowMut;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
@ -94,14 +95,18 @@ impl ObjectImpl for BaseWebRTCSrc {
|
||||||
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: [{}]",
|
.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(", ")
|
Codecs::video_codecs()
|
||||||
|
.map(|c| c.name.as_str())
|
||||||
|
.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: [{}]",
|
.blurb(&format!("Names of audio codecs to be be used during the SDP negotiation. Valid values: [{}]",
|
||||||
Codecs::audio_codec_names().into_iter().collect::<Vec<String>>().join(", ")
|
Codecs::audio_codecs()
|
||||||
|
.map(|c| c.name.as_str())
|
||||||
|
.join(", ")
|
||||||
))
|
))
|
||||||
.element_spec(&glib::ParamSpecString::builder("audio-codec-name").build())
|
.element_spec(&glib::ParamSpecString::builder("audio-codec-name").build())
|
||||||
.build(),
|
.build(),
|
||||||
|
@ -271,12 +276,12 @@ impl Default for Settings {
|
||||||
signaller: signaller.upcast(),
|
signaller: signaller.upcast(),
|
||||||
meta: Default::default(),
|
meta: Default::default(),
|
||||||
audio_codecs: Codecs::audio_codecs()
|
audio_codecs: Codecs::audio_codecs()
|
||||||
.into_iter()
|
.filter(|codec| !codec.is_raw)
|
||||||
.filter(|codec| codec.has_decoder())
|
.cloned()
|
||||||
.collect(),
|
.collect(),
|
||||||
video_codecs: Codecs::video_codecs()
|
video_codecs: Codecs::video_codecs()
|
||||||
.into_iter()
|
.filter(|codec| !codec.is_raw)
|
||||||
.filter(|codec| codec.has_decoder())
|
.cloned()
|
||||||
.collect(),
|
.collect(),
|
||||||
enable_data_channel_navigation: DEFAULT_ENABLE_DATA_CHANNEL_NAVIGATION,
|
enable_data_channel_navigation: DEFAULT_ENABLE_DATA_CHANNEL_NAVIGATION,
|
||||||
do_retransmission: DEFAULT_DO_RETRANSMISSION,
|
do_retransmission: DEFAULT_DO_RETRANSMISSION,
|
||||||
|
@ -630,7 +635,13 @@ impl Session {
|
||||||
}
|
}
|
||||||
srcpad.set_target(Some(&ghostpad)).unwrap();
|
srcpad.set_target(Some(&ghostpad)).unwrap();
|
||||||
} else {
|
} else {
|
||||||
gst::debug!(CAT, obj = element, "Unused webrtcbin pad {webrtcbin_pad:?}");
|
gst::debug!(
|
||||||
|
CAT,
|
||||||
|
obj = element,
|
||||||
|
"Unused webrtcbin pad {} {:?}",
|
||||||
|
webrtcbin_pad.name(),
|
||||||
|
webrtcbin_pad.current_caps(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
ghostpad
|
ghostpad
|
||||||
}
|
}
|
||||||
|
@ -1326,11 +1337,13 @@ impl BaseWebRTCSrc {
|
||||||
impl ElementImpl for BaseWebRTCSrc {
|
impl ElementImpl for BaseWebRTCSrc {
|
||||||
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(|| {
|
||||||
|
// Ignore specific raw caps from Codecs: they are covered by VIDEO_CAPS & AUDIO_CAPS
|
||||||
|
|
||||||
let mut video_caps_builder = gst::Caps::builder_full()
|
let mut video_caps_builder = gst::Caps::builder_full()
|
||||||
.structure_with_any_features(VIDEO_CAPS.structure(0).unwrap().to_owned())
|
.structure_with_any_features(VIDEO_CAPS.structure(0).unwrap().to_owned())
|
||||||
.structure(RTP_CAPS.structure(0).unwrap().to_owned());
|
.structure(RTP_CAPS.structure(0).unwrap().to_owned());
|
||||||
|
|
||||||
for codec in Codecs::video_codecs() {
|
for codec in Codecs::video_codecs().filter(|codec| !codec.is_raw) {
|
||||||
video_caps_builder =
|
video_caps_builder =
|
||||||
video_caps_builder.structure(codec.caps.structure(0).unwrap().to_owned());
|
video_caps_builder.structure(codec.caps.structure(0).unwrap().to_owned());
|
||||||
}
|
}
|
||||||
|
@ -1339,7 +1352,7 @@ impl ElementImpl for BaseWebRTCSrc {
|
||||||
.structure_with_any_features(AUDIO_CAPS.structure(0).unwrap().to_owned())
|
.structure_with_any_features(AUDIO_CAPS.structure(0).unwrap().to_owned())
|
||||||
.structure(RTP_CAPS.structure(0).unwrap().to_owned());
|
.structure(RTP_CAPS.structure(0).unwrap().to_owned());
|
||||||
|
|
||||||
for codec in Codecs::audio_codecs() {
|
for codec in Codecs::audio_codecs().filter(|codec| !codec.is_raw) {
|
||||||
audio_caps_builder =
|
audio_caps_builder =
|
||||||
audio_caps_builder.structure(codec.caps.structure(0).unwrap().to_owned());
|
audio_caps_builder.structure(codec.caps.structure(0).unwrap().to_owned());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue