| /* |
| * 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, ¤t_pos)) { |
| return false; |
| } |
| |
| // Time Description |
| if (!ParseTimeDescription(message, ¤t_pos)) { |
| return false; |
| } |
| |
| // Media Description |
| if (!ParseMediaDescription(message, ¤t_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 |