Home | History | Annotate | Download | only in media
      1 /*
      2  * libjingle
      3  * Copyright 2004 Google Inc.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are met:
      7  *
      8  *  1. Redistributions of source code must retain the above copyright notice,
      9  *     this list of conditions and the following disclaimer.
     10  *  2. Redistributions in binary form must reproduce the above copyright notice,
     11  *     this list of conditions and the following disclaimer in the documentation
     12  *     and/or other materials provided with the distribution.
     13  *  3. The name of the author may not be used to endorse or promote products
     14  *     derived from this software without specific prior written permission.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
     17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
     18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
     19  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
     22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
     23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
     24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
     25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     26  */
     27 
     28 #include <string>
     29 
     30 #include "talk/session/media/mediasessionclient.h"
     31 
     32 #include "talk/base/helpers.h"
     33 #include "talk/base/logging.h"
     34 #include "talk/base/stringencode.h"
     35 #include "talk/base/stringutils.h"
     36 #include "talk/media/base/cryptoparams.h"
     37 #include "talk/media/base/capturemanager.h"
     38 #include "talk/media/sctp/sctpdataengine.h"
     39 #include "talk/p2p/base/constants.h"
     40 #include "talk/p2p/base/parsing.h"
     41 #include "talk/session/media/mediamessages.h"
     42 #include "talk/session/media/srtpfilter.h"
     43 #include "talk/xmllite/qname.h"
     44 #include "talk/xmllite/xmlconstants.h"
     45 #include "talk/xmpp/constants.h"
     46 
     47 namespace cricket {
     48 
     49 #if !defined(DISABLE_MEDIA_ENGINE_FACTORY)
     50 MediaSessionClient::MediaSessionClient(
     51     const buzz::Jid& jid, SessionManager *manager)
     52     : jid_(jid),
     53       session_manager_(manager),
     54       focus_call_(NULL),
     55       channel_manager_(new ChannelManager(session_manager_->worker_thread())),
     56       desc_factory_(channel_manager_,
     57           session_manager_->transport_desc_factory()),
     58       multisession_enabled_(false) {
     59   Construct();
     60 }
     61 #endif
     62 
     63 MediaSessionClient::MediaSessionClient(
     64     const buzz::Jid& jid, SessionManager *manager,
     65     MediaEngineInterface* media_engine,
     66     DataEngineInterface* data_media_engine,
     67     DeviceManagerInterface* device_manager)
     68     : jid_(jid),
     69       session_manager_(manager),
     70       focus_call_(NULL),
     71       channel_manager_(new ChannelManager(
     72           media_engine, data_media_engine,
     73           device_manager, new CaptureManager(),
     74           session_manager_->worker_thread())),
     75       desc_factory_(channel_manager_,
     76                     session_manager_->transport_desc_factory()),
     77       multisession_enabled_(false) {
     78   Construct();
     79 }
     80 
     81 void MediaSessionClient::Construct() {
     82   // Register ourselves as the handler of audio and video sessions.
     83   session_manager_->AddClient(NS_JINGLE_RTP, this);
     84   // Forward device notifications.
     85   SignalDevicesChange.repeat(channel_manager_->SignalDevicesChange);
     86   // Bring up the channel manager.
     87   // In previous versions of ChannelManager, this was done automatically
     88   // in the constructor.
     89   channel_manager_->Init();
     90 }
     91 
     92 MediaSessionClient::~MediaSessionClient() {
     93   // Destroy all calls
     94   std::map<uint32, Call *>::iterator it;
     95   while (calls_.begin() != calls_.end()) {
     96     std::map<uint32, Call *>::iterator it = calls_.begin();
     97     DestroyCall((*it).second);
     98   }
     99 
    100   // Delete channel manager. This will wait for the channels to exit
    101   delete channel_manager_;
    102 
    103   // Remove ourselves from the client map.
    104   session_manager_->RemoveClient(NS_JINGLE_RTP);
    105 }
    106 
    107 Call *MediaSessionClient::CreateCall() {
    108   Call *call = new Call(this);
    109   calls_[call->id()] = call;
    110   SignalCallCreate(call);
    111   return call;
    112 }
    113 
    114 void MediaSessionClient::OnSessionCreate(Session *session,
    115                                          bool received_initiate) {
    116   if (received_initiate) {
    117     session->SignalState.connect(this, &MediaSessionClient::OnSessionState);
    118   }
    119 }
    120 
    121 void MediaSessionClient::OnSessionState(BaseSession* base_session,
    122                                         BaseSession::State state) {
    123   // MediaSessionClient can only be used with a Session*, so it's
    124   // safe to cast here.
    125   Session* session = static_cast<Session*>(base_session);
    126 
    127   if (state == Session::STATE_RECEIVEDINITIATE) {
    128     // The creation of the call must happen after the session has
    129     // processed the initiate message because we need the
    130     // remote_description to know what content names to use in the
    131     // call.
    132 
    133     // If our accept would have no codecs, then we must reject this call.
    134     const SessionDescription* offer = session->remote_description();
    135     const SessionDescription* accept = CreateAnswer(offer, CallOptions());
    136     const ContentInfo* audio_content = GetFirstAudioContent(accept);
    137     bool audio_rejected = (!audio_content) ? true : audio_content->rejected;
    138     const AudioContentDescription* audio_desc = (!audio_content) ? NULL :
    139         static_cast<const AudioContentDescription*>(audio_content->description);
    140 
    141     // For some reason, we need a call even if we reject. So, either find a
    142     // matching call or create a new one.
    143     // The matching of existing calls is used to support the multi-session mode
    144     // required for p2p handoffs: ie. once a MUC call is established, a new
    145     // session may be established for the same call but is direct between the
    146     // clients. To indicate that this is the case, the initiator of the incoming
    147     // session is set to be the same as the remote name of the MUC for the
    148     // existing session, thus the client can know that this is a new session for
    149     // the existing call, rather than a whole new call.
    150     Call* call = NULL;
    151     if (multisession_enabled_) {
    152       call = FindCallByRemoteName(session->initiator_name());
    153     }
    154 
    155     if (call == NULL) {
    156       // Could not find a matching call, so create a new one.
    157       call = CreateCall();
    158     }
    159 
    160     session_map_[session->id()] = call;
    161     call->IncomingSession(session, offer);
    162 
    163     if (audio_rejected || !audio_desc || audio_desc->codecs().size() == 0) {
    164       session->Reject(STR_TERMINATE_INCOMPATIBLE_PARAMETERS);
    165     }
    166     delete accept;
    167   }
    168 }
    169 
    170 void MediaSessionClient::DestroyCall(Call *call) {
    171   // Change focus away, signal destruction
    172 
    173   if (call == focus_call_)
    174     SetFocus(NULL);
    175   SignalCallDestroy(call);
    176 
    177   // Remove it from calls_ map and delete
    178 
    179   std::map<uint32, Call *>::iterator it = calls_.find(call->id());
    180   if (it != calls_.end())
    181     calls_.erase(it);
    182 
    183   delete call;
    184 }
    185 
    186 void MediaSessionClient::OnSessionDestroy(Session *session) {
    187   // Find the call this session is in, remove it
    188   SessionMap::iterator it = session_map_.find(session->id());
    189   ASSERT(it != session_map_.end());
    190   if (it != session_map_.end()) {
    191     Call *call = (*it).second;
    192     session_map_.erase(it);
    193     call->RemoveSession(session);
    194   }
    195 }
    196 
    197 Call *MediaSessionClient::GetFocus() {
    198   return focus_call_;
    199 }
    200 
    201 void MediaSessionClient::SetFocus(Call *call) {
    202   Call *old_focus_call = focus_call_;
    203   if (focus_call_ != call) {
    204     if (focus_call_ != NULL)
    205       focus_call_->EnableChannels(false);
    206     focus_call_ = call;
    207     if (focus_call_ != NULL)
    208       focus_call_->EnableChannels(true);
    209     SignalFocus(focus_call_, old_focus_call);
    210   }
    211 }
    212 
    213 void MediaSessionClient::JoinCalls(Call *call_to_join, Call *call) {
    214   // Move all sessions from call to call_to_join, delete call.
    215   // If call_to_join has focus, added sessions should have enabled channels.
    216 
    217   if (focus_call_ == call)
    218     SetFocus(NULL);
    219   call_to_join->Join(call, focus_call_ == call_to_join);
    220   DestroyCall(call);
    221 }
    222 
    223 Session *MediaSessionClient::CreateSession(Call *call) {
    224   std::string id;
    225   return CreateSession(id, call);
    226 }
    227 
    228 Session *MediaSessionClient::CreateSession(const std::string& id, Call* call) {
    229   const std::string& type = NS_JINGLE_RTP;
    230   Session *session = session_manager_->CreateSession(id, jid().Str(), type);
    231   session_map_[session->id()] = call;
    232   return session;
    233 }
    234 
    235 Call *MediaSessionClient::FindCallByRemoteName(const std::string &remote_name) {
    236   SessionMap::const_iterator call;
    237   for (call = session_map_.begin(); call != session_map_.end(); ++call) {
    238     std::vector<Session *> sessions = call->second->sessions();
    239     std::vector<Session *>::const_iterator session;
    240     for (session = sessions.begin(); session != sessions.end(); ++session) {
    241       if (remote_name == (*session)->remote_name()) {
    242         return call->second;
    243       }
    244     }
    245   }
    246 
    247   return NULL;
    248 }
    249 
    250 // TODO(pthatcher): Move all of the parsing and writing functions into
    251 // mediamessages.cc, with unit tests.
    252 bool ParseGingleAudioCodec(const buzz::XmlElement* element, AudioCodec* out) {
    253   int id = GetXmlAttr(element, QN_ID, -1);
    254   if (id < 0)
    255     return false;
    256 
    257   std::string name = GetXmlAttr(element, QN_NAME, buzz::STR_EMPTY);
    258   int clockrate = GetXmlAttr(element, QN_CLOCKRATE, 0);
    259   int bitrate = GetXmlAttr(element, QN_BITRATE, 0);
    260   int channels = GetXmlAttr(element, QN_CHANNELS, 1);
    261   *out = AudioCodec(id, name, clockrate, bitrate, channels, 0);
    262   return true;
    263 }
    264 
    265 bool ParseGingleVideoCodec(const buzz::XmlElement* element, VideoCodec* out) {
    266   int id = GetXmlAttr(element, QN_ID, -1);
    267   if (id < 0)
    268     return false;
    269 
    270   std::string name = GetXmlAttr(element, QN_NAME, buzz::STR_EMPTY);
    271   int width = GetXmlAttr(element, QN_WIDTH, 0);
    272   int height = GetXmlAttr(element, QN_HEIGHT, 0);
    273   int framerate = GetXmlAttr(element, QN_FRAMERATE, 0);
    274 
    275   *out = VideoCodec(id, name, width, height, framerate, 0);
    276   return true;
    277 }
    278 
    279 // Parses an ssrc string as a legacy stream.  If it fails, returns
    280 // false and fills an error message.
    281 bool ParseSsrcAsLegacyStream(const std::string& ssrc_str,
    282                              std::vector<StreamParams>* streams,
    283                              ParseError* error) {
    284   if (!ssrc_str.empty()) {
    285     uint32 ssrc;
    286     if (!talk_base::FromString(ssrc_str, &ssrc)) {
    287       return BadParse("Missing or invalid ssrc.", error);
    288     }
    289 
    290     streams->push_back(StreamParams::CreateLegacy(ssrc));
    291   }
    292   return true;
    293 }
    294 
    295 void ParseGingleSsrc(const buzz::XmlElement* parent_elem,
    296                      const buzz::QName& name,
    297                      MediaContentDescription* media) {
    298   const buzz::XmlElement* ssrc_elem = parent_elem->FirstNamed(name);
    299   if (ssrc_elem) {
    300     ParseError error;
    301     ParseSsrcAsLegacyStream(
    302         ssrc_elem->BodyText(), &(media->mutable_streams()), &error);
    303   }
    304 }
    305 
    306 bool ParseCryptoParams(const buzz::XmlElement* element,
    307                        CryptoParams* out,
    308                        ParseError* error) {
    309   if (!element->HasAttr(QN_CRYPTO_SUITE)) {
    310     return BadParse("crypto: crypto-suite attribute missing ", error);
    311   } else if (!element->HasAttr(QN_CRYPTO_KEY_PARAMS)) {
    312     return BadParse("crypto: key-params attribute missing ", error);
    313   } else if (!element->HasAttr(QN_CRYPTO_TAG)) {
    314     return BadParse("crypto: tag attribute missing ", error);
    315   }
    316 
    317   const std::string& crypto_suite = element->Attr(QN_CRYPTO_SUITE);
    318   const std::string& key_params = element->Attr(QN_CRYPTO_KEY_PARAMS);
    319   const int tag = GetXmlAttr(element, QN_CRYPTO_TAG, 0);
    320   const std::string& session_params =
    321       element->Attr(QN_CRYPTO_SESSION_PARAMS);  // Optional.
    322 
    323   *out = CryptoParams(tag, crypto_suite, key_params, session_params);
    324   return true;
    325 }
    326 
    327 
    328 // Parse the first encryption element found with a matching 'usage'
    329 // element.
    330 // <usage/> is specific to Gingle. In Jingle, <crypto/> is already
    331 // scoped to a content.
    332 // Return false if there was an encryption element and it could not be
    333 // parsed.
    334 bool ParseGingleEncryption(const buzz::XmlElement* desc,
    335                            const buzz::QName& usage,
    336                            MediaContentDescription* media,
    337                            ParseError* error) {
    338   for (const buzz::XmlElement* encryption = desc->FirstNamed(QN_ENCRYPTION);
    339        encryption != NULL;
    340        encryption = encryption->NextNamed(QN_ENCRYPTION)) {
    341     if (encryption->FirstNamed(usage) != NULL) {
    342       media->set_crypto_required(
    343           GetXmlAttr(encryption, QN_ENCRYPTION_REQUIRED, false));
    344       for (const buzz::XmlElement* crypto = encryption->FirstNamed(QN_CRYPTO);
    345            crypto != NULL;
    346            crypto = crypto->NextNamed(QN_CRYPTO)) {
    347         CryptoParams params;
    348         if (!ParseCryptoParams(crypto, &params, error)) {
    349           return false;
    350         }
    351         media->AddCrypto(params);
    352       }
    353       break;
    354     }
    355   }
    356   return true;
    357 }
    358 
    359 void ParseBandwidth(const buzz::XmlElement* parent_elem,
    360                     MediaContentDescription* media) {
    361   const buzz::XmlElement* bw_elem = GetXmlChild(parent_elem, LN_BANDWIDTH);
    362   int bandwidth_kbps = -1;
    363   if (bw_elem && talk_base::FromString(bw_elem->BodyText(), &bandwidth_kbps)) {
    364     if (bandwidth_kbps >= 0) {
    365       media->set_bandwidth(bandwidth_kbps * 1000);
    366     }
    367   }
    368 }
    369 
    370 bool ParseGingleAudioContent(const buzz::XmlElement* content_elem,
    371                              ContentDescription** content,
    372                              ParseError* error) {
    373   AudioContentDescription* audio = new AudioContentDescription();
    374 
    375   if (content_elem->FirstElement()) {
    376     for (const buzz::XmlElement* codec_elem =
    377              content_elem->FirstNamed(QN_GINGLE_AUDIO_PAYLOADTYPE);
    378          codec_elem != NULL;
    379          codec_elem = codec_elem->NextNamed(QN_GINGLE_AUDIO_PAYLOADTYPE)) {
    380       AudioCodec codec;
    381       if (ParseGingleAudioCodec(codec_elem, &codec)) {
    382         audio->AddCodec(codec);
    383       }
    384     }
    385   } else {
    386     // For backward compatibility, we can assume the other client is
    387     // an old version of Talk if it has no audio payload types at all.
    388     audio->AddCodec(AudioCodec(103, "ISAC", 16000, -1, 1, 1));
    389     audio->AddCodec(AudioCodec(0, "PCMU", 8000, 64000, 1, 0));
    390   }
    391 
    392   ParseGingleSsrc(content_elem, QN_GINGLE_AUDIO_SRCID, audio);
    393 
    394   if (!ParseGingleEncryption(content_elem, QN_GINGLE_AUDIO_CRYPTO_USAGE,
    395                              audio, error)) {
    396     return false;
    397   }
    398 
    399   *content = audio;
    400   return true;
    401 }
    402 
    403 bool ParseGingleVideoContent(const buzz::XmlElement* content_elem,
    404                              ContentDescription** content,
    405                              ParseError* error) {
    406   VideoContentDescription* video = new VideoContentDescription();
    407 
    408   for (const buzz::XmlElement* codec_elem =
    409            content_elem->FirstNamed(QN_GINGLE_VIDEO_PAYLOADTYPE);
    410        codec_elem != NULL;
    411        codec_elem = codec_elem->NextNamed(QN_GINGLE_VIDEO_PAYLOADTYPE)) {
    412     VideoCodec codec;
    413     if (ParseGingleVideoCodec(codec_elem, &codec)) {
    414       video->AddCodec(codec);
    415     }
    416   }
    417 
    418   ParseGingleSsrc(content_elem, QN_GINGLE_VIDEO_SRCID, video);
    419   ParseBandwidth(content_elem, video);
    420 
    421   if (!ParseGingleEncryption(content_elem, QN_GINGLE_VIDEO_CRYPTO_USAGE,
    422                              video, error)) {
    423     return false;
    424   }
    425 
    426   *content = video;
    427   return true;
    428 }
    429 
    430 void ParsePayloadTypeParameters(const buzz::XmlElement* element,
    431                                 std::map<std::string, std::string>* paramap) {
    432   for (const buzz::XmlElement* param = element->FirstNamed(QN_PARAMETER);
    433        param != NULL; param = param->NextNamed(QN_PARAMETER)) {
    434     std::string name  = GetXmlAttr(param, QN_PAYLOADTYPE_PARAMETER_NAME,
    435                                    buzz::STR_EMPTY);
    436     std::string value = GetXmlAttr(param, QN_PAYLOADTYPE_PARAMETER_VALUE,
    437                                    buzz::STR_EMPTY);
    438     if (!name.empty() && !value.empty()) {
    439       paramap->insert(make_pair(name, value));
    440     }
    441   }
    442 }
    443 
    444 void ParseFeedbackParams(const buzz::XmlElement* element,
    445                          FeedbackParams* params) {
    446   for (const buzz::XmlElement* param = element->FirstNamed(QN_JINGLE_RTCP_FB);
    447        param != NULL; param = param->NextNamed(QN_JINGLE_RTCP_FB)) {
    448     std::string type = GetXmlAttr(param, QN_TYPE, buzz::STR_EMPTY);
    449     std::string subtype = GetXmlAttr(param, QN_SUBTYPE, buzz::STR_EMPTY);
    450     if (!type.empty()) {
    451       params->Add(FeedbackParam(type, subtype));
    452     }
    453   }
    454 }
    455 
    456 void AddFeedbackParams(const FeedbackParams& additional_params,
    457                        FeedbackParams* params) {
    458   for (size_t i = 0; i < additional_params.params().size(); ++i) {
    459     params->Add(additional_params.params()[i]);
    460   }
    461 }
    462 
    463 int FindWithDefault(const std::map<std::string, std::string>& map,
    464                     const std::string& key, const int def) {
    465   std::map<std::string, std::string>::const_iterator iter = map.find(key);
    466   return (iter == map.end()) ? def : atoi(iter->second.c_str());
    467 }
    468 
    469 
    470 // Parse the first encryption element found.
    471 // Return false if there was an encryption element and it could not be
    472 // parsed.
    473 bool ParseJingleEncryption(const buzz::XmlElement* content_elem,
    474                            MediaContentDescription* media,
    475                            ParseError* error) {
    476   const buzz::XmlElement* encryption =
    477           content_elem->FirstNamed(QN_ENCRYPTION);
    478   if (encryption == NULL) {
    479       return true;
    480   }
    481 
    482   media->set_crypto_required(
    483       GetXmlAttr(encryption, QN_ENCRYPTION_REQUIRED, false));
    484 
    485   for (const buzz::XmlElement* crypto = encryption->FirstNamed(QN_CRYPTO);
    486        crypto != NULL;
    487        crypto = crypto->NextNamed(QN_CRYPTO)) {
    488     CryptoParams params;
    489     if (!ParseCryptoParams(crypto, &params, error)) {
    490       return false;
    491     }
    492     media->AddCrypto(params);
    493   }
    494   return true;
    495 }
    496 
    497 bool ParseJingleAudioCodec(const buzz::XmlElement* elem, AudioCodec* codec) {
    498   int id = GetXmlAttr(elem, QN_ID, -1);
    499   if (id < 0)
    500     return false;
    501 
    502   std::string name = GetXmlAttr(elem, QN_NAME, buzz::STR_EMPTY);
    503   int clockrate = GetXmlAttr(elem, QN_CLOCKRATE, 0);
    504   int channels = GetXmlAttr(elem, QN_CHANNELS, 1);
    505 
    506   std::map<std::string, std::string> paramap;
    507   ParsePayloadTypeParameters(elem, &paramap);
    508   int bitrate = FindWithDefault(paramap, PAYLOADTYPE_PARAMETER_BITRATE, 0);
    509 
    510   *codec = AudioCodec(id, name, clockrate, bitrate, channels, 0);
    511   ParseFeedbackParams(elem, &codec->feedback_params);
    512   return true;
    513 }
    514 
    515 bool ParseJingleVideoCodec(const buzz::XmlElement* elem, VideoCodec* codec) {
    516   int id = GetXmlAttr(elem, QN_ID, -1);
    517   if (id < 0)
    518     return false;
    519 
    520   std::string name = GetXmlAttr(elem, QN_NAME, buzz::STR_EMPTY);
    521 
    522   std::map<std::string, std::string> paramap;
    523   ParsePayloadTypeParameters(elem, &paramap);
    524   int width = FindWithDefault(paramap, PAYLOADTYPE_PARAMETER_WIDTH, 0);
    525   int height = FindWithDefault(paramap, PAYLOADTYPE_PARAMETER_HEIGHT, 0);
    526   int framerate = FindWithDefault(paramap, PAYLOADTYPE_PARAMETER_FRAMERATE, 0);
    527 
    528   *codec = VideoCodec(id, name, width, height, framerate, 0);
    529   codec->params = paramap;
    530   ParseFeedbackParams(elem, &codec->feedback_params);
    531   return true;
    532 }
    533 
    534 bool ParseJingleDataCodec(const buzz::XmlElement* elem, DataCodec* codec) {
    535   int id = GetXmlAttr(elem, QN_ID, -1);
    536   if (id < 0)
    537     return false;
    538 
    539   std::string name = GetXmlAttr(elem, QN_NAME, buzz::STR_EMPTY);
    540 
    541   *codec = DataCodec(id, name, 0);
    542   ParseFeedbackParams(elem, &codec->feedback_params);
    543   return true;
    544 }
    545 
    546 bool ParseJingleStreamsOrLegacySsrc(const buzz::XmlElement* desc_elem,
    547                                     MediaContentDescription* media,
    548                                     ParseError* error) {
    549   if (HasJingleStreams(desc_elem)) {
    550     if (!ParseJingleStreams(desc_elem, &(media->mutable_streams()), error)) {
    551       return false;
    552     }
    553   } else {
    554     const std::string ssrc_str = desc_elem->Attr(QN_SSRC);
    555     if (!ParseSsrcAsLegacyStream(
    556             ssrc_str, &(media->mutable_streams()), error)) {
    557       return false;
    558     }
    559   }
    560   return true;
    561 }
    562 
    563 bool ParseJingleAudioContent(const buzz::XmlElement* content_elem,
    564                              ContentDescription** content,
    565                              ParseError* error) {
    566   talk_base::scoped_ptr<AudioContentDescription> audio(
    567       new AudioContentDescription());
    568 
    569   FeedbackParams content_feedback_params;
    570   ParseFeedbackParams(content_elem, &content_feedback_params);
    571 
    572   for (const buzz::XmlElement* payload_elem =
    573            content_elem->FirstNamed(QN_JINGLE_RTP_PAYLOADTYPE);
    574       payload_elem != NULL;
    575       payload_elem = payload_elem->NextNamed(QN_JINGLE_RTP_PAYLOADTYPE)) {
    576     AudioCodec codec;
    577     if (ParseJingleAudioCodec(payload_elem, &codec)) {
    578       AddFeedbackParams(content_feedback_params, &codec.feedback_params);
    579       audio->AddCodec(codec);
    580     }
    581   }
    582 
    583   if (!ParseJingleStreamsOrLegacySsrc(content_elem, audio.get(), error)) {
    584     return false;
    585   }
    586 
    587   if (!ParseJingleEncryption(content_elem, audio.get(), error)) {
    588     return false;
    589   }
    590 
    591   audio->set_rtcp_mux(content_elem->FirstNamed(QN_JINGLE_RTCP_MUX) != NULL);
    592 
    593   RtpHeaderExtensions hdrexts;
    594   if (!ParseJingleRtpHeaderExtensions(content_elem, &hdrexts, error)) {
    595     return false;
    596   }
    597   audio->set_rtp_header_extensions(hdrexts);
    598 
    599   *content = audio.release();
    600   return true;
    601 }
    602 
    603 bool ParseJingleVideoContent(const buzz::XmlElement* content_elem,
    604                              ContentDescription** content,
    605                              ParseError* error) {
    606   talk_base::scoped_ptr<VideoContentDescription> video(
    607       new VideoContentDescription());
    608 
    609   FeedbackParams content_feedback_params;
    610   ParseFeedbackParams(content_elem, &content_feedback_params);
    611 
    612   for (const buzz::XmlElement* payload_elem =
    613            content_elem->FirstNamed(QN_JINGLE_RTP_PAYLOADTYPE);
    614       payload_elem != NULL;
    615       payload_elem = payload_elem->NextNamed(QN_JINGLE_RTP_PAYLOADTYPE)) {
    616     VideoCodec codec;
    617     if (ParseJingleVideoCodec(payload_elem, &codec)) {
    618       AddFeedbackParams(content_feedback_params, &codec.feedback_params);
    619       video->AddCodec(codec);
    620     }
    621   }
    622 
    623   if (!ParseJingleStreamsOrLegacySsrc(content_elem, video.get(), error)) {
    624     return false;
    625   }
    626   ParseBandwidth(content_elem, video.get());
    627 
    628   if (!ParseJingleEncryption(content_elem, video.get(), error)) {
    629     return false;
    630   }
    631 
    632   video->set_rtcp_mux(content_elem->FirstNamed(QN_JINGLE_RTCP_MUX) != NULL);
    633 
    634   RtpHeaderExtensions hdrexts;
    635   if (!ParseJingleRtpHeaderExtensions(content_elem, &hdrexts, error)) {
    636     return false;
    637   }
    638   video->set_rtp_header_extensions(hdrexts);
    639 
    640   *content = video.release();
    641   return true;
    642 }
    643 
    644 bool ParseJingleSctpDataContent(const buzz::XmlElement* content_elem,
    645                                 ContentDescription** content,
    646                                 ParseError* error) {
    647   talk_base::scoped_ptr<DataContentDescription> data(
    648       new DataContentDescription());
    649   data->set_protocol(kMediaProtocolSctp);
    650 
    651   for (const buzz::XmlElement* stream_elem =
    652            content_elem->FirstNamed(QN_JINGLE_DRAFT_SCTP_STREAM);
    653        stream_elem != NULL;
    654        stream_elem = stream_elem->NextNamed(QN_JINGLE_DRAFT_SCTP_STREAM)) {
    655     StreamParams stream;
    656     stream.groupid = stream_elem->Attr(QN_NICK);
    657     stream.id = stream_elem->Attr(QN_NAME);
    658     uint32 sid;
    659     if (!talk_base::FromString(stream_elem->Attr(QN_SID), &sid)) {
    660       return BadParse("Missing or invalid sid.", error);
    661     }
    662     if (sid > kMaxSctpSid) {
    663       return BadParse("SID is greater than max value.", error);
    664     }
    665 
    666     stream.ssrcs.push_back(sid);
    667     data->mutable_streams().push_back(stream);
    668   }
    669 
    670   *content = data.release();
    671   return true;
    672 }
    673 
    674 bool ParseJingleRtpDataContent(const buzz::XmlElement* content_elem,
    675                                ContentDescription** content,
    676                                ParseError* error) {
    677   DataContentDescription* data = new DataContentDescription();
    678 
    679   FeedbackParams content_feedback_params;
    680   ParseFeedbackParams(content_elem, &content_feedback_params);
    681 
    682   for (const buzz::XmlElement* payload_elem =
    683            content_elem->FirstNamed(QN_JINGLE_RTP_PAYLOADTYPE);
    684       payload_elem != NULL;
    685       payload_elem = payload_elem->NextNamed(QN_JINGLE_RTP_PAYLOADTYPE)) {
    686     DataCodec codec;
    687     if (ParseJingleDataCodec(payload_elem, &codec)) {
    688       AddFeedbackParams(content_feedback_params, &codec.feedback_params);
    689       data->AddCodec(codec);
    690     }
    691   }
    692 
    693   if (!ParseJingleStreamsOrLegacySsrc(content_elem, data, error)) {
    694     return false;
    695   }
    696   ParseBandwidth(content_elem, data);
    697 
    698   if (!ParseJingleEncryption(content_elem, data, error)) {
    699     return false;
    700   }
    701 
    702   data->set_rtcp_mux(content_elem->FirstNamed(QN_JINGLE_RTCP_MUX) != NULL);
    703 
    704   *content = data;
    705   return true;
    706 }
    707 
    708 bool MediaSessionClient::ParseContent(SignalingProtocol protocol,
    709                                       const buzz::XmlElement* content_elem,
    710                                       ContentDescription** content,
    711                                       ParseError* error) {
    712   if (protocol == PROTOCOL_GINGLE) {
    713     const std::string& content_type = content_elem->Name().Namespace();
    714     if (NS_GINGLE_AUDIO == content_type) {
    715       return ParseGingleAudioContent(content_elem, content, error);
    716     } else if (NS_GINGLE_VIDEO == content_type) {
    717       return ParseGingleVideoContent(content_elem, content, error);
    718     } else {
    719       return BadParse("Unknown content type: " + content_type, error);
    720     }
    721   } else {
    722     const std::string& content_type = content_elem->Name().Namespace();
    723     // We use the XMLNS of the <description> element to determine if
    724     // it's RTP or SCTP.
    725     if (content_type == NS_JINGLE_DRAFT_SCTP) {
    726       return ParseJingleSctpDataContent(content_elem, content, error);
    727     }
    728 
    729     std::string media;
    730     if (!RequireXmlAttr(content_elem, QN_JINGLE_CONTENT_MEDIA, &media, error))
    731       return false;
    732 
    733     if (media == JINGLE_CONTENT_MEDIA_AUDIO) {
    734       return ParseJingleAudioContent(content_elem, content, error);
    735     } else if (media == JINGLE_CONTENT_MEDIA_VIDEO) {
    736       return ParseJingleVideoContent(content_elem, content, error);
    737     } else if (media == JINGLE_CONTENT_MEDIA_DATA) {
    738       return ParseJingleRtpDataContent(content_elem, content, error);
    739     } else {
    740       return BadParse("Unknown media: " + media, error);
    741     }
    742   }
    743 }
    744 
    745 buzz::XmlElement* CreateGingleAudioCodecElem(const AudioCodec& codec) {
    746   buzz::XmlElement* payload_type =
    747       new buzz::XmlElement(QN_GINGLE_AUDIO_PAYLOADTYPE, true);
    748   AddXmlAttr(payload_type, QN_ID, codec.id);
    749   payload_type->AddAttr(QN_NAME, codec.name);
    750   if (codec.clockrate > 0)
    751     AddXmlAttr(payload_type, QN_CLOCKRATE, codec.clockrate);
    752   if (codec.bitrate > 0)
    753     AddXmlAttr(payload_type, QN_BITRATE, codec.bitrate);
    754   if (codec.channels > 1)
    755     AddXmlAttr(payload_type, QN_CHANNELS, codec.channels);
    756   return payload_type;
    757 }
    758 
    759 buzz::XmlElement* CreateGingleVideoCodecElem(const VideoCodec& codec) {
    760   buzz::XmlElement* payload_type =
    761       new buzz::XmlElement(QN_GINGLE_VIDEO_PAYLOADTYPE, true);
    762   AddXmlAttr(payload_type, QN_ID, codec.id);
    763   payload_type->AddAttr(QN_NAME, codec.name);
    764   AddXmlAttr(payload_type, QN_WIDTH, codec.width);
    765   AddXmlAttr(payload_type, QN_HEIGHT, codec.height);
    766   AddXmlAttr(payload_type, QN_FRAMERATE, codec.framerate);
    767   return payload_type;
    768 }
    769 
    770 buzz::XmlElement* CreateGingleSsrcElem(const buzz::QName& name, uint32 ssrc) {
    771   buzz::XmlElement* elem = new buzz::XmlElement(name, true);
    772   if (ssrc) {
    773     SetXmlBody(elem, ssrc);
    774   }
    775   return elem;
    776 }
    777 
    778 buzz::XmlElement* CreateBandwidthElem(const buzz::QName& name, int bps) {
    779   int kbps = bps / 1000;
    780   buzz::XmlElement* elem = new buzz::XmlElement(name);
    781   elem->AddAttr(buzz::QN_TYPE, "AS");
    782   SetXmlBody(elem, kbps);
    783   return elem;
    784 }
    785 
    786 // For Jingle, usage_qname is empty.
    787 buzz::XmlElement* CreateJingleEncryptionElem(const CryptoParamsVec& cryptos,
    788                                              bool required) {
    789   buzz::XmlElement* encryption_elem = new buzz::XmlElement(QN_ENCRYPTION);
    790 
    791   if (required) {
    792     encryption_elem->SetAttr(QN_ENCRYPTION_REQUIRED, "true");
    793   }
    794 
    795   for (CryptoParamsVec::const_iterator i = cryptos.begin();
    796        i != cryptos.end();
    797        ++i) {
    798     buzz::XmlElement* crypto_elem = new buzz::XmlElement(QN_CRYPTO);
    799 
    800     AddXmlAttr(crypto_elem, QN_CRYPTO_TAG, i->tag);
    801     crypto_elem->AddAttr(QN_CRYPTO_SUITE, i->cipher_suite);
    802     crypto_elem->AddAttr(QN_CRYPTO_KEY_PARAMS, i->key_params);
    803     if (!i->session_params.empty()) {
    804       crypto_elem->AddAttr(QN_CRYPTO_SESSION_PARAMS, i->session_params);
    805     }
    806     encryption_elem->AddElement(crypto_elem);
    807   }
    808   return encryption_elem;
    809 }
    810 
    811 buzz::XmlElement* CreateGingleEncryptionElem(const CryptoParamsVec& cryptos,
    812                                              const buzz::QName& usage_qname,
    813                                              bool required) {
    814   buzz::XmlElement* encryption_elem =
    815       CreateJingleEncryptionElem(cryptos, required);
    816 
    817   if (required) {
    818     encryption_elem->SetAttr(QN_ENCRYPTION_REQUIRED, "true");
    819   }
    820 
    821   buzz::XmlElement* usage_elem = new buzz::XmlElement(usage_qname);
    822   encryption_elem->AddElement(usage_elem);
    823 
    824   return encryption_elem;
    825 }
    826 
    827 buzz::XmlElement* CreateGingleAudioContentElem(
    828     const AudioContentDescription* audio,
    829     bool crypto_required) {
    830   buzz::XmlElement* elem =
    831       new buzz::XmlElement(QN_GINGLE_AUDIO_CONTENT, true);
    832 
    833   for (AudioCodecs::const_iterator codec = audio->codecs().begin();
    834        codec != audio->codecs().end(); ++codec) {
    835     elem->AddElement(CreateGingleAudioCodecElem(*codec));
    836   }
    837   if (audio->has_ssrcs()) {
    838     elem->AddElement(CreateGingleSsrcElem(
    839         QN_GINGLE_AUDIO_SRCID, audio->first_ssrc()));
    840   }
    841 
    842   const CryptoParamsVec& cryptos = audio->cryptos();
    843   if (!cryptos.empty()) {
    844     elem->AddElement(CreateGingleEncryptionElem(cryptos,
    845                                                 QN_GINGLE_AUDIO_CRYPTO_USAGE,
    846                                                 crypto_required));
    847   }
    848   return elem;
    849 }
    850 
    851 buzz::XmlElement* CreateGingleVideoContentElem(
    852     const VideoContentDescription* video,
    853     bool crypto_required) {
    854   buzz::XmlElement* elem =
    855       new buzz::XmlElement(QN_GINGLE_VIDEO_CONTENT, true);
    856 
    857   for (VideoCodecs::const_iterator codec = video->codecs().begin();
    858        codec != video->codecs().end(); ++codec) {
    859     elem->AddElement(CreateGingleVideoCodecElem(*codec));
    860   }
    861   if (video->has_ssrcs()) {
    862     elem->AddElement(CreateGingleSsrcElem(
    863         QN_GINGLE_VIDEO_SRCID, video->first_ssrc()));
    864   }
    865   if (video->bandwidth() != kAutoBandwidth) {
    866     elem->AddElement(CreateBandwidthElem(QN_GINGLE_VIDEO_BANDWIDTH,
    867                                          video->bandwidth()));
    868   }
    869 
    870   const CryptoParamsVec& cryptos = video->cryptos();
    871   if (!cryptos.empty()) {
    872     elem->AddElement(CreateGingleEncryptionElem(cryptos,
    873                                                 QN_GINGLE_VIDEO_CRYPTO_USAGE,
    874                                                 crypto_required));
    875   }
    876 
    877   return elem;
    878 }
    879 
    880 template <class T>
    881 buzz::XmlElement* CreatePayloadTypeParameterElem(
    882     const std::string& name, T value) {
    883   buzz::XmlElement* elem = new buzz::XmlElement(QN_PARAMETER);
    884 
    885   elem->AddAttr(QN_PAYLOADTYPE_PARAMETER_NAME, name);
    886   AddXmlAttr(elem, QN_PAYLOADTYPE_PARAMETER_VALUE, value);
    887 
    888   return elem;
    889 }
    890 
    891 void AddRtcpFeedbackElem(buzz::XmlElement* elem,
    892                       const FeedbackParams& feedback_params) {
    893   std::vector<FeedbackParam>::const_iterator it;
    894   for (it = feedback_params.params().begin();
    895        it != feedback_params.params().end(); ++it) {
    896     buzz::XmlElement* fb_elem = new buzz::XmlElement(QN_JINGLE_RTCP_FB);
    897     fb_elem->AddAttr(QN_TYPE, it->id());
    898     fb_elem->AddAttr(QN_SUBTYPE, it->param());
    899     elem->AddElement(fb_elem);
    900   }
    901 }
    902 
    903 buzz::XmlElement* CreateJingleAudioCodecElem(const AudioCodec& codec) {
    904   buzz::XmlElement* elem = new buzz::XmlElement(QN_JINGLE_RTP_PAYLOADTYPE);
    905 
    906   AddXmlAttr(elem, QN_ID, codec.id);
    907   elem->AddAttr(QN_NAME, codec.name);
    908   if (codec.clockrate > 0) {
    909     AddXmlAttr(elem, QN_CLOCKRATE, codec.clockrate);
    910   }
    911   if (codec.bitrate > 0) {
    912     elem->AddElement(CreatePayloadTypeParameterElem(
    913         PAYLOADTYPE_PARAMETER_BITRATE, codec.bitrate));
    914   }
    915   if (codec.channels > 1) {
    916     AddXmlAttr(elem, QN_CHANNELS, codec.channels);
    917   }
    918 
    919   AddRtcpFeedbackElem(elem, codec.feedback_params);
    920 
    921   return elem;
    922 }
    923 
    924 buzz::XmlElement* CreateJingleVideoCodecElem(const VideoCodec& codec) {
    925   buzz::XmlElement* elem = new buzz::XmlElement(QN_JINGLE_RTP_PAYLOADTYPE);
    926 
    927   AddXmlAttr(elem, QN_ID, codec.id);
    928   elem->AddAttr(QN_NAME, codec.name);
    929   elem->AddElement(CreatePayloadTypeParameterElem(
    930       PAYLOADTYPE_PARAMETER_WIDTH, codec.width));
    931   elem->AddElement(CreatePayloadTypeParameterElem(
    932       PAYLOADTYPE_PARAMETER_HEIGHT, codec.height));
    933   elem->AddElement(CreatePayloadTypeParameterElem(
    934       PAYLOADTYPE_PARAMETER_FRAMERATE, codec.framerate));
    935 
    936   AddRtcpFeedbackElem(elem, codec.feedback_params);
    937 
    938   CodecParameterMap::const_iterator param_iter;
    939   for (param_iter = codec.params.begin(); param_iter != codec.params.end();
    940        ++param_iter) {
    941     elem->AddElement(CreatePayloadTypeParameterElem(param_iter->first,
    942                                                     param_iter->second));
    943   }
    944 
    945   return elem;
    946 }
    947 
    948 buzz::XmlElement* CreateJingleDataCodecElem(const DataCodec& codec) {
    949   buzz::XmlElement* elem = new buzz::XmlElement(QN_JINGLE_RTP_PAYLOADTYPE);
    950 
    951   AddXmlAttr(elem, QN_ID, codec.id);
    952   elem->AddAttr(QN_NAME, codec.name);
    953 
    954   AddRtcpFeedbackElem(elem, codec.feedback_params);
    955 
    956   return elem;
    957 }
    958 
    959 void WriteLegacyJingleSsrc(const MediaContentDescription* media,
    960                            buzz::XmlElement* elem) {
    961   if (media->has_ssrcs()) {
    962     AddXmlAttr(elem, QN_SSRC, media->first_ssrc());
    963   }
    964 }
    965 
    966 void WriteJingleStreamsOrLegacySsrc(const MediaContentDescription* media,
    967                                     buzz::XmlElement* desc_elem) {
    968   if (!media->multistream()) {
    969     WriteLegacyJingleSsrc(media, desc_elem);
    970   } else {
    971     WriteJingleStreams(media->streams(), desc_elem);
    972   }
    973 }
    974 
    975 buzz::XmlElement* CreateJingleAudioContentElem(
    976     const AudioContentDescription* audio, bool crypto_required) {
    977   buzz::XmlElement* elem =
    978       new buzz::XmlElement(QN_JINGLE_RTP_CONTENT, true);
    979 
    980   elem->SetAttr(QN_JINGLE_CONTENT_MEDIA, JINGLE_CONTENT_MEDIA_AUDIO);
    981   WriteJingleStreamsOrLegacySsrc(audio, elem);
    982 
    983   for (AudioCodecs::const_iterator codec = audio->codecs().begin();
    984        codec != audio->codecs().end(); ++codec) {
    985     elem->AddElement(CreateJingleAudioCodecElem(*codec));
    986   }
    987 
    988   const CryptoParamsVec& cryptos = audio->cryptos();
    989   if (!cryptos.empty()) {
    990     elem->AddElement(CreateJingleEncryptionElem(cryptos, crypto_required));
    991   }
    992 
    993   if (audio->rtcp_mux()) {
    994     elem->AddElement(new buzz::XmlElement(QN_JINGLE_RTCP_MUX));
    995   }
    996 
    997   WriteJingleRtpHeaderExtensions(audio->rtp_header_extensions(), elem);
    998 
    999   return elem;
   1000 }
   1001 
   1002 buzz::XmlElement* CreateJingleVideoContentElem(
   1003     const VideoContentDescription* video, bool crypto_required) {
   1004   buzz::XmlElement* elem =
   1005       new buzz::XmlElement(QN_JINGLE_RTP_CONTENT, true);
   1006 
   1007   elem->SetAttr(QN_JINGLE_CONTENT_MEDIA, JINGLE_CONTENT_MEDIA_VIDEO);
   1008   WriteJingleStreamsOrLegacySsrc(video, elem);
   1009 
   1010   for (VideoCodecs::const_iterator codec = video->codecs().begin();
   1011        codec != video->codecs().end(); ++codec) {
   1012     elem->AddElement(CreateJingleVideoCodecElem(*codec));
   1013   }
   1014 
   1015   const CryptoParamsVec& cryptos = video->cryptos();
   1016   if (!cryptos.empty()) {
   1017     elem->AddElement(CreateJingleEncryptionElem(cryptos, crypto_required));
   1018   }
   1019 
   1020   if (video->rtcp_mux()) {
   1021     elem->AddElement(new buzz::XmlElement(QN_JINGLE_RTCP_MUX));
   1022   }
   1023 
   1024   if (video->bandwidth() != kAutoBandwidth) {
   1025     elem->AddElement(CreateBandwidthElem(QN_JINGLE_RTP_BANDWIDTH,
   1026                                          video->bandwidth()));
   1027   }
   1028 
   1029   WriteJingleRtpHeaderExtensions(video->rtp_header_extensions(), elem);
   1030 
   1031   return elem;
   1032 }
   1033 
   1034 buzz::XmlElement* CreateJingleSctpDataContentElem(
   1035     const DataContentDescription* data) {
   1036   buzz::XmlElement* content_elem =
   1037       new buzz::XmlElement(QN_JINGLE_DRAFT_SCTP_CONTENT, true);
   1038   for (std::vector<StreamParams>::const_iterator
   1039            stream = data->streams().begin();
   1040        stream != data->streams().end(); ++stream) {
   1041     buzz::XmlElement* stream_elem =
   1042       new buzz::XmlElement(QN_JINGLE_DRAFT_SCTP_STREAM, false);
   1043     AddXmlAttrIfNonEmpty(stream_elem, QN_NICK, stream->groupid);
   1044     AddXmlAttrIfNonEmpty(stream_elem, QN_NAME, stream->id);
   1045     if (!stream->ssrcs.empty()) {
   1046       AddXmlAttr(stream_elem, QN_SID, stream->ssrcs[0]);
   1047     }
   1048     content_elem->AddElement(stream_elem);
   1049   }
   1050   return content_elem;;
   1051 }
   1052 
   1053 buzz::XmlElement* CreateJingleRtpDataContentElem(
   1054     const DataContentDescription* data, bool crypto_required) {
   1055 
   1056   buzz::XmlElement* elem =
   1057       new buzz::XmlElement(QN_JINGLE_RTP_CONTENT, true);
   1058 
   1059   elem->SetAttr(QN_JINGLE_CONTENT_MEDIA, JINGLE_CONTENT_MEDIA_DATA);
   1060   WriteJingleStreamsOrLegacySsrc(data, elem);
   1061 
   1062   for (DataCodecs::const_iterator codec = data->codecs().begin();
   1063        codec != data->codecs().end(); ++codec) {
   1064     elem->AddElement(CreateJingleDataCodecElem(*codec));
   1065   }
   1066 
   1067   const CryptoParamsVec& cryptos = data->cryptos();
   1068   if (!cryptos.empty()) {
   1069     elem->AddElement(CreateJingleEncryptionElem(cryptos, crypto_required));
   1070   }
   1071 
   1072   if (data->rtcp_mux()) {
   1073     elem->AddElement(new buzz::XmlElement(QN_JINGLE_RTCP_MUX));
   1074   }
   1075 
   1076   if (data->bandwidth() != kAutoBandwidth) {
   1077     elem->AddElement(CreateBandwidthElem(QN_JINGLE_RTP_BANDWIDTH,
   1078                                          data->bandwidth()));
   1079   }
   1080 
   1081   return elem;
   1082 }
   1083 
   1084 bool IsSctp(const DataContentDescription* data) {
   1085   return (data->protocol() == kMediaProtocolSctp ||
   1086     data->protocol() == kMediaProtocolDtlsSctp);
   1087 }
   1088 
   1089 buzz::XmlElement* CreateJingleDataContentElem(
   1090     const DataContentDescription* data, bool crypto_required) {
   1091   if (IsSctp(data)) {
   1092     return CreateJingleSctpDataContentElem(data);
   1093   } else {
   1094     return CreateJingleRtpDataContentElem(data, crypto_required);
   1095   }
   1096 }
   1097 
   1098 bool MediaSessionClient::IsWritable(SignalingProtocol protocol,
   1099                                     const ContentDescription* content) {
   1100   const MediaContentDescription* media =
   1101       static_cast<const MediaContentDescription*>(content);
   1102   if (protocol == PROTOCOL_GINGLE &&
   1103       media->type() == MEDIA_TYPE_DATA) {
   1104     return false;
   1105   }
   1106   return true;
   1107 }
   1108 
   1109 bool MediaSessionClient::WriteContent(SignalingProtocol protocol,
   1110                                       const ContentDescription* content,
   1111                                       buzz::XmlElement** elem,
   1112                                       WriteError* error) {
   1113   const MediaContentDescription* media =
   1114       static_cast<const MediaContentDescription*>(content);
   1115   bool crypto_required = secure() == SEC_REQUIRED;
   1116 
   1117   if (media->type() == MEDIA_TYPE_AUDIO) {
   1118     const AudioContentDescription* audio =
   1119         static_cast<const AudioContentDescription*>(media);
   1120     if (protocol == PROTOCOL_GINGLE) {
   1121       *elem = CreateGingleAudioContentElem(audio, crypto_required);
   1122     } else {
   1123       *elem = CreateJingleAudioContentElem(audio, crypto_required);
   1124     }
   1125   } else if (media->type() == MEDIA_TYPE_VIDEO) {
   1126     const VideoContentDescription* video =
   1127         static_cast<const VideoContentDescription*>(media);
   1128     if (protocol == PROTOCOL_GINGLE) {
   1129       *elem = CreateGingleVideoContentElem(video, crypto_required);
   1130     } else {
   1131       *elem = CreateJingleVideoContentElem(video, crypto_required);
   1132     }
   1133   } else if (media->type() == MEDIA_TYPE_DATA) {
   1134     const DataContentDescription* data =
   1135         static_cast<const DataContentDescription*>(media);
   1136     if (protocol == PROTOCOL_GINGLE) {
   1137       return BadWrite("Data channel not supported with Gingle.", error);
   1138     } else {
   1139       *elem = CreateJingleDataContentElem(data, crypto_required);
   1140     }
   1141   } else {
   1142     return BadWrite("Unknown content type: " +
   1143                     talk_base::ToString<int>(media->type()), error);
   1144   }
   1145 
   1146   return true;
   1147 }
   1148 
   1149 }  // namespace cricket
   1150