Adding AddTrack/RemoveTrack to native PeerConnection API.
Also, now creating the RtpSender/RtpReceiver proxy objects immediately,
rather than waiting until when GetSenders/GetReceivers is called.
Review URL: https://codereview.webrtc.org/1563403002
Cr-Commit-Position: refs/heads/master@{#11259}
diff --git a/talk/app/webrtc/peerconnection.cc b/talk/app/webrtc/peerconnection.cc
index ccca18a..beae770 100644
--- a/talk/app/webrtc/peerconnection.cc
+++ b/talk/app/webrtc/peerconnection.cc
@@ -734,6 +734,80 @@
observer_->OnRenegotiationNeeded();
}
+rtc::scoped_refptr<RtpSenderInterface> PeerConnection::AddTrack(
+ MediaStreamTrackInterface* track,
+ std::vector<MediaStreamInterface*> streams) {
+ TRACE_EVENT0("webrtc", "PeerConnection::AddTrack");
+ if (IsClosed()) {
+ return nullptr;
+ }
+ if (streams.size() >= 2) {
+ LOG(LS_ERROR)
+ << "Adding a track with two streams is not currently supported.";
+ return nullptr;
+ }
+ // TODO(deadbeef): Support adding a track to two different senders.
+ if (FindSenderForTrack(track) != senders_.end()) {
+ LOG(LS_ERROR) << "Sender for track " << track->id() << " already exists.";
+ return nullptr;
+ }
+
+ // TODO(deadbeef): Support adding a track to multiple streams.
+ rtc::scoped_refptr<RtpSenderInterface> new_sender;
+ if (track->kind() == MediaStreamTrackInterface::kAudioKind) {
+ new_sender = RtpSenderProxy::Create(
+ signaling_thread(),
+ new AudioRtpSender(static_cast<AudioTrackInterface*>(track),
+ session_.get(), stats_.get()));
+ if (!streams.empty()) {
+ new_sender->set_stream_id(streams[0]->label());
+ }
+ const TrackInfo* track_info = FindTrackInfo(
+ local_audio_tracks_, new_sender->stream_id(), track->id());
+ if (track_info) {
+ new_sender->SetSsrc(track_info->ssrc);
+ }
+ } else if (track->kind() == MediaStreamTrackInterface::kVideoKind) {
+ new_sender = RtpSenderProxy::Create(
+ signaling_thread(),
+ new VideoRtpSender(static_cast<VideoTrackInterface*>(track),
+ session_.get()));
+ if (!streams.empty()) {
+ new_sender->set_stream_id(streams[0]->label());
+ }
+ const TrackInfo* track_info = FindTrackInfo(
+ local_video_tracks_, new_sender->stream_id(), track->id());
+ if (track_info) {
+ new_sender->SetSsrc(track_info->ssrc);
+ }
+ } else {
+ LOG(LS_ERROR) << "CreateSender called with invalid kind: " << track->kind();
+ return rtc::scoped_refptr<RtpSenderInterface>();
+ }
+
+ senders_.push_back(new_sender);
+ observer_->OnRenegotiationNeeded();
+ return new_sender;
+}
+
+bool PeerConnection::RemoveTrack(RtpSenderInterface* sender) {
+ TRACE_EVENT0("webrtc", "PeerConnection::RemoveTrack");
+ if (IsClosed()) {
+ return false;
+ }
+
+ auto it = std::find(senders_.begin(), senders_.end(), sender);
+ if (it == senders_.end()) {
+ LOG(LS_ERROR) << "Couldn't find sender " << sender->id() << " to remove.";
+ return false;
+ }
+ (*it)->Stop();
+ senders_.erase(it);
+
+ observer_->OnRenegotiationNeeded();
+ return true;
+}
+
rtc::scoped_refptr<DtmfSenderInterface> PeerConnection::CreateDtmfSender(
AudioTrackInterface* track) {
TRACE_EVENT0("webrtc", "PeerConnection::CreateDtmfSender");
@@ -759,39 +833,32 @@
const std::string& kind,
const std::string& stream_id) {
TRACE_EVENT0("webrtc", "PeerConnection::CreateSender");
- RtpSenderInterface* new_sender;
+ rtc::scoped_refptr<RtpSenderInterface> new_sender;
if (kind == MediaStreamTrackInterface::kAudioKind) {
- new_sender = new AudioRtpSender(session_.get(), stats_.get());
+ new_sender = RtpSenderProxy::Create(
+ signaling_thread(), new AudioRtpSender(session_.get(), stats_.get()));
} else if (kind == MediaStreamTrackInterface::kVideoKind) {
- new_sender = new VideoRtpSender(session_.get());
+ new_sender = RtpSenderProxy::Create(signaling_thread(),
+ new VideoRtpSender(session_.get()));
} else {
LOG(LS_ERROR) << "CreateSender called with invalid kind: " << kind;
- return rtc::scoped_refptr<RtpSenderInterface>();
+ return new_sender;
}
if (!stream_id.empty()) {
new_sender->set_stream_id(stream_id);
}
senders_.push_back(new_sender);
- return RtpSenderProxy::Create(signaling_thread(), new_sender);
+ return new_sender;
}
std::vector<rtc::scoped_refptr<RtpSenderInterface>> PeerConnection::GetSenders()
const {
- std::vector<rtc::scoped_refptr<RtpSenderInterface>> senders;
- for (const auto& sender : senders_) {
- senders.push_back(RtpSenderProxy::Create(signaling_thread(), sender.get()));
- }
- return senders;
+ return senders_;
}
std::vector<rtc::scoped_refptr<RtpReceiverInterface>>
PeerConnection::GetReceivers() const {
- std::vector<rtc::scoped_refptr<RtpReceiverInterface>> receivers;
- for (const auto& receiver : receivers_) {
- receivers.push_back(
- RtpReceiverProxy::Create(signaling_thread(), receiver.get()));
- }
- return receivers;
+ return receivers_;
}
bool PeerConnection::GetStats(StatsObserver* observer,
@@ -1257,13 +1324,17 @@
void PeerConnection::CreateAudioReceiver(MediaStreamInterface* stream,
AudioTrackInterface* audio_track,
uint32_t ssrc) {
- receivers_.push_back(new AudioRtpReceiver(audio_track, ssrc, session_.get()));
+ receivers_.push_back(RtpReceiverProxy::Create(
+ signaling_thread(),
+ new AudioRtpReceiver(audio_track, ssrc, session_.get())));
}
void PeerConnection::CreateVideoReceiver(MediaStreamInterface* stream,
VideoTrackInterface* video_track,
uint32_t ssrc) {
- receivers_.push_back(new VideoRtpReceiver(video_track, ssrc, session_.get()));
+ receivers_.push_back(RtpReceiverProxy::Create(
+ signaling_thread(),
+ new VideoRtpReceiver(video_track, ssrc, session_.get())));
}
// TODO(deadbeef): Keep RtpReceivers around even if track goes away in remote
@@ -1355,8 +1426,9 @@
}
// Normal case; we've never seen this track before.
- AudioRtpSender* new_sender =
- new AudioRtpSender(track, stream->label(), session_.get(), stats_.get());
+ rtc::scoped_refptr<RtpSenderInterface> new_sender = RtpSenderProxy::Create(
+ signaling_thread(),
+ new AudioRtpSender(track, stream->label(), session_.get(), stats_.get()));
senders_.push_back(new_sender);
// If the sender has already been configured in SDP, we call SetSsrc,
// which will connect the sender to the underlying transport. This can
@@ -1396,8 +1468,9 @@
}
// Normal case; we've never seen this track before.
- VideoRtpSender* new_sender =
- new VideoRtpSender(track, stream->label(), session_.get());
+ rtc::scoped_refptr<RtpSenderInterface> new_sender = RtpSenderProxy::Create(
+ signaling_thread(),
+ new VideoRtpSender(track, stream->label(), session_.get()));
senders_.push_back(new_sender);
const TrackInfo* track_info =
FindTrackInfo(local_video_tracks_, stream->label(), track->id());
diff --git a/talk/app/webrtc/peerconnection.h b/talk/app/webrtc/peerconnection.h
index 6e2b967..03b5853 100644
--- a/talk/app/webrtc/peerconnection.h
+++ b/talk/app/webrtc/peerconnection.h
@@ -83,6 +83,11 @@
bool AddStream(MediaStreamInterface* local_stream) override;
void RemoveStream(MediaStreamInterface* local_stream) override;
+ rtc::scoped_refptr<RtpSenderInterface> AddTrack(
+ MediaStreamTrackInterface* track,
+ std::vector<MediaStreamInterface*> streams) override;
+ bool RemoveTrack(RtpSenderInterface* sender) override;
+
virtual WebRtcSession* session() { return session_.get(); }
rtc::scoped_refptr<DtmfSenderInterface> CreateDtmfSender(
diff --git a/talk/app/webrtc/peerconnectioninterface.h b/talk/app/webrtc/peerconnectioninterface.h
index b9afbad..da99a7b 100644
--- a/talk/app/webrtc/peerconnectioninterface.h
+++ b/talk/app/webrtc/peerconnectioninterface.h
@@ -331,6 +331,23 @@
// remote peer is notified.
virtual void RemoveStream(MediaStreamInterface* stream) = 0;
+ // TODO(deadbeef): Make the following two methods pure virtual once
+ // implemented by all subclasses of PeerConnectionInterface.
+ // Add a new MediaStreamTrack to be sent on this PeerConnection.
+ // |streams| indicates which stream labels the track should be associated
+ // with.
+ virtual rtc::scoped_refptr<RtpSenderInterface> AddTrack(
+ MediaStreamTrackInterface* track,
+ std::vector<MediaStreamInterface*> streams) {
+ return nullptr;
+ }
+
+ // Remove an RtpSender from this PeerConnection.
+ // Returns true on success.
+ virtual bool RemoveTrack(RtpSenderInterface* sender) {
+ return false;
+ }
+
// Returns pointer to the created DtmfSender on success.
// Otherwise returns NULL.
virtual rtc::scoped_refptr<DtmfSenderInterface> CreateDtmfSender(
diff --git a/talk/app/webrtc/peerconnectioninterface_unittest.cc b/talk/app/webrtc/peerconnectioninterface_unittest.cc
index c3789b7..b1f718b 100644
--- a/talk/app/webrtc/peerconnectioninterface_unittest.cc
+++ b/talk/app/webrtc/peerconnectioninterface_unittest.cc
@@ -1025,6 +1025,101 @@
EXPECT_EQ(0u, pc_->local_streams()->count());
}
+// Test for AddTrack and RemoveTrack methods.
+// Tests that the created offer includes tracks we added,
+// and that the RtpSenders are created correctly.
+// Also tests that RemoveTrack removes the tracks from subsequent offers.
+TEST_F(PeerConnectionInterfaceTest, AddTrackRemoveTrack) {
+ CreatePeerConnection();
+ // Create a dummy stream, so tracks share a stream label.
+ scoped_refptr<MediaStreamInterface> stream(
+ pc_factory_->CreateLocalMediaStream(kStreamLabel1));
+ std::vector<MediaStreamInterface*> stream_list;
+ stream_list.push_back(stream.get());
+ scoped_refptr<AudioTrackInterface> audio_track(
+ pc_factory_->CreateAudioTrack("audio_track", nullptr));
+ scoped_refptr<VideoTrackInterface> video_track(
+ pc_factory_->CreateVideoTrack("video_track", nullptr));
+ auto audio_sender = pc_->AddTrack(audio_track, stream_list);
+ auto video_sender = pc_->AddTrack(video_track, stream_list);
+ EXPECT_EQ(kStreamLabel1, audio_sender->stream_id());
+ EXPECT_EQ("audio_track", audio_sender->id());
+ EXPECT_EQ(audio_track, audio_sender->track());
+ EXPECT_EQ(kStreamLabel1, video_sender->stream_id());
+ EXPECT_EQ("video_track", video_sender->id());
+ EXPECT_EQ(video_track, video_sender->track());
+
+ // Now create an offer and check for the senders.
+ scoped_ptr<SessionDescriptionInterface> offer;
+ ASSERT_TRUE(DoCreateOffer(offer.accept(), nullptr));
+
+ const cricket::ContentInfo* audio_content =
+ cricket::GetFirstAudioContent(offer->description());
+ const cricket::AudioContentDescription* audio_desc =
+ static_cast<const cricket::AudioContentDescription*>(
+ audio_content->description);
+ EXPECT_TRUE(
+ ContainsTrack(audio_desc->streams(), kStreamLabel1, "audio_track"));
+
+ const cricket::ContentInfo* video_content =
+ cricket::GetFirstVideoContent(offer->description());
+ const cricket::VideoContentDescription* video_desc =
+ static_cast<const cricket::VideoContentDescription*>(
+ video_content->description);
+ EXPECT_TRUE(
+ ContainsTrack(video_desc->streams(), kStreamLabel1, "video_track"));
+
+ EXPECT_TRUE(DoSetLocalDescription(offer.release()));
+
+ // Now try removing the tracks.
+ EXPECT_TRUE(pc_->RemoveTrack(audio_sender));
+ EXPECT_TRUE(pc_->RemoveTrack(video_sender));
+
+ // Create a new offer and ensure it doesn't contain the removed senders.
+ ASSERT_TRUE(DoCreateOffer(offer.accept(), nullptr));
+
+ audio_content = cricket::GetFirstAudioContent(offer->description());
+ audio_desc = static_cast<const cricket::AudioContentDescription*>(
+ audio_content->description);
+ EXPECT_FALSE(
+ ContainsTrack(audio_desc->streams(), kStreamLabel1, "audio_track"));
+
+ video_content = cricket::GetFirstVideoContent(offer->description());
+ video_desc = static_cast<const cricket::VideoContentDescription*>(
+ video_content->description);
+ EXPECT_FALSE(
+ ContainsTrack(video_desc->streams(), kStreamLabel1, "video_track"));
+
+ EXPECT_TRUE(DoSetLocalDescription(offer.release()));
+
+ // Calling RemoveTrack on a sender no longer attached to a PeerConnection
+ // should return false.
+ EXPECT_FALSE(pc_->RemoveTrack(audio_sender));
+ EXPECT_FALSE(pc_->RemoveTrack(video_sender));
+}
+
+// Test creating senders without a stream specified,
+// expecting a random stream ID to be generated.
+TEST_F(PeerConnectionInterfaceTest, AddTrackWithoutStream) {
+ CreatePeerConnection();
+ // Create a dummy stream, so tracks share a stream label.
+ scoped_refptr<AudioTrackInterface> audio_track(
+ pc_factory_->CreateAudioTrack("audio_track", nullptr));
+ scoped_refptr<VideoTrackInterface> video_track(
+ pc_factory_->CreateVideoTrack("video_track", nullptr));
+ auto audio_sender =
+ pc_->AddTrack(audio_track, std::vector<MediaStreamInterface*>());
+ auto video_sender =
+ pc_->AddTrack(video_track, std::vector<MediaStreamInterface*>());
+ EXPECT_EQ("audio_track", audio_sender->id());
+ EXPECT_EQ(audio_track, audio_sender->track());
+ EXPECT_EQ("video_track", video_sender->id());
+ EXPECT_EQ(video_track, video_sender->track());
+ // If the ID is truly a random GUID, it should be infinitely unlikely they
+ // will be the same.
+ EXPECT_NE(video_sender->stream_id(), audio_sender->stream_id());
+}
+
TEST_F(PeerConnectionInterfaceTest, CreateOfferReceiveAnswer) {
InitiateCall();
WaitAndVerifyOnAddStream(kStreamLabel1);
diff --git a/talk/app/webrtc/peerconnectionproxy.h b/talk/app/webrtc/peerconnectionproxy.h
index 3c983d7..d76a6b8 100644
--- a/talk/app/webrtc/peerconnectionproxy.h
+++ b/talk/app/webrtc/peerconnectionproxy.h
@@ -41,6 +41,11 @@
remote_streams)
PROXY_METHOD1(bool, AddStream, MediaStreamInterface*)
PROXY_METHOD1(void, RemoveStream, MediaStreamInterface*)
+ PROXY_METHOD2(rtc::scoped_refptr<RtpSenderInterface>,
+ AddTrack,
+ MediaStreamTrackInterface*,
+ std::vector<MediaStreamInterface*>)
+ PROXY_METHOD1(bool, RemoveTrack, RtpSenderInterface*)
PROXY_METHOD1(rtc::scoped_refptr<DtmfSenderInterface>,
CreateDtmfSender, AudioTrackInterface*)
PROXY_METHOD2(rtc::scoped_refptr<RtpSenderInterface>,
diff --git a/talk/app/webrtc/rtpsender.cc b/talk/app/webrtc/rtpsender.cc
index 91e484b..72a9114 100644
--- a/talk/app/webrtc/rtpsender.cc
+++ b/talk/app/webrtc/rtpsender.cc
@@ -75,6 +75,21 @@
track_->AddSink(sink_adapter_.get());
}
+AudioRtpSender::AudioRtpSender(AudioTrackInterface* track,
+ AudioProviderInterface* provider,
+ StatsCollector* stats)
+ : id_(track->id()),
+ stream_id_(rtc::CreateRandomUuid()),
+ provider_(provider),
+ stats_(stats),
+ track_(track),
+ cached_track_enabled_(track->enabled()),
+ sink_adapter_(new LocalAudioSinkAdapter()) {
+ RTC_DCHECK(provider != nullptr);
+ track_->RegisterObserver(this);
+ track_->AddSink(sink_adapter_.get());
+}
+
AudioRtpSender::AudioRtpSender(AudioProviderInterface* provider,
StatsCollector* stats)
: id_(rtc::CreateRandomUuid()),
@@ -211,6 +226,17 @@
track_->RegisterObserver(this);
}
+VideoRtpSender::VideoRtpSender(VideoTrackInterface* track,
+ VideoProviderInterface* provider)
+ : id_(track->id()),
+ stream_id_(rtc::CreateRandomUuid()),
+ provider_(provider),
+ track_(track),
+ cached_track_enabled_(track->enabled()) {
+ RTC_DCHECK(provider != nullptr);
+ track_->RegisterObserver(this);
+}
+
VideoRtpSender::VideoRtpSender(VideoProviderInterface* provider)
: id_(rtc::CreateRandomUuid()),
stream_id_(rtc::CreateRandomUuid()),
diff --git a/talk/app/webrtc/rtpsender.h b/talk/app/webrtc/rtpsender.h
index dd846b5..9e055c1 100644
--- a/talk/app/webrtc/rtpsender.h
+++ b/talk/app/webrtc/rtpsender.h
@@ -78,6 +78,11 @@
AudioProviderInterface* provider,
StatsCollector* stats);
+ // Randomly generates stream_id.
+ AudioRtpSender(AudioTrackInterface* track,
+ AudioProviderInterface* provider,
+ StatsCollector* stats);
+
// Randomly generates id and stream_id.
AudioRtpSender(AudioProviderInterface* provider, StatsCollector* stats);
@@ -136,6 +141,9 @@
const std::string& stream_id,
VideoProviderInterface* provider);
+ // Randomly generates stream_id.
+ VideoRtpSender(VideoTrackInterface* track, VideoProviderInterface* provider);
+
// Randomly generates id and stream_id.
explicit VideoRtpSender(VideoProviderInterface* provider);