| /* |
| * 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 "webrtc/ortc/rtptransportcontrolleradapter.h" |
| |
| #include <algorithm> // For "remove", "find". |
| #include <set> |
| #include <sstream> |
| #include <unordered_map> |
| #include <utility> // For std::move. |
| |
| #include "webrtc/api/proxy.h" |
| #include "webrtc/media/base/mediaconstants.h" |
| #include "webrtc/ortc/ortcrtpreceiveradapter.h" |
| #include "webrtc/ortc/ortcrtpsenderadapter.h" |
| #include "webrtc/ortc/rtpparametersconversion.h" |
| #include "webrtc/ortc/rtptransportadapter.h" |
| #include "webrtc/rtc_base/checks.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) { |
| std::unique_ptr<RtpTransportControllerAdapter> wrapped( |
| new RtpTransportControllerAdapter(config, channel_manager, event_log, |
| signaling_thread, worker_thread)); |
| return RtpTransportControllerProxyWithInternal< |
| RtpTransportControllerAdapter>::Create(signaling_thread, worker_thread, |
| std::move(wrapped)); |
| } |
| |
| RtpTransportControllerAdapter::~RtpTransportControllerAdapter() { |
| RTC_DCHECK_RUN_ON(signaling_thread_); |
| if (!transport_proxies_.empty()) { |
| 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_, |
| cricket::CA_OFFER, nullptr)) { |
| break; |
| } |
| if (!voice_channel_->SetRemoteContent(&remote_audio_description_, |
| cricket::CA_ANSWER, 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_, |
| cricket::CA_OFFER, nullptr)) { |
| break; |
| } |
| if (!video_channel_->SetRemoteContent(&remote_video_description_, |
| cricket::CA_ANSWER, 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; |
| } |
| } |
| |
| cricket::RtpTransceiverDirection local_direction = |
| cricket::RtpTransceiverDirection::FromMediaContentDirection( |
| local_audio_description_.direction()); |
| int bandwidth = cricket::kAutoBandwidth; |
| if (parameters.encodings.size() == 1u) { |
| if (parameters.encodings[0].max_bitrate_bps) { |
| bandwidth = *parameters.encodings[0].max_bitrate_bps; |
| } |
| local_direction.send = parameters.encodings[0].active; |
| } else { |
| local_direction.send = false; |
| } |
| 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.ToMediaContentDirection()); |
| remote_audio_description_.set_direction( |
| local_direction.Reversed().ToMediaContentDirection()); |
| |
| // Set remote content first, to ensure the stream is created with the correct |
| // codec. |
| if (!voice_channel_->SetRemoteContent(&remote_audio_description_, |
| cricket::CA_OFFER, nullptr)) { |
| LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR, |
| "Failed to apply remote parameters to media channel."); |
| } |
| if (!voice_channel_->SetLocalContent(&local_audio_description_, |
| cricket::CA_ANSWER, 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; |
| } |
| } |
| |
| cricket::RtpTransceiverDirection local_direction = |
| cricket::RtpTransceiverDirection::FromMediaContentDirection( |
| local_video_description_.direction()); |
| int bandwidth = cricket::kAutoBandwidth; |
| if (parameters.encodings.size() == 1u) { |
| if (parameters.encodings[0].max_bitrate_bps) { |
| bandwidth = *parameters.encodings[0].max_bitrate_bps; |
| } |
| local_direction.send = parameters.encodings[0].active; |
| } else { |
| local_direction.send = false; |
| } |
| 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.ToMediaContentDirection()); |
| remote_video_description_.set_direction( |
| local_direction.Reversed().ToMediaContentDirection()); |
| |
| // Set remote content first, to ensure the stream is created with the correct |
| // codec. |
| if (!video_channel_->SetRemoteContent(&remote_video_description_, |
| cricket::CA_OFFER, nullptr)) { |
| LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR, |
| "Failed to apply remote parameters to media channel."); |
| } |
| if (!video_channel_->SetLocalContent(&local_video_description_, |
| cricket::CA_ANSWER, 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(); |
| } |
| |
| cricket::RtpTransceiverDirection local_direction = |
| cricket::RtpTransceiverDirection::FromMediaContentDirection( |
| local_audio_description_.direction()); |
| 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; |
| } |
| } |
| |
| local_direction.recv = |
| !parameters.encodings.empty() && parameters.encodings[0].active; |
| |
| // 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.ToMediaContentDirection()); |
| remote_audio_description_.set_direction( |
| local_direction.Reversed().ToMediaContentDirection()); |
| |
| if (!voice_channel_->SetLocalContent(&local_audio_description_, |
| cricket::CA_OFFER, nullptr)) { |
| LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR, |
| "Failed to apply local parameters to media channel."); |
| } |
| if (!voice_channel_->SetRemoteContent(&remote_audio_description_, |
| cricket::CA_ANSWER, 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(); |
| } |
| |
| cricket::RtpTransceiverDirection local_direction = |
| cricket::RtpTransceiverDirection::FromMediaContentDirection( |
| local_video_description_.direction()); |
| 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; |
| } |
| } |
| |
| local_direction.recv = |
| !parameters.encodings.empty() && parameters.encodings[0].active; |
| |
| // 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.ToMediaContentDirection()); |
| remote_video_description_.set_direction( |
| local_direction.Reversed().ToMediaContentDirection()); |
| |
| if (!video_channel_->SetLocalContent(&local_video_description_, |
| cricket::CA_OFFER, nullptr)) { |
| LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR, |
| "Failed to apply local parameters to media channel."); |
| } |
| if (!video_channel_->SetRemoteContent(&remote_video_description_, |
| cricket::CA_ANSWER, 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) |
| : signaling_thread_(signaling_thread), |
| worker_thread_(worker_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; |
| |
| call_send_rtp_transport_controller_ = |
| new RtpTransportControllerSend(Clock::GetRealTimeClock(), event_log_); |
| call_.reset(webrtc::Call::Create( |
| call_config, std::unique_ptr<RtpTransportControllerSendInterface>( |
| call_send_rtp_transport_controller_))); |
| } |
| |
| 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."); |
| } |
| RTCError err = MaybeSetCryptos(inner_transport, &local_audio_description_, |
| &remote_audio_description_); |
| if (!err.ok()) { |
| return err; |
| } |
| // 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."); |
| } |
| RTCError err = MaybeSetCryptos(inner_transport, &local_video_description_, |
| &remote_video_description_); |
| if (!err.ok()) { |
| return err; |
| } |
| // 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."); |
| } |
| RTCError err = MaybeSetCryptos(inner_transport, &local_audio_description_, |
| &remote_audio_description_); |
| if (!err.ok()) { |
| return err; |
| } |
| // 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."); |
| } |
| RTCError err = MaybeSetCryptos(inner_transport, &local_video_description_, |
| &remote_video_description_); |
| if (!err.ok()) { |
| return err; |
| } |
| // 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_->GetRtpPacketTransport()->GetInternal(), |
| inner_audio_transport_->GetRtcpPacketTransport() |
| ? inner_audio_transport_->GetRtcpPacketTransport()->GetInternal() |
| : nullptr, |
| signaling_thread_, "audio", false, 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_->GetRtpPacketTransport()->GetInternal(), |
| inner_video_transport_->GetRtcpPacketTransport() |
| ? inner_video_transport_->GetRtcpPacketTransport()->GetInternal() |
| : nullptr, |
| signaling_thread_, "video", false, 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; |
| } |
| |
| RTCError RtpTransportControllerAdapter::MaybeSetCryptos( |
| RtpTransportInterface* rtp_transport, |
| cricket::MediaContentDescription* local_description, |
| cricket::MediaContentDescription* remote_description) { |
| if (rtp_transport->GetInternal()->is_srtp_transport()) { |
| if (!rtp_transport->GetInternal()->send_key() || |
| !rtp_transport->GetInternal()->receive_key()) { |
| LOG_AND_RETURN_ERROR(webrtc::RTCErrorType::UNSUPPORTED_PARAMETER, |
| "The SRTP send key or receive key is not set.") |
| } |
| std::vector<cricket::CryptoParams> cryptos; |
| cryptos.push_back(*(rtp_transport->GetInternal()->receive_key())); |
| local_description->set_cryptos(cryptos); |
| |
| cryptos.clear(); |
| cryptos.push_back(*(rtp_transport->GetInternal()->send_key())); |
| remote_description->set_cryptos(cryptos); |
| } |
| return RTCError::OK(); |
| } |
| |
| } // namespace webrtc |