xref: /aosp_15_r20/external/webrtc/test/pc/e2e/sdp/sdp_changer.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1 /*
2  *  Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 #include "test/pc/e2e/sdp/sdp_changer.h"
12 
13 #include <utility>
14 
15 #include "absl/memory/memory.h"
16 #include "api/jsep_session_description.h"
17 #include "api/test/pclf/media_configuration.h"
18 #include "media/base/media_constants.h"
19 #include "p2p/base/p2p_constants.h"
20 #include "pc/sdp_utils.h"
21 #include "rtc_base/strings/string_builder.h"
22 
23 namespace webrtc {
24 namespace webrtc_pc_e2e {
25 namespace {
26 
CodecRequiredParamsToString(const std::map<std::string,std::string> & codec_required_params)27 std::string CodecRequiredParamsToString(
28     const std::map<std::string, std::string>& codec_required_params) {
29   rtc::StringBuilder out;
30   for (const auto& entry : codec_required_params) {
31     out << entry.first << "=" << entry.second << ";";
32   }
33   return out.str();
34 }
35 
SupportedCodecsToString(rtc::ArrayView<const RtpCodecCapability> supported_codecs)36 std::string SupportedCodecsToString(
37     rtc::ArrayView<const RtpCodecCapability> supported_codecs) {
38   rtc::StringBuilder out;
39   for (const auto& codec : supported_codecs) {
40     out << codec.name;
41     if (!codec.parameters.empty()) {
42       out << "(";
43       for (const auto& param : codec.parameters) {
44         out << param.first << "=" << param.second << ";";
45       }
46       out << ")";
47     }
48     out << "; ";
49   }
50   return out.str();
51 }
52 
53 }  // namespace
54 
FilterVideoCodecCapabilities(rtc::ArrayView<const VideoCodecConfig> video_codecs,bool use_rtx,bool use_ulpfec,bool use_flexfec,rtc::ArrayView<const RtpCodecCapability> supported_codecs)55 std::vector<RtpCodecCapability> FilterVideoCodecCapabilities(
56     rtc::ArrayView<const VideoCodecConfig> video_codecs,
57     bool use_rtx,
58     bool use_ulpfec,
59     bool use_flexfec,
60     rtc::ArrayView<const RtpCodecCapability> supported_codecs) {
61   std::vector<RtpCodecCapability> output_codecs;
62   // Find requested codecs among supported and add them to output in the order
63   // they were requested.
64   for (auto& codec_request : video_codecs) {
65     size_t size_before = output_codecs.size();
66     for (auto& codec : supported_codecs) {
67       if (codec.name != codec_request.name) {
68         continue;
69       }
70       bool parameters_matched = true;
71       for (const auto& item : codec_request.required_params) {
72         auto it = codec.parameters.find(item.first);
73         if (it == codec.parameters.end()) {
74           parameters_matched = false;
75           break;
76         }
77         if (item.second != it->second) {
78           parameters_matched = false;
79           break;
80         }
81       }
82       if (parameters_matched) {
83         output_codecs.push_back(codec);
84       }
85     }
86     RTC_CHECK_GT(output_codecs.size(), size_before)
87         << "Codec with name=" << codec_request.name << " and params {"
88         << CodecRequiredParamsToString(codec_request.required_params)
89         << "} is unsupported for this peer connection. Supported codecs are: "
90         << SupportedCodecsToString(supported_codecs);
91   }
92 
93   // Add required FEC and RTX codecs to output.
94   for (auto& codec : supported_codecs) {
95     if (codec.name == cricket::kRtxCodecName && use_rtx) {
96       output_codecs.push_back(codec);
97     } else if (codec.name == cricket::kFlexfecCodecName && use_flexfec) {
98       output_codecs.push_back(codec);
99     } else if ((codec.name == cricket::kRedCodecName ||
100                 codec.name == cricket::kUlpfecCodecName) &&
101                use_ulpfec) {
102       // Red and ulpfec should be enabled or disabled together.
103       output_codecs.push_back(codec);
104     }
105   }
106   return output_codecs;
107 }
108 
109 // If offer has no simulcast video sections - do nothing.
110 //
111 // If offer has simulcast video sections - for each section creates
112 // SimulcastSectionInfo and put it into `context_`.
FillSimulcastContext(SessionDescriptionInterface * offer)113 void SignalingInterceptor::FillSimulcastContext(
114     SessionDescriptionInterface* offer) {
115   for (auto& content : offer->description()->contents()) {
116     cricket::MediaContentDescription* media_desc = content.media_description();
117     if (media_desc->type() != cricket::MediaType::MEDIA_TYPE_VIDEO) {
118       continue;
119     }
120     if (media_desc->HasSimulcast()) {
121       // We support only single stream simulcast sections with rids.
122       RTC_CHECK_EQ(media_desc->mutable_streams().size(), 1);
123       RTC_CHECK(media_desc->mutable_streams()[0].has_rids());
124 
125       // Create SimulcastSectionInfo for this video section.
126       SimulcastSectionInfo info(content.mid(), content.type,
127                                 media_desc->mutable_streams()[0].rids());
128 
129       // Set new rids basing on created SimulcastSectionInfo.
130       std::vector<cricket::RidDescription> rids;
131       cricket::SimulcastDescription simulcast_description;
132       for (std::string& rid : info.rids) {
133         rids.emplace_back(rid, cricket::RidDirection::kSend);
134         simulcast_description.send_layers().AddLayer(
135             cricket::SimulcastLayer(rid, false));
136       }
137       media_desc->mutable_streams()[0].set_rids(rids);
138       media_desc->set_simulcast_description(simulcast_description);
139 
140       info.simulcast_description = media_desc->simulcast_description();
141       for (const auto& extension : media_desc->rtp_header_extensions()) {
142         if (extension.uri == RtpExtension::kMidUri) {
143           info.mid_extension = extension;
144         } else if (extension.uri == RtpExtension::kRidUri) {
145           info.rid_extension = extension;
146         } else if (extension.uri == RtpExtension::kRepairedRidUri) {
147           info.rrid_extension = extension;
148         }
149       }
150       RTC_CHECK_NE(info.rid_extension.id, 0);
151       RTC_CHECK_NE(info.mid_extension.id, 0);
152       bool transport_description_found = false;
153       for (auto& transport_info : offer->description()->transport_infos()) {
154         if (transport_info.content_name == info.mid) {
155           info.transport_description = transport_info.description;
156           transport_description_found = true;
157           break;
158         }
159       }
160       RTC_CHECK(transport_description_found);
161 
162       context_.AddSimulcastInfo(info);
163     }
164   }
165 }
166 
PatchOffer(std::unique_ptr<SessionDescriptionInterface> offer,const VideoCodecConfig & first_codec)167 LocalAndRemoteSdp SignalingInterceptor::PatchOffer(
168     std::unique_ptr<SessionDescriptionInterface> offer,
169     const VideoCodecConfig& first_codec) {
170   for (auto& content : offer->description()->contents()) {
171     context_.mids_order.push_back(content.mid());
172     cricket::MediaContentDescription* media_desc = content.media_description();
173     if (media_desc->type() != cricket::MediaType::MEDIA_TYPE_VIDEO) {
174       continue;
175     }
176     if (content.media_description()->streams().empty()) {
177       // It means that this media section describes receive only media section
178       // in SDP.
179       RTC_CHECK_EQ(content.media_description()->direction(),
180                    RtpTransceiverDirection::kRecvOnly);
181       continue;
182     }
183     media_desc->set_conference_mode(params_.use_conference_mode);
184   }
185 
186   if (!params_.stream_label_to_simulcast_streams_count.empty()) {
187     // Because simulcast enabled `params_.video_codecs` has only 1 element.
188     if (first_codec.name == cricket::kVp8CodecName) {
189       return PatchVp8Offer(std::move(offer));
190     }
191 
192     if (first_codec.name == cricket::kVp9CodecName) {
193       return PatchVp9Offer(std::move(offer));
194     }
195   }
196 
197   auto offer_for_remote = CloneSessionDescription(offer.get());
198   return LocalAndRemoteSdp(std::move(offer), std::move(offer_for_remote));
199 }
200 
PatchVp8Offer(std::unique_ptr<SessionDescriptionInterface> offer)201 LocalAndRemoteSdp SignalingInterceptor::PatchVp8Offer(
202     std::unique_ptr<SessionDescriptionInterface> offer) {
203   FillSimulcastContext(offer.get());
204   if (!context_.HasSimulcast()) {
205     auto offer_for_remote = CloneSessionDescription(offer.get());
206     return LocalAndRemoteSdp(std::move(offer), std::move(offer_for_remote));
207   }
208 
209   // Clone original offer description. We mustn't access original offer after
210   // this point.
211   std::unique_ptr<cricket::SessionDescription> desc =
212       offer->description()->Clone();
213 
214   for (auto& info : context_.simulcast_infos) {
215     // For each simulcast section we have to perform:
216     //   1. Swap MID and RID header extensions
217     //   2. Remove RIDs from streams and remove SimulcastDescription
218     //   3. For each RID duplicate media section
219     cricket::ContentInfo* simulcast_content = desc->GetContentByName(info.mid);
220 
221     // Now we need to prepare common prototype for "m=video" sections, in which
222     // single simulcast section will be converted. Do it before removing content
223     // because otherwise description will be deleted.
224     std::unique_ptr<cricket::MediaContentDescription> prototype_media_desc =
225         simulcast_content->media_description()->Clone();
226 
227     // Remove simulcast video section from offer.
228     RTC_CHECK(desc->RemoveContentByName(simulcast_content->mid()));
229     // Clear `simulcast_content`, because now it is pointing to removed object.
230     simulcast_content = nullptr;
231 
232     // Swap mid and rid extensions, so remote peer will understand rid as mid.
233     // Also remove rid extension.
234     std::vector<webrtc::RtpExtension> extensions =
235         prototype_media_desc->rtp_header_extensions();
236     for (auto ext_it = extensions.begin(); ext_it != extensions.end();) {
237       if (ext_it->uri == RtpExtension::kRidUri) {
238         // We don't need rid extension for remote peer.
239         ext_it = extensions.erase(ext_it);
240         continue;
241       }
242       if (ext_it->uri == RtpExtension::kRepairedRidUri) {
243         // We don't support RTX in simulcast.
244         ext_it = extensions.erase(ext_it);
245         continue;
246       }
247       if (ext_it->uri == RtpExtension::kMidUri) {
248         ext_it->id = info.rid_extension.id;
249       }
250       ++ext_it;
251     }
252 
253     prototype_media_desc->ClearRtpHeaderExtensions();
254     prototype_media_desc->set_rtp_header_extensions(extensions);
255 
256     // We support only single stream inside video section with simulcast
257     RTC_CHECK_EQ(prototype_media_desc->mutable_streams().size(), 1);
258     // This stream must have rids.
259     RTC_CHECK(prototype_media_desc->mutable_streams()[0].has_rids());
260 
261     // Remove rids and simulcast description from media description.
262     prototype_media_desc->mutable_streams()[0].set_rids({});
263     prototype_media_desc->set_simulcast_description(
264         cricket::SimulcastDescription());
265 
266     // For each rid add separate video section.
267     for (std::string& rid : info.rids) {
268       desc->AddContent(rid, info.media_protocol_type,
269                        prototype_media_desc->Clone());
270     }
271   }
272 
273   // Now we need to add bundle line to have all media bundled together.
274   cricket::ContentGroup bundle_group(cricket::GROUP_TYPE_BUNDLE);
275   for (auto& content : desc->contents()) {
276     bundle_group.AddContentName(content.mid());
277   }
278   if (desc->HasGroup(cricket::GROUP_TYPE_BUNDLE)) {
279     desc->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
280   }
281   desc->AddGroup(bundle_group);
282 
283   // Update transport_infos to add TransportInfo for each new media section.
284   std::vector<cricket::TransportInfo> transport_infos = desc->transport_infos();
285   transport_infos.erase(std::remove_if(
286       transport_infos.begin(), transport_infos.end(),
287       [this](const cricket::TransportInfo& ti) {
288         // Remove transport infos that correspond to simulcast video sections.
289         return context_.simulcast_infos_by_mid.find(ti.content_name) !=
290                context_.simulcast_infos_by_mid.end();
291       }));
292   for (auto& info : context_.simulcast_infos) {
293     for (auto& rid : info.rids) {
294       transport_infos.emplace_back(rid, info.transport_description);
295     }
296   }
297   desc->set_transport_infos(transport_infos);
298 
299   // Create patched offer.
300   auto patched_offer =
301       std::make_unique<JsepSessionDescription>(SdpType::kOffer);
302   patched_offer->Initialize(std::move(desc), offer->session_id(),
303                             offer->session_version());
304   return LocalAndRemoteSdp(std::move(offer), std::move(patched_offer));
305 }
306 
PatchVp9Offer(std::unique_ptr<SessionDescriptionInterface> offer)307 LocalAndRemoteSdp SignalingInterceptor::PatchVp9Offer(
308     std::unique_ptr<SessionDescriptionInterface> offer) {
309   rtc::UniqueRandomIdGenerator ssrcs_generator;
310   for (auto& content : offer->description()->contents()) {
311     for (auto& stream : content.media_description()->streams()) {
312       for (auto& ssrc : stream.ssrcs) {
313         ssrcs_generator.AddKnownId(ssrc);
314       }
315     }
316   }
317 
318   for (auto& content : offer->description()->contents()) {
319     if (content.media_description()->type() !=
320         cricket::MediaType::MEDIA_TYPE_VIDEO) {
321       // We are interested in only video tracks
322       continue;
323     }
324     if (content.media_description()->direction() ==
325         RtpTransceiverDirection::kRecvOnly) {
326       // If direction is receive only, then there is no media in this track from
327       // sender side, so we needn't to do anything with this track.
328       continue;
329     }
330     RTC_CHECK_EQ(content.media_description()->streams().size(), 1);
331     cricket::StreamParams& stream =
332         content.media_description()->mutable_streams()[0];
333     RTC_CHECK_EQ(stream.stream_ids().size(), 2)
334         << "Expected 2 stream ids in video stream: 1st - sync_group, 2nd - "
335            "unique label";
336     std::string stream_label = stream.stream_ids()[1];
337 
338     auto it =
339         params_.stream_label_to_simulcast_streams_count.find(stream_label);
340     if (it == params_.stream_label_to_simulcast_streams_count.end()) {
341       continue;
342     }
343     int svc_layers_count = it->second;
344 
345     RTC_CHECK(stream.has_ssrc_groups()) << "Only SVC with RTX is supported";
346     RTC_CHECK_EQ(stream.ssrc_groups.size(), 1)
347         << "Too many ssrc groups in the track";
348     std::vector<uint32_t> primary_ssrcs;
349     stream.GetPrimarySsrcs(&primary_ssrcs);
350     RTC_CHECK(primary_ssrcs.size() == 1);
351     for (int i = 1; i < svc_layers_count; ++i) {
352       uint32_t ssrc = ssrcs_generator.GenerateId();
353       primary_ssrcs.push_back(ssrc);
354       stream.add_ssrc(ssrc);
355       stream.AddFidSsrc(ssrc, ssrcs_generator.GenerateId());
356     }
357     stream.ssrc_groups.push_back(
358         cricket::SsrcGroup(cricket::kSimSsrcGroupSemantics, primary_ssrcs));
359   }
360   auto offer_for_remote = CloneSessionDescription(offer.get());
361   return LocalAndRemoteSdp(std::move(offer), std::move(offer_for_remote));
362 }
363 
PatchAnswer(std::unique_ptr<SessionDescriptionInterface> answer,const VideoCodecConfig & first_codec)364 LocalAndRemoteSdp SignalingInterceptor::PatchAnswer(
365     std::unique_ptr<SessionDescriptionInterface> answer,
366     const VideoCodecConfig& first_codec) {
367   for (auto& content : answer->description()->contents()) {
368     cricket::MediaContentDescription* media_desc = content.media_description();
369     if (media_desc->type() != cricket::MediaType::MEDIA_TYPE_VIDEO) {
370       continue;
371     }
372     if (content.media_description()->direction() !=
373         RtpTransceiverDirection::kRecvOnly) {
374       continue;
375     }
376     media_desc->set_conference_mode(params_.use_conference_mode);
377   }
378 
379   if (!params_.stream_label_to_simulcast_streams_count.empty()) {
380     // Because simulcast enabled `params_.video_codecs` has only 1 element.
381     if (first_codec.name == cricket::kVp8CodecName) {
382       return PatchVp8Answer(std::move(answer));
383     }
384 
385     if (first_codec.name == cricket::kVp9CodecName) {
386       return PatchVp9Answer(std::move(answer));
387     }
388   }
389 
390   auto answer_for_remote = CloneSessionDescription(answer.get());
391   return LocalAndRemoteSdp(std::move(answer), std::move(answer_for_remote));
392 }
393 
PatchVp8Answer(std::unique_ptr<SessionDescriptionInterface> answer)394 LocalAndRemoteSdp SignalingInterceptor::PatchVp8Answer(
395     std::unique_ptr<SessionDescriptionInterface> answer) {
396   if (!context_.HasSimulcast()) {
397     auto answer_for_remote = CloneSessionDescription(answer.get());
398     return LocalAndRemoteSdp(std::move(answer), std::move(answer_for_remote));
399   }
400 
401   std::unique_ptr<cricket::SessionDescription> desc =
402       answer->description()->Clone();
403 
404   for (auto& info : context_.simulcast_infos) {
405     cricket::ContentInfo* simulcast_content =
406         desc->GetContentByName(info.rids[0]);
407     RTC_CHECK(simulcast_content);
408 
409     // Get media description, which will be converted to simulcast answer.
410     std::unique_ptr<cricket::MediaContentDescription> media_desc =
411         simulcast_content->media_description()->Clone();
412     // Set `simulcast_content` to nullptr, because then it will be removed, so
413     // it will point to deleted object.
414     simulcast_content = nullptr;
415 
416     // Remove separate media sections for simulcast streams.
417     for (auto& rid : info.rids) {
418       RTC_CHECK(desc->RemoveContentByName(rid));
419     }
420 
421     // Patch `media_desc` to make it simulcast answer description.
422     // Restore mid/rid rtp header extensions
423     std::vector<webrtc::RtpExtension> extensions =
424         media_desc->rtp_header_extensions();
425     // First remove existing rid/mid header extensions.
426     extensions.erase(std::remove_if(extensions.begin(), extensions.end(),
427                                     [](const webrtc::RtpExtension& e) {
428                                       return e.uri == RtpExtension::kMidUri ||
429                                              e.uri == RtpExtension::kRidUri ||
430                                              e.uri ==
431                                                  RtpExtension::kRepairedRidUri;
432                                     }));
433 
434     // Then add right ones.
435     extensions.push_back(info.mid_extension);
436     extensions.push_back(info.rid_extension);
437     // extensions.push_back(info.rrid_extension);
438     media_desc->ClearRtpHeaderExtensions();
439     media_desc->set_rtp_header_extensions(extensions);
440 
441     // Add StreamParams with rids for receive.
442     RTC_CHECK_EQ(media_desc->mutable_streams().size(), 0);
443     std::vector<cricket::RidDescription> rids;
444     for (auto& rid : info.rids) {
445       rids.emplace_back(rid, cricket::RidDirection::kReceive);
446     }
447     cricket::StreamParams stream_params;
448     stream_params.set_rids(rids);
449     media_desc->mutable_streams().push_back(stream_params);
450 
451     // Restore SimulcastDescription. It should correspond to one from offer,
452     // but it have to have receive layers instead of send. So we need to put
453     // send layers from offer to receive layers in answer.
454     cricket::SimulcastDescription simulcast_description;
455     for (const auto& layer : info.simulcast_description.send_layers()) {
456       simulcast_description.receive_layers().AddLayerWithAlternatives(layer);
457     }
458     media_desc->set_simulcast_description(simulcast_description);
459 
460     // Add simulcast media section.
461     desc->AddContent(info.mid, info.media_protocol_type, std::move(media_desc));
462   }
463 
464   desc = RestoreMediaSectionsOrder(std::move(desc));
465 
466   // Now we need to add bundle line to have all media bundled together.
467   cricket::ContentGroup bundle_group(cricket::GROUP_TYPE_BUNDLE);
468   for (auto& content : desc->contents()) {
469     bundle_group.AddContentName(content.mid());
470   }
471   if (desc->HasGroup(cricket::GROUP_TYPE_BUNDLE)) {
472     desc->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
473   }
474   desc->AddGroup(bundle_group);
475 
476   // Fix transport_infos: it have to have single info for simulcast section.
477   std::vector<cricket::TransportInfo> transport_infos = desc->transport_infos();
478   std::map<std::string, cricket::TransportDescription>
479       mid_to_transport_description;
480   for (auto info_it = transport_infos.begin();
481        info_it != transport_infos.end();) {
482     auto it = context_.simulcast_infos_by_rid.find(info_it->content_name);
483     if (it != context_.simulcast_infos_by_rid.end()) {
484       // This transport info correspond to some extra added media section.
485       mid_to_transport_description.insert(
486           {it->second->mid, info_it->description});
487       info_it = transport_infos.erase(info_it);
488     } else {
489       ++info_it;
490     }
491   }
492   for (auto& info : context_.simulcast_infos) {
493     transport_infos.emplace_back(info.mid,
494                                  mid_to_transport_description.at(info.mid));
495   }
496   desc->set_transport_infos(transport_infos);
497 
498   auto patched_answer =
499       std::make_unique<JsepSessionDescription>(SdpType::kAnswer);
500   patched_answer->Initialize(std::move(desc), answer->session_id(),
501                              answer->session_version());
502   return LocalAndRemoteSdp(std::move(answer), std::move(patched_answer));
503 }
504 
505 std::unique_ptr<cricket::SessionDescription>
RestoreMediaSectionsOrder(std::unique_ptr<cricket::SessionDescription> source)506 SignalingInterceptor::RestoreMediaSectionsOrder(
507     std::unique_ptr<cricket::SessionDescription> source) {
508   std::unique_ptr<cricket::SessionDescription> out = source->Clone();
509   for (auto& mid : context_.mids_order) {
510     RTC_CHECK(out->RemoveContentByName(mid));
511   }
512   RTC_CHECK_EQ(out->contents().size(), 0);
513   for (auto& mid : context_.mids_order) {
514     cricket::ContentInfo* content = source->GetContentByName(mid);
515     RTC_CHECK(content);
516     out->AddContent(mid, content->type, content->media_description()->Clone());
517   }
518   return out;
519 }
520 
PatchVp9Answer(std::unique_ptr<SessionDescriptionInterface> answer)521 LocalAndRemoteSdp SignalingInterceptor::PatchVp9Answer(
522     std::unique_ptr<SessionDescriptionInterface> answer) {
523   auto answer_for_remote = CloneSessionDescription(answer.get());
524   return LocalAndRemoteSdp(std::move(answer), std::move(answer_for_remote));
525 }
526 
527 std::vector<std::unique_ptr<IceCandidateInterface>>
PatchOffererIceCandidates(rtc::ArrayView<const IceCandidateInterface * const> candidates)528 SignalingInterceptor::PatchOffererIceCandidates(
529     rtc::ArrayView<const IceCandidateInterface* const> candidates) {
530   std::vector<std::unique_ptr<IceCandidateInterface>> out;
531   for (auto* candidate : candidates) {
532     auto simulcast_info_it =
533         context_.simulcast_infos_by_mid.find(candidate->sdp_mid());
534     if (simulcast_info_it != context_.simulcast_infos_by_mid.end()) {
535       // This is candidate for simulcast section, so it should be transformed
536       // into candidates for replicated sections. The sdpMLineIndex is set to
537       // -1 and ignored if the rid is present.
538       for (const std::string& rid : simulcast_info_it->second->rids) {
539         out.push_back(CreateIceCandidate(rid, -1, candidate->candidate()));
540       }
541     } else {
542       out.push_back(CreateIceCandidate(candidate->sdp_mid(),
543                                        candidate->sdp_mline_index(),
544                                        candidate->candidate()));
545     }
546   }
547   RTC_CHECK_GT(out.size(), 0);
548   return out;
549 }
550 
551 std::vector<std::unique_ptr<IceCandidateInterface>>
PatchAnswererIceCandidates(rtc::ArrayView<const IceCandidateInterface * const> candidates)552 SignalingInterceptor::PatchAnswererIceCandidates(
553     rtc::ArrayView<const IceCandidateInterface* const> candidates) {
554   std::vector<std::unique_ptr<IceCandidateInterface>> out;
555   for (auto* candidate : candidates) {
556     auto simulcast_info_it =
557         context_.simulcast_infos_by_rid.find(candidate->sdp_mid());
558     if (simulcast_info_it != context_.simulcast_infos_by_rid.end()) {
559       // This is candidate for replicated section, created from single simulcast
560       // section, so it should be transformed into candidates for simulcast
561       // section.
562       out.push_back(CreateIceCandidate(simulcast_info_it->second->mid, 0,
563                                        candidate->candidate()));
564     } else if (!context_.simulcast_infos_by_rid.empty()) {
565       // When using simulcast and bundle, put everything on the first m-line.
566       out.push_back(CreateIceCandidate("", 0, candidate->candidate()));
567     } else {
568       out.push_back(CreateIceCandidate(candidate->sdp_mid(),
569                                        candidate->sdp_mline_index(),
570                                        candidate->candidate()));
571     }
572   }
573   RTC_CHECK_GT(out.size(), 0);
574   return out;
575 }
576 
SimulcastSectionInfo(const std::string & mid,cricket::MediaProtocolType media_protocol_type,const std::vector<cricket::RidDescription> & rids_desc)577 SignalingInterceptor::SimulcastSectionInfo::SimulcastSectionInfo(
578     const std::string& mid,
579     cricket::MediaProtocolType media_protocol_type,
580     const std::vector<cricket::RidDescription>& rids_desc)
581     : mid(mid), media_protocol_type(media_protocol_type) {
582   for (auto& rid : rids_desc) {
583     rids.push_back(rid.rid);
584   }
585 }
586 
AddSimulcastInfo(const SimulcastSectionInfo & info)587 void SignalingInterceptor::SignalingContext::AddSimulcastInfo(
588     const SimulcastSectionInfo& info) {
589   simulcast_infos.push_back(info);
590   bool inserted =
591       simulcast_infos_by_mid.insert({info.mid, &simulcast_infos.back()}).second;
592   RTC_CHECK(inserted);
593   for (auto& rid : info.rids) {
594     inserted =
595         simulcast_infos_by_rid.insert({rid, &simulcast_infos.back()}).second;
596     RTC_CHECK(inserted);
597   }
598 }
599 
600 }  // namespace webrtc_pc_e2e
601 }  // namespace webrtc
602