mirror of
https://github.com/livekit/livekit.git
synced 2026-10-23 17:22:59 +00:00
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:
1 parent
84ab0f82af
commit
ac4db4575f
4 files changed
+120
-9
No files matched your search
+107
-1
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user