Refactor MediaSession to unify audio/video codec handling

since the offer/answer rules do not depend on the media type for
the most part. Also make use of recently introduced Codec types.

BUG=webrtc:15214

Change-Id: Ieae27247a8910c3fcaa9609dca0297985907f86a
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/327740
Commit-Queue: Philipp Hancke <phancke@microsoft.com>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Reviewed-by: Florent Castelli <orphis@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#41221}
diff --git a/pc/media_session.cc b/pc/media_session.cc
index 0eb2bec..573e352 100644
--- a/pc/media_session.cc
+++ b/pc/media_session.cc
@@ -117,52 +117,38 @@
 
 namespace {
 
-bool IsRtxCodec(const Codec& codec) {
-  return absl::EqualsIgnoreCase(codec.name, kRtxCodecName);
-}
-
 bool IsRtxCodec(const webrtc::RtpCodecCapability& capability) {
   return absl::EqualsIgnoreCase(capability.name, kRtxCodecName);
 }
 
 bool ContainsRtxCodec(const std::vector<Codec>& codecs) {
-  for (const auto& codec : codecs) {
-    if (IsRtxCodec(codec)) {
-      return true;
-    }
-  }
-  return false;
-}
-
-bool IsRedCodec(const Codec& codec) {
-  return absl::EqualsIgnoreCase(codec.name, kRedCodecName);
+  return absl::c_find_if(codecs, [](const Codec& c) {
+           return c.GetResiliencyType() == Codec::ResiliencyType::kRtx;
+         }) != codecs.end();
 }
 
 bool IsRedCodec(const webrtc::RtpCodecCapability& capability) {
   return absl::EqualsIgnoreCase(capability.name, kRedCodecName);
 }
 
-bool IsFlexfecCodec(const Codec& codec) {
-  return absl::EqualsIgnoreCase(codec.name, kFlexfecCodecName);
-}
-
 bool ContainsFlexfecCodec(const std::vector<Codec>& codecs) {
-  for (const auto& codec : codecs) {
-    if (IsFlexfecCodec(codec)) {
-      return true;
-    }
-  }
-  return false;
-}
-
-bool IsUlpfecCodec(const Codec& codec) {
-  return absl::EqualsIgnoreCase(codec.name, kUlpfecCodecName);
+  return absl::c_find_if(codecs, [](const Codec& c) {
+           return c.GetResiliencyType() == Codec::ResiliencyType::kFlexfec;
+         }) != codecs.end();
 }
 
 bool IsComfortNoiseCodec(const Codec& codec) {
   return absl::EqualsIgnoreCase(codec.name, kComfortNoiseCodecName);
 }
 
+void StripCNCodecs(AudioCodecs* audio_codecs) {
+  audio_codecs->erase(std::remove_if(audio_codecs->begin(), audio_codecs->end(),
+                                     [](const AudioCodec& codec) {
+                                       return IsComfortNoiseCodec(codec);
+                                     }),
+                      audio_codecs->end());
+}
+
 RtpTransceiverDirection NegotiateRtpTransceiverDirection(
     RtpTransceiverDirection offer,
     RtpTransceiverDirection wants) {
@@ -756,7 +742,7 @@
   }));
   for (const Codec& potential_match : codecs2) {
     if (potential_match.Matches(codec_to_match)) {
-      if (IsRtxCodec(codec_to_match)) {
+      if (codec_to_match.GetResiliencyType() == Codec::ResiliencyType::kRtx) {
         int apt_value_1 = 0;
         int apt_value_2 = 0;
         if (!codec_to_match.GetParam(kCodecParamAssociatedPayloadType,
@@ -770,7 +756,8 @@
                                    apt_value_2)) {
           continue;
         }
-      } else if (IsRedCodec(codec_to_match)) {
+      } else if (codec_to_match.GetResiliencyType() ==
+                 Codec::ResiliencyType::kRed) {
         auto red_parameters_1 =
             codec_to_match.params.find(kCodecParamNotInNameValueFormat);
         auto red_parameters_2 =
@@ -838,7 +825,7 @@
       Codec negotiated = ours;
       NegotiatePacketization(ours, *theirs, &negotiated);
       negotiated.IntersectFeedbackParams(*theirs);
-      if (IsRtxCodec(negotiated)) {
+      if (negotiated.GetResiliencyType() == Codec::ResiliencyType::kRtx) {
         const auto apt_it =
             theirs->params.find(kCodecParamAssociatedPayloadType);
         // FindMatchingCodec shouldn't return something with no apt value.
@@ -850,7 +837,8 @@
         if (rtx_time_it != theirs->params.end()) {
           negotiated.SetParam(kCodecParamRtxTime, rtx_time_it->second);
         }
-      } else if (IsRedCodec(negotiated)) {
+      } else if (negotiated.GetResiliencyType() ==
+                 Codec::ResiliencyType::kRed) {
         const auto red_it =
             theirs->params.find(kCodecParamNotInNameValueFormat);
         if (red_it != theirs->params.end()) {
@@ -960,7 +948,8 @@
   // The two-pass splitting of the loops means preferring payload types
   // of actual codecs with respect to collisions.
   for (const Codec& reference_codec : reference_codecs) {
-    if (!IsRtxCodec(reference_codec) && !IsRedCodec(reference_codec) &&
+    if (reference_codec.GetResiliencyType() != Codec::ResiliencyType::kRtx &&
+        reference_codec.GetResiliencyType() != Codec::ResiliencyType::kRed &&
         !FindMatchingCodec(reference_codecs, *offered_codecs,
                            reference_codec)) {
       Codec codec = reference_codec;
@@ -971,7 +960,7 @@
 
   // Add all new RTX or RED codecs.
   for (const Codec& reference_codec : reference_codecs) {
-    if (IsRtxCodec(reference_codec) &&
+    if (reference_codec.GetResiliencyType() == Codec::ResiliencyType::kRtx &&
         !FindMatchingCodec(reference_codecs, *offered_codecs,
                            reference_codec)) {
       Codec rtx_codec = reference_codec;
@@ -994,7 +983,8 @@
           rtc::ToString(matching_codec->id);
       used_pltypes->FindAndSetIdUsed(&rtx_codec);
       offered_codecs->push_back(rtx_codec);
-    } else if (IsRedCodec(reference_codec) &&
+    } else if (reference_codec.GetResiliencyType() ==
+                   Codec::ResiliencyType::kRed &&
                !FindMatchingCodec(reference_codecs, *offered_codecs,
                                   reference_codec)) {
       Codec red_codec = reference_codec;
@@ -1058,7 +1048,8 @@
       if (found_codec_with_correct_pt) {
         // RED may already have been added if its primary codec is before RED
         // in the codec list.
-        bool is_red_codec = IsRedCodec(*found_codec_with_correct_pt);
+        bool is_red_codec = found_codec_with_correct_pt->GetResiliencyType() ==
+                            Codec::ResiliencyType::kRed;
         if (!is_red_codec || !red_was_added) {
           filtered_codecs.push_back(*found_codec_with_correct_pt);
           red_was_added = is_red_codec ? true : red_was_added;
@@ -1067,14 +1058,15 @@
         // Search for the matching rtx or red codec.
         if (want_red || want_rtx) {
           for (const auto& codec : codecs) {
-            if (IsRtxCodec(codec)) {
+            if (codec.GetResiliencyType() == Codec::ResiliencyType::kRtx) {
               const auto apt =
                   codec.params.find(cricket::kCodecParamAssociatedPayloadType);
               if (apt != codec.params.end() && apt->second == id) {
                 filtered_codecs.push_back(codec);
                 break;
               }
-            } else if (IsRedCodec(codec)) {
+            } else if (codec.GetResiliencyType() ==
+                       Codec::ResiliencyType::kRed) {
               // For RED, do not insert the codec again if it was already
               // inserted. audio/red for opus gets enabled by having RED before
               // the primary codec.
@@ -1324,14 +1316,6 @@
   }
 }
 
-void StripCNCodecs(AudioCodecs* audio_codecs) {
-  audio_codecs->erase(std::remove_if(audio_codecs->begin(), audio_codecs->end(),
-                                     [](const AudioCodec& codec) {
-                                       return IsComfortNoiseCodec(codec);
-                                     }),
-                      audio_codecs->end());
-}
-
 bool SetCodecsInAnswer(const MediaContentDescription* offer,
                        const std::vector<Codec>& local_codecs,
                        const MediaDescriptionOptions& media_description_options,
@@ -1502,25 +1486,25 @@
       .description.secure();
 }
 
-webrtc::RTCErrorOr<AudioCodecs> GetNegotiatedAudioCodecsForOffer(
+webrtc::RTCErrorOr<AudioCodecs> GetNegotiatedCodecsForOffer(
     const MediaDescriptionOptions& media_description_options,
     const MediaSessionOptions& session_options,
     const ContentInfo* current_content,
-    const AudioCodecs& audio_codecs,
-    const AudioCodecs& supported_audio_codecs) {
-  AudioCodecs filtered_codecs;
+    const std::vector<Codec>& codecs,
+    const std::vector<Codec>& supported_codecs) {
+  std::vector<Codec> filtered_codecs;
   if (!media_description_options.codec_preferences.empty()) {
     // Add the codecs from the current transceiver's codec preferences.
     // They override any existing codecs from previous negotiations.
-    filtered_codecs =
-        MatchCodecPreference(media_description_options.codec_preferences,
-                             audio_codecs, supported_audio_codecs);
+    filtered_codecs = MatchCodecPreference(
+        media_description_options.codec_preferences, codecs, supported_codecs);
   } else {
     // Add the codecs from current content if it exists and is not rejected nor
     // recycled.
     if (current_content && !current_content->rejected &&
         current_content->name == media_description_options.mid) {
-      if (!IsMediaContentOfType(current_content, MEDIA_TYPE_AUDIO)) {
+      if (!IsMediaContentOfType(current_content,
+                                media_description_options.type)) {
         // Can happen if the remote side re-uses a MID while recycling.
         LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR,
                              "Media type for content with mid='" +
@@ -1529,81 +1513,31 @@
       }
       const MediaContentDescription* mcd = current_content->media_description();
       for (const Codec& codec : mcd->codecs()) {
-        if (FindMatchingCodec(mcd->codecs(), audio_codecs, codec)) {
+        if (FindMatchingCodec(mcd->codecs(), codecs, codec)) {
           filtered_codecs.push_back(codec);
         }
       }
     }
-    // Add other supported audio codecs.
-    for (const Codec& codec : supported_audio_codecs) {
+    // Add other supported codecs.
+    for (const Codec& codec : supported_codecs) {
       absl::optional<Codec> found_codec =
-          FindMatchingCodec(supported_audio_codecs, audio_codecs, codec);
+          FindMatchingCodec(supported_codecs, codecs, codec);
       if (found_codec &&
-          !FindMatchingCodec(supported_audio_codecs, filtered_codecs, codec)) {
-        // Use the `found_codec` from `audio_codecs` because it has the
+          !FindMatchingCodec(supported_codecs, filtered_codecs, codec)) {
+        // Use the `found_codec` from `codecs` because it has the
         // correctly mapped payload type.
-        filtered_codecs.push_back(*found_codec);
-      }
-    }
-  }
-  if (!session_options.vad_enabled) {
-    // If application doesn't want CN codecs in offer.
-    StripCNCodecs(&filtered_codecs);
-  }
-  return filtered_codecs;
-}
-
-webrtc::RTCErrorOr<VideoCodecs> GetNegotiatedVideoCodecsForOffer(
-    const MediaDescriptionOptions& media_description_options,
-    const MediaSessionOptions& session_options,
-    const ContentInfo* current_content,
-    const VideoCodecs& video_codecs,
-    const VideoCodecs& supported_video_codecs) {
-  VideoCodecs filtered_codecs;
-
-  if (!media_description_options.codec_preferences.empty()) {
-    // Add the codecs from the current transceiver's codec preferences.
-    // They override any existing codecs from previous negotiations.
-    filtered_codecs =
-        MatchCodecPreference(media_description_options.codec_preferences,
-                             video_codecs, supported_video_codecs);
-  } else {
-    // Add the codecs from current content if it exists and is not rejected nor
-    // recycled.
-    if (current_content && !current_content->rejected &&
-        current_content->name == media_description_options.mid) {
-      if (!IsMediaContentOfType(current_content, MEDIA_TYPE_VIDEO)) {
-        // Can happen if the remote side re-uses a MID while recycling.
-        LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR,
-                             "Media type for content with mid='" +
-                                 current_content->name +
-                                 "' does not match previous type.");
-      }
-      const MediaContentDescription* mcd = current_content->media_description();
-      for (const Codec& codec : mcd->codecs()) {
-        if (FindMatchingCodec(mcd->codecs(), video_codecs, codec)) {
-          filtered_codecs.push_back(codec);
-        }
-      }
-    }
-    // Add other supported video codecs.
-    for (const Codec& codec : supported_video_codecs) {
-      absl::optional<Codec> found_codec =
-          FindMatchingCodec(supported_video_codecs, video_codecs, codec);
-      if (found_codec &&
-          !FindMatchingCodec(supported_video_codecs, filtered_codecs, codec)) {
-        // Use the `found_codec` from `video_codecs` because it has the
-        // correctly mapped payload type.
-        if (IsRtxCodec(codec)) {
+        // This is only done for video since we do not yet have rtx for audio.
+        if (media_description_options.type == MEDIA_TYPE_VIDEO &&
+            found_codec->GetResiliencyType() == Codec::ResiliencyType::kRtx) {
           // For RTX we might need to adjust the apt parameter if we got a
           // remote offer without RTX for a codec for which we support RTX.
           auto referenced_codec =
-              GetAssociatedCodecForRtx(supported_video_codecs, codec);
+              GetAssociatedCodecForRtx(supported_codecs, codec);
           RTC_DCHECK(referenced_codec);
 
           // Find the codec we should be referencing and point to it.
           absl::optional<Codec> changed_referenced_codec = FindMatchingCodec(
-              supported_video_codecs, filtered_codecs, *referenced_codec);
+              supported_codecs, filtered_codecs, *referenced_codec);
           if (changed_referenced_codec) {
             found_codec->SetParam(kCodecParamAssociatedPayloadType,
                                   changed_referenced_codec->id);
@@ -1614,35 +1548,39 @@
     }
   }
 
-  if (session_options.raw_packetization_for_video) {
+  if (media_description_options.type == MEDIA_TYPE_AUDIO &&
+      !session_options.vad_enabled) {
+    // If application doesn't want CN codecs in offer.
+    StripCNCodecs(&filtered_codecs);
+  } else if (media_description_options.type == MEDIA_TYPE_VIDEO &&
+             session_options.raw_packetization_for_video) {
     for (Codec& codec : filtered_codecs) {
       if (codec.IsMediaCodec()) {
         codec.packetization = kPacketizationParamRaw;
       }
     }
   }
-
   return filtered_codecs;
 }
 
-webrtc::RTCErrorOr<AudioCodecs> GetNegotiatedAudioCodecsForAnswer(
+webrtc::RTCErrorOr<AudioCodecs> GetNegotiatedCodecsForAnswer(
     const MediaDescriptionOptions& media_description_options,
     const MediaSessionOptions& session_options,
     const ContentInfo* current_content,
-    const AudioCodecs& audio_codecs,
-    const AudioCodecs& supported_audio_codecs) {
-  AudioCodecs filtered_codecs;
+    const std::vector<Codec>& codecs,
+    const std::vector<Codec>& supported_codecs) {
+  std::vector<Codec> filtered_codecs;
 
   if (!media_description_options.codec_preferences.empty()) {
-    filtered_codecs =
-        MatchCodecPreference(media_description_options.codec_preferences,
-                             audio_codecs, supported_audio_codecs);
+    filtered_codecs = MatchCodecPreference(
+        media_description_options.codec_preferences, codecs, supported_codecs);
   } else {
     // Add the codecs from current content if it exists and is not rejected nor
     // recycled.
     if (current_content && !current_content->rejected &&
         current_content->name == media_description_options.mid) {
-      if (!IsMediaContentOfType(current_content, MEDIA_TYPE_AUDIO)) {
+      if (!IsMediaContentOfType(current_content,
+                                media_description_options.type)) {
         // Can happen if the remote side re-uses a MID while recycling.
         LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR,
                              "Media type for content with mid='" +
@@ -1651,75 +1589,33 @@
       }
       const MediaContentDescription* mcd = current_content->media_description();
       for (const Codec& codec : mcd->codecs()) {
-        if (FindMatchingCodec(mcd->codecs(), audio_codecs, codec)) {
+        if (FindMatchingCodec(mcd->codecs(), codecs, codec)) {
           filtered_codecs.push_back(codec);
         }
       }
     }
-    // Add other supported audio codecs.
-    for (const Codec& codec : supported_audio_codecs) {
-      if (FindMatchingCodec(supported_audio_codecs, audio_codecs, codec) &&
-          !FindMatchingCodec(supported_audio_codecs, filtered_codecs, codec)) {
-        // We should use the local codec with local parameters and the codec id
-        // would be correctly mapped in `NegotiateCodecs`.
-        filtered_codecs.push_back(codec);
-      }
-    }
-  }
-  if (!session_options.vad_enabled) {
-    // If application doesn't want CN codecs in answer.
-    StripCNCodecs(&filtered_codecs);
-  }
-  return filtered_codecs;
-}
-
-webrtc::RTCErrorOr<VideoCodecs> GetNegotiatedVideoCodecsForAnswer(
-    const MediaDescriptionOptions& media_description_options,
-    const MediaSessionOptions& session_options,
-    const ContentInfo* current_content,
-    const VideoCodecs& video_codecs,
-    const VideoCodecs& supported_video_codecs) {
-  VideoCodecs filtered_codecs;
-
-  if (!media_description_options.codec_preferences.empty()) {
-    filtered_codecs =
-        MatchCodecPreference(media_description_options.codec_preferences,
-                             video_codecs, supported_video_codecs);
-  } else {
-    // Add the codecs from current content if it exists and is not rejected nor
-    // recycled.
-    if (current_content && !current_content->rejected &&
-        current_content->name == media_description_options.mid) {
-      if (!IsMediaContentOfType(current_content, MEDIA_TYPE_VIDEO)) {
-        // Can happen if the remote side re-uses a MID while recycling.
-        LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR,
-                             "Media type for content with mid='" +
-                                 current_content->name +
-                                 "' does not match previous type.");
-      }
-      const MediaContentDescription* mcd = current_content->media_description();
-      for (const Codec& codec : mcd->codecs()) {
-        if (FindMatchingCodec(mcd->codecs(), video_codecs, codec)) {
-          filtered_codecs.push_back(codec);
-        }
-      }
-    }
-
     // Add other supported video codecs.
-    VideoCodecs other_video_codecs;
-    for (const Codec& codec : supported_video_codecs) {
-      if (FindMatchingCodec(supported_video_codecs, video_codecs, codec) &&
-          !FindMatchingCodec(supported_video_codecs, filtered_codecs, codec)) {
+    std::vector<Codec> other_codecs;
+    for (const Codec& codec : supported_codecs) {
+      if (FindMatchingCodec(supported_codecs, codecs, codec) &&
+          !FindMatchingCodec(supported_codecs, filtered_codecs, codec)) {
         // We should use the local codec with local parameters and the codec id
         // would be correctly mapped in `NegotiateCodecs`.
-        other_video_codecs.push_back(codec);
+        other_codecs.push_back(codec);
       }
     }
 
-    // Use ComputeCodecsUnion to avoid having duplicate payload IDs
-    filtered_codecs = ComputeCodecsUnion(filtered_codecs, other_video_codecs);
+    // Use ComputeCodecsUnion to avoid having duplicate payload IDs.
+    // This is a no-op for audio until RTX is added.
+    filtered_codecs = ComputeCodecsUnion(filtered_codecs, other_codecs);
   }
-  if (session_options.raw_packetization_for_video) {
+
+  if (media_description_options.type == MEDIA_TYPE_AUDIO &&
+      !session_options.vad_enabled) {
+    // If application doesn't want CN codecs in offer.
+    StripCNCodecs(&filtered_codecs);
+  } else if (media_description_options.type == MEDIA_TYPE_VIDEO &&
+             session_options.raw_packetization_for_video) {
     for (Codec& codec : filtered_codecs) {
       if (codec.IsMediaCodec()) {
         codec.packetization = kPacketizationParamRaw;
@@ -2470,20 +2366,14 @@
     IceCredentialsIterator* ice_credentials) const {
   RTC_DCHECK(media_description_options.type == MEDIA_TYPE_AUDIO ||
              media_description_options.type == MEDIA_TYPE_VIDEO);
-  webrtc::RTCErrorOr<std::vector<Codec>> error_or_filtered_codecs;
-  if (media_description_options.type == MEDIA_TYPE_AUDIO) {
-    const AudioCodecs& supported_codecs =
-        GetAudioCodecsForOffer(media_description_options.direction);
-    error_or_filtered_codecs = GetNegotiatedAudioCodecsForOffer(
-        media_description_options, session_options, current_content, codecs,
-        supported_codecs);
-  } else {
-    const VideoCodecs& supported_codecs =
-        GetVideoCodecsForOffer(media_description_options.direction);
-    error_or_filtered_codecs = GetNegotiatedVideoCodecsForOffer(
-        media_description_options, session_options, current_content, codecs,
-        supported_codecs);
-  }
+
+  const std::vector<Codec>& supported_codecs =
+      media_description_options.type == MEDIA_TYPE_AUDIO
+          ? GetAudioCodecsForOffer(media_description_options.direction)
+          : GetVideoCodecsForOffer(media_description_options.direction);
+  webrtc::RTCErrorOr<std::vector<Codec>> error_or_filtered_codecs =
+      GetNegotiatedCodecsForOffer(media_description_options, session_options,
+                                  current_content, codecs, supported_codecs);
   if (!error_or_filtered_codecs.ok()) {
     return error_or_filtered_codecs.MoveError();
   }
@@ -2649,19 +2539,13 @@
   auto offer_rtd = offer_content_description->direction();
   auto answer_rtd = NegotiateRtpTransceiverDirection(offer_rtd, wants_rtd);
 
-  std::vector<Codec> supported_codecs;
-  webrtc::RTCErrorOr<std::vector<Codec>> error_or_filtered_codecs;
-  if (media_description_options.type == MEDIA_TYPE_AUDIO) {
-    supported_codecs = GetAudioCodecsForAnswer(offer_rtd, answer_rtd);
-    error_or_filtered_codecs = GetNegotiatedAudioCodecsForAnswer(
-        media_description_options, session_options, current_content, codecs,
-        supported_codecs);
-  } else {
-    supported_codecs = GetVideoCodecsForAnswer(offer_rtd, answer_rtd);
-    error_or_filtered_codecs = GetNegotiatedVideoCodecsForAnswer(
-        media_description_options, session_options, current_content, codecs,
-        supported_codecs);
-  }
+  const std::vector<Codec>& supported_codecs =
+      media_description_options.type == MEDIA_TYPE_AUDIO
+          ? GetAudioCodecsForAnswer(offer_rtd, answer_rtd)
+          : GetVideoCodecsForAnswer(offer_rtd, answer_rtd);
+  webrtc::RTCErrorOr<std::vector<Codec>> error_or_filtered_codecs =
+      GetNegotiatedCodecsForAnswer(media_description_options, session_options,
+                                   current_content, codecs, supported_codecs);
   if (!error_or_filtered_codecs.ok()) {
     return error_or_filtered_codecs.MoveError();
   }
@@ -2671,8 +2555,7 @@
   bool has_common_media_codecs =
       std::find_if(filtered_codecs.begin(), filtered_codecs.end(),
                    [](const Codec& c) {
-                     return !(IsRedCodec(c) || IsComfortNoiseCodec(c) ||
-                              IsUlpfecCodec(c) || IsFlexfecCodec(c));
+                     return c.IsMediaCodec() && !IsComfortNoiseCodec(c);
                    }) != filtered_codecs.end();
 
   bool bundle_enabled = offer_description->HasGroup(GROUP_TYPE_BUNDLE) &&
@@ -2852,7 +2735,7 @@
     if (!FindMatchingCodec(audio_send_codecs_, audio_recv_codecs_, send)) {
       // It doesn't make sense to have an RTX codec we support sending but not
       // receiving.
-      RTC_DCHECK(!IsRtxCodec(send));
+      RTC_DCHECK(send.GetResiliencyType() != Codec::ResiliencyType::kRtx);
     }
   }
   for (const Codec& recv : audio_recv_codecs_) {