|  | /* | 
|  | *  Copyright 2017 The WebRTC project authors. All Rights Reserved. | 
|  | * | 
|  | *  Use of this source code is governed by a BSD-style license | 
|  | *  that can be found in the LICENSE file in the root of the source | 
|  | *  tree. An additional intellectual property rights grant can be found | 
|  | *  in the file PATENTS.  All contributing project authors may | 
|  | *  be found in the AUTHORS file in the root of the source tree. | 
|  | */ | 
|  |  | 
|  | #include "ortc/rtptransportcontrolleradapter.h" | 
|  |  | 
|  | #include <algorithm>  // For "remove", "find". | 
|  | #include <set> | 
|  | #include <sstream> | 
|  | #include <unordered_map> | 
|  | #include <utility>  // For std::move. | 
|  |  | 
|  | #include "api/proxy.h" | 
|  | #include "media/base/mediaconstants.h" | 
|  | #include "ortc/ortcrtpreceiveradapter.h" | 
|  | #include "ortc/ortcrtpsenderadapter.h" | 
|  | #include "ortc/rtpparametersconversion.h" | 
|  | #include "ortc/rtptransportadapter.h" | 
|  | #include "pc/rtpmediautils.h" | 
|  | #include "rtc_base/checks.h" | 
|  | #include "rtc_base/ptr_util.h" | 
|  |  | 
|  | namespace webrtc { | 
|  |  | 
|  | // Note: It's assumed that each individual list doesn't have conflicts, since | 
|  | // they should have been detected already by rtpparametersconversion.cc. This | 
|  | // only needs to detect conflicts *between* A and B. | 
|  | template <typename C1, typename C2> | 
|  | static RTCError CheckForIdConflicts( | 
|  | const std::vector<C1>& codecs_a, | 
|  | const cricket::RtpHeaderExtensions& extensions_a, | 
|  | const cricket::StreamParamsVec& streams_a, | 
|  | const std::vector<C2>& codecs_b, | 
|  | const cricket::RtpHeaderExtensions& extensions_b, | 
|  | const cricket::StreamParamsVec& streams_b) { | 
|  | std::ostringstream oss; | 
|  | // Since it's assumed that C1 and C2 are different types, codecs_a and | 
|  | // codecs_b should never contain the same payload type, and thus we can just | 
|  | // use a set. | 
|  | std::set<int> seen_payload_types; | 
|  | for (const C1& codec : codecs_a) { | 
|  | seen_payload_types.insert(codec.id); | 
|  | } | 
|  | for (const C2& codec : codecs_b) { | 
|  | if (!seen_payload_types.insert(codec.id).second) { | 
|  | oss << "Same payload type used for audio and video codecs: " << codec.id; | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, oss.str()); | 
|  | } | 
|  | } | 
|  | // Audio and video *may* use the same header extensions, so use a map. | 
|  | std::unordered_map<int, std::string> seen_extensions; | 
|  | for (const webrtc::RtpExtension& extension : extensions_a) { | 
|  | seen_extensions[extension.id] = extension.uri; | 
|  | } | 
|  | for (const webrtc::RtpExtension& extension : extensions_b) { | 
|  | if (seen_extensions.find(extension.id) != seen_extensions.end() && | 
|  | seen_extensions.at(extension.id) != extension.uri) { | 
|  | oss << "Same ID used for different RTP header extensions: " | 
|  | << extension.id; | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, oss.str()); | 
|  | } | 
|  | } | 
|  | std::set<uint32_t> seen_ssrcs; | 
|  | for (const cricket::StreamParams& stream : streams_a) { | 
|  | seen_ssrcs.insert(stream.ssrcs.begin(), stream.ssrcs.end()); | 
|  | } | 
|  | for (const cricket::StreamParams& stream : streams_b) { | 
|  | for (uint32_t ssrc : stream.ssrcs) { | 
|  | if (!seen_ssrcs.insert(ssrc).second) { | 
|  | oss << "Same SSRC used for audio and video senders: " << ssrc; | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, oss.str()); | 
|  | } | 
|  | } | 
|  | } | 
|  | return RTCError::OK(); | 
|  | } | 
|  |  | 
|  | BEGIN_OWNED_PROXY_MAP(RtpTransportController) | 
|  | PROXY_SIGNALING_THREAD_DESTRUCTOR() | 
|  | PROXY_CONSTMETHOD0(std::vector<RtpTransportInterface*>, GetTransports) | 
|  | protected: | 
|  | RtpTransportControllerAdapter* GetInternal() override { | 
|  | return internal(); | 
|  | } | 
|  | END_PROXY_MAP() | 
|  |  | 
|  | // static | 
|  | std::unique_ptr<RtpTransportControllerInterface> | 
|  | RtpTransportControllerAdapter::CreateProxied( | 
|  | const cricket::MediaConfig& config, | 
|  | cricket::ChannelManager* channel_manager, | 
|  | webrtc::RtcEventLog* event_log, | 
|  | rtc::Thread* signaling_thread, | 
|  | rtc::Thread* worker_thread, | 
|  | rtc::Thread* network_thread) { | 
|  | std::unique_ptr<RtpTransportControllerAdapter> wrapped( | 
|  | new RtpTransportControllerAdapter(config, channel_manager, event_log, | 
|  | signaling_thread, worker_thread, | 
|  | network_thread)); | 
|  | return RtpTransportControllerProxyWithInternal< | 
|  | RtpTransportControllerAdapter>::Create(signaling_thread, worker_thread, | 
|  | std::move(wrapped)); | 
|  | } | 
|  |  | 
|  | RtpTransportControllerAdapter::~RtpTransportControllerAdapter() { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread_); | 
|  | if (!transport_proxies_.empty()) { | 
|  | RTC_LOG(LS_ERROR) | 
|  | << "Destroying RtpTransportControllerAdapter while RtpTransports " | 
|  | "are still using it; this is unsafe."; | 
|  | } | 
|  | if (voice_channel_) { | 
|  | // This would mean audio RTP senders/receivers that are using us haven't | 
|  | // been destroyed. This isn't safe (see error log above). | 
|  | DestroyVoiceChannel(); | 
|  | } | 
|  | if (voice_channel_) { | 
|  | // This would mean video RTP senders/receivers that are using us haven't | 
|  | // been destroyed. This isn't safe (see error log above). | 
|  | DestroyVideoChannel(); | 
|  | } | 
|  | // Call must be destroyed on the worker thread. | 
|  | worker_thread_->Invoke<void>( | 
|  | RTC_FROM_HERE, | 
|  | rtc::Bind(&RtpTransportControllerAdapter::Close_w, this)); | 
|  | } | 
|  |  | 
|  | RTCErrorOr<std::unique_ptr<RtpTransportInterface>> | 
|  | RtpTransportControllerAdapter::CreateProxiedRtpTransport( | 
|  | const RtpTransportParameters& parameters, | 
|  | PacketTransportInterface* rtp, | 
|  | PacketTransportInterface* rtcp) { | 
|  | if (!transport_proxies_.empty() && (parameters.keepalive != keepalive_)) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_MODIFICATION, | 
|  | "Cannot create RtpTransport with different keep-alive " | 
|  | "from the RtpTransports already associated with this " | 
|  | "transport controller."); | 
|  | } | 
|  | auto result = RtpTransportAdapter::CreateProxied(parameters, rtp, rtcp, this); | 
|  | if (result.ok()) { | 
|  | transport_proxies_.push_back(result.value().get()); | 
|  | transport_proxies_.back()->GetInternal()->SignalDestroyed.connect( | 
|  | this, &RtpTransportControllerAdapter::OnRtpTransportDestroyed); | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | RTCErrorOr<std::unique_ptr<SrtpTransportInterface>> | 
|  | RtpTransportControllerAdapter::CreateProxiedSrtpTransport( | 
|  | const RtpTransportParameters& parameters, | 
|  | PacketTransportInterface* rtp, | 
|  | PacketTransportInterface* rtcp) { | 
|  | auto result = | 
|  | RtpTransportAdapter::CreateSrtpProxied(parameters, rtp, rtcp, this); | 
|  | if (result.ok()) { | 
|  | transport_proxies_.push_back(result.value().get()); | 
|  | transport_proxies_.back()->GetInternal()->SignalDestroyed.connect( | 
|  | this, &RtpTransportControllerAdapter::OnRtpTransportDestroyed); | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | RTCErrorOr<std::unique_ptr<OrtcRtpSenderInterface>> | 
|  | RtpTransportControllerAdapter::CreateProxiedRtpSender( | 
|  | cricket::MediaType kind, | 
|  | RtpTransportInterface* transport_proxy) { | 
|  | RTC_DCHECK(transport_proxy); | 
|  | RTC_DCHECK(std::find(transport_proxies_.begin(), transport_proxies_.end(), | 
|  | transport_proxy) != transport_proxies_.end()); | 
|  | std::unique_ptr<OrtcRtpSenderAdapter> new_sender( | 
|  | new OrtcRtpSenderAdapter(kind, transport_proxy, this)); | 
|  | RTCError err; | 
|  | switch (kind) { | 
|  | case cricket::MEDIA_TYPE_AUDIO: | 
|  | err = AttachAudioSender(new_sender.get(), transport_proxy->GetInternal()); | 
|  | break; | 
|  | case cricket::MEDIA_TYPE_VIDEO: | 
|  | err = AttachVideoSender(new_sender.get(), transport_proxy->GetInternal()); | 
|  | break; | 
|  | case cricket::MEDIA_TYPE_DATA: | 
|  | RTC_NOTREACHED(); | 
|  | } | 
|  | if (!err.ok()) { | 
|  | return std::move(err); | 
|  | } | 
|  |  | 
|  | return OrtcRtpSenderAdapter::CreateProxy(std::move(new_sender)); | 
|  | } | 
|  |  | 
|  | RTCErrorOr<std::unique_ptr<OrtcRtpReceiverInterface>> | 
|  | RtpTransportControllerAdapter::CreateProxiedRtpReceiver( | 
|  | cricket::MediaType kind, | 
|  | RtpTransportInterface* transport_proxy) { | 
|  | RTC_DCHECK(transport_proxy); | 
|  | RTC_DCHECK(std::find(transport_proxies_.begin(), transport_proxies_.end(), | 
|  | transport_proxy) != transport_proxies_.end()); | 
|  | std::unique_ptr<OrtcRtpReceiverAdapter> new_receiver( | 
|  | new OrtcRtpReceiverAdapter(kind, transport_proxy, this)); | 
|  | RTCError err; | 
|  | switch (kind) { | 
|  | case cricket::MEDIA_TYPE_AUDIO: | 
|  | err = AttachAudioReceiver(new_receiver.get(), | 
|  | transport_proxy->GetInternal()); | 
|  | break; | 
|  | case cricket::MEDIA_TYPE_VIDEO: | 
|  | err = AttachVideoReceiver(new_receiver.get(), | 
|  | transport_proxy->GetInternal()); | 
|  | break; | 
|  | case cricket::MEDIA_TYPE_DATA: | 
|  | RTC_NOTREACHED(); | 
|  | } | 
|  | if (!err.ok()) { | 
|  | return std::move(err); | 
|  | } | 
|  |  | 
|  | return OrtcRtpReceiverAdapter::CreateProxy(std::move(new_receiver)); | 
|  | } | 
|  |  | 
|  | std::vector<RtpTransportInterface*> | 
|  | RtpTransportControllerAdapter::GetTransports() const { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread_); | 
|  | return transport_proxies_; | 
|  | } | 
|  |  | 
|  | RTCError RtpTransportControllerAdapter::SetRtpTransportParameters( | 
|  | const RtpTransportParameters& parameters, | 
|  | RtpTransportInterface* inner_transport) { | 
|  | if ((video_channel_ != nullptr || voice_channel_ != nullptr) && | 
|  | (parameters.keepalive != keepalive_)) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_MODIFICATION, | 
|  | "Cannot change keep-alive settings after creating " | 
|  | "media streams or additional transports for the same " | 
|  | "transport controller."); | 
|  | } | 
|  | // Call must be configured on the worker thread. | 
|  | worker_thread_->Invoke<void>( | 
|  | RTC_FROM_HERE, | 
|  | rtc::Bind(&RtpTransportControllerAdapter::SetRtpTransportParameters_w, | 
|  | this, parameters)); | 
|  |  | 
|  | do { | 
|  | if (inner_transport == inner_audio_transport_) { | 
|  | CopyRtcpParametersToDescriptions(parameters.rtcp, | 
|  | &local_audio_description_, | 
|  | &remote_audio_description_); | 
|  | if (!voice_channel_->SetLocalContent(&local_audio_description_, | 
|  | SdpType::kOffer, nullptr)) { | 
|  | break; | 
|  | } | 
|  | if (!voice_channel_->SetRemoteContent(&remote_audio_description_, | 
|  | SdpType::kAnswer, nullptr)) { | 
|  | break; | 
|  | } | 
|  | } else if (inner_transport == inner_video_transport_) { | 
|  | CopyRtcpParametersToDescriptions(parameters.rtcp, | 
|  | &local_video_description_, | 
|  | &remote_video_description_); | 
|  | if (!video_channel_->SetLocalContent(&local_video_description_, | 
|  | SdpType::kOffer, nullptr)) { | 
|  | break; | 
|  | } | 
|  | if (!video_channel_->SetRemoteContent(&remote_video_description_, | 
|  | SdpType::kAnswer, nullptr)) { | 
|  | break; | 
|  | } | 
|  | } | 
|  | return RTCError::OK(); | 
|  | } while (false); | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR, | 
|  | "Failed to apply new RTCP parameters."); | 
|  | } | 
|  |  | 
|  | void RtpTransportControllerAdapter::SetRtpTransportParameters_w( | 
|  | const RtpTransportParameters& parameters) { | 
|  | call_send_rtp_transport_controller_->SetKeepAliveConfig(parameters.keepalive); | 
|  | } | 
|  |  | 
|  | RTCError RtpTransportControllerAdapter::ValidateAndApplyAudioSenderParameters( | 
|  | const RtpParameters& parameters, | 
|  | uint32_t* primary_ssrc) { | 
|  | RTC_DCHECK(voice_channel_); | 
|  | RTC_DCHECK(have_audio_sender_); | 
|  |  | 
|  | auto codecs_result = ToCricketCodecs<cricket::AudioCodec>(parameters.codecs); | 
|  | if (!codecs_result.ok()) { | 
|  | return codecs_result.MoveError(); | 
|  | } | 
|  |  | 
|  | auto extensions_result = | 
|  | ToCricketRtpHeaderExtensions(parameters.header_extensions); | 
|  | if (!extensions_result.ok()) { | 
|  | return extensions_result.MoveError(); | 
|  | } | 
|  |  | 
|  | auto stream_params_result = MakeSendStreamParamsVec( | 
|  | parameters.encodings, inner_audio_transport_->GetParameters().rtcp.cname, | 
|  | local_audio_description_); | 
|  | if (!stream_params_result.ok()) { | 
|  | return stream_params_result.MoveError(); | 
|  | } | 
|  |  | 
|  | // Check that audio/video sender aren't using the same IDs to refer to | 
|  | // different things, if they share the same transport. | 
|  | if (inner_audio_transport_ == inner_video_transport_) { | 
|  | RTCError err = CheckForIdConflicts( | 
|  | codecs_result.value(), extensions_result.value(), | 
|  | stream_params_result.value(), remote_video_description_.codecs(), | 
|  | remote_video_description_.rtp_header_extensions(), | 
|  | local_video_description_.streams()); | 
|  | if (!err.ok()) { | 
|  | return err; | 
|  | } | 
|  | } | 
|  |  | 
|  | bool local_send = false; | 
|  | int bandwidth = cricket::kAutoBandwidth; | 
|  | if (parameters.encodings.size() == 1u) { | 
|  | if (parameters.encodings[0].max_bitrate_bps) { | 
|  | bandwidth = *parameters.encodings[0].max_bitrate_bps; | 
|  | } | 
|  | local_send = parameters.encodings[0].active; | 
|  | } | 
|  | const bool local_recv = | 
|  | RtpTransceiverDirectionHasRecv(local_audio_description_.direction()); | 
|  | const auto local_direction = | 
|  | RtpTransceiverDirectionFromSendRecv(local_send, local_recv); | 
|  | if (primary_ssrc && !stream_params_result.value().empty()) { | 
|  | *primary_ssrc = stream_params_result.value()[0].first_ssrc(); | 
|  | } | 
|  |  | 
|  | // Validation is done, so we can attempt applying the descriptions. Sent | 
|  | // codecs and header extensions go in remote description, streams go in | 
|  | // local. | 
|  | // | 
|  | // If there are no codecs or encodings, just leave the previous set of | 
|  | // codecs. The media engine doesn't like an empty set of codecs. | 
|  | if (local_audio_description_.streams().empty() && | 
|  | remote_audio_description_.codecs().empty()) { | 
|  | } else { | 
|  | remote_audio_description_.set_codecs(codecs_result.MoveValue()); | 
|  | } | 
|  | remote_audio_description_.set_rtp_header_extensions( | 
|  | extensions_result.MoveValue()); | 
|  | remote_audio_description_.set_bandwidth(bandwidth); | 
|  | local_audio_description_.mutable_streams() = stream_params_result.MoveValue(); | 
|  | // Direction set based on encoding "active" flag. | 
|  | local_audio_description_.set_direction(local_direction); | 
|  | remote_audio_description_.set_direction( | 
|  | RtpTransceiverDirectionReversed(local_direction)); | 
|  |  | 
|  | // Set remote content first, to ensure the stream is created with the correct | 
|  | // codec. | 
|  | if (!voice_channel_->SetRemoteContent(&remote_audio_description_, | 
|  | SdpType::kOffer, nullptr)) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR, | 
|  | "Failed to apply remote parameters to media channel."); | 
|  | } | 
|  | if (!voice_channel_->SetLocalContent(&local_audio_description_, | 
|  | SdpType::kAnswer, nullptr)) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR, | 
|  | "Failed to apply local parameters to media channel."); | 
|  | } | 
|  | return RTCError::OK(); | 
|  | } | 
|  |  | 
|  | RTCError RtpTransportControllerAdapter::ValidateAndApplyVideoSenderParameters( | 
|  | const RtpParameters& parameters, | 
|  | uint32_t* primary_ssrc) { | 
|  | RTC_DCHECK(video_channel_); | 
|  | RTC_DCHECK(have_video_sender_); | 
|  |  | 
|  | auto codecs_result = ToCricketCodecs<cricket::VideoCodec>(parameters.codecs); | 
|  | if (!codecs_result.ok()) { | 
|  | return codecs_result.MoveError(); | 
|  | } | 
|  |  | 
|  | auto extensions_result = | 
|  | ToCricketRtpHeaderExtensions(parameters.header_extensions); | 
|  | if (!extensions_result.ok()) { | 
|  | return extensions_result.MoveError(); | 
|  | } | 
|  |  | 
|  | auto stream_params_result = MakeSendStreamParamsVec( | 
|  | parameters.encodings, inner_video_transport_->GetParameters().rtcp.cname, | 
|  | local_video_description_); | 
|  | if (!stream_params_result.ok()) { | 
|  | return stream_params_result.MoveError(); | 
|  | } | 
|  |  | 
|  | // Check that audio/video sender aren't using the same IDs to refer to | 
|  | // different things, if they share the same transport. | 
|  | if (inner_audio_transport_ == inner_video_transport_) { | 
|  | RTCError err = CheckForIdConflicts( | 
|  | codecs_result.value(), extensions_result.value(), | 
|  | stream_params_result.value(), remote_audio_description_.codecs(), | 
|  | remote_audio_description_.rtp_header_extensions(), | 
|  | local_audio_description_.streams()); | 
|  | if (!err.ok()) { | 
|  | return err; | 
|  | } | 
|  | } | 
|  |  | 
|  | bool local_send = false; | 
|  | int bandwidth = cricket::kAutoBandwidth; | 
|  | if (parameters.encodings.size() == 1u) { | 
|  | if (parameters.encodings[0].max_bitrate_bps) { | 
|  | bandwidth = *parameters.encodings[0].max_bitrate_bps; | 
|  | } | 
|  | local_send = parameters.encodings[0].active; | 
|  | } | 
|  | const bool local_recv = | 
|  | RtpTransceiverDirectionHasRecv(local_audio_description_.direction()); | 
|  | const auto local_direction = | 
|  | RtpTransceiverDirectionFromSendRecv(local_send, local_recv); | 
|  | if (primary_ssrc && !stream_params_result.value().empty()) { | 
|  | *primary_ssrc = stream_params_result.value()[0].first_ssrc(); | 
|  | } | 
|  |  | 
|  | // Validation is done, so we can attempt applying the descriptions. Sent | 
|  | // codecs and header extensions go in remote description, streams go in | 
|  | // local. | 
|  | // | 
|  | // If there are no codecs or encodings, just leave the previous set of | 
|  | // codecs. The media engine doesn't like an empty set of codecs. | 
|  | if (local_video_description_.streams().empty() && | 
|  | remote_video_description_.codecs().empty()) { | 
|  | } else { | 
|  | remote_video_description_.set_codecs(codecs_result.MoveValue()); | 
|  | } | 
|  | remote_video_description_.set_rtp_header_extensions( | 
|  | extensions_result.MoveValue()); | 
|  | remote_video_description_.set_bandwidth(bandwidth); | 
|  | local_video_description_.mutable_streams() = stream_params_result.MoveValue(); | 
|  | // Direction set based on encoding "active" flag. | 
|  | local_video_description_.set_direction(local_direction); | 
|  | remote_video_description_.set_direction( | 
|  | RtpTransceiverDirectionReversed(local_direction)); | 
|  |  | 
|  | // Set remote content first, to ensure the stream is created with the correct | 
|  | // codec. | 
|  | if (!video_channel_->SetRemoteContent(&remote_video_description_, | 
|  | SdpType::kOffer, nullptr)) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR, | 
|  | "Failed to apply remote parameters to media channel."); | 
|  | } | 
|  | if (!video_channel_->SetLocalContent(&local_video_description_, | 
|  | SdpType::kAnswer, nullptr)) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR, | 
|  | "Failed to apply local parameters to media channel."); | 
|  | } | 
|  | return RTCError::OK(); | 
|  | } | 
|  |  | 
|  | RTCError RtpTransportControllerAdapter::ValidateAndApplyAudioReceiverParameters( | 
|  | const RtpParameters& parameters) { | 
|  | RTC_DCHECK(voice_channel_); | 
|  | RTC_DCHECK(have_audio_receiver_); | 
|  |  | 
|  | auto codecs_result = ToCricketCodecs<cricket::AudioCodec>(parameters.codecs); | 
|  | if (!codecs_result.ok()) { | 
|  | return codecs_result.MoveError(); | 
|  | } | 
|  |  | 
|  | auto extensions_result = | 
|  | ToCricketRtpHeaderExtensions(parameters.header_extensions); | 
|  | if (!extensions_result.ok()) { | 
|  | return extensions_result.MoveError(); | 
|  | } | 
|  |  | 
|  | auto stream_params_result = ToCricketStreamParamsVec(parameters.encodings); | 
|  | if (!stream_params_result.ok()) { | 
|  | return stream_params_result.MoveError(); | 
|  | } | 
|  |  | 
|  | // Check that audio/video receive aren't using the same IDs to refer to | 
|  | // different things, if they share the same transport. | 
|  | if (inner_audio_transport_ == inner_video_transport_) { | 
|  | RTCError err = CheckForIdConflicts( | 
|  | codecs_result.value(), extensions_result.value(), | 
|  | stream_params_result.value(), local_video_description_.codecs(), | 
|  | local_video_description_.rtp_header_extensions(), | 
|  | remote_video_description_.streams()); | 
|  | if (!err.ok()) { | 
|  | return err; | 
|  | } | 
|  | } | 
|  |  | 
|  | const bool local_send = | 
|  | RtpTransceiverDirectionHasSend(local_audio_description_.direction()); | 
|  | const bool local_recv = | 
|  | !parameters.encodings.empty() && parameters.encodings[0].active; | 
|  | const auto local_direction = | 
|  | RtpTransceiverDirectionFromSendRecv(local_send, local_recv); | 
|  |  | 
|  | // Validation is done, so we can attempt applying the descriptions. Received | 
|  | // codecs and header extensions go in local description, streams go in | 
|  | // remote. | 
|  | // | 
|  | // If there are no codecs or encodings, just leave the previous set of | 
|  | // codecs. The media engine doesn't like an empty set of codecs. | 
|  | if (remote_audio_description_.streams().empty() && | 
|  | local_audio_description_.codecs().empty()) { | 
|  | } else { | 
|  | local_audio_description_.set_codecs(codecs_result.MoveValue()); | 
|  | } | 
|  | local_audio_description_.set_rtp_header_extensions( | 
|  | extensions_result.MoveValue()); | 
|  | remote_audio_description_.mutable_streams() = | 
|  | stream_params_result.MoveValue(); | 
|  | // Direction set based on encoding "active" flag. | 
|  | local_audio_description_.set_direction(local_direction); | 
|  | remote_audio_description_.set_direction( | 
|  | RtpTransceiverDirectionReversed(local_direction)); | 
|  |  | 
|  | if (!voice_channel_->SetLocalContent(&local_audio_description_, | 
|  | SdpType::kOffer, nullptr)) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR, | 
|  | "Failed to apply local parameters to media channel."); | 
|  | } | 
|  | if (!voice_channel_->SetRemoteContent(&remote_audio_description_, | 
|  | SdpType::kAnswer, nullptr)) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR, | 
|  | "Failed to apply remote parameters to media channel."); | 
|  | } | 
|  | return RTCError::OK(); | 
|  | } | 
|  |  | 
|  | RTCError RtpTransportControllerAdapter::ValidateAndApplyVideoReceiverParameters( | 
|  | const RtpParameters& parameters) { | 
|  | RTC_DCHECK(video_channel_); | 
|  | RTC_DCHECK(have_video_receiver_); | 
|  |  | 
|  | auto codecs_result = ToCricketCodecs<cricket::VideoCodec>(parameters.codecs); | 
|  | if (!codecs_result.ok()) { | 
|  | return codecs_result.MoveError(); | 
|  | } | 
|  |  | 
|  | auto extensions_result = | 
|  | ToCricketRtpHeaderExtensions(parameters.header_extensions); | 
|  | if (!extensions_result.ok()) { | 
|  | return extensions_result.MoveError(); | 
|  | } | 
|  |  | 
|  | int bandwidth = cricket::kAutoBandwidth; | 
|  | auto stream_params_result = ToCricketStreamParamsVec(parameters.encodings); | 
|  | if (!stream_params_result.ok()) { | 
|  | return stream_params_result.MoveError(); | 
|  | } | 
|  |  | 
|  | // Check that audio/video receiver aren't using the same IDs to refer to | 
|  | // different things, if they share the same transport. | 
|  | if (inner_audio_transport_ == inner_video_transport_) { | 
|  | RTCError err = CheckForIdConflicts( | 
|  | codecs_result.value(), extensions_result.value(), | 
|  | stream_params_result.value(), local_audio_description_.codecs(), | 
|  | local_audio_description_.rtp_header_extensions(), | 
|  | remote_audio_description_.streams()); | 
|  | if (!err.ok()) { | 
|  | return err; | 
|  | } | 
|  | } | 
|  |  | 
|  | const bool local_send = | 
|  | RtpTransceiverDirectionHasSend(local_video_description_.direction()); | 
|  | const bool local_recv = | 
|  | !parameters.encodings.empty() && parameters.encodings[0].active; | 
|  | const auto local_direction = | 
|  | RtpTransceiverDirectionFromSendRecv(local_send, local_recv); | 
|  |  | 
|  | // Validation is done, so we can attempt applying the descriptions. Received | 
|  | // codecs and header extensions go in local description, streams go in | 
|  | // remote. | 
|  | // | 
|  | // If there are no codecs or encodings, just leave the previous set of | 
|  | // codecs. The media engine doesn't like an empty set of codecs. | 
|  | if (remote_video_description_.streams().empty() && | 
|  | local_video_description_.codecs().empty()) { | 
|  | } else { | 
|  | local_video_description_.set_codecs(codecs_result.MoveValue()); | 
|  | } | 
|  | local_video_description_.set_rtp_header_extensions( | 
|  | extensions_result.MoveValue()); | 
|  | local_video_description_.set_bandwidth(bandwidth); | 
|  | remote_video_description_.mutable_streams() = | 
|  | stream_params_result.MoveValue(); | 
|  | // Direction set based on encoding "active" flag. | 
|  | local_video_description_.set_direction(local_direction); | 
|  | remote_video_description_.set_direction( | 
|  | RtpTransceiverDirectionReversed(local_direction)); | 
|  |  | 
|  | if (!video_channel_->SetLocalContent(&local_video_description_, | 
|  | SdpType::kOffer, nullptr)) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR, | 
|  | "Failed to apply local parameters to media channel."); | 
|  | } | 
|  | if (!video_channel_->SetRemoteContent(&remote_video_description_, | 
|  | SdpType::kAnswer, nullptr)) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR, | 
|  | "Failed to apply remote parameters to media channel."); | 
|  | } | 
|  | return RTCError::OK(); | 
|  | } | 
|  |  | 
|  | RtpTransportControllerAdapter::RtpTransportControllerAdapter( | 
|  | const cricket::MediaConfig& config, | 
|  | cricket::ChannelManager* channel_manager, | 
|  | webrtc::RtcEventLog* event_log, | 
|  | rtc::Thread* signaling_thread, | 
|  | rtc::Thread* worker_thread, | 
|  | rtc::Thread* network_thread) | 
|  | : signaling_thread_(signaling_thread), | 
|  | worker_thread_(worker_thread), | 
|  | network_thread_(network_thread), | 
|  | media_config_(config), | 
|  | channel_manager_(channel_manager), | 
|  | event_log_(event_log), | 
|  | call_send_rtp_transport_controller_(nullptr) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread_); | 
|  | RTC_DCHECK(channel_manager_); | 
|  | // Add "dummy" codecs to the descriptions, because the media engines | 
|  | // currently reject empty lists of codecs. Note that these codecs will never | 
|  | // actually be used, because when parameters are set, the dummy codecs will | 
|  | // be replaced by actual codecs before any send/receive streams are created. | 
|  | static const cricket::AudioCodec dummy_audio(0, cricket::kPcmuCodecName, 8000, | 
|  | 0, 1); | 
|  | static const cricket::VideoCodec dummy_video(96, cricket::kVp8CodecName); | 
|  | local_audio_description_.AddCodec(dummy_audio); | 
|  | remote_audio_description_.AddCodec(dummy_audio); | 
|  | local_video_description_.AddCodec(dummy_video); | 
|  | remote_video_description_.AddCodec(dummy_video); | 
|  |  | 
|  | worker_thread_->Invoke<void>( | 
|  | RTC_FROM_HERE, | 
|  | rtc::Bind(&RtpTransportControllerAdapter::Init_w, this)); | 
|  | } | 
|  |  | 
|  | // TODO(nisse): Duplicates corresponding method in PeerConnection (used | 
|  | // to be in MediaController). | 
|  | void RtpTransportControllerAdapter::Init_w() { | 
|  | RTC_DCHECK(worker_thread_->IsCurrent()); | 
|  | RTC_DCHECK(!call_); | 
|  |  | 
|  | const int kMinBandwidthBps = 30000; | 
|  | const int kStartBandwidthBps = 300000; | 
|  | const int kMaxBandwidthBps = 2000000; | 
|  |  | 
|  | webrtc::Call::Config call_config(event_log_); | 
|  | call_config.audio_state = channel_manager_->media_engine()->GetAudioState(); | 
|  | call_config.bitrate_config.min_bitrate_bps = kMinBandwidthBps; | 
|  | call_config.bitrate_config.start_bitrate_bps = kStartBandwidthBps; | 
|  | call_config.bitrate_config.max_bitrate_bps = kMaxBandwidthBps; | 
|  | std::unique_ptr<RtpTransportControllerSend> controller_send = | 
|  | rtc::MakeUnique<RtpTransportControllerSend>( | 
|  | Clock::GetRealTimeClock(), event_log_, | 
|  | call_config.network_controller_factory, call_config.bitrate_config); | 
|  | call_send_rtp_transport_controller_ = controller_send.get(); | 
|  | call_.reset(webrtc::Call::Create(call_config, std::move(controller_send))); | 
|  | } | 
|  |  | 
|  | void RtpTransportControllerAdapter::Close_w() { | 
|  | call_.reset(); | 
|  | call_send_rtp_transport_controller_ = nullptr; | 
|  | } | 
|  |  | 
|  | RTCError RtpTransportControllerAdapter::AttachAudioSender( | 
|  | OrtcRtpSenderAdapter* sender, | 
|  | RtpTransportInterface* inner_transport) { | 
|  | if (have_audio_sender_) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::UNSUPPORTED_OPERATION, | 
|  | "Using two audio RtpSenders with the same " | 
|  | "RtpTransportControllerAdapter is not currently " | 
|  | "supported."); | 
|  | } | 
|  | if (inner_audio_transport_ && inner_audio_transport_ != inner_transport) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::UNSUPPORTED_OPERATION, | 
|  | "Using different transports for the audio " | 
|  | "RtpSender and RtpReceiver is not currently " | 
|  | "supported."); | 
|  | } | 
|  |  | 
|  | // If setting new transport, extract its RTCP parameters and create voice | 
|  | // channel. | 
|  | if (!inner_audio_transport_) { | 
|  | CopyRtcpParametersToDescriptions(inner_transport->GetParameters().rtcp, | 
|  | &local_audio_description_, | 
|  | &remote_audio_description_); | 
|  | inner_audio_transport_ = inner_transport; | 
|  | CreateVoiceChannel(); | 
|  | } | 
|  | have_audio_sender_ = true; | 
|  | sender->SignalDestroyed.connect( | 
|  | this, &RtpTransportControllerAdapter::OnAudioSenderDestroyed); | 
|  | return RTCError::OK(); | 
|  | } | 
|  |  | 
|  | RTCError RtpTransportControllerAdapter::AttachVideoSender( | 
|  | OrtcRtpSenderAdapter* sender, | 
|  | RtpTransportInterface* inner_transport) { | 
|  | if (have_video_sender_) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::UNSUPPORTED_OPERATION, | 
|  | "Using two video RtpSenders with the same " | 
|  | "RtpTransportControllerAdapter is not currently " | 
|  | "supported."); | 
|  | } | 
|  | if (inner_video_transport_ && inner_video_transport_ != inner_transport) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::UNSUPPORTED_OPERATION, | 
|  | "Using different transports for the video " | 
|  | "RtpSender and RtpReceiver is not currently " | 
|  | "supported."); | 
|  | } | 
|  |  | 
|  | // If setting new transport, extract its RTCP parameters and create video | 
|  | // channel. | 
|  | if (!inner_video_transport_) { | 
|  | CopyRtcpParametersToDescriptions(inner_transport->GetParameters().rtcp, | 
|  | &local_video_description_, | 
|  | &remote_video_description_); | 
|  | inner_video_transport_ = inner_transport; | 
|  | CreateVideoChannel(); | 
|  | } | 
|  | have_video_sender_ = true; | 
|  | sender->SignalDestroyed.connect( | 
|  | this, &RtpTransportControllerAdapter::OnVideoSenderDestroyed); | 
|  | return RTCError::OK(); | 
|  | } | 
|  |  | 
|  | RTCError RtpTransportControllerAdapter::AttachAudioReceiver( | 
|  | OrtcRtpReceiverAdapter* receiver, | 
|  | RtpTransportInterface* inner_transport) { | 
|  | if (have_audio_receiver_) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::UNSUPPORTED_OPERATION, | 
|  | "Using two audio RtpReceivers with the same " | 
|  | "RtpTransportControllerAdapter is not currently " | 
|  | "supported."); | 
|  | } | 
|  | if (inner_audio_transport_ && inner_audio_transport_ != inner_transport) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::UNSUPPORTED_OPERATION, | 
|  | "Using different transports for the audio " | 
|  | "RtpReceiver and RtpReceiver is not currently " | 
|  | "supported."); | 
|  | } | 
|  |  | 
|  | // If setting new transport, extract its RTCP parameters and create voice | 
|  | // channel. | 
|  | if (!inner_audio_transport_) { | 
|  | CopyRtcpParametersToDescriptions(inner_transport->GetParameters().rtcp, | 
|  | &local_audio_description_, | 
|  | &remote_audio_description_); | 
|  | inner_audio_transport_ = inner_transport; | 
|  | CreateVoiceChannel(); | 
|  | } | 
|  | have_audio_receiver_ = true; | 
|  | receiver->SignalDestroyed.connect( | 
|  | this, &RtpTransportControllerAdapter::OnAudioReceiverDestroyed); | 
|  | return RTCError::OK(); | 
|  | } | 
|  |  | 
|  | RTCError RtpTransportControllerAdapter::AttachVideoReceiver( | 
|  | OrtcRtpReceiverAdapter* receiver, | 
|  | RtpTransportInterface* inner_transport) { | 
|  | if (have_video_receiver_) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::UNSUPPORTED_OPERATION, | 
|  | "Using two video RtpReceivers with the same " | 
|  | "RtpTransportControllerAdapter is not currently " | 
|  | "supported."); | 
|  | } | 
|  | if (inner_video_transport_ && inner_video_transport_ != inner_transport) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::UNSUPPORTED_OPERATION, | 
|  | "Using different transports for the video " | 
|  | "RtpReceiver and RtpReceiver is not currently " | 
|  | "supported."); | 
|  | } | 
|  | // If setting new transport, extract its RTCP parameters and create video | 
|  | // channel. | 
|  | if (!inner_video_transport_) { | 
|  | CopyRtcpParametersToDescriptions(inner_transport->GetParameters().rtcp, | 
|  | &local_video_description_, | 
|  | &remote_video_description_); | 
|  | inner_video_transport_ = inner_transport; | 
|  | CreateVideoChannel(); | 
|  | } | 
|  | have_video_receiver_ = true; | 
|  | receiver->SignalDestroyed.connect( | 
|  | this, &RtpTransportControllerAdapter::OnVideoReceiverDestroyed); | 
|  | return RTCError::OK(); | 
|  | } | 
|  |  | 
|  | void RtpTransportControllerAdapter::OnRtpTransportDestroyed( | 
|  | RtpTransportAdapter* transport) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread_); | 
|  | auto it = std::find_if(transport_proxies_.begin(), transport_proxies_.end(), | 
|  | [transport](RtpTransportInterface* proxy) { | 
|  | return proxy->GetInternal() == transport; | 
|  | }); | 
|  | if (it == transport_proxies_.end()) { | 
|  | RTC_NOTREACHED(); | 
|  | return; | 
|  | } | 
|  | transport_proxies_.erase(it); | 
|  | } | 
|  |  | 
|  | void RtpTransportControllerAdapter::OnAudioSenderDestroyed() { | 
|  | if (!have_audio_sender_) { | 
|  | RTC_NOTREACHED(); | 
|  | return; | 
|  | } | 
|  | // Empty parameters should result in sending being stopped. | 
|  | RTCError err = | 
|  | ValidateAndApplyAudioSenderParameters(RtpParameters(), nullptr); | 
|  | RTC_DCHECK(err.ok()); | 
|  | have_audio_sender_ = false; | 
|  | if (!have_audio_receiver_) { | 
|  | DestroyVoiceChannel(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void RtpTransportControllerAdapter::OnVideoSenderDestroyed() { | 
|  | if (!have_video_sender_) { | 
|  | RTC_NOTREACHED(); | 
|  | return; | 
|  | } | 
|  | // Empty parameters should result in sending being stopped. | 
|  | RTCError err = | 
|  | ValidateAndApplyVideoSenderParameters(RtpParameters(), nullptr); | 
|  | RTC_DCHECK(err.ok()); | 
|  | have_video_sender_ = false; | 
|  | if (!have_video_receiver_) { | 
|  | DestroyVideoChannel(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void RtpTransportControllerAdapter::OnAudioReceiverDestroyed() { | 
|  | if (!have_audio_receiver_) { | 
|  | RTC_NOTREACHED(); | 
|  | return; | 
|  | } | 
|  | // Empty parameters should result in receiving being stopped. | 
|  | RTCError err = ValidateAndApplyAudioReceiverParameters(RtpParameters()); | 
|  | RTC_DCHECK(err.ok()); | 
|  | have_audio_receiver_ = false; | 
|  | if (!have_audio_sender_) { | 
|  | DestroyVoiceChannel(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void RtpTransportControllerAdapter::OnVideoReceiverDestroyed() { | 
|  | if (!have_video_receiver_) { | 
|  | RTC_NOTREACHED(); | 
|  | return; | 
|  | } | 
|  | // Empty parameters should result in receiving being stopped. | 
|  | RTCError err = ValidateAndApplyVideoReceiverParameters(RtpParameters()); | 
|  | RTC_DCHECK(err.ok()); | 
|  | have_video_receiver_ = false; | 
|  | if (!have_video_sender_) { | 
|  | DestroyVideoChannel(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void RtpTransportControllerAdapter::CreateVoiceChannel() { | 
|  | voice_channel_ = channel_manager_->CreateVoiceChannel( | 
|  | call_.get(), media_config_, inner_audio_transport_->GetInternal(), | 
|  | signaling_thread_, "audio", false, rtc::CryptoOptions(), | 
|  | cricket::AudioOptions()); | 
|  | RTC_DCHECK(voice_channel_); | 
|  | voice_channel_->Enable(true); | 
|  | } | 
|  |  | 
|  | void RtpTransportControllerAdapter::CreateVideoChannel() { | 
|  | video_channel_ = channel_manager_->CreateVideoChannel( | 
|  | call_.get(), media_config_, inner_video_transport_->GetInternal(), | 
|  | signaling_thread_, "video", false, rtc::CryptoOptions(), | 
|  | cricket::VideoOptions()); | 
|  | RTC_DCHECK(video_channel_); | 
|  | video_channel_->Enable(true); | 
|  | } | 
|  |  | 
|  | void RtpTransportControllerAdapter::DestroyVoiceChannel() { | 
|  | RTC_DCHECK(voice_channel_); | 
|  | channel_manager_->DestroyVoiceChannel(voice_channel_); | 
|  | voice_channel_ = nullptr; | 
|  | inner_audio_transport_ = nullptr; | 
|  | } | 
|  |  | 
|  | void RtpTransportControllerAdapter::DestroyVideoChannel() { | 
|  | RTC_DCHECK(video_channel_); | 
|  | channel_manager_->DestroyVideoChannel(video_channel_); | 
|  | video_channel_ = nullptr; | 
|  | inner_video_transport_ = nullptr; | 
|  | } | 
|  |  | 
|  | void RtpTransportControllerAdapter::CopyRtcpParametersToDescriptions( | 
|  | const RtcpParameters& params, | 
|  | cricket::MediaContentDescription* local, | 
|  | cricket::MediaContentDescription* remote) { | 
|  | local->set_rtcp_mux(params.mux); | 
|  | remote->set_rtcp_mux(params.mux); | 
|  | local->set_rtcp_reduced_size(params.reduced_size); | 
|  | remote->set_rtcp_reduced_size(params.reduced_size); | 
|  | for (cricket::StreamParams& stream_params : local->mutable_streams()) { | 
|  | stream_params.cname = params.cname; | 
|  | } | 
|  | } | 
|  |  | 
|  | uint32_t RtpTransportControllerAdapter::GenerateUnusedSsrc( | 
|  | std::set<uint32_t>* new_ssrcs) const { | 
|  | uint32_t ssrc; | 
|  | do { | 
|  | ssrc = rtc::CreateRandomNonZeroId(); | 
|  | } while ( | 
|  | cricket::GetStreamBySsrc(local_audio_description_.streams(), ssrc) || | 
|  | cricket::GetStreamBySsrc(remote_audio_description_.streams(), ssrc) || | 
|  | cricket::GetStreamBySsrc(local_video_description_.streams(), ssrc) || | 
|  | cricket::GetStreamBySsrc(remote_video_description_.streams(), ssrc) || | 
|  | !new_ssrcs->insert(ssrc).second); | 
|  | return ssrc; | 
|  | } | 
|  |  | 
|  | RTCErrorOr<cricket::StreamParamsVec> | 
|  | RtpTransportControllerAdapter::MakeSendStreamParamsVec( | 
|  | std::vector<RtpEncodingParameters> encodings, | 
|  | const std::string& cname, | 
|  | const cricket::MediaContentDescription& description) const { | 
|  | if (encodings.size() > 1u) { | 
|  | LOG_AND_RETURN_ERROR(webrtc::RTCErrorType::UNSUPPORTED_PARAMETER, | 
|  | "ORTC API implementation doesn't currently " | 
|  | "support simulcast or layered encodings."); | 
|  | } else if (encodings.empty()) { | 
|  | return cricket::StreamParamsVec(); | 
|  | } | 
|  | RtpEncodingParameters& encoding = encodings[0]; | 
|  | std::set<uint32_t> new_ssrcs; | 
|  | if (encoding.ssrc) { | 
|  | new_ssrcs.insert(*encoding.ssrc); | 
|  | } | 
|  | if (encoding.rtx && encoding.rtx->ssrc) { | 
|  | new_ssrcs.insert(*encoding.rtx->ssrc); | 
|  | } | 
|  | // May need to fill missing SSRCs with generated ones. | 
|  | if (!encoding.ssrc) { | 
|  | if (!description.streams().empty()) { | 
|  | encoding.ssrc.emplace(description.streams()[0].first_ssrc()); | 
|  | } else { | 
|  | encoding.ssrc.emplace(GenerateUnusedSsrc(&new_ssrcs)); | 
|  | } | 
|  | } | 
|  | if (encoding.rtx && !encoding.rtx->ssrc) { | 
|  | uint32_t existing_rtx_ssrc; | 
|  | if (!description.streams().empty() && | 
|  | description.streams()[0].GetFidSsrc( | 
|  | description.streams()[0].first_ssrc(), &existing_rtx_ssrc)) { | 
|  | encoding.rtx->ssrc.emplace(existing_rtx_ssrc); | 
|  | } else { | 
|  | encoding.rtx->ssrc.emplace(GenerateUnusedSsrc(&new_ssrcs)); | 
|  | } | 
|  | } | 
|  |  | 
|  | auto result = ToCricketStreamParamsVec(encodings); | 
|  | if (!result.ok()) { | 
|  | return result.MoveError(); | 
|  | } | 
|  | // If conversion was successful, there should be one StreamParams. | 
|  | RTC_DCHECK_EQ(1u, result.value().size()); | 
|  | result.value()[0].cname = cname; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | }  // namespace webrtc |