From f6ca82d1772dfdb9c033ede7c0c402f06be384ec Mon Sep 17 00:00:00 2001 From: Raja Subramanian Date: Tue, 14 Oct 2025 10:05:16 +0530 Subject: [PATCH] Revert to using silence packets for audio dummy start. (#3999) Effectively reverts https://github.com/livekit/livekit/pull/3984. Using padding only packets for audio dummy start introduces dependencies on other services and is not a necessary change. Would have been good to use padding only for audio also from t=0. We can re-visit this for better compatbility down the line. --- pkg/sfu/downtrack.go | 73 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/pkg/sfu/downtrack.go b/pkg/sfu/downtrack.go index dc8ea8979..d6ebd8d5b 100644 --- a/pkg/sfu/downtrack.go +++ b/pkg/sfu/downtrack.go @@ -2399,8 +2399,16 @@ func (d *DownTrack) sendPaddingOnMute() { // let uptrack have chance to send packet before we send padding time.Sleep(waitBeforeSendPaddingOnMute) + if d.kind == webrtc.RTPCodecTypeVideo { + d.sendPaddingOnMuteForVideo() + } else if d.Mime() == mime.MimeTypeOpus { + d.sendSilentFrameOnMuteForOpus() + } +} + +func (d *DownTrack) sendPaddingOnMuteForVideo() { numPackets := maxPaddingOnMuteDuration / paddingOnMuteInterval - for i := 0; i < int(numPackets); i++ { + for i := range int(numPackets) { if d.rtpStats.IsActive() || d.IsClosed() { return } @@ -2412,6 +2420,69 @@ func (d *DownTrack) sendPaddingOnMute() { } } +func (d *DownTrack) sendSilentFrameOnMuteForOpus() { + frameRate := uint32(50) + frameDuration := time.Duration(1000/frameRate) * time.Millisecond + numFrames := frameRate * uint32(maxPaddingOnMuteDuration/time.Second) + first := true + for { + if d.rtpStats.IsActive() || d.IsClosed() || numFrames <= 0 { + return + } + if first { + first = false + d.params.Logger.Debugw("sending padding on mute") + } + snts, _, err := d.forwarder.GetSnTsForBlankFrames(frameRate, 1) + if err != nil { + d.params.Logger.Warnw("could not get SN/TS for blank frame", err) + return + } + for i := range len(snts) { + hdr := &rtp.Header{ + Version: 2, + Padding: false, + Marker: true, + PayloadType: uint8(d.payloadType.Load()), + SequenceNumber: uint16(snts[i].extSequenceNumber), + Timestamp: uint32(snts[i].extTimestamp), + SSRC: d.ssrc, + } + d.addDummyExtensions(hdr) + + payload, err := d.getOpusBlankFrame(false) + if err != nil { + d.params.Logger.Warnw("could not get blank frame", err) + return + } + + headerSize := hdr.MarshalSize() + d.rtpStats.Update( + mono.UnixNano(), + snts[i].extSequenceNumber, + snts[i].extTimestamp, + hdr.Marker, + headerSize, + 0, + len(payload), // although this is using empty frames, mark as padding as these are used to trigger Pion OnTrack only + false, + ) + d.pacer.Enqueue(&pacer.Packet{ + Header: hdr, + HeaderSize: headerSize, + Payload: payload, + ProbeClusterId: ccutils.ProbeClusterId(d.probeClusterId.Load()), + AbsSendTimeExtID: uint8(d.absSendTimeExtID), + TransportWideExtID: uint8(d.transportWideExtID), + WriteStream: d.writeStream, + }) + } + + numFrames-- + time.Sleep(frameDuration) + } +} + func (d *DownTrack) HandleRTCPSenderReportData( _payloadType webrtc.PayloadType, layer int32,