Merge pull request #49 from flussonic/dev
Add verbose output to stream info and info cmd to example client
This commit is contained in:
commit
8ab5808054
15
README.md
15
README.md
@ -12,6 +12,7 @@ brokenness in cheap closed-source cameras.
|
|||||||
Progress:
|
Progress:
|
||||||
|
|
||||||
* [x] client support
|
* [x] client support
|
||||||
|
* [x] basic authentication.
|
||||||
* [x] digest authentication.
|
* [x] digest authentication.
|
||||||
* [x] RTP over TCP via RTSP interleaved channels.
|
* [x] RTP over TCP via RTSP interleaved channels.
|
||||||
* [x] RTP over UDP (experimental).
|
* [x] RTP over UDP (experimental).
|
||||||
@ -63,12 +64,24 @@ Help welcome!
|
|||||||
Try the `mp4` example. It streams from an RTSP server to a `.mp4` file until
|
Try the `mp4` example. It streams from an RTSP server to a `.mp4` file until
|
||||||
you hit ctrl-C.
|
you hit ctrl-C.
|
||||||
|
|
||||||
```
|
```shell
|
||||||
$ cargo run --example client mp4 --url rtsp://ip.address.goes.here/ --username admin --password test out.mp4
|
$ cargo run --example client mp4 --url rtsp://ip.address.goes.here/ --username admin --password test out.mp4
|
||||||
...
|
...
|
||||||
^C
|
^C
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Example client
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ cargo run --example client <CMD>
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
## Acknowledgements
|
## Acknowledgements
|
||||||
|
|
||||||
This builds on the whole Rust ecosystem. A couple folks have been especially
|
This builds on the whole Rust ecosystem. A couple folks have been especially
|
||||||
|
@ -28,8 +28,12 @@ struct Source {
|
|||||||
|
|
||||||
#[derive(StructOpt)]
|
#[derive(StructOpt)]
|
||||||
enum Cmd {
|
enum Cmd {
|
||||||
|
/// Write available audio and video streams to mp4 file
|
||||||
Mp4(mp4::Opts),
|
Mp4(mp4::Opts),
|
||||||
|
/// Get realtime metadata of onvif stream, use Ctrl+C to stop
|
||||||
Metadata(metadata::Opts),
|
Metadata(metadata::Opts),
|
||||||
|
/// Get info about available streams and exit
|
||||||
|
Info(metadata::Opts),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_logging() -> mylog::Handle {
|
fn init_logging() -> mylog::Handle {
|
||||||
@ -78,6 +82,7 @@ async fn main_inner() -> Result<(), Error> {
|
|||||||
let cmd = Cmd::from_args();
|
let cmd = Cmd::from_args();
|
||||||
match cmd {
|
match cmd {
|
||||||
Cmd::Mp4(opts) => mp4::run(opts).await,
|
Cmd::Mp4(opts) => mp4::run(opts).await,
|
||||||
Cmd::Metadata(opts) => metadata::run(opts).await,
|
Cmd::Metadata(opts) => metadata::run(opts, false).await,
|
||||||
|
Cmd::Info(opts) => metadata::run(opts, true).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,16 +14,20 @@ pub struct Opts {
|
|||||||
src: super::Source,
|
src: super::Source,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(opts: Opts) -> Result<(), Error> {
|
pub async fn run(opts: Opts, is_info: bool) -> Result<(), Error> {
|
||||||
let session_group = Arc::new(SessionGroup::default());
|
let session_group = Arc::new(SessionGroup::default());
|
||||||
let r = run_inner(opts, session_group.clone()).await;
|
let r = run_inner(opts, session_group.clone(), is_info).await;
|
||||||
if let Err(e) = session_group.await_teardown().await {
|
if let Err(e) = session_group.await_teardown().await {
|
||||||
error!("TEARDOWN failed: {}", e);
|
error!("TEARDOWN failed: {}", e);
|
||||||
}
|
}
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_inner(opts: Opts, session_group: Arc<SessionGroup>) -> Result<(), Error> {
|
async fn run_inner(
|
||||||
|
opts: Opts,
|
||||||
|
session_group: Arc<SessionGroup>,
|
||||||
|
is_info: bool,
|
||||||
|
) -> Result<(), Error> {
|
||||||
let stop = tokio::signal::ctrl_c();
|
let stop = tokio::signal::ctrl_c();
|
||||||
|
|
||||||
let creds = super::creds(opts.src.username, opts.src.password);
|
let creds = super::creds(opts.src.username, opts.src.password);
|
||||||
@ -35,6 +39,12 @@ async fn run_inner(opts: Opts, session_group: Arc<SessionGroup>) -> Result<(), E
|
|||||||
.session_group(session_group),
|
.session_group(session_group),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
if is_info {
|
||||||
|
for stream in session.streams() {
|
||||||
|
println!("{:#?}", stream);
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
let onvif_stream_i = session
|
let onvif_stream_i = session
|
||||||
.streams()
|
.streams()
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -546,7 +546,6 @@ pub struct Presentation {
|
|||||||
/// Information about a stream offered within a presentation.
|
/// Information about a stream offered within a presentation.
|
||||||
///
|
///
|
||||||
/// Currently if multiple formats are offered, this only describes the first.
|
/// Currently if multiple formats are offered, this only describes the first.
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Stream {
|
pub struct Stream {
|
||||||
/// Media type, as specified in the [IANA SDP parameters media
|
/// Media type, as specified in the [IANA SDP parameters media
|
||||||
/// registry](https://www.iana.org/assignments/sdp-parameters/sdp-parameters.xhtml#sdp-parameters-1).
|
/// registry](https://www.iana.org/assignments/sdp-parameters/sdp-parameters.xhtml#sdp-parameters-1).
|
||||||
@ -593,7 +592,22 @@ pub struct Stream {
|
|||||||
state: StreamState,
|
state: StreamState,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl std::fmt::Debug for Stream {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||||
|
f.debug_struct("Stream")
|
||||||
|
.field("media", &self.media)
|
||||||
|
.field("control", &self.control.as_ref().map(Url::as_str))
|
||||||
|
.field("encoding_name", &self.encoding_name)
|
||||||
|
.field("rtp_payload_type", &self.rtp_payload_type)
|
||||||
|
.field("clock_rate", &self.clock_rate)
|
||||||
|
.field("channels", &self.channels)
|
||||||
|
.field("depacketizer", &self.depacketizer)
|
||||||
|
.field("UDP", &self.sockets)
|
||||||
|
.field("state", &self.state)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct UdpSockets {
|
struct UdpSockets {
|
||||||
local_ip: IpAddr,
|
local_ip: IpAddr,
|
||||||
local_rtp_port: u16,
|
local_rtp_port: u16,
|
||||||
@ -604,6 +618,20 @@ struct UdpSockets {
|
|||||||
rtcp_socket: UdpSocket,
|
rtcp_socket: UdpSocket,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for UdpSockets {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||||
|
f.debug_struct("UDP")
|
||||||
|
.field("local_ip", &self.local_ip)
|
||||||
|
.field("local_rtp_port", &self.local_rtp_port)
|
||||||
|
.field("remote_ip", &self.remote_ip)
|
||||||
|
.field("remote_rtp_port", &self.remote_rtp_port)
|
||||||
|
.field("rtp_socket", &self.rtp_socket)
|
||||||
|
.field("remote_rtcp_port", &self.remote_rtcp_port)
|
||||||
|
.field("rtcp_socket", &self.rtcp_socket)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Stream {
|
impl Stream {
|
||||||
/// Returns the parameters for this stream.
|
/// Returns the parameters for this stream.
|
||||||
///
|
///
|
||||||
|
Loading…
Reference in New Issue
Block a user