blob: 2fcb959d07d8cc4620ee8dbcee493e3438b81790 [file] [log] [blame]
/*
* libjingle
* Copyright 2011, 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/app/webrtc/webrtcsdp.h"
#include <stdio.h>
#include <string>
#include <vector>
#include "talk/base/logging.h"
#include "talk/base/stringutils.h"
#include "talk/p2p/base/relayport.h"
#include "talk/p2p/base/stunport.h"
#include "talk/p2p/base/udpport.h"
#include "talk/session/phone/codec.h"
#include "talk/session/phone/cryptoparams.h"
#include "talk/session/phone/mediasession.h"
#include "talk/session/phone/mediasessionclient.h"
using cricket::AudioContentDescription;
using cricket::Candidate;
using cricket::ContentDescription;
using cricket::CryptoParams;
using cricket::MediaContentDescription;
using cricket::MediaType;
using cricket::StreamParams;
using cricket::VideoContentDescription;
using talk_base::SocketAddress;
namespace webrtc {
// Line prefix
static const int kLinePrefixLength = 2;
static const char kLinePrefixVersion[] = "v=";
static const char kLinePrefixOrigin[] = "o=";
static const char kLinePrefixSessionName[] = "s=";
static const char kLinePrefixSessionInfo[] = "i=";
static const char kLinePrefixSessionUri[] = "u=";
static const char kLinePrefixSessionEmail[] = "e=";
static const char kLinePrefixSessionPhone[] = "p=";
static const char kLinePrefixSessionConnection[] = "c=";
static const char kLinePrefixSessionBandwidth[] = "b=";
static const char kLinePrefixTiming[] = "t=";
static const char kLinePrefixRepeatTimes[] = "r=";
static const char kLinePrefixTimeZone[] = "z=";
static const char kLinePrefixEncryptionKey[] = "k=";
static const char kLinePrefixMedia[] = "m=";
static const char kLinePrefixAttributes[] = "a=";
// Attributes
static const char kAttributeMid[] = "mid:";
static const char kAttributeRtcpMux[] = "rtcp-mux";
static const char kAttributeSsrc[] = "ssrc:";
static const char kAttributeCname[] = "cname:";
static const char kAttributeMslabel[] = "mslabel:";
static const char kAttributeLabel[] = "label:";
static const char kAttributeCrypto[] = "crypto:";
static const char kAttributeCandidate[] = "candidate:";
static const char kAttributeCandidateTyp[] = "typ";
static const char kAttributeCandidateName[] = "name";
static const char kAttributeCandidateNetworkName[] = "network_name";
static const char kAttributeCandidateUsername[] = "username";
static const char kAttributeCandidatePassword[] = "password";
static const char kAttributeCandidateGeneration[] = "generation";
static const char kAttributeRtpmap[] = "rtpmap:";
// Candidate
static const char kCandidateHost[] = "host";
static const char kCandidateSrflx[] = "srflx";
// TODO: How to map the prflx with circket candidate type
// static const char kCandidatePrflx[] = "prflx";
static const char kCandidateRelay[] = "relay";
static const char kSdpDelimiter = ' ';
static const char kLineBreak[] = "\r\n";
// TODO: Generate the Session and Time description
// instead of hardcoding.
static const char kSessionVersion[] = "v=0";
static const char kSessionOrigin[] = "o=- 0 0 IN IP4 127.0.0.1";
static const char kSessionName[] = "s=";
static const char kTimeDescription[] = "t=0 0";
static const char kAttrGroup[] = "a=group:BUNDLE audio video";
static const int kIceComponent = 1;
static const int kIceFoundation = 1;
static const char kMediaTypeVideo[] = "video";
static const char kMediaTypeAudio[] = "audio";
static void BuildMediaDescription(const cricket::ContentInfo& content_info,
const std::vector<Candidate>& candidates,
const MediaType media_type,
std::string* message);
static void BuildRtpMap(const MediaContentDescription* media_desc,
const MediaType media_type,
std::string* message);
static void BuildCandidate(const std::vector<Candidate>& candidates,
const MediaType media_type,
std::string* message);
static bool ParseSessionDescription(const std::string& message, size_t* pos);
static bool ParseTimeDescription(const std::string& message, size_t* pos);
static bool ParseMediaDescription(const std::string& message, size_t* pos,
cricket::SessionDescription* desc,
std::vector<Candidate>* candidates);
static bool ParseContent(const std::string& message,
const MediaType media_type,
size_t* pos,
ContentDescription* content,
std::vector<Candidate>* candidates);
// Helper functions
#define LOG_PREFIX_PARSING_ERROR(line_prefix) LOG(LS_ERROR) \
<< "Failed to parse the \"" << line_prefix << "\" line";
#define LOG_LINE_PARSING_ERROR(line) LOG(LS_ERROR) \
<< "Failed to parse line:" << line;
static bool AddLine(const std::string& line, std::string* message) {
if (!message)
return false;
message->append(line);
message->append(kLineBreak);
return true;
}
static bool GetLine(const std::string& message,
size_t* pos,
std::string* line) {
size_t line_begin = *pos;
size_t line_end = message.find('\n', line_begin);
if (line_end == std::string::npos) {
return false;
}
// Update the new start position
*pos = line_end + 1;
if (line_end > 0 && (message.at(line_end - 1) == '\r')) {
--line_end;
}
*line = message.substr(line_begin, (line_end - line_begin));
return true;
}
static bool GetLineWithPrefix(const std::string& message, size_t* pos,
std::string* line, const char* type) {
if (message.compare(*pos, kLinePrefixLength, type) != 0) {
return false;
}
if (!GetLine(message, pos, line))
return false;
return true;
}
static bool HasPrefix(const std::string& line,
const std::string& prefix,
size_t pos) {
return (line.compare(pos, prefix.size(), prefix) == 0);
}
static bool HasPrefix(const std::string& line,
const std::string& prefix) {
return HasPrefix(line, prefix, 0);
}
static bool HasAttribute(const std::string& line,
const std::string& attribute) {
return (line.compare(kLinePrefixLength, attribute.size(), attribute) == 0);
}
std::string SdpSerialize(const cricket::SessionDescription& desc,
const std::vector<Candidate>& candidates) {
std::string message;
// Session Description.
AddLine(kSessionVersion, &message);
AddLine(kSessionOrigin, &message);
AddLine(kSessionName, &message);
// Time Description.
AddLine(kTimeDescription, &message);
const cricket::ContentInfo* audio_content = GetFirstAudioContent(&desc);
const cricket::ContentInfo* video_content = GetFirstVideoContent(&desc);
// Group
if (audio_content && video_content)
AddLine(kAttrGroup, &message);
// Media Description
if (audio_content) {
BuildMediaDescription(*audio_content, candidates,
cricket::MEDIA_TYPE_AUDIO, &message);
}
if (video_content) {
BuildMediaDescription(*video_content, candidates,
cricket::MEDIA_TYPE_VIDEO, &message);
}
return message;
}
bool SdpDeserialize(const std::string& message,
cricket::SessionDescription* desc,
std::vector<Candidate>* candidates) {
size_t current_pos = 0;
// Session Description
if (!ParseSessionDescription(message, &current_pos)) {
return false;
}
// Time Description
if (!ParseTimeDescription(message, &current_pos)) {
return false;
}
// Media Description
if (!ParseMediaDescription(message, &current_pos, desc, candidates)) {
return false;
}
return true;
}
void BuildMediaDescription(const cricket::ContentInfo& content_info,
const std::vector<Candidate>& candidates,
const MediaType media_type,
std::string* message) {
ASSERT(message != NULL);
// TODO: Rethink if we should use sprintfn instead of stringstream.
// According to the style guide, streams should only be used for logging.
// http://google-styleguide.googlecode.com/svn/
// trunk/cppguide.xml?showone=Streams#Streams
std::ostringstream os;
const MediaContentDescription* media_desc =
static_cast<const MediaContentDescription*> (
content_info.description);
ASSERT(media_desc != NULL);
// m=<media> <port> <proto> <fmt>
// fmt is a list of payload type numbers that MAY be used in the session.
const char* type = NULL;
if (media_type == cricket::MEDIA_TYPE_AUDIO)
type = kMediaTypeAudio;
else if (media_type == cricket::MEDIA_TYPE_VIDEO)
type = kMediaTypeVideo;
else
ASSERT(false);
std::string fmt;
if (media_type == cricket::MEDIA_TYPE_VIDEO) {
const VideoContentDescription* video_desc =
static_cast<const VideoContentDescription*>(media_desc);
for (std::vector<cricket::VideoCodec>::const_iterator it =
video_desc->codecs().begin();
it != video_desc->codecs().end(); ++it) {
fmt.append(" ");
fmt.append(talk_base::ToString<int>(it->id));
}
} else if (media_type == cricket::MEDIA_TYPE_AUDIO) {
const AudioContentDescription* audio_desc =
static_cast<const AudioContentDescription*>(media_desc);
for (std::vector<cricket::AudioCodec>::const_iterator it =
audio_desc->codecs().begin();
it != audio_desc->codecs().end(); ++it) {
fmt.append(" ");
fmt.append(talk_base::ToString<int>(it->id));
}
}
const int port = 0;
const char* proto = "RTP/AVPF";
os.str("");
os << kLinePrefixMedia << type << " " << port << " " << proto << fmt;
AddLine(os.str(), message);
// a=mid:<media>
os.str("");
os << kLinePrefixAttributes << kAttributeMid << type;
AddLine(os.str(), message);
// a=rtcp-mux
if (media_desc->rtcp_mux()) {
os.str("");
os << kLinePrefixAttributes << kAttributeRtcpMux;
AddLine(os.str(), message);
}
// a=crypto:<tag> <crypto-suite> <key-params> [<session-params>]
for (std::vector<CryptoParams>::const_iterator it =
media_desc->cryptos().begin();
it != media_desc->cryptos().end(); ++it) {
os.str("");
os << kLinePrefixAttributes << kAttributeCrypto << it->tag << " "
<< it->cipher_suite << " "
<< it->key_params << " "
<< it->session_params;
AddLine(os.str(), message);
}
// a=rtpmap:<payload type> <encoding name>/<clock rate>
// [/<encodingparameters>]
BuildRtpMap(media_desc, media_type, message);
// rfc5245
// a=candidate:<foundation> <component-id> <transport> <priority>
// <connection-address> <port> typ <candidate-types>
// [raddr <connection-address>] [rport <port>]
BuildCandidate(candidates, media_type, message);
// draft - Mechanisms for Media Source Selection in SDP
// a=ssrc:<ssrc-id> <attribute>:<value>
// a=ssrc:<ssrc-id> cname:<value> mslabel:<value> label:<value>
for (cricket::StreamParamsVec::const_iterator it =
media_desc->streams().begin();
it != media_desc->streams().end(); ++it) {
// Require that the track belongs to a media stream,
// ie the sync_label is set. This extra check is necessary since the
// MediaContentDescription always contains a streamparam with an ssrc even
// if no track or media stream have been created.
if (it->sync_label.empty()) continue;
os.str("");
os << kLinePrefixAttributes << kAttributeSsrc << it->ssrcs[0] << " "
<< kAttributeCname << it->cname << " "
<< kAttributeMslabel << it->sync_label << " "
<< kAttributeLabel << it->name;
AddLine(os.str(), message);
}
}
void BuildRtpMap(const MediaContentDescription* media_desc,
const MediaType media_type,
std::string* message) {
ASSERT(message != NULL);
ASSERT(media_desc != NULL);
std::ostringstream os;
if (media_type == cricket::MEDIA_TYPE_VIDEO) {
const VideoContentDescription* video_desc =
static_cast<const VideoContentDescription*>(media_desc);
for (std::vector<cricket::VideoCodec>::const_iterator it =
video_desc->codecs().begin();
it != video_desc->codecs().end(); ++it) {
// a=rtpmap:<payload type> <encoding name>/<clock rate>
// [/<encodingparameters>]
os.str("");
os << kLinePrefixAttributes << kAttributeRtpmap << it->id << " "
<< it->name << "/" << 0;
AddLine(os.str(), message);
}
} else if (media_type == cricket::MEDIA_TYPE_AUDIO) {
const AudioContentDescription* audio_desc =
static_cast<const AudioContentDescription*>(media_desc);
for (std::vector<cricket::AudioCodec>::const_iterator it =
audio_desc->codecs().begin();
it != audio_desc->codecs().end(); ++it) {
// a=rtpmap:<payload type> <encoding name>/<clock rate>
// [/<encodingparameters>]
os.str("");
os << kLinePrefixAttributes << kAttributeRtpmap << it->id << " "
<< it->name << "/" << it->clockrate;
AddLine(os.str(), message);
}
}
}
void BuildCandidate(const std::vector<Candidate>& candidates,
const MediaType media_type,
std::string* message) {
std::ostringstream os;
for (std::vector<Candidate>::const_iterator it = candidates.begin();
it != candidates.end(); ++it) {
// a=candidate:<foundation> <component-id> <transport> <priority>
// <connection-address> <port> typ <candidate-types>
// [raddr <connection-address>] [rport <port>]
// *(SP extension-att-name SP extension-att-value)
if (((media_type == cricket::MEDIA_TYPE_VIDEO) &&
(it->name() == "video_rtcp" || it->name() == "video_rtp")) ||
((media_type == cricket::MEDIA_TYPE_AUDIO) &&
(it->name() == "rtp" || it->name() == "rtcp"))) {
std::string type;
// Map the cricket candidate type to "host" / "srflx" / "prflx" / "relay"
if (it->type() == cricket::LOCAL_PORT_TYPE) {
type = kCandidateHost;
} else if (it->type() == cricket::STUN_PORT_TYPE) {
type = kCandidateSrflx;
} else if (it->type() == cricket::RELAY_PORT_TYPE) {
type = kCandidateRelay;
} else {
ASSERT(false);
}
os.str("");
os << kLinePrefixAttributes << kAttributeCandidate
<< kIceFoundation << " " << kIceComponent << " "
<< it->protocol() << " " << it->preference_str() << " "
<< it->address().IPAsString() << " "
<< it->address().PortAsString() << " "
<< kAttributeCandidateTyp << " " << type << " "
<< kAttributeCandidateName << " " << it->name() << " "
<< kAttributeCandidateNetworkName << " " << it->network_name() << " "
<< kAttributeCandidateUsername << " " << it->username() << " "
<< kAttributeCandidatePassword << " " << it->password() << " "
<< kAttributeCandidateGeneration << " " << it->generation();
AddLine(os.str(), message);
}
}
}
bool ParseSessionDescription(const std::string& message, size_t* pos) {
std::string line;
// v= (protocol version)
if (!GetLineWithPrefix(message, pos, &line, kLinePrefixVersion)) {
LOG_PREFIX_PARSING_ERROR(kLinePrefixVersion);
return false;
}
// o= (originator and session identifier)
if (!GetLineWithPrefix(message, pos, &line, kLinePrefixOrigin)) {
LOG_PREFIX_PARSING_ERROR(kLinePrefixOrigin);
return false;
}
// s= (session name)
if (!GetLineWithPrefix(message, pos, &line, kLinePrefixSessionName)) {
LOG_PREFIX_PARSING_ERROR(kLinePrefixSessionName);
return false;
}
// Optional lines
// Those are the optional lines, so shouldn't return false if not present.
// i=* (session information)
GetLineWithPrefix(message, pos, &line, kLinePrefixSessionInfo);
// u=* (URI of description)
GetLineWithPrefix(message, pos, &line, kLinePrefixSessionUri);
// e=* (email address)
GetLineWithPrefix(message, pos, &line, kLinePrefixSessionEmail);
// p=* (phone number)
GetLineWithPrefix(message, pos, &line, kLinePrefixSessionPhone);
// c=* (connection information -- not required if included in
// all media)
GetLineWithPrefix(message, pos, &line, kLinePrefixSessionConnection);
// b=* (zero or more bandwidth information lines)
while (GetLineWithPrefix(message, pos, &line, kLinePrefixSessionBandwidth)) {
// By pass zero or more b lines.
}
return true;
}
bool ParseTimeDescription(const std::string& message, size_t* pos) {
std::string line;
// One or more time descriptions ("t=" and "r=" lines; see below)
// t= (time the session is active)
// r=* (zero or more repeat times)
// Ensure there's at least one time description
if (!GetLineWithPrefix(message, pos, &line, kLinePrefixTiming)) {
LOG_PREFIX_PARSING_ERROR(kLinePrefixTiming);
return false;
}
while (GetLineWithPrefix(message, pos, &line, kLinePrefixRepeatTimes)) {
// By pass zero or more r lines.
}
// Go through the rest of the time descriptions
while (GetLineWithPrefix(message, pos, &line, kLinePrefixTiming)) {
while (GetLineWithPrefix(message, pos, &line, kLinePrefixRepeatTimes)) {
// By pass zero or more r lines.
}
}
// z=* (time zone adjustments)
GetLineWithPrefix(message, pos, &line, kLinePrefixTimeZone);
// k=* (encryption key)
GetLineWithPrefix(message, pos, &line, kLinePrefixEncryptionKey);
// a=* (zero or more session attribute lines)
while (GetLineWithPrefix(message, pos, &line, kLinePrefixAttributes)) {
// TODO: parse the a=group:BUNDLE
}
return true;
}
bool ParseMediaDescription(const std::string& message, size_t* pos,
cricket::SessionDescription* desc,
std::vector<Candidate>* candidates) {
ASSERT(desc != NULL);
ASSERT(candidates != NULL);
std::string line;
// Zero or more media descriptions
// m=<media> <port> <proto> <fmt>
while (GetLineWithPrefix(message, pos, &line, kLinePrefixMedia)) {
MediaType media_type = cricket::MEDIA_TYPE_VIDEO;
ContentDescription* content = NULL;
if (HasAttribute(line, kMediaTypeVideo)) {
media_type = cricket::MEDIA_TYPE_VIDEO;
content = new VideoContentDescription();
desc->AddContent(cricket::CN_VIDEO, cricket::NS_JINGLE_RTP, content);
} else if (HasAttribute(line, kMediaTypeAudio)) {
media_type = cricket::MEDIA_TYPE_AUDIO;
content = new AudioContentDescription();
desc->AddContent(cricket::CN_AUDIO, cricket::NS_JINGLE_RTP, content);
} else {
LOG(LS_WARNING) << "Unsupported media type: " << line;
}
if (!ParseContent(message, media_type, pos, content, candidates))
return false;
}
return true;
}
bool ParseContent(const std::string& message,
const MediaType media_type,
size_t* pos,
ContentDescription* content,
std::vector<Candidate>* candidates) {
ASSERT(candidates != NULL);
std::string line;
// Loop until the next m line
while (!HasPrefix(message, kLinePrefixMedia, *pos)) {
if (!GetLine(message, pos, &line)) {
if (*pos >= message.size())
return true; // Done parsing
else
return false;
}
if (!content) {
// Unsupported media type, just skip it.
continue;
}
if (!HasPrefix(line, kLinePrefixAttributes)) {
// TODO: Handle other lines if needed.
continue;
}
MediaContentDescription* media_desc =
static_cast<MediaContentDescription*> (content);
std::vector<std::string> fields;
talk_base::split(line.substr(kLinePrefixLength), kSdpDelimiter, &fields);
if (HasAttribute(line, kAttributeMid)) {
continue;
} else if (HasAttribute(line, kAttributeRtcpMux)) {
media_desc->set_rtcp_mux(true);
} else if (HasAttribute(line, kAttributeSsrc)) {
// a=ssrc:<ssrc-id> cname:<value> mslabel:<value> label:<value>
uint32 ssrc = 0;
std::string cname;
std::string mslabel;
std::string label;
for (std::vector<std::string>::const_iterator it = fields.begin();
it != fields.end(); ++it) {
if (HasPrefix(*it, kAttributeSsrc)) {
ASSERT(it == fields.begin());
ssrc = talk_base::FromString<uint32>(
it->substr(strlen(kAttributeSsrc)));
} else if (HasPrefix(*it, kAttributeCname)) {
cname = it->substr(strlen(kAttributeCname));
} else if (HasPrefix(*it, kAttributeMslabel)) {
mslabel = it->substr(strlen(kAttributeMslabel));
} else if (HasPrefix(*it, kAttributeLabel)) {
label = it->substr(strlen(kAttributeLabel));
}
}
StreamParams stream;
stream.name = label;
stream.cname = cname;
stream.sync_label = mslabel;
stream.ssrcs.push_back(ssrc);
media_desc->AddStream(stream);
} else if (HasAttribute(line, kAttributeCrypto)) {
// a=crypto:<tag> <crypto-suite> <key-params> [<session-params>]
if (fields.size() < 3) { // 3 mandatory fields
LOG_LINE_PARSING_ERROR(line);
return false;
}
int tag = talk_base::FromString<int>(
fields[0].substr(strlen(kAttributeCrypto)));
const std::string crypto_suite = fields[1];
const std::string key_params = fields[2];
media_desc->AddCrypto(CryptoParams(tag, crypto_suite, key_params, ""));
} else if (HasAttribute(line, kAttributeCandidate)) {
// a=candidate:<foundation> <component-id> <transport> <priority>
// <connection-address> <port> typ <candidate-types>
// [raddr <connection-address>] [rport <port>]
// *(SP extension-att-name SP extension-att-value)
// 8 mandatory fields
if (fields.size() < 8 || (fields[6] != kAttributeCandidateTyp)) {
LOG_LINE_PARSING_ERROR(line);
return false;
}
const std::string transport = fields[2];
const float priority = talk_base::FromString<float>(fields[3]);
const std::string connection_address = fields[4];
const int port = talk_base::FromString<int>(fields[5]);
std::string candidate_type;
const std::string type = fields[7];
if (type == kCandidateHost) {
candidate_type = cricket::LOCAL_PORT_TYPE;
} else if (type == kCandidateSrflx) {
candidate_type = cricket::STUN_PORT_TYPE;
} else if (type == kCandidateRelay) {
candidate_type = cricket::RELAY_PORT_TYPE;
} else {
LOG(LS_ERROR) << "Unsupported candidate type from line: " << line;
return false;
}
// extension
std::string name;
std::string network_name;
std::string username;
std::string password;
uint32 generation = 0;
for (size_t i = 8; i < (fields.size() - 1); ++i) {
const std::string field = fields.at(i);
if (field == kAttributeCandidateName) {
name = fields.at(++i);
} else if (field == kAttributeCandidateNetworkName) {
network_name = fields.at(++i);
} else if (field == kAttributeCandidateUsername) {
username = fields.at(++i);
} else if (field == kAttributeCandidatePassword) {
password = fields.at(++i);
} else if (field == kAttributeCandidateGeneration) {
generation = talk_base::FromString<uint32>(fields.at(++i));
}
}
SocketAddress address(connection_address, port);
Candidate candidate(name, transport, address, priority, username,
password, candidate_type, network_name, generation);
candidates->push_back(candidate);
} else if (HasAttribute(line, kAttributeRtpmap)) {
// a=rtpmap:<payload type> <encoding name>/<clock rate>
// [/<encodingparameters>]
// 2 mandatory fields
if (fields.size() < 2) {
LOG_LINE_PARSING_ERROR(line);
return false;
}
const int payload_type = talk_base::FromString<int>(
fields[0].substr(strlen(kAttributeRtpmap)));
const std::string encoder = fields[1];
const size_t pos = encoder.find("/");
if (pos == std::string::npos)
return false;
const std::string encoding_name = encoder.substr(0, pos);
const int clock_rate =
talk_base::FromString<int>(encoder.substr(pos + 1));
if (media_type == cricket::MEDIA_TYPE_VIDEO) {
VideoContentDescription* video_desc =
static_cast<VideoContentDescription*>(media_desc);
video_desc->AddCodec(cricket::VideoCodec(payload_type, encoding_name,
0, 0, 0, 0));
} else if (media_type == cricket::MEDIA_TYPE_AUDIO) {
AudioContentDescription* audio_desc =
static_cast<AudioContentDescription*>(media_desc);
audio_desc->AddCodec(cricket::AudioCodec(payload_type, encoding_name,
clock_rate, 0, 0, 0));
}
} else {
LOG(LS_WARNING) << "Unsupported line: " << line;
}
}
return true;
}
} // namespace webrtc