| /* |
| * 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/webrtcjson.h" |
| |
| #include <stdio.h> |
| #include <string> |
| #include <vector> |
| |
| #include "talk/base/json.h" |
| #include "talk/base/logging.h" |
| #include "talk/base/stringutils.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" |
| |
| namespace webrtc { |
| static const int kIceComponent = 1; |
| static const int kIceFoundation = 1; |
| |
| static std::vector<Json::Value> ReadValues(const Json::Value& value, |
| const std::string& key); |
| |
| static void BuildContent( |
| const cricket::SessionDescription* sdp, |
| const cricket::ContentInfo& content_info, |
| const std::vector<cricket::Candidate>& candidates, |
| bool video, |
| Json::Value* content); |
| |
| static void BuildCandidate(const std::vector<cricket::Candidate>& candidates, |
| bool video, |
| std::vector<Json::Value>* jcandidates); |
| |
| static void BuildRtpMapParams(const cricket::ContentInfo& content_info, |
| bool video, |
| std::vector<Json::Value>* rtpmap); |
| |
| static void BuildCrypto(const cricket::ContentInfo& content_info, |
| bool video, |
| std::vector<Json::Value>* cryptos); |
| |
| static void BuildTrack(const cricket::SessionDescription* sdp, |
| bool video, |
| std::vector<Json::Value>* track); |
| |
| bool ParseContent(const Json::Value& jmessage, |
| cricket::SessionDescription* sdp, |
| std::vector<cricket::Candidate>* candidates); |
| |
| static bool ParseAudioCodec(const Json::Value& value, |
| cricket::AudioContentDescription* content); |
| static bool ParseVideoCodec(const Json::Value& value, |
| cricket::VideoContentDescription* content); |
| static bool ParseCrypto(const Json::Value& content, |
| cricket::MediaContentDescription* desc); |
| |
| static bool ParseCandidates(const Json::Value& content, |
| std::vector<cricket::Candidate>* candidates); |
| |
| static bool ParseTrack(const Json::Value& content, |
| cricket::MediaContentDescription* content_desc); |
| |
| static void Append(Json::Value* object, const std::string& key, bool value); |
| static void Append(Json::Value* object, const std::string& key, |
| const char* value); |
| static void Append(Json::Value* object, const std::string& key, int value); |
| static void Append(Json::Value* object, const std::string& key, |
| const std::string& value); |
| static void Append(Json::Value* object, const std::string& key, uint32 value); |
| static void Append(Json::Value* object, const std::string& key, |
| const Json::Value& value); |
| static void Append(Json::Value* object, |
| const std::string& key, |
| const std::vector<Json::Value>& values); |
| |
| void JsonSerializeSessionDescription( |
| const cricket::SessionDescription* sdp, |
| const std::vector<cricket::Candidate>& candidates, |
| Json::Value* message) { |
| |
| const cricket::ContentInfo* audio_content = GetFirstAudioContent(sdp); |
| const cricket::ContentInfo* video_content = GetFirstVideoContent(sdp); |
| |
| std::vector<Json::Value> together; |
| together.push_back("audio"); |
| together.push_back("video"); |
| |
| std::vector<Json::Value> contents; |
| |
| if (audio_content) { |
| Json::Value content; |
| BuildContent(sdp, *audio_content, candidates, false, &content); |
| contents.push_back(content); |
| } |
| |
| if (video_content) { |
| Json::Value content; |
| BuildContent(sdp, *video_content, candidates, true, &content); |
| contents.push_back(content); |
| } |
| |
| Append(message, "content", contents); |
| Append(message, "TOGETHER", together); |
| } |
| |
| void BuildContent( |
| const cricket::SessionDescription* sdp, |
| const cricket::ContentInfo& content_info, |
| const std::vector<cricket::Candidate>& candidates, |
| bool video, |
| Json::Value* content) { |
| std::string label("media"); |
| // TODO: Use enum instead of bool video to prepare for other |
| // media types such as the data media stream. |
| if (video) { |
| Append(content, label, "video"); |
| } else { |
| Append(content, label, "audio"); |
| } |
| |
| const cricket::MediaContentDescription* media_info = |
| static_cast<const cricket::MediaContentDescription*> ( |
| content_info.description); |
| if (media_info->rtcp_mux()) { |
| Append(content, "rtcp_mux", true); |
| } |
| |
| // rtpmap |
| std::vector<Json::Value> rtpmap; |
| BuildRtpMapParams(content_info, video, &rtpmap); |
| Append(content, "rtpmap", rtpmap); |
| |
| // crypto |
| std::vector<Json::Value> crypto; |
| BuildCrypto(content_info, video, &crypto); |
| Append(content, "crypto", crypto); |
| |
| // candidate |
| std::vector<Json::Value> jcandidates; |
| BuildCandidate(candidates, video, &jcandidates); |
| Append(content, "candidate", jcandidates); |
| |
| // track |
| std::vector<Json::Value> track; |
| BuildTrack(sdp, video, &track); |
| Append(content, "track", track); |
| } |
| |
| void BuildRtpMapParams(const cricket::ContentInfo& content_info, |
| bool video, |
| std::vector<Json::Value>* rtpmap) { |
| if (!video) { |
| const cricket::AudioContentDescription* audio = |
| static_cast<const cricket::AudioContentDescription*>( |
| content_info.description); |
| |
| std::vector<cricket::AudioCodec>::const_iterator iter = |
| audio->codecs().begin(); |
| std::vector<cricket::AudioCodec>::const_iterator iter_end = |
| audio->codecs().end(); |
| for (; iter != iter_end; ++iter) { |
| Json::Value codec; |
| std::string codec_str(std::string("audio/").append(iter->name)); |
| // adding clockrate |
| Append(&codec, "clockrate", iter->clockrate); |
| Append(&codec, "codec", codec_str); |
| Json::Value codec_id; |
| Append(&codec_id, talk_base::ToString(iter->id), codec); |
| rtpmap->push_back(codec_id); |
| } |
| } else { |
| const cricket::VideoContentDescription* video = |
| static_cast<const cricket::VideoContentDescription*>( |
| content_info.description); |
| |
| std::vector<cricket::VideoCodec>::const_iterator iter = |
| video->codecs().begin(); |
| std::vector<cricket::VideoCodec>::const_iterator iter_end = |
| video->codecs().end(); |
| for (; iter != iter_end; ++iter) { |
| Json::Value codec; |
| std::string codec_str(std::string("video/").append(iter->name)); |
| Append(&codec, "codec", codec_str); |
| Json::Value codec_id; |
| Append(&codec_id, talk_base::ToString(iter->id), codec); |
| rtpmap->push_back(codec_id); |
| } |
| } |
| } |
| |
| void BuildCrypto(const cricket::ContentInfo& content_info, |
| bool video, |
| std::vector<Json::Value>* cryptos) { |
| const cricket::MediaContentDescription* content_desc = |
| static_cast<const cricket::MediaContentDescription*>( |
| content_info.description); |
| std::vector<cricket::CryptoParams>::const_iterator iter = |
| content_desc->cryptos().begin(); |
| std::vector<cricket::CryptoParams>::const_iterator iter_end = |
| content_desc->cryptos().end(); |
| for (; iter != iter_end; ++iter) { |
| Json::Value crypto; |
| Append(&crypto, "cipher_suite", iter->cipher_suite); |
| Append(&crypto, "key_params", iter->key_params); |
| cryptos->push_back(crypto); |
| } |
| } |
| |
| void BuildCandidate(const std::vector<cricket::Candidate>& candidates, |
| bool video, |
| std::vector<Json::Value>* jcandidates) { |
| std::vector<cricket::Candidate>::const_iterator iter = |
| candidates.begin(); |
| std::vector<cricket::Candidate>::const_iterator iter_end = |
| candidates.end(); |
| for (; iter != iter_end; ++iter) { |
| if ((video && (!iter->name().compare("video_rtcp") || |
| (!iter->name().compare("video_rtp")))) || |
| (!video && (!iter->name().compare("rtp") || |
| (!iter->name().compare("rtcp"))))) { |
| Json::Value jcandidate; |
| Append(&jcandidate, "component", kIceComponent); |
| Append(&jcandidate, "foundation", kIceFoundation); |
| Append(&jcandidate, "generation", iter->generation()); |
| Append(&jcandidate, "proto", iter->protocol()); |
| Append(&jcandidate, "priority", iter->preference_str()); |
| Append(&jcandidate, "ip", iter->address().IPAsString()); |
| Append(&jcandidate, "port", iter->address().PortAsString()); |
| Append(&jcandidate, "type", iter->type()); |
| Append(&jcandidate, "name", iter->name()); |
| Append(&jcandidate, "network_name", iter->network_name()); |
| Append(&jcandidate, "username", iter->username()); |
| Append(&jcandidate, "password", iter->password()); |
| jcandidates->push_back(jcandidate); |
| } |
| } |
| } |
| |
| void BuildTrack(const cricket::SessionDescription* sdp, |
| bool video, |
| std::vector<Json::Value>* tracks) { |
| const cricket::ContentInfo* content; |
| if (video) |
| content = GetFirstVideoContent(sdp); |
| else |
| content = GetFirstAudioContent(sdp); |
| |
| if (!content) |
| return; |
| |
| const cricket::MediaContentDescription* desc = |
| static_cast<const cricket::MediaContentDescription*>( |
| content->description); |
| for (cricket::StreamParamsVec::const_iterator it = desc->streams().begin(); |
| it != desc->streams().end(); |
| ++it) { |
| // TODO: Support ssrcsgroups. |
| Json::Value track; |
| Append(&track, "ssrc", it->ssrcs[0]); |
| Append(&track, "cname", it->cname); |
| Append(&track, "stream_label", it->sync_label); |
| Append(&track, "label", it->name); |
| tracks->push_back(track); |
| } |
| } |
| |
| bool JsonDeserializeSessionDescription( |
| const Json::Value& message, |
| cricket::SessionDescription* sdp, |
| std::vector<cricket::Candidate>* candidates) { |
| |
| ASSERT(sdp != NULL); |
| ASSERT(candidates != NULL); |
| |
| if (sdp == NULL || candidates == NULL) |
| return false; |
| |
| // Get content |
| std::vector<Json::Value> contents = ReadValues(message, "content"); |
| if (contents.size() == 0) |
| return false; |
| for (size_t i = 0; i < contents.size(); ++i) { |
| Json::Value content = contents[i]; |
| // candidates |
| if (!ParseCandidates(content, candidates)) |
| return false; |
| // rtcp_mux |
| bool rtcp_mux; |
| if (!GetBoolFromJsonObject(content, "rtcp_mux", &rtcp_mux)) |
| rtcp_mux = false; |
| // rtpmap |
| if (content["media"].asString().compare("audio") == 0) { |
| cricket::AudioContentDescription* audio_content = |
| new cricket::AudioContentDescription(); |
| if (!ParseAudioCodec(content, audio_content)) |
| return false; |
| audio_content->set_rtcp_mux(rtcp_mux); |
| audio_content->SortCodecs(); |
| // crypto |
| if (!ParseCrypto(content, audio_content)) |
| return false; |
| // tracks |
| if (!ParseTrack(content, audio_content)) |
| return false; |
| (sdp)->AddContent(cricket::CN_AUDIO, |
| cricket::NS_JINGLE_RTP, audio_content); |
| } else if (content["media"].asString().compare("video") == 0) { |
| cricket::VideoContentDescription* video_content = |
| new cricket::VideoContentDescription(); |
| if (!ParseVideoCodec(content, video_content)) |
| return false; |
| video_content->set_rtcp_mux(rtcp_mux); |
| video_content->SortCodecs(); |
| // crypto |
| if (!ParseCrypto(content, video_content)) |
| return false; |
| if (!ParseTrack(content, video_content)) |
| return false; |
| (sdp)->AddContent(cricket::CN_VIDEO, |
| cricket::NS_JINGLE_RTP, video_content); |
| } |
| } |
| return true; |
| } |
| |
| bool ParseAudioCodec(const Json::Value& value, |
| cricket::AudioContentDescription* content) { |
| std::vector<Json::Value> rtpmap(ReadValues(value, "rtpmap")); |
| // When there's no codecs in common, rtpmap can be empty. |
| if (rtpmap.empty()) |
| return true; |
| |
| std::vector<Json::Value>::const_iterator iter = |
| rtpmap.begin(); |
| std::vector<Json::Value>::const_iterator iter_end = |
| rtpmap.end(); |
| for (; iter != iter_end; ++iter) { |
| cricket::AudioCodec codec; |
| std::string pltype(iter->begin().memberName()); |
| talk_base::FromString(pltype, &codec.id); |
| Json::Value codec_info((*iter)[pltype]); |
| std::string codec_name; |
| if (!GetStringFromJsonObject(codec_info, "codec", &codec_name)) |
| continue; |
| std::vector<std::string> tokens; |
| talk_base::split(codec_name, '/', &tokens); |
| codec.name = tokens[1]; |
| GetIntFromJsonObject(codec_info, "clockrate", &codec.clockrate); |
| content->AddCodec(codec); |
| } |
| |
| return true; |
| } |
| |
| bool ParseVideoCodec(const Json::Value& value, |
| cricket::VideoContentDescription* content) { |
| std::vector<Json::Value> rtpmap(ReadValues(value, "rtpmap")); |
| // When there's no codecs in common, rtpmap can be empty. |
| if (rtpmap.empty()) |
| return true; |
| |
| std::vector<Json::Value>::const_iterator iter = |
| rtpmap.begin(); |
| std::vector<Json::Value>::const_iterator iter_end = |
| rtpmap.end(); |
| for (; iter != iter_end; ++iter) { |
| cricket::VideoCodec codec; |
| std::string pltype(iter->begin().memberName()); |
| talk_base::FromString(pltype, &codec.id); |
| Json::Value codec_info((*iter)[pltype]); |
| std::vector<std::string> tokens; |
| talk_base::split(codec_info["codec"].asString(), '/', &tokens); |
| codec.name = tokens[1]; |
| content->AddCodec(codec); |
| } |
| return true; |
| } |
| |
| bool ParseCandidates(const Json::Value& content, |
| std::vector<cricket::Candidate>* candidates) { |
| std::vector<Json::Value> jcandidates(ReadValues(content, "candidate")); |
| std::vector<Json::Value>::const_iterator iter = |
| jcandidates.begin(); |
| std::vector<Json::Value>::const_iterator iter_end = |
| jcandidates.end(); |
| for (; iter != iter_end; ++iter) { |
| cricket::Candidate cand; |
| |
| unsigned int generation; |
| if (!GetUIntFromJsonObject(*iter, "generation", &generation)) |
| return false; |
| cand.set_generation_str(talk_base::ToString(generation)); |
| |
| std::string proto; |
| if (!GetStringFromJsonObject(*iter, "proto", &proto)) |
| return false; |
| cand.set_protocol(proto); |
| |
| std::string priority; |
| if (!GetStringFromJsonObject(*iter, "priority", &priority)) |
| return false; |
| cand.set_preference_str(priority); |
| |
| std::string str; |
| talk_base::SocketAddress addr; |
| if (!GetStringFromJsonObject(*iter, "ip", &str)) |
| return false; |
| addr.SetIP(str); |
| if (!GetStringFromJsonObject(*iter, "port", &str)) |
| return false; |
| int port; |
| if (!talk_base::FromString(str, &port)) |
| return false; |
| addr.SetPort(port); |
| cand.set_address(addr); |
| |
| if (!GetStringFromJsonObject(*iter, "type", &str)) |
| return false; |
| cand.set_type(str); |
| |
| if (!GetStringFromJsonObject(*iter, "name", &str)) |
| return false; |
| cand.set_name(str); |
| |
| if (!GetStringFromJsonObject(*iter, "network_name", &str)) |
| return false; |
| cand.set_network_name(str); |
| |
| if (!GetStringFromJsonObject(*iter, "username", &str)) |
| return false; |
| cand.set_username(str); |
| |
| if (!GetStringFromJsonObject(*iter, "password", &str)) |
| return false; |
| cand.set_password(str); |
| |
| candidates->push_back(cand); |
| } |
| return true; |
| } |
| |
| bool ParseCrypto(const Json::Value& content, |
| cricket::MediaContentDescription* desc) { |
| std::vector<Json::Value> jcryptos(ReadValues(content, "crypto")); |
| std::vector<Json::Value>::const_iterator iter = |
| jcryptos.begin(); |
| std::vector<Json::Value>::const_iterator iter_end = |
| jcryptos.end(); |
| for (; iter != iter_end; ++iter) { |
| cricket::CryptoParams crypto; |
| |
| std::string cipher_suite; |
| if (!GetStringFromJsonObject(*iter, "cipher_suite", &cipher_suite)) |
| return false; |
| crypto.cipher_suite = cipher_suite; |
| |
| std::string key_params; |
| if (!GetStringFromJsonObject(*iter, "key_params", &key_params)) |
| return false; |
| crypto.key_params= key_params; |
| |
| desc->AddCrypto(crypto); |
| } |
| return true; |
| } |
| |
| bool ParseTrack(const Json::Value& content, |
| cricket::MediaContentDescription* content_desc) { |
| ASSERT(content_desc != NULL); |
| if (!content_desc) |
| return false; |
| |
| std::vector<Json::Value> tracks(ReadValues(content, "track")); |
| std::vector<Json::Value>::const_iterator iter = |
| tracks.begin(); |
| std::vector<Json::Value>::const_iterator iter_end = |
| tracks.end(); |
| for (; iter != iter_end; ++iter) { |
| uint32 ssrc; |
| std::string label; |
| std::string cname; |
| std::string stream_label; |
| if (!GetUIntFromJsonObject(*iter, "ssrc", &ssrc)) |
| return false; |
| // label is optional, it will be empty string if doesn't exist |
| GetStringFromJsonObject(*iter, "label", &label); |
| if (!GetStringFromJsonObject(*iter, "cname", &cname)) |
| return false; |
| // stream_label is optional, it will be the same as cname if it |
| // doesn't exist. |
| GetStringFromJsonObject(*iter, "stream_label", &stream_label); |
| if (stream_label.empty()) |
| stream_label = cname; |
| cricket::StreamParams stream; |
| stream.name = label; |
| stream.cname = cname; |
| stream.sync_label = stream_label; |
| stream.ssrcs.push_back(ssrc); |
| content_desc->AddStream(stream); |
| } |
| return true; |
| } |
| |
| std::vector<Json::Value> ReadValues( |
| const Json::Value& value, const std::string& key) { |
| std::vector<Json::Value> objects; |
| for (Json::Value::ArrayIndex i = 0; i < value[key].size(); ++i) { |
| objects.push_back(value[key][i]); |
| } |
| return objects; |
| } |
| |
| void Append(Json::Value* object, const std::string& key, bool value) { |
| (*object)[key] = Json::Value(value); |
| } |
| |
| void Append(Json::Value* object, const std::string& key, const char* value) { |
| (*object)[key] = Json::Value(value); |
| } |
| |
| void Append(Json::Value* object, const std::string& key, int value) { |
| (*object)[key] = Json::Value(value); |
| } |
| |
| void Append(Json::Value* object, const std::string& key, |
| const std::string& value) { |
| (*object)[key] = Json::Value(value); |
| } |
| |
| void Append(Json::Value* object, const std::string& key, uint32 value) { |
| (*object)[key] = Json::Value(value); |
| } |
| |
| void Append(Json::Value* object, const std::string& key, |
| const Json::Value& value) { |
| (*object)[key] = value; |
| } |
| |
| void Append(Json::Value* object, |
| const std::string & key, |
| const std::vector<Json::Value>& values) { |
| for (std::vector<Json::Value>::const_iterator iter = values.begin(); |
| iter != values.end(); ++iter) { |
| (*object)[key].append(*iter); |
| } |
| } |
| |
| } // namespace webrtc |