commit 6e9b9dfa04887e6dbafa64de19b21a1200e34d21
parent 062bb857f6cfc776a62a0b664dffb7634a030fcd
Author: Dan Baker <dbaker@mozilla.com>
Date: Mon, 1 Dec 2025 16:50:07 -0700
Bug 2000941 - Vendor libwebrtc from ea1ac2490e
Upstream commit: https://webrtc.googlesource.com/src/+/ea1ac2490e8ef8dbc27cef8a2d78854f312e29f2
Fix Opus stereo decoding of mono FEC to get trivial stereo output.
When a mono Opus encoder sends FEC, and a stereo decoder is used, the decoder can produce non-trivial stereo audio. This change adds logic to ensure that if the last received packet was mono encoded, the decoded output is forced to be trivial stereo when decoding in stereo mode. A new unit test is added to cover this scenario.
Bug: webrtc:376493209
Change-Id: Ibb9790b11f528b736c7a38b74445b3cdf4cf836c
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/407803
Reviewed-by: Henrik Lundin <henrik.lundin@webrtc.org>
Commit-Queue: Markus Lindroth <lindroth@google.com>
Reviewed-by: Jakob Ivarsson‎ <jakobi@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#45569}
Diffstat:
3 files changed, 88 insertions(+), 27 deletions(-)
diff --git a/third_party/libwebrtc/README.mozilla.last-vendor b/third_party/libwebrtc/README.mozilla.last-vendor
@@ -1,4 +1,4 @@
# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
-libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2025-12-01T23:47:38.995292+00:00.
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2025-12-01T23:49:54.037970+00:00.
# base of lastest vendoring
-5c966b64e4
+ea1ac2490e
diff --git a/third_party/libwebrtc/modules/audio_coding/codecs/opus/audio_decoder_opus_unittest.cc b/third_party/libwebrtc/modules/audio_coding/codecs/opus/audio_decoder_opus_unittest.cc
@@ -366,6 +366,60 @@ TEST(AudioDecoderOpusTest, MonoEncoderStereoDecoderOutputsTrivialStereoPlc) {
EXPECT_TRUE(IsTrivialStereo(decoded_frame));
}
+TEST(AudioDecoderOpusTest, MonoEncoderStereoDecoderOutputsTrivialStereoFec) {
+ const Environment env = EnvironmentFactory().Create();
+ AudioEncoderOpusConfig encoder_config =
+ GetEncoderConfig(/*num_channels=*/1, /*dtx_enabled=*/false);
+ encoder_config.fec_enabled = true;
+ AudioEncoderOpusImpl encoder(env, encoder_config, kPayloadType);
+ // FEC will only be encoded if there is packet loss.
+ encoder.OnReceivedUplinkPacketLossFraction(0.2);
+
+ constexpr size_t kDecoderNumChannels = 2;
+ AudioDecoderOpusImpl decoder(env.field_trials(), kDecoderNumChannels,
+ kSampleRateHz);
+ std::vector<int16_t> decoded_frame(kEncoderFrameLength * kDecoderNumChannels);
+
+ PCMFile pcm_file;
+ pcm_file.Open(test::ResourcePath("near48_mono", "pcm"), kSampleRateHz, "rb");
+ pcm_file.ReadStereo(false);
+
+ AudioFrame audio_frame;
+ uint32_t rtp_timestamp = 0xFFFu;
+ uint32_t timestamp = 0;
+ // Encode and decode until FEC is found in a packet.
+ bool fec_found = false;
+ while (!fec_found && !pcm_file.EndOfFile()) {
+ pcm_file.Read10MsData(audio_frame);
+ Buffer payload;
+ encoder.Encode(rtp_timestamp++, audio_frame.data_view().data(), &payload);
+
+ // Ignore empty payloads: the encoder needs more audio to produce a packet.
+ if (payload.empty()) {
+ continue;
+ }
+
+ // Decode `payload`.
+ std::vector<ParseResult> parse_results =
+ decoder.ParsePayload(std::move(payload), timestamp++);
+ if (parse_results.size() == 1) {
+ // No FEC frame encoded. Decode to update the decoder state.
+ parse_results[0].frame->Decode(decoded_frame);
+ continue;
+ }
+ ASSERT_EQ(parse_results.size(), 2u);
+ ASSERT_EQ(parse_results[0].priority, 1); // FEC frame.
+ fec_found = true;
+
+ std::optional<DecodeResult> decode_results =
+ parse_results[0].frame->Decode(decoded_frame);
+ ASSERT_TRUE(decode_results.has_value());
+ EXPECT_EQ(decode_results->num_decoded_samples, decoded_frame.size());
+ EXPECT_TRUE(IsTrivialStereo(decoded_frame));
+ }
+ EXPECT_TRUE(fec_found);
+}
+
TEST(AudioDecoderOpusTest,
StereoEncoderStereoDecoderOutputsNonTrivialStereoComfortNoise) {
const Environment env = EnvironmentFactory().Create();
diff --git a/third_party/libwebrtc/modules/audio_coding/codecs/opus/opus_interface.cc b/third_party/libwebrtc/modules/audio_coding/codecs/opus/opus_interface.cc
@@ -505,24 +505,48 @@ static int DecodeNative(OpusDecInst* inst,
int16_t* decoded,
int16_t* audio_type,
int decode_fec) {
- int res = -1;
+ int decoded_samples_per_channel = -1;
if (inst->decoder) {
- res = opus_decode(
+ if (encoded_bytes > 0) {
+ // TODO: https://issues.webrtc.org/376493209 - When fixed, remove block
+ // below.
+ inst->last_packet_num_channels = opus_packet_get_nb_channels(encoded);
+ RTC_DCHECK(inst->last_packet_num_channels == 1 ||
+ inst->last_packet_num_channels == 2);
+ }
+ decoded_samples_per_channel = opus_decode(
inst->decoder, encoded, static_cast<opus_int32>(encoded_bytes),
reinterpret_cast<opus_int16*>(decoded), frame_size, decode_fec);
} else {
- res = opus_multistream_decode(inst->multistream_decoder, encoded,
- static_cast<opus_int32>(encoded_bytes),
- reinterpret_cast<opus_int16*>(decoded),
- frame_size, decode_fec);
+ decoded_samples_per_channel = opus_multistream_decode(
+ inst->multistream_decoder, encoded,
+ static_cast<opus_int32>(encoded_bytes),
+ reinterpret_cast<opus_int16*>(decoded), frame_size, decode_fec);
}
- if (res <= 0)
+ if (decoded_samples_per_channel <= 0)
return -1;
*audio_type = DetermineAudioType(inst, encoded_bytes);
- return res;
+ if (inst->decoder) {
+ // TODO: https://issues.webrtc.org/376493209 - When fixed, remove block
+ // below.
+ // When stereo decoding is enabled and the last observed non-empty packet
+ // encoded mono audio, the Opus decoder may generate non-trivial stereo
+ // audio. As that is undesired, in that case make sure that `decoded`
+ // contains trivial stereo audio by copying the left channel into the right
+ // one.
+ if (inst->channels == 2 && inst->last_packet_num_channels == 1) {
+ int num_channels = inst->channels;
+ for (int i = 0; i < decoded_samples_per_channel * num_channels;
+ i += num_channels) {
+ decoded[i + 1] = decoded[i];
+ }
+ }
+ }
+
+ return decoded_samples_per_channel;
}
static int DecodePlc(OpusDecInst* inst, int16_t* decoded) {
@@ -553,28 +577,11 @@ int WebRtcOpus_Decode(OpusDecInst* inst,
decoded_samples_per_channel = DecodeNative(
inst, encoded, encoded_bytes,
MaxFrameSizePerChannel(inst->sample_rate_hz), decoded, audio_type, 0);
-
- // TODO: https://issues.webrtc.org/376493209 - When fixed, remove block
- // below.
- inst->last_packet_num_channels = opus_packet_get_nb_channels(encoded);
- RTC_DCHECK(inst->last_packet_num_channels == 1 ||
- inst->last_packet_num_channels == 2);
}
if (decoded_samples_per_channel < 0) {
return -1;
}
- // TODO: https://issues.webrtc.org/376493209 - When fixed, remove block below.
- // When stereo decoding is enabled and the last observed non-empty packet
- // encoded mono audio, the Opus decoder may generate non-trivial stereo audio.
- // As that is undesired, in that case make sure that `decoded` contains
- // trivial stereo audio by copying the left channel into the right one.
- if (inst->channels == 2 && inst->last_packet_num_channels == 1) {
- for (int i = 0; i < decoded_samples_per_channel << 1; i += 2) {
- decoded[i + 1] = decoded[i];
- }
- }
-
return decoded_samples_per_channel;
}