Populate sdp_fmtp_line and channels of RTCCodecStats

Change RtpCodecCapability::parameters and RtpCodecParameters::parameters
to map from unordered_map to get welldefined FMTP lines.

Bug: webrtc:7061
Change-Id: Ie61f76bbab915d72369e36e3f40ea11838827940
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/168190
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Karl Wiberg <kwiberg@webrtc.org>
Commit-Queue: Johannes Kron <kron@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#30512}
diff --git a/pc/rtc_stats_collector.cc b/pc/rtc_stats_collector.cc
index 116b4ba..5b34f1c 100644
--- a/pc/rtc_stats_collector.cc
+++ b/pc/rtc_stats_collector.cc
@@ -10,6 +10,7 @@
 
 #include "pc/rtc_stats_collector.h"
 
+#include <map>
 #include <memory>
 #include <string>
 #include <utility>
@@ -24,6 +25,7 @@
 #include "p2p/base/port.h"
 #include "pc/peer_connection.h"
 #include "pc/rtc_stats_traversal.h"
+#include "pc/webrtc_sdp.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/strings/string_builder.h"
 #include "rtc_base/time_utils.h"
@@ -235,6 +237,14 @@
   if (codec_params.clock_rate) {
     codec_stats->clock_rate = static_cast<uint32_t>(*codec_params.clock_rate);
   }
+  if (codec_params.num_channels) {
+    codec_stats->channels = *codec_params.num_channels;
+  }
+
+  rtc::StringBuilder fmtp;
+  if (WriteFmtpParameters(codec_params.parameters, &fmtp)) {
+    codec_stats->sdp_fmtp_line = fmtp.Release();
+  }
   return codec_stats;
 }
 
diff --git a/pc/rtc_stats_collector_unittest.cc b/pc/rtc_stats_collector_unittest.cc
index f5b3d6d..f886e14 100644
--- a/pc/rtc_stats_collector_unittest.cc
+++ b/pc/rtc_stats_collector_unittest.cc
@@ -813,6 +813,8 @@
   inbound_audio_codec.kind = cricket::MEDIA_TYPE_AUDIO;
   inbound_audio_codec.name = "opus";
   inbound_audio_codec.clock_rate = 1337;
+  inbound_audio_codec.num_channels = 1;
+  inbound_audio_codec.parameters = {{"minptime", "10"}, {"useinbandfec", "1"}};
   voice_media_info.receive_codecs.insert(
       std::make_pair(inbound_audio_codec.payload_type, inbound_audio_codec));
 
@@ -821,6 +823,7 @@
   outbound_audio_codec.kind = cricket::MEDIA_TYPE_AUDIO;
   outbound_audio_codec.name = "isac";
   outbound_audio_codec.clock_rate = 1338;
+  outbound_audio_codec.num_channels = 2;
   voice_media_info.send_codecs.insert(
       std::make_pair(outbound_audio_codec.payload_type, outbound_audio_codec));
 
@@ -835,6 +838,9 @@
   inbound_video_codec.kind = cricket::MEDIA_TYPE_VIDEO;
   inbound_video_codec.name = "H264";
   inbound_video_codec.clock_rate = 1339;
+  inbound_video_codec.parameters = {{"level-asymmetry-allowed", "1"},
+                                    {"packetization-mode", "1"},
+                                    {"profile-level-id", "42001f"}};
   video_media_info.receive_codecs.insert(
       std::make_pair(inbound_video_codec.payload_type, inbound_video_codec));
 
@@ -856,18 +862,23 @@
   expected_inbound_audio_codec.payload_type = 1;
   expected_inbound_audio_codec.mime_type = "audio/opus";
   expected_inbound_audio_codec.clock_rate = 1337;
+  expected_inbound_audio_codec.channels = 1;
+  expected_inbound_audio_codec.sdp_fmtp_line = "minptime=10;useinbandfec=1";
 
   RTCCodecStats expected_outbound_audio_codec("RTCCodec_AudioMid_Outbound_2",
                                               report->timestamp_us());
   expected_outbound_audio_codec.payload_type = 2;
   expected_outbound_audio_codec.mime_type = "audio/isac";
   expected_outbound_audio_codec.clock_rate = 1338;
+  expected_outbound_audio_codec.channels = 2;
 
   RTCCodecStats expected_inbound_video_codec("RTCCodec_VideoMid_Inbound_3",
                                              report->timestamp_us());
   expected_inbound_video_codec.payload_type = 3;
   expected_inbound_video_codec.mime_type = "video/H264";
   expected_inbound_video_codec.clock_rate = 1339;
+  expected_inbound_video_codec.sdp_fmtp_line =
+      "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f";
 
   RTCCodecStats expected_outbound_video_codec("RTCCodec_VideoMid_Outbound_4",
                                               report->timestamp_us());
diff --git a/pc/rtc_stats_integrationtest.cc b/pc/rtc_stats_integrationtest.cc
index e3f522b..93a46ef 100644
--- a/pc/rtc_stats_integrationtest.cc
+++ b/pc/rtc_stats_integrationtest.cc
@@ -445,8 +445,14 @@
     verifier.TestMemberIsDefined(codec.payload_type);
     verifier.TestMemberIsDefined(codec.mime_type);
     verifier.TestMemberIsPositive<uint32_t>(codec.clock_rate);
-    verifier.TestMemberIsUndefined(codec.channels);
-    verifier.TestMemberIsUndefined(codec.sdp_fmtp_line);
+
+    if (codec.mime_type->rfind("audio", 0) == 0)
+      verifier.TestMemberIsPositive<uint32_t>(codec.channels);
+    else
+      verifier.TestMemberIsUndefined(codec.channels);
+
+    // sdp_fmtp_line is an optional field.
+    verifier.MarkMemberTested(codec.sdp_fmtp_line, true);
     return verifier.ExpectAllMembersSuccessfullyTested();
   }
 
diff --git a/pc/rtp_parameters_conversion.cc b/pc/rtp_parameters_conversion.cc
index 93f28f1..9c7a337 100644
--- a/pc/rtp_parameters_conversion.cc
+++ b/pc/rtp_parameters_conversion.cc
@@ -164,7 +164,7 @@
     }
     cricket_codec.AddFeedbackParam(result.MoveValue());
   }
-  cricket_codec.params.insert(codec.parameters.begin(), codec.parameters.end());
+  cricket_codec.params = codec.parameters;
   return std::move(cricket_codec);
 }
 
@@ -366,8 +366,7 @@
     }
   }
   ToRtpCodecParametersTypeSpecific(cricket_codec, &codec_param);
-  codec_param.parameters.insert(cricket_codec.params.begin(),
-                                cricket_codec.params.end());
+  codec_param.parameters = cricket_codec.params;
   return codec_param;
 }
 
diff --git a/pc/webrtc_sdp.cc b/pc/webrtc_sdp.cc
index 575f339..d49684e 100644
--- a/pc/webrtc_sdp.cc
+++ b/pc/webrtc_sdp.cc
@@ -1785,24 +1785,6 @@
   *os << parameter_name << kSdpDelimiterEqual << parameter_value;
 }
 
-void WriteFmtpParameters(const cricket::CodecParameterMap& parameters,
-                         rtc::StringBuilder* os) {
-  bool first = true;
-  for (const auto& entry : parameters) {
-    const std::string& key = entry.first;
-    const std::string& value = entry.second;
-    // Parameters are a semicolon-separated list, no spaces.
-    // The list is separated from the header by a space.
-    if (first) {
-      *os << kSdpDelimiterSpace;
-      first = false;
-    } else {
-      *os << kSdpDelimiterSemicolon;
-    }
-    WriteFmtpParameter(key, value, os);
-  }
-}
-
 bool IsFmtpParam(const std::string& name) {
   // RFC 4855, section 3 specifies the mapping of media format parameters to SDP
   // parameters. Only ptime, maxptime, channels and rate are placed outside of
@@ -1811,31 +1793,35 @@
   return name != kCodecParamPTime && name != kCodecParamMaxPTime;
 }
 
-// Retreives fmtp parameters from |params|, which may contain other parameters
-// as well, and puts them in |fmtp_parameters|.
-void GetFmtpParams(const cricket::CodecParameterMap& params,
-                   cricket::CodecParameterMap* fmtp_parameters) {
-  for (const auto& entry : params) {
+bool WriteFmtpParameters(const cricket::CodecParameterMap& parameters,
+                         rtc::StringBuilder* os) {
+  bool empty = true;
+  const char* delimiter = "";  // No delimiter before first parameter.
+  for (const auto& entry : parameters) {
     const std::string& key = entry.first;
     const std::string& value = entry.second;
+
     if (IsFmtpParam(key)) {
-      (*fmtp_parameters)[key] = value;
+      *os << delimiter;
+      // A semicolon before each subsequent parameter.
+      delimiter = kSdpDelimiterSemicolon;
+      WriteFmtpParameter(key, value, os);
+      empty = false;
     }
   }
+
+  return !empty;
 }
 
 template <class T>
 void AddFmtpLine(const T& codec, std::string* message) {
-  cricket::CodecParameterMap fmtp_parameters;
-  GetFmtpParams(codec.params, &fmtp_parameters);
-  if (fmtp_parameters.empty()) {
-    // No need to add an fmtp if it will have no (optional) parameters.
-    return;
-  }
   rtc::StringBuilder os;
   WriteFmtpHeader(codec.id, &os);
-  WriteFmtpParameters(fmtp_parameters, &os);
-  AddLine(os.str(), message);
+  os << kSdpDelimiterSpace;
+  // Create FMTP line and check that it's nonempty.
+  if (WriteFmtpParameters(codec.params, &os)) {
+    AddLine(os.str(), message);
+  }
   return;
 }
 
diff --git a/pc/webrtc_sdp.h b/pc/webrtc_sdp.h
index 94008a0..588e02f 100644
--- a/pc/webrtc_sdp.h
+++ b/pc/webrtc_sdp.h
@@ -22,12 +22,17 @@
 
 #include <string>
 
+#include "media/base/codec.h"
 #include "rtc_base/system/rtc_export.h"
 
 namespace cricket {
 class Candidate;
 }  // namespace cricket
 
+namespace rtc {
+class StringBuilder;
+}  // namespace rtc
+
 namespace webrtc {
 class IceCandidateInterface;
 class JsepIceCandidate;
@@ -95,6 +100,13 @@
                                SdpParseError* error,
                                bool is_raw);
 
+// Generates an FMTP line based on |parameters|. Please note that some
+// parameters are not considered to be part of the FMTP line, see the function
+// IsFmtpParam(). Returns true if the set of FMTP parameters is nonempty, false
+// otherwise.
+bool WriteFmtpParameters(const cricket::CodecParameterMap& parameters,
+                         rtc::StringBuilder* os);
+
 }  // namespace webrtc
 
 #endif  // PC_WEBRTC_SDP_H_