Friday, November 4, 2016

Controlling bandwidth usage in WebRTC (and how googSuspendBelowMinBitrate works)

There are cases when we would like to limit the maximum bitrate being transmitted by WebRTC to avoid wasting resources in the user endpoints or save money reducing the bandwidth usage in our servers.   This is because the maximum bitrate by default in Chrome is around 2Mbps and for many use cases a much lower bitrate provides still pretty good quality.   BTW using a lower bitrate can also help with stability of quality in multiparty scenarios by reducing the amount of competition among different streams.

There is no simple API to configure the maximum bitrate in WebRTC (although there is one in ORTC) but there are 3 ways to do this by mangling the SDP.

1. Use the standard b=AS:BITRATE (Chrome) or b=TIAS:BITRATE (Firefox) attributes in the SDP for the audio or video channel[1]
2. Use codec specific attributes (this work at least for opus audio codec with maxaveragebitrate property) [2]
3. Use the proprietary x-max-bitrate attribute in the video channel of the SDP answer.  For example with something like this:

answer.sdp += "a=fmtp:100 x-google-max-bitrate=500\r\n";

My network sucks right now (I blame my neighbors wifis) and the bitrate is not very stable, but you can see how in average it stays under 500kbps.


There are also people interested on changing the minimum bitrate being used (the default is 30kbps in Chrome and increasing it is very dangerous unless you are in a very controlled environment). This can also be done mangling the SDP:

answer.sdp += "a=fmtp:100 x-google-min-bitrate=1000\r\n";



Another requirement is changing the initial bitrate to speed up the initialization and start with a higher bitrate than the default one (300 kbps).   This can also be done mangling the SDP:

answer.sdp += "a=fmtp:100 x-google-start-bitrate=1000\r\n";

In the graph you can see a session where the start bitrate and the minbitrate where set to 1000 kbps.



For the last three years we have seen comments in the mailing lists and issue tracker about something called "suspend below min bitrate" (3).  I was very curious about this feature as you can see in the ticket comments and finally decided to play with it and also  take a look at the code.

The feature is very simple to understand. If you enable it then WebRTC will stop sending video as soon as the bandwidth estimation goes below the minimum bitrate.  Otherwise by default WebRTC insists on sending minBitrate even if it creates congestion in the network.

To enable it you just need to pass an optional proprietary constrain while creating the PeerConnection:

var pc = new RTCPeerConnection({ iceServers: [] }, { optional: [{ "googSuspendBelowMinBitrate": true }] });
       
With that in place the video stops as soon as you don't have enough bandwidth for the video.  So if the bandwidth estimation is 60kbps and the audio needs 40kbps the video will still be sent until bandwidth estimation is >= 40kbps.

One of the tricky/interesting parts is the algorithm to allocate the bandwidth to different streams (for example for audio y video or for multiple streams in a single peerconnection or for different qualities when using simulcast).  For that there is a class BitrateAllocator (bitrate_allocator.cc) in Google's webrtc code.

def allocate_bitrates(bitrate):
   sum_max_bitrate = sum(stream.max_bitrate) for stream in streams
   sum_min_bitrate = sum(stream.min_bitrate) for stream in streams
   if bitrate > sum_max_bitrate:    
      # All streams get max_bitrate
   elif bitrate > sum(stream.min_bitrate) for stream in streams:
      # Each stream gets stream.min_bitrate + (bitrate - sum_min_bitrate) / streams.length
   else:
      foreach(stream in streams)
         if suspend_below_min_bitrate:
             stream.allocated_bitrate = min(stream.min_bitrate, bitrate)
         else:
             stream.allocated_bitrate = stream.min_bitrate
         bitrate -= stream.allocated_bitrate

One of the problems with this functionality is detecting when this feature is activated to change the UI or notify the receiver that the video has been suspended.  The only solution at this point is using getStats to monitor the encoded video bytes but some kind of callback/notification is under consideration [4]

In case you have the same question I had, the video recovers automatically when the network conditions improves again.   You can see a graph with the whole suspension + reactivation I generated by degrading artificially my network conditions to force a bandwidth estimation < 40kbps.



[1] https://tools.ietf.org/html/rfc4566#section-5.8
[2] https://tools.ietf.org/html/rfc7587#section-7
[3] https://bugs.chromium.org/p/webrtc/issues/detail?id=2436
[4] https://bugs.chromium.org/p/webrtc/issues/detail?id=5825