Coder Social home page Coder Social logo

retina's Introduction

retina

crates.io version Documentation CI

High-level RTSP multimedia streaming library, in Rust. Good support for ONVIF RTSP/1.0 IP surveillance cameras, as needed by Moonfire NVR. Works around brokenness in cheap closed-source cameras.

Status: In production use in Moonfire NVR. Many missing features.

Progress:

  • client support
    • basic authentication.
    • digest authentication.
    • RTP over TCP via RTSP interleaved channels.
    • RTP over UDP (experimental).
      • re-order buffer. (Out-of-order packets are dropped now.)
    • RTSP/1.0.
    • RTSP/2.0.
    • SRTP.
    • ONVIF backchannel support (for sending audio).
    • ONVIF replay mode.
    • receiving RTCP Sender Reports (currently only uses the timestamp)
    • sending RTCP Receiver Reports
  • server support
  • I/O modes
    • async with tokio
    • async-std
    • synchronous with std only
  • codec depacketization
    • video
      • H.264
        • SVC
        • periodic infra refresh
        • multiple slices per picture
        • multiple SPS/PPS
        • interleaved mode
        • AAC output format
        • Annex B output format (#44)
        • (RFC 6184)
      • MJPEG
    • audio
      • AAC
        • interleaving
      • RFC 3551 codecs: G.711, G.723, L8/L16
    • application: ONVIF metadata
  • clean, stable API. (See #47.)
  • quality errors
    • detailed error description text.
    • programmatically inspectable error type.
  • good functional testing coverage. (Currently lightly / unevenly tested. Most depacketizers have no tests.)
  • fuzz testing. (In progress.)
  • benchmark

Help welcome!

Getting started

Try the mp4 example. It streams from an RTSP server to a .mp4 file until you hit ctrl-C.

$ cargo run --package client mp4 --url rtsp://ip.address.goes.here/ --username admin --password test out.mp4
...
^C

Example client

$ cargo run --package client <CMD>

Where CMD:

  • info - Gets info about available streams and exits.
  • mp4 - Writes RTSP streams to mp4 file; exit with Ctrl+C.
  • onvif - Gets realtime onvif metadata if available; exit with Ctrl+C.
  • jpeg - Writes depacketized JPEG images to disk; exit with CTRL+C.

Example WebRTC proxy

This allows viewing a H.264 video stream from your browser, with the help of webrtc-rs.

$ cargo run --package webrtc-proxy -- --help

Acknowledgements

This builds on the whole Rust ecosystem. A couple folks have been especially helpful:

Why "retina"?

It's a working name. Other ideas welcome. I started by looking at dictionary words with the letters R, T, S, and P in order and picking out ones related to video:

$ egrep '^r.*t.*s.*p' /usr/share/dict/words'
retinoscope close but too long, thus retina
retrospect good name for an NVR, but I already picked Moonfire
rotascope misspelling of "rotascope" (animation tool) or archaic name for "gyroscope"?

License

Your choice of MIT or Apache; see LICENSE-MIT.txt or LICENSE-APACHE, respectively.

retina's People

Contributors

curid avatar kodx avatar lattice0 avatar nemosupremo avatar scottlamb avatar spencercw avatar valpackett avatar yujincheng08 avatar zanshi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

retina's Issues

ONVIF backchannel support

Hello,
I know it's not implemented yet but I want to understand a future interface and maybe implement it.
Currently, Session<Subscribe> has not had so many options.

Enhancement Request: Wrapper for logging continuous access

Currently the client halts when an error is encountered.

Would it be possible without much effort to have a wrapper around the example program where errors are treated as they are in moonfire-nvr and the same recourse, e.g. attempt opening a new connection or continue within a set time (this might be parameterized, too), resorted to? It would be helpful to have some parameterized values such as wait-timeout-before-reconnecting which one could use to determine what time is needed before Reolink cameras will accept a new connection and treat it as new rather than resuming an old one.

I have witnessed that if I connect and error will occur. If I immediately launch the example script again, it looks like the client example script immediately errors out because of transmissions from Reolink from the prior session. This is my guess. I find if I wait a few minutes, I can at least make a new connection and get some data before another error is encountered. This time from an opening connection to an error condition seems to vary from 4 seconds to 20 seconds to 1 minute. I can see why Reolink camera's rtsp servers are frowned upon.

Goal: I would like to be able to point multiple sessions of this client to cameras and create log files that document the errors encountered such as in moonfire-nvr. This is to be able to stress test a camera and/or network.

What's good about this client is that it can be run in parallel with other session unlike in moonfire-nvr where everything has to be coordinated for the database.

I tried to make sens of lines 46-57 to see if I could create an indefinite loop, but my lack of understanding rust programming caused me to realize I do not know the fundamental loop structures, not do I know (without doing some code study of moonfire-nvr) what higher level handling occurs when errors are encountered.

Configurable Kill Timeout and Possible Preferred Timeout?

I created a script which launches multiple test rtsp clients against an array of Reolink cameras. I noticed the many of the instances would end, some with the error message that the default time of 10 seconds had been exceeded. So I modified mp4.rs changing 10 to 30 at line 568.

I've noticed a substantial improvement -- whereas some connections would die with seconds, now all four remain connected after several minutes. (Going on 11 minutes now without any dropping, whereas with 10 I had some drop almost instantly.)

A user-configurable timeout on the command line would be very helpful.

Moreover, a more elegant solution might be to have two settings... an absolute timeout where the program ends, and then a preferred timeout which, if the time between packets exceeds, yet remains under the absolute value, the date/time and delay value is stored to be printed out upon termination. This would give the user an indication of what might be a more appropriate setting. Example: currently the kill timeout is 10 seconds. Change the code by having a preferred timeout at 10 seconds and a kill timeout at 30. Then when someone like me with delays runs, I'll get a good run and I can kill all the processes after an interval, e.g. 5 minutes, and then evaluate what the seconds were between 10 and 30 to fine-tune a future kill timeout.

I'll try to do this by some hacking, but I'm finding my Perl-like assumptions about code management are really being challenged in rust.

example: Fatal: Failed precondition: must SETUP before PLAY

root@39e12bac9e7a:/home/dev/external/retina# cargo run --example client mp4 --url rtsp://192.168.1.22:10554/udp/av0_0 --username admin --password 123456 out.mp4
    Finished dev [unoptimized + debuginfo] target(s) in 0.27s
     Running `target/debug/examples/client mp4 --url 'rtsp://192.168.1.22:10554/udp/av0_0' --username admin --password 123456 out.mp4`
E20211128 20:55:52.260 main client] Fatal: Failed precondition: must SETUP before PLAY


h.265 support

It would be nice to have support for H.265, since my devices support it. I already read in a different issue that you haven't added support for it yet. You could use this as a tracking issue whenever you decide to add it, if you want to :)

Build Fails

git cloned my forked copy (jlpooleen/retina) into /usr/local/src

 cd retina
 cargo build
 ...
   Compiling retina v0.0.1 (/usr/local/src/retina)
error[E0658]: use of unstable library feature 'str_split_once': newly added
   --> src/client/parse.rs:273:18
    |
273 |                 .split_once(' ')
    |                  ^^^^^^^^^^
    |
    = note: see issue #74773 <https://github.com/rust-lang/rust/issues/74773> for more information

error[E0658]: use of unstable library feature 'str_split_once': newly added
   --> src/client/parse.rs:285:18
    |
285 |                 .split_once(' ')
    |                  ^^^^^^^^^^
    |
    = note: see issue #74773 <https://github.com/rust-lang/rust/issues/74773> for more information

error[E0658]: use of unstable library feature 'str_split_once': newly added
   --> src/client/parse.rs:310:18
    |
310 |                 .split_once('/')
    |                  ^^^^^^^^^^
    |
    = note: see issue #74773 <https://github.com/rust-lang/rust/issues/74773> for more information

error[E0658]: use of unstable library feature 'str_split_once': newly added
   --> src/client/parse.rs:553:18
    |
553 |                 .split_once('=')
    |                  ^^^^^^^^^^
    |
    = note: see issue #74773 <https://github.com/rust-lang/rust/issues/74773> for more information

error[E0658]: use of unstable library feature 'str_split_once': newly added
   --> src/codec/aac.rs:323:14
    |
323 |             .split_once('=')
    |              ^^^^^^^^^^
    |
    = note: see issue #74773 <https://github.com/rust-lang/rust/issues/74773> for more information

error[E0658]: use of unstable library feature 'str_split_once': newly added
   --> src/codec/h264.rs:472:41
    |
472 |             let (key, value) = p.trim().split_once('=').unwrap();
    |                                         ^^^^^^^^^^
    |
    = note: see issue #74773 <https://github.com/rust-lang/rust/issues/74773> for more information

error: aborting due to 6 previous errors

For more information about this error, try `rustc --explain E0658`.
error: could not compile `retina`

To learn more, run the command again with --verbose.
warning: build failed, waiting for other jobs to finish...
error: build failed
ares /usr/local/src/retina #

Followed suggested explanation of error:

ares /usr/local/src/retina # rustc --explain E0658
#[repr(u128)] // error: use of unstable library feature 'repr128'
enum Foo {
    Bar(u64),
}
```

If you're using a stable or a beta version of rustc, you won't be able to use
any unstable features. In order to do so, please switch to a nightly version of
rustc (by using [rustup]).

If you're using a nightly version of rustc, just add the corresponding feature
to be able to use it:

```
#![feature(repr128)]

#[repr(u128)] // ok!
enum Foo {
    Bar(u64),
}
```

[rustup]: https://rust-lang.github.io/rustup/concepts/channels.html

Here's what I have installed (high watermark from Gentoo repository):

ares /usr/local/src/retina # eix -I rust
[I] app-eselect/eselect-rust
     Available versions:  20200419
     Installed versions:  20200419(16:34:41 02/07/21)
     Homepage:            https://wiki.gentoo.org/wiki/Project:Rust
     Description:         Eselect module for management of multiple Rust versions

[I] dev-lang/rust
     Available versions:  (stable) 1.51.0-r2(stable/1.51)^t ~1.52.1(stable/1.52)^t
       {clippy debug doc miri nightly parallel-compiler rls rustfmt system-bootstrap system-llvm test verify-sig wasm ABI_MIPS="n32 n64 o32" ABI_S390="32 64" ABI_X86="32 64 x32" CPU_FLAGS_X86="sse2" LLVM_TARGETS="AArch64 AMDGPU ARM AVR BPF Hexagon Lanai MSP430 Mips NVPTX PowerPC RISCV Sparc SystemZ WebAssembly X86 XCore"}
     Installed versions:  1.51.0-r2(stable/1.51)^t(22:22:19 05/12/21)(-clippy -debug -doc -miri -nightly -parallel-compiler -rls -rustfmt -system-bootstrap -system-llvm -test -verify-sig -wasm ABI_MIPS="-n32 -n64 -o32" ABI_S390="-32 -64" ABI_X86="64 -32 -x32" CPU_FLAGS_X86="sse2" LLVM_TARGETS="X86 -AArch64 -AMDGPU -ARM -AVR -BPF -Hexagon -Lanai -MSP430 -Mips -NVPTX -PowerPC -RISCV -Sparc -SystemZ -WebAssembly -XCore")
     Homepage:            https://www.rust-lang.org/
     Description:         Systems programming language from Mozilla

[I] virtual/rust
     Available versions:  1.51.0 ~1.52.1 {ABI_MIPS="n32 n64 o32" ABI_S390="32 64" ABI_X86="32 64 x32"}
     Installed versions:  1.51.0(22:23:52 05/12/21)(ABI_MIPS="-n32 -n64 -o32" ABI_S390="-32 -64" ABI_X86="64 -32 -x32")
     Description:         Virtual for Rust language compiler

Found 3 matches
ares /usr/local/src/retina #

ares /usr/local/src/retina # cargo -V
cargo 1.51.0
ares /usr/local/src/retina #

Save RTSP in "fragmented" mp4 -> *.ts.

hi Scott,

is it possible to save RTSP in "fragmented / fmp4 / *.ts " mp4 to get rid of "seek" when recording?

        self.inner
            .seek(SeekFrom::Start(u64::from(self.mdat_start - 8)))
            .await?;
        self.inner
            .write_all(&u32::try_from(self.mdat_pos + 8 - self.mdat_start)?.to_be_bytes()[..])
            .await?;

errors when feeding VStarcam data to ffmpeg

As discussed in #13, connecting to a VStarcam camera and feeding its frames to ffmpeg produced ffmpeg errors. From discussion there:

@LucasZanella wrote:

However, on my app, while the frame producing works, passing retina::codec::VideoFrame::data().borrow() to the ffmpeg nal units parser sometimes reject, and sometimes parse and send to the decoder, which procues

[h264 @ 0x559374083fc0] non-existing PPS 16 referenced
[h264 @ 0x559374083fc0] Invalid NAL unit 0, skipping.
[h264 @ 0x559374083fc0] Invalid NAL unit 0, skipping.
[h264 @ 0x559374083fc0] Invalid NAL unit 0, skipping.
[h264 @ 0x559374083fc0] Invalid NAL unit 0, skipping.
[h264 @ 0x559374083fc0] no frame!

Here's one VideoFrame:

[2021-07-27T19:52:20Z INFO liborwell::rtsp::retina_client] video frame: VideoFrame { timestamp: 189124 (mod-2^32: 189124), npt 2.061, start_ctx: RtspMessageContext { pos: 42441, received_wall: WallTime(Timespec { sec: 1627415540, nsec: 361449153 }), received: Instant { tv_sec: 421014, tv_nsec: 736674930 } }, end_ctx: RtspMessageContext { pos: 42441, received_wall: WallTime(Timespec { sec: 1627415540, nsec: 361449153 }), received: Instant { tv_sec: 421014, tv_nsec: 736674930 } }, loss: 0, new_parameters: None, is_random_access_point: false, is_disposable: false, data_len: 383 }

I wrote:

I haven't tried feeding video directly from retina to ffmpeg yet, but in principle it should work. The frames should be fine to pass to ffmpeg. How are you setting up the stream with ffmpeg? You'll likely need to pass it the extra_data from VideoParameters.

The log messages from ffmpeg suggest it's not seeing a valid stream—NAL unit types should never be 0, and I think it's rare for the PPS id to be 16 rather than 0. But maybe the problem is just that without the extra data, ffmpeg is expecting a stream in Annex B format, and my code is passing it instead in AVC format. (The former means that NAL units are separated by the bytes 00 00 01, and the latter means that each NAL unit is preceded by its length in bytes as a big-endian number which can be 2, 3, or 4 bytes long. My code uses 4 bytes.) If you prefer to get Annex B data, it'd be possible to add a knob to retina to tell it that. Or conversion isn't terribly difficult: you can scan through NAL units and change the prefix to be 00 00 01.

I suppose I could add a retina example that decodes with ffmpeg into raw images or something. What ffmpeg crate are you using?

When you don't get the packet follows marked packet with same timestamp error, have you tried saving a .mp4 and playing it back in your favorite video player? Does it work?


@LucasZanella wrote:

For ffmpeg I'm using https://github.com/lucaszanella/rust-ffmpeg-1 which uses https://github.com/lucaszanella/rust-ffmpeg-sys-1 (this one is not needed, I just added some vdpau linking stuff, the original could be used). I had to modify the rust-ffmpeg-1 to add support for ffmpeg's av_parser_parse2 which parses the individual nal units. The original project doe snot have this and he doesn't want to maintain. My patch is very experimental.

I haven't tried feeding video directly from retina to ffmpeg yet, but in principle it should work. The frames should be fine to pass to ffmpeg. How are you setting up the stream with ffmpeg? You'll likely need to pass it the extra_data from VideoParameters.

I've never needed to pass additional parameters to ffmpeg, just the nal units. I extracted the h264 bitstream from a big buck bunny .mp4 file and passed to ffmpeg calling av_parser_parse2 to break into individual nal units and then passed those units using avcodec_send_packet and it works. The same process is not working for retina. When my code used to be all C++, I used to pass the output of ZLMediaKit to ffmpeg in this way also and it worked.

Even though av_parser_parse2 has the option to pass pts, dts, etc, I never used but I'll read more about these parameters.

VideoParameters debug:

Some(Video(VideoParameters { rfc6381_codec: "avc1.4D002A", pixel_dimensions: (1920, 1080), pixel_aspect_ratio: None, frame_rate: Some((2, 15)), extra_data: Length: 41 (0x29) bytes
0000:   01 4d 00 2a  ff e1 00 1a  67 4d 00 2a  9d a8 1e 00   .M.*....gM.*....
0010:   89 f9 66 e0  20 20 28 00  00 03 00 08  00 00 03 00   ..f.  (.........
0020:   7c 20 01 00  04 68 ee 3c  80                         | ...h.<. }))

I've sent you a dump of the camera via email.

If you prefer to get Annex B data, it'd be possible to add a knob to retina to tell it that. Or conversion isn't terribly difficult: you can scan through NAL units and change the prefix to be 00 00 01.

do you have experience in which types the rtsp clients out there do these things? I've never took a deep look on how ZLMediaKit does, I simply used it and now I'm getting deeper into RTSP/RTP/h264/etc because rust had no rtsp clients so I had to make one.

This is how I extracted the big buck bunny to make it work:

ffmpeg -i BigBuckBunny_512kb.mp4 -vbsf h264_mp4toannexb -vcodec copy -an big_buck_bunny_1280_720.h264

as you see by h264_mp4toannexb, it's as you supposed.

May I know why you use the AVC format in your code? Isn't the Annex B proper for streaming?

custom TCP/UDP transporter support

I'm integrating retina in my project right now and there's one crucial thing I'll need: a custom TCP/UDP transporter.

Rust's hyper HTTP crate supports a custom TCP transporter though something that implements AsyncWrite/AsyncRead. Can I make a PR to add support for this? I did not read yet how you make the TCP calls but would be nice to have the same support as hyper. I don't know if your library relies on tokio streams. If not, that could be difficult. Do you have suggestions?

Here's how I did on my client: https://github.com/lattice0/rust_rtsp_client/blob/489491ee70f6432dca801c49053d55b13264524e/src/client.rs#L181

Here's a test I did on hyper using a custom transporter: https://github.com/lattice0/rust_hyper_custom_transporter/blob/master/src/custom_req.rs

About UDP, tokio has no traits for it so we'd have to think about how to do it.

interoperability doc

Write up a doc about how Retina's implementation compares and interoperates with other RTSP clients and servers. Some points to mention:

  • how it joins base URLs with control URLs. (As described in #9, other implementations do various crazy things.)
  • what the various policy knobs do and when they're necessary
  • other camera-specific bugs it works around
  • anything it expects that other RTSP libraries don't and why. (eg from #13 it cares more about the RTP marker flag and timestamps than ffmpeg does, and I think the advantage is trimming a frame of latency. but it seems we'll need at least need to relax this a little.)

Maybe a chart of different RTSP implementations' choices would be helpful. I also like to have links to the relevant parts of other open source libraries.

Unable to parse SDP: No origin line.

Copied over from scottlamb/moonfire-nvr#187 . It'd be nice to interop with cameras that send the following non-compliant SDP. Currently, Retina produces an Unable to parse SDP: No origin line error message. While the error message is correct, we don't actually use the origin line, so there's no reason we can't tolerate its absence.

v=0
s=streamed by the macro-video rtsp server
t=0 0
a=control:*
a=range:npt=0-
a=x-qt-text-nam:streamed by the macro-video rtsp server
c=IN IP4 0.0.0.0
m=video 0 RTP/AVP 96
b=AS:500
a=rtpmap:96 H264/90000
a=fmtp:96 profile-level-id=TeAo;packetization-mode=1;sprop-parameter-sets=J03gKI1oBQBboQAAAwABAAADACgPFCKg,KO4BNJI=
a=control:track1

The error originates in the sdp-types crate, so we'll probably address it there as mentioned in the moonfire-nvr bug. Then in Retina:

  • bump Cargo.toml to pick up the new sdp-types
  • add a test case in case we switch SDP crates again or something,
  • mention the workaround in the change log and release a new version for Moonfire NVR and other software to use

Wireshark Filter Recommendations

I'm using wireshark and currently I have in effect a filter using the ip of the camera I'm testing retina's client against. My filter consists of the simple:

 host 192.168.1.48

My camera is assigned the IP 192.168.1.48. Are there any other filters you recommend which might abbreviate the log file? For instance, perhaps all the data packets are not desired, just events. Using ffmpeg for 24 seconds generates a wireshark log file of 69MB.

I think having this "Issue" would be helpful as a location for recommendations of what filter settings to use for particular classes of investigation.

VStarcam: "bad access unit" debug messages

With the new modifications, I'm getting a stream with no errors but this warning:

[2021-08-21T05:32:37Z DEBUG retina::codec::h264] bad access unit (ended by mark) at ts 1147 (mod-2^32: 1147), npt 0.000
    errors are:
    * same timestamp as previous access unit
    NALs are:
      0: NalHeader { nal_ref_idc: 3, nal_unit_type: SliceLayerWithoutPartitioningNonIdr }

should it happen? I had no time to look into it deep and I can provide more info if needed. I was just testing if everything went well with the merge.

I don't get decoded frames yet but I think this has to do with what we talked before about the VCL thing etc. By the way, do you have a place for me to read about those without getting into tedious details about h264 encoding internals?

I cleaned my ffmpeg code, it compiles on the retina branch but have to send packets from retina to it. Gonna try to make the example soon so you can test with your cameras too, if you want

Enhancement: piping output into retina

This is an enhancement request. I'm not sure if it is account for in the tree of tasks under the section server support -->
I/O modes --> async-std

The Raspberry Pi project libcamera has an example of running an rtsp server by running their program libcamera-vid and piping it into cVLC to handle the rtsp server portion. (The rtsp server for cVLC may be LIVE555, but I'm not sure.)

Following instructions at https://www.raspberrypi.com/documentation/accessories/camera.html#libcamera-and-libcamera-apps
which states to run a server, use this command line:

libcamera-vid -t 0 --inline -o - | cvlc stream:///dev/stdin --sout '#rtp{sdp=rtsp://:8554/stream1}' :demux=h264

Instead, I used this modified version which includes the IP address of the Pi server running this command:

libcamera-vid -t 0 --inline -o - | cvlc stream:///dev/stdin --sout '#rtp{sdp=rtsp://192.168.1.12:8554/stream1}' :demux=h264

I'm having problems with Moonfire-nvr running Retina and shaking hands with the Raspberry Pi Zero 2's instance of the above command line, See Moonfire-nvr Issue #192.

The above consists of two parts: 1) libcamera-vid getting the data from the attached camera then piping it int 2) cVLC. I'm wondering if Retina could easily be enhancement to accept piped input the way cVLC does so I could accomplishing something like:

  libcamera-vid -t 0 --inline -o - | retina.....

Either accepting STDIN, or some file descriptor one could configure.

access raw RTCP packet (headers and all)

Hello. To integrate Gstreamer and WebRTC into Retina, need to transfer the raw RTCP packet. Can you add SenderReport::raw(&self) -> &[u8]?

let launch = "rtpsession name=session \
    appsrc name=rtpsrc ! session.recv_rtp_sink \
    appsrc name=rtcpsrc ! session.recv_rtcp_sink \  
    session.recv_rtp_src ! rtph264depay ! h264parse ! vaapidecodebin ! autovideosink";

UDP

It'd be nice to support RTP/UDP in addition to RTP/TCP (interleaved channels) for at least a couple reasons:

  • counterintuitively, I think this would be more reliable with some cameras:
    • at least those affected by #17
    • even modern live555 servers because its buffer management is terrible. If a send buffer fills mid-packet, it will block the whole server on that one stream, so it might be better for live555 to not have any RTP/TCP clients at all.
  • possibly lower latency, depending on network conditions and implementation. At least in theory this comes at the expense of more dropped packets.

Work for receiving data over UDP:

  • separate "sessions" from "connections". They're separate types already, but they're always created and destroyed together, and I think some fields might be in the wrong place.
  • manage the UDP socket(s). I think the simplest model is for each UDP session to own its UDP socket(s) (maybe just one, or maybe up to two per stream: one for RTP, one for RTCP). If a session is dropped without TEARDOWN, it gets handed off to some background task that retries the teardown periodically until success or timeout, then closes the socket(s). Note this is more IO library-specific logic. (It's possible to share UDP sockets between sessions, but I'm not seeing any real advantage right now, and it adds complexity.)
  • in combination with #7: we'd need to define a trait for UDP, and we might want to take an "opener" rather than an existing TCP connection, so that we can retry opening the connection if it closes when we still want to send a TEARDOWN or other request.
  • consider how to handle out-of-order packets. On a LAN, I think we might get away with just dropping them, and that might be the only situation I care about for now. But I think other software like ffmpeg implements reorder buffers: if we're waiting for packet n and n+1 shows up, queue n+1 until at least x ms pass or the queue size exceeds y bytes.
  • send RTCP RR packets (reception reports) to help the server pace packets appropriately.

When sending data (a client using the ONVIF audio back channel, or a server, neither of which is implemented yet anyway), we'd also need to implement pacing.

support basic authentication

From scottlamb/moonfire-nvr#151. When configured with username and password, Anpviz IPC-D250 reports:

W20210823 05:40:40.871 s-ChuckRear-sub moonfire_nvr::streamer] ChuckRear-sub: sleeping for Duration { secs: 1, nanos: 0 } after error: [192.168.254.254:39730(me)->192.168.254.5:554@2021-08-23T05:40:40, 0@2021-08-23T05:40:40] Unauthorized response to DESCRIBE CSeq=1: Non-digest authentication requested: Basic realm="/"

AAC depacketization unit tests

Right now the loss_since_mark fields in aac.rs talk about how they're important for determining if a fragment is complete or not:

retina/src/codec/aac.rs

Lines 437 to 441 in 0178e7f

/// True iff there was loss immediately before the packet that started this
/// aggregate. The distinction between old and recent loss is relevant
/// because only the latter should be capable of causing following fragments
/// to be too short.
loss_since_mark: bool,

...but they're not actually used or passed on in the AudioFrame. That's suspect. But there are no tests, and I want to add tests before messing with the logic to know I'm making things better, not worse.

Kill Signal For Intended Termination

I'm creating a Perl script to launch multiple sessions of retina against an array of cameras. The purpose of this endeavor is to isolate error conditions and create a data set of unique error conditions which may be addressed in your development.

I just sent a "kill -HUP [process #}" and checked my output log of the process so-killed and there was no entry. What kind of signal should I send to a background running retina process so that it will gracefully shut down and the log will reflect the requested shutdown?

Here's a current example of the command I'm forking off (from a Perl script using "system($cmd)"):

 cargo run --example client mp4 /tmp/retina/Garage_East_20210605_073201.mp4 --url rtsp://192.168.1.49:554/h264Preview_01_main --username retina --password testingisfun  >/tmp/retina/Garage_East_20210605_073201.log  2>&1 &

I haven't, but will, isolate STD and STDERR, to see if the regular output of each frame can be isolated from error conditions.

Here's the last 5 lines of a log of a process launched in the format above where I sent the active process a "kill -HUP [process number]":

jlpoole@ares /usr/local/src/retina $ tail -n 5 /tmp/retina/Peck_West_Alley_20210605_073201.log
2729606549 (mod-2^32: 2729606549), npt 3820.544: 512-byte audio frame
3003918369 (mod-2^32: 3003918369), npt 3820.507: 21397-byte video frame
3003921339 (mod-2^32: 3003921339), npt 3820.540: 22484-byte video frame
3003924309 (mod-2^32: 3003924309), npt 3820.573: 20732-byte video frame
2729607573 (mod-2^32: 2729607573), npt 3820.608: 512-byte audio frame
jlpoole@ares
/usr/local/src/retina $

mp4 example: avoid half-written output file

Right now the .mp4 example writes directly to the output filename and only writes the moov information on success. So if there's an error (eg in #30 (comment)) you end up with a .mp4 file which doesn't play correctly. I don't want to complicate it too much (given that it's just an example rather than intended to be a fully productionized tool) but I think it'd be simple enough to do the following:

  • append .partial to the filename and only rename that part away after writing the index, so it's obvious when the file is half-written
  • on error, finish writing the indexes for the frames we already have, if any
  • if we don't have any frames when exiting, just delete the .partial file

check if we make unnecessary syscalls with UDP

When using Transport::Udp, <Session as futures::Stream>::poll_next calls into poll_udp, which ultimately polls each UDP socket:

while let Poll::Ready(r) = sockets.rtcp_socket.poll_recv(cx, buf) {

while let Poll::Ready(r) = sockets.rtp_socket.poll_recv(cx, buf) {

poll_next is (intended to be) called any time the Stream was awakened for any reason: any one of the sockets or the keepalive timeout.

I suspect these are actually turning into extra recv syscalls on each socket (even ones that epoll_wait hasn't said are ready since recv last returned EWOULDBLOCK) until we hit the one that actually is ready. (This thought was inspired by reading about performance problems with tokio's FuturesUnordered in this discussion. We're doing things in a round-robin order rather than using FuturesUnordered, but the effect is probably similar.)

There are typically about five sockets involved when using UDP (RTSP TCP socket, and (RTP, RTCP) x (video, audio)), and syscalls are expensive, so making up to 5X as many syscalls as necessary is bad.

We should check for this. Maybe just by examining strace output of a UDP session and/or inspecting the tokio code.

If we indeed are making unnecessary syscalls, we should restructure the code to avoid it. A couple possible structures:

  • Spawn a separate tokio task for each socket (and perhaps the keepalive), writing into a channel. Session's poll_next just reads the other side of the channel rather than doing any work itself. Remember to clean up the tasks when the Session is dropped.
  • Use the upcoming tokio TaskSet.

That might be worth doing even if tokio/mio stops us from reaching the syscall stage; that code might be expensive enough to be worth avoiding too.

It'd be nice to have a proper benchmark of UDP, too, like we have for TCP.

consider more efficient buffering model

Accumulate NAL unit fragments into a ring buffer instead of via ref-counted Bytes fragments.

Mirrored ring buffer?

My first idea: use a mirrored ring buffer (umsafe VM tricks) slice-deque or similar for the read buffer.

Advantages:

  • It'd be very easy to use large enough read buffers (#5) without memcpying to get contiguous slices.
  • We wouldn't need the bytes crate, which I think might be more a tokio-only thing than something familiar to async-std folks or folks using threads with pure std.
  • No allocations of bytes::BytesMut or their behind-the-scenes Shared. The only regularly occurring allocations would be for final frames.
  • the pieces array in h264::Depacketizer would be smaller: 16 bytes per element rather than the current (on 64-bit platforms) 32 bytes for a bytes::Bytes (plus another 32 bytes for the Shared mentioned above). Some CPU cache efficiency improvements.

Disadvantages:

  • slice-deque isn't well-maintained now unfortunately. gnzlbg/slice_deque#94 but there are enough people who care about it that it might be forked if necessary.
  • Creating and destroying sessions would be more expensive, but I don't think this happens often enough to care about.
  • Some internal implementation complexity as described below.

Here's how it would work:

  • We'd read from the TcpStream into the uninitialized portion of this buffer via tokio::io::AsyncRead::poll_read.
  • rather than rtsp_types::Message<Bytes>, we'd use rtsp_types::Message<Handle> or something which has a ref to the input buffer and the range within it.
  • depacketizers could keep around Range<u64> of input byte positions; Depacketizer::pull would get a const ref to the input buffer. It could use this to look up those ranges into slices into the buffer.
  • depacketizers would need to advertise their low water position (earliest byte they still need). Eg, in the H.264 depacketizer, this would be self.pieces.first().map(|p| p.0).
  • depacketizers would need to copy stuff before yielding it in the Stream (as it's impractical for the consumer to communicate its low water position back in this way), but I decided to do that anyway (#4).
  • the Stream that returns PacketItems would also need to copy for the same reason. I don't expect this stream to be commonly used anyway.
  • retina::client::Demuxed would use a BinaryHeap to track the lowest of the low water positions of each depacketizer, lazily updated. It'd drop data from the front of the input buffer accordingly.
  • the two streams (Demuxed which returns CodecItems, and the PacketItem stream currently built into Session<Playing>) would be implemented via a common ancestor instead of the former being implemented in terms of the latter.

safe ring buffer

But actually we could use a plain safe ring buffer. The only problematic case is when a ring wraps back over. In that case we can either:

  • do a contiguous read (just the end piece), and copy any incomplete portion over to the beginning of the ring before doing a second read.
  • do a writev style read (read into the end and beginning), and likewise if the beginning read is incomplete, memmove the second piece to make some extra space (expanding if necessary), then copy over the first piece.

The overhead from the extra copy is less than a single read so it's not a big deal.

slab for UDP

The ring buffer approach makes sense for TCP (both requests/replies and interleaved data messages). Less sense for UDP, particularly if using recvmmsg on Linux to support reading multiple messages at once. The problem is that we don't know the byte length of the packets, and the biggest possible packet (65,535 bytes - 20 (IP header size) - 8 (UDP header size) = 65,507 bytes) is much larger than a typical packet (non-fragmented, 1,500-byte MTU - 28 bytes of headers = 1,472 bytes), and I don't want 97+% waste.

So for UDP, here's another idea: have a slab of packet buffers, read into one of them with recvmsg or maybe multiple simultaneously with recvmmsg. Use scatter/gather into a slab-based primary buffer that's of reasonable length and an overflow buffer for the remainder. When overflow happens, we extend the primary buffer with the overflow. "Reasonable length" can be initially our PMTU length (to the peer, on the assumption the path is roughly symmetrical), then based on the largest length we've seen.

buffer size limits and API changes

We don't really have any limit on how much stuff we buffer right now. It's even worse when using TCP and the ring buffer and Demuxed, because if one stream stops mid-access unit, we'll grow and grow even if the other streams are all behaving. We should have a limit on the total size of the TCP ring buffer. Also for UDP we should have a limit on the total size we keep around. Perhaps within SessionOptions and UdpTransportOptions, respectively.

Related open API question: do we want to just allow access to the most recent packet (with the packet-level API) or frame (with the Demuxed API)? Probably makes sense then to stop using futures::Stream (which because Rust doesn't have GAT can't return items borrowed from the stream). Or do we want to allow the caller to hold onto things for longer (subject to the limits above)? If the latter, I think we need some sort of extra step where you check if the item you got is still good. And if we continue to have these be Send/Sync, we need to have some sort of internal locking.

GeoVision GV-EBD4701 SDP won't parse

{
    'Manufacturer': 'GeoVision_2',
    'Model': 'GV-EBD4701',
    'FirmwareVersion': 'V101_2020_10_29',
    'SerialNumber': '000017807364',
    'HardwareId': 'GV-EBD4701'
}
v=0
o=- 1001 1 IN IP4 192.168.5.237
s=VCP IPC Realtime stream
m=video 0 RTP/AVP 105
c=IN IP4 192.168.5.237
a=control:rtsp://192.168.5.237/media/video1/video
a=rtpmap:105 H264/90000
a=fmtp:105 profile-level-id=4d4032; packetization-mode=1; sprop-parameter-sets=Z01AMpWgCoAwfiZuAgICgAAB9AAAdTBC,aO48gA==
a=recvonly
m=application 0 RTP/AVP 107
c=IN IP4 192.168.5.237
a=control:rtsp://192.168.5.237/media/video1/metadata
a=rtpmap:107 vnd.onvif.metadata/90000
a=fmtp:107 DecoderTag=h3c-v3 RTCP=0
a=recvonly

The sdp crate says: SdpInvalidSyntax: m=.

I believe the problem is that according to RFC 8866 section 9 it's supposed to have a t= first.

ffmpeg example

I'd like to write an ffmpeg example. I already have some ffmpeg decode code that almost works with retina, but do you think the dev-dependencies would have too many deps? Because ffmpeg brings too many deps:

├── ffmpeg-next v4.4.0-dev (/home/dev/orwell/deps/rust-ffmpeg-1)
│   ├── bitflags v1.3.1
│   ├── ffmpeg-sys-next v4.3.4 (/home/dev/orwell/deps/rust-ffmpeg-sys-1)
│   │   └── libc v0.2.99
│   │   [build-dependencies]
│   │   ├── bindgen v0.54.0
│   │   │   ├── bitflags v1.3.1
│   │   │   ├── cexpr v0.4.0
│   │   │   │   └── nom v5.1.2
│   │   │   │       └── memchr v2.4.0
│   │   │   │       [build-dependencies]
│   │   │   │       └── version_check v0.9.3
│   │   │   ├── cfg-if v0.1.10
│   │   │   ├── clang-sys v0.29.3
│   │   │   │   ├── glob v0.3.0
│   │   │   │   ├── libc v0.2.99
│   │   │   │   └── libloading v0.5.2
│   │   │   │       [build-dependencies]
│   │   │   │       └── cc v1.0.69
│   │   │   │   [build-dependencies]
│   │   │   │   └── glob v0.3.0
│   │   │   ├── clap v2.33.3
│   │   │   │   ├── ansi_term v0.11.0
│   │   │   │   ├── atty v0.2.14 (*)
│   │   │   │   ├── bitflags v1.3.1
│   │   │   │   ├── strsim v0.8.0
│   │   │   │   ├── textwrap v0.11.0
│   │   │   │   │   └── unicode-width v0.1.8
│   │   │   │   ├── unicode-width v0.1.8
│   │   │   │   └── vec_map v0.8.2
│   │   │   ├── env_logger v0.7.1
│   │   │   │   ├── atty v0.2.14 (*)
│   │   │   │   ├── humantime v1.3.0
│   │   │   │   │   └── quick-error v1.2.3
│   │   │   │   ├── log v0.4.14 (*)
│   │   │   │   ├── regex v1.5.4 (*)
│   │   │   │   └── termcolor v1.1.2
│   │   │   ├── lazy_static v1.4.0
│   │   │   ├── lazycell v1.3.0
│   │   │   ├── log v0.4.14 (*)
│   │   │   ├── peeking_take_while v0.1.2
│   │   │   ├── proc-macro2 v1.0.28 (*)
│   │   │   ├── quote v1.0.9 (*)
│   │   │   ├── regex v1.5.4 (*)
│   │   │   ├── rustc-hash v1.1.0
│   │   │   ├── shlex v0.1.1
│   │   │   └── which v3.1.1
│   │   │       └── libc v0.2.99
│   │   ├── cc v1.0.69
│   │   ├── num_cpus v1.13.0 (*)
│   │   └── pkg-config v0.3.19
│   └── libc v0.2.99

should I do it as a separate project? If so, should I put it inside retina or should I do another separate repo?

no control url

My VStarcam camera is failing here:

https://github.com/scottlamb/retina/blob/main/src/client/parse.rs#L424

as you can see, it has no control attribute, but it has control attributes on the media descriptions.

sdp: SessionDescription { version: 0, origin: Origin { username: "VSTC", session_id: 3836310016, session_version: 3836310016, network_type: "IN", address_type: "IP4", unicast_address: "192.168.1.198" }, session_name: "streamed by the VSTARCAM RTSP server", session_information: None, uri: None, email_address: Some("NONE"), phone_number: None, connection_information: Some(ConnectionInformation { network_type: "IN", address_type: "IP4", address: Some(Address { address: "0.0.0.0", ttl: None, range: None }) }), bandwidth: [], time_descriptions: [TimeDescription { timing: Timing { start_time: 0, stop_time: 0 }, repeat_times: [] }], time_zones: [], encryption_key: None, attributes: [], media_descriptions: [MediaDescription { media_name: MediaName { media: "video", port: RangedPort { value: 0, range: None }, protos: ["RTP", "AVP"], formats: ["96"] }, media_title: None, connection_information: None, bandwidth: [Bandwidth { experimental: false, bandwidth_type: "AS", bandwidth: 1536 }], encryption_key: None, attributes: [Attribute { key: "control", value: Some("track0") }, Attribute { key: "rtpmap", value: Some("96 H264/90000") }, Attribute { key: "fmtp", value: Some("96 packetization-mode=1;profile-level-id=4d002a;sprop-parameter-sets=Z00AKp2oHgCJ+WbgICAoAAADAAgAAAMAfCA=,aO48gA==") }] }, MediaDescription { media_name: MediaName { media: "audio", port: RangedPort { value: 0, range: None }, protos: ["RTP", "AVP"], formats: ["8"] }, media_title: None, connection_information: None, bandwidth: [Bandwidth { experimental: false, bandwidth_type: "AS", bandwidth: 64 }], encryption_key: None, attributes: [Attribute { key: "control", value: Some("track1") }, Attribute { key: "rtpmap", value: Some("8 PCMA/8000/1") }] }] }

Here's the raw response:

RTSP/1.0 200 OK
Cseq: 2
Date: Mon, Jul 26 2021 17:35:15 GMT
Content-Type: application/sdp
Content-Length: 403

v=0
o=VSTC 3836309504 3836309504 IN IP4 192.168.1.198
s=streamed by the VSTARCAM RTSP server
e=NONE
c=IN IP4 0.0.0.0
t=0 0
m=video 0 RTP/AVP 96
b=AS:1536
a=control:track0
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1;profile-level-id=4d002a;sprop-parameter-sets=Z00AKp2oHgCJ+WbgICAoAAADAAgAAAMAfCA=,aO48gA==
m=audio 0 RTP/AVP 8	 
b=AS:64
a=control:track1
a=rtpmap:8 PCMA/8000/1

is the control really needed?

Anpviz IPC-D250 SDP won't parse

scottlamb/moonfire-nvr#151 (comment)

This currently produces the error Unable to parse SDP: SdpInvalidSyntax: a= (using the sdp crate parser). Similarly, the sdp-types parser says Unable to parse SDP: Unexpected line 5 starting with 'a'. (Note that 5 is apparently a 0-indexed line number.)

hex dump:

Length: 827 (0x33b) bytes
0000:   76 3d 30 0d  0a 6f 3d 2d  20 31 31 30  39 31 36 32   v=0..o=- 1109162
0010:   30 31 34 32  31 39 31 38  32 20 31 31  30 39 31 36   014219182 110916
0020:   32 30 31 34  32 31 39 31  39 32 20 49  4e 20 49 50   2014219192 IN IP
0030:   34 20 78 2e  79 2e 7a 2e  77 0d 0a 73  3d 52 54 53   4 x.y.z.w..s=RTS
0040:   50 2f 52 54  50 20 73 74  72 65 61 6d  20 66 72 6f   P/RTP stream fro
0050:   6d 20 61 6e  6a 76 69 73  69 6f 6e 20  69 70 63 61   m anjvision ipca
0060:   6d 65 72 61  0d 0a 65 3d  4e 4f 4e 45  0d 0a 63 3d   mera..e=NONE..c=
0070:   49 4e 20 49  50 34 20 30  2e 30 2e 30  2e 30 0d 0a   IN IP4 0.0.0.0..
0080:   61 3d 74 6f  6f 6c 3a 4c  49 56 45 35  35 35 20 53   a=tool:LIVE555 S
0090:   74 72 65 61  6d 69 6e 67  20 4d 65 64  69 61 20 76   treaming Media v
00a0:   32 30 31 31  2e 30 35 2e  32 35 20 43  48 41 4d 2e   2011.05.25 CHAM.
00b0:   4c 49 40 41  4e 4a 56 49  53 49 4f 4e  2e 43 4f 4d   [email protected]
00c0:   0d 0a 74 3d  30 20 30 0d  0a 61 3d 72  61 6e 67 65   ..t=0 0..a=range
00d0:   3a 6e 70 74  3d 30 2d 0d  0a 61 3d 63  6f 6e 74 72   :npt=0-..a=contr
00e0:   6f 6c 3a 2a  0d 0a 6d 3d  76 69 64 65  6f 20 30 20   ol:*..m=video 0
00f0:   52 54 50 2f  41 56 50 20  39 36 0d 0a  61 3d 72 74   RTP/AVP 96..a=rt
0100:   70 6d 61 70  3a 39 36 20  48 32 36 34  2f 39 30 30   pmap:96 H264/900
0110:   30 30 0d 0a  61 3d 63 6f  6e 74 72 6f  6c 3a 74 72   00..a=control:tr
0120:   61 63 6b 49  44 3d 31 0d  0a 61 3d 66  6d 74 70 3a   ackID=1..a=fmtp:
0130:   39 36 20 70  72 6f 66 69  6c 65 2d 6c  65 76 65 6c   96 profile-level
0140:   2d 69 64 3d  34 44 34 30  31 46 3b 70  61 63 6b 65   -id=4D401F;packe
0150:   74 69 7a 61  74 69 6f 6e  2d 6d 6f 64  65 3d 30 3b   tization-mode=0;
0160:   73 70 72 6f  70 2d 70 61  72 61 6d 65  74 65 72 2d   sprop-parameter-
0170:   73 65 74 73  3d 5a 30 31  41 48 35 57  67 4c 41 53   sets=Z01AH5WgLAS
0180:   61 62 41 51  3d 2c 61 4f  34 38 67 41  3d 3d 3b 63   abAQ=,aO48gA==;c
0190:   6f 6e 66 69  67 3d 30 30  30 30 30 30  30 31 36 37   onfig=0000000167
01a0:   34 64 34 30  31 66 39 35  61 30 32 63  30 34 39 61   4d401f95a02c049a
01b0:   36 63 30 34  30 30 30 30  30 30 30 31  36 38 65 65   6c040000000168ee
01c0:   33 63 38 30  30 30 30 30  30 30 30 31  30 36 66 30   3c800000000106f0
01d0:   32 63 30 34  34 35 63 36  66 35 30 30  30 36 32 30   2c0445c6f5000620
01e0:   65 62 63 32  66 33 66 37  36 33 39 65  34 38 32 35   ebc2f3f7639e4825
01f0:   30 62 66 63  62 35 36 31  62 62 32 62  38 35 64 64   0bfcb561bb2b85dd
0200:   61 36 66 65  35 66 30 36  63 63 38 62  38 38 37 62   a6fe5f06cc8b887b
0210:   36 61 39 31  35 66 35 61  61 33 62 65  62 66 66 66   6a915f5aa3bebfff
0220:   66 66 66 66  66 66 66 66  37 33 38 30  0d 0a 61 3d   ffffffff7380..a=
0230:   78 2d 64 69  6d 65 6e 73  69 6f 6e 73  3a 20 37 30   x-dimensions: 70
0240:   34 2c 20 35  37 36 0d 0a  61 3d 78 2d  66 72 61 6d   4, 576..a=x-fram
0250:   65 72 61 74  65 3a 20 31  32 0d 0a 6d  3d 61 75 64   erate: 12..m=aud
0260:   69 6f 20 30  20 52 54 50  2f 41 56 50  20 30 0d 0a   io 0 RTP/AVP 0..
0270:   61 3d 72 74  70 6d 61 70  3a 30 20 4d  50 45 47 34   a=rtpmap:0 MPEG4
0280:   2d 47 45 4e  45 52 49 43  2f 31 36 30  30 30 2f 32   -GENERIC/16000/2
0290:   0d 0a 61 3d  66 6d 74 70  3a 30 20 63  6f 6e 66 69   ..a=fmtp:0 confi
02a0:   67 3d 31 34  30 38 0d 0a  61 3d 63 6f  6e 74 72 6f   g=1408..a=contro
02b0:   6c 3a 74 72  61 63 6b 49  44 3d 32 0d  0a 61 3d 4d   l:trackID=2..a=M
02c0:   65 64 69 61  5f 68 65 61  64 65 72 3a  4d 45 44 49   edia_header:MEDI
02d0:   41 49 4e 46  4f 3d 34 39  34 44 34 42  34 38 30 31   AINFO=494D4B4801
02e0:   30 31 30 30  30 30 30 34  30 30 30 31  30 30 31 30   0100000400010010
02f0:   37 31 30 31  31 30 34 30  31 46 30 30  30 30 30 30   710110401F000000
0300:   46 41 30 30  30 30 30 30  30 30 30 30  30 30 30 30   FA00000000000000
0310:   30 30 30 30  30 30 30 30  30 30 30 30  30 30 30 30   0000000000000000
0320:   30 30 30 30  30 30 3b 0d  0a 61 3d 61  70 70 76 65   000000;..a=appve
0330:   72 73 69 6f  6e 3a 31 2e  30 0d 0a                   rsion:1.0..

raw:

v=0
o=- 1109162014219182 1109162014219192 IN IP4 x.y.z.w
s=RTSP/RTP stream from anjvision ipcamera
e=NONE
c=IN IP4 0.0.0.0
a=tool:LIVE555 Streaming Media v2011.05.25 [email protected]
t=0 0
a=range:npt=0-
a=control:*
m=video 0 RTP/AVP 96
a=rtpmap:96 H264/90000
a=control:trackID=1
a=fmtp:96 profile-level-id=4D401F;packetization-mode=0;sprop-parameter-sets=Z01AH5WgLASabAQ=,aO48gA==;config=00000001674d401f95a02c049a6c040000000168ee3c800000000106f02c0445c6f5000620ebc2f3f7639e48250bfcb561bb2b85dda6fe5f06cc8b887b6a915f5aa3bebfffffffffff7380
a=x-dimensions: 704, 576
a=x-framerate: 12
m=audio 0 RTP/AVP 0
a=rtpmap:0 MPEG4-GENERIC/16000/2
a=fmtp:0 config=1408
a=control:trackID=2
a=Media_header:MEDIAINFO=494D4B48010100000400010010710110401F000000FA000000000000000000000000000000000000;
a=appversion:1.0

Looks like the error is that there's an a= line between c= and t=. The a= has to be after the t=, according to the grammar in RFC 8866 section 9:

   ; SDP Syntax
   session-description = version-field
                         origin-field
                         session-name-field
                         [information-field]
                         [uri-field]
                         *email-field
                         *phone-field
                         [connection-field]
                         *bandwidth-field
                         1*time-description
                         [key-field]
                         *attribute-field
                         *media-description

I've been thinking of switching to sdp-types (to minimize the number of different authors in Retina's dependency list), so I'll file an issue there and see if its author is amenable to making the parser more forgiving.

missing docs on docs.rs

Looks like the root cause is a rustdoc bug. Nonetheless, tracking here. There might be a quick workaround; if not, I'll probably need to nudge docs.rs (via a new release or by asking the folks who run it) for a rebuild anyway when the problem is fixed.

rust-lang/docs.rs#1632

unsafe bytes

I see that you're using https://docs.rs/bytes/1.0.1/bytes/ for zero-copy, but it uses a lot of unsafe code. Shouldn't we at least give a feature option that swaps bytes by a safe version that does some copies? I'd prefer to use the safe version to minimize the amount of unsafe calls in my app.

I'm very concerned about this :(

breaking changes for v0.4.0

Work underway on the next branch.

Breaking changes I'm considering:

  • hiding fields in favor of accessors. (Also: taking advantage of to reduce memory usage by keeping around Box<str> instead of String.)
  • similarly, maybe we want to add a way to pass through codec-level non-fatal errors. CodecItem::Error or the like. We could just make CodecItem #[non_exhaustive] rather than figure out the details now. (edit: just did the #[non_exhaustive] for now.)
  • removing the deprecated Session::teardown method.
  • remove the pub from a few items that got it without much thought. (they're already #[doc(hidden)])
  • replacing VideoFrame::new_parameters: Option<Box<VideoParameters>> with a boolean. You can obtain the latest parameters via Stream::parameters, so there's no good reason for the preemptive clone or for the extra bytes in every VideoFrame. Except:
    • after retina::client::Demuxed::poll_next returns Pending, currently Stream::parameters may reflect changes in the upcoming frame. This is probably confusing / worth avoiding.
    • Demuxed doesn't offer easy to access to the streams, but there's no reason it shouldn't.
  • replace retina::codec::Parameters with retina::codec::ParametersRef, to avoid having to clone the parameters when returning them from retina::client::Stream::parameters.
  • have Stream::setup take an options struct to allow customizing depacketizer options. Useful for e.g. #44.
  • alter the expectations for PacketContext to be paired with a new type StreamContext, rather than just RtspConnectionContext. I'd expose StreamContext as an accessor on the stream rather than include it in MessageFrame. Notably, MessageFrame has a pair of PacketContexts, so this should shrink MessageFrame's size by four socket addresses, at the cost of the caller having to pull more stuff together to get enough information to find the packet in a pcap file.
  • switch VideoFrame::data() and friends from returning a &Bytes to returning a &[u8], maybe adding a VideoFrame::take_data() that returns a Vec<u8>. If the caller wants to actually modify the bytes in-place for some reason, this would be better. I don't think there's any way to go from a Bytes back to a BytesMut, even if there are no other references. (I see an issue tokio-rs/bytes#350 that seems to be saying the same.)
  • PacketItem should expose RTCP compound packets, not just sender reports.

I'm open to others.

Deferred

I'm going to punt on returning anything borrowed from the Session/Demuxed. This can't be done with the futures::stream::Stream interface (as Rust doesn't have GATs yet / it's unclear if Stream can be extended to support them anyway). I think switching to just an inherent method async fn next(&mut self) -> ...Item might be worthwhile, which would let us borrow from a ring buffer (see #6). But I don't want to make the improvements already implemented wait for that, and folks say "version numbers are cheap".

  • possibly PacketItem and CodecItem should include a &Stream to save the caller having to look it up. They should have it handy anyway. CodecItem::VideoFrame could also return the params: Option<&VideoParameters> (likewise audio/message media) rather than requiring the caller match on an Parameters enum.
  • reverse #4 decision: have codec items expose a Buf that borrows from the internal buffers, rather than copying into a contiguous buffer. If we borrow, we don't have to make a new Vec of chunks for each frame. We'll make it easy for the caller to copy into a new contiguous Vec as before, or to copy into some other structure (like a single Vec for multiple frames, a ring buffer, etc).

Other deferred stuff, because again version numbers are cheap, and these aren't as fully baked as the stuff above the fold:

  • switching from log to tracing. This would allow the caller to supply context for interpreting the log messages. Motivating example: Moonfire NVR maintains all its streams from the same tokio thread pool. When Retina logs a message about a stream, it's unclear which stream it applies to. Retina at least knows what URL we're talking with, but Moonfire NVR can wrap that in better context like the name of the camera and main/sub stream, and tracing seems like a popular way to augment log messages in this way. Retina might also put in its own spans for each session.
    • why defer: using tracing properly at the Moonfire NVR level would involve moving stuff into fields rather than just the message and rethinking the log format. Not quite ready to take that on.
  • revamping how packet loss is reported at the CodecItem level. I don't like that right now an item's loss field doubles as the number of packets lost (not necessarily consecutively) and information about if the current frame is "complete". I wonder if it's better to represent these as orthogonal concepts. Sometimes we know the loss was in a previous frame; sometimes we're uncertain; sometimes we know we're missing part of the current frame (a prefix, one or more interior regions, or a suffix). I'm not totally sure what the best representation is, but it might involve losing the loss field. Having a separate CodecItem::Loss would also give a way to represent richer information about the loss (like the packet number range, the times of the packet before/after the loss).
    • why defer: because I haven't thought through the best representation yet.
  • removing PlayOptions::enforce_timestamps_with_max_jump_secs. It's already optional, but I'm not sure it belongs at this level at all. The caller can do this validation if they care to, and it may not be used often enough to be worth advertising in this library. They may instead want to do something more sophisticated than trusting the RTP timestamps at all, given that many cameras don't use the monotonic clock to produce them. If I come up with one really battle-tested reliable strategy, it might belong in this library, but I don't think enforce_timestamps_with_max_jump_secs is that.
    • why defer: I wonder if I should go even further and make the u32->crate::Timestamp step optional/customizable. Also requires more design effort.

packet follows marked packet with same timestamp

cargo run --example client mp4 --url rtsp://192.168.1.198:10554/tcp/av0_0 --username admin --password 123456 --initial-timestamp ignore /home/dev/orwell/video_samples/tmp/cam_test.mp4

gives

E20210727 19:24:02.308 main client] Fatal: [172.17.0.2:50402(me)->192.168.1.198:10554@2021-07-27T19:23:55, 119614@2021-07-27T19:24:02, channel=0, stream=0, ssrc=0000173a] packet follows marked packet with same timestamp

from here

return Err("packet follows marked packet with same timestamp".into());

I don't know why --initial-timestamp ignore is needed but without it I get

"Expected rtptime on PLAY with mode {:?}, missing on \

Anyways, the packet follows marked packet with same timestamp error occurs after around 30 calls to push, like this:

push
push
push
push
push
push
push
push
push
push
push
push
push
push
push
push
8023 (mod-2^32: 8023), npt 0.000: 111304-byte video frame
push
E20210727 19:36:04.662 main client] Fatal: [172.17.0.2:50572(me)->192.168.1.198:10554@2021-07-27T19:36:01, 113756@2021-07-27T19:36:04, channel=0, stream=0, ssrc=00001f57] packet follows marked packet with same timestamp

as you see, a video frame is generated and then we get the error. It always happens after the first video frame is emitted.

The strange thing is that on my app, the retina client does not produce this error. I'm trying to figure out what is different.

[section moved to #15]

Do you have any idea on the error on the mp4 example, and on my client?

support live555 servers older than 2017.06.04 (eg some Reolink models), which have buggy RTP/TCP

Latest understanding: these cameras are using LIVE555 Streaming Media v2013.04.08 (according to the session-level tool attribute in their DESCRIBE response) which has at least two major bugs described in live555's changelog:

2015.05.03:
- Updated the "RTSPServer" implementation to fix a bug in RTP/RTCP-over-TCP streaming.
  Before, if the "RTSPClientConnection" object closed before the "RTSPClientSession" object,
  and the TCP connection was also being used for RTP/RTCP-over-TCP streaming, then the
  streaming state (in the "RTSPClientSession") would stay alive, even though the TCP socket
  had closed (and the socket number possibly reused for a subsequent connection).
  This could cause a problem when the "RTSPClientSession" was later reclaimed (due to inactivity).
  Now, whenever a "RTSPClientConnection" object is closed (due to the RTSP TCP connection closing),
  we make sure that we also close any stream that had been using the same TCP connection
  for RTP/RTCP-over-TCP streaming.  (Thanks to Kirill Zhegulev for noting this issue.)
2013.12.04:
- Updated the "sendDataOverTCP()" function (in "RTPInterface.cpp") to allow for the possibility of
  one of the "send()" calls partially succeeding - i.e., writing some, but not all, of its data.

also, there was a follow-up to the 2015.05.03 entry two years later:

2017.06.04:
- Fixed a bug in "RTPInterface::removeStreamSocket()" that could cause not all 'TCP stream'
  records for a given socket number to be removed if a TCP socket I/O error occurred
  (during RTP/RTCP-over-TCP streaming).  (Thanks to Gerald Hansink et al for reporting this.)

Currently your best bet to successfully talk with these cameras is to ask the manufacturer for a firmware upgrade (see note below for the Reolink 420-5MP / hardware version IPC_51516M5M) or connect with UDP via retina::client::Transport::Udp (#30). We have some other ideas below.


Original comment:

As mentioned in this Apr 29th comment on a moonfire-nvr issue, Reolink cameras have an odd behavior. They sometimes set up a stream with one ssrc, eg:

RTP-Info: url=trackID=1;seq=40477;rtptime=2077624148;ssrc=c517011f,url=trackID=2;seq=0;rtptime=0;ssrc=00000000

then send RTP packets for both this one and another ssrc. I think the latter is for an earlier failed session or something. Sometimes they'll even send the RTP packets for the other ssrc before the connection is even authenticated, which seems like a security hole (if you expose the cameras to an untrusted network, which is probably a bad idea anyway).

Currently when this happens, Retina will fail with an error such as the following (from one of @jlpoolen 's logs today in this moonfire-nvr issue):

Expected ssrc=Some(21cd90d7) seq=Some(8f4a) got ssrc=6d87df1a seq=88b9 ts=1866533263 (mod-2^32: 1866533263), npt 0.000 at [192.168.1.88:33514(me)->192.168.1.48:554@2021-08-14T11:00:57 pos=1510@2021-08-14T11:00:57]

I think we should add an option to so that when we know what ssrc to expect, we just (log and) ignore packets with any other ssrc. This will likely make these cameras behave acceptably, especially if (as I suspect) the other ssrc goes away after a minute or so when the stale session times out.

Version Information

It would be helpful to have a self-publishing of the version running.

jlpoole@taurus /usr/local/src/retina $ cargo run --example client 
    Finished dev [unoptimized + debuginfo] target(s) in 0.05s
     Running `target/debug/examples/client`
retina 0.2.0

USAGE:
    client <SUBCOMMAND>

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

SUBCOMMANDS:
    help        Prints this message or the help of the given subcommand(s)
    metadata    
    mp4         
jlpoole@taurus /usr/local/src/retina $ cargo run --example client -V
error: Found argument '-V' which wasn't expected, or isn't valid in this context

USAGE:
    cargo run --example <NAME>...

For more information try --help
jlpoole@taurus /usr/local/src/retina $ cargo run --example client --version
error: Found argument '--version' which wasn't expected, or isn't valid in this context
	Did you mean --verbose?

USAGE:
    cargo run --example <NAME>... --verbose

For more information try --help
jlpoole@taurus /usr/local/src/retina $ 

no function or associated item named `unmarshal` found for struct

I'm trying to figure out what is happening here. Compiling the project on its folder has no errors, but compiling my project when it depends on retina, gives this:

error[E0599]: no function or associated item named `unmarshal` found for struct `Header` in the current scope
   --> src/external/retina/src/client/rtp.rs:193:49
    |
193 |             let h = match rtcp::header::Header::unmarshal(&data) {
    |                                                 ^^^^^^^^^ function or associated item not found in `Header`
    |
    = help: items from traits can only be used if the trait is in scope
    = note: the following trait is implemented but not in scope; perhaps add a `use` for it:
            `use webrtc_util::marshal::Unmarshal;`

error[E0599]: no function or associated item named `unmarshal` found for struct `rtcp::sender_report::SenderReport` in the current scope
   --> src/external/retina/src/client/rtp.rs:211:66
    |
211 |                     let pkt = rtcp::sender_report::SenderReport::unmarshal(&pkt)
    |                                                                  ^^^^^^^^^ function or associated item not found in `rtcp::sender_report::SenderReport`
    |
    = help: items from traits can only be used if the trait is in scope
    = note: the following trait is implemented but not in scope; perhaps add a `use` for it:
            `use webrtc_util::marshal::Unmarshal;`

error: aborting due to 2 previous errors; 1 warning emitted

have you had something like this? I have no idea about what is happening

use larger read buffers

I was mildly surprised Moonfire NVR's CPU usage didn't go down noticeably when switching from ffmpeg to retina. There's not much CPU used in retina's code itself but I think it's making too many syscalls because it's using buffers that have too little available space. The histogram below counts reads that filled the buffer (and thus will require a follow-up syscall) bucketed by the available space in the buffer when the read started:

[slamb@nuc ~]$ cat recvsize.bt
#!/usr/bin/bpftrace

tracepoint:syscalls:sys_enter_recvfrom /pid == (uint64) $1/ {
    @sizes[tid] = (int64) args->size;
}

tracepoint:syscalls:sys_exit_recvfrom /pid == (uint64) $1/ {
    if (@sizes[tid] > 0 && args->ret == @sizes[tid]) {
        @full_read_sizes = hist(@sizes[tid]);
    }
    delete(@sizes[tid]);
}

interval:s:60 { exit() }
[slamb@nuc ~]$ sudo ./recvsize.bt "$(pidof moonfire-nvr)"
Attaching 3 probes...


@full_read_sizes:
[1]                    1 |                                                    |
[2, 4)                 0 |                                                    |
[4, 8)                 6 |                                                    |
[8, 16)               16 |                                                    |
[16, 32)              47 |                                                    |
[32, 64)             125 |                                                    |
[64, 128)            267 |@                                                   |
[128, 256)           531 |@@@                                                 |
[256, 512)          1119 |@@@@@@                                              |
[512, 1K)           8319 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[1K, 2K)            3132 |@@@@@@@@@@@@@@@@@@@                                 |
[2K, 4K)             149 |                                                    |
[4K, 8K)              42 |                                                    |

That's ~230 times per second the buffer filled (across 12 video streams); in most cases it was reading into a buffer with less than 1 KiB available.

I'm letting tokio_util::codec::Framed do the buffer management now, but I think I should do it myself instead. Or at least call reserve(4096) before returning from Codec::decode (regardless of whether it was able to pull a full message).

PLAY response has no RTP-Info header

On my Vstarcam, I'm getting

let rtp_info = response
, that is:

    let rtp_info = response
        .header(&rtsp_types::headers::RTP_INFO)
        .ok_or_else(|| "PLAY response has no RTP-Info header".to_string())?;

Does it have to have an RTP-Info header? According to https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rtsp/8432535c-ae1e-4d86-82de-29528cec1d3c,

This header is also sent by clients in the Play request when predictive stream selection is used. For more information on predictive stream selection, see section 2.2.6.10.5.

mine

PLAY rtsp://192.168.1.198:10554/tcp/av0_0 RTSP/1.0
Authorization: Digest username="admin", realm="RTSPD", nonce="kx2i6xqrqe44c86b667qy88lq1cl5670", uri="rtsp://192.168.1.198:10554/tcp/av0_0", response="a57bb4a903a2cddc2a40563b692da576"
CSeq: 4
Range: npt=0.000-
Session: 1632991860795359396
User-Agent: moonfire-rtsp test

RTSP/1.0 200 OK
Cseq: 4
Date: Mon, Jul 26 2021 19:14:55 GMT
Session: 1632991860795359396

yours

PLAY rtsp://192.168.1.198:10554/tcp/av0_0 RTSP/1.0
Authorization: Digest username="admin", algorithm="MD5", realm="RTSPD", nonce="03xah8kf4k0f228i77hp792zh70pd45q", uri="/tcp/av0_0", response="aa0fc4dbf695e0cb3be8cada1d1c7318"
CSeq: 5
Session: 4480630546592316416
User-Agent: RRTSP Client

RTSP/1.0 200 OK
Cseq: 5
Date: Mon, Jul 26 2021 19:17:16 GMT
Session: 4480630546592316416

VStarcam: EOF while expecting response to DESCRIBE CSeq 2

Tried a new Vstarcam camera that I haven't tried before and got:

thread 'retina_client_cam1' panicked at 'called Result::unwrap()on anErr value: RtspReadError { conn_ctx: ConnectionContext { local_addr: 172.17.0.2:57760, peer_addr: 192.168.1.140:10554, established_wall: WallTime(Timespec { sec: 1640306208, nsec: 801928570 }), established: Instant { tv_sec: 7103, tv_nsec: 209861352 } }, msg_ctx: RtspMessageContext { pos: 119, received_wall: WallTime(Timespec { sec: 1640306209, nsec: 106381244 }), received: Instant { tv_sec: 7103, tv_nsec: 514314058 } }, source: Custom { kind: UnexpectedEof, error: "EOF while expecting response to DESCRIBE CSeq 2" } }', /home/dev/orwell/liborwell/src/rtsp/retina_client.rs:132:77

capture sent in email

I have no idea what's happening but I remember we might have stumbled upon this before?

On the capture the camera basically closes connection once we receive the DESCRIBE. I think it's malformed

API design: should depacketizers provide data as impl Buf or Bytes/Vec<u8>?

Right now retina::codec::VideoFrame implements Buf and supports only H.264. It always has two chunks: an AVC length prefix and the header+body of the (single) picture NAL (note I'll need to extend it for multiple NALs). The idea was to support zero-copy, but I think right now it's kind of the worst of all worlds:

  • if the NAL is fragmented (most of the bytes and maybe even most of the NALs on a typical stream), it copies it all into a BytesMut with a guesstimated size. It might copy again to grow partway through, and then probably (I haven't looked at profiles) copies it again on BytesMut::freeze due to tokio-rs/bytes#401.
  • if the caller wants a single contiguous buffer, they'll end up copying it themselves. (Maybe with bytes::Buf::copy_to_bytes, maybe not.)
  • you can only iterate through it once, which might be a problem for some usages.

AudioFrame and MessageFrame provide a Bytes directly; there's no good reason all three frame bytes shouldn't present their data in the same way.

I'd prefer to follow one of two paths. I haven't made up my mind as to which:

  • truly support zero-copy via data(&self) -> impl Buf<'self>. VideoFrame needs a Vec of chunks. If you want to iterate multiple times, you can just call data as many times as you want. You can use Buf::chunks_vectored + std::io::Write::write_vectored / tokio::io::AsyncWriteExt::write_vectored.
  • put everything into a single buffer. Accumulate Bytes in a reused Vec in Depacketizer::push then concatenate them during Depacketizer::pull when the total size is known, avoiding the extra copies mentioned above.

Arguments in favor of zero-copy (custom Buf implementation with multiple chunks):

  • People like zero-copy; it's generally assumed to be more efficient (but see below).
  • One neat API trick this would allow (for H.264) is selecting the Annex B encoding (00 00 00 01 between NALs) or the AVC encoding (length prefix between NALs) via something like h264(&self) -> Option<&H264Frame> then data_as_annexb(&self) -> impl Buf or data_as_avc(&self, len_prefix_size: usize) -> impl Buf. With the single-buffer approach I'd probably make folks choose when setting up the depacketizer instead. (I guess it'd also be fairly efficient to have a &mut accessor on the VideoFrame which does a one-way conversion from 4-byte AVC to Annex B, but that's a weird API.) I can imagine someone doing some fan-out thing where they actually want both encodings.

Arguments in favor of a single Bytes or Vec<u8>:

  • More convenient / simpler to get right IMHO. The zero-copy APIs seem half-baked/finicky, eg tokio's tokio::io::AsyncWriteExt::write_buf just writes the first chunk, and there's no write_all_vectored in either std::io::Write or tokio::io::AsyncWriteExt.
  • It might actually be more efficient. The answer isn't obvious to me. With my IP cameras, the IDR frames can be half a megabyte, fragmented across hundreds of RTP packets of 1400ish bytes. Just the &[Bytes] is then tens of kilobytes (four pointers per element). Far too big for the no-alloc path of SmallVec<[Bytes; N]>. And if someone's doing a writev call later, they have to set up/iterate through hundreds of IoSlices to write the whole thing at once. (And you can't even reuse a Vec<IoSlice> between iterations without trickery because it expects to operate on a mutable slice of initialized IoSlice objects.) It wouldn't surprise me if zero-copy is actually slower.
  • I'm not sure but I think Bytes is mostly just a tokio thing. async std folks might use just Vec<u8> or something instead. Although I'll likely keep using Bytes internally anyway to keep the individual packets around between push and pull.

Right now I'm leaning toward single Bytes. I might try benchmarking both but if the performance is close I think simplicity should win.

mp4 example: Ignoring h264 video stream because it's unsupported

On the cargo run example, I get:

     Running `target/debug/examples/client mp4 --url 'rtsp://192.168.1.22:10554/tcp/av0_0' --username admin --password 123456 out.mp4`
W20211130 01:57:55.777 main retina::codec::h264] Ignoring bad H.264 format-specific-params "packetization-mode=1;profile-level-id=00f004;sprop-parameter-sets=6QDwBE/LCAAAH0gAB1TgIAAAAAA=,AAAAAA==": bad sprop-parameter-sets: bad NAL header e9
I20211130 01:57:55.779 main client::mp4] Ignoring h264 video stream because it's unsupported
I20211130 01:57:55.779 main client::mp4] No suitable video stream found
I20211130 01:57:55.781 main client::mp4] Ignoring pcma audio stream because it can't be placed into a .mp4 file without transcoding
I20211130 01:57:55.781 main client::mp4] No suitable audio stream found
E20211130 01:57:55.782 main client] Fatal: Exiting because no video or audio stream was selected; see info log messages above

from

"Ignoring {} video stream because it's unsupported",

On my app, I get just this first video frame, and then nothing more:

video frame: VideoFrame { timestamp: 2022 (mod-2^32: 2022), npt 0.000, start_ctx: PacketContext(Tcp { msg_ctx: RtspMessageContext { pos: 868, received_wall: WallTime(Timespec { sec: 1638237968, nsec: 431245911 }), received: Instant { tv_sec: 83828, tv_nsec: 228186887 } }, channel_id: 0 }), end_ctx: PacketContext(Tcp { msg_ctx: RtspMessageContext { pos: 29964, received_wall: WallTime(Timespec { sec: 1638237968, nsec: 439222146 }), received: Instant { tv_sec: 83828, tv_nsec: 236162701 } }, channel_id: 0 }), loss: 0, new_parameters: Some(VideoParameters { rfc6381_codec: "avc1.4D0028", pixel_dimensions: (1920, 1080), pixel_aspect_ratio: None, frame_rate: Some((2002, 60060)), extra_data: Length: 35 (0x23) bytes
    0000:   01 4d 00 28  ff e1 00 14  67 4d 00 28  e9 00 f0 04   .M.(....gM.(....
    0010:   4f cb 08 00  00 1f 48 00  07 54 e0 20  01 00 04 68   O.....H..T. ...h
    0020:   ea 8f 20                                             ..  }), is_random_access_point: true, is_disposable: false, data_len: 30023 }

on the line

tokio::select! {
                                    item = session.next() => {
                                        match item {
                                            Some(Ok(CodecItem::MessageFrame(m))) => {
                                                info!("{}: {}\n", &m.timestamp, std::str::from_utf8(&m.data[..]).unwrap());
                                            },
                                            Some(Ok(CodecItem::VideoFrame(v))) => {
                                                info!("video frame: {:?}", v);

packet capture of cargo run example sent via email

annexb support

I just created this conversion function and it worked on ffmpeg for decoding (at least it shows the camera image, gotta see with movement though, it's static because it's pointed to somewhere random)

/// Converts from AVCC format to annex b format
pub fn avcc_to_annex_b_cursor(
    data: &[u8],
    nal_units: &mut Vec<u8>,
) -> Result<(), AnnexConversionError> {
    let mut data_cursor = Cursor::new(data);
    let mut nal_lenght_bytes = [0u8; 4];
    while let Ok(bytes_read) = data_cursor.read(&mut nal_lenght_bytes) {
        if bytes_read == 0 {
            break;
        }
        if bytes_read != nal_lenght_bytes.len() || bytes_read == 0 {
            return Err(AnnexConversionError::NalLenghtParseError);
        }
        let nal_length = u32::from_be_bytes(nal_lenght_bytes) as usize;
        nal_units.push(0);
        nal_units.push(0);
        nal_units.push(1);

        if nal_length == 0 {
            return Err(AnnexConversionError::NalLenghtParseError);
        }
        let mut nal_unit = vec![0u8; nal_length];
        let bytes_read = data_cursor.read(&mut nal_unit);
        match bytes_read {
            Ok(bytes_read) => {
                nal_units.extend_from_slice(&nal_unit[0..bytes_read]);
                //TODO: this is never called so we don't ever detect EOF
                if bytes_read == 0 {
                    break;
                } else if bytes_read < nal_unit.len() {
                    return Err(AnnexConversionError::NalUnitExtendError);
                }
            }
            Err(e) => return Err(AnnexConversionError::IoError(e)),
        };
    }
    Ok(())
}

I used the hardcoded NALULenghtMinusOne=3 from

// Hardcode lengthSizeMinusOne to 3, matching TransformSampleData's 4-byte
, but should be subject to change if you change in the code.

Anyways, this was just to test. I think it would be better if retina had a way to specify AVCC or annexb. Can I do a PR to do this option? I think here:

data.extend_from_slice(&nal.len.to_be_bytes()[..]);
it should not write the length and append 0x000001, when annexb format is true. But maybe also support this function for people that want to still receive avcc but get the nalu sometimes? By the way this is not the best implementation because Cursor does not tell me about EOF as you see in the comments

mp4 example: align video and audio tracks with edit list

Right now the mp4 example just assumes the first frame from each track starts at NPT ("normal play time") 0. That's not really true. Depending on chance, it might be misaligned by as much as one full video frame (eg ~33 ms at 30 fps, 200 ms at 5 fps). The right thing to do is to add an mp4 edit list. It could either skip the beginning of the earlier track (so they both start at the same time) or delay the other (to not lose any information).

Debug Mode for example client mp4 ? Enhancement

I'm sorry, I'm just not going to have time to get up to speed on rust, but I think I can offer something of value if my attempts fail and there is some instruction on how to provide debugging information. I just updated Issue #28 and since I was referencing the online help, I did not see anything that suggest how to provide debugging information. It there is a way, it might be in the instructions and/or active help.

ares /usr/local/src/retina # date; cargo run --example client mp4 --url rtsp://192.168.1.12:5000   /tmp/retina_out.mp4;date;
Fri Feb  4 19:40:48 PST 2022
warning: field is never read: `sdp`
   --> src/client/mod.rs:189:5
    |
189 |     sdp: SessionDescription,
    |     ^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: `#[warn(dead_code)]` on by default

warning: field is never read: `name`
  --> src/codec/aac.rs:53:5
   |
53 |     name: &'static str,
   |     ^^^^^^^^^^^^^^^^^^

warning: field is never read: `loss_since_mark`
   --> src/codec/aac.rs:478:5
    |
478 |     loss_since_mark: bool,
    |     ^^^^^^^^^^^^^^^^^^^^^

warning: field is never read: `established`
   --> src/lib.rs:215:5
    |
215 |     established: std::time::Instant,
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

warning: `retina` (lib) generated 4 warnings
    Finished dev [unoptimized + debuginfo] target(s) in 0.07s
     Running `target/debug/examples/client mp4 --url 'rtsp://192.168.1.12:5000' /tmp/retina_out.mp4`
E20220204 19:40:48.849 main client] Fatal: Unable to connect to RTSP server: Connection refused (os error 111)
Fri Feb  4 19:40:48 PST 2022
ares /usr/local/src/retina # ls -la /tmp/ret*
ls: cannot access '/tmp/ret*': No such file or directory
ares /usr/local/src/retina #  # no partial file,  Issue #32 ?

Read a RTSP stream and publish it to a RTMP server

First of all thanks a lot for this amazing effort,
I´m looking for a pure Rust RSTP client library for an IoT project and wondering if it´s possible to read a RTSP stream and publish it to a RTMP server. Can you please let me know if this is possible or if it's any plan to support this?

Enhancement: Duration Flag For Example Client?

Enhancement request.

When running the client example, e.g.

 date; cargo run --example client mp4 --url rtsp://192.168.1.54:554/h264Preview_01_main --username moon --password fire123 /tmp/retina_out.mp4;date;

it would be helpful to have a duration flag such as "-d 40" which specifies how long the client should run in seconds and then gracefully shut down and/or close the connection.

Current options are:

jlpoole@taurus /usr/local/src/retina $ cargo run --example client -h
cargo-run 
Run a binary or example of the local package

USAGE:
    cargo run [OPTIONS] [--] [args]...

OPTIONS:
    -q, --quiet                      No output printed to stdout
	--bin <NAME>...              Name of the bin target to run
	--example <NAME>...          Name of the example target to run
    -p, --package <SPEC>             Package with the target to run
    -j, --jobs <N>                   Number of parallel jobs, defaults to # of CPUs
	--release                    Build artifacts in release mode, with optimizations
	--profile <PROFILE-NAME>     Build artifacts with the specified profile
	--features <FEATURES>...     Space or comma separated list of features to activate
	--all-features               Activate all available features
	--no-default-features        Do not activate the `default` feature
	--target <TRIPLE>...         Build for the target triple
	--target-dir <DIRECTORY>     Directory for all generated artifacts
	--manifest-path <PATH>       Path to Cargo.toml
	--message-format <FMT>...    Error format
	--unit-graph                 Output build graph in JSON (unstable)
	--ignore-rust-version        Ignore `rust-version` specification in packages (unstable)
    -v, --verbose                    Use verbose output (-vv very verbose/build.rs output)
	--color <WHEN>               Coloring: auto, always, never
	--frozen                     Require Cargo.lock and cache are up to date
	--locked                     Require Cargo.lock is up to date
	--offline                    Run without accessing the network
	--config <KEY=VALUE>...      Override a configuration value (unstable)
    -Z <FLAG>...                     Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details
    -h, --help                       Prints help information

ARGS:
    <args>...    

Run `cargo help run` for more detailed information.

jlpoole@tau

UDP reorder buffer

I just saw a case for the first time where having a UDP reorder buffer would really help. For some reason, I'm fairly regularly getting packets out of order from my Reolink camera today, eg:

W20211021 11:12:36.908 tokio-runtime-worker moonfire_nvr::stream] reolink-main: lost 1 RTP packets @ 192.168.5.225:6974->192.168.1.121:45572@2021-10-21T11:12:36
I20211021 11:12:39.976 tokio-runtime-worker retina::client::rtp] Skipping out-of-order seq=6a93 when expecting ssrc=Some(2a0a5257) seq=Some(6a95)
W20211021 11:12:40.127 tokio-runtime-worker moonfire_nvr::stream] reolink-main: lost 1 RTP packets @ 192.168.5.225:6974->192.168.1.121:45572@2021-10-21T11:12:40
I20211021 11:12:40.245 tokio-runtime-worker retina::client::rtp] Skipping out-of-order seq=6ac0 when expecting ssrc=Some(2a0a5257) seq=Some(6ac2)
W20211021 11:12:40.250 tokio-runtime-worker moonfire_nvr::stream] reolink-main: lost 1 RTP packets @ 192.168.5.225:6974->192.168.1.121:45572@2021-10-21T11:12:40
I20211021 11:12:42.445 tokio-runtime-worker retina::client::rtp] Skipping out-of-order seq=6bc2 when expecting ssrc=Some(2a0a5257) seq=Some(6bc4)
W20211021 11:12:42.495 tokio-runtime-worker moonfire_nvr::stream] reolink-main: lost 1 RTP packets @ 192.168.5.225:6974->192.168.1.121:45572@2021-10-21T11:12:42
I20211021 11:12:43.310 tokio-runtime-worker retina::client::rtp] Skipping out-of-order seq=6c25 when expecting ssrc=Some(2a0a5257) seq=Some(6c27)
W20211021 11:12:43.360 tokio-runtime-worker moonfire_nvr::stream] reolink-main: lost 1 RTP packets @ 192.168.5.225:6974->192.168.1.121:45572@2021-10-21T11:12:43
I20211021 11:12:45.604 tokio-runtime-worker retina::client::rtp] Skipping out-of-order seq=6d50 when expecting ssrc=Some(2a0a5257) seq=Some(6d52)
W20211021 11:12:45.753 tokio-runtime-worker moonfire_nvr::stream] reolink-main: lost 1 RTP packets @ 192.168.5.225:6974->192.168.1.121:45572@2021-10-21T11:12:45
I20211021 11:12:45.996 tokio-runtime-worker retina::client::rtp] Skipping out-of-order seq=0ad9 when expecting ssrc=Some(1c9421e0) seq=Some(0adb)
W20211021 11:12:46.095 tokio-runtime-worker moonfire_nvr::stream] reolink-sub: lost 1 RTP packets @ 192.168.5.225:6970->192.168.1.121:21924@2021-10-21T11:12:46
I20211021 11:12:46.232 tokio-runtime-worker retina::client::rtp] Skipping out-of-order seq=6d9f when expecting ssrc=Some(2a0a5257) seq=Some(6da1)
W20211021 11:12:46.241 tokio-runtime-worker moonfire_nvr::stream] reolink-main: lost 1 RTP packets @ 192.168.5.225:6974->192.168.1.121:45572@2021-10-21T11:12:46
I20211021 11:12:46.464 tokio-runtime-worker retina::client::rtp] Skipping out-of-order seq=6db8 when expecting ssrc=Some(2a0a5257) seq=Some(6dbb)
W20211021 11:12:46.519 tokio-runtime-worker moonfire_nvr::stream] reolink-main: lost 1 RTP packets @ 192.168.5.225:6974->192.168.1.121:45572@2021-10-21T11:12:46
I20211021 11:12:49.625 tokio-runtime-worker retina::client::rtp] Skipping out-of-order seq=6f4b when expecting ssrc=Some(2a0a5257) seq=Some(6f4d)
W20211021 11:12:49.663 tokio-runtime-worker moonfire_nvr::stream] reolink-main: lost 1 RTP packets @ 192.168.5.225:6974->192.168.1.121:45572@2021-10-21T11:12:49

This is on my development workstation, which is on a different network segment than the camera in question. My best guess is that my router is reordering the packets for some reason (rather than the camera or switch or workstation doing so). In any case, it's not that weird of a scenario, so it'd be nice to handle it better.

I think we should have a reorder buffer with parameters for maximum delay (eg 500 ms) and maximum amount to buffer (specified in packets and/or bytes).

retry TEARDOWN and avoid having to wait for session expiration

After trying out the new Retina 0.3.1 TEARDOWN logic with UDP sessions overnight, I see these log messages:

Sep 09 22:53:27 nuc moonfire-nvr[2940163]: tokio-runtime-worker retina::client] Background TEARDOWN: Ok(())
Sep 09 22:57:59 nuc moonfire-nvr[2940163]: tokio-runtime-worker retina::client] Background TEARDOWN: Err("[192.168.5.3:42002(me)->192.168.5.101:554@2021-09-09T22:22:10] Error writing to RTSP peer: Broken pipe (os error 32)")
Sep 09 22:58:59 nuc moonfire-nvr[2940163]: tokio-runtime-worker retina::client] Background TEARDOWN: Err("[192.168.5.3:41990(me)->192.168.5.101:554@2021-09-09T22:22:10] Error writing to RTSP peer: Broken pipe (os error 32)")
Sep 09 23:19:27 nuc moonfire-nvr[2940163]: tokio-runtime-worker retina::client] Background TEARDOWN: Ok(())
Sep 10 00:42:16 nuc moonfire-nvr[2940163]: tokio-runtime-worker retina::client] Background TEARDOWN: Err("[192.168.5.3:33594(me)->192.168.5.104:554@2021-09-09T23:19:28] Error writing to RTSP peer: Broken pipe (os error 32)")
Sep 10 01:03:30 nuc moonfire-nvr[2940163]: tokio-runtime-worker retina::client] Background TEARDOWN: Err("[192.168.5.3:34470(me)->192.168.5.102:554@2021-09-09T22:22:11] Error writing to RTSP peer: Broken pipe (os error 32)")
Sep 10 02:40:29 nuc moonfire-nvr[2940163]: tokio-runtime-worker retina::client] Background TEARDOWN: Err("[192.168.5.3:57580(me)->192.168.5.104:554@2021-09-09T22:22:10] Error writing to RTSP peer: Broken pipe (os error 32)")
Sep 10 03:28:36 nuc moonfire-nvr[2940163]: tokio-runtime-worker retina::client] Background TEARDOWN: Ok(())
Sep 10 03:46:18 nuc moonfire-nvr[2940163]: tokio-runtime-worker retina::client] Background TEARDOWN: Err("[192.168.5.3:44742(me)->192.168.5.101:554@2021-09-09T22:58:59] Error writing to RTSP peer: Broken pipe (os error 32)")
Sep 10 03:46:18 nuc moonfire-nvr[2940163]: tokio-runtime-worker retina::client] Background TEARDOWN: Err("[192.168.5.3:44818(me)->192.168.5.101:554@2021-09-09T22:59:59] Error writing to RTSP peer: Broken pipe (os error 32)")
Sep 10 05:03:49 nuc moonfire-nvr[2940163]: tokio-runtime-worker retina::client] Background TEARDOWN: Err("[192.168.5.3:46364(me)->192.168.5.102:554@2021-09-10T01:04:30] Error writing to RTSP peer: Broken pipe (os error 32)")
Sep 10 05:38:48 nuc moonfire-nvr[2940163]: tokio-runtime-worker retina::client] Background TEARDOWN: Err("[192.168.5.3:48316(me)->192.168.5.104:554@2021-09-10T02:41:29] Error writing to RTSP peer: Broken pipe (os error 32)")
Sep 10 06:57:56 nuc moonfire-nvr[2940163]: tokio-runtime-worker retina::client] Background TEARDOWN: Err("[192.168.5.3:51720(me)->192.168.5.104:554@2021-09-10T03:28:37] Error writing to RTSP peer: Broken pipe (os error 32)")
Sep 10 07:18:00 nuc moonfire-nvr[2940163]: tokio-runtime-worker retina::client] Background TEARDOWN: Err("[192.168.5.3:34462(me)->192.168.5.102:554@2021-09-09T22:22:10] Error writing to RTSP peer: Broken pipe (os error 32)")

In other words, most attempts failed because the connection was lost. Matching original errors:

Sep 09 22:53:27 nuc moonfire-nvr[2940163]: s-west_side-main moonfire_nvr::streamer] west_side-main: sleeping for PT1S after error: [192.168.5.3:57566(me)->192.168.5.104:554@2021-09-09T22:22:10, 192.168.5.104:8346->192.168.5.3:19542@2021-09-09T22:53:27, stream=0, ssrc=601c8a2c, seq=0000992e] NAL header ef has F bit set
Sep 09 22:57:59 nuc moonfire-nvr[2940163]: s-back_west-sub moonfire_nvr::streamer] back_west-sub: sleeping for PT1S after error: [192.168.5.3:42002(me)->192.168.5.101:554@2021-09-09T22:22:10, 6399@2021-09-09T22:57:59] Error reading from RTSP peer: Connection reset by peer (os error 104)
Sep 09 22:58:59 nuc moonfire-nvr[2940163]: s-back_west-main moonfire_nvr::streamer] back_west-main: sleeping for PT1S after error: [192.168.5.3:41990(me)->192.168.5.101:554@2021-09-09T22:22:10, 6562@2021-09-09T22:58:59] Error reading from RTSP peer: Connection reset by peer (os error 104)
Sep 09 23:19:27 nuc moonfire-nvr[2940163]: s-west_side-main moonfire_nvr::streamer] west_side-main: sleeping for PT1S after error: [192.168.5.3:59856(me)->192.168.5.104:554@2021-09-09T22:53:28, 192.168.5.104:8350->192.168.5.3:38554@2021-09-09T23:19:27, stream=0, ssrc=15df6214, seq=0000392f] bad header ad in STAP-A
Sep 10 00:42:16 nuc moonfire-nvr[2940163]: s-west_side-main moonfire_nvr::streamer] west_side-main: sleeping for PT1S after error: [192.168.5.3:33594(me)->192.168.5.104:554@2021-09-09T23:19:28, 12796@2021-09-10T00:42:16] Error reading from RTSP peer: Connection reset by peer (os error 104)
Sep 10 01:03:30 nuc moonfire-nvr[2940163]: s-back_east-main moonfire_nvr::streamer] back_east-main: sleeping for PT1S after error: [192.168.5.3:34470(me)->192.168.5.102:554@2021-09-09T22:22:11, 23473@2021-09-10T01:03:30] Error reading from RTSP peer: Connection reset by peer (os error 104)
Sep 10 02:40:29 nuc moonfire-nvr[2940163]: s-west_side-sub moonfire_nvr::streamer] west_side-sub: sleeping for PT1S after error: [192.168.5.3:57580(me)->192.168.5.104:554@2021-09-09T22:22:10, 36635@2021-09-10T02:40:29] Error reading from RTSP peer: Connection reset by peer (os error 104)
Sep 10 03:28:36 nuc moonfire-nvr[2940163]: s-west_side-main moonfire_nvr::streamer] west_side-main: sleeping for PT1S after error: [192.168.5.3:39700(me)->192.168.5.104:554@2021-09-10T00:43:16, 192.168.5.104:8354->192.168.5.3:10646@2021-09-10T03:28:36, stream=0, ssrc=142611f4, seq=00004cf0] Non-fragmented NAL 0d while fragment in progress
Sep 10 03:46:18 nuc moonfire-nvr[2940163]: s-back_west-sub moonfire_nvr::streamer] back_west-sub: sleeping for PT1S after error: [192.168.5.3:44742(me)->192.168.5.101:554@2021-09-09T22:58:59, 40579@2021-09-10T03:46:18] Error reading from RTSP peer: Connection reset by peer (os error 104)
Sep 10 03:46:18 nuc moonfire-nvr[2940163]: s-back_west-main moonfire_nvr::streamer] back_west-main: sleeping for PT1S after error: [192.168.5.3:44818(me)->192.168.5.101:554@2021-09-09T22:59:59, 40473@2021-09-10T03:46:18] Error reading from RTSP peer: Connection reset by peer (os error 104)
Sep 10 03:47:18 nuc moonfire-nvr[2940163]: s-back_west-main moonfire_nvr::streamer] back_west-main: sleeping for PT1S after error: [192.168.5.3:37462(me)->192.168.5.101:554@2021-09-10T03:47:18, 0@2021-09-10T03:47:18] Unauthorized response to DESCRIBE CSeq=1: Non-digest authentication requested: Basic realm="4419b6696b42", Basic realm="4419b6696b42"
Sep 10 05:03:49 nuc moonfire-nvr[2940163]: s-back_east-main moonfire_nvr::streamer] back_east-main: sleeping for PT1S after error: [192.168.5.3:46364(me)->192.168.5.102:554@2021-09-10T01:04:30, 34081@2021-09-10T05:03:49] Error reading from RTSP peer: Connection reset by peer (os error 104)
Sep 10 05:38:48 nuc moonfire-nvr[2940163]: s-west_side-sub moonfire_nvr::streamer] west_side-sub: sleeping for PT1S after error: [192.168.5.3:48316(me)->192.168.5.104:554@2021-09-10T02:41:29, 25618@2021-09-10T05:38:48] Error reading from RTSP peer: Connection reset by peer (os error 104)
Sep 10 06:57:56 nuc moonfire-nvr[2940163]: s-west_side-main moonfire_nvr::streamer] west_side-main: sleeping for PT1S after error: [192.168.5.3:51720(me)->192.168.5.104:554@2021-09-10T03:28:37, 29999@2021-09-10T06:57:56] Error reading from RTSP peer: Connection reset by peer (os error 104)
Sep 10 07:18:00 nuc moonfire-nvr[2940163]: s-back_east-sub moonfire_nvr::streamer] back_east-sub: sleeping for PT1S after error: [192.168.5.3:34462(me)->192.168.5.102:554@2021-09-09T22:22:10, 74450@2021-09-10T07:18:00] Error reading from RTSP peer: Connection reset by peer (os error 104)

After each TEARDOWN failure, we waited for the session to expire before retrying.

Sep 09 22:58:00 nuc moonfire-nvr[2940163]: s-back_west-sub moonfire_nvr::streamer] back_west-sub: Waiting 58.998622284s for 1 stale sessions to expire
Sep 09 22:58:59 nuc moonfire-nvr[2940163]: s-back_west-sub moonfire_nvr::streamer] back_west-sub: Done waiting
Sep 09 22:59:00 nuc moonfire-nvr[2940163]: s-back_west-main moonfire_nvr::streamer] back_west-main: Waiting 58.999854808s for 1 stale sessions to expire
Sep 09 22:59:59 nuc moonfire-nvr[2940163]: s-back_west-main moonfire_nvr::streamer] back_west-main: Done waiting
Sep 10 00:42:17 nuc moonfire-nvr[2940163]: s-west_side-main moonfire_nvr::streamer] west_side-main: Waiting 58.997838487s for 1 stale sessions to expire
Sep 10 00:43:16 nuc moonfire-nvr[2940163]: s-west_side-main moonfire_nvr::streamer] west_side-main: Done waiting
Sep 10 01:03:31 nuc moonfire-nvr[2940163]: s-back_east-main moonfire_nvr::streamer] back_east-main: Waiting 58.99851865s for 1 stale sessions to expire
Sep 10 01:04:30 nuc moonfire-nvr[2940163]: s-back_east-main moonfire_nvr::streamer] back_east-main: Done waiting
Sep 10 02:40:30 nuc moonfire-nvr[2940163]: s-west_side-sub moonfire_nvr::streamer] west_side-sub: Waiting 58.99778954s for 1 stale sessions to expire
Sep 10 02:41:29 nuc moonfire-nvr[2940163]: s-west_side-sub moonfire_nvr::streamer] west_side-sub: Done waiting
Sep 10 03:46:19 nuc moonfire-nvr[2940163]: s-back_west-sub moonfire_nvr::streamer] back_west-sub: Waiting 59.005662614s for 2 stale sessions to expire
Sep 10 03:46:19 nuc moonfire-nvr[2940163]: s-back_west-main moonfire_nvr::streamer] back_west-main: Waiting 58.998409337s for 2 stale sessions to expire
Sep 10 03:47:18 nuc moonfire-nvr[2940163]: s-back_west-sub moonfire_nvr::streamer] back_west-sub: Done waiting
Sep 10 03:47:18 nuc moonfire-nvr[2940163]: s-back_west-main moonfire_nvr::streamer] back_west-main: Done waiting
Sep 10 05:03:50 nuc moonfire-nvr[2940163]: s-back_east-main moonfire_nvr::streamer] back_east-main: Waiting 58.998603346s for 1 stale sessions to expire
Sep 10 05:04:49 nuc moonfire-nvr[2940163]: s-back_east-main moonfire_nvr::streamer] back_east-main: Done waiting
Sep 10 05:38:49 nuc moonfire-nvr[2940163]: s-west_side-sub moonfire_nvr::streamer] west_side-sub: Waiting 58.998713597s for 1 stale sessions to expire
Sep 10 05:39:48 nuc moonfire-nvr[2940163]: s-west_side-sub moonfire_nvr::streamer] west_side-sub: Done waiting
Sep 10 06:57:57 nuc moonfire-nvr[2940163]: s-west_side-main moonfire_nvr::streamer] west_side-main: Waiting 58.998505047s for 1 stale sessions to expire
Sep 10 06:58:56 nuc moonfire-nvr[2940163]: s-west_side-main moonfire_nvr::streamer] west_side-main: Done waiting
Sep 10 07:18:01 nuc moonfire-nvr[2940163]: s-back_east-sub moonfire_nvr::streamer] back_east-sub: Waiting 58.995361566s for 1 stale sessions to expire
Sep 10 07:19:00 nuc moonfire-nvr[2940163]: s-back_east-sub moonfire_nvr::streamer] back_east-sub: Done waiting
Sep 10 08:44:38 nuc moonfire-nvr[2940163]: s-back_west-sub moonfire_nvr::streamer] back_west-sub: Waiting 58.998242234s for 1 stale sessions to expire
Sep 10 08:45:37 nuc moonfire-nvr[2940163]: s-back_west-sub moonfire_nvr::streamer] back_west-sub: Done waiting

Most likely starting a fresh connection and issuing a TEARDOWN would have been successful, and then we could have resumed the stream a minute sooner.

So:

  1. at least in the "connection reset by peer" (on read) / "broken pipe" (on write) case, and probably the "RTSP framing error" case, retry with a fresh connection at least once.
  2. improve the SessionGroup API so there's a way to wait for all the maybe_playing stale session to be gone. There's a wait to wait for all teardowns to be attempted once, and a way to wait for the session expiration time, but not a way to wait for "all have either been torn down successfully (possibly on retry) or have expired".

Chat

Hey, I think this project is what I need to, I would like to chat with you about this, is there some discord server to talk or something similar?

Nice work.

aac.rs miss a frequency arm "0x4 => 41_000,"

let sampling_frequency = match r
.read_u8(4)
.map_err(|e| format!("unable to read sampling_frequency: {}", e))?
{
0x0 => 96_000,
0x1 => 88_200,
0x2 => 64_000,
0x3 => 48_000,
0x4 => 41_000, //<----- missing
0x5 => 32_000,
0x6 => 24_000,
0x7 => 22_050,
0x8 => 16_000,
0x9 => 12_000,
0xa => 11_025,
0xb => 8_000,
0xc => 7_350,

work around invalid sprop-parameter-sets from VStarcam cameras

I was trying to use the example but it failed with #41. I'll try to debug this when I have more time. I was just testing retina new updates with Vstarcam since I promised I'd test if everything works. On my code, I'm getting:

thread 'retina_client_cam1' panicked at 'called `Result::unwrap()` on an `Err` value: RtspResponseError { conn_ctx: ConnectionContext { local_addr: 172.17.0.2:48346, peer_addr: 192.168.1.22:10554, established_wall: WallTime(Timespec { sec: 1638133120, nsec: 895297209 }), established: Instant { tv_sec: 3367, tv_nsec: 506005897 } }, msg_ctx: RtspMessageContext { pos: 119, received_wall: WallTime(Timespec { sec: 1638133120, nsec: 963622369 }), received: Instant { tv_sec: 3367, tv_nsec: 574330667 } }, method: Describe, cseq: 2, status: Ok, description: "bad NAL header e9" }', /home/dev/project/src/rtsp/retina_client.rs:150:44

I'm sending you the wireshark capture via email.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.