refactor(pc): unify audio RED linking in payload type redesign

Refactored audio RED linking from the legacy LinkRed helper into the
unified expansion logic in MergeRedCodec. This completes the separation of the legacy and redesign paths for RED.

Bug: webrtc:360058654
Change-Id: Iaf7003b94ade7600b55eb7c027eddf6774a7f7c1
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/475461
Commit-Queue: Harald Alvestrand <hta@webrtc.org>
Reviewed-by: Tomas Gunnarsson <tommi@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#47818}
diff --git a/g3doc/todo/payload_type_redesign.md b/g3doc/todo/payload_type_redesign.md
index 0810674..ffae917 100644
--- a/g3doc/todo/payload_type_redesign.md
+++ b/g3doc/todo/payload_type_redesign.md
@@ -177,9 +177,7 @@
 3.  Links redundancy codecs to the primary PT (e.g., setting the `apt` parameter
     for RTX).
 
-**Current Status:** RTX linking is fully unified. RED linking for audio still
-partially relies on a legacy `LinkRed` helper, which will be refactored into the
-unified expansion logic in a future step.
+**Current Status:** RTX and RED linking are fully unified. RED linking for audio has been refactored into the unified expansion logic in `MergeRedCodec`.
 
 ## Testing Strategy
 
diff --git a/pc/codec_vendor.cc b/pc/codec_vendor.cc
index cbea16b..cb5d16a 100644
--- a/pc/codec_vendor.cc
+++ b/pc/codec_vendor.cc
@@ -214,6 +214,7 @@
 }
 
 RTCError MergeRedCodec(const CodecConfiguration& config,
+                       const Codec& primary_codec,
                        absl::string_view mid,
                        CodecList& offered_codecs,
                        PayloadTypeSuggester& pt_suggester,
@@ -224,16 +225,36 @@
   auto red_it = absl::c_find_if(offered_codecs, [&](const Codec& c) {
     return c.name == kRedCodecName && c.type == config.codec.type;
   });
+
+  Codec red;
+  bool newly_created = false;
   if (red_it == offered_codecs.end()) {
-    Codec red = (config.codec.type == Codec::Type::kAudio)
-                    ? CreateAudioCodec({kRedCodecName, 48000, 2})
-                    : CreateVideoCodec(kRedCodecName);
+    red = (config.codec.type == Codec::Type::kAudio)
+              ? CreateAudioCodec({kRedCodecName, 48000, 2})
+              : CreateVideoCodec(kRedCodecName);
     RTCErrorOr<PayloadType> result =
         pt_suggester.SuggestPayloadType(mid, red, pick_from_top_of_range);
     if (!result.ok()) {
       return result.MoveError();
     }
     red.id = result.value();
+    newly_created = true;
+  } else {
+    red = *red_it;
+  }
+
+  if (config.codec.type == Codec::Type::kAudio &&
+      absl::EqualsIgnoreCase(config.codec.name, kOpusCodecName)) {
+    if (red.params.empty()) {
+      StringBuilder param;
+      // Opus RED uses Opus as both the primary payload and
+      // the redundancy payload, with different timestamp offsets.
+      param << primary_codec.id.value() << "/" << primary_codec.id.value();
+      red.SetParam(kCodecParamNotInNameValueFormat, param.str());
+    }
+  }
+
+  if (newly_created) {
     offered_codecs.push_back(red);
 
     if (config.codec.type == Codec::Type::kVideo) {
@@ -254,6 +275,9 @@
         RTC_DCHECK(rtx_res.error().type() == RTCErrorType::RESOURCE_EXHAUSTED);
       }
     }
+  } else if (red_it != offered_codecs.end()) {
+    // Update the codec in the list if it was modified
+    *red_it = red;
   }
   return RTCError::OK();
 }
@@ -347,8 +371,8 @@
     }
 
     // 3. Handle RED
-    error = MergeRedCodec(config, mid, offered_codecs, pt_suggester,
-                          pick_from_top_of_range);
+    error = MergeRedCodec(config, primary_codec, mid, offered_codecs,
+                          pt_suggester, pick_from_top_of_range);
     if (!error.ok()) {
       return error;
     }
@@ -771,6 +795,8 @@
           absl::EqualsIgnoreCase(codec.name, kRedCodecName)) {
         if (codec.params.empty()) {
           StringBuilder param;
+          // Opus RED uses Opus as both the primary payload and
+          // the redundancy payload, with different timestamp offsets.
           param << first_opus_pt << "/" << first_opus_pt;
           codec.SetParam(kCodecParamNotInNameValueFormat, param.str());
         }
@@ -835,7 +861,6 @@
     }
   }
 
-  LinkRed(codecs);
   return RTCError::OK();
 }