Add silent frame for pcmu/a (#4258)

* Add silent frame for pcmu/a

* use red

* solve comment
This commit is contained in:
cnderrauber authored and GitHub committed 2026-01-21 16:11:08 +08:00
1 parent 0c33b8c671
commit 01bd966ff8
1 file changed
+89 -17
+89 -17
View File
@@ -139,6 +139,56 @@ var (
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}
// PCMU (G.711 µ-law) silence frame - 0xff represents zero amplitude
// 160 samples = 20ms at 8kHz sample rate
PCMUSilenceFrame = []byte{
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
}
// PCMA (G.711 A-law) silence frame - 0xd5 represents zero amplitude
// 160 samples = 20ms at 8kHz sample rate
PCMASilenceFrame = []byte{
0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5,
0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5,
0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5,
0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5,
0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5,
0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5,
0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5,
0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5,
0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5,
0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5,
0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5,
0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5,
0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5,
0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5,
0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5,
0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5,
0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5,
0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5,
0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5,
0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5,
}
dummyAbsSendTimeExt, _ = rtp.NewAbsSendTimeExtension(mono.Now()).Marshal()
dummyTransportCCExt, _ = rtp.TransportCCExtension{TransportSequence: 12345}.Marshal()
)
@@ -1697,9 +1747,13 @@ func (d *DownTrack) writeBlankFrameRTP(duration float32, generation uint32) chan
var getBlankFrame func(bool) ([]byte, error)
switch mimeType {
case mime.MimeTypeOpus:
getBlankFrame = d.getOpusBlankFrame
getBlankFrame = d.getAudioBlankFrameFunc(OpusSilenceFrame)
case mime.MimeTypeRED:
getBlankFrame = d.getOpusRedBlankFrame
case mime.MimeTypePCMU:
getBlankFrame = d.getAudioBlankFrameFunc(PCMUSilenceFrame)
case mime.MimeTypePCMA:
getBlankFrame = d.getAudioBlankFrameFunc(PCMASilenceFrame)
case mime.MimeTypeVP8:
getBlankFrame = d.getVP8BlankFrame
case mime.MimeTypeH264:
@@ -1710,7 +1764,8 @@ func (d *DownTrack) writeBlankFrameRTP(duration float32, generation uint32) chan
}
frameRate := uint32(30)
if mimeType == mime.MimeTypeOpus || mimeType == mime.MimeTypeRED {
if mimeType == mime.MimeTypeOpus || mimeType == mime.MimeTypeRED ||
mimeType == mime.MimeTypePCMU || mimeType == mime.MimeTypePCMA {
frameRate = 50
}
@@ -1801,15 +1856,17 @@ func (d *DownTrack) maybeAddTrailer(buf []byte) int {
return len(d.params.Trailer)
}
func (d *DownTrack) getOpusBlankFrame(_frameEndNeeded bool) ([]byte, error) {
func (d *DownTrack) getAudioBlankFrameFunc(silentPayload []byte) func(_frameEndNeeded bool) ([]byte, error) {
// silence frame
// Used shortly after muting to ensure residual noise does not keep
// generating noise at the decoder after the stream is stopped
// i. e. comfort noise generation actually not producing something comfortable.
payload := make([]byte, 1000)
copy(payload[0:], OpusSilenceFrame)
trailerLen := d.maybeAddTrailer(payload[len(OpusSilenceFrame):])
return payload[:len(OpusSilenceFrame)+trailerLen], nil
return func(_frameEndNeeded bool) ([]byte, error) {
payload := make([]byte, 1000)
copy(payload[0:], silentPayload)
trailerLen := d.maybeAddTrailer(payload[len(silentPayload):])
return payload[:len(silentPayload)+trailerLen], nil
}
}
func (d *DownTrack) getOpusRedBlankFrame(_frameEndNeeded bool) ([]byte, error) {
@@ -2443,8 +2500,8 @@ func (d *DownTrack) sendPaddingOnMute() {
if d.kind == webrtc.RTPCodecTypeVideo {
d.sendPaddingOnMuteForVideo()
} else if d.Mime() == mime.MimeTypeOpus {
d.sendSilentFrameOnMuteForOpus()
} else {
d.sendSilentFrameOnMuteForAudio()
}
}
@@ -2462,7 +2519,29 @@ func (d *DownTrack) sendPaddingOnMuteForVideo() {
}
}
func (d *DownTrack) sendSilentFrameOnMuteForOpus() {
func (d *DownTrack) sendSilentFrameOnMuteForAudio() {
var (
payload []byte
err error
)
switch d.Mime() {
case mime.MimeTypeOpus:
payload, err = d.getAudioBlankFrameFunc(OpusSilenceFrame)(false)
case mime.MimeTypeRED:
payload, err = d.getOpusRedBlankFrame(false)
case mime.MimeTypePCMU:
payload, err = d.getAudioBlankFrameFunc(PCMUSilenceFrame)(false)
case mime.MimeTypePCMA:
payload, err = d.getAudioBlankFrameFunc(PCMASilenceFrame)(false)
default:
d.params.Logger.Infow("unsupported mime type for silent frame on mute", "mimeType", d.Mime())
return
}
if err != nil {
d.params.Logger.Warnw("could not get blank frame", err)
return
}
frameRate := uint32(50)
frameDuration := time.Duration(1000/frameRate) * time.Millisecond
numFrames := frameRate * uint32(maxPaddingOnMuteDuration/time.Second)
@@ -2491,13 +2570,6 @@ func (d *DownTrack) sendSilentFrameOnMuteForOpus() {
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(),