Giter VIP home page Giter VIP logo

str0m's Introduction

str0m

str0m logo

A synchronous sans I/O WebRTC implementation in Rust.

This is a Sans I/O implementation meaning the Rtc instance itself is not doing any network talking. Furthermore it has no internal threads or async tasks. All operations are synchronously happening from the calls of the public API.

This is deliberately not a standard RTCPeerConnection API since that isn't a great fit for Rust. See more details in below section.

Join us

We are discussing str0m things on Zulip. Join us using this invitation link. Or browse the discussions anonymously at str0m.zulipchat.com

silly clip showing video playing

Usage

The chat example shows how to connect multiple browsers together and act as an SFU (Selective Forwarding Unit). The example multiplexes all traffic over one server UDP socket and uses two threads (one for the web server, and one for the SFU loop).

TLS

For the browser to do WebRTC, all traffic must be under TLS. The project ships with a self-signed certificate that is used for the examples. The certificate is for hostname str0m.test since TLD .test should never resolve to a real DNS name.

cargo run --example chat

The log should prompt you to connect a browser to https://10.0.0.103:3000 – this will most likely cause a security warning that you must get the browser to accept.

The http-post example roughly illustrates how to receive media data from a browser client. The example is single threaded and is a bit simpler than the chat. It is a good starting point to understand the API.

cargo run --example http-post

Passive

For passive connections, i.e. where the media and initial OFFER is made by a remote peer, we need these steps to open the connection.

// Instantiate a new Rtc instance.
let mut rtc = Rtc::new();

//  Add some ICE candidate such as a locally bound UDP port.
let addr = "1.2.3.4:5000".parse().unwrap();
let candidate = Candidate::host(addr, "udp").unwrap();
rtc.add_local_candidate(candidate);

// Accept an incoming offer from the remote peer
// and get the corresponding answer.
let offer = todo!();
let answer = rtc.sdp_api().accept_offer(offer).unwrap();

// Forward the answer to the remote peer.

// Go to _run loop_

Active

Active connections means we are making the inital OFFER and waiting for a remote ANSWER to start the connection.

// Instantiate a new Rtc instance.
let mut rtc = Rtc::new();

// Add some ICE candidate such as a locally bound UDP port.
let addr = "1.2.3.4:5000".parse().unwrap();
let candidate = Candidate::host(addr, "udp").unwrap();
rtc.add_local_candidate(candidate);

// Create a `SdpApi`. The change lets us make multiple changes
// before sending the offer.
let mut change = rtc.sdp_api();

// Do some change. A valid OFFER needs at least one "m-line" (media).
let mid = change.add_media(MediaKind::Audio, Direction::SendRecv, None, None);

// Get the offer.
let (offer, pending) = change.apply().unwrap();

// Forward the offer to the remote peer and await the answer.
// How to transfer this is outside the scope for this library.
let answer = todo!();

// Apply answer.
rtc.sdp_api().accept_answer(pending, answer).unwrap();

// Go to _run loop_

Run loop

Driving the state of the Rtc forward is a run loop that, regardless of sync or async, looks like this.

// Buffer for reading incoming UDP packets.
let mut buf = vec![0; 2000];

// A UdpSocket we obtained _somehow_.
let socket: UdpSocket = todo!();

loop {
    // Poll output until we get a timeout. The timeout means we
    // are either awaiting UDP socket input or the timeout to happen.
    let timeout = match rtc.poll_output().unwrap() {
        // Stop polling when we get the timeout.
        Output::Timeout(v) => v,

        // Transmit this data to the remote peer. Typically via
        // a UDP socket. The destination IP comes from the ICE
        // agent. It might change during the session.
        Output::Transmit(v) => {
            socket.send_to(&v.contents, v.destination).unwrap();
            continue;
        }

        // Events are mainly incoming media data from the remote
        // peer, but also data channel data and statistics.
        Output::Event(v) => {

            // Abort if we disconnect.
            if v == Event::IceConnectionStateChange(IceConnectionState::Disconnected) {
                return;
            }

            // TODO: handle more cases of v here, such as incoming media data.

            continue;
        }
    };

    // Duration until timeout.
    let duration = timeout - Instant::now();

    // socket.set_read_timeout(Some(0)) is not ok
    if duration.is_zero() {
        // Drive time forwards in rtc straight away.
        rtc.handle_input(Input::Timeout(Instant::now())).unwrap();
        continue;
    }

    socket.set_read_timeout(Some(duration)).unwrap();

    // Scale up buffer to receive an entire UDP packet.
    buf.resize(2000, 0);

    // Try to receive. Because we have a timeout on the socket,
    // we will either receive a packet, or timeout.
    // This is where having an async loop shines. We can await multiple things to
    // happen such as outgoing media data, the timeout and incoming network traffic.
    // When using async there is no need to set timeout on the socket.
    let input = match socket.recv_from(&mut buf) {
        Ok((n, source)) => {
            // UDP data received.
            buf.truncate(n);
            Input::Receive(
                Instant::now(),
                Receive {
                    proto: Protocol::Udp,
                    source,
                    destination: socket.local_addr().unwrap(),
                    contents: buf.as_slice().try_into().unwrap(),
                },
            )
        }

        Err(e) => match e.kind() {
            // Expected error for set_read_timeout().
            // One for windows, one for the rest.
            ErrorKind::WouldBlock
                | ErrorKind::TimedOut => Input::Timeout(Instant::now()),

            e => {
                eprintln!("Error: {:?}", e);
                return; // abort
            }
        },
    };

    // Input is either a Timeout or Receive of data. Both drive the state forward.
    rtc.handle_input(input).unwrap();
}

Sending media data

When creating the media, we can decide which codecs to support, and they are negotiated with the remote side. Each codec corresponds to a "payload type" (PT). To send media data we need to figure out which PT to use when sending.

// Obtain mid from Event::MediaAdded
let mid: Mid = todo!();

// Create a media writer for the mid.
let writer = rtc.writer(mid).unwrap();

// Get the payload type (pt) for the wanted codec.
let pt = writer.payload_params().nth(0).unwrap().pt();

// Write the data
let wallclock = todo!();   // Absolute time of the data
let media_time = todo!();  // Media time, in RTP time
let data: &[u8] = todo!(); // Actual data
writer.write(pt, wallclock, media_time, data).unwrap();

Media time, wallclock and local time

str0m has three main concepts of time. "now", media time and wallclock.

Now

Some calls in str0m, such as Rtc::handle_input takes a now argument that is a std::time::Instant. These calls "drive the time forward" in the internal state. This is used for everything like deciding when to produce various feedback reports (RTCP) to remote peers, to bandwidth estimation (BWE) and statistics.

Str0m has no internal clock calls. I.e. str0m never calls Instant::now() itself. All time is external input. That means it's possible to construct test cases driving an Rtc instance faster than realtime (see the integration tests).

Media time

Each RTP header has a 32 bit number that str0m calls media time. Media time is in some time base that is dependent on the codec, however all codecs in str0m use 90_000Hz for video and 48_000Hz for audio.

For video the MediaTime type is <timestamp>/90_000 str0m extends the 32 bit number in the RTP header to 64 bit taking into account "rollover". 64 bit is such a large number the user doesn't need to think about rollovers.

Wallclock

With wallclock str0m means the time a sample of media was produced at an originating source. I.e. if we are talking into a microphone the wallclock is the NTP time the sound is sampled.

We can't know the exact wallclock for media from a remote peer since not every device is synchronized with NTP. Every sender does periodically produce a Sender Report (SR) that contains the peer's idea of its wallclock, however this number can be very wrong compared to "real" NTP time.

Furthermore, not all remote devices will have a linear idea of time passing that exactly matches the local time. A minute on the remote peer might not be exactly one minute locally.

These timestamps become important when handling simultaneous audio from multiple peers.

When writing media we need to provide str0m with an estimated wallclock. The simplest strategy is to only trust local time and use arrival time of the incoming UDP packet. Another simple strategy is to lock some time T at the first UDP packet, and then offset each wallclock using MediaTime, i.e. for video we could have T + <media time>/90_000

A production worthy SFU probably needs an even more sophisticated strategy weighing in all possible time sources to get a good estimate of the remote wallclock for a packet.

Project status

Str0m was originally developed by Martin Algesten of Lookback. We use str0m for a specific use case: str0m as a server SFU (as opposed to peer-2-peer). That means we are heavily testing and developing the parts needed for our use case. Str0m is intended to be an all-purpose WebRTC library, which means it should also work for peer-2-peer (mostly thinking about the ICE agent), but these areas have not received as much attention and testing.

Performance is very good, there have been some work the discover and optimize bottlenecks. Such efforts are of course never ending with diminishing returns. While there are no glaringly obvious performance bottlenecks, more work is always welcome – both algorithmically and allocation/cloning in hot paths etc.

Design

Output from the Rtc instance can be grouped into three kinds.

  1. Events (such as receiving media or data channel data).
  2. Network output. Data to be sent, typically from a UDP socket.
  3. Timeouts. Indicates when the instance next expects a time input.

Input to the Rtc instance is:

  1. User operations (such as sending media or data channel data).
  2. Network input. Typically read from a UDP socket.
  3. Timeouts. As obtained from the output above.

The correct use can be seen in the above Run loop or in the examples.

Sans I/O is a pattern where we turn both network input/output as well as time passing into external input to the API. This means str0m has no internal threads, just an enormous state machine that is driven forward by different kinds of input.

Sample or RTP level?

Str0m defaults to the "sample level" which treats the RTP as an internal detail. The user will thus mainly interact with:

  1. Event::MediaData to receive full "samples" (audio frames or video frames).
  2. Writer::write to write full samples.
  3. Writer::request_keyframe to request keyframes.

Sample level

All codecs such as h264, vp8, vp9 and opus outputs what we call "Samples". A sample has a very specific meaning for audio, but this project uses it in a broader sense, where a sample is either a video or audio time stamped chunk of encoded data that typically represents a chunk of audio, or one single frame for video.

Samples are not suitable to use directly in UDP (RTP) packets - for one they are too big. Samples are therefore further chunked up by codec specific payloaders into RTP packets.

RTP mode

Str0m also provides an RTP level API. This would be similar to many other RTP libraries where the RTP packets themselves are the the API surface towards the user (when building an SFU one would often talk about "forwarding RTP packets", while with str0m we can also "forward samples"). Using this API requires a deeper knowledge of RTP and WebRTC.

To enable RTP mode

let rtc = Rtc::builder()
    // Enable RTP mode for this Rtc instance.
    // This disables `MediaEvent` and the `Writer::write` API.
    .set_rtp_mode(true)
    .build();

RTP mode gives us some new API points.

  1. Event::RtpPacket emitted for every incoming RTP packet. Empty packets for bandwidth estimation are silently discarded.
  2. StreamTx::write_rtp to write outgoing RTP packets.
  3. StreamRx::request_keyframe to request keyframes from remote.

NIC enumeration and TURN (and STUN)

The ICE RFC talks about "gathering ice candidates". This means inspecting the local network interfaces and potentially binding UDP sockets on each usable interface. Since str0m is Sans I/O, this part is outside the scope of what str0m does. How the user figures out local IP addresses, via config or via looking up local NICs is not something str0m cares about.

TURN is a way of obtaining IP addresses that can be used as fallback in case direct connections fail. We consider TURN similar to enumerating local network interfaces – it's a way of obtaining sockets.

All discovered candidates, be they local (NIC) or remote sockets (TURN), are added to str0m and str0m will perform the task of ICE agent, forming "candidate pairs" and figuring out the best connection while the actual task of sending the network traffic is left to the user.

The importance of &mut self

Rust shines when we can eschew locks and heavily rely &mut for data write access. Since str0m has no internal threads, we never have to deal with shared data. Furthermore the the internals of the library is organized such that we don't need multiple references to the same entities. In str0m there are no Rc, Mutex, mpsc, Arc(*), or other locks.

This means all input to the lib can be modelled as handle_something(&mut self, something).

(*) Ok. There is one Arc if you use Windows where we also require openssl.

Not a standard WebRTC "Peer Connection" API

The library deliberately steps away from the "standard" WebRTC API as seen in JavaScript and/or webrtc-rs (or Pion in Go). There are few reasons for this.

First, in the standard API, events are callbacks, which are not a great fit for Rust. Callbacks require some kind of reference (ownership?) over the entity the callback is being dispatched upon. I.e. if in Rust we want pc.addEventListener(x), x needs to be wholly owned by pc, or have some shared reference (like Arc). Shared references means shared data, and to get mutable shared data, we will need some kind of lock. i.e. Arc<Mutex<EventListener>> or similar.

As an alternative we could turn all events into mpsc channels, but listening to multiple channels is awkward without async.

Second, in the standard API, entities like RTCPeerConnection and RTCRtpTransceiver, are easily clonable and/or long lived references. I.e. pc.getTranscievers() returns objects that can be retained and owned by the caller. This pattern is fine for garbage collected or reference counted languages, but not great with Rust.

Panics, Errors and unwraps

Str0m adheres to fail-fast. That means rather than brushing state bugs under the carpet, it panics. We make a distinction between errors and bugs.

  • Errors are as a result of incorrect or impossible to understand user input.
  • Bugs are broken internal invariants (assumptions).

If you scan the str0m code you find a few unwrap() (or expect()). These will (should) always be accompanied by a code comment that explains why the unwrap is okay. This is an internal invariant, a state assumption that str0m is responsible for maintaining.

We do not believe it's correct to change every unwrap()/expect() into unwrap_or_else(), if let Some(x) = x { ... } etc, because doing so brushes an actual problem (an incorrect assumption) under the carpet. Trying to hobble along with an incorrect state would at best result in broken behavior, at worst a security risk!

Panics are our friends: panic means bug

And also: str0m should never panic on any user input. If you encounter a panic, please report it!

Catching panics

Panics should be incredibly rare, or we have a serious problem as a project. For an SFU, it might not be ideal if str0m encounters a bug and brings the entire server down with it.

For those who want an extra level of safety, we recommend looking at catch_unwind to safely discard a faulty Rtc instance. Since Rtc has no internal threads, locks or async tasks, discarding the instance never risk poisoning locks or other issues that can happen when catching a panic.

FAQ

Features

Below is a brief comparison of features between libWebRTC and str0m to help you determine if str0m is suitable for your project.

Feature str0m libWebRTC
Peer Connection API
SDP
ICE
Data Channels
Send/Recv Reports
Transport Wide CC
Bandwidth Estimation
Simulcast
NACK
Packetize
Fixed Depacketize Buffer
Adaptive Jitter Buffer
Video/audio capture
Video/audio encode
Video/audio decode
Audio render
Turn
Network interface enum

Platform Support

Platforms str0m is compiled and tested on:

Platform Compiled Tested
x86_64-pc-windows-msvc
x86_64-unknown-linux-gnu
x86_64-apple-darwin

If your platform isn't listed but is supported by Rust, we'd love for you to give str0m a try and share your experience. We greatly appreciate your feedback!

Does str0m support IPv4, IPv6, UDP and TCP?

Certainly! str0m fully support IPv4, IPv6, UDP and TCP protocols.

Can I utilize str0m with any Rust async runtime?

Absolutely! str0m is fully sync, ensuring that it integrates seamlessly with any Rust async runtime you opt for.

Can I create a client with str0m?

Of course! You have the freedom to create a client with str0m. However, please note that some common client features like media encoding, decoding, and capture are not included in str0m. But don't let that stop you from building amazing applications!

Can I use str0m in a media server?

Yes! str0m excels as a server component with support for both RTP API and Sample API. You can easily build that recording server or SFU you dreamt of in Rust!

Can I deploy the chat example into production?

While the chat example showcases how to use str0m's API, it's not intended for production use or heavy load. Writing a full-featured SFU or MCU (Multipoint Control Unit) is a significant undertaking, involving various design decisions based on production requirements.

Discovered a bug? Here's how to share it with us

We'd love to hear about it! Please submit an issue and consider joining our Zulip community to discuss further. For a seamless reporting experience, refer to this exemplary bug report: #382. We appreciate your contribution to making str0m better!

I am allergic to SDP can you help me?

Yes use the direct API!


str0m's chat runs on Zulip and is sponsored by Zulip for open source projects.

Zulip logo

License: MIT OR Apache-2.0

str0m's People

Contributors

algesten avatar altonen avatar biceneutron avatar bogdlyt avatar davibe avatar david-flok avatar efer-ms avatar evdokimovs avatar gak avatar geekylthyosaur avatar giangndm avatar h1t avatar k0nserv avatar koushiro avatar lexnv avatar logist322 avatar morajabi avatar oxleys avatar pthatcher avatar thomaseizinger avatar tskinn avatar xnorpx 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar

str0m's Issues

Obey payload parameters for feedback

a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli

The SDP configures which feedback mechanisms to use for the PT. We currently ignore these, but should obey them (apart from goog-remb, which we haven't implemented).

Notice the config is per PT which causes some headaches for session wide feedback TWCC. Should we send TWCC if just one PT has it enabled? Or do we need to see traffic on that PT? Unclear.

Chat room example

Make a full chat room where each incoming voice/camera is reflected out to all other connected clients.

Reduce number of allocations

The impls has a few Vec<u8> arguments. It would be good to look through the instances of these to see where we can re-use buffers (or use ring buffers), and where we can use lifetimes to avoid internal allocation.

Zero allocations is a non-goal, but we can do better than we currently do.

Handle RTP receive register probation better

Currently if the receive register is in "probation", we drop the incoming RTP packet.

This is not great, because we miss the first packets of the first sample. We need to either reconsider the probation mechanism (do we need it?). It's in the RFC for RTP, but it doesn't seem like libwebrtc does it.

Another strategy would be to retain incoming RTP packets until probation is over and "replay them" into the depacketizing buffer.

Make minimum DTLS version configurable

By default we should disallow DTLS 1.0. To do this we need a PR to land in rust-openssl crate: sfackler/rust-openssl#1886

The man page in openssl: https://www.openssl.org/docs/man3.1/man3/DTLSv1_2_method.html - tells us DTLSv1_2_method is deprecated. The way to limit the DTLS version (or TLS for that matter), is to use SSL_CTX_set_min_proto_version. In the Rust wrapper of openssl this corresponds to https://docs.rs/openssl/0.10.50/openssl/ssl/struct.SslContextBuilder.html#method.set_min_proto_version however SslVersion constant lacks the values we need: https://docs.rs/openssl/0.10.50/openssl/ssl/struct.SslVersion.html

Improve SSRC allocation

SSRC allocation needs to be dynamic and function in two "modes". There are a number of scenarios that needs to be handled.

Classic mode

In classic mode, if the direction of an m-line is such that "I might send", the SDP contains the following lines (regardless of offer or answer).

a=rtpmap:98 VP9/90000
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
…
a=ssrc-group:FID 482634678 553121561
a=ssrc:482634678 cname:7zASaW9vFA6IT6Qe
a=ssrc:482634678 msid:xTscQI56sDGGHeMvl27qpDQTlVg7Sv1GrFc0 1dcf2d05-d3a4-482a-8bbe-6c6b217a9f3e
a=ssrc:553121561 cname:7zASaW9vFA6IT6Qe
a=ssrc:553121561 msid:xTscQI56sDGGHeMvl27qpDQTlVg7Sv1GrFc0 1dcf2d05-d3a4-42a-8bbe-6c6b217a9f3e
  1. The relationship between rtx and VP9 tells us we are using a separate RTX stream for repairs.
  2. a=group:FID tells us 553121561 repairs the stream 482634678
  3. a=ssrc:482634678 tells us this is the main SSRC, because it comes first (order is important!)
  4. a=ssrc:553121561 tells us this is the repair SSRC, because it comes second.

Rid mode

With simulcast, Chrome started doing this in a new way. Instead of advertising the SSRC it's going to use in advance, it relies on RTP header extensions to communicate the role of the packet. The receiver will still arrive at the same relationship as the Classic mode, but must discover it dynamically.

a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
…
a=rtpmap:98 VP9/90000
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
...
a=rid:h send
a=rid:l send
a=simulcast:send h;l

Instead of giving us any a=ssrc lines, we are negotiating the headers mid, rtp-stream-id and repaired-rtp-stream-id. This means that when an RTP packet arrives,

  1. All three headers are necessary for this dynamic mode.
  2. a=rid lines and a=simulcast tells us the expected header values for rtp-stream-id.
  3. mid + rtp-stream-id tells us the SSRC of the packet is the main stream.
  4. mid + repaired-rtp-stream-id tells the SSRC of the packet is the repair stream.

It appears Chrome will send us a couple of empty repair packets as "info" on stream start.

There was another way

Before the Rid way, simulcast was triggered by munging the SDP and adding a line like this, together with a=ssrc lines for all the SSRCs. This told us these 3 SSRC were used for different simulcast levels.

a=ssrc-group:SIM 659652645 1982135572 3604909222

It's unclear if we need to support this mode at all. It might just been an experiment that can be forgotten.

To solve

We need to support both Classic mode and Rid mode. It might be Chrome eventually goes only for the Rid mode. We need dynamic detection and appropriate handling.

Any m-line may start in one direction and then switch to the opposite (or sendrecv). When we detect the mode will allow for sending packets, we either need to allocate SSRC straight away for Classic mode, or maybe later in Rid mode. The direction change will result in an SDP negotation, and if we are in Classic mode, the SSRC we intend to use must be communicated as a=ssrc lines straight away.

Sequence counters and sender reports are tracked per SSRC, we need to represent the SSRC allocation and structure in a non-confusing, robust way.

For simulcast the most usual setup is that the browser sends multiple streams (with Rid), and the SFU will forward one of those to another peer. Sending simulcast from the SFU is unusual, but needs to be supported.


The current situation works, but the code to handle the situations is too spread out and the logic is not obvious.

bug(Windows) Missing (openssl) DLLs

I am trying to use str0m in VS Code on Windows 10, when I cargo run it appears some DLL files are not found:

exit code: 0xc0000135, STATUS_DLL_NOT_FOUND

full output:

Compiling str0m-chat v0.1.0 (C:\Users\douga\Documents2\code\RUST-projects\str0m-chat)
Finished dev [unoptimized + debuginfo] target(s) in 1m 00s
Running `target\debug\str0m-chat.exe`
error: process didn't exit successfully: `target\debug\str0m-chat.exe` (exit code: 0xc0000135, STATUS_DLL_NOT_FOUND)

That is in VC code's PowerShell terminal only. When I run cargo run in Bash (MINGW64) it runs fine. So I ran ldd in Bash and it shows me the DLLs required by the executable:

$ ldd target/debug/str0m-chat.exe
ntdll.dll => /c/WINDOWS/SYSTEM32/ntdll.dll (0x7ffb1fd10000)
KERNEL32.DLL => /c/WINDOWS/System32/KERNEL32.DLL (0x7ffb1fb70000)
KERNELBASE.dll => /c/WINDOWS/System32/KERNELBASE.dll (0x7ffb1d5f0000)
bcrypt.dll => /c/WINDOWS/System32/bcrypt.dll (0x7ffb1d410000)
ucrtbase.dll => /c/WINDOWS/System32/ucrtbase.dll (0x7ffb1dab0000)
VCRUNTIME140.dll => /c/WINDOWS/SYSTEM32/VCRUNTIME140.dll (0x7ffb03f60000)
libssl-1_1-x64.dll => /mingw64/bin/libssl-1_1-x64.dll (0x7ffb014d0000)
msvcrt.dll => /c/WINDOWS/System32/msvcrt.dll (0x7ffb1fad0000)
libcrypto-1_1-x64.dll => /mingw64/bin/libcrypto-1_1-x64.dll (0x7ffad8050000)
ADVAPI32.dll => /c/WINDOWS/System32/ADVAPI32.dll (0x7ffb1e950000)
sechost.dll => /c/WINDOWS/System32/sechost.dll (0x7ffb1fc30000)
RPCRT4.dll => /c/WINDOWS/System32/RPCRT4.dll (0x7ffb1f0c0000)
USER32.dll => /c/WINDOWS/System32/USER32.dll (0x7ffb1e7a0000)
win32u.dll => /c/WINDOWS/System32/win32u.dll (0x7ffb1da80000)
GDI32.dll => /c/WINDOWS/System32/GDI32.dll (0x7ffb1eaa0000)
gdi32full.dll => /c/WINDOWS/System32/gdi32full.dll (0x7ffb1dc50000)
msvcp_win.dll => /c/WINDOWS/System32/msvcp_win.dll (0x7ffb1dbb0000)
WS2_32.dll => /c/WINDOWS/System32/WS2_32.dll (0x7ffb1fa60000)

Two of them are in MINGW64 instead of SYSTEM32:

libssl-1_1-x64.dll => /mingw64/bin/libssl-1_1-x64.dll (0x7ffb014d0000)
libcrypto-1_1-x64.dll => /mingw64/bin/libcrypto-1_1-x64.dll (0x7ffad8050000)

So I figure this is some sort of OpenSSL install issue on Windows.

Has anyone else run into this and discovered any ways around it?

Allocate Sender/Receiver in pairs

Sender and Receiver sit under Media (and media corresponds to an m-line).

Any m-line can at any point change direction. Even if something starts RecvOnly, we can later become SendRecv (or even SendOnly).

If we want to send a PLI to a RecvOnly, the RTCP packet wants us to have both the incoming media SSRC as well as some local SSRC (which would be the "Sender SSRC"). For now we've used SSRC 0, but this is pretty bad since the SSRC is a components of the (AES crypto) IV in SRTCP – and 0 being guessable and static is pretty crap.

Currently when applying an SDP offer, we create corresponding Senders for each remote a=ssrc.

However, this breaks down when enabling simulcast, since that makes Chrome stop sending a=ssrc and instead requires us to dynamically create Receivers using the rid/repair-rid RTP header extensions. In this scenario we don't create the corresponding Senders, and thus lacks a "Sender SSRC" for feedback packets.

Rather than loosely matching Sender/Receiver, let's make them hard paired in a new struct called something like Transceiver or Pair. This way we get a static guarantee they always come in pairs and we know which one is allocated to match which.

The final bit of the puzzle is knowing which SSRC (both in and out) are definitely okay, and which might need recreating due to SSRC conflicts. For old school a=ssrc, we can simply avoid the SSRC the other side declares, but for dynamic allocation, there is this risk:

  1. Discover new incoming SSRC from RTP packet.
  2. Create Receiver from rid header extension - also create corresponding Sender.
  3. Discover second new incoming SSRC from RTP packet.

Sender SSRC in 2 could conflict with incoming SSRC in 3.

Support firefox

Getting (clear) error messages in the console for Firefox. This is probably a good way to work through issues that might affect other browsers too.

image

FIR is broken

Chrome complains like so:

[41956:36355:0116/203007.888959:WARNING:fir.cc(59)] Packet is too small to be a valid FIR packet.
[41956:36355:0116/203007.888998:WARNING:rtcp_receiver.cc(557)] 1 RTCP blocks were skipped due to being malformed or of unrecognized/unsupported type, during the past 10 second period.

Support simulcast

Simulcast is the function of sending multiple RTP streams for the same track at different bitrate levels. An SFU can chose which bitrate level is appropriate given the bandwidth.

     Bad link -> send low bitrate                                               
                  │                                                             
                  │                                                             
                  │                                                             
┌─────────────┐   │    ┌─────────────┐     High    ┌─────────────┐              
│             │   ▼    │             │◀────────────│             │              
│  Receiver   │◀ ─ ─ ─ │     SFU     │             │  Sender 1   │◀───── Camera 
│             │        │             │◀────────────│             │              
└─────────────┘        └─────────────┘      Low    └─────────────┘              

The idea is that Event::MediaData(MediaData) will also carry information about the simulcast level. The MediaWriter should have a builder pattern where we can add on the simulcast level for sending.

MIDs are not 3 characters

MIDs can be any length (well... there's probably some limit in the standard... but it's a lot more than 3). Lengths of 1 or 2 are probably the most common (libwebrtc just increments "0", "1", and it's good to stay small for the header extension value). So maybe it will never matter in practice, but it doesn't seem to make sense to me to make the MID type a [u8; 3]. It's probably best to use String or Vec, or if perf around it really did matter somehow, Arc or smallvec or CompactString or something fancy.

Invalid RTCP packet

Running chrome with --enable-logging=stderr --vmodule="*/webrtc/*=2" gives us these kinds of rows.

[20362:40195:0111/182232.831763:WARNING:common_header.cc(73)] Invalid RTCP header: Padding bit set but 0 padding size specified.
[20362:40195:0111/182232.831784:WARNING:rtcp_receiver.cc(462)] Incoming invalid RTCP packet
[20362:40195:0111/182232.831802:WARNING:common_header.cc(73)] Invalid RTCP header: Padding bit set but 0 padding size specified.
[20362:40195:0111/182232.831814:WARNING:rtcp_receiver.cc(462)] Incoming invalid RTCP packet

Fix confusion of m-line per data channel

Turns out I've misunderstood how multiple data channels are represented in the SDP. The SDP level is one m-line for the SCTP association, of which there is only one for the entire RTC session. I.e. the single m-line is inserted on the first data-channel added to the session, and any additional channels are multiplexed over the same association.

Fails to parse (munged) SDP message

I am trialing to use this library for rust-libp2p as a replacement for webrtc-rs. The protocol we implement using WebRTC is using ice-lite and we rely on SDP munging.

The SDP offer I am trying to feed into str0m errors on the parsing step.

This is my SDP message:

v=0
o=- 0 0 IN IP4 172.17.0.1
s=-
c=IN IP4 172.17.0.1
t=0 0
m=application 9999 UDP/DTLS/SCTP webrtc-datachannel
a=mid:0
a=ice-options:ice2
a=ice-ufrag:libp2p+webrtc+v1/a75469cf670c4079f8c06af4a963c8a1:libp2p+webrtc+v1/a75469cf670c4079f8c06af4a963c8a1
a=ice-pwd:libp2p+webrtc+v1/a75469cf670c4079f8c06af4a963c8a1:libp2p+webrtc+v1/a75469cf670c4079f8c06af4a963c8a1
a=fingerprint:sha-256 FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF
a=setup:actpass
a=sctp-port:5000
a=max-message-size:16384

The parser fails with:

Unexpected `a`\nExpected `c`\nsdp line\n

We have this code working in multiple other languages and browser bindings. From my limited research, this SDP message is still correct. Is the parser perhaps too strict here?

Any help is much appreciated!

DCEP message type mismatches with the RFC standard

When I initiated a data channel in a webrtc-rs client and try to establish the data channel connection with str0m server, I got this error message from webrtc-rs:

ERROR webrtc_data::data_channel] Failed to handle DCEP: Util(Std(StdError(InvalidMessageType(1))))

The cause is that, now the DATA_CHANNEL_ACK message type of DCEP messages is defined as 0x01 in str0m. But according to RFC 8832, this type should be 0x02.

Markerbit is always true when sending audio opus

https://www.rfc-editor.org/rfc/rfc3551.html#section-4.1

2023-04-04T14:40:20.162098Z TRACE str0m::session: Poll RTP: RtpHeader { version: 2, has_padding: false, has_extension: true, marker: true, payload_type: Pt(111), sequence_number: 13128, timestamp: 438720, ssrc: Ssrc(3618840797), ext_vals: ExtensionValues { mid: 1 abs_send_time: 3889608020.161961 voice_activity: true audio_level: -30 transport_cc: 90 }, header_len: 24 }

We need to set markerbit to 0 for audio in general, we need to set it to 1 marking start of talkspurt.

But for this issue, ensure that markerbit is 0 is enough.

Make public API for Candidate::server_reflexive

As I use this project in our production app, I'm going to document which types aren't exposed from the public API of str0m.

  • CandidateKind
  • Candidate::new — cannot create a Server Reflexive candidate in any way

API for informational header extensions

We should support these RTP header exensions:

  • urn:3gpp:video-orientation
  • urn:ietf:params:rtp-hdrext:ssrc-audio-level

And potentially also:

  • http://www.webrtc.org/experiments/rtp-hdrext/color-space
  • http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
  • http://www.webrtc.org/experiments/rtp-hdrext/video-timing
  • http://www.webrtc.org/experiments/rtp-hdrext/playout-delay

To make it possible to provide these, we might want to go back to a builder pattern for Media::write().

Hande PLI/FIR

We both need events for PLI/FIR and ability to send it, per media.

Using `str0m` in `rust-libp2p`

Following the advice from here, I am opening a separate issue!

Here is what we want to do: Have libp2p applications running in the browser connect to server nodes that don't have a valid TLS certificate. Being in the browser, we don't have control over the WebRTC stack but use the RTCPeerConnection API.

The entire protocol is described here: https://github.com/libp2p/specs/blob/master/webrtc/webrtc-direct.md#browser-to-public-server

We also don't have separate STUN servers. Instead, the client knows the server's address through an out-of-band mechanism. We have a self-descriptive address format that looks like this:

/ip4/172.28.0.1/udp/9999/webrtc/certhash/uEiD8c9GWyEurSjh9UkY-YWXaXBJZIHo179zx9IpgDFFKgw/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN

The component after the /certhash part (uEiD8c9GWyEurSjh9UkY-YWXaXBJZIHo179zx9IpgDFFKgw) is the server's certificate fingerprint. This gives us enough information to directly connect to the server.

We want to use str0m for the server part of this setup. The node will always be publicly reachable, hence we are in ICE-lite mode. We only ever need data channels, no media.

Concrete questions I have:

  • Is there a way how I can disable certificate fingerprint validation?
  • The spec describes to perform SDP munging but that doesn't seem to be possible with the sdp_api. I think I achieved the same thing with the direct_api and adding candidates myself. Is there something I should be aware of when doing it that way?

More general questions:

  • Is this possible at all with str0m?
  • Is this a usecase you want to support?

SCTP channel allocation before SCTP init is wrong

If n the inital OFFER we add a data channel, there's a risk we allocate the incorrect SCTP channel id.

        // RFC 8831
        // Unless otherwise defined or negotiated, the
        // streams are picked based on the DTLS role (the client picks even
        // stream identifiers, and the server picks odd stream identifiers).

To allocate odd/even means we must know if we're client or server in the SCTP setup. The setup is controlled by the DTLS a=setup:active attribute that we only know once we've made the very first OFFER/ANSWER dance. Hence we got a chicken and egg situation – can't allocate the sctp channel before OFFER/ANSWER, but also want to create the initial channel already in the first OFFER.

Add support for GCM cipher suites for DTLS-SRTP

Opening issue so we don't lose track.

It said that SRTP_AEAD_AES_128_GCM has lower complexity than SRTP_AES128_CM_SHA180

Context from Zullip discussion.

  • const DTLS_SRTP: &str = "SRTP_AES128_CM_SHA1_80;SRTP_AEAD_AES_128_GCM";
  • const DTLS_SRTP: &str = "SRTP_AES128_CM_SHA1_80";
    then we'd be negotiating both, however we'd also need to implement GCM support but OpenSSL supports it so shouldn't be too challenging

Bug in matching h264 codecs

H264 codecs proposed by the browser are not matched by str0m. This is because of a bug in parsing the profile-level-id in the a=fmtp line.

Another bug is that the PT is not locked down by what the browser proposes.

RTP packet is never resend when received NACK

str0m/src/streams/send.rs

Lines 422 to 428 in 2627fcb

fn do_poll_packet_resend(&mut self, now: Instant, is_padding: bool) -> Option<NextPacket<'_>> {
if self.rtx.is_none() || self.rtx_pt.is_none() {
// We're not doing resends for non-RTX.
return None;
}
let seq_no = loop {

self.rtx_pt is never set then the above logic never passes cause the RTP packet doesn't resend when received NACK

ice-lite doesn't disconnect connections

It's a simple bug. Because ice-lite means not doing the binding requests, we simply don't schedule anything, which isn't right, because the connections not receiving any incoming binding requests, don't get purged.

Rejecting m-lines

Related to #187, I noticed that when Str0m doesn't match any codecs for an m-line it still indicates that the m-line is okay to use.

I believe str0m should be rejecting the m-line by setting the port to 0 in the resulting SDP.

This is specified in section 5.3 of JSEP

For each offered m= section, if any of the following conditions are true, the corresponding m= section in the answer MUST be marked as rejected by setting the port in the m= line to zero, as indicated in [RFC3264], Section 6, and further processing for this m= section can be skipped:

  • The associated RtpTransceiver has been stopped.
  • None of the offered media formats are supported and, if applicable, allowed by codec preferences.
  • The bundle policy is "max-bundle", and this is not the first m= section or in the same bundle group as the first m= section.
  • The bundle policy is "balanced", and this is not the first m= section for this media type or in the same bundle group as the first m= section for this media type.
  • This m= section is in a bundle group, and the group's offerer tagged m= section is being rejected due to one of the above reasons. This requires all m= sections in the bundle group to be rejected, as specified in [I-D.ietf-mmusic-sdp-bundle-negotiation], Section 7.3.3.

(emphasis mine)

RTP level media API

The media API currently works only on "Sample" level, full H264/VP8/etc packets. The RTP level is an internal concern to str0m and there are built-in packetizer/depacketizer to go from sample level to RTP level. https://github.com/algesten/str0m/tree/main/packet

The case against RTP level API

The WebRTC related RFC sometimes seem to consider SFU (Signal Forward Units) as something that just takes incoming RTP packets and routes them to other clients. This solves a quite narrow use case where not many peers participate in the same session.

RTP packets can't pass through an SFU unaltered because many RTP header fields depends on situation:

  • Header extensions – negotiated in SDP, must be fulfilled specific to the peer-peer connection.
  • Sequence numbers – must not be increased if you're not actively sending.
  • TWCC (transport wide congestion control header) – must be set for entire peer.
  • Bandwidth estimation / congestion control – must be handled for entire peer.
  • PLI – if the sending peer has a bad connection, all downstream peers would shout PLI ("PLI storms" is a problem).
  • Nacks and RTX resends – at the very least the original packets must be buffered to handle resends.

The above requires at least some level of rewriting the RTP header.

               Bad link                            ┌─────────────┐
                  │                                │             │
                  │                         ┌──────│  Sender 1   │
                  │                         │      │             │
┌─────────────┐   │    ┌─────────────┐      │      └─────────────┘
│             │   ▼    │             │      │                     
│  Receiver   │◀ ─ ─ ─ │     SFU     │◀─────┤                     
│             │        │             │      │                     
└─────────────┘        └─────────────┘      │      ┌─────────────┐
                                            │      │             │
                                            └──────│  Sender 2   │
                                                   │             │
                                                   └─────────────┘

The receiver here will miss different packets from Sender 1 and Sender 2. In a naive SFU, the NACK, PLI and FIR would be forwarded back to source. That means an increased pressure on bandwidth for all peers, not just the Receiver.

To make the SFU smarter, we can implement various levels of RTP buffering.

  1. Buffer packets to handle NACK. This is very simple, just handle resends on the last leg (str0m does this internally).
  2. Buffer packet sequences to handle some PLI/FIR without hitting the Sender. This requires a knowledge of what is a keyframe, which in turn requires us to reassemble RTP into Samples.

Make an RTP level API

The above doesn't mean it's impossible to make an RTP level API. It just means the RTP level can't be thought of as "simply write this RTP packet to the wire". At the very least all headers must be fixed up to be correct given the SDP, TWCC etc.

I think we want a method to switch either an m-line, or the entire session into "RTP mode", which means instead of emitting Event::MediaData(MediaData), we have another event such as Event::RtpData(RtpData), and also MediaWriter::write_rtp for the other direction.

Document entire public API

Before publishing to crates.io, we want to have at least rudimentary docs for all public types and functions. Ideally we also have lots of examples, but this can come later.

Support for Opus DTX

Currently there is no support for dtx.

Add:

Support for dtx in FormatParams
Change Opus packetizer to be able to write markerbit.

Wrong handle with Offer contain receiver only MID

When the client created an offer with some receiver-only MID, it doesn't create stream_tx, therefore rtc.direct_api().stream_tx_by_mid will return None

v=0
o=- 1006561046140636132 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0 1 2 3
a=extmap-allow-mixed
a=msid-semantic: WMS bf76fbfd-8d53-4b62-b2d0-e540a2968830
m=video 9 UDP/TLS/RTP/SAVPF 96 97 102 103 104 105 106 107 108 109 127 125 39 40 98 99 100 101 112 113 116 117 118 (120 more lines) mid=0
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:CVLQ
a=ice-pwd:P8RYVOdjcj8WIO+LyWYyQ73A
a=ice-options:trickle
a=fingerprint:sha-256 9A:53:C6:C8:35:27:B7:AD:C6:D6:F5:D9:01:37:5F:0C:2C:01:50:17:82:DE:EF:A4:F7:FC:55:24:88:CA:81:56
a=setup:actpass
a=mid:0
a=extmap:1 urn:ietf:params:rtp-hdrext:toffset
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:3 urn:3gpp:video-orientation
a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space
a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
a=sendonly
a=msid:bf76fbfd-8d53-4b62-b2d0-e540a2968830 59a18ea0-a36d-4b98-9440-b92d5f21136e
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=rtpmap:102 H264/90000
a=rtcp-fb:102 goog-remb
a=rtcp-fb:102 transport-cc
a=rtcp-fb:102 ccm fir
a=rtcp-fb:102 nack
a=rtcp-fb:102 nack pli
a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
a=rtpmap:103 rtx/90000
a=fmtp:103 apt=102
a=rtpmap:104 H264/90000
a=rtcp-fb:104 goog-remb
a=rtcp-fb:104 transport-cc
a=rtcp-fb:104 ccm fir
a=rtcp-fb:104 nack
a=rtcp-fb:104 nack pli
a=fmtp:104 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f
a=rtpmap:105 rtx/90000
a=fmtp:105 apt=104
a=rtpmap:106 H264/90000
a=rtcp-fb:106 goog-remb
a=rtcp-fb:106 transport-cc
a=rtcp-fb:106 ccm fir
a=rtcp-fb:106 nack
a=rtcp-fb:106 nack pli
a=fmtp:106 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
a=rtpmap:107 rtx/90000
a=fmtp:107 apt=106
a=rtpmap:108 H264/90000
a=rtcp-fb:108 goog-remb
a=rtcp-fb:108 transport-cc
a=rtcp-fb:108 ccm fir
a=rtcp-fb:108 nack
a=rtcp-fb:108 nack pli
a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
a=rtpmap:109 rtx/90000
a=fmtp:109 apt=108
a=rtpmap:127 H264/90000
a=rtcp-fb:127 goog-remb
a=rtcp-fb:127 transport-cc
a=rtcp-fb:127 ccm fir
a=rtcp-fb:127 nack
a=rtcp-fb:127 nack pli
a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f
a=rtpmap:125 rtx/90000
a=fmtp:125 apt=127
a=rtpmap:39 H264/90000
a=rtcp-fb:39 goog-remb
a=rtcp-fb:39 transport-cc
a=rtcp-fb:39 ccm fir
a=rtcp-fb:39 nack
a=rtcp-fb:39 nack pli
a=fmtp:39 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=4d001f
a=rtpmap:40 rtx/90000
a=fmtp:40 apt=39
a=rtpmap:98 VP9/90000
a=rtcp-fb:98 goog-remb
a=rtcp-fb:98 transport-cc
a=rtcp-fb:98 ccm fir
a=rtcp-fb:98 nack
a=rtcp-fb:98 nack pli
a=fmtp:98 profile-id=0
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
a=rtpmap:100 VP9/90000
a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=fmtp:100 profile-id=2
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:112 H264/90000
a=rtcp-fb:112 goog-remb
a=rtcp-fb:112 transport-cc
a=rtcp-fb:112 ccm fir
a=rtcp-fb:112 nack
a=rtcp-fb:112 nack pli
a=fmtp:112 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=64001f
a=rtpmap:113 rtx/90000
a=fmtp:113 apt=112
a=rtpmap:116 red/90000
a=rtpmap:117 rtx/90000
a=fmtp:117 apt=116
a=rtpmap:118 ulpfec/90000
a=rid:0 send
a=rid:1 send
a=rid:2 send
a=simulcast:send 0;1;2
m=audio 9 UDP/TLS/RTP/SAVPF 111 63 9 0 8 13 110 126 (25 more lines) mid=1
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:CVLQ
a=ice-pwd:P8RYVOdjcj8WIO+LyWYyQ73A
a=ice-options:trickle
a=fingerprint:sha-256 9A:53:C6:C8:35:27:B7:AD:C6:D6:F5:D9:01:37:5F:0C:2C:01:50:17:82:DE:EF:A4:F7:FC:55:24:88:CA:81:56
a=setup:actpass
a=mid:1
a=extmap:14 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid
a=recvonly
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=rtcp-fb:111 transport-cc
a=fmtp:111 minptime=10;useinbandfec=1
a=rtpmap:63 red/48000/2
a=fmtp:63 111/111
a=rtpmap:9 G722/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:13 CN/8000
a=rtpmap:110 telephone-event/48000
a=rtpmap:126 telephone-event/8000
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 35 36 37 38 102 103 104 105 106 107 108 109 127 125 39 40 41 42 43 44 45 46 47 48 112 113 114 115 116 117 118 49 (181 more lines) mid=2
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:CVLQ
a=ice-pwd:P8RYVOdjcj8WIO+LyWYyQ73A
a=ice-options:trickle
a=fingerprint:sha-256 9A:53:C6:C8:35:27:B7:AD:C6:D6:F5:D9:01:37:5F:0C:2C:01:50:17:82:DE:EF:A4:F7:FC:55:24:88:CA:81:56
a=setup:actpass
a=mid:2
a=extmap:1 urn:ietf:params:rtp-hdrext:toffset
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:3 urn:3gpp:video-orientation
a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space
a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
a=recvonly
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=rtpmap:98 VP9/90000
a=rtcp-fb:98 goog-remb
a=rtcp-fb:98 transport-cc
a=rtcp-fb:98 ccm fir
a=rtcp-fb:98 nack
a=rtcp-fb:98 nack pli
a=fmtp:98 profile-id=0
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
a=rtpmap:100 VP9/90000
a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=fmtp:100 profile-id=2
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:35 VP9/90000
a=rtcp-fb:35 goog-remb
a=rtcp-fb:35 transport-cc
a=rtcp-fb:35 ccm fir
a=rtcp-fb:35 nack
a=rtcp-fb:35 nack pli
a=fmtp:35 profile-id=1
a=rtpmap:36 rtx/90000
a=fmtp:36 apt=35
a=rtpmap:37 VP9/90000
a=rtcp-fb:37 goog-remb
a=rtcp-fb:37 transport-cc
a=rtcp-fb:37 ccm fir
a=rtcp-fb:37 nack
a=rtcp-fb:37 nack pli
a=fmtp:37 profile-id=3
a=rtpmap:38 rtx/90000
a=fmtp:38 apt=37
a=rtpmap:102 H264/90000
a=rtcp-fb:102 goog-remb
a=rtcp-fb:102 transport-cc
a=rtcp-fb:102 ccm fir
a=rtcp-fb:102 nack
a=rtcp-fb:102 nack pli
a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
a=rtpmap:103 rtx/90000
a=fmtp:103 apt=102
a=rtpmap:104 H264/90000
a=rtcp-fb:104 goog-remb
a=rtcp-fb:104 transport-cc
a=rtcp-fb:104 ccm fir
a=rtcp-fb:104 nack
a=rtcp-fb:104 nack pli
a=fmtp:104 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f
a=rtpmap:105 rtx/90000
a=fmtp:105 apt=104
a=rtpmap:106 H264/90000
a=rtcp-fb:106 goog-remb
a=rtcp-fb:106 transport-cc
a=rtcp-fb:106 ccm fir
a=rtcp-fb:106 nack
a=rtcp-fb:106 nack pli
a=fmtp:106 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
a=rtpmap:107 rtx/90000
a=fmtp:107 apt=106
a=rtpmap:108 H264/90000
a=rtcp-fb:108 goog-remb
a=rtcp-fb:108 transport-cc
a=rtcp-fb:108 ccm fir
a=rtcp-fb:108 nack
a=rtcp-fb:108 nack pli
a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
a=rtpmap:109 rtx/90000
a=fmtp:109 apt=108
a=rtpmap:127 H264/90000
a=rtcp-fb:127 goog-remb
a=rtcp-fb:127 transport-cc
a=rtcp-fb:127 ccm fir
a=rtcp-fb:127 nack
a=rtcp-fb:127 nack pli
a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f
a=rtpmap:125 rtx/90000
a=fmtp:125 apt=127
a=rtpmap:39 H264/90000
a=rtcp-fb:39 goog-remb
a=rtcp-fb:39 transport-cc
a=rtcp-fb:39 ccm fir
a=rtcp-fb:39 nack
a=rtcp-fb:39 nack pli
a=fmtp:39 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=4d001f
a=rtpmap:40 rtx/90000
a=fmtp:40 apt=39
a=rtpmap:41 H264/90000
a=rtcp-fb:41 goog-remb
a=rtcp-fb:41 transport-cc
a=rtcp-fb:41 ccm fir
a=rtcp-fb:41 nack
a=rtcp-fb:41 nack pli
a=fmtp:41 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=f4001f
a=rtpmap:42 rtx/90000
a=fmtp:42 apt=41
a=rtpmap:43 H264/90000
a=rtcp-fb:43 goog-remb
a=rtcp-fb:43 transport-cc
a=rtcp-fb:43 ccm fir
a=rtcp-fb:43 nack
a=rtcp-fb:43 nack pli
a=fmtp:43 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=f4001f
a=rtpmap:44 rtx/90000
a=fmtp:44 apt=43
a=rtpmap:45 AV1/90000
a=rtcp-fb:45 goog-remb
a=rtcp-fb:45 transport-cc
a=rtcp-fb:45 ccm fir
a=rtcp-fb:45 nack
a=rtcp-fb:45 nack pli
a=rtpmap:46 rtx/90000
a=fmtp:46 apt=45
a=rtpmap:47 AV1/90000
a=rtcp-fb:47 goog-remb
a=rtcp-fb:47 transport-cc
a=rtcp-fb:47 ccm fir
a=rtcp-fb:47 nack
a=rtcp-fb:47 nack pli
a=fmtp:47 profile=1
a=rtpmap:48 rtx/90000
a=fmtp:48 apt=47
a=rtpmap:112 H264/90000
a=rtcp-fb:112 goog-remb
a=rtcp-fb:112 transport-cc
a=rtcp-fb:112 ccm fir
a=rtcp-fb:112 nack
a=rtcp-fb:112 nack pli
a=fmtp:112 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=64001f
a=rtpmap:113 rtx/90000
a=fmtp:113 apt=112
a=rtpmap:114 H264/90000
a=rtcp-fb:114 goog-remb
a=rtcp-fb:114 transport-cc
a=rtcp-fb:114 ccm fir
a=rtcp-fb:114 nack
a=rtcp-fb:114 nack pli
a=fmtp:114 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=64001f
a=rtpmap:115 rtx/90000
a=fmtp:115 apt=114
a=rtpmap:116 red/90000
a=rtpmap:117 rtx/90000
a=fmtp:117 apt=116
a=rtpmap:118 ulpfec/90000
a=rtpmap:49 flexfec-03/90000
a=rtcp-fb:49 goog-remb
a=rtcp-fb:49 transport-cc
a=fmtp:49 repair-window=10000000
m=application 9 UDP/DTLS/SCTP webrtc-datachannel (9 more lines) mid=3
c=IN IP4 0.0.0.0
a=ice-ufrag:CVLQ
a=ice-pwd:P8RYVOdjcj8WIO+LyWYyQ73A
a=ice-options:trickle
a=fingerprint:sha-256 9A:53:C6:C8:35:27:B7:AD:C6:D6:F5:D9:01:37:5F:0C:2C:01:50:17:82:DE:EF:A4:F7:FC:55:24:88:CA:81:56
a=setup:actpass
a=mid:3
a=sctp-port:5000
a=max-message-size:262144

Rewrite chat example without tokio

If we multiplex the server socket, we can avoid having to listen to multiple UDP sockets. This could probably reduce complexity in the example.

MediaData::network_time

The current value of MediaData::network_time is the receive time of the first packet that makes up the MediaData. However this is not necessarily the first packet to have been received due to reordering. It's also not clear whether this value should be when the first or last packet was received.

We should decided which it is and adjust the code.

Also, I think it would be good if MediaData contained a field to reflect the delay caused by building the sample i.e. the difference between when the first packet arrived and the last.

ICE agent in ice-lite never times out

If an ICE agent doesn't receive any UDP traffic at all. I..e. no candidate is ever nominated, the agent never times out at all.

That means we get forever hanging clients when there is no UDP connectivity.

Safari CPU and other issues

There are multiple issues running the examples in Safari.

One single video stream typically works, but not always. Firing up 3 incoming videos typically takes the entire browser down. It will start consuming 60-100% CPU, even if all windows are closed, and the process still running – force quitting will stop it.

Fuzzy Codec Matching

Firefox cannot negotiate an Opus track with str0m atm but I think it should be possible.

The reason for this is that Firefox produces the following CodecSpec

 CodecSpec {
    codec: Opus,
    clock_rate: 48000,
    channels: Some(
        2,
    ),
    format: FormatParams {
        min_p_time: None,
        use_inband_fec: Some(
            true,
        ),
        level_asymmetry_allowed: None,
        packetization_mode: None,
        profile_level_id: None,
        profile_id: None,
    },
}

That str0m tries to match exactly against

  CodecSpec {
    codec: Opus,
    clock_rate: 48000,
    channels: Some(
        2,
    ),
    format: FormatParams {
        min_p_time: Some(
            10,
        ),
        use_inband_fec: Some(
            true,
        ),
        level_asymmetry_allowed: None,
        packetization_mode: None,
        profile_level_id: None,
        profile_id: None,
    },
}
}

I think in this case str0m should accept the match despite the min_p_time value not being specified by Firefox, unsure how it should default.

There's a TODO comment about this.

Datachannel reliable mode hashmap performance

This might belong to the SCTP crate, but I will put it here for now for visibility.

When running data channels in reliable mode I saw hash map being on top in the performance.

Switching the datachannel to unreliable made it better.

image

   _ = self.rtc.direct_api().create_data_channel(ChannelConfig {
                            label: "".into(),
                            negotiated: Some(1337),
                            ordered: true,
                            reliability: Reliability::Reliable,
                            ..Default::default()
                        });

Send pacer

Given #3 implement a pacer for outgoing traffic. It needs to prioritise some types of data over other. The order is probably

  1. ICE (STUN)
  2. DTLS
  3. RTP

Re-export subcrate types

We will only publish str0m to crates.io and we want all types that appear in the public API to be re-exported. The task is to audit that all the relevant types from project subcrates are re-exported in str0m.

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.