Reland "Add AddTransceiver and GetTransceivers to PeerConnection"

This reverts commit 8b13f96e2d4b0449e54a3665121a4302ceb56e80.

Original change's description:
> Revert "Add AddTransceiver and GetTransceivers to PeerConnection"
> 
> This reverts commit f93d2800d9b0d5818a5a383def0aaef3d441df3a.
> 
> Reason for revert: https://logs.chromium.org/v/?s=chromium%2Fbb%2Fchromium.webrtc.fyi%2Fios-device%2F5804%2F%2B%2Frecipes%2Fsteps%2Fcompile%2F0%2Fstdout
> 
> Original change's description:
> > Add AddTransceiver and GetTransceivers to PeerConnection
> > 
> > WebRTC 1.0 has added the transceiver API to PeerConnection. This
> > is the first step towards exposing this to WebRTC consumers. For
> > now, transceivers can be added and fetched but there is not yet
> > support for creating offers/answers or setting local/remote
> > descriptions. That support ("Unified Plan") will be added in
> > follow-up CLs.
> > 
> > The transceiver API is currently only available if the application
> > opts in by specifying the kUnifiedPlan SDP semantics when creating
> > the PeerConnection.
> > 
> > Bug: webrtc:7600
> > Change-Id: I0b8ee24b489b45bb4c5f60b699bd20c61af01d8e
> > Reviewed-on: https://webrtc-review.googlesource.com/23880
> > Commit-Queue: Steve Anton <steveanton@webrtc.org>
> > Reviewed-by: Peter Thatcher <pthatcher@webrtc.org>
> > Reviewed-by: Henrik Boström <hbos@webrtc.org>
> > Cr-Commit-Position: refs/heads/master@{#20896}
> 
> TBR=steveanton@webrtc.org,zhihuang@webrtc.org,hbos@webrtc.org,pthatcher@webrtc.org
> 
> Change-Id: Ie91ea4988dba25c20e2532114d3a9d859a932d4c
> No-Presubmit: true
> No-Tree-Checks: true
> No-Try: true
> Bug: webrtc:7600
> Reviewed-on: https://webrtc-review.googlesource.com/26400
> Reviewed-by: Steve Anton <steveanton@webrtc.org>
> Commit-Queue: Steve Anton <steveanton@webrtc.org>
> Cr-Commit-Position: refs/heads/master@{#20897}

TBR=steveanton@webrtc.org,zhihuang@webrtc.org,hbos@webrtc.org,pthatcher@webrtc.org

Change-Id: I19fdf08c54f09302794e998a0ffddb82ae0d7b41
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: webrtc:7600
Reviewed-on: https://webrtc-review.googlesource.com/26401
Commit-Queue: Steve Anton <steveanton@webrtc.org>
Reviewed-by: Steve Anton <steveanton@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#20898}
diff --git a/api/peerconnectioninterface.h b/api/peerconnectioninterface.h
index a7b4ef6..a1e7d7e 100644
--- a/api/peerconnectioninterface.h
+++ b/api/peerconnectioninterface.h
@@ -82,6 +82,7 @@
 #include "api/rtceventlogoutput.h"
 #include "api/rtpreceiverinterface.h"
 #include "api/rtpsenderinterface.h"
+#include "api/rtptransceiverinterface.h"
 #include "api/setremotedescriptionobserverinterface.h"
 #include "api/stats/rtcstatscollectorcallback.h"
 #include "api/statstypes.h"
@@ -601,6 +602,57 @@
   // Returns true on success.
   virtual bool RemoveTrack(RtpSenderInterface* sender) = 0;
 
+  // AddTransceiver creates a new RtpTransceiver and adds it to the set of
+  // transceivers. Adding a transceiver will cause future calls to CreateOffer
+  // to add a media description for the corresponding transceiver.
+  //
+  // The initial value of |mid| in the returned transceiver is null. Setting a
+  // new session description may change it to a non-null value.
+  //
+  // https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-addtransceiver
+  //
+  // Optionally, an RtpTransceiverInit structure can be specified to configure
+  // the transceiver from construction. If not specified, the transceiver will
+  // default to having a direction of kSendRecv and not be part of any streams.
+  //
+  // These methods are only available when Unified Plan is enabled (see
+  // RTCConfiguration).
+  //
+  // Common errors:
+  // - INTERNAL_ERROR: The configuration does not have Unified Plan enabled.
+  // TODO(steveanton): Make these pure virtual once downstream projects have
+  // updated.
+
+  // Adds a transceiver with a sender set to transmit the given track. The kind
+  // of the transceiver (and sender/receiver) will be derived from the kind of
+  // the track.
+  // Errors:
+  // - INVALID_PARAMETER: |track| is null.
+  virtual RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>>
+  AddTransceiver(rtc::scoped_refptr<MediaStreamTrackInterface> track) {
+    return RTCError(RTCErrorType::INTERNAL_ERROR, "not implemented");
+  }
+  virtual RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>>
+  AddTransceiver(rtc::scoped_refptr<MediaStreamTrackInterface> track,
+                 const RtpTransceiverInit& init) {
+    return RTCError(RTCErrorType::INTERNAL_ERROR, "not implemented");
+  }
+
+  // Adds a transceiver with the given kind. Can either be MEDIA_TYPE_AUDIO or
+  // MEDIA_TYPE_VIDEO.
+  // Errors:
+  // - INVALID_PARAMETER: |media_type| is not MEDIA_TYPE_AUDIO or
+  //                      MEDIA_TYPE_VIDEO.
+  virtual RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>>
+  AddTransceiver(cricket::MediaType media_type) {
+    return RTCError(RTCErrorType::INTERNAL_ERROR, "not implemented");
+  }
+  virtual RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>>
+  AddTransceiver(cricket::MediaType media_type,
+                 const RtpTransceiverInit& init) {
+    return RTCError(RTCErrorType::INTERNAL_ERROR, "not implemented");
+  }
+
   // Returns pointer to a DtmfSender on success. Otherwise returns null.
   //
   // This API is no longer part of the standard; instead DtmfSenders are
@@ -650,6 +702,15 @@
     return std::vector<rtc::scoped_refptr<RtpReceiverInterface>>();
   }
 
+  // Get all RtpTransceivers, created either through AddTransceiver, AddTrack or
+  // by a remote description applied with SetRemoteDescription.
+  // Note: This method is only available when Unified Plan is enabled (see
+  // RTCConfiguration).
+  virtual std::vector<rtc::scoped_refptr<RtpTransceiverInterface>>
+  GetTransceivers() const {
+    return {};
+  }
+
   virtual bool GetStats(StatsObserver* observer,
                         MediaStreamTrackInterface* track,
                         StatsOutputLevel level) = 0;
diff --git a/api/peerconnectionproxy.h b/api/peerconnectionproxy.h
index bdd9fcb..1d8cf5f 100644
--- a/api/peerconnectionproxy.h
+++ b/api/peerconnectionproxy.h
@@ -33,6 +33,20 @@
                 MediaStreamTrackInterface*,
                 std::vector<MediaStreamInterface*>)
   PROXY_METHOD1(bool, RemoveTrack, RtpSenderInterface*)
+  PROXY_METHOD1(RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>>,
+                AddTransceiver,
+                rtc::scoped_refptr<MediaStreamTrackInterface>)
+  PROXY_METHOD2(RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>>,
+                AddTransceiver,
+                rtc::scoped_refptr<MediaStreamTrackInterface>,
+                const RtpTransceiverInit&)
+  PROXY_METHOD1(RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>>,
+                AddTransceiver,
+                cricket::MediaType)
+  PROXY_METHOD2(RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>>,
+                AddTransceiver,
+                cricket::MediaType,
+                const RtpTransceiverInit&)
   PROXY_METHOD1(rtc::scoped_refptr<DtmfSenderInterface>,
                 CreateDtmfSender,
                 AudioTrackInterface*)
@@ -44,6 +58,8 @@
                      GetSenders)
   PROXY_CONSTMETHOD0(std::vector<rtc::scoped_refptr<RtpReceiverInterface>>,
                      GetReceivers)
+  PROXY_CONSTMETHOD0(std::vector<rtc::scoped_refptr<RtpTransceiverInterface>>,
+                     GetTransceivers)
   PROXY_METHOD3(bool,
                 GetStats,
                 StatsObserver*,
diff --git a/api/rtptransceiverinterface.h b/api/rtptransceiverinterface.h
index 3eb246a..88607b2 100644
--- a/api/rtptransceiverinterface.h
+++ b/api/rtptransceiverinterface.h
@@ -12,6 +12,7 @@
 #define API_RTPTRANSCEIVERINTERFACE_H_
 
 #include <string>
+#include <vector>
 
 #include "api/optional.h"
 #include "api/rtpreceiverinterface.h"
@@ -20,6 +21,7 @@
 
 namespace webrtc {
 
+// https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiverdirection
 enum class RtpTransceiverDirection {
   kSendRecv,
   kSendOnly,
@@ -27,6 +29,21 @@
   kInactive
 };
 
+// Structure for initializing an RtpTransceiver in a call to
+// PeerConnectionInterface::AddTransceiver.
+// https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiverinit
+struct RtpTransceiverInit final {
+  // Direction of the RtpTransceiver. See RtpTransceiverInterface::direction().
+  RtpTransceiverDirection direction = RtpTransceiverDirection::kSendRecv;
+
+  // The added RtpTransceiver will be added to these streams.
+  // TODO(bugs.webrtc.org/7600): Not implemented.
+  std::vector<rtc::scoped_refptr<MediaStreamInterface>> streams;
+
+  // TODO(bugs.webrtc.org/7600): Not implemented.
+  std::vector<RtpEncodingParameters> send_encodings;
+};
+
 // The RtpTransceiverInterface maps to the RTCRtpTransceiver defined by the
 // WebRTC specification. A transceiver represents a combination of an RtpSender
 // and an RtpReceiver than share a common mid. As defined in JSEP, an
diff --git a/pc/peerconnection.cc b/pc/peerconnection.cc
index 65eb603..2c6cc4b 100644
--- a/pc/peerconnection.cc
+++ b/pc/peerconnection.cc
@@ -1205,6 +1205,110 @@
   return true;
 }
 
+RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>>
+PeerConnection::AddTransceiver(
+    rtc::scoped_refptr<MediaStreamTrackInterface> track) {
+  return AddTransceiver(track, RtpTransceiverInit());
+}
+
+RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>>
+PeerConnection::AddTransceiver(
+    rtc::scoped_refptr<MediaStreamTrackInterface> track,
+    const RtpTransceiverInit& init) {
+  if (!IsUnifiedPlan()) {
+    LOG_AND_RETURN_ERROR(
+        RTCErrorType::INTERNAL_ERROR,
+        "AddTransceiver only supported when Unified Plan is enabled.");
+  }
+  if (!track) {
+    LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, "track is null");
+  }
+  cricket::MediaType media_type;
+  if (track->kind() == MediaStreamTrackInterface::kAudioKind) {
+    media_type = cricket::MEDIA_TYPE_AUDIO;
+  } else if (track->kind() == MediaStreamTrackInterface::kVideoKind) {
+    media_type = cricket::MEDIA_TYPE_VIDEO;
+  } else {
+    LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
+                         "Track kind is not audio or video");
+  }
+  return AddTransceiver(media_type, track, init);
+}
+
+RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>>
+PeerConnection::AddTransceiver(cricket::MediaType media_type) {
+  return AddTransceiver(media_type, RtpTransceiverInit());
+}
+
+RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>>
+PeerConnection::AddTransceiver(cricket::MediaType media_type,
+                               const RtpTransceiverInit& init) {
+  if (!IsUnifiedPlan()) {
+    LOG_AND_RETURN_ERROR(
+        RTCErrorType::INTERNAL_ERROR,
+        "AddTransceiver only supported when Unified Plan is enabled.");
+  }
+  if (!(media_type == cricket::MEDIA_TYPE_AUDIO ||
+        media_type == cricket::MEDIA_TYPE_VIDEO)) {
+    LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
+                         "media type is not audio or video");
+  }
+  return AddTransceiver(media_type, nullptr, init);
+}
+
+RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>>
+PeerConnection::AddTransceiver(
+    cricket::MediaType media_type,
+    rtc::scoped_refptr<MediaStreamTrackInterface> track,
+    const RtpTransceiverInit& init) {
+  RTC_DCHECK((media_type == cricket::MEDIA_TYPE_AUDIO ||
+              media_type == cricket::MEDIA_TYPE_VIDEO));
+  if (track) {
+    RTC_DCHECK_EQ(media_type,
+                  (track->kind() == MediaStreamTrackInterface::kAudioKind
+                       ? cricket::MEDIA_TYPE_AUDIO
+                       : cricket::MEDIA_TYPE_VIDEO));
+  }
+
+  // TODO(bugs.webrtc.org/7600): Verify init.
+
+  rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>> sender;
+  rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>
+      receiver;
+  std::string receiver_id = rtc::CreateRandomUuid();
+  if (media_type == cricket::MEDIA_TYPE_AUDIO) {
+    sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
+        signaling_thread(), new AudioRtpSender(nullptr, stats_.get()));
+    receiver = RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create(
+        signaling_thread(), new AudioRtpReceiver(receiver_id, {}, 0, nullptr));
+  } else {
+    RTC_DCHECK_EQ(cricket::MEDIA_TYPE_VIDEO, media_type);
+    sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
+        signaling_thread(), new VideoRtpSender(nullptr));
+    receiver = RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create(
+        signaling_thread(),
+        new VideoRtpReceiver(receiver_id, {}, worker_thread(), 0, nullptr));
+  }
+  // TODO(bugs.webrtc.org/7600): Initializing the sender/receiver with a null
+  // channel prevents users from calling SetParameters on them, which is needed
+  // to be in compliance with the spec.
+
+  if (track) {
+    sender->SetTrack(track);
+  }
+
+  rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
+      transceiver = RtpTransceiverProxyWithInternal<RtpTransceiver>::Create(
+          signaling_thread(), new RtpTransceiver(sender, receiver));
+  transceiver->SetDirection(init.direction);
+
+  transceivers_.push_back(transceiver);
+
+  observer_->OnRenegotiationNeeded();
+
+  return rtc::scoped_refptr<RtpTransceiverInterface>(transceiver);
+}
+
 rtc::scoped_refptr<DtmfSenderInterface> PeerConnection::CreateDtmfSender(
     AudioTrackInterface* track) {
   TRACE_EVENT0("webrtc", "PeerConnection::CreateDtmfSender");
@@ -1297,6 +1401,16 @@
   return all_receivers;
 }
 
+std::vector<rtc::scoped_refptr<RtpTransceiverInterface>>
+PeerConnection::GetTransceivers() const {
+  RTC_DCHECK(IsUnifiedPlan());
+  std::vector<rtc::scoped_refptr<RtpTransceiverInterface>> all_transceivers;
+  for (auto transceiver : transceivers_) {
+    all_transceivers.push_back(transceiver);
+  }
+  return all_transceivers;
+}
+
 bool PeerConnection::GetStats(StatsObserver* observer,
                               MediaStreamTrackInterface* track,
                               StatsOutputLevel level) {
diff --git a/pc/peerconnection.h b/pc/peerconnection.h
index 2bbce48..d262ede 100644
--- a/pc/peerconnection.h
+++ b/pc/peerconnection.h
@@ -94,6 +94,17 @@
       std::vector<MediaStreamInterface*> streams) override;
   bool RemoveTrack(RtpSenderInterface* sender) override;
 
+  RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> AddTransceiver(
+      rtc::scoped_refptr<MediaStreamTrackInterface> track) override;
+  RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> AddTransceiver(
+      rtc::scoped_refptr<MediaStreamTrackInterface> track,
+      const RtpTransceiverInit& init) override;
+  RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> AddTransceiver(
+      cricket::MediaType media_type) override;
+  RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> AddTransceiver(
+      cricket::MediaType media_type,
+      const RtpTransceiverInit& init) override;
+
   // Gets the DTLS SSL certificate associated with the audio transport on the
   // remote side. This will become populated once the DTLS connection with the
   // peer has been completed, as indicated by the ICE connection state
@@ -114,6 +125,8 @@
       const override;
   std::vector<rtc::scoped_refptr<RtpReceiverInterface>> GetReceivers()
       const override;
+  std::vector<rtc::scoped_refptr<RtpTransceiverInterface>> GetTransceivers()
+      const override;
 
   rtc::scoped_refptr<DataChannelInterface> CreateDataChannel(
       const std::string& label,
@@ -327,6 +340,11 @@
   void RemoveVideoTrack(VideoTrackInterface* track,
                         MediaStreamInterface* stream);
 
+  RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> AddTransceiver(
+      cricket::MediaType media_type,
+      rtc::scoped_refptr<MediaStreamTrackInterface> track,
+      const RtpTransceiverInit& init);
+
   void SetIceConnectionState(IceConnectionState new_state);
   // Called any time the IceGatheringState changes
   void OnIceGatheringChange(IceGatheringState new_state);
diff --git a/pc/peerconnection_rtp_unittest.cc b/pc/peerconnection_rtp_unittest.cc
index 0421124..e7ac8c7 100644
--- a/pc/peerconnection_rtp_unittest.cc
+++ b/pc/peerconnection_rtp_unittest.cc
@@ -27,11 +27,16 @@
 #include "rtc_base/refcountedobject.h"
 #include "rtc_base/scoped_ref_ptr.h"
 #include "rtc_base/thread.h"
+#include "test/gmock.h"
 
 // This file contains tests for RTP Media API-related behavior of
 // |webrtc::PeerConnection|, see https://w3c.github.io/webrtc-pc/#rtp-media-api.
 
-namespace {
+namespace webrtc {
+
+using RTCConfiguration = PeerConnectionInterface::RTCConfiguration;
+using ::testing::ElementsAre;
+using ::testing::UnorderedElementsAre;
 
 const uint32_t kDefaultTimeout = 10000u;
 
@@ -55,28 +60,37 @@
 class PeerConnectionRtpTest : public testing::Test {
  public:
   PeerConnectionRtpTest()
-      : pc_factory_(webrtc::CreatePeerConnectionFactory(
-            rtc::Thread::Current(),
-            rtc::Thread::Current(),
-            rtc::Thread::Current(),
-            FakeAudioCaptureModule::Create(),
-            webrtc::CreateBuiltinAudioEncoderFactory(),
-            webrtc::CreateBuiltinAudioDecoderFactory(),
-            nullptr,
-            nullptr)) {}
+      : pc_factory_(
+            CreatePeerConnectionFactory(rtc::Thread::Current(),
+                                        rtc::Thread::Current(),
+                                        rtc::Thread::Current(),
+                                        FakeAudioCaptureModule::Create(),
+                                        CreateBuiltinAudioEncoderFactory(),
+                                        CreateBuiltinAudioDecoderFactory(),
+                                        nullptr,
+                                        nullptr)) {}
 
-  std::unique_ptr<webrtc::PeerConnectionWrapper> CreatePeerConnection() {
-    webrtc::PeerConnectionInterface::RTCConfiguration config;
-    auto observer = rtc::MakeUnique<webrtc::MockPeerConnectionObserver>();
+  std::unique_ptr<PeerConnectionWrapper> CreatePeerConnection() {
+    return CreatePeerConnection(RTCConfiguration());
+  }
+
+  std::unique_ptr<PeerConnectionWrapper> CreatePeerConnectionWithUnifiedPlan() {
+    RTCConfiguration config;
+    config.sdp_semantics = SdpSemantics::kUnifiedPlan;
+    return CreatePeerConnection(config);
+  }
+
+  std::unique_ptr<PeerConnectionWrapper> CreatePeerConnection(
+      const RTCConfiguration& config) {
+    auto observer = rtc::MakeUnique<MockPeerConnectionObserver>();
     auto pc = pc_factory_->CreatePeerConnection(config, nullptr, nullptr,
                                                 observer.get());
-    return std::unique_ptr<webrtc::PeerConnectionWrapper>(
-        new webrtc::PeerConnectionWrapper(pc_factory_, pc,
-                                          std::move(observer)));
+    return rtc::MakeUnique<PeerConnectionWrapper>(pc_factory_, pc,
+                                                  std::move(observer));
   }
 
  protected:
-  rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> pc_factory_;
+  rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory_;
 };
 
 // These tests cover |webrtc::PeerConnectionObserver| callbacks firing upon
@@ -87,7 +101,7 @@
   auto caller = CreatePeerConnection();
   auto callee = CreatePeerConnection();
 
-  rtc::scoped_refptr<webrtc::AudioTrackInterface> audio_track(
+  rtc::scoped_refptr<AudioTrackInterface> audio_track(
       pc_factory_->CreateAudioTrack("audio_track", nullptr));
   EXPECT_TRUE(caller->pc()->AddTrack(audio_track.get(), {}));
   ASSERT_TRUE(
@@ -107,9 +121,9 @@
   auto caller = CreatePeerConnection();
   auto callee = CreatePeerConnection();
 
-  rtc::scoped_refptr<webrtc::AudioTrackInterface> audio_track(
+  rtc::scoped_refptr<AudioTrackInterface> audio_track(
       pc_factory_->CreateAudioTrack("audio_track", nullptr));
-  auto stream = webrtc::MediaStream::Create("audio_stream");
+  auto stream = MediaStream::Create("audio_stream");
   EXPECT_TRUE(caller->pc()->AddTrack(audio_track.get(), {stream.get()}));
   ASSERT_TRUE(
       callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal(),
@@ -128,7 +142,7 @@
   auto caller = CreatePeerConnection();
   auto callee = CreatePeerConnection();
 
-  rtc::scoped_refptr<webrtc::AudioTrackInterface> audio_track(
+  rtc::scoped_refptr<AudioTrackInterface> audio_track(
       pc_factory_->CreateAudioTrack("audio_track", nullptr));
   auto sender = caller->pc()->AddTrack(audio_track.get(), {});
   ASSERT_TRUE(
@@ -150,9 +164,9 @@
   auto caller = CreatePeerConnection();
   auto callee = CreatePeerConnection();
 
-  rtc::scoped_refptr<webrtc::AudioTrackInterface> audio_track(
+  rtc::scoped_refptr<AudioTrackInterface> audio_track(
       pc_factory_->CreateAudioTrack("audio_track", nullptr));
-  auto stream = webrtc::MediaStream::Create("audio_stream");
+  auto stream = MediaStream::Create("audio_stream");
   auto sender = caller->pc()->AddTrack(audio_track.get(), {stream.get()});
   ASSERT_TRUE(
       callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal(),
@@ -173,12 +187,12 @@
   auto caller = CreatePeerConnection();
   auto callee = CreatePeerConnection();
 
-  rtc::scoped_refptr<webrtc::AudioTrackInterface> audio_track1(
+  rtc::scoped_refptr<AudioTrackInterface> audio_track1(
       pc_factory_->CreateAudioTrack("audio_track1", nullptr));
-  rtc::scoped_refptr<webrtc::AudioTrackInterface> audio_track2(
+  rtc::scoped_refptr<AudioTrackInterface> audio_track2(
       pc_factory_->CreateAudioTrack("audio_track2", nullptr));
-  auto stream = webrtc::MediaStream::Create("shared_audio_stream");
-  std::vector<webrtc::MediaStreamInterface*> streams{stream.get()};
+  auto stream = MediaStream::Create("shared_audio_stream");
+  std::vector<MediaStreamInterface*> streams{stream.get()};
   auto sender1 = caller->pc()->AddTrack(audio_track1.get(), streams);
   auto sender2 = caller->pc()->AddTrack(audio_track2.get(), streams);
   ASSERT_TRUE(
@@ -194,7 +208,7 @@
                                    static_cast<webrtc::RTCError*>(nullptr)));
   ASSERT_EQ(callee->observer()->add_track_events_.size(), 2u);
   EXPECT_EQ(
-      std::vector<rtc::scoped_refptr<webrtc::RtpReceiverInterface>>{
+      std::vector<rtc::scoped_refptr<RtpReceiverInterface>>{
           callee->observer()->add_track_events_[0].receiver},
       callee->observer()->remove_track_events_);
 
@@ -438,4 +452,146 @@
   EXPECT_FALSE(observer->called());
 }
 
-}  // namespace
+// RtpTransceiver Tests
+
+// Test that by default there are no transceivers with Unified Plan.
+TEST_F(PeerConnectionRtpTest, PeerConnectionHasNoTransceivers) {
+  auto caller = CreatePeerConnectionWithUnifiedPlan();
+  EXPECT_THAT(caller->pc()->GetTransceivers(), ElementsAre());
+}
+
+// Test that a transceiver created with the audio kind has the correct initial
+// properties.
+TEST_F(PeerConnectionRtpTest, AddTransceiverHasCorrectInitProperties) {
+  auto caller = CreatePeerConnectionWithUnifiedPlan();
+
+  auto transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
+  EXPECT_EQ(rtc::nullopt, transceiver->mid());
+  EXPECT_FALSE(transceiver->stopped());
+  EXPECT_EQ(RtpTransceiverDirection::kSendRecv, transceiver->direction());
+  EXPECT_EQ(rtc::nullopt, transceiver->current_direction());
+}
+
+// Test that adding a transceiver with the audio kind creates an audio sender
+// and audio receiver with the receiver having a live audio track.
+TEST_F(PeerConnectionRtpTest,
+       AddAudioTransceiverCreatesAudioSenderAndReceiver) {
+  auto caller = CreatePeerConnectionWithUnifiedPlan();
+
+  auto transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
+
+  ASSERT_TRUE(transceiver->sender());
+  EXPECT_EQ(cricket::MEDIA_TYPE_AUDIO, transceiver->sender()->media_type());
+
+  ASSERT_TRUE(transceiver->receiver());
+  EXPECT_EQ(cricket::MEDIA_TYPE_AUDIO, transceiver->receiver()->media_type());
+
+  auto track = transceiver->receiver()->track();
+  ASSERT_TRUE(track);
+  EXPECT_EQ(MediaStreamTrackInterface::kAudioKind, track->kind());
+  EXPECT_EQ(MediaStreamTrackInterface::TrackState::kLive, track->state());
+}
+
+// Test that adding a transceiver with the video kind creates an video sender
+// and video receiver with the receiver having a live video track.
+TEST_F(PeerConnectionRtpTest,
+       AddAudioTransceiverCreatesVideoSenderAndReceiver) {
+  auto caller = CreatePeerConnectionWithUnifiedPlan();
+
+  auto transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO);
+
+  ASSERT_TRUE(transceiver->sender());
+  EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, transceiver->sender()->media_type());
+
+  ASSERT_TRUE(transceiver->receiver());
+  EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, transceiver->receiver()->media_type());
+
+  auto track = transceiver->receiver()->track();
+  ASSERT_TRUE(track);
+  EXPECT_EQ(MediaStreamTrackInterface::kVideoKind, track->kind());
+  EXPECT_EQ(MediaStreamTrackInterface::TrackState::kLive, track->state());
+}
+
+// Test that after a call to AddTransceiver, the transceiver shows in
+// GetTransceivers(), the transceiver's sender shows in GetSenders(), and the
+// transceiver's receiver shows in GetReceivers().
+TEST_F(PeerConnectionRtpTest, AddTransceiverShowsInLists) {
+  auto caller = CreatePeerConnectionWithUnifiedPlan();
+
+  auto transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
+  EXPECT_EQ(
+      std::vector<rtc::scoped_refptr<RtpTransceiverInterface>>{transceiver},
+      caller->pc()->GetTransceivers());
+  EXPECT_EQ(
+      std::vector<rtc::scoped_refptr<RtpSenderInterface>>{
+          transceiver->sender()},
+      caller->pc()->GetSenders());
+  EXPECT_EQ(
+      std::vector<rtc::scoped_refptr<RtpReceiverInterface>>{
+          transceiver->receiver()},
+      caller->pc()->GetReceivers());
+}
+
+// Test that the direction passed in through the AddTransceiver init parameter
+// is set in the returned transceiver.
+TEST_F(PeerConnectionRtpTest, AddTransceiverWithDirectionIsReflected) {
+  auto caller = CreatePeerConnectionWithUnifiedPlan();
+
+  RtpTransceiverInit init;
+  init.direction = RtpTransceiverDirection::kSendOnly;
+  auto transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO, init);
+  EXPECT_EQ(RtpTransceiverDirection::kSendOnly, transceiver->direction());
+}
+
+TEST_F(PeerConnectionRtpTest, AddTransceiverWithInvalidKindReturnsError) {
+  auto caller = CreatePeerConnectionWithUnifiedPlan();
+
+  auto result = caller->pc()->AddTransceiver(cricket::MEDIA_TYPE_DATA);
+  EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, result.error().type());
+}
+
+// Test that calling AddTransceiver with a track creates a transceiver which has
+// its sender's track set to the passed-in track.
+TEST_F(PeerConnectionRtpTest, AddTransceiverWithTrackCreatesSenderWithTrack) {
+  auto caller = CreatePeerConnectionWithUnifiedPlan();
+
+  auto audio_track = caller->CreateAudioTrack("audio track");
+  auto transceiver = caller->AddTransceiver(audio_track);
+
+  auto sender = transceiver->sender();
+  ASSERT_TRUE(sender->track());
+  EXPECT_EQ(audio_track, sender->track());
+
+  auto receiver = transceiver->receiver();
+  ASSERT_TRUE(receiver->track());
+  EXPECT_EQ(MediaStreamTrackInterface::kAudioKind, receiver->track()->kind());
+  EXPECT_EQ(MediaStreamTrackInterface::TrackState::kLive,
+            receiver->track()->state());
+}
+
+// Test that calling AddTransceiver twice with the same track creates distinct
+// transceivers, senders with the same track.
+TEST_F(PeerConnectionRtpTest,
+       AddTransceiverTwiceWithSameTrackCreatesMultipleTransceivers) {
+  auto caller = CreatePeerConnectionWithUnifiedPlan();
+
+  auto audio_track = caller->CreateAudioTrack("audio track");
+
+  auto transceiver1 = caller->AddTransceiver(audio_track);
+  auto transceiver2 = caller->AddTransceiver(audio_track);
+
+  EXPECT_NE(transceiver1, transceiver2);
+
+  auto sender1 = transceiver1->sender();
+  auto sender2 = transceiver2->sender();
+  EXPECT_NE(sender1, sender2);
+  EXPECT_EQ(audio_track, sender1->track());
+  EXPECT_EQ(audio_track, sender2->track());
+
+  EXPECT_THAT(caller->pc()->GetTransceivers(),
+              UnorderedElementsAre(transceiver1, transceiver2));
+  EXPECT_THAT(caller->pc()->GetSenders(),
+              UnorderedElementsAre(sender1, sender2));
+}
+
+}  // namespace webrtc
diff --git a/pc/peerconnectionwrapper.cc b/pc/peerconnectionwrapper.cc
index 1b92efa..121cc64 100644
--- a/pc/peerconnectionwrapper.cc
+++ b/pc/peerconnectionwrapper.cc
@@ -179,22 +179,64 @@
   return observer->result();
 }
 
+rtc::scoped_refptr<RtpTransceiverInterface>
+PeerConnectionWrapper::AddTransceiver(cricket::MediaType media_type) {
+  RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> result =
+      pc()->AddTransceiver(media_type);
+  EXPECT_EQ(RTCErrorType::NONE, result.error().type());
+  return result.MoveValue();
+}
+
+rtc::scoped_refptr<RtpTransceiverInterface>
+PeerConnectionWrapper::AddTransceiver(cricket::MediaType media_type,
+                                      const RtpTransceiverInit& init) {
+  RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> result =
+      pc()->AddTransceiver(media_type, init);
+  EXPECT_EQ(RTCErrorType::NONE, result.error().type());
+  return result.MoveValue();
+}
+
+rtc::scoped_refptr<RtpTransceiverInterface>
+PeerConnectionWrapper::AddTransceiver(
+    rtc::scoped_refptr<MediaStreamTrackInterface> track) {
+  RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> result =
+      pc()->AddTransceiver(track);
+  EXPECT_EQ(RTCErrorType::NONE, result.error().type());
+  return result.MoveValue();
+}
+
+rtc::scoped_refptr<RtpTransceiverInterface>
+PeerConnectionWrapper::AddTransceiver(
+    rtc::scoped_refptr<MediaStreamTrackInterface> track,
+    const RtpTransceiverInit& init) {
+  RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> result =
+      pc()->AddTransceiver(track, init);
+  EXPECT_EQ(RTCErrorType::NONE, result.error().type());
+  return result.MoveValue();
+}
+
+rtc::scoped_refptr<AudioTrackInterface> PeerConnectionWrapper::CreateAudioTrack(
+    const std::string& label) {
+  return pc_factory()->CreateAudioTrack(label, nullptr);
+}
+
+rtc::scoped_refptr<VideoTrackInterface> PeerConnectionWrapper::CreateVideoTrack(
+    const std::string& label) {
+  auto video_source = pc_factory()->CreateVideoSource(
+      rtc::MakeUnique<cricket::FakeVideoCapturer>());
+  return pc_factory()->CreateVideoTrack(label, video_source);
+}
+
 rtc::scoped_refptr<RtpSenderInterface> PeerConnectionWrapper::AddAudioTrack(
     const std::string& track_label,
     std::vector<MediaStreamInterface*> streams) {
-  auto media_stream_track =
-      pc_factory()->CreateAudioTrack(track_label, nullptr);
-  return pc()->AddTrack(media_stream_track, std::move(streams));
+  return pc()->AddTrack(CreateAudioTrack(track_label), std::move(streams));
 }
 
 rtc::scoped_refptr<RtpSenderInterface> PeerConnectionWrapper::AddVideoTrack(
     const std::string& track_label,
     std::vector<MediaStreamInterface*> streams) {
-  auto video_source = pc_factory()->CreateVideoSource(
-      rtc::MakeUnique<cricket::FakeVideoCapturer>());
-  auto media_stream_track =
-      pc_factory()->CreateVideoTrack(track_label, video_source);
-  return pc()->AddTrack(media_stream_track, std::move(streams));
+  return pc()->AddTrack(CreateVideoTrack(track_label), std::move(streams));
 }
 
 PeerConnectionInterface::SignalingState
diff --git a/pc/peerconnectionwrapper.h b/pc/peerconnectionwrapper.h
index 8918a71..e7d19ea 100644
--- a/pc/peerconnectionwrapper.h
+++ b/pc/peerconnectionwrapper.h
@@ -93,6 +93,28 @@
   bool SetRemoteDescription(std::unique_ptr<SessionDescriptionInterface> desc,
                             RTCError* error_out);
 
+  // The following are wrappers for the underlying PeerConnection's
+  // AddTransceiver method. They return the result of calling AddTransceiver
+  // with the given arguments, DCHECKing if there is an error.
+  rtc::scoped_refptr<RtpTransceiverInterface> AddTransceiver(
+      cricket::MediaType media_type);
+  rtc::scoped_refptr<RtpTransceiverInterface> AddTransceiver(
+      cricket::MediaType media_type,
+      const RtpTransceiverInit& init);
+  rtc::scoped_refptr<RtpTransceiverInterface> AddTransceiver(
+      rtc::scoped_refptr<MediaStreamTrackInterface> track);
+  rtc::scoped_refptr<RtpTransceiverInterface> AddTransceiver(
+      rtc::scoped_refptr<MediaStreamTrackInterface> track,
+      const RtpTransceiverInit& init);
+
+  // Returns a new dummy audio track with the given label.
+  rtc::scoped_refptr<AudioTrackInterface> CreateAudioTrack(
+      const std::string& label);
+
+  // Returns a new dummy video track with the given label.
+  rtc::scoped_refptr<VideoTrackInterface> CreateVideoTrack(
+      const std::string& label);
+
   // Calls the underlying PeerConnection's AddTrack method with an audio media
   // stream track not bound to any source.
   rtc::scoped_refptr<RtpSenderInterface> AddAudioTrack(
diff --git a/pc/rtpreceiver.cc b/pc/rtpreceiver.cc
index da7e642..4968ac1 100644
--- a/pc/rtpreceiver.cc
+++ b/pc/rtpreceiver.cc
@@ -22,16 +22,16 @@
 namespace webrtc {
 
 AudioRtpReceiver::AudioRtpReceiver(
-    const std::string& track_id,
+    const std::string& receiver_id,
     std::vector<rtc::scoped_refptr<MediaStreamInterface>> streams,
     uint32_t ssrc,
     cricket::VoiceChannel* channel)
-    : id_(track_id),
+    : id_(receiver_id),
       ssrc_(ssrc),
       channel_(channel),
       track_(AudioTrackProxy::Create(
           rtc::Thread::Current(),
-          AudioTrack::Create(track_id,
+          AudioTrack::Create(receiver_id,
                              RemoteAudioSource::Create(ssrc, channel)))),
       streams_(std::move(streams)),
       cached_track_enabled_(track_->enabled()) {
diff --git a/pc/rtpreceiver.h b/pc/rtpreceiver.h
index a1af13c..6df4642 100644
--- a/pc/rtpreceiver.h
+++ b/pc/rtpreceiver.h
@@ -50,11 +50,10 @@
   // 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,
+      const std::string& receiver_id,
       std::vector<rtc::scoped_refptr<MediaStreamInterface>> streams,
       uint32_t ssrc,
       cricket::VoiceChannel* channel);
-
   virtual ~AudioRtpReceiver();
 
   // ObserverInterface implementation