diff --git a/README.md b/README.md index d0b1d5f..7a4fe59 100644 --- a/README.md +++ b/README.md @@ -78,9 +78,9 @@ $ cargo run --example client Where CMD: -* **info** - Get info about available streams and exit -* **mp4** - Write RTSP streams to mp4 file, exit with Ctrl+C -* **metadata** - Get realtime onvif metadata if available, exit with Ctrl+C +* **info** - Gets info about available streams and exits. +* **mp4** - Writes RTSP streams to mp4 file; exit with Ctrl+C. +* **metadata** - Gets realtime onvif metadata if available; exit with Ctrl+C. ## Acknowledgements diff --git a/examples/client/info.rs b/examples/client/info.rs new file mode 100644 index 0000000..93e500f --- /dev/null +++ b/examples/client/info.rs @@ -0,0 +1,43 @@ +// Copyright (C) 2022 Scott Lamb +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Gets info about available streams and exits. + +use anyhow::Error; + +#[derive(structopt::StructOpt)] +pub struct Opts { + #[structopt(flatten)] + src: super::Source, + + /// Prints the SDP (Session Description Protocol) session description. + #[structopt(long)] + print_sdp: bool, + + /// Prints debug output for each decoded stream. + #[structopt(long)] + print_streams: bool, +} + +pub async fn run(opts: Opts) -> Result<(), Error> { + let creds = super::creds(opts.src.username.clone(), opts.src.password.clone()); + let session = retina::client::Session::describe( + opts.src.url.clone(), + retina::client::SessionOptions::default() + .creds(creds) + .user_agent("Retina sdp example".to_owned()), + ) + .await?; + if opts.print_sdp { + println!("SDP:\n{}\n\n", std::str::from_utf8(session.sdp())?); + } + if opts.print_streams { + for (i, stream) in session.streams().iter().enumerate() { + println!("stream {}:\n{:#?}\n\n", i, stream); + } + } + if !opts.print_sdp && !opts.print_streams { + eprintln!("You probably wanted at least one of --print-sdp or --print-streams?"); + } + Ok(()) +} diff --git a/examples/client/main.rs b/examples/client/main.rs index 35aa50f..4c658f4 100644 --- a/examples/client/main.rs +++ b/examples/client/main.rs @@ -3,8 +3,9 @@ //! RTSP client examples. -mod metadata; +mod info; mod mp4; +mod onvif; use anyhow::Error; use log::{error, info}; @@ -28,12 +29,12 @@ struct Source { #[derive(StructOpt)] enum Cmd { - /// Write available audio and video streams to mp4 file + /// Gets info about available streams and exits. + Info(info::Opts), + /// Writes available audio and video streams to mp4 file; use Ctrl+C to stop. Mp4(mp4::Opts), - /// Get realtime metadata of onvif stream, use Ctrl+C to stop - Metadata(metadata::Opts), - /// Get info about available streams and exit - Info(metadata::Opts), + /// Follows ONVIF metadata stream; use Ctrl+C to stop. + Onvif(onvif::Opts), } fn init_logging() -> mylog::Handle { @@ -81,8 +82,8 @@ fn creds( async fn main_inner() -> Result<(), Error> { let cmd = Cmd::from_args(); match cmd { + Cmd::Info(opts) => info::run(opts).await, Cmd::Mp4(opts) => mp4::run(opts).await, - Cmd::Metadata(opts) => metadata::run(opts, false).await, - Cmd::Info(opts) => metadata::run(opts, true).await, + Cmd::Onvif(opts) => onvif::run(opts).await, } } diff --git a/examples/client/metadata.rs b/examples/client/onvif.rs similarity index 82% rename from examples/client/metadata.rs rename to examples/client/onvif.rs index 7fd86df..2d3f25d 100644 --- a/examples/client/metadata.rs +++ b/examples/client/onvif.rs @@ -14,20 +14,16 @@ pub struct Opts { src: super::Source, } -pub async fn run(opts: Opts, is_info: bool) -> Result<(), Error> { +pub async fn run(opts: Opts) -> Result<(), Error> { let session_group = Arc::new(SessionGroup::default()); - let r = run_inner(opts, session_group.clone(), is_info).await; + let r = run_inner(opts, session_group.clone()).await; if let Err(e) = session_group.await_teardown().await { error!("TEARDOWN failed: {}", e); } r } -async fn run_inner( - opts: Opts, - session_group: Arc, - is_info: bool, -) -> Result<(), Error> { +async fn run_inner(opts: Opts, session_group: Arc) -> Result<(), Error> { let stop = tokio::signal::ctrl_c(); let creds = super::creds(opts.src.username, opts.src.password); @@ -39,12 +35,6 @@ async fn run_inner( .session_group(session_group), ) .await?; - if is_info { - for stream in session.streams() { - println!("{:#?}", stream); - } - return Ok(()); - } let onvif_stream_i = session .streams() .iter() diff --git a/src/client/mod.rs b/src/client/mod.rs index d10781f..54ccdc4 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -758,7 +758,9 @@ pub trait State {} /// Initial state after a `DESCRIBE`; use via `Session`. #[doc(hidden)] -pub struct Described(()); +pub struct Described { + sdp: Bytes, +} impl State for Described {} enum KeepaliveState { @@ -1127,6 +1129,8 @@ impl Session { description, }) })?; + let describe_status = response.status(); + let sdp = response.into_body(); Ok(Session( Box::pin(SessionInner { conn: Some(conn), @@ -1137,16 +1141,25 @@ impl Session { session: None, describe_ctx: msg_ctx, describe_cseq: cseq, - describe_status: response.status(), + describe_status, keepalive_state: KeepaliveState::Idle, keepalive_timer: None, maybe_playing: false, udp_next_poll_i: 0, }), - Described(()), + Described { sdp }, )) } + /// Returns the raw SDP (Session Description Protocol) session description of this URL. + /// + /// Retina interprets the SDP automatically, but the raw bytes may be useful for debugging. + /// They're accessibled in the `Session` state. Currently, they're discarded on + /// `play` to reduce memory usage. + pub fn sdp(&self) -> &[u8] { + &self.1.sdp + } + /// Sends a `SETUP` request for a stream. /// /// Note these can't reasonably be pipelined because subsequent requests