Relanding: Adding support for Unified Plan offer/answer negotiation to the mediasession layer.

This layer takes in a simplified "options" struct and the current local description,
and generates a new offer/answer. Previously the options struct assumed there would
only be one media description per media type (audio/video), but it now supports
N number of audio/video descriptions.

The |add_legacy_stream| options is removed from the mediasession.cc/.h
in this CL.

The next step is to add the ability for PeerConnection/WebRtcSession to create
"options" to represent multiple RtpTransceivers, and apply the Unified Plan
descriptions correctly. Right now, only Plan B descriptions will be
generated in unit tests.

BUG=chromium:465349

Review-Url: https://codereview.webrtc.org/2991693002
Cr-Original-Commit-Position: refs/heads/master@{#19343}
Committed: a77e6bbd30
Review-Url: https://codereview.webrtc.org/2991693002
Cr-Commit-Position: refs/heads/master@{#19394}
This commit is contained in:
zhihuang 2017-08-17 14:10:50 -07:00 committed by Commit Bot
parent 825f65e9d2
commit 1c378ed83b
8 changed files with 2727 additions and 1656 deletions

File diff suppressed because it is too large Load Diff

View File

@ -102,83 +102,72 @@ RtpTransceiverDirection
NegotiateRtpTransceiverDirection(RtpTransceiverDirection offer,
RtpTransceiverDirection wants);
struct MediaSessionOptions {
MediaSessionOptions()
: recv_audio(true),
recv_video(false),
data_channel_type(DCT_NONE),
is_muc(false),
vad_enabled(true), // When disabled, removes all CN codecs from SDP.
rtcp_mux_enabled(true),
bundle_enabled(false),
video_bandwidth(kAutoBandwidth),
data_bandwidth(kDataMaxBandwidth),
rtcp_cname(kDefaultRtcpCname) {}
// Options for an RtpSender contained with an media description/"m=" section.
struct SenderOptions {
std::string track_id;
std::string stream_id;
int num_sim_layers;
};
bool has_audio() const {
return recv_audio || HasSendMediaStream(MEDIA_TYPE_AUDIO);
}
bool has_video() const {
return recv_video || HasSendMediaStream(MEDIA_TYPE_VIDEO);
}
bool has_data() const { return data_channel_type != DCT_NONE; }
// Options for an individual media description/"m=" section.
struct MediaDescriptionOptions {
MediaDescriptionOptions(MediaType type,
const std::string& mid,
RtpTransceiverDirection direction,
bool stopped)
: type(type), mid(mid), direction(direction), stopped(stopped) {}
// Add a stream with MediaType type and id.
// All streams with the same sync_label will get the same CNAME.
// All ids must be unique.
void AddSendStream(MediaType type,
const std::string& id,
const std::string& sync_label);
void AddSendVideoStream(const std::string& id,
const std::string& sync_label,
// TODO(deadbeef): When we don't support Plan B, there will only be one
// sender per media description and this can be simplified.
void AddAudioSender(const std::string& track_id,
const std::string& stream_id);
void AddVideoSender(const std::string& track_id,
const std::string& stream_id,
int num_sim_layers);
void RemoveSendStream(MediaType type, const std::string& id);
// Internally just uses sender_options.
void AddRtpDataChannel(const std::string& track_id,
const std::string& stream_id);
// Helper function.
void AddSendStreamInternal(MediaType type,
const std::string& id,
const std::string& sync_label,
MediaType type;
std::string mid;
RtpTransceiverDirection direction;
bool stopped;
TransportOptions transport_options;
// Note: There's no equivalent "RtpReceiverOptions" because only send
// stream information goes in the local descriptions.
std::vector<SenderOptions> sender_options;
private:
// Doesn't DCHECK on |type|.
void AddSenderInternal(const std::string& track_id,
const std::string& stream_id,
int num_sim_layers);
};
bool HasSendMediaStream(MediaType type) const;
// Provides a mechanism for describing how m= sections should be generated.
// The m= section with index X will use media_description_options[X]. There
// must be an option for each existing section if creating an answer, or a
// subsequent offer.
struct MediaSessionOptions {
MediaSessionOptions() {}
// TODO(deadbeef): Put all the audio/video/data-specific options into a map
// structure (content name -> options).
// MediaSessionDescriptionFactory assumes there will never be more than one
// audio/video/data content, but this will change with unified plan.
bool recv_audio;
bool recv_video;
DataChannelType data_channel_type;
bool is_muc;
bool vad_enabled;
bool rtcp_mux_enabled;
bool bundle_enabled;
// bps. -1 == auto.
int video_bandwidth;
int data_bandwidth;
bool enable_ice_renomination = false;
// content name ("mid") => options.
std::map<std::string, TransportOptions> transport_options;
std::string rtcp_cname;
bool has_audio() const { return HasMediaDescription(MEDIA_TYPE_AUDIO); }
bool has_video() const { return HasMediaDescription(MEDIA_TYPE_VIDEO); }
bool has_data() const { return HasMediaDescription(MEDIA_TYPE_DATA); }
bool HasMediaDescription(MediaType type) const;
DataChannelType data_channel_type = DCT_NONE;
bool is_muc = false;
bool vad_enabled = true; // When disabled, removes all CN codecs from SDP.
bool rtcp_mux_enabled = true;
bool bundle_enabled = false;
std::string rtcp_cname = kDefaultRtcpCname;
rtc::CryptoOptions crypto_options;
struct Stream {
Stream(MediaType type,
const std::string& id,
const std::string& sync_label,
int num_sim_layers)
: type(type), id(id), sync_label(sync_label),
num_sim_layers(num_sim_layers) {
}
MediaType type;
std::string id;
std::string sync_label;
int num_sim_layers;
};
typedef std::vector<Stream> Streams;
Streams streams;
// List of media description options in the same order that the media
// descriptions will be generated.
std::vector<MediaDescriptionOptions> media_description_options;
};
// "content" (as used in XEP-0166) descriptions for voice and video.
@ -277,7 +266,6 @@ class MediaContentDescription : public ContentDescription {
streams_.push_back(sp);
}
// Sets the CNAME of all StreamParams if it have not been set.
// This can be used to set the CNAME of legacy streams.
void SetCnameIfEmpty(const std::string& cname) {
for (cricket::StreamParamsVec::iterator it = streams_.begin();
it != streams_.end(); ++it) {
@ -469,11 +457,6 @@ class MediaSessionDescriptionFactory {
void set_data_codecs(const DataCodecs& codecs) { data_codecs_ = codecs; }
SecurePolicy secure() const { return secure_; }
void set_secure(SecurePolicy s) { secure_ = s; }
// Decides if a StreamParams shall be added to the audio and video media
// content in SessionDescription when CreateOffer and CreateAnswer is called
// even if |options| don't include a Stream. This is needed to support legacy
// applications. |add_legacy_| is true per default.
void set_add_legacy_streams(bool add_legacy) { add_legacy_ = add_legacy; }
void set_enable_encrypted_rtp_header_extensions(bool enable) {
enable_encrypted_rtp_header_extensions_ = enable;
@ -493,13 +476,15 @@ class MediaSessionDescriptionFactory {
const AudioCodecs& GetAudioCodecsForAnswer(
const RtpTransceiverDirection& offer,
const RtpTransceiverDirection& answer) const;
void GetCodecsToOffer(const SessionDescription* current_description,
const AudioCodecs& supported_audio_codecs,
const VideoCodecs& supported_video_codecs,
const DataCodecs& supported_data_codecs,
AudioCodecs* audio_codecs,
VideoCodecs* video_codecs,
DataCodecs* data_codecs) const;
void GetCodecsForOffer(const SessionDescription* current_description,
AudioCodecs* audio_codecs,
VideoCodecs* video_codecs,
DataCodecs* data_codecs) const;
void GetCodecsForAnswer(const SessionDescription* current_description,
const SessionDescription* remote_offer,
AudioCodecs* audio_codecs,
VideoCodecs* video_codecs,
DataCodecs* data_codecs) const;
void GetRtpHdrExtsToOffer(const SessionDescription* current_description,
RtpHeaderExtensions* audio_extensions,
RtpHeaderExtensions* video_extensions) const;
@ -526,7 +511,9 @@ class MediaSessionDescriptionFactory {
// error.
bool AddAudioContentForOffer(
const MediaSessionOptions& options,
const MediaDescriptionOptions& media_description_options,
const MediaSessionOptions& session_options,
const ContentInfo* current_content,
const SessionDescription* current_description,
const RtpHeaderExtensions& audio_rtp_extensions,
const AudioCodecs& audio_codecs,
@ -534,7 +521,9 @@ class MediaSessionDescriptionFactory {
SessionDescription* desc) const;
bool AddVideoContentForOffer(
const MediaSessionOptions& options,
const MediaDescriptionOptions& media_description_options,
const MediaSessionOptions& session_options,
const ContentInfo* current_content,
const SessionDescription* current_description,
const RtpHeaderExtensions& video_rtp_extensions,
const VideoCodecs& video_codecs,
@ -542,43 +531,66 @@ class MediaSessionDescriptionFactory {
SessionDescription* desc) const;
bool AddDataContentForOffer(
const MediaSessionOptions& options,
const MediaDescriptionOptions& media_description_options,
const MediaSessionOptions& session_options,
const ContentInfo* current_content,
const SessionDescription* current_description,
DataCodecs* data_codecs,
const DataCodecs& data_codecs,
StreamParamsVec* current_streams,
SessionDescription* desc) const;
bool AddAudioContentForAnswer(const SessionDescription* offer,
const MediaSessionOptions& options,
const SessionDescription* current_description,
const TransportInfo* bundle_transport,
StreamParamsVec* current_streams,
SessionDescription* answer) const;
bool AddAudioContentForAnswer(
const MediaDescriptionOptions& media_description_options,
const MediaSessionOptions& session_options,
const ContentInfo* offer_content,
const SessionDescription* offer_description,
const ContentInfo* current_content,
const SessionDescription* current_description,
const TransportInfo* bundle_transport,
const AudioCodecs& audio_codecs,
StreamParamsVec* current_streams,
SessionDescription* answer) const;
bool AddVideoContentForAnswer(const SessionDescription* offer,
const MediaSessionOptions& options,
const SessionDescription* current_description,
const TransportInfo* bundle_transport,
StreamParamsVec* current_streams,
SessionDescription* answer) const;
bool AddVideoContentForAnswer(
const MediaDescriptionOptions& media_description_options,
const MediaSessionOptions& session_options,
const ContentInfo* offer_content,
const SessionDescription* offer_description,
const ContentInfo* current_content,
const SessionDescription* current_description,
const TransportInfo* bundle_transport,
const VideoCodecs& video_codecs,
StreamParamsVec* current_streams,
SessionDescription* answer) const;
bool AddDataContentForAnswer(const SessionDescription* offer,
const MediaSessionOptions& options,
const SessionDescription* current_description,
const TransportInfo* bundle_transport,
StreamParamsVec* current_streams,
SessionDescription* answer) const;
bool AddDataContentForAnswer(
const MediaDescriptionOptions& media_description_options,
const MediaSessionOptions& session_options,
const ContentInfo* offer_content,
const SessionDescription* offer_description,
const ContentInfo* current_content,
const SessionDescription* current_description,
const TransportInfo* bundle_transport,
const DataCodecs& data_codecs,
StreamParamsVec* current_streams,
SessionDescription* answer) const;
void ComputeAudioCodecsIntersectionAndUnion();
AudioCodecs audio_send_codecs_;
AudioCodecs audio_recv_codecs_;
// Intersection of send and recv.
AudioCodecs audio_sendrecv_codecs_;
// Union of send and recv.
AudioCodecs all_audio_codecs_;
RtpHeaderExtensions audio_rtp_extensions_;
VideoCodecs video_codecs_;
RtpHeaderExtensions video_rtp_extensions_;
DataCodecs data_codecs_;
SecurePolicy secure_;
bool add_legacy_;
bool enable_encrypted_rtp_header_extensions_ = false;
// TODO(zhihuang): Rename secure_ to sdec_policy_; rename the related getter
// and setter.
SecurePolicy secure_ = SEC_DISABLED;
std::string lang_;
const TransportDescriptionFactory* transport_desc_factory_;
};

File diff suppressed because it is too large Load Diff

View File

@ -133,32 +133,45 @@ bool IsValidOfferToReceiveMedia(int value) {
(value <= Options::kMaxOfferToReceiveMedia);
}
// Add the stream and RTP data channel info to |session_options|.
void AddSendStreams(
cricket::MediaSessionOptions* session_options,
// Add options to |[audio/video]_media_description_options| from |senders|.
void AddRtpSenderOptions(
const std::vector<rtc::scoped_refptr<
RtpSenderProxyWithInternal<RtpSenderInternal>>>& senders,
const std::map<std::string, rtc::scoped_refptr<DataChannel>>&
rtp_data_channels) {
session_options->streams.clear();
cricket::MediaDescriptionOptions* audio_media_description_options,
cricket::MediaDescriptionOptions* video_media_description_options) {
for (const auto& sender : senders) {
session_options->AddSendStream(sender->media_type(), sender->id(),
sender->internal()->stream_id());
if (sender->media_type() == cricket::MEDIA_TYPE_AUDIO) {
if (audio_media_description_options) {
audio_media_description_options->AddAudioSender(
sender->id(), sender->internal()->stream_id());
}
} else {
RTC_DCHECK(sender->media_type() == cricket::MEDIA_TYPE_VIDEO);
if (video_media_description_options) {
video_media_description_options->AddVideoSender(
sender->id(), sender->internal()->stream_id(), 1);
}
}
}
}
// Add options to |session_options| from |rtp_data_channels|.
void AddRtpDataChannelOptions(
const std::map<std::string, rtc::scoped_refptr<DataChannel>>&
rtp_data_channels,
cricket::MediaDescriptionOptions* data_media_description_options) {
if (!data_media_description_options) {
return;
}
// Check for data channels.
for (const auto& kv : rtp_data_channels) {
const DataChannel* channel = kv.second;
if (channel->state() == DataChannel::kConnecting ||
channel->state() == DataChannel::kOpen) {
// |streamid| and |sync_label| are both set to the DataChannel label
// here so they can be signaled the same way as MediaStreams and Tracks.
// For MediaStreams, the sync_label is the MediaStream label and the
// track label is the same as |streamid|.
const std::string& streamid = channel->label();
const std::string& sync_label = channel->label();
session_options->AddSendStream(cricket::MEDIA_TYPE_DATA, streamid,
sync_label);
// Legacy RTP data channels are signaled with the track/stream ID set to
// the data channel's label.
data_media_description_options->AddRtpDataChannel(channel->label(),
channel->label());
}
}
}
@ -314,92 +327,62 @@ std::string GenerateRtcpCname() {
return cname;
}
bool ExtractMediaSessionOptions(
const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options,
bool is_offer,
cricket::MediaSessionOptions* session_options) {
typedef PeerConnectionInterface::RTCOfferAnswerOptions RTCOfferAnswerOptions;
if (!IsValidOfferToReceiveMedia(rtc_options.offer_to_receive_audio) ||
!IsValidOfferToReceiveMedia(rtc_options.offer_to_receive_video)) {
return false;
}
// If constraints don't prevent us, we always accept video.
if (rtc_options.offer_to_receive_audio != RTCOfferAnswerOptions::kUndefined) {
session_options->recv_audio = (rtc_options.offer_to_receive_audio > 0);
} else {
session_options->recv_audio = true;
}
// For offers, we only offer video if we have it or it's forced by options.
// For answers, we will always accept video (if offered).
if (rtc_options.offer_to_receive_video != RTCOfferAnswerOptions::kUndefined) {
session_options->recv_video = (rtc_options.offer_to_receive_video > 0);
} else if (is_offer) {
session_options->recv_video = false;
} else {
session_options->recv_video = true;
}
session_options->vad_enabled = rtc_options.voice_activity_detection;
session_options->bundle_enabled = rtc_options.use_rtp_mux;
for (auto& kv : session_options->transport_options) {
kv.second.ice_restart = rtc_options.ice_restart;
}
return true;
bool ValidateOfferAnswerOptions(
const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options) {
return IsValidOfferToReceiveMedia(rtc_options.offer_to_receive_audio) &&
IsValidOfferToReceiveMedia(rtc_options.offer_to_receive_video);
}
bool ParseConstraintsForAnswer(const MediaConstraintsInterface* constraints,
cricket::MediaSessionOptions* session_options) {
bool value = false;
size_t mandatory_constraints_satisfied = 0;
// kOfferToReceiveAudio defaults to true according to spec.
if (!FindConstraint(constraints,
MediaConstraintsInterface::kOfferToReceiveAudio, &value,
&mandatory_constraints_satisfied) ||
value) {
session_options->recv_audio = true;
}
// kOfferToReceiveVideo defaults to false according to spec. But
// if it is an answer and video is offered, we should still accept video
// per default.
value = false;
if (!FindConstraint(constraints,
MediaConstraintsInterface::kOfferToReceiveVideo, &value,
&mandatory_constraints_satisfied) ||
value) {
session_options->recv_video = true;
}
if (FindConstraint(constraints,
MediaConstraintsInterface::kVoiceActivityDetection, &value,
&mandatory_constraints_satisfied)) {
session_options->vad_enabled = value;
}
if (FindConstraint(constraints, MediaConstraintsInterface::kUseRtpMux, &value,
&mandatory_constraints_satisfied)) {
session_options->bundle_enabled = value;
} else {
// kUseRtpMux defaults to true according to spec.
session_options->bundle_enabled = true;
}
bool ice_restart = false;
if (FindConstraint(constraints, MediaConstraintsInterface::kIceRestart,
&value, &mandatory_constraints_satisfied)) {
// kIceRestart defaults to false according to spec.
ice_restart = true;
}
for (auto& kv : session_options->transport_options) {
kv.second.ice_restart = ice_restart;
}
// From |rtc_options|, fill parts of |session_options| shared by all generated
// m= sections (in other words, nothing that involves a map/array).
void ExtractSharedMediaSessionOptions(
const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options,
cricket::MediaSessionOptions* session_options) {
session_options->vad_enabled = rtc_options.voice_activity_detection;
session_options->bundle_enabled = rtc_options.use_rtp_mux;
}
bool ConvertConstraintsToOfferAnswerOptions(
const MediaConstraintsInterface* constraints,
PeerConnectionInterface::RTCOfferAnswerOptions* offer_answer_options) {
if (!constraints) {
return true;
}
bool value = false;
size_t mandatory_constraints_satisfied = 0;
if (FindConstraint(constraints,
MediaConstraintsInterface::kOfferToReceiveAudio, &value,
&mandatory_constraints_satisfied)) {
offer_answer_options->offer_to_receive_audio =
value ? PeerConnectionInterface::RTCOfferAnswerOptions::
kOfferToReceiveMediaTrue
: 0;
}
if (FindConstraint(constraints,
MediaConstraintsInterface::kOfferToReceiveVideo, &value,
&mandatory_constraints_satisfied)) {
offer_answer_options->offer_to_receive_video =
value ? PeerConnectionInterface::RTCOfferAnswerOptions::
kOfferToReceiveMediaTrue
: 0;
}
if (FindConstraint(constraints,
MediaConstraintsInterface::kVoiceActivityDetection, &value,
&mandatory_constraints_satisfied)) {
offer_answer_options->voice_activity_detection = value;
}
if (FindConstraint(constraints, MediaConstraintsInterface::kUseRtpMux, &value,
&mandatory_constraints_satisfied)) {
offer_answer_options->use_rtp_mux = value;
}
if (FindConstraint(constraints, MediaConstraintsInterface::kIceRestart,
&value, &mandatory_constraints_satisfied)) {
offer_answer_options->ice_restart = value;
}
return mandatory_constraints_satisfied == constraints->GetMandatory().size();
}
@ -842,49 +825,17 @@ void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer,
LOG(LS_ERROR) << "CreateOffer - observer is NULL.";
return;
}
RTCOfferAnswerOptions options;
PeerConnectionInterface::RTCOfferAnswerOptions offer_answer_options;
// Always create an offer even if |ConvertConstraintsToOfferAnswerOptions|
// returns false for now. Because |ConvertConstraintsToOfferAnswerOptions|
// compares the mandatory fields parsed with the mandatory fields added in the
// |constraints| and some downstream applications might create offers with
// mandatory fields which would not be parsed in the helper method. For
// example, in Chromium/remoting, |kEnableDtlsSrtp| is added to the
// |constraints| as a mandatory field but it is not parsed.
ConvertConstraintsToOfferAnswerOptions(constraints, &offer_answer_options);
bool value;
size_t mandatory_constraints = 0;
if (FindConstraint(constraints,
MediaConstraintsInterface::kOfferToReceiveAudio,
&value,
&mandatory_constraints)) {
options.offer_to_receive_audio =
value ? RTCOfferAnswerOptions::kOfferToReceiveMediaTrue : 0;
}
if (FindConstraint(constraints,
MediaConstraintsInterface::kOfferToReceiveVideo,
&value,
&mandatory_constraints)) {
options.offer_to_receive_video =
value ? RTCOfferAnswerOptions::kOfferToReceiveMediaTrue : 0;
}
if (FindConstraint(constraints,
MediaConstraintsInterface::kVoiceActivityDetection,
&value,
&mandatory_constraints)) {
options.voice_activity_detection = value;
}
if (FindConstraint(constraints,
MediaConstraintsInterface::kIceRestart,
&value,
&mandatory_constraints)) {
options.ice_restart = value;
}
if (FindConstraint(constraints,
MediaConstraintsInterface::kUseRtpMux,
&value,
&mandatory_constraints)) {
options.use_rtp_mux = value;
}
CreateOffer(observer, options);
CreateOffer(observer, offer_answer_options);
}
void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer,
@ -895,14 +846,15 @@ void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer,
return;
}
cricket::MediaSessionOptions session_options;
if (!GetOptionsForOffer(options, &session_options)) {
if (!ValidateOfferAnswerOptions(options)) {
std::string error = "CreateOffer called with invalid options.";
LOG(LS_ERROR) << error;
PostCreateSessionDescriptionFailure(observer, error);
return;
}
cricket::MediaSessionOptions session_options;
GetOptionsForOffer(options, &session_options);
session_->CreateOffer(observer, options, session_options);
}
@ -915,14 +867,26 @@ void PeerConnection::CreateAnswer(
return;
}
cricket::MediaSessionOptions session_options;
if (!GetOptionsForAnswer(constraints, &session_options)) {
if (!session_->remote_description() ||
session_->remote_description()->type() !=
SessionDescriptionInterface::kOffer) {
std::string error = "CreateAnswer called without remote offer.";
LOG(LS_ERROR) << error;
PostCreateSessionDescriptionFailure(observer, error);
return;
}
PeerConnectionInterface::RTCOfferAnswerOptions offer_answer_options;
if (!ConvertConstraintsToOfferAnswerOptions(constraints,
&offer_answer_options)) {
std::string error = "CreateAnswer called with invalid constraints.";
LOG(LS_ERROR) << error;
PostCreateSessionDescriptionFailure(observer, error);
return;
}
cricket::MediaSessionOptions session_options;
GetOptionsForAnswer(offer_answer_options, &session_options);
session_->CreateAnswer(observer, session_options);
}
@ -935,12 +899,7 @@ void PeerConnection::CreateAnswer(CreateSessionDescriptionObserver* observer,
}
cricket::MediaSessionOptions session_options;
if (!GetOptionsForAnswer(options, &session_options)) {
std::string error = "CreateAnswer called with invalid options.";
LOG(LS_ERROR) << error;
PostCreateSessionDescriptionFailure(observer, error);
return;
}
GetOptionsForAnswer(options, &session_options);
session_->CreateAnswer(observer, session_options);
}
@ -1698,121 +1657,242 @@ void PeerConnection::PostCreateSessionDescriptionFailure(
MSG_CREATE_SESSIONDESCRIPTION_FAILED, msg);
}
bool PeerConnection::GetOptionsForOffer(
void PeerConnection::GetOptionsForOffer(
const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options,
cricket::MediaSessionOptions* session_options) {
// TODO(deadbeef): Once we have transceivers, enumerate them here instead of
// ContentInfos.
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);
// By default, generate sendrecv/recvonly m= sections.
bool recv_audio = true;
bool recv_video = true;
// By default, only offer a new m= section if we have media to send with it.
bool offer_new_audio_description = send_audio;
bool offer_new_video_description = send_video;
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);
offer_new_audio_description =
offer_new_audio_description || (rtc_options.offer_to_receive_audio > 0);
}
if (rtc_options.offer_to_receive_video != RTCOfferAnswerOptions::kUndefined) {
recv_video = (rtc_options.offer_to_receive_video > 0);
offer_new_video_description =
offer_new_video_description || (rtc_options.offer_to_receive_video > 0);
}
rtc::Optional<size_t> audio_index;
rtc::Optional<size_t> video_index;
rtc::Optional<size_t> data_index;
// If a current description exists, generate m= sections in the same order,
// using the first audio/video/data section that appears and rejecting
// extraneous ones.
if (session_->local_description()) {
for (const cricket::ContentInfo& content :
session_->local_description()->description()->contents()) {
session_options->transport_options[content.name] =
cricket::TransportOptions();
}
}
session_options->enable_ice_renomination =
configuration_.enable_ice_renomination;
if (!ExtractMediaSessionOptions(rtc_options, true, session_options)) {
return false;
GenerateMediaDescriptionOptions(
session_->local_description(),
cricket::RtpTransceiverDirection(send_audio, recv_audio),
cricket::RtpTransceiverDirection(send_video, recv_video), &audio_index,
&video_index, &data_index, session_options);
}
AddSendStreams(session_options, senders_, rtp_data_channels_);
// Offer to receive audio/video if the constraint is not set and there are
// send streams, or we're currently receiving.
if (rtc_options.offer_to_receive_audio == RTCOfferAnswerOptions::kUndefined) {
session_options->recv_audio =
session_options->HasSendMediaStream(cricket::MEDIA_TYPE_AUDIO) ||
!remote_audio_tracks_.empty();
// Add audio/video/data m= sections to the end if needed.
if (!audio_index && offer_new_audio_description) {
session_options->media_description_options.push_back(
cricket::MediaDescriptionOptions(
cricket::MEDIA_TYPE_AUDIO, cricket::CN_AUDIO,
cricket::RtpTransceiverDirection(send_audio, recv_audio), false));
audio_index = rtc::Optional<size_t>(
session_options->media_description_options.size() - 1);
}
if (rtc_options.offer_to_receive_video == RTCOfferAnswerOptions::kUndefined) {
session_options->recv_video =
session_options->HasSendMediaStream(cricket::MEDIA_TYPE_VIDEO) ||
!remote_video_tracks_.empty();
if (!video_index && offer_new_video_description) {
session_options->media_description_options.push_back(
cricket::MediaDescriptionOptions(
cricket::MEDIA_TYPE_VIDEO, cricket::CN_VIDEO,
cricket::RtpTransceiverDirection(send_video, recv_video), false));
video_index = rtc::Optional<size_t>(
session_options->media_description_options.size() - 1);
}
if (!data_index && offer_new_data_description) {
session_options->media_description_options.push_back(
cricket::MediaDescriptionOptions(
cricket::MEDIA_TYPE_DATA, cricket::CN_DATA,
cricket::RtpTransceiverDirection(true, true), false));
data_index = rtc::Optional<size_t>(
session_options->media_description_options.size() - 1);
}
cricket::MediaDescriptionOptions* audio_media_description_options =
!audio_index ? nullptr
: &session_options->media_description_options[*audio_index];
cricket::MediaDescriptionOptions* video_media_description_options =
!video_index ? nullptr
: &session_options->media_description_options[*video_index];
cricket::MediaDescriptionOptions* data_media_description_options =
!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(senders_, audio_media_description_options,
video_media_description_options);
AddRtpDataChannelOptions(rtp_data_channels_, data_media_description_options);
// Intentionally unset the data channel type for RTP data channel with the
// second condition. Otherwise the RTP data channels would be successfully
// negotiated by default and the unit tests in WebRtcDataBrowserTest will fail
// when building with chromium. We want to leave RTP data channels broken, so
// people won't try to use them.
if (HasDataChannels() && session_->data_channel_type() != cricket::DCT_RTP) {
if (!rtp_data_channels_.empty() ||
session_->data_channel_type() != cricket::DCT_RTP) {
session_options->data_channel_type = session_->data_channel_type();
}
session_options->bundle_enabled =
session_options->bundle_enabled &&
(session_options->has_audio() || session_options->has_video() ||
session_options->has_data());
session_options->rtcp_cname = rtcp_cname_;
session_options->crypto_options = factory_->options().crypto_options;
return true;
}
void PeerConnection::InitializeOptionsForAnswer(
void PeerConnection::GetOptionsForAnswer(
const RTCOfferAnswerOptions& rtc_options,
cricket::MediaSessionOptions* session_options) {
session_options->recv_audio = false;
session_options->recv_video = false;
session_options->enable_ice_renomination =
configuration_.enable_ice_renomination;
}
ExtractSharedMediaSessionOptions(rtc_options, session_options);
void PeerConnection::FinishOptionsForAnswer(
cricket::MediaSessionOptions* session_options) {
// TODO(deadbeef): Once we have transceivers, enumerate them here instead of
// ContentInfos.
if (session_->remote_description()) {
// Initialize the transport_options map.
for (const cricket::ContentInfo& content :
session_->remote_description()->description()->contents()) {
session_options->transport_options[content.name] =
cricket::TransportOptions();
}
// Figure out transceiver directional preferences.
bool send_audio = HasRtpSender(cricket::MEDIA_TYPE_AUDIO);
bool send_video = HasRtpSender(cricket::MEDIA_TYPE_VIDEO);
// By default, generate sendrecv/recvonly m= sections. The direction is also
// restricted by the direction in the offer.
bool recv_audio = true;
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);
}
AddSendStreams(session_options, senders_, rtp_data_channels_);
// RTP data channel is handled in MediaSessionOptions::AddStream. SCTP streams
// are not signaled in the SDP so does not go through that path and must be
// handled here.
if (rtc_options.offer_to_receive_video != RTCOfferAnswerOptions::kUndefined) {
recv_video = (rtc_options.offer_to_receive_video > 0);
}
rtc::Optional<size_t> audio_index;
rtc::Optional<size_t> video_index;
rtc::Optional<size_t> data_index;
// There should be a pending remote description that's an offer...
RTC_DCHECK(session_->remote_description());
RTC_DCHECK(session_->remote_description()->type() ==
SessionDescriptionInterface::kOffer);
// Generate m= sections that match those in the offer.
// Note that mediasession.cc will handle intersection our preferred direction
// with the offered direction.
GenerateMediaDescriptionOptions(
session_->remote_description(),
cricket::RtpTransceiverDirection(send_audio, recv_audio),
cricket::RtpTransceiverDirection(send_video, recv_video), &audio_index,
&video_index, &data_index, session_options);
cricket::MediaDescriptionOptions* audio_media_description_options =
!audio_index ? nullptr
: &session_options->media_description_options[*audio_index];
cricket::MediaDescriptionOptions* video_media_description_options =
!video_index ? nullptr
: &session_options->media_description_options[*video_index];
cricket::MediaDescriptionOptions* data_media_description_options =
!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(senders_, audio_media_description_options,
video_media_description_options);
AddRtpDataChannelOptions(rtp_data_channels_, data_media_description_options);
// Intentionally unset the data channel type for RTP data channel. Otherwise
// the RTP data channels would be successfully negotiated by default and the
// unit tests in WebRtcDataBrowserTest will fail when building with chromium.
// We want to leave RTP data channels broken, so people won't try to use them.
if (session_->data_channel_type() != cricket::DCT_RTP) {
if (!rtp_data_channels_.empty() ||
session_->data_channel_type() != cricket::DCT_RTP) {
session_options->data_channel_type = session_->data_channel_type();
}
session_options->bundle_enabled =
session_options->bundle_enabled &&
(session_options->has_audio() || session_options->has_video() ||
session_options->has_data());
session_options->rtcp_cname = rtcp_cname_;
session_options->crypto_options = factory_->options().crypto_options;
}
bool PeerConnection::GetOptionsForAnswer(
const MediaConstraintsInterface* constraints,
void PeerConnection::GenerateMediaDescriptionOptions(
const SessionDescriptionInterface* session_desc,
cricket::RtpTransceiverDirection audio_direction,
cricket::RtpTransceiverDirection video_direction,
rtc::Optional<size_t>* audio_index,
rtc::Optional<size_t>* video_index,
rtc::Optional<size_t>* data_index,
cricket::MediaSessionOptions* session_options) {
InitializeOptionsForAnswer(session_options);
if (!ParseConstraintsForAnswer(constraints, session_options)) {
return false;
for (const cricket::ContentInfo& content :
session_desc->description()->contents()) {
if (IsAudioContent(&content)) {
// If we already have an audio m= section, reject this extra one.
if (*audio_index) {
session_options->media_description_options.push_back(
cricket::MediaDescriptionOptions(
cricket::MEDIA_TYPE_AUDIO, content.name,
cricket::RtpTransceiverDirection(false, false), true));
} else {
session_options->media_description_options.push_back(
cricket::MediaDescriptionOptions(
cricket::MEDIA_TYPE_AUDIO, content.name, audio_direction,
!audio_direction.send && !audio_direction.recv));
*audio_index = rtc::Optional<size_t>(
session_options->media_description_options.size() - 1);
}
} else if (IsVideoContent(&content)) {
// If we already have an video m= section, reject this extra one.
if (*video_index) {
session_options->media_description_options.push_back(
cricket::MediaDescriptionOptions(
cricket::MEDIA_TYPE_VIDEO, content.name,
cricket::RtpTransceiverDirection(false, false), true));
} else {
session_options->media_description_options.push_back(
cricket::MediaDescriptionOptions(
cricket::MEDIA_TYPE_VIDEO, content.name, video_direction,
!video_direction.send && !video_direction.recv));
*video_index = rtc::Optional<size_t>(
session_options->media_description_options.size() - 1);
}
} else {
RTC_DCHECK(IsDataContent(&content));
// If we already have an data m= section, reject this extra one.
if (*data_index) {
session_options->media_description_options.push_back(
cricket::MediaDescriptionOptions(
cricket::MEDIA_TYPE_DATA, content.name,
cricket::RtpTransceiverDirection(false, false), true));
} else {
session_options->media_description_options.push_back(
cricket::MediaDescriptionOptions(
cricket::MEDIA_TYPE_DATA, content.name,
// Direction for data sections is meaningless, but legacy
// endpoints might expect sendrecv.
cricket::RtpTransceiverDirection(true, true), false));
*data_index = rtc::Optional<size_t>(
session_options->media_description_options.size() - 1);
}
}
}
session_options->rtcp_cname = rtcp_cname_;
FinishOptionsForAnswer(session_options);
return true;
}
bool PeerConnection::GetOptionsForAnswer(
const RTCOfferAnswerOptions& options,
cricket::MediaSessionOptions* session_options) {
InitializeOptionsForAnswer(session_options);
if (!ExtractMediaSessionOptions(options, false, session_options)) {
return false;
}
session_options->rtcp_cname = rtcp_cname_;
FinishOptionsForAnswer(session_options);
return true;
}
void PeerConnection::RemoveTracks(cricket::MediaType media_type) {
@ -2285,6 +2365,15 @@ void PeerConnection::OnDataChannelOpenMessage(
observer_->OnDataChannel(std::move(proxy_channel));
}
bool PeerConnection::HasRtpSender(cricket::MediaType type) const {
return std::find_if(
senders_.begin(), senders_.end(),
[type](const rtc::scoped_refptr<
RtpSenderProxyWithInternal<RtpSenderInternal>>& sender) {
return sender->media_type() == type;
}) != senders_.end();
}
RtpSenderInternal* PeerConnection::FindSenderById(const std::string& id) {
auto it = std::find_if(
senders_.begin(), senders_.end(),

View File

@ -32,28 +32,12 @@ class MediaStreamObserver;
class VideoRtpReceiver;
class RtcEventLog;
// Populates |session_options| from |rtc_options|, and returns true if options
// are valid.
// |session_options|->transport_options map entries must exist in order for
// them to be populated from |rtc_options|.
bool ExtractMediaSessionOptions(
// TODO(zhihuang): Remove this declaration when the WebRtcSession tests don't
// need it.
void ExtractSharedMediaSessionOptions(
const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options,
bool is_offer,
cricket::MediaSessionOptions* session_options);
// Populates |session_options| from |constraints|, and returns true if all
// mandatory constraints are satisfied.
// Assumes that |session_options|->transport_options map entries exist.
// Will also set defaults if corresponding constraints are not present:
// recv_audio=true, recv_video=true, bundle_enabled=true.
// Other fields will be left with existing values.
//
// Deprecated. Will be removed once callers that use constraints are gone.
// TODO(hta): Remove when callers are gone.
// https://bugs.chromium.org/p/webrtc/issues/detail?id=5617
bool ParseConstraintsForAnswer(const MediaConstraintsInterface* constraints,
cricket::MediaSessionOptions* session_options);
// PeerConnection implements the PeerConnectionInterface interface.
// It uses WebRtcSession to implement the PeerConnection functionality.
class PeerConnection : public PeerConnectionInterface,
@ -244,26 +228,24 @@ class PeerConnection : public PeerConnectionInterface,
// Returns a MediaSessionOptions struct with options decided by |options|,
// the local MediaStreams and DataChannels.
virtual bool GetOptionsForOffer(
void GetOptionsForOffer(
const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options,
cricket::MediaSessionOptions* session_options);
// Returns a MediaSessionOptions struct with options decided by
// |constraints|, the local MediaStreams and DataChannels.
// Deprecated, use version without constraints.
virtual bool GetOptionsForAnswer(
const MediaConstraintsInterface* constraints,
cricket::MediaSessionOptions* session_options);
virtual bool GetOptionsForAnswer(
const RTCOfferAnswerOptions& options,
cricket::MediaSessionOptions* session_options);
void GetOptionsForAnswer(const RTCOfferAnswerOptions& options,
cricket::MediaSessionOptions* session_options);
void InitializeOptionsForAnswer(
cricket::MediaSessionOptions* session_options);
// Helper function for options processing.
// Deprecated.
virtual void FinishOptionsForAnswer(
// Generates MediaDescriptionOptions for the |session_opts| based on existing
// local description or remote description.
void GenerateMediaDescriptionOptions(
const SessionDescriptionInterface* session_desc,
cricket::RtpTransceiverDirection audio_direction,
cricket::RtpTransceiverDirection video_direction,
rtc::Optional<size_t>* audio_index,
rtc::Optional<size_t>* video_index,
rtc::Optional<size_t>* data_index,
cricket::MediaSessionOptions* session_options);
// Remove all local and remote tracks of type |media_type|.
@ -361,6 +343,7 @@ class PeerConnection : public PeerConnectionInterface,
void OnDataChannelOpenMessage(const std::string& label,
const InternalDataChannelInit& config);
bool HasRtpSender(cricket::MediaType type) const;
RtpSenderInternal* FindSenderById(const std::string& id);
std::vector<rtc::scoped_refptr<

View File

@ -1184,6 +1184,57 @@ class PeerConnectionInterfaceTest : public testing::Test {
return audio_desc->streams()[0].cname;
}
std::unique_ptr<SessionDescriptionInterface> CreateOfferWithOptions(
const RTCOfferAnswerOptions& offer_answer_options) {
RTC_DCHECK(pc_);
rtc::scoped_refptr<MockCreateSessionDescriptionObserver> observer(
new rtc::RefCountedObject<MockCreateSessionDescriptionObserver>());
pc_->CreateOffer(observer, offer_answer_options);
EXPECT_EQ_WAIT(true, observer->called(), kTimeout);
return observer->MoveDescription();
}
void CreateOfferWithOptionsAsRemoteDescription(
std::unique_ptr<SessionDescriptionInterface>* desc,
const RTCOfferAnswerOptions& offer_answer_options) {
*desc = CreateOfferWithOptions(offer_answer_options);
ASSERT_TRUE(desc != nullptr);
std::string sdp;
EXPECT_TRUE((*desc)->ToString(&sdp));
SessionDescriptionInterface* remote_offer =
webrtc::CreateSessionDescription(SessionDescriptionInterface::kOffer,
sdp, NULL);
EXPECT_TRUE(DoSetRemoteDescription(remote_offer));
EXPECT_EQ(PeerConnectionInterface::kHaveRemoteOffer, observer_.state_);
}
void CreateOfferWithOptionsAsLocalDescription(
std::unique_ptr<SessionDescriptionInterface>* desc,
const RTCOfferAnswerOptions& offer_answer_options) {
*desc = CreateOfferWithOptions(offer_answer_options);
ASSERT_TRUE(desc != nullptr);
std::string sdp;
EXPECT_TRUE((*desc)->ToString(&sdp));
SessionDescriptionInterface* new_offer = webrtc::CreateSessionDescription(
SessionDescriptionInterface::kOffer, sdp, NULL);
EXPECT_TRUE(DoSetLocalDescription(new_offer));
EXPECT_EQ(PeerConnectionInterface::kHaveLocalOffer, observer_.state_);
}
bool HasCNCodecs(const cricket::ContentInfo* content) {
const cricket::ContentDescription* description = content->description;
RTC_DCHECK(description);
const cricket::AudioContentDescription* audio_content_desc =
static_cast<const cricket::AudioContentDescription*>(description);
RTC_DCHECK(audio_content_desc);
for (size_t i = 0; i < audio_content_desc->codecs().size(); ++i) {
if (audio_content_desc->codecs()[i].name == "CN")
return true;
}
return false;
}
std::unique_ptr<rtc::VirtualSocketServer> vss_;
rtc::AutoSocketServerThread main_;
cricket::FakePortAllocator* port_allocator_ = nullptr;
@ -3494,6 +3545,224 @@ TEST_F(PeerConnectionInterfaceTest, SetBitrateCurrentLessThanImplicitMin) {
EXPECT_TRUE(pc_->SetBitrate(bitrate).ok());
}
// The following tests verify that the offer can be created correctly.
TEST_F(PeerConnectionInterfaceTest,
CreateOfferFailsWithInvalidOfferToReceiveAudio) {
RTCOfferAnswerOptions rtc_options;
// Setting offer_to_receive_audio to a value lower than kUndefined or greater
// than kMaxOfferToReceiveMedia should be treated as invalid.
rtc_options.offer_to_receive_audio = RTCOfferAnswerOptions::kUndefined - 1;
CreatePeerConnection();
EXPECT_FALSE(CreateOfferWithOptions(rtc_options));
rtc_options.offer_to_receive_audio =
RTCOfferAnswerOptions::kMaxOfferToReceiveMedia + 1;
EXPECT_FALSE(CreateOfferWithOptions(rtc_options));
}
TEST_F(PeerConnectionInterfaceTest,
CreateOfferFailsWithInvalidOfferToReceiveVideo) {
RTCOfferAnswerOptions rtc_options;
// Setting offer_to_receive_video to a value lower than kUndefined or greater
// than kMaxOfferToReceiveMedia should be treated as invalid.
rtc_options.offer_to_receive_video = RTCOfferAnswerOptions::kUndefined - 1;
CreatePeerConnection();
EXPECT_FALSE(CreateOfferWithOptions(rtc_options));
rtc_options.offer_to_receive_video =
RTCOfferAnswerOptions::kMaxOfferToReceiveMedia + 1;
EXPECT_FALSE(CreateOfferWithOptions(rtc_options));
}
// Test that the audio and video content will be added to an offer if both
// |offer_to_receive_audio| and |offer_to_receive_video| options are 1.
TEST_F(PeerConnectionInterfaceTest, CreateOfferWithAudioVideoOptions) {
RTCOfferAnswerOptions rtc_options;
rtc_options.offer_to_receive_audio = 1;
rtc_options.offer_to_receive_video = 1;
std::unique_ptr<SessionDescriptionInterface> offer;
CreatePeerConnection();
offer = CreateOfferWithOptions(rtc_options);
ASSERT_TRUE(offer);
EXPECT_NE(nullptr, GetFirstAudioContent(offer->description()));
EXPECT_NE(nullptr, GetFirstVideoContent(offer->description()));
}
// Test that only audio content will be added to the offer if only
// |offer_to_receive_audio| options is 1.
TEST_F(PeerConnectionInterfaceTest, CreateOfferWithAudioOnlyOptions) {
RTCOfferAnswerOptions rtc_options;
rtc_options.offer_to_receive_audio = 1;
rtc_options.offer_to_receive_video = 0;
std::unique_ptr<SessionDescriptionInterface> offer;
CreatePeerConnection();
offer = CreateOfferWithOptions(rtc_options);
ASSERT_TRUE(offer);
EXPECT_NE(nullptr, GetFirstAudioContent(offer->description()));
EXPECT_EQ(nullptr, GetFirstVideoContent(offer->description()));
}
// Test that only video content will be added if only |offer_to_receive_video|
// options is 1.
TEST_F(PeerConnectionInterfaceTest, CreateOfferWithVideoOnlyOptions) {
RTCOfferAnswerOptions rtc_options;
rtc_options.offer_to_receive_audio = 0;
rtc_options.offer_to_receive_video = 1;
std::unique_ptr<SessionDescriptionInterface> offer;
CreatePeerConnection();
offer = CreateOfferWithOptions(rtc_options);
ASSERT_TRUE(offer);
EXPECT_EQ(nullptr, GetFirstAudioContent(offer->description()));
EXPECT_NE(nullptr, GetFirstVideoContent(offer->description()));
}
// Test that if |voice_activity_detection| is false, no CN codec is added to the
// offer.
TEST_F(PeerConnectionInterfaceTest, CreateOfferWithVADOptions) {
RTCOfferAnswerOptions rtc_options;
rtc_options.offer_to_receive_audio = 1;
rtc_options.offer_to_receive_video = 0;
std::unique_ptr<SessionDescriptionInterface> offer;
CreatePeerConnection();
offer = CreateOfferWithOptions(rtc_options);
ASSERT_TRUE(offer);
const cricket::ContentInfo* audio_content =
offer->description()->GetContentByName(cricket::CN_AUDIO);
ASSERT_TRUE(audio_content);
// |voice_activity_detection| is true by default.
EXPECT_TRUE(HasCNCodecs(audio_content));
rtc_options.voice_activity_detection = false;
CreatePeerConnection();
offer = CreateOfferWithOptions(rtc_options);
ASSERT_TRUE(offer);
audio_content = offer->description()->GetContentByName(cricket::CN_AUDIO);
ASSERT_TRUE(audio_content);
EXPECT_FALSE(HasCNCodecs(audio_content));
}
// Test that no media content will be added to the offer if using default
// RTCOfferAnswerOptions.
TEST_F(PeerConnectionInterfaceTest, CreateOfferWithDefaultOfferAnswerOptions) {
RTCOfferAnswerOptions rtc_options;
std::unique_ptr<SessionDescriptionInterface> offer;
CreatePeerConnection();
offer = CreateOfferWithOptions(rtc_options);
ASSERT_TRUE(offer);
EXPECT_EQ(nullptr, GetFirstAudioContent(offer->description()));
EXPECT_EQ(nullptr, GetFirstVideoContent(offer->description()));
}
// Test that if |ice_restart| is true, the ufrag/pwd will change, otherwise
// ufrag/pwd will be the same in the new offer.
TEST_F(PeerConnectionInterfaceTest, CreateOfferWithIceRestart) {
RTCOfferAnswerOptions rtc_options;
rtc_options.ice_restart = false;
rtc_options.offer_to_receive_audio = 1;
std::unique_ptr<SessionDescriptionInterface> offer;
CreatePeerConnection();
CreateOfferWithOptionsAsLocalDescription(&offer, rtc_options);
auto ufrag1 = offer->description()
->GetTransportInfoByName(cricket::CN_AUDIO)
->description.ice_ufrag;
auto pwd1 = offer->description()
->GetTransportInfoByName(cricket::CN_AUDIO)
->description.ice_pwd;
// |ice_restart| is false, the ufrag/pwd shouldn't change.
CreateOfferWithOptionsAsLocalDescription(&offer, rtc_options);
auto ufrag2 = offer->description()
->GetTransportInfoByName(cricket::CN_AUDIO)
->description.ice_ufrag;
auto pwd2 = offer->description()
->GetTransportInfoByName(cricket::CN_AUDIO)
->description.ice_pwd;
// |ice_restart| is true, the ufrag/pwd should change.
rtc_options.ice_restart = true;
CreateOfferWithOptionsAsLocalDescription(&offer, rtc_options);
auto ufrag3 = offer->description()
->GetTransportInfoByName(cricket::CN_AUDIO)
->description.ice_ufrag;
auto pwd3 = offer->description()
->GetTransportInfoByName(cricket::CN_AUDIO)
->description.ice_pwd;
EXPECT_EQ(ufrag1, ufrag2);
EXPECT_EQ(pwd1, pwd2);
EXPECT_NE(ufrag2, ufrag3);
EXPECT_NE(pwd2, pwd3);
}
// Test that if |use_rtp_mux| is true, the bundling will be enabled in the
// offer; if it is false, there won't be any bundle group in the offer.
TEST_F(PeerConnectionInterfaceTest, CreateOfferWithRtpMux) {
RTCOfferAnswerOptions rtc_options;
rtc_options.offer_to_receive_audio = 1;
rtc_options.offer_to_receive_video = 1;
std::unique_ptr<SessionDescriptionInterface> offer;
CreatePeerConnection();
rtc_options.use_rtp_mux = true;
offer = CreateOfferWithOptions(rtc_options);
ASSERT_TRUE(offer);
EXPECT_NE(nullptr, GetFirstAudioContent(offer->description()));
EXPECT_NE(nullptr, GetFirstVideoContent(offer->description()));
EXPECT_TRUE(offer->description()->HasGroup(cricket::GROUP_TYPE_BUNDLE));
rtc_options.use_rtp_mux = false;
offer = CreateOfferWithOptions(rtc_options);
ASSERT_TRUE(offer);
EXPECT_NE(nullptr, GetFirstAudioContent(offer->description()));
EXPECT_NE(nullptr, GetFirstVideoContent(offer->description()));
EXPECT_FALSE(offer->description()->HasGroup(cricket::GROUP_TYPE_BUNDLE));
}
// If SetMandatoryReceiveAudio(false) and SetMandatoryReceiveVideo(false) are
// called for the answer constraints, but an audio and a video section were
// offered, there will still be an audio and a video section in the answer.
TEST_F(PeerConnectionInterfaceTest,
RejectAudioAndVideoInAnswerWithConstraints) {
// Offer both audio and video.
RTCOfferAnswerOptions rtc_offer_options;
rtc_offer_options.offer_to_receive_audio = 1;
rtc_offer_options.offer_to_receive_video = 1;
CreatePeerConnection();
std::unique_ptr<SessionDescriptionInterface> offer;
CreateOfferWithOptionsAsRemoteDescription(&offer, rtc_offer_options);
EXPECT_NE(nullptr, GetFirstAudioContent(offer->description()));
EXPECT_NE(nullptr, GetFirstVideoContent(offer->description()));
// Since an offer has been created with both audio and video,
// Answers will contain the media types that exist in the offer regardless of
// the value of |answer_options.has_audio| and |answer_options.has_video|.
FakeConstraints answer_c;
// Reject both audio and video.
answer_c.SetMandatoryReceiveAudio(false);
answer_c.SetMandatoryReceiveVideo(false);
std::unique_ptr<SessionDescriptionInterface> answer;
ASSERT_TRUE(DoCreateAnswer(&answer, &answer_c));
const cricket::ContentInfo* audio_content =
GetFirstAudioContent(answer->description());
const cricket::ContentInfo* video_content =
GetFirstVideoContent(answer->description());
ASSERT_NE(nullptr, audio_content);
ASSERT_NE(nullptr, video_content);
EXPECT_TRUE(audio_content->rejected);
EXPECT_TRUE(video_content->rejected);
}
class PeerConnectionMediaConfigTest : public testing::Test {
protected:
void SetUp() override {
@ -3502,8 +3771,7 @@ class PeerConnectionMediaConfigTest : public testing::Test {
}
const cricket::MediaConfig TestCreatePeerConnection(
const PeerConnectionInterface::RTCConfiguration& config,
const MediaConstraintsInterface *constraints) {
const MediaConstraintsInterface* constraints) {
rtc::scoped_refptr<PeerConnectionInterface> pc(pcf_->CreatePeerConnection(
config, constraints, nullptr, nullptr, &observer_));
EXPECT_TRUE(pc.get());
@ -3585,177 +3853,6 @@ TEST_F(PeerConnectionMediaConfigTest,
EXPECT_TRUE(media_config.video.suspend_below_min_bitrate);
}
// The following tests verify that session options are created correctly.
// TODO(deadbeef): Convert these tests to be more end-to-end. Instead of
// "verify options are converted correctly", should be "pass options into
// CreateOffer and verify the correct offer is produced."
TEST(CreateSessionOptionsTest, GetOptionsForOfferWithInvalidAudioOption) {
RTCOfferAnswerOptions rtc_options;
rtc_options.offer_to_receive_audio = RTCOfferAnswerOptions::kUndefined - 1;
cricket::MediaSessionOptions options;
EXPECT_FALSE(ExtractMediaSessionOptions(rtc_options, true, &options));
rtc_options.offer_to_receive_audio =
RTCOfferAnswerOptions::kMaxOfferToReceiveMedia + 1;
EXPECT_FALSE(ExtractMediaSessionOptions(rtc_options, true, &options));
}
TEST(CreateSessionOptionsTest, GetOptionsForOfferWithInvalidVideoOption) {
RTCOfferAnswerOptions rtc_options;
rtc_options.offer_to_receive_video = RTCOfferAnswerOptions::kUndefined - 1;
cricket::MediaSessionOptions options;
EXPECT_FALSE(ExtractMediaSessionOptions(rtc_options, true, &options));
rtc_options.offer_to_receive_video =
RTCOfferAnswerOptions::kMaxOfferToReceiveMedia + 1;
EXPECT_FALSE(ExtractMediaSessionOptions(rtc_options, true, &options));
}
// Test that a MediaSessionOptions is created for an offer if
// OfferToReceiveAudio and OfferToReceiveVideo options are set.
TEST(CreateSessionOptionsTest, GetMediaSessionOptionsForOfferWithAudioVideo) {
RTCOfferAnswerOptions rtc_options;
rtc_options.offer_to_receive_audio = 1;
rtc_options.offer_to_receive_video = 1;
cricket::MediaSessionOptions options;
EXPECT_TRUE(ExtractMediaSessionOptions(rtc_options, true, &options));
EXPECT_TRUE(options.has_audio());
EXPECT_TRUE(options.has_video());
EXPECT_TRUE(options.bundle_enabled);
}
// Test that a correct MediaSessionOptions is created for an offer if
// OfferToReceiveAudio is set.
TEST(CreateSessionOptionsTest, GetMediaSessionOptionsForOfferWithAudio) {
RTCOfferAnswerOptions rtc_options;
rtc_options.offer_to_receive_audio = 1;
cricket::MediaSessionOptions options;
EXPECT_TRUE(ExtractMediaSessionOptions(rtc_options, true, &options));
EXPECT_TRUE(options.has_audio());
EXPECT_FALSE(options.has_video());
EXPECT_TRUE(options.bundle_enabled);
}
// Test that a correct MediaSessionOptions is created for an offer if
// the default OfferOptions are used.
TEST(CreateSessionOptionsTest, GetDefaultMediaSessionOptionsForOffer) {
RTCOfferAnswerOptions rtc_options;
cricket::MediaSessionOptions options;
options.transport_options["audio"] = cricket::TransportOptions();
options.transport_options["video"] = cricket::TransportOptions();
EXPECT_TRUE(ExtractMediaSessionOptions(rtc_options, true, &options));
EXPECT_TRUE(options.has_audio());
EXPECT_FALSE(options.has_video());
EXPECT_TRUE(options.bundle_enabled);
EXPECT_TRUE(options.vad_enabled);
EXPECT_FALSE(options.transport_options["audio"].ice_restart);
EXPECT_FALSE(options.transport_options["video"].ice_restart);
}
// Test that a correct MediaSessionOptions is created for an offer if
// OfferToReceiveVideo is set.
TEST(CreateSessionOptionsTest, GetMediaSessionOptionsForOfferWithVideo) {
RTCOfferAnswerOptions rtc_options;
rtc_options.offer_to_receive_audio = 0;
rtc_options.offer_to_receive_video = 1;
cricket::MediaSessionOptions options;
EXPECT_TRUE(ExtractMediaSessionOptions(rtc_options, true, &options));
EXPECT_FALSE(options.has_audio());
EXPECT_TRUE(options.has_video());
EXPECT_TRUE(options.bundle_enabled);
}
// Test that a correct MediaSessionOptions is created for an offer if
// UseRtpMux is set to false.
TEST(CreateSessionOptionsTest,
GetMediaSessionOptionsForOfferWithBundleDisabled) {
RTCOfferAnswerOptions rtc_options;
rtc_options.offer_to_receive_audio = 1;
rtc_options.offer_to_receive_video = 1;
rtc_options.use_rtp_mux = false;
cricket::MediaSessionOptions options;
EXPECT_TRUE(ExtractMediaSessionOptions(rtc_options, true, &options));
EXPECT_TRUE(options.has_audio());
EXPECT_TRUE(options.has_video());
EXPECT_FALSE(options.bundle_enabled);
}
// Test that a correct MediaSessionOptions is created to restart ice if
// IceRestart is set. It also tests that subsequent MediaSessionOptions don't
// have |audio_transport_options.ice_restart| etc. set.
TEST(CreateSessionOptionsTest, GetMediaSessionOptionsForOfferWithIceRestart) {
RTCOfferAnswerOptions rtc_options;
rtc_options.ice_restart = true;
cricket::MediaSessionOptions options;
options.transport_options["audio"] = cricket::TransportOptions();
options.transport_options["video"] = cricket::TransportOptions();
EXPECT_TRUE(ExtractMediaSessionOptions(rtc_options, true, &options));
EXPECT_TRUE(options.transport_options["audio"].ice_restart);
EXPECT_TRUE(options.transport_options["video"].ice_restart);
rtc_options = RTCOfferAnswerOptions();
EXPECT_TRUE(ExtractMediaSessionOptions(rtc_options, true, &options));
EXPECT_FALSE(options.transport_options["audio"].ice_restart);
EXPECT_FALSE(options.transport_options["video"].ice_restart);
}
// Test that the MediaConstraints in an answer don't affect if audio and video
// is offered in an offer but that if kOfferToReceiveAudio or
// kOfferToReceiveVideo constraints are true in an offer, the media type will be
// included in subsequent answers.
TEST(CreateSessionOptionsTest, MediaConstraintsInAnswer) {
FakeConstraints answer_c;
answer_c.SetMandatoryReceiveAudio(true);
answer_c.SetMandatoryReceiveVideo(true);
cricket::MediaSessionOptions answer_options;
EXPECT_TRUE(ParseConstraintsForAnswer(&answer_c, &answer_options));
EXPECT_TRUE(answer_options.has_audio());
EXPECT_TRUE(answer_options.has_video());
RTCOfferAnswerOptions rtc_offer_options;
cricket::MediaSessionOptions offer_options;
EXPECT_TRUE(
ExtractMediaSessionOptions(rtc_offer_options, false, &offer_options));
EXPECT_TRUE(offer_options.has_audio());
EXPECT_TRUE(offer_options.has_video());
RTCOfferAnswerOptions updated_rtc_offer_options;
updated_rtc_offer_options.offer_to_receive_audio = 1;
updated_rtc_offer_options.offer_to_receive_video = 1;
cricket::MediaSessionOptions updated_offer_options;
EXPECT_TRUE(ExtractMediaSessionOptions(updated_rtc_offer_options, false,
&updated_offer_options));
EXPECT_TRUE(updated_offer_options.has_audio());
EXPECT_TRUE(updated_offer_options.has_video());
// Since an offer has been created with both audio and video, subsequent
// offers and answers should contain both audio and video.
// Answers will only contain the media types that exist in the offer
// regardless of the value of |updated_answer_options.has_audio| and
// |updated_answer_options.has_video|.
FakeConstraints updated_answer_c;
answer_c.SetMandatoryReceiveAudio(false);
answer_c.SetMandatoryReceiveVideo(false);
cricket::MediaSessionOptions updated_answer_options;
EXPECT_TRUE(
ParseConstraintsForAnswer(&updated_answer_c, &updated_answer_options));
EXPECT_TRUE(updated_answer_options.has_audio());
EXPECT_TRUE(updated_answer_options.has_video());
}
// Tests a few random fields being different.
TEST(RTCConfigurationTest, ComparisonOperators) {
PeerConnectionInterface::RTCConfiguration a;

File diff suppressed because it is too large Load Diff

View File

@ -30,24 +30,30 @@ static const char kFailedDueToSessionShutdown[] =
static const uint64_t kInitSessionVersion = 2;
static bool CompareStream(const MediaSessionOptions::Stream& stream1,
const MediaSessionOptions::Stream& stream2) {
return stream1.id < stream2.id;
static bool CompareSenderOptions(const cricket::SenderOptions& sender1,
const cricket::SenderOptions& sender2) {
return sender1.track_id < sender2.track_id;
}
static bool SameId(const MediaSessionOptions::Stream& stream1,
const MediaSessionOptions::Stream& stream2) {
return stream1.id == stream2.id;
static bool SameId(const cricket::SenderOptions& sender1,
const cricket::SenderOptions& sender2) {
return sender1.track_id == sender2.track_id;
}
// Checks if each Stream within the |streams| has unique id.
static bool ValidStreams(const MediaSessionOptions::Streams& streams) {
MediaSessionOptions::Streams sorted_streams = streams;
std::sort(sorted_streams.begin(), sorted_streams.end(), CompareStream);
MediaSessionOptions::Streams::iterator it =
std::adjacent_find(sorted_streams.begin(), sorted_streams.end(),
SameId);
return it == sorted_streams.end();
// Check that each sender has a unique ID.
static bool ValidMediaSessionOptions(
const cricket::MediaSessionOptions& session_options) {
std::vector<cricket::SenderOptions> sorted_senders;
for (const cricket::MediaDescriptionOptions& media_description_options :
session_options.media_description_options) {
sorted_senders.insert(sorted_senders.end(),
media_description_options.sender_options.begin(),
media_description_options.sender_options.end());
}
std::sort(sorted_senders.begin(), sorted_senders.end(), CompareSenderOptions);
std::vector<cricket::SenderOptions>::iterator it =
std::adjacent_find(sorted_senders.begin(), sorted_senders.end(), SameId);
return it == sorted_senders.end();
}
enum {
@ -128,7 +134,6 @@ WebRtcSessionDescriptionFactory::WebRtcSessionDescriptionFactory(
session_id_(session_id),
certificate_request_state_(CERTIFICATE_NOT_NEEDED) {
RTC_DCHECK(signaling_thread_);
session_desc_factory_.set_add_legacy_streams(false);
bool dtls_enabled = cert_generator_ || certificate;
// SRTP-SDES is disabled if DTLS is on.
SetSdesPolicy(dtls_enabled ? cricket::SEC_DISABLED : cricket::SEC_REQUIRED);
@ -237,8 +242,8 @@ void WebRtcSessionDescriptionFactory::CreateOffer(
return;
}
if (!ValidStreams(session_options.streams)) {
error += " called with invalid media streams.";
if (!ValidMediaSessionOptions(session_options)) {
error += " called with invalid session options";
LOG(LS_ERROR) << error;
PostCreateSessionDescriptionFailed(observer, error);
return;
@ -279,8 +284,8 @@ void WebRtcSessionDescriptionFactory::CreateAnswer(
return;
}
if (!ValidStreams(session_options.streams)) {
error += " called with invalid media streams.";
if (!ValidMediaSessionOptions(session_options)) {
error += " called with invalid session options.";
LOG(LS_ERROR) << error;
PostCreateSessionDescriptionFailed(observer, error);
return;
@ -340,13 +345,12 @@ void WebRtcSessionDescriptionFactory::OnMessage(rtc::Message* msg) {
void WebRtcSessionDescriptionFactory::InternalCreateOffer(
CreateSessionDescriptionRequest request) {
if (session_->local_description()) {
for (const cricket::TransportInfo& transport :
session_->local_description()->description()->transport_infos()) {
// If the needs-ice-restart flag is set as described by JSEP, we should
// generate an offer with a new ufrag/password to trigger an ICE restart.
if (session_->NeedsIceRestart(transport.content_name)) {
request.options.transport_options[transport.content_name].ice_restart =
true;
// If the needs-ice-restart flag is set as described by JSEP, we should
// generate an offer with a new ufrag/password to trigger an ICE restart.
for (cricket::MediaDescriptionOptions& options :
request.options.media_description_options) {
if (session_->NeedsIceRestart(options.mid)) {
options.transport_options.ice_restart = true;
}
}
}
@ -375,13 +379,11 @@ void WebRtcSessionDescriptionFactory::InternalCreateOffer(
return;
}
if (session_->local_description()) {
for (const cricket::ContentInfo& content :
session_->local_description()->description()->contents()) {
// Include all local ICE candidates in the SessionDescription unless
// an ICE restart was requested.
if (!request.options.transport_options[content.name].ice_restart) {
for (const cricket::MediaDescriptionOptions& options :
request.options.media_description_options) {
if (!options.transport_options.ice_restart) {
CopyCandidatesFromSessionDescription(session_->local_description(),
content.name, offer);
options.mid, offer);
}
}
}
@ -391,18 +393,18 @@ void WebRtcSessionDescriptionFactory::InternalCreateOffer(
void WebRtcSessionDescriptionFactory::InternalCreateAnswer(
CreateSessionDescriptionRequest request) {
if (session_->remote_description()) {
for (const cricket::ContentInfo& content :
session_->remote_description()->description()->contents()) {
for (cricket::MediaDescriptionOptions& options :
request.options.media_description_options) {
// According to http://tools.ietf.org/html/rfc5245#section-9.2.1.1
// an answer should also contain new ICE ufrag and password if an offer
// has been received with new ufrag and password.
request.options.transport_options[content.name].ice_restart =
session_->IceRestartPending(content.name);
options.transport_options.ice_restart =
session_->IceRestartPending(options.mid);
// We should pass the current SSL role to the transport description
// factory, if there is already an existing ongoing session.
rtc::SSLRole ssl_role;
if (session_->GetSslRole(content.name, &ssl_role)) {
request.options.transport_options[content.name].prefer_passive_role =
if (session_->GetSslRole(options.mid, &ssl_role)) {
options.transport_options.prefer_passive_role =
(rtc::SSL_SERVER == ssl_role);
}
}
@ -433,13 +435,13 @@ void WebRtcSessionDescriptionFactory::InternalCreateAnswer(
return;
}
if (session_->local_description()) {
for (const cricket::ContentInfo& content :
session_->local_description()->description()->contents()) {
// Include all local ICE candidates in the SessionDescription unless
// the remote peer has requested an ICE restart.
if (!request.options.transport_options[content.name].ice_restart) {
// Include all local ICE candidates in the SessionDescription unless
// the remote peer has requested an ICE restart.
for (const cricket::MediaDescriptionOptions& options :
request.options.media_description_options) {
if (!options.transport_options.ice_restart) {
CopyCandidatesFromSessionDescription(session_->local_description(),
content.name, answer);
options.mid, answer);
}
}
}