mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer-rs.git
synced 2024-09-27 06:20:05 +00:00
157 lines
6.9 KiB
Rust
157 lines
6.9 KiB
Rust
// This example demonstrates how to set up a rtsp server using GStreamer.
|
|
// While the "rtsp-server" example is about streaming media to connecting
|
|
// clients, this example is mainly about recording media that clients
|
|
// send to the server. For this, the launch syntax pipeline, that is passed
|
|
// to this example's cli is spawned and the client's media is streamed into it.
|
|
|
|
use std::{env, ptr};
|
|
|
|
use anyhow::Error;
|
|
use derive_more::{Display, Error};
|
|
use glib::translate::*;
|
|
use gst_rtsp_server::prelude::*;
|
|
|
|
#[path = "../examples-common.rs"]
|
|
mod examples_common;
|
|
|
|
#[derive(Debug, Display, Error)]
|
|
#[display(fmt = "Could not get mount points")]
|
|
struct NoMountPoints;
|
|
|
|
#[derive(Debug, Display, Error)]
|
|
#[display(fmt = "Usage: {_0} LAUNCH_LINE")]
|
|
struct UsageError(#[error(not(source))] String);
|
|
|
|
fn main_loop() -> Result<(), Error> {
|
|
let args: Vec<_> = env::args().collect();
|
|
|
|
if args.len() != 2 {
|
|
return Err(Error::from(UsageError(args[0].clone())));
|
|
}
|
|
|
|
// Mostly analog to the rtsp-server example, the server is created
|
|
// and the factory for our test mount is configured.
|
|
let main_loop = glib::MainLoop::new(None, false);
|
|
let server = gst_rtsp_server::RTSPServer::new();
|
|
// Much like HTTP servers, RTSP servers have multiple endpoints that
|
|
// provide or take different streams. Here, we ask our server to give
|
|
// us a reference to its list of endpoints, so we can add our
|
|
// test endpoint.
|
|
let mounts = server.mount_points().ok_or(NoMountPoints)?;
|
|
// Next, we create a factory for the endpoint we want to create.
|
|
// The job of the factory is to create a new pipeline for each client that
|
|
// connects, or (if configured to do so) to reuse an existing pipeline.
|
|
let factory = gst_rtsp_server::RTSPMediaFactory::new();
|
|
// Here we configure a method of authentication that we want the
|
|
// server to require from clients.
|
|
let auth = gst_rtsp_server::RTSPAuth::new();
|
|
let token = gst_rtsp_server::RTSPToken::new(&[(
|
|
gst_rtsp_server::RTSP_TOKEN_MEDIA_FACTORY_ROLE,
|
|
&"user",
|
|
)]);
|
|
let basic = gst_rtsp_server::RTSPAuth::make_basic("user", "password");
|
|
// For proper authentication, we want to use encryption. And there's no
|
|
// encryption without a certificate!
|
|
let cert = gio::TlsCertificate::from_pem(
|
|
"-----BEGIN CERTIFICATE-----\
|
|
MIICJjCCAY+gAwIBAgIBBzANBgkqhkiG9w0BAQUFADCBhjETMBEGCgmSJomT8ixk\
|
|
ARkWA0NPTTEXMBUGCgmSJomT8ixkARkWB0VYQU1QTEUxHjAcBgNVBAsTFUNlcnRp\
|
|
ZmljYXRlIEF1dGhvcml0eTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5jb20xHTAbBgkq\
|
|
hkiG9w0BCQEWDmNhQGV4YW1wbGUuY29tMB4XDTExMDExNzE5NDcxN1oXDTIxMDEx\
|
|
NDE5NDcxN1owSzETMBEGCgmSJomT8ixkARkWA0NPTTEXMBUGCgmSJomT8ixkARkW\
|
|
B0VYQU1QTEUxGzAZBgNVBAMTEnNlcnZlci5leGFtcGxlLmNvbTBcMA0GCSqGSIb3\
|
|
DQEBAQUAA0sAMEgCQQDYScTxk55XBmbDM9zzwO+grVySE4rudWuzH2PpObIonqbf\
|
|
hRoAalKVluG9jvbHI81eXxCdSObv1KBP1sbN5RzpAgMBAAGjIjAgMAkGA1UdEwQC\
|
|
MAAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQEFBQADgYEAYx6fMqT1\
|
|
Gvo0jq88E8mc+bmp4LfXD4wJ7KxYeadQxt75HFRpj4FhFO3DOpVRFgzHlOEo3Fwk\
|
|
PZOKjvkT0cbcoEq5whLH25dHoQxGoVQgFyAP5s+7Vp5AlHh8Y/vAoXeEVyy/RCIH\
|
|
QkhUlAflfDMcrrYjsmwoOPSjhx6Mm/AopX4=\
|
|
-----END CERTIFICATE-----\
|
|
-----BEGIN PRIVATE KEY-----\
|
|
MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEA2EnE8ZOeVwZmwzPc\
|
|
88DvoK1ckhOK7nVrsx9j6TmyKJ6m34UaAGpSlZbhvY72xyPNXl8QnUjm79SgT9bG\
|
|
zeUc6QIDAQABAkBRFJZ32VbqWMP9OVwDJLiwC01AlYLnka0mIQZbT/2xq9dUc9GW\
|
|
U3kiVw4lL8v/+sPjtTPCYYdzHHOyDen6znVhAiEA9qJT7BtQvRxCvGrAhr9MS022\
|
|
tTdPbW829BoUtIeH64cCIQDggG5i48v7HPacPBIH1RaSVhXl8qHCpQD3qrIw3FMw\
|
|
DwIga8PqH5Sf5sHedy2+CiK0V4MRfoU4c3zQ6kArI+bEgSkCIQCLA1vXBiE31B5s\
|
|
bdHoYa1BXebfZVd+1Hd95IfEM5mbRwIgSkDuQwV55BBlvWph3U8wVIMIb4GStaH8\
|
|
W535W8UBbEg=-----END PRIVATE KEY-----",
|
|
)?;
|
|
|
|
// Bindable versions were added in b1f515178a363df0322d7adbd5754e1f6e2083c9
|
|
// This declares that the user "user" (once authenticated) has a role that
|
|
// allows them to access and construct media factories.
|
|
unsafe {
|
|
gst_rtsp_server::ffi::gst_rtsp_media_factory_add_role(
|
|
factory.to_glib_none().0,
|
|
"user".to_glib_none().0,
|
|
gst_rtsp_server::RTSP_PERM_MEDIA_FACTORY_ACCESS
|
|
.to_glib_none()
|
|
.0,
|
|
<bool as StaticType>::static_type().into_glib() as *const u8,
|
|
true.into_glib() as *const u8,
|
|
gst_rtsp_server::RTSP_PERM_MEDIA_FACTORY_CONSTRUCT.as_ptr() as *const u8,
|
|
<bool as StaticType>::static_type().into_glib() as *const u8,
|
|
true.into_glib() as *const u8,
|
|
ptr::null_mut::<u8>(),
|
|
);
|
|
}
|
|
|
|
auth.set_tls_certificate(Some(&cert));
|
|
auth.add_basic(basic.as_str(), &token);
|
|
// Here, we tell the RTSP server about the authentication method we
|
|
// configured above.
|
|
server.set_auth(Some(&auth));
|
|
|
|
factory.set_launch(args[1].as_str());
|
|
// Tell the RTSP server that we want to work in RECORD mode (clients send)
|
|
// data to us.
|
|
factory.set_transport_mode(gst_rtsp_server::RTSPTransportMode::RECORD);
|
|
// The RTSP protocol allows a couple of different profiles for the actually
|
|
// used protocol of data-transmission. With this, we can limit the selection
|
|
// from which connecting clients have to choose.
|
|
// SAVP/SAVPF are via SRTP (encrypted), that's what the S is for.
|
|
// The F in the end is for feedback (an extension that allows more bidirectional
|
|
// feedback between sender and receiver). AV is just Audio/Video, P is Profile :)
|
|
// The default, old RTP profile is AVP
|
|
factory.set_profiles(gst_rtsp::RTSPProfile::SAVP | gst_rtsp::RTSPProfile::SAVPF);
|
|
|
|
// Now we add a new mount-point and tell the RTSP server to use the factory
|
|
// we configured beforehand. This factory will take on the job of creating
|
|
// a pipeline, which will take on the incoming data of connected clients.
|
|
mounts.add_factory("/test", factory);
|
|
|
|
// Attach the server to our main context.
|
|
// A main context is the thing where other stuff is registering itself for its
|
|
// events (e.g. sockets, GStreamer bus, ...) and the main loop is something that
|
|
// polls the main context for its events and dispatches them to whoever is
|
|
// interested in them. In this example, we only do have one, so we can
|
|
// leave the context parameter empty, it will automatically select
|
|
// the default one.
|
|
let id = server.attach(None)?;
|
|
|
|
println!(
|
|
"Stream ready at rtsps://127.0.0.1:{}/test",
|
|
server.bound_port()
|
|
);
|
|
|
|
// Start the mainloop. From this point on, the server will start to take
|
|
// incoming connections from clients.
|
|
main_loop.run();
|
|
|
|
id.remove();
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn example_main() -> Result<(), Error> {
|
|
gst::init()?;
|
|
main_loop()
|
|
}
|
|
|
|
fn main() {
|
|
match examples_common::run(example_main) {
|
|
Ok(r) => r,
|
|
Err(e) => eprintln!("Error! {e}"),
|
|
}
|
|
}
|