/*
* Copyright 2004 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 "pc/media_session.h"
#include <stddef.h>
#include <cstdint>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <tuple>
#include <vector>
#include "absl/algorithm/container.h"
#include "absl/strings/match.h"
#include "absl/strings/string_view.h"
#include "api/audio_codecs/audio_format.h"
#include "api/candidate.h"
#include "api/media_types.h"
#include "api/rtp_parameters.h"
#include "api/rtp_transceiver_direction.h"
#include "media/base/codec.h"
#include "media/base/media_constants.h"
#include "media/base/rid_description.h"
#include "media/base/stream_params.h"
#include "media/base/test_utils.h"
#include "media/sctp/sctp_transport_internal.h"
#include "p2p/base/p2p_constants.h"
#include "p2p/base/transport_description.h"
#include "p2p/base/transport_description_factory.h"
#include "p2p/base/transport_info.h"
#include "pc/media_protocol_names.h"
#include "pc/rtp_media_utils.h"
#include "pc/rtp_parameters_conversion.h"
#include "pc/session_description.h"
#include "pc/simulcast_description.h"
#include "rtc_base/checks.h"
#include "rtc_base/fake_ssl_identity.h"
#include "rtc_base/logging.h"
#include "rtc_base/rtc_certificate.h"
#include "rtc_base/ssl_identity.h"
#include "rtc_base/string_encode.h"
#include "rtc_base/strings/string_builder.h"
#include "rtc_base/unique_id_generator.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/scoped_key_value_config.h"
namespace cricket {
namespace {
using ::rtc::UniqueRandomIdGenerator;
using ::testing::
Bool;
using ::testing::Combine;
using ::testing::Contains;
using ::testing::ElementsAre;
using ::testing::ElementsAreArray;
using ::testing::Eq;
using ::testing::Field;
using ::testing::IsEmpty;
using ::testing::
Not;
using ::testing::Pointwise;
using ::testing::SizeIs;
using ::testing::UnorderedElementsAreArray;
using ::testing::Values;
using ::testing::ValuesIn;
using ::webrtc::RtpExtension;
using ::webrtc::RtpTransceiverDirection;
using Candidates = std::vector<Candidate>;
Codec CreateRedAudioCodec(absl::string_view encoding_id) {
Codec red = CreateAudioCodec(63,
"red", 48000, 2);
red.SetParam(kCodecParamNotInNameValueFormat,
std::string(encoding_id) +
'/' + std::string(encoding_id));
return red;
}
const Codec kAudioCodecs1[] = {CreateAudioCodec(111,
"opus", 48000, 2),
CreateRedAudioCodec(
"111"),
CreateAudioCodec(102,
"iLBC", 8000, 1),
CreateAudioCodec(0,
"PCMU", 8000, 1),
CreateAudioCodec(8,
"PCMA", 8000, 1),
CreateAudioCodec(107,
"CN", 48000, 1)};
const Codec kAudioCodecs2[] = {
CreateAudioCodec(126,
"foo", 16000, 1),
CreateAudioCodec(0,
"PCMU", 8000, 1),
CreateAudioCodec(127,
"iLBC", 8000, 1),
};
const Codec kAudioCodecsAnswer[] = {
CreateAudioCodec(102,
"iLBC", 8000, 1),
CreateAudioCodec(0,
"PCMU", 8000, 1),
};
const Codec kVideoCodecs1[] = {CreateVideoCodec(96,
"H264-SVC"),
CreateVideoCodec(97,
"H264")};
const Codec kVideoCodecs1Reverse[] = {CreateVideoCodec(97,
"H264"),
CreateVideoCodec(96,
"H264-SVC")};
const Codec kVideoCodecs2[] = {CreateVideoCodec(126,
"H264"),
CreateVideoCodec(127,
"H263")};
const Codec kVideoCodecsAnswer[] = {CreateVideoCodec(97,
"H264")};
const RtpExtension kAudioRtpExtension1[] = {
RtpExtension(
"urn:ietf:params:rtp-hdrext:ssrc-audio-level", 8),
RtpExtension(
"http://google.com/testing/audio_something", 10),
};
const RtpExtension kAudioRtpExtensionEncrypted1[] = {
RtpExtension(
"urn:ietf:params:rtp-hdrext:ssrc-audio-level", 8),
RtpExtension(
"http://google.com/testing/audio_something", 11, true),
};
const RtpExtension kAudioRtpExtension2[] = {
RtpExtension(
"urn:ietf:params:rtp-hdrext:ssrc-audio-level", 2),
RtpExtension(
"http://google.com/testing/audio_something_else", 8),
RtpExtension(
"http://google.com/testing/both_audio_and_video", 7),
};
const RtpExtension kAudioRtpExtensionEncrypted2[] = {
RtpExtension(
"urn:ietf:params:rtp-hdrext:ssrc-audio-level", 2),
RtpExtension(
"http://google.com/testing/audio_something", 13, true),
RtpExtension(
"http://google.com/testing/audio_something_else", 5, true),
};
const RtpExtension kAudioRtpExtension3[] = {
RtpExtension(
"http://google.com/testing/audio_something", 2),
RtpExtension(
"http://google.com/testing/both_audio_and_video", 3),
};
const RtpExtension kAudioRtpExtensionMixedEncryption1[] = {
RtpExtension(
"urn:ietf:params:rtp-hdrext:ssrc-audio-level", 8),
RtpExtension(
"http://google.com/testing/audio_something", 9),
RtpExtension(
"urn:ietf:params:rtp-hdrext:ssrc-audio-level", 10,
true),
RtpExtension(
"http://google.com/testing/audio_something", 11, true),
RtpExtension(
"http://google.com/testing/audio_something_else", 12, true),
};
const RtpExtension kAudioRtpExtensionMixedEncryption2[] = {
RtpExtension(
"urn:ietf:params:rtp-hdrext:ssrc-audio-level", 5),
RtpExtension(
"http://google.com/testing/audio_something", 6),
RtpExtension(
"urn:ietf:params:rtp-hdrext:ssrc-audio-level", 7,
true),
RtpExtension(
"http://google.com/testing/audio_something", 8, true),
RtpExtension(
"http://google.com/testing/audio_something_else", 9),
};
const RtpExtension kAudioRtpExtensionAnswer[] = {
RtpExtension(
"urn:ietf:params:rtp-hdrext:ssrc-audio-level", 8),
};
const RtpExtension kAudioRtpExtensionEncryptedAnswer[] = {
RtpExtension(
"urn:ietf:params:rtp-hdrext:ssrc-audio-level", 8),
RtpExtension(
"http://google.com/testing/audio_something", 11, true),
};
const RtpExtension kAudioRtpExtensionMixedEncryptionAnswerEncryptionEnabled[] =
{
RtpExtension(
"urn:ietf:params:rtp-hdrext:ssrc-audio-level", 10,
true),
RtpExtension(
"http://google.com/testing/audio_something", 11, true),
};
const RtpExtension kAudioRtpExtensionMixedEncryptionAnswerEncryptionDisabled[] =
{
RtpExtension(
"urn:ietf:params:rtp-hdrext:ssrc-audio-level", 8),
RtpExtension(
"http://google.com/testing/audio_something", 9),
};
const RtpExtension kVideoRtpExtension1[] = {
RtpExtension(
"urn:ietf:params:rtp-hdrext:toffset", 14),
RtpExtension(
"http://google.com/testing/video_something", 13),
};
const RtpExtension kVideoRtpExtensionEncrypted1[] = {
RtpExtension(
"urn:ietf:params:rtp-hdrext:toffset", 14),
RtpExtension(
"http://google.com/testing/video_something", 7, true),
};
const RtpExtension kVideoRtpExtension2[] = {
RtpExtension(
"urn:ietf:params:rtp-hdrext:toffset", 2),
RtpExtension(
"http://google.com/testing/video_something_else", 14),
RtpExtension(
"http://google.com/testing/both_audio_and_video", 7),
};
const RtpExtension kVideoRtpExtensionEncrypted2[] = {
RtpExtension(
"urn:ietf:params:rtp-hdrext:toffset", 8),
RtpExtension(
"http://google.com/testing/video_something", 10, true),
RtpExtension(
"http://google.com/testing/video_something_else", 4, true),
};
const RtpExtension kVideoRtpExtension3[] = {
RtpExtension(
"http://google.com/testing/video_something", 4),
RtpExtension(
"http://google.com/testing/both_audio_and_video", 5),
};
const RtpExtension kVideoRtpExtensionMixedEncryption[] = {
RtpExtension(
"urn:ietf:params:rtp-hdrext:toffset", 14),
RtpExtension(
"http://google.com/testing/video_something", 13),
RtpExtension(
"urn:ietf:params:rtp-hdrext:toffset", 15,
true),
RtpExtension(
"http://google.com/testing/video_something", 16, true),
};
const RtpExtension kVideoRtpExtensionAnswer[] = {
RtpExtension(
"urn:ietf:params:rtp-hdrext:toffset", 14),
};
const RtpExtension kVideoRtpExtensionEncryptedAnswer[] = {
RtpExtension(
"urn:ietf:params:rtp-hdrext:toffset", 14),
RtpExtension(
"http://google.com/testing/video_something", 7, true),
};
const RtpExtension kVideoRtpExtensionMixedEncryptionAnswerEncryptionEnabled[] =
{
RtpExtension(
"urn:ietf:params:rtp-hdrext:toffset", 15,
true),
RtpExtension(
"http://google.com/testing/video_something", 16, true),
};
const RtpExtension kVideoRtpExtensionMixedEncryptionAnswerEncryptionDisabled[] =
{
RtpExtension(
"urn:ietf:params:rtp-hdrext:toffset", 14),
RtpExtension(
"http://google.com/testing/video_something", 13),
};
const RtpExtension kRtpExtensionTransportSequenceNumber01[] = {
RtpExtension(
"http://www.ietf.org/id/"
"draft-holmer-rmcat-transport-wide-cc-extensions-01",
1),
};
const RtpExtension kRtpExtensionTransportSequenceNumber01And02[] = {
RtpExtension(
"http://www.ietf.org/id/"
"draft-holmer-rmcat-transport-wide-cc-extensions-01",
1),
RtpExtension(
"http://www.webrtc.org/experiments/rtp-hdrext/transport-wide-cc-02",
2),
};
const RtpExtension kRtpExtensionTransportSequenceNumber02[] = {
RtpExtension(
"http://www.webrtc.org/experiments/rtp-hdrext/transport-wide-cc-02",
2),
};
const RtpExtension kRtpExtensionGenericFrameDescriptorUri00[] = {
RtpExtension(
"http://www.webrtc.org/experiments/rtp-hdrext/"
"generic-frame-descriptor-00",
3),
};
const uint32_t kSimulcastParamsSsrc[] = {10, 11, 20, 21, 30, 31};
const uint32_t kSimSsrc[] = {10, 20, 30};
const uint32_t kFec1Ssrc[] = {10, 11};
const uint32_t kFec2Ssrc[] = {20, 21};
const uint32_t kFec3Ssrc[] = {30, 31};
const char kMediaStream1[] =
"stream_1";
const char kMediaStream2[] =
"stream_2";
const char kVideoTrack1[] =
"video_1";
const char kVideoTrack2[] =
"video_2";
const char kAudioTrack1[] =
"audio_1";
const char kAudioTrack2[] =
"audio_2";
const char kAudioTrack3[] =
"audio_3";
const char* kMediaProtocols[] = {
"RTP/AVP",
"RTP/SAVP",
"RTP/AVPF",
"RTP/SAVPF"};
const char* kMediaProtocolsDtls[] = {
"TCP/TLS/RTP/SAVPF",
"TCP/TLS/RTP/SAVP",
"UDP/TLS/RTP/SAVPF",
"UDP/TLS/RTP/SAVP"};
// These constants are used to make the code using "AddMediaDescriptionOptions"
// more readable.
constexpr
bool kStopped =
true;
constexpr
bool kActive =
false;
// Helper used for debugging. It reports the media type and the parameters.
std::string FullMimeType(Codec codec) {
rtc::StringBuilder sb;
switch (codec.type) {
case Codec::Type::kAudio:
sb <<
"audio/";
break;
case Codec::Type::kVideo:
sb <<
"video/";
break;
}
sb << codec.name;
for (
auto& param : codec.params) {
sb <<
";" << param.first <<
"=" << param.second;
}
return sb.Release();
}
bool IsMediaContentOfType(
const ContentInfo* content, MediaType media_type) {
RTC_DCHECK(content);
return content->media_description()->type() == media_type;
}
RtpTransceiverDirection GetMediaDirection(
const ContentInfo* content) {
RTC_DCHECK(content);
return content->media_description()->direction();
}
void AddRtxCodec(
const Codec& rtx_codec, std::vector<Codec>* codecs) {
RTC_LOG(LS_VERBOSE) <<
"Adding RTX codec " << FullMimeType(rtx_codec);
ASSERT_FALSE(FindCodecById(*codecs, rtx_codec.id));
codecs->push_back(rtx_codec);
}
std::vector<std::string> GetCodecNames(
const std::vector<Codec>& codecs) {
std::vector<std::string> codec_names;
codec_names.reserve(codecs.size());
for (
const auto& codec : codecs) {
codec_names.push_back(codec.name);
}
return codec_names;
}
// This is used for test only. MIDs are not the identification of the
// MediaDescriptionOptions since some end points may not support MID and the SDP
// may not contain 'mid'.
std::vector<MediaDescriptionOptions>::iterator FindFirstMediaDescriptionByMid(
const std::string& mid,
MediaSessionOptions* opts) {
return absl::c_find_if(
opts->media_description_options,
[&mid](
const MediaDescriptionOptions& t) {
return t.mid == mid; });
}
std::vector<MediaDescriptionOptions>::const_iterator
FindFirstMediaDescriptionByMid(
const std::string& mid,
const MediaSessionOptions& opts) {
return absl::c_find_if(
opts.media_description_options,
[&mid](
const MediaDescriptionOptions& t) {
return t.mid == mid; });
}
// Add a media section to the `session_options`.
void AddMediaDescriptionOptions(MediaType type,
const std::string& mid,
RtpTransceiverDirection direction,
bool stopped,
MediaSessionOptions* opts) {
opts->media_description_options.push_back(
MediaDescriptionOptions(type, mid, direction, stopped));
}
void AddAudioVideoSections(RtpTransceiverDirection direction,
MediaSessionOptions* opts) {
AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO,
"audio", direction, kActive,
opts);
AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO,
"video", direction, kActive,
opts);
}
void AddDataSection(RtpTransceiverDirection direction,
MediaSessionOptions* opts) {
AddMediaDescriptionOptions(MEDIA_TYPE_DATA,
"data", direction, kActive, opts);
}
void AttachSenderToMediaDescriptionOptions(
const std::string& mid,
MediaType type,
const std::string& track_id,
const std::vector<std::string>& stream_ids,
const std::vector<RidDescription>& rids,
const SimulcastLayerList& simulcast_layers,
int num_sim_layer,
MediaSessionOptions* session_options) {
auto it = FindFirstMediaDescriptionByMid(mid, session_options);
switch (type) {
case MEDIA_TYPE_AUDIO:
it->AddAudioSender(track_id, stream_ids);
break;
case MEDIA_TYPE_VIDEO:
it->AddVideoSender(track_id, stream_ids, rids, simulcast_layers,
num_sim_layer);
break;
default:
RTC_DCHECK_NOTREACHED();
}
}
void AttachSenderToMediaDescriptionOptions(
const std::string& mid,
MediaType type,
const std::string& track_id,
const std::vector<std::string>& stream_ids,
int num_sim_layer,
MediaSessionOptions* session_options) {
AttachSenderToMediaDescriptionOptions(mid, type, track_id, stream_ids, {},
SimulcastLayerList(), num_sim_layer,
session_options);
}
void DetachSenderFromMediaSection(
const std::string& mid,
const std::string& track_id,
MediaSessionOptions* session_options) {
std::vector<SenderOptions>& sender_options_list =
FindFirstMediaDescriptionByMid(mid, session_options)->sender_options;
auto sender_it = absl::c_find_if(
sender_options_list, [track_id](
const SenderOptions& sender_options) {
return sender_options.track_id == track_id;
});
RTC_DCHECK(sender_it != sender_options_list.end());
sender_options_list.erase(sender_it);
}
// Helper function used to create recv-only audio MediaSessionOptions.
MediaSessionOptions CreateAudioMediaSession() {
MediaSessionOptions session_options;
AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO,
"audio",
RtpTransceiverDirection::kRecvOnly, kActive,
&session_options);
return session_options;
}
// TODO(zhihuang): Most of these tests were written while MediaSessionOptions
// was designed for Plan B SDP, where only one audio "m=" section and one video
// "m=" section could be generated, and ordering couldn't be controlled. Many of
// these tests may be obsolete as a result, and should be refactored or removed.
class MediaSessionDescriptionFactoryTest :
public testing::Test {
public:
MediaSessionDescriptionFactoryTest()
: tdf1_(field_trials),
tdf2_(field_trials),
f1_(nullptr,
false, &ssrc_generator1, &tdf1_, nullptr),
f2_(nullptr,
false, &ssrc_generator2, &tdf2_, nullptr) {
f1_.set_audio_codecs(MAKE_VECTOR(kAudioCodecs1),
MAKE_VECTOR(kAudioCodecs1));
f1_.set_video_codecs(MAKE_VECTOR(kVideoCodecs1),
MAKE_VECTOR(kVideoCodecs1));
f2_.set_audio_codecs(MAKE_VECTOR(kAudioCodecs2),
MAKE_VECTOR(kAudioCodecs2));
f2_.set_video_codecs(MAKE_VECTOR(kVideoCodecs2),
MAKE_VECTOR(kVideoCodecs2));
tdf1_.set_certificate(rtc::RTCCertificate::Create(
std::unique_ptr<rtc::SSLIdentity>(
new rtc::FakeSSLIdentity(
"id1"))));
tdf2_.set_certificate(rtc::RTCCertificate::Create(
std::unique_ptr<rtc::SSLIdentity>(
new rtc::FakeSSLIdentity(
"id2"))));
}
// Create a video StreamParamsVec object with:
// - one video stream with 3 simulcast streams and FEC,
StreamParamsVec CreateComplexVideoStreamParamsVec() {
SsrcGroup sim_group(
"SIM", MAKE_VECTOR(kSimSsrc));
SsrcGroup fec_group1(
"FEC", MAKE_VECTOR(kFec1Ssrc));
SsrcGroup fec_group2(
"FEC", MAKE_VECTOR(kFec2Ssrc));
SsrcGroup fec_group3(
"FEC", MAKE_VECTOR(kFec3Ssrc));
std::vector<SsrcGroup> ssrc_groups;
ssrc_groups.push_back(sim_group);
ssrc_groups.push_back(fec_group1);
ssrc_groups.push_back(fec_group2);
ssrc_groups.push_back(fec_group3);
StreamParams simulcast_params;
simulcast_params.id = kVideoTrack1;
simulcast_params.ssrcs = MAKE_VECTOR(kSimulcastParamsSsrc);
simulcast_params.ssrc_groups = ssrc_groups;
simulcast_params.cname =
"Video_SIM_FEC";
simulcast_params.set_stream_ids({kMediaStream1});
StreamParamsVec video_streams;
video_streams.push_back(simulcast_params);
return video_streams;
}
// Returns true if the transport info contains "renomination" as an
// ICE option.
bool GetIceRenomination(
const TransportInfo* transport_info) {
return absl::c_linear_search(transport_info->description.transport_options,
"renomination");
}
void TestTransportInfo(
bool offer,
const MediaSessionOptions& options,
bool has_current_desc) {
const std::string current_audio_ufrag =
"current_audio_ufrag";
const std::string current_audio_pwd =
"current_audio_pwd";
const std::string current_video_ufrag =
"current_video_ufrag";
const std::string current_video_pwd =
"current_video_pwd";
const std::string current_data_ufrag =
"current_data_ufrag";
const std::string current_data_pwd =
"current_data_pwd";
std::unique_ptr<SessionDescription> current_desc;
std::unique_ptr<SessionDescription> desc;
if (has_current_desc) {
current_desc = std::make_unique<SessionDescription>();
current_desc->AddTransportInfo(TransportInfo(
"audio",
TransportDescription(current_audio_ufrag, current_audio_pwd)));
current_desc->AddTransportInfo(TransportInfo(
"video",
TransportDescription(current_video_ufrag, current_video_pwd)));
current_desc->AddTransportInfo(TransportInfo(
"data", TransportDescription(current_data_ufrag, current_data_pwd)));
}
if (offer) {
desc = f1_.CreateOfferOrError(options, current_desc.get()).MoveValue();
}
else {
std::unique_ptr<SessionDescription> offer_desc;
offer_desc = f1_.CreateOfferOrError(options, nullptr).MoveValue();
desc =
f1_.CreateAnswerOrError(offer_desc.get(), options, current_desc.get())
.MoveValue();
}
ASSERT_TRUE(desc);
const TransportInfo* ti_audio = desc->GetTransportInfoByName(
"audio");
if (options.has_audio()) {
if (has_current_desc) {
EXPECT_EQ(current_audio_ufrag, ti_audio->description.ice_ufrag);
EXPECT_EQ(current_audio_pwd, ti_audio->description.ice_pwd);
}
else {
EXPECT_EQ(
static_cast<size_t>(ICE_UFRAG_LENGTH),
ti_audio->description.ice_ufrag.size());
EXPECT_EQ(
static_cast<size_t>(ICE_PWD_LENGTH),
ti_audio->description.ice_pwd.size());
}
auto media_desc_options_it =
FindFirstMediaDescriptionByMid(
"audio", options);
EXPECT_EQ(
media_desc_options_it->transport_options.enable_ice_renomination,
GetIceRenomination(ti_audio));
}
const TransportInfo* ti_video = desc->GetTransportInfoByName(
"video");
if (options.has_video()) {
auto media_desc_options_it =
FindFirstMediaDescriptionByMid(
"video", options);
if (options.bundle_enabled) {
EXPECT_EQ(ti_audio->description.ice_ufrag,
ti_video->description.ice_ufrag);
EXPECT_EQ(ti_audio->description.ice_pwd, ti_video->description.ice_pwd);
}
else {
if (has_current_desc) {
EXPECT_EQ(current_video_ufrag, ti_video->description.ice_ufrag);
EXPECT_EQ(current_video_pwd, ti_video->description.ice_pwd);
}
else {
EXPECT_EQ(
static_cast<size_t>(ICE_UFRAG_LENGTH),
ti_video->description.ice_ufrag.size());
EXPECT_EQ(
static_cast<size_t>(ICE_PWD_LENGTH),
ti_video->description.ice_pwd.size());
}
}
EXPECT_EQ(
media_desc_options_it->transport_options.enable_ice_renomination,
GetIceRenomination(ti_video));
}
const TransportInfo* ti_data = desc->GetTransportInfoByName(
"data");
if (options.has_data()) {
if (options.bundle_enabled) {
EXPECT_EQ(ti_audio->description.ice_ufrag,
ti_data->description.ice_ufrag);
EXPECT_EQ(ti_audio->description.ice_pwd, ti_data->description.ice_pwd);
}
else {
if (has_current_desc) {
EXPECT_EQ(current_data_ufrag, ti_data->description.ice_ufrag);
EXPECT_EQ(current_data_pwd, ti_data->description.ice_pwd);
}
else {
EXPECT_EQ(
static_cast<size_t>(ICE_UFRAG_LENGTH),
ti_data->description.ice_ufrag.size());
EXPECT_EQ(
static_cast<size_t>(ICE_PWD_LENGTH),
ti_data->description.ice_pwd.size());
}
}
auto media_desc_options_it =
FindFirstMediaDescriptionByMid(
"data", options);
EXPECT_EQ(
media_desc_options_it->transport_options.enable_ice_renomination,
GetIceRenomination(ti_data));
}
}
// This test that the audio and video media direction is set to
// `expected_direction_in_answer` in an answer if the offer direction is set
// to `direction_in_offer` and the answer is willing to both send and receive.
void TestMediaDirectionInAnswer(
RtpTransceiverDirection direction_in_offer,
RtpTransceiverDirection expected_direction_in_answer) {
MediaSessionOptions offer_opts;
AddAudioVideoSections(direction_in_offer, &offer_opts);
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(offer_opts, nullptr).MoveValue();
ASSERT_TRUE(offer.get());
ContentInfo* ac_offer = offer->GetContentByName(
"audio");
ASSERT_TRUE(ac_offer);
ContentInfo* vc_offer = offer->GetContentByName(
"video");
ASSERT_TRUE(vc_offer);
MediaSessionOptions answer_opts;
AddAudioVideoSections(RtpTransceiverDirection::kSendRecv, &answer_opts);
std::unique_ptr<SessionDescription> answer =
f2_.CreateAnswerOrError(offer.get(), answer_opts, nullptr).MoveValue();
const AudioContentDescription* acd_answer =
GetFirstAudioContentDescription(answer.get());
EXPECT_EQ(expected_direction_in_answer, acd_answer->direction());
const VideoContentDescription* vcd_answer =
GetFirstVideoContentDescription(answer.get());
EXPECT_EQ(expected_direction_in_answer, vcd_answer->direction());
}
bool VerifyNoCNCodecs(
const ContentInfo* content) {
RTC_DCHECK(content);
RTC_CHECK(content->media_description());
for (
const Codec& codec : content->media_description()->codecs()) {
if (codec.name ==
"CN") {
return false;
}
}
return true;
}
void TestTransportSequenceNumberNegotiation(
const RtpHeaderExtensions& local,
const RtpHeaderExtensions& offered,
const RtpHeaderExtensions& expectedAnswer) {
MediaSessionOptions opts;
AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
SetAudioVideoRtpHeaderExtensions(offered, offered, &opts);
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(opts, nullptr).MoveValue();
ASSERT_TRUE(offer.get());
SetAudioVideoRtpHeaderExtensions(local, local, &opts);
std::unique_ptr<SessionDescription> answer =
f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
EXPECT_THAT(
expectedAnswer,
UnorderedElementsAreArray(GetFirstAudioContentDescription(answer.get())
->rtp_header_extensions()));
EXPECT_THAT(
expectedAnswer,
UnorderedElementsAreArray(GetFirstVideoContentDescription(answer.get())
->rtp_header_extensions()));
}
std::vector<webrtc::RtpHeaderExtensionCapability>
HeaderExtensionCapabilitiesFromRtpExtensions(RtpHeaderExtensions extensions) {
std::vector<webrtc::RtpHeaderExtensionCapability> capabilities;
for (
const auto& extension : extensions) {
webrtc::RtpHeaderExtensionCapability capability(
extension.uri, extension.id, extension.encrypt,
webrtc::RtpTransceiverDirection::kSendRecv);
capabilities.push_back(capability);
}
return capabilities;
}
void SetAudioVideoRtpHeaderExtensions(RtpHeaderExtensions audio_exts,
RtpHeaderExtensions video_exts,
MediaSessionOptions* opts) {
std::vector<webrtc::RtpHeaderExtensionCapability> audio_caps =
HeaderExtensionCapabilitiesFromRtpExtensions(audio_exts);
std::vector<webrtc::RtpHeaderExtensionCapability> video_caps =
HeaderExtensionCapabilitiesFromRtpExtensions(video_exts);
for (
auto& entry : opts->media_description_options) {
switch (entry.type) {
case MEDIA_TYPE_AUDIO:
entry.header_extensions = audio_caps;
break;
case MEDIA_TYPE_VIDEO:
entry.header_extensions = video_caps;
break;
default:
break;
}
}
}
protected:
webrtc::test::ScopedKeyValueConfig field_trials;
UniqueRandomIdGenerator ssrc_generator1;
UniqueRandomIdGenerator ssrc_generator2;
TransportDescriptionFactory tdf1_;
TransportDescriptionFactory tdf2_;
MediaSessionDescriptionFactory f1_;
MediaSessionDescriptionFactory f2_;
};
// Create a typical audio offer, and ensure it matches what we expect.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioOffer) {
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(CreateAudioMediaSession(), nullptr).MoveValue();
ASSERT_TRUE(offer.get());
const ContentInfo* ac = offer->GetContentByName(
"audio");
const ContentInfo* vc = offer->GetContentByName(
"video");
ASSERT_TRUE(ac);
EXPECT_FALSE(vc);
EXPECT_EQ(MediaProtocolType::kRtp, ac->type);
const MediaContentDescription* acd = ac->media_description();
EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
EXPECT_THAT(f1_.audio_sendrecv_codecs(), ElementsAreArray(acd->codecs()));
EXPECT_EQ(0U, acd->first_ssrc());
// no sender is attached.
EXPECT_EQ(kAutoBandwidth, acd->bandwidth());
// default bandwidth (auto)
EXPECT_TRUE(acd->rtcp_mux());
// rtcp-mux defaults on
EXPECT_EQ(kMediaProtocolDtlsSavpf, acd->protocol());
}
// Create an offer with just Opus and RED.
TEST_F(MediaSessionDescriptionFactoryTest,
TestCreateAudioOfferWithJustOpusAndRed) {
// First, prefer to only use opus and red.
std::vector<webrtc::RtpCodecCapability> preferences;
preferences.push_back(
webrtc::ToRtpCodecCapability(f1_.audio_sendrecv_codecs()[0]));
preferences.push_back(
webrtc::ToRtpCodecCapability(f1_.audio_sendrecv_codecs()[1]));
EXPECT_EQ(
"opus", preferences[0].name);
EXPECT_EQ(
"red", preferences[1].name);
auto opts = CreateAudioMediaSession();
opts.media_description_options.at(0).codec_preferences = preferences;
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(opts, nullptr).MoveValue();
ASSERT_TRUE(offer.get());
const ContentInfo* ac = offer->GetContentByName(
"audio");
const ContentInfo* vc = offer->GetContentByName(
"video");
ASSERT_TRUE(ac != NULL);
ASSERT_TRUE(vc == NULL);
EXPECT_EQ(MediaProtocolType::kRtp, ac->type);
const MediaContentDescription* acd = ac->media_description();
EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
EXPECT_EQ(2U, acd->codecs().size());
EXPECT_EQ(
"opus", acd->codecs()[0].name);
EXPECT_EQ(
"red", acd->codecs()[1].name);
}
// Create an offer with RED before Opus, which enables RED with Opus encoding.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioOfferWithRedForOpus) {
// First, prefer to only use opus and red.
std::vector<webrtc::RtpCodecCapability> preferences;
preferences.push_back(
webrtc::ToRtpCodecCapability(f1_.audio_sendrecv_codecs()[1]));
preferences.push_back(
webrtc::ToRtpCodecCapability(f1_.audio_sendrecv_codecs()[0]));
EXPECT_EQ(
"red", preferences[0].name);
EXPECT_EQ(
"opus", preferences[1].name);
auto opts = CreateAudioMediaSession();
opts.media_description_options.at(0).codec_preferences = preferences;
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(opts, nullptr).MoveValue();
ASSERT_TRUE(offer.get());
const ContentInfo* ac = offer->GetContentByName(
"audio");
const ContentInfo* vc = offer->GetContentByName(
"video");
ASSERT_TRUE(ac != NULL);
ASSERT_TRUE(vc == NULL);
EXPECT_EQ(MediaProtocolType::kRtp, ac->type);
const MediaContentDescription* acd = ac->media_description();
EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
EXPECT_EQ(2U, acd->codecs().size());
EXPECT_EQ(
"red", acd->codecs()[0].name);
EXPECT_EQ(
"opus", acd->codecs()[1].name);
}
// Create a typical video offer, and ensure it matches what we expect.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoOffer) {
MediaSessionOptions opts;
AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(opts, nullptr).MoveValue();
ASSERT_TRUE(offer.get());
const ContentInfo* ac = offer->GetContentByName(
"audio");
const ContentInfo* vc = offer->GetContentByName(
"video");
ASSERT_TRUE(ac);
ASSERT_TRUE(vc);
EXPECT_EQ(MediaProtocolType::kRtp, ac->type);
EXPECT_EQ(MediaProtocolType::kRtp, vc->type);
const MediaContentDescription* acd = ac->media_description();
const MediaContentDescription* vcd = vc->media_description();
EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
EXPECT_EQ(f1_.audio_sendrecv_codecs(), acd->codecs());
EXPECT_EQ(0U, acd->first_ssrc());
// no sender is attached
EXPECT_EQ(kAutoBandwidth, acd->bandwidth());
// default bandwidth (auto)
EXPECT_TRUE(acd->rtcp_mux());
// rtcp-mux defaults on
EXPECT_EQ(kMediaProtocolDtlsSavpf, acd->protocol());
EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type());
EXPECT_EQ(f1_.video_sendrecv_codecs(), vcd->codecs());
EXPECT_EQ(0U, vcd->first_ssrc());
// no sender is attached
EXPECT_EQ(kAutoBandwidth, vcd->bandwidth());
// default bandwidth (auto)
EXPECT_TRUE(vcd->rtcp_mux());
// rtcp-mux defaults on
EXPECT_EQ(kMediaProtocolDtlsSavpf, vcd->protocol());
}
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateOfferWithCustomCodecs) {
MediaSessionOptions opts;
webrtc::SdpAudioFormat audio_format(
"custom-audio", 8000, 2);
Codec custom_audio_codec = CreateAudioCodec(audio_format);
custom_audio_codec.id = 123;
// picked at random, but valid
auto audio_options = MediaDescriptionOptions(
MEDIA_TYPE_AUDIO,
"0", RtpTransceiverDirection::kSendRecv, kActive);
audio_options.codecs_to_include.push_back(custom_audio_codec);
opts.media_description_options.push_back(audio_options);
Codec custom_video_codec = CreateVideoCodec(
"custom-video");
custom_video_codec.id = 124;
// picked at random, but valid
auto video_options = MediaDescriptionOptions(
MEDIA_TYPE_VIDEO,
"1", RtpTransceiverDirection::kSendRecv, kActive);
video_options.codecs_to_include.push_back(custom_video_codec);
opts.media_description_options.push_back(video_options);
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(opts, nullptr).MoveValue();
ASSERT_TRUE(offer.get());
const ContentInfo* ac = offer->GetContentByName(
"0");
const ContentInfo* vc = offer->GetContentByName(
"1");
ASSERT_TRUE(ac);
ASSERT_TRUE(vc);
EXPECT_EQ(MediaProtocolType::kRtp, ac->type);
EXPECT_EQ(MediaProtocolType::kRtp, vc->type);
const MediaContentDescription* acd = ac->media_description();
const MediaContentDescription* vcd = vc->media_description();
EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
ASSERT_EQ(acd->codecs().size(), 1U);
// Fields in codec are set during the gen process, so simple compare
// does not work.
EXPECT_EQ(acd->codecs()[0].name, custom_audio_codec.name);
RTC_LOG(LS_ERROR) <<
"DEBUG: audio PT assigned is " << acd->codecs()[0].id;
EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type());
ASSERT_EQ(vcd->codecs().size(), 1U);
EXPECT_EQ(vcd->codecs()[0].name, custom_video_codec.name);
}
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAnswerWithCustomCodecs) {
MediaSessionOptions offer_opts;
MediaSessionOptions answer_opts;
AddAudioVideoSections(RtpTransceiverDirection::kSendRecv, &offer_opts);
// Create custom codecs and add to answer. These will override
// the normally generated codec list in the answer.
// This breaks O/A rules - the responsibility for obeying those is
// on the caller, not on this function.
webrtc::SdpAudioFormat audio_format(
"custom-audio", 8000, 2);
Codec custom_audio_codec = CreateAudioCodec(audio_format);
custom_audio_codec.id = 123;
// picked at random, but valid
auto audio_options = MediaDescriptionOptions(
MEDIA_TYPE_AUDIO,
"audio", RtpTransceiverDirection::kSendRecv, kActive);
audio_options.codecs_to_include.push_back(custom_audio_codec);
answer_opts.media_description_options.push_back(audio_options);
Codec custom_video_codec = CreateVideoCodec(
"custom-video");
custom_video_codec.id = 124;
auto video_options = MediaDescriptionOptions(
MEDIA_TYPE_VIDEO,
"video", RtpTransceiverDirection::kSendRecv, kActive);
video_options.codecs_to_include.push_back(custom_video_codec);
answer_opts.media_description_options.push_back(video_options);
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(offer_opts, nullptr).MoveValue();
ASSERT_TRUE(offer.get());
std::unique_ptr<SessionDescription> answer =
f1_.CreateAnswerOrError(offer.get(), answer_opts, nullptr).MoveValue();
const ContentInfo* ac = answer->GetContentByName(
"audio");
const ContentInfo* vc = answer->GetContentByName(
"video");
ASSERT_TRUE(ac);
ASSERT_TRUE(vc);
EXPECT_EQ(MediaProtocolType::kRtp, ac->type);
EXPECT_EQ(MediaProtocolType::kRtp, vc->type);
const MediaContentDescription* acd = ac->media_description();
const MediaContentDescription* vcd = vc->media_description();
EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
ASSERT_EQ(acd->codecs().size(), 1U);
// Fields in codec are set during the gen process, so simple compare
// does not work.
EXPECT_EQ(acd->codecs()[0].name, custom_audio_codec.name);
EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type());
ASSERT_EQ(vcd->codecs().size(), 1U);
EXPECT_EQ(vcd->codecs()[0].name, custom_video_codec.name);
}
// Test creating an offer with bundle where the Codecs have the same dynamic
// RTP paylod type. The test verifies that the offer don't contain the
// duplicate RTP payload types.
TEST_F(MediaSessionDescriptionFactoryTest, TestBundleOfferWithSameCodecPlType) {
const Codec& offered_video_codec = f2_.video_sendrecv_codecs()[0];
const Codec& offered_audio_codec = f2_.audio_sendrecv_codecs()[0];
ASSERT_EQ(offered_video_codec.id, offered_audio_codec.id);
MediaSessionOptions opts;
AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
opts.bundle_enabled =
true;
std::unique_ptr<SessionDescription> offer =
f2_.CreateOfferOrError(opts, nullptr).MoveValue();
const VideoContentDescription* vcd =
GetFirstVideoContentDescription(offer.get());
const AudioContentDescription* acd =
GetFirstAudioContentDescription(offer.get());
ASSERT_TRUE(vcd);
ASSERT_TRUE(acd);
EXPECT_NE(vcd->codecs()[0].id, acd->codecs()[0].id);
EXPECT_EQ(vcd->codecs()[0].name, offered_video_codec.name);
EXPECT_EQ(acd->codecs()[0].name, offered_audio_codec.name);
}
// Test creating an updated offer with bundle, audio, video and data
// after an audio only session has been negotiated.
TEST_F(MediaSessionDescriptionFactoryTest,
TestCreateUpdatedVideoOfferWithBundle) {
MediaSessionOptions opts;
AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO,
"audio",
RtpTransceiverDirection::kRecvOnly, kActive,
&opts);
AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO,
"video",
RtpTransceiverDirection::kInactive, kStopped,
&opts);
opts.bundle_enabled =
true;
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(opts, nullptr).MoveValue();
std::unique_ptr<SessionDescription> answer =
f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
MediaSessionOptions updated_opts;
AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &updated_opts);
updated_opts.bundle_enabled =
true;
std::unique_ptr<SessionDescription> updated_offer(
f1_.CreateOfferOrError(updated_opts, answer.get()).MoveValue());
const AudioContentDescription* acd =
GetFirstAudioContentDescription(updated_offer.get());
const VideoContentDescription* vcd =
GetFirstVideoContentDescription(updated_offer.get());
EXPECT_TRUE(vcd);
EXPECT_TRUE(acd);
EXPECT_EQ(kMediaProtocolDtlsSavpf, acd->protocol());
EXPECT_EQ(kMediaProtocolDtlsSavpf, vcd->protocol());
}
// Create an SCTP data offer with bundle without error.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateSctpDataOffer) {
MediaSessionOptions opts;
opts.bundle_enabled =
true;
AddDataSection(RtpTransceiverDirection::kSendRecv, &opts);
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(opts, nullptr).MoveValue();
EXPECT_TRUE(offer.get());
EXPECT_TRUE(offer->GetContentByName(
"data"));
auto dcd = GetFirstSctpDataContentDescription(offer.get());
ASSERT_TRUE(dcd);
// Since this transport is insecure, the protocol should be "SCTP".
EXPECT_EQ(kMediaProtocolUdpDtlsSctp, dcd->protocol());
}
// Create an SCTP data offer with bundle without error.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateSecureSctpDataOffer) {
MediaSessionOptions opts;
opts.bundle_enabled =
true;
AddDataSection(RtpTransceiverDirection::kSendRecv, &opts);
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(opts, nullptr).MoveValue();
EXPECT_TRUE(offer.get());
EXPECT_TRUE(offer->GetContentByName(
"data"));
auto dcd = GetFirstSctpDataContentDescription(offer.get());
ASSERT_TRUE(dcd);
// The protocol should now be "UDP/DTLS/SCTP"
EXPECT_EQ(kMediaProtocolUdpDtlsSctp, dcd->protocol());
}
// Test creating an sctp data channel from an already generated offer.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateImplicitSctpDataOffer) {
MediaSessionOptions opts;
opts.bundle_enabled =
true;
AddDataSection(RtpTransceiverDirection::kSendRecv, &opts);
std::unique_ptr<SessionDescription> offer1(
f1_.CreateOfferOrError(opts, nullptr).MoveValue());
ASSERT_TRUE(offer1.get());
const ContentInfo* data = offer1->GetContentByName(
"data");
ASSERT_TRUE(data);
ASSERT_EQ(kMediaProtocolUdpDtlsSctp, data->media_description()->protocol());
std::unique_ptr<SessionDescription> offer2(
f1_.CreateOfferOrError(opts, offer1.get()).MoveValue());
data = offer2->GetContentByName(
"data");
ASSERT_TRUE(data);
EXPECT_EQ(kMediaProtocolUdpDtlsSctp, data->media_description()->protocol());
}
// Test that if BUNDLE is enabled and all media sections are rejected then the
// BUNDLE group is not present in the re-offer.
TEST_F(MediaSessionDescriptionFactoryTest, ReOfferNoBundleGroupIfAllRejected) {
MediaSessionOptions opts;
opts.bundle_enabled =
true;
AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO,
"audio",
RtpTransceiverDirection::kSendRecv, kActive,
&opts);
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(opts, nullptr).MoveValue();
opts.media_description_options[0].stopped =
true;
std::unique_ptr<SessionDescription> reoffer =
f1_.CreateOfferOrError(opts, offer.get()).MoveValue();
EXPECT_FALSE(reoffer->GetGroupByName(GROUP_TYPE_BUNDLE));
}
// Test that if BUNDLE is enabled and the remote re-offer does not include a
// BUNDLE group since all media sections are rejected, then the re-answer also
// does not include a BUNDLE group.
TEST_F(MediaSessionDescriptionFactoryTest, ReAnswerNoBundleGroupIfAllRejected) {
MediaSessionOptions opts;
opts.bundle_enabled =
true;
AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO,
"audio",
RtpTransceiverDirection::kSendRecv, kActive,
&opts);
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(opts, nullptr).MoveValue();
std::unique_ptr<SessionDescription> answer =
f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
opts.media_description_options[0].stopped =
true;
std::unique_ptr<SessionDescription> reoffer =
f1_.CreateOfferOrError(opts, offer.get()).MoveValue();
std::unique_ptr<SessionDescription> reanswer =
f2_.CreateAnswerOrError(reoffer.get(), opts, answer.get()).MoveValue();
EXPECT_FALSE(reanswer->GetGroupByName(GROUP_TYPE_BUNDLE));
}
// Test that if BUNDLE is enabled and the previous offerer-tagged media section
// was rejected then the new offerer-tagged media section is the non-rejected
// media section.
TEST_F(MediaSessionDescriptionFactoryTest, ReOfferChangeBundleOffererTagged) {
MediaSessionOptions opts;
opts.bundle_enabled =
true;
AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO,
"audio",
RtpTransceiverDirection::kSendRecv, kActive,
&opts);
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(opts, nullptr).MoveValue();
// Reject the audio m= section and add a video m= section.
opts.media_description_options[0].stopped =
true;
AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO,
"video",
RtpTransceiverDirection::kSendRecv, kActive,
&opts);
std::unique_ptr<SessionDescription> reoffer =
f1_.CreateOfferOrError(opts, offer.get()).MoveValue();
const ContentGroup* bundle_group = reoffer->GetGroupByName(GROUP_TYPE_BUNDLE);
ASSERT_TRUE(bundle_group);
EXPECT_FALSE(bundle_group->HasContentName(
"audio"));
EXPECT_TRUE(bundle_group->HasContentName(
"video"));
}
// Test that if BUNDLE is enabled and the previous offerer-tagged media section
// was rejected and a new media section is added, then the re-answer BUNDLE
// group will contain only the non-rejected media section.
TEST_F(MediaSessionDescriptionFactoryTest, ReAnswerChangedBundleOffererTagged) {
MediaSessionOptions opts;
opts.bundle_enabled =
true;
AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO,
"audio",
RtpTransceiverDirection::kSendRecv, kActive,
&opts);
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(opts, nullptr).MoveValue();
std::unique_ptr<SessionDescription> answer =
f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
// Reject the audio m= section and add a video m= section.
opts.media_description_options[0].stopped =
true;
AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO,
"video",
RtpTransceiverDirection::kSendRecv, kActive,
&opts);
std::unique_ptr<SessionDescription> reoffer =
f1_.CreateOfferOrError(opts, offer.get()).MoveValue();
std::unique_ptr<SessionDescription> reanswer =
f2_.CreateAnswerOrError(reoffer.get(), opts, answer.get()).MoveValue();
const ContentGroup* bundle_group =
reanswer->GetGroupByName(GROUP_TYPE_BUNDLE);
ASSERT_TRUE(bundle_group);
EXPECT_FALSE(bundle_group->HasContentName(
"audio"));
EXPECT_TRUE(bundle_group->HasContentName(
"video"));
}
TEST_F(MediaSessionDescriptionFactoryTest,
CreateAnswerForOfferWithMultipleBundleGroups) {
// Create an offer with 4 m= sections, initially without BUNDLE groups.
MediaSessionOptions opts;
opts.bundle_enabled =
false;
AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO,
"1",
RtpTransceiverDirection::kSendRecv, kActive,
&opts);
AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO,
"2",
RtpTransceiverDirection::kSendRecv, kActive,
&opts);
AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO,
"3",
RtpTransceiverDirection::kSendRecv, kActive,
&opts);
AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO,
"4",
RtpTransceiverDirection::kSendRecv, kActive,
&opts);
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(opts, nullptr).MoveValue();
ASSERT_TRUE(offer->groups().empty());
// Munge the offer to have two groups. Offers like these cannot be generated
// without munging, but it is valid to receive such offers from remote
// endpoints.
ContentGroup bundle_group1(GROUP_TYPE_BUNDLE);
bundle_group1.AddContentName(
"1");
bundle_group1.AddContentName(
"2");
ContentGroup bundle_group2(GROUP_TYPE_BUNDLE);
bundle_group2.AddContentName(
"3");
bundle_group2.AddContentName(
"4");
offer->AddGroup(bundle_group1);
offer->AddGroup(bundle_group2);
// If BUNDLE is enabled, the answer to this offer should accept both BUNDLE
// groups.
opts.bundle_enabled =
true;
std::unique_ptr<SessionDescription> answer =
f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
std::vector<
const ContentGroup*> answer_groups =
answer->GetGroupsByName(GROUP_TYPE_BUNDLE);
ASSERT_EQ(answer_groups.size(), 2u);
EXPECT_EQ(answer_groups[0]->content_names().size(), 2u);
EXPECT_TRUE(answer_groups[0]->HasContentName(
"1"));
EXPECT_TRUE(answer_groups[0]->HasContentName(
"2"));
EXPECT_EQ(answer_groups[1]->content_names().size(), 2u);
EXPECT_TRUE(answer_groups[1]->HasContentName(
"3"));
EXPECT_TRUE(answer_groups[1]->HasContentName(
"4"));
// If BUNDLE is disabled, the answer to this offer should reject both BUNDLE
// groups.
opts.bundle_enabled =
false;
answer = f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
answer_groups = answer->GetGroupsByName(GROUP_TYPE_BUNDLE);
// Rejected groups are still listed, but they are empty.
ASSERT_EQ(answer_groups.size(), 2u);
EXPECT_TRUE(answer_groups[0]->content_names().empty());
EXPECT_TRUE(answer_groups[1]->content_names().empty());
}
// Test that if the BUNDLE offerer-tagged media section is changed in a reoffer
// and there is still a non-rejected media section that was in the initial
// offer, then the ICE credentials do not change in the reoffer offerer-tagged
// media section.
TEST_F(MediaSessionDescriptionFactoryTest,
ReOfferChangeBundleOffererTaggedKeepsIceCredentials) {
MediaSessionOptions opts;
opts.bundle_enabled =
true;
AddAudioVideoSections(RtpTransceiverDirection::kSendRecv, &opts);
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(opts, nullptr).MoveValue();
std::unique_ptr<SessionDescription> answer =
f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
// Reject the audio m= section.
opts.media_description_options[0].stopped =
true;
std::unique_ptr<SessionDescription> reoffer =
f1_.CreateOfferOrError(opts, offer.get()).MoveValue();
const TransportDescription* offer_tagged =
offer->GetTransportDescriptionByName(
"audio");
ASSERT_TRUE(offer_tagged);
const TransportDescription* reoffer_tagged =
reoffer->GetTransportDescriptionByName(
"video");
ASSERT_TRUE(reoffer_tagged);
EXPECT_EQ(offer_tagged->ice_ufrag, reoffer_tagged->ice_ufrag);
EXPECT_EQ(offer_tagged->ice_pwd, reoffer_tagged->ice_pwd);
}
// Test that if the BUNDLE offerer-tagged media section is changed in a reoffer
// and there is still a non-rejected media section that was in the initial
// offer, then the ICE credentials do not change in the reanswer answerer-tagged
// media section.
TEST_F(MediaSessionDescriptionFactoryTest,
ReAnswerChangeBundleOffererTaggedKeepsIceCredentials) {
MediaSessionOptions opts;
opts.bundle_enabled =
true;
AddAudioVideoSections(RtpTransceiverDirection::kSendRecv, &opts);
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(opts, nullptr).MoveValue();
std::unique_ptr<SessionDescription> answer =
f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
// Reject the audio m= section.
opts.media_description_options[0].stopped =
true;
std::unique_ptr<SessionDescription> reoffer =
f1_.CreateOfferOrError(opts, offer.get()).MoveValue();
std::unique_ptr<SessionDescription> reanswer =
f2_.CreateAnswerOrError(reoffer.get(), opts, answer.get()).MoveValue();
const TransportDescription* answer_tagged =
answer->GetTransportDescriptionByName(
"audio");
ASSERT_TRUE(answer_tagged);
const TransportDescription* reanswer_tagged =
reanswer->GetTransportDescriptionByName(
"video");
ASSERT_TRUE(reanswer_tagged);
EXPECT_EQ(answer_tagged->ice_ufrag, reanswer_tagged->ice_ufrag);
EXPECT_EQ(answer_tagged->ice_pwd, reanswer_tagged->ice_pwd);
}
// Create an audio, video offer without legacy StreamParams.
TEST_F(MediaSessionDescriptionFactoryTest,
TestCreateOfferWithoutLegacyStreams) {
MediaSessionOptions opts;
AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(opts, nullptr).MoveValue();
ASSERT_TRUE(offer.get());
const ContentInfo* ac = offer->GetContentByName(
"audio");
const ContentInfo* vc = offer->GetContentByName(
"video");
ASSERT_TRUE(ac);
ASSERT_TRUE(vc);
const MediaContentDescription* acd = ac->media_description();
const MediaContentDescription* vcd = vc->media_description();
EXPECT_FALSE(vcd->has_ssrcs());
// No StreamParams.
EXPECT_FALSE(acd->has_ssrcs());
// No StreamParams.
}
// Creates an audio+video sendonly offer.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateSendOnlyOffer) {
MediaSessionOptions opts;
AddAudioVideoSections(RtpTransceiverDirection::kSendOnly, &opts);
AttachSenderToMediaDescriptionOptions(
"video", MEDIA_TYPE_VIDEO, kVideoTrack1,
{kMediaStream1}, 1, &opts);
AttachSenderToMediaDescriptionOptions(
"audio", MEDIA_TYPE_AUDIO, kAudioTrack1,
{kMediaStream1}, 1, &opts);
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(opts, nullptr).MoveValue();
ASSERT_TRUE(offer.get());
EXPECT_EQ(2u, offer->contents().size());
EXPECT_TRUE(IsMediaContentOfType(&offer->contents()[0], MEDIA_TYPE_AUDIO));
EXPECT_TRUE(IsMediaContentOfType(&offer->contents()[1], MEDIA_TYPE_VIDEO));
EXPECT_EQ(RtpTransceiverDirection::kSendOnly,
GetMediaDirection(&offer->contents()[0]));
EXPECT_EQ(RtpTransceiverDirection::kSendOnly,
GetMediaDirection(&offer->contents()[1]));
}
// Verifies that the order of the media contents in the current
// SessionDescription is preserved in the new SessionDescription.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateOfferContentOrder) {
MediaSessionOptions opts;
AddDataSection(RtpTransceiverDirection::kSendRecv, &opts);
std::unique_ptr<SessionDescription> offer1(
f1_.CreateOfferOrError(opts, nullptr).MoveValue());
ASSERT_TRUE(offer1.get());
EXPECT_EQ(1u, offer1->contents().size());
EXPECT_TRUE(IsMediaContentOfType(&offer1->contents()[0], MEDIA_TYPE_DATA));
AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO,
"video",
RtpTransceiverDirection::kRecvOnly, kActive,
&opts);
std::unique_ptr<SessionDescription> offer2(
f1_.CreateOfferOrError(opts, offer1.get()).MoveValue());
ASSERT_TRUE(offer2.get());
EXPECT_EQ(2u, offer2->contents().size());
EXPECT_TRUE(IsMediaContentOfType(&offer2->contents()[0], MEDIA_TYPE_DATA));
EXPECT_TRUE(IsMediaContentOfType(&offer2->contents()[1], MEDIA_TYPE_VIDEO));
AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO,
"audio",
RtpTransceiverDirection::kRecvOnly, kActive,
&opts);
std::unique_ptr<SessionDescription> offer3(
f1_.CreateOfferOrError(opts, offer2.get()).MoveValue());
ASSERT_TRUE(offer3.get());
EXPECT_EQ(3u, offer3->contents().size());
EXPECT_TRUE(IsMediaContentOfType(&offer3->contents()[0], MEDIA_TYPE_DATA));
EXPECT_TRUE(IsMediaContentOfType(&offer3->contents()[1], MEDIA_TYPE_VIDEO));
EXPECT_TRUE(IsMediaContentOfType(&offer3->contents()[2], MEDIA_TYPE_AUDIO));
}
// Create a typical audio answer, and ensure it matches what we expect.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswer) {
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(CreateAudioMediaSession(), nullptr).MoveValue();
ASSERT_TRUE(offer.get());
std::unique_ptr<SessionDescription> answer =
f2_.CreateAnswerOrError(offer.get(), CreateAudioMediaSession(), nullptr)
.MoveValue();
const ContentInfo* ac = answer->GetContentByName(
"audio");
const ContentInfo* vc = answer->GetContentByName(
"video");
ASSERT_TRUE(ac);
EXPECT_FALSE(vc);
EXPECT_EQ(MediaProtocolType::kRtp, ac->type);
const MediaContentDescription* acd = ac->media_description();
EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
EXPECT_THAT(acd->codecs(), ElementsAreArray(kAudioCodecsAnswer));
EXPECT_EQ(0U, acd->first_ssrc());
// no sender is attached
EXPECT_EQ(kAutoBandwidth, acd->bandwidth());
// negotiated auto bw
EXPECT_TRUE(acd->rtcp_mux());
// negotiated rtcp-mux
EXPECT_EQ(kMediaProtocolDtlsSavpf, acd->protocol());
}
// Create a typical audio answer with GCM ciphers enabled, and ensure it
// matches what we expect.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswerGcm) {
MediaSessionOptions opts = CreateAudioMediaSession();
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(opts, nullptr).MoveValue();
ASSERT_TRUE(offer.get());
std::unique_ptr<SessionDescription> answer =
f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
const ContentInfo* ac = answer->GetContentByName(
"audio");
const ContentInfo* vc = answer->GetContentByName(
"video");
ASSERT_TRUE(ac);
EXPECT_FALSE(vc);
EXPECT_EQ(MediaProtocolType::kRtp, ac->type);
const MediaContentDescription* acd = ac->media_description();
EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
EXPECT_THAT(acd->codecs(), ElementsAreArray(kAudioCodecsAnswer));
EXPECT_EQ(0U, acd->first_ssrc());
// no sender is attached
EXPECT_EQ(kAutoBandwidth, acd->bandwidth());
// negotiated auto bw
EXPECT_TRUE(acd->rtcp_mux());
// negotiated rtcp-mux
EXPECT_EQ(kMediaProtocolDtlsSavpf, acd->protocol());
}
// Create an audio answer with no common codecs, and ensure it is rejected.
TEST_F(MediaSessionDescriptionFactoryTest,
TestCreateAudioAnswerWithNoCommonCodecs) {
MediaSessionOptions opts;
AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO,
"audio",
RtpTransceiverDirection::kSendRecv, kActive,
&opts);
std::vector f1_codecs = {CreateAudioCodec(96,
"opus", 48000, 1)};
f1_.set_audio_codecs(f1_codecs, f1_codecs);
std::vector f2_codecs = {CreateAudioCodec(0,
"PCMU", 8000, 1)};
f2_.set_audio_codecs(f2_codecs, f2_codecs);
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(opts, nullptr).MoveValue();
std::unique_ptr<SessionDescription> answer =
f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
const ContentInfo* ac = answer->GetContentByName(
"audio");
ASSERT_TRUE(ac);
EXPECT_TRUE(ac->rejected);
}
// Create a typical video answer, and ensure it matches what we expect.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswer) {
MediaSessionOptions opts;
AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(opts, nullptr).MoveValue();
ASSERT_TRUE(offer.get());
std::unique_ptr<SessionDescription> answer =
f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
const ContentInfo* ac = answer->GetContentByName(
"audio");
const ContentInfo* vc = answer->GetContentByName(
"video");
ASSERT_TRUE(ac);
ASSERT_TRUE(vc);
EXPECT_EQ(MediaProtocolType::kRtp, ac->type);
EXPECT_EQ(MediaProtocolType::kRtp, vc->type);
const MediaContentDescription* acd = ac->media_description();
const MediaContentDescription* vcd = vc->media_description();
EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
EXPECT_THAT(acd->codecs(), ElementsAreArray(kAudioCodecsAnswer));
EXPECT_EQ(kAutoBandwidth, acd->bandwidth());
// negotiated auto bw
EXPECT_EQ(0U, acd->first_ssrc());
// no sender is attached
EXPECT_TRUE(acd->rtcp_mux());
// negotiated rtcp-mux
EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type());
EXPECT_THAT(vcd->codecs(), ElementsAreArray(kVideoCodecsAnswer));
EXPECT_EQ(0U, vcd->first_ssrc());
// no sender is attached
EXPECT_TRUE(vcd->rtcp_mux());
// negotiated rtcp-mux
EXPECT_EQ(kMediaProtocolDtlsSavpf, vcd->protocol());
}
// Create a video answer with no common codecs, and ensure it is rejected.
TEST_F(MediaSessionDescriptionFactoryTest,
TestCreateVideoAnswerWithNoCommonCodecs) {
MediaSessionOptions opts;
AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO,
"video",
RtpTransceiverDirection::kSendRecv, kActive,
&opts);
std::vector f1_codecs = {CreateVideoCodec(96,
"H264")};
f1_.set_video_codecs(f1_codecs, f1_codecs);
std::vector f2_codecs = {CreateVideoCodec(97,
"VP8")};
f2_.set_video_codecs(f2_codecs, f2_codecs);
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(opts, nullptr).MoveValue();
std::unique_ptr<SessionDescription> answer =
f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
const ContentInfo* vc = answer->GetContentByName(
"video");
ASSERT_TRUE(vc);
EXPECT_TRUE(vc->rejected);
}
// Create a video answer with no common codecs (but a common FEC codec), and
// ensure it is rejected.
TEST_F(MediaSessionDescriptionFactoryTest,
TestCreateVideoAnswerWithOnlyFecCodecsCommon) {
MediaSessionOptions opts;
AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO,
"video",
RtpTransceiverDirection::kSendRecv, kActive,
&opts);
std::vector f1_codecs = {CreateVideoCodec(96,
"H264"),
CreateVideoCodec(118,
"flexfec-03")};
f1_.set_video_codecs(f1_codecs, f1_codecs);
std::vector f2_codecs = {CreateVideoCodec(97,
"VP8"),
CreateVideoCodec(118,
"flexfec-03")};
f2_.set_video_codecs(f2_codecs, f2_codecs);
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(opts, nullptr).MoveValue();
std::unique_ptr<SessionDescription> answer =
f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
const ContentInfo* vc = answer->GetContentByName(
"video");
ASSERT_TRUE(vc);
EXPECT_TRUE(vc->rejected);
}
// The use_sctpmap flag should be set in an Sctp DataContentDescription by
// default. The answer's use_sctpmap flag should match the offer's.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataAnswerUsesSctpmap) {
MediaSessionOptions opts;
AddDataSection(RtpTransceiverDirection::kSendRecv, &opts);
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(opts, nullptr).MoveValue();
ASSERT_TRUE(offer.get());
ContentInfo* dc_offer = offer->GetContentByName(
"data");
ASSERT_TRUE(dc_offer);
SctpDataContentDescription* dcd_offer =
dc_offer->media_description()->as_sctp();
EXPECT_TRUE(dcd_offer->use_sctpmap());
std::unique_ptr<SessionDescription> answer =
f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
const ContentInfo* dc_answer = answer->GetContentByName(
"data");
ASSERT_TRUE(dc_answer);
const SctpDataContentDescription* dcd_answer =
dc_answer->media_description()->as_sctp();
EXPECT_TRUE(dcd_answer->use_sctpmap());
}
// The answer's use_sctpmap flag should match the offer's.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataAnswerWithoutSctpmap) {
MediaSessionOptions opts;
AddDataSection(RtpTransceiverDirection::kSendRecv, &opts);
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(opts, nullptr).MoveValue();
ASSERT_TRUE(offer.get());
ContentInfo* dc_offer = offer->GetContentByName(
"data");
ASSERT_TRUE(dc_offer);
SctpDataContentDescription* dcd_offer =
dc_offer->media_description()->as_sctp();
dcd_offer->set_use_sctpmap(
false);
std::unique_ptr<SessionDescription> answer =
f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
const ContentInfo* dc_answer = answer->GetContentByName(
"data");
ASSERT_TRUE(dc_answer);
const SctpDataContentDescription* dcd_answer =
dc_answer->media_description()->as_sctp();
EXPECT_FALSE(dcd_answer->use_sctpmap());
}
// Test that a valid answer will be created for "DTLS/SCTP", "UDP/DTLS/SCTP"
// and "TCP/DTLS/SCTP" offers.
TEST_F(MediaSessionDescriptionFactoryTest,
TestCreateDataAnswerToDifferentOfferedProtos) {
MediaSessionOptions opts;
AddDataSection(RtpTransceiverDirection::kSendRecv, &opts);
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(opts, nullptr).MoveValue();
ASSERT_TRUE(offer.get());
ContentInfo* dc_offer = offer->GetContentByName(
"data");
ASSERT_TRUE(dc_offer);
SctpDataContentDescription* dcd_offer =
dc_offer->media_description()->as_sctp();
ASSERT_TRUE(dcd_offer);
std::vector<std::string> protos = {
"DTLS/SCTP",
"UDP/DTLS/SCTP",
"TCP/DTLS/SCTP"};
for (
const std::string& proto : protos) {
dcd_offer->set_protocol(proto);
std::unique_ptr<SessionDescription> answer =
f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
const ContentInfo* dc_answer = answer->GetContentByName(
"data");
ASSERT_TRUE(dc_answer);
const SctpDataContentDescription* dcd_answer =
dc_answer->media_description()->as_sctp();
EXPECT_FALSE(dc_answer->rejected);
EXPECT_EQ(proto, dcd_answer->protocol());
}
}
TEST_F(MediaSessionDescriptionFactoryTest,
TestCreateDataAnswerToOfferWithDefinedMessageSize) {
MediaSessionOptions opts;
AddDataSection(RtpTransceiverDirection::kSendRecv, &opts);
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(opts, nullptr).MoveValue();
ASSERT_TRUE(offer.get());
ContentInfo* dc_offer = offer->GetContentByName(
"data");
ASSERT_TRUE(dc_offer);
SctpDataContentDescription* dcd_offer =
dc_offer->media_description()->as_sctp();
ASSERT_TRUE(dcd_offer);
dcd_offer->set_max_message_size(1234);
std::unique_ptr<SessionDescription> answer =
f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
const ContentInfo* dc_answer = answer->GetContentByName(
"data");
ASSERT_TRUE(dc_answer);
const SctpDataContentDescription* dcd_answer =
dc_answer->media_description()->as_sctp();
EXPECT_FALSE(dc_answer->rejected);
EXPECT_EQ(1234, dcd_answer->max_message_size());
}
TEST_F(MediaSessionDescriptionFactoryTest,
TestCreateDataAnswerToOfferWithZeroMessageSize) {
MediaSessionOptions opts;
AddDataSection(RtpTransceiverDirection::kSendRecv, &opts);
std::unique_ptr<SessionDescription> offer =
f1_.CreateOfferOrError(opts, nullptr).MoveValue();
ASSERT_TRUE(offer.get());
ContentInfo* dc_offer = offer->GetContentByName(
"data");
ASSERT_TRUE(dc_offer);
SctpDataContentDescription* dcd_offer =
dc_offer->media_description()->as_sctp();
ASSERT_TRUE(dcd_offer);
dcd_offer->set_max_message_size(0);
std::unique_ptr<SessionDescription> answer =
f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
--> --------------------
--> maximum size reached
--> --------------------