LK-105 (Opus DTX) (#140)

* LK-105 (Opus DTX)

https://linear.app/livekit/issue/LK-105/allow-enabling-of-opus-dtx

Enable/Disable Opus DTX using SDP answer based on setting in
`AddTrack` request.

Testing:
--------
Chrome and Firefox work. Having audio problems with Safari
(maybe the Safari 15 issue as I am not getting media)

* Check that receiver has no tracks

* Skip non-audio transceivers

* A small clean up to not use pendin track outside lock and also append with spread

* Address comments from review by David

* Update pkg/rtc/participant.go

Co-authored-by: David Zhao <david@davidzhao.com>

* Pull in tagged version of webrtc and lk protocol

Co-authored-by: David Zhao <david@davidzhao.com>
This commit is contained in:
Raja SubramanianandDavid Zhao authored and GitHub committed 2021-10-13 11:22:33 +05:30
1 parent 84ab0f82af
commit ac4db4575f
4 files changed
+120 -9

No files matched your search

+107 -1
View File
@@ -2,6 +2,7 @@ package rtc
import (
"fmt"
"strings"
"io"
"sync"
"sync/atomic"
@@ -281,6 +282,8 @@ func (p *ParticipantImpl) HandleOffer(sdp webrtc.SessionDescription) (answer web
return
}
p.configureReceiverDTX()
answer, err = p.publisher.pc.CreateAnswer(nil)
if err != nil {
stats.PromServiceOperationCounter.WithLabelValues("answer", "error", "create").Add(1)
@@ -296,7 +299,7 @@ func (p *ParticipantImpl) HandleOffer(sdp webrtc.SessionDescription) (answer web
logger.Debugw("sending answer to client",
"participant", p.Identity(), "pID", p.ID(),
//"sdp", sdp.SDP,
//"answer sdp", answer.SDP,
)
err = p.writeMessage(&livekit.SignalResponse{
Message: &livekit.SignalResponse_Answer{
@@ -340,6 +343,7 @@ func (p *ParticipantImpl) AddTrack(req *livekit.AddTrackRequest) {
Width: req.Width,
Height: req.Height,
Muted: req.Muted,
DisableDtx: req.DisableDtx,
}
p.pendingTracks[req.Cid] = ti
@@ -1073,6 +1077,108 @@ func (p *ParticipantImpl) rtcpSendWorker() {
}
}
func (p *ParticipantImpl) configureReceiverDTX() {
//
// DTX (Discontinuous Transmission) allows audio bandwidth saving
// by not sending packets during silence periods.
//
// Publisher side DTX can enabled by included `usedtx=1` in
// the `fmtp` line corresponding to audio codec (Opus) in SDP.
// By doing this in the SDP `answer`, it can be controlled from
// server side and avoid doing it in all the client SDKs.
//
// Ideally, a publisher should be able to specify per audio
// track if DTX should be enabled. But, translating the
// DTX preference of publisher to the correct transceiver
// is non-deterministic due to the lack of a synchronizing id
// like the track id. The codec preference to set DTX needs
// to be done
// - after calling `SetRemoteDescription` which sets up
// the transceivers, but there are no tracks in the
// transceiver yet
// - before calling `CreateAnswer`
// Due to the absensce of tracks when it is required to set DTX,
// it is not possible to cross reference against a pending track
// with the same track id.
//
// Due to the restriction above and given that in practice
// most of the time there is going to be only one audio track
// that is published, do the following
// - if there is no pending audio track, no-op
// - if there are no audio transceivers without tracks, no-op
// - else, apply the DTX setting from pending audio track
// to the audio transceiver without no tracks
//
// NOTE: The above logic will fail if there is an `offer` SDP with
// multiple audio tracks. At that point, there might be a need to
// rely on something like order of tracks. TODO
//
enableDTX := false
p.lock.RLock()
var pendingTrack *livekit.TrackInfo
for _, track := range p.pendingTracks {
if track.Type == livekit.TrackType_AUDIO {
pendingTrack = track
break
}
}
if pendingTrack == nil {
p.lock.RUnlock()
return
}
enableDTX = !pendingTrack.DisableDtx
p.lock.RUnlock()
transceivers := p.publisher.pc.GetTransceivers()
for _, transceiver := range transceivers {
if transceiver.Kind() != webrtc.RTPCodecTypeAudio {
continue
}
receiver := transceiver.Receiver()
if receiver == nil || receiver.Track() != nil {
continue
}
modifiedReceiverCodecs := []webrtc.RTPCodecParameters{}
receiverCodecs := receiver.GetParameters().Codecs
for _, receiverCodec := range receiverCodecs {
if receiverCodec.MimeType == webrtc.MimeTypeOpus {
fmtpUseDTX := "usedtx=1"
// remove occurrence in the middle
sdpFmtpLine := strings.ReplaceAll(receiverCodec.SDPFmtpLine, fmtpUseDTX + ";", "")
// remove occurrence at the end
sdpFmtpLine = strings.ReplaceAll(sdpFmtpLine, fmtpUseDTX, "")
if enableDTX {
sdpFmtpLine += ";" + fmtpUseDTX
}
receiverCodec.SDPFmtpLine = sdpFmtpLine
}
modifiedReceiverCodecs = append(modifiedReceiverCodecs, receiverCodec)
}
//
// As `SetCodecPreferences` on a transceiver replaces all codecs,
// cycle through sender codecs also and add them before calling
// `SetCodecPreferences`
//
senderCodecs := []webrtc.RTPCodecParameters{}
sender := transceiver.Sender()
if sender != nil {
senderCodecs = sender.GetParameters().Codecs
}
err := transceiver.SetCodecPreferences(append(modifiedReceiverCodecs, senderCodecs...))
if err != nil {
logger.Warnw("failed to SetCodecPreferences", err)
}
}
}
func (p *ParticipantImpl) DebugInfo() map[string]interface{} {
info := map[string]interface{}{
"ID": p.id,