diff --git a/tools-webrtc/valgrind/gtest_exclude/ortc_unittests.gtest-memcheck.txt b/tools-webrtc/valgrind/gtest_exclude/ortc_unittests.gtest-memcheck.txt new file mode 100644 index 0000000000..50e032b61c --- /dev/null +++ b/tools-webrtc/valgrind/gtest_exclude/ortc_unittests.gtest-memcheck.txt @@ -0,0 +1,3 @@ +# Tests that are failing when run under memcheck. +# https://code.google.com/p/webrtc/issues/detail?id=4387 +OrtcFactoryIntegrationTest.* diff --git a/webrtc/BUILD.gn b/webrtc/BUILD.gn index 6853f8e466..92237325c4 100644 --- a/webrtc/BUILD.gn +++ b/webrtc/BUILD.gn @@ -258,6 +258,7 @@ if (!build_with_chromium) { "media", "modules", "modules/video_capture:video_capture_internal_impl", + "ortc", "p2p", "pc", "sdk", diff --git a/webrtc/api/BUILD.gn b/webrtc/api/BUILD.gn index 78079b31fa..716f8c9c10 100644 --- a/webrtc/api/BUILD.gn +++ b/webrtc/api/BUILD.gn @@ -56,7 +56,6 @@ rtc_static_library("libjingle_peerconnection_api") { "mediatypes.cc", "mediatypes.h", "notifier.h", - "ortcfactoryinterface.h", "peerconnectionfactoryproxy.h", "peerconnectioninterface.h", "peerconnectionproxy.h", @@ -70,7 +69,6 @@ rtc_static_library("libjingle_peerconnection_api") { "statstypes.cc", "statstypes.h", "streamcollection.h", - "udptransportinterface.h", "umametrics.h", "videosourceproxy.h", "videotracksource.h", @@ -87,6 +85,27 @@ rtc_static_library("libjingle_peerconnection_api") { ] } +rtc_source_set("ortc_api") { + check_includes = false # TODO(deadbeef): Remove (bugs.webrtc.org/6828) + sources = [ + "ortc/ortcfactoryinterface.h", + "ortc/ortcrtpreceiverinterface.h", + "ortc/ortcrtpsenderinterface.h", + "ortc/packettransportinterface.h", + "ortc/rtptransportcontrollerinterface.h", + "ortc/rtptransportinterface.h", + "ortc/udptransportinterface.h", + ] + + # For mediastreaminterface.h, etc. + # TODO(deadbeef): Create a separate target for the common things ORTC and + # PeerConnection code shares, so that ortc_api can depend on that instead of + # libjingle_peerconnection_api. + public_deps = [ + ":libjingle_peerconnection_api", + ] +} + # TODO(ossu): Remove once downstream projects have updated. rtc_source_set("libjingle_peerconnection") { public_deps = [ diff --git a/webrtc/api/mediatypes.cc b/webrtc/api/mediatypes.cc index 97b0189b7a..fbcb53d85b 100644 --- a/webrtc/api/mediatypes.cc +++ b/webrtc/api/mediatypes.cc @@ -9,27 +9,39 @@ */ #include "webrtc/api/mediatypes.h" + +#include "webrtc/api/mediastreaminterface.h" #include "webrtc/base/checks.h" +namespace { +static const char* kMediaTypeData = "data"; +} // namespace + namespace cricket { std::string MediaTypeToString(MediaType type) { - std::string type_str; switch (type) { case MEDIA_TYPE_AUDIO: - type_str = "audio"; - break; + return webrtc::MediaStreamTrackInterface::kAudioKind; case MEDIA_TYPE_VIDEO: - type_str = "video"; - break; + return webrtc::MediaStreamTrackInterface::kVideoKind; case MEDIA_TYPE_DATA: - type_str = "data"; - break; - default: - RTC_NOTREACHED(); - break; + return kMediaTypeData; + } + // Not reachable; avoids compile warning. + FATAL(); +} + +MediaType MediaTypeFromString(const std::string& type_str) { + if (type_str == webrtc::MediaStreamTrackInterface::kAudioKind) { + return MEDIA_TYPE_AUDIO; + } else if (type_str == webrtc::MediaStreamTrackInterface::kVideoKind) { + return MEDIA_TYPE_VIDEO; + } else if (type_str == kMediaTypeData) { + return MEDIA_TYPE_DATA; + } else { + FATAL(); } - return type_str; } } // namespace cricket diff --git a/webrtc/api/mediatypes.h b/webrtc/api/mediatypes.h index 19acf4b94e..ec3a70a5e8 100644 --- a/webrtc/api/mediatypes.h +++ b/webrtc/api/mediatypes.h @@ -22,6 +22,9 @@ enum MediaType { }; std::string MediaTypeToString(MediaType type); +// Aborts on invalid string. Only expected to be used on strings that are +// guaranteed to be valid, such as MediaStreamTrackInterface::kind(). +MediaType MediaTypeFromString(const std::string& type_str); } // namespace cricket diff --git a/webrtc/api/ortc/ortcfactoryinterface.h b/webrtc/api/ortc/ortcfactoryinterface.h new file mode 100644 index 0000000000..855e3b0b58 --- /dev/null +++ b/webrtc/api/ortc/ortcfactoryinterface.h @@ -0,0 +1,230 @@ +/* + * 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. + */ + +#ifndef WEBRTC_API_ORTC_ORTCFACTORYINTERFACE_H_ +#define WEBRTC_API_ORTC_ORTCFACTORYINTERFACE_H_ + +#include +#include +#include // For std::move. + +#include "webrtc/api/mediaconstraintsinterface.h" +#include "webrtc/api/mediastreaminterface.h" +#include "webrtc/api/mediatypes.h" +#include "webrtc/api/ortc/ortcrtpreceiverinterface.h" +#include "webrtc/api/ortc/ortcrtpsenderinterface.h" +#include "webrtc/api/ortc/packettransportinterface.h" +#include "webrtc/api/ortc/rtptransportcontrollerinterface.h" +#include "webrtc/api/ortc/rtptransportinterface.h" +#include "webrtc/api/ortc/udptransportinterface.h" +#include "webrtc/api/rtcerror.h" +#include "webrtc/api/rtpparameters.h" +#include "webrtc/base/network.h" +#include "webrtc/base/scoped_ref_ptr.h" +#include "webrtc/base/thread.h" +#include "webrtc/p2p/base/packetsocketfactory.h" + +namespace webrtc { + +// TODO(deadbeef): This should be part of /api/, but currently it's not and +// including its header violates checkdeps rules. +class AudioDeviceModule; + +// WARNING: This is experimental/under development, so use at your own risk; no +// guarantee about API stability is guaranteed here yet. +// +// This class is the ORTC analog of PeerConnectionFactory. It acts as a factory +// for ORTC objects that can be connected to each other. +// +// Some of these objects may not be represented by the ORTC specification, but +// follow the same general principles. +// +// If one of the factory methods takes another object as an argument, it MUST +// have been created by the same OrtcFactory. +// +// On object lifetimes: objects should be destroyed in this order: +// 1. Objects created by the factory. +// 2. The factory itself. +// 3. Objects passed into OrtcFactoryInterface::Create. +class OrtcFactoryInterface { + public: + // |network_thread| is the thread on which packets are sent and received. + // If null, a new rtc::Thread with a default socket server is created. + // + // |signaling_thread| is used for callbacks to the consumer of the API. If + // null, the current thread will be used, which assumes that the API consumer + // is running a message loop on this thread (either using an existing + // rtc::Thread, or by calling rtc::Thread::Current()->ProcessMessages). + // + // |network_manager| is used to determine which network interfaces are + // available. This is used for ICE, for example. If null, a default + // implementation will be used. Only accessed on |network_thread|. + // + // |socket_factory| is used (on the network thread) for creating sockets. If + // it's null, a default implementation will be used, which assumes + // |network_thread| is a normal rtc::Thread. + // + // |adm| is optional, and allows a different audio device implementation to + // be injected; otherwise a platform-specific module will be used that will + // use the default audio input. + // + // Note that the OrtcFactoryInterface does not take ownership of any of the + // objects passed in, and as previously stated, these objects can't be + // destroyed before the factory is. + static RTCErrorOr> Create( + rtc::Thread* network_thread, + rtc::Thread* signaling_thread, + rtc::NetworkManager* network_manager, + rtc::PacketSocketFactory* socket_factory, + AudioDeviceModule* adm); + + // Constructor for convenience which uses default implementations of + // everything (though does still require that the current thread runs a + // message loop; see above). + static RTCErrorOr> Create() { + return Create(nullptr, nullptr, nullptr, nullptr, nullptr); + } + + virtual ~OrtcFactoryInterface() {} + + // Creates an RTP transport controller, which is used in calls to + // CreateRtpTransport methods. If your application has some notion of a + // "call", you should create one transport controller per call. + // + // However, if you only are using one RtpTransport object, this doesn't need + // to be called explicitly; CreateRtpTransport will create one automatically + // if |rtp_transport_controller| is null. See below. + // + // TODO(deadbeef): Add MediaConfig and RtcEventLog arguments? + virtual RTCErrorOr> + CreateRtpTransportController() = 0; + + // Creates an RTP transport using the provided packet transports and + // transport controller. + // + // |rtp| will be used for sending RTP packets, and |rtcp| for RTCP packets. + // + // |rtp| can't be null. |rtcp| must be non-null if and only if + // |rtcp_parameters.mux| is false, indicating that RTCP muxing isn't used. + // Note that if RTCP muxing isn't enabled initially, it can still enabled + // later through SetRtcpParameters. + // + // If |transport_controller| is null, one will automatically be created, and + // its lifetime managed by the returned RtpTransport. This should only be + // done if a single RtpTransport is being used to communicate with the remote + // endpoint. + virtual RTCErrorOr> CreateRtpTransport( + const RtcpParameters& rtcp_parameters, + PacketTransportInterface* rtp, + PacketTransportInterface* rtcp, + RtpTransportControllerInterface* transport_controller) = 0; + + // Returns the capabilities of an RTP sender of type |kind|. These + // capabilities can be used to determine what RtpParameters to use to create + // an RtpSender. + // + // If for some reason you pass in MEDIA_TYPE_DATA, returns an empty structure. + virtual RtpCapabilities GetRtpSenderCapabilities( + cricket::MediaType kind) const = 0; + + // Creates an RTP sender with |track|. Will not start sending until Send is + // called. This is provided as a convenience; it's equivalent to calling + // CreateRtpSender with a kind (see below), followed by SetTrack. + // + // |track| and |transport| must not be null. + virtual RTCErrorOr> CreateRtpSender( + rtc::scoped_refptr track, + RtpTransportInterface* transport) = 0; + + // Overload of CreateRtpSender allows creating the sender without a track. + // + // |kind| must be MEDIA_TYPE_AUDIO or MEDIA_TYPE_VIDEO. + virtual RTCErrorOr> CreateRtpSender( + cricket::MediaType kind, + RtpTransportInterface* transport) = 0; + + // Returns the capabilities of an RTP receiver of type |kind|. These + // capabilities can be used to determine what RtpParameters to use to create + // an RtpReceiver. + // + // If for some reason you pass in MEDIA_TYPE_DATA, returns an empty structure. + virtual RtpCapabilities GetRtpReceiverCapabilities( + cricket::MediaType kind) const = 0; + + // Creates an RTP receiver of type |kind|. Will not start receiving media + // until Receive is called. + // + // |kind| must be MEDIA_TYPE_AUDIO or MEDIA_TYPE_VIDEO. + // + // |transport| must not be null. + virtual RTCErrorOr> + CreateRtpReceiver(cricket::MediaType kind, + RtpTransportInterface* transport) = 0; + + // Create a UDP transport with IP address family |family|, using a port + // within the specified range. + // + // |family| must be AF_INET or AF_INET6. + // + // |min_port|/|max_port| values of 0 indicate no range restriction. + // + // Returns an error if the transport wasn't successfully created. + virtual RTCErrorOr> + CreateUdpTransport(int family, uint16_t min_port, uint16_t max_port) = 0; + + // Method for convenience that has no port range restrictions. + RTCErrorOr> CreateUdpTransport( + int family) { + return CreateUdpTransport(family, 0, 0); + } + + // NOTE: The methods below to create tracks/sources return scoped_refptrs + // rather than unique_ptrs, because these interfaces are also used with + // PeerConnection, where everything is ref-counted. + + // Creates a audio source representing the default microphone input. + // |options| decides audio processing settings. + virtual rtc::scoped_refptr CreateAudioSource( + const cricket::AudioOptions& options) = 0; + + // Version of the above method that uses default options. + rtc::scoped_refptr CreateAudioSource() { + return CreateAudioSource(cricket::AudioOptions()); + } + + // Creates a video source object wrapping and taking ownership of |capturer|. + // + // |constraints| can be used for selection of resolution and frame rate, and + // may be null if no constraints are desired. + virtual rtc::scoped_refptr CreateVideoSource( + std::unique_ptr capturer, + const MediaConstraintsInterface* constraints) = 0; + + // Version of the above method that omits |constraints|. + rtc::scoped_refptr CreateVideoSource( + std::unique_ptr capturer) { + return CreateVideoSource(std::move(capturer), nullptr); + } + + // Creates a new local video track wrapping |source|. The same |source| can + // be used in several tracks. + virtual rtc::scoped_refptr CreateVideoTrack( + const std::string& id, + VideoTrackSourceInterface* source) = 0; + + // Creates an new local audio track wrapping |source|. + virtual rtc::scoped_refptr CreateAudioTrack( + const std::string& id, + AudioSourceInterface* source) = 0; +}; + +} // namespace webrtc + +#endif // WEBRTC_API_ORTC_ORTCFACTORYINTERFACE_H_ diff --git a/webrtc/api/ortc/ortcrtpreceiverinterface.h b/webrtc/api/ortc/ortcrtpreceiverinterface.h new file mode 100644 index 0000000000..1fad29c581 --- /dev/null +++ b/webrtc/api/ortc/ortcrtpreceiverinterface.h @@ -0,0 +1,84 @@ +/* + * 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. + */ + +// This file contains interfaces for RtpReceivers: +// http://publications.ortc.org/2016/20161202/#rtcrtpreceiver* +// +// However, underneath the RtpReceiver is an RtpTransport, rather than a +// DtlsTransport. This is to allow different types of RTP transports (besides +// DTLS-SRTP) to be used. + +#ifndef WEBRTC_API_ORTC_ORTCRTPRECEIVERINTERFACE_H_ +#define WEBRTC_API_ORTC_ORTCRTPRECEIVERINTERFACE_H_ + +#include "webrtc/api/mediastreaminterface.h" +#include "webrtc/api/mediatypes.h" +#include "webrtc/api/ortc/rtptransportinterface.h" +#include "webrtc/api/rtcerror.h" +#include "webrtc/api/rtpparameters.h" + +namespace webrtc { + +// Note: Since receiver capabilities may depend on how the OrtcFactory was +// created, instead of a static "GetCapabilities" method on this interface, +// there is a "GetRtpReceiverCapabilities" method on the OrtcFactory. +class OrtcRtpReceiverInterface { + public: + virtual ~OrtcRtpReceiverInterface() {} + + // Returns a track representing the media received by this receiver. + // + // Currently, this will return null until Receive has been successfully + // called. Also, a new track will be created every time the primary SSRC + // changes. + // + // If encodings are removed, GetTrack will return null. Though deactivating + // an encoding (setting |active| to false) will not do this. + // + // In the future, these limitations will be fixed, and GetTrack will return + // the same track for the lifetime of the RtpReceiver. So it's not + // recommended to write code that depends on this non-standard behavior. + virtual rtc::scoped_refptr GetTrack() const = 0; + + // Once supported, will switch to receiving media on a new transport. + // However, this is not currently supported and will always return an error. + virtual RTCError SetTransport(RtpTransportInterface* transport) = 0; + // Returns previously set (or constructed-with) transport. + virtual RtpTransportInterface* GetTransport() const = 0; + + // Start receiving media with |parameters| (if |parameters| contains an + // active encoding). + // + // There are no limitations to how the parameters can be changed after the + // initial call to Receive, as long as they're valid (for example, they can't + // use the same payload type for two codecs). + virtual RTCError Receive(const RtpParameters& parameters) = 0; + // Returns parameters that were last successfully passed into Receive, or + // empty parameters if that hasn't yet occurred. + // + // Note that for parameters that are described as having an "implementation + // default" value chosen, GetParameters() will return those chosen defaults, + // with the exception of SSRCs which have special behavior. See + // rtpparameters.h for more details. + virtual RtpParameters GetParameters() const = 0; + + // Audio or video receiver? + // + // Once GetTrack() starts always returning a track, this method will be + // redundant, as one can call "GetTrack()->kind()". However, it's still a + // nice convenience, and is symmetric with OrtcRtpSenderInterface::GetKind. + virtual cricket::MediaType GetKind() const = 0; + + // TODO(deadbeef): GetContributingSources +}; + +} // namespace webrtc + +#endif // WEBRTC_API_ORTC_ORTCRTPRECEIVERINTERFACE_H_ diff --git a/webrtc/api/ortc/ortcrtpsenderinterface.h b/webrtc/api/ortc/ortcrtpsenderinterface.h new file mode 100644 index 0000000000..e369b539e4 --- /dev/null +++ b/webrtc/api/ortc/ortcrtpsenderinterface.h @@ -0,0 +1,77 @@ +/* + * 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. + */ + +// This file contains interfaces for RtpSenders: +// http://publications.ortc.org/2016/20161202/#rtcrtpsender* +// +// However, underneath the RtpSender is an RtpTransport, rather than a +// DtlsTransport. This is to allow different types of RTP transports (besides +// DTLS-SRTP) to be used. + +#ifndef WEBRTC_API_ORTC_ORTCRTPSENDERINTERFACE_H_ +#define WEBRTC_API_ORTC_ORTCRTPSENDERINTERFACE_H_ + +#include "webrtc/api/mediatypes.h" +#include "webrtc/api/mediastreaminterface.h" +#include "webrtc/api/ortc/rtptransportinterface.h" +#include "webrtc/api/rtcerror.h" +#include "webrtc/api/rtpparameters.h" + +namespace webrtc { + +// Note: Since sender capabilities may depend on how the OrtcFactory was +// created, instead of a static "GetCapabilities" method on this interface, +// there is a "GetRtpSenderCapabilities" method on the OrtcFactory. +class OrtcRtpSenderInterface { + public: + virtual ~OrtcRtpSenderInterface() {} + + // Sets the source of media that will be sent by this sender. + // + // If Send has already been called, will immediately switch to sending this + // track. If |track| is null, will stop sending media. + // + // Returns INVALID_PARAMETER error if an audio track is set on a video + // RtpSender, or vice-versa. + virtual RTCError SetTrack(MediaStreamTrackInterface* track) = 0; + // Returns previously set (or constructed-with) track. + virtual rtc::scoped_refptr GetTrack() const = 0; + + // Once supported, will switch to sending media on a new transport. However, + // this is not currently supported and will always return an error. + virtual RTCError SetTransport(RtpTransportInterface* transport) = 0; + // Returns previously set (or constructed-with) transport. + virtual RtpTransportInterface* GetTransport() const = 0; + + // Start sending media with |parameters| (if |parameters| contains an active + // encoding). + // + // There are no limitations to how the parameters can be changed after the + // initial call to Send, as long as they're valid (for example, they can't + // use the same payload type for two codecs). + virtual RTCError Send(const RtpParameters& parameters) = 0; + // Returns parameters that were last successfully passed into Send, or empty + // parameters if that hasn't yet occurred. + // + // Note that for parameters that are described as having an "implementation + // default" value chosen, GetParameters() will return those chosen defaults, + // with the exception of SSRCs which have special behavior. See + // rtpparameters.h for more details. + virtual RtpParameters GetParameters() const = 0; + + // Audio or video sender? + virtual cricket::MediaType GetKind() const = 0; + + // TODO(deadbeef): SSRC conflict signal. +}; + +} // namespace webrtc + +#endif // WEBRTC_API_ORTC_ORTCRTPSENDERINTERFACE_H_ diff --git a/webrtc/api/ortc/packettransportinterface.h b/webrtc/api/ortc/packettransportinterface.h new file mode 100644 index 0000000000..2677ce62ca --- /dev/null +++ b/webrtc/api/ortc/packettransportinterface.h @@ -0,0 +1,38 @@ +/* + * 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. + */ + +#ifndef WEBRTC_API_ORTC_PACKETTRANSPORTINTERFACE_H_ +#define WEBRTC_API_ORTC_PACKETTRANSPORTINTERFACE_H_ + +namespace rtc { + +class PacketTransportInternal; + +} // namespace rtc + +namespace webrtc { + +// Base class for different packet-based transports. +class PacketTransportInterface { + public: + virtual ~PacketTransportInterface() {} + + protected: + // Only for internal use. Returns a pointer to an internal interface, for use + // by the implementation. + virtual rtc::PacketTransportInternal* GetInternal() = 0; + + // Classes that can use this internal interface. + friend class RtpTransportControllerAdapter; +}; + +} // namespace webrtc + +#endif // WEBRTC_API_ORTC_PACKETTRANSPORTINTERFACE_H_ diff --git a/webrtc/api/ortc/rtptransportcontrollerinterface.h b/webrtc/api/ortc/rtptransportcontrollerinterface.h new file mode 100644 index 0000000000..d1d0e448b7 --- /dev/null +++ b/webrtc/api/ortc/rtptransportcontrollerinterface.h @@ -0,0 +1,57 @@ +/* + * 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. + */ + +#ifndef WEBRTC_API_ORTC_RTPTRANSPORTCONTROLLERINTERFACE_H_ +#define WEBRTC_API_ORTC_RTPTRANSPORTCONTROLLERINTERFACE_H_ + +#include + +#include "webrtc/api/ortc/rtptransportinterface.h" + +namespace webrtc { + +class RtpTransportControllerAdapter; + +// Used to group RTP transports between a local endpoint and the same remote +// endpoint, for the purpose of sharing bandwidth estimation and other things. +// +// Comparing this to the PeerConnection model, non-budled audio/video would use +// two RtpTransports with a single RtpTransportController, whereas bundled +// media would use a single RtpTransport, and two PeerConnections would use +// independent RtpTransportControllers. +// +// RtpTransports are associated with this controller when they're created, by +// passing the controller into OrtcFactory's relevant "CreateRtpTransport" +// method. When a transport is destroyed, it's automatically disassociated. +// GetTransports returns all currently associated transports. +// +// This is the RTP equivalent of "IceTransportController" in ORTC; RtpTransport +// is to RtpTransportController as IceTransport is to IceTransportController. +class RtpTransportControllerInterface { + public: + virtual ~RtpTransportControllerInterface() {} + + // Returns all transports associated with this controller (see explanation + // above). No ordering is guaranteed. + virtual std::vector GetTransports() const = 0; + + protected: + // Only for internal use. Returns a pointer to an internal interface, for use + // by the implementation. + virtual RtpTransportControllerAdapter* GetInternal() = 0; + + // Classes that can use this internal interface. + friend class OrtcFactory; + friend class RtpTransportAdapter; +}; + +} // namespace webrtc + +#endif // WEBRTC_API_ORTC_RTPTRANSPORTCONTROLLERINTERFACE_H_ diff --git a/webrtc/api/ortc/rtptransportinterface.h b/webrtc/api/ortc/rtptransportinterface.h new file mode 100644 index 0000000000..942dc506f0 --- /dev/null +++ b/webrtc/api/ortc/rtptransportinterface.h @@ -0,0 +1,103 @@ +/* + * 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. + */ + +#ifndef WEBRTC_API_ORTC_RTPTRANSPORTINTERFACE_H_ +#define WEBRTC_API_ORTC_RTPTRANSPORTINTERFACE_H_ + +#include + +#include "webrtc/api/ortc/packettransportinterface.h" +#include "webrtc/api/rtcerror.h" +#include "webrtc/base/optional.h" + +namespace webrtc { + +class RtpTransportAdapter; + +struct RtcpParameters { + // The SSRC to be used in the "SSRC of packet sender" field. If not set, one + // will be chosen by the implementation. + // TODO(deadbeef): Not implemented. + rtc::Optional ssrc; + + // The Canonical Name (CNAME) used by RTCP (e.g. in SDES messages). + // + // If empty in the construction of the RtpTransport, one will be generated by + // the implementation, and returned in GetRtcpParameters. Multiple + // RtpTransports created by the same OrtcFactory will use the same generated + // CNAME. + // + // If empty when passed into SetRtcpParameters, the CNAME simply won't be + // modified. + std::string cname; + + // Send reduced-size RTCP? + bool reduced_size = false; + + // Send RTCP multiplexed on the RTP transport? + bool mux = true; + + bool operator==(const RtcpParameters& o) const { + return ssrc == o.ssrc && cname == o.cname && + reduced_size == o.reduced_size && mux == o.mux; + } + bool operator!=(const RtcpParameters& o) const { return !(*this == o); } +}; + +// Base class for different types of RTP transports that can be created by an +// OrtcFactory. Used by RtpSenders/RtpReceivers. +// +// This is not present in the standard ORTC API, but exists here for a few +// reasons. Firstly, it allows different types of RTP transports to be used: +// DTLS-SRTP (which is required for the web), but also SDES-SRTP and +// unencrypted RTP. It also simplifies the handling of RTCP muxing, and +// provides a better API point for it. +// +// Note that Edge's implementation of ORTC provides a similar API point, called +// RTCSrtpSdesTransport: +// https://msdn.microsoft.com/en-us/library/mt502527(v=vs.85).aspx +class RtpTransportInterface { + public: + virtual ~RtpTransportInterface() {} + + // Returns packet transport that's used to send RTP packets. + virtual PacketTransportInterface* GetRtpPacketTransport() const = 0; + + // Returns separate packet transport that's used to send RTCP packets. If + // RTCP multiplexing is being used, returns null. + virtual PacketTransportInterface* GetRtcpPacketTransport() const = 0; + + // Set/get RTCP params. Can be used to enable RTCP muxing or reduced-size + // RTCP if initially not enabled. + // + // Changing |mux| from "true" to "false" is not allowed, and changing the + // CNAME is currently unsupported. + virtual RTCError SetRtcpParameters(const RtcpParameters& parameters) = 0; + // Returns last set or constructed-with parameters. If |cname| was empty in + // construction, the generated CNAME will be present in the returned + // parameters (see above). + virtual RtcpParameters GetRtcpParameters() const = 0; + + protected: + // Only for internal use. Returns a pointer to an internal interface, for use + // by the implementation. + virtual RtpTransportAdapter* GetInternal() = 0; + + // Classes that can use this internal interface. + friend class OrtcFactory; + friend class OrtcRtpSenderAdapter; + friend class OrtcRtpReceiverAdapter; + friend class RtpTransportControllerAdapter; + friend class RtpTransportAdapter; +}; + +} // namespace webrtc + +#endif // WEBRTC_API_ORTC_RTPTRANSPORTINTERFACE_H_ diff --git a/webrtc/api/udptransportinterface.h b/webrtc/api/ortc/udptransportinterface.h similarity index 71% rename from webrtc/api/udptransportinterface.h rename to webrtc/api/ortc/udptransportinterface.h index 30858e6830..278107671b 100644 --- a/webrtc/api/udptransportinterface.h +++ b/webrtc/api/ortc/udptransportinterface.h @@ -8,9 +8,10 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_API_UDPTRANSPORTINTERFACE_H_ -#define WEBRTC_API_UDPTRANSPORTINTERFACE_H_ +#ifndef WEBRTC_API_ORTC_UDPTRANSPORTINTERFACE_H_ +#define WEBRTC_API_ORTC_UDPTRANSPORTINTERFACE_H_ +#include "webrtc/api/ortc/packettransportinterface.h" #include "webrtc/api/proxy.h" #include "webrtc/base/socketaddress.h" @@ -26,10 +27,8 @@ namespace webrtc { // // Calling SetRemoteAddress sets the destination of outgoing packets; without a // destination, packets can't be sent, but they can be received. -class UdpTransportInterface { +class UdpTransportInterface : virtual public PacketTransportInterface { public: - virtual ~UdpTransportInterface() {} - // Get the address of the socket allocated for this transport. virtual rtc::SocketAddress GetLocalAddress() const = 0; @@ -45,15 +44,6 @@ class UdpTransportInterface { virtual rtc::SocketAddress GetRemoteAddress() const = 0; }; -// TODO(deadbeef): Move this to .cc file and out of api/. What threads methods -// are called on is an implementation detail. -BEGIN_OWNED_PROXY_MAP(UdpTransport) - PROXY_WORKER_THREAD_DESTRUCTOR() - PROXY_WORKER_CONSTMETHOD0(rtc::SocketAddress, GetLocalAddress) - PROXY_WORKER_METHOD1(bool, SetRemoteAddress, const rtc::SocketAddress&) - PROXY_WORKER_CONSTMETHOD0(rtc::SocketAddress, GetRemoteAddress) -END_PROXY_MAP() - } // namespace webrtc -#endif // WEBRTC_API_UDPTRANSPORTINTERFACE_H_ +#endif // WEBRTC_API_ORTC_UDPTRANSPORTINTERFACE_H_ diff --git a/webrtc/api/ortcfactoryinterface.h b/webrtc/api/ortcfactoryinterface.h deleted file mode 100644 index 8d46d6865e..0000000000 --- a/webrtc/api/ortcfactoryinterface.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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. - */ - -#ifndef WEBRTC_API_ORTCFACTORYINTERFACE_H_ -#define WEBRTC_API_ORTCFACTORYINTERFACE_H_ - -#include - -#include "webrtc/api/udptransportinterface.h" -#include "webrtc/base/network.h" -#include "webrtc/base/thread.h" -#include "webrtc/p2p/base/packetsocketfactory.h" - -namespace webrtc { - -// WARNING: This is experimental/under development, so use at your own risk; no -// guarantee about API stability is guaranteed here yet. -// -// This class is the ORTC analog of PeerConnectionFactory. It acts as a factory -// for ORTC objects that can be connected to each other. -// -// Some of these objects may not be represented by the ORTC specification, but -// follow the same general principles. -// -// On object lifetimes: The factory must not be destroyed before destroying the -// objects it created, and the objects passed into the factory must not be -// destroyed before destroying the factory. -class OrtcFactoryInterface { - public: - // |network_thread| is the thread on which packets are sent and received. - // If null, a new rtc::Thread with a default socket server is created. - // - // |signaling_thread| is used for callbacks to the consumer of the API. If - // null, the current thread will be used, which assumes that the API consumer - // is running a message loop on this thread (either using an existing - // rtc::Thread, or by calling rtc::Thread::Current()->ProcessMessages). - // - // |network_manager| is used to determine which network interfaces are - // available. This is used for ICE, for example. If null, a default - // implementation will be used. Only accessed on |network_thread|. - // - // |socket_factory| is used (on the network thread) for creating sockets. If - // it's null, a default implementation will be used, which assumes - // |network_thread| is a normal rtc::Thread. - // - // Note that the OrtcFactoryInterface does not take ownership of any of the - // objects - // passed in, and as previously stated, these objects can't be destroyed - // before the factory is. - static std::unique_ptr Create( - rtc::Thread* network_thread, - rtc::Thread* signaling_thread, - rtc::NetworkManager* network_manager, - rtc::PacketSocketFactory* socket_factory); - // Constructor for convenience which uses default implementations of - // everything (though does still require that the current thread runs a - // message loop; see above). - static std::unique_ptr Create() { - return Create(nullptr, nullptr, nullptr, nullptr); - } - - virtual ~OrtcFactoryInterface() {} - - virtual std::unique_ptr - CreateUdpTransport(int family, uint16_t min_port, uint16_t max_port) = 0; - // Method for convenience that has no port range restrictions. - std::unique_ptr CreateUdpTransport(int family) { - return CreateUdpTransport(family, 0, 0); - } -}; - -} // namespace webrtc - -#endif // WEBRTC_API_ORTCFACTORYINTERFACE_H_ diff --git a/webrtc/api/peerconnectioninterface.h b/webrtc/api/peerconnectioninterface.h index a2e5e1bc73..69bae293bf 100644 --- a/webrtc/api/peerconnectioninterface.h +++ b/webrtc/api/peerconnectioninterface.h @@ -68,7 +68,6 @@ #define WEBRTC_API_PEERCONNECTIONINTERFACE_H_ #include -#include #include #include #include @@ -901,7 +900,7 @@ class PeerConnectionFactoryInterface : public rtc::RefCountInterface { virtual rtc::scoped_refptr CreateLocalMediaStream(const std::string& label) = 0; - // Creates a AudioSourceInterface. + // Creates an AudioSourceInterface. // |options| decides audio processing settings. virtual rtc::scoped_refptr CreateAudioSource( const cricket::AudioOptions& options) = 0; diff --git a/webrtc/api/proxy.h b/webrtc/api/proxy.h index 5634cfe9f8..46c424d927 100644 --- a/webrtc/api/proxy.h +++ b/webrtc/api/proxy.h @@ -353,18 +353,18 @@ class MethodCall5 : public rtc::Message, // Helper macros to reduce code duplication. -#define PROXY_MAP_BOILERPLATE(c) \ - template \ - class c##ProxyWithInternal; \ - typedef c##ProxyWithInternal c##Proxy; \ - template \ - class c##ProxyWithInternal : public c##Interface { \ - protected: \ - typedef c##Interface C; \ - \ - public: \ - const INTERNAL_CLASS* internal() const { return c_.get(); } \ - INTERNAL_CLASS* internal() { return c_.get(); } +#define PROXY_MAP_BOILERPLATE(c) \ + template \ + class c##ProxyWithInternal; \ + typedef c##ProxyWithInternal c##Proxy; \ + template \ + class c##ProxyWithInternal : public c##Interface { \ + protected: \ + typedef c##Interface C; \ + \ + public: \ + const INTERNAL_CLASS* internal() const { return c_; } \ + INTERNAL_CLASS* internal() { return c_; } #define END_PROXY_MAP() \ }; @@ -403,6 +403,11 @@ class MethodCall5 : public rtc::Message, void DestroyInternal() { c_ = nullptr; } \ rtc::scoped_refptr c_; +// Note: This doesn't use a unique_ptr, because it intends to handle a corner +// case where an object's deletion triggers a callback that calls back into +// this proxy object. If relying on a unique_ptr to delete the object, its +// inner pointer would be set to null before this reentrant callback would have +// a chance to run, resulting in a segfault. #define OWNED_PROXY_MAP_BOILERPLATE(c) \ public: \ ~c##ProxyWithInternal() { \ @@ -412,8 +417,8 @@ class MethodCall5 : public rtc::Message, } \ \ private: \ - void DestroyInternal() { c_.reset(nullptr); } \ - std::unique_ptr c_; + void DestroyInternal() { delete c_; } \ + INTERNAL_CLASS* c_; #define BEGIN_SIGNALING_PROXY_MAP(c) \ PROXY_MAP_BOILERPLATE(c) \ @@ -438,16 +443,16 @@ class MethodCall5 : public rtc::Message, worker_thread, c); \ } -#define BEGIN_OWNED_PROXY_MAP(c) \ - PROXY_MAP_BOILERPLATE(c) \ - WORKER_PROXY_MAP_BOILERPLATE(c) \ - OWNED_PROXY_MAP_BOILERPLATE(c) \ - public: \ - static std::unique_ptr Create( \ - rtc::Thread* signaling_thread, rtc::Thread* worker_thread, \ - INTERNAL_CLASS* c) { \ - return std::unique_ptr( \ - new c##ProxyWithInternal(signaling_thread, worker_thread, c)); \ +#define BEGIN_OWNED_PROXY_MAP(c) \ + PROXY_MAP_BOILERPLATE(c) \ + WORKER_PROXY_MAP_BOILERPLATE(c) \ + OWNED_PROXY_MAP_BOILERPLATE(c) \ + public: \ + static std::unique_ptr Create( \ + rtc::Thread* signaling_thread, rtc::Thread* worker_thread, \ + std::unique_ptr c) { \ + return std::unique_ptr(new c##ProxyWithInternal( \ + signaling_thread, worker_thread, c.release())); \ } #define PROXY_SIGNALING_THREAD_DESTRUCTOR() \ @@ -464,95 +469,109 @@ class MethodCall5 : public rtc::Message, #define PROXY_METHOD0(r, method) \ r method() override { \ - MethodCall0 call(c_.get(), &C::method); \ + MethodCall0 call(c_, &C::method); \ return call.Marshal(RTC_FROM_HERE, signaling_thread_); \ } #define PROXY_CONSTMETHOD0(r, method) \ r method() const override { \ - ConstMethodCall0 call(c_.get(), &C::method); \ + ConstMethodCall0 call(c_, &C::method); \ return call.Marshal(RTC_FROM_HERE, signaling_thread_); \ } -#define PROXY_METHOD1(r, method, t1) \ - r method(t1 a1) override { \ - MethodCall1 call(c_.get(), &C::method, std::move(a1)); \ - return call.Marshal(RTC_FROM_HERE, signaling_thread_); \ +#define PROXY_METHOD1(r, method, t1) \ + r method(t1 a1) override { \ + MethodCall1 call(c_, &C::method, std::move(a1)); \ + return call.Marshal(RTC_FROM_HERE, signaling_thread_); \ } -#define PROXY_CONSTMETHOD1(r, method, t1) \ - r method(t1 a1) const override { \ - ConstMethodCall1 call(c_.get(), &C::method, std::move(a1)); \ - return call.Marshal(RTC_FROM_HERE, signaling_thread_); \ +#define PROXY_CONSTMETHOD1(r, method, t1) \ + r method(t1 a1) const override { \ + ConstMethodCall1 call(c_, &C::method, std::move(a1)); \ + return call.Marshal(RTC_FROM_HERE, signaling_thread_); \ } -#define PROXY_METHOD2(r, method, t1, t2) \ - r method(t1 a1, t2 a2) override { \ - MethodCall2 call(c_.get(), &C::method, std::move(a1), \ - std::move(a2)); \ - return call.Marshal(RTC_FROM_HERE, signaling_thread_); \ +#define PROXY_METHOD2(r, method, t1, t2) \ + r method(t1 a1, t2 a2) override { \ + MethodCall2 call(c_, &C::method, std::move(a1), \ + std::move(a2)); \ + return call.Marshal(RTC_FROM_HERE, signaling_thread_); \ } -#define PROXY_METHOD3(r, method, t1, t2, t3) \ - r method(t1 a1, t2 a2, t3 a3) override { \ - MethodCall3 call(c_.get(), &C::method, std::move(a1), \ - std::move(a2), std::move(a3)); \ - return call.Marshal(RTC_FROM_HERE, signaling_thread_); \ +#define PROXY_METHOD3(r, method, t1, t2, t3) \ + r method(t1 a1, t2 a2, t3 a3) override { \ + MethodCall3 call(c_, &C::method, std::move(a1), \ + std::move(a2), std::move(a3)); \ + return call.Marshal(RTC_FROM_HERE, signaling_thread_); \ } #define PROXY_METHOD4(r, method, t1, t2, t3, t4) \ r method(t1 a1, t2 a2, t3 a3, t4 a4) override { \ - MethodCall4 call(c_.get(), &C::method, \ - std::move(a1), std::move(a2), \ - std::move(a3), std::move(a4)); \ + MethodCall4 call(c_, &C::method, std::move(a1), \ + std::move(a2), std::move(a3), \ + std::move(a4)); \ return call.Marshal(RTC_FROM_HERE, signaling_thread_); \ } -#define PROXY_METHOD5(r, method, t1, t2, t3, t4, t5) \ - r method(t1 a1, t2 a2, t3 a3, t4 a4, t5 a5) override { \ - MethodCall5 call( \ - c_.get(), &C::method, std::move(a1), std::move(a2), std::move(a3), \ - std::move(a4), std::move(a5)); \ - return call.Marshal(RTC_FROM_HERE, signaling_thread_); \ +#define PROXY_METHOD5(r, method, t1, t2, t3, t4, t5) \ + r method(t1 a1, t2 a2, t3 a3, t4 a4, t5 a5) override { \ + MethodCall5 call(c_, &C::method, std::move(a1), \ + std::move(a2), std::move(a3), \ + std::move(a4), std::move(a5)); \ + return call.Marshal(RTC_FROM_HERE, signaling_thread_); \ } // Define methods which should be invoked on the worker thread. #define PROXY_WORKER_METHOD0(r, method) \ r method() override { \ - MethodCall0 call(c_.get(), &C::method); \ + MethodCall0 call(c_, &C::method); \ return call.Marshal(RTC_FROM_HERE, worker_thread_); \ } #define PROXY_WORKER_CONSTMETHOD0(r, method) \ r method() const override { \ - ConstMethodCall0 call(c_.get(), &C::method); \ + ConstMethodCall0 call(c_, &C::method); \ return call.Marshal(RTC_FROM_HERE, worker_thread_); \ } -#define PROXY_WORKER_METHOD1(r, method, t1) \ - r method(t1 a1) override { \ - MethodCall1 call(c_.get(), &C::method, std::move(a1)); \ - return call.Marshal(RTC_FROM_HERE, worker_thread_); \ +#define PROXY_WORKER_METHOD1(r, method, t1) \ + r method(t1 a1) override { \ + MethodCall1 call(c_, &C::method, std::move(a1)); \ + return call.Marshal(RTC_FROM_HERE, worker_thread_); \ } -#define PROXY_WORKER_CONSTMETHOD1(r, method, t1) \ - r method(t1 a1) const override { \ - ConstMethodCall1 call(c_.get(), &C::method, std::move(a1)); \ - return call.Marshal(RTC_FROM_HERE, worker_thread_); \ +#define PROXY_WORKER_CONSTMETHOD1(r, method, t1) \ + r method(t1 a1) const override { \ + ConstMethodCall1 call(c_, &C::method, std::move(a1)); \ + return call.Marshal(RTC_FROM_HERE, worker_thread_); \ } -#define PROXY_WORKER_METHOD2(r, method, t1, t2) \ - r method(t1 a1, t2 a2) override { \ - MethodCall2 call(c_.get(), &C::method, std::move(a1), \ - std::move(a2)); \ - return call.Marshal(RTC_FROM_HERE, worker_thread_); \ +#define PROXY_WORKER_METHOD2(r, method, t1, t2) \ + r method(t1 a1, t2 a2) override { \ + MethodCall2 call(c_, &C::method, std::move(a1), \ + std::move(a2)); \ + return call.Marshal(RTC_FROM_HERE, worker_thread_); \ } -#define PROXY_WORKER_CONSTMETHOD2(r, method, t1, t2) \ - r method(t1 a1, t2 a2) const override { \ - ConstMethodCall2 call(c_.get(), &C::method, std::move(a1), \ - std::move(a2)); \ - return call.Marshal(RTC_FROM_HERE, worker_thread_); \ +#define PROXY_WORKER_CONSTMETHOD2(r, method, t1, t2) \ + r method(t1 a1, t2 a2) const override { \ + ConstMethodCall2 call(c_, &C::method, std::move(a1), \ + std::move(a2)); \ + return call.Marshal(RTC_FROM_HERE, worker_thread_); \ + } + +#define PROXY_WORKER_METHOD3(r, method, t1, t2, t3) \ + r method(t1 a1, t2 a2, t3 a3) override { \ + MethodCall3 call(c_, &C::method, std::move(a1), \ + std::move(a2), std::move(a3)); \ + return call.Marshal(RTC_FROM_HERE, worker_thread_); \ + } + +#define PROXY_WORKER_CONSTMETHOD3(r, method, t1, t2) \ + r method(t1 a1, t2 a2, t3 a3) const override { \ + ConstMethodCall3 call(c_, &C::method, std::move(a1), \ + std::move(a2), std::move(a3)); \ + return call.Marshal(RTC_FROM_HERE, worker_thread_); \ } } // namespace webrtc diff --git a/webrtc/api/rtcerror.h b/webrtc/api/rtcerror.h index 1c130c0d6e..2ba78371eb 100644 --- a/webrtc/api/rtcerror.h +++ b/webrtc/api/rtcerror.h @@ -224,7 +224,7 @@ class RTCErrorOr { // NOTE: Not explicit - we want to use RTCErrorOr as a return type // so it is convenient and sensible to be able to do 'return T()' // when the return type is RTCErrorOr. - RTCErrorOr(T value) : value_(std::move(value)) {} + RTCErrorOr(T&& value) : value_(std::move(value)) {} // Delete the copy constructor and assignment operator; there aren't any use // cases where you should need to copy an RTCErrorOr, as opposed to moving diff --git a/webrtc/api/rtpparameters.h b/webrtc/api/rtpparameters.h index f506c4031c..e4fe47b845 100644 --- a/webrtc/api/rtpparameters.h +++ b/webrtc/api/rtpparameters.h @@ -16,6 +16,7 @@ #include #include "webrtc/api/mediatypes.h" +#include "webrtc/config.h" #include "webrtc/base/optional.h" namespace webrtc { @@ -47,14 +48,13 @@ enum class FecMechanism { // Used in RtcpFeedback struct. enum class RtcpFeedbackType { - ACK, CCM, NACK, REMB, // "goog-remb" TRANSPORT_CC, }; -// Used in RtcpFeedback struct when type is ACK, NACK or CCM. +// Used in RtcpFeedback struct when type is NACK or CCM. enum class RtcpFeedbackMessageType { // Equivalent to {type: "nack", parameter: undefined} in ORTC. GENERIC_NACK, @@ -76,7 +76,7 @@ enum class DegradationPreference { enum class PriorityType { VERY_LOW, LOW, MEDIUM, HIGH }; struct RtcpFeedback { - RtcpFeedbackType type = RtcpFeedbackType::ACK; + RtcpFeedbackType type = RtcpFeedbackType::CCM; // Equivalent to ORTC "parameter" field with slight differences: // 1. It's an enum instead of a string. @@ -84,6 +84,12 @@ struct RtcpFeedback { // rather than an unset "parameter" value. rtc::Optional message_type; + // Constructors for convenience. + RtcpFeedback() {} + explicit RtcpFeedback(RtcpFeedbackType type) : type(type) {} + RtcpFeedback(RtcpFeedbackType type, RtcpFeedbackMessageType message_type) + : type(type), message_type(message_type) {} + bool operator==(const RtcpFeedback& o) const { return type == o.type && message_type == o.message_type; } @@ -126,7 +132,12 @@ struct RtpCodecCapability { std::vector rtcp_feedback; // Codec-specific parameters that must be signaled to the remote party. + // // Corresponds to "a=fmtp" parameters in SDP. + // + // Contrary to ORTC, these parameters are named using all lowercase strings. + // This helps make the mapping to SDP simpler, if an application is using + // SDP. Boolean values are represented by the string "1". std::unordered_map parameters; // Codec-specific parameters that may optionally be signaled to the remote @@ -184,6 +195,12 @@ struct RtpHeaderExtensionCapability { // TODO(deadbeef): Not implemented. bool preferred_encrypt = false; + // Constructors for convenience. + RtpHeaderExtensionCapability() = default; + explicit RtpHeaderExtensionCapability(const std::string& uri) : uri(uri) {} + RtpHeaderExtensionCapability(const std::string& uri, int preferred_id) + : uri(uri), preferred_id(preferred_id) {} + bool operator==(const RtpHeaderExtensionCapability& o) const { return uri == o.uri && preferred_id == o.preferred_id && preferred_encrypt == o.preferred_encrypt; @@ -193,33 +210,23 @@ struct RtpHeaderExtensionCapability { } }; -// Used in RtpParameters; represents a specific configuration of a header -// extension. -struct RtpHeaderExtensionParameters { - // URI of this extension, as defined in RFC5285. - std::string uri; - - // ID value that goes in the packet. - int id = 0; - - // If true, the value in the header is encrypted. - // TODO(deadbeef): Not implemented. - bool encrypt = false; - - bool operator==(const RtpHeaderExtensionParameters& o) const { - return uri == o.uri && id == o.id && encrypt == o.encrypt; - } - bool operator!=(const RtpHeaderExtensionParameters& o) const { - return !(*this == o); - } -}; +// See webrtc/config.h. Has "uri" and "id" fields. +// TODO(deadbeef): This is missing the "encrypt" flag, which is unimplemented. +typedef RtpExtension RtpHeaderExtensionParameters; struct RtpFecParameters { // If unset, a value is chosen by the implementation. + // Works just like RtpEncodingParameters::ssrc. rtc::Optional ssrc; FecMechanism mechanism = FecMechanism::RED; + // Constructors for convenience. + RtpFecParameters() = default; + explicit RtpFecParameters(FecMechanism mechanism) : mechanism(mechanism) {} + RtpFecParameters(FecMechanism mechanism, uint32_t ssrc) + : ssrc(ssrc), mechanism(mechanism) {} + bool operator==(const RtpFecParameters& o) const { return ssrc == o.ssrc && mechanism == o.mechanism; } @@ -228,33 +235,48 @@ struct RtpFecParameters { struct RtpRtxParameters { // If unset, a value is chosen by the implementation. + // Works just like RtpEncodingParameters::ssrc. rtc::Optional ssrc; + // Constructors for convenience. + RtpRtxParameters() = default; + explicit RtpRtxParameters(uint32_t ssrc) : ssrc(ssrc) {} + bool operator==(const RtpRtxParameters& o) const { return ssrc == o.ssrc; } bool operator!=(const RtpRtxParameters& o) const { return !(*this == o); } }; struct RtpEncodingParameters { // If unset, a value is chosen by the implementation. + // + // Note that the chosen value is NOT returned by GetParameters, because it + // may change due to an SSRC conflict, in which case the conflict is handled + // internally without any event. Another way of looking at this is that an + // unset SSRC acts as a "wildcard" SSRC. rtc::Optional ssrc; // Can be used to reference a codec in the |codecs| member of the // RtpParameters that contains this RtpEncodingParameters. If unset, the - // implementation will choose the first possible codec. - // TODO(deadbeef): Not implemented. + // implementation will choose the first possible codec (if a sender), or + // prepare to receive any codec (for a receiver). + // TODO(deadbeef): Not implemented. Implementation of RtpSender will always + // choose the first codec from the list. rtc::Optional codec_payload_type; // Specifies the FEC mechanism, if set. - // TODO(deadbeef): Not implemented. + // TODO(deadbeef): Not implemented. Current implementation will use whatever + // FEC codecs are available, including red+ulpfec. rtc::Optional fec; // Specifies the RTX parameters, if set. - // TODO(deadbeef): Not implemented. + // TODO(deadbeef): Not implemented with PeerConnection senders/receivers. rtc::Optional rtx; // Only used for audio. If set, determines whether or not discontinuous // transmission will be used, if an available codec supports it. If not // set, the implementation default setting will be used. + // TODO(deadbeef): Not implemented. Current implementation will use a CN + // codec as long as it's present. rtc::Optional dtx; // The relative priority of this encoding. @@ -264,7 +286,13 @@ struct RtpEncodingParameters { // If set, this represents the Transport Independent Application Specific // maximum bandwidth defined in RFC3890. If unset, there is no maximum // bitrate. + // // Just called "maxBitrate" in ORTC spec. + // + // TODO(deadbeef): With ORTC RtpSenders, this currently sets the total + // bandwidth for the entire bandwidth estimator (audio and video). This is + // just always how "b=AS" was handled, but it's not correct and should be + // fixed. rtc::Optional max_bitrate_bps; // TODO(deadbeef): Not implemented. @@ -281,7 +309,7 @@ struct RtpEncodingParameters { // For an RtpSender, set to true to cause this encoding to be sent, and false // for it not to be sent. For an RtpReceiver, set to true to cause the // encoding to be decoded, and false for it to be ignored. - // TODO(deadbeef): RtpReceiver part is not implemented. + // TODO(deadbeef): Not implemented for PeerConnection RtpReceivers. bool active = true; // Value to use for RID RTP header extension. @@ -320,7 +348,7 @@ struct RtpCodecParameters { cricket::MediaType kind = cricket::MEDIA_TYPE_AUDIO; // Payload type used to identify this codec in RTP packets. - // This MUST always be present, and must be unique across all codecs using + // This must always be present, and must be unique across all codecs using // the same transport. int payload_type = 0; @@ -329,7 +357,9 @@ struct RtpCodecParameters { // The number of audio channels used. Unset for video codecs. If unset for // audio, the implementation default is used. - // TODO(deadbeef): The "implementation default" part is unimplemented. + // TODO(deadbeef): The "implementation default" part isn't fully implemented. + // Only defaults to 1, even though some codecs (such as opus) should really + // default to 2. rtc::Optional num_channels; // The maximum packetization time to be used by an RtpSender. @@ -343,12 +373,18 @@ struct RtpCodecParameters { rtc::Optional ptime; // Feedback mechanisms to be used for this codec. - // TODO(deadbeef): Not implemented. + // TODO(deadbeef): Not implemented with PeerConnection senders/receivers. std::vector rtcp_feedback; // Codec-specific parameters that must be signaled to the remote party. + // // Corresponds to "a=fmtp" parameters in SDP. - // TODO(deadbeef): Not implemented. + // + // Contrary to ORTC, these parameters are named using all lowercase strings. + // This helps make the mapping to SDP simpler, if an application is using + // SDP. Boolean values are represented by the string "1". + // + // TODO(deadbeef): Not implemented with PeerConnection senders/receivers. std::unordered_map parameters; bool operator==(const RtpCodecParameters& o) const { @@ -370,7 +406,9 @@ struct RtpCapabilities { // Supported RTP header extensions. std::vector header_extensions; - // Supported Forward Error Correction (FEC) mechanisms. + // Supported Forward Error Correction (FEC) mechanisms. Note that the RED, + // ulpfec and flexfec codecs used by these mechanisms will still appear in + // |codecs|. std::vector fec; bool operator==(const RtpCapabilities& o) const { @@ -380,8 +418,8 @@ struct RtpCapabilities { bool operator!=(const RtpCapabilities& o) const { return !(*this == o); } }; -// Note that unlike in ORTC, an RtcpParameters is not included in -// RtpParameters, because our API will include an additional "RtpTransport" +// Note that unlike in ORTC, an RtcpParameters structure is not included in +// RtpParameters, because our API includes an additional "RtpTransport" // abstraction on which RTCP parameters are set. struct RtpParameters { // Used when calling getParameters/setParameters with a PeerConnection @@ -397,7 +435,7 @@ struct RtpParameters { std::vector codecs; - // TODO(deadbeef): Not implemented. + // TODO(deadbeef): Not implemented with PeerConnection senders/receivers. std::vector header_extensions; std::vector encodings; diff --git a/webrtc/config.cc b/webrtc/config.cc index 6ffd1c3fd1..e0c490d1ec 100644 --- a/webrtc/config.cc +++ b/webrtc/config.cc @@ -72,6 +72,9 @@ const char* RtpExtension::kPlayoutDelayUri = "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay"; const int RtpExtension::kPlayoutDelayDefaultId = 6; +const int RtpExtension::kMinId = 1; +const int RtpExtension::kMaxId = 14; + bool RtpExtension::IsSupportedForAudio(const std::string& uri) { return uri == webrtc::RtpExtension::kAudioLevelUri || uri == webrtc::RtpExtension::kTransportSequenceNumberUri; diff --git a/webrtc/config.h b/webrtc/config.h index 22b279c418..f8c9e8b797 100644 --- a/webrtc/config.h +++ b/webrtc/config.h @@ -96,6 +96,10 @@ struct RtpExtension { static const char* kPlayoutDelayUri; static const int kPlayoutDelayDefaultId; + // Inclusive min and max IDs for one-byte header extensions, per RFC5285. + static const int kMinId; + static const int kMaxId; + std::string uri; int id; }; diff --git a/webrtc/media/base/codec.h b/webrtc/media/base/codec.h index 0aa0d58ef7..5e49785e9d 100644 --- a/webrtc/media/base/codec.h +++ b/webrtc/media/base/codec.h @@ -26,6 +26,7 @@ typedef std::map CodecParameterMap; class FeedbackParam { public: + FeedbackParam() = default; FeedbackParam(const std::string& id, const std::string& param) : id_(id), param_(param) { diff --git a/webrtc/media/base/fakemediaengine.h b/webrtc/media/base/fakemediaengine.h index d9a79ccca2..fc59fa7d7d 100644 --- a/webrtc/media/base/fakemediaengine.h +++ b/webrtc/media/base/fakemediaengine.h @@ -206,6 +206,8 @@ template class RtpHelper : public Base { return ""; return send_streams_[0].cname; } + const RtcpParameters& send_rtcp_parameters() { return send_rtcp_parameters_; } + const RtcpParameters& recv_rtcp_parameters() { return recv_rtcp_parameters_; } bool ready_to_send() const { return ready_to_send_; @@ -246,6 +248,12 @@ template class RtpHelper : public Base { send_extensions_ = extensions; return true; } + void set_send_rtcp_parameters(const RtcpParameters& params) { + send_rtcp_parameters_ = params; + } + void set_recv_rtcp_parameters(const RtcpParameters& params) { + recv_rtcp_parameters_ = params; + } virtual void OnPacketReceived(rtc::CopyOnWriteBuffer* packet, const rtc::PacketTime& packet_time) { rtp_packets_.push_back(std::string(packet->data(), packet->size())); @@ -278,6 +286,8 @@ template class RtpHelper : public Base { std::list rtcp_packets_; std::vector send_streams_; std::vector receive_streams_; + RtcpParameters send_rtcp_parameters_; + RtcpParameters recv_rtcp_parameters_; std::set muted_streams_; std::map rtp_send_parameters_; std::map rtp_receive_parameters_; @@ -318,6 +328,7 @@ class FakeVoiceMediaChannel : public RtpHelper { const AudioOptions& options() const { return options_; } int max_bps() const { return max_bps_; } virtual bool SetSendParameters(const AudioSendParameters& params) { + set_send_rtcp_parameters(params.rtcp); return (SetSendCodecs(params.codecs) && SetSendRtpHeaderExtensions(params.extensions) && SetMaxSendBandwidth(params.max_bandwidth_bps) && @@ -325,6 +336,7 @@ class FakeVoiceMediaChannel : public RtpHelper { } virtual bool SetRecvParameters(const AudioRecvParameters& params) { + set_recv_rtcp_parameters(params.rtcp); return (SetRecvCodecs(params.codecs) && SetRecvRtpHeaderExtensions(params.extensions)); } @@ -519,11 +531,13 @@ class FakeVideoMediaChannel : public RtpHelper { } int max_bps() const { return max_bps_; } bool SetSendParameters(const VideoSendParameters& params) override { + set_send_rtcp_parameters(params.rtcp); return (SetSendCodecs(params.codecs) && SetSendRtpHeaderExtensions(params.extensions) && SetMaxSendBandwidth(params.max_bandwidth_bps)); } bool SetRecvParameters(const VideoRecvParameters& params) override { + set_recv_rtcp_parameters(params.rtcp); return (SetRecvCodecs(params.codecs) && SetRecvRtpHeaderExtensions(params.extensions)); } @@ -643,10 +657,12 @@ class FakeDataMediaChannel : public RtpHelper { int max_bps() const { return max_bps_; } virtual bool SetSendParameters(const DataSendParameters& params) { + set_send_rtcp_parameters(params.rtcp); return (SetSendCodecs(params.codecs) && SetMaxSendBandwidth(params.max_bandwidth_bps)); } virtual bool SetRecvParameters(const DataRecvParameters& params) { + set_recv_rtcp_parameters(params.rtcp); return SetRecvCodecs(params.codecs); } virtual bool SetSend(bool send) { return set_sending(send); } diff --git a/webrtc/ortc/BUILD.gn b/webrtc/ortc/BUILD.gn index a9195110c7..ebba3b86ed 100644 --- a/webrtc/ortc/BUILD.gn +++ b/webrtc/ortc/BUILD.gn @@ -12,18 +12,69 @@ if (is_android) { import("//build/config/android/rules.gni") } +rtc_static_library("ortc") { + defines = [] + sources = [ + "ortcfactory.cc", + "ortcfactory.h", + "ortcrtpreceiveradapter.cc", + "ortcrtpreceiveradapter.h", + "ortcrtpsenderadapter.cc", + "ortcrtpsenderadapter.h", + "rtpparametersconversion.cc", + "rtpparametersconversion.h", + "rtptransportadapter.cc", + "rtptransportadapter.h", + "rtptransportcontrolleradapter.cc", + "rtptransportcontrolleradapter.h", + ] + + # TODO(deadbeef): Create a separate target for the common things ORTC and + # PeerConnection code shares, so that ortc can depend on that instead of + # libjingle_peerconnection. + deps = [ + "../pc:libjingle_peerconnection", + ] + + public_deps = [ + "../api:ortc_api", + ] + + if (!build_with_chromium && is_clang) { + # Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163). + suppressed_configs += [ "//build/config/clang:find_bad_constructs" ] + } +} + if (rtc_include_tests) { rtc_test("ortc_unittests") { testonly = true sources = [ - "dummy_test.cc", + "ortcfactory_integrationtest.cc", + "ortcfactory_unittest.cc", + "ortcrtpreceiver_unittest.cc", + "ortcrtpsender_unittest.cc", + "rtpparametersconversion_unittest.cc", + "rtptransport_unittest.cc", + "rtptransportcontroller_unittest.cc", + "testrtpparameters.cc", + "testrtpparameters.h", ] deps = [ - "../base:rtc_base_tests_main", + ":ortc", + "../base:rtc_base_tests_utils", + "../media:rtc_unittest_main", + "../pc:pc_test_utils", + "../system_wrappers:metrics_default", ] + if (!build_with_chromium && is_clang) { + # Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163). + suppressed_configs += [ "//build/config/clang:find_bad_constructs" ] + } + if (is_android) { deps += [ "//testing/android/native_test:native_test_support" ] } diff --git a/webrtc/ortc/DEPS b/webrtc/ortc/DEPS new file mode 100644 index 0000000000..de152dd01a --- /dev/null +++ b/webrtc/ortc/DEPS @@ -0,0 +1,17 @@ +include_rules = [ + "+webrtc/api", + "+webrtc/base", + "+webrtc/call", + "+webrtc/logging/rtc_event_log", + "+webrtc/media", + "+webrtc/modules/audio_coding", + "+webrtc/p2p", + "+webrtc/pc", + + "+webrtc/modules/rtp_rtcp", + "+webrtc/system_wrappers", + + "+webrtc/modules/audio_device", + "+webrtc/modules/video_coding", + "+webrtc/modules/video_render", +] diff --git a/webrtc/ortc/OWNERS b/webrtc/ortc/OWNERS new file mode 100644 index 0000000000..d51c5e4b88 --- /dev/null +++ b/webrtc/ortc/OWNERS @@ -0,0 +1,6 @@ +deadbeef@webrtc.org + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gn=* +per-file *.gni=* diff --git a/webrtc/ortc/dummy_test.cc b/webrtc/ortc/dummy_test.cc deleted file mode 100644 index 1e0dc41da5..0000000000 --- a/webrtc/ortc/dummy_test.cc +++ /dev/null @@ -1,16 +0,0 @@ -/* - * 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/test/gtest.h" - -// This is just a placeholder test so that trybots can be updated to run the -// new "ortc_unittests" target. -TEST(DummyOrtcTest, Test) { -} diff --git a/webrtc/ortc/ortcfactory.cc b/webrtc/ortc/ortcfactory.cc new file mode 100644 index 0000000000..c0d54d16f0 --- /dev/null +++ b/webrtc/ortc/ortcfactory.cc @@ -0,0 +1,504 @@ +/* + * 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/ortcfactory.h" + +#include +#include +#include // For std::move. + +#include "webrtc/api/proxy.h" +#include "webrtc/api/mediastreamtrackproxy.h" +#include "webrtc/api/rtcerror.h" +#include "webrtc/api/videosourceproxy.h" +#include "webrtc/base/asyncpacketsocket.h" +#include "webrtc/base/bind.h" +#include "webrtc/base/checks.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/logging.h" +#include "webrtc/logging/rtc_event_log/rtc_event_log.h" +#include "webrtc/media/base/mediaconstants.h" +#include "webrtc/modules/audio_coding/codecs/builtin_audio_decoder_factory.h" +#include "webrtc/ortc/ortcrtpreceiveradapter.h" +#include "webrtc/ortc/ortcrtpsenderadapter.h" +#include "webrtc/ortc/rtpparametersconversion.h" +#include "webrtc/ortc/rtptransportadapter.h" +#include "webrtc/ortc/rtptransportcontrolleradapter.h" +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/base/udptransport.h" +#include "webrtc/pc/channelmanager.h" +#include "webrtc/pc/localaudiosource.h" +#include "webrtc/pc/audiotrack.h" +#include "webrtc/pc/videocapturertracksource.h" +#include "webrtc/pc/videotrack.h" + +namespace { + +const int kDefaultRtcpCnameLength = 16; + +// Asserts that all of the built-in capabilities can be converted to +// RtpCapabilities. If they can't, something's wrong (for example, maybe a new +// feedback mechanism is supported, but an enum value wasn't added to +// rtpparameters.h). +template +webrtc::RtpCapabilities ToRtpCapabilitiesWithAsserts( + const std::vector& cricket_codecs, + const cricket::RtpHeaderExtensions& cricket_extensions) { + webrtc::RtpCapabilities capabilities = + webrtc::ToRtpCapabilities(cricket_codecs, cricket_extensions); + RTC_DCHECK_EQ(capabilities.codecs.size(), cricket_codecs.size()); + for (size_t i = 0; i < capabilities.codecs.size(); ++i) { + RTC_DCHECK_EQ(capabilities.codecs[i].rtcp_feedback.size(), + cricket_codecs[i].feedback_params.params().size()); + } + RTC_DCHECK_EQ(capabilities.header_extensions.size(), + cricket_extensions.size()); + return capabilities; +} + +} // namespace + +namespace webrtc { + +// Note that this proxy class uses the network thread as the "worker" thread. +BEGIN_OWNED_PROXY_MAP(OrtcFactory) +PROXY_SIGNALING_THREAD_DESTRUCTOR() +PROXY_METHOD0(RTCErrorOr>, + CreateRtpTransportController) +PROXY_METHOD4(RTCErrorOr>, + CreateRtpTransport, + const RtcpParameters&, + PacketTransportInterface*, + PacketTransportInterface*, + RtpTransportControllerInterface*) +PROXY_CONSTMETHOD1(RtpCapabilities, + GetRtpSenderCapabilities, + cricket::MediaType) +PROXY_METHOD2(RTCErrorOr>, + CreateRtpSender, + rtc::scoped_refptr, + RtpTransportInterface*) +PROXY_METHOD2(RTCErrorOr>, + CreateRtpSender, + cricket::MediaType, + RtpTransportInterface*) +PROXY_CONSTMETHOD1(RtpCapabilities, + GetRtpReceiverCapabilities, + cricket::MediaType) +PROXY_METHOD2(RTCErrorOr>, + CreateRtpReceiver, + cricket::MediaType, + RtpTransportInterface*) +PROXY_WORKER_METHOD3(RTCErrorOr>, + CreateUdpTransport, + int, + uint16_t, + uint16_t) +PROXY_METHOD1(rtc::scoped_refptr, + CreateAudioSource, + const cricket::AudioOptions&) +PROXY_METHOD2(rtc::scoped_refptr, + CreateVideoSource, + std::unique_ptr, + const MediaConstraintsInterface*) +PROXY_METHOD2(rtc::scoped_refptr, + CreateVideoTrack, + const std::string&, + VideoTrackSourceInterface*) +PROXY_METHOD2(rtc::scoped_refptr, + CreateAudioTrack, + const std::string&, + AudioSourceInterface*) +END_PROXY_MAP() + +// static +RTCErrorOr> OrtcFactory::Create( + rtc::Thread* network_thread, + rtc::Thread* signaling_thread, + rtc::NetworkManager* network_manager, + rtc::PacketSocketFactory* socket_factory, + AudioDeviceModule* adm, + std::unique_ptr media_engine) { + // Hop to signaling thread if needed. + if (signaling_thread && !signaling_thread->IsCurrent()) { + return signaling_thread + ->Invoke>>( + RTC_FROM_HERE, + rtc::Bind(&OrtcFactory::Create_s, network_thread, signaling_thread, + network_manager, socket_factory, adm, + media_engine.release())); + } + return Create_s(network_thread, signaling_thread, network_manager, + socket_factory, adm, media_engine.release()); +} + +RTCErrorOr> OrtcFactoryInterface::Create( + rtc::Thread* network_thread, + rtc::Thread* signaling_thread, + rtc::NetworkManager* network_manager, + rtc::PacketSocketFactory* socket_factory, + AudioDeviceModule* adm) { + return OrtcFactory::Create(network_thread, signaling_thread, network_manager, + socket_factory, adm, nullptr); +} + +OrtcFactory::OrtcFactory(rtc::Thread* network_thread, + rtc::Thread* signaling_thread, + rtc::NetworkManager* network_manager, + rtc::PacketSocketFactory* socket_factory, + AudioDeviceModule* adm) + : network_thread_(network_thread), + signaling_thread_(signaling_thread), + network_manager_(network_manager), + socket_factory_(socket_factory), + adm_(adm), + null_event_log_(RtcEventLog::CreateNull()), + audio_decoder_factory_(CreateBuiltinAudioDecoderFactory()) { + if (!rtc::CreateRandomString(kDefaultRtcpCnameLength, &default_cname_)) { + LOG(LS_ERROR) << "Failed to generate CNAME?"; + RTC_NOTREACHED(); + } + if (!network_thread_) { + owned_network_thread_ = rtc::Thread::CreateWithSocketServer(); + owned_network_thread_->Start(); + network_thread_ = owned_network_thread_.get(); + } + + // The worker thread is created internally because it's an implementation + // detail, and consumers of the API don't need to really know about it. + worker_thread_ = rtc::Thread::Create(); + worker_thread_->Start(); + + if (signaling_thread_) { + RTC_DCHECK_RUN_ON(signaling_thread_); + } else { + signaling_thread_ = rtc::Thread::Current(); + if (!signaling_thread_) { + // If this thread isn't already wrapped by an rtc::Thread, create a + // wrapper and own it in this class. + signaling_thread_ = rtc::ThreadManager::Instance()->WrapCurrentThread(); + wraps_signaling_thread_ = true; + } + } + if (!network_manager_) { + owned_network_manager_.reset(new rtc::BasicNetworkManager()); + network_manager_ = owned_network_manager_.get(); + } + if (!socket_factory_) { + owned_socket_factory_.reset( + new rtc::BasicPacketSocketFactory(network_thread_)); + socket_factory_ = owned_socket_factory_.get(); + } +} + +OrtcFactory::~OrtcFactory() { + RTC_DCHECK_RUN_ON(signaling_thread_); + if (wraps_signaling_thread_) { + rtc::ThreadManager::Instance()->UnwrapCurrentThread(); + } +} + +RTCErrorOr> +OrtcFactory::CreateRtpTransportController() { + RTC_DCHECK_RUN_ON(signaling_thread_); + return RtpTransportControllerAdapter::CreateProxied( + cricket::MediaConfig(), channel_manager_.get(), null_event_log_.get(), + signaling_thread_, worker_thread_.get()); +} + +RTCErrorOr> +OrtcFactory::CreateRtpTransport( + const RtcpParameters& rtcp_parameters, + PacketTransportInterface* rtp, + PacketTransportInterface* rtcp, + RtpTransportControllerInterface* transport_controller) { + RTC_DCHECK_RUN_ON(signaling_thread_); + RtcpParameters copied_parameters = rtcp_parameters; + if (copied_parameters.cname.empty()) { + copied_parameters.cname = default_cname_; + } + if (transport_controller) { + return transport_controller->GetInternal()->CreateProxiedRtpTransport( + copied_parameters, rtp, rtcp); + } else { + // If |transport_controller| is null, create one automatically, which the + // returned RtpTransport will own. + auto controller_result = CreateRtpTransportController(); + if (!controller_result.ok()) { + return controller_result.MoveError(); + } + auto controller = controller_result.MoveValue(); + auto transport_result = + controller->GetInternal()->CreateProxiedRtpTransport(copied_parameters, + rtp, rtcp); + // If RtpTransport was successfully created, transfer ownership of + // |rtp_transport_controller|. Otherwise it will go out of scope and be + // deleted automatically. + if (transport_result.ok()) { + transport_result.value() + ->GetInternal() + ->TakeOwnershipOfRtpTransportController(std::move(controller)); + } + return transport_result; + } +} + +RtpCapabilities OrtcFactory::GetRtpSenderCapabilities( + cricket::MediaType kind) const { + RTC_DCHECK_RUN_ON(signaling_thread_); + switch (kind) { + case cricket::MEDIA_TYPE_AUDIO: { + cricket::AudioCodecs cricket_codecs; + cricket::RtpHeaderExtensions cricket_extensions; + channel_manager_->GetSupportedAudioSendCodecs(&cricket_codecs); + channel_manager_->GetSupportedAudioRtpHeaderExtensions( + &cricket_extensions); + return ToRtpCapabilitiesWithAsserts(cricket_codecs, cricket_extensions); + } + case cricket::MEDIA_TYPE_VIDEO: { + cricket::VideoCodecs cricket_codecs; + cricket::RtpHeaderExtensions cricket_extensions; + channel_manager_->GetSupportedVideoCodecs(&cricket_codecs); + channel_manager_->GetSupportedVideoRtpHeaderExtensions( + &cricket_extensions); + return ToRtpCapabilitiesWithAsserts(cricket_codecs, cricket_extensions); + } + case cricket::MEDIA_TYPE_DATA: + return RtpCapabilities(); + } + // Not reached; avoids compile warning. + FATAL(); +} + +RTCErrorOr> +OrtcFactory::CreateRtpSender( + rtc::scoped_refptr track, + RtpTransportInterface* transport) { + RTC_DCHECK_RUN_ON(signaling_thread_); + if (!track) { + LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, + "Cannot pass null track into CreateRtpSender."); + } + auto result = + CreateRtpSender(cricket::MediaTypeFromString(track->kind()), transport); + if (!result.ok()) { + return result; + } + auto err = result.value()->SetTrack(track); + if (!err.ok()) { + return std::move(err); + } + return result; +} + +RTCErrorOr> +OrtcFactory::CreateRtpSender(cricket::MediaType kind, + RtpTransportInterface* transport) { + RTC_DCHECK_RUN_ON(signaling_thread_); + if (kind == cricket::MEDIA_TYPE_DATA) { + LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, + "Cannot create data RtpSender."); + } + if (!transport) { + LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, + "Cannot pass null transport into CreateRtpSender."); + } + return transport->GetInternal() + ->rtp_transport_controller() + ->CreateProxiedRtpSender(kind, transport); +} + +RtpCapabilities OrtcFactory::GetRtpReceiverCapabilities( + cricket::MediaType kind) const { + RTC_DCHECK_RUN_ON(signaling_thread_); + switch (kind) { + case cricket::MEDIA_TYPE_AUDIO: { + cricket::AudioCodecs cricket_codecs; + cricket::RtpHeaderExtensions cricket_extensions; + channel_manager_->GetSupportedAudioReceiveCodecs(&cricket_codecs); + channel_manager_->GetSupportedAudioRtpHeaderExtensions( + &cricket_extensions); + return ToRtpCapabilitiesWithAsserts(cricket_codecs, cricket_extensions); + } + case cricket::MEDIA_TYPE_VIDEO: { + cricket::VideoCodecs cricket_codecs; + cricket::RtpHeaderExtensions cricket_extensions; + channel_manager_->GetSupportedVideoCodecs(&cricket_codecs); + channel_manager_->GetSupportedVideoRtpHeaderExtensions( + &cricket_extensions); + return ToRtpCapabilitiesWithAsserts(cricket_codecs, cricket_extensions); + } + case cricket::MEDIA_TYPE_DATA: + return RtpCapabilities(); + } + // Not reached; avoids compile warning. + FATAL(); +} + +RTCErrorOr> +OrtcFactory::CreateRtpReceiver(cricket::MediaType kind, + RtpTransportInterface* transport) { + RTC_DCHECK_RUN_ON(signaling_thread_); + if (kind == cricket::MEDIA_TYPE_DATA) { + LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, + "Cannot create data RtpReceiver."); + } + if (!transport) { + LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, + "Cannot pass null transport into CreateRtpReceiver."); + } + return transport->GetInternal() + ->rtp_transport_controller() + ->CreateProxiedRtpReceiver(kind, transport); +} + +// UdpTransport expects all methods to be called on one thread, which needs to +// be the network thread, since that's where its socket can safely be used. So +// return a proxy to the created UdpTransport. +BEGIN_OWNED_PROXY_MAP(UdpTransport) +PROXY_WORKER_THREAD_DESTRUCTOR() +PROXY_WORKER_CONSTMETHOD0(rtc::SocketAddress, GetLocalAddress) +PROXY_WORKER_METHOD1(bool, SetRemoteAddress, const rtc::SocketAddress&) +PROXY_WORKER_CONSTMETHOD0(rtc::SocketAddress, GetRemoteAddress) +protected: +rtc::PacketTransportInternal* GetInternal() override { + return internal(); +} +END_PROXY_MAP() + +RTCErrorOr> +OrtcFactory::CreateUdpTransport(int family, + uint16_t min_port, + uint16_t max_port) { + RTC_DCHECK_RUN_ON(network_thread_); + if (family != AF_INET && family != AF_INET6) { + LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, + "Address family must be AF_INET or AF_INET6."); + } + if (min_port > max_port) { + LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_RANGE, + "Port range invalid; minimum port must be less than " + "or equal to max port."); + } + std::unique_ptr socket( + socket_factory_->CreateUdpSocket( + rtc::SocketAddress(rtc::GetAnyIP(family), 0), min_port, max_port)); + if (!socket) { + // Only log at warning level, because this method may be called with + // specific port ranges to determine if a port is available, expecting the + // possibility of an error. + LOG_AND_RETURN_ERROR_EX(RTCErrorType::RESOURCE_EXHAUSTED, + "Local socket allocation failure.", LS_WARNING); + } + LOG(LS_INFO) << "Created UDP socket with address " + << socket->GetLocalAddress().ToSensitiveString() << "."; + // Make a unique debug name (for logging/diagnostics only). + std::ostringstream oss; + static int udp_id = 0; + oss << "udp" << udp_id++; + return UdpTransportProxyWithInternal::Create( + signaling_thread_, network_thread_, + std::unique_ptr( + new cricket::UdpTransport(oss.str(), std::move(socket)))); +} + +rtc::scoped_refptr OrtcFactory::CreateAudioSource( + const cricket::AudioOptions& options) { + RTC_DCHECK_RUN_ON(signaling_thread_); + return rtc::scoped_refptr( + LocalAudioSource::Create(&options)); +} + +rtc::scoped_refptr OrtcFactory::CreateVideoSource( + std::unique_ptr capturer, + const MediaConstraintsInterface* constraints) { + RTC_DCHECK_RUN_ON(signaling_thread_); + rtc::scoped_refptr source( + VideoCapturerTrackSource::Create( + worker_thread_.get(), std::move(capturer), constraints, false)); + return VideoTrackSourceProxy::Create(signaling_thread_, worker_thread_.get(), + source); +} + +rtc::scoped_refptr OrtcFactory::CreateVideoTrack( + const std::string& id, + VideoTrackSourceInterface* source) { + RTC_DCHECK_RUN_ON(signaling_thread_); + rtc::scoped_refptr track(VideoTrack::Create(id, source)); + return VideoTrackProxy::Create(signaling_thread_, worker_thread_.get(), + track); +} + +rtc::scoped_refptr OrtcFactory::CreateAudioTrack( + const std::string& id, + AudioSourceInterface* source) { + RTC_DCHECK_RUN_ON(signaling_thread_); + rtc::scoped_refptr track(AudioTrack::Create(id, source)); + return AudioTrackProxy::Create(signaling_thread_, track); +} + +// static +RTCErrorOr> OrtcFactory::Create_s( + rtc::Thread* network_thread, + rtc::Thread* signaling_thread, + rtc::NetworkManager* network_manager, + rtc::PacketSocketFactory* socket_factory, + AudioDeviceModule* adm, + cricket::MediaEngineInterface* media_engine) { + // Add the unique_ptr wrapper back. + std::unique_ptr owned_media_engine( + media_engine); + std::unique_ptr new_factory(new OrtcFactory( + network_thread, signaling_thread, network_manager, socket_factory, adm)); + RTCError err = new_factory->Initialize(std::move(owned_media_engine)); + if (!err.ok()) { + return std::move(err); + } + // Return a proxy so that any calls on the returned object (including + // destructor) happen on the signaling thread. + rtc::Thread* signaling = new_factory->signaling_thread(); + rtc::Thread* network = new_factory->network_thread(); + return OrtcFactoryProxy::Create(signaling, network, std::move(new_factory)); +} + +RTCError OrtcFactory::Initialize( + std::unique_ptr media_engine) { + RTC_DCHECK_RUN_ON(signaling_thread_); + // TODO(deadbeef): Get rid of requirement to hop to worker thread here. + if (!media_engine) { + media_engine = + worker_thread_->Invoke>( + RTC_FROM_HERE, rtc::Bind(&OrtcFactory::CreateMediaEngine_w, this)); + } + + channel_manager_.reset(new cricket::ChannelManager( + std::move(media_engine), worker_thread_.get(), network_thread_)); + channel_manager_->SetVideoRtxEnabled(true); + if (!channel_manager_->Init()) { + LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR, + "Failed to initialize ChannelManager."); + } + return RTCError::OK(); +} + +std::unique_ptr +OrtcFactory::CreateMediaEngine_w() { + RTC_DCHECK_RUN_ON(worker_thread_.get()); + // The null arguments are optional factories that could be passed into the + // OrtcFactory, but aren't yet. + // + // Note that |adm_| may be null, in which case the platform-specific default + // AudioDeviceModule will be used. + return std::unique_ptr( + cricket::WebRtcMediaEngineFactory::Create(adm_, audio_decoder_factory_, + nullptr, nullptr, nullptr)); +} + +} // namespace webrtc diff --git a/webrtc/ortc/ortcfactory.h b/webrtc/ortc/ortcfactory.h new file mode 100644 index 0000000000..71f525d884 --- /dev/null +++ b/webrtc/ortc/ortcfactory.h @@ -0,0 +1,144 @@ +/* + * 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. + */ + +#ifndef WEBRTC_ORTC_ORTCFACTORY_H_ +#define WEBRTC_ORTC_ORTCFACTORY_H_ + +#include +#include + +#include "webrtc/api/ortc/ortcfactoryinterface.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/scoped_ref_ptr.h" +#include "webrtc/media/base/mediaengine.h" +#include "webrtc/media/engine/webrtcmediaengine.h" +#include "webrtc/pc/channelmanager.h" + +namespace webrtc { + +// Implementation of OrtcFactoryInterface. +// +// See ortcfactoryinterface.h for documentation. +class OrtcFactory : public OrtcFactoryInterface { + public: + ~OrtcFactory() override; + + // Internal-only Create method that allows passing in a fake media engine, + // for testing. + static RTCErrorOr> Create( + rtc::Thread* network_thread, + rtc::Thread* signaling_thread, + rtc::NetworkManager* network_manager, + rtc::PacketSocketFactory* socket_factory, + AudioDeviceModule* adm, + std::unique_ptr media_engine); + + RTCErrorOr> + CreateRtpTransportController() override; + + RTCErrorOr> CreateRtpTransport( + const RtcpParameters& rtcp_parameters, + PacketTransportInterface* rtp, + PacketTransportInterface* rtcp, + RtpTransportControllerInterface* transport_controller) override; + + RtpCapabilities GetRtpSenderCapabilities( + cricket::MediaType kind) const override; + + RTCErrorOr> CreateRtpSender( + rtc::scoped_refptr track, + RtpTransportInterface* transport) override; + + RTCErrorOr> CreateRtpSender( + cricket::MediaType kind, + RtpTransportInterface* transport) override; + + RtpCapabilities GetRtpReceiverCapabilities( + cricket::MediaType kind) const override; + + RTCErrorOr> CreateRtpReceiver( + cricket::MediaType kind, + RtpTransportInterface* transport) override; + + RTCErrorOr> + CreateUdpTransport(int family, uint16_t min_port, uint16_t max_port) override; + + rtc::scoped_refptr CreateAudioSource( + const cricket::AudioOptions& options) override; + + rtc::scoped_refptr CreateVideoSource( + std::unique_ptr capturer, + const MediaConstraintsInterface* constraints) override; + + rtc::scoped_refptr CreateVideoTrack( + const std::string& id, + VideoTrackSourceInterface* source) override; + + rtc::scoped_refptr CreateAudioTrack( + const std::string& id, + AudioSourceInterface* source) override; + + rtc::Thread* network_thread() { return network_thread_; } + rtc::Thread* worker_thread() { return worker_thread_.get(); } + rtc::Thread* signaling_thread() { return signaling_thread_; } + + private: + // Should only be called by OrtcFactoryInterface::Create. + OrtcFactory(rtc::Thread* network_thread, + rtc::Thread* signaling_thread, + rtc::NetworkManager* network_manager, + rtc::PacketSocketFactory* socket_factory, + AudioDeviceModule* adm); + + // Thread::Invoke doesn't support move-only arguments, so we need to remove + // the unique_ptr wrapper from media_engine. TODO(deadbeef): Fix this. + static RTCErrorOr> Create_s( + rtc::Thread* network_thread, + rtc::Thread* signaling_thread, + rtc::NetworkManager* network_manager, + rtc::PacketSocketFactory* socket_factory, + AudioDeviceModule* adm, + cricket::MediaEngineInterface* media_engine); + + // Performs initialization that can fail. Called by factory method after + // construction, and if it fails, no object is returned. + RTCError Initialize( + std::unique_ptr media_engine); + std::unique_ptr CreateMediaEngine_w(); + + // Threads and networking objects. + rtc::Thread* network_thread_; + rtc::Thread* signaling_thread_; + rtc::NetworkManager* network_manager_; + rtc::PacketSocketFactory* socket_factory_; + AudioDeviceModule* adm_; + // If we created/own the objects above, these will be non-null and thus will + // be released automatically upon destruction. + std::unique_ptr owned_network_thread_; + bool wraps_signaling_thread_ = false; + std::unique_ptr owned_network_manager_; + std::unique_ptr owned_socket_factory_; + // We always own the worker thread. + std::unique_ptr worker_thread_; + // Media-releated objects. + std::unique_ptr null_event_log_; + rtc::scoped_refptr audio_decoder_factory_; + std::unique_ptr channel_manager_; + // Default CNAME to use for RtpTransports if none is passed in. + std::string default_cname_; + + friend class OrtcFactoryInterface; + + RTC_DISALLOW_COPY_AND_ASSIGN(OrtcFactory); +}; + +} // namespace webrtc + +#endif // WEBRTC_ORTC_ORTCFACTORY_H_ diff --git a/webrtc/ortc/ortcfactory_integrationtest.cc b/webrtc/ortc/ortcfactory_integrationtest.cc new file mode 100644 index 0000000000..e935f068a3 --- /dev/null +++ b/webrtc/ortc/ortcfactory_integrationtest.cc @@ -0,0 +1,512 @@ +/* + * 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 +#include // For std::pair, std::move. + +#include "webrtc/api/ortc/ortcfactoryinterface.h" +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/fakenetwork.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/virtualsocketserver.h" +#include "webrtc/ortc/testrtpparameters.h" +#include "webrtc/p2p/base/udptransport.h" +#include "webrtc/pc/test/fakeaudiocapturemodule.h" +#include "webrtc/pc/test/fakeperiodicvideocapturer.h" +#include "webrtc/pc/test/fakevideotrackrenderer.h" + +namespace { + +const int kDefaultTimeout = 10000; // 10 seconds. +// Default number of audio/video frames to wait for before considering a test a +// success. +const int kDefaultNumFrames = 3; +const rtc::IPAddress kIPv4LocalHostAddress = + rtc::IPAddress(0x7F000001); // 127.0.0.1 + +} // namespace + +namespace webrtc { + +// Used to test that things work end-to-end when using the default +// implementations of threads/etc. provided by OrtcFactory, with the exception +// of using a virtual network. +// +// By default, the virtual network manager doesn't enumerate any networks, but +// sockets can still be created in this state. +class OrtcFactoryIntegrationTest : public testing::Test { + public: + OrtcFactoryIntegrationTest() + : virtual_socket_server_(&physical_socket_server_), + network_thread_(&virtual_socket_server_), + fake_audio_capture_module1_(FakeAudioCaptureModule::Create()), + fake_audio_capture_module2_(FakeAudioCaptureModule::Create()) { + // Sockets are bound to the ANY address, so this is needed to tell the + // virtual network which address to use in this case. + virtual_socket_server_.SetDefaultRoute(kIPv4LocalHostAddress); + network_thread_.Start(); + // Need to create after network thread is started. + ortc_factory1_ = OrtcFactoryInterface::Create( + &network_thread_, nullptr, &fake_network_manager_, + nullptr, fake_audio_capture_module1_) + .MoveValue(); + ortc_factory2_ = OrtcFactoryInterface::Create( + &network_thread_, nullptr, &fake_network_manager_, + nullptr, fake_audio_capture_module2_) + .MoveValue(); + } + + protected: + typedef std::pair, + std::unique_ptr> + UdpTransportPair; + typedef std::pair, + std::unique_ptr> + RtpTransportPair; + typedef std::pair, + std::unique_ptr> + RtpTransportControllerPair; + + // Helper function that creates one UDP transport each for |ortc_factory1_| + // and |ortc_factory2_|, and connects them. + UdpTransportPair CreateAndConnectUdpTransportPair() { + auto transport1 = ortc_factory1_->CreateUdpTransport(AF_INET).MoveValue(); + auto transport2 = ortc_factory2_->CreateUdpTransport(AF_INET).MoveValue(); + transport1->SetRemoteAddress( + rtc::SocketAddress(virtual_socket_server_.GetDefaultRoute(AF_INET), + transport2->GetLocalAddress().port())); + transport2->SetRemoteAddress( + rtc::SocketAddress(virtual_socket_server_.GetDefaultRoute(AF_INET), + transport1->GetLocalAddress().port())); + return {std::move(transport1), std::move(transport2)}; + } + + // Creates one transport controller each for |ortc_factory1_| and + // |ortc_factory2_|. + RtpTransportControllerPair CreateRtpTransportControllerPair() { + return {ortc_factory1_->CreateRtpTransportController().MoveValue(), + ortc_factory2_->CreateRtpTransportController().MoveValue()}; + } + + // Helper function that creates a pair of RtpTransports between + // |ortc_factory1_| and |ortc_factory2_|. Expected to be called with the + // result of CreateAndConnectUdpTransportPair. |rtcp_udp_transports| can be + // empty if RTCP muxing is used. |transport_controllers| can be empty if + // these transports are being created using a default transport controller. + RtpTransportPair CreateRtpTransportPair( + const RtcpParameters& rtcp_parameters, + const UdpTransportPair& rtp_udp_transports, + const UdpTransportPair& rtcp_udp_transports, + const RtpTransportControllerPair& transport_controllers) { + auto transport_result1 = ortc_factory1_->CreateRtpTransport( + rtcp_parameters, rtp_udp_transports.first.get(), + rtcp_udp_transports.first.get(), transport_controllers.first.get()); + auto transport_result2 = ortc_factory2_->CreateRtpTransport( + rtcp_parameters, rtp_udp_transports.second.get(), + rtcp_udp_transports.second.get(), transport_controllers.second.get()); + return {transport_result1.MoveValue(), transport_result2.MoveValue()}; + } + + // For convenience when |rtcp_udp_transports| and |transport_controllers| + // aren't needed. + RtpTransportPair CreateRtpTransportPair( + const RtcpParameters& rtcp_parameters, + const UdpTransportPair& rtp_udp_transports) { + return CreateRtpTransportPair(rtcp_parameters, rtp_udp_transports, + UdpTransportPair(), + RtpTransportControllerPair()); + } + + // Ends up using fake audio capture module, which was passed into OrtcFactory + // on creation. + rtc::scoped_refptr CreateLocalAudioTrack( + const std::string& id, + OrtcFactoryInterface* ortc_factory) { + // Disable echo cancellation to make test more efficient. + cricket::AudioOptions options; + options.echo_cancellation.emplace(true); + rtc::scoped_refptr source = + ortc_factory->CreateAudioSource(options); + return ortc_factory->CreateAudioTrack(id, source); + } + + // Stores created capturer in |fake_video_capturers_|. + rtc::scoped_refptr + CreateLocalVideoTrackAndFakeCapturer(const std::string& id, + OrtcFactoryInterface* ortc_factory) { + cricket::FakeVideoCapturer* fake_capturer = + new webrtc::FakePeriodicVideoCapturer(); + fake_video_capturers_.push_back(fake_capturer); + rtc::scoped_refptr source = + ortc_factory->CreateVideoSource( + std::unique_ptr(fake_capturer)); + return rtc::scoped_refptr( + ortc_factory->CreateVideoTrack(id, source)); + } + + rtc::PhysicalSocketServer physical_socket_server_; + rtc::VirtualSocketServer virtual_socket_server_; + rtc::Thread network_thread_; + rtc::FakeNetworkManager fake_network_manager_; + rtc::scoped_refptr fake_audio_capture_module1_; + rtc::scoped_refptr fake_audio_capture_module2_; + std::unique_ptr ortc_factory1_; + std::unique_ptr ortc_factory2_; + // Actually owned by video tracks. + std::vector fake_video_capturers_; +}; + +// Very basic end-to-end test with a single pair of audio RTP sender and +// receiver. +// +// Uses muxed RTCP, and minimal parameters with a hard-coded config that's +// known to work. +TEST_F(OrtcFactoryIntegrationTest, BasicOneWayAudioRtpSenderAndReceiver) { + auto udp_transports = CreateAndConnectUdpTransportPair(); + auto rtp_transports = + CreateRtpTransportPair(MakeRtcpMuxParameters(), udp_transports); + + auto sender_result = ortc_factory1_->CreateRtpSender( + cricket::MEDIA_TYPE_AUDIO, rtp_transports.first.get()); + auto receiver_result = ortc_factory2_->CreateRtpReceiver( + cricket::MEDIA_TYPE_AUDIO, rtp_transports.second.get()); + ASSERT_TRUE(sender_result.ok()); + ASSERT_TRUE(receiver_result.ok()); + auto sender = sender_result.MoveValue(); + auto receiver = receiver_result.MoveValue(); + + RTCError error = + sender->SetTrack(CreateLocalAudioTrack("audio", ortc_factory1_.get())); + EXPECT_TRUE(error.ok()); + + RtpParameters opus_parameters = MakeMinimalOpusParameters(); + EXPECT_TRUE(receiver->Receive(opus_parameters).ok()); + EXPECT_TRUE(sender->Send(opus_parameters).ok()); + // Sender and receiver are connected and configured; audio frames should be + // able to flow at this point. + EXPECT_TRUE_WAIT( + fake_audio_capture_module2_->frames_received() > kDefaultNumFrames, + kDefaultTimeout); +} + +// Very basic end-to-end test with a single pair of video RTP sender and +// receiver. +// +// Uses muxed RTCP, and minimal parameters with a hard-coded config that's +// known to work. +TEST_F(OrtcFactoryIntegrationTest, BasicOneWayVideoRtpSenderAndReceiver) { + auto udp_transports = CreateAndConnectUdpTransportPair(); + auto rtp_transports = + CreateRtpTransportPair(MakeRtcpMuxParameters(), udp_transports); + + auto sender_result = ortc_factory1_->CreateRtpSender( + cricket::MEDIA_TYPE_VIDEO, rtp_transports.first.get()); + auto receiver_result = ortc_factory2_->CreateRtpReceiver( + cricket::MEDIA_TYPE_VIDEO, rtp_transports.second.get()); + ASSERT_TRUE(sender_result.ok()); + ASSERT_TRUE(receiver_result.ok()); + auto sender = sender_result.MoveValue(); + auto receiver = receiver_result.MoveValue(); + + RTCError error = sender->SetTrack( + CreateLocalVideoTrackAndFakeCapturer("video", ortc_factory1_.get())); + EXPECT_TRUE(error.ok()); + + RtpParameters vp8_parameters = MakeMinimalVp8Parameters(); + EXPECT_TRUE(receiver->Receive(vp8_parameters).ok()); + EXPECT_TRUE(sender->Send(vp8_parameters).ok()); + FakeVideoTrackRenderer fake_renderer( + static_cast(receiver->GetTrack().get())); + // Sender and receiver are connected and configured; video frames should be + // able to flow at this point. + EXPECT_TRUE_WAIT(fake_renderer.num_rendered_frames() > kDefaultNumFrames, + kDefaultTimeout); +} + +// Test that if the track is changed while sending, the sender seamlessly +// transitions to sending it and frames are received end-to-end. +// +// Only doing this for video, since given that audio is sourced from a single +// fake audio capture module, the audio track is just a dummy object. +// TODO(deadbeef): Change this when possible. +TEST_F(OrtcFactoryIntegrationTest, SetTrackWhileSending) { + auto udp_transports = CreateAndConnectUdpTransportPair(); + auto rtp_transports = + CreateRtpTransportPair(MakeRtcpMuxParameters(), udp_transports); + + auto sender_result = ortc_factory1_->CreateRtpSender( + cricket::MEDIA_TYPE_VIDEO, rtp_transports.first.get()); + auto receiver_result = ortc_factory2_->CreateRtpReceiver( + cricket::MEDIA_TYPE_VIDEO, rtp_transports.second.get()); + ASSERT_TRUE(sender_result.ok()); + ASSERT_TRUE(receiver_result.ok()); + auto sender = sender_result.MoveValue(); + auto receiver = receiver_result.MoveValue(); + + RTCError error = sender->SetTrack( + CreateLocalVideoTrackAndFakeCapturer("video_1", ortc_factory1_.get())); + EXPECT_TRUE(error.ok()); + RtpParameters vp8_parameters = MakeMinimalVp8Parameters(); + EXPECT_TRUE(receiver->Receive(vp8_parameters).ok()); + EXPECT_TRUE(sender->Send(vp8_parameters).ok()); + FakeVideoTrackRenderer fake_renderer( + static_cast(receiver->GetTrack().get())); + // Expect for some initial number of frames to be received. + EXPECT_TRUE_WAIT(fake_renderer.num_rendered_frames() > kDefaultNumFrames, + kDefaultTimeout); + // Stop the old capturer, set a new track, and verify new frames are received + // from the new track. Stopping the old capturer ensures that we aren't + // actually still getting frames from it. + fake_video_capturers_[0]->Stop(); + int prev_num_frames = fake_renderer.num_rendered_frames(); + error = sender->SetTrack( + CreateLocalVideoTrackAndFakeCapturer("video_2", ortc_factory1_.get())); + EXPECT_TRUE(error.ok()); + EXPECT_TRUE_WAIT( + fake_renderer.num_rendered_frames() > kDefaultNumFrames + prev_num_frames, + kDefaultTimeout); +} + +// End-to-end test with two pairs of RTP senders and receivers, for audio and +// video. +// +// Uses muxed RTCP, and minimal parameters with hard-coded configs that are +// known to work. +TEST_F(OrtcFactoryIntegrationTest, + BasicTwoWayAudioVideoRtpSendersAndReceivers) { + auto udp_transports = CreateAndConnectUdpTransportPair(); + auto rtp_transports = + CreateRtpTransportPair(MakeRtcpMuxParameters(), udp_transports); + + // Create all the senders and receivers (four per endpoint). + auto audio_sender_result1 = ortc_factory1_->CreateRtpSender( + cricket::MEDIA_TYPE_AUDIO, rtp_transports.first.get()); + auto video_sender_result1 = ortc_factory1_->CreateRtpSender( + cricket::MEDIA_TYPE_VIDEO, rtp_transports.first.get()); + auto audio_receiver_result1 = ortc_factory1_->CreateRtpReceiver( + cricket::MEDIA_TYPE_AUDIO, rtp_transports.first.get()); + auto video_receiver_result1 = ortc_factory1_->CreateRtpReceiver( + cricket::MEDIA_TYPE_VIDEO, rtp_transports.first.get()); + ASSERT_TRUE(audio_sender_result1.ok()); + ASSERT_TRUE(video_sender_result1.ok()); + ASSERT_TRUE(audio_receiver_result1.ok()); + ASSERT_TRUE(video_receiver_result1.ok()); + auto audio_sender1 = audio_sender_result1.MoveValue(); + auto video_sender1 = video_sender_result1.MoveValue(); + auto audio_receiver1 = audio_receiver_result1.MoveValue(); + auto video_receiver1 = video_receiver_result1.MoveValue(); + + auto audio_sender_result2 = ortc_factory2_->CreateRtpSender( + cricket::MEDIA_TYPE_AUDIO, rtp_transports.second.get()); + auto video_sender_result2 = ortc_factory2_->CreateRtpSender( + cricket::MEDIA_TYPE_VIDEO, rtp_transports.second.get()); + auto audio_receiver_result2 = ortc_factory2_->CreateRtpReceiver( + cricket::MEDIA_TYPE_AUDIO, rtp_transports.second.get()); + auto video_receiver_result2 = ortc_factory2_->CreateRtpReceiver( + cricket::MEDIA_TYPE_VIDEO, rtp_transports.second.get()); + ASSERT_TRUE(audio_sender_result2.ok()); + ASSERT_TRUE(video_sender_result2.ok()); + ASSERT_TRUE(audio_receiver_result2.ok()); + ASSERT_TRUE(video_receiver_result2.ok()); + auto audio_sender2 = audio_sender_result2.MoveValue(); + auto video_sender2 = video_sender_result2.MoveValue(); + auto audio_receiver2 = audio_receiver_result2.MoveValue(); + auto video_receiver2 = video_receiver_result2.MoveValue(); + + // Add fake tracks. + RTCError error = audio_sender1->SetTrack( + CreateLocalAudioTrack("audio", ortc_factory1_.get())); + EXPECT_TRUE(error.ok()); + error = video_sender1->SetTrack( + CreateLocalVideoTrackAndFakeCapturer("video", ortc_factory1_.get())); + EXPECT_TRUE(error.ok()); + error = audio_sender2->SetTrack( + CreateLocalAudioTrack("audio", ortc_factory2_.get())); + EXPECT_TRUE(error.ok()); + error = video_sender2->SetTrack( + CreateLocalVideoTrackAndFakeCapturer("video", ortc_factory2_.get())); + EXPECT_TRUE(error.ok()); + + // "sent_X_parameters1" are the parameters that endpoint 1 sends with and + // endpoint 2 receives with. + RtpParameters sent_opus_parameters1 = + MakeMinimalOpusParametersWithSsrc(0xdeadbeef); + RtpParameters sent_vp8_parameters1 = + MakeMinimalVp8ParametersWithSsrc(0xbaadfeed); + RtpParameters sent_opus_parameters2 = + MakeMinimalOpusParametersWithSsrc(0x13333337); + RtpParameters sent_vp8_parameters2 = + MakeMinimalVp8ParametersWithSsrc(0x12345678); + + // Configure the senders' and receivers' parameters. + EXPECT_TRUE(audio_receiver1->Receive(sent_opus_parameters2).ok()); + EXPECT_TRUE(video_receiver1->Receive(sent_vp8_parameters2).ok()); + EXPECT_TRUE(audio_receiver2->Receive(sent_opus_parameters1).ok()); + EXPECT_TRUE(video_receiver2->Receive(sent_vp8_parameters1).ok()); + EXPECT_TRUE(audio_sender1->Send(sent_opus_parameters1).ok()); + EXPECT_TRUE(video_sender1->Send(sent_vp8_parameters1).ok()); + EXPECT_TRUE(audio_sender2->Send(sent_opus_parameters2).ok()); + EXPECT_TRUE(video_sender2->Send(sent_vp8_parameters2).ok()); + + FakeVideoTrackRenderer fake_video_renderer1( + static_cast(video_receiver1->GetTrack().get())); + FakeVideoTrackRenderer fake_video_renderer2( + static_cast(video_receiver2->GetTrack().get())); + + // Senders and receivers are connected and configured; audio and video frames + // should be able to flow at this point. + EXPECT_TRUE_WAIT( + fake_audio_capture_module1_->frames_received() > kDefaultNumFrames && + fake_video_renderer1.num_rendered_frames() > kDefaultNumFrames && + fake_audio_capture_module2_->frames_received() > kDefaultNumFrames && + fake_video_renderer2.num_rendered_frames() > kDefaultNumFrames, + kDefaultTimeout); +} + +// End-to-end test with two pairs of RTP senders and receivers, for audio and +// video. Unlike the test above, this attempts to make the parameters as +// complex as possible. +// +// Uses non-muxed RTCP, with separate audio/video transports, and a full set of +// parameters, as would normally be used in a PeerConnection. +// +// TODO(deadbeef): Update this test as more audio/video features become +// supported. +TEST_F(OrtcFactoryIntegrationTest, FullTwoWayAudioVideoRtpSendersAndReceivers) { + // We want four pairs of UDP transports for this test, for audio/video and + // RTP/RTCP. + auto audio_rtp_udp_transports = CreateAndConnectUdpTransportPair(); + auto audio_rtcp_udp_transports = CreateAndConnectUdpTransportPair(); + auto video_rtp_udp_transports = CreateAndConnectUdpTransportPair(); + auto video_rtcp_udp_transports = CreateAndConnectUdpTransportPair(); + + // Since we have multiple RTP transports on each side, we need an RTP + // transport controller. + auto transport_controllers = CreateRtpTransportControllerPair(); + + RtcpParameters audio_rtcp_parameters; + audio_rtcp_parameters.mux = false; + auto audio_rtp_transports = + CreateRtpTransportPair(audio_rtcp_parameters, audio_rtp_udp_transports, + audio_rtcp_udp_transports, transport_controllers); + + RtcpParameters video_rtcp_parameters; + video_rtcp_parameters.mux = false; + video_rtcp_parameters.reduced_size = true; + auto video_rtp_transports = + CreateRtpTransportPair(video_rtcp_parameters, video_rtp_udp_transports, + video_rtcp_udp_transports, transport_controllers); + + // Create all the senders and receivers (four per endpoint). + auto audio_sender_result1 = ortc_factory1_->CreateRtpSender( + cricket::MEDIA_TYPE_AUDIO, audio_rtp_transports.first.get()); + auto video_sender_result1 = ortc_factory1_->CreateRtpSender( + cricket::MEDIA_TYPE_VIDEO, video_rtp_transports.first.get()); + auto audio_receiver_result1 = ortc_factory1_->CreateRtpReceiver( + cricket::MEDIA_TYPE_AUDIO, audio_rtp_transports.first.get()); + auto video_receiver_result1 = ortc_factory1_->CreateRtpReceiver( + cricket::MEDIA_TYPE_VIDEO, video_rtp_transports.first.get()); + ASSERT_TRUE(audio_sender_result1.ok()); + ASSERT_TRUE(video_sender_result1.ok()); + ASSERT_TRUE(audio_receiver_result1.ok()); + ASSERT_TRUE(video_receiver_result1.ok()); + auto audio_sender1 = audio_sender_result1.MoveValue(); + auto video_sender1 = video_sender_result1.MoveValue(); + auto audio_receiver1 = audio_receiver_result1.MoveValue(); + auto video_receiver1 = video_receiver_result1.MoveValue(); + + auto audio_sender_result2 = ortc_factory2_->CreateRtpSender( + cricket::MEDIA_TYPE_AUDIO, audio_rtp_transports.second.get()); + auto video_sender_result2 = ortc_factory2_->CreateRtpSender( + cricket::MEDIA_TYPE_VIDEO, video_rtp_transports.second.get()); + auto audio_receiver_result2 = ortc_factory2_->CreateRtpReceiver( + cricket::MEDIA_TYPE_AUDIO, audio_rtp_transports.second.get()); + auto video_receiver_result2 = ortc_factory2_->CreateRtpReceiver( + cricket::MEDIA_TYPE_VIDEO, video_rtp_transports.second.get()); + ASSERT_TRUE(audio_sender_result2.ok()); + ASSERT_TRUE(video_sender_result2.ok()); + ASSERT_TRUE(audio_receiver_result2.ok()); + ASSERT_TRUE(video_receiver_result2.ok()); + auto audio_sender2 = audio_sender_result2.MoveValue(); + auto video_sender2 = video_sender_result2.MoveValue(); + auto audio_receiver2 = audio_receiver_result2.MoveValue(); + auto video_receiver2 = video_receiver_result2.MoveValue(); + + RTCError error = audio_sender1->SetTrack( + CreateLocalAudioTrack("audio", ortc_factory1_.get())); + EXPECT_TRUE(error.ok()); + error = video_sender1->SetTrack( + CreateLocalVideoTrackAndFakeCapturer("video", ortc_factory1_.get())); + EXPECT_TRUE(error.ok()); + error = audio_sender2->SetTrack( + CreateLocalAudioTrack("audio", ortc_factory2_.get())); + EXPECT_TRUE(error.ok()); + error = video_sender2->SetTrack( + CreateLocalVideoTrackAndFakeCapturer("video", ortc_factory2_.get())); + EXPECT_TRUE(error.ok()); + + // Use different codecs in different directions for extra challenge. + RtpParameters opus_send_parameters = MakeFullOpusParameters(); + RtpParameters isac_send_parameters = MakeFullIsacParameters(); + RtpParameters vp8_send_parameters = MakeFullVp8Parameters(); + RtpParameters vp9_send_parameters = MakeFullVp9Parameters(); + + // Remove "payload_type" from receive parameters. Receiver will need to + // discern the payload type from packets received. + RtpParameters opus_receive_parameters = opus_send_parameters; + RtpParameters isac_receive_parameters = isac_send_parameters; + RtpParameters vp8_receive_parameters = vp8_send_parameters; + RtpParameters vp9_receive_parameters = vp9_send_parameters; + opus_receive_parameters.encodings[0].codec_payload_type.reset(); + isac_receive_parameters.encodings[0].codec_payload_type.reset(); + vp8_receive_parameters.encodings[0].codec_payload_type.reset(); + vp9_receive_parameters.encodings[0].codec_payload_type.reset(); + + // Configure the senders' and receivers' parameters. + // + // Note: Intentionally, the top codec in the receive parameters does not + // match the codec sent by the other side. If "Receive" is called with a list + // of codecs, the receiver should be prepared to receive any of them, not + // just the one on top. + EXPECT_TRUE(audio_receiver1->Receive(opus_receive_parameters).ok()); + EXPECT_TRUE(video_receiver1->Receive(vp8_receive_parameters).ok()); + EXPECT_TRUE(audio_receiver2->Receive(isac_receive_parameters).ok()); + EXPECT_TRUE(video_receiver2->Receive(vp9_receive_parameters).ok()); + EXPECT_TRUE(audio_sender1->Send(opus_send_parameters).ok()); + EXPECT_TRUE(video_sender1->Send(vp8_send_parameters).ok()); + EXPECT_TRUE(audio_sender2->Send(isac_send_parameters).ok()); + EXPECT_TRUE(video_sender2->Send(vp9_send_parameters).ok()); + + FakeVideoTrackRenderer fake_video_renderer1( + static_cast(video_receiver1->GetTrack().get())); + FakeVideoTrackRenderer fake_video_renderer2( + static_cast(video_receiver2->GetTrack().get())); + + // Senders and receivers are connected and configured; audio and video frames + // should be able to flow at this point. + EXPECT_TRUE_WAIT( + fake_audio_capture_module1_->frames_received() > kDefaultNumFrames && + fake_video_renderer1.num_rendered_frames() > kDefaultNumFrames && + fake_audio_capture_module2_->frames_received() > kDefaultNumFrames && + fake_video_renderer2.num_rendered_frames() > kDefaultNumFrames, + kDefaultTimeout); +} + +// TODO(deadbeef): End-to-end test for multiple senders/receivers of the same +// media type, once that's supported. Currently, it is not because the +// BaseChannel model relies on there being a single VoiceChannel and +// VideoChannel, and these only support a single set of codecs/etc. per +// send/receive direction. + +// TODO(deadbeef): End-to-end test for simulcast, once that's supported by this +// API. + +} // namespace webrtc diff --git a/webrtc/ortc/ortcfactory_unittest.cc b/webrtc/ortc/ortcfactory_unittest.cc new file mode 100644 index 0000000000..80e679b10e --- /dev/null +++ b/webrtc/ortc/ortcfactory_unittest.cc @@ -0,0 +1,240 @@ +/* + * 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 + +#include "webrtc/base/fakenetwork.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/virtualsocketserver.h" +#include "webrtc/media/base/fakemediaengine.h" +#include "webrtc/ortc/ortcfactory.h" +#include "webrtc/ortc/testrtpparameters.h" +#include "webrtc/p2p/base/fakepackettransport.h" + +namespace webrtc { + +// This test uses a virtual network and fake media engine, in order to test the +// OrtcFactory at only an API level. Any end-to-end test should go in +// ortcfactory_integrationtest.cc instead. +class OrtcFactoryTest : public testing::Test { + public: + OrtcFactoryTest() + : virtual_socket_server_(&physical_socket_server_), + socket_server_scope_(&virtual_socket_server_), + fake_packet_transport_("fake transport") { + ortc_factory_ = + OrtcFactory::Create(nullptr, nullptr, &fake_network_manager_, nullptr, + nullptr, + std::unique_ptr( + new cricket::FakeMediaEngine())) + .MoveValue(); + } + + protected: + // Uses a single pre-made FakePacketTransport, so shouldn't be called twice in + // the same test. + std::unique_ptr + CreateRtpTransportWithFakePacketTransport() { + return ortc_factory_ + ->CreateRtpTransport(MakeRtcpMuxParameters(), &fake_packet_transport_, + nullptr, nullptr) + .MoveValue(); + } + + rtc::PhysicalSocketServer physical_socket_server_; + rtc::VirtualSocketServer virtual_socket_server_; + rtc::SocketServerScope socket_server_scope_; + rtc::FakeNetworkManager fake_network_manager_; + rtc::FakePacketTransport fake_packet_transport_; + std::unique_ptr ortc_factory_; +}; + +TEST_F(OrtcFactoryTest, CanCreateMultipleRtpTransportControllers) { + auto controller_result1 = ortc_factory_->CreateRtpTransportController(); + EXPECT_TRUE(controller_result1.ok()); + auto controller_result2 = ortc_factory_->CreateRtpTransportController(); + EXPECT_TRUE(controller_result1.ok()); +} + +// Simple test for the successful cases of CreateRtpTransport. +TEST_F(OrtcFactoryTest, CreateRtpTransportWithAndWithoutMux) { + rtc::FakePacketTransport rtp("rtp"); + rtc::FakePacketTransport rtcp("rtcp"); + // With muxed RTCP. + RtcpParameters rtcp_parameters; + rtcp_parameters.mux = true; + auto result = ortc_factory_->CreateRtpTransport(rtcp_parameters, &rtp, + nullptr, nullptr); + EXPECT_TRUE(result.ok()); + result.MoveValue().reset(); + // With non-muxed RTCP. + rtcp_parameters.mux = false; + result = + ortc_factory_->CreateRtpTransport(rtcp_parameters, &rtp, &rtcp, nullptr); + EXPECT_TRUE(result.ok()); +} + +// If no CNAME is provided, one should be generated and returned by +// GetRtpParameters. +TEST_F(OrtcFactoryTest, CreateRtpTransportGeneratesCname) { + rtc::FakePacketTransport rtp("rtp"); + RtcpParameters rtcp_parameters; + rtcp_parameters.mux = true; + auto result = ortc_factory_->CreateRtpTransport(rtcp_parameters, &rtp, + nullptr, nullptr); + ASSERT_TRUE(result.ok()); + EXPECT_FALSE(result.value()->GetRtcpParameters().cname.empty()); +} + +// Extension of the above test; multiple transports created by the same factory +// should use the same generated CNAME. +TEST_F(OrtcFactoryTest, MultipleRtpTransportsUseSameGeneratedCname) { + rtc::FakePacketTransport packet_transport1("1"); + rtc::FakePacketTransport packet_transport2("2"); + RtcpParameters rtcp_parameters; + rtcp_parameters.mux = true; + // Sanity check. + ASSERT_TRUE(rtcp_parameters.cname.empty()); + auto result = ortc_factory_->CreateRtpTransport( + rtcp_parameters, &packet_transport1, nullptr, nullptr); + ASSERT_TRUE(result.ok()); + auto rtp_transport1 = result.MoveValue(); + result = ortc_factory_->CreateRtpTransport( + rtcp_parameters, &packet_transport2, nullptr, nullptr); + ASSERT_TRUE(result.ok()); + auto rtp_transport2 = result.MoveValue(); + RtcpParameters params1 = rtp_transport1->GetRtcpParameters(); + RtcpParameters params2 = rtp_transport2->GetRtcpParameters(); + EXPECT_FALSE(params1.cname.empty()); + EXPECT_EQ(params1.cname, params2.cname); +} + +TEST_F(OrtcFactoryTest, CreateRtpTransportWithNoPacketTransport) { + auto result = ortc_factory_->CreateRtpTransport(MakeRtcpMuxParameters(), + nullptr, nullptr, nullptr); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, result.error().type()); +} + +// If the |mux| member of the RtcpParameters is false, both an RTP and RTCP +// packet transport are needed. +TEST_F(OrtcFactoryTest, CreateRtpTransportWithMissingRtcpTransport) { + rtc::FakePacketTransport rtp("rtp"); + RtcpParameters rtcp_parameters; + rtcp_parameters.mux = false; + auto result = ortc_factory_->CreateRtpTransport(rtcp_parameters, &rtp, + nullptr, nullptr); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, result.error().type()); +} + +// If the |mux| member of the RtcpParameters is true, only an RTP packet +// transport is necessary. So, passing in an RTCP transport is most likely +// an accident, and thus should be treated as an error. +TEST_F(OrtcFactoryTest, CreateRtpTransportWithExtraneousRtcpTransport) { + rtc::FakePacketTransport rtp("rtp"); + rtc::FakePacketTransport rtcp("rtcp"); + RtcpParameters rtcp_parameters; + rtcp_parameters.mux = true; + auto result = + ortc_factory_->CreateRtpTransport(rtcp_parameters, &rtp, &rtcp, nullptr); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, result.error().type()); +} + +// Basic test that CreateUdpTransport works with AF_INET and AF_INET6. +TEST_F(OrtcFactoryTest, CreateUdpTransport) { + auto result = ortc_factory_->CreateUdpTransport(AF_INET); + EXPECT_TRUE(result.ok()); + result = ortc_factory_->CreateUdpTransport(AF_INET6); + EXPECT_TRUE(result.ok()); +} + +// Test CreateUdpPort with the |min_port| and |max_port| arguments. +TEST_F(OrtcFactoryTest, CreateUdpTransportWithPortRange) { + auto socket_result1 = ortc_factory_->CreateUdpTransport(AF_INET, 2000, 2002); + ASSERT_TRUE(socket_result1.ok()); + EXPECT_EQ(2000, socket_result1.value()->GetLocalAddress().port()); + auto socket_result2 = ortc_factory_->CreateUdpTransport(AF_INET, 2000, 2002); + ASSERT_TRUE(socket_result2.ok()); + EXPECT_EQ(2001, socket_result2.value()->GetLocalAddress().port()); + auto socket_result3 = ortc_factory_->CreateUdpTransport(AF_INET, 2000, 2002); + ASSERT_TRUE(socket_result3.ok()); + EXPECT_EQ(2002, socket_result3.value()->GetLocalAddress().port()); + + // All sockets in the range have been exhausted, so the next call should + // fail. + auto failed_result = ortc_factory_->CreateUdpTransport(AF_INET, 2000, 2002); + EXPECT_EQ(RTCErrorType::RESOURCE_EXHAUSTED, failed_result.error().type()); + + // If one socket is destroyed, that port should be freed up again. + socket_result2.MoveValue().reset(); + auto socket_result4 = ortc_factory_->CreateUdpTransport(AF_INET, 2000, 2002); + ASSERT_TRUE(socket_result4.ok()); + EXPECT_EQ(2001, socket_result4.value()->GetLocalAddress().port()); +} + +// Basic test that CreateUdpTransport works with AF_INET and AF_INET6. +TEST_F(OrtcFactoryTest, CreateUdpTransportWithInvalidAddressFamily) { + auto result = ortc_factory_->CreateUdpTransport(12345); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, result.error().type()); +} + +TEST_F(OrtcFactoryTest, CreateUdpTransportWithInvalidPortRange) { + auto result = ortc_factory_->CreateUdpTransport(AF_INET, 3000, 2000); + EXPECT_EQ(RTCErrorType::INVALID_RANGE, result.error().type()); +} + +// Just sanity check that each "GetCapabilities" method returns some codecs. +TEST_F(OrtcFactoryTest, GetSenderAndReceiverCapabilities) { + RtpCapabilities audio_send_caps = + ortc_factory_->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_AUDIO); + EXPECT_GT(audio_send_caps.codecs.size(), 0u); + RtpCapabilities video_send_caps = + ortc_factory_->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_VIDEO); + EXPECT_GT(video_send_caps.codecs.size(), 0u); + RtpCapabilities audio_receive_caps = + ortc_factory_->GetRtpReceiverCapabilities(cricket::MEDIA_TYPE_AUDIO); + EXPECT_GT(audio_receive_caps.codecs.size(), 0u); + RtpCapabilities video_receive_caps = + ortc_factory_->GetRtpReceiverCapabilities(cricket::MEDIA_TYPE_VIDEO); + EXPECT_GT(video_receive_caps.codecs.size(), 0u); +} + +// Calling CreateRtpSender with a null track should fail, since that makes it +// impossible to know whether to create an audio or video sender. The +// application should be using the method that takes a cricket::MediaType +// instead. +TEST_F(OrtcFactoryTest, CreateSenderWithNullTrack) { + auto rtp_transport = CreateRtpTransportWithFakePacketTransport(); + auto result = ortc_factory_->CreateRtpSender(nullptr, rtp_transport.get()); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, result.error().type()); +} + +// Calling CreateRtpSender or CreateRtpReceiver with MEDIA_TYPE_DATA should +// fail. +TEST_F(OrtcFactoryTest, CreateSenderOrReceieverWithInvalidKind) { + auto rtp_transport = CreateRtpTransportWithFakePacketTransport(); + auto sender_result = ortc_factory_->CreateRtpSender(cricket::MEDIA_TYPE_DATA, + rtp_transport.get()); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, sender_result.error().type()); + auto receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_DATA, rtp_transport.get()); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, receiver_result.error().type()); +} + +TEST_F(OrtcFactoryTest, CreateSendersOrReceieversWithNullTransport) { + auto sender_result = + ortc_factory_->CreateRtpSender(cricket::MEDIA_TYPE_AUDIO, nullptr); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, sender_result.error().type()); + auto receiver_result = + ortc_factory_->CreateRtpReceiver(cricket::MEDIA_TYPE_AUDIO, nullptr); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, receiver_result.error().type()); +} + +} // namespace webrtc diff --git a/webrtc/ortc/ortcrtpreceiver_unittest.cc b/webrtc/ortc/ortcrtpreceiver_unittest.cc new file mode 100644 index 0000000000..1764af0db5 --- /dev/null +++ b/webrtc/ortc/ortcrtpreceiver_unittest.cc @@ -0,0 +1,547 @@ +/* + * 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 + +#include "webrtc/base/gunit.h" +#include "webrtc/media/base/fakemediaengine.h" +#include "webrtc/p2p/base/fakepackettransport.h" +#include "webrtc/ortc/ortcfactory.h" +#include "webrtc/ortc/testrtpparameters.h" +#include "webrtc/pc/test/fakevideotracksource.h" + +namespace webrtc { + +// This test uses an individual RtpReceiver using only the public interface, +// and verifies that it behaves as designed at an API level. Also tests that +// parameters are applied to the audio/video engines as expected. Network and +// media interfaces are faked to isolate what's being tested. +// +// This test shouldn't result any any actual media being sent. That sort of +// test should go in ortcfactory_integrationtest.cc. +class OrtcRtpReceiverTest : public testing::Test { + public: + OrtcRtpReceiverTest() : fake_packet_transport_("fake") { + fake_media_engine_ = new cricket::FakeMediaEngine(); + // Note: This doesn't need to use fake network classes, since we already + // use FakePacketTransport. + auto ortc_factory_result = OrtcFactory::Create( + nullptr, nullptr, nullptr, nullptr, nullptr, + std::unique_ptr(fake_media_engine_)); + ortc_factory_ = ortc_factory_result.MoveValue(); + RtcpParameters rtcp_parameters; + rtcp_parameters.mux = true; + auto rtp_transport_result = ortc_factory_->CreateRtpTransport( + rtcp_parameters, &fake_packet_transport_, nullptr, nullptr); + rtp_transport_ = rtp_transport_result.MoveValue(); + } + + protected: + // Owned by |ortc_factory_|. + cricket::FakeMediaEngine* fake_media_engine_; + rtc::FakePacketTransport fake_packet_transport_; + std::unique_ptr ortc_factory_; + std::unique_ptr rtp_transport_; +}; + +// See ortcrtpreceiverinterface.h for the current expectations of what GetTrack +// will return after calls to Receive. +// TODO(deadbeef): Replace this test when the non-standard behavior is fixed +// and GetTrack starts returning the same track for the lifetime of the +// receiver. +TEST_F(OrtcRtpReceiverTest, GetTrack) { + auto receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_VIDEO, rtp_transport_.get()); + ASSERT_TRUE(receiver_result.ok()); + auto receiver = receiver_result.MoveValue(); + + // Track initially expected to be null. + EXPECT_EQ(nullptr, receiver_result.value().get()); + + EXPECT_TRUE(receiver->Receive(MakeMinimalVp8ParametersWithNoSsrc()).ok()); + auto initial_track = receiver->GetTrack(); + EXPECT_NE(nullptr, initial_track); + + // Codec changing but SSRC (or lack thereof) isn't; shouldn't create new track + EXPECT_TRUE(receiver->Receive(MakeMinimalVp9ParametersWithNoSsrc()).ok()); + EXPECT_EQ(initial_track, receiver->GetTrack()); + + // Explicitly set SSRC and expect a different track. + EXPECT_TRUE( + receiver->Receive(MakeMinimalVp9ParametersWithSsrc(0xdeadbeef)).ok()); + auto next_track = receiver->GetTrack(); + EXPECT_NE(next_track, initial_track); + + // Deactivating the encoding shouldn't change the track. + RtpParameters inactive_encoding = + MakeMinimalVp9ParametersWithSsrc(0xdeadbeef); + inactive_encoding.encodings[0].active = false; + EXPECT_TRUE(receiver->Receive(inactive_encoding).ok()); + EXPECT_EQ(next_track, receiver->GetTrack()); + + // Removing all encodings *is* expected to clear the track. + RtpParameters no_encodings = MakeMinimalVp9ParametersWithSsrc(0xdeadbeef); + no_encodings.encodings.clear(); + EXPECT_TRUE(receiver->Receive(no_encodings).ok()); + EXPECT_EQ(nullptr, receiver->GetTrack()); +} + +// Currently SetTransport isn't supported. When it is, replace this test with a +// test/tests for it. +TEST_F(OrtcRtpReceiverTest, SetTransportFails) { + rtc::FakePacketTransport fake_packet_transport("another_transport"); + RtcpParameters rtcp_parameters; + rtcp_parameters.mux = true; + auto rtp_transport_result = ortc_factory_->CreateRtpTransport( + rtcp_parameters, &fake_packet_transport, nullptr, nullptr); + auto rtp_transport = rtp_transport_result.MoveValue(); + + auto receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_AUDIO, rtp_transport_.get()); + auto receiver = receiver_result.MoveValue(); + EXPECT_EQ(RTCErrorType::UNSUPPORTED_OPERATION, + receiver->SetTransport(rtp_transport.get()).type()); +} + +TEST_F(OrtcRtpReceiverTest, GetTransport) { + auto result = ortc_factory_->CreateRtpReceiver(cricket::MEDIA_TYPE_AUDIO, + rtp_transport_.get()); + EXPECT_EQ(rtp_transport_.get(), result.value()->GetTransport()); +} + +// Test that "Receive" causes the expected parameters to be applied to the media +// engine level, for an audio receiver. +TEST_F(OrtcRtpReceiverTest, ReceiveAppliesAudioParametersToMediaEngine) { + auto audio_receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_AUDIO, rtp_transport_.get()); + auto audio_receiver = audio_receiver_result.MoveValue(); + + // First, create parameters with all the bells and whistles. + RtpParameters parameters; + + RtpCodecParameters opus_codec; + opus_codec.name = "opus"; + opus_codec.kind = cricket::MEDIA_TYPE_AUDIO; + opus_codec.payload_type = 120; + opus_codec.clock_rate.emplace(48000); + opus_codec.num_channels.emplace(2); + opus_codec.parameters["minptime"] = "10"; + opus_codec.rtcp_feedback.emplace_back(RtcpFeedbackType::TRANSPORT_CC); + parameters.codecs.push_back(std::move(opus_codec)); + + // Add two codecs, expecting the first to be used. + // TODO(deadbeef): Once "codec_payload_type" is supported, use it to select a + // codec that's not at the top of the list. + RtpCodecParameters isac_codec; + isac_codec.name = "ISAC"; + isac_codec.kind = cricket::MEDIA_TYPE_AUDIO; + isac_codec.payload_type = 110; + isac_codec.clock_rate.emplace(16000); + parameters.codecs.push_back(std::move(isac_codec)); + + RtpEncodingParameters encoding; + encoding.ssrc.emplace(0xdeadbeef); + parameters.encodings.push_back(std::move(encoding)); + + parameters.header_extensions.emplace_back( + "urn:ietf:params:rtp-hdrext:ssrc-audio-level", 3); + + EXPECT_TRUE(audio_receiver->Receive(parameters).ok()); + + // Now verify that the parameters were applied to the fake media engine layer + // that exists below BaseChannel. + cricket::FakeVoiceMediaChannel* fake_voice_channel = + fake_media_engine_->GetVoiceChannel(0); + ASSERT_NE(nullptr, fake_voice_channel); + EXPECT_TRUE(fake_voice_channel->playout()); + + // Verify codec parameters. + ASSERT_GT(fake_voice_channel->recv_codecs().size(), 0u); + const cricket::AudioCodec& top_codec = fake_voice_channel->recv_codecs()[0]; + EXPECT_EQ("opus", top_codec.name); + EXPECT_EQ(120, top_codec.id); + EXPECT_EQ(48000, top_codec.clockrate); + EXPECT_EQ(2u, top_codec.channels); + ASSERT_NE(top_codec.params.end(), top_codec.params.find("minptime")); + EXPECT_EQ("10", top_codec.params.at("minptime")); + + // Verify encoding parameters. + ASSERT_EQ(1u, fake_voice_channel->recv_streams().size()); + const cricket::StreamParams& recv_stream = + fake_voice_channel->recv_streams()[0]; + EXPECT_EQ(1u, recv_stream.ssrcs.size()); + EXPECT_EQ(0xdeadbeef, recv_stream.first_ssrc()); + + // Verify header extensions. + ASSERT_EQ(1u, fake_voice_channel->recv_extensions().size()); + const RtpExtension& extension = fake_voice_channel->recv_extensions()[0]; + EXPECT_EQ("urn:ietf:params:rtp-hdrext:ssrc-audio-level", extension.uri); + EXPECT_EQ(3, extension.id); +} + +// Test that "Receive" causes the expected parameters to be applied to the media +// engine level, for a video receiver. +TEST_F(OrtcRtpReceiverTest, ReceiveAppliesVideoParametersToMediaEngine) { + auto video_receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_VIDEO, rtp_transport_.get()); + auto video_receiver = video_receiver_result.MoveValue(); + + // First, create parameters with all the bells and whistles. + RtpParameters parameters; + + RtpCodecParameters vp8_codec; + vp8_codec.name = "VP8"; + vp8_codec.kind = cricket::MEDIA_TYPE_VIDEO; + vp8_codec.payload_type = 99; + // Try a couple types of feedback params. "Generic NACK" is a bit of a + // special case, so test it here. + vp8_codec.rtcp_feedback.emplace_back(RtcpFeedbackType::CCM, + RtcpFeedbackMessageType::FIR); + vp8_codec.rtcp_feedback.emplace_back(RtcpFeedbackType::NACK, + RtcpFeedbackMessageType::GENERIC_NACK); + parameters.codecs.push_back(std::move(vp8_codec)); + + RtpCodecParameters vp8_rtx_codec; + vp8_rtx_codec.name = "rtx"; + vp8_rtx_codec.kind = cricket::MEDIA_TYPE_VIDEO; + vp8_rtx_codec.payload_type = 100; + vp8_rtx_codec.parameters["apt"] = "99"; + parameters.codecs.push_back(std::move(vp8_rtx_codec)); + + // Add two codecs, expecting the first to be used. + // TODO(deadbeef): Once "codec_payload_type" is supported, use it to select a + // codec that's not at the top of the list. + RtpCodecParameters vp9_codec; + vp9_codec.name = "VP9"; + vp9_codec.kind = cricket::MEDIA_TYPE_VIDEO; + vp9_codec.payload_type = 102; + parameters.codecs.push_back(std::move(vp9_codec)); + + RtpCodecParameters vp9_rtx_codec; + vp9_rtx_codec.name = "rtx"; + vp9_rtx_codec.kind = cricket::MEDIA_TYPE_VIDEO; + vp9_rtx_codec.payload_type = 103; + vp9_rtx_codec.parameters["apt"] = "102"; + parameters.codecs.push_back(std::move(vp9_rtx_codec)); + + RtpEncodingParameters encoding; + encoding.ssrc.emplace(0xdeadbeef); + encoding.rtx.emplace(0xbaadfeed); + parameters.encodings.push_back(std::move(encoding)); + + parameters.header_extensions.emplace_back("urn:3gpp:video-orientation", 4); + parameters.header_extensions.emplace_back( + "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay", 6); + + EXPECT_TRUE(video_receiver->Receive(parameters).ok()); + + // Now verify that the parameters were applied to the fake media engine layer + // that exists below BaseChannel. + cricket::FakeVideoMediaChannel* fake_video_channel = + fake_media_engine_->GetVideoChannel(0); + ASSERT_NE(nullptr, fake_video_channel); + + // Verify codec parameters. + ASSERT_GE(fake_video_channel->recv_codecs().size(), 2u); + const cricket::VideoCodec& top_codec = fake_video_channel->recv_codecs()[0]; + EXPECT_EQ("VP8", top_codec.name); + EXPECT_EQ(99, top_codec.id); + EXPECT_TRUE(top_codec.feedback_params.Has({"ccm", "fir"})); + EXPECT_TRUE(top_codec.feedback_params.Has(cricket::FeedbackParam("nack"))); + + const cricket::VideoCodec& rtx_codec = fake_video_channel->recv_codecs()[1]; + EXPECT_EQ("rtx", rtx_codec.name); + EXPECT_EQ(100, rtx_codec.id); + ASSERT_NE(rtx_codec.params.end(), rtx_codec.params.find("apt")); + EXPECT_EQ("99", rtx_codec.params.at("apt")); + + // Verify encoding parameters. + ASSERT_EQ(1u, fake_video_channel->recv_streams().size()); + const cricket::StreamParams& recv_stream = + fake_video_channel->recv_streams()[0]; + EXPECT_EQ(2u, recv_stream.ssrcs.size()); + EXPECT_EQ(0xdeadbeef, recv_stream.first_ssrc()); + uint32_t rtx_ssrc = 0u; + EXPECT_TRUE(recv_stream.GetFidSsrc(recv_stream.first_ssrc(), &rtx_ssrc)); + EXPECT_EQ(0xbaadfeed, rtx_ssrc); + + // Verify header extensions. + ASSERT_EQ(2u, fake_video_channel->recv_extensions().size()); + const RtpExtension& extension1 = fake_video_channel->recv_extensions()[0]; + EXPECT_EQ("urn:3gpp:video-orientation", extension1.uri); + EXPECT_EQ(4, extension1.id); + const RtpExtension& extension2 = fake_video_channel->recv_extensions()[1]; + EXPECT_EQ("http://www.webrtc.org/experiments/rtp-hdrext/playout-delay", + extension2.uri); + EXPECT_EQ(6, extension2.id); +} + +// Test changing both the receive codec and SSRC at the same time, and verify +// that the new parameters are applied to the media engine level. +TEST_F(OrtcRtpReceiverTest, CallingReceiveTwiceChangesParameters) { + auto audio_receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_AUDIO, rtp_transport_.get()); + auto audio_receiver = audio_receiver_result.MoveValue(); + RTCError error = + audio_receiver->Receive(MakeMinimalOpusParametersWithSsrc(0x11111111)); + EXPECT_TRUE(error.ok()); + error = + audio_receiver->Receive(MakeMinimalIsacParametersWithSsrc(0x22222222)); + EXPECT_TRUE(error.ok()); + + cricket::FakeVoiceMediaChannel* fake_voice_channel = + fake_media_engine_->GetVoiceChannel(0); + ASSERT_NE(nullptr, fake_voice_channel); + ASSERT_GT(fake_voice_channel->recv_codecs().size(), 0u); + EXPECT_EQ("ISAC", fake_voice_channel->recv_codecs()[0].name); + ASSERT_EQ(1u, fake_voice_channel->recv_streams().size()); + EXPECT_EQ(0x22222222u, fake_voice_channel->recv_streams()[0].first_ssrc()); + + auto video_receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_VIDEO, rtp_transport_.get()); + auto video_receiver = video_receiver_result.MoveValue(); + error = video_receiver->Receive(MakeMinimalVp8ParametersWithSsrc(0x33333333)); + EXPECT_TRUE(error.ok()); + error = video_receiver->Receive(MakeMinimalVp9ParametersWithSsrc(0x44444444)); + EXPECT_TRUE(error.ok()); + + cricket::FakeVideoMediaChannel* fake_video_channel = + fake_media_engine_->GetVideoChannel(0); + ASSERT_NE(nullptr, fake_video_channel); + ASSERT_GT(fake_video_channel->recv_codecs().size(), 0u); + EXPECT_EQ("VP9", fake_video_channel->recv_codecs()[0].name); + ASSERT_EQ(1u, fake_video_channel->recv_streams().size()); + EXPECT_EQ(0x44444444u, fake_video_channel->recv_streams()[0].first_ssrc()); +} + +// Ensure that if the |active| flag of RtpEncodingParameters is set to false, +// playout stops at the media engine level. Note that this is only applicable +// to audio (at least currently). +TEST_F(OrtcRtpReceiverTest, DeactivatingEncodingStopsPlayout) { + auto audio_receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_AUDIO, rtp_transport_.get()); + auto audio_receiver = audio_receiver_result.MoveValue(); + RtpParameters parameters = MakeMinimalOpusParameters(); + EXPECT_TRUE(audio_receiver->Receive(parameters).ok()); + + // Expect "playout" flag to initially be true. + cricket::FakeVoiceMediaChannel* fake_voice_channel = + fake_media_engine_->GetVoiceChannel(0); + ASSERT_NE(nullptr, fake_voice_channel); + EXPECT_TRUE(fake_voice_channel->playout()); + + // Deactivate encoding and expect it to change to false. + parameters.encodings[0].active = false; + EXPECT_TRUE(audio_receiver->Receive(parameters).ok()); + EXPECT_FALSE(fake_voice_channel->playout()); +} + +// Ensure that calling Receive with an empty list of encodings causes receive +// streams at the media engine level to be cleared. +TEST_F(OrtcRtpReceiverTest, + CallingReceiveWithEmptyEncodingsClearsReceiveStreams) { + auto audio_receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_AUDIO, rtp_transport_.get()); + auto audio_receiver = audio_receiver_result.MoveValue(); + RtpParameters parameters = MakeMinimalOpusParameters(); + EXPECT_TRUE(audio_receiver->Receive(parameters).ok()); + parameters.encodings.clear(); + EXPECT_TRUE(audio_receiver->Receive(parameters).ok()); + + cricket::FakeVoiceMediaChannel* fake_voice_channel = + fake_media_engine_->GetVoiceChannel(0); + ASSERT_NE(nullptr, fake_voice_channel); + EXPECT_TRUE(fake_voice_channel->recv_streams().empty()); + + auto video_receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_VIDEO, rtp_transport_.get()); + auto video_receiver = video_receiver_result.MoveValue(); + parameters = MakeMinimalVp8Parameters(); + EXPECT_TRUE(video_receiver->Receive(parameters).ok()); + parameters.encodings.clear(); + EXPECT_TRUE(video_receiver->Receive(parameters).ok()); + + cricket::FakeVideoMediaChannel* fake_video_channel = + fake_media_engine_->GetVideoChannel(0); + ASSERT_NE(nullptr, fake_video_channel); + EXPECT_TRUE(fake_video_channel->recv_streams().empty()); +} + +// These errors should be covered by rtpparametersconversion_unittest.cc, but +// we should at least test that those errors are propogated from calls to +// Receive, with a few examples. +TEST_F(OrtcRtpReceiverTest, ReceiveReturnsErrorOnInvalidParameters) { + auto result = ortc_factory_->CreateRtpReceiver(cricket::MEDIA_TYPE_AUDIO, + rtp_transport_.get()); + auto receiver = result.MoveValue(); + // CCM feedback missing message type. + RtpParameters invalid_feedback = MakeMinimalOpusParameters(); + invalid_feedback.codecs[0].rtcp_feedback.emplace_back(RtcpFeedbackType::CCM); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, + receiver->Receive(invalid_feedback).type()); + // Payload type greater than 127. + RtpParameters invalid_pt = MakeMinimalOpusParameters(); + invalid_pt.codecs[0].payload_type = 128; + EXPECT_EQ(RTCErrorType::INVALID_RANGE, receiver->Receive(invalid_pt).type()); + // Duplicate header extension IDs. + RtpParameters duplicate_ids = MakeMinimalOpusParameters(); + duplicate_ids.header_extensions.emplace_back("foo", 5); + duplicate_ids.header_extensions.emplace_back("bar", 5); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, + receiver->Receive(duplicate_ids).type()); +} + +// Two receivers using the same transport shouldn't be able to use the same +// payload type to refer to different codecs, same header extension IDs to +// refer to different extensions, or same SSRC. +TEST_F(OrtcRtpReceiverTest, ReceiveReturnsErrorOnIdConflicts) { + auto audio_receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_AUDIO, rtp_transport_.get()); + auto video_receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_VIDEO, rtp_transport_.get()); + auto audio_receiver = audio_receiver_result.MoveValue(); + auto video_receiver = video_receiver_result.MoveValue(); + + // First test payload type conflict. + RtpParameters audio_parameters = MakeMinimalOpusParameters(); + RtpParameters video_parameters = MakeMinimalVp8Parameters(); + audio_parameters.codecs[0].payload_type = 100; + video_parameters.codecs[0].payload_type = 100; + EXPECT_TRUE(audio_receiver->Receive(audio_parameters).ok()); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, + video_receiver->Receive(video_parameters).type()); + + // Test header extension ID conflict. + video_parameters.codecs[0].payload_type = 110; + audio_parameters.header_extensions.emplace_back("foo", 4); + video_parameters.header_extensions.emplace_back("bar", 4); + EXPECT_TRUE(audio_receiver->Receive(audio_parameters).ok()); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, + video_receiver->Receive(video_parameters).type()); + + // Test SSRC conflict. Have an RTX SSRC that conflicts with a primary SSRC + // for extra challenge. + video_parameters.header_extensions[0].uri = "foo"; + audio_parameters.encodings[0].ssrc.emplace(0xabbaabba); + audio_parameters.encodings[0].rtx.emplace(0xdeadbeef); + video_parameters.encodings[0].ssrc.emplace(0xdeadbeef); + EXPECT_TRUE(audio_receiver->Receive(audio_parameters).ok()); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, + video_receiver->Receive(video_parameters).type()); + + // Sanity check that parameters can be set if the conflicts are all resolved. + video_parameters.encodings[0].ssrc.emplace(0xbaadf00d); + EXPECT_TRUE(video_receiver->Receive(video_parameters).ok()); +} + +// Ensure that deleting a receiver causes receive streams at the media engine +// level to be cleared. +TEST_F(OrtcRtpReceiverTest, DeletingReceiverClearsReceiveStreams) { + auto audio_receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_AUDIO, rtp_transport_.get()); + auto audio_receiver = audio_receiver_result.MoveValue(); + EXPECT_TRUE(audio_receiver->Receive(MakeMinimalOpusParameters()).ok()); + + // Also create an audio sender, to prevent the voice channel from being + // completely deleted. + auto audio_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_AUDIO, rtp_transport_.get()); + auto audio_sender = audio_sender_result.MoveValue(); + EXPECT_TRUE(audio_sender->Send(MakeMinimalOpusParameters()).ok()); + + audio_receiver.reset(nullptr); + cricket::FakeVoiceMediaChannel* fake_voice_channel = + fake_media_engine_->GetVoiceChannel(0); + ASSERT_NE(nullptr, fake_voice_channel); + EXPECT_TRUE(fake_voice_channel->recv_streams().empty()); + + auto video_receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_VIDEO, rtp_transport_.get()); + auto video_receiver = video_receiver_result.MoveValue(); + EXPECT_TRUE(video_receiver->Receive(MakeMinimalVp8Parameters()).ok()); + + // Also create an video sender, to prevent the video channel from being + // completely deleted. + auto video_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_VIDEO, rtp_transport_.get()); + auto video_sender = video_sender_result.MoveValue(); + EXPECT_TRUE(video_sender->Send(MakeMinimalVp8Parameters()).ok()); + + video_receiver.reset(nullptr); + cricket::FakeVideoMediaChannel* fake_video_channel = + fake_media_engine_->GetVideoChannel(0); + ASSERT_NE(nullptr, fake_video_channel); + EXPECT_TRUE(fake_video_channel->recv_streams().empty()); +} + +// If Receive hasn't been called, GetParameters should return empty parameters. +TEST_F(OrtcRtpReceiverTest, GetDefaultParameters) { + auto result = ortc_factory_->CreateRtpReceiver(cricket::MEDIA_TYPE_AUDIO, + rtp_transport_.get()); + EXPECT_EQ(RtpParameters(), result.value()->GetParameters()); + result = ortc_factory_->CreateRtpReceiver(cricket::MEDIA_TYPE_VIDEO, + rtp_transport_.get()); + EXPECT_EQ(RtpParameters(), result.value()->GetParameters()); +} + +// Test that GetParameters returns the last parameters passed into Receive, +// along with the implementation-default values filled in where they were left +// unset. +TEST_F(OrtcRtpReceiverTest, + GetParametersReturnsLastSetParametersWithDefaultsFilled) { + auto audio_receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_AUDIO, rtp_transport_.get()); + auto audio_receiver = audio_receiver_result.MoveValue(); + + RtpParameters opus_parameters = MakeMinimalOpusParameters(); + EXPECT_TRUE(audio_receiver->Receive(opus_parameters).ok()); + EXPECT_EQ(opus_parameters, audio_receiver->GetParameters()); + + RtpParameters isac_parameters = MakeMinimalIsacParameters(); + // Sanity check that num_channels actually is left unset. + ASSERT_FALSE(isac_parameters.codecs[0].num_channels); + EXPECT_TRUE(audio_receiver->Receive(isac_parameters).ok()); + // Should be filled with a default "num channels" of 1. + // TODO(deadbeef): This should actually default to 2 for some codecs. Update + // this test once that's implemented. + isac_parameters.codecs[0].num_channels.emplace(1); + EXPECT_EQ(isac_parameters, audio_receiver->GetParameters()); + + auto video_receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_VIDEO, rtp_transport_.get()); + auto video_receiver = video_receiver_result.MoveValue(); + + RtpParameters vp8_parameters = MakeMinimalVp8Parameters(); + // Sanity check that clock_rate actually is left unset. + EXPECT_TRUE(video_receiver->Receive(vp8_parameters).ok()); + // Should be filled with a default clock rate of 90000. + vp8_parameters.codecs[0].clock_rate.emplace(90000); + EXPECT_EQ(vp8_parameters, video_receiver->GetParameters()); + + RtpParameters vp9_parameters = MakeMinimalVp9Parameters(); + // Sanity check that clock_rate actually is left unset. + EXPECT_TRUE(video_receiver->Receive(vp9_parameters).ok()); + // Should be filled with a default clock rate of 90000. + vp9_parameters.codecs[0].clock_rate.emplace(90000); + EXPECT_EQ(vp9_parameters, video_receiver->GetParameters()); +} + +TEST_F(OrtcRtpReceiverTest, GetKind) { + auto audio_receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_AUDIO, rtp_transport_.get()); + auto video_receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_VIDEO, rtp_transport_.get()); + auto audio_receiver = audio_receiver_result.MoveValue(); + auto video_receiver = video_receiver_result.MoveValue(); + EXPECT_EQ(cricket::MEDIA_TYPE_AUDIO, audio_receiver->GetKind()); + EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, video_receiver->GetKind()); +} + +} // namespace webrtc diff --git a/webrtc/ortc/ortcrtpreceiveradapter.cc b/webrtc/ortc/ortcrtpreceiveradapter.cc new file mode 100644 index 0000000000..aefa4d313f --- /dev/null +++ b/webrtc/ortc/ortcrtpreceiveradapter.cc @@ -0,0 +1,168 @@ +/* + * 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/ortcrtpreceiveradapter.h" + +#include + +#include "webrtc/base/checks.h" +#include "webrtc/base/helpers.h" // For "CreateRandomX". +#include "webrtc/media/base/mediaconstants.h" +#include "webrtc/ortc/rtptransportadapter.h" + +namespace { + +void FillAudioReceiverParameters(webrtc::RtpParameters* parameters) { + for (webrtc::RtpCodecParameters& codec : parameters->codecs) { + if (!codec.num_channels) { + codec.num_channels = rtc::Optional(1); + } + } +} + +void FillVideoReceiverParameters(webrtc::RtpParameters* parameters) { + for (webrtc::RtpCodecParameters& codec : parameters->codecs) { + if (!codec.clock_rate) { + codec.clock_rate = rtc::Optional(cricket::kVideoCodecClockrate); + } + } +} + +} // namespace + +namespace webrtc { + +BEGIN_OWNED_PROXY_MAP(OrtcRtpReceiver) +PROXY_SIGNALING_THREAD_DESTRUCTOR() +PROXY_CONSTMETHOD0(rtc::scoped_refptr, GetTrack) +PROXY_METHOD1(RTCError, SetTransport, RtpTransportInterface*) +PROXY_CONSTMETHOD0(RtpTransportInterface*, GetTransport) +PROXY_METHOD1(RTCError, Receive, const RtpParameters&) +PROXY_CONSTMETHOD0(RtpParameters, GetParameters) +PROXY_CONSTMETHOD0(cricket::MediaType, GetKind) +END_PROXY_MAP() + +// static +std::unique_ptr OrtcRtpReceiverAdapter::CreateProxy( + std::unique_ptr wrapped_receiver) { + RTC_DCHECK(wrapped_receiver); + rtc::Thread* signaling = + wrapped_receiver->rtp_transport_controller_->signaling_thread(); + rtc::Thread* worker = + wrapped_receiver->rtp_transport_controller_->worker_thread(); + return OrtcRtpReceiverProxy::Create(signaling, worker, + std::move(wrapped_receiver)); +} + +OrtcRtpReceiverAdapter::~OrtcRtpReceiverAdapter() { + internal_receiver_ = nullptr; + SignalDestroyed(); +} + +rtc::scoped_refptr OrtcRtpReceiverAdapter::GetTrack() + const { + return internal_receiver_ ? internal_receiver_->track() : nullptr; +} + +RTCError OrtcRtpReceiverAdapter::SetTransport( + RtpTransportInterface* transport) { + LOG_AND_RETURN_ERROR( + RTCErrorType::UNSUPPORTED_OPERATION, + "Changing the transport of an RtpReceiver is not yet supported."); +} + +RtpTransportInterface* OrtcRtpReceiverAdapter::GetTransport() const { + return transport_; +} + +RTCError OrtcRtpReceiverAdapter::Receive(const RtpParameters& parameters) { + RtpParameters filled_parameters = parameters; + RTCError err; + switch (kind_) { + case cricket::MEDIA_TYPE_AUDIO: + FillAudioReceiverParameters(&filled_parameters); + err = rtp_transport_controller_->ValidateAndApplyAudioReceiverParameters( + filled_parameters); + if (!err.ok()) { + return err; + } + break; + case cricket::MEDIA_TYPE_VIDEO: + FillVideoReceiverParameters(&filled_parameters); + err = rtp_transport_controller_->ValidateAndApplyVideoReceiverParameters( + filled_parameters); + if (!err.ok()) { + return err; + } + break; + case cricket::MEDIA_TYPE_DATA: + RTC_NOTREACHED(); + return webrtc::RTCError(webrtc::RTCErrorType::INTERNAL_ERROR); + } + last_applied_parameters_ = filled_parameters; + + // Now that parameters were applied, can create (or recreate) the internal + // receiver. + // + // This is analogous to a PeerConnection creating a receiver after + // SetRemoteDescription is successful. + MaybeRecreateInternalReceiver(); + return RTCError::OK(); +} + +RtpParameters OrtcRtpReceiverAdapter::GetParameters() const { + return last_applied_parameters_; +} + +cricket::MediaType OrtcRtpReceiverAdapter::GetKind() const { + return kind_; +} + +OrtcRtpReceiverAdapter::OrtcRtpReceiverAdapter( + cricket::MediaType kind, + RtpTransportInterface* transport, + RtpTransportControllerAdapter* rtp_transport_controller) + : kind_(kind), + transport_(transport), + rtp_transport_controller_(rtp_transport_controller) {} + +void OrtcRtpReceiverAdapter::MaybeRecreateInternalReceiver() { + if (last_applied_parameters_.encodings.empty()) { + internal_receiver_ = nullptr; + return; + } + // An SSRC of 0 is valid; this is used to identify "the default SSRC" (which + // is the first one seen by the underlying media engine). + uint32_t ssrc = 0; + if (last_applied_parameters_.encodings[0].ssrc) { + ssrc = *last_applied_parameters_.encodings[0].ssrc; + } + if (internal_receiver_ && ssrc == internal_receiver_->ssrc()) { + // SSRC not changing; nothing to do. + return; + } + internal_receiver_ = nullptr; + switch (kind_) { + case cricket::MEDIA_TYPE_AUDIO: + internal_receiver_ = + new AudioRtpReceiver(rtc::CreateRandomUuid(), ssrc, + rtp_transport_controller_->voice_channel()); + break; + case cricket::MEDIA_TYPE_VIDEO: + internal_receiver_ = new VideoRtpReceiver( + rtc::CreateRandomUuid(), rtp_transport_controller_->worker_thread(), + ssrc, rtp_transport_controller_->video_channel()); + break; + case cricket::MEDIA_TYPE_DATA: + RTC_NOTREACHED(); + } +} + +} // namespace webrtc diff --git a/webrtc/ortc/ortcrtpreceiveradapter.h b/webrtc/ortc/ortcrtpreceiveradapter.h new file mode 100644 index 0000000000..8e081e7547 --- /dev/null +++ b/webrtc/ortc/ortcrtpreceiveradapter.h @@ -0,0 +1,79 @@ +/* + * 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. + */ + +#ifndef WEBRTC_ORTC_ORTCRTPRECEIVERADAPTER_H_ +#define WEBRTC_ORTC_ORTCRTPRECEIVERADAPTER_H_ + +#include + +#include "webrtc/api/ortc/ortcrtpreceiverinterface.h" +#include "webrtc/api/rtcerror.h" +#include "webrtc/api/rtpparameters.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/thread.h" +#include "webrtc/ortc/rtptransportcontrolleradapter.h" +#include "webrtc/pc/rtpreceiver.h" + +namespace webrtc { + +// Implementation of OrtcRtpReceiverInterface that works with +// RtpTransportAdapter, and wraps a VideoRtpReceiver/AudioRtpReceiver that's +// normally used with the PeerConnection. +// +// TODO(deadbeef): When BaseChannel is split apart into separate +// "RtpReceiver"/"RtpTransceiver"/"RtpReceiver"/"RtpReceiver" objects, this +// adapter object can be removed. +class OrtcRtpReceiverAdapter : public OrtcRtpReceiverInterface { + public: + // Wraps |wrapped_receiver| in a proxy that will safely call methods on the + // correct thread. + static std::unique_ptr CreateProxy( + std::unique_ptr wrapped_receiver); + + // Should only be called by RtpTransportControllerAdapter. + OrtcRtpReceiverAdapter( + cricket::MediaType kind, + RtpTransportInterface* transport, + RtpTransportControllerAdapter* rtp_transport_controller); + ~OrtcRtpReceiverAdapter() override; + + // OrtcRtpReceiverInterface implementation. + rtc::scoped_refptr GetTrack() const override; + + RTCError SetTransport(RtpTransportInterface* transport) override; + RtpTransportInterface* GetTransport() const override; + + RTCError Receive(const RtpParameters& parameters) override; + RtpParameters GetParameters() const override; + + cricket::MediaType GetKind() const override; + + // Used so that the RtpTransportControllerAdapter knows when it can + // deallocate resources allocated for this object. + sigslot::signal0<> SignalDestroyed; + + private: + void MaybeRecreateInternalReceiver(); + + cricket::MediaType kind_; + RtpTransportInterface* transport_; + RtpTransportControllerAdapter* rtp_transport_controller_; + // Scoped refptr due to ref-counted interface, but we should be the only + // reference holder. + rtc::scoped_refptr internal_receiver_; + RtpParameters last_applied_parameters_; + + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(OrtcRtpReceiverAdapter); +}; + +} // namespace webrtc + +#endif // WEBRTC_ORTC_ORTCRTPRECEIVERADAPTER_H_ diff --git a/webrtc/ortc/ortcrtpsender_unittest.cc b/webrtc/ortc/ortcrtpsender_unittest.cc new file mode 100644 index 0000000000..954b9978a3 --- /dev/null +++ b/webrtc/ortc/ortcrtpsender_unittest.cc @@ -0,0 +1,667 @@ +/* + * 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 + +#include "webrtc/base/gunit.h" +#include "webrtc/media/base/fakemediaengine.h" +#include "webrtc/p2p/base/fakepackettransport.h" +#include "webrtc/ortc/ortcfactory.h" +#include "webrtc/ortc/testrtpparameters.h" +#include "webrtc/pc/test/fakevideotracksource.h" + +namespace webrtc { + +// This test uses an individual RtpSender using only the public interface, and +// verifies that its behaves as designed at an API level. Also tests that +// parameters are applied to the audio/video engines as expected. Network and +// media interfaces are faked to isolate what's being tested. +// +// This test shouldn't result any any actual media being sent. That sort of +// test should go in ortcfactory_integrationtest.cc. +class OrtcRtpSenderTest : public testing::Test { + public: + OrtcRtpSenderTest() : fake_packet_transport_("fake") { + // Need to set the fake packet transport to writable, in order to test that + // the "send" flag is applied to the media engine based on the encoding + // |active| flag. + fake_packet_transport_.SetWritable(true); + fake_media_engine_ = new cricket::FakeMediaEngine(); + // Note: This doesn't need to use fake network classes, since we already + // use FakePacketTransport. + auto ortc_factory_result = OrtcFactory::Create( + nullptr, nullptr, nullptr, nullptr, nullptr, + std::unique_ptr(fake_media_engine_)); + ortc_factory_ = ortc_factory_result.MoveValue(); + RtcpParameters rtcp_parameters; + rtcp_parameters.mux = true; + auto rtp_transport_result = ortc_factory_->CreateRtpTransport( + rtcp_parameters, &fake_packet_transport_, nullptr, nullptr); + rtp_transport_ = rtp_transport_result.MoveValue(); + } + + protected: + rtc::scoped_refptr CreateAudioTrack( + const std::string& id) { + return ortc_factory_->CreateAudioTrack(id, nullptr); + } + + rtc::scoped_refptr CreateVideoTrack( + const std::string& id) { + return rtc::scoped_refptr( + ortc_factory_->CreateVideoTrack(id, FakeVideoTrackSource::Create())); + } + + // Owned by |ortc_factory_|. + cricket::FakeMediaEngine* fake_media_engine_; + rtc::FakePacketTransport fake_packet_transport_; + std::unique_ptr ortc_factory_; + std::unique_ptr rtp_transport_; +}; + +TEST_F(OrtcRtpSenderTest, GetAndSetTrack) { + // Test GetTrack with a sender constructed with a track. + auto audio_track = CreateAudioTrack("audio"); + auto audio_sender_result = + ortc_factory_->CreateRtpSender(audio_track, rtp_transport_.get()); + auto audio_sender = audio_sender_result.MoveValue(); + EXPECT_EQ(audio_track, audio_sender->GetTrack()); + + // Test GetTrack after SetTrack. + auto video_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_VIDEO, rtp_transport_.get()); + auto video_sender = video_sender_result.MoveValue(); + auto video_track = CreateVideoTrack("video1"); + EXPECT_TRUE(video_sender->SetTrack(video_track).ok()); + EXPECT_EQ(video_track, video_sender->GetTrack()); + video_track = CreateVideoTrack("video2"); + EXPECT_TRUE(video_sender->SetTrack(video_track).ok()); + EXPECT_EQ(video_track, video_sender->GetTrack()); +} + +// Test that track can be set when previously unset, even after Send has been +// called. +TEST_F(OrtcRtpSenderTest, SetTrackWhileSending) { + auto audio_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_AUDIO, rtp_transport_.get()); + auto audio_sender = audio_sender_result.MoveValue(); + EXPECT_TRUE(audio_sender->Send(MakeMinimalOpusParameters()).ok()); + EXPECT_TRUE(audio_sender->SetTrack(CreateAudioTrack("audio")).ok()); + + auto video_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_VIDEO, rtp_transport_.get()); + auto video_sender = video_sender_result.MoveValue(); + EXPECT_TRUE(video_sender->Send(MakeMinimalVp8Parameters()).ok()); + EXPECT_TRUE(video_sender->SetTrack(CreateVideoTrack("video")).ok()); +} + +// Test that track can be changed mid-sending. Differs from the above test in +// that the track is set and being changed, rather than unset and being set for +// the first time. +TEST_F(OrtcRtpSenderTest, ChangeTrackWhileSending) { + auto audio_sender_result = ortc_factory_->CreateRtpSender( + CreateAudioTrack("audio1"), rtp_transport_.get()); + auto audio_sender = audio_sender_result.MoveValue(); + EXPECT_TRUE(audio_sender->Send(MakeMinimalOpusParameters()).ok()); + EXPECT_TRUE(audio_sender->SetTrack(CreateAudioTrack("audio2")).ok()); + + auto video_sender_result = ortc_factory_->CreateRtpSender( + CreateVideoTrack("video1"), rtp_transport_.get()); + auto video_sender = video_sender_result.MoveValue(); + EXPECT_TRUE(video_sender->Send(MakeMinimalVp8Parameters()).ok()); + EXPECT_TRUE(video_sender->SetTrack(CreateVideoTrack("video2")).ok()); +} + +// Test that track can be set to null while sending. +TEST_F(OrtcRtpSenderTest, UnsetTrackWhileSending) { + auto audio_sender_result = ortc_factory_->CreateRtpSender( + CreateAudioTrack("audio"), rtp_transport_.get()); + auto audio_sender = audio_sender_result.MoveValue(); + EXPECT_TRUE(audio_sender->Send(MakeMinimalOpusParameters()).ok()); + EXPECT_TRUE(audio_sender->SetTrack(nullptr).ok()); + + auto video_sender_result = ortc_factory_->CreateRtpSender( + CreateVideoTrack("video"), rtp_transport_.get()); + auto video_sender = video_sender_result.MoveValue(); + EXPECT_TRUE(video_sender->Send(MakeMinimalVp8Parameters()).ok()); + EXPECT_TRUE(video_sender->SetTrack(nullptr).ok()); +} + +// Shouldn't be able to set an audio track on a video sender or vice versa. +TEST_F(OrtcRtpSenderTest, SetTrackOfWrongKindFails) { + auto audio_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_AUDIO, rtp_transport_.get()); + auto audio_sender = audio_sender_result.MoveValue(); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, + audio_sender->SetTrack(CreateVideoTrack("video")).type()); + + auto video_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_VIDEO, rtp_transport_.get()); + auto video_sender = video_sender_result.MoveValue(); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, + video_sender->SetTrack(CreateAudioTrack("audio")).type()); +} + +// Currently SetTransport isn't supported. When it is, replace this test with a +// test/tests for it. +TEST_F(OrtcRtpSenderTest, SetTransportFails) { + rtc::FakePacketTransport fake_packet_transport("another_transport"); + RtcpParameters rtcp_parameters; + rtcp_parameters.mux = true; + auto rtp_transport_result = ortc_factory_->CreateRtpTransport( + rtcp_parameters, &fake_packet_transport, nullptr, nullptr); + auto rtp_transport = rtp_transport_result.MoveValue(); + + auto sender_result = ortc_factory_->CreateRtpSender(cricket::MEDIA_TYPE_AUDIO, + rtp_transport_.get()); + auto sender = sender_result.MoveValue(); + EXPECT_EQ(RTCErrorType::UNSUPPORTED_OPERATION, + sender->SetTransport(rtp_transport.get()).type()); +} + +TEST_F(OrtcRtpSenderTest, GetTransport) { + auto result = ortc_factory_->CreateRtpSender(cricket::MEDIA_TYPE_AUDIO, + rtp_transport_.get()); + EXPECT_EQ(rtp_transport_.get(), result.value()->GetTransport()); +} + +// Test that "Send" causes the expected parameters to be applied to the media +// engine level, for an audio sender. +TEST_F(OrtcRtpSenderTest, SendAppliesAudioParametersToMediaEngine) { + auto audio_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_AUDIO, rtp_transport_.get()); + auto audio_sender = audio_sender_result.MoveValue(); + + // First, create parameters with all the bells and whistles. + RtpParameters parameters; + + RtpCodecParameters opus_codec; + opus_codec.name = "opus"; + opus_codec.kind = cricket::MEDIA_TYPE_AUDIO; + opus_codec.payload_type = 120; + opus_codec.clock_rate.emplace(48000); + opus_codec.num_channels.emplace(2); + opus_codec.parameters["minptime"] = "10"; + opus_codec.rtcp_feedback.emplace_back(RtcpFeedbackType::TRANSPORT_CC); + parameters.codecs.push_back(std::move(opus_codec)); + + // Add two codecs, expecting the first to be used. + // TODO(deadbeef): Once "codec_payload_type" is supported, use it to select a + // codec that's not at the top of the list. + RtpCodecParameters isac_codec; + isac_codec.name = "ISAC"; + isac_codec.kind = cricket::MEDIA_TYPE_AUDIO; + isac_codec.payload_type = 110; + isac_codec.clock_rate.emplace(16000); + parameters.codecs.push_back(std::move(isac_codec)); + + RtpEncodingParameters encoding; + encoding.ssrc.emplace(0xdeadbeef); + encoding.max_bitrate_bps.emplace(20000); + parameters.encodings.push_back(std::move(encoding)); + + parameters.header_extensions.emplace_back( + "urn:ietf:params:rtp-hdrext:ssrc-audio-level", 3); + + EXPECT_TRUE(audio_sender->Send(parameters).ok()); + + // Now verify that the parameters were applied to the fake media engine layer + // that exists below BaseChannel. + cricket::FakeVoiceMediaChannel* fake_voice_channel = + fake_media_engine_->GetVoiceChannel(0); + ASSERT_NE(nullptr, fake_voice_channel); + EXPECT_TRUE(fake_voice_channel->sending()); + + // Verify codec parameters. + ASSERT_GT(fake_voice_channel->send_codecs().size(), 0u); + const cricket::AudioCodec& top_codec = fake_voice_channel->send_codecs()[0]; + EXPECT_EQ("opus", top_codec.name); + EXPECT_EQ(120, top_codec.id); + EXPECT_EQ(48000, top_codec.clockrate); + EXPECT_EQ(2u, top_codec.channels); + ASSERT_NE(top_codec.params.end(), top_codec.params.find("minptime")); + EXPECT_EQ("10", top_codec.params.at("minptime")); + + // Verify encoding parameters. + EXPECT_EQ(20000, fake_voice_channel->max_bps()); + ASSERT_EQ(1u, fake_voice_channel->send_streams().size()); + const cricket::StreamParams& send_stream = + fake_voice_channel->send_streams()[0]; + EXPECT_EQ(1u, send_stream.ssrcs.size()); + EXPECT_EQ(0xdeadbeef, send_stream.first_ssrc()); + + // Verify header extensions. + ASSERT_EQ(1u, fake_voice_channel->send_extensions().size()); + const RtpExtension& extension = fake_voice_channel->send_extensions()[0]; + EXPECT_EQ("urn:ietf:params:rtp-hdrext:ssrc-audio-level", extension.uri); + EXPECT_EQ(3, extension.id); +} + +// Test that "Send" causes the expected parameters to be applied to the media +// engine level, for a video sender. +TEST_F(OrtcRtpSenderTest, SendAppliesVideoParametersToMediaEngine) { + auto video_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_VIDEO, rtp_transport_.get()); + auto video_sender = video_sender_result.MoveValue(); + + // First, create parameters with all the bells and whistles. + RtpParameters parameters; + + RtpCodecParameters vp8_codec; + vp8_codec.name = "VP8"; + vp8_codec.kind = cricket::MEDIA_TYPE_VIDEO; + vp8_codec.payload_type = 99; + // Try a couple types of feedback params. "Generic NACK" is a bit of a + // special case, so test it here. + vp8_codec.rtcp_feedback.emplace_back(RtcpFeedbackType::CCM, + RtcpFeedbackMessageType::FIR); + vp8_codec.rtcp_feedback.emplace_back(RtcpFeedbackType::NACK, + RtcpFeedbackMessageType::GENERIC_NACK); + parameters.codecs.push_back(std::move(vp8_codec)); + + RtpCodecParameters vp8_rtx_codec; + vp8_rtx_codec.name = "rtx"; + vp8_rtx_codec.kind = cricket::MEDIA_TYPE_VIDEO; + vp8_rtx_codec.payload_type = 100; + vp8_rtx_codec.parameters["apt"] = "99"; + parameters.codecs.push_back(std::move(vp8_rtx_codec)); + + // Add two codecs, expecting the first to be used. + // TODO(deadbeef): Once "codec_payload_type" is supported, use it to select a + // codec that's not at the top of the list. + RtpCodecParameters vp9_codec; + vp9_codec.name = "VP9"; + vp9_codec.kind = cricket::MEDIA_TYPE_VIDEO; + vp9_codec.payload_type = 102; + parameters.codecs.push_back(std::move(vp9_codec)); + + RtpCodecParameters vp9_rtx_codec; + vp9_rtx_codec.name = "rtx"; + vp9_rtx_codec.kind = cricket::MEDIA_TYPE_VIDEO; + vp9_rtx_codec.payload_type = 103; + vp9_rtx_codec.parameters["apt"] = "102"; + parameters.codecs.push_back(std::move(vp9_rtx_codec)); + + RtpEncodingParameters encoding; + encoding.ssrc.emplace(0xdeadbeef); + encoding.rtx.emplace(0xbaadfeed); + encoding.max_bitrate_bps.emplace(99999); + parameters.encodings.push_back(std::move(encoding)); + + parameters.header_extensions.emplace_back("urn:3gpp:video-orientation", 4); + parameters.header_extensions.emplace_back( + "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay", 6); + + EXPECT_TRUE(video_sender->Send(parameters).ok()); + + // Now verify that the parameters were applied to the fake media engine layer + // that exists below BaseChannel. + cricket::FakeVideoMediaChannel* fake_video_channel = + fake_media_engine_->GetVideoChannel(0); + ASSERT_NE(nullptr, fake_video_channel); + EXPECT_TRUE(fake_video_channel->sending()); + + // Verify codec parameters. + ASSERT_GE(fake_video_channel->send_codecs().size(), 2u); + const cricket::VideoCodec& top_codec = fake_video_channel->send_codecs()[0]; + EXPECT_EQ("VP8", top_codec.name); + EXPECT_EQ(99, top_codec.id); + EXPECT_TRUE(top_codec.feedback_params.Has({"ccm", "fir"})); + EXPECT_TRUE(top_codec.feedback_params.Has(cricket::FeedbackParam("nack"))); + + const cricket::VideoCodec& rtx_codec = fake_video_channel->send_codecs()[1]; + EXPECT_EQ("rtx", rtx_codec.name); + EXPECT_EQ(100, rtx_codec.id); + ASSERT_NE(rtx_codec.params.end(), rtx_codec.params.find("apt")); + EXPECT_EQ("99", rtx_codec.params.at("apt")); + + // Verify encoding parameters. + EXPECT_EQ(99999, fake_video_channel->max_bps()); + ASSERT_EQ(1u, fake_video_channel->send_streams().size()); + const cricket::StreamParams& send_stream = + fake_video_channel->send_streams()[0]; + EXPECT_EQ(2u, send_stream.ssrcs.size()); + EXPECT_EQ(0xdeadbeef, send_stream.first_ssrc()); + uint32_t rtx_ssrc = 0u; + EXPECT_TRUE(send_stream.GetFidSsrc(send_stream.first_ssrc(), &rtx_ssrc)); + EXPECT_EQ(0xbaadfeed, rtx_ssrc); + + // Verify header extensions. + ASSERT_EQ(2u, fake_video_channel->send_extensions().size()); + const RtpExtension& extension1 = fake_video_channel->send_extensions()[0]; + EXPECT_EQ("urn:3gpp:video-orientation", extension1.uri); + EXPECT_EQ(4, extension1.id); + const RtpExtension& extension2 = fake_video_channel->send_extensions()[1]; + EXPECT_EQ("http://www.webrtc.org/experiments/rtp-hdrext/playout-delay", + extension2.uri); + EXPECT_EQ(6, extension2.id); +} + +// Ensure that when primary or RTX SSRCs are left unset, they're generated +// automatically. +TEST_F(OrtcRtpSenderTest, SendGeneratesSsrcsWhenEmpty) { + auto audio_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_AUDIO, rtp_transport_.get()); + auto audio_sender = audio_sender_result.MoveValue(); + RtpParameters parameters = MakeMinimalOpusParametersWithNoSsrc(); + // Default RTX parameters, with no SSRC. + parameters.encodings[0].rtx.emplace(); + EXPECT_TRUE(audio_sender->Send(parameters).ok()); + + cricket::FakeVoiceMediaChannel* fake_voice_channel = + fake_media_engine_->GetVoiceChannel(0); + ASSERT_NE(nullptr, fake_voice_channel); + ASSERT_EQ(1u, fake_voice_channel->send_streams().size()); + const cricket::StreamParams& audio_send_stream = + fake_voice_channel->send_streams()[0]; + EXPECT_NE(0u, audio_send_stream.first_ssrc()); + uint32_t rtx_ssrc = 0u; + EXPECT_TRUE( + audio_send_stream.GetFidSsrc(audio_send_stream.first_ssrc(), &rtx_ssrc)); + EXPECT_NE(0u, rtx_ssrc); + EXPECT_NE(audio_send_stream.first_ssrc(), rtx_ssrc); + + auto video_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_VIDEO, rtp_transport_.get()); + auto video_sender = video_sender_result.MoveValue(); + parameters = MakeMinimalVp8ParametersWithNoSsrc(); + // Default RTX parameters, with no SSRC. + parameters.encodings[0].rtx.emplace(); + EXPECT_TRUE(video_sender->Send(parameters).ok()); + + cricket::FakeVideoMediaChannel* fake_video_channel = + fake_media_engine_->GetVideoChannel(0); + ASSERT_NE(nullptr, fake_video_channel); + ASSERT_EQ(1u, fake_video_channel->send_streams().size()); + const cricket::StreamParams& video_send_stream = + fake_video_channel->send_streams()[0]; + EXPECT_NE(0u, video_send_stream.first_ssrc()); + rtx_ssrc = 0u; + EXPECT_TRUE( + video_send_stream.GetFidSsrc(video_send_stream.first_ssrc(), &rtx_ssrc)); + EXPECT_NE(0u, rtx_ssrc); + EXPECT_NE(video_send_stream.first_ssrc(), rtx_ssrc); + EXPECT_NE(video_send_stream.first_ssrc(), audio_send_stream.first_ssrc()); +} + +// Test changing both the send codec and SSRC at the same time, and verify that +// the new parameters are applied to the media engine level. +TEST_F(OrtcRtpSenderTest, CallingSendTwiceChangesParameters) { + auto audio_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_AUDIO, rtp_transport_.get()); + auto audio_sender = audio_sender_result.MoveValue(); + EXPECT_TRUE( + audio_sender->Send(MakeMinimalOpusParametersWithSsrc(0x11111111)).ok()); + EXPECT_TRUE( + audio_sender->Send(MakeMinimalIsacParametersWithSsrc(0x22222222)).ok()); + + cricket::FakeVoiceMediaChannel* fake_voice_channel = + fake_media_engine_->GetVoiceChannel(0); + ASSERT_NE(nullptr, fake_voice_channel); + ASSERT_GT(fake_voice_channel->send_codecs().size(), 0u); + EXPECT_EQ("ISAC", fake_voice_channel->send_codecs()[0].name); + ASSERT_EQ(1u, fake_voice_channel->send_streams().size()); + EXPECT_EQ(0x22222222u, fake_voice_channel->send_streams()[0].first_ssrc()); + + auto video_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_VIDEO, rtp_transport_.get()); + auto video_sender = video_sender_result.MoveValue(); + EXPECT_TRUE( + video_sender->Send(MakeMinimalVp8ParametersWithSsrc(0x33333333)).ok()); + EXPECT_TRUE( + video_sender->Send(MakeMinimalVp9ParametersWithSsrc(0x44444444)).ok()); + + cricket::FakeVideoMediaChannel* fake_video_channel = + fake_media_engine_->GetVideoChannel(0); + ASSERT_NE(nullptr, fake_video_channel); + ASSERT_GT(fake_video_channel->send_codecs().size(), 0u); + EXPECT_EQ("VP9", fake_video_channel->send_codecs()[0].name); + ASSERT_EQ(1u, fake_video_channel->send_streams().size()); + EXPECT_EQ(0x44444444u, fake_video_channel->send_streams()[0].first_ssrc()); +} + +// Ensure that if the |active| flag of RtpEncodingParameters is set to false, +// sending stops at the media engine level. +TEST_F(OrtcRtpSenderTest, DeactivatingEncodingStopsSending) { + auto audio_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_AUDIO, rtp_transport_.get()); + auto audio_sender = audio_sender_result.MoveValue(); + RtpParameters parameters = MakeMinimalOpusParameters(); + EXPECT_TRUE(audio_sender->Send(parameters).ok()); + + // Expect "sending" flag to initially be true. + cricket::FakeVoiceMediaChannel* fake_voice_channel = + fake_media_engine_->GetVoiceChannel(0); + ASSERT_NE(nullptr, fake_voice_channel); + EXPECT_TRUE(fake_voice_channel->sending()); + + // Deactivate encoding and expect it to change to false. + parameters.encodings[0].active = false; + EXPECT_TRUE(audio_sender->Send(parameters).ok()); + EXPECT_FALSE(fake_voice_channel->sending()); + + // Try the same thing for video now. + auto video_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_VIDEO, rtp_transport_.get()); + auto video_sender = video_sender_result.MoveValue(); + parameters = MakeMinimalVp8Parameters(); + EXPECT_TRUE(video_sender->Send(parameters).ok()); + + cricket::FakeVideoMediaChannel* fake_video_channel = + fake_media_engine_->GetVideoChannel(0); + ASSERT_NE(nullptr, fake_video_channel); + EXPECT_TRUE(fake_video_channel->sending()); + + parameters.encodings[0].active = false; + EXPECT_TRUE(video_sender->Send(parameters).ok()); + EXPECT_FALSE(fake_video_channel->sending()); +} + +// Ensure that calling Send with an empty list of encodings causes send streams +// at the media engine level to be cleared. +TEST_F(OrtcRtpSenderTest, CallingSendWithEmptyEncodingsClearsSendStreams) { + auto audio_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_AUDIO, rtp_transport_.get()); + auto audio_sender = audio_sender_result.MoveValue(); + RtpParameters parameters = MakeMinimalOpusParameters(); + EXPECT_TRUE(audio_sender->Send(parameters).ok()); + parameters.encodings.clear(); + EXPECT_TRUE(audio_sender->Send(parameters).ok()); + + cricket::FakeVoiceMediaChannel* fake_voice_channel = + fake_media_engine_->GetVoiceChannel(0); + ASSERT_NE(nullptr, fake_voice_channel); + EXPECT_TRUE(fake_voice_channel->send_streams().empty()); + + auto video_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_VIDEO, rtp_transport_.get()); + auto video_sender = video_sender_result.MoveValue(); + parameters = MakeMinimalVp8Parameters(); + EXPECT_TRUE(video_sender->Send(parameters).ok()); + parameters.encodings.clear(); + EXPECT_TRUE(video_sender->Send(parameters).ok()); + + cricket::FakeVideoMediaChannel* fake_video_channel = + fake_media_engine_->GetVideoChannel(0); + ASSERT_NE(nullptr, fake_video_channel); + EXPECT_TRUE(fake_video_channel->send_streams().empty()); +} + +// These errors should be covered by rtpparametersconversion_unittest.cc, but +// we should at least test that those errors are propogated from calls to Send, +// with a few examples. +TEST_F(OrtcRtpSenderTest, SendReturnsErrorOnInvalidParameters) { + auto result = ortc_factory_->CreateRtpSender(cricket::MEDIA_TYPE_VIDEO, + rtp_transport_.get()); + auto sender = result.MoveValue(); + // NACK feedback missing message type. + RtpParameters invalid_feedback = MakeMinimalVp8Parameters(); + invalid_feedback.codecs[0].rtcp_feedback.emplace_back(RtcpFeedbackType::NACK); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, + sender->Send(invalid_feedback).type()); + // Negative payload type. + RtpParameters invalid_pt = MakeMinimalVp8Parameters(); + invalid_pt.codecs[0].payload_type = -1; + EXPECT_EQ(RTCErrorType::INVALID_RANGE, sender->Send(invalid_pt).type()); + // Duplicate codec payload types. + RtpParameters duplicate_payload_types = MakeMinimalVp8Parameters(); + duplicate_payload_types.codecs.push_back(duplicate_payload_types.codecs[0]); + duplicate_payload_types.codecs.back().name = "VP9"; + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, + sender->Send(duplicate_payload_types).type()); +} + +// Two senders using the same transport shouldn't be able to use the same +// payload type to refer to different codecs, same header extension IDs to +// refer to different extensions, or same SSRC. +TEST_F(OrtcRtpSenderTest, SendReturnsErrorOnIdConflicts) { + auto audio_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_AUDIO, rtp_transport_.get()); + auto video_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_VIDEO, rtp_transport_.get()); + auto audio_sender = audio_sender_result.MoveValue(); + auto video_sender = video_sender_result.MoveValue(); + + // First test payload type conflict. + RtpParameters audio_parameters = MakeMinimalOpusParameters(); + RtpParameters video_parameters = MakeMinimalVp8Parameters(); + audio_parameters.codecs[0].payload_type = 100; + video_parameters.codecs[0].payload_type = 100; + EXPECT_TRUE(audio_sender->Send(audio_parameters).ok()); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, + video_sender->Send(video_parameters).type()); + + // Test header extension ID conflict. + video_parameters.codecs[0].payload_type = 110; + audio_parameters.header_extensions.emplace_back("foo", 4); + video_parameters.header_extensions.emplace_back("bar", 4); + EXPECT_TRUE(audio_sender->Send(audio_parameters).ok()); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, + video_sender->Send(video_parameters).type()); + + // Test SSRC conflict. Have an RTX SSRC that conflicts with a primary SSRC + // for extra challenge. + video_parameters.header_extensions[0].uri = "foo"; + audio_parameters.encodings[0].ssrc.emplace(0xdeadbeef); + video_parameters.encodings[0].ssrc.emplace(0xabbaabba); + video_parameters.encodings[0].rtx.emplace(0xdeadbeef); + EXPECT_TRUE(audio_sender->Send(audio_parameters).ok()); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, + video_sender->Send(video_parameters).type()); + + // Sanity check that parameters can be set if the conflicts are all resolved. + video_parameters.encodings[0].rtx->ssrc.emplace(0xbaadf00d); + EXPECT_TRUE(video_sender->Send(video_parameters).ok()); +} + +// Ensure that deleting a sender causes send streams at the media engine level +// to be cleared. +TEST_F(OrtcRtpSenderTest, DeletingSenderClearsSendStreams) { + auto audio_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_AUDIO, rtp_transport_.get()); + auto audio_sender = audio_sender_result.MoveValue(); + EXPECT_TRUE(audio_sender->Send(MakeMinimalOpusParameters()).ok()); + + // Also create an audio receiver, to prevent the voice channel from being + // completely deleted. + auto audio_receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_AUDIO, rtp_transport_.get()); + auto audio_receiver = audio_receiver_result.MoveValue(); + EXPECT_TRUE(audio_receiver->Receive(MakeMinimalOpusParameters()).ok()); + + audio_sender.reset(nullptr); + cricket::FakeVoiceMediaChannel* fake_voice_channel = + fake_media_engine_->GetVoiceChannel(0); + ASSERT_NE(nullptr, fake_voice_channel); + EXPECT_TRUE(fake_voice_channel->send_streams().empty()); + + auto video_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_VIDEO, rtp_transport_.get()); + auto video_sender = video_sender_result.MoveValue(); + EXPECT_TRUE(video_sender->Send(MakeMinimalVp8Parameters()).ok()); + + // Also create an video receiver, to prevent the video channel from being + // completely deleted. + auto video_receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_VIDEO, rtp_transport_.get()); + auto video_receiver = video_receiver_result.MoveValue(); + EXPECT_TRUE(video_receiver->Receive(MakeMinimalVp8Parameters()).ok()); + + video_sender.reset(nullptr); + cricket::FakeVideoMediaChannel* fake_video_channel = + fake_media_engine_->GetVideoChannel(0); + ASSERT_NE(nullptr, fake_video_channel); + EXPECT_TRUE(fake_video_channel->send_streams().empty()); +} + +// If Send hasn't been called, GetParameters should return empty parameters. +TEST_F(OrtcRtpSenderTest, GetDefaultParameters) { + auto result = ortc_factory_->CreateRtpSender(cricket::MEDIA_TYPE_AUDIO, + rtp_transport_.get()); + EXPECT_EQ(RtpParameters(), result.value()->GetParameters()); + result = ortc_factory_->CreateRtpSender(cricket::MEDIA_TYPE_VIDEO, + rtp_transport_.get()); + EXPECT_EQ(RtpParameters(), result.value()->GetParameters()); +} + +// Test that GetParameters returns the last parameters passed into Send, along +// with the implementation-default values filled in where they were left unset. +TEST_F(OrtcRtpSenderTest, + GetParametersReturnsLastSetParametersWithDefaultsFilled) { + auto audio_sender_result = ortc_factory_->CreateRtpSender( + CreateAudioTrack("audio"), rtp_transport_.get()); + auto audio_sender = audio_sender_result.MoveValue(); + + RtpParameters opus_parameters = MakeMinimalOpusParameters(); + EXPECT_TRUE(audio_sender->Send(opus_parameters).ok()); + EXPECT_EQ(opus_parameters, audio_sender->GetParameters()); + + RtpParameters isac_parameters = MakeMinimalIsacParameters(); + // Sanity check that num_channels actually is left unset. + ASSERT_FALSE(isac_parameters.codecs[0].num_channels); + EXPECT_TRUE(audio_sender->Send(isac_parameters).ok()); + // Should be filled with a default "num channels" of 1. + // TODO(deadbeef): This should actually default to 2 for some codecs. Update + // this test once that's implemented. + isac_parameters.codecs[0].num_channels.emplace(1); + EXPECT_EQ(isac_parameters, audio_sender->GetParameters()); + + auto video_sender_result = ortc_factory_->CreateRtpSender( + CreateVideoTrack("video"), rtp_transport_.get()); + auto video_sender = video_sender_result.MoveValue(); + + RtpParameters vp8_parameters = MakeMinimalVp8Parameters(); + // Sanity check that clock_rate actually is left unset. + EXPECT_TRUE(video_sender->Send(vp8_parameters).ok()); + // Should be filled with a default clock rate of 90000. + vp8_parameters.codecs[0].clock_rate.emplace(90000); + EXPECT_EQ(vp8_parameters, video_sender->GetParameters()); + + RtpParameters vp9_parameters = MakeMinimalVp9Parameters(); + // Sanity check that clock_rate actually is left unset. + EXPECT_TRUE(video_sender->Send(vp9_parameters).ok()); + // Should be filled with a default clock rate of 90000. + vp9_parameters.codecs[0].clock_rate.emplace(90000); + EXPECT_EQ(vp9_parameters, video_sender->GetParameters()); +} + +TEST_F(OrtcRtpSenderTest, GetKind) { + // Construct one sender from the "kind" enum and another from a track. + auto audio_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_AUDIO, rtp_transport_.get()); + auto video_sender_result = ortc_factory_->CreateRtpSender( + CreateVideoTrack("video"), rtp_transport_.get()); + auto audio_sender = audio_sender_result.MoveValue(); + auto video_sender = video_sender_result.MoveValue(); + EXPECT_EQ(cricket::MEDIA_TYPE_AUDIO, audio_sender->GetKind()); + EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, video_sender->GetKind()); +} + +} // namespace webrtc diff --git a/webrtc/ortc/ortcrtpsenderadapter.cc b/webrtc/ortc/ortcrtpsenderadapter.cc new file mode 100644 index 0000000000..be2b65dac3 --- /dev/null +++ b/webrtc/ortc/ortcrtpsenderadapter.cc @@ -0,0 +1,178 @@ +/* + * 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/ortcrtpsenderadapter.h" + +#include + +#include "webrtc/base/checks.h" +#include "webrtc/media/base/mediaconstants.h" +#include "webrtc/ortc/rtptransportadapter.h" + +namespace { + +void FillAudioSenderParameters(webrtc::RtpParameters* parameters) { + for (webrtc::RtpCodecParameters& codec : parameters->codecs) { + if (!codec.num_channels) { + codec.num_channels = rtc::Optional(1); + } + } +} + +void FillVideoSenderParameters(webrtc::RtpParameters* parameters) { + for (webrtc::RtpCodecParameters& codec : parameters->codecs) { + if (!codec.clock_rate) { + codec.clock_rate = rtc::Optional(cricket::kVideoCodecClockrate); + } + } +} + +} // namespace + +namespace webrtc { + +BEGIN_OWNED_PROXY_MAP(OrtcRtpSender) +PROXY_SIGNALING_THREAD_DESTRUCTOR() +PROXY_METHOD1(RTCError, SetTrack, MediaStreamTrackInterface*) +PROXY_CONSTMETHOD0(rtc::scoped_refptr, GetTrack) +PROXY_METHOD1(RTCError, SetTransport, RtpTransportInterface*) +PROXY_CONSTMETHOD0(RtpTransportInterface*, GetTransport) +PROXY_METHOD1(RTCError, Send, const RtpParameters&) +PROXY_CONSTMETHOD0(RtpParameters, GetParameters) +PROXY_CONSTMETHOD0(cricket::MediaType, GetKind) +END_PROXY_MAP() + +// static +std::unique_ptr OrtcRtpSenderAdapter::CreateProxy( + std::unique_ptr wrapped_sender) { + RTC_DCHECK(wrapped_sender); + rtc::Thread* signaling = + wrapped_sender->rtp_transport_controller_->signaling_thread(); + rtc::Thread* worker = + wrapped_sender->rtp_transport_controller_->worker_thread(); + return OrtcRtpSenderProxy::Create(signaling, worker, + std::move(wrapped_sender)); +} + +OrtcRtpSenderAdapter::~OrtcRtpSenderAdapter() { + internal_sender_ = nullptr; + SignalDestroyed(); +} + +RTCError OrtcRtpSenderAdapter::SetTrack(MediaStreamTrackInterface* track) { + if (track && cricket::MediaTypeFromString(track->kind()) != kind_) { + LOG_AND_RETURN_ERROR( + RTCErrorType::INVALID_PARAMETER, + "Track kind (audio/video) doesn't match the kind of this sender."); + } + if (internal_sender_ && !internal_sender_->SetTrack(track)) { + // Since we checked the track type above, this should never happen... + RTC_NOTREACHED(); + LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR, + "Failed to set track on RtpSender."); + } + track_ = track; + return RTCError::OK(); +} + +rtc::scoped_refptr OrtcRtpSenderAdapter::GetTrack() + const { + return track_; +} + +RTCError OrtcRtpSenderAdapter::SetTransport(RtpTransportInterface* transport) { + LOG_AND_RETURN_ERROR( + RTCErrorType::UNSUPPORTED_OPERATION, + "Changing the transport of an RtpSender is not yet supported."); +} + +RtpTransportInterface* OrtcRtpSenderAdapter::GetTransport() const { + return transport_; +} + +RTCError OrtcRtpSenderAdapter::Send(const RtpParameters& parameters) { + RtpParameters filled_parameters = parameters; + RTCError err; + uint32_t ssrc = 0; + switch (kind_) { + case cricket::MEDIA_TYPE_AUDIO: + FillAudioSenderParameters(&filled_parameters); + err = rtp_transport_controller_->ValidateAndApplyAudioSenderParameters( + filled_parameters, &ssrc); + if (!err.ok()) { + return err; + } + break; + case cricket::MEDIA_TYPE_VIDEO: + FillVideoSenderParameters(&filled_parameters); + err = rtp_transport_controller_->ValidateAndApplyVideoSenderParameters( + filled_parameters, &ssrc); + if (!err.ok()) { + return err; + } + break; + case cricket::MEDIA_TYPE_DATA: + RTC_NOTREACHED(); + return webrtc::RTCError(webrtc::RTCErrorType::INTERNAL_ERROR); + } + last_applied_parameters_ = filled_parameters; + + // Now that parameters were applied, can call SetSsrc on the internal sender. + // This is analogous to a PeerConnection calling SetSsrc after + // SetLocalDescription is successful. + // + // If there were no encodings, this SSRC may be 0, which is valid. + if (!internal_sender_) { + CreateInternalSender(); + } + internal_sender_->SetSsrc(ssrc); + + return RTCError::OK(); +} + +RtpParameters OrtcRtpSenderAdapter::GetParameters() const { + return last_applied_parameters_; +} + +cricket::MediaType OrtcRtpSenderAdapter::GetKind() const { + return kind_; +} + +OrtcRtpSenderAdapter::OrtcRtpSenderAdapter( + cricket::MediaType kind, + RtpTransportInterface* transport, + RtpTransportControllerAdapter* rtp_transport_controller) + : kind_(kind), + transport_(transport), + rtp_transport_controller_(rtp_transport_controller) {} + +void OrtcRtpSenderAdapter::CreateInternalSender() { + switch (kind_) { + case cricket::MEDIA_TYPE_AUDIO: + internal_sender_ = new AudioRtpSender( + rtp_transport_controller_->voice_channel(), nullptr); + break; + case cricket::MEDIA_TYPE_VIDEO: + internal_sender_ = + new VideoRtpSender(rtp_transport_controller_->video_channel()); + break; + case cricket::MEDIA_TYPE_DATA: + RTC_NOTREACHED(); + } + if (track_) { + if (!internal_sender_->SetTrack(track_)) { + // Since we checked the track type when it was set, this should never + // happen... + RTC_NOTREACHED(); + } + } +} + +} // namespace webrtc diff --git a/webrtc/ortc/ortcrtpsenderadapter.h b/webrtc/ortc/ortcrtpsenderadapter.h new file mode 100644 index 0000000000..9b60f15300 --- /dev/null +++ b/webrtc/ortc/ortcrtpsenderadapter.h @@ -0,0 +1,79 @@ +/* + * 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. + */ + +#ifndef WEBRTC_ORTC_ORTCRTPSENDERADAPTER_H_ +#define WEBRTC_ORTC_ORTCRTPSENDERADAPTER_H_ + +#include + +#include "webrtc/api/ortc/ortcrtpsenderinterface.h" +#include "webrtc/api/rtcerror.h" +#include "webrtc/api/rtpparameters.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/ortc/rtptransportcontrolleradapter.h" +#include "webrtc/pc/rtpsender.h" + +namespace webrtc { + +// Implementation of OrtcRtpSenderInterface that works with RtpTransportAdapter, +// and wraps a VideoRtpSender/AudioRtpSender that's normally used with the +// PeerConnection. +// +// TODO(deadbeef): When BaseChannel is split apart into separate +// "RtpSender"/"RtpTransceiver"/"RtpSender"/"RtpReceiver" objects, this adapter +// object can be removed. +class OrtcRtpSenderAdapter : public OrtcRtpSenderInterface { + public: + // Wraps |wrapped_sender| in a proxy that will safely call methods on the + // correct thread. + static std::unique_ptr CreateProxy( + std::unique_ptr wrapped_sender); + + // Should only be called by RtpTransportControllerAdapter. + OrtcRtpSenderAdapter(cricket::MediaType kind, + RtpTransportInterface* transport, + RtpTransportControllerAdapter* rtp_transport_controller); + ~OrtcRtpSenderAdapter() override; + + // OrtcRtpSenderInterface implementation. + RTCError SetTrack(MediaStreamTrackInterface* track) override; + rtc::scoped_refptr GetTrack() const override; + + RTCError SetTransport(RtpTransportInterface* transport) override; + RtpTransportInterface* GetTransport() const override; + + RTCError Send(const RtpParameters& parameters) override; + RtpParameters GetParameters() const override; + + cricket::MediaType GetKind() const override; + + // Used so that the RtpTransportControllerAdapter knows when it can + // deallocate resources allocated for this object. + sigslot::signal0<> SignalDestroyed; + + private: + void CreateInternalSender(); + + cricket::MediaType kind_; + RtpTransportInterface* transport_; + RtpTransportControllerAdapter* rtp_transport_controller_; + // Scoped refptr due to ref-counted interface, but we should be the only + // reference holder. + rtc::scoped_refptr internal_sender_; + rtc::scoped_refptr track_; + RtpParameters last_applied_parameters_; + + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(OrtcRtpSenderAdapter); +}; + +} // namespace webrtc + +#endif // WEBRTC_ORTC_ORTCRTPSENDERADAPTER_H_ diff --git a/webrtc/ortc/rtpparametersconversion.cc b/webrtc/ortc/rtpparametersconversion.cc new file mode 100644 index 0000000000..63ee9c74da --- /dev/null +++ b/webrtc/ortc/rtpparametersconversion.cc @@ -0,0 +1,376 @@ +/* + * 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/rtpparametersconversion.h" + +#include +#include +#include + +#include "webrtc/media/base/rtputils.h" + +namespace webrtc { + +RTCErrorOr ToCricketFeedbackParam( + const RtcpFeedback& feedback) { + switch (feedback.type) { + case RtcpFeedbackType::CCM: + if (!feedback.message_type) { + LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, + "Missing message type in CCM RtcpFeedback."); + } else if (*feedback.message_type != RtcpFeedbackMessageType::FIR) { + LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, + "Invalid message type in CCM RtcpFeedback."); + } + return cricket::FeedbackParam(cricket::kRtcpFbParamCcm, + cricket::kRtcpFbCcmParamFir); + case RtcpFeedbackType::NACK: + if (!feedback.message_type) { + LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, + "Missing message type in NACK RtcpFeedback."); + } + switch (*feedback.message_type) { + case RtcpFeedbackMessageType::GENERIC_NACK: + return cricket::FeedbackParam(cricket::kRtcpFbParamNack); + case RtcpFeedbackMessageType::PLI: + return cricket::FeedbackParam(cricket::kRtcpFbParamNack, + cricket::kRtcpFbNackParamPli); + default: + LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, + "Invalid message type in NACK RtcpFeedback."); + } + case RtcpFeedbackType::REMB: + if (feedback.message_type) { + LOG_AND_RETURN_ERROR( + RTCErrorType::INVALID_PARAMETER, + "Didn't expect message type in REMB RtcpFeedback."); + } + return cricket::FeedbackParam(cricket::kRtcpFbParamRemb); + case RtcpFeedbackType::TRANSPORT_CC: + if (feedback.message_type) { + LOG_AND_RETURN_ERROR( + RTCErrorType::INVALID_PARAMETER, + "Didn't expect message type in transport-cc RtcpFeedback."); + } + return cricket::FeedbackParam(cricket::kRtcpFbParamTransportCc); + } + // Not reached; avoids compile warning. + FATAL(); +} + +template +static RTCError ToCricketCodecTypeSpecific(const RtpCodecParameters& codec, + C* cricket_codec); + +template <> +RTCError ToCricketCodecTypeSpecific( + const RtpCodecParameters& codec, + cricket::AudioCodec* cricket_codec) { + if (codec.kind != cricket::MEDIA_TYPE_AUDIO) { + LOG_AND_RETURN_ERROR( + RTCErrorType::INVALID_PARAMETER, + "Can't use video codec with audio sender or receiver."); + } + if (!codec.num_channels) { + LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, + "Missing number of channels for audio codec."); + } + if (*codec.num_channels <= 0) { + LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_RANGE, + "Number of channels must be positive."); + } + cricket_codec->channels = *codec.num_channels; + if (!codec.clock_rate) { + LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, + "Missing codec clock rate."); + } + if (*codec.clock_rate <= 0) { + LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_RANGE, + "Clock rate must be positive."); + } + cricket_codec->clockrate = *codec.clock_rate; + return RTCError::OK(); +} + +// Video codecs don't use num_channels or clock_rate, but they should at least +// be validated to ensure the application isn't trying to do something it +// doesn't intend to. +template <> +RTCError ToCricketCodecTypeSpecific( + const RtpCodecParameters& codec, + cricket::VideoCodec*) { + if (codec.kind != cricket::MEDIA_TYPE_VIDEO) { + LOG_AND_RETURN_ERROR( + RTCErrorType::INVALID_PARAMETER, + "Can't use audio codec with video sender or receiver."); + } + if (codec.num_channels) { + LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, + "Video codec shouldn't have num_channels."); + } + if (!codec.clock_rate) { + LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, + "Missing codec clock rate."); + } + if (*codec.clock_rate != cricket::kVideoCodecClockrate) { + LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, + "Video clock rate must be 90000."); + } + return RTCError::OK(); +} + +template +RTCErrorOr ToCricketCodec(const RtpCodecParameters& codec) { + C cricket_codec; + // Start with audio/video specific conversion. + RTCError err = ToCricketCodecTypeSpecific(codec, &cricket_codec); + if (!err.ok()) { + return std::move(err); + } + cricket_codec.name = codec.name; + if (!cricket::IsValidRtpPayloadType(codec.payload_type)) { + std::ostringstream oss; + oss << "Invalid payload type: " << codec.payload_type; + LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_RANGE, oss.str()); + } + cricket_codec.id = codec.payload_type; + for (const RtcpFeedback& feedback : codec.rtcp_feedback) { + auto result = ToCricketFeedbackParam(feedback); + if (!result.ok()) { + return result.MoveError(); + } + cricket_codec.AddFeedbackParam(result.MoveValue()); + } + cricket_codec.params.insert(codec.parameters.begin(), codec.parameters.end()); + return std::move(cricket_codec); +} + +template RTCErrorOr ToCricketCodec( + const RtpCodecParameters& codec); +template RTCErrorOr ToCricketCodec( + const RtpCodecParameters& codec); + +template +RTCErrorOr> ToCricketCodecs( + const std::vector& codecs) { + std::vector cricket_codecs; + std::set seen_payload_types; + for (const RtpCodecParameters& codec : codecs) { + auto result = ToCricketCodec(codec); + if (!result.ok()) { + return result.MoveError(); + } + if (!seen_payload_types.insert(codec.payload_type).second) { + std::ostringstream oss; + oss << "Duplicate payload type: " << codec.payload_type; + LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, oss.str()); + } + cricket_codecs.push_back(result.MoveValue()); + } + return std::move(cricket_codecs); +} + +template RTCErrorOr> ToCricketCodecs< + cricket::AudioCodec>(const std::vector& codecs); + +template RTCErrorOr> ToCricketCodecs< + cricket::VideoCodec>(const std::vector& codecs); + +RTCErrorOr ToCricketRtpHeaderExtensions( + const std::vector& extensions) { + cricket::RtpHeaderExtensions cricket_extensions; + std::ostringstream err_writer; + std::set seen_header_extension_ids; + for (const RtpHeaderExtensionParameters& extension : extensions) { + if (extension.id < RtpHeaderExtensionParameters::kMinId || + extension.id > RtpHeaderExtensionParameters::kMaxId) { + err_writer << "Invalid header extension id: " << extension.id; + LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_RANGE, err_writer.str()); + } + if (!seen_header_extension_ids.insert(extension.id).second) { + err_writer << "Duplicate header extension id: " << extension.id; + LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, err_writer.str()); + } + cricket_extensions.push_back(extension); + } + return std::move(cricket_extensions); +} + +RTCErrorOr ToCricketStreamParamsVec( + const std::vector& encodings) { + if (encodings.size() > 1u) { + LOG_AND_RETURN_ERROR(RTCErrorType::UNSUPPORTED_PARAMETER, + "ORTC API implementation doesn't currently " + "support simulcast or layered encodings."); + } else if (encodings.empty()) { + return cricket::StreamParamsVec(); + } + cricket::StreamParamsVec cricket_streams; + const RtpEncodingParameters& encoding = encodings[0]; + if (encoding.rtx && encoding.rtx->ssrc && !encoding.ssrc) { + LOG_AND_RETURN_ERROR(RTCErrorType::UNSUPPORTED_PARAMETER, + "Setting an RTX SSRC explicitly while leaving the " + "primary SSRC unset is not currently supported."); + } + if (encoding.ssrc) { + cricket::StreamParams stream_params; + stream_params.add_ssrc(*encoding.ssrc); + if (encoding.rtx && encoding.rtx->ssrc) { + stream_params.AddFidSsrc(*encoding.ssrc, *encoding.rtx->ssrc); + } + cricket_streams.push_back(std::move(stream_params)); + } + return std::move(cricket_streams); +} + +rtc::Optional ToRtcpFeedback( + const cricket::FeedbackParam& cricket_feedback) { + if (cricket_feedback.id() == cricket::kRtcpFbParamCcm) { + if (cricket_feedback.param() == cricket::kRtcpFbCcmParamFir) { + return rtc::Optional( + {RtcpFeedbackType::CCM, RtcpFeedbackMessageType::FIR}); + } else { + LOG(LS_WARNING) << "Unsupported parameter for CCM RTCP feedback: " + << cricket_feedback.param(); + return rtc::Optional(); + } + } else if (cricket_feedback.id() == cricket::kRtcpFbParamNack) { + if (cricket_feedback.param().empty()) { + return rtc::Optional( + {RtcpFeedbackType::NACK, RtcpFeedbackMessageType::GENERIC_NACK}); + } else if (cricket_feedback.param() == cricket::kRtcpFbNackParamPli) { + return rtc::Optional( + {RtcpFeedbackType::NACK, RtcpFeedbackMessageType::PLI}); + } else { + LOG(LS_WARNING) << "Unsupported parameter for NACK RTCP feedback: " + << cricket_feedback.param(); + return rtc::Optional(); + } + } else if (cricket_feedback.id() == cricket::kRtcpFbParamRemb) { + if (!cricket_feedback.param().empty()) { + LOG(LS_WARNING) << "Unsupported parameter for REMB RTCP feedback: " + << cricket_feedback.param(); + return rtc::Optional(); + } else { + return rtc::Optional(RtcpFeedback(RtcpFeedbackType::REMB)); + } + } else if (cricket_feedback.id() == cricket::kRtcpFbParamTransportCc) { + if (!cricket_feedback.param().empty()) { + LOG(LS_WARNING) + << "Unsupported parameter for transport-cc RTCP feedback: " + << cricket_feedback.param(); + return rtc::Optional(); + } else { + return rtc::Optional( + RtcpFeedback(RtcpFeedbackType::TRANSPORT_CC)); + } + } + LOG(LS_WARNING) << "Unsupported RTCP feedback type: " + << cricket_feedback.id(); + return rtc::Optional(); +} + +template +cricket::MediaType KindOfCodec(); + +template <> +cricket::MediaType KindOfCodec() { + return cricket::MEDIA_TYPE_AUDIO; +} + +template <> +cricket::MediaType KindOfCodec() { + return cricket::MEDIA_TYPE_VIDEO; +} + +template +static void ToRtpCodecCapabilityTypeSpecific(const C& cricket_codec, + RtpCodecCapability* codec); + +template <> +void ToRtpCodecCapabilityTypeSpecific( + const cricket::AudioCodec& cricket_codec, + RtpCodecCapability* codec) { + codec->num_channels = + rtc::Optional(static_cast(cricket_codec.channels)); +} + +template <> +void ToRtpCodecCapabilityTypeSpecific( + const cricket::VideoCodec& cricket_codec, + RtpCodecCapability* codec) {} + +template +RtpCodecCapability ToRtpCodecCapability(const C& cricket_codec) { + RtpCodecCapability codec; + codec.name = cricket_codec.name; + codec.kind = KindOfCodec(); + codec.clock_rate.emplace(cricket_codec.clockrate); + codec.preferred_payload_type.emplace(cricket_codec.id); + for (const cricket::FeedbackParam& cricket_feedback : + cricket_codec.feedback_params.params()) { + rtc::Optional feedback = ToRtcpFeedback(cricket_feedback); + if (feedback) { + codec.rtcp_feedback.push_back(feedback.MoveValue()); + } + } + ToRtpCodecCapabilityTypeSpecific(cricket_codec, &codec); + codec.parameters.insert(cricket_codec.params.begin(), + cricket_codec.params.end()); + return codec; +} + +template RtpCodecCapability ToRtpCodecCapability( + const cricket::AudioCodec& cricket_codec); +template RtpCodecCapability ToRtpCodecCapability( + const cricket::VideoCodec& cricket_codec); + +template +RtpCapabilities ToRtpCapabilities( + const std::vector& cricket_codecs, + const cricket::RtpHeaderExtensions& cricket_extensions) { + RtpCapabilities capabilities; + bool have_red = false; + bool have_ulpfec = false; + bool have_flexfec = false; + for (const C& cricket_codec : cricket_codecs) { + if (cricket_codec.name == cricket::kRedCodecName) { + have_red = true; + } else if (cricket_codec.name == cricket::kUlpfecCodecName) { + have_ulpfec = true; + } else if (cricket_codec.name == cricket::kFlexfecCodecName) { + have_flexfec = true; + } + capabilities.codecs.push_back(ToRtpCodecCapability(cricket_codec)); + } + for (const RtpExtension& cricket_extension : cricket_extensions) { + capabilities.header_extensions.emplace_back(cricket_extension.uri, + cricket_extension.id); + } + if (have_red) { + capabilities.fec.push_back(FecMechanism::RED); + } + if (have_red && have_ulpfec) { + capabilities.fec.push_back(FecMechanism::RED_AND_ULPFEC); + } + if (have_flexfec) { + capabilities.fec.push_back(FecMechanism::FLEXFEC); + } + return capabilities; +} + +template RtpCapabilities ToRtpCapabilities( + const std::vector& cricket_codecs, + const cricket::RtpHeaderExtensions& cricket_extensions); +template RtpCapabilities ToRtpCapabilities( + const std::vector& cricket_codecs, + const cricket::RtpHeaderExtensions& cricket_extensions); + +} // namespace webrtc diff --git a/webrtc/ortc/rtpparametersconversion.h b/webrtc/ortc/rtpparametersconversion.h new file mode 100644 index 0000000000..a1680a205a --- /dev/null +++ b/webrtc/ortc/rtpparametersconversion.h @@ -0,0 +1,96 @@ +/* + * 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. + */ + +#ifndef WEBRTC_ORTC_RTPPARAMETERSCONVERSION_H_ +#define WEBRTC_ORTC_RTPPARAMETERSCONVERSION_H_ + +#include +#include + +#include "webrtc/api/rtcerror.h" +#include "webrtc/api/rtpparameters.h" +#include "webrtc/base/optional.h" +#include "webrtc/pc/mediasession.h" +#include "webrtc/media/base/codec.h" + +namespace webrtc { + +// NOTE: Some functions are templated for convenience, such that template-based +// code dealing with AudioContentDescription and VideoContentDescription can +// use this easily. Such methods are usable with cricket::AudioCodec and +// cricket::VideoCodec. + +//*************************************************************************** +// Functions for converting from new webrtc:: structures to old cricket:: +// structures. +// +// As the return values imply, all of these functions do validation of the +// parameters and return an error if they're invalid. It's expected that any +// default values (such as video clock rate of 90000) have been filled by the +// time the webrtc:: structure is being converted to the cricket:: one. +// +// These are expected to be used when parameters are passed into an RtpSender +// or RtpReceiver, and need to be validated and converted so they can be +// applied to the media engine level. +//*************************************************************************** + +// Returns error on invalid input. Certain message types are only valid for +// certain feedback types. +RTCErrorOr ToCricketFeedbackParam( + const RtcpFeedback& feedback); + +// Verifies that the codec kind is correct, and it has mandatory parameters +// filled, with values in valid ranges. +template +RTCErrorOr ToCricketCodec(const RtpCodecParameters& codec); + +// Verifies that payload types aren't duplicated, in addition to normal +// validation. +template +RTCErrorOr> ToCricketCodecs( + const std::vector& codecs); + +// Validates that header extension IDs aren't duplicated. +RTCErrorOr ToCricketRtpHeaderExtensions( + const std::vector& extensions); + +// SSRCs are allowed to be ommitted. This may be used for receive parameters +// where SSRCs are unsignaled. +RTCErrorOr ToCricketStreamParamsVec( + const std::vector& encodings); + +//***************************************************************************** +// Functions for converting from old cricket:: structures to new webrtc:: +// structures. Unlike the above functions, these are permissive with regards to +// input validation; it's assumed that any necessary validation already +// occurred. +// +// These are expected to be used either to convert from audio/video engine +// capabilities to RtpCapabilities, or to convert from already-parsed SDP +// (in the form of cricket:: structures) to webrtc:: structures. The latter +// functionality is not yet implemented. +//***************************************************************************** + +// Returns empty value if |cricket_feedback| is a feedback type not +// supported/recognized. +rtc::Optional ToRtcpFeedback( + const cricket::FeedbackParam& cricket_feedback); + +template +RtpCodecCapability ToRtpCodecCapability(const C& cricket_codec); + +template +RtpCapabilities ToRtpCapabilities( + const std::vector& cricket_codecs, + const cricket::RtpHeaderExtensions& cricket_extensions); + +} // namespace webrtc + +#endif // WEBRTC_ORTC_RTPPARAMETERSCONVERSION_H_ diff --git a/webrtc/ortc/rtpparametersconversion_unittest.cc b/webrtc/ortc/rtpparametersconversion_unittest.cc new file mode 100644 index 0000000000..6bb335c78d --- /dev/null +++ b/webrtc/ortc/rtpparametersconversion_unittest.cc @@ -0,0 +1,535 @@ +/* + * 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 + +#include "webrtc/base/gunit.h" +#include "webrtc/ortc/rtpparametersconversion.h" +#include "webrtc/ortc/testrtpparameters.h" + +namespace webrtc { + +TEST(RtpParametersConversionTest, ToCricketFeedbackParam) { + auto result = ToCricketFeedbackParam( + {RtcpFeedbackType::CCM, RtcpFeedbackMessageType::FIR}); + EXPECT_EQ(cricket::FeedbackParam("ccm", "fir"), result.value()); + result = ToCricketFeedbackParam( + {RtcpFeedbackType::NACK, RtcpFeedbackMessageType::GENERIC_NACK}); + EXPECT_EQ(cricket::FeedbackParam("nack"), result.value()); + result = ToCricketFeedbackParam( + {RtcpFeedbackType::NACK, RtcpFeedbackMessageType::PLI}); + EXPECT_EQ(cricket::FeedbackParam("nack", "pli"), result.value()); + result = ToCricketFeedbackParam(RtcpFeedback(RtcpFeedbackType::REMB)); + EXPECT_EQ(cricket::FeedbackParam("goog-remb"), result.value()); + result = ToCricketFeedbackParam(RtcpFeedback(RtcpFeedbackType::TRANSPORT_CC)); + EXPECT_EQ(cricket::FeedbackParam("transport-cc"), result.value()); +} + +TEST(RtpParametersConversionTest, ToCricketFeedbackParamErrors) { + // CCM with missing or invalid message type. + auto result = ToCricketFeedbackParam(RtcpFeedback(RtcpFeedbackType::CCM)); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, result.error().type()); + result = ToCricketFeedbackParam( + {RtcpFeedbackType::CCM, RtcpFeedbackMessageType::PLI}); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, result.error().type()); + // NACK with missing or invalid message type. + result = ToCricketFeedbackParam(RtcpFeedback(RtcpFeedbackType::NACK)); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, result.error().type()); + result = ToCricketFeedbackParam( + {RtcpFeedbackType::NACK, RtcpFeedbackMessageType::FIR}); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, result.error().type()); + // REMB with message type (should be left empty). + result = ToCricketFeedbackParam( + {RtcpFeedbackType::REMB, RtcpFeedbackMessageType::GENERIC_NACK}); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, result.error().type()); + // TRANSPORT_CC with message type (should be left empty). + result = ToCricketFeedbackParam( + {RtcpFeedbackType::TRANSPORT_CC, RtcpFeedbackMessageType::FIR}); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, result.error().type()); +} + +TEST(RtpParametersConversionTest, ToAudioCodec) { + RtpCodecParameters codec; + codec.name = "AuDiO"; + codec.kind = cricket::MEDIA_TYPE_AUDIO; + codec.payload_type = 120; + codec.clock_rate.emplace(36000); + codec.num_channels.emplace(6); + codec.parameters["foo"] = "bar"; + codec.rtcp_feedback.emplace_back(RtcpFeedbackType::TRANSPORT_CC); + auto result = ToCricketCodec(codec); + ASSERT_TRUE(result.ok()); + + EXPECT_EQ("AuDiO", result.value().name); + EXPECT_EQ(120, result.value().id); + EXPECT_EQ(36000, result.value().clockrate); + EXPECT_EQ(6u, result.value().channels); + ASSERT_EQ(1u, result.value().params.size()); + EXPECT_EQ("bar", result.value().params["foo"]); + EXPECT_EQ(1u, result.value().feedback_params.params().size()); + EXPECT_TRUE(result.value().feedback_params.Has( + cricket::FeedbackParam("transport-cc"))); +} + +TEST(RtpParametersConversionTest, ToVideoCodec) { + RtpCodecParameters codec; + codec.name = "coolcodec"; + codec.kind = cricket::MEDIA_TYPE_VIDEO; + codec.payload_type = 101; + codec.clock_rate.emplace(90000); + codec.parameters["foo"] = "bar"; + codec.parameters["PING"] = "PONG"; + codec.rtcp_feedback.emplace_back(RtcpFeedbackType::TRANSPORT_CC); + codec.rtcp_feedback.emplace_back(RtcpFeedbackType::NACK, + RtcpFeedbackMessageType::PLI); + auto result = ToCricketCodec(codec); + ASSERT_TRUE(result.ok()); + + EXPECT_EQ("coolcodec", result.value().name); + EXPECT_EQ(101, result.value().id); + EXPECT_EQ(90000, result.value().clockrate); + ASSERT_EQ(2u, result.value().params.size()); + EXPECT_EQ("bar", result.value().params["foo"]); + EXPECT_EQ("PONG", result.value().params["PING"]); + EXPECT_EQ(2u, result.value().feedback_params.params().size()); + EXPECT_TRUE(result.value().feedback_params.Has( + cricket::FeedbackParam("transport-cc"))); + EXPECT_TRUE(result.value().feedback_params.Has( + cricket::FeedbackParam("nack", "pli"))); +} + +// Trying to convert to an AudioCodec if the kind is "video" should fail. +TEST(RtpParametersConversionTest, ToCricketCodecInvalidKind) { + RtpCodecParameters audio_codec; + audio_codec.name = "opus"; + audio_codec.kind = cricket::MEDIA_TYPE_VIDEO; + audio_codec.payload_type = 111; + audio_codec.clock_rate.emplace(48000); + audio_codec.num_channels.emplace(2); + + RtpCodecParameters video_codec; + video_codec.name = "VP8"; + video_codec.kind = cricket::MEDIA_TYPE_AUDIO; + video_codec.payload_type = 102; + video_codec.clock_rate.emplace(90000); + + auto audio_result = ToCricketCodec(audio_codec); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, audio_result.error().type()); + + auto video_result = ToCricketCodec(video_codec); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, video_result.error().type()); + + // Sanity check that if the kind is correct, the conversion succeeds. + audio_codec.kind = cricket::MEDIA_TYPE_AUDIO; + video_codec.kind = cricket::MEDIA_TYPE_VIDEO; + audio_result = ToCricketCodec(audio_codec); + EXPECT_TRUE(audio_result.ok()); + video_result = ToCricketCodec(video_codec); + EXPECT_TRUE(video_result.ok()); +} + +TEST(RtpParametersConversionTest, ToAudioCodecInvalidParameters) { + // Missing channels. + RtpCodecParameters codec; + codec.name = "opus"; + codec.kind = cricket::MEDIA_TYPE_AUDIO; + codec.payload_type = 111; + codec.clock_rate.emplace(48000); + auto result = ToCricketCodec(codec); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, result.error().type()); + + // Negative number of channels. + codec.num_channels.emplace(-1); + result = ToCricketCodec(codec); + EXPECT_EQ(RTCErrorType::INVALID_RANGE, result.error().type()); + + // Missing clock rate. + codec.num_channels.emplace(2); + codec.clock_rate.reset(); + result = ToCricketCodec(codec); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, result.error().type()); + + // Negative clock rate. + codec.clock_rate.emplace(-48000); + result = ToCricketCodec(codec); + EXPECT_EQ(RTCErrorType::INVALID_RANGE, result.error().type()); + + // Sanity check that conversion succeeds if these errors are fixed. + codec.clock_rate.emplace(48000); + result = ToCricketCodec(codec); + EXPECT_TRUE(result.ok()); +} + +TEST(RtpParametersConversionTest, ToVideoCodecInvalidParameters) { + // Missing clock rate. + RtpCodecParameters codec; + codec.name = "VP8"; + codec.kind = cricket::MEDIA_TYPE_VIDEO; + codec.payload_type = 102; + auto result = ToCricketCodec(codec); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, result.error().type()); + + // Invalid clock rate. + codec.clock_rate.emplace(48000); + result = ToCricketCodec(codec); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, result.error().type()); + + // Channels set (should be unset). + codec.clock_rate.emplace(90000); + codec.num_channels.emplace(2); + result = ToCricketCodec(codec); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, result.error().type()); + + // Sanity check that conversion succeeds if these errors are fixed. + codec.num_channels.reset(); + result = ToCricketCodec(codec); + EXPECT_TRUE(result.ok()); +} + +TEST(RtpParametersConversionTest, ToCricketCodecInvalidPayloadType) { + RtpCodecParameters codec; + codec.name = "VP8"; + codec.kind = cricket::MEDIA_TYPE_VIDEO; + codec.clock_rate.emplace(90000); + + codec.payload_type = -1000; + auto result = ToCricketCodec(codec); + EXPECT_EQ(RTCErrorType::INVALID_RANGE, result.error().type()); + + // Max payload type is 127. + codec.payload_type = 128; + result = ToCricketCodec(codec); + EXPECT_EQ(RTCErrorType::INVALID_RANGE, result.error().type()); + + // Sanity check that conversion succeeds with a valid payload type. + codec.payload_type = 127; + result = ToCricketCodec(codec); + EXPECT_TRUE(result.ok()); +} + +// There are already tests for ToCricketFeedbackParam, but ensure that those +// errors are propagated from ToCricketCodec. +TEST(RtpParametersConversionTest, ToCricketCodecInvalidRtcpFeedback) { + RtpCodecParameters codec; + codec.name = "VP8"; + codec.kind = cricket::MEDIA_TYPE_VIDEO; + codec.clock_rate.emplace(90000); + codec.payload_type = 99; + codec.rtcp_feedback.emplace_back(RtcpFeedbackType::CCM, + RtcpFeedbackMessageType::PLI); + + auto result = ToCricketCodec(codec); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, result.error().type()); + + // Sanity check that conversion succeeds without invalid feedback. + codec.rtcp_feedback.clear(); + result = ToCricketCodec(codec); + EXPECT_TRUE(result.ok()); +} + +TEST(RtpParametersConversionTest, ToCricketCodecs) { + std::vector codecs; + RtpCodecParameters codec; + codec.name = "VP8"; + codec.kind = cricket::MEDIA_TYPE_VIDEO; + codec.clock_rate.emplace(90000); + codec.payload_type = 99; + codecs.push_back(codec); + + codec.name = "VP9"; + codec.payload_type = 100; + codecs.push_back(codec); + + auto result = ToCricketCodecs(codecs); + ASSERT_TRUE(result.ok()); + ASSERT_EQ(2u, result.value().size()); + EXPECT_EQ("VP8", result.value()[0].name); + EXPECT_EQ(99, result.value()[0].id); + EXPECT_EQ("VP9", result.value()[1].name); + EXPECT_EQ(100, result.value()[1].id); +} + +TEST(RtpParametersConversionTest, ToCricketCodecsDuplicatePayloadType) { + std::vector codecs; + RtpCodecParameters codec; + codec.name = "VP8"; + codec.kind = cricket::MEDIA_TYPE_VIDEO; + codec.clock_rate.emplace(90000); + codec.payload_type = 99; + codecs.push_back(codec); + + codec.name = "VP9"; + codec.payload_type = 99; + codecs.push_back(codec); + + auto result = ToCricketCodecs(codecs); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, result.error().type()); + + // Sanity check that this succeeds without the duplicate payload type. + codecs[1].payload_type = 120; + result = ToCricketCodecs(codecs); + EXPECT_TRUE(result.ok()); +} + +TEST(RtpParametersConversionTest, ToCricketRtpHeaderExtensions) { + std::vector extensions = { + {"http://example.com", 1}, {"urn:foo:bar", 14}}; + auto result = ToCricketRtpHeaderExtensions(extensions); + ASSERT_TRUE(result.ok()); + ASSERT_EQ(2u, result.value().size()); + EXPECT_EQ("http://example.com", result.value()[0].uri); + EXPECT_EQ(1, result.value()[0].id); + EXPECT_EQ("urn:foo:bar", result.value()[1].uri); + EXPECT_EQ(14, result.value()[1].id); +} + +TEST(RtpParametersConversionTest, ToCricketRtpHeaderExtensionsErrors) { + // First, IDs outside the range 1-14. + std::vector extensions = { + {"http://example.com", 0}}; + auto result = ToCricketRtpHeaderExtensions(extensions); + EXPECT_EQ(RTCErrorType::INVALID_RANGE, result.error().type()); + + extensions[0].id = 15; + result = ToCricketRtpHeaderExtensions(extensions); + EXPECT_EQ(RTCErrorType::INVALID_RANGE, result.error().type()); + + // Duplicate IDs. + extensions = {{"http://example.com", 1}, {"urn:foo:bar", 1}}; + result = ToCricketRtpHeaderExtensions(extensions); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, result.error().type()); +} + +TEST(RtpParametersConversionTest, ToCricketStreamParamsVecSimple) { + std::vector encodings; + RtpEncodingParameters encoding; + encoding.ssrc.emplace(0xbaadf00d); + encodings.push_back(encoding); + auto result = ToCricketStreamParamsVec(encodings); + ASSERT_TRUE(result.ok()); + ASSERT_EQ(1u, result.value().size()); + EXPECT_EQ(1u, result.value()[0].ssrcs.size()); + EXPECT_EQ(0xbaadf00d, result.value()[0].first_ssrc()); +} + +TEST(RtpParametersConversionTest, ToCricketStreamParamsVecWithRtx) { + std::vector encodings; + RtpEncodingParameters encoding; + // Test a corner case SSRC of 0. + encoding.ssrc.emplace(0u); + encoding.rtx.emplace(0xdeadbeef); + encodings.push_back(encoding); + auto result = ToCricketStreamParamsVec(encodings); + ASSERT_TRUE(result.ok()); + ASSERT_EQ(1u, result.value().size()); + EXPECT_EQ(2u, result.value()[0].ssrcs.size()); + EXPECT_EQ(0u, result.value()[0].first_ssrc()); + uint32_t rtx_ssrc = 0; + EXPECT_TRUE(result.value()[0].GetFidSsrc(0u, &rtx_ssrc)); + EXPECT_EQ(0xdeadbeef, rtx_ssrc); +} + +// No encodings should be accepted; an endpoint may want to prepare a +// decoder/encoder without having something to receive/send yet. +TEST(RtpParametersConversionTest, ToCricketStreamParamsVecNoEncodings) { + std::vector encodings; + auto result = ToCricketStreamParamsVec(encodings); + ASSERT_TRUE(result.ok()); + EXPECT_EQ(0u, result.value().size()); +} + +// An encoding without SSRCs should be accepted. This could be the case when +// SSRCs aren't signaled and payload-type based demuxing is used. +TEST(RtpParametersConversionTest, ToCricketStreamParamsVecMissingSsrcs) { + std::vector encodings = {{}}; + // Creates RtxParameters with empty SSRC. + encodings[0].rtx.emplace(); + auto result = ToCricketStreamParamsVec(encodings); + ASSERT_TRUE(result.ok()); + EXPECT_EQ(0u, result.value().size()); +} + +// The media engine doesn't have a way of receiving an RTX SSRC that's known +// with a primary SSRC that's unknown, so this should produce an error. +TEST(RtpParametersConversionTest, ToStreamParamsWithPrimarySsrcSetAndRtxUnset) { + std::vector encodings = {{}}; + encodings[0].rtx.emplace(0xdeadbeef); + EXPECT_EQ(RTCErrorType::UNSUPPORTED_PARAMETER, + ToCricketStreamParamsVec(encodings).error().type()); +} + +// TODO(deadbeef): Update this test when we support multiple encodings. +TEST(RtpParametersConversionTest, ToCricketStreamParamsVecMultipleEncodings) { + std::vector encodings = {{}, {}}; + auto result = ToCricketStreamParamsVec(encodings); + EXPECT_EQ(RTCErrorType::UNSUPPORTED_PARAMETER, result.error().type()); +} + +TEST(RtpParametersConversionTest, ToRtcpFeedback) { + rtc::Optional result = ToRtcpFeedback({"ccm", "fir"}); + EXPECT_EQ(RtcpFeedback(RtcpFeedbackType::CCM, RtcpFeedbackMessageType::FIR), + *result); + result = ToRtcpFeedback(cricket::FeedbackParam("nack")); + EXPECT_EQ(RtcpFeedback(RtcpFeedbackType::NACK, + RtcpFeedbackMessageType::GENERIC_NACK), + *result); + result = ToRtcpFeedback({"nack", "pli"}); + EXPECT_EQ(RtcpFeedback(RtcpFeedbackType::NACK, RtcpFeedbackMessageType::PLI), + *result); + result = ToRtcpFeedback(cricket::FeedbackParam("goog-remb")); + EXPECT_EQ(RtcpFeedback(RtcpFeedbackType::REMB), *result); + result = ToRtcpFeedback(cricket::FeedbackParam("transport-cc")); + EXPECT_EQ(RtcpFeedback(RtcpFeedbackType::TRANSPORT_CC), *result); +} + +TEST(RtpParametersConversionTest, ToRtcpFeedbackErrors) { + // CCM with missing or invalid message type. + rtc::Optional result = ToRtcpFeedback({"ccm", "pli"}); + EXPECT_FALSE(result); + result = ToRtcpFeedback(cricket::FeedbackParam("ccm")); + EXPECT_FALSE(result); + // NACK with missing or invalid message type. + result = ToRtcpFeedback({"nack", "fir"}); + EXPECT_FALSE(result); + // REMB with message type (should be left empty). + result = ToRtcpFeedback({"goog-remb", "pli"}); + EXPECT_FALSE(result); + // TRANSPORT_CC with message type (should be left empty). + result = ToRtcpFeedback({"transport-cc", "fir"}); + EXPECT_FALSE(result); + // Unknown message type. + result = ToRtcpFeedback(cricket::FeedbackParam("foo")); + EXPECT_FALSE(result); +} + +TEST(RtpParametersConversionTest, ToAudioRtpCodecCapability) { + cricket::AudioCodec cricket_codec; + cricket_codec.name = "foo"; + cricket_codec.id = 50; + cricket_codec.clockrate = 22222; + cricket_codec.channels = 4; + cricket_codec.params["foo"] = "bar"; + cricket_codec.feedback_params.Add(cricket::FeedbackParam("transport-cc")); + RtpCodecCapability codec = ToRtpCodecCapability(cricket_codec); + + EXPECT_EQ("foo", codec.name); + EXPECT_EQ(cricket::MEDIA_TYPE_AUDIO, codec.kind); + EXPECT_EQ(rtc::Optional(50), codec.preferred_payload_type); + EXPECT_EQ(rtc::Optional(22222), codec.clock_rate); + EXPECT_EQ(rtc::Optional(4), codec.num_channels); + ASSERT_EQ(1u, codec.parameters.size()); + EXPECT_EQ("bar", codec.parameters["foo"]); + EXPECT_EQ(1u, codec.rtcp_feedback.size()); + EXPECT_EQ(RtcpFeedback(RtcpFeedbackType::TRANSPORT_CC), + codec.rtcp_feedback[0]); +} + +TEST(RtpParametersConversionTest, ToVideoRtpCodecCapability) { + cricket::VideoCodec cricket_codec; + cricket_codec.name = "VID"; + cricket_codec.id = 101; + cricket_codec.clockrate = 80000; + cricket_codec.params["foo"] = "bar"; + cricket_codec.params["ANOTHER"] = "param"; + cricket_codec.feedback_params.Add(cricket::FeedbackParam("transport-cc")); + cricket_codec.feedback_params.Add({"nack", "pli"}); + RtpCodecCapability codec = ToRtpCodecCapability(cricket_codec); + + EXPECT_EQ("VID", codec.name); + EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, codec.kind); + EXPECT_EQ(rtc::Optional(101), codec.preferred_payload_type); + EXPECT_EQ(rtc::Optional(80000), codec.clock_rate); + ASSERT_EQ(2u, codec.parameters.size()); + EXPECT_EQ("bar", codec.parameters["foo"]); + EXPECT_EQ("param", codec.parameters["ANOTHER"]); + EXPECT_EQ(2u, codec.rtcp_feedback.size()); + EXPECT_EQ(RtcpFeedback(RtcpFeedbackType::TRANSPORT_CC), + codec.rtcp_feedback[0]); + EXPECT_EQ(RtcpFeedback(RtcpFeedbackType::NACK, RtcpFeedbackMessageType::PLI), + codec.rtcp_feedback[1]); +} + +// An unknown feedback param should just be ignored. +TEST(RtpParametersConversionTest, ToRtpCodecCapabilityUnknownFeedbackParam) { + cricket::AudioCodec cricket_codec; + cricket_codec.name = "foo"; + cricket_codec.id = 50; + cricket_codec.clockrate = 22222; + cricket_codec.channels = 4; + cricket_codec.params["foo"] = "bar"; + cricket_codec.feedback_params.Add({"unknown", "param"}); + cricket_codec.feedback_params.Add(cricket::FeedbackParam("transport-cc")); + RtpCodecCapability codec = ToRtpCodecCapability(cricket_codec); + + ASSERT_EQ(1u, codec.rtcp_feedback.size()); + EXPECT_EQ(RtcpFeedback(RtcpFeedbackType::TRANSPORT_CC), + codec.rtcp_feedback[0]); +} + +// Most of ToRtpCapabilities is tested by ToRtpCodecCapability, but we need to +// test that the result of ToRtpCodecCapability ends up in the result, and that +// the "fec" list is assembled correctly. +TEST(RtpParametersConversionTest, ToRtpCapabilities) { + cricket::VideoCodec vp8; + vp8.name = "VP8"; + vp8.id = 101; + vp8.clockrate = 90000; + + cricket::VideoCodec red; + red.name = "red"; + red.id = 102; + red.clockrate = 90000; + + cricket::VideoCodec ulpfec; + ulpfec.name = "ulpfec"; + ulpfec.id = 103; + ulpfec.clockrate = 90000; + + cricket::VideoCodec flexfec; + flexfec.name = "flexfec-03"; + flexfec.id = 102; + flexfec.clockrate = 90000; + + RtpCapabilities capabilities = ToRtpCapabilities( + {vp8, ulpfec}, {{"uri", 1}, {"uri2", 3}}); + ASSERT_EQ(2u, capabilities.codecs.size()); + EXPECT_EQ("VP8", capabilities.codecs[0].name); + EXPECT_EQ("ulpfec", capabilities.codecs[1].name); + ASSERT_EQ(2u, capabilities.header_extensions.size()); + EXPECT_EQ("uri", capabilities.header_extensions[0].uri); + EXPECT_EQ(1, capabilities.header_extensions[0].preferred_id); + EXPECT_EQ("uri2", capabilities.header_extensions[1].uri); + EXPECT_EQ(3, capabilities.header_extensions[1].preferred_id); + EXPECT_EQ(0u, capabilities.fec.size()); + + capabilities = ToRtpCapabilities( + {vp8, red, ulpfec}, cricket::RtpHeaderExtensions()); + EXPECT_EQ(3u, capabilities.codecs.size()); + EXPECT_EQ(2u, capabilities.fec.size()); + EXPECT_NE(capabilities.fec.end(), + std::find(capabilities.fec.begin(), capabilities.fec.end(), + FecMechanism::RED)); + EXPECT_NE(capabilities.fec.end(), + std::find(capabilities.fec.begin(), capabilities.fec.end(), + FecMechanism::RED_AND_ULPFEC)); + + capabilities = ToRtpCapabilities( + {vp8, red, flexfec}, cricket::RtpHeaderExtensions()); + EXPECT_EQ(3u, capabilities.codecs.size()); + EXPECT_EQ(2u, capabilities.fec.size()); + EXPECT_NE(capabilities.fec.end(), + std::find(capabilities.fec.begin(), capabilities.fec.end(), + FecMechanism::RED)); + EXPECT_NE(capabilities.fec.end(), + std::find(capabilities.fec.begin(), capabilities.fec.end(), + FecMechanism::FLEXFEC)); +} + +} // namespace webrtc diff --git a/webrtc/ortc/rtptransport_unittest.cc b/webrtc/ortc/rtptransport_unittest.cc new file mode 100644 index 0000000000..6d162301be --- /dev/null +++ b/webrtc/ortc/rtptransport_unittest.cc @@ -0,0 +1,227 @@ +/* + * 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 + +#include "webrtc/base/gunit.h" +#include "webrtc/media/base/fakemediaengine.h" +#include "webrtc/ortc/ortcfactory.h" +#include "webrtc/ortc/testrtpparameters.h" +#include "webrtc/p2p/base/fakepackettransport.h" + +namespace webrtc { + +// This test uses fake packet transports and a fake media engine, in order to +// test the RtpTransport at only an API level. Any end-to-end test should go in +// ortcfactory_integrationtest.cc instead. +class RtpTransportTest : public testing::Test { + public: + RtpTransportTest() { + fake_media_engine_ = new cricket::FakeMediaEngine(); + // Note: This doesn't need to use fake network classes, since it uses + // FakePacketTransports. + auto result = OrtcFactory::Create( + nullptr, nullptr, nullptr, nullptr, nullptr, + std::unique_ptr(fake_media_engine_)); + ortc_factory_ = result.MoveValue(); + } + + protected: + // Owned by |ortc_factory_|. + cricket::FakeMediaEngine* fake_media_engine_; + std::unique_ptr ortc_factory_; +}; + +// Test GetRtpPacketTransport and GetRtcpPacketTransport, with and without RTCP +// muxing. +TEST_F(RtpTransportTest, GetPacketTransports) { + rtc::FakePacketTransport rtp("rtp"); + rtc::FakePacketTransport rtcp("rtcp"); + // With muxed RTCP. + RtcpParameters rtcp_parameters; + rtcp_parameters.mux = true; + auto result = ortc_factory_->CreateRtpTransport(rtcp_parameters, &rtp, + nullptr, nullptr); + ASSERT_TRUE(result.ok()); + EXPECT_EQ(&rtp, result.value()->GetRtpPacketTransport()); + EXPECT_EQ(nullptr, result.value()->GetRtcpPacketTransport()); + result.MoveValue().reset(); + // With non-muxed RTCP. + rtcp_parameters.mux = false; + result = + ortc_factory_->CreateRtpTransport(rtcp_parameters, &rtp, &rtcp, nullptr); + ASSERT_TRUE(result.ok()); + EXPECT_EQ(&rtp, result.value()->GetRtpPacketTransport()); + EXPECT_EQ(&rtcp, result.value()->GetRtcpPacketTransport()); +} + +// If an RtpTransport starts out un-muxed and then starts muxing, the RTCP +// packet transport should be forgotten and GetRtcpPacketTransport should +// return null. +TEST_F(RtpTransportTest, EnablingRtcpMuxingUnsetsRtcpTransport) { + rtc::FakePacketTransport rtp("rtp"); + rtc::FakePacketTransport rtcp("rtcp"); + + // Create non-muxed. + RtcpParameters rtcp_parameters; + rtcp_parameters.mux = false; + auto result = + ortc_factory_->CreateRtpTransport(rtcp_parameters, &rtp, &rtcp, nullptr); + ASSERT_TRUE(result.ok()); + auto rtp_transport = result.MoveValue(); + + // Enable muxing. + rtcp_parameters.mux = true; + EXPECT_TRUE(rtp_transport->SetRtcpParameters(rtcp_parameters).ok()); + EXPECT_EQ(nullptr, rtp_transport->GetRtcpPacketTransport()); +} + +TEST_F(RtpTransportTest, GetAndSetRtcpParameters) { + rtc::FakePacketTransport rtp("rtp"); + rtc::FakePacketTransport rtcp("rtcp"); + // Start with non-muxed RTCP. + RtcpParameters rtcp_parameters; + rtcp_parameters.mux = false; + rtcp_parameters.cname = "teST"; + rtcp_parameters.reduced_size = false; + auto result = + ortc_factory_->CreateRtpTransport(rtcp_parameters, &rtp, &rtcp, nullptr); + ASSERT_TRUE(result.ok()); + auto transport = result.MoveValue(); + EXPECT_EQ(rtcp_parameters, transport->GetRtcpParameters()); + + // Changing the CNAME is currently unsupported. + rtcp_parameters.cname = "different"; + EXPECT_EQ(RTCErrorType::UNSUPPORTED_OPERATION, + transport->SetRtcpParameters(rtcp_parameters).type()); + rtcp_parameters.cname = "teST"; + + // Enable RTCP muxing and reduced-size RTCP. + rtcp_parameters.mux = true; + rtcp_parameters.reduced_size = true; + EXPECT_TRUE(transport->SetRtcpParameters(rtcp_parameters).ok()); + EXPECT_EQ(rtcp_parameters, transport->GetRtcpParameters()); + + // Empty CNAME should result in the existing CNAME being used. + rtcp_parameters.cname.clear(); + EXPECT_TRUE(transport->SetRtcpParameters(rtcp_parameters).ok()); + EXPECT_EQ("teST", transport->GetRtcpParameters().cname); + + // Disabling RTCP muxing after enabling shouldn't be allowed, since enabling + // muxing should have made the RTP transport forget about the RTCP packet + // transport initially passed into it. + rtcp_parameters.mux = false; + EXPECT_EQ(RTCErrorType::INVALID_STATE, + transport->SetRtcpParameters(rtcp_parameters).type()); +} + +// When Send or Receive is called on a sender or receiver, the RTCP parameters +// from the RtpTransport underneath the sender should be applied to the created +// media stream. The only relevant parameters (currently) are |cname| and +// |reduced_size|. +TEST_F(RtpTransportTest, SendAndReceiveApplyRtcpParametersToMediaEngine) { + // First, create video transport with reduced-size RTCP. + rtc::FakePacketTransport fake_packet_transport1("1"); + RtcpParameters rtcp_parameters; + rtcp_parameters.mux = true; + rtcp_parameters.reduced_size = true; + rtcp_parameters.cname = "foo"; + auto rtp_transport_result = ortc_factory_->CreateRtpTransport( + rtcp_parameters, &fake_packet_transport1, nullptr, nullptr); + auto video_transport = rtp_transport_result.MoveValue(); + + // Create video sender and call Send, expecting parameters to be applied. + auto sender_result = ortc_factory_->CreateRtpSender(cricket::MEDIA_TYPE_VIDEO, + video_transport.get()); + auto video_sender = sender_result.MoveValue(); + EXPECT_TRUE(video_sender->Send(MakeMinimalVp8Parameters()).ok()); + cricket::FakeVideoMediaChannel* fake_video_channel = + fake_media_engine_->GetVideoChannel(0); + ASSERT_NE(nullptr, fake_video_channel); + EXPECT_TRUE(fake_video_channel->send_rtcp_parameters().reduced_size); + ASSERT_EQ(1u, fake_video_channel->send_streams().size()); + const cricket::StreamParams& video_send_stream = + fake_video_channel->send_streams()[0]; + EXPECT_EQ("foo", video_send_stream.cname); + + // Create video receiver and call Receive, expecting parameters to be applied + // (minus |cname|, since that's the sent cname, not received). + auto receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_VIDEO, video_transport.get()); + auto video_receiver = receiver_result.MoveValue(); + EXPECT_TRUE( + video_receiver->Receive(MakeMinimalVp8ParametersWithSsrc(0xdeadbeef)) + .ok()); + EXPECT_TRUE(fake_video_channel->recv_rtcp_parameters().reduced_size); + + // Create audio transport with non-reduced size RTCP. + rtc::FakePacketTransport fake_packet_transport2("2"); + rtcp_parameters.reduced_size = false; + rtcp_parameters.cname = "bar"; + rtp_transport_result = ortc_factory_->CreateRtpTransport( + rtcp_parameters, &fake_packet_transport2, nullptr, nullptr); + auto audio_transport = rtp_transport_result.MoveValue(); + + // Create audio sender and call Send, expecting parameters to be applied. + sender_result = ortc_factory_->CreateRtpSender(cricket::MEDIA_TYPE_AUDIO, + audio_transport.get()); + auto audio_sender = sender_result.MoveValue(); + EXPECT_TRUE(audio_sender->Send(MakeMinimalIsacParameters()).ok()); + + cricket::FakeVoiceMediaChannel* fake_voice_channel = + fake_media_engine_->GetVoiceChannel(0); + ASSERT_NE(nullptr, fake_voice_channel); + EXPECT_FALSE(fake_voice_channel->send_rtcp_parameters().reduced_size); + ASSERT_EQ(1u, fake_voice_channel->send_streams().size()); + const cricket::StreamParams& audio_send_stream = + fake_voice_channel->send_streams()[0]; + EXPECT_EQ("bar", audio_send_stream.cname); + + // Create audio receiver and call Receive, expecting parameters to be applied + // (minus |cname|, since that's the sent cname, not received). + receiver_result = ortc_factory_->CreateRtpReceiver(cricket::MEDIA_TYPE_AUDIO, + audio_transport.get()); + auto audio_receiver = receiver_result.MoveValue(); + EXPECT_TRUE( + audio_receiver->Receive(MakeMinimalOpusParametersWithSsrc(0xbaadf00d)) + .ok()); + EXPECT_FALSE(fake_voice_channel->recv_rtcp_parameters().reduced_size); +} + +// When SetRtcpParameters is called, the modified parameters should be applied +// to the media engine. +// TODO(deadbeef): Once the implementation supports changing the CNAME, +// test that here. +TEST_F(RtpTransportTest, SetRtcpParametersAppliesParametersToMediaEngine) { + rtc::FakePacketTransport fake_packet_transport("fake"); + RtcpParameters rtcp_parameters; + rtcp_parameters.mux = true; + rtcp_parameters.reduced_size = false; + auto rtp_transport_result = ortc_factory_->CreateRtpTransport( + rtcp_parameters, &fake_packet_transport, nullptr, nullptr); + auto rtp_transport = rtp_transport_result.MoveValue(); + + // Create video sender and call Send, applying an initial set of parameters. + auto sender_result = ortc_factory_->CreateRtpSender(cricket::MEDIA_TYPE_VIDEO, + rtp_transport.get()); + auto sender = sender_result.MoveValue(); + EXPECT_TRUE(sender->Send(MakeMinimalVp8Parameters()).ok()); + + // Modify parameters and expect them to be changed at the media engine level. + rtcp_parameters.reduced_size = true; + EXPECT_TRUE(rtp_transport->SetRtcpParameters(rtcp_parameters).ok()); + + cricket::FakeVideoMediaChannel* fake_video_channel = + fake_media_engine_->GetVideoChannel(0); + ASSERT_NE(nullptr, fake_video_channel); + EXPECT_TRUE(fake_video_channel->send_rtcp_parameters().reduced_size); +} + +} // namespace webrtc diff --git a/webrtc/ortc/rtptransportadapter.cc b/webrtc/ortc/rtptransportadapter.cc new file mode 100644 index 0000000000..439f9a83b4 --- /dev/null +++ b/webrtc/ortc/rtptransportadapter.cc @@ -0,0 +1,129 @@ +/* + * 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/rtptransportadapter.h" + +#include // For std::find. +#include +#include +#include // For std::move. + +#include "webrtc/api/proxy.h" +#include "webrtc/base/logging.h" + +namespace webrtc { + +BEGIN_OWNED_PROXY_MAP(RtpTransport) +PROXY_SIGNALING_THREAD_DESTRUCTOR() +PROXY_CONSTMETHOD0(PacketTransportInterface*, GetRtpPacketTransport) +PROXY_CONSTMETHOD0(PacketTransportInterface*, GetRtcpPacketTransport) +PROXY_METHOD1(RTCError, SetRtcpParameters, const RtcpParameters&) +PROXY_CONSTMETHOD0(RtcpParameters, GetRtcpParameters) +protected: +RtpTransportAdapter* GetInternal() override { + return internal(); +} +END_PROXY_MAP() + +// static +RTCErrorOr> +RtpTransportAdapter::CreateProxied( + const RtcpParameters& rtcp_parameters, + PacketTransportInterface* rtp, + PacketTransportInterface* rtcp, + RtpTransportControllerAdapter* rtp_transport_controller) { + if (!rtp) { + LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, + "Must provide an RTP packet transport."); + } + if (!rtcp_parameters.mux && !rtcp) { + LOG_AND_RETURN_ERROR( + RTCErrorType::INVALID_PARAMETER, + "Must provide an RTCP packet transport when RTCP muxing is not used."); + } + if (rtcp_parameters.mux && rtcp) { + LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, + "Creating an RtpTransport with RTCP muxing enabled, " + "with a separate RTCP packet transport?"); + } + if (!rtp_transport_controller) { + // Since OrtcFactory::CreateRtpTransport creates an RtpTransportController + // automatically when one isn't passed in, this should never be reached. + RTC_NOTREACHED(); + LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, + "Must provide an RTP transport controller."); + } + return RtpTransportProxyWithInternal::Create( + rtp_transport_controller->signaling_thread(), + rtp_transport_controller->worker_thread(), + std::unique_ptr(new RtpTransportAdapter( + rtcp_parameters, rtp, rtcp, rtp_transport_controller))); +} + +void RtpTransportAdapter::TakeOwnershipOfRtpTransportController( + std::unique_ptr controller) { + RTC_DCHECK_EQ(rtp_transport_controller_, controller->GetInternal()); + RTC_DCHECK(owned_rtp_transport_controller_.get() == nullptr); + owned_rtp_transport_controller_ = std::move(controller); +} + +RtpTransportAdapter::RtpTransportAdapter( + const RtcpParameters& rtcp_parameters, + PacketTransportInterface* rtp, + PacketTransportInterface* rtcp, + RtpTransportControllerAdapter* rtp_transport_controller) + : rtp_packet_transport_(rtp), + rtcp_packet_transport_(rtcp), + rtp_transport_controller_(rtp_transport_controller), + rtcp_parameters_(rtcp_parameters) { + RTC_DCHECK(rtp_transport_controller); + // CNAME should have been filled by OrtcFactory if empty. + RTC_DCHECK(!rtcp_parameters_.cname.empty()); +} + +RtpTransportAdapter::~RtpTransportAdapter() { + SignalDestroyed(this); +} + +PacketTransportInterface* RtpTransportAdapter::GetRtpPacketTransport() const { + return rtp_packet_transport_; +} + +PacketTransportInterface* RtpTransportAdapter::GetRtcpPacketTransport() const { + return rtcp_packet_transport_; +} + +RTCError RtpTransportAdapter::SetRtcpParameters( + const RtcpParameters& parameters) { + if (!parameters.mux && rtcp_parameters_.mux) { + LOG_AND_RETURN_ERROR(webrtc::RTCErrorType::INVALID_STATE, + "Can't disable RTCP muxing after enabling."); + } + if (!parameters.cname.empty() && parameters.cname != rtcp_parameters_.cname) { + LOG_AND_RETURN_ERROR(webrtc::RTCErrorType::UNSUPPORTED_OPERATION, + "Changing the RTCP CNAME is currently unsupported."); + } + // If the CNAME is empty, use the existing one. + RtcpParameters copy = parameters; + if (copy.cname.empty()) { + copy.cname = rtcp_parameters_.cname; + } + RTCError err = rtp_transport_controller_->SetRtcpParameters(copy, this); + if (!err.ok()) { + return err; + } + rtcp_parameters_ = copy; + if (rtcp_parameters_.mux) { + rtcp_packet_transport_ = nullptr; + } + return RTCError::OK(); +} + +} // namespace webrtc diff --git a/webrtc/ortc/rtptransportadapter.h b/webrtc/ortc/rtptransportadapter.h new file mode 100644 index 0000000000..169ae613db --- /dev/null +++ b/webrtc/ortc/rtptransportadapter.h @@ -0,0 +1,83 @@ +/* + * 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. + */ + +#ifndef WEBRTC_ORTC_RTPTRANSPORTADAPTER_H_ +#define WEBRTC_ORTC_RTPTRANSPORTADAPTER_H_ + +#include +#include + +#include "webrtc/api/ortc/rtptransportinterface.h" +#include "webrtc/api/rtcerror.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/media/base/streamparams.h" +#include "webrtc/ortc/rtptransportcontrolleradapter.h" +#include "webrtc/pc/channel.h" + +namespace webrtc { + +// Implementation of RtpTransportInterface to be used with RtpSenderAdapter, +// RtpReceiverAdapter, and RtpTransportControllerAdapter classes. +// +// TODO(deadbeef): When BaseChannel is split apart into separate +// "RtpTransport"/"RtpTransceiver"/"RtpSender"/"RtpReceiver" objects, this +// adapter object can be removed. +class RtpTransportAdapter : public RtpTransportInterface { + public: + // |rtp| can't be null. |rtcp| can if RTCP muxing is used immediately (meaning + // |rtcp_parameters.mux| is also true). + static RTCErrorOr> CreateProxied( + const RtcpParameters& rtcp_parameters, + PacketTransportInterface* rtp, + PacketTransportInterface* rtcp, + RtpTransportControllerAdapter* rtp_transport_controller); + ~RtpTransportAdapter() override; + + // RtpTransportInterface implementation. + PacketTransportInterface* GetRtpPacketTransport() const override; + PacketTransportInterface* GetRtcpPacketTransport() const override; + RTCError SetRtcpParameters(const RtcpParameters& parameters) override; + RtcpParameters GetRtcpParameters() const override { return rtcp_parameters_; } + + // Methods used internally by OrtcFactory. + RtpTransportControllerAdapter* rtp_transport_controller() { + return rtp_transport_controller_; + } + void TakeOwnershipOfRtpTransportController( + std::unique_ptr controller); + + // Used by RtpTransportControllerAdapter to tell when it should stop + // returning this transport from GetTransports(). + sigslot::signal1 SignalDestroyed; + + protected: + RtpTransportAdapter* GetInternal() override { return this; } + + private: + RtpTransportAdapter(const RtcpParameters& rtcp_parameters, + PacketTransportInterface* rtp, + PacketTransportInterface* rtcp, + RtpTransportControllerAdapter* rtp_transport_controller); + + PacketTransportInterface* rtp_packet_transport_; + PacketTransportInterface* rtcp_packet_transport_; + RtpTransportControllerAdapter* rtp_transport_controller_; + // Non-null if this class owns the transport controller. + std::unique_ptr + owned_rtp_transport_controller_; + RtcpParameters rtcp_parameters_; + + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RtpTransportAdapter); +}; + +} // namespace webrtc + +#endif // WEBRTC_ORTC_RTPTRANSPORTADAPTER_H_ diff --git a/webrtc/ortc/rtptransportcontroller_unittest.cc b/webrtc/ortc/rtptransportcontroller_unittest.cc new file mode 100644 index 0000000000..40e9851d24 --- /dev/null +++ b/webrtc/ortc/rtptransportcontroller_unittest.cc @@ -0,0 +1,195 @@ +/* + * 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 + +#include "webrtc/base/gunit.h" +#include "webrtc/media/base/fakemediaengine.h" +#include "webrtc/ortc/ortcfactory.h" +#include "webrtc/ortc/testrtpparameters.h" +#include "webrtc/p2p/base/fakepackettransport.h" + +namespace webrtc { + +// This test uses fake packet transports and a fake media engine, in order to +// test the RtpTransportController at only an API level. Any end-to-end test +// should go in ortcfactory_integrationtest.cc instead. +// +// Currently, this test mainly focuses on the limitations of the "adapter" +// RtpTransportController implementation. Only one of each type of +// sender/receiver can be created, and the sender/receiver of the same media +// type must use the same transport. +class RtpTransportControllerTest : public testing::Test { + public: + RtpTransportControllerTest() { + // Note: This doesn't need to use fake network classes, since it uses + // FakePacketTransports. + auto result = + OrtcFactory::Create(nullptr, nullptr, nullptr, nullptr, nullptr, + std::unique_ptr( + new cricket::FakeMediaEngine())); + ortc_factory_ = result.MoveValue(); + rtp_transport_controller_ = + ortc_factory_->CreateRtpTransportController().MoveValue(); + } + + protected: + std::unique_ptr ortc_factory_; + std::unique_ptr rtp_transport_controller_; +}; + +TEST_F(RtpTransportControllerTest, GetTransports) { + rtc::FakePacketTransport packet_transport1("one"); + rtc::FakePacketTransport packet_transport2("two"); + + auto rtp_transport_result1 = ortc_factory_->CreateRtpTransport( + MakeRtcpMuxParameters(), &packet_transport1, nullptr, + rtp_transport_controller_.get()); + ASSERT_TRUE(rtp_transport_result1.ok()); + + auto rtp_transport_result2 = ortc_factory_->CreateRtpTransport( + MakeRtcpMuxParameters(), &packet_transport2, nullptr, + rtp_transport_controller_.get()); + ASSERT_TRUE(rtp_transport_result2.ok()); + + auto returned_transports = rtp_transport_controller_->GetTransports(); + ASSERT_EQ(2u, returned_transports.size()); + EXPECT_EQ(rtp_transport_result1.value().get(), returned_transports[0]); + EXPECT_EQ(rtp_transport_result2.value().get(), returned_transports[1]); + + // If a transport is deleted, it shouldn't be returned any more. + rtp_transport_result1.MoveValue().reset(); + returned_transports = rtp_transport_controller_->GetTransports(); + ASSERT_EQ(1u, returned_transports.size()); + EXPECT_EQ(rtp_transport_result2.value().get(), returned_transports[0]); +} + +// Create RtpSenders and RtpReceivers on top of RtpTransports controlled by the +// same RtpTransportController. Currently only one each of audio/video is +// supported. +TEST_F(RtpTransportControllerTest, AttachMultipleSendersAndReceivers) { + rtc::FakePacketTransport audio_packet_transport("audio"); + rtc::FakePacketTransport video_packet_transport("video"); + + auto audio_rtp_transport_result = ortc_factory_->CreateRtpTransport( + MakeRtcpMuxParameters(), &audio_packet_transport, nullptr, + rtp_transport_controller_.get()); + ASSERT_TRUE(audio_rtp_transport_result.ok()); + auto audio_rtp_transport = audio_rtp_transport_result.MoveValue(); + + auto video_rtp_transport_result = ortc_factory_->CreateRtpTransport( + MakeRtcpMuxParameters(), &video_packet_transport, nullptr, + rtp_transport_controller_.get()); + ASSERT_TRUE(video_rtp_transport_result.ok()); + auto video_rtp_transport = video_rtp_transport_result.MoveValue(); + + auto audio_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_AUDIO, audio_rtp_transport.get()); + EXPECT_TRUE(audio_sender_result.ok()); + auto audio_receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_AUDIO, audio_rtp_transport.get()); + EXPECT_TRUE(audio_receiver_result.ok()); + auto video_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_VIDEO, video_rtp_transport.get()); + EXPECT_TRUE(video_sender_result.ok()); + auto video_receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_VIDEO, video_rtp_transport.get()); + EXPECT_TRUE(video_receiver_result.ok()); + + // Now that we have one each of audio/video senders/receivers, trying to + // create more on top of the same controller is expected to fail. + // TODO(deadbeef): Update this test once multiple senders/receivers on top of + // the same controller is supported. + auto failed_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_AUDIO, audio_rtp_transport.get()); + EXPECT_EQ(RTCErrorType::UNSUPPORTED_OPERATION, + failed_sender_result.error().type()); + auto failed_receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_AUDIO, audio_rtp_transport.get()); + EXPECT_EQ(RTCErrorType::UNSUPPORTED_OPERATION, + failed_receiver_result.error().type()); + failed_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_VIDEO, video_rtp_transport.get()); + EXPECT_EQ(RTCErrorType::UNSUPPORTED_OPERATION, + failed_sender_result.error().type()); + failed_receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_VIDEO, video_rtp_transport.get()); + EXPECT_EQ(RTCErrorType::UNSUPPORTED_OPERATION, + failed_receiver_result.error().type()); + + // If we destroy the existing sender/receiver using a transport controller, + // we should be able to make a new one, despite the above limitation. + audio_sender_result.MoveValue().reset(); + audio_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_AUDIO, audio_rtp_transport.get()); + EXPECT_TRUE(audio_sender_result.ok()); + audio_receiver_result.MoveValue().reset(); + audio_receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_AUDIO, audio_rtp_transport.get()); + EXPECT_TRUE(audio_receiver_result.ok()); + video_sender_result.MoveValue().reset(); + video_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_VIDEO, video_rtp_transport.get()); + EXPECT_TRUE(video_sender_result.ok()); + video_receiver_result.MoveValue().reset(); + video_receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_VIDEO, video_rtp_transport.get()); + EXPECT_TRUE(video_receiver_result.ok()); +} + +// Given the current limitations of the BaseChannel-based implementation, it's +// not possible for an audio sender and receiver to use different RtpTransports. +// TODO(deadbeef): Once this is supported, update/replace this test. +TEST_F(RtpTransportControllerTest, + SenderAndReceiverUsingDifferentTransportsUnsupported) { + rtc::FakePacketTransport packet_transport1("one"); + rtc::FakePacketTransport packet_transport2("two"); + + auto rtp_transport_result1 = ortc_factory_->CreateRtpTransport( + MakeRtcpMuxParameters(), &packet_transport1, nullptr, + rtp_transport_controller_.get()); + ASSERT_TRUE(rtp_transport_result1.ok()); + auto rtp_transport1 = rtp_transport_result1.MoveValue(); + + auto rtp_transport_result2 = ortc_factory_->CreateRtpTransport( + MakeRtcpMuxParameters(), &packet_transport2, nullptr, + rtp_transport_controller_.get()); + ASSERT_TRUE(rtp_transport_result2.ok()); + auto rtp_transport2 = rtp_transport_result2.MoveValue(); + + // Create an audio sender on transport 1, then try to create a receiver on 2. + auto audio_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_AUDIO, rtp_transport1.get()); + EXPECT_TRUE(audio_sender_result.ok()); + auto audio_receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_AUDIO, rtp_transport2.get()); + EXPECT_EQ(RTCErrorType::UNSUPPORTED_OPERATION, + audio_receiver_result.error().type()); + // Delete the sender; now we should be ok to create the receiver on 2. + audio_sender_result.MoveValue().reset(); + audio_receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_AUDIO, rtp_transport2.get()); + EXPECT_TRUE(audio_receiver_result.ok()); + + // Do the same thing for video, reversing 1 and 2 (for variety). + auto video_sender_result = ortc_factory_->CreateRtpSender( + cricket::MEDIA_TYPE_VIDEO, rtp_transport2.get()); + EXPECT_TRUE(video_sender_result.ok()); + auto video_receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_VIDEO, rtp_transport1.get()); + EXPECT_EQ(RTCErrorType::UNSUPPORTED_OPERATION, + video_receiver_result.error().type()); + video_sender_result.MoveValue().reset(); + video_receiver_result = ortc_factory_->CreateRtpReceiver( + cricket::MEDIA_TYPE_VIDEO, rtp_transport1.get()); + EXPECT_TRUE(video_receiver_result.ok()); +} + +} // namespace webrtc diff --git a/webrtc/ortc/rtptransportcontrolleradapter.cc b/webrtc/ortc/rtptransportcontrolleradapter.cc new file mode 100644 index 0000000000..08e943a200 --- /dev/null +++ b/webrtc/ortc/rtptransportcontrolleradapter.cc @@ -0,0 +1,899 @@ +/* + * 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 // For "remove", "find". +#include +#include +#include +#include // 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 +static RTCError CheckForIdConflicts( + const std::vector& codecs_a, + const cricket::RtpHeaderExtensions& extensions_a, + const cricket::StreamParamsVec& streams_a, + const std::vector& 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 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 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 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, GetTransports) +protected: +RtpTransportControllerAdapter* GetInternal() override { + return internal(); +} +END_PROXY_MAP() + +// static +std::unique_ptr +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 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> +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> +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 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> +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 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 +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(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(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(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(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* 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 +RtpTransportControllerAdapter::MakeSendStreamParamsVec( + std::vector 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 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 diff --git a/webrtc/ortc/rtptransportcontrolleradapter.h b/webrtc/ortc/rtptransportcontrolleradapter.h new file mode 100644 index 0000000000..4e02b95955 --- /dev/null +++ b/webrtc/ortc/rtptransportcontrolleradapter.h @@ -0,0 +1,202 @@ +/* + * 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. + */ + +#ifndef WEBRTC_ORTC_RTPTRANSPORTCONTROLLERADAPTER_H_ +#define WEBRTC_ORTC_RTPTRANSPORTCONTROLLERADAPTER_H_ + +#include +#include +#include +#include + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/thread.h" +#include "webrtc/call/call.h" +#include "webrtc/logging/rtc_event_log/rtc_event_log.h" +#include "webrtc/api/ortc/ortcrtpreceiverinterface.h" +#include "webrtc/api/ortc/ortcrtpsenderinterface.h" +#include "webrtc/api/ortc/rtptransportcontrollerinterface.h" +#include "webrtc/api/ortc/rtptransportinterface.h" +#include "webrtc/pc/channelmanager.h" +#include "webrtc/pc/mediacontroller.h" +#include "webrtc/media/base/mediachannel.h" // For MediaConfig. + +namespace webrtc { + +class RtpTransportAdapter; +class OrtcRtpSenderAdapter; +class OrtcRtpReceiverAdapter; + +// Implementation of RtpTransportControllerInterface. Wraps a MediaController, +// a VoiceChannel and VideoChannel, and maintains a list of dependent RTP +// transports. +// +// When used along with an RtpSenderAdapter or RtpReceiverAdapter, the +// sender/receiver passes its parameters along to this class, which turns them +// into cricket:: media descriptions (the interface used by BaseChannel). +// +// Due to the fact that BaseChannel has different subclasses for audio/video, +// the actual BaseChannel object is not created until an RtpSender/RtpReceiver +// needs them. +// +// All methods should be called on the signaling thread. +// +// TODO(deadbeef): When BaseChannel is split apart into separate +// "RtpSender"/"RtpTransceiver"/"RtpSender"/"RtpReceiver" objects, this adapter +// object can be replaced by a "real" one. +class RtpTransportControllerAdapter : public RtpTransportControllerInterface, + public sigslot::has_slots<> { + public: + // Creates a proxy that will call "public interface" methods on the correct + // thread. + // + // Doesn't take ownership of any objects passed in. + // + // |channel_manager| must not be null. + static std::unique_ptr CreateProxied( + const cricket::MediaConfig& config, + cricket::ChannelManager* channel_manager, + webrtc::RtcEventLog* event_log, + rtc::Thread* signaling_thread, + rtc::Thread* worker_thread); + + ~RtpTransportControllerAdapter() override; + + // RtpTransportControllerInterface implementation. + std::vector GetTransports() const override; + + // These methods are used by OrtcFactory to create RtpTransports, RtpSenders + // and RtpReceivers using this controller. Called "CreateProxied" because + // these methods return proxies that will safely call methods on the correct + // thread. + RTCErrorOr> CreateProxiedRtpTransport( + const RtcpParameters& rtcp_parameters, + PacketTransportInterface* rtp, + PacketTransportInterface* rtcp); + // |transport_proxy| needs to be a proxy to a transport because the + // application may call GetTransport() on the returned sender or receiver, + // and expects it to return a thread-safe transport proxy. + RTCErrorOr> CreateProxiedRtpSender( + cricket::MediaType kind, + RtpTransportInterface* transport_proxy); + RTCErrorOr> + CreateProxiedRtpReceiver(cricket::MediaType kind, + RtpTransportInterface* transport_proxy); + + // Methods used internally by other "adapter" classes. + rtc::Thread* signaling_thread() const { return signaling_thread_; } + rtc::Thread* worker_thread() const { return worker_thread_; } + + RTCError SetRtcpParameters(const RtcpParameters& parameters, + RtpTransportInterface* inner_transport); + + cricket::VoiceChannel* voice_channel() { return voice_channel_; } + cricket::VideoChannel* video_channel() { return video_channel_; } + + // |primary_ssrc| out parameter is filled with either + // |parameters.encodings[0].ssrc|, or a generated SSRC if that's left unset. + RTCError ValidateAndApplyAudioSenderParameters( + const RtpParameters& parameters, + uint32_t* primary_ssrc); + RTCError ValidateAndApplyVideoSenderParameters( + const RtpParameters& parameters, + uint32_t* primary_ssrc); + RTCError ValidateAndApplyAudioReceiverParameters( + const RtpParameters& parameters); + RTCError ValidateAndApplyVideoReceiverParameters( + const RtpParameters& parameters); + + protected: + RtpTransportControllerAdapter* GetInternal() override { return this; } + + private: + // Only expected to be called by RtpTransportControllerAdapter::CreateProxied. + RtpTransportControllerAdapter(const cricket::MediaConfig& config, + cricket::ChannelManager* channel_manager, + webrtc::RtcEventLog* event_log, + rtc::Thread* signaling_thread, + rtc::Thread* worker_thread); + + // These return an error if another of the same type of object is already + // attached, or if |transport_proxy| can't be used with the sender/receiver + // due to the limitation that the sender/receiver of the same media type must + // use the same transport. + RTCError AttachAudioSender(OrtcRtpSenderAdapter* sender, + RtpTransportInterface* inner_transport); + RTCError AttachVideoSender(OrtcRtpSenderAdapter* sender, + RtpTransportInterface* inner_transport); + RTCError AttachAudioReceiver(OrtcRtpReceiverAdapter* receiver, + RtpTransportInterface* inner_transport); + RTCError AttachVideoReceiver(OrtcRtpReceiverAdapter* receiver, + RtpTransportInterface* inner_transport); + + void OnRtpTransportDestroyed(RtpTransportAdapter* transport); + + void OnAudioSenderDestroyed(); + void OnVideoSenderDestroyed(); + void OnAudioReceiverDestroyed(); + void OnVideoReceiverDestroyed(); + + void CreateVoiceChannel(); + void CreateVideoChannel(); + void DestroyVoiceChannel(); + void DestroyVideoChannel(); + + void CopyRtcpParametersToDescriptions( + const RtcpParameters& params, + cricket::MediaContentDescription* local, + cricket::MediaContentDescription* remote); + + // Helper function to generate an SSRC that doesn't match one in any of the + // "content description" structs, or in |new_ssrcs| (which is needed since + // multiple SSRCs may be generated in one go). + uint32_t GenerateUnusedSsrc(std::set* new_ssrcs) const; + + // |description| is the matching description where existing SSRCs can be + // found. + // + // This is a member function because it may need to generate SSRCs that don't + // match existing ones, which is more than ToStreamParamsVec does. + RTCErrorOr MakeSendStreamParamsVec( + std::vector encodings, + const std::string& cname, + const cricket::MediaContentDescription& description) const; + + rtc::Thread* signaling_thread_; + rtc::Thread* worker_thread_; + // |transport_proxies_| and |inner_audio_transport_|/|inner_audio_transport_| + // are somewhat redundant, but the latter are only set when + // RtpSenders/RtpReceivers are attached to the transport. + std::vector transport_proxies_; + RtpTransportInterface* inner_audio_transport_ = nullptr; + RtpTransportInterface* inner_video_transport_ = nullptr; + std::unique_ptr media_controller_; + + // BaseChannel takes content descriptions as input, so we store them here + // such that they can be updated when a new RtpSenderAdapter/ + // RtpReceiverAdapter attaches itself. + cricket::AudioContentDescription local_audio_description_; + cricket::AudioContentDescription remote_audio_description_; + cricket::VideoContentDescription local_video_description_; + cricket::VideoContentDescription remote_video_description_; + cricket::VoiceChannel* voice_channel_ = nullptr; + cricket::VideoChannel* video_channel_ = nullptr; + bool have_audio_sender_ = false; + bool have_video_sender_ = false; + bool have_audio_receiver_ = false; + bool have_video_receiver_ = false; + + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RtpTransportControllerAdapter); +}; + +} // namespace webrtc + +#endif // WEBRTC_ORTC_RTPTRANSPORTCONTROLLERADAPTER_H_ diff --git a/webrtc/ortc/testrtpparameters.cc b/webrtc/ortc/testrtpparameters.cc new file mode 100644 index 0000000000..de2e7d5bd2 --- /dev/null +++ b/webrtc/ortc/testrtpparameters.cc @@ -0,0 +1,311 @@ +/* + * 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/testrtpparameters.h" + +#include +#include + +namespace webrtc { + +RtpParameters MakeMinimalOpusParameters() { + RtpParameters parameters; + RtpCodecParameters opus_codec; + opus_codec.name = "opus"; + opus_codec.kind = cricket::MEDIA_TYPE_AUDIO; + opus_codec.payload_type = 111; + opus_codec.clock_rate.emplace(48000); + opus_codec.num_channels.emplace(2); + parameters.codecs.push_back(std::move(opus_codec)); + RtpEncodingParameters encoding; + encoding.codec_payload_type.emplace(111); + parameters.encodings.push_back(std::move(encoding)); + return parameters; +} + +RtpParameters MakeMinimalIsacParameters() { + RtpParameters parameters; + RtpCodecParameters isac_codec; + isac_codec.name = "ISAC"; + isac_codec.kind = cricket::MEDIA_TYPE_AUDIO; + isac_codec.payload_type = 103; + isac_codec.clock_rate.emplace(16000); + parameters.codecs.push_back(std::move(isac_codec)); + RtpEncodingParameters encoding; + encoding.codec_payload_type.emplace(111); + parameters.encodings.push_back(std::move(encoding)); + return parameters; +} + +RtpParameters MakeMinimalOpusParametersWithSsrc(uint32_t ssrc) { + RtpParameters parameters = MakeMinimalOpusParameters(); + parameters.encodings[0].ssrc.emplace(ssrc); + return parameters; +} + +RtpParameters MakeMinimalIsacParametersWithSsrc(uint32_t ssrc) { + RtpParameters parameters = MakeMinimalIsacParameters(); + parameters.encodings[0].ssrc.emplace(ssrc); + return parameters; +} + +RtpParameters MakeMinimalVideoParameters(const char* codec_name) { + RtpParameters parameters; + RtpCodecParameters vp8_codec; + vp8_codec.name = codec_name; + vp8_codec.kind = cricket::MEDIA_TYPE_VIDEO; + vp8_codec.payload_type = 96; + parameters.codecs.push_back(std::move(vp8_codec)); + RtpEncodingParameters encoding; + encoding.codec_payload_type.emplace(96); + parameters.encodings.push_back(std::move(encoding)); + return parameters; +} + +RtpParameters MakeMinimalVp8Parameters() { + return MakeMinimalVideoParameters("VP8"); +} + +RtpParameters MakeMinimalVp9Parameters() { + return MakeMinimalVideoParameters("VP9"); +} + +RtpParameters MakeMinimalVp8ParametersWithSsrc(uint32_t ssrc) { + RtpParameters parameters = MakeMinimalVp8Parameters(); + parameters.encodings[0].ssrc.emplace(ssrc); + return parameters; +} + +RtpParameters MakeMinimalVp9ParametersWithSsrc(uint32_t ssrc) { + RtpParameters parameters = MakeMinimalVp9Parameters(); + parameters.encodings[0].ssrc.emplace(ssrc); + return parameters; +} + +// Note: Currently, these "WithNoSsrc" methods are identical to the normal +// "MakeMinimal" methods, but with the added guarantee that they will never be +// changed to include an SSRC. + +RtpParameters MakeMinimalOpusParametersWithNoSsrc() { + RtpParameters parameters = MakeMinimalOpusParameters(); + RTC_DCHECK(!parameters.encodings[0].ssrc); + return parameters; +} + +RtpParameters MakeMinimalIsacParametersWithNoSsrc() { + RtpParameters parameters = MakeMinimalIsacParameters(); + RTC_DCHECK(!parameters.encodings[0].ssrc); + return parameters; +} + +RtpParameters MakeMinimalVp8ParametersWithNoSsrc() { + RtpParameters parameters = MakeMinimalVp8Parameters(); + RTC_DCHECK(!parameters.encodings[0].ssrc); + return parameters; +} + +RtpParameters MakeMinimalVp9ParametersWithNoSsrc() { + RtpParameters parameters = MakeMinimalVp9Parameters(); + RTC_DCHECK(!parameters.encodings[0].ssrc); + return parameters; +} + +// Make audio parameters with all the available properties configured and +// features used, and with multiple codecs offered. Obtained by taking a +// snapshot of a default PeerConnection offer (and adding other things, like +// bitrate limit). +// +// See "MakeFullOpusParameters"/"MakeFullIsacParameters" below. +RtpParameters MakeFullAudioParameters(int preferred_payload_type) { + RtpParameters parameters; + + RtpCodecParameters opus_codec; + opus_codec.name = "opus"; + opus_codec.kind = cricket::MEDIA_TYPE_AUDIO; + opus_codec.payload_type = 111; + opus_codec.clock_rate.emplace(48000); + opus_codec.num_channels.emplace(2); + opus_codec.parameters["minptime"] = "10"; + opus_codec.parameters["useinbandfec"] = "1"; + opus_codec.parameters["usedtx"] = "1"; + opus_codec.parameters["stereo"] = "1"; + opus_codec.rtcp_feedback.emplace_back(RtcpFeedbackType::TRANSPORT_CC); + parameters.codecs.push_back(std::move(opus_codec)); + + RtpCodecParameters isac_codec; + isac_codec.name = "ISAC"; + isac_codec.kind = cricket::MEDIA_TYPE_AUDIO; + isac_codec.payload_type = 103; + isac_codec.clock_rate.emplace(16000); + parameters.codecs.push_back(std::move(isac_codec)); + + RtpCodecParameters cn_codec; + cn_codec.name = "CN"; + cn_codec.kind = cricket::MEDIA_TYPE_AUDIO; + cn_codec.payload_type = 106; + cn_codec.clock_rate.emplace(32000); + parameters.codecs.push_back(std::move(cn_codec)); + + RtpCodecParameters dtmf_codec; + dtmf_codec.name = "telephone-event"; + dtmf_codec.kind = cricket::MEDIA_TYPE_AUDIO; + dtmf_codec.payload_type = 126; + dtmf_codec.clock_rate.emplace(8000); + parameters.codecs.push_back(std::move(dtmf_codec)); + + // "codec_payload_type" isn't implemented, so we need to reorder codecs to + // cause one to be used. + // TODO(deadbeef): Remove this when it becomes unnecessary. + std::sort(parameters.codecs.begin(), parameters.codecs.end(), + [preferred_payload_type](const RtpCodecParameters& a, + const RtpCodecParameters& b) { + return a.payload_type == preferred_payload_type; + }); + + // Intentionally leave out SSRC so one's chosen automatically. + RtpEncodingParameters encoding; + encoding.codec_payload_type.emplace(preferred_payload_type); + encoding.dtx.emplace(DtxStatus::ENABLED); + // 20 kbps. + encoding.max_bitrate_bps.emplace(20000); + parameters.encodings.push_back(std::move(encoding)); + + parameters.header_extensions.emplace_back( + "urn:ietf:params:rtp-hdrext:ssrc-audio-level", 1); + return parameters; +} + +RtpParameters MakeFullOpusParameters() { + return MakeFullAudioParameters(111); +} + +RtpParameters MakeFullIsacParameters() { + return MakeFullAudioParameters(103); +} + +// Make video parameters with all the available properties configured and +// features used, and with multiple codecs offered. Obtained by taking a +// snapshot of a default PeerConnection offer (and adding other things, like +// bitrate limit). +// +// See "MakeFullVp8Parameters"/"MakeFullVp9Parameters" below. +RtpParameters MakeFullVideoParameters(int preferred_payload_type) { + RtpParameters parameters; + + RtpCodecParameters vp8_codec; + vp8_codec.name = "VP8"; + vp8_codec.kind = cricket::MEDIA_TYPE_VIDEO; + vp8_codec.payload_type = 100; + vp8_codec.clock_rate.emplace(90000); + vp8_codec.rtcp_feedback.emplace_back(RtcpFeedbackType::CCM, + RtcpFeedbackMessageType::FIR); + vp8_codec.rtcp_feedback.emplace_back(RtcpFeedbackType::NACK, + RtcpFeedbackMessageType::GENERIC_NACK); + vp8_codec.rtcp_feedback.emplace_back(RtcpFeedbackType::NACK, + RtcpFeedbackMessageType::PLI); + vp8_codec.rtcp_feedback.emplace_back(RtcpFeedbackType::REMB); + vp8_codec.rtcp_feedback.emplace_back(RtcpFeedbackType::TRANSPORT_CC); + parameters.codecs.push_back(std::move(vp8_codec)); + + RtpCodecParameters vp8_rtx_codec; + vp8_rtx_codec.name = "rtx"; + vp8_rtx_codec.kind = cricket::MEDIA_TYPE_VIDEO; + vp8_rtx_codec.payload_type = 96; + vp8_rtx_codec.clock_rate.emplace(90000); + vp8_rtx_codec.parameters["apt"] = "100"; + parameters.codecs.push_back(std::move(vp8_rtx_codec)); + + RtpCodecParameters vp9_codec; + vp9_codec.name = "VP9"; + vp9_codec.kind = cricket::MEDIA_TYPE_VIDEO; + vp9_codec.payload_type = 101; + vp9_codec.clock_rate.emplace(90000); + vp9_codec.rtcp_feedback.emplace_back(RtcpFeedbackType::CCM, + RtcpFeedbackMessageType::FIR); + vp9_codec.rtcp_feedback.emplace_back(RtcpFeedbackType::NACK, + RtcpFeedbackMessageType::GENERIC_NACK); + vp9_codec.rtcp_feedback.emplace_back(RtcpFeedbackType::NACK, + RtcpFeedbackMessageType::PLI); + vp9_codec.rtcp_feedback.emplace_back(RtcpFeedbackType::REMB); + vp9_codec.rtcp_feedback.emplace_back(RtcpFeedbackType::TRANSPORT_CC); + parameters.codecs.push_back(std::move(vp9_codec)); + + RtpCodecParameters vp9_rtx_codec; + vp9_rtx_codec.name = "rtx"; + vp9_rtx_codec.kind = cricket::MEDIA_TYPE_VIDEO; + vp9_rtx_codec.payload_type = 97; + vp9_rtx_codec.clock_rate.emplace(90000); + vp9_rtx_codec.parameters["apt"] = "101"; + parameters.codecs.push_back(std::move(vp9_rtx_codec)); + + RtpCodecParameters red_codec; + red_codec.name = "red"; + red_codec.kind = cricket::MEDIA_TYPE_VIDEO; + red_codec.payload_type = 116; + red_codec.clock_rate.emplace(90000); + parameters.codecs.push_back(std::move(red_codec)); + + RtpCodecParameters red_rtx_codec; + red_rtx_codec.name = "rtx"; + red_rtx_codec.kind = cricket::MEDIA_TYPE_VIDEO; + red_rtx_codec.payload_type = 98; + red_rtx_codec.clock_rate.emplace(90000); + red_rtx_codec.parameters["apt"] = "116"; + parameters.codecs.push_back(std::move(red_rtx_codec)); + + RtpCodecParameters ulpfec_codec; + ulpfec_codec.name = "ulpfec"; + ulpfec_codec.kind = cricket::MEDIA_TYPE_VIDEO; + ulpfec_codec.payload_type = 117; + ulpfec_codec.clock_rate.emplace(90000); + parameters.codecs.push_back(std::move(ulpfec_codec)); + + // "codec_payload_type" isn't implemented, so we need to reorder codecs to + // cause one to be used. + // TODO(deadbeef): Remove this when it becomes unnecessary. + std::sort(parameters.codecs.begin(), parameters.codecs.end(), + [preferred_payload_type](const RtpCodecParameters& a, + const RtpCodecParameters& b) { + return a.payload_type == preferred_payload_type; + }); + + // Intentionally leave out SSRC so one's chosen automatically. + RtpEncodingParameters encoding; + encoding.codec_payload_type.emplace(preferred_payload_type); + encoding.fec.emplace(FecMechanism::RED_AND_ULPFEC); + // Will create default RtxParameters, with unset SSRC. + encoding.rtx.emplace(); + // 100 kbps. + encoding.max_bitrate_bps.emplace(100000); + parameters.encodings.push_back(std::move(encoding)); + + parameters.header_extensions.emplace_back( + "urn:ietf:params:rtp-hdrext:toffset", 2); + parameters.header_extensions.emplace_back( + "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time", 3); + parameters.header_extensions.emplace_back("urn:3gpp:video-orientation", 4); + parameters.header_extensions.emplace_back( + "http://www.ietf.org/id/" + "draft-holmer-rmcat-transport-wide-cc-extensions-01", + 5); + parameters.header_extensions.emplace_back( + "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay", 6); + return parameters; +} + +RtpParameters MakeFullVp8Parameters() { + return MakeFullVideoParameters(100); +} + +RtpParameters MakeFullVp9Parameters() { + return MakeFullVideoParameters(101); +} + +} // namespace webrtc diff --git a/webrtc/ortc/testrtpparameters.h b/webrtc/ortc/testrtpparameters.h new file mode 100644 index 0000000000..87108ca44f --- /dev/null +++ b/webrtc/ortc/testrtpparameters.h @@ -0,0 +1,72 @@ +/* + * 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. + */ + +#ifndef WEBRTC_ORTC_TESTRTPPARAMETERS_H_ +#define WEBRTC_ORTC_TESTRTPPARAMETERS_H_ + +#include "webrtc/api/ortc/rtptransportinterface.h" +#include "webrtc/api/rtpparameters.h" + +namespace webrtc { + +// Helper methods to create RtpParameters to use for sending/receiving. +// +// "MakeMinimal" methods contain the minimal necessary information for an +// RtpSender or RtpReceiver to function. The "MakeFull" methods are the +// opposite, and include all features that would normally be offered by a +// PeerConnection, and in some cases additional ones. +// +// These methods are intended to be used for end-to-end testing (such as in +// ortcfactory_integrationtest.cc), or unit testing that doesn't care about the +// specific contents of the parameters. Tests should NOT assume that these +// methods will not change; tests that are testing that a specific value in the +// parameters is applied properly should construct the parameters in the test +// itself. + +inline RtcpParameters MakeRtcpMuxParameters() { + RtcpParameters rtcp_parameters; + rtcp_parameters.mux = true; + return rtcp_parameters; +} + +RtpParameters MakeMinimalOpusParameters(); +RtpParameters MakeMinimalIsacParameters(); +RtpParameters MakeMinimalOpusParametersWithSsrc(uint32_t ssrc); +RtpParameters MakeMinimalIsacParametersWithSsrc(uint32_t ssrc); + +RtpParameters MakeMinimalVp8Parameters(); +RtpParameters MakeMinimalVp9Parameters(); +RtpParameters MakeMinimalVp8ParametersWithSsrc(uint32_t ssrc); +RtpParameters MakeMinimalVp9ParametersWithSsrc(uint32_t ssrc); + +// Will create an encoding with no SSRC (meaning "match first SSRC seen" for a +// receiver, or "pick one automatically" for a sender). +RtpParameters MakeMinimalOpusParametersWithNoSsrc(); +RtpParameters MakeMinimalIsacParametersWithNoSsrc(); +RtpParameters MakeMinimalVp8ParametersWithNoSsrc(); +RtpParameters MakeMinimalVp9ParametersWithNoSsrc(); + +// Make audio parameters with all the available properties configured and +// features used, and with multiple codecs offered. Obtained by taking a +// snapshot of a default PeerConnection offer (and adding other things, like +// bitrate limit). +RtpParameters MakeFullOpusParameters(); +RtpParameters MakeFullIsacParameters(); + +// Make video parameters with all the available properties configured and +// features used, and with multiple codecs offered. Obtained by taking a +// snapshot of a default PeerConnection offer (and adding other things, like +// bitrate limit). +RtpParameters MakeFullVp8Parameters(); +RtpParameters MakeFullVp9Parameters(); + +} // namespace webrtc + +#endif // WEBRTC_ORTC_TESTRTPPARAMETERS_H_ diff --git a/webrtc/p2p/base/fakepackettransport.h b/webrtc/p2p/base/fakepackettransport.h index 03966f9ca0..7e2f08437e 100644 --- a/webrtc/p2p/base/fakepackettransport.h +++ b/webrtc/p2p/base/fakepackettransport.h @@ -13,6 +13,7 @@ #include +#include "webrtc/api/ortc/packettransportinterface.h" #include "webrtc/base/asyncinvoker.h" #include "webrtc/base/copyonwritebuffer.h" #include "webrtc/p2p/base/packettransportinternal.h" @@ -20,7 +21,8 @@ namespace rtc { // Used to simulate a packet-based transport. -class FakePacketTransport : public PacketTransportInternal { +class FakePacketTransport : public PacketTransportInternal, + public webrtc::PacketTransportInterface { public: explicit FakePacketTransport(const std::string& debug_name) : debug_name_(debug_name) {} @@ -85,6 +87,9 @@ class FakePacketTransport : public PacketTransportInternal { bool GetOption(Socket::Option opt, int* value) override { return true; } int GetError() override { return 0; } + protected: + PacketTransportInternal* GetInternal() override { return this; } + private: void set_writable(bool writable) { if (writable_ == writable) { diff --git a/webrtc/p2p/base/packettransportinternal.h b/webrtc/p2p/base/packettransportinternal.h index 5789c62eea..325c00e65c 100644 --- a/webrtc/p2p/base/packettransportinternal.h +++ b/webrtc/p2p/base/packettransportinternal.h @@ -1,5 +1,5 @@ /* - * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * 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 @@ -30,8 +30,6 @@ struct SentPacket; class PacketTransportInternal : public sigslot::has_slots<> { public: - virtual ~PacketTransportInternal() {} - // Identify the object for logging and debug purpose. virtual std::string debug_name() const = 0; @@ -59,7 +57,7 @@ class PacketTransportInternal : public sigslot::has_slots<> { // supported by all transport types. virtual int SetOption(rtc::Socket::Option opt, int value) = 0; - // TODO(pthatcher): Once Chrome's MockPacketTransportInternal implements + // TODO(pthatcher): Once Chrome's MockPacketTransportInterface implements // this, remove the default implementation. virtual bool GetOption(rtc::Socket::Option opt, int* value) { return false; } diff --git a/webrtc/p2p/base/udptransport.h b/webrtc/p2p/base/udptransport.h index e8bd49382f..1cf8e424db 100644 --- a/webrtc/p2p/base/udptransport.h +++ b/webrtc/p2p/base/udptransport.h @@ -14,7 +14,7 @@ #include #include -#include "webrtc/api/udptransportinterface.h" +#include "webrtc/api/ortc/udptransportinterface.h" #include "webrtc/base/asyncpacketsocket.h" // For PacketOptions. #include "webrtc/base/optional.h" #include "webrtc/base/thread_checker.h" @@ -31,8 +31,8 @@ namespace cricket { // Implementation of UdpTransportInterface. // Used by OrtcFactory. -class UdpTransport : public webrtc::UdpTransportInterface, - public rtc::PacketTransportInternal { +class UdpTransport : public rtc::PacketTransportInternal, + public webrtc::UdpTransportInterface { public: // |transport_name| is only used for identification/logging. // |socket| must be non-null. @@ -64,6 +64,9 @@ class UdpTransport : public webrtc::UdpTransportInterface, int GetError() override { return send_error_; } + protected: + PacketTransportInternal* GetInternal() override { return this; } + private: void OnSocketReadPacket(rtc::AsyncPacketSocket* socket, const char* data, @@ -80,6 +83,7 @@ class UdpTransport : public webrtc::UdpTransportInterface, rtc::SocketAddress remote_address_; rtc::ThreadChecker network_thread_checker_; }; + } // namespace cricket #endif // WEBRTC_P2P_BASE_UDPTRANSPORT_H_ diff --git a/webrtc/pc/BUILD.gn b/webrtc/pc/BUILD.gn index 54471c6620..0d86bc3f01 100644 --- a/webrtc/pc/BUILD.gn +++ b/webrtc/pc/BUILD.gn @@ -103,8 +103,6 @@ rtc_static_library("libjingle_peerconnection") { "mediastreamobserver.cc", "mediastreamobserver.h", "mediastreamtrack.h", - "ortcfactory.cc", - "ortcfactory.h", "peerconnection.cc", "peerconnection.h", "peerconnectionfactory.cc", @@ -225,6 +223,38 @@ if (rtc_include_tests) { } } + rtc_source_set("pc_test_utils") { + testonly = true + sources = [ + "test/fakeaudiocapturemodule.cc", + "test/fakeaudiocapturemodule.h", + "test/fakedatachannelprovider.h", + "test/fakeperiodicvideocapturer.h", + "test/fakertccertificategenerator.h", + "test/fakevideotrackrenderer.h", + "test/fakevideotracksource.h", + "test/mock_datachannel.h", + "test/mock_peerconnection.h", + "test/mock_webrtcsession.h", + "test/mockpeerconnectionobservers.h", + "test/peerconnectiontestwrapper.cc", + "test/peerconnectiontestwrapper.h", + "test/rtcstatsobtainer.h", + "test/testsdpstrings.h", + ] + + deps = [ + ":libjingle_peerconnection", + "../base:rtc_base_tests_utils", + "//testing/gmock", + ] + + if (!build_with_chromium && is_clang) { + # Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163). + suppressed_configs += [ "//build/config/clang:find_bad_constructs" ] + } + } + config("peerconnection_unittests_config") { # The warnings below are enabled by default. Since GN orders compiler flags # for a target before flags from configs, the only way to disable such @@ -256,7 +286,6 @@ if (rtc_include_tests) { "localaudiosource_unittest.cc", "mediaconstraintsinterface_unittest.cc", "mediastream_unittest.cc", - "ortcfactory_unittest.cc", "peerconnection_unittest.cc", "peerconnectionendtoend_unittest.cc", "peerconnectionfactory_unittest.cc", @@ -267,21 +296,7 @@ if (rtc_include_tests) { "rtpsenderreceiver_unittest.cc", "sctputils_unittest.cc", "statscollector_unittest.cc", - "test/fakeaudiocapturemodule.cc", - "test/fakeaudiocapturemodule.h", "test/fakeaudiocapturemodule_unittest.cc", - "test/fakedatachannelprovider.h", - "test/fakeperiodicvideocapturer.h", - "test/fakertccertificategenerator.h", - "test/fakevideotrackrenderer.h", - "test/fakevideotracksource.h", - "test/mock_datachannel.h", - "test/mock_peerconnection.h", - "test/mock_webrtcsession.h", - "test/mockpeerconnectionobservers.h", - "test/peerconnectiontestwrapper.cc", - "test/peerconnectiontestwrapper.h", - "test/rtcstatsobtainer.h", "test/testsdpstrings.h", "trackmediainfomap_unittest.cc", "videocapturertracksource_unittest.cc", @@ -336,6 +351,7 @@ if (rtc_include_tests) { deps += [ ":libjingle_peerconnection", + ":pc_test_utils", "..:webrtc_common", "../api:fakemetricsobserver", "../base:rtc_base_tests_utils", diff --git a/webrtc/pc/channel.cc b/webrtc/pc/channel.cc index 02d93f79e7..9ac9d201ab 100644 --- a/webrtc/pc/channel.cc +++ b/webrtc/pc/channel.cc @@ -1258,10 +1258,14 @@ bool BaseChannel::SetRtcpMux_n(bool enable, case CA_ANSWER: ret = rtcp_mux_filter_.SetAnswer(enable, src); if (ret && rtcp_mux_filter_.IsActive()) { - // We activated RTCP mux, close down the RTCP transport. + // We permanently activated RTCP muxing; signal that we no longer need + // the RTCP transport. + std::string debug_name = transport_name_.empty() + ? rtp_packet_transport_->debug_name() + : transport_name_; + ; LOG(LS_INFO) << "Enabling rtcp-mux for " << content_name() - << " by destroying RTCP transport for " - << transport_name(); + << "; no longer need RTCP transport for " << debug_name; if (rtcp_packet_transport_) { SetTransport_n(true, nullptr, nullptr); SignalRtcpMuxFullyActive(transport_name_); diff --git a/webrtc/pc/channelmanager.cc b/webrtc/pc/channelmanager.cc index 5362b0fd6c..150dfd9a9d 100644 --- a/webrtc/pc/channelmanager.cc +++ b/webrtc/pc/channelmanager.cc @@ -213,14 +213,31 @@ VoiceChannel* ChannelManager::CreateVoiceChannel( return worker_thread_->Invoke( RTC_FROM_HERE, Bind(&ChannelManager::CreateVoiceChannel_w, this, media_controller, - rtp_transport, rtcp_transport, signaling_thread, content_name, - srtp_required, options)); + rtp_transport, rtcp_transport, rtp_transport, rtcp_transport, + signaling_thread, content_name, srtp_required, options)); +} + +VoiceChannel* ChannelManager::CreateVoiceChannel( + webrtc::MediaControllerInterface* media_controller, + rtc::PacketTransportInternal* rtp_transport, + rtc::PacketTransportInternal* rtcp_transport, + rtc::Thread* signaling_thread, + const std::string& content_name, + bool srtp_required, + const AudioOptions& options) { + return worker_thread_->Invoke( + RTC_FROM_HERE, + Bind(&ChannelManager::CreateVoiceChannel_w, this, media_controller, + nullptr, nullptr, rtp_transport, rtcp_transport, signaling_thread, + content_name, srtp_required, options)); } VoiceChannel* ChannelManager::CreateVoiceChannel_w( webrtc::MediaControllerInterface* media_controller, - DtlsTransportInternal* rtp_transport, - DtlsTransportInternal* rtcp_transport, + DtlsTransportInternal* rtp_dtls_transport, + DtlsTransportInternal* rtcp_dtls_transport, + rtc::PacketTransportInternal* rtp_packet_transport, + rtc::PacketTransportInternal* rtcp_packet_transport, rtc::Thread* signaling_thread, const std::string& content_name, bool srtp_required, @@ -234,13 +251,14 @@ VoiceChannel* ChannelManager::CreateVoiceChannel_w( if (!media_channel) return nullptr; - VoiceChannel* voice_channel = new VoiceChannel( - worker_thread_, network_thread_, signaling_thread, media_engine_.get(), - media_channel, content_name, rtcp_transport == nullptr, srtp_required); + VoiceChannel* voice_channel = + new VoiceChannel(worker_thread_, network_thread_, signaling_thread, + media_engine_.get(), media_channel, content_name, + rtcp_packet_transport == nullptr, srtp_required); voice_channel->SetCryptoOptions(crypto_options_); - if (!voice_channel->Init_w(rtp_transport, rtcp_transport, rtp_transport, - rtcp_transport)) { + if (!voice_channel->Init_w(rtp_dtls_transport, rtcp_dtls_transport, + rtp_packet_transport, rtcp_packet_transport)) { delete voice_channel; return nullptr; } @@ -282,14 +300,31 @@ VideoChannel* ChannelManager::CreateVideoChannel( return worker_thread_->Invoke( RTC_FROM_HERE, Bind(&ChannelManager::CreateVideoChannel_w, this, media_controller, - rtp_transport, rtcp_transport, signaling_thread, content_name, - srtp_required, options)); + rtp_transport, rtcp_transport, rtp_transport, rtcp_transport, + signaling_thread, content_name, srtp_required, options)); +} + +VideoChannel* ChannelManager::CreateVideoChannel( + webrtc::MediaControllerInterface* media_controller, + rtc::PacketTransportInternal* rtp_transport, + rtc::PacketTransportInternal* rtcp_transport, + rtc::Thread* signaling_thread, + const std::string& content_name, + bool srtp_required, + const VideoOptions& options) { + return worker_thread_->Invoke( + RTC_FROM_HERE, + Bind(&ChannelManager::CreateVideoChannel_w, this, media_controller, + nullptr, nullptr, rtp_transport, rtcp_transport, signaling_thread, + content_name, srtp_required, options)); } VideoChannel* ChannelManager::CreateVideoChannel_w( webrtc::MediaControllerInterface* media_controller, - DtlsTransportInternal* rtp_transport, - DtlsTransportInternal* rtcp_transport, + DtlsTransportInternal* rtp_dtls_transport, + DtlsTransportInternal* rtcp_dtls_transport, + rtc::PacketTransportInternal* rtp_packet_transport, + rtc::PacketTransportInternal* rtcp_packet_transport, rtc::Thread* signaling_thread, const std::string& content_name, bool srtp_required, @@ -305,10 +340,10 @@ VideoChannel* ChannelManager::CreateVideoChannel_w( VideoChannel* video_channel = new VideoChannel( worker_thread_, network_thread_, signaling_thread, media_channel, - content_name, rtcp_transport == nullptr, srtp_required); + content_name, rtcp_packet_transport == nullptr, srtp_required); video_channel->SetCryptoOptions(crypto_options_); - if (!video_channel->Init_w(rtp_transport, rtcp_transport, rtp_transport, - rtcp_transport)) { + if (!video_channel->Init_w(rtp_dtls_transport, rtcp_dtls_transport, + rtp_packet_transport, rtcp_packet_transport)) { delete video_channel; return NULL; } diff --git a/webrtc/pc/channelmanager.h b/webrtc/pc/channelmanager.h index 8c6ee7feee..b763fa14fc 100644 --- a/webrtc/pc/channelmanager.h +++ b/webrtc/pc/channelmanager.h @@ -95,6 +95,15 @@ class ChannelManager { const std::string& content_name, bool srtp_required, const AudioOptions& options); + // Version of the above that takes PacketTransportInternal. + VoiceChannel* CreateVoiceChannel( + webrtc::MediaControllerInterface* media_controller, + rtc::PacketTransportInternal* rtp_transport, + rtc::PacketTransportInternal* rtcp_transport, + rtc::Thread* signaling_thread, + const std::string& content_name, + bool srtp_required, + const AudioOptions& options); // Destroys a voice channel created with the Create API. void DestroyVoiceChannel(VoiceChannel* voice_channel); // Creates a video channel, synced with the specified voice channel, and @@ -107,6 +116,15 @@ class ChannelManager { const std::string& content_name, bool srtp_required, const VideoOptions& options); + // Version of the above that takes PacketTransportInternal. + VideoChannel* CreateVideoChannel( + webrtc::MediaControllerInterface* media_controller, + rtc::PacketTransportInternal* rtp_transport, + rtc::PacketTransportInternal* rtcp_transport, + rtc::Thread* signaling_thread, + const std::string& content_name, + bool srtp_required, + const VideoOptions& options); // Destroys a video channel created with the Create API. void DestroyVideoChannel(VideoChannel* video_channel); RtpDataChannel* CreateRtpDataChannel( @@ -160,8 +178,10 @@ class ChannelManager { bool SetCryptoOptions_w(const rtc::CryptoOptions& crypto_options); VoiceChannel* CreateVoiceChannel_w( webrtc::MediaControllerInterface* media_controller, - DtlsTransportInternal* rtp_transport, - DtlsTransportInternal* rtcp_transport, + DtlsTransportInternal* rtp_dtls_transport, + DtlsTransportInternal* rtcp_dtls_transport, + rtc::PacketTransportInternal* rtp_packet_transport, + rtc::PacketTransportInternal* rtcp_packet_transport, rtc::Thread* signaling_thread, const std::string& content_name, bool srtp_required, @@ -169,8 +189,10 @@ class ChannelManager { void DestroyVoiceChannel_w(VoiceChannel* voice_channel); VideoChannel* CreateVideoChannel_w( webrtc::MediaControllerInterface* media_controller, - DtlsTransportInternal* rtp_transport, - DtlsTransportInternal* rtcp_transport, + DtlsTransportInternal* rtp_dtls_transport, + DtlsTransportInternal* rtcp_dtls_transport, + rtc::PacketTransportInternal* rtp_packet_transport, + rtc::PacketTransportInternal* rtcp_packet_transport, rtc::Thread* signaling_thread, const std::string& content_name, bool srtp_required, diff --git a/webrtc/pc/mediacontroller.h b/webrtc/pc/mediacontroller.h index 85617aff0a..f5a9034918 100644 --- a/webrtc/pc/mediacontroller.h +++ b/webrtc/pc/mediacontroller.h @@ -23,10 +23,12 @@ class Call; class VoiceEngine; class RtcEventLog; -// The MediaController currently owns shared state between media channels, but -// in the future will create and own RtpSenders and RtpReceivers. +// The MediaController currently owns shared state between media channels. +// Abstract interface is defined here such that it can be faked/mocked for +// tests, but no other real reason. class MediaControllerInterface { public: + // Will never return nullptr. static MediaControllerInterface* Create( const cricket::MediaConfig& config, rtc::Thread* worker_thread, diff --git a/webrtc/pc/mediasession.cc b/webrtc/pc/mediasession.cc index 5e38088389..05e6ccbd5c 100644 --- a/webrtc/pc/mediasession.cc +++ b/webrtc/pc/mediasession.cc @@ -404,12 +404,10 @@ class UsedPayloadTypes : public UsedIds { class UsedRtpHeaderExtensionIds : public UsedIds { public: UsedRtpHeaderExtensionIds() - : UsedIds(kLocalIdMin, kLocalIdMax) {} + : UsedIds(webrtc::RtpExtension::kMinId, + webrtc::RtpExtension::kMaxId) {} private: - // Min and Max local identifier for one-byte header extensions, per RFC5285. - static const int kLocalIdMin = 1; - static const int kLocalIdMax = 14; }; static bool IsSctp(const MediaContentDescription* desc) { @@ -1281,7 +1279,6 @@ MediaSessionDescriptionFactory::MediaSessionDescriptionFactory( transport_desc_factory_(transport_desc_factory) { channel_manager->GetSupportedAudioSendCodecs(&audio_send_codecs_); channel_manager->GetSupportedAudioReceiveCodecs(&audio_recv_codecs_); - channel_manager->GetSupportedAudioSendCodecs(&audio_send_codecs_); channel_manager->GetSupportedAudioRtpHeaderExtensions(&audio_rtp_extensions_); channel_manager->GetSupportedVideoCodecs(&video_codecs_); channel_manager->GetSupportedVideoRtpHeaderExtensions(&video_rtp_extensions_); diff --git a/webrtc/pc/mediasession.h b/webrtc/pc/mediasession.h index f864fe99b7..a901e1d75a 100644 --- a/webrtc/pc/mediasession.h +++ b/webrtc/pc/mediasession.h @@ -92,6 +92,10 @@ struct RtpTransceiverDirection { MediaContentDirection md); MediaContentDirection ToMediaContentDirection() const; + + RtpTransceiverDirection Reversed() const { + return RtpTransceiverDirection(recv, send); + } }; RtpTransceiverDirection diff --git a/webrtc/pc/ortcfactory.cc b/webrtc/pc/ortcfactory.cc deleted file mode 100644 index aa5e1819fb..0000000000 --- a/webrtc/pc/ortcfactory.cc +++ /dev/null @@ -1,125 +0,0 @@ -/* - * 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/pc/ortcfactory.h" - -#include -#include // For std::move. - -#include "webrtc/base/bind.h" -#include "webrtc/base/asyncpacketsocket.h" -#include "webrtc/p2p/base/basicpacketsocketfactory.h" -#include "webrtc/p2p/base/udptransport.h" - -namespace webrtc { - -// static -std::unique_ptr OrtcFactoryInterface::Create( - rtc::Thread* network_thread, - rtc::Thread* signaling_thread, - rtc::NetworkManager* network_manager, - rtc::PacketSocketFactory* socket_factory) { - // Hop to signaling thread if needed. - if (signaling_thread && !signaling_thread->IsCurrent()) { - // The template parameters are necessary because there are two - // OrtcFactoryInterface::Create methods, so the types can't be derived from - // just the function pointer. - return signaling_thread->Invoke>( - RTC_FROM_HERE, - rtc::Bind, rtc::Thread*, - rtc::Thread*, rtc::NetworkManager*, - rtc::PacketSocketFactory*>(&OrtcFactoryInterface::Create, - network_thread, signaling_thread, - network_manager, socket_factory)); - } - OrtcFactory* new_factory = - new OrtcFactory(network_thread, signaling_thread, - network_manager, socket_factory); - // Return a proxy so that any calls on the returned object (including - // destructor) happen on the signaling thread. - return OrtcFactoryProxy::Create(new_factory->signaling_thread(), - new_factory->network_thread(), new_factory); -} - -OrtcFactory::OrtcFactory(rtc::Thread* network_thread, - rtc::Thread* signaling_thread, - rtc::NetworkManager* network_manager, - rtc::PacketSocketFactory* socket_factory) - : network_thread_(network_thread), - signaling_thread_(signaling_thread), - network_manager_(network_manager), - socket_factory_(socket_factory) { - if (!network_thread_) { - owned_network_thread_ = rtc::Thread::CreateWithSocketServer(); - owned_network_thread_->Start(); - network_thread_ = owned_network_thread_.get(); - } - - // The worker thread is created internally because it's an implementation - // detail, and consumers of the API don't need to really know about it. - owned_worker_thread_ = rtc::Thread::Create(); - owned_worker_thread_->Start(); - - if (signaling_thread_) { - RTC_DCHECK_RUN_ON(signaling_thread_); - } else { - signaling_thread_ = rtc::Thread::Current(); - if (!signaling_thread_) { - // If this thread isn't already wrapped by an rtc::Thread, create a - // wrapper and own it in this class. - signaling_thread_ = rtc::ThreadManager::Instance()->WrapCurrentThread(); - wraps_signaling_thread_ = true; - } - } - if (!network_manager_) { - owned_network_manager_.reset(new rtc::BasicNetworkManager()); - network_manager_ = owned_network_manager_.get(); - } - if (!socket_factory_) { - owned_socket_factory_.reset( - new rtc::BasicPacketSocketFactory(network_thread_)); - socket_factory_ = owned_socket_factory_.get(); - } -} - -OrtcFactory::~OrtcFactory() { - RTC_DCHECK_RUN_ON(signaling_thread_); - if (wraps_signaling_thread_) { - rtc::ThreadManager::Instance()->UnwrapCurrentThread(); - } -} - -std::unique_ptr OrtcFactory::CreateUdpTransport( - int family, - uint16_t min_port, - uint16_t max_port) { - if (!network_thread_->IsCurrent()) { - RTC_DCHECK_RUN_ON(signaling_thread_); - return network_thread_->Invoke>( - RTC_FROM_HERE, rtc::Bind(&OrtcFactory::CreateUdpTransport, this, family, - min_port, max_port)); - } - std::unique_ptr socket( - socket_factory_->CreateUdpSocket( - rtc::SocketAddress(rtc::GetAnyIP(family), 0), min_port, max_port)); - if (!socket) { - LOG(LS_WARNING) << "Local socket allocation failure."; - return nullptr; - } - LOG(LS_INFO) << "Created UDP socket with address " - << socket->GetLocalAddress().ToSensitiveString() << "."; - // Use proxy so that calls to the returned object are invoked on the network - // thread. - return UdpTransportProxy::Create( - signaling_thread_, network_thread_, - new cricket::UdpTransport(std::string(), std::move(socket))); -} - -} // namespace webrtc diff --git a/webrtc/pc/ortcfactory.h b/webrtc/pc/ortcfactory.h deleted file mode 100644 index 65fe10fd53..0000000000 --- a/webrtc/pc/ortcfactory.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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. - */ - -#ifndef WEBRTC_PC_ORTCFACTORY_H_ -#define WEBRTC_PC_ORTCFACTORY_H_ - -#include - -#include "webrtc/api/ortcfactoryinterface.h" -#include "webrtc/base/constructormagic.h" - -namespace webrtc { - -// Implementation of OrtcFactoryInterface. -// -// See ortcfactoryinterface.h for documentation. -class OrtcFactory : public OrtcFactoryInterface { - public: - OrtcFactory(rtc::Thread* network_thread, - rtc::Thread* signaling_thread, - rtc::NetworkManager* network_manager, - rtc::PacketSocketFactory* socket_factory); - ~OrtcFactory() override; - std::unique_ptr - CreateUdpTransport(int family, uint16_t min_port, uint16_t max_port) override; - - rtc::Thread* network_thread() { return network_thread_; } - rtc::Thread* worker_thread() { return owned_worker_thread_.get(); } - rtc::Thread* signaling_thread() { return signaling_thread_; } - - private: - rtc::Thread* network_thread_; - rtc::Thread* signaling_thread_; - rtc::NetworkManager* network_manager_; - rtc::PacketSocketFactory* socket_factory_; - // If we created/own the objects above, these will be non-null and thus will - // be released automatically upon destruction. - std::unique_ptr owned_network_thread_; - std::unique_ptr owned_worker_thread_; - bool wraps_signaling_thread_ = false; - std::unique_ptr owned_network_manager_; - std::unique_ptr owned_socket_factory_; - RTC_DISALLOW_COPY_AND_ASSIGN(OrtcFactory); -}; - -BEGIN_OWNED_PROXY_MAP(OrtcFactory) - PROXY_SIGNALING_THREAD_DESTRUCTOR() - PROXY_METHOD3(std::unique_ptr, - CreateUdpTransport, - int, - uint16_t, - uint16_t) -END_PROXY_MAP() - -} // namespace webrtc - -#endif // WEBRTC_PC_ORTCFACTORY_H_ diff --git a/webrtc/pc/ortcfactory_unittest.cc b/webrtc/pc/ortcfactory_unittest.cc deleted file mode 100644 index 53286312c7..0000000000 --- a/webrtc/pc/ortcfactory_unittest.cc +++ /dev/null @@ -1,122 +0,0 @@ -/* - * 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 - -#include "webrtc/api/ortcfactoryinterface.h" -#include "webrtc/base/fakenetwork.h" -#include "webrtc/base/gunit.h" -#include "webrtc/base/physicalsocketserver.h" -#include "webrtc/base/virtualsocketserver.h" -#include "webrtc/p2p/base/udptransport.h" - -namespace { - -const int kDefaultTimeout = 10000; // 10 seconds. -static const rtc::IPAddress kIPv4LocalHostAddress = - rtc::IPAddress(0x7F000001); // 127.0.0.1 - -class PacketReceiver : public sigslot::has_slots<> { - public: - explicit PacketReceiver(rtc::PacketTransportInternal* transport) { - transport->SignalReadPacket.connect(this, &PacketReceiver::OnReadPacket); - } - int packets_read() const { return packets_read_; } - - private: - void OnReadPacket(rtc::PacketTransportInternal*, - const char*, - size_t, - const rtc::PacketTime&, - int) { - ++packets_read_; - } - - int packets_read_ = 0; -}; - -} // namespace - -namespace webrtc { - -// Used to test that things work end-to-end when using the default -// implementations of threads/etc. provided by OrtcFactory, with the exception -// of using a virtual network. -// -// By default, the virtual network manager doesn't enumerate any networks, but -// sockets can still be created in this state. -class OrtcFactoryTest : public testing::Test { - public: - OrtcFactoryTest() - : virtual_socket_server_(&physical_socket_server_), - network_thread_(&virtual_socket_server_), - ortc_factory_(OrtcFactoryInterface::Create(&network_thread_, - nullptr, - &fake_network_manager_, - nullptr)) { - // Sockets are bound to the ANY address, so this is needed to tell the - // virtual network which address to use in this case. - virtual_socket_server_.SetDefaultRoute(kIPv4LocalHostAddress); - network_thread_.Start(); - } - - protected: - rtc::PhysicalSocketServer physical_socket_server_; - rtc::VirtualSocketServer virtual_socket_server_; - rtc::Thread network_thread_; - rtc::FakeNetworkManager fake_network_manager_; - std::unique_ptr ortc_factory_; -}; - -TEST_F(OrtcFactoryTest, EndToEndUdpTransport) { - std::unique_ptr transport1 = - ortc_factory_->CreateUdpTransport(AF_INET); - std::unique_ptr transport2 = - ortc_factory_->CreateUdpTransport(AF_INET); - ASSERT_NE(nullptr, transport1); - ASSERT_NE(nullptr, transport2); - // Sockets are bound to the ANY address, so we need to provide the IP address - // explicitly. - transport1->SetRemoteAddress( - rtc::SocketAddress(virtual_socket_server_.GetDefaultRoute(AF_INET), - transport2->GetLocalAddress().port())); - transport2->SetRemoteAddress( - rtc::SocketAddress(virtual_socket_server_.GetDefaultRoute(AF_INET), - transport1->GetLocalAddress().port())); - - // TODO(deadbeef): Once there's something (RTP senders/receivers) that can - // use UdpTransport end-to-end, use that for this end-to-end test instead of - // making assumptions about the implementation. - // - // For now, this assumes the returned object is a UdpTransportProxy that wraps - // a UdpTransport. - cricket::UdpTransport* internal_transport1 = - static_cast*>( - transport1.get()) - ->internal(); - cricket::UdpTransport* internal_transport2 = - static_cast*>( - transport2.get()) - ->internal(); - // Need to call internal "SendPacket" method on network thread. - network_thread_.Invoke( - RTC_FROM_HERE, [internal_transport1, internal_transport2]() { - PacketReceiver receiver1(internal_transport1); - PacketReceiver receiver2(internal_transport2); - internal_transport1->SendPacket("foo", sizeof("foo"), - rtc::PacketOptions(), 0); - internal_transport2->SendPacket("foo", sizeof("foo"), - rtc::PacketOptions(), 0); - EXPECT_EQ_WAIT(1, receiver1.packets_read(), kDefaultTimeout); - EXPECT_EQ_WAIT(1, receiver2.packets_read(), kDefaultTimeout); - }); -} - -} // namespace webrtc diff --git a/webrtc/pc/peerconnection.cc b/webrtc/pc/peerconnection.cc index ee9341ecb4..c3ec0b2aad 100644 --- a/webrtc/pc/peerconnection.cc +++ b/webrtc/pc/peerconnection.cc @@ -1615,9 +1615,10 @@ void PeerConnection::CreateAudioReceiver(MediaStreamInterface* stream, uint32_t ssrc) { rtc::scoped_refptr> receiver = RtpReceiverProxyWithInternal::Create( - signaling_thread(), new AudioRtpReceiver(stream, track_id, ssrc, - session_->voice_channel())); - + signaling_thread(), + new AudioRtpReceiver(track_id, ssrc, session_->voice_channel())); + stream->AddTrack( + static_cast(receiver->internal()->track().get())); receivers_.push_back(receiver); std::vector> streams; streams.push_back(rtc::scoped_refptr(stream)); @@ -1630,8 +1631,10 @@ void PeerConnection::CreateVideoReceiver(MediaStreamInterface* stream, rtc::scoped_refptr> receiver = RtpReceiverProxyWithInternal::Create( signaling_thread(), - new VideoRtpReceiver(stream, track_id, factory_->worker_thread(), - ssrc, session_->video_channel())); + new VideoRtpReceiver(track_id, factory_->worker_thread(), ssrc, + session_->video_channel())); + stream->AddTrack( + static_cast(receiver->internal()->track().get())); receivers_.push_back(receiver); std::vector> streams; streams.push_back(rtc::scoped_refptr(stream)); diff --git a/webrtc/pc/proxy_unittest.cc b/webrtc/pc/proxy_unittest.cc index 148b74210e..d7acdc193d 100644 --- a/webrtc/pc/proxy_unittest.cc +++ b/webrtc/pc/proxy_unittest.cc @@ -284,8 +284,9 @@ class OwnedProxyTest : public testing::Test { public: OwnedProxyTest() : foo_(new Foo()), - foo_proxy_( - FooProxy::Create(&signaling_thread_, &worker_thread_, foo_)) { + foo_proxy_(FooProxy::Create(&signaling_thread_, + &worker_thread_, + std::unique_ptr(foo_))) { signaling_thread_.Start(); worker_thread_.Start(); } diff --git a/webrtc/pc/rtpreceiver.cc b/webrtc/pc/rtpreceiver.cc index f57babb4d4..6073b15a97 100644 --- a/webrtc/pc/rtpreceiver.cc +++ b/webrtc/pc/rtpreceiver.cc @@ -18,8 +18,7 @@ namespace webrtc { -AudioRtpReceiver::AudioRtpReceiver(MediaStreamInterface* stream, - const std::string& track_id, +AudioRtpReceiver::AudioRtpReceiver(const std::string& track_id, uint32_t ssrc, cricket::VoiceChannel* channel) : id_(track_id), @@ -34,7 +33,6 @@ AudioRtpReceiver::AudioRtpReceiver(MediaStreamInterface* stream, track_->RegisterObserver(this); track_->GetSource()->RegisterAudioObserver(this); Reconfigure(); - stream->AddTrack(track_); if (channel_) { channel_->SignalFirstPacketReceived.connect( this, &AudioRtpReceiver::OnFirstPacketReceived); @@ -137,8 +135,7 @@ void AudioRtpReceiver::OnFirstPacketReceived(cricket::BaseChannel* channel) { received_first_packet_ = true; } -VideoRtpReceiver::VideoRtpReceiver(MediaStreamInterface* stream, - const std::string& track_id, +VideoRtpReceiver::VideoRtpReceiver(const std::string& track_id, rtc::Thread* worker_thread, uint32_t ssrc, cricket::VideoChannel* channel) @@ -164,7 +161,6 @@ VideoRtpReceiver::VideoRtpReceiver(MediaStreamInterface* stream, RTC_NOTREACHED(); } } - stream->AddTrack(track_); if (channel_) { channel_->SignalFirstPacketReceived.connect( this, &VideoRtpReceiver::OnFirstPacketReceived); diff --git a/webrtc/pc/rtpreceiver.h b/webrtc/pc/rtpreceiver.h index c135f227c0..513f90c7ab 100644 --- a/webrtc/pc/rtpreceiver.h +++ b/webrtc/pc/rtpreceiver.h @@ -34,6 +34,9 @@ namespace webrtc { class RtpReceiverInternal : public RtpReceiverInterface { public: virtual void Stop() = 0; + // This SSRC is used as an identifier for the receiver between the API layer + // and the WebRtcVideoEngine2, WebRtcVoiceEngine layer. + virtual uint32_t ssrc() const = 0; }; class AudioRtpReceiver : public ObserverInterface, @@ -41,8 +44,11 @@ class AudioRtpReceiver : public ObserverInterface, public rtc::RefCountedObject, public sigslot::has_slots<> { public: - AudioRtpReceiver(MediaStreamInterface* stream, - const std::string& track_id, + // An SSRC of 0 will create a receiver that will match the first SSRC it + // sees. + // TODO(deadbeef): Use rtc::Optional, or have another constructor that + // doesn't take an SSRC, and make this one DCHECK(ssrc != 0). + AudioRtpReceiver(const std::string& track_id, uint32_t ssrc, cricket::VoiceChannel* channel); @@ -74,6 +80,7 @@ class AudioRtpReceiver : public ObserverInterface, // RtpReceiverInternal implementation. void Stop() override; + uint32_t ssrc() const override { return ssrc_; } void SetObserver(RtpReceiverObserverInterface* observer) override; @@ -99,8 +106,9 @@ class AudioRtpReceiver : public ObserverInterface, class VideoRtpReceiver : public rtc::RefCountedObject, public sigslot::has_slots<> { public: - VideoRtpReceiver(MediaStreamInterface* stream, - const std::string& track_id, + // An SSRC of 0 will create a receiver that will match the first SSRC it + // sees. + VideoRtpReceiver(const std::string& track_id, rtc::Thread* worker_thread, uint32_t ssrc, cricket::VideoChannel* channel); @@ -127,6 +135,7 @@ class VideoRtpReceiver : public rtc::RefCountedObject, // RtpReceiverInternal implementation. void Stop() override; + uint32_t ssrc() const override { return ssrc_; } void SetObserver(RtpReceiverObserverInterface* observer) override; diff --git a/webrtc/pc/rtpsenderreceiver_unittest.cc b/webrtc/pc/rtpsenderreceiver_unittest.cc index 105d9d31d4..5ddf6c4c81 100644 --- a/webrtc/pc/rtpsenderreceiver_unittest.cc +++ b/webrtc/pc/rtpsenderreceiver_unittest.cc @@ -59,7 +59,7 @@ class RtpSenderReceiverTest : public testing::Test, public: RtpSenderReceiverTest() : // Create fake media engine/etc. so we can create channels to use to - // test RtpSenders/RtpReceivers. + // test RtpSenders/RtpReceivers. media_engine_(new cricket::FakeMediaEngine()), channel_manager_( std::unique_ptr(media_engine_), @@ -67,7 +67,7 @@ class RtpSenderReceiverTest : public testing::Test, rtc::Thread::Current()), fake_call_(Call::Config(&event_log_)), fake_media_controller_(&channel_manager_, &fake_call_), - stream_(MediaStream::Create(kStreamLabel1)) { + local_stream_(MediaStream::Create(kStreamLabel1)) { // Create channels to be used by the RtpSenders and RtpReceivers. channel_manager_.Init(); bool srtp_required = true; @@ -126,17 +126,17 @@ class RtpSenderReceiverTest : public testing::Test, rtc::scoped_refptr source( FakeVideoTrackSource::Create(is_screencast)); video_track_ = VideoTrack::Create(kVideoTrackId, source); - EXPECT_TRUE(stream_->AddTrack(video_track_)); + EXPECT_TRUE(local_stream_->AddTrack(video_track_)); } void CreateAudioRtpSender() { CreateAudioRtpSender(nullptr); } void CreateAudioRtpSender(rtc::scoped_refptr source) { audio_track_ = AudioTrack::Create(kAudioTrackId, source); - EXPECT_TRUE(stream_->AddTrack(audio_track_)); + EXPECT_TRUE(local_stream_->AddTrack(audio_track_)); audio_rtp_sender_ = - new AudioRtpSender(stream_->GetAudioTracks()[0], stream_->label(), - voice_channel_, nullptr); + new AudioRtpSender(local_stream_->GetAudioTracks()[0], + local_stream_->label(), voice_channel_, nullptr); audio_rtp_sender_->SetSsrc(kAudioSsrc); audio_rtp_sender_->GetOnDestroyedSignal()->connect( this, &RtpSenderReceiverTest::OnAudioSenderDestroyed); @@ -149,8 +149,9 @@ class RtpSenderReceiverTest : public testing::Test, void CreateVideoRtpSender(bool is_screencast) { AddVideoTrack(is_screencast); - video_rtp_sender_ = new VideoRtpSender(stream_->GetVideoTracks()[0], - stream_->label(), video_channel_); + video_rtp_sender_ = + new VideoRtpSender(local_stream_->GetVideoTracks()[0], + local_stream_->label(), video_channel_); video_rtp_sender_->SetSsrc(kVideoSsrc); VerifyVideoChannelInput(); } @@ -166,19 +167,15 @@ class RtpSenderReceiverTest : public testing::Test, } void CreateAudioRtpReceiver() { - audio_track_ = AudioTrack::Create( - kAudioTrackId, RemoteAudioSource::Create(kAudioSsrc, NULL)); - EXPECT_TRUE(stream_->AddTrack(audio_track_)); - audio_rtp_receiver_ = new AudioRtpReceiver(stream_, kAudioTrackId, - kAudioSsrc, voice_channel_); + audio_rtp_receiver_ = + new AudioRtpReceiver(kAudioTrackId, kAudioSsrc, voice_channel_); audio_track_ = audio_rtp_receiver_->audio_track(); VerifyVoiceChannelOutput(); } void CreateVideoRtpReceiver() { - video_rtp_receiver_ = - new VideoRtpReceiver(stream_, kVideoTrackId, rtc::Thread::Current(), - kVideoSsrc, video_channel_); + video_rtp_receiver_ = new VideoRtpReceiver( + kVideoTrackId, rtc::Thread::Current(), kVideoSsrc, video_channel_); video_track_ = video_rtp_receiver_->video_track(); VerifyVideoChannelOutput(); } @@ -263,7 +260,7 @@ class RtpSenderReceiverTest : public testing::Test, rtc::scoped_refptr video_rtp_sender_; rtc::scoped_refptr audio_rtp_receiver_; rtc::scoped_refptr video_rtp_receiver_; - rtc::scoped_refptr stream_; + rtc::scoped_refptr local_stream_; rtc::scoped_refptr video_track_; rtc::scoped_refptr audio_track_; bool audio_sender_destroyed_signal_fired_ = false; @@ -717,8 +714,9 @@ TEST_F(RtpSenderReceiverTest, // Setting detailed overrides the default non-screencast mode. This should be // applied even if the track is set on construction. video_track_->set_content_hint(VideoTrackInterface::ContentHint::kDetailed); - video_rtp_sender_ = new VideoRtpSender(stream_->GetVideoTracks()[0], - stream_->label(), video_channel_); + video_rtp_sender_ = + new VideoRtpSender(local_stream_->GetVideoTracks()[0], + local_stream_->label(), video_channel_); video_track_->set_enabled(true); // Sender is not ready to send (no SSRC) so no option should have been set. diff --git a/webrtc/pc/webrtcsdp.cc b/webrtc/pc/webrtcsdp.cc index b749d6ff32..13d09a6ee9 100644 --- a/webrtc/pc/webrtcsdp.cc +++ b/webrtc/pc/webrtcsdp.cc @@ -204,8 +204,6 @@ static const char kDummyPort[] = "9"; // RFC 3556 static const char kApplicationSpecificMaximum[] = "AS"; -static const int kDefaultVideoClockrate = 90000; - static const char kDefaultSctpmapProtocol[] = "webrtc-datachannel"; // RTP payload type is in the 0-127 range. Use -1 to indicate "all" payload @@ -1737,8 +1735,8 @@ void BuildRtpMap(const MediaContentDescription* media_desc, // [/] if (it->id != kWildcardPayloadType) { InitAttrLine(kAttributeRtpmap, &os); - os << kSdpDelimiterColon << it->id << " " << it->name - << "/" << kDefaultVideoClockrate; + os << kSdpDelimiterColon << it->id << " " << it->name << "/" + << cricket::kVideoCodecClockrate; AddLine(os.str(), message); } AddRtcpFbLines(*it, message);