sdp.cpp 22 KB
Newer Older
1
/*
2
 *  Copyright (C) 2004, 2005, 2006, 2008, 2009, 2010, 2011 Savoir-Faire Linux Inc.
3
4
 *
 *  Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com>
5
 *  Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com>
6
7
8
9
10
11
12
13
14
15
16
17
18
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
Tristan Matthews's avatar
Tristan Matthews committed
19
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20
21
22
23
24
25
26
27
28
29
30
 *
 *  Additional permission under GNU GPL version 3 section 7:
 *
 *  If you modify this program, or any covered work, by linking or
 *  combining it with the OpenSSL project's OpenSSL library (or a
 *  modified version of that library), containing parts covered by the
 *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
 *  grants you additional permission to convey the resulting work.
 *  Corresponding Source for a non-source form of such a combination
 *  shall include the source code for the parts of OpenSSL used as well
 *  as that of the covered work.
31
32
 */

33
34
35
36
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

37
#include "sdp.h"
38
#include "logger.h"
39
#include "manager.h"
40

41
#include <algorithm>
42

43
44
45
46
#ifdef SFL_VIDEO
#include "video/libav_utils.h"
#endif

47
using std::string;
48
using std::map;
49
50
51
using std::vector;
using std::stringstream;

52
Sdp::Sdp(pj_pool_t *pool)
53
    : memPool_(pool)
54
    , negotiator_(NULL)
55
    , localSession_(NULL)
56
    , remoteSession_(NULL)
57
58
    , activeLocalSession_(NULL)
    , activeRemoteSession_(NULL)
59
60
    , audio_codec_list_()
    , video_codec_list_()
61
    , sessionAudioMedia_()
62
    , sessionVideoMedia_()
63
64
    , localIpAddr_()
    , remoteIpAddr_()
65
    , localAudioPort_(0)
66
    , localVideoPort_(0)
67
    , remoteAudioPort_(0)
68
    , remoteVideoPort_(0)
69
    , zrtpHelloHash_()
70
    , srtpCrypto_()
71
    , telephoneEventPayload_(101) // same as asterisk
72
{}
73

74
void Sdp::setActiveLocalSdpSession(const pjmedia_sdp_session *sdp)
75
{
76
    activeLocalSession_ = (pjmedia_sdp_session*) sdp;
77

Rafaël Carré's avatar
Rafaël Carré committed
78
    if (activeLocalSession_->media_count < 1)
79
        return;
80

81
82
    for (unsigned media = 0; media < activeLocalSession_->media_count; ++media) {
        pjmedia_sdp_media *current = activeLocalSession_->media[media];
83

84
85
86
        for (unsigned fmt = 0; fmt < current->desc.fmt_count; ++fmt) {
            static const pj_str_t STR_RTPMAP = { (char*) "rtpmap", 6 };
            pjmedia_sdp_attr *attribute = pjmedia_sdp_media_find_attr(current, &STR_RTPMAP, NULL);
87

88
89
90
            if (!attribute) {
                ERROR("Could not find rtpmap attribute");
                break;
91
            }
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108

            pjmedia_sdp_rtpmap *rtpmap;
            pjmedia_sdp_attr_to_rtpmap(memPool_, attribute, &rtpmap);

            string type(current->desc.media.ptr, current->desc.media.slen);
            if (type == "audio") {
                const int pt = pj_strtoul(&rtpmap->pt);
                sfl::Codec *codec = Manager::instance().audioCodecFactory.getCodec(pt);
                if (codec)
                    sessionAudioMedia_.push_back(codec);
                else {
                    DEBUG("Could not get codec for payload type %lu", pt);
                    break;
                }
            } else if (type == "video")
                sessionVideoMedia_.push_back(string(rtpmap->enc_name.ptr, rtpmap->enc_name.slen));
        }
109
    }
110
111
}

Rafaël Carré's avatar
Rafaël Carré committed
112
113


114
void Sdp::setActiveRemoteSdpSession(const pjmedia_sdp_session *sdp)
115
{
116
    activeRemoteSession_ = (pjmedia_sdp_session*) sdp;
117

118
    if (!sdp) {
Tristan Matthews's avatar
Tristan Matthews committed
119
        ERROR("Remote sdp is NULL while parsing telephone event attribute");
Rafaël Carré's avatar
Rafaël Carré committed
120
121
122
        return;
    }

123
    for (unsigned i = 0; i < sdp->media_count; i++) {
124
        if (pj_stricmp2(&sdp->media[i]->desc.media, "audio") == 0) {
Rafaël Carré's avatar
Rafaël Carré committed
125
126
127
            pjmedia_sdp_media *r_media = sdp->media[i];
            static const pj_str_t STR_TELEPHONE_EVENT = { (char*) "telephone-event", 15};
            pjmedia_sdp_attr *attribute = pjmedia_sdp_attr_find(r_media->attr_count, r_media->attr, &STR_TELEPHONE_EVENT, NULL);
128

Rafaël Carré's avatar
Rafaël Carré committed
129
130
            if (attribute != NULL) {
                pjmedia_sdp_rtpmap *rtpmap;
131
132
                pjmedia_sdp_attr_to_rtpmap(memPool_, attribute, &rtpmap);
                telephoneEventPayload_ = pj_strtoul(&rtpmap->pt);
Rafaël Carré's avatar
Rafaël Carré committed
133
            }
134

Rafaël Carré's avatar
Rafaël Carré committed
135
136
            return;
        }
137
    }
Rafaël Carré's avatar
Rafaël Carré committed
138

Tristan Matthews's avatar
Tristan Matthews committed
139
    ERROR("Could not found dtmf event from remote sdp");
140
141
}

142
string Sdp::getSessionVideoCodec() const
143
{
144
145
    if (sessionVideoMedia_.empty()) {
        DEBUG("Session video media is empty");
Tristan Matthews's avatar
Tristan Matthews committed
146
        return "";
147
    }
148
    return sessionVideoMedia_[0];
149
150
}

151
string Sdp::getAudioCodecName() const
152
{
153
    try {
Tristan Matthews's avatar
Tristan Matthews committed
154
        sfl::AudioCodec *codec = getSessionAudioMedia();
155
156
157
158
        return codec ? codec->getMimeSubtype() : "";
    } catch (...) {
        return "";
    }
159
160
}

161
sfl::AudioCodec* Sdp::getSessionAudioMedia() const
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
162
{
163
    if (sessionAudioMedia_.empty())
164
        throw SdpException("No codec description for this media");
165

166
    return dynamic_cast<sfl::AudioCodec *>(sessionAudioMedia_[0]);
167
168
}

Tristan Matthews's avatar
Tristan Matthews committed
169

170
pjmedia_sdp_media *Sdp::setMediaDescriptorLine(bool audio)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
171
{
172
    pjmedia_sdp_media *med = PJ_POOL_ZALLOC_T(memPool_, pjmedia_sdp_media);
173

174
    med->desc.media = audio ? pj_str((char*) "audio") : pj_str((char*) "video");
175
    med->desc.port_count = 1;
176
    med->desc.port = audio ? localAudioPort_ : localVideoPort_;
177
    // in case of sdes, media are tagged as "RTP/SAVP", RTP/AVP elsewhere
178
    med->desc.transport = pj_str(srtpCrypto_.empty() ? (char*) "RTP/AVP" : (char*) "RTP/SAVP");
179

180
    int dynamic_payload = 96;
181

182
    med->desc.fmt_count = audio ? audio_codec_list_.size() : video_codec_list_.size();
183
    for (unsigned i = 0; i < med->desc.fmt_count; ++i) {
Tristan Matthews's avatar
Tristan Matthews committed
184
        unsigned clock_rate;
185
        string enc_name;
Tristan Matthews's avatar
Tristan Matthews committed
186
187
188
189
190
191
192
193
194
195
196
        int payload;

        if (audio) {
            sfl::Codec *codec = audio_codec_list_[i];
            payload = codec->getPayloadType();
            enc_name = codec->getMimeSubtype();
            clock_rate = codec->getClockRate();
            // G722 require G722/8000 media description even if it is 16000 codec
            if (codec->getPayloadType () == 9)
                clock_rate = 8000;
        } else {
197
198
            // FIXME: get this key from header
            enc_name = video_codec_list_[i]["name"];
Tristan Matthews's avatar
Tristan Matthews committed
199
            clock_rate = 90000;
200
            payload = dynamic_payload;
Tristan Matthews's avatar
Tristan Matthews committed
201
        }
202

Tristan Matthews's avatar
Tristan Matthews committed
203
204
        std::ostringstream s;
        s << payload;
205
        pj_strdup2(memPool_, &med->desc.fmt[i], s.str().c_str());
206
207
208
209
210

        // Add a rtpmap field for each codec
        // We could add one only for dynamic payloads because the codecs with static RTP payloads
        // are entirely defined in the RFC 3351, but if we want to add other attributes like an asymmetric
        // connection, the rtpmap attribute will be useful to specify for which codec it is applicable
211
        pjmedia_sdp_rtpmap rtpmap;
212

213
        rtpmap.pt = med->desc.fmt[i];
214
        rtpmap.enc_name = pj_str((char*) enc_name.c_str());
215
        rtpmap.clock_rate = clock_rate;
Rafaël Carré's avatar
Rafaël Carré committed
216
        rtpmap.param.ptr = ((char* const)"");
217
        rtpmap.param.slen = 0;
218

Rafaël Carré's avatar
Rafaël Carré committed
219
        pjmedia_sdp_attr *attr;
220
        pjmedia_sdp_rtpmap_to_attr(memPool_, &rtpmap, &attr);
221

222
        med->attr[med->attr_count++] = attr;
223
224
225
226
227
228
229
230
231
        if (enc_name == "H264") {
            std::ostringstream os;
            // FIXME: this should not be hardcoded, it will determine what profile and level
            // our peer will send us
            os << "fmtp:" << dynamic_payload << " profile-level-id=428014";
            med->attr[med->attr_count++] = pjmedia_sdp_attr_create(memPool_, os.str().c_str(), NULL);
        }
        if (not audio)
            dynamic_payload++;
232
    }
233

234
    med->attr[med->attr_count++] = pjmedia_sdp_attr_create(memPool_, "sendrecv", NULL);
235
    if (!zrtpHelloHash_.empty())
236
        addZrtpAttribute(med, zrtpHelloHash_);
237

238
239
    if (audio)
        setTelephoneEventRtpmap(med);
240

Rafaël Carré's avatar
Rafaël Carré committed
241
    return med;
242
}
243

244
245
void Sdp::setTelephoneEventRtpmap(pjmedia_sdp_media *med)
{
Rafaël Carré's avatar
Rafaël Carré committed
246
    pjmedia_sdp_attr *attr_rtpmap = static_cast<pjmedia_sdp_attr *>(pj_pool_zalloc(memPool_, sizeof(pjmedia_sdp_attr)));
247
248
    attr_rtpmap->name = pj_str((char *) "rtpmap");
    attr_rtpmap->value = pj_str((char *) "101 telephone-event/8000");
249
250
251

    med->attr[med->attr_count++] = attr_rtpmap;

Rafaël Carré's avatar
Rafaël Carré committed
252
    pjmedia_sdp_attr *attr_fmtp = static_cast<pjmedia_sdp_attr *>(pj_pool_zalloc(memPool_, sizeof(pjmedia_sdp_attr)));
253
254
    attr_fmtp->name = pj_str((char *) "fmtp");
    attr_fmtp->value = pj_str((char *) "101 0-15");
255
256
257
258

    med->attr[med->attr_count++] = attr_fmtp;
}

259
void Sdp::setLocalMediaVideoCapabilities(const vector<map<string, string> > &codecs)
260
{
261
262
    video_codec_list_.clear();
#ifdef SFL_VIDEO
263
264
265
266
    if (codecs.empty())
        WARN("No selected video codec while building local SDP offer");
    else
        video_codec_list_ = codecs;
267
#else
268
    (void) codecs;
269
#endif
270
}
271

272
void Sdp::setLocalMediaAudioCapabilities(const vector<int> &selectedCodecs)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
273
{
274
    if (selectedCodecs.empty())
275
        WARN("No selected codec while building local SDP offer");
276

277
    audio_codec_list_.clear();
278
    for (vector<int>::const_iterator i = selectedCodecs.begin(); i != selectedCodecs.end(); ++i) {
Tristan Matthews's avatar
Tristan Matthews committed
279
        sfl::Codec *codec = Manager::instance().audioCodecFactory.getCodec(*i);
280
281

        if (codec)
Tristan Matthews's avatar
Tristan Matthews committed
282
            audio_codec_list_.push_back(codec);
283
        else
284
            WARN("Couldn't find audio codec");
285
    }
286
287
}

Tristan Matthews's avatar
Tristan Matthews committed
288
289
290
291
292
namespace {
    void printSession(const pjmedia_sdp_session *session)
    {
        char buffer[2048];
        size_t size = pjmedia_sdp_print(session, buffer, sizeof(buffer));
293
        string sessionStr(buffer, std::min(size, sizeof(buffer)));
Tristan Matthews's avatar
Tristan Matthews committed
294
295
296
297
        DEBUG("%s", sessionStr.c_str());
    }
}

298
int Sdp::createLocalSession(const vector<int> &selectedAudioCodecs, const vector<map<string, string> > &selectedVideoCodecs)
299
{
300
301
    setLocalMediaAudioCapabilities(selectedAudioCodecs);
    setLocalMediaVideoCapabilities(selectedVideoCodecs);
302

Rafaël Carré's avatar
Rafaël Carré committed
303
304
    localSession_ = PJ_POOL_ZALLOC_T(memPool_, pjmedia_sdp_session);
    localSession_->conn = PJ_POOL_ZALLOC_T(memPool_, pjmedia_sdp_conn);
305

306
    /* Initialize the fields of the struct */
Rafaël Carré's avatar
Rafaël Carré committed
307
308
    localSession_->origin.version = 0;
    pj_time_val tv;
309
    pj_gettimeofday(&tv);
310

311
    localSession_->origin.user = pj_str(pj_gethostname()->ptr);
Rafaël Carré's avatar
Rafaël Carré committed
312
313
    // Use Network Time Protocol format timestamp to ensure uniqueness.
    localSession_->origin.id = tv.sec + 2208988800UL;
314
315
316
    localSession_->origin.net_type = pj_str((char*) "IN");
    localSession_->origin.addr_type = pj_str((char*) "IP4");
    localSession_->origin.addr = pj_str((char*) localIpAddr_.c_str());
317

318
    localSession_->name = pj_str((char*) PACKAGE);
319

Rafaël Carré's avatar
Rafaël Carré committed
320
321
322
323
324
325
326
327
328
329
330
    localSession_->conn->net_type = localSession_->origin.net_type;
    localSession_->conn->addr_type = localSession_->origin.addr_type;
    localSession_->conn->addr = localSession_->origin.addr;

    // RFC 3264: An offer/answer model session description protocol
    // As the session is created and destroyed through an external signaling mean (SIP), the line
    // should have a value of "0 0".
    localSession_->time.start = 0;
    localSession_->time.stop = 0;

    // For DTMF RTP events
331
    const bool audio = true;
332
    localSession_->media_count = 1;
333
    localSession_->media[0] = setMediaDescriptorLine(audio);
334
335
336
337
    if (not selectedVideoCodecs.empty()) {
        localSession_->media[1] = setMediaDescriptorLine(!audio);
        ++localSession_->media_count;
    }
338

339
    if (!srtpCrypto_.empty())
340
        addSdesAttribute(srtpCrypto_);
341

Tristan Matthews's avatar
Tristan Matthews committed
342
343
    DEBUG("SDP: Local SDP Session:");
    printSession(localSession_);
344

345
    return pjmedia_sdp_validate(localSession_);
346
}
347

348
void Sdp::createOffer(const vector<int> &selectedCodecs, const vector<map<string, string> > &videoCodecs)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
349
{
350
    if (createLocalSession(selectedCodecs, videoCodecs) != PJ_SUCCESS)
351
352
353
        ERROR("Failed to create initial offer");
    else if (pjmedia_sdp_neg_create_w_local_offer(memPool_, localSession_, &negotiator_) != PJ_SUCCESS)
        ERROR("Failed to create an initial SDP negotiator");
354
355
}

356
void Sdp::receiveOffer(const pjmedia_sdp_session* remote,
357
                       const vector<int> &selectedCodecs,
358
                       const vector<map<string, string> > &videoCodecs)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
359
{
360
    if (!remote) {
361
        ERROR("Remote session is NULL");
362
363
        return;
    }
Emmanuel Milou's avatar
Emmanuel Milou committed
364

365
    DEBUG("Remote SDP Session:");
Tristan Matthews's avatar
Tristan Matthews committed
366
    printSession(remote);
367

368
    if (!localSession_ and createLocalSession(selectedCodecs, videoCodecs) != PJ_SUCCESS) {
369
        ERROR("Failed to create initial offer");
Tristan Matthews's avatar
Tristan Matthews committed
370
371
        return;
    }
372
373
374

    remoteSession_ = pjmedia_sdp_session_clone(memPool_, remote);

375
376
    pjmedia_sdp_neg_create_w_remote_offer(memPool_, localSession_,
                                          remoteSession_, &negotiator_);
377
}
378

Rafaël Carré's avatar
Rafaël Carré committed
379
void Sdp::startNegotiation()
380
{
381
    if (negotiator_ == NULL) {
Tristan Matthews's avatar
Tristan Matthews committed
382
        ERROR("Can't start negotiation with invalid negotiator");
383
384
385
        return;
    }

386
387
    const pjmedia_sdp_session *active_local;
    const pjmedia_sdp_session *active_remote;
Emmanuel Milou's avatar
Emmanuel Milou committed
388

389
    if (pjmedia_sdp_neg_get_state(negotiator_) != PJMEDIA_SDP_NEG_STATE_WAIT_NEGO)
Tristan Matthews's avatar
Tristan Matthews committed
390
        WARN("Negotiator not in right state for negotiation");
Emmanuel Milou's avatar
Emmanuel Milou committed
391

392
    if (pjmedia_sdp_neg_negotiate(memPool_, negotiator_, 0) != PJ_SUCCESS)
Rafaël Carré's avatar
Rafaël Carré committed
393
        return;
394

Rafaël Carré's avatar
Rafaël Carré committed
395
    if (pjmedia_sdp_neg_get_active_local(negotiator_, &active_local) != PJ_SUCCESS)
Tristan Matthews's avatar
Tristan Matthews committed
396
        ERROR("Could not retrieve local active session");
397
398
    else
        setActiveLocalSdpSession(active_local);
399

Rafaël Carré's avatar
Rafaël Carré committed
400
    if (pjmedia_sdp_neg_get_active_remote(negotiator_, &active_remote) != PJ_SUCCESS)
Tristan Matthews's avatar
Tristan Matthews committed
401
        ERROR("Could not retrieve remote active session");
402
403
    else
        setActiveRemoteSdpSession(active_remote);
404
}
405

406
namespace
Tristan Matthews's avatar
Tristan Matthews committed
407
{
Rafaël Carré's avatar
Rafaël Carré committed
408
    vector<string> split(const string &s, char delim)
409
    {
Rafaël Carré's avatar
Rafaël Carré committed
410
        vector<string> elems;
411
412
413
414
415
416
417
418
        stringstream ss(s);
        string item;
        while(getline(ss, item, delim))
            elems.push_back(item);
        return elems;
    }
} // end anonymous namespace

419
string Sdp::getLineFromSession(const pjmedia_sdp_session *sess, const string &keyword) const
420
{
Rafaël Carré's avatar
Rafaël Carré committed
421
    char buffer[2048];
422
    int size = pjmedia_sdp_print(sess, buffer, sizeof buffer);
423
424
425
    string sdp(buffer, size);
    const vector<string> tokens(split(sdp, '\n'));
    for (vector<string>::const_iterator iter = tokens.begin(); iter != tokens.end(); ++iter)
426
427
428
        if ((*iter).find(keyword) != string::npos)
            return *iter;
    return "";
429
}
Tristan Matthews's avatar
Tristan Matthews committed
430

431
string Sdp::getIncomingVideoDescription() const
432
{
433
    stringstream ss;
434
    ss << "v=0" << std::endl;
435
436
437
    ss << "o=- 0 0 IN IP4 " << localIpAddr_ << std::endl;
    ss << "s=sflphone" << std::endl;
    ss << "c=IN IP4 " << remoteIpAddr_ << std::endl;
438
    ss << "t=0 0" << std::endl;
439

440
    std::string videoLine(getLineFromSession(activeLocalSession_, "m=video"));
441
    ss << videoLine << std::endl;
442

443
444
445
    int payload_num;
    if (sscanf(videoLine.c_str(), "m=video %*d %*s %d", &payload_num) != 1)
        payload_num = 0;
446
447
448

    std::ostringstream s;
    s << "a=rtpmap:";
449
    s << payload_num;
450

451
    std::string vCodecLine(getLineFromSession(activeLocalSession_, s.str()));
452
    ss << vCodecLine << std::endl;
453

454
    unsigned videoIdx;
455
    for (videoIdx = 0; videoIdx < activeLocalSession_->media_count and pj_stricmp2(&activeLocalSession_->media[videoIdx]->desc.media, "video") != 0; ++videoIdx)
456
        ;
457
458

    // get direction string
Rafaël Carré's avatar
Rafaël Carré committed
459
    static const pj_str_t DIRECTIONS[] = {
Tristan Matthews's avatar
Tristan Matthews committed
460
461
462
463
464
        {(char*) "sendrecv", 8},
        {(char*) "sendonly", 8},
        {(char*) "recvonly", 8},
        {(char*) "inactive", 8},
        {NULL, 0}
Rafaël Carré's avatar
Rafaël Carré committed
465
    };
466
467
    pjmedia_sdp_attr *direction = NULL;

Rafaël Carré's avatar
Rafaël Carré committed
468
    const pj_str_t *guess = DIRECTIONS;
469
    while (!direction and guess->ptr)
Rafaël Carré's avatar
Rafaël Carré committed
470
        direction = pjmedia_sdp_media_find_attr(activeLocalSession_->media[videoIdx], guess++, NULL);
471

472
    if (direction)
Rafaël Carré's avatar
Rafaël Carré committed
473
        ss << "a=" + std::string(direction->name.ptr, direction->name.slen) << std::endl;
474

475
476
    return ss.str();
}
477

478
std::string Sdp::getOutgoingVideoCodec() const
479
480
{
    string str("a=rtpmap:");
481
    str += getOutgoingVideoPayload();
482
483
484
485
486
487
    string vCodecLine(getLineFromSession(activeRemoteSession_, str));
    char codec_buf[32];
    codec_buf[0] = '\0';
    sscanf(vCodecLine.c_str(), "a=rtpmap:%*d %31[^/]", codec_buf);
    return string(codec_buf);
}
488

489
490
491
492
493
494
495
496
497
498
namespace {
    vector<map<string, string> >::const_iterator
        findCodecInList(const vector<map<string, string> > &codecs, const string &codec)
        {
            for (vector<map<string, string> >::const_iterator i = codecs.begin(); i != codecs.end(); ++i) {
                map<string, string>::const_iterator name = i->find("name");
                if (name != i->end() and (codec == name->second))
                    return i;
            }
            return codecs.end();
499
        }
500
501
502
503
504
505
506
507
508
509
}

std::string
Sdp::getOutgoingVideoField(const std::string &codec, const char *key) const
{
    const vector<map<string, string> >::const_iterator i = findCodecInList(video_codec_list_, codec);
    if (i != video_codec_list_.end()) {
        map<string, string>::const_iterator field = i->find(key);
        if (field != i->end())
            return field->second;
510
    }
511
    return "";
512
}
513

514
515
std::string
Sdp::getOutgoingVideoPayload() const
516
517
518
519
520
521
522
523
{
    string videoLine(getLineFromSession(activeRemoteSession_, "m=video"));
    int payload_num;
    if (sscanf(videoLine.c_str(), "m=video %*d %*s %d", &payload_num) != 1)
        payload_num = 0;
    std::ostringstream os;
    os << payload_num;
    return os.str();
524
525
}

526
void Sdp::addSdesAttribute(const vector<std::string>& crypto)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
527
{
528
    for (vector<std::string>::const_iterator iter = crypto.begin();
529
            iter != crypto.end(); ++iter) {
530
        pj_str_t val = { (char*)(*iter).c_str(), static_cast<pj_ssize_t>((*iter).size()) };
Rafaël Carré's avatar
Rafaël Carré committed
531
        pjmedia_sdp_attr *attr = pjmedia_sdp_attr_create(memPool_, "crypto", &val);
532

Rafaël Carré's avatar
Rafaël Carré committed
533
        for (unsigned i = 0; i < localSession_->media_count; i++)
534
535
            if (pjmedia_sdp_media_add_attr(localSession_->media[i], attr) != PJ_SUCCESS)
                throw SdpException("Could not add sdes attribute to media");
536
    }
537
538
539
}


540
void Sdp::addZrtpAttribute(pjmedia_sdp_media* media, std::string hash)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
541
{
542
    /* Format: ":version value" */
543
    std::string val = "1.10 " + hash;
544
    pj_str_t value = { (char*)val.c_str(), static_cast<pj_ssize_t>(val.size()) };
Rafaël Carré's avatar
Rafaël Carré committed
545
    pjmedia_sdp_attr *attr = pjmedia_sdp_attr_create(memPool_, "zrtp-hash", &value);
546

547
548
    if (pjmedia_sdp_media_add_attr(media, attr) != PJ_SUCCESS)
        throw SdpException("Could not add zrtp attribute to media");
549
550
}

Tristan Matthews's avatar
Tristan Matthews committed
551
552
553
554
555
556
557
558
namespace {
    // Returns index of desired media attribute, or -1 if not found */
    int getIndexOfAttribute(const pjmedia_sdp_session * const session, const char * const type)
    {
        if (!session) {
            ERROR("Session is NULL when looking for \"%s\" attribute", type);
            return -1;
        }
559
        size_t i = 0;
Tristan Matthews's avatar
Tristan Matthews committed
560
561
562
563
564
565
566
567
568
569
        while (i < session->media_count and pj_stricmp2(&session->media[i]->desc.media, type) != 0)
            ++i;

        if (i == session->media_count)
            return -1;
        else
            return i;
    }
}

Rafaël Carré's avatar
Rafaël Carré committed
570
void Sdp::addAttributeToLocalAudioMedia(const char *attr)
571
{
Tristan Matthews's avatar
Tristan Matthews committed
572
573
    const int i = getIndexOfAttribute(localSession_, "audio");
    if (i == -1)
Tristan Matthews's avatar
Tristan Matthews committed
574
        return;
575
576
    pjmedia_sdp_attr *attribute = pjmedia_sdp_attr_create(memPool_, attr, NULL);
    pjmedia_sdp_media_add_attr(localSession_->media[i], attribute);
577
578
}

Rafaël Carré's avatar
Rafaël Carré committed
579
void Sdp::removeAttributeFromLocalAudioMedia(const char *attr)
580
{
Tristan Matthews's avatar
Tristan Matthews committed
581
582
    const int i = getIndexOfAttribute(localSession_, "audio");
    if (i == -1)
Tristan Matthews's avatar
Tristan Matthews committed
583
584
        return;
    pjmedia_sdp_media_remove_all_attr(localSession_->media[i], attr);
585
586
}

Rafaël Carré's avatar
Rafaël Carré committed
587
void Sdp::removeAttributeFromLocalVideoMedia(const char *attr)
588
{
Tristan Matthews's avatar
Tristan Matthews committed
589
590
591
    const int i = getIndexOfAttribute(localSession_, "video");
    if (i == -1)
        return;
592
    pjmedia_sdp_media_remove_all_attr(localSession_->media[i], attr);
593
594
}

Rafaël Carré's avatar
Rafaël Carré committed
595
void Sdp::addAttributeToLocalVideoMedia(const char *attr)
596
{
Tristan Matthews's avatar
Tristan Matthews committed
597
598
599
    const int i = getIndexOfAttribute(localSession_, "video");
    if (i == -1)
        return;
600
    pjmedia_sdp_attr *attribute = pjmedia_sdp_attr_create(memPool_, attr, NULL);
601
    pjmedia_sdp_media_add_attr(localSession_->media[i], attribute);
602
603
}

604
void Sdp::setMediaTransportInfoFromRemoteSdp()
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
605
{
Rafaël Carré's avatar
Rafaël Carré committed
606
    if (!activeRemoteSession_) {
Tristan Matthews's avatar
Tristan Matthews committed
607
        ERROR("Remote sdp is NULL while parsing media");
608
609
        return;
    }
610

Tristan Matthews's avatar
Tristan Matthews committed
611
    for (unsigned i = 0; i < activeRemoteSession_->media_count; ++i) {
612
        if (pj_stricmp2(&activeRemoteSession_->media[i]->desc.media, "audio") == 0) {
613
            remoteAudioPort_ = activeRemoteSession_->media[i]->desc.port;
614
            remoteIpAddr_ = std::string(activeRemoteSession_->conn->addr.ptr, activeRemoteSession_->conn->addr.slen);
615
        } else if (pj_stricmp2(&activeRemoteSession_->media[i]->desc.media, "video") == 0) {
616
            remoteVideoPort_ = activeRemoteSession_->media[i]->desc.port;
617
618
            remoteIpAddr_ = std::string(activeRemoteSession_->conn->addr.ptr, activeRemoteSession_->conn->addr.slen);
        }
Tristan Matthews's avatar
Tristan Matthews committed
619
    }
620
621
}

622
void Sdp::getRemoteSdpCryptoFromOffer(const pjmedia_sdp_session* remote_sdp, CryptoOffer& crypto_offer)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
623
{
Rafaël Carré's avatar
Rafaël Carré committed
624
    for (unsigned i = 0; i < remote_sdp->media_count; ++i) {
625
626
        pjmedia_sdp_media *media = remote_sdp->media[i];

Rafaël Carré's avatar
Rafaël Carré committed
627
        for (unsigned j = 0; j < media->attr_count; j++) {
628
            pjmedia_sdp_attr *attribute = media->attr[j];
629

630
631
632
            // @TODO our parser require the "a=crypto:" to be present
            if (pj_stricmp2(&attribute->name, "crypto") == 0)
                crypto_offer.push_back("a=crypto:" + std::string(attribute->value.ptr, attribute->value.slen));
633
        }
634
    }
635
}
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656

bool Sdp::getOutgoingVideoSettings(map<string, string> &args) const
{
#ifdef SFL_VIDEO
    string codec(getOutgoingVideoCodec());
    if (not codec.empty()) {
        const string encoder(libav_utils::encodersMap()[codec]);
        if (encoder.empty()) {
            DEBUG("Couldn't find encoder for \"%s\"\n", codec.c_str());
            return false;
        } else {
            args["codec"] = encoder;
            args["bitrate"] = getOutgoingVideoField(codec, "bitrate");
            args["parameters"] = getOutgoingVideoField(codec, "parameters");
            args["payload_type"] = getOutgoingVideoPayload();
        }
        return true;
    }
#endif
    return false;
}