This CL adds the following interfaces: * RtpTransportController * RtpTransport * RtpSender * RtpReceiver They're implemented on top of the "BaseChannel" object, which is normally used in a PeerConnection, and roughly corresponds to an SDP "m=" section. As a result of this, there are several limitations: * You can only have one of each type of sender and receiver (audio/video) on top of the same transport controller. * The sender/receiver with the same media type must use the same RTP transport. * You can't change the transport after creating the sender or receiver. * Some of the parameters aren't supported. Later, these "adapter" objects will be gradually replaced by real objects that don't have these limitations, as "BaseChannel", "MediaChannel" and related code is restructured. In this CL, we essentially have: ORTC adapter objects -> BaseChannel -> Media engine PeerConnection -> BaseChannel -> Media engine And later we hope to have simply: PeerConnection -> "Real" ORTC objects -> Media engine See the linked bug for more context. BUG=webrtc:7013 TBR=stefan@webrtc.org Review-Url: https://codereview.webrtc.org/2675173003 Cr-Commit-Position: refs/heads/master@{#16842}
900 lines
34 KiB
C++
900 lines
34 KiB
C++
/*
|
|
* 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 <sstream>
|
|
#include <set>
|
|
#include <unordered_map>
|
|
#include <utility> // For std::move.
|
|
|
|
#include "webrtc/api/proxy.h"
|
|
#include "webrtc/base/checks.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"
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
RTCErrorOr<std::unique_ptr<RtpTransportInterface>>
|
|
RtpTransportControllerAdapter::CreateProxiedRtpTransport(
|
|
const RtcpParameters& rtcp_parameters,
|
|
PacketTransportInterface* rtp,
|
|
PacketTransportInterface* rtcp) {
|
|
auto result =
|
|
RtpTransportAdapter::CreateProxied(rtcp_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::SetRtcpParameters(
|
|
const RtcpParameters& parameters,
|
|
RtpTransportInterface* inner_transport) {
|
|
do {
|
|
if (inner_transport == inner_audio_transport_) {
|
|
CopyRtcpParametersToDescriptions(parameters, &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, &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.");
|
|
}
|
|
|
|
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_->GetRtcpParameters().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_->GetRtcpParameters().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_controller_(MediaControllerInterface::Create(config,
|
|
worker_thread,
|
|
channel_manager,
|
|
event_log)) {
|
|
RTC_DCHECK_RUN_ON(signaling_thread_);
|
|
RTC_DCHECK(channel_manager);
|
|
// MediaControllerInterface::Create should never fail.
|
|
RTC_DCHECK(media_controller_);
|
|
// 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);
|
|
}
|
|
|
|
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->GetRtcpParameters(),
|
|
&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->GetRtcpParameters(),
|
|
&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->GetRtcpParameters(),
|
|
&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->GetRtcpParameters(),
|
|
&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_ = media_controller_->channel_manager()->CreateVoiceChannel(
|
|
media_controller_.get(),
|
|
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_ = media_controller_->channel_manager()->CreateVideoChannel(
|
|
media_controller_.get(),
|
|
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_);
|
|
media_controller_->channel_manager()->DestroyVoiceChannel(voice_channel_);
|
|
voice_channel_ = nullptr;
|
|
inner_audio_transport_ = nullptr;
|
|
}
|
|
|
|
void RtpTransportControllerAdapter::DestroyVideoChannel() {
|
|
RTC_DCHECK(video_channel_);
|
|
media_controller_->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
|