Skip to content

Commit b3522a6

Browse files
authored
Sample TWCC implementation (#1957)
* encoder bitrate change based on twcc * Change to 5% inc and dec * modify percentages * ema based calc * EMa fix * Nits * Readme * flip * memset remove * Readme update, move enable flags to createSampleConfiguration * Add codecov token
1 parent 8c86130 commit b3522a6

File tree

6 files changed

+232
-91
lines changed

6 files changed

+232
-91
lines changed

Diff for: .github/workflows/codecov.yml

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ jobs:
1313
runs-on: ubuntu-20.04
1414
env:
1515
AWS_KVS_LOG_LEVEL: 2
16+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
1617
permissions:
1718
id-token: write
1819
contents: read

Diff for: README.md

+45-8
Original file line numberDiff line numberDiff line change
@@ -396,21 +396,32 @@ createLwsIotCredentialProvider(
396396
freeIotCredentialProvider(&pSampleConfiguration->pCredentialProvider);
397397
```
398398
399-
## Use of TWCC
400-
In order to listen in on TWCC reports, the application must set up a callback using the `peerConnectionOnSenderBandwidthEstimation` API. In our samples, it is set up like this:
399+
## TWCC support
400+
401+
Transport Wide Congestion Control (TWCC) is a mechanism in WebRTC designed to enhance the performance and reliability of real-time communication over the internet. TWCC addresses the challenges of network congestion by providing detailed feedback on the transport of packets across the network, enabling adaptive bitrate control and optimization of media streams in real-time. This feedback mechanism is crucial for maintaining high-quality audio and video communication, as it allows senders to adjust their transmission strategies based on comprehensive information about packet losses, delays, and jitter experienced across the entire transport path.
402+
403+
The importance of TWCC in WebRTC lies in its ability to ensure efficient use of available network bandwidth while minimizing the negative impacts of network congestion. By monitoring the delivery of packets across the network, TWCC helps identify bottlenecks and adjust the media transmission rates accordingly. This dynamic approach to congestion control is essential for preventing degradation in call quality, such as pixelation, stuttering, or drops in audio and video streams, especially in environments with fluctuating network conditions.
404+
405+
To learn more about TWCC, check [TWCC spec](https://datatracker.ietf.org/doc/html/draft-holmer-rmcat-transport-wide-cc-extensions-01)
406+
407+
### Enabling TWCC support
408+
409+
TWCC is enabled by default in the SDK samples (via `pSampleConfiguration->enableTwcc`) flag. In order to disable it, set this flag to `FALSE`.
401410
402411
```c
403-
CHK_STATUS(peerConnectionOnSenderBandwidthEstimation(pSampleStreamingSession->pPeerConnection, (UINT64) pSampleStreamingSession,
404-
sampleSenderBandwidthEstimationHandler));
412+
pSampleConfiguration->enableTwcc = FALSE;
405413
```
406414

407-
Note that TWCC is disabled by default in the SDK samples. In order to enable it, set the `disableSenderSideBandwidthEstimation` flag to FALSE. For example,
408-
415+
If not using the samples directly, 2 things need to be done to set up Twcc:
416+
1. Set the `disableSenderSideBandwidthEstimation` to `FALSE`:
409417
```c
410-
RtcConfiguration configuration;
411418
configuration.kvsRtcConfiguration.disableSenderSideBandwidthEstimation = FALSE;
412419
```
413-
420+
2. Set the callback that will have the business logic to modify the bitrate based on packet loss information. The callback can be set using `peerConnectionOnSenderBandwidthEstimation()`:
421+
```c
422+
CHK_STATUS(peerConnectionOnSenderBandwidthEstimation(pSampleStreamingSession->pPeerConnection, (UINT64) pSampleStreamingSession,
423+
sampleSenderBandwidthEstimationHandler));
424+
```
414425
415426
## Use Pre-generated Certificates
416427
The certificate generating function ([createCertificateAndKey](https://awslabs.github.io/amazon-kinesis-video-streams-webrtc-sdk-c/Dtls__openssl_8c.html#a451c48525b0c0a8919a880d6834c1f7f)) in createDtlsSession() can take between 5 - 15 seconds in low performance embedded devices, it is called for every peer connection creation when KVS WebRTC receives an offer. To avoid this extra start-up latency, certificate can be pre-generated and passed in when offer comes.
@@ -510,6 +521,32 @@ To disable threadpool, run `cmake .. -DENABLE_KVS_THREADPOOL=OFF`
510521
### Thread stack sizes
511522
The default thread stack size for the KVS WebRTC SDK is 64 kb. Notable stack sizes that may need to be changed for your specific application will be the ConnectionListener Receiver thread and the media sender threads. Please modify the stack sizes for these media dependent threads to be suitable for the media your application is processing.
512523
524+
### Set up TWCC
525+
TWCC is a mechanism in WebRTC designed to enhance the performance and reliability of real-time communication over the Internet. TWCC addresses the challenges of network congestion by providing detailed feedback on the transport of packets across the network, enabling adaptive bitrate control and optimization of
526+
media streams in real-time. This feedback mechanism is crucial for maintaining high-quality audio and video communication, as it allows senders to adjust their transmission strategies based on comprehensive information about packet losses, delays, and jitter experienced across the entire transport path.
527+
The importance of TWCC in WebRTC lies in its ability to ensure efficient use of available network bandwidth while minimizing the negative impacts of network congestion. By monitoring the delivery of packets across the network, TWCC helps identify bottlenecks and adjust the media transmission rates accordingly.
528+
This dynamic approach to congestion control is essential for preventing degradation in call quality, such as pixelation, stuttering, or drops in audio and video streams, especially in environments with fluctuating network conditions. To learn more about TWCC, you can refer to the [RFC draft](https://datatracker.ietf.org/doc/html/draft-holmer-rmcat-transport-wide-cc-extensions-01)
529+
530+
In order to enable TWCC usage in the SDK, 2 things need to be set up:
531+
532+
1. Set the `disableSenderSideBandwidthEstimation` to FALSE. In our samples, the value is set using `enableTwcc` flag in `pSampleConfiguration`
533+
534+
```c
535+
pSampleConfiguration->enableTwcc = TRUE; // to enable TWCC
536+
pSampleConfiguration->enableTwcc = FALSE; // to disable TWCC
537+
configuration.kvsRtcConfiguration.disableSenderSideBandwidthEstimation = !pSampleConfiguration->enableTwcc;
538+
```
539+
540+
2. Set the callback that will have the business logic to modify the bitrate based on packet loss information. The callback can be set using `peerConnectionOnSenderBandwidthEstimation()`.
541+
542+
```c
543+
CHK_STATUS(peerConnectionOnSenderBandwidthEstimation(pSampleStreamingSession->pPeerConnection, (UINT64) pSampleStreamingSession,
544+
sampleSenderBandwidthEstimationHandler));
545+
```
546+
547+
By default, our SDK enables TWCC listener. The SDK has a sample implementation to integrate TWCC into the Gstreamer pipeline via the `sampleSenderBandwidthEstimationHandler` callback. To get more details, look for this specific callback.
548+
549+
513550
### Setting ICE related timeouts
514551
515552
There are some default timeout values set for different steps in ICE in the [KvsRtcConfiguration](https://awslabs.github.io/amazon-kinesis-video-streams-webrtc-sdk-c/structKvsRtcConfiguration.html). These are configurable in the application. While the defaults are generous, there could be applications that might need more flexibility to improve chances of connection establishment because of poor network.

Diff for: samples/Common.c

+73-23
Original file line numberDiff line numberDiff line change
@@ -414,13 +414,13 @@ STATUS initializePeerConnection(PSampleConfiguration pSampleConfiguration, PRtcP
414414
configuration.kvsRtcConfiguration.iceSetInterfaceFilterFunc = NULL;
415415

416416
// disable TWCC
417-
configuration.kvsRtcConfiguration.disableSenderSideBandwidthEstimation = TRUE;
417+
configuration.kvsRtcConfiguration.disableSenderSideBandwidthEstimation = !(pSampleConfiguration->enableTwcc);
418+
DLOGI("TWCC is : %s", configuration.kvsRtcConfiguration.disableSenderSideBandwidthEstimation ? "Disabled" : "Enabled");
418419

419420
// Set the ICE mode explicitly
420421
configuration.iceTransportPolicy = ICE_TRANSPORT_POLICY_ALL;
421422

422423
configuration.kvsRtcConfiguration.enableIceStats = pSampleConfiguration->enableIceStats;
423-
configuration.kvsRtcConfiguration.disableSenderSideBandwidthEstimation = TRUE;
424424
// Set the STUN server
425425
PCHAR pKinesisVideoStunUrlPostFix = KINESIS_VIDEO_STUN_URL_POSTFIX;
426426
// If region is in CN, add CN region uri postfix
@@ -554,10 +554,12 @@ STATUS createSampleStreamingSession(PSampleConfiguration pSampleConfiguration, P
554554

555555
ATOMIC_STORE_BOOL(&pSampleStreamingSession->terminateFlag, FALSE);
556556
ATOMIC_STORE_BOOL(&pSampleStreamingSession->candidateGatheringDone, FALSE);
557-
558557
pSampleStreamingSession->peerConnectionMetrics.peerConnectionStats.peerConnectionStartTime = GETTIME() / HUNDREDS_OF_NANOS_IN_A_MILLISECOND;
559-
// Flag to enable SDK to calculate selected ice server, local, remote and candidate pair stats.
560-
pSampleConfiguration->enableIceStats = FALSE;
558+
559+
if (pSampleConfiguration->enableTwcc) {
560+
pSampleStreamingSession->twccMetadata.updateLock = MUTEX_CREATE(TRUE);
561+
}
562+
561563
CHK_STATUS(initializePeerConnection(pSampleConfiguration, &pSampleStreamingSession->pPeerConnection));
562564
CHK_STATUS(peerConnectionOnIceCandidate(pSampleStreamingSession->pPeerConnection, (UINT64) pSampleStreamingSession, onIceCandidateHandler));
563565
CHK_STATUS(
@@ -604,8 +606,10 @@ STATUS createSampleStreamingSession(PSampleConfiguration pSampleConfiguration, P
604606
CHK_STATUS(transceiverOnBandwidthEstimation(pSampleStreamingSession->pAudioRtcRtpTransceiver, (UINT64) pSampleStreamingSession,
605607
sampleBandwidthEstimationHandler));
606608
// twcc bandwidth estimation
607-
CHK_STATUS(peerConnectionOnSenderBandwidthEstimation(pSampleStreamingSession->pPeerConnection, (UINT64) pSampleStreamingSession,
608-
sampleSenderBandwidthEstimationHandler));
609+
if (pSampleConfiguration->enableTwcc) {
610+
CHK_STATUS(peerConnectionOnSenderBandwidthEstimation(pSampleStreamingSession->pPeerConnection, (UINT64) pSampleStreamingSession,
611+
sampleSenderBandwidthEstimationHandler));
612+
}
609613
pSampleStreamingSession->startUpLatency = 0;
610614
CleanUp:
611615

@@ -656,6 +660,12 @@ STATUS freeSampleStreamingSession(PSampleStreamingSession* ppSampleStreamingSess
656660
}
657661
MUTEX_UNLOCK(pSampleConfiguration->sampleConfigurationObjLock);
658662

663+
if (pSampleConfiguration->enableTwcc) {
664+
if (IS_VALID_MUTEX_VALUE(pSampleStreamingSession->twccMetadata.updateLock)) {
665+
MUTEX_FREE(pSampleStreamingSession->twccMetadata.updateLock);
666+
}
667+
}
668+
659669
CHK_LOG_ERR(closePeerConnection(pSampleStreamingSession->pPeerConnection));
660670
CHK_LOG_ERR(freePeerConnection(&pSampleStreamingSession->pPeerConnection));
661671
SAFE_MEMFREE(pSampleStreamingSession);
@@ -706,27 +716,61 @@ VOID sampleBandwidthEstimationHandler(UINT64 customData, DOUBLE maximumBitrate)
706716
DLOGV("received bitrate suggestion: %f", maximumBitrate);
707717
}
708718

719+
// Sample callback for TWCC. Average packet is calculated with exponential moving average (EMA). If average packet lost is <= 5%,
720+
// the current bitrate is increased by 5%. If more than 5%, the current bitrate
721+
// is reduced by percent lost. Bitrate update is allowed every second and is increased/decreased upto the limits
709722
VOID sampleSenderBandwidthEstimationHandler(UINT64 customData, UINT32 txBytes, UINT32 rxBytes, UINT32 txPacketsCnt, UINT32 rxPacketsCnt,
710723
UINT64 duration)
711724
{
712-
UNUSED_PARAM(customData);
713725
UNUSED_PARAM(duration);
714-
UNUSED_PARAM(rxBytes);
715-
UNUSED_PARAM(txBytes);
726+
UINT64 videoBitrate, audioBitrate;
727+
UINT64 currentTimeMs, timeDiff;
716728
UINT32 lostPacketsCnt = txPacketsCnt - rxPacketsCnt;
717-
UINT32 percentLost = lostPacketsCnt * 100 / txPacketsCnt;
718-
UINT32 bitrate = 1024;
719-
if (percentLost < 2) {
720-
// increase encoder bitrate by 2 percent
721-
bitrate *= 1.02f;
722-
} else if (percentLost > 5) {
723-
// decrease encoder bitrate by packet loss percent
724-
bitrate *= (1.0f - percentLost / 100.0f);
725-
}
726-
// otherwise keep bitrate the same
727-
728-
DLOGV("received sender bitrate estimation: suggested bitrate %u sent: %u bytes %u packets received: %u bytes %u packets in %lu msec", bitrate,
729-
txBytes, txPacketsCnt, rxBytes, rxPacketsCnt, duration / 10000ULL);
729+
DOUBLE percentLost = (DOUBLE) ((txPacketsCnt > 0) ? (lostPacketsCnt * 100 / txPacketsCnt) : 0.0);
730+
SampleStreamingSession* pSampleStreamingSession = (SampleStreamingSession*) customData;
731+
732+
if (pSampleStreamingSession == NULL) {
733+
DLOGW("Invalid streaming session (NULL object)");
734+
return;
735+
}
736+
737+
// Calculate packet loss
738+
pSampleStreamingSession->twccMetadata.averagePacketLoss =
739+
EMA_ACCUMULATOR_GET_NEXT(pSampleStreamingSession->twccMetadata.averagePacketLoss, ((DOUBLE) percentLost));
740+
741+
currentTimeMs = GETTIME();
742+
timeDiff = currentTimeMs - pSampleStreamingSession->twccMetadata.lastAdjustmentTimeMs;
743+
if (timeDiff < TWCC_BITRATE_ADJUSTMENT_INTERVAL_MS) {
744+
// Too soon for another adjustment
745+
return;
746+
}
747+
748+
MUTEX_LOCK(pSampleStreamingSession->twccMetadata.updateLock);
749+
videoBitrate = pSampleStreamingSession->twccMetadata.currentVideoBitrate;
750+
audioBitrate = pSampleStreamingSession->twccMetadata.currentAudioBitrate;
751+
752+
if (pSampleStreamingSession->twccMetadata.averagePacketLoss <= 5) {
753+
// increase encoder bitrate by 5 percent with a cap at MAX_BITRATE
754+
videoBitrate = (UINT64) MIN(videoBitrate * 1.05, MAX_VIDEO_BITRATE_KBPS);
755+
// increase encoder bitrate by 5 percent with a cap at MAX_BITRATE
756+
audioBitrate = (UINT64) MIN(audioBitrate * 1.05, MAX_AUDIO_BITRATE_BPS);
757+
} else {
758+
// decrease encoder bitrate by average packet loss percent, with a cap at MIN_BITRATE
759+
videoBitrate = (UINT64) MAX(videoBitrate * (1.0 - pSampleStreamingSession->twccMetadata.averagePacketLoss / 100.0), MIN_VIDEO_BITRATE_KBPS);
760+
// decrease encoder bitrate by average packet loss percent, with a cap at MIN_BITRATE
761+
audioBitrate = (UINT64) MAX(audioBitrate * (1.0 - pSampleStreamingSession->twccMetadata.averagePacketLoss / 100.0), MIN_AUDIO_BITRATE_BPS);
762+
}
763+
764+
// Update the session with the new bitrate and adjustment time
765+
pSampleStreamingSession->twccMetadata.newVideoBitrate = videoBitrate;
766+
pSampleStreamingSession->twccMetadata.newAudioBitrate = audioBitrate;
767+
MUTEX_UNLOCK(pSampleStreamingSession->twccMetadata.updateLock);
768+
769+
pSampleStreamingSession->twccMetadata.lastAdjustmentTimeMs = currentTimeMs;
770+
771+
DLOGI("Adjustment made: average packet loss = %.2f%%, timediff: %llu ms", pSampleStreamingSession->twccMetadata.averagePacketLoss, timeDiff);
772+
DLOGI("Suggested video bitrate %u kbps, suggested audio bitrate: %u bps, sent: %u bytes %u packets received: %u bytes %u packets in %lu msec",
773+
videoBitrate, audioBitrate, txBytes, txPacketsCnt, rxBytes, rxPacketsCnt, duration / 10000ULL);
730774
}
731775

732776
STATUS handleRemoteCandidate(PSampleStreamingSession pSampleStreamingSession, PSignalingMessage pSignalingMessage)
@@ -915,6 +959,12 @@ STATUS createSampleConfiguration(PCHAR channelName, SIGNALING_CHANNEL_ROLE_TYPE
915959
pSampleConfiguration->pregenerateCertTimerId = MAX_UINT32;
916960
pSampleConfiguration->signalingClientMetrics.version = SIGNALING_CLIENT_METRICS_CURRENT_VERSION;
917961

962+
// Flag to enable SDK to calculate selected ice server, local, remote and candidate pair stats.
963+
pSampleConfiguration->enableIceStats = FALSE;
964+
965+
// Flag to enable/disable TWCC
966+
pSampleConfiguration->enableTwcc = TRUE;
967+
918968
ATOMIC_STORE_BOOL(&pSampleConfiguration->interrupted, FALSE);
919969
ATOMIC_STORE_BOOL(&pSampleConfiguration->mediaThreadStarted, FALSE);
920970
ATOMIC_STORE_BOOL(&pSampleConfiguration->appTerminateFlag, FALSE);

Diff for: samples/Samples.h

+18
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ extern "C" {
8383
#define MAX_SIGNALING_CLIENT_METRICS_MESSAGE_SIZE 736 // strlen(SIGNALING_CLIENT_METRICS_JSON_TEMPLATE) + 20 * 10
8484
#define MAX_ICE_AGENT_METRICS_MESSAGE_SIZE 113 // strlen(ICE_AGENT_METRICS_JSON_TEMPLATE) + 20 * 2
8585

86+
#define TWCC_BITRATE_ADJUSTMENT_INTERVAL_MS 1000 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND
87+
#define MIN_VIDEO_BITRATE_KBPS 512 // Unit kilobits/sec. Value could change based on codec.
88+
#define MAX_VIDEO_BITRATE_KBPS 2048000 // Unit kilobits/sec. Value could change based on codec.
89+
#define MIN_AUDIO_BITRATE_BPS 4000 // Unit bits/sec. Value could change based on codec.
90+
#define MAX_AUDIO_BITRATE_BPS 650000 // Unit bits/sec. Value could change based on codec.
91+
8692
typedef enum {
8793
SAMPLE_STREAMING_VIDEO_ONLY,
8894
SAMPLE_STREAMING_AUDIO_VIDEO,
@@ -160,6 +166,7 @@ typedef struct {
160166
PCHAR rtspUri;
161167
UINT32 logLevel;
162168
BOOL enableIceStats;
169+
BOOL enableTwcc;
163170
} SampleConfiguration, *PSampleConfiguration;
164171

165172
typedef struct {
@@ -179,6 +186,16 @@ typedef struct {
179186

180187
typedef VOID (*StreamSessionShutdownCallback)(UINT64, PSampleStreamingSession);
181188

189+
typedef struct {
190+
MUTEX updateLock;
191+
UINT64 lastAdjustmentTimeMs;
192+
UINT64 currentVideoBitrate;
193+
UINT64 currentAudioBitrate;
194+
UINT64 newVideoBitrate;
195+
UINT64 newAudioBitrate;
196+
DOUBLE averagePacketLoss;
197+
} TwccMetadata, *PTwccMetadata;
198+
182199
struct __SampleStreamingSession {
183200
volatile ATOMIC_BOOL terminateFlag;
184201
volatile ATOMIC_BOOL candidateGatheringDone;
@@ -198,6 +215,7 @@ struct __SampleStreamingSession {
198215
UINT64 startUpLatency;
199216
RtcMetricsHistory rtcMetricsHistory;
200217
BOOL remoteCanTrickleIce;
218+
TwccMetadata twccMetadata;
201219

202220
// this is called when the SampleStreamingSession is being freed
203221
StreamSessionShutdownCallback shutdownCallback;

0 commit comments

Comments
 (0)