Skip to content

Commit

Permalink
support setMungedSDP.
Browse files Browse the repository at this point in the history
  • Loading branch information
cloudwebrtc committed Sep 14, 2023
1 parent cecb34d commit 4815aef
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 1 deletion.
167 changes: 166 additions & 1 deletion lib/src/core/transport.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import 'dart:async';

import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
import 'package:sdp_transform/sdp_transform.dart' as sdp_transform;

import '../exceptions.dart';
import '../extensions.dart';
Expand All @@ -26,6 +27,30 @@ import '../support/platform.dart';
import '../types/other.dart';
import '../utils.dart';

const ddExtensionURI =
'https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension';

/* The svc codec (av1/vp9) would use a very low bitrate at the begining and
increase slowly by the bandwidth estimator until it reach the target bitrate. The
process commonly cost more than 10 seconds cause subscriber will get blur video at
the first few seconds. So we use a 70% of target bitrate here as the start bitrate to
eliminate this issue.
*/
const startBitrateForSVC = 0.7;

class TrackBitrateInfo {
String? cid;
rtc.RTCRtpTransceiver? transceiver;
String codec;
int maxbr;
TrackBitrateInfo({
required this.cid,
required this.transceiver,
required this.codec,
required this.maxbr,
});
}

typedef TransportOnOffer = void Function(rtc.RTCSessionDescription offer);
typedef PeerConnectionCreate = Future<rtc.RTCPeerConnection> Function(
Map<String, dynamic> configuration,
Expand All @@ -35,6 +60,7 @@ typedef PeerConnectionCreate = Future<rtc.RTCPeerConnection> Function(
class Transport extends Disposable {
final rtc.RTCPeerConnection pc;
final List<rtc.RTCIceCandidate> _pendingCandidates = [];
final List<TrackBitrateInfo> _bitrateTrackers = [];
bool restartingIce = false;
bool renegotiate = false;
TransportOnOffer? onOffer;
Expand Down Expand Up @@ -150,8 +176,70 @@ class Transport extends Disposable {
// actually negotiate
logger.fine('starting to negotiate');
final offer = await pc.createOffer(options?.toMap() ?? <String, dynamic>{});

final sdpParsed = sdp_transform.parse(offer.sdp ?? '');
sdpParsed['media']?.forEach((media) {
if (media['type'] == 'video') {
ensureVideoDDExtensionForSVC(media, media['type'], media['port'],
media['protocol'], media['payloads']);

// mung sdp for codec bitrate setting that can't apply by sendEncoding
for (var trackbr in _bitrateTrackers) {
ensureVideoDDExtensionForSVC(media, media['type'], media['port'],
media['protocol'], media['payloads']);

if (media['msid'] == null ||
trackbr.cid == null ||
!(media['msid'] as String).contains(trackbr.cid!)) {
continue;
}

var codecPayload = 0;
for (var rtp in media['rtp']) {
if (rtp['codec']?.toUpperCase() == trackbr.codec.toUpperCase()) {
codecPayload = rtp['payload'];
continue;
}
continue;
}

if (codecPayload == 0) {
continue;
}

var fmtpFound = false;
for (var fmtp in media['fmtp']) {
if (fmtp['payload'] == codecPayload) {
if (!(fmtp['config'] as String)
.contains('x-google-start-bitrate')) {
fmtp['config'] +=
';x-google-start-bitrate=${trackbr.maxbr * startBitrateForSVC}';
}
if (!(fmtp['config'] as String)
.contains('x-google-max-bitrate')) {
fmtp['config'] += ';x-google-max-bitrate=${trackbr.maxbr}';
}
fmtpFound = true;
break;
}
}

if (!fmtpFound) {
media['fmtp']?.add({
'payload': codecPayload,
'config':
'x-google-start-bitrate=${trackbr.maxbr * startBitrateForSVC};x-google-max-bitrate=${trackbr.maxbr}',
});
}

continue;
}
}
});

try {
await pc.setLocalDescription(offer);
await setMungedSDP(
sd: offer, munged: sdp_transform.write(sdpParsed, null));
} catch (e) {
throw NegotiationError(e.toString());
}
Expand Down Expand Up @@ -192,4 +280,81 @@ class Transport extends Disposable {
}
return null;
}

void setTrackBitrateInfo(TrackBitrateInfo info) {
_bitrateTrackers.add(info);
}

bool ensureVideoDDExtensionForSVC(
Map<String, dynamic> media,
String? type,
num port,
String protocol,
String? payloads,
) {
final codec = media['rtp']?[0]?['codec']?.toLowerCase();
if (!isSVCCodec(codec)) {
return false;
}

var maxID = 0;
bool ddFound = false;
List<dynamic>? ext = media['ext'];
if (ext != null) {
for (var e in ext) {
if (e['uri'] == ddExtensionURI) {
ddFound = true;
continue;
}
if (e['value'] > maxID) {
maxID = e['value'];
}
}
}

if (!ddFound) {
ext?.add({
'value': maxID + 1,
'uri': ddExtensionURI,
});
}

return ddFound;
}

Future<void> setMungedSDP(
{required rtc.RTCSessionDescription sd,
String? munged,
bool? remote}) async {
if (munged != null) {
final originalSdp = sd.sdp;
sd.sdp = munged;
try {
logger.fine(
'setting munged ${remote == true ? 'remote' : 'local'} description');
if (remote == true) {
await pc.setRemoteDescription(sd);
} else {
await pc.setLocalDescription(sd);
}
return;
} catch (e) {
logger.warning(
'not able to set ${sd.type}, falling back to unmodified sdp error: $e, sdp: $munged ');
sd.sdp = originalSdp;
}
}

try {
if (remote == true) {
await pc.setRemoteDescription(sd);
} else {
await pc.setLocalDescription(sd);
}
} catch (e) {
// this error cannot always be caught.ght
logger.warning('unable to set ${sd.type}, error: $e, sdp: ${sd.sdp}');
rethrow;
}
}
}
15 changes: 15 additions & 0 deletions lib/src/participant/local.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import 'package:flutter/foundation.dart';

import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
import 'package:livekit_client/livekit_client.dart';
import 'package:livekit_client/src/core/transport.dart';
import 'package:meta/meta.dart';

import '../core/engine.dart';
Expand Down Expand Up @@ -263,6 +265,19 @@ class LocalParticipant extends Participant<LocalTrackPublication> {
await sender.setParameters(parameters);
}

if (kIsWeb &&
lkBrowser() == BrowserType.firefox &&
track.kind == TrackType.AUDIO) {
//TOOD:
} else if (isSVCCodec(publishOptions.videoCodec) &&
encodings?.first.maxBitrate != null) {
room.engine.publisher?.setTrackBitrateInfo(TrackBitrateInfo(
cid: track.getCid(),
transceiver: track.transceiver,
codec: publishOptions.videoCodec,
maxbr: encodings![0].maxBitrate! ~/ 1000));
}

await room.engine.negotiate();

final pub = LocalTrackPublication<LocalVideoTrack>(
Expand Down

0 comments on commit 4815aef

Please sign in to comment.