Implement PeerConnection::AddTrack/RemoveTrack for Unified Plan

Bug: webrtc:7600
Change-Id: I2a48426a29ac67b6bdbd7817fe07273cdd5fd980
Reviewed-on: https://webrtc-review.googlesource.com/31647
Commit-Queue: Steve Anton <steveanton@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Peter Thatcher <pthatcher@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#21305}
This commit is contained in:
Steve Anton
2017-12-14 10:23:57 -08:00
committed by Commit Bot
parent f1a7a8c602
commit f9381f0e73
11 changed files with 541 additions and 66 deletions

View File

@ -590,10 +590,24 @@ class PeerConnectionInterface : public rtc::RefCountInterface {
virtual void RemoveStream(MediaStreamInterface* stream) = 0; virtual void RemoveStream(MediaStreamInterface* stream) = 0;
// Add a new MediaStreamTrack to be sent on this PeerConnection, and return // Add a new MediaStreamTrack to be sent on this PeerConnection, and return
// the newly created RtpSender. // the newly created RtpSender. The RtpSender will be associated with the
// streams specified in the |stream_labels| list.
// //
// Errors:
// - INVALID_PARAMETER: |track| is null, has a kind other than audio or video,
// or a sender already exists for the track.
// - INVALID_STATE: The PeerConnection is closed.
// TODO(steveanton): Remove default implementation once downstream
// implementations have been updated.
virtual RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>>
AddTrackWithStreamLabels(rtc::scoped_refptr<MediaStreamTrackInterface> track,
const std::vector<std::string>& stream_labels) {
return RTCError(RTCErrorType::UNSUPPORTED_OPERATION, "Not implemented");
}
// |streams| indicates which stream labels the track should be associated // |streams| indicates which stream labels the track should be associated
// with. // with.
// TODO(steveanton): Remove this overload once callers have moved to the
// signature with stream labels.
virtual rtc::scoped_refptr<RtpSenderInterface> AddTrack( virtual rtc::scoped_refptr<RtpSenderInterface> AddTrack(
MediaStreamTrackInterface* track, MediaStreamTrackInterface* track,
std::vector<MediaStreamInterface*> streams) = 0; std::vector<MediaStreamInterface*> streams) = 0;

View File

@ -28,6 +28,10 @@ BEGIN_SIGNALING_PROXY_MAP(PeerConnection)
PROXY_METHOD0(rtc::scoped_refptr<StreamCollectionInterface>, remote_streams) PROXY_METHOD0(rtc::scoped_refptr<StreamCollectionInterface>, remote_streams)
PROXY_METHOD1(bool, AddStream, MediaStreamInterface*) PROXY_METHOD1(bool, AddStream, MediaStreamInterface*)
PROXY_METHOD1(void, RemoveStream, MediaStreamInterface*) PROXY_METHOD1(void, RemoveStream, MediaStreamInterface*)
PROXY_METHOD2(RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>>,
AddTrackWithStreamLabels,
rtc::scoped_refptr<MediaStreamTrackInterface>,
const std::vector<std::string>&);
PROXY_METHOD2(rtc::scoped_refptr<RtpSenderInterface>, PROXY_METHOD2(rtc::scoped_refptr<RtpSenderInterface>,
AddTrack, AddTrack,
MediaStreamTrackInterface*, MediaStreamTrackInterface*,

View File

@ -38,7 +38,7 @@ struct RtpTransceiverInit final {
// The added RtpTransceiver will be added to these streams. // The added RtpTransceiver will be added to these streams.
// TODO(bugs.webrtc.org/7600): Not implemented. // TODO(bugs.webrtc.org/7600): Not implemented.
std::vector<rtc::scoped_refptr<MediaStreamInterface>> streams; std::vector<std::string> stream_labels;
// TODO(bugs.webrtc.org/7600): Not implemented. // TODO(bugs.webrtc.org/7600): Not implemented.
std::vector<RtpEncodingParameters> send_encodings; std::vector<RtpEncodingParameters> send_encodings;

View File

@ -1005,83 +1005,195 @@ rtc::scoped_refptr<RtpSenderInterface> PeerConnection::AddTrack(
MediaStreamTrackInterface* track, MediaStreamTrackInterface* track,
std::vector<MediaStreamInterface*> streams) { std::vector<MediaStreamInterface*> streams) {
TRACE_EVENT0("webrtc", "PeerConnection::AddTrack"); TRACE_EVENT0("webrtc", "PeerConnection::AddTrack");
if (IsClosed()) { std::vector<std::string> stream_labels;
for (auto* stream : streams) {
if (!stream) {
RTC_LOG(LS_ERROR) << "Stream list has null element.";
return nullptr;
}
stream_labels.push_back(stream->label());
}
auto sender_or_error = AddTrackWithStreamLabels(track, stream_labels);
if (!sender_or_error.ok()) {
return nullptr; return nullptr;
} }
if (streams.size() >= 2) { return sender_or_error.MoveValue();
RTC_LOG(LS_ERROR) }
<< "Adding a track with two streams is not currently supported.";
return nullptr; RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>>
PeerConnection::AddTrackWithStreamLabels(
rtc::scoped_refptr<MediaStreamTrackInterface> track,
const std::vector<std::string>& stream_labels) {
TRACE_EVENT0("webrtc", "PeerConnection::AddTrackWithStreamLabels");
if (!track) {
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, "Track is null.");
}
if (!(track->kind() == MediaStreamTrackInterface::kAudioKind ||
track->kind() == MediaStreamTrackInterface::kVideoKind)) {
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
"Track has invalid kind: " + track->kind());
}
// TODO(bugs.webrtc.org/7932): Support adding a track to multiple streams.
if (stream_labels.size() > 1u) {
LOG_AND_RETURN_ERROR(
RTCErrorType::UNSUPPORTED_OPERATION,
"AddTrack with more than one stream is not currently supported.");
}
if (IsClosed()) {
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_STATE,
"PeerConnection is closed.");
} }
if (FindSenderForTrack(track)) { if (FindSenderForTrack(track)) {
RTC_LOG(LS_ERROR) << "Sender for track " << track->id() LOG_AND_RETURN_ERROR(
<< " already exists."; RTCErrorType::INVALID_PARAMETER,
return nullptr; "Sender already exists for track " + track->id() + ".");
} }
// TODO(bugs.webrtc.org/7933): MediaSession expects the sender to have exactly
// one stream. AddTrackInternal will return an error if there is more than one
// stream, but if the caller specifies none then we need to generate a random
// stream label.
std::vector<std::string> adjusted_stream_labels = stream_labels;
if (stream_labels.empty()) {
adjusted_stream_labels.push_back(rtc::CreateRandomUuid());
}
RTC_DCHECK_EQ(1, adjusted_stream_labels.size());
auto sender_or_error =
(IsUnifiedPlan() ? AddTrackUnifiedPlan(track, adjusted_stream_labels)
: AddTrackPlanB(track, adjusted_stream_labels));
if (sender_or_error.ok()) {
observer_->OnRenegotiationNeeded();
}
return sender_or_error;
}
// TODO(deadbeef): Support adding a track to multiple streams. RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>>
rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>> new_sender; PeerConnection::AddTrackPlanB(
rtc::scoped_refptr<MediaStreamTrackInterface> track,
const std::vector<std::string>& stream_labels) {
if (track->kind() == MediaStreamTrackInterface::kAudioKind) { if (track->kind() == MediaStreamTrackInterface::kAudioKind) {
new_sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create( auto new_sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
signaling_thread(), signaling_thread(),
new AudioRtpSender(static_cast<AudioTrackInterface*>(track), new AudioRtpSender(static_cast<AudioTrackInterface*>(track.get()),
voice_channel(), stats_.get())); voice_channel(), stats_.get()));
GetAudioTransceiver()->internal()->AddSender(new_sender); GetAudioTransceiver()->internal()->AddSender(new_sender);
if (!streams.empty()) { new_sender->internal()->set_stream_ids(stream_labels);
new_sender->internal()->set_stream_id(streams[0]->label());
}
const RtpSenderInfo* sender_info = const RtpSenderInfo* sender_info =
FindSenderInfo(local_audio_sender_infos_, FindSenderInfo(local_audio_sender_infos_,
new_sender->internal()->stream_id(), track->id()); new_sender->internal()->stream_id(), track->id());
if (sender_info) { if (sender_info) {
new_sender->internal()->SetSsrc(sender_info->first_ssrc); new_sender->internal()->SetSsrc(sender_info->first_ssrc);
} }
} else if (track->kind() == MediaStreamTrackInterface::kVideoKind) { return rtc::scoped_refptr<RtpSenderInterface>(new_sender);
new_sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create( } else {
RTC_DCHECK_EQ(MediaStreamTrackInterface::kVideoKind, track->kind());
auto new_sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
signaling_thread(), signaling_thread(),
new VideoRtpSender(static_cast<VideoTrackInterface*>(track), new VideoRtpSender(static_cast<VideoTrackInterface*>(track.get()),
video_channel())); video_channel()));
GetVideoTransceiver()->internal()->AddSender(new_sender); GetVideoTransceiver()->internal()->AddSender(new_sender);
if (!streams.empty()) { new_sender->internal()->set_stream_ids(stream_labels);
new_sender->internal()->set_stream_id(streams[0]->label());
}
const RtpSenderInfo* sender_info = const RtpSenderInfo* sender_info =
FindSenderInfo(local_video_sender_infos_, FindSenderInfo(local_video_sender_infos_,
new_sender->internal()->stream_id(), track->id()); new_sender->internal()->stream_id(), track->id());
if (sender_info) { if (sender_info) {
new_sender->internal()->SetSsrc(sender_info->first_ssrc); new_sender->internal()->SetSsrc(sender_info->first_ssrc);
} }
} else { return rtc::scoped_refptr<RtpSenderInterface>(new_sender);
RTC_LOG(LS_ERROR) << "CreateSender called with invalid kind: "
<< track->kind();
return rtc::scoped_refptr<RtpSenderInterface>();
} }
}
observer_->OnRenegotiationNeeded(); RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>>
return new_sender; PeerConnection::AddTrackUnifiedPlan(
rtc::scoped_refptr<MediaStreamTrackInterface> track,
const std::vector<std::string>& stream_labels) {
auto transceiver = FindFirstTransceiverForAddedTrack(track);
if (transceiver) {
if (transceiver->direction() == RtpTransceiverDirection::kRecvOnly) {
transceiver->SetDirection(RtpTransceiverDirection::kSendRecv);
} else if (transceiver->direction() == RtpTransceiverDirection::kInactive) {
transceiver->SetDirection(RtpTransceiverDirection::kSendOnly);
}
} else {
cricket::MediaType media_type =
(track->kind() == MediaStreamTrackInterface::kAudioKind
? cricket::MEDIA_TYPE_AUDIO
: cricket::MEDIA_TYPE_VIDEO);
transceiver = CreateTransceiver(media_type);
transceiver->internal()->set_created_by_addtrack(true);
transceiver->SetDirection(RtpTransceiverDirection::kSendRecv);
}
transceiver->sender()->SetTrack(track);
transceiver->internal()->sender_internal()->set_stream_ids(stream_labels);
return transceiver->sender();
}
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
PeerConnection::FindFirstTransceiverForAddedTrack(
rtc::scoped_refptr<MediaStreamTrackInterface> track) {
RTC_DCHECK(track);
for (auto transceiver : transceivers_) {
if (!transceiver->sender()->track() &&
cricket::MediaTypeToString(transceiver->internal()->media_type()) ==
track->kind() &&
!transceiver->internal()->has_ever_been_used_to_send()) {
return transceiver;
}
}
return nullptr;
} }
bool PeerConnection::RemoveTrack(RtpSenderInterface* sender) { bool PeerConnection::RemoveTrack(RtpSenderInterface* sender) {
TRACE_EVENT0("webrtc", "PeerConnection::RemoveTrack"); TRACE_EVENT0("webrtc", "PeerConnection::RemoveTrack");
return RemoveTrackInternal(sender).ok();
}
RTCError PeerConnection::RemoveTrackInternal(
rtc::scoped_refptr<RtpSenderInterface> sender) {
if (!sender) {
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, "Sender is null.");
}
if (IsClosed()) { if (IsClosed()) {
return false; LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_STATE,
"PeerConnection is closed.");
} }
if (IsUnifiedPlan()) {
bool removed; auto transceiver = FindTransceiverBySender(sender);
if (sender->media_type() == cricket::MEDIA_TYPE_AUDIO) { if (!transceiver || !sender->track()) {
removed = GetAudioTransceiver()->internal()->RemoveSender(sender); return RTCError::OK();
}
sender->SetTrack(nullptr);
if (transceiver->direction() == RtpTransceiverDirection::kSendRecv) {
transceiver->internal()->SetDirection(RtpTransceiverDirection::kRecvOnly);
} else if (transceiver->direction() == RtpTransceiverDirection::kSendOnly) {
transceiver->internal()->SetDirection(RtpTransceiverDirection::kInactive);
}
} else { } else {
RTC_DCHECK_EQ(cricket::MEDIA_TYPE_VIDEO, sender->media_type()); bool removed;
removed = GetVideoTransceiver()->internal()->RemoveSender(sender); if (sender->media_type() == cricket::MEDIA_TYPE_AUDIO) {
removed = GetAudioTransceiver()->internal()->RemoveSender(sender);
} else {
RTC_DCHECK_EQ(cricket::MEDIA_TYPE_VIDEO, sender->media_type());
removed = GetVideoTransceiver()->internal()->RemoveSender(sender);
}
if (!removed) {
LOG_AND_RETURN_ERROR(
RTCErrorType::INVALID_PARAMETER,
"Couldn't find sender " + sender->id() + " to remove.");
}
} }
if (!removed) {
RTC_LOG(LS_ERROR) << "Couldn't find sender " << sender->id()
<< " to remove.";
return false;
}
observer_->OnRenegotiationNeeded(); observer_->OnRenegotiationNeeded();
return true; return RTCError::OK();
}
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
PeerConnection::FindTransceiverBySender(
rtc::scoped_refptr<RtpSenderInterface> sender) {
for (auto transceiver : transceivers_) {
if (transceiver->sender() == sender) {
return transceiver;
}
}
return nullptr;
} }
RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>>
@ -1151,10 +1263,26 @@ PeerConnection::AddTransceiver(
// TODO(bugs.webrtc.org/7600): Verify init. // TODO(bugs.webrtc.org/7600): Verify init.
auto transceiver = CreateTransceiver(media_type);
transceiver->SetDirection(init.direction);
if (track) {
transceiver->sender()->SetTrack(track);
}
observer_->OnRenegotiationNeeded();
return rtc::scoped_refptr<RtpTransceiverInterface>(transceiver);
}
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
PeerConnection::CreateTransceiver(cricket::MediaType media_type) {
rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>> sender; rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>> sender;
rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>> rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>
receiver; receiver;
std::string receiver_id = rtc::CreateRandomUuid(); std::string receiver_id = rtc::CreateRandomUuid();
// 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 (media_type == cricket::MEDIA_TYPE_AUDIO) { if (media_type == cricket::MEDIA_TYPE_AUDIO) {
sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create( sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
signaling_thread(), new AudioRtpSender(nullptr, stats_.get())); signaling_thread(), new AudioRtpSender(nullptr, stats_.get()));
@ -1168,24 +1296,11 @@ PeerConnection::AddTransceiver(
signaling_thread(), signaling_thread(),
new VideoRtpReceiver(receiver_id, {}, worker_thread(), 0, nullptr)); 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>> rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
transceiver = RtpTransceiverProxyWithInternal<RtpTransceiver>::Create( transceiver = RtpTransceiverProxyWithInternal<RtpTransceiver>::Create(
signaling_thread(), new RtpTransceiver(sender, receiver)); signaling_thread(), new RtpTransceiver(sender, receiver));
transceiver->SetDirection(init.direction);
transceivers_.push_back(transceiver); transceivers_.push_back(transceiver);
return transceiver;
observer_->OnRenegotiationNeeded();
return rtc::scoped_refptr<RtpTransceiverInterface>(transceiver);
} }
rtc::scoped_refptr<DtmfSenderInterface> PeerConnection::CreateDtmfSender( rtc::scoped_refptr<DtmfSenderInterface> PeerConnection::CreateDtmfSender(

View File

@ -89,6 +89,9 @@ class PeerConnection : public PeerConnectionInterface,
bool AddStream(MediaStreamInterface* local_stream) override; bool AddStream(MediaStreamInterface* local_stream) override;
void RemoveStream(MediaStreamInterface* local_stream) override; void RemoveStream(MediaStreamInterface* local_stream) override;
RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> AddTrackWithStreamLabels(
rtc::scoped_refptr<MediaStreamTrackInterface> track,
const std::vector<std::string>& stream_labels) override;
rtc::scoped_refptr<RtpSenderInterface> AddTrack( rtc::scoped_refptr<RtpSenderInterface> AddTrack(
MediaStreamTrackInterface* track, MediaStreamTrackInterface* track,
std::vector<MediaStreamInterface*> streams) override; std::vector<MediaStreamInterface*> streams) override;
@ -342,11 +345,37 @@ class PeerConnection : public PeerConnectionInterface,
void RemoveVideoTrack(VideoTrackInterface* track, void RemoveVideoTrack(VideoTrackInterface* track,
MediaStreamInterface* stream); MediaStreamInterface* stream);
// AddTrack implementation when Unified Plan is specified.
RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> AddTrackUnifiedPlan(
rtc::scoped_refptr<MediaStreamTrackInterface> track,
const std::vector<std::string>& stream_labels);
// AddTrack implementation when Plan B is specified.
RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> AddTrackPlanB(
rtc::scoped_refptr<MediaStreamTrackInterface> track,
const std::vector<std::string>& stream_labels);
// Returns the first RtpTransceiver suitable for a newly added track, if such
// transceiver is available.
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
FindFirstTransceiverForAddedTrack(
rtc::scoped_refptr<MediaStreamTrackInterface> track);
// RemoveTrack that returns an RTCError.
RTCError RemoveTrackInternal(rtc::scoped_refptr<RtpSenderInterface> sender);
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
FindTransceiverBySender(rtc::scoped_refptr<RtpSenderInterface> sender);
RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> AddTransceiver( RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> AddTransceiver(
cricket::MediaType media_type, cricket::MediaType media_type,
rtc::scoped_refptr<MediaStreamTrackInterface> track, rtc::scoped_refptr<MediaStreamTrackInterface> track,
const RtpTransceiverInit& init); const RtpTransceiverInit& init);
// Create a new RtpTransceiver of the given type and add it to the list of
// transceivers.
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
CreateTransceiver(cricket::MediaType media_type);
void SetIceConnectionState(IceConnectionState new_state); void SetIceConnectionState(IceConnectionState new_state);
// Called any time the IceGatheringState changes // Called any time the IceGatheringState changes
void OnIceGatheringChange(IceGatheringState new_state); void OnIceGatheringChange(IceGatheringState new_state);

View File

@ -452,7 +452,7 @@ TEST_F(PeerConnectionRtpLegacyObserverTest,
EXPECT_FALSE(observer->called()); EXPECT_FALSE(observer->called());
} }
// RtpTransceiver Tests // RtpTransceiver Tests.
// Test that by default there are no transceivers with Unified Plan. // Test that by default there are no transceivers with Unified Plan.
TEST_F(PeerConnectionRtpTest, PeerConnectionHasNoTransceivers) { TEST_F(PeerConnectionRtpTest, PeerConnectionHasNoTransceivers) {
@ -602,4 +602,277 @@ TEST_F(PeerConnectionRtpTest, UnifiedPlanCanClosePeerConnection) {
caller->pc()->Close(); caller->pc()->Close();
} }
// Unified Plan AddTrack tests.
class PeerConnectionRtpUnifiedPlanTest : public PeerConnectionRtpTest {};
// Test that adding an audio track creates a new audio RtpSender with the given
// track.
TEST_F(PeerConnectionRtpUnifiedPlanTest, AddAudioTrackCreatesAudioSender) {
auto caller = CreatePeerConnectionWithUnifiedPlan();
auto audio_track = caller->CreateAudioTrack("a");
auto sender = caller->pc()->AddTrack(audio_track, {});
ASSERT_TRUE(sender);
EXPECT_EQ(cricket::MEDIA_TYPE_AUDIO, sender->media_type());
EXPECT_EQ(audio_track, sender->track());
}
// Test that adding a video track creates a new video RtpSender with the given
// track.
TEST_F(PeerConnectionRtpUnifiedPlanTest, AddVideoTrackCreatesVideoSender) {
auto caller = CreatePeerConnectionWithUnifiedPlan();
auto video_track = caller->CreateVideoTrack("a");
auto sender = caller->pc()->AddTrack(video_track, {});
ASSERT_TRUE(sender);
EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, sender->media_type());
EXPECT_EQ(video_track, sender->track());
}
// Test that adding a track to a new PeerConnection creates an RtpTransceiver
// with the sender that AddTrack returns and in the sendrecv direction.
TEST_F(PeerConnectionRtpUnifiedPlanTest, AddFirstTrackCreatesTransceiver) {
auto caller = CreatePeerConnectionWithUnifiedPlan();
auto sender = caller->AddAudioTrack("a");
ASSERT_TRUE(sender);
auto transceivers = caller->pc()->GetTransceivers();
ASSERT_EQ(1u, transceivers.size());
EXPECT_EQ(sender, transceivers[0]->sender());
EXPECT_EQ(RtpTransceiverDirection::kSendRecv, transceivers[0]->direction());
}
// Test that if a transceiver of the same type but no track had been added to
// the PeerConnection and later a call to AddTrack is made, the resulting sender
// is the transceiver's sender and the sender's track is the newly-added track.
TEST_F(PeerConnectionRtpUnifiedPlanTest, AddTrackReusesTransceiver) {
auto caller = CreatePeerConnectionWithUnifiedPlan();
auto transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
auto audio_track = caller->CreateAudioTrack("a");
auto sender = caller->pc()->AddTrack(audio_track, {});
ASSERT_TRUE(sender);
auto transceivers = caller->pc()->GetTransceivers();
ASSERT_EQ(1u, transceivers.size());
EXPECT_EQ(transceiver, transceivers[0]);
EXPECT_EQ(sender, transceiver->sender());
EXPECT_EQ(audio_track, sender->track());
}
// Test that adding two tracks to a new PeerConnection creates two
// RtpTransceivers in the same order.
TEST_F(PeerConnectionRtpUnifiedPlanTest, TwoAddTrackCreatesTwoTransceivers) {
auto caller = CreatePeerConnectionWithUnifiedPlan();
auto sender1 = caller->AddAudioTrack("a");
auto sender2 = caller->AddVideoTrack("v");
ASSERT_TRUE(sender2);
auto transceivers = caller->pc()->GetTransceivers();
ASSERT_EQ(2u, transceivers.size());
EXPECT_EQ(sender1, transceivers[0]->sender());
EXPECT_EQ(sender2, transceivers[1]->sender());
}
// Test that if there are multiple transceivers with no sending track then a
// later call to AddTrack will use the one of the same type as the newly-added
// track.
TEST_F(PeerConnectionRtpUnifiedPlanTest, AddTrackReusesTransceiverOfType) {
auto caller = CreatePeerConnectionWithUnifiedPlan();
auto audio_transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
auto video_transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO);
auto sender = caller->AddVideoTrack("v");
ASSERT_EQ(2u, caller->pc()->GetTransceivers().size());
EXPECT_NE(sender, audio_transceiver->sender());
EXPECT_EQ(sender, video_transceiver->sender());
}
// Test that if the only transceivers that do not have a sending track have a
// different type from the added track, then AddTrack will create a new
// transceiver for the track.
TEST_F(PeerConnectionRtpUnifiedPlanTest,
AddTrackDoesNotReuseTransceiverOfWrongType) {
auto caller = CreatePeerConnectionWithUnifiedPlan();
caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
auto sender = caller->AddVideoTrack("v");
auto transceivers = caller->pc()->GetTransceivers();
ASSERT_EQ(2u, transceivers.size());
EXPECT_NE(sender, transceivers[0]->sender());
EXPECT_EQ(sender, transceivers[1]->sender());
}
// Test that the first available transceiver is reused by AddTrack when multiple
// are available.
TEST_F(PeerConnectionRtpUnifiedPlanTest,
AddTrackReusesFirstMatchingTransceiver) {
auto caller = CreatePeerConnectionWithUnifiedPlan();
caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
auto sender = caller->AddAudioTrack("a");
auto transceivers = caller->pc()->GetTransceivers();
ASSERT_EQ(2u, transceivers.size());
EXPECT_EQ(sender, transceivers[0]->sender());
EXPECT_NE(sender, transceivers[1]->sender());
}
// Test that a call to AddTrack that reuses a transceiver will change the
// direction from inactive to sendonly.
TEST_F(PeerConnectionRtpUnifiedPlanTest,
AddTrackChangesDirectionFromInactiveToSendOnly) {
auto caller = CreatePeerConnectionWithUnifiedPlan();
RtpTransceiverInit init;
init.direction = RtpTransceiverDirection::kInactive;
auto transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO, init);
caller->observer()->clear_negotiation_needed();
ASSERT_TRUE(caller->AddAudioTrack("a"));
EXPECT_TRUE(caller->observer()->negotiation_needed());
EXPECT_EQ(RtpTransceiverDirection::kSendOnly, transceiver->direction());
}
// Test that a call to AddTrack that reuses a transceiver will change the
// direction from recvonly to sendrecv.
TEST_F(PeerConnectionRtpUnifiedPlanTest,
AddTrackChangesDirectionFromRecvOnlyToSendRecv) {
auto caller = CreatePeerConnectionWithUnifiedPlan();
RtpTransceiverInit init;
init.direction = RtpTransceiverDirection::kRecvOnly;
auto transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO, init);
caller->observer()->clear_negotiation_needed();
ASSERT_TRUE(caller->AddAudioTrack("a"));
EXPECT_TRUE(caller->observer()->negotiation_needed());
EXPECT_EQ(RtpTransceiverDirection::kSendRecv, transceiver->direction());
}
// Unified Plan AddTrack error handling.
TEST_F(PeerConnectionRtpUnifiedPlanTest, AddTrackErrorIfClosed) {
auto caller = CreatePeerConnectionWithUnifiedPlan();
auto audio_track = caller->CreateAudioTrack("a");
caller->pc()->Close();
caller->observer()->clear_negotiation_needed();
EXPECT_FALSE(caller->pc()->AddTrack(audio_track, {}));
EXPECT_FALSE(caller->observer()->negotiation_needed());
}
TEST_F(PeerConnectionRtpUnifiedPlanTest, AddTrackErrorIfTrackAlreadyHasSender) {
auto caller = CreatePeerConnectionWithUnifiedPlan();
auto audio_track = caller->CreateAudioTrack("a");
ASSERT_TRUE(caller->pc()->AddTrack(audio_track, {}));
caller->observer()->clear_negotiation_needed();
EXPECT_FALSE(caller->pc()->AddTrack(audio_track, {}));
EXPECT_FALSE(caller->observer()->negotiation_needed());
}
// Unified Plan RemoveTrack tests.
// Test that calling RemoveTrack on a sender with a previously-added track
// clears the sender's track.
TEST_F(PeerConnectionRtpUnifiedPlanTest, RemoveTrackClearsSenderTrack) {
auto caller = CreatePeerConnectionWithUnifiedPlan();
auto sender = caller->AddAudioTrack("a");
ASSERT_TRUE(caller->pc()->RemoveTrack(sender));
EXPECT_FALSE(sender->track());
}
// Test that calling RemoveTrack on a sender where the transceiver is configured
// in the sendrecv direction changes the transceiver's direction to recvonly.
TEST_F(PeerConnectionRtpUnifiedPlanTest,
RemoveTrackChangesDirectionFromSendRecvToRecvOnly) {
auto caller = CreatePeerConnectionWithUnifiedPlan();
RtpTransceiverInit init;
init.direction = RtpTransceiverDirection::kSendRecv;
auto transceiver =
caller->AddTransceiver(caller->CreateAudioTrack("a"), init);
caller->observer()->clear_negotiation_needed();
ASSERT_TRUE(caller->pc()->RemoveTrack(transceiver->sender()));
EXPECT_TRUE(caller->observer()->negotiation_needed());
EXPECT_EQ(RtpTransceiverDirection::kRecvOnly, transceiver->direction());
EXPECT_TRUE(caller->observer()->renegotiation_needed_);
}
// Test that calling RemoveTrack on a sender where the transceiver is configured
// in the sendonly direction changes the transceiver's direction to inactive.
TEST_F(PeerConnectionRtpUnifiedPlanTest,
RemoveTrackChangesDirectionFromSendOnlyToInactive) {
auto caller = CreatePeerConnectionWithUnifiedPlan();
RtpTransceiverInit init;
init.direction = RtpTransceiverDirection::kSendOnly;
auto transceiver =
caller->AddTransceiver(caller->CreateAudioTrack("a"), init);
caller->observer()->clear_negotiation_needed();
ASSERT_TRUE(caller->pc()->RemoveTrack(transceiver->sender()));
EXPECT_TRUE(caller->observer()->negotiation_needed());
EXPECT_EQ(RtpTransceiverDirection::kInactive, transceiver->direction());
}
// Test that calling RemoveTrack with a sender that has a null track results in
// no change in state.
TEST_F(PeerConnectionRtpUnifiedPlanTest, RemoveTrackWithNullSenderTrackIsNoOp) {
auto caller = CreatePeerConnectionWithUnifiedPlan();
auto sender = caller->AddAudioTrack("a");
auto transceiver = caller->pc()->GetTransceivers()[0];
ASSERT_TRUE(sender->SetTrack(nullptr));
caller->observer()->clear_negotiation_needed();
ASSERT_TRUE(caller->pc()->RemoveTrack(sender));
EXPECT_FALSE(caller->observer()->negotiation_needed());
EXPECT_EQ(RtpTransceiverDirection::kSendRecv, transceiver->direction());
}
// Unified Plan RemoveTrack error handling.
TEST_F(PeerConnectionRtpUnifiedPlanTest, RemoveTrackErrorIfClosed) {
auto caller = CreatePeerConnectionWithUnifiedPlan();
auto sender = caller->AddAudioTrack("a");
caller->pc()->Close();
caller->observer()->clear_negotiation_needed();
EXPECT_FALSE(caller->pc()->RemoveTrack(sender));
EXPECT_FALSE(caller->observer()->negotiation_needed());
}
TEST_F(PeerConnectionRtpUnifiedPlanTest,
RemoveTrackNoErrorIfTrackAlreadyRemoved) {
auto caller = CreatePeerConnectionWithUnifiedPlan();
auto sender = caller->AddAudioTrack("a");
ASSERT_TRUE(caller->pc()->RemoveTrack(sender));
caller->observer()->clear_negotiation_needed();
EXPECT_TRUE(caller->pc()->RemoveTrack(sender));
EXPECT_FALSE(caller->observer()->negotiation_needed());
}
} // namespace webrtc } // namespace webrtc

View File

@ -46,7 +46,7 @@ void LocalAudioSinkAdapter::SetSink(cricket::AudioSource::Sink* sink) {
sink_ = sink; sink_ = sink;
} }
AudioRtpSender::AudioRtpSender(AudioTrackInterface* track, AudioRtpSender::AudioRtpSender(rtc::scoped_refptr<AudioTrackInterface> track,
const std::vector<std::string>& stream_ids, const std::vector<std::string>& stream_ids,
cricket::VoiceChannel* channel, cricket::VoiceChannel* channel,
StatsCollector* stats) StatsCollector* stats)
@ -65,7 +65,7 @@ AudioRtpSender::AudioRtpSender(AudioTrackInterface* track,
CreateDtmfSender(); CreateDtmfSender();
} }
AudioRtpSender::AudioRtpSender(AudioTrackInterface* track, AudioRtpSender::AudioRtpSender(rtc::scoped_refptr<AudioTrackInterface> track,
cricket::VoiceChannel* channel, cricket::VoiceChannel* channel,
StatsCollector* stats) StatsCollector* stats)
: id_(track->id()), : id_(track->id()),
@ -303,7 +303,7 @@ void AudioRtpSender::CreateDtmfSender() {
DtmfSenderProxy::Create(rtc::Thread::Current(), sender.get()); DtmfSenderProxy::Create(rtc::Thread::Current(), sender.get());
} }
VideoRtpSender::VideoRtpSender(VideoTrackInterface* track, VideoRtpSender::VideoRtpSender(rtc::scoped_refptr<VideoTrackInterface> track,
const std::vector<std::string>& stream_ids, const std::vector<std::string>& stream_ids,
cricket::VideoChannel* channel) cricket::VideoChannel* channel)
: id_(track->id()), : id_(track->id()),
@ -318,7 +318,7 @@ VideoRtpSender::VideoRtpSender(VideoTrackInterface* track,
track_->RegisterObserver(this); track_->RegisterObserver(this);
} }
VideoRtpSender::VideoRtpSender(VideoTrackInterface* track, VideoRtpSender::VideoRtpSender(rtc::scoped_refptr<VideoTrackInterface> track,
cricket::VideoChannel* channel) cricket::VideoChannel* channel)
: id_(track->id()), : id_(track->id()),
// TODO(steveanton): With Unified Plan this should be empty. // TODO(steveanton): With Unified Plan this should be empty.

View File

@ -83,14 +83,14 @@ class AudioRtpSender : public DtmfProviderInterface,
// StatsCollector provided so that Add/RemoveLocalAudioTrack can be called // StatsCollector provided so that Add/RemoveLocalAudioTrack can be called
// at the appropriate times. // at the appropriate times.
// |channel| can be null if one does not exist yet. // |channel| can be null if one does not exist yet.
AudioRtpSender(AudioTrackInterface* track, AudioRtpSender(rtc::scoped_refptr<AudioTrackInterface> track,
const std::vector<std::string>& stream_id, const std::vector<std::string>& stream_id,
cricket::VoiceChannel* channel, cricket::VoiceChannel* channel,
StatsCollector* stats); StatsCollector* stats);
// Randomly generates stream_id. // Randomly generates stream_id.
// |channel| can be null if one does not exist yet. // |channel| can be null if one does not exist yet.
AudioRtpSender(AudioTrackInterface* track, AudioRtpSender(rtc::scoped_refptr<AudioTrackInterface> track,
cricket::VoiceChannel* channel, cricket::VoiceChannel* channel,
StatsCollector* stats); StatsCollector* stats);
@ -181,13 +181,14 @@ class VideoRtpSender : public ObserverInterface,
public rtc::RefCountedObject<RtpSenderInternal> { public rtc::RefCountedObject<RtpSenderInternal> {
public: public:
// |channel| can be null if one does not exist yet. // |channel| can be null if one does not exist yet.
VideoRtpSender(VideoTrackInterface* track, VideoRtpSender(rtc::scoped_refptr<VideoTrackInterface> track,
const std::vector<std::string>& stream_id, const std::vector<std::string>& stream_id,
cricket::VideoChannel* channel); cricket::VideoChannel* channel);
// Randomly generates stream_id. // Randomly generates stream_id.
// |channel| can be null if one does not exist yet. // |channel| can be null if one does not exist yet.
VideoRtpSender(VideoTrackInterface* track, cricket::VideoChannel* channel); VideoRtpSender(rtc::scoped_refptr<VideoTrackInterface> track,
cricket::VideoChannel* channel);
// Randomly generates id and stream_id. // Randomly generates id and stream_id.
// |channel| can be null if one does not exist yet. // |channel| can be null if one does not exist yet.

View File

@ -113,6 +113,19 @@ bool RtpTransceiver::RemoveReceiver(RtpReceiverInterface* receiver) {
return true; return true;
} }
rtc::scoped_refptr<RtpSenderInternal> RtpTransceiver::sender_internal() const {
RTC_DCHECK(unified_plan_);
RTC_CHECK_EQ(1u, senders_.size());
return senders_[0]->internal();
}
rtc::scoped_refptr<RtpReceiverInternal> RtpTransceiver::receiver_internal()
const {
RTC_DCHECK(unified_plan_);
RTC_CHECK_EQ(1u, receivers_.size());
return receivers_[0]->internal();
}
rtc::Optional<std::string> RtpTransceiver::mid() const { rtc::Optional<std::string> RtpTransceiver::mid() const {
return mid_; return mid_;
} }

View File

@ -109,6 +109,25 @@ class RtpTransceiver final
return receivers_; return receivers_;
} }
// Returns the backing object for the transceiver's Unified Plan sender.
rtc::scoped_refptr<RtpSenderInternal> sender_internal() const;
// Returns the backing object for the transceiver's Unified Plan receiver.
rtc::scoped_refptr<RtpReceiverInternal> receiver_internal() const;
// According to JSEP rules for SetRemoteDescription, RtpTransceivers can be
// reused only if they were added by AddTrack.
void set_created_by_addtrack(bool created_by_addtrack) {
created_by_addtrack_ = created_by_addtrack;
}
bool created_by_addtrack() const { return created_by_addtrack_; }
// Returns true if this transceiver has ever had the current direction set to
// sendonly or sendrecv.
bool has_ever_been_used_to_send() const {
return has_ever_been_used_to_send_;
}
// RtpTransceiverInterface implementation. // RtpTransceiverInterface implementation.
rtc::Optional<std::string> mid() const override; rtc::Optional<std::string> mid() const override;
rtc::scoped_refptr<RtpSenderInterface> sender() const override; rtc::scoped_refptr<RtpSenderInterface> sender() const override;
@ -133,6 +152,10 @@ class RtpTransceiver final
RtpTransceiverDirection direction_ = RtpTransceiverDirection::kInactive; RtpTransceiverDirection direction_ = RtpTransceiverDirection::kInactive;
rtc::Optional<RtpTransceiverDirection> current_direction_; rtc::Optional<RtpTransceiverDirection> current_direction_;
rtc::Optional<std::string> mid_; rtc::Optional<std::string> mid_;
bool created_by_addtrack_ = false;
// TODO(steveanton): Implement this once there is a mechanism to set the
// current direction.
bool has_ever_been_used_to_send_ = false;
cricket::BaseChannel* channel_ = nullptr; cricket::BaseChannel* channel_ = nullptr;
}; };

View File

@ -158,6 +158,9 @@ class MockPeerConnectionObserver : public PeerConnectionObserver {
return candidates; return candidates;
} }
bool negotiation_needed() const { return renegotiation_needed_; }
void clear_negotiation_needed() { renegotiation_needed_ = false; }
rtc::scoped_refptr<PeerConnectionInterface> pc_; rtc::scoped_refptr<PeerConnectionInterface> pc_;
PeerConnectionInterface::SignalingState state_; PeerConnectionInterface::SignalingState state_;
std::vector<std::unique_ptr<IceCandidateInterface>> candidates_; std::vector<std::unique_ptr<IceCandidateInterface>> candidates_;