mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-13 13:50:40 +01:00
Add support for JSEP offer/answer with transceivers
This change adds support to PeerConnection's CreateOffer/ CreateAnswer/SetLocalDescription/SetRemoteDescription for Unified Plan SDP mapping to/from RtpTransceivers. This behavior is enabled using the kUnifiedPlan SDP semantics in the PeerConnection configuration. Bug: webrtc:7600 Change-Id: I4b44f5d3690887d387bf9c47eac00db8ec974571 Reviewed-on: https://webrtc-review.googlesource.com/28341 Commit-Queue: Steve Anton <steveanton@webrtc.org> Reviewed-by: Peter Thatcher <pthatcher@webrtc.org> Cr-Commit-Position: refs/heads/master@{#21442}
This commit is contained in:
parent
24de1735b7
commit
dcc3c02468
10 changed files with 1518 additions and 155 deletions
|
@ -29,6 +29,9 @@ enum class RtpTransceiverDirection {
|
|||
kInactive
|
||||
};
|
||||
|
||||
// This is provided as a debugging aid. The format of the output is unspecified.
|
||||
std::ostream& operator<<(std::ostream& os, RtpTransceiverDirection direction);
|
||||
|
||||
// Structure for initializing an RtpTransceiver in a call to
|
||||
// PeerConnectionInterface::AddTransceiver.
|
||||
// https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiverinit
|
||||
|
|
|
@ -400,6 +400,7 @@ if (rtc_include_tests) {
|
|||
"peerconnection_datachannel_unittest.cc",
|
||||
"peerconnection_ice_unittest.cc",
|
||||
"peerconnection_integrationtest.cc",
|
||||
"peerconnection_jsep_unittest.cc",
|
||||
"peerconnection_media_unittest.cc",
|
||||
"peerconnection_rtp_unittest.cc",
|
||||
"peerconnection_signaling_unittest.cc",
|
||||
|
|
|
@ -1309,16 +1309,16 @@ SessionDescription* MediaSessionDescriptionFactory::CreateOffer(
|
|||
|
||||
// Iterate through the media description options, matching with existing media
|
||||
// descriptions in |current_description|.
|
||||
int msection_index = 0;
|
||||
size_t msection_index = 0;
|
||||
for (const MediaDescriptionOptions& media_description_options :
|
||||
session_options.media_description_options) {
|
||||
const ContentInfo* current_content = nullptr;
|
||||
if (current_description &&
|
||||
msection_index <
|
||||
static_cast<int>(current_description->contents().size())) {
|
||||
msection_index < current_description->contents().size()) {
|
||||
current_content = ¤t_description->contents()[msection_index];
|
||||
// Media type must match.
|
||||
RTC_DCHECK(IsMediaContentOfType(current_content,
|
||||
// Media type must match unless this media section is being recycled.
|
||||
RTC_DCHECK(current_content->rejected ||
|
||||
IsMediaContentOfType(current_content,
|
||||
media_description_options.type));
|
||||
}
|
||||
switch (media_description_options.type) {
|
||||
|
@ -1424,7 +1424,7 @@ SessionDescription* MediaSessionDescriptionFactory::CreateAnswer(
|
|||
session_options.media_description_options.size());
|
||||
// Iterate through the media description options, matching with existing
|
||||
// media descriptions in |current_description|.
|
||||
int msection_index = 0;
|
||||
size_t msection_index = 0;
|
||||
for (const MediaDescriptionOptions& media_description_options :
|
||||
session_options.media_description_options) {
|
||||
const ContentInfo* offer_content = &offer->contents()[msection_index];
|
||||
|
@ -1435,8 +1435,7 @@ SessionDescription* MediaSessionDescriptionFactory::CreateAnswer(
|
|||
RTC_DCHECK(media_description_options.mid == offer_content->name);
|
||||
const ContentInfo* current_content = nullptr;
|
||||
if (current_description &&
|
||||
msection_index <
|
||||
static_cast<int>(current_description->contents().size())) {
|
||||
msection_index < current_description->contents().size()) {
|
||||
current_content = ¤t_description->contents()[msection_index];
|
||||
}
|
||||
switch (media_description_options.type) {
|
||||
|
@ -1802,8 +1801,8 @@ bool MediaSessionDescriptionFactory::AddAudioContentForOffer(
|
|||
GetAudioCodecsForOffer(media_description_options.direction);
|
||||
|
||||
AudioCodecs filtered_codecs;
|
||||
// Add the codecs from current content if exists.
|
||||
if (current_content) {
|
||||
// Add the codecs from current content if it exists and is not being recycled.
|
||||
if (current_content && !current_content->rejected) {
|
||||
RTC_CHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_AUDIO));
|
||||
const AudioContentDescription* acd =
|
||||
current_content->media_description()->as_audio();
|
||||
|
@ -1877,8 +1876,8 @@ bool MediaSessionDescriptionFactory::AddVideoContentForOffer(
|
|||
&crypto_suites);
|
||||
|
||||
VideoCodecs filtered_codecs;
|
||||
// Add the codecs from current content if exists.
|
||||
if (current_content) {
|
||||
// Add the codecs from current content if it exists and is not being recycled.
|
||||
if (current_content && !current_content->rejected) {
|
||||
RTC_CHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_VIDEO));
|
||||
const VideoContentDescription* vcd =
|
||||
current_content->media_description()->as_video();
|
||||
|
@ -2038,8 +2037,8 @@ bool MediaSessionDescriptionFactory::AddAudioContentForAnswer(
|
|||
GetAudioCodecsForAnswer(offer_rtd, answer_rtd);
|
||||
|
||||
AudioCodecs filtered_codecs;
|
||||
// Add the codecs from current content if exists.
|
||||
if (current_content) {
|
||||
// Add the codecs from current content if it exists and is not being recycled.
|
||||
if (current_content && !current_content->rejected) {
|
||||
RTC_CHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_AUDIO));
|
||||
const AudioContentDescription* acd =
|
||||
current_content->media_description()->as_audio();
|
||||
|
@ -2120,8 +2119,8 @@ bool MediaSessionDescriptionFactory::AddVideoContentForAnswer(
|
|||
}
|
||||
|
||||
VideoCodecs filtered_codecs;
|
||||
// Add the codecs from current content if exists.
|
||||
if (current_content) {
|
||||
// Add the codecs from current content if it exists and is not being recycled.
|
||||
if (current_content && !current_content->rejected) {
|
||||
RTC_CHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_VIDEO));
|
||||
const VideoContentDescription* vcd =
|
||||
current_content->media_description()->as_video();
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "pc/peerconnection.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
@ -330,6 +331,11 @@ bool MediaSectionsInSameOrder(const SessionDescription* existing_desc,
|
|||
}
|
||||
|
||||
for (size_t i = 0; i < existing_desc->contents().size(); ++i) {
|
||||
if (existing_desc->contents()[i].rejected) {
|
||||
// If the media section can be recycled, it's valid for the MID and media
|
||||
// type to change.
|
||||
continue;
|
||||
}
|
||||
if (new_desc->contents()[i].name != existing_desc->contents()[i].name) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1637,34 +1643,69 @@ RTCError PeerConnection::ApplyLocalDescription(
|
|||
}
|
||||
}
|
||||
|
||||
// Take a reference to the old local description since it's used below to
|
||||
// compare against the new local description. When setting the new local
|
||||
// description, grab ownership of the replaced session description in case it
|
||||
// is the same as |old_local_description|, to keep it alive for the duration
|
||||
// of the method.
|
||||
const SessionDescriptionInterface* old_local_description =
|
||||
local_description();
|
||||
std::unique_ptr<SessionDescriptionInterface> replaced_local_description;
|
||||
if (type == SdpType::kAnswer) {
|
||||
replaced_local_description = pending_local_description_
|
||||
? std::move(pending_local_description_)
|
||||
: std::move(current_local_description_);
|
||||
current_local_description_ = std::move(desc);
|
||||
pending_local_description_ = nullptr;
|
||||
current_remote_description_ = std::move(pending_remote_description_);
|
||||
} else {
|
||||
replaced_local_description = std::move(pending_local_description_);
|
||||
pending_local_description_ = std::move(desc);
|
||||
}
|
||||
// The session description to apply now must be accessed by
|
||||
// |local_description()|.
|
||||
RTC_DCHECK(local_description());
|
||||
|
||||
// Transport and Media channels will be created only when offer is set.
|
||||
if (type == SdpType::kOffer) {
|
||||
// TODO(mallinath) - Handle CreateChannel failure, as new local description
|
||||
// is applied. Restore back to old description.
|
||||
RTCError error = CreateChannels(local_description()->description());
|
||||
if (IsUnifiedPlan()) {
|
||||
RTCError error = UpdateTransceiversAndDataChannels(
|
||||
cricket::CS_LOCAL, old_local_description, *local_description());
|
||||
if (!error.ok()) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
for (auto transceiver : transceivers_) {
|
||||
const ContentInfo* content =
|
||||
FindMediaSectionForTransceiver(transceiver, local_description());
|
||||
if (!content) {
|
||||
continue;
|
||||
}
|
||||
const MediaContentDescription* media_desc = content->media_description();
|
||||
if (type == SdpType::kPrAnswer || type == SdpType::kAnswer) {
|
||||
transceiver->internal()->set_current_direction(media_desc->direction());
|
||||
}
|
||||
if (content->rejected && !transceiver->stopped()) {
|
||||
transceiver->Stop();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Transport and Media channels will be created only when offer is set.
|
||||
if (type == SdpType::kOffer) {
|
||||
// TODO(bugs.webrtc.org/4676) - Handle CreateChannel failure, as new local
|
||||
// description is applied. Restore back to old description.
|
||||
RTCError error = CreateChannels(*local_description()->description());
|
||||
if (!error.ok()) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove unused channels if MediaContentDescription is rejected.
|
||||
RemoveUnusedChannels(local_description()->description());
|
||||
// Remove unused channels if MediaContentDescription is rejected.
|
||||
RemoveUnusedChannels(local_description()->description());
|
||||
}
|
||||
|
||||
error = UpdateSessionState(type, cricket::CS_LOCAL);
|
||||
if (!error.ok()) {
|
||||
return error;
|
||||
}
|
||||
|
||||
if (remote_description()) {
|
||||
// Now that we have a local description, we can push down remote candidates.
|
||||
UseCandidatesInSessionDescription(remote_description());
|
||||
|
@ -1682,29 +1723,31 @@ RTCError PeerConnection::ApplyLocalDescription(
|
|||
AllocateSctpSids(role);
|
||||
}
|
||||
|
||||
// Update state and SSRC of local MediaStreams and DataChannels based on the
|
||||
// local session description.
|
||||
const cricket::ContentInfo* audio_content =
|
||||
GetFirstAudioContent(local_description()->description());
|
||||
if (audio_content) {
|
||||
if (audio_content->rejected) {
|
||||
RemoveSenders(cricket::MEDIA_TYPE_AUDIO);
|
||||
} else {
|
||||
const cricket::AudioContentDescription* audio_desc =
|
||||
audio_content->media_description()->as_audio();
|
||||
UpdateLocalSenders(audio_desc->streams(), audio_desc->type());
|
||||
if (!IsUnifiedPlan()) {
|
||||
// Update state and SSRC of local MediaStreams and DataChannels based on the
|
||||
// local session description.
|
||||
const cricket::ContentInfo* audio_content =
|
||||
GetFirstAudioContent(local_description()->description());
|
||||
if (audio_content) {
|
||||
if (audio_content->rejected) {
|
||||
RemoveSenders(cricket::MEDIA_TYPE_AUDIO);
|
||||
} else {
|
||||
const cricket::AudioContentDescription* audio_desc =
|
||||
audio_content->media_description()->as_audio();
|
||||
UpdateLocalSenders(audio_desc->streams(), audio_desc->type());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cricket::ContentInfo* video_content =
|
||||
GetFirstVideoContent(local_description()->description());
|
||||
if (video_content) {
|
||||
if (video_content->rejected) {
|
||||
RemoveSenders(cricket::MEDIA_TYPE_VIDEO);
|
||||
} else {
|
||||
const cricket::VideoContentDescription* video_desc =
|
||||
video_content->media_description()->as_video();
|
||||
UpdateLocalSenders(video_desc->streams(), video_desc->type());
|
||||
const cricket::ContentInfo* video_content =
|
||||
GetFirstVideoContent(local_description()->description());
|
||||
if (video_content) {
|
||||
if (video_content->rejected) {
|
||||
RemoveSenders(cricket::MEDIA_TYPE_VIDEO);
|
||||
} else {
|
||||
const cricket::VideoContentDescription* video_desc =
|
||||
video_content->media_description()->as_video();
|
||||
UpdateLocalSenders(video_desc->streams(), video_desc->type());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1787,13 +1830,14 @@ RTCError PeerConnection::ApplyRemoteDescription(
|
|||
// Update stats here so that we have the most recent stats for tracks and
|
||||
// streams that might be removed by updating the session description.
|
||||
stats_->UpdateStats(kStatsOutputLevelStandard);
|
||||
// Takes the ownership of |desc|. On success, remote_description() is updated
|
||||
// to reflect the description that was passed in.
|
||||
|
||||
// Take a reference to the old remote description since it's used below to
|
||||
// compare against the new remote description. When setting the new remote
|
||||
// description, grab ownership of the replaced session description in case it
|
||||
// is the same as |old_remote_description|, to keep it alive for the duration
|
||||
// of the method.
|
||||
const SessionDescriptionInterface* old_remote_description =
|
||||
remote_description();
|
||||
// Grab ownership of the description being replaced for the remainder of this
|
||||
// method, since it's used below as |old_remote_description|.
|
||||
std::unique_ptr<SessionDescriptionInterface> replaced_remote_description;
|
||||
SdpType type = desc->GetType();
|
||||
if (type == SdpType::kAnswer) {
|
||||
|
@ -1812,17 +1856,25 @@ RTCError PeerConnection::ApplyRemoteDescription(
|
|||
RTC_DCHECK(remote_description());
|
||||
|
||||
// Transport and Media channels will be created only when offer is set.
|
||||
if (type == SdpType::kOffer) {
|
||||
// TODO(mallinath) - Handle CreateChannel failure, as new local description
|
||||
// is applied. Restore back to old description.
|
||||
RTCError error = CreateChannels(remote_description()->description());
|
||||
if (IsUnifiedPlan()) {
|
||||
RTCError error = UpdateTransceiversAndDataChannels(
|
||||
cricket::CS_REMOTE, old_remote_description, *remote_description());
|
||||
if (!error.ok()) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (type == SdpType::kOffer) {
|
||||
// TODO(bugs.webrtc.org/4676) - Handle CreateChannel failure, as new local
|
||||
// description is applied. Restore back to old description.
|
||||
RTCError error = CreateChannels(*remote_description()->description());
|
||||
if (!error.ok()) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove unused channels if MediaContentDescription is rejected.
|
||||
RemoveUnusedChannels(remote_description()->description());
|
||||
// Remove unused channels if MediaContentDescription is rejected.
|
||||
RemoveUnusedChannels(remote_description()->description());
|
||||
}
|
||||
|
||||
// NOTE: Candidates allocation will be initiated only when SetLocalDescription
|
||||
// is called.
|
||||
|
@ -1887,6 +1939,43 @@ RTCError PeerConnection::ApplyRemoteDescription(
|
|||
AllocateSctpSids(role);
|
||||
}
|
||||
|
||||
if (IsUnifiedPlan()) {
|
||||
for (auto transceiver : transceivers_) {
|
||||
const ContentInfo* content =
|
||||
FindMediaSectionForTransceiver(transceiver, remote_description());
|
||||
if (!content) {
|
||||
continue;
|
||||
}
|
||||
const MediaContentDescription* media_desc = content->media_description();
|
||||
RtpTransceiverDirection local_direction =
|
||||
RtpTransceiverDirectionReversed(media_desc->direction());
|
||||
// From the WebRTC specification, steps 2.2.8.5/6 of section 4.4.1.6 "Set
|
||||
// the RTCSessionDescription: If direction is sendrecv or recvonly, and
|
||||
// transceiver's current direction is neither sendrecv nor recvonly,
|
||||
// process the addition of a remote track for the media description.
|
||||
if (RtpTransceiverDirectionHasRecv(local_direction) &&
|
||||
(!transceiver->current_direction() ||
|
||||
!RtpTransceiverDirectionHasRecv(
|
||||
*transceiver->current_direction()))) {
|
||||
// TODO(bugs.webrtc.org/7600): Process the addition of a remote track.
|
||||
}
|
||||
// If direction is sendonly or inactive, and transceiver's current
|
||||
// direction is neither sendonly nor inactive, process the removal of a
|
||||
// remote track for the media description.
|
||||
if (!RtpTransceiverDirectionHasRecv(local_direction) &&
|
||||
(!transceiver->current_direction() ||
|
||||
RtpTransceiverDirectionHasRecv(*transceiver->current_direction()))) {
|
||||
// TODO(bugs.webrtc.org/7600): Process the removal of a remote track.
|
||||
}
|
||||
if (type == SdpType::kPrAnswer || type == SdpType::kAnswer) {
|
||||
transceiver->internal()->set_current_direction(local_direction);
|
||||
}
|
||||
if (content->rejected && !transceiver->stopped()) {
|
||||
transceiver->Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cricket::ContentInfo* audio_content =
|
||||
GetFirstAudioContent(remote_description()->description());
|
||||
const cricket::ContentInfo* video_content =
|
||||
|
@ -1910,64 +1999,256 @@ RTCError PeerConnection::ApplyRemoteDescription(
|
|||
// since only at that point will new streams have all their tracks.
|
||||
rtc::scoped_refptr<StreamCollection> new_streams(StreamCollection::Create());
|
||||
|
||||
// TODO(steveanton): When removing RTP senders/receivers in response to a
|
||||
// rejected media section, there is some cleanup logic that expects the voice/
|
||||
// video channel to still be set. But in this method the voice/video channel
|
||||
// would have been destroyed by the SetRemoteDescription caller above so the
|
||||
// cleanup that relies on them fails to run. The RemoveSenders calls should be
|
||||
// moved to right before the DestroyChannel calls to fix this.
|
||||
if (!IsUnifiedPlan()) {
|
||||
// TODO(steveanton): When removing RTP senders/receivers in response to a
|
||||
// rejected media section, there is some cleanup logic that expects the
|
||||
// voice/ video channel to still be set. But in this method the voice/video
|
||||
// channel would have been destroyed by the SetRemoteDescription caller
|
||||
// above so the cleanup that relies on them fails to run. The RemoveSenders
|
||||
// calls should be moved to right before the DestroyChannel calls to fix
|
||||
// this.
|
||||
|
||||
// Find all audio rtp streams and create corresponding remote AudioTracks
|
||||
// and MediaStreams.
|
||||
if (audio_content) {
|
||||
if (audio_content->rejected) {
|
||||
RemoveSenders(cricket::MEDIA_TYPE_AUDIO);
|
||||
} else {
|
||||
bool default_audio_track_needed =
|
||||
!remote_peer_supports_msid_ &&
|
||||
RtpTransceiverDirectionHasSend(audio_desc->direction());
|
||||
UpdateRemoteSendersList(GetActiveStreams(audio_desc),
|
||||
default_audio_track_needed, audio_desc->type(),
|
||||
new_streams);
|
||||
// Find all audio rtp streams and create corresponding remote AudioTracks
|
||||
// and MediaStreams.
|
||||
if (audio_content) {
|
||||
if (audio_content->rejected) {
|
||||
RemoveSenders(cricket::MEDIA_TYPE_AUDIO);
|
||||
} else {
|
||||
bool default_audio_track_needed =
|
||||
!remote_peer_supports_msid_ &&
|
||||
RtpTransceiverDirectionHasSend(audio_desc->direction());
|
||||
UpdateRemoteSendersList(GetActiveStreams(audio_desc),
|
||||
default_audio_track_needed, audio_desc->type(),
|
||||
new_streams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find all video rtp streams and create corresponding remote VideoTracks
|
||||
// and MediaStreams.
|
||||
if (video_content) {
|
||||
if (video_content->rejected) {
|
||||
RemoveSenders(cricket::MEDIA_TYPE_VIDEO);
|
||||
} else {
|
||||
bool default_video_track_needed =
|
||||
!remote_peer_supports_msid_ &&
|
||||
RtpTransceiverDirectionHasSend(video_desc->direction());
|
||||
UpdateRemoteSendersList(GetActiveStreams(video_desc),
|
||||
default_video_track_needed, video_desc->type(),
|
||||
new_streams);
|
||||
// Find all video rtp streams and create corresponding remote VideoTracks
|
||||
// and MediaStreams.
|
||||
if (video_content) {
|
||||
if (video_content->rejected) {
|
||||
RemoveSenders(cricket::MEDIA_TYPE_VIDEO);
|
||||
} else {
|
||||
bool default_video_track_needed =
|
||||
!remote_peer_supports_msid_ &&
|
||||
RtpTransceiverDirectionHasSend(video_desc->direction());
|
||||
UpdateRemoteSendersList(GetActiveStreams(video_desc),
|
||||
default_video_track_needed, video_desc->type(),
|
||||
new_streams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the DataChannels with the information from the remote peer.
|
||||
if (data_desc) {
|
||||
if (rtc::starts_with(data_desc->protocol().data(),
|
||||
cricket::kMediaProtocolRtpPrefix)) {
|
||||
UpdateRemoteRtpDataChannels(GetActiveStreams(data_desc));
|
||||
// Update the DataChannels with the information from the remote peer.
|
||||
if (data_desc) {
|
||||
if (rtc::starts_with(data_desc->protocol().data(),
|
||||
cricket::kMediaProtocolRtpPrefix)) {
|
||||
UpdateRemoteRtpDataChannels(GetActiveStreams(data_desc));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate new_streams and notify the observer about new MediaStreams.
|
||||
for (size_t i = 0; i < new_streams->count(); ++i) {
|
||||
MediaStreamInterface* new_stream = new_streams->at(i);
|
||||
stats_->AddStream(new_stream);
|
||||
observer_->OnAddStream(
|
||||
rtc::scoped_refptr<MediaStreamInterface>(new_stream));
|
||||
}
|
||||
// Iterate new_streams and notify the observer about new MediaStreams.
|
||||
for (size_t i = 0; i < new_streams->count(); ++i) {
|
||||
MediaStreamInterface* new_stream = new_streams->at(i);
|
||||
stats_->AddStream(new_stream);
|
||||
observer_->OnAddStream(
|
||||
rtc::scoped_refptr<MediaStreamInterface>(new_stream));
|
||||
}
|
||||
|
||||
UpdateEndedRemoteMediaStreams();
|
||||
UpdateEndedRemoteMediaStreams();
|
||||
}
|
||||
|
||||
return RTCError::OK();
|
||||
}
|
||||
|
||||
RTCError PeerConnection::UpdateTransceiversAndDataChannels(
|
||||
cricket::ContentSource source,
|
||||
const SessionDescriptionInterface* old_session,
|
||||
const SessionDescriptionInterface& new_session) {
|
||||
RTC_DCHECK(IsUnifiedPlan());
|
||||
|
||||
auto bundle_group_or_error = GetEarlyBundleGroup(*new_session.description());
|
||||
if (!bundle_group_or_error.ok()) {
|
||||
return bundle_group_or_error.MoveError();
|
||||
}
|
||||
const cricket::ContentGroup* bundle_group = bundle_group_or_error.MoveValue();
|
||||
|
||||
const ContentInfos& old_contents =
|
||||
(old_session ? old_session->description()->contents() : ContentInfos());
|
||||
const ContentInfos& new_contents = new_session.description()->contents();
|
||||
|
||||
for (size_t i = 0; i < new_contents.size(); ++i) {
|
||||
const cricket::ContentInfo& new_content = new_contents[i];
|
||||
const cricket::ContentInfo* old_content =
|
||||
(i < old_contents.size() ? &old_contents[i] : nullptr);
|
||||
cricket::MediaType media_type = new_content.media_description()->type();
|
||||
seen_mids_.insert(new_content.name);
|
||||
if (media_type == cricket::MEDIA_TYPE_AUDIO ||
|
||||
media_type == cricket::MEDIA_TYPE_VIDEO) {
|
||||
auto transceiver_or_error =
|
||||
AssociateTransceiver(source, i, new_content, old_content);
|
||||
if (!transceiver_or_error.ok()) {
|
||||
return transceiver_or_error.MoveError();
|
||||
}
|
||||
auto transceiver = transceiver_or_error.MoveValue();
|
||||
if (source == cricket::CS_LOCAL && transceiver->stopped()) {
|
||||
continue;
|
||||
}
|
||||
RTCError error =
|
||||
UpdateTransceiverChannel(transceiver, new_content, bundle_group);
|
||||
if (!error.ok()) {
|
||||
return error;
|
||||
}
|
||||
} else if (media_type == cricket::MEDIA_TYPE_DATA) {
|
||||
// TODO(bugs.webrtc.org/7600): Add support for data channels with Unified
|
||||
// Plan.
|
||||
} else {
|
||||
LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR,
|
||||
"Unknown section type.");
|
||||
}
|
||||
}
|
||||
|
||||
return RTCError::OK();
|
||||
}
|
||||
|
||||
RTCError PeerConnection::UpdateTransceiverChannel(
|
||||
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
|
||||
transceiver,
|
||||
const cricket::ContentInfo& content,
|
||||
const cricket::ContentGroup* bundle_group) {
|
||||
RTC_DCHECK(IsUnifiedPlan());
|
||||
RTC_DCHECK(transceiver);
|
||||
cricket::BaseChannel* channel = transceiver->internal()->channel();
|
||||
if (content.rejected) {
|
||||
if (channel) {
|
||||
transceiver->internal()->SetChannel(nullptr);
|
||||
DestroyBaseChannel(channel);
|
||||
}
|
||||
} else {
|
||||
if (!channel) {
|
||||
if (transceiver->internal()->media_type() == cricket::MEDIA_TYPE_AUDIO) {
|
||||
channel = CreateVoiceChannel(
|
||||
content.name,
|
||||
GetTransportNameForMediaSection(content.name, bundle_group));
|
||||
} else {
|
||||
RTC_DCHECK_EQ(cricket::MEDIA_TYPE_VIDEO,
|
||||
transceiver->internal()->media_type());
|
||||
channel = CreateVideoChannel(
|
||||
content.name,
|
||||
GetTransportNameForMediaSection(content.name, bundle_group));
|
||||
}
|
||||
if (!channel) {
|
||||
LOG_AND_RETURN_ERROR(
|
||||
RTCErrorType::INTERNAL_ERROR,
|
||||
"Failed to create channel for mid=" + content.name);
|
||||
}
|
||||
transceiver->internal()->SetChannel(channel);
|
||||
}
|
||||
}
|
||||
return RTCError::OK();
|
||||
}
|
||||
|
||||
RTCErrorOr<rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>>
|
||||
PeerConnection::AssociateTransceiver(cricket::ContentSource source,
|
||||
size_t mline_index,
|
||||
const ContentInfo& content,
|
||||
const ContentInfo* old_content) {
|
||||
RTC_DCHECK(IsUnifiedPlan());
|
||||
// If the m= section is being recycled (rejected in previous remote
|
||||
// description, not rejected in current description), dissociate the currently
|
||||
// associated RtpTransceiver by setting its mid property to null, and discard
|
||||
// the mapping between the transceiver and its m= section index.
|
||||
if (old_content && old_content->rejected && !content.rejected) {
|
||||
auto old_transceiver = GetAssociatedTransceiver(old_content->name);
|
||||
if (old_transceiver) {
|
||||
old_transceiver->internal()->set_mid(rtc::nullopt);
|
||||
old_transceiver->internal()->set_mline_index(rtc::nullopt);
|
||||
}
|
||||
}
|
||||
const MediaContentDescription* media_desc = content.media_description();
|
||||
auto transceiver = GetAssociatedTransceiver(content.name);
|
||||
if (source == cricket::CS_LOCAL) {
|
||||
// Find the RtpTransceiver that corresponds to this m= section, using the
|
||||
// mapping between transceivers and m= section indices established when
|
||||
// creating the offer.
|
||||
if (!transceiver) {
|
||||
transceiver = GetTransceiverByMLineIndex(mline_index);
|
||||
}
|
||||
if (!transceiver) {
|
||||
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
|
||||
"Unknown transceiver");
|
||||
}
|
||||
} else {
|
||||
RTC_DCHECK_EQ(source, cricket::CS_REMOTE);
|
||||
// If the m= section is sendrecv or recvonly, and there are RtpTransceivers
|
||||
// of the same type...
|
||||
if (!transceiver &&
|
||||
RtpTransceiverDirectionHasRecv(media_desc->direction())) {
|
||||
transceiver = FindAvailableTransceiverToReceive(media_desc->type());
|
||||
}
|
||||
// If no RtpTransceiver was found in the previous step, create one with a
|
||||
// recvonly direction.
|
||||
if (!transceiver) {
|
||||
transceiver = CreateTransceiver(media_desc->type());
|
||||
transceiver->internal()->set_direction(
|
||||
RtpTransceiverDirection::kRecvOnly);
|
||||
}
|
||||
}
|
||||
RTC_DCHECK(transceiver);
|
||||
if (transceiver->internal()->media_type() != media_desc->type()) {
|
||||
LOG_AND_RETURN_ERROR(
|
||||
RTCErrorType::INVALID_PARAMETER,
|
||||
"Transceiver type does not match media description type.");
|
||||
}
|
||||
// Associate the found or created RtpTransceiver with the m= section by
|
||||
// setting the value of the RtpTransceiver's mid property to the MID of the m=
|
||||
// section, and establish a mapping between the transceiver and the index of
|
||||
// the m= section.
|
||||
transceiver->internal()->set_mid(content.name);
|
||||
transceiver->internal()->set_mline_index(mline_index);
|
||||
return std::move(transceiver);
|
||||
}
|
||||
|
||||
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
|
||||
PeerConnection::GetAssociatedTransceiver(const std::string& mid) const {
|
||||
RTC_DCHECK(IsUnifiedPlan());
|
||||
for (auto transceiver : transceivers_) {
|
||||
if (transceiver->mid() == mid) {
|
||||
return transceiver;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
|
||||
PeerConnection::GetTransceiverByMLineIndex(size_t mline_index) const {
|
||||
RTC_DCHECK(IsUnifiedPlan());
|
||||
for (auto transceiver : transceivers_) {
|
||||
if (transceiver->internal()->mline_index() == mline_index) {
|
||||
return transceiver;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
|
||||
PeerConnection::FindAvailableTransceiverToReceive(
|
||||
cricket::MediaType media_type) const {
|
||||
RTC_DCHECK(IsUnifiedPlan());
|
||||
// From JSEP section 5.10 (Applying a Remote Description):
|
||||
// If the m= section is sendrecv or recvonly, and there are RtpTransceivers of
|
||||
// the same type that were added to the PeerConnection by addTrack and are not
|
||||
// associated with any m= section and are not stopped, find the first such
|
||||
// RtpTransceiver.
|
||||
for (auto transceiver : transceivers_) {
|
||||
if (transceiver->internal()->media_type() == media_type &&
|
||||
transceiver->internal()->created_by_addtrack() && !transceiver->mid() &&
|
||||
!transceiver->stopped()) {
|
||||
return transceiver;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const cricket::ContentInfo* PeerConnection::FindMediaSectionForTransceiver(
|
||||
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
|
||||
transceiver,
|
||||
|
@ -2655,10 +2936,30 @@ void PeerConnection::PostCreateSessionDescriptionFailure(
|
|||
}
|
||||
|
||||
void PeerConnection::GetOptionsForOffer(
|
||||
const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options,
|
||||
const PeerConnectionInterface::RTCOfferAnswerOptions& offer_answer_options,
|
||||
cricket::MediaSessionOptions* session_options) {
|
||||
ExtractSharedMediaSessionOptions(rtc_options, session_options);
|
||||
ExtractSharedMediaSessionOptions(offer_answer_options, session_options);
|
||||
|
||||
if (IsUnifiedPlan()) {
|
||||
GetOptionsForUnifiedPlanOffer(offer_answer_options, session_options);
|
||||
} else {
|
||||
GetOptionsForPlanBOffer(offer_answer_options, session_options);
|
||||
}
|
||||
|
||||
// Apply ICE restart flag and renomination flag.
|
||||
for (auto& options : session_options->media_description_options) {
|
||||
options.transport_options.ice_restart = offer_answer_options.ice_restart;
|
||||
options.transport_options.enable_ice_renomination =
|
||||
configuration_.enable_ice_renomination;
|
||||
}
|
||||
|
||||
session_options->rtcp_cname = rtcp_cname_;
|
||||
session_options->crypto_options = factory_->options().crypto_options;
|
||||
}
|
||||
|
||||
void PeerConnection::GetOptionsForPlanBOffer(
|
||||
const PeerConnectionInterface::RTCOfferAnswerOptions& offer_answer_options,
|
||||
cricket::MediaSessionOptions* session_options) {
|
||||
// Figure out transceiver directional preferences.
|
||||
bool send_audio = HasRtpSender(cricket::MEDIA_TYPE_AUDIO);
|
||||
bool send_video = HasRtpSender(cricket::MEDIA_TYPE_VIDEO);
|
||||
|
@ -2673,15 +2974,19 @@ void PeerConnection::GetOptionsForOffer(
|
|||
bool offer_new_data_description = HasDataChannels();
|
||||
|
||||
// The "offer_to_receive_X" options allow those defaults to be overridden.
|
||||
if (rtc_options.offer_to_receive_audio != RTCOfferAnswerOptions::kUndefined) {
|
||||
recv_audio = (rtc_options.offer_to_receive_audio > 0);
|
||||
if (offer_answer_options.offer_to_receive_audio !=
|
||||
RTCOfferAnswerOptions::kUndefined) {
|
||||
recv_audio = (offer_answer_options.offer_to_receive_audio > 0);
|
||||
offer_new_audio_description =
|
||||
offer_new_audio_description || (rtc_options.offer_to_receive_audio > 0);
|
||||
offer_new_audio_description ||
|
||||
(offer_answer_options.offer_to_receive_audio > 0);
|
||||
}
|
||||
if (rtc_options.offer_to_receive_video != RTCOfferAnswerOptions::kUndefined) {
|
||||
recv_video = (rtc_options.offer_to_receive_video > 0);
|
||||
if (offer_answer_options.offer_to_receive_video !=
|
||||
RTCOfferAnswerOptions::kUndefined) {
|
||||
recv_video = (offer_answer_options.offer_to_receive_video > 0);
|
||||
offer_new_video_description =
|
||||
offer_new_video_description || (rtc_options.offer_to_receive_video > 0);
|
||||
offer_new_video_description ||
|
||||
(offer_answer_options.offer_to_receive_video > 0);
|
||||
}
|
||||
|
||||
rtc::Optional<size_t> audio_index;
|
||||
|
@ -2733,13 +3038,6 @@ void PeerConnection::GetOptionsForOffer(
|
|||
!data_index ? nullptr
|
||||
: &session_options->media_description_options[*data_index];
|
||||
|
||||
// Apply ICE restart flag and renomination flag.
|
||||
for (auto& options : session_options->media_description_options) {
|
||||
options.transport_options.ice_restart = rtc_options.ice_restart;
|
||||
options.transport_options.enable_ice_renomination =
|
||||
configuration_.enable_ice_renomination;
|
||||
}
|
||||
|
||||
AddRtpSenderOptions(GetSendersInternal(), audio_media_description_options,
|
||||
video_media_description_options);
|
||||
AddRtpDataChannelOptions(rtp_data_channels_, data_media_description_options);
|
||||
|
@ -2752,16 +3050,162 @@ void PeerConnection::GetOptionsForOffer(
|
|||
if (!rtp_data_channels_.empty() || data_channel_type() != cricket::DCT_RTP) {
|
||||
session_options->data_channel_type = data_channel_type();
|
||||
}
|
||||
}
|
||||
|
||||
// Find a new MID that is not already in |used_mids|, then add it to |used_mids|
|
||||
// and return a reference to it.
|
||||
// Generated MIDs should be no more than 3 bytes long to take up less space in
|
||||
// the RTP packet.
|
||||
static const std::string& AllocateMid(std::set<std::string>* used_mids) {
|
||||
RTC_DCHECK(used_mids);
|
||||
// We're boring: just generate MIDs 0, 1, 2, ...
|
||||
size_t i = 0;
|
||||
std::set<std::string>::iterator it;
|
||||
bool inserted;
|
||||
do {
|
||||
std::string mid = rtc::ToString(i++);
|
||||
auto insert_result = used_mids->insert(mid);
|
||||
it = insert_result.first;
|
||||
inserted = insert_result.second;
|
||||
} while (!inserted);
|
||||
return *it;
|
||||
}
|
||||
|
||||
static cricket::MediaDescriptionOptions
|
||||
GetMediaDescriptionOptionsForTransceiver(
|
||||
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
|
||||
transceiver,
|
||||
const std::string& mid) {
|
||||
cricket::MediaDescriptionOptions media_description_options(
|
||||
transceiver->internal()->media_type(), mid, transceiver->direction(),
|
||||
transceiver->stopped());
|
||||
cricket::SenderOptions sender_options;
|
||||
sender_options.track_id = transceiver->sender()->id();
|
||||
sender_options.stream_ids = transceiver->sender()->stream_ids();
|
||||
// TODO(bugs.webrtc.org/7600): Set num_sim_layers to the number of encodings
|
||||
// set in the RTP parameters when the transceiver was added.
|
||||
sender_options.num_sim_layers = 1;
|
||||
media_description_options.sender_options.push_back(sender_options);
|
||||
return media_description_options;
|
||||
}
|
||||
|
||||
void PeerConnection::GetOptionsForUnifiedPlanOffer(
|
||||
const RTCOfferAnswerOptions& offer_answer_options,
|
||||
cricket::MediaSessionOptions* session_options) {
|
||||
// Rules for generating an offer are dictated by JSEP sections 5.2.1 (Initial
|
||||
// Offers) and 5.2.2 (Subsequent Offers).
|
||||
RTC_DCHECK_EQ(session_options->media_description_options.size(), 0);
|
||||
const ContentInfos& local_contents =
|
||||
(local_description() ? local_description()->description()->contents()
|
||||
: ContentInfos());
|
||||
const ContentInfos& remote_contents =
|
||||
(remote_description() ? remote_description()->description()->contents()
|
||||
: ContentInfos());
|
||||
// The mline indices that can be recycled. New transceivers should reuse these
|
||||
// slots first.
|
||||
std::queue<size_t> recycleable_mline_indices;
|
||||
// Track the MIDs used in previous offer/answer exchanges and the current
|
||||
// offer so that new, unique MIDs are generated.
|
||||
std::set<std::string> used_mids = seen_mids_;
|
||||
// First, go through each media section that exists in either the local or
|
||||
// remote description and generate a media section in this offer for the
|
||||
// associated transceiver. If a media section can be recycled, generate a
|
||||
// default, rejected media section here that can be later overwritten.
|
||||
for (size_t i = 0;
|
||||
i < std::max(local_contents.size(), remote_contents.size()); ++i) {
|
||||
// Either |local_content| or |remote_content| is non-null.
|
||||
const ContentInfo* local_content =
|
||||
(i < local_contents.size() ? &local_contents[i] : nullptr);
|
||||
const ContentInfo* remote_content =
|
||||
(i < remote_contents.size() ? &remote_contents[i] : nullptr);
|
||||
bool had_been_rejected = (local_content && local_content->rejected) ||
|
||||
(remote_content && remote_content->rejected);
|
||||
const std::string& mid =
|
||||
(local_content ? local_content->name : remote_content->name);
|
||||
cricket::MediaType media_type =
|
||||
(local_content ? local_content->media_description()->type()
|
||||
: remote_content->media_description()->type());
|
||||
if (media_type == cricket::MEDIA_TYPE_AUDIO ||
|
||||
media_type == cricket::MEDIA_TYPE_VIDEO) {
|
||||
auto transceiver = GetAssociatedTransceiver(mid);
|
||||
RTC_CHECK(transceiver);
|
||||
// A media section is considered eligible for recycling if it is marked as
|
||||
// rejected in either the local or remote description.
|
||||
if (had_been_rejected) {
|
||||
session_options->media_description_options.push_back(
|
||||
cricket::MediaDescriptionOptions(
|
||||
transceiver->internal()->media_type(), mid,
|
||||
RtpTransceiverDirection::kInactive,
|
||||
/*stopped=*/true));
|
||||
recycleable_mline_indices.push(i);
|
||||
} else {
|
||||
session_options->media_description_options.push_back(
|
||||
GetMediaDescriptionOptionsForTransceiver(transceiver, mid));
|
||||
// CreateOffer shouldn't really cause any state changes in
|
||||
// PeerConnection, but we need a way to match new transceivers to new
|
||||
// media sections in SetLocalDescription and JSEP specifies this is done
|
||||
// by recording the index of the media section generated for the
|
||||
// transceiver in the offer.
|
||||
transceiver->internal()->set_mline_index(i);
|
||||
}
|
||||
} else {
|
||||
RTC_CHECK_EQ(cricket::MEDIA_TYPE_DATA, media_type);
|
||||
// TODO(bugs.webrtc.org/7600): Add support for data channels with Unified
|
||||
// Plan.
|
||||
}
|
||||
}
|
||||
// Next, look for transceivers that are newly added (that is, are not stopped
|
||||
// and not associated). Reuse media sections marked as recyclable first,
|
||||
// otherwise append to the end of the offer. New media sections should be
|
||||
// added in the order they were added to the PeerConnection.
|
||||
for (auto transceiver : transceivers_) {
|
||||
if (transceiver->mid() || transceiver->stopped()) {
|
||||
continue;
|
||||
}
|
||||
size_t mline_index;
|
||||
if (!recycleable_mline_indices.empty()) {
|
||||
mline_index = recycleable_mline_indices.front();
|
||||
recycleable_mline_indices.pop();
|
||||
session_options->media_description_options[mline_index] =
|
||||
GetMediaDescriptionOptionsForTransceiver(transceiver,
|
||||
AllocateMid(&used_mids));
|
||||
} else {
|
||||
mline_index = session_options->media_description_options.size();
|
||||
session_options->media_description_options.push_back(
|
||||
GetMediaDescriptionOptionsForTransceiver(transceiver,
|
||||
AllocateMid(&used_mids)));
|
||||
}
|
||||
// See comment above for why CreateOffer changes the transceiver's state.
|
||||
transceiver->internal()->set_mline_index(mline_index);
|
||||
}
|
||||
// TODO(bugs.webrtc.org/7600): Add support for data channels with Unified
|
||||
// Plan.
|
||||
}
|
||||
|
||||
void PeerConnection::GetOptionsForAnswer(
|
||||
const RTCOfferAnswerOptions& offer_answer_options,
|
||||
cricket::MediaSessionOptions* session_options) {
|
||||
ExtractSharedMediaSessionOptions(offer_answer_options, session_options);
|
||||
|
||||
if (IsUnifiedPlan()) {
|
||||
GetOptionsForUnifiedPlanAnswer(offer_answer_options, session_options);
|
||||
} else {
|
||||
GetOptionsForPlanBAnswer(offer_answer_options, session_options);
|
||||
}
|
||||
|
||||
// Apply ICE renomination flag.
|
||||
for (auto& options : session_options->media_description_options) {
|
||||
options.transport_options.enable_ice_renomination =
|
||||
configuration_.enable_ice_renomination;
|
||||
}
|
||||
|
||||
session_options->rtcp_cname = rtcp_cname_;
|
||||
session_options->crypto_options = factory_->options().crypto_options;
|
||||
}
|
||||
|
||||
void PeerConnection::GetOptionsForAnswer(
|
||||
const RTCOfferAnswerOptions& rtc_options,
|
||||
void PeerConnection::GetOptionsForPlanBAnswer(
|
||||
const PeerConnectionInterface::RTCOfferAnswerOptions& offer_answer_options,
|
||||
cricket::MediaSessionOptions* session_options) {
|
||||
ExtractSharedMediaSessionOptions(rtc_options, session_options);
|
||||
|
||||
// Figure out transceiver directional preferences.
|
||||
bool send_audio = HasRtpSender(cricket::MEDIA_TYPE_AUDIO);
|
||||
bool send_video = HasRtpSender(cricket::MEDIA_TYPE_VIDEO);
|
||||
|
@ -2772,11 +3216,13 @@ void PeerConnection::GetOptionsForAnswer(
|
|||
bool recv_video = true;
|
||||
|
||||
// The "offer_to_receive_X" options allow those defaults to be overridden.
|
||||
if (rtc_options.offer_to_receive_audio != RTCOfferAnswerOptions::kUndefined) {
|
||||
recv_audio = (rtc_options.offer_to_receive_audio > 0);
|
||||
if (offer_answer_options.offer_to_receive_audio !=
|
||||
RTCOfferAnswerOptions::kUndefined) {
|
||||
recv_audio = (offer_answer_options.offer_to_receive_audio > 0);
|
||||
}
|
||||
if (rtc_options.offer_to_receive_video != RTCOfferAnswerOptions::kUndefined) {
|
||||
recv_video = (rtc_options.offer_to_receive_video > 0);
|
||||
if (offer_answer_options.offer_to_receive_video !=
|
||||
RTCOfferAnswerOptions::kUndefined) {
|
||||
recv_video = (offer_answer_options.offer_to_receive_video > 0);
|
||||
}
|
||||
|
||||
rtc::Optional<size_t> audio_index;
|
||||
|
@ -2805,12 +3251,6 @@ void PeerConnection::GetOptionsForAnswer(
|
|||
!data_index ? nullptr
|
||||
: &session_options->media_description_options[*data_index];
|
||||
|
||||
// Apply ICE renomination flag.
|
||||
for (auto& options : session_options->media_description_options) {
|
||||
options.transport_options.enable_ice_renomination =
|
||||
configuration_.enable_ice_renomination;
|
||||
}
|
||||
|
||||
AddRtpSenderOptions(GetSendersInternal(), audio_media_description_options,
|
||||
video_media_description_options);
|
||||
AddRtpDataChannelOptions(rtp_data_channels_, data_media_description_options);
|
||||
|
@ -2822,9 +3262,30 @@ void PeerConnection::GetOptionsForAnswer(
|
|||
if (!rtp_data_channels_.empty() || data_channel_type() != cricket::DCT_RTP) {
|
||||
session_options->data_channel_type = data_channel_type();
|
||||
}
|
||||
}
|
||||
|
||||
session_options->rtcp_cname = rtcp_cname_;
|
||||
session_options->crypto_options = factory_->options().crypto_options;
|
||||
void PeerConnection::GetOptionsForUnifiedPlanAnswer(
|
||||
const PeerConnectionInterface::RTCOfferAnswerOptions& offer_answer_options,
|
||||
cricket::MediaSessionOptions* session_options) {
|
||||
// Rules for generating an answer are dictated by JSEP sections 5.3.1 (Initial
|
||||
// Answers) and 5.3.2 (Subsequent Answers).
|
||||
RTC_DCHECK(remote_description());
|
||||
RTC_DCHECK(remote_description()->GetType() == SdpType::kOffer);
|
||||
for (const ContentInfo& content :
|
||||
remote_description()->description()->contents()) {
|
||||
cricket::MediaType media_type = content.media_description()->type();
|
||||
if (media_type == cricket::MEDIA_TYPE_AUDIO ||
|
||||
media_type == cricket::MEDIA_TYPE_VIDEO) {
|
||||
auto transceiver = GetAssociatedTransceiver(content.name);
|
||||
RTC_CHECK(transceiver);
|
||||
session_options->media_description_options.push_back(
|
||||
GetMediaDescriptionOptionsForTransceiver(transceiver, content.name));
|
||||
} else {
|
||||
RTC_CHECK_EQ(cricket::MEDIA_TYPE_DATA, media_type);
|
||||
// TODO(bugs.webrtc.org/7600): Add support for data channels with Unified
|
||||
// Plan.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PeerConnection::GenerateMediaDescriptionOptions(
|
||||
|
@ -3540,11 +4001,11 @@ void PeerConnection::StopRtcEventLog_w() {
|
|||
|
||||
cricket::BaseChannel* PeerConnection::GetChannel(
|
||||
const std::string& content_name) {
|
||||
if (voice_channel() && voice_channel()->content_name() == content_name) {
|
||||
return voice_channel();
|
||||
}
|
||||
if (video_channel() && video_channel()->content_name() == content_name) {
|
||||
return video_channel();
|
||||
for (auto transceiver : transceivers_) {
|
||||
cricket::BaseChannel* channel = transceiver->internal()->channel();
|
||||
if (channel && channel->content_name() == content_name) {
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
if (rtp_data_channel() &&
|
||||
rtp_data_channel()->content_name() == content_name) {
|
||||
|
@ -3779,9 +4240,9 @@ RTCError PeerConnection::PushdownTransportDescription(
|
|||
tinfo.content_name, tinfo.description, type, &error);
|
||||
}
|
||||
if (!success) {
|
||||
LOG_AND_RETURN_ERROR(
|
||||
RTCErrorType::INVALID_PARAMETER,
|
||||
"Failed to push down transport description: " + error);
|
||||
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
|
||||
"Failed to push down transport description for " +
|
||||
tinfo.content_name + ": " + error);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4308,22 +4769,30 @@ std::string PeerConnection::GetTransportNameForMediaSection(
|
|||
return *first_content_name;
|
||||
}
|
||||
|
||||
RTCError PeerConnection::CreateChannels(const SessionDescription* desc) {
|
||||
RTC_DCHECK(desc);
|
||||
|
||||
RTCErrorOr<const cricket::ContentGroup*> PeerConnection::GetEarlyBundleGroup(
|
||||
const SessionDescription& desc) const {
|
||||
const cricket::ContentGroup* bundle_group = nullptr;
|
||||
if (configuration_.bundle_policy ==
|
||||
PeerConnectionInterface::kBundlePolicyMaxBundle) {
|
||||
bundle_group = desc->GetGroupByName(cricket::GROUP_TYPE_BUNDLE);
|
||||
bundle_group = desc.GetGroupByName(cricket::GROUP_TYPE_BUNDLE);
|
||||
if (!bundle_group) {
|
||||
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
|
||||
"max-bundle configured but session description "
|
||||
"has no BUNDLE group");
|
||||
}
|
||||
}
|
||||
return std::move(bundle_group);
|
||||
}
|
||||
|
||||
RTCError PeerConnection::CreateChannels(const SessionDescription& desc) {
|
||||
auto bundle_group_or_error = GetEarlyBundleGroup(desc);
|
||||
if (!bundle_group_or_error.ok()) {
|
||||
return bundle_group_or_error.MoveError();
|
||||
}
|
||||
const cricket::ContentGroup* bundle_group = bundle_group_or_error.MoveValue();
|
||||
|
||||
// Creating the media channels and transport proxies.
|
||||
const cricket::ContentInfo* voice = cricket::GetFirstAudioContent(desc);
|
||||
const cricket::ContentInfo* voice = cricket::GetFirstAudioContent(&desc);
|
||||
if (voice && !voice->rejected &&
|
||||
!GetAudioTransceiver()->internal()->channel()) {
|
||||
cricket::VoiceChannel* voice_channel = CreateVoiceChannel(
|
||||
|
@ -4336,7 +4805,7 @@ RTCError PeerConnection::CreateChannels(const SessionDescription* desc) {
|
|||
GetAudioTransceiver()->internal()->SetChannel(voice_channel);
|
||||
}
|
||||
|
||||
const cricket::ContentInfo* video = cricket::GetFirstVideoContent(desc);
|
||||
const cricket::ContentInfo* video = cricket::GetFirstVideoContent(&desc);
|
||||
if (video && !video->rejected &&
|
||||
!GetVideoTransceiver()->internal()->channel()) {
|
||||
cricket::VideoChannel* video_channel = CreateVideoChannel(
|
||||
|
@ -4349,7 +4818,7 @@ RTCError PeerConnection::CreateChannels(const SessionDescription* desc) {
|
|||
GetVideoTransceiver()->internal()->SetChannel(video_channel);
|
||||
}
|
||||
|
||||
const cricket::ContentInfo* data = cricket::GetFirstDataContent(desc);
|
||||
const cricket::ContentInfo* data = cricket::GetFirstDataContent(&desc);
|
||||
if (data_channel_type_ != cricket::DCT_NONE && data && !data->rejected &&
|
||||
!rtp_data_channel_ && !sctp_transport_) {
|
||||
if (!CreateDataChannel(data->name, GetTransportNameForMediaSection(
|
||||
|
|
|
@ -412,6 +412,43 @@ class PeerConnection : public PeerConnectionInterface,
|
|||
RTCError ApplyRemoteDescription(
|
||||
std::unique_ptr<SessionDescriptionInterface> desc);
|
||||
|
||||
// Updates the local RtpTransceivers according to the JSEP rules. Called as
|
||||
// part of setting the local/remote description.
|
||||
RTCError UpdateTransceiversAndDataChannels(
|
||||
cricket::ContentSource source,
|
||||
const SessionDescriptionInterface* old_session,
|
||||
const SessionDescriptionInterface& new_session);
|
||||
|
||||
// Either creates or destroys the transceiver's BaseChannel according to the
|
||||
// given media section.
|
||||
RTCError UpdateTransceiverChannel(
|
||||
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
|
||||
transceiver,
|
||||
const cricket::ContentInfo& content,
|
||||
const cricket::ContentGroup* bundle_group);
|
||||
|
||||
// Associate the given transceiver according to the JSEP rules.
|
||||
RTCErrorOr<
|
||||
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>>
|
||||
AssociateTransceiver(cricket::ContentSource source,
|
||||
size_t mline_index,
|
||||
const cricket::ContentInfo& content,
|
||||
const cricket::ContentInfo* old_content);
|
||||
|
||||
// Returns the RtpTransceiver, if found, that is associated to the given MID.
|
||||
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
|
||||
GetAssociatedTransceiver(const std::string& mid) const;
|
||||
|
||||
// Returns the RtpTransceiver, if found, that was assigned to the given mline
|
||||
// index in CreateOffer.
|
||||
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
|
||||
GetTransceiverByMLineIndex(size_t mline_index) const;
|
||||
|
||||
// Returns an RtpTransciever, if available, that can be used to receive the
|
||||
// given media type according to JSEP rules.
|
||||
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
|
||||
FindAvailableTransceiverToReceive(cricket::MediaType media_type) const;
|
||||
|
||||
// Returns the media section in the given session description that is
|
||||
// associated with the RtpTransceiver. Returns null if none found or this
|
||||
// RtpTransceiver is not associated. Logic varies depending on the
|
||||
|
@ -427,14 +464,30 @@ class PeerConnection : public PeerConnectionInterface,
|
|||
|
||||
// Returns a MediaSessionOptions struct with options decided by |options|,
|
||||
// the local MediaStreams and DataChannels.
|
||||
void GetOptionsForOffer(
|
||||
const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options,
|
||||
void GetOptionsForOffer(const PeerConnectionInterface::RTCOfferAnswerOptions&
|
||||
offer_answer_options,
|
||||
cricket::MediaSessionOptions* session_options);
|
||||
void GetOptionsForPlanBOffer(
|
||||
const PeerConnectionInterface::RTCOfferAnswerOptions&
|
||||
offer_answer_options,
|
||||
cricket::MediaSessionOptions* session_options);
|
||||
void GetOptionsForUnifiedPlanOffer(
|
||||
const PeerConnectionInterface::RTCOfferAnswerOptions&
|
||||
offer_answer_options,
|
||||
cricket::MediaSessionOptions* session_options);
|
||||
|
||||
// Returns a MediaSessionOptions struct with options decided by
|
||||
// |constraints|, the local MediaStreams and DataChannels.
|
||||
void GetOptionsForAnswer(const RTCOfferAnswerOptions& options,
|
||||
void GetOptionsForAnswer(const RTCOfferAnswerOptions& offer_answer_options,
|
||||
cricket::MediaSessionOptions* session_options);
|
||||
void GetOptionsForPlanBAnswer(
|
||||
const PeerConnectionInterface::RTCOfferAnswerOptions&
|
||||
offer_answer_options,
|
||||
cricket::MediaSessionOptions* session_options);
|
||||
void GetOptionsForUnifiedPlanAnswer(
|
||||
const PeerConnectionInterface::RTCOfferAnswerOptions&
|
||||
offer_answer_options,
|
||||
cricket::MediaSessionOptions* session_options);
|
||||
|
||||
// Generates MediaDescriptionOptions for the |session_opts| based on existing
|
||||
// local description or remote description.
|
||||
|
@ -706,7 +759,15 @@ class PeerConnection : public PeerConnectionInterface,
|
|||
// Allocates media channels based on the |desc|. If |desc| doesn't have
|
||||
// the BUNDLE option, this method will disable BUNDLE in PortAllocator.
|
||||
// This method will also delete any existing media channels before creating.
|
||||
RTCError CreateChannels(const cricket::SessionDescription* desc);
|
||||
RTCError CreateChannels(const cricket::SessionDescription& desc);
|
||||
|
||||
// If the BUNDLE policy is max-bundle, then we know for sure that all
|
||||
// transports will be bundled from the start. This method returns the BUNDLE
|
||||
// group if that's the case, or null if BUNDLE will be negotiated later. An
|
||||
// error is returned if max-bundle is specified but the session description
|
||||
// does not have a BUNDLE group.
|
||||
RTCErrorOr<const cricket::ContentGroup*> GetEarlyBundleGroup(
|
||||
const cricket::SessionDescription& desc) const;
|
||||
|
||||
// Helper methods to create media channels.
|
||||
cricket::VoiceChannel* CreateVoiceChannel(const std::string& mid,
|
||||
|
@ -859,6 +920,9 @@ class PeerConnection : public PeerConnectionInterface,
|
|||
std::vector<
|
||||
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>>
|
||||
transceivers_;
|
||||
// MIDs that have been seen either by SetLocalDescription or
|
||||
// SetRemoteDescription over the life of the PeerConnection.
|
||||
std::set<std::string> seen_mids_;
|
||||
|
||||
SessionError session_error_ = SessionError::kNone;
|
||||
std::string session_error_desc_;
|
||||
|
|
734
pc/peerconnection_jsep_unittest.cc
Normal file
734
pc/peerconnection_jsep_unittest.cc
Normal file
|
@ -0,0 +1,734 @@
|
|||
/*
|
||||
* 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 "api/audio_codecs/builtin_audio_decoder_factory.h"
|
||||
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
|
||||
#include "pc/mediasession.h"
|
||||
#include "pc/peerconnectionwrapper.h"
|
||||
#include "pc/sdputils.h"
|
||||
#ifdef WEBRTC_ANDROID
|
||||
#include "pc/test/androidtestinitializer.h"
|
||||
#endif
|
||||
#include "pc/test/fakeaudiocapturemodule.h"
|
||||
#include "rtc_base/gunit.h"
|
||||
#include "rtc_base/ptr_util.h"
|
||||
#include "rtc_base/virtualsocketserver.h"
|
||||
#include "test/gmock.h"
|
||||
|
||||
// This file contains tests that ensure the PeerConnection's implementation of
|
||||
// CreateOffer/CreateAnswer/SetLocalDescription/SetRemoteDescription conform
|
||||
// to the JavaScript Session Establishment Protocol (JSEP).
|
||||
// For now these semantics are only available when configuring the
|
||||
// PeerConnection with Unified Plan, but eventually that will be the default.
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
using cricket::MediaContentDescription;
|
||||
using RTCConfiguration = PeerConnectionInterface::RTCConfiguration;
|
||||
using ::testing::Values;
|
||||
using ::testing::Combine;
|
||||
using ::testing::ElementsAre;
|
||||
|
||||
class PeerConnectionJsepTest : public ::testing::Test {
|
||||
protected:
|
||||
typedef std::unique_ptr<PeerConnectionWrapper> WrapperPtr;
|
||||
|
||||
PeerConnectionJsepTest()
|
||||
: vss_(new rtc::VirtualSocketServer()), main_(vss_.get()) {
|
||||
#ifdef WEBRTC_ANDROID
|
||||
InitializeAndroidObjects();
|
||||
#endif
|
||||
pc_factory_ = CreatePeerConnectionFactory(
|
||||
rtc::Thread::Current(), rtc::Thread::Current(), rtc::Thread::Current(),
|
||||
FakeAudioCaptureModule::Create(), CreateBuiltinAudioEncoderFactory(),
|
||||
CreateBuiltinAudioDecoderFactory(), nullptr, nullptr);
|
||||
}
|
||||
|
||||
WrapperPtr CreatePeerConnection() {
|
||||
RTCConfiguration config;
|
||||
config.sdp_semantics = SdpSemantics::kUnifiedPlan;
|
||||
return CreatePeerConnection(config);
|
||||
}
|
||||
|
||||
WrapperPtr CreatePeerConnection(const RTCConfiguration& config) {
|
||||
auto observer = rtc::MakeUnique<MockPeerConnectionObserver>();
|
||||
auto pc = pc_factory_->CreatePeerConnection(config, nullptr, nullptr,
|
||||
observer.get());
|
||||
if (!pc) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return rtc::MakeUnique<PeerConnectionWrapper>(pc_factory_, pc,
|
||||
std::move(observer));
|
||||
}
|
||||
|
||||
std::unique_ptr<rtc::VirtualSocketServer> vss_;
|
||||
rtc::AutoSocketServerThread main_;
|
||||
rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory_;
|
||||
};
|
||||
|
||||
// Tests for JSEP initial offer generation.
|
||||
|
||||
// Test that an offer created by a PeerConnection with no transceivers generates
|
||||
// no media sections.
|
||||
TEST_F(PeerConnectionJsepTest, EmptyInitialOffer) {
|
||||
auto caller = CreatePeerConnection();
|
||||
auto offer = caller->CreateOffer();
|
||||
EXPECT_EQ(0u, offer->description()->contents().size());
|
||||
}
|
||||
|
||||
// Test that an initial offer with one audio track generates one audio media
|
||||
// section.
|
||||
TEST_F(PeerConnectionJsepTest, AudioOnlyInitialOffer) {
|
||||
auto caller = CreatePeerConnection();
|
||||
caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
|
||||
auto offer = caller->CreateOffer();
|
||||
|
||||
auto contents = offer->description()->contents();
|
||||
ASSERT_EQ(1u, contents.size());
|
||||
EXPECT_EQ(cricket::MEDIA_TYPE_AUDIO, contents[0].media_description()->type());
|
||||
}
|
||||
|
||||
// Test than an initial offer with one video track generates one video media
|
||||
// section
|
||||
TEST_F(PeerConnectionJsepTest, VideoOnlyInitialOffer) {
|
||||
auto caller = CreatePeerConnection();
|
||||
caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO);
|
||||
auto offer = caller->CreateOffer();
|
||||
|
||||
auto contents = offer->description()->contents();
|
||||
ASSERT_EQ(1u, contents.size());
|
||||
EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, contents[0].media_description()->type());
|
||||
}
|
||||
|
||||
// Test that multiple media sections in the initial offer are ordered in the
|
||||
// order the transceivers were added to the PeerConnection. This is required by
|
||||
// JSEP section 5.2.1.
|
||||
TEST_F(PeerConnectionJsepTest, MediaSectionsInInitialOfferOrderedCorrectly) {
|
||||
auto caller = CreatePeerConnection();
|
||||
caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO);
|
||||
caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
|
||||
RtpTransceiverInit init;
|
||||
init.direction = RtpTransceiverDirection::kSendOnly;
|
||||
caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO, init);
|
||||
auto offer = caller->CreateOffer();
|
||||
|
||||
auto contents = offer->description()->contents();
|
||||
ASSERT_EQ(3u, contents.size());
|
||||
|
||||
const MediaContentDescription* media_description1 =
|
||||
contents[0].media_description();
|
||||
EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, media_description1->type());
|
||||
EXPECT_EQ(RtpTransceiverDirection::kSendRecv,
|
||||
media_description1->direction());
|
||||
|
||||
const MediaContentDescription* media_description2 =
|
||||
contents[1].media_description();
|
||||
EXPECT_EQ(cricket::MEDIA_TYPE_AUDIO, media_description2->type());
|
||||
EXPECT_EQ(RtpTransceiverDirection::kSendRecv,
|
||||
media_description2->direction());
|
||||
|
||||
const MediaContentDescription* media_description3 =
|
||||
contents[2].media_description();
|
||||
EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, media_description3->type());
|
||||
EXPECT_EQ(RtpTransceiverDirection::kSendOnly,
|
||||
media_description3->direction());
|
||||
}
|
||||
|
||||
// Test that media sections in the initial offer have different mids.
|
||||
TEST_F(PeerConnectionJsepTest, MediaSectionsInInitialOfferHaveDifferentMids) {
|
||||
auto caller = CreatePeerConnection();
|
||||
caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
|
||||
caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
|
||||
auto offer = caller->CreateOffer();
|
||||
|
||||
std::string sdp;
|
||||
offer->ToString(&sdp);
|
||||
RTC_LOG(LS_INFO) << sdp;
|
||||
|
||||
auto contents = offer->description()->contents();
|
||||
ASSERT_EQ(2u, contents.size());
|
||||
EXPECT_NE(contents[0].name, contents[1].name);
|
||||
}
|
||||
|
||||
TEST_F(PeerConnectionJsepTest,
|
||||
StoppedTransceiverHasNoMediaSectionInInitialOffer) {
|
||||
auto caller = CreatePeerConnection();
|
||||
auto transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
|
||||
transceiver->Stop();
|
||||
|
||||
auto offer = caller->CreateOffer();
|
||||
EXPECT_EQ(0u, offer->description()->contents().size());
|
||||
}
|
||||
|
||||
// Tests for JSEP SetLocalDescription with a local offer.
|
||||
|
||||
TEST_F(PeerConnectionJsepTest, SetLocalEmptyOfferCreatesNoTransceivers) {
|
||||
auto caller = CreatePeerConnection();
|
||||
ASSERT_TRUE(caller->SetLocalDescription(caller->CreateOffer()));
|
||||
|
||||
EXPECT_THAT(caller->pc()->GetTransceivers(), ElementsAre());
|
||||
EXPECT_THAT(caller->pc()->GetSenders(), ElementsAre());
|
||||
EXPECT_THAT(caller->pc()->GetReceivers(), ElementsAre());
|
||||
}
|
||||
|
||||
TEST_F(PeerConnectionJsepTest, SetLocalOfferSetsTransceiverMid) {
|
||||
auto caller = CreatePeerConnection();
|
||||
auto audio_transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
|
||||
auto video_transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO);
|
||||
|
||||
auto offer = caller->CreateOffer();
|
||||
std::string audio_mid = offer->description()->contents()[0].name;
|
||||
std::string video_mid = offer->description()->contents()[1].name;
|
||||
|
||||
ASSERT_TRUE(caller->SetLocalDescription(std::move(offer)));
|
||||
|
||||
EXPECT_EQ(audio_mid, audio_transceiver->mid());
|
||||
EXPECT_EQ(video_mid, video_transceiver->mid());
|
||||
}
|
||||
|
||||
// Tests for JSEP SetRemoteDescription with a remote offer.
|
||||
|
||||
// Test that setting a remote offer with sendrecv audio and video creates two
|
||||
// transceivers, one for receiving audio and one for receiving video.
|
||||
TEST_F(PeerConnectionJsepTest, SetRemoteOfferCreatesTransceivers) {
|
||||
auto caller = CreatePeerConnection();
|
||||
auto caller_audio = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
|
||||
auto caller_video = caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO);
|
||||
auto callee = CreatePeerConnection();
|
||||
|
||||
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
|
||||
|
||||
auto transceivers = callee->pc()->GetTransceivers();
|
||||
ASSERT_EQ(2u, transceivers.size());
|
||||
EXPECT_EQ(cricket::MEDIA_TYPE_AUDIO,
|
||||
transceivers[0]->receiver()->media_type());
|
||||
EXPECT_EQ(caller_audio->mid(), transceivers[0]->mid());
|
||||
EXPECT_EQ(RtpTransceiverDirection::kRecvOnly, transceivers[0]->direction());
|
||||
EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO,
|
||||
transceivers[1]->receiver()->media_type());
|
||||
EXPECT_EQ(caller_video->mid(), transceivers[1]->mid());
|
||||
EXPECT_EQ(RtpTransceiverDirection::kRecvOnly, transceivers[1]->direction());
|
||||
}
|
||||
|
||||
// Test that setting a remote offer with an audio track will reuse the
|
||||
// transceiver created for a local audio track added by AddTrack.
|
||||
// This is specified in JSEP section 5.10 (Applying a Remote Description). The
|
||||
// intent is to preserve backwards compatibility with clients who only use the
|
||||
// AddTrack API.
|
||||
TEST_F(PeerConnectionJsepTest, SetRemoteOfferReusesTransceiverFromAddTrack) {
|
||||
auto caller = CreatePeerConnection();
|
||||
caller->AddAudioTrack("a");
|
||||
auto caller_audio = caller->pc()->GetTransceivers()[0];
|
||||
auto callee = CreatePeerConnection();
|
||||
callee->AddAudioTrack("a");
|
||||
|
||||
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
|
||||
|
||||
auto transceivers = callee->pc()->GetTransceivers();
|
||||
ASSERT_EQ(1u, transceivers.size());
|
||||
EXPECT_EQ(MediaStreamTrackInterface::kAudioKind,
|
||||
transceivers[0]->receiver()->track()->kind());
|
||||
EXPECT_EQ(caller_audio->mid(), transceivers[0]->mid());
|
||||
}
|
||||
|
||||
// Test that setting a remote offer with an audio track marked sendonly will not
|
||||
// reuse a transceiver created by AddTrack. JSEP only allows the transceiver to
|
||||
// be reused if the offer direction is sendrecv or recvonly.
|
||||
TEST_F(PeerConnectionJsepTest,
|
||||
SetRemoteOfferDoesNotReuseTransceiverIfDirectionSendOnly) {
|
||||
auto caller = CreatePeerConnection();
|
||||
caller->AddAudioTrack("a");
|
||||
auto caller_audio = caller->pc()->GetTransceivers()[0];
|
||||
caller_audio->SetDirection(RtpTransceiverDirection::kSendOnly);
|
||||
auto callee = CreatePeerConnection();
|
||||
callee->AddAudioTrack("a");
|
||||
|
||||
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
|
||||
|
||||
auto transceivers = callee->pc()->GetTransceivers();
|
||||
ASSERT_EQ(2u, transceivers.size());
|
||||
EXPECT_EQ(rtc::nullopt, transceivers[0]->mid());
|
||||
EXPECT_EQ(caller_audio->mid(), transceivers[1]->mid());
|
||||
}
|
||||
|
||||
// Test that setting a remote offer with an audio track will not reuse a
|
||||
// transceiver added by AddTransceiver. The logic for reusing a transceiver is
|
||||
// specific to those added by AddTrack and is tested above.
|
||||
TEST_F(PeerConnectionJsepTest,
|
||||
SetRemoteOfferDoesNotReuseTransceiverFromAddTransceiver) {
|
||||
auto caller = CreatePeerConnection();
|
||||
caller->AddAudioTrack("a");
|
||||
auto callee = CreatePeerConnection();
|
||||
auto transceiver = callee->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
|
||||
|
||||
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
|
||||
|
||||
auto transceivers = callee->pc()->GetTransceivers();
|
||||
ASSERT_EQ(2u, transceivers.size());
|
||||
EXPECT_EQ(rtc::nullopt, transceivers[0]->mid());
|
||||
EXPECT_EQ(caller->pc()->GetTransceivers()[0]->mid(), transceivers[1]->mid());
|
||||
EXPECT_EQ(MediaStreamTrackInterface::kAudioKind,
|
||||
transceivers[1]->receiver()->track()->kind());
|
||||
}
|
||||
|
||||
// Test that setting a remote offer with an audio track will not reuse a
|
||||
// transceiver created for a local video track added by AddTrack.
|
||||
TEST_F(PeerConnectionJsepTest,
|
||||
SetRemoteOfferDoesNotReuseTransceiverOfWrongType) {
|
||||
auto caller = CreatePeerConnection();
|
||||
caller->AddAudioTrack("a");
|
||||
auto callee = CreatePeerConnection();
|
||||
auto video_sender = callee->AddVideoTrack("v");
|
||||
|
||||
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
|
||||
|
||||
auto transceivers = callee->pc()->GetTransceivers();
|
||||
ASSERT_EQ(2u, transceivers.size());
|
||||
EXPECT_EQ(rtc::nullopt, transceivers[0]->mid());
|
||||
EXPECT_EQ(caller->pc()->GetTransceivers()[0]->mid(), transceivers[1]->mid());
|
||||
EXPECT_EQ(MediaStreamTrackInterface::kAudioKind,
|
||||
transceivers[1]->receiver()->track()->kind());
|
||||
}
|
||||
|
||||
// Test that setting a remote offer with an audio track will not reuse a
|
||||
// stopped transceiver.
|
||||
TEST_F(PeerConnectionJsepTest, SetRemoteOfferDoesNotReuseStoppedTransceiver) {
|
||||
auto caller = CreatePeerConnection();
|
||||
caller->AddAudioTrack("a");
|
||||
auto callee = CreatePeerConnection();
|
||||
callee->AddAudioTrack("a");
|
||||
callee->pc()->GetTransceivers()[0]->Stop();
|
||||
|
||||
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
|
||||
|
||||
auto transceivers = callee->pc()->GetTransceivers();
|
||||
ASSERT_EQ(2u, transceivers.size());
|
||||
EXPECT_EQ(rtc::nullopt, transceivers[0]->mid());
|
||||
EXPECT_TRUE(transceivers[0]->stopped());
|
||||
EXPECT_EQ(caller->pc()->GetTransceivers()[0]->mid(), transceivers[1]->mid());
|
||||
EXPECT_FALSE(transceivers[1]->stopped());
|
||||
}
|
||||
|
||||
// Test that audio and video transceivers created on the remote side with
|
||||
// AddTrack will all be reused if there is the same number of audio/video tracks
|
||||
// in the remote offer. Additionally, this tests that transceivers are
|
||||
// successfully matched even if they are in a different order on the remote
|
||||
// side.
|
||||
TEST_F(PeerConnectionJsepTest, SetRemoteOfferReusesTransceiversOfBothTypes) {
|
||||
auto caller = CreatePeerConnection();
|
||||
caller->AddVideoTrack("v");
|
||||
caller->AddAudioTrack("a");
|
||||
auto callee = CreatePeerConnection();
|
||||
callee->AddAudioTrack("a");
|
||||
callee->AddVideoTrack("v");
|
||||
|
||||
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
|
||||
|
||||
auto caller_transceivers = caller->pc()->GetTransceivers();
|
||||
auto callee_transceivers = callee->pc()->GetTransceivers();
|
||||
ASSERT_EQ(2u, callee_transceivers.size());
|
||||
EXPECT_EQ(caller_transceivers[0]->mid(), callee_transceivers[1]->mid());
|
||||
EXPECT_EQ(caller_transceivers[1]->mid(), callee_transceivers[0]->mid());
|
||||
}
|
||||
|
||||
// Tests for JSEP initial CreateAnswer.
|
||||
|
||||
// Test that the answer to a remote offer creates media sections for each
|
||||
// offered media in the same order and with the same mids.
|
||||
TEST_F(PeerConnectionJsepTest, CreateAnswerHasSameMidsAsOffer) {
|
||||
auto caller = CreatePeerConnection();
|
||||
auto first_transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO);
|
||||
auto second_transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
|
||||
auto third_transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO);
|
||||
auto callee = CreatePeerConnection();
|
||||
|
||||
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
|
||||
|
||||
auto answer = callee->CreateAnswer();
|
||||
auto contents = answer->description()->contents();
|
||||
ASSERT_EQ(3u, contents.size());
|
||||
EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, contents[0].media_description()->type());
|
||||
EXPECT_EQ(*first_transceiver->mid(), contents[0].name);
|
||||
EXPECT_EQ(cricket::MEDIA_TYPE_AUDIO, contents[1].media_description()->type());
|
||||
EXPECT_EQ(*second_transceiver->mid(), contents[1].name);
|
||||
EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, contents[2].media_description()->type());
|
||||
EXPECT_EQ(*third_transceiver->mid(), contents[2].name);
|
||||
}
|
||||
|
||||
// Test that an answering media section is marked as rejected if the underlying
|
||||
// transceiver has been stopped.
|
||||
TEST_F(PeerConnectionJsepTest, CreateAnswerRejectsStoppedTransceiver) {
|
||||
auto caller = CreatePeerConnection();
|
||||
caller->AddAudioTrack("a");
|
||||
auto callee = CreatePeerConnection();
|
||||
|
||||
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
|
||||
|
||||
callee->pc()->GetTransceivers()[0]->Stop();
|
||||
|
||||
auto answer = callee->CreateAnswer();
|
||||
auto contents = answer->description()->contents();
|
||||
ASSERT_EQ(1u, contents.size());
|
||||
EXPECT_TRUE(contents[0].rejected);
|
||||
}
|
||||
|
||||
// Test that CreateAnswer will generate media sections which will only send or
|
||||
// receive if the offer indicates it can do the reciprocating direction.
|
||||
// The full matrix is tested more extensively in MediaSession.
|
||||
TEST_F(PeerConnectionJsepTest, CreateAnswerNegotiatesDirection) {
|
||||
auto caller = CreatePeerConnection();
|
||||
RtpTransceiverInit init;
|
||||
init.direction = RtpTransceiverDirection::kSendOnly;
|
||||
caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO, init);
|
||||
auto callee = CreatePeerConnection();
|
||||
callee->AddAudioTrack("a");
|
||||
|
||||
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
|
||||
|
||||
auto answer = callee->CreateAnswer();
|
||||
auto contents = answer->description()->contents();
|
||||
ASSERT_EQ(1u, contents.size());
|
||||
EXPECT_EQ(RtpTransceiverDirection::kRecvOnly,
|
||||
contents[0].media_description()->direction());
|
||||
}
|
||||
|
||||
// Tests for JSEP SetLocalDescription with a local answer.
|
||||
// Note that these test only the additional behaviors not covered by
|
||||
// SetLocalDescription with a local offer.
|
||||
|
||||
// Test that SetLocalDescription with an answer sets the current_direction
|
||||
// property of the transceivers mentioned in the session description.
|
||||
TEST_F(PeerConnectionJsepTest, SetLocalAnswerUpdatesCurrentDirection) {
|
||||
auto caller = CreatePeerConnection();
|
||||
auto caller_audio = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
|
||||
caller_audio->SetDirection(RtpTransceiverDirection::kRecvOnly);
|
||||
auto callee = CreatePeerConnection();
|
||||
callee->AddAudioTrack("a");
|
||||
|
||||
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
|
||||
ASSERT_TRUE(callee->SetLocalDescription(callee->CreateAnswer()));
|
||||
|
||||
auto transceivers = callee->pc()->GetTransceivers();
|
||||
ASSERT_EQ(1u, transceivers.size());
|
||||
// Since the offer was recvonly and the transceiver direction is sendrecv,
|
||||
// the negotiated direction will be sendonly.
|
||||
EXPECT_EQ(RtpTransceiverDirection::kSendOnly,
|
||||
transceivers[0]->current_direction());
|
||||
}
|
||||
|
||||
// Tests for JSEP SetRemoteDescription with a remote answer.
|
||||
// Note that these test only the additional behaviors not covered by
|
||||
// SetRemoteDescription with a remote offer.
|
||||
|
||||
TEST_F(PeerConnectionJsepTest, SetRemoteAnswerUpdatesCurrentDirection) {
|
||||
auto caller = CreatePeerConnection();
|
||||
caller->AddAudioTrack("a");
|
||||
auto callee = CreatePeerConnection();
|
||||
callee->AddAudioTrack("a");
|
||||
auto callee_audio = callee->pc()->GetTransceivers()[0];
|
||||
callee_audio->SetDirection(RtpTransceiverDirection::kSendOnly);
|
||||
|
||||
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
|
||||
ASSERT_TRUE(
|
||||
caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
|
||||
|
||||
auto transceivers = caller->pc()->GetTransceivers();
|
||||
ASSERT_EQ(1u, transceivers.size());
|
||||
// Since the remote transceiver was set to sendonly, the negotiated direction
|
||||
// in the answer would be sendonly which we apply as recvonly to the local
|
||||
// transceiver.
|
||||
EXPECT_EQ(RtpTransceiverDirection::kRecvOnly,
|
||||
transceivers[0]->current_direction());
|
||||
}
|
||||
|
||||
// Tests for multiple round trips.
|
||||
|
||||
// Test that setting a transceiver with the inactive direction does not stop it
|
||||
// on either the caller or the callee.
|
||||
TEST_F(PeerConnectionJsepTest, SettingTransceiverInactiveDoesNotStopIt) {
|
||||
auto caller = CreatePeerConnection();
|
||||
caller->AddAudioTrack("a");
|
||||
auto callee = CreatePeerConnection();
|
||||
callee->AddAudioTrack("a");
|
||||
callee->pc()->GetTransceivers()[0]->SetDirection(
|
||||
RtpTransceiverDirection::kInactive);
|
||||
|
||||
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
|
||||
ASSERT_TRUE(
|
||||
caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
|
||||
|
||||
EXPECT_FALSE(caller->pc()->GetTransceivers()[0]->stopped());
|
||||
EXPECT_FALSE(callee->pc()->GetTransceivers()[0]->stopped());
|
||||
}
|
||||
|
||||
// Test that if a transceiver had been associated and later stopped, then a
|
||||
// media section is still generated for it and the media section is marked as
|
||||
// rejected.
|
||||
TEST_F(PeerConnectionJsepTest,
|
||||
ReOfferMediaSectionForAssociatedStoppedTransceiverIsRejected) {
|
||||
auto caller = CreatePeerConnection();
|
||||
auto transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
|
||||
auto callee = CreatePeerConnection();
|
||||
|
||||
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
|
||||
ASSERT_TRUE(
|
||||
caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
|
||||
|
||||
ASSERT_TRUE(transceiver->mid());
|
||||
transceiver->Stop();
|
||||
|
||||
auto reoffer = caller->CreateOffer();
|
||||
auto contents = reoffer->description()->contents();
|
||||
ASSERT_EQ(1u, contents.size());
|
||||
EXPECT_TRUE(contents[0].rejected);
|
||||
}
|
||||
|
||||
// Test that stopping an associated transceiver on the caller side will stop the
|
||||
// corresponding transceiver on the remote side when the remote offer is
|
||||
// applied.
|
||||
TEST_F(PeerConnectionJsepTest,
|
||||
StoppingTransceiverInOfferStopsTransceiverOnRemoteSide) {
|
||||
auto caller = CreatePeerConnection();
|
||||
auto transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
|
||||
auto callee = CreatePeerConnection();
|
||||
|
||||
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
|
||||
ASSERT_TRUE(
|
||||
caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
|
||||
|
||||
transceiver->Stop();
|
||||
|
||||
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
|
||||
|
||||
auto transceivers = callee->pc()->GetTransceivers();
|
||||
EXPECT_TRUE(transceivers[0]->stopped());
|
||||
EXPECT_TRUE(transceivers[0]->mid());
|
||||
}
|
||||
|
||||
// Test that CreateOffer will only generate a recycled media section if the
|
||||
// transceiver to be recycled has been seen stopped by the other side first.
|
||||
TEST_F(PeerConnectionJsepTest,
|
||||
CreateOfferDoesNotRecycleMediaSectionIfFirstStopped) {
|
||||
auto caller = CreatePeerConnection();
|
||||
auto first_transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
|
||||
auto callee = CreatePeerConnection();
|
||||
|
||||
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
|
||||
ASSERT_TRUE(
|
||||
caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
|
||||
|
||||
auto second_transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
|
||||
first_transceiver->Stop();
|
||||
|
||||
auto reoffer = caller->CreateOffer();
|
||||
auto contents = reoffer->description()->contents();
|
||||
ASSERT_EQ(2u, contents.size());
|
||||
EXPECT_TRUE(contents[0].rejected);
|
||||
EXPECT_FALSE(contents[1].rejected);
|
||||
}
|
||||
|
||||
// Test that the offer/answer and transceivers for both the caller and callee
|
||||
// side are generated/updated correctly when recycling an audio/video media
|
||||
// section as a media section of either the same or opposite type.
|
||||
class RecycleMediaSectionTest
|
||||
: public PeerConnectionJsepTest,
|
||||
public testing::WithParamInterface<
|
||||
std::tuple<cricket::MediaType, cricket::MediaType>> {
|
||||
protected:
|
||||
RecycleMediaSectionTest() {
|
||||
first_type_ = std::get<0>(GetParam());
|
||||
second_type_ = std::get<1>(GetParam());
|
||||
}
|
||||
|
||||
cricket::MediaType first_type_;
|
||||
cricket::MediaType second_type_;
|
||||
};
|
||||
|
||||
TEST_P(RecycleMediaSectionTest, VerifyOfferAnswerAndTransceivers) {
|
||||
auto caller = CreatePeerConnection();
|
||||
auto first_transceiver = caller->AddTransceiver(first_type_);
|
||||
auto callee = CreatePeerConnection();
|
||||
|
||||
ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
|
||||
|
||||
std::string first_mid = *first_transceiver->mid();
|
||||
first_transceiver->Stop();
|
||||
|
||||
ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
|
||||
|
||||
auto second_transceiver = caller->AddTransceiver(second_type_);
|
||||
|
||||
// The offer should reuse the previous media section but allocate a new MID
|
||||
// and change the media type.
|
||||
auto offer = caller->CreateOffer();
|
||||
auto offer_contents = offer->description()->contents();
|
||||
ASSERT_EQ(1u, offer_contents.size());
|
||||
EXPECT_FALSE(offer_contents[0].rejected);
|
||||
EXPECT_EQ(second_type_, offer_contents[0].media_description()->type());
|
||||
std::string second_mid = offer_contents[0].name;
|
||||
EXPECT_NE(first_mid, second_mid);
|
||||
|
||||
// Setting the local offer will dissociate the previous transceiver and set
|
||||
// the MID for the new transceiver.
|
||||
ASSERT_TRUE(
|
||||
caller->SetLocalDescription(CloneSessionDescription(offer.get())));
|
||||
EXPECT_EQ(rtc::nullopt, first_transceiver->mid());
|
||||
EXPECT_EQ(second_mid, second_transceiver->mid());
|
||||
|
||||
// Setting the remote offer will dissociate the previous transceiver and
|
||||
// create a new transceiver for the media section.
|
||||
ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
|
||||
auto callee_transceivers = callee->pc()->GetTransceivers();
|
||||
ASSERT_EQ(2u, callee_transceivers.size());
|
||||
EXPECT_EQ(rtc::nullopt, callee_transceivers[0]->mid());
|
||||
EXPECT_EQ(first_type_, callee_transceivers[0]->receiver()->media_type());
|
||||
EXPECT_EQ(second_mid, callee_transceivers[1]->mid());
|
||||
EXPECT_EQ(second_type_, callee_transceivers[1]->receiver()->media_type());
|
||||
|
||||
// The answer should have only one media section for the new transceiver.
|
||||
auto answer = callee->CreateAnswer();
|
||||
auto answer_contents = answer->description()->contents();
|
||||
ASSERT_EQ(1u, answer_contents.size());
|
||||
EXPECT_FALSE(answer_contents[0].rejected);
|
||||
EXPECT_EQ(second_mid, answer_contents[0].name);
|
||||
EXPECT_EQ(second_type_, answer_contents[0].media_description()->type());
|
||||
|
||||
// Setting the local answer should succeed.
|
||||
ASSERT_TRUE(
|
||||
callee->SetLocalDescription(CloneSessionDescription(answer.get())));
|
||||
|
||||
// Setting the remote answer should succeed.
|
||||
ASSERT_TRUE(caller->SetRemoteDescription(std::move(answer)));
|
||||
}
|
||||
|
||||
// Test all combinations of audio and video as the first and second media type
|
||||
// for the media section. This is needed for full test coverage because
|
||||
// MediaSession has separate functions for processing audio and video media
|
||||
// sections.
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
PeerConnectionJsepTest,
|
||||
RecycleMediaSectionTest,
|
||||
Combine(Values(cricket::MEDIA_TYPE_AUDIO, cricket::MEDIA_TYPE_VIDEO),
|
||||
Values(cricket::MEDIA_TYPE_AUDIO, cricket::MEDIA_TYPE_VIDEO)));
|
||||
|
||||
// Tests for MID properties.
|
||||
|
||||
static void RenameSection(size_t mline_index,
|
||||
const std::string& new_mid,
|
||||
SessionDescriptionInterface* sdesc) {
|
||||
cricket::SessionDescription* desc = sdesc->description();
|
||||
std::string old_mid = desc->contents()[mline_index].name;
|
||||
desc->contents()[mline_index].name = new_mid;
|
||||
desc->transport_infos()[mline_index].content_name = new_mid;
|
||||
const cricket::ContentGroup* bundle =
|
||||
desc->GetGroupByName(cricket::GROUP_TYPE_BUNDLE);
|
||||
if (bundle) {
|
||||
cricket::ContentGroup new_bundle = *bundle;
|
||||
if (new_bundle.RemoveContentName(old_mid)) {
|
||||
new_bundle.AddContentName(new_mid);
|
||||
}
|
||||
desc->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
|
||||
desc->AddGroup(new_bundle);
|
||||
}
|
||||
}
|
||||
|
||||
// Test that two PeerConnections can have a successful offer/answer exchange if
|
||||
// the MIDs are changed from the defaults.
|
||||
TEST_F(PeerConnectionJsepTest, OfferAnswerWithChangedMids) {
|
||||
constexpr char kFirstMid[] = "nondefaultmid";
|
||||
constexpr char kSecondMid[] = "randommid";
|
||||
|
||||
auto caller = CreatePeerConnection();
|
||||
caller->AddAudioTrack("a");
|
||||
caller->AddAudioTrack("b");
|
||||
auto callee = CreatePeerConnection();
|
||||
|
||||
auto offer = caller->CreateOffer();
|
||||
RenameSection(0, kFirstMid, offer.get());
|
||||
RenameSection(1, kSecondMid, offer.get());
|
||||
|
||||
ASSERT_TRUE(
|
||||
caller->SetLocalDescription(CloneSessionDescription(offer.get())));
|
||||
auto caller_transceivers = caller->pc()->GetTransceivers();
|
||||
EXPECT_EQ(kFirstMid, caller_transceivers[0]->mid());
|
||||
EXPECT_EQ(kSecondMid, caller_transceivers[1]->mid());
|
||||
|
||||
ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
|
||||
auto callee_transceivers = callee->pc()->GetTransceivers();
|
||||
EXPECT_EQ(kFirstMid, callee_transceivers[0]->mid());
|
||||
EXPECT_EQ(kSecondMid, callee_transceivers[1]->mid());
|
||||
|
||||
auto answer = callee->CreateAnswer();
|
||||
auto answer_contents = answer->description()->contents();
|
||||
EXPECT_EQ(kFirstMid, answer_contents[0].name);
|
||||
EXPECT_EQ(kSecondMid, answer_contents[1].name);
|
||||
|
||||
ASSERT_TRUE(
|
||||
callee->SetLocalDescription(CloneSessionDescription(answer.get())));
|
||||
ASSERT_TRUE(caller->SetRemoteDescription(std::move(answer)));
|
||||
}
|
||||
|
||||
// Test that CreateOffer will generate a MID that is not already used if the
|
||||
// default it would have picked is already taken. This is tested by using a
|
||||
// third PeerConnection to determine what the default would be for the second
|
||||
// media section then setting that as the first media section's MID.
|
||||
TEST_F(PeerConnectionJsepTest, CreateOfferGeneratesUniqueMidIfAlreadyTaken) {
|
||||
// First, find what the default MID is for the second media section.
|
||||
auto pc = CreatePeerConnection();
|
||||
pc->AddAudioTrack("a");
|
||||
pc->AddAudioTrack("b");
|
||||
auto default_offer = pc->CreateOffer();
|
||||
std::string default_second_mid =
|
||||
default_offer->description()->contents()[1].name;
|
||||
|
||||
// Now, do an offer/answer with one track which has the MID set to the default
|
||||
// second MID.
|
||||
auto caller = CreatePeerConnection();
|
||||
caller->AddAudioTrack("a");
|
||||
auto callee = CreatePeerConnection();
|
||||
|
||||
auto offer = caller->CreateOffer();
|
||||
RenameSection(0, default_second_mid, offer.get());
|
||||
|
||||
ASSERT_TRUE(
|
||||
caller->SetLocalDescription(CloneSessionDescription(offer.get())));
|
||||
ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
|
||||
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
|
||||
|
||||
// Add a second track and ensure that the MID is different.
|
||||
caller->AddAudioTrack("b");
|
||||
|
||||
auto reoffer = caller->CreateOffer();
|
||||
auto reoffer_contents = reoffer->description()->contents();
|
||||
EXPECT_EQ(default_second_mid, reoffer_contents[0].name);
|
||||
EXPECT_NE(reoffer_contents[0].name, reoffer_contents[1].name);
|
||||
}
|
||||
|
||||
// Test that a reoffer initiated by the callee adds a new track to the caller.
|
||||
TEST_F(PeerConnectionJsepTest, CalleeDoesReoffer) {
|
||||
auto caller = CreatePeerConnection();
|
||||
caller->AddAudioTrack("a");
|
||||
auto callee = CreatePeerConnection();
|
||||
callee->AddAudioTrack("a");
|
||||
callee->AddVideoTrack("v");
|
||||
|
||||
ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
|
||||
|
||||
EXPECT_EQ(1u, caller->pc()->GetTransceivers().size());
|
||||
EXPECT_EQ(2u, callee->pc()->GetTransceivers().size());
|
||||
|
||||
ASSERT_TRUE(callee->ExchangeOfferAnswerWith(caller.get()));
|
||||
|
||||
EXPECT_EQ(2u, caller->pc()->GetTransceivers().size());
|
||||
EXPECT_EQ(2u, callee->pc()->GetTransceivers().size());
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
|
@ -179,6 +179,45 @@ bool PeerConnectionWrapper::SetSdp(
|
|||
return observer->result();
|
||||
}
|
||||
|
||||
bool PeerConnectionWrapper::ExchangeOfferAnswerWith(
|
||||
PeerConnectionWrapper* answerer) {
|
||||
RTC_DCHECK(answerer);
|
||||
if (answerer == this) {
|
||||
RTC_LOG(LS_ERROR) << "Cannot exchange offer/answer with ourself!";
|
||||
return false;
|
||||
}
|
||||
auto offer = CreateOffer();
|
||||
EXPECT_TRUE(offer);
|
||||
if (!offer) {
|
||||
return false;
|
||||
}
|
||||
bool set_local_offer =
|
||||
SetLocalDescription(CloneSessionDescription(offer.get()));
|
||||
EXPECT_TRUE(set_local_offer);
|
||||
if (!set_local_offer) {
|
||||
return false;
|
||||
}
|
||||
bool set_remote_offer = answerer->SetRemoteDescription(std::move(offer));
|
||||
EXPECT_TRUE(set_remote_offer);
|
||||
if (!set_remote_offer) {
|
||||
return false;
|
||||
}
|
||||
auto answer = answerer->CreateAnswer();
|
||||
EXPECT_TRUE(answer);
|
||||
if (!answer) {
|
||||
return false;
|
||||
}
|
||||
bool set_local_answer =
|
||||
answerer->SetLocalDescription(CloneSessionDescription(answer.get()));
|
||||
EXPECT_TRUE(set_local_answer);
|
||||
if (!set_local_answer) {
|
||||
return false;
|
||||
}
|
||||
bool set_remote_answer = SetRemoteDescription(std::move(answer));
|
||||
EXPECT_TRUE(set_remote_answer);
|
||||
return set_remote_answer;
|
||||
}
|
||||
|
||||
rtc::scoped_refptr<RtpTransceiverInterface>
|
||||
PeerConnectionWrapper::AddTransceiver(cricket::MediaType media_type) {
|
||||
RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> result =
|
||||
|
|
|
@ -93,6 +93,21 @@ class PeerConnectionWrapper {
|
|||
bool SetRemoteDescription(std::unique_ptr<SessionDescriptionInterface> desc,
|
||||
RTCError* error_out);
|
||||
|
||||
// Does a round of offer/answer with the local PeerConnectionWrapper
|
||||
// generating the offer and the given PeerConnectionWrapper generating the
|
||||
// answer.
|
||||
// Equivalent to:
|
||||
// 1. this->CreateOffer()
|
||||
// 2. this->SetLocalDescription(offer)
|
||||
// 3. answerer->SetRemoteDescription(offer)
|
||||
// 4. answerer->CreateAnswer()
|
||||
// 5. answerer->SetLocalDescription(answer)
|
||||
// 6. this->SetRemoteDescription(answer)
|
||||
// Returns true if all steps succeed, false otherwise.
|
||||
// Suggested usage:
|
||||
// ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
|
||||
bool ExchangeOfferAnswerWith(PeerConnectionWrapper* answerer);
|
||||
|
||||
// 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.
|
||||
|
|
|
@ -12,8 +12,14 @@
|
|||
|
||||
#include <string>
|
||||
|
||||
#include "pc/rtpmediautils.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, RtpTransceiverDirection direction) {
|
||||
return os << RtpTransceiverDirectionToString(direction);
|
||||
}
|
||||
|
||||
RtpTransceiver::RtpTransceiver(cricket::MediaType media_type)
|
||||
: unified_plan_(false), media_type_(media_type) {
|
||||
RTC_DCHECK(media_type == cricket::MEDIA_TYPE_AUDIO ||
|
||||
|
@ -142,6 +148,13 @@ rtc::scoped_refptr<RtpReceiverInterface> RtpTransceiver::receiver() const {
|
|||
return receivers_[0];
|
||||
}
|
||||
|
||||
void RtpTransceiver::set_current_direction(RtpTransceiverDirection direction) {
|
||||
current_direction_ = direction;
|
||||
if (RtpTransceiverDirectionHasSend(*current_direction_)) {
|
||||
has_ever_been_used_to_send_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool RtpTransceiver::stopped() const {
|
||||
return stopped_;
|
||||
}
|
||||
|
@ -152,7 +165,7 @@ RtpTransceiverDirection RtpTransceiver::direction() const {
|
|||
|
||||
void RtpTransceiver::SetDirection(RtpTransceiverDirection new_direction) {
|
||||
// TODO(steveanton): This should fire OnNegotiationNeeded.
|
||||
direction_ = new_direction;
|
||||
set_direction(new_direction);
|
||||
}
|
||||
|
||||
rtc::Optional<RtpTransceiverDirection> RtpTransceiver::current_direction()
|
||||
|
|
|
@ -115,6 +115,33 @@ class RtpTransceiver final
|
|||
// Returns the backing object for the transceiver's Unified Plan receiver.
|
||||
rtc::scoped_refptr<RtpReceiverInternal> receiver_internal() const;
|
||||
|
||||
// RtpTransceivers are not associated until they have a corresponding media
|
||||
// section set in SetLocalDescription or SetRemoteDescription. Therefore,
|
||||
// when setting a local offer we need a way to remember which transceiver was
|
||||
// used to create which media section in the offer. Storing the mline index
|
||||
// in CreateOffer is specified in JSEP to allow us to do that.
|
||||
rtc::Optional<size_t> mline_index() const { return mline_index_; }
|
||||
void set_mline_index(rtc::Optional<size_t> mline_index) {
|
||||
mline_index_ = mline_index;
|
||||
}
|
||||
|
||||
// Sets the MID for this transceiver. If the MID is not null, then the
|
||||
// transceiver is considered "associated" with the media section that has the
|
||||
// same MID.
|
||||
void set_mid(const rtc::Optional<std::string>& mid) { mid_ = mid; }
|
||||
|
||||
// Sets the intended direction for this transceiver. Intended to be used
|
||||
// internally over SetDirection since this does not trigger a negotiation
|
||||
// needed callback.
|
||||
void set_direction(RtpTransceiverDirection direction) {
|
||||
direction_ = direction;
|
||||
}
|
||||
|
||||
// Sets the current direction for this transceiver as negotiated in an offer/
|
||||
// answer exchange. The current direction is null before an answer with this
|
||||
// transceiver has been set.
|
||||
void set_current_direction(RtpTransceiverDirection direction);
|
||||
|
||||
// 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) {
|
||||
|
@ -152,9 +179,8 @@ class RtpTransceiver final
|
|||
RtpTransceiverDirection direction_ = RtpTransceiverDirection::kInactive;
|
||||
rtc::Optional<RtpTransceiverDirection> current_direction_;
|
||||
rtc::Optional<std::string> mid_;
|
||||
rtc::Optional<size_t> mline_index_;
|
||||
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;
|
||||
|
|
Loading…
Reference in a new issue