webrtc/talk/session/media/mediasession.cc
jiayl@webrtc.org e7d47a1473 Maintain the order of the m-lines in CreateOffer and CreateAnswer.
The order in the offer follows the order in the current local description.
The order in the answer follows the order in the current offer.

BUG=2395
R=wu@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/12799004

git-svn-id: http://webrtc.googlecode.com/svn/trunk@6828 4adac7df-926f-26a2-2b94-8c16560cd09d
2014-08-05 19:19:05 +00:00

1899 lines
64 KiB
C++

/*
* libjingle
* Copyright 2004 Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "talk/session/media/mediasession.h"
#include <functional>
#include <map>
#include <set>
#include <utility>
#include "webrtc/base/helpers.h"
#include "webrtc/base/logging.h"
#include "webrtc/base/scoped_ptr.h"
#include "webrtc/base/stringutils.h"
#include "talk/media/base/constants.h"
#include "talk/media/base/cryptoparams.h"
#include "talk/p2p/base/constants.h"
#include "talk/session/media/channelmanager.h"
#include "talk/session/media/srtpfilter.h"
#include "talk/xmpp/constants.h"
#ifdef HAVE_SCTP
#include "talk/media/sctp/sctpdataengine.h"
#else
static const uint32 kMaxSctpSid = 1023;
#endif
namespace {
const char kInline[] = "inline:";
}
namespace cricket {
using rtc::scoped_ptr;
// RTP Profile names
// http://www.iana.org/assignments/rtp-parameters/rtp-parameters.xml
// RFC4585
const char kMediaProtocolAvpf[] = "RTP/AVPF";
// RFC5124
const char kMediaProtocolDtlsSavpf[] = "UDP/TLS/RTP/SAVPF";
// This should be replaced by "UDP/TLS/RTP/SAVPF", but we need to support it for
// now to be compatible with previous Chrome versions.
const char kMediaProtocolSavpf[] = "RTP/SAVPF";
const char kMediaProtocolRtpPrefix[] = "RTP/";
const char kMediaProtocolSctp[] = "SCTP";
const char kMediaProtocolDtlsSctp[] = "DTLS/SCTP";
static bool IsMediaContentOfType(const ContentInfo* content,
MediaType media_type) {
if (!IsMediaContent(content)) {
return false;
}
const MediaContentDescription* mdesc =
static_cast<const MediaContentDescription*>(content->description);
return mdesc && mdesc->type() == media_type;
}
static bool CreateCryptoParams(int tag, const std::string& cipher,
CryptoParams *out) {
std::string key;
key.reserve(SRTP_MASTER_KEY_BASE64_LEN);
if (!rtc::CreateRandomString(SRTP_MASTER_KEY_BASE64_LEN, &key)) {
return false;
}
out->tag = tag;
out->cipher_suite = cipher;
out->key_params = kInline;
out->key_params += key;
return true;
}
#ifdef HAVE_SRTP
static bool AddCryptoParams(const std::string& cipher_suite,
CryptoParamsVec *out) {
int size = static_cast<int>(out->size());
out->resize(size + 1);
return CreateCryptoParams(size, cipher_suite, &out->at(size));
}
void AddMediaCryptos(const CryptoParamsVec& cryptos,
MediaContentDescription* media) {
for (CryptoParamsVec::const_iterator crypto = cryptos.begin();
crypto != cryptos.end(); ++crypto) {
media->AddCrypto(*crypto);
}
}
bool CreateMediaCryptos(const std::vector<std::string>& crypto_suites,
MediaContentDescription* media) {
CryptoParamsVec cryptos;
for (std::vector<std::string>::const_iterator it = crypto_suites.begin();
it != crypto_suites.end(); ++it) {
if (!AddCryptoParams(*it, &cryptos)) {
return false;
}
}
AddMediaCryptos(cryptos, media);
return true;
}
#endif
const CryptoParamsVec* GetCryptos(const MediaContentDescription* media) {
if (!media) {
return NULL;
}
return &media->cryptos();
}
bool FindMatchingCrypto(const CryptoParamsVec& cryptos,
const CryptoParams& crypto,
CryptoParams* out) {
for (CryptoParamsVec::const_iterator it = cryptos.begin();
it != cryptos.end(); ++it) {
if (crypto.Matches(*it)) {
*out = *it;
return true;
}
}
return false;
}
// For audio, HMAC 32 is prefered because of the low overhead.
void GetSupportedAudioCryptoSuites(
std::vector<std::string>* crypto_suites) {
#ifdef HAVE_SRTP
crypto_suites->push_back(CS_AES_CM_128_HMAC_SHA1_32);
crypto_suites->push_back(CS_AES_CM_128_HMAC_SHA1_80);
#endif
}
void GetSupportedVideoCryptoSuites(
std::vector<std::string>* crypto_suites) {
GetSupportedDefaultCryptoSuites(crypto_suites);
}
void GetSupportedDataCryptoSuites(
std::vector<std::string>* crypto_suites) {
GetSupportedDefaultCryptoSuites(crypto_suites);
}
void GetSupportedDefaultCryptoSuites(
std::vector<std::string>* crypto_suites) {
#ifdef HAVE_SRTP
crypto_suites->push_back(CS_AES_CM_128_HMAC_SHA1_80);
#endif
}
// For video support only 80-bit SHA1 HMAC. For audio 32-bit HMAC is
// tolerated unless bundle is enabled because it is low overhead. Pick the
// crypto in the list that is supported.
static bool SelectCrypto(const MediaContentDescription* offer,
bool bundle,
CryptoParams *crypto) {
bool audio = offer->type() == MEDIA_TYPE_AUDIO;
const CryptoParamsVec& cryptos = offer->cryptos();
for (CryptoParamsVec::const_iterator i = cryptos.begin();
i != cryptos.end(); ++i) {
if (CS_AES_CM_128_HMAC_SHA1_80 == i->cipher_suite ||
(CS_AES_CM_128_HMAC_SHA1_32 == i->cipher_suite && audio && !bundle)) {
return CreateCryptoParams(i->tag, i->cipher_suite, crypto);
}
}
return false;
}
static const StreamParams* FindFirstStreamParamsByCname(
const StreamParamsVec& params_vec,
const std::string& cname) {
for (StreamParamsVec::const_iterator it = params_vec.begin();
it != params_vec.end(); ++it) {
if (cname == it->cname)
return &*it;
}
return NULL;
}
// Generates a new CNAME or the CNAME of an already existing StreamParams
// if a StreamParams exist for another Stream in streams with sync_label
// sync_label.
static bool GenerateCname(const StreamParamsVec& params_vec,
const MediaSessionOptions::Streams& streams,
const std::string& synch_label,
std::string* cname) {
ASSERT(cname != NULL);
if (!cname)
return false;
// Check if a CNAME exist for any of the other synched streams.
for (MediaSessionOptions::Streams::const_iterator stream_it = streams.begin();
stream_it != streams.end() ; ++stream_it) {
if (synch_label != stream_it->sync_label)
continue;
StreamParams param;
// groupid is empty for StreamParams generated using
// MediaSessionDescriptionFactory.
if (GetStreamByIds(params_vec, "", stream_it->id,
&param)) {
*cname = param.cname;
return true;
}
}
// No other stream seems to exist that we should sync with.
// Generate a random string for the RTCP CNAME, as stated in RFC 6222.
// This string is only used for synchronization, and therefore is opaque.
do {
if (!rtc::CreateRandomString(16, cname)) {
ASSERT(false);
return false;
}
} while (FindFirstStreamParamsByCname(params_vec, *cname));
return true;
}
// Generate random SSRC values that are not already present in |params_vec|.
// The generated values are added to |ssrcs|.
// |num_ssrcs| is the number of the SSRC will be generated.
static void GenerateSsrcs(const StreamParamsVec& params_vec,
int num_ssrcs,
std::vector<uint32>* ssrcs) {
for (int i = 0; i < num_ssrcs; i++) {
uint32 candidate;
do {
candidate = rtc::CreateRandomNonZeroId();
} while (GetStreamBySsrc(params_vec, candidate, NULL) ||
std::count(ssrcs->begin(), ssrcs->end(), candidate) > 0);
ssrcs->push_back(candidate);
}
}
// Returns false if we exhaust the range of SIDs.
static bool GenerateSctpSid(const StreamParamsVec& params_vec,
uint32* sid) {
if (params_vec.size() > kMaxSctpSid) {
LOG(LS_WARNING) <<
"Could not generate an SCTP SID: too many SCTP streams.";
return false;
}
while (true) {
uint32 candidate = rtc::CreateRandomNonZeroId() % kMaxSctpSid;
if (!GetStreamBySsrc(params_vec, candidate, NULL)) {
*sid = candidate;
return true;
}
}
}
static bool GenerateSctpSids(const StreamParamsVec& params_vec,
std::vector<uint32>* sids) {
uint32 sid;
if (!GenerateSctpSid(params_vec, &sid)) {
LOG(LS_WARNING) << "Could not generated an SCTP SID.";
return false;
}
sids->push_back(sid);
return true;
}
// Finds all StreamParams of all media types and attach them to stream_params.
static void GetCurrentStreamParams(const SessionDescription* sdesc,
StreamParamsVec* stream_params) {
if (!sdesc)
return;
const ContentInfos& contents = sdesc->contents();
for (ContentInfos::const_iterator content = contents.begin();
content != contents.end(); ++content) {
if (!IsMediaContent(&*content)) {
continue;
}
const MediaContentDescription* media =
static_cast<const MediaContentDescription*>(
content->description);
const StreamParamsVec& streams = media->streams();
for (StreamParamsVec::const_iterator it = streams.begin();
it != streams.end(); ++it) {
stream_params->push_back(*it);
}
}
}
// Filters the data codecs for the data channel type.
void FilterDataCodecs(std::vector<DataCodec>* codecs, bool sctp) {
// Filter RTP codec for SCTP and vice versa.
int codec_id = sctp ? kGoogleRtpDataCodecId : kGoogleSctpDataCodecId;
for (std::vector<DataCodec>::iterator iter = codecs->begin();
iter != codecs->end();) {
if (iter->id == codec_id) {
iter = codecs->erase(iter);
} else {
++iter;
}
}
}
template <typename IdStruct>
class UsedIds {
public:
UsedIds(int min_allowed_id, int max_allowed_id)
: min_allowed_id_(min_allowed_id),
max_allowed_id_(max_allowed_id),
next_id_(max_allowed_id) {
}
// Loops through all Id in |ids| and changes its id if it is
// already in use by another IdStruct. Call this methods with all Id
// in a session description to make sure no duplicate ids exists.
// Note that typename Id must be a type of IdStruct.
template <typename Id>
void FindAndSetIdUsed(std::vector<Id>* ids) {
for (typename std::vector<Id>::iterator it = ids->begin();
it != ids->end(); ++it) {
FindAndSetIdUsed(&*it);
}
}
// Finds and sets an unused id if the |idstruct| id is already in use.
void FindAndSetIdUsed(IdStruct* idstruct) {
const int original_id = idstruct->id;
int new_id = idstruct->id;
if (original_id > max_allowed_id_ || original_id < min_allowed_id_) {
// If the original id is not in range - this is an id that can't be
// dynamically changed.
return;
}
if (IsIdUsed(original_id)) {
new_id = FindUnusedId();
LOG(LS_WARNING) << "Duplicate id found. Reassigning from " << original_id
<< " to " << new_id;
idstruct->id = new_id;
}
SetIdUsed(new_id);
}
private:
// Returns the first unused id in reverse order.
// This hopefully reduce the risk of more collisions. We want to change the
// default ids as little as possible.
int FindUnusedId() {
while (IsIdUsed(next_id_) && next_id_ >= min_allowed_id_) {
--next_id_;
}
ASSERT(next_id_ >= min_allowed_id_);
return next_id_;
}
bool IsIdUsed(int new_id) {
return id_set_.find(new_id) != id_set_.end();
}
void SetIdUsed(int new_id) {
id_set_.insert(new_id);
}
const int min_allowed_id_;
const int max_allowed_id_;
int next_id_;
std::set<int> id_set_;
};
// Helper class used for finding duplicate RTP payload types among audio, video
// and data codecs. When bundle is used the payload types may not collide.
class UsedPayloadTypes : public UsedIds<Codec> {
public:
UsedPayloadTypes()
: UsedIds<Codec>(kDynamicPayloadTypeMin, kDynamicPayloadTypeMax) {
}
private:
static const int kDynamicPayloadTypeMin = 96;
static const int kDynamicPayloadTypeMax = 127;
};
// Helper class used for finding duplicate RTP Header extension ids among
// audio and video extensions.
class UsedRtpHeaderExtensionIds : public UsedIds<RtpHeaderExtension> {
public:
UsedRtpHeaderExtensionIds()
: UsedIds<RtpHeaderExtension>(kLocalIdMin, kLocalIdMax) {
}
private:
// Min and Max local identifier for one-byte header extensions, per RFC5285.
static const int kLocalIdMin = 1;
static const int kLocalIdMax = 14;
};
static bool IsSctp(const MediaContentDescription* desc) {
return ((desc->protocol() == kMediaProtocolSctp) ||
(desc->protocol() == kMediaProtocolDtlsSctp));
}
// Adds a StreamParams for each Stream in Streams with media type
// media_type to content_description.
// |current_params| - All currently known StreamParams of any media type.
template <class C>
static bool AddStreamParams(
MediaType media_type,
const MediaSessionOptions::Streams& streams,
StreamParamsVec* current_streams,
MediaContentDescriptionImpl<C>* content_description,
const bool add_legacy_stream) {
const bool include_rtx_stream =
ContainsRtxCodec(content_description->codecs());
if (streams.empty() && add_legacy_stream) {
// TODO(perkj): Remove this legacy stream when all apps use StreamParams.
std::vector<uint32> ssrcs;
if (IsSctp(content_description)) {
GenerateSctpSids(*current_streams, &ssrcs);
} else {
int num_ssrcs = include_rtx_stream ? 2 : 1;
GenerateSsrcs(*current_streams, num_ssrcs, &ssrcs);
}
if (include_rtx_stream) {
content_description->AddLegacyStream(ssrcs[0], ssrcs[1]);
content_description->set_multistream(true);
} else {
content_description->AddLegacyStream(ssrcs[0]);
}
return true;
}
MediaSessionOptions::Streams::const_iterator stream_it;
for (stream_it = streams.begin();
stream_it != streams.end(); ++stream_it) {
if (stream_it->type != media_type)
continue; // Wrong media type.
StreamParams param;
// groupid is empty for StreamParams generated using
// MediaSessionDescriptionFactory.
if (!GetStreamByIds(*current_streams, "", stream_it->id,
&param)) {
// This is a new stream.
// Get a CNAME. Either new or same as one of the other synched streams.
std::string cname;
if (!GenerateCname(*current_streams, streams, stream_it->sync_label,
&cname)) {
return false;
}
std::vector<uint32> ssrcs;
if (IsSctp(content_description)) {
GenerateSctpSids(*current_streams, &ssrcs);
} else {
GenerateSsrcs(*current_streams, stream_it->num_sim_layers, &ssrcs);
}
StreamParams stream_param;
stream_param.id = stream_it->id;
// Add the generated ssrc.
for (size_t i = 0; i < ssrcs.size(); ++i) {
stream_param.ssrcs.push_back(ssrcs[i]);
}
if (stream_it->num_sim_layers > 1) {
SsrcGroup group(kSimSsrcGroupSemantics, stream_param.ssrcs);
stream_param.ssrc_groups.push_back(group);
}
// Generate an extra ssrc for include_rtx_stream case.
if (include_rtx_stream) {
std::vector<uint32> rtx_ssrc;
GenerateSsrcs(*current_streams, 1, &rtx_ssrc);
stream_param.AddFidSsrc(ssrcs[0], rtx_ssrc[0]);
content_description->set_multistream(true);
}
stream_param.cname = cname;
stream_param.sync_label = stream_it->sync_label;
content_description->AddStream(stream_param);
// Store the new StreamParams in current_streams.
// This is necessary so that we can use the CNAME for other media types.
current_streams->push_back(stream_param);
} else {
content_description->AddStream(param);
}
}
return true;
}
// Updates the transport infos of the |sdesc| according to the given
// |bundle_group|. The transport infos of the content names within the
// |bundle_group| should be updated to use the ufrag and pwd of the first
// content within the |bundle_group|.
static bool UpdateTransportInfoForBundle(const ContentGroup& bundle_group,
SessionDescription* sdesc) {
// The bundle should not be empty.
if (!sdesc || !bundle_group.FirstContentName()) {
return false;
}
// We should definitely have a transport for the first content.
std::string selected_content_name = *bundle_group.FirstContentName();
const TransportInfo* selected_transport_info =
sdesc->GetTransportInfoByName(selected_content_name);
if (!selected_transport_info) {
return false;
}
// Set the other contents to use the same ICE credentials.
const std::string selected_ufrag =
selected_transport_info->description.ice_ufrag;
const std::string selected_pwd =
selected_transport_info->description.ice_pwd;
for (TransportInfos::iterator it =
sdesc->transport_infos().begin();
it != sdesc->transport_infos().end(); ++it) {
if (bundle_group.HasContentName(it->content_name) &&
it->content_name != selected_content_name) {
it->description.ice_ufrag = selected_ufrag;
it->description.ice_pwd = selected_pwd;
}
}
return true;
}
// Gets the CryptoParamsVec of the given |content_name| from |sdesc|, and
// sets it to |cryptos|.
static bool GetCryptosByName(const SessionDescription* sdesc,
const std::string& content_name,
CryptoParamsVec* cryptos) {
if (!sdesc || !cryptos) {
return false;
}
const ContentInfo* content = sdesc->GetContentByName(content_name);
if (!IsMediaContent(content) || !content->description) {
return false;
}
const MediaContentDescription* media_desc =
static_cast<const MediaContentDescription*>(content->description);
*cryptos = media_desc->cryptos();
return true;
}
// Predicate function used by the remove_if.
// Returns true if the |crypto|'s cipher_suite is not found in |filter|.
static bool CryptoNotFound(const CryptoParams crypto,
const CryptoParamsVec* filter) {
if (filter == NULL) {
return true;
}
for (CryptoParamsVec::const_iterator it = filter->begin();
it != filter->end(); ++it) {
if (it->cipher_suite == crypto.cipher_suite) {
return false;
}
}
return true;
}
// Prunes the |target_cryptos| by removing the crypto params (cipher_suite)
// which are not available in |filter|.
static void PruneCryptos(const CryptoParamsVec& filter,
CryptoParamsVec* target_cryptos) {
if (!target_cryptos) {
return;
}
target_cryptos->erase(std::remove_if(target_cryptos->begin(),
target_cryptos->end(),
bind2nd(ptr_fun(CryptoNotFound),
&filter)),
target_cryptos->end());
}
static bool IsRtpContent(SessionDescription* sdesc,
const std::string& content_name) {
bool is_rtp = false;
ContentInfo* content = sdesc->GetContentByName(content_name);
if (IsMediaContent(content)) {
MediaContentDescription* media_desc =
static_cast<MediaContentDescription*>(content->description);
if (!media_desc) {
return false;
}
is_rtp = media_desc->protocol().empty() ||
rtc::starts_with(media_desc->protocol().data(),
kMediaProtocolRtpPrefix);
}
return is_rtp;
}
// Updates the crypto parameters of the |sdesc| according to the given
// |bundle_group|. The crypto parameters of all the contents within the
// |bundle_group| should be updated to use the common subset of the
// available cryptos.
static bool UpdateCryptoParamsForBundle(const ContentGroup& bundle_group,
SessionDescription* sdesc) {
// The bundle should not be empty.
if (!sdesc || !bundle_group.FirstContentName()) {
return false;
}
bool common_cryptos_needed = false;
// Get the common cryptos.
const ContentNames& content_names = bundle_group.content_names();
CryptoParamsVec common_cryptos;
for (ContentNames::const_iterator it = content_names.begin();
it != content_names.end(); ++it) {
if (!IsRtpContent(sdesc, *it)) {
continue;
}
// The common cryptos are needed if any of the content does not have DTLS
// enabled.
if (!sdesc->GetTransportInfoByName(*it)->description.secure()) {
common_cryptos_needed = true;
}
if (it == content_names.begin()) {
// Initial the common_cryptos with the first content in the bundle group.
if (!GetCryptosByName(sdesc, *it, &common_cryptos)) {
return false;
}
if (common_cryptos.empty()) {
// If there's no crypto params, we should just return.
return true;
}
} else {
CryptoParamsVec cryptos;
if (!GetCryptosByName(sdesc, *it, &cryptos)) {
return false;
}
PruneCryptos(cryptos, &common_cryptos);
}
}
if (common_cryptos.empty() && common_cryptos_needed) {
return false;
}
// Update to use the common cryptos.
for (ContentNames::const_iterator it = content_names.begin();
it != content_names.end(); ++it) {
if (!IsRtpContent(sdesc, *it)) {
continue;
}
ContentInfo* content = sdesc->GetContentByName(*it);
if (IsMediaContent(content)) {
MediaContentDescription* media_desc =
static_cast<MediaContentDescription*>(content->description);
if (!media_desc) {
return false;
}
media_desc->set_cryptos(common_cryptos);
}
}
return true;
}
template <class C>
static bool ContainsRtxCodec(const std::vector<C>& codecs) {
typename std::vector<C>::const_iterator it;
for (it = codecs.begin(); it != codecs.end(); ++it) {
if (IsRtxCodec(*it)) {
return true;
}
}
return false;
}
template <class C>
static bool IsRtxCodec(const C& codec) {
return stricmp(codec.name.c_str(), kRtxCodecName) == 0;
}
// Create a media content to be offered in a session-initiate,
// according to the given options.rtcp_mux, options.is_muc,
// options.streams, codecs, secure_transport, crypto, and streams. If we don't
// currently have crypto (in current_cryptos) and it is enabled (in
// secure_policy), crypto is created (according to crypto_suites). If
// add_legacy_stream is true, and current_streams is empty, a legacy
// stream is created. The created content is added to the offer.
template <class C>
static bool CreateMediaContentOffer(
const MediaSessionOptions& options,
const std::vector<C>& codecs,
const SecurePolicy& secure_policy,
const CryptoParamsVec* current_cryptos,
const std::vector<std::string>& crypto_suites,
const RtpHeaderExtensions& rtp_extensions,
bool add_legacy_stream,
StreamParamsVec* current_streams,
MediaContentDescriptionImpl<C>* offer) {
offer->AddCodecs(codecs);
offer->SortCodecs();
if (secure_policy == SEC_REQUIRED) {
offer->set_crypto_required(CT_SDES);
}
offer->set_rtcp_mux(options.rtcp_mux_enabled);
offer->set_multistream(options.is_muc);
offer->set_rtp_header_extensions(rtp_extensions);
if (!AddStreamParams(
offer->type(), options.streams, current_streams,
offer, add_legacy_stream)) {
return false;
}
#ifdef HAVE_SRTP
if (secure_policy != SEC_DISABLED) {
if (current_cryptos) {
AddMediaCryptos(*current_cryptos, offer);
}
if (offer->cryptos().empty()) {
if (!CreateMediaCryptos(crypto_suites, offer)) {
return false;
}
}
}
#endif
if (offer->crypto_required() == CT_SDES && offer->cryptos().empty()) {
return false;
}
return true;
}
template <class C>
static void NegotiateCodecs(const std::vector<C>& local_codecs,
const std::vector<C>& offered_codecs,
std::vector<C>* negotiated_codecs) {
typename std::vector<C>::const_iterator ours;
for (ours = local_codecs.begin();
ours != local_codecs.end(); ++ours) {
typename std::vector<C>::const_iterator theirs;
for (theirs = offered_codecs.begin();
theirs != offered_codecs.end(); ++theirs) {
if (ours->Matches(*theirs)) {
C negotiated = *ours;
negotiated.IntersectFeedbackParams(*theirs);
if (IsRtxCodec(negotiated)) {
// Only negotiate RTX if kCodecParamAssociatedPayloadType has been
// set.
std::string apt_value;
if (!theirs->GetParam(kCodecParamAssociatedPayloadType, &apt_value)) {
LOG(LS_WARNING) << "RTX missing associated payload type.";
continue;
}
negotiated.SetParam(kCodecParamAssociatedPayloadType, apt_value);
}
negotiated.id = theirs->id;
// RFC3264: Although the answerer MAY list the formats in their desired
// order of preference, it is RECOMMENDED that unless there is a
// specific reason, the answerer list formats in the same relative order
// they were present in the offer.
negotiated.preference = theirs->preference;
negotiated_codecs->push_back(negotiated);
}
}
}
}
template <class C>
static bool FindMatchingCodec(const std::vector<C>& codecs,
const C& codec_to_match,
C* found_codec) {
for (typename std::vector<C>::const_iterator it = codecs.begin();
it != codecs.end(); ++it) {
if (it->Matches(codec_to_match)) {
if (found_codec != NULL) {
*found_codec= *it;
}
return true;
}
}
return false;
}
// Adds all codecs from |reference_codecs| to |offered_codecs| that dont'
// already exist in |offered_codecs| and ensure the payload types don't
// collide.
template <class C>
static void FindCodecsToOffer(
const std::vector<C>& reference_codecs,
std::vector<C>* offered_codecs,
UsedPayloadTypes* used_pltypes) {
typedef std::map<int, C> RtxCodecReferences;
RtxCodecReferences new_rtx_codecs;
// Find all new RTX codecs.
for (typename std::vector<C>::const_iterator it = reference_codecs.begin();
it != reference_codecs.end(); ++it) {
if (!FindMatchingCodec<C>(*offered_codecs, *it, NULL) && IsRtxCodec(*it)) {
C rtx_codec = *it;
int referenced_pl_type =
rtc::FromString<int>(0,
rtx_codec.params[kCodecParamAssociatedPayloadType]);
new_rtx_codecs.insert(std::pair<int, C>(referenced_pl_type,
rtx_codec));
}
}
// Add all new codecs that are not RTX codecs.
for (typename std::vector<C>::const_iterator it = reference_codecs.begin();
it != reference_codecs.end(); ++it) {
if (!FindMatchingCodec<C>(*offered_codecs, *it, NULL) && !IsRtxCodec(*it)) {
C codec = *it;
int original_payload_id = codec.id;
used_pltypes->FindAndSetIdUsed(&codec);
offered_codecs->push_back(codec);
// If this codec is referenced by a new RTX codec, update the reference
// in the RTX codec with the new payload type.
typename RtxCodecReferences::iterator rtx_it =
new_rtx_codecs.find(original_payload_id);
if (rtx_it != new_rtx_codecs.end()) {
C& rtx_codec = rtx_it->second;
rtx_codec.params[kCodecParamAssociatedPayloadType] =
rtc::ToString(codec.id);
}
}
}
// Add all new RTX codecs.
for (typename RtxCodecReferences::iterator it = new_rtx_codecs.begin();
it != new_rtx_codecs.end(); ++it) {
C& rtx_codec = it->second;
used_pltypes->FindAndSetIdUsed(&rtx_codec);
offered_codecs->push_back(rtx_codec);
}
}
static bool FindByUri(const RtpHeaderExtensions& extensions,
const RtpHeaderExtension& ext_to_match,
RtpHeaderExtension* found_extension) {
for (RtpHeaderExtensions::const_iterator it = extensions.begin();
it != extensions.end(); ++it) {
// We assume that all URIs are given in a canonical format.
if (it->uri == ext_to_match.uri) {
if (found_extension != NULL) {
*found_extension = *it;
}
return true;
}
}
return false;
}
static void FindAndSetRtpHdrExtUsed(
const RtpHeaderExtensions& reference_extensions,
RtpHeaderExtensions* offered_extensions,
const RtpHeaderExtensions& other_extensions,
UsedRtpHeaderExtensionIds* used_extensions) {
for (RtpHeaderExtensions::const_iterator it = reference_extensions.begin();
it != reference_extensions.end(); ++it) {
if (!FindByUri(*offered_extensions, *it, NULL)) {
RtpHeaderExtension ext;
if (!FindByUri(other_extensions, *it, &ext)) {
ext = *it;
used_extensions->FindAndSetIdUsed(&ext);
}
offered_extensions->push_back(ext);
}
}
}
static void NegotiateRtpHeaderExtensions(
const RtpHeaderExtensions& local_extensions,
const RtpHeaderExtensions& offered_extensions,
RtpHeaderExtensions* negotiated_extenstions) {
RtpHeaderExtensions::const_iterator ours;
for (ours = local_extensions.begin();
ours != local_extensions.end(); ++ours) {
RtpHeaderExtension theirs;
if (FindByUri(offered_extensions, *ours, &theirs)) {
// We respond with their RTP header extension id.
negotiated_extenstions->push_back(theirs);
}
}
}
static void StripCNCodecs(AudioCodecs* audio_codecs) {
AudioCodecs::iterator iter = audio_codecs->begin();
while (iter != audio_codecs->end()) {
if (stricmp(iter->name.c_str(), kComfortNoiseCodecName) == 0) {
iter = audio_codecs->erase(iter);
} else {
++iter;
}
}
}
// Create a media content to be answered in a session-accept,
// according to the given options.rtcp_mux, options.streams, codecs,
// crypto, and streams. If we don't currently have crypto (in
// current_cryptos) and it is enabled (in secure_policy), crypto is
// created (according to crypto_suites). If add_legacy_stream is
// true, and current_streams is empty, a legacy stream is created.
// The codecs, rtcp_mux, and crypto are all negotiated with the offer
// from the incoming session-initiate. If the negotiation fails, this
// method returns false. The created content is added to the offer.
template <class C>
static bool CreateMediaContentAnswer(
const MediaContentDescriptionImpl<C>* offer,
const MediaSessionOptions& options,
const std::vector<C>& local_codecs,
const SecurePolicy& sdes_policy,
const CryptoParamsVec* current_cryptos,
const RtpHeaderExtensions& local_rtp_extenstions,
StreamParamsVec* current_streams,
bool add_legacy_stream,
bool bundle_enabled,
MediaContentDescriptionImpl<C>* answer) {
std::vector<C> negotiated_codecs;
NegotiateCodecs(local_codecs, offer->codecs(), &negotiated_codecs);
answer->AddCodecs(negotiated_codecs);
answer->SortCodecs();
answer->set_protocol(offer->protocol());
RtpHeaderExtensions negotiated_rtp_extensions;
NegotiateRtpHeaderExtensions(local_rtp_extenstions,
offer->rtp_header_extensions(),
&negotiated_rtp_extensions);
answer->set_rtp_header_extensions(negotiated_rtp_extensions);
answer->set_rtcp_mux(options.rtcp_mux_enabled && offer->rtcp_mux());
if (sdes_policy != SEC_DISABLED) {
CryptoParams crypto;
if (SelectCrypto(offer, bundle_enabled, &crypto)) {
if (current_cryptos) {
FindMatchingCrypto(*current_cryptos, crypto, &crypto);
}
answer->AddCrypto(crypto);
}
}
if (answer->cryptos().empty() &&
(offer->crypto_required() == CT_SDES || sdes_policy == SEC_REQUIRED)) {
return false;
}
if (!AddStreamParams(
answer->type(), options.streams, current_streams,
answer, add_legacy_stream)) {
return false; // Something went seriously wrong.
}
// Make sure the answer media content direction is per default set as
// described in RFC3264 section 6.1.
switch (offer->direction()) {
case MD_INACTIVE:
answer->set_direction(MD_INACTIVE);
break;
case MD_SENDONLY:
answer->set_direction(MD_RECVONLY);
break;
case MD_RECVONLY:
answer->set_direction(MD_SENDONLY);
break;
case MD_SENDRECV:
answer->set_direction(MD_SENDRECV);
break;
default:
break;
}
return true;
}
static bool IsMediaProtocolSupported(MediaType type,
const std::string& protocol,
bool secure_transport) {
// Data channels can have a protocol of SCTP or SCTP/DTLS.
if (type == MEDIA_TYPE_DATA &&
((protocol == kMediaProtocolSctp && !secure_transport)||
(protocol == kMediaProtocolDtlsSctp && secure_transport))) {
return true;
}
// Since not all applications serialize and deserialize the media protocol,
// we will have to accept |protocol| to be empty.
return protocol == kMediaProtocolAvpf || protocol.empty() ||
protocol == kMediaProtocolSavpf ||
(protocol == kMediaProtocolDtlsSavpf && secure_transport);
}
static void SetMediaProtocol(bool secure_transport,
MediaContentDescription* desc) {
if (!desc->cryptos().empty() || secure_transport)
desc->set_protocol(kMediaProtocolSavpf);
else
desc->set_protocol(kMediaProtocolAvpf);
}
// Gets the TransportInfo of the given |content_name| from the
// |current_description|. If doesn't exist, returns a new one.
static const TransportDescription* GetTransportDescription(
const std::string& content_name,
const SessionDescription* current_description) {
const TransportDescription* desc = NULL;
if (current_description) {
const TransportInfo* info =
current_description->GetTransportInfoByName(content_name);
if (info) {
desc = &info->description;
}
}
return desc;
}
// Gets the current DTLS state from the transport description.
static bool IsDtlsActive(
const std::string& content_name,
const SessionDescription* current_description) {
if (!current_description)
return false;
const ContentInfo* content =
current_description->GetContentByName(content_name);
if (!content)
return false;
const TransportDescription* current_tdesc =
GetTransportDescription(content_name, current_description);
if (!current_tdesc)
return false;
return current_tdesc->secure();
}
std::string MediaTypeToString(MediaType type) {
std::string type_str;
switch (type) {
case MEDIA_TYPE_AUDIO:
type_str = "audio";
break;
case MEDIA_TYPE_VIDEO:
type_str = "video";
break;
case MEDIA_TYPE_DATA:
type_str = "data";
break;
default:
ASSERT(false);
break;
}
return type_str;
}
void MediaSessionOptions::AddStream(MediaType type,
const std::string& id,
const std::string& sync_label) {
AddStreamInternal(type, id, sync_label, 1);
}
void MediaSessionOptions::AddVideoStream(
const std::string& id,
const std::string& sync_label,
int num_sim_layers) {
AddStreamInternal(MEDIA_TYPE_VIDEO, id, sync_label, num_sim_layers);
}
void MediaSessionOptions::AddStreamInternal(
MediaType type,
const std::string& id,
const std::string& sync_label,
int num_sim_layers) {
streams.push_back(Stream(type, id, sync_label, num_sim_layers));
if (type == MEDIA_TYPE_VIDEO)
has_video = true;
else if (type == MEDIA_TYPE_AUDIO)
has_audio = true;
// If we haven't already set the data_channel_type, and we add a
// stream, we assume it's an RTP data stream.
else if (type == MEDIA_TYPE_DATA && data_channel_type == DCT_NONE)
data_channel_type = DCT_RTP;
}
void MediaSessionOptions::RemoveStream(MediaType type,
const std::string& id) {
Streams::iterator stream_it = streams.begin();
for (; stream_it != streams.end(); ++stream_it) {
if (stream_it->type == type && stream_it->id == id) {
streams.erase(stream_it);
return;
}
}
ASSERT(false);
}
MediaSessionDescriptionFactory::MediaSessionDescriptionFactory(
const TransportDescriptionFactory* transport_desc_factory)
: secure_(SEC_DISABLED),
add_legacy_(true),
transport_desc_factory_(transport_desc_factory) {
}
MediaSessionDescriptionFactory::MediaSessionDescriptionFactory(
ChannelManager* channel_manager,
const TransportDescriptionFactory* transport_desc_factory)
: secure_(SEC_DISABLED),
add_legacy_(true),
transport_desc_factory_(transport_desc_factory) {
channel_manager->GetSupportedAudioCodecs(&audio_codecs_);
channel_manager->GetSupportedAudioRtpHeaderExtensions(&audio_rtp_extensions_);
channel_manager->GetSupportedVideoCodecs(&video_codecs_);
channel_manager->GetSupportedVideoRtpHeaderExtensions(&video_rtp_extensions_);
channel_manager->GetSupportedDataCodecs(&data_codecs_);
}
SessionDescription* MediaSessionDescriptionFactory::CreateOffer(
const MediaSessionOptions& options,
const SessionDescription* current_description) const {
scoped_ptr<SessionDescription> offer(new SessionDescription());
StreamParamsVec current_streams;
GetCurrentStreamParams(current_description, &current_streams);
AudioCodecs audio_codecs;
VideoCodecs video_codecs;
DataCodecs data_codecs;
GetCodecsToOffer(current_description, &audio_codecs, &video_codecs,
&data_codecs);
if (!options.vad_enabled) {
// If application doesn't want CN codecs in offer.
StripCNCodecs(&audio_codecs);
}
RtpHeaderExtensions audio_rtp_extensions;
RtpHeaderExtensions video_rtp_extensions;
GetRtpHdrExtsToOffer(current_description, &audio_rtp_extensions,
&video_rtp_extensions);
bool audio_added = false;
bool video_added = false;
bool data_added = false;
// Iterate through the contents of |current_description| to maintain the order
// of the m-lines in the new offer.
if (current_description) {
ContentInfos::const_iterator it = current_description->contents().begin();
for (; it != current_description->contents().end(); ++it) {
if (IsMediaContentOfType(&*it, MEDIA_TYPE_AUDIO) && options.has_audio) {
if (!AddAudioContentForOffer(options, current_description,
audio_rtp_extensions, audio_codecs,
&current_streams, offer.get())) {
return NULL;
}
audio_added = true;
} else if (IsMediaContentOfType(&*it, MEDIA_TYPE_VIDEO) &&
options.has_video) {
if (!AddVideoContentForOffer(options, current_description,
video_rtp_extensions, video_codecs,
&current_streams, offer.get())) {
return NULL;
}
video_added = true;
} else if (options.has_data()) {
ASSERT(IsMediaContentOfType(&*it, MEDIA_TYPE_DATA));
if (!AddDataContentForOffer(options, current_description, &data_codecs,
&current_streams, offer.get())) {
return NULL;
}
data_added = true;
}
}
}
// Append contents that are not in |current_description|.
if (!audio_added && options.has_audio &&
!AddAudioContentForOffer(options, current_description,
audio_rtp_extensions, audio_codecs,
&current_streams, offer.get())) {
return NULL;
}
if (!video_added && options.has_video &&
!AddVideoContentForOffer(options, current_description,
video_rtp_extensions, video_codecs,
&current_streams, offer.get())) {
return NULL;
}
if (!data_added && options.has_data() &&
!AddDataContentForOffer(options, current_description, &data_codecs,
&current_streams, offer.get())) {
return NULL;
}
// Bundle the contents together, if we've been asked to do so, and update any
// parameters that need to be tweaked for BUNDLE.
if (options.bundle_enabled) {
ContentGroup offer_bundle(GROUP_TYPE_BUNDLE);
for (ContentInfos::const_iterator content = offer->contents().begin();
content != offer->contents().end(); ++content) {
offer_bundle.AddContentName(content->name);
}
offer->AddGroup(offer_bundle);
if (!UpdateTransportInfoForBundle(offer_bundle, offer.get())) {
LOG(LS_ERROR) << "CreateOffer failed to UpdateTransportInfoForBundle.";
return NULL;
}
if (!UpdateCryptoParamsForBundle(offer_bundle, offer.get())) {
LOG(LS_ERROR) << "CreateOffer failed to UpdateCryptoParamsForBundle.";
return NULL;
}
}
return offer.release();
}
SessionDescription* MediaSessionDescriptionFactory::CreateAnswer(
const SessionDescription* offer, const MediaSessionOptions& options,
const SessionDescription* current_description) const {
// The answer contains the intersection of the codecs in the offer with the
// codecs we support, ordered by our local preference. As indicated by
// XEP-0167, we retain the same payload ids from the offer in the answer.
scoped_ptr<SessionDescription> answer(new SessionDescription());
StreamParamsVec current_streams;
GetCurrentStreamParams(current_description, &current_streams);
if (offer) {
ContentInfos::const_iterator it = offer->contents().begin();
for (; it != offer->contents().end(); ++it) {
if (IsMediaContentOfType(&*it, MEDIA_TYPE_AUDIO)) {
if (!AddAudioContentForAnswer(offer, options, current_description,
&current_streams, answer.get())) {
return NULL;
}
} else if (IsMediaContentOfType(&*it, MEDIA_TYPE_VIDEO)) {
if (!AddVideoContentForAnswer(offer, options, current_description,
&current_streams, answer.get())) {
return NULL;
}
} else {
ASSERT(IsMediaContentOfType(&*it, MEDIA_TYPE_DATA));
if (!AddDataContentForAnswer(offer, options, current_description,
&current_streams, answer.get())) {
return NULL;
}
}
}
}
// If the offer supports BUNDLE, and we want to use it too, create a BUNDLE
// group in the answer with the appropriate content names.
if (offer->HasGroup(GROUP_TYPE_BUNDLE) && options.bundle_enabled) {
const ContentGroup* offer_bundle = offer->GetGroupByName(GROUP_TYPE_BUNDLE);
ContentGroup answer_bundle(GROUP_TYPE_BUNDLE);
for (ContentInfos::const_iterator content = answer->contents().begin();
content != answer->contents().end(); ++content) {
if (!content->rejected && offer_bundle->HasContentName(content->name)) {
answer_bundle.AddContentName(content->name);
}
}
if (answer_bundle.FirstContentName()) {
answer->AddGroup(answer_bundle);
// Share the same ICE credentials and crypto params across all contents,
// as BUNDLE requires.
if (!UpdateTransportInfoForBundle(answer_bundle, answer.get())) {
LOG(LS_ERROR) << "CreateAnswer failed to UpdateTransportInfoForBundle.";
return NULL;
}
if (!UpdateCryptoParamsForBundle(answer_bundle, answer.get())) {
LOG(LS_ERROR) << "CreateAnswer failed to UpdateCryptoParamsForBundle.";
return NULL;
}
}
}
return answer.release();
}
void MediaSessionDescriptionFactory::GetCodecsToOffer(
const SessionDescription* current_description,
AudioCodecs* audio_codecs,
VideoCodecs* video_codecs,
DataCodecs* data_codecs) const {
UsedPayloadTypes used_pltypes;
audio_codecs->clear();
video_codecs->clear();
data_codecs->clear();
// First - get all codecs from the current description if the media type
// is used.
// Add them to |used_pltypes| so the payloadtype is not reused if a new media
// type is added.
if (current_description) {
const AudioContentDescription* audio =
GetFirstAudioContentDescription(current_description);
if (audio) {
*audio_codecs = audio->codecs();
used_pltypes.FindAndSetIdUsed<AudioCodec>(audio_codecs);
}
const VideoContentDescription* video =
GetFirstVideoContentDescription(current_description);
if (video) {
*video_codecs = video->codecs();
used_pltypes.FindAndSetIdUsed<VideoCodec>(video_codecs);
}
const DataContentDescription* data =
GetFirstDataContentDescription(current_description);
if (data) {
*data_codecs = data->codecs();
used_pltypes.FindAndSetIdUsed<DataCodec>(data_codecs);
}
}
// Add our codecs that are not in |current_description|.
FindCodecsToOffer<AudioCodec>(audio_codecs_, audio_codecs, &used_pltypes);
FindCodecsToOffer<VideoCodec>(video_codecs_, video_codecs, &used_pltypes);
FindCodecsToOffer<DataCodec>(data_codecs_, data_codecs, &used_pltypes);
}
void MediaSessionDescriptionFactory::GetRtpHdrExtsToOffer(
const SessionDescription* current_description,
RtpHeaderExtensions* audio_extensions,
RtpHeaderExtensions* video_extensions) const {
// All header extensions allocated from the same range to avoid potential
// issues when using BUNDLE.
UsedRtpHeaderExtensionIds used_ids;
audio_extensions->clear();
video_extensions->clear();
// First - get all extensions from the current description if the media type
// is used.
// Add them to |used_ids| so the local ids are not reused if a new media
// type is added.
if (current_description) {
const AudioContentDescription* audio =
GetFirstAudioContentDescription(current_description);
if (audio) {
*audio_extensions = audio->rtp_header_extensions();
used_ids.FindAndSetIdUsed(audio_extensions);
}
const VideoContentDescription* video =
GetFirstVideoContentDescription(current_description);
if (video) {
*video_extensions = video->rtp_header_extensions();
used_ids.FindAndSetIdUsed(video_extensions);
}
}
// Add our default RTP header extensions that are not in
// |current_description|.
FindAndSetRtpHdrExtUsed(audio_rtp_header_extensions(), audio_extensions,
*video_extensions, &used_ids);
FindAndSetRtpHdrExtUsed(video_rtp_header_extensions(), video_extensions,
*audio_extensions, &used_ids);
}
bool MediaSessionDescriptionFactory::AddTransportOffer(
const std::string& content_name,
const TransportOptions& transport_options,
const SessionDescription* current_desc,
SessionDescription* offer_desc) const {
if (!transport_desc_factory_)
return false;
const TransportDescription* current_tdesc =
GetTransportDescription(content_name, current_desc);
rtc::scoped_ptr<TransportDescription> new_tdesc(
transport_desc_factory_->CreateOffer(transport_options, current_tdesc));
bool ret = (new_tdesc.get() != NULL &&
offer_desc->AddTransportInfo(TransportInfo(content_name, *new_tdesc)));
if (!ret) {
LOG(LS_ERROR)
<< "Failed to AddTransportOffer, content name=" << content_name;
}
return ret;
}
TransportDescription* MediaSessionDescriptionFactory::CreateTransportAnswer(
const std::string& content_name,
const SessionDescription* offer_desc,
const TransportOptions& transport_options,
const SessionDescription* current_desc) const {
if (!transport_desc_factory_)
return NULL;
const TransportDescription* offer_tdesc =
GetTransportDescription(content_name, offer_desc);
const TransportDescription* current_tdesc =
GetTransportDescription(content_name, current_desc);
return
transport_desc_factory_->CreateAnswer(offer_tdesc, transport_options,
current_tdesc);
}
bool MediaSessionDescriptionFactory::AddTransportAnswer(
const std::string& content_name,
const TransportDescription& transport_desc,
SessionDescription* answer_desc) const {
if (!answer_desc->AddTransportInfo(TransportInfo(content_name,
transport_desc))) {
LOG(LS_ERROR)
<< "Failed to AddTransportAnswer, content name=" << content_name;
return false;
}
return true;
}
bool MediaSessionDescriptionFactory::AddAudioContentForOffer(
const MediaSessionOptions& options,
const SessionDescription* current_description,
const RtpHeaderExtensions& audio_rtp_extensions,
const AudioCodecs& audio_codecs,
StreamParamsVec* current_streams,
SessionDescription* desc) const {
cricket::SecurePolicy sdes_policy =
IsDtlsActive(CN_AUDIO, current_description) ?
cricket::SEC_DISABLED : secure();
scoped_ptr<AudioContentDescription> audio(new AudioContentDescription());
std::vector<std::string> crypto_suites;
GetSupportedAudioCryptoSuites(&crypto_suites);
if (!CreateMediaContentOffer(
options,
audio_codecs,
sdes_policy,
GetCryptos(GetFirstAudioContentDescription(current_description)),
crypto_suites,
audio_rtp_extensions,
add_legacy_,
current_streams,
audio.get())) {
return false;
}
audio->set_lang(lang_);
bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED);
SetMediaProtocol(secure_transport, audio.get());
desc->AddContent(CN_AUDIO, NS_JINGLE_RTP, audio.release());
if (!AddTransportOffer(CN_AUDIO, options.transport_options,
current_description, desc)) {
return false;
}
return true;
}
bool MediaSessionDescriptionFactory::AddVideoContentForOffer(
const MediaSessionOptions& options,
const SessionDescription* current_description,
const RtpHeaderExtensions& video_rtp_extensions,
const VideoCodecs& video_codecs,
StreamParamsVec* current_streams,
SessionDescription* desc) const {
cricket::SecurePolicy sdes_policy =
IsDtlsActive(CN_VIDEO, current_description) ?
cricket::SEC_DISABLED : secure();
scoped_ptr<VideoContentDescription> video(new VideoContentDescription());
std::vector<std::string> crypto_suites;
GetSupportedVideoCryptoSuites(&crypto_suites);
if (!CreateMediaContentOffer(
options,
video_codecs,
sdes_policy,
GetCryptos(GetFirstVideoContentDescription(current_description)),
crypto_suites,
video_rtp_extensions,
add_legacy_,
current_streams,
video.get())) {
return false;
}
video->set_bandwidth(options.video_bandwidth);
bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED);
SetMediaProtocol(secure_transport, video.get());
desc->AddContent(CN_VIDEO, NS_JINGLE_RTP, video.release());
if (!AddTransportOffer(CN_VIDEO, options.transport_options,
current_description, desc)) {
return false;
}
return true;
}
bool MediaSessionDescriptionFactory::AddDataContentForOffer(
const MediaSessionOptions& options,
const SessionDescription* current_description,
DataCodecs* data_codecs,
StreamParamsVec* current_streams,
SessionDescription* desc) const {
bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED);
scoped_ptr<DataContentDescription> data(new DataContentDescription());
bool is_sctp = (options.data_channel_type == DCT_SCTP);
FilterDataCodecs(data_codecs, is_sctp);
cricket::SecurePolicy sdes_policy =
IsDtlsActive(CN_DATA, current_description) ?
cricket::SEC_DISABLED : secure();
std::vector<std::string> crypto_suites;
if (is_sctp) {
// SDES doesn't make sense for SCTP, so we disable it, and we only
// get SDES crypto suites for RTP-based data channels.
sdes_policy = cricket::SEC_DISABLED;
// Unlike SetMediaProtocol below, we need to set the protocol
// before we call CreateMediaContentOffer. Otherwise,
// CreateMediaContentOffer won't know this is SCTP and will
// generate SSRCs rather than SIDs.
data->set_protocol(
secure_transport ? kMediaProtocolDtlsSctp : kMediaProtocolSctp);
} else {
GetSupportedDataCryptoSuites(&crypto_suites);
}
if (!CreateMediaContentOffer(
options,
*data_codecs,
sdes_policy,
GetCryptos(GetFirstDataContentDescription(current_description)),
crypto_suites,
RtpHeaderExtensions(),
add_legacy_,
current_streams,
data.get())) {
return false;
}
if (is_sctp) {
desc->AddContent(CN_DATA, NS_JINGLE_DRAFT_SCTP, data.release());
} else {
data->set_bandwidth(options.data_bandwidth);
SetMediaProtocol(secure_transport, data.get());
desc->AddContent(CN_DATA, NS_JINGLE_RTP, data.release());
}
if (!AddTransportOffer(CN_DATA, options.transport_options,
current_description, desc)) {
return false;
}
return true;
}
bool MediaSessionDescriptionFactory::AddAudioContentForAnswer(
const SessionDescription* offer,
const MediaSessionOptions& options,
const SessionDescription* current_description,
StreamParamsVec* current_streams,
SessionDescription* answer) const {
const ContentInfo* audio_content = GetFirstAudioContent(offer);
scoped_ptr<TransportDescription> audio_transport(
CreateTransportAnswer(audio_content->name, offer,
options.transport_options,
current_description));
if (!audio_transport) {
return false;
}
AudioCodecs audio_codecs = audio_codecs_;
if (!options.vad_enabled) {
StripCNCodecs(&audio_codecs);
}
bool bundle_enabled =
offer->HasGroup(GROUP_TYPE_BUNDLE) && options.bundle_enabled;
scoped_ptr<AudioContentDescription> audio_answer(
new AudioContentDescription());
// Do not require or create SDES cryptos if DTLS is used.
cricket::SecurePolicy sdes_policy =
audio_transport->secure() ? cricket::SEC_DISABLED : secure();
if (!CreateMediaContentAnswer(
static_cast<const AudioContentDescription*>(
audio_content->description),
options,
audio_codecs,
sdes_policy,
GetCryptos(GetFirstAudioContentDescription(current_description)),
audio_rtp_extensions_,
current_streams,
add_legacy_,
bundle_enabled,
audio_answer.get())) {
return false; // Fails the session setup.
}
bool rejected = !options.has_audio || audio_content->rejected ||
!IsMediaProtocolSupported(MEDIA_TYPE_AUDIO,
audio_answer->protocol(),
audio_transport->secure());
if (!rejected) {
AddTransportAnswer(audio_content->name, *(audio_transport.get()), answer);
} else {
// RFC 3264
// The answer MUST contain the same number of m-lines as the offer.
LOG(LS_INFO) << "Audio is not supported in the answer.";
}
answer->AddContent(audio_content->name, audio_content->type, rejected,
audio_answer.release());
return true;
}
bool MediaSessionDescriptionFactory::AddVideoContentForAnswer(
const SessionDescription* offer,
const MediaSessionOptions& options,
const SessionDescription* current_description,
StreamParamsVec* current_streams,
SessionDescription* answer) const {
const ContentInfo* video_content = GetFirstVideoContent(offer);
scoped_ptr<TransportDescription> video_transport(
CreateTransportAnswer(video_content->name, offer,
options.transport_options,
current_description));
if (!video_transport) {
return false;
}
scoped_ptr<VideoContentDescription> video_answer(
new VideoContentDescription());
// Do not require or create SDES cryptos if DTLS is used.
cricket::SecurePolicy sdes_policy =
video_transport->secure() ? cricket::SEC_DISABLED : secure();
bool bundle_enabled =
offer->HasGroup(GROUP_TYPE_BUNDLE) && options.bundle_enabled;
if (!CreateMediaContentAnswer(
static_cast<const VideoContentDescription*>(
video_content->description),
options,
video_codecs_,
sdes_policy,
GetCryptos(GetFirstVideoContentDescription(current_description)),
video_rtp_extensions_,
current_streams,
add_legacy_,
bundle_enabled,
video_answer.get())) {
return false;
}
bool rejected = !options.has_video || video_content->rejected ||
!IsMediaProtocolSupported(MEDIA_TYPE_VIDEO,
video_answer->protocol(),
video_transport->secure());
if (!rejected) {
if (!AddTransportAnswer(video_content->name, *(video_transport.get()),
answer)) {
return false;
}
video_answer->set_bandwidth(options.video_bandwidth);
} else {
// RFC 3264
// The answer MUST contain the same number of m-lines as the offer.
LOG(LS_INFO) << "Video is not supported in the answer.";
}
answer->AddContent(video_content->name, video_content->type, rejected,
video_answer.release());
return true;
}
bool MediaSessionDescriptionFactory::AddDataContentForAnswer(
const SessionDescription* offer,
const MediaSessionOptions& options,
const SessionDescription* current_description,
StreamParamsVec* current_streams,
SessionDescription* answer) const {
const ContentInfo* data_content = GetFirstDataContent(offer);
scoped_ptr<TransportDescription> data_transport(
CreateTransportAnswer(data_content->name, offer,
options.transport_options,
current_description));
if (!data_transport) {
return false;
}
bool is_sctp = (options.data_channel_type == DCT_SCTP);
std::vector<DataCodec> data_codecs(data_codecs_);
FilterDataCodecs(&data_codecs, is_sctp);
scoped_ptr<DataContentDescription> data_answer(
new DataContentDescription());
// Do not require or create SDES cryptos if DTLS is used.
cricket::SecurePolicy sdes_policy =
data_transport->secure() ? cricket::SEC_DISABLED : secure();
bool bundle_enabled =
offer->HasGroup(GROUP_TYPE_BUNDLE) && options.bundle_enabled;
if (!CreateMediaContentAnswer(
static_cast<const DataContentDescription*>(
data_content->description),
options,
data_codecs_,
sdes_policy,
GetCryptos(GetFirstDataContentDescription(current_description)),
RtpHeaderExtensions(),
current_streams,
add_legacy_,
bundle_enabled,
data_answer.get())) {
return false; // Fails the session setup.
}
bool rejected = !options.has_data() || data_content->rejected ||
!IsMediaProtocolSupported(MEDIA_TYPE_DATA,
data_answer->protocol(),
data_transport->secure());
if (!rejected) {
data_answer->set_bandwidth(options.data_bandwidth);
if (!AddTransportAnswer(data_content->name, *(data_transport.get()),
answer)) {
return false;
}
} else {
// RFC 3264
// The answer MUST contain the same number of m-lines as the offer.
LOG(LS_INFO) << "Data is not supported in the answer.";
}
answer->AddContent(data_content->name, data_content->type, rejected,
data_answer.release());
return true;
}
bool IsMediaContent(const ContentInfo* content) {
return (content &&
(content->type == NS_JINGLE_RTP ||
content->type == NS_JINGLE_DRAFT_SCTP));
}
bool IsAudioContent(const ContentInfo* content) {
return IsMediaContentOfType(content, MEDIA_TYPE_AUDIO);
}
bool IsVideoContent(const ContentInfo* content) {
return IsMediaContentOfType(content, MEDIA_TYPE_VIDEO);
}
bool IsDataContent(const ContentInfo* content) {
return IsMediaContentOfType(content, MEDIA_TYPE_DATA);
}
static const ContentInfo* GetFirstMediaContent(const ContentInfos& contents,
MediaType media_type) {
for (ContentInfos::const_iterator content = contents.begin();
content != contents.end(); content++) {
if (IsMediaContentOfType(&*content, media_type)) {
return &*content;
}
}
return NULL;
}
const ContentInfo* GetFirstAudioContent(const ContentInfos& contents) {
return GetFirstMediaContent(contents, MEDIA_TYPE_AUDIO);
}
const ContentInfo* GetFirstVideoContent(const ContentInfos& contents) {
return GetFirstMediaContent(contents, MEDIA_TYPE_VIDEO);
}
const ContentInfo* GetFirstDataContent(const ContentInfos& contents) {
return GetFirstMediaContent(contents, MEDIA_TYPE_DATA);
}
static const ContentInfo* GetFirstMediaContent(const SessionDescription* sdesc,
MediaType media_type) {
if (sdesc == NULL)
return NULL;
return GetFirstMediaContent(sdesc->contents(), media_type);
}
const ContentInfo* GetFirstAudioContent(const SessionDescription* sdesc) {
return GetFirstMediaContent(sdesc, MEDIA_TYPE_AUDIO);
}
const ContentInfo* GetFirstVideoContent(const SessionDescription* sdesc) {
return GetFirstMediaContent(sdesc, MEDIA_TYPE_VIDEO);
}
const ContentInfo* GetFirstDataContent(const SessionDescription* sdesc) {
return GetFirstMediaContent(sdesc, MEDIA_TYPE_DATA);
}
const MediaContentDescription* GetFirstMediaContentDescription(
const SessionDescription* sdesc, MediaType media_type) {
const ContentInfo* content = GetFirstMediaContent(sdesc, media_type);
const ContentDescription* description = content ? content->description : NULL;
return static_cast<const MediaContentDescription*>(description);
}
const AudioContentDescription* GetFirstAudioContentDescription(
const SessionDescription* sdesc) {
return static_cast<const AudioContentDescription*>(
GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_AUDIO));
}
const VideoContentDescription* GetFirstVideoContentDescription(
const SessionDescription* sdesc) {
return static_cast<const VideoContentDescription*>(
GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_VIDEO));
}
const DataContentDescription* GetFirstDataContentDescription(
const SessionDescription* sdesc) {
return static_cast<const DataContentDescription*>(
GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_DATA));
}
bool GetMediaChannelNameFromComponent(
int component, MediaType media_type, std::string* channel_name) {
if (media_type == MEDIA_TYPE_AUDIO) {
if (component == ICE_CANDIDATE_COMPONENT_RTP) {
*channel_name = GICE_CHANNEL_NAME_RTP;
return true;
} else if (component == ICE_CANDIDATE_COMPONENT_RTCP) {
*channel_name = GICE_CHANNEL_NAME_RTCP;
return true;
}
} else if (media_type == MEDIA_TYPE_VIDEO) {
if (component == ICE_CANDIDATE_COMPONENT_RTP) {
*channel_name = GICE_CHANNEL_NAME_VIDEO_RTP;
return true;
} else if (component == ICE_CANDIDATE_COMPONENT_RTCP) {
*channel_name = GICE_CHANNEL_NAME_VIDEO_RTCP;
return true;
}
} else if (media_type == MEDIA_TYPE_DATA) {
if (component == ICE_CANDIDATE_COMPONENT_RTP) {
*channel_name = GICE_CHANNEL_NAME_DATA_RTP;
return true;
} else if (component == ICE_CANDIDATE_COMPONENT_RTCP) {
*channel_name = GICE_CHANNEL_NAME_DATA_RTCP;
return true;
}
}
return false;
}
bool GetMediaComponentFromChannelName(
const std::string& channel_name, int* component) {
if (channel_name == GICE_CHANNEL_NAME_RTP ||
channel_name == GICE_CHANNEL_NAME_VIDEO_RTP ||
channel_name == GICE_CHANNEL_NAME_DATA_RTP) {
*component = ICE_CANDIDATE_COMPONENT_RTP;
return true;
} else if (channel_name == GICE_CHANNEL_NAME_RTCP ||
channel_name == GICE_CHANNEL_NAME_VIDEO_RTCP ||
channel_name == GICE_CHANNEL_NAME_DATA_RTP) {
*component = ICE_CANDIDATE_COMPONENT_RTCP;
return true;
}
return false;
}
bool GetMediaTypeFromChannelName(
const std::string& channel_name, MediaType* media_type) {
if (channel_name == GICE_CHANNEL_NAME_RTP ||
channel_name == GICE_CHANNEL_NAME_RTCP) {
*media_type = MEDIA_TYPE_AUDIO;
return true;
} else if (channel_name == GICE_CHANNEL_NAME_VIDEO_RTP ||
channel_name == GICE_CHANNEL_NAME_VIDEO_RTCP) {
*media_type = MEDIA_TYPE_VIDEO;
return true;
} else if (channel_name == GICE_CHANNEL_NAME_DATA_RTP ||
channel_name == GICE_CHANNEL_NAME_DATA_RTCP) {
*media_type = MEDIA_TYPE_DATA;
return true;
}
return false;
}
} // namespace cricket