sdp.cpp 27.9 KB
Newer Older
1
/*
2
 *  Copyright (C) 2004-2012 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
19
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 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
#include "sipaccount.h"
43

44
#ifdef HAVE_OPUS
45
#include "audio/codecs/opus.h"
46
#endif
47

48
49
50
51
#ifdef SFL_VIDEO
#include "video/libav_utils.h"
#endif

52
using std::string;
53
using std::map;
54
55
56
using std::vector;
using std::stringstream;

57
Sdp::Sdp(pj_pool_t *pool)
58
    : memPool_(pool)
59
    , negotiator_(NULL)
60
    , localSession_(NULL)
61
    , remoteSession_(NULL)
62
63
    , activeLocalSession_(NULL)
    , activeRemoteSession_(NULL)
64
65
    , audio_codec_list_()
    , video_codec_list_()
66
    , sessionAudioMedia_()
67
    , sessionVideoMedia_()
68
    , publishedIpAddr_()
69
    , remoteIpAddr_()
70
71
72
73
    , localAudioDataPort_(0)
    , localAudioControlPort_(0)
    , localVideoDataPort_(0)
    , localVideoControlPort_(0)
74
    , remoteAudioPort_(0)
75
    , remoteVideoPort_(0)
76
    , zrtpHelloHash_()
77
    , srtpCrypto_()
78
    , telephoneEventPayload_(101) // same as asterisk
79
{}
80

81
82
83
84
85
86
87
88
Sdp::~Sdp()
{
    SIPAccount::releasePort(localAudioDataPort_);
#ifdef SFL_VIDEO
    SIPAccount::releasePort(localVideoDataPort_);
#endif
}

89
90
91
namespace {
    bool hasPayload(const std::vector<sfl::AudioCodec*> &codecs, int pt)
    {
92
93
        for (const auto &i : codecs)
            if (i and i->getPayloadType() == pt)
94
95
96
                return true;
        return false;
    }
97
98
99
100
101

    bool hasCodec(const std::vector<std::string> &codecs, const std::string &codec)
    {
        return std::find(codecs.begin(), codecs.end(), codec) != codecs.end();
    }
102
103
104
}


105
void Sdp::setActiveLocalSdpSession(const pjmedia_sdp_session *sdp)
106
{
107
    activeLocalSession_ = (pjmedia_sdp_session*) sdp;
108

109
110
    for (unsigned i = 0; i < activeLocalSession_->media_count; ++i) {
        pjmedia_sdp_media *current = activeLocalSession_->media[i];
111

112
113
        for (unsigned fmt = 0; fmt < current->desc.fmt_count; ++fmt) {
            static const pj_str_t STR_RTPMAP = { (char*) "rtpmap", 6 };
114
            pjmedia_sdp_attr *rtpMapAttribute = pjmedia_sdp_media_find_attr(current, &STR_RTPMAP, &current->desc.fmt[fmt]);
115

116
            if (!rtpMapAttribute) {
117
118
                ERROR("Could not find rtpmap attribute");
                break;
119
            }
120
121

            pjmedia_sdp_rtpmap *rtpmap;
122
            pjmedia_sdp_attr_to_rtpmap(memPool_, rtpMapAttribute, &rtpmap);
123

124
            if (!pj_stricmp2(&current->desc.media, "audio")) {
125
                const unsigned long pt = pj_strtoul(&current->desc.fmt[fmt]);
126
                if (pt != telephoneEventPayload_ and not hasPayload(sessionAudioMedia_, pt)) {
127
128
129
130
131
                    sfl::AudioCodec *codec = Manager::instance().audioCodecFactory.getCodec(pt);
                    if (codec)
                        sessionAudioMedia_.push_back(codec);
                    else
                        ERROR("Could not get codec for payload type %lu", pt);
132
                }
133
134
135
136
137
            } else if (!pj_stricmp2(&current->desc.media, "video")) {
                const string codec(rtpmap->enc_name.ptr, rtpmap->enc_name.slen);
                if (not hasCodec(sessionVideoMedia_, codec))
                    sessionVideoMedia_.push_back(codec);
            }
138
        }
139
    }
140
141
}

Rafaël Carré's avatar
Rafaël Carré committed
142

143
void Sdp::setActiveRemoteSdpSession(const pjmedia_sdp_session *sdp)
144
{
145
    if (!sdp) {
146
        ERROR("Remote sdp is NULL");
Rafaël Carré's avatar
Rafaël Carré committed
147
148
149
        return;
    }

150
151
152
    activeRemoteSession_ = (pjmedia_sdp_session*) sdp;

    bool parsedTelelphoneEvent = false;
153
    for (unsigned i = 0; i < sdp->media_count; i++) {
154
155
        pjmedia_sdp_media *r_media = sdp->media[i];
        if (!pj_stricmp2(&r_media->desc.media, "audio")) {
156

157
158
159
160
161
162
163
164
165
166
            if (not parsedTelelphoneEvent) {
                static const pj_str_t STR_TELEPHONE_EVENT = { (char*) "telephone-event", 15};
                pjmedia_sdp_attr *telephoneEvent = pjmedia_sdp_attr_find(r_media->attr_count, r_media->attr, &STR_TELEPHONE_EVENT, NULL);

                if (telephoneEvent != NULL) {
                    pjmedia_sdp_rtpmap *rtpmap;
                    pjmedia_sdp_attr_to_rtpmap(memPool_, telephoneEvent, &rtpmap);
                    telephoneEventPayload_ = pj_strtoul(&rtpmap->pt);
                    parsedTelelphoneEvent = true;
                }
Rafaël Carré's avatar
Rafaël Carré committed
167
            }
168

169
170
171
            // add audio codecs from remote as needed
            for (unsigned fmt = 0; fmt < r_media->desc.fmt_count; ++fmt) {

172
173
                const unsigned long pt = pj_strtoul(&r_media->desc.fmt[fmt]);
                if (pt != telephoneEventPayload_ and not hasPayload(sessionAudioMedia_, pt)) {
174
175
176
177
178
179
180
181
                    sfl::AudioCodec *codec = Manager::instance().audioCodecFactory.getCodec(pt);
                    if (codec) {
                        DEBUG("Adding codec with new payload type %d", pt);
                        sessionAudioMedia_.push_back(codec);
                    } else
                        DEBUG("Could not get codec for payload type %lu", pt);
                }
            }
Rafaël Carré's avatar
Rafaël Carré committed
182
        }
183
    }
184
    DEBUG("Ready to decode %u audio codecs", sessionAudioMedia_.size());
185
186
}

187
string Sdp::getSessionVideoCodec() const
188
{
189
190
    if (sessionVideoMedia_.empty()) {
        DEBUG("Session video media is empty");
Tristan Matthews's avatar
Tristan Matthews committed
191
        return "";
192
    }
193
    return sessionVideoMedia_[0];
194
195
}

196
string Sdp::getAudioCodecNames() const
197
{
198
199
200
201
202
203
204
205
206
207
    std::string result;
    char sep = ' ';
    for (std::vector<sfl::AudioCodec*>::const_iterator i = sessionAudioMedia_.begin();
         i != sessionAudioMedia_.end(); ++i) {
        if (i == sessionAudioMedia_.end() - 1)
            sep = '\0';
        if (*i)
            result += (*i)->getMimeSubtype() + sep;
    }
    return result;
208
209
}

210
void Sdp::getSessionAudioMedia(std::vector<sfl::AudioCodec*> &codecs) const
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
211
{
212
    codecs = sessionAudioMedia_;
213
214
}

Tristan Matthews's avatar
Tristan Matthews committed
215

216
pjmedia_sdp_media *
217
Sdp::setMediaDescriptorLines(bool audio)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
218
{
219
    pjmedia_sdp_media *med = PJ_POOL_ZALLOC_T(memPool_, pjmedia_sdp_media);
220

221
    med->desc.media = audio ? pj_str((char*) "audio") : pj_str((char*) "video");
222
    med->desc.port_count = 1;
223
    med->desc.port = audio ? localAudioDataPort_ : localVideoDataPort_;
224
    // in case of sdes, media are tagged as "RTP/SAVP", RTP/AVP elsewhere
225
    med->desc.transport = pj_str(srtpCrypto_.empty() ? (char*) "RTP/AVP" : (char*) "RTP/SAVP");
226

227
    int dynamic_payload = 96;
228

229
    med->desc.fmt_count = audio ? audio_codec_list_.size() : video_codec_list_.size();
230

231
    for (unsigned i = 0; i < med->desc.fmt_count; ++i) {
Tristan Matthews's avatar
Tristan Matthews committed
232
        unsigned clock_rate;
233
        string enc_name;
Tristan Matthews's avatar
Tristan Matthews committed
234
        int payload;
235
        unsigned channels;
Tristan Matthews's avatar
Tristan Matthews committed
236
237

        if (audio) {
238
            sfl::AudioCodec *codec = audio_codec_list_[i];
Tristan Matthews's avatar
Tristan Matthews committed
239
240
241
            payload = codec->getPayloadType();
            enc_name = codec->getMimeSubtype();
            clock_rate = codec->getClockRate();
242
            channels = codec->getChannels();
Tristan Matthews's avatar
Tristan Matthews committed
243
244
245
246
            // G722 require G722/8000 media description even if it is 16000 codec
            if (codec->getPayloadType () == 9)
                clock_rate = 8000;
        } else {
247
248
            // FIXME: get this key from header
            enc_name = video_codec_list_[i]["name"];
Tristan Matthews's avatar
Tristan Matthews committed
249
            clock_rate = 90000;
250
            payload = dynamic_payload;
Tristan Matthews's avatar
Tristan Matthews committed
251
        }
252

Tristan Matthews's avatar
Tristan Matthews committed
253
254
        std::ostringstream s;
        s << payload;
255
        pj_strdup2(memPool_, &med->desc.fmt[i], s.str().c_str());
256
257
258
259
260

        // 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
261
        pjmedia_sdp_rtpmap rtpmap;
262

263
        rtpmap.pt = med->desc.fmt[i];
264
        rtpmap.enc_name = pj_str((char*) enc_name.c_str());
265
        rtpmap.clock_rate = clock_rate;
266

267
#ifdef HAVE_OPUS
268
269
        // Opus sample rate is allways declared as 48000 and channel num is allways 2 in rtpmap as per
        // http://tools.ietf.org/html/draft-spittka-payload-rtp-opus-03#section-6.2
Tristan Matthews's avatar
Tristan Matthews committed
270
        if (payload == Opus::PAYLOAD_TYPE) {
271
272
273
274
            rtpmap.clock_rate = 48000;
            rtpmap.param.ptr = ((char* const)"2");
            rtpmap.param.slen = 1;
        } else
275
#endif
276
277
278
279
        {
            rtpmap.param.ptr = ((char* const)"");
            rtpmap.param.slen = 0;
        }
280

Rafaël Carré's avatar
Rafaël Carré committed
281
        pjmedia_sdp_attr *attr;
282
        pjmedia_sdp_rtpmap_to_attr(memPool_, &rtpmap, &attr);
283

284
        med->attr[med->attr_count++] = attr;
285

286
#ifdef HAVE_OPUS
287
        // Declare stereo support for opus
Tristan Matthews's avatar
Tristan Matthews committed
288
        if (payload == Opus::PAYLOAD_TYPE) {
289
            std::ostringstream os;
Tristan Matthews's avatar
Tristan Matthews committed
290
            os << "fmtp:" << payload << " stereo=1; sprop-stereo=" << (channels > 1);
291
292
            med->attr[med->attr_count++] = pjmedia_sdp_attr_create(memPool_, os.str().c_str(), NULL);
        }
293
#endif
294
#ifdef SFL_VIDEO
295
296
297
298
        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
299
300
301
302
            std::string profileLevelID(video_codec_list_[i]["parameters"]);
            if (profileLevelID.empty())
                profileLevelID = libav_utils::MAX_H264_PROFILE_LEVEL_ID;
            os << "fmtp:" << dynamic_payload << " " << profileLevelID;
303
304
            med->attr[med->attr_count++] = pjmedia_sdp_attr_create(memPool_, os.str().c_str(), NULL);
        }
305
#endif
306
307
        if (not audio)
            dynamic_payload++;
308
    }
309

310
    med->attr[med->attr_count++] = pjmedia_sdp_attr_create(memPool_, "sendrecv", NULL);
311
    if (!zrtpHelloHash_.empty())
312
        addZrtpAttribute(med, zrtpHelloHash_);
313

314
    if (audio) {
315
        setTelephoneEventRtpmap(med);
316
317
        addRTCPAttribute(med); // video has its own RTCP
    }
318

Rafaël Carré's avatar
Rafaël Carré committed
319
    return med;
320
}
321

322
323
324
325

void Sdp::addRTCPAttribute(pjmedia_sdp_media *med)
{
    std::ostringstream os;
326
    os << publishedIpAddr_ << ":" << localAudioControlPort_;
327
    const std::string str(os.str());
328
    pj_str_t input_str = pj_str((char*) str.c_str());
329
330
331
332
333
334
335
336
337
338
339
    pj_sockaddr outputAddr;
    pj_status_t status = pj_sockaddr_parse(PJ_AF_UNSPEC, 0, &input_str, &outputAddr);
    if (status != PJ_SUCCESS) {
        ERROR("Could not parse address %s", str.c_str());
        return;
    }
    pjmedia_sdp_attr *attr = pjmedia_sdp_attr_create_rtcp(memPool_, &outputAddr);
    if (attr)
        pjmedia_sdp_attr_add(&med->attr_count, med->attr, attr);
}

340
341
342
343
344
345
346
void
Sdp::setPublishedIP(const std::string &ip_addr)
{
    publishedIpAddr_ = ip_addr;
    if (localSession_) {
        localSession_->origin.addr = pj_str((char*) publishedIpAddr_.c_str());
        localSession_->conn->addr = localSession_->origin.addr;
347
        if (pjmedia_sdp_validate(localSession_) != PJ_SUCCESS)
348
349
350
351
            ERROR("Could not validate SDP");
    }
}

352
353
354
355
356
357
358
void
Sdp::updatePorts(const std::vector<pj_sockaddr_in> &sockets)
{
    localAudioDataPort_     = pj_ntohs(sockets[0].sin_port);
    localAudioControlPort_  = pj_ntohs(sockets[1].sin_port);
    localVideoDataPort_     = pj_ntohs(sockets[2].sin_port);
    localVideoControlPort_  = pj_ntohs(sockets[3].sin_port);
359
360
361
362
363
364
365
366
367
368
369
370
371
372

    if (localSession_) {
        if (localSession_->media[0]) {
            localSession_->media[0]->desc.port = localAudioDataPort_;
            // update RTCP attribute
            if (pjmedia_sdp_media_remove_all_attr(localSession_->media[0], "rtcp"))
                addRTCPAttribute(localSession_->media[0]);
        }
        if (localSession_->media[1])
            localSession_->media[1]->desc.port = localVideoDataPort_;

        if (not pjmedia_sdp_validate(localSession_))
            ERROR("Could not validate SDP");
    }
373
374
}

375

376
377
void Sdp::setTelephoneEventRtpmap(pjmedia_sdp_media *med)
{
378
379
380
381
382
    std::ostringstream s;
    s << telephoneEventPayload_;
    ++med->desc.fmt_count;
    pj_strdup2(memPool_, &med->desc.fmt[med->desc.fmt_count - 1], s.str().c_str());

Rafaël Carré's avatar
Rafaël Carré committed
383
    pjmedia_sdp_attr *attr_rtpmap = static_cast<pjmedia_sdp_attr *>(pj_pool_zalloc(memPool_, sizeof(pjmedia_sdp_attr)));
384
385
    attr_rtpmap->name = pj_str((char *) "rtpmap");
    attr_rtpmap->value = pj_str((char *) "101 telephone-event/8000");
386
387
388

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

Rafaël Carré's avatar
Rafaël Carré committed
389
    pjmedia_sdp_attr *attr_fmtp = static_cast<pjmedia_sdp_attr *>(pj_pool_zalloc(memPool_, sizeof(pjmedia_sdp_attr)));
390
391
    attr_fmtp->name = pj_str((char *) "fmtp");
    attr_fmtp->value = pj_str((char *) "101 0-15");
392
393
394
395

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

396
void Sdp::setLocalMediaVideoCapabilities(const vector<map<string, string> > &codecs)
397
{
398
399
    video_codec_list_.clear();
#ifdef SFL_VIDEO
400
401
402
403
    if (codecs.empty())
        WARN("No selected video codec while building local SDP offer");
    else
        video_codec_list_ = codecs;
404
#else
405
    (void) codecs;
406
#endif
407
}
408

409
void Sdp::setLocalMediaAudioCapabilities(const vector<int> &selectedCodecs)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
410
{
411
    if (selectedCodecs.empty())
412
        WARN("No selected codec while building local SDP offer");
413

414
    audio_codec_list_.clear();
415
416
    for (const auto &i : selectedCodecs) {
        sfl::AudioCodec *codec = Manager::instance().audioCodecFactory.getCodec(i);
417
418

        if (codec)
Tristan Matthews's avatar
Tristan Matthews committed
419
            audio_codec_list_.push_back(codec);
420
        else
421
            WARN("Couldn't find audio codec");
422
    }
423
424
}

Tristan Matthews's avatar
Tristan Matthews committed
425
426
427
428
429
namespace {
    void printSession(const pjmedia_sdp_session *session)
    {
        char buffer[2048];
        size_t size = pjmedia_sdp_print(session, buffer, sizeof(buffer));
430
        string sessionStr(buffer, std::min(size, sizeof(buffer)));
Tristan Matthews's avatar
Tristan Matthews committed
431
432
433
434
        DEBUG("%s", sessionStr.c_str());
    }
}

435
int Sdp::createLocalSession(const vector<int> &selectedAudioCodecs, const vector<map<string, string> > &selectedVideoCodecs)
436
{
437
438
    setLocalMediaAudioCapabilities(selectedAudioCodecs);
    setLocalMediaVideoCapabilities(selectedVideoCodecs);
439

Rafaël Carré's avatar
Rafaël Carré committed
440
441
    localSession_ = PJ_POOL_ZALLOC_T(memPool_, pjmedia_sdp_session);
    localSession_->conn = PJ_POOL_ZALLOC_T(memPool_, pjmedia_sdp_conn);
442

443
    /* Initialize the fields of the struct */
Rafaël Carré's avatar
Rafaël Carré committed
444
445
    localSession_->origin.version = 0;
    pj_time_val tv;
446
    pj_gettimeofday(&tv);
447

448
    localSession_->origin.user = pj_str(pj_gethostname()->ptr);
Rafaël Carré's avatar
Rafaël Carré committed
449
450
    // Use Network Time Protocol format timestamp to ensure uniqueness.
    localSession_->origin.id = tv.sec + 2208988800UL;
451
452
    localSession_->origin.net_type = pj_str((char*) "IN");
    localSession_->origin.addr_type = pj_str((char*) "IP4");
453
    localSession_->origin.addr = pj_str((char*) publishedIpAddr_.c_str());
454

455
    localSession_->name = pj_str((char*) PACKAGE);
456

Rafaël Carré's avatar
Rafaël Carré committed
457
458
459
460
461
462
463
464
465
466
467
    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
468
    const bool audio = true;
469
    localSession_->media_count = 1;
470
    localSession_->media[0] = setMediaDescriptorLines(audio);
471
    if (not selectedVideoCodecs.empty()) {
472
        localSession_->media[1] = setMediaDescriptorLines(!audio);
473
474
        ++localSession_->media_count;
    }
475

476
    if (!srtpCrypto_.empty())
477
        addSdesAttribute(srtpCrypto_);
478

Tristan Matthews's avatar
Tristan Matthews committed
479
480
    DEBUG("SDP: Local SDP Session:");
    printSession(localSession_);
481

482
    return pjmedia_sdp_validate(localSession_);
483
}
484

485
486
487
bool
Sdp::createOffer(const vector<int> &selectedCodecs,
                 const vector<map<string, string> > &videoCodecs)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
488
{
489
    if (createLocalSession(selectedCodecs, videoCodecs) != PJ_SUCCESS) {
490
        ERROR("Failed to create initial offer");
491
492
493
494
        return false;
    }

    if (pjmedia_sdp_neg_create_w_local_offer(memPool_, localSession_, &negotiator_) != PJ_SUCCESS) {
495
        ERROR("Failed to create an initial SDP negotiator");
496
        return false;
497
    }
498
    return true;
499
500
}

501
void Sdp::receiveOffer(const pjmedia_sdp_session* remote,
502
                       const vector<int> &selectedCodecs,
503
                       const vector<map<string, string> > &videoCodecs)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
504
{
505
    if (!remote) {
506
        ERROR("Remote session is NULL");
507
508
        return;
    }
Emmanuel Milou's avatar
Emmanuel Milou committed
509

510
    DEBUG("Remote SDP Session:");
Tristan Matthews's avatar
Tristan Matthews committed
511
    printSession(remote);
512

513
    if (!localSession_ and createLocalSession(selectedCodecs, videoCodecs) != PJ_SUCCESS) {
514
        ERROR("Failed to create initial offer");
Tristan Matthews's avatar
Tristan Matthews committed
515
516
        return;
    }
517
518
519

    remoteSession_ = pjmedia_sdp_session_clone(memPool_, remote);

520
521
522
    if (pjmedia_sdp_neg_create_w_remote_offer(memPool_, localSession_,
            remoteSession_, &negotiator_) != PJ_SUCCESS)
        ERROR("Failed to initialize negotiator");
523
}
524

Rafaël Carré's avatar
Rafaël Carré committed
525
void Sdp::startNegotiation()
526
{
527
    if (negotiator_ == NULL) {
Tristan Matthews's avatar
Tristan Matthews committed
528
        ERROR("Can't start negotiation with invalid negotiator");
529
530
531
        return;
    }

532
533
    const pjmedia_sdp_session *active_local;
    const pjmedia_sdp_session *active_remote;
Emmanuel Milou's avatar
Emmanuel Milou committed
534

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

538
    if (pjmedia_sdp_neg_negotiate(memPool_, negotiator_, 0) != PJ_SUCCESS)
Rafaël Carré's avatar
Rafaël Carré committed
539
        return;
540

Rafaël Carré's avatar
Rafaël Carré committed
541
    if (pjmedia_sdp_neg_get_active_local(negotiator_, &active_local) != PJ_SUCCESS)
Tristan Matthews's avatar
Tristan Matthews committed
542
        ERROR("Could not retrieve local active session");
543
544
    else
        setActiveLocalSdpSession(active_local);
545

Rafaël Carré's avatar
Rafaël Carré committed
546
    if (pjmedia_sdp_neg_get_active_remote(negotiator_, &active_remote) != PJ_SUCCESS)
Tristan Matthews's avatar
Tristan Matthews committed
547
        ERROR("Could not retrieve remote active session");
548
549
    else
        setActiveRemoteSdpSession(active_remote);
550
}
551

552
namespace
Tristan Matthews's avatar
Tristan Matthews committed
553
{
Rafaël Carré's avatar
Rafaël Carré committed
554
    vector<string> split(const string &s, char delim)
555
    {
Rafaël Carré's avatar
Rafaël Carré committed
556
        vector<string> elems;
557
558
559
560
561
562
563
564
        stringstream ss(s);
        string item;
        while(getline(ss, item, delim))
            elems.push_back(item);
        return elems;
    }
} // end anonymous namespace

565
string Sdp::getLineFromSession(const pjmedia_sdp_session *sess, const string &keyword) const
566
{
Rafaël Carré's avatar
Rafaël Carré committed
567
    char buffer[2048];
568
    int size = pjmedia_sdp_print(sess, buffer, sizeof buffer);
569
570
    string sdp(buffer, size);
    const vector<string> tokens(split(sdp, '\n'));
571
572
573
    for (const auto &item : tokens)
        if (item.find(keyword) != string::npos)
            return item;
574
    return "";
575
}
Tristan Matthews's avatar
Tristan Matthews committed
576

577
578
579
580
// FIXME:
// Here we filter out parts of the SDP that libavformat doesn't need to
// know about...we should probably give the video decoder thread the original
// SDP and deal with the streams properly at that level
581
string Sdp::getIncomingVideoDescription() const
582
{
583
584
585
    pjmedia_sdp_session *videoSession = pjmedia_sdp_session_clone(memPool_, activeLocalSession_);
    if (!videoSession) {
        ERROR("Could not clone SDP");
586
        return "";
587
588
    }

589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
    // deactivate non-video media
    bool hasVideo = false;
    for (unsigned i = 0; i < videoSession->media_count; i++)
        if (pj_stricmp2(&videoSession->media[i]->desc.media, "video")) {
            if (pjmedia_sdp_media_deactivate(memPool_, videoSession->media[i]) != PJ_SUCCESS)
                ERROR("Could not deactivate media");
        } else {
            hasVideo = true;
        }

    if (not hasVideo) {
        DEBUG("No video present in active local SDP");
        return "";
    }

    char buffer[4096];
    size_t size = pjmedia_sdp_print(videoSession, buffer, sizeof(buffer));
    string sessionStr(buffer, std::min(size, sizeof(buffer)));
607

608
    // FIXME: find a way to get rid of the "m=audio..." line with PJSIP
609

610
611
612
    const size_t audioPos = sessionStr.find("m=audio");
    const size_t newline2 = sessionStr.find('\n', audioPos);
    const size_t newline1 = sessionStr.rfind('\n', audioPos);
613

614
615
    sessionStr.erase(newline1, newline2 - newline1);
    return sessionStr;
616
}
617

618
std::string Sdp::getOutgoingVideoCodec() const
619
620
{
    string str("a=rtpmap:");
621
622
623
    std::stringstream os;
    os << getOutgoingVideoPayload();
    str += os.str();
624
625
626
627
628
629
    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);
}
630

631
632
633
634
635
636
637
638
639
640
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();
641
        }
642
643
644
645
646
647
648
649
650
651
}

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;
652
    }
653
    return "";
654
}
655

656
int
657
Sdp::getOutgoingVideoPayload() const
658
659
660
661
662
{
    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;
663
664
665
666
    return payload_num;
}

void
667
668
Sdp::getProfileLevelID(const pjmedia_sdp_session *session,
                       std::string &profile, int payload) const
669
{
670
    std::ostringstream os;
671
    os << "a=fmtp:" << payload;
672
    string fmtpLine(getLineFromSession(session, os.str()));
673
674
675
676
    const std::string needle("profile-level-id=");
    const size_t DIGITS_IN_PROFILE_LEVEL_ID = 6;
    const size_t needleLength = needle.size() + DIGITS_IN_PROFILE_LEVEL_ID;
    const size_t pos = fmtpLine.find(needle);
Tristan Matthews's avatar
Tristan Matthews committed
677
    if (pos != std::string::npos and fmtpLine.size() >= (pos + needleLength)) {
678
        profile = fmtpLine.substr(pos, needleLength);
Tristan Matthews's avatar
Tristan Matthews committed
679
680
        DEBUG("Using %s", profile.c_str());
    }
681
682
}

683
void Sdp::addSdesAttribute(const vector<std::string>& crypto)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
684
{
685
686
    for (const auto &item : crypto) {
        pj_str_t val = { (char*) item.c_str(), static_cast<pj_ssize_t>(item.size()) };
Rafaël Carré's avatar
Rafaël Carré committed
687
        pjmedia_sdp_attr *attr = pjmedia_sdp_attr_create(memPool_, "crypto", &val);
688

Rafaël Carré's avatar
Rafaël Carré committed
689
        for (unsigned i = 0; i < localSession_->media_count; i++)
690
691
            if (pjmedia_sdp_media_add_attr(localSession_->media[i], attr) != PJ_SUCCESS)
                throw SdpException("Could not add sdes attribute to media");
692
    }
693
694
695
}


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

703
704
    if (pjmedia_sdp_media_add_attr(media, attr) != PJ_SUCCESS)
        throw SdpException("Could not add zrtp attribute to media");
705
706
}

Tristan Matthews's avatar
Tristan Matthews committed
707
708
709
710
711
712
713
714
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;
        }
715
        size_t i = 0;
Tristan Matthews's avatar
Tristan Matthews committed
716
717
718
719
720
721
722
723
724
725
        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
726
void Sdp::addAttributeToLocalAudioMedia(const char *attr)
727
{
Tristan Matthews's avatar
Tristan Matthews committed
728
729
    const int i = getIndexOfAttribute(localSession_, "audio");
    if (i == -1)
Tristan Matthews's avatar
Tristan Matthews committed
730
        return;
731
732
    pjmedia_sdp_attr *attribute = pjmedia_sdp_attr_create(memPool_, attr, NULL);
    pjmedia_sdp_media_add_attr(localSession_->media[i], attribute);
733
734
}

Rafaël Carré's avatar
Rafaël Carré committed
735
void Sdp::removeAttributeFromLocalAudioMedia(const char *attr)
736
{
Tristan Matthews's avatar
Tristan Matthews committed
737
738
    const int i = getIndexOfAttribute(localSession_, "audio");
    if (i == -1)
Tristan Matthews's avatar
Tristan Matthews committed
739
740
        return;
    pjmedia_sdp_media_remove_all_attr(localSession_->media[i], attr);
741
742
}

Rafaël Carré's avatar
Rafaël Carré committed
743
void Sdp::removeAttributeFromLocalVideoMedia(const char *attr)
744
{
Tristan Matthews's avatar
Tristan Matthews committed
745
746
747
    const int i = getIndexOfAttribute(localSession_, "video");
    if (i == -1)
        return;
748
    pjmedia_sdp_media_remove_all_attr(localSession_->media[i], attr);
749
750
}

Rafaël Carré's avatar
Rafaël Carré committed
751
void Sdp::addAttributeToLocalVideoMedia(const char *attr)
752
{
Tristan Matthews's avatar
Tristan Matthews committed
753
754
755
    const int i = getIndexOfAttribute(localSession_, "video");
    if (i == -1)
        return;
756
    pjmedia_sdp_attr *attribute = pjmedia_sdp_attr_create(memPool_, attr, NULL);
757
    pjmedia_sdp_media_add_attr(localSession_->media[i], attribute);
758
759
}

760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775

void
Sdp::updateRemoteIP(unsigned index)
{
    // Remote connection information may be in the SDP Session or in the media
    // for that session
    pjmedia_sdp_conn *conn = activeRemoteSession_->conn ? activeRemoteSession_->conn :
                             activeRemoteSession_->media[index]->conn ? activeRemoteSession_->media[index]->conn :
                             NULL;
    if (conn)
        remoteIpAddr_ = std::string(conn->addr.ptr, conn->addr.slen);
    else
        ERROR("Could not get remote IP from SDP or SDP Media");
}


776
void Sdp::setMediaTransportInfoFromRemoteSdp()
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
777
{
Rafaël Carré's avatar
Rafaël Carré committed
778
    if (!activeRemoteSession_) {
Tristan Matthews's avatar
Tristan Matthews committed
779
        ERROR("Remote sdp is NULL while parsing media");
780
781
        return;
    }
782

Tristan Matthews's avatar
Tristan Matthews committed
783
    for (unsigned i = 0; i < activeRemoteSession_->media_count; ++i) {
784
        if (pj_stricmp2(&activeRemoteSession_->media[i]->desc.media, "audio") == 0) {
785
            remoteAudioPort_ = activeRemoteSession_->media[i]->desc.port;
786
            updateRemoteIP(i);
787
        } else if (pj_stricmp2(&activeRemoteSession_->media[i]->desc.media, "video") == 0) {
788
            remoteVideoPort_ = activeRemoteSession_->media[i]->desc.port;
789
            updateRemoteIP(i);
790
        }
Tristan Matthews's avatar
Tristan Matthews committed
791
    }
792
793
}

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

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

802
803
804
            // @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));
805
        }