rtp.c 53.6 KB
Newer Older
1
/*****************************************************************************
gbazin's avatar
   
gbazin committed
2
 * rtp.c: rtp stream output module
3
 *****************************************************************************
4
 * Copyright (C) 2003-2004 the VideoLAN team
5
 * Copyright © 2007-2008 Rémi Denis-Courmont
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 *
 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
 *
 * 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 2 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
dionoea's avatar
dionoea committed
21
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22
23
24
25
26
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
27

28
29
30
31
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

32
#include <vlc_common.h>
33
#include <vlc_plugin.h>
zorglub's avatar
zorglub committed
34
35
#include <vlc_sout.h>
#include <vlc_block.h>
36

zorglub's avatar
zorglub committed
37
38
39
40
#include <vlc_httpd.h>
#include <vlc_url.h>
#include <vlc_network.h>
#include <vlc_charset.h>
41
#include <vlc_strings.h>
42
#include <srtp.h>
43

44
45
#include "rtp.h"

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
46
#ifdef HAVE_UNISTD_H
47
#   include <sys/types.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
48
#   include <unistd.h>
49
50
#   include <fcntl.h>
#   include <sys/stat.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
51
#endif
52
53
54
55
56
57
58
59
60
#ifdef HAVE_LINUX_DCCP_H
#   include <linux/dccp.h>
#endif
#ifndef IPPROTO_DCCP
# define IPPROTO_DCCP 33
#endif
#ifndef IPPROTO_UDPLITE
# define IPPROTO_UDPLITE 136
#endif
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
61
62
63

#include <errno.h>

64
65
#include <assert.h>

66
67
68
/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
69

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
70
71
#define DEST_TEXT N_("Destination")
#define DEST_LONGTEXT N_( \
72
    "This is the output URL that will be used." )
73
74
#define SDP_TEXT N_("SDP")
#define SDP_LONGTEXT N_( \
75
76
77
78
    "This allows you to specify how the SDP (Session Descriptor) for this RTP "\
    "session will be made available. You must use an url: http://location to " \
    "access the SDP via HTTP, rtsp://location for RTSP access, and sap:// " \
    "for the SDP to be announced via SAP." )
79
80
#define SAP_TEXT N_("SAP announcing")
#define SAP_LONGTEXT N_("Announce this session with SAP.")
81
82
#define MUX_TEXT N_("Muxer")
#define MUX_LONGTEXT N_( \
83
84
    "This allows you to specify the muxer used for the streaming output. " \
    "Default is to use no muxer (standard RTP stream)." )
hartman's avatar
hartman committed
85
86
87

#define NAME_TEXT N_("Session name")
#define NAME_LONGTEXT N_( \
88
89
    "This is the name of the session that will be announced in the SDP " \
    "(Session Descriptor)." )
90
#define DESC_TEXT N_("Session description")
hartman's avatar
hartman committed
91
#define DESC_LONGTEXT N_( \
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
92
93
    "This allows you to give a short description with details about the stream, " \
    "that will be announced in the SDP (Session Descriptor)." )
hartman's avatar
hartman committed
94
95
#define URL_TEXT N_("Session URL")
#define URL_LONGTEXT N_( \
96
97
98
    "This allows you to give an URL with more details about the stream " \
    "(often the website of the streaming organization), that will " \
    "be announced in the SDP (Session Descriptor)." )
hartman's avatar
hartman committed
99
100
#define EMAIL_TEXT N_("Session email")
#define EMAIL_LONGTEXT N_( \
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
101
102
103
104
105
106
107
    "This allows you to give a contact mail address for the stream, that will " \
    "be announced in the SDP (Session Descriptor)." )
#define PHONE_TEXT N_("Session phone number")
#define PHONE_LONGTEXT N_( \
    "This allows you to give a contact telephone number for the stream, that will " \
    "be announced in the SDP (Session Descriptor)." )

108
109
#define PORT_TEXT N_("Port")
#define PORT_LONGTEXT N_( \
110
    "This allows you to specify the base port for the RTP streaming." )
111
112
#define PORT_AUDIO_TEXT N_("Audio port")
#define PORT_AUDIO_LONGTEXT N_( \
113
    "This allows you to specify the default audio port for the RTP streaming." )
114
115
#define PORT_VIDEO_TEXT N_("Video port")
#define PORT_VIDEO_LONGTEXT N_( \
116
    "This allows you to specify the default video port for the RTP streaming." )
117

118
#define TTL_TEXT N_("Hop limit (TTL)")
119
#define TTL_LONGTEXT N_( \
120
121
122
    "This is the hop limit (also known as \"Time-To-Live\" or TTL) of " \
    "the multicast packets sent by the stream output (0 = use operating " \
    "system built-in default).")
123

124
125
126
127
128
#define RTCP_MUX_TEXT N_("RTP/RTCP multiplexing")
#define RTCP_MUX_LONGTEXT N_( \
    "This sends and receives RTCP packet multiplexed over the same port " \
    "as RTP packets." )

129
130
131
132
#define PROTO_TEXT N_("Transport protocol")
#define PROTO_LONGTEXT N_( \
    "This selects which transport protocol to use for RTP." )

133
134
135
136
137
138
139
140
141
#define SRTP_KEY_TEXT N_("SRTP key (hexadecimal)")
#define SRTP_KEY_LONGTEXT N_( \
    "RTP packets will be integrity-protected and ciphered "\
    "with this Secure RTP master shared secret key.")

#define SRTP_SALT_TEXT N_("SRTP salt (hexadecimal)")
#define SRTP_SALT_LONGTEXT N_( \
    "Secure RTP requires a (non-secret) master salt value.")

142
143
144
145
146
147
148
static const char *const ppsz_protos[] = {
    "dccp", "sctp", "tcp", "udp", "udplite",
};

static const char *const ppsz_protocols[] = {
    "DCCP", "SCTP", "TCP", "UDP", "UDP-Lite",
};
149

150
#define RFC3016_TEXT N_("MP4A LATM")
151
#define RFC3016_LONGTEXT N_( \
152
    "This allows you to stream MPEG4 LATM audio streams (see RFC3016)." )
153

154
155
156
static int  Open ( vlc_object_t * );
static void Close( vlc_object_t * );

157
#define SOUT_CFG_PREFIX "sout-rtp-"
158
#define MAX_EMPTY_BLOCKS 200
159

160
vlc_module_begin();
161
    set_shortname( N_("RTP"));
162
    set_description( N_("RTP stream output") );
163
164
    set_capability( "sout stream", 0 );
    add_shortcut( "rtp" );
zorglub's avatar
zorglub committed
165
166
    set_category( CAT_SOUT );
    set_subcategory( SUBCAT_SOUT_STREAM );
167

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
168
    add_string( SOUT_CFG_PREFIX "dst", "", NULL, DEST_TEXT,
169
                DEST_LONGTEXT, true );
170
        change_unsafe();
171
    add_string( SOUT_CFG_PREFIX "sdp", "", NULL, SDP_TEXT,
172
                SDP_LONGTEXT, true );
173
    add_string( SOUT_CFG_PREFIX "mux", "", NULL, MUX_TEXT,
174
                MUX_LONGTEXT, true );
175
176
    add_bool( SOUT_CFG_PREFIX "sap", false, NULL, SAP_TEXT, SAP_LONGTEXT,
              true );
177

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
178
    add_string( SOUT_CFG_PREFIX "name", "", NULL, NAME_TEXT,
179
                NAME_LONGTEXT, true );
hartman's avatar
hartman committed
180
    add_string( SOUT_CFG_PREFIX "description", "", NULL, DESC_TEXT,
181
                DESC_LONGTEXT, true );
hartman's avatar
hartman committed
182
    add_string( SOUT_CFG_PREFIX "url", "", NULL, URL_TEXT,
183
                URL_LONGTEXT, true );
hartman's avatar
hartman committed
184
    add_string( SOUT_CFG_PREFIX "email", "", NULL, EMAIL_TEXT,
185
                EMAIL_LONGTEXT, true );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
186
    add_string( SOUT_CFG_PREFIX "phone", "", NULL, PHONE_TEXT,
187
                PHONE_LONGTEXT, true );
188

189
    add_string( SOUT_CFG_PREFIX "proto", "udp", NULL, PROTO_TEXT,
190
                PROTO_LONGTEXT, false );
191
        change_string_list( ppsz_protos, ppsz_protocols, NULL );
192
    add_integer( SOUT_CFG_PREFIX "port", 50004, NULL, PORT_TEXT,
193
                 PORT_LONGTEXT, true );
194
    add_integer( SOUT_CFG_PREFIX "port-audio", 50000, NULL, PORT_AUDIO_TEXT,
195
                 PORT_AUDIO_LONGTEXT, true );
196
    add_integer( SOUT_CFG_PREFIX "port-video", 50002, NULL, PORT_VIDEO_TEXT,
197
                 PORT_VIDEO_LONGTEXT, true );
198

Marian Durkovic's avatar
Marian Durkovic committed
199
    add_integer( SOUT_CFG_PREFIX "ttl", 0, NULL, TTL_TEXT,
200
201
202
                 TTL_LONGTEXT, true );
    add_bool( SOUT_CFG_PREFIX "rtcp-mux", false, NULL,
              RTCP_MUX_TEXT, RTCP_MUX_LONGTEXT, false );
203

204
205
206
207
208
    add_string( SOUT_CFG_PREFIX "key", "", NULL,
                SRTP_KEY_TEXT, SRTP_KEY_LONGTEXT, false );
    add_string( SOUT_CFG_PREFIX "salt", "", NULL,
                SRTP_SALT_TEXT, SRTP_SALT_LONGTEXT, false );

209
    add_bool( SOUT_CFG_PREFIX "mp4a-latm", 0, NULL, RFC3016_TEXT,
210
                 RFC3016_LONGTEXT, false );
211

212
213
214
215
216
217
    set_callbacks( Open, Close );
vlc_module_end();

/*****************************************************************************
 * Exported prototypes
 *****************************************************************************/
218
static const char *const ppsz_sout_options[] = {
219
    "dst", "name", "port", "port-audio", "port-video", "*sdp", "ttl", "mux",
220
    "sap", "description", "url", "email", "phone",
221
    "proto", "rtcp-mux", "key", "salt",
222
    "mp4a-latm", NULL
223
224
};

225
static sout_stream_id_t *Add ( sout_stream_t *, es_format_t * );
226
static int               Del ( sout_stream_t *, sout_stream_id_t * );
gbazin's avatar
   
gbazin committed
227
static int               Send( sout_stream_t *, sout_stream_id_t *,
228
                               block_t* );
229
230
231
232
static sout_stream_id_t *MuxAdd ( sout_stream_t *, es_format_t * );
static int               MuxDel ( sout_stream_t *, sout_stream_id_t * );
static int               MuxSend( sout_stream_t *, sout_stream_id_t *,
                                  block_t* );
233

234
static sout_access_out_t *GrabberCreate( sout_stream_t *p_sout );
235
static void* ThreadSend( vlc_object_t *p_this );
236

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
237
static void SDPHandleUrl( sout_stream_t *, const char * );
238

zorglub's avatar
zorglub committed
239
static int SapSetup( sout_stream_t *p_stream );
240
static int FileSetup( sout_stream_t *p_stream );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
241
static int HttpSetup( sout_stream_t *p_stream, const vlc_url_t * );
242

243
244
struct sout_stream_sys_t
{
245
    /* SDP */
246
247
248
    char    *psz_sdp;
    vlc_mutex_t  lock_sdp;

249
    /* SDP to disk */
250
    bool b_export_sdp_file;
251
    char *psz_sdp_file;
252
253

    /* SDP via SAP */
254
    bool b_export_sap;
255
256
    session_descriptor_t *p_session;

257
    /* SDP via HTTP */
258
259
260
    httpd_host_t *p_httpd_host;
    httpd_file_t *p_httpd_file;

261
    /* RTSP */
262
263
264
    rtsp_stream_t *rtsp;

    /* */
265
266
267
268
269
270
    char     *psz_destination;
    uint8_t   proto;
    uint8_t   i_ttl;
    uint16_t  i_port;
    uint16_t  i_port_audio;
    uint16_t  i_port_video;
271
272
    bool b_latm;
    bool rtcp_mux;
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287

    /* when need to use a private one or when using muxer */
    int i_payload_type;

    /* in case we do TS/PS over rtp */
    sout_mux_t        *p_mux;
    sout_access_out_t *p_grab;
    block_t           *packet;

    /* */
    vlc_mutex_t      lock_es;
    int              i_es;
    sout_stream_id_t **es;
};

288
typedef int (*pf_rtp_packetizer_t)( sout_stream_id_t *, block_t * );
289

290
291
292
typedef struct rtp_sink_t
{
    int rtp_fd;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
293
    rtcp_sender_t *rtcp;
294
295
} rtp_sink_t;

296
297
struct sout_stream_id_t
{
298
299
    VLC_COMMON_MEMBERS

300
301
    sout_stream_t *p_stream;
    /* rtp field */
302
303
    uint16_t    i_sequence;
    uint8_t     i_payload_type;
304
305
306
    uint8_t     ssrc[4];

    /* for sdp */
307
    const char  *psz_enc;
308
    char        *psz_fmtp;
309
310
311
    int          i_clock_rate;
    int          i_port;
    int          i_cat;
312
    int          i_channels;
313
    int          i_bitrate;
314
315

    /* Packetizer specific fields */
316
317
    int                 i_mtu;
    srtp_session_t     *srtp;
318
319
    pf_rtp_packetizer_t pf_packetize;

320
    /* Packets sinks */
321
    vlc_mutex_t       lock_sink;
322
323
    int               sinkc;
    rtp_sink_t       *sinkv;
324
    rtsp_stream_id_t *rtsp_id;
325
    int              *listen_fd;
326
327
328

    block_fifo_t     *p_fifo;
    int64_t           i_caching;
329
330
};

331
332
333
334
335
336
/*****************************************************************************
 * Open:
 *****************************************************************************/
static int Open( vlc_object_t *p_this )
{
    sout_stream_t       *p_stream = (sout_stream_t*)p_this;
337
    sout_instance_t     *p_sout = p_stream->p_sout;
338
    sout_stream_sys_t   *p_sys = NULL;
339
    config_chain_t      *p_cfg = NULL;
340
    char                *psz;
341
    bool          b_rtsp = false;
342

343
344
    config_ChainParse( p_stream, SOUT_CFG_PREFIX,
                       ppsz_sout_options, p_stream->p_cfg );
345
346

    p_sys = malloc( sizeof( sout_stream_sys_t ) );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
347
348
    if( p_sys == NULL )
        return VLC_ENOMEM;
349

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
350
    p_sys->psz_destination = var_GetNonEmptyString( p_stream, SOUT_CFG_PREFIX "dst" );
351

352
353
354
    p_sys->i_port       = var_GetInteger( p_stream, SOUT_CFG_PREFIX "port" );
    p_sys->i_port_audio = var_GetInteger( p_stream, SOUT_CFG_PREFIX "port-audio" );
    p_sys->i_port_video = var_GetInteger( p_stream, SOUT_CFG_PREFIX "port-video" );
355
    p_sys->rtcp_mux   = var_GetBool( p_stream, SOUT_CFG_PREFIX "rtcp-mux" );
356

357
358
    p_sys->psz_sdp_file = NULL;

359
360
361
362
363
364
    if( p_sys->i_port_audio == p_sys->i_port_video )
    {
        msg_Err( p_stream, "audio and video port cannot be the same" );
        p_sys->i_port_audio = 0;
        p_sys->i_port_video = 0;
    }
365

366
    for( p_cfg = p_stream->p_cfg; p_cfg != NULL; p_cfg = p_cfg->p_next )
367
    {
368
369
370
        if( !strcmp( p_cfg->psz_name, "sdp" )
         && ( p_cfg->psz_value != NULL )
         && !strncasecmp( p_cfg->psz_value, "rtsp:", 5 ) )
371
        {
372
            b_rtsp = true;
373
            break;
374
        }
375
376
377
    }
    if( !b_rtsp )
    {
378
379
380
        psz = var_GetNonEmptyString( p_stream, SOUT_CFG_PREFIX "sdp" );
        if( psz != NULL )
        {
381
            if( !strncasecmp( psz, "rtsp:", 5 ) )
382
                b_rtsp = true;
383
384
            free( psz );
        }
385
    }
386

387
388
    /* Transport protocol */
    p_sys->proto = IPPROTO_UDP;
389
    psz = var_GetNonEmptyString (p_stream, SOUT_CFG_PREFIX"proto");
390

391
392
393
394
    if ((psz == NULL) || !strcasecmp (psz, "udp"))
        (void)0; /* default */
    else
    if (!strcasecmp (psz, "dccp"))
395
    {
396
        p_sys->proto = IPPROTO_DCCP;
397
        p_sys->rtcp_mux = true; /* Force RTP/RTCP mux */
398
    }
399
#if 0
400
    else
401
    if (!strcasecmp (psz, "sctp"))
402
403
    {
        p_sys->proto = IPPROTO_TCP;
404
        p_sys->rtcp_mux = true; /* Force RTP/RTCP mux */
405
    }
406
407
#endif
#if 0
408
    else
409
410
411
    if (!strcasecmp (psz, "tcp"))
    {
        p_sys->proto = IPPROTO_TCP;
412
        p_sys->rtcp_mux = true; /* Force RTP/RTCP mux */
413
    }
414
#endif
415
416
    else
    if (!strcasecmp (psz, "udplite") || !strcasecmp (psz, "udp-lite"))
417
        p_sys->proto = IPPROTO_UDPLITE;
418
419
420
421
    else
        msg_Warn (p_this, "unknown or unsupported transport protocol \"%s\"",
                  psz);
    free (psz);
422
    var_Create (p_this, "dccp-service", VLC_VAR_STRING);
423

424
    if( ( p_sys->psz_destination == NULL ) && !b_rtsp )
425
    {
426
        msg_Err( p_stream, "missing destination and not in RTSP mode" );
427
428
429
        free( p_sys );
        return VLC_EGENERIC;
    }
430

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
431
432
    p_sys->i_ttl = var_GetInteger( p_stream, SOUT_CFG_PREFIX "ttl" );
    if( p_sys->i_ttl == 0 )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
433
434
435
436
437
    {
        /* Normally, we should let the default hop limit up to the core,
         * but we have to know it to build our SDP properly, which is why
         * we ask the core. FIXME: broken when neither sout-rtp-ttl nor
         * ttl are set. */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
438
        p_sys->i_ttl = config_GetInt( p_stream, "ttl" );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
439
440
    }

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
441
    p_sys->b_latm = var_GetBool( p_stream, SOUT_CFG_PREFIX "mp4a-latm" );
442

443
    p_sys->i_payload_type = 96;
444
445
    p_sys->i_es = 0;
    p_sys->es   = NULL;
446
    p_sys->rtsp = NULL;
447
448
    p_sys->psz_sdp = NULL;

449
450
    p_sys->b_export_sap = false;
    p_sys->b_export_sdp_file = false;
zorglub's avatar
zorglub committed
451
452
    p_sys->p_session = NULL;

453
454
    p_sys->p_httpd_host = NULL;
    p_sys->p_httpd_file = NULL;
Laurent Aimar's avatar
Laurent Aimar committed
455
456

    p_stream->p_sys     = p_sys;
457

458
459
    vlc_mutex_init( &p_sys->lock_sdp );
    vlc_mutex_init( &p_sys->lock_es );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
460

461
462
    psz = var_GetNonEmptyString( p_stream, SOUT_CFG_PREFIX "mux" );
    if( psz != NULL )
463
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
464
        sout_stream_id_t *id;
465
466

        /* Check muxer type */
467
468
469
        if( strncasecmp( psz, "ps", 2 )
         && strncasecmp( psz, "mpeg1", 5 )
         && strncasecmp( psz, "ts", 2 ) )
470
        {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
471
            msg_Err( p_stream, "unsupported muxer type for RTP (only TS/PS)" );
472
            free( psz );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
473
474
            vlc_mutex_destroy( &p_sys->lock_sdp );
            vlc_mutex_destroy( &p_sys->lock_es );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
475
            free( p_sys );
476
477
478
            return VLC_EGENERIC;
        }

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
479
480
481
        p_sys->p_grab = GrabberCreate( p_stream );
        p_sys->p_mux = sout_MuxNew( p_sout, psz, p_sys->p_grab );
        free( psz );
482

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
483
        if( p_sys->p_mux == NULL )
484
        {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
485
486
487
488
489
490
            msg_Err( p_stream, "cannot create muxer" );
            sout_AccessOutDelete( p_sys->p_grab );
            vlc_mutex_destroy( &p_sys->lock_sdp );
            vlc_mutex_destroy( &p_sys->lock_es );
            free( p_sys );
            return VLC_EGENERIC;
491
        }
492

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
493
494
        id = Add( p_stream, NULL );
        if( id == NULL )
495
        {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
496
            sout_MuxDelete( p_sys->p_mux );
497
            sout_AccessOutDelete( p_sys->p_grab );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
498
499
            vlc_mutex_destroy( &p_sys->lock_sdp );
            vlc_mutex_destroy( &p_sys->lock_es );
500
501
502
503
504
            free( p_sys );
            return VLC_EGENERIC;
        }

        p_sys->packet = NULL;
505

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
506
507
508
        p_stream->pf_add  = MuxAdd;
        p_stream->pf_del  = MuxDel;
        p_stream->pf_send = MuxSend;
509
510
511
    }
    else
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
512
513
514
        p_sys->p_mux    = NULL;
        p_sys->p_grab   = NULL;

515
516
517
        p_stream->pf_add    = Add;
        p_stream->pf_del    = Del;
        p_stream->pf_send   = Send;
518
    }
519

520
521
522
    if( var_GetBool( p_stream, SOUT_CFG_PREFIX"sap" ) )
        SDPHandleUrl( p_stream, "sap" );

523
524
    psz = var_GetNonEmptyString( p_stream, SOUT_CFG_PREFIX "sdp" );
    if( psz != NULL )
525
    {
526
        config_chain_t *p_cfg;
527

528
        SDPHandleUrl( p_stream, psz );
529
530

        for( p_cfg = p_stream->p_cfg; p_cfg != NULL; p_cfg = p_cfg->p_next )
Laurent Aimar's avatar
Laurent Aimar committed
531
        {
532
            if( !strcmp( p_cfg->psz_name, "sdp" ) )
533
            {
534
535
536
                if( p_cfg->psz_value == NULL || *p_cfg->psz_value == '\0' )
                    continue;

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
537
                /* needed both :sout-rtp-sdp= and rtp{sdp=} can be used */
538
                if( !strcmp( p_cfg->psz_value, psz ) )
539
540
541
                    continue;

                SDPHandleUrl( p_stream, p_cfg->psz_value );
542
543
            }
        }
544
        free( psz );
545
546
    }

547
548
549
    /* update p_sout->i_out_pace_nocontrol */
    p_stream->p_sout->i_out_pace_nocontrol++;

550
551
552
553
554
555
556
557
558
559
560
    return VLC_SUCCESS;
}

/*****************************************************************************
 * Close:
 *****************************************************************************/
static void Close( vlc_object_t * p_this )
{
    sout_stream_t     *p_stream = (sout_stream_t*)p_this;
    sout_stream_sys_t *p_sys = p_stream->p_sys;

561
562
563
    /* update p_sout->i_out_pace_nocontrol */
    p_stream->p_sout->i_out_pace_nocontrol--;

564
565
    if( p_sys->p_mux )
    {
566
567
568
        assert( p_sys->i_es == 1 );
        Del( p_stream, p_sys->es[0] );

569
570
571
572
        sout_MuxDelete( p_sys->p_mux );
        sout_AccessOutDelete( p_sys->p_grab );
        if( p_sys->packet )
        {
573
            block_Release( p_sys->packet );
574
        }
575
        if( p_sys->b_export_sap )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
576
        {
577
578
579
            p_sys->p_mux = NULL;
            SapSetup( p_stream );
        }
580
581
    }

582
583
    if( p_sys->rtsp != NULL )
        RtspUnsetup( p_sys->rtsp );
584

585
    vlc_mutex_destroy( &p_sys->lock_sdp );
586
    vlc_mutex_destroy( &p_sys->lock_es );
587
588

    if( p_sys->p_httpd_file )
Laurent Aimar's avatar
Laurent Aimar committed
589
        httpd_FileDelete( p_sys->p_httpd_file );
Laurent Aimar's avatar
Laurent Aimar committed
590

591
    if( p_sys->p_httpd_host )
Laurent Aimar's avatar
Laurent Aimar committed
592
        httpd_HostDelete( p_sys->p_httpd_host );
Laurent Aimar's avatar
Laurent Aimar committed
593

594
    free( p_sys->psz_sdp );
Laurent Aimar's avatar
Laurent Aimar committed
595

Sam Hocevar's avatar
Sam Hocevar committed
596
597
598
599
600
601
602
    if( p_sys->b_export_sdp_file )
    {
#ifdef HAVE_UNISTD_H
        unlink( p_sys->psz_sdp_file );
#endif
        free( p_sys->psz_sdp_file );
    }
603
    free( p_sys->psz_destination );
604
605
606
    free( p_sys );
}

607
608
609
/*****************************************************************************
 * SDPHandleUrl:
 *****************************************************************************/
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
610
static void SDPHandleUrl( sout_stream_t *p_stream, const char *psz_url )
611
612
613
614
615
616
617
618
619
{
    sout_stream_sys_t *p_sys = p_stream->p_sys;
    vlc_url_t url;

    vlc_UrlParse( &url, psz_url, 0 );
    if( url.psz_protocol && !strcasecmp( url.psz_protocol, "http" ) )
    {
        if( p_sys->p_httpd_file )
        {
620
            msg_Err( p_stream, "you can use sdp=http:// only once" );
621
            goto out;
622
623
624
625
        }

        if( HttpSetup( p_stream, &url ) )
        {
626
            msg_Err( p_stream, "cannot export SDP as HTTP" );
627
628
629
630
        }
    }
    else if( url.psz_protocol && !strcasecmp( url.psz_protocol, "rtsp" ) )
    {
631
        if( p_sys->rtsp != NULL )
632
        {
633
            msg_Err( p_stream, "you can use sdp=rtsp:// only once" );
634
            goto out;
635
636
        }

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
637
        /* FIXME test if destination is multicast or no destination at all */
638
639
        p_sys->rtsp = RtspSetup( p_stream, &url );
        if( p_sys->rtsp == NULL )
640
        {
641
            msg_Err( p_stream, "cannot export SDP as RTSP" );
642
        }
643
644
645
646

        if( p_sys->p_mux != NULL )
        {
            sout_stream_id_t *id = p_sys->es[0];
647
            id->rtsp_id = RtspAddId( p_sys->rtsp, id, 0, GetDWBE( id->ssrc ),
648
649
650
                                     p_sys->psz_destination, p_sys->i_ttl,
                                     id->i_port, id->i_port + 1 );
        }
651
    }
652
    else if( ( url.psz_protocol && !strcasecmp( url.psz_protocol, "sap" ) ) ||
653
             ( url.psz_host && !strcasecmp( url.psz_host, "sap" ) ) )
654
    {
655
        p_sys->b_export_sap = true;
656
657
658
659
660
661
        SapSetup( p_stream );
    }
    else if( url.psz_protocol && !strcasecmp( url.psz_protocol, "file" ) )
    {
        if( p_sys->b_export_sdp_file )
        {
662
            msg_Err( p_stream, "you can use sdp=file:// only once" );
663
            goto out;
664
        }
665
        p_sys->b_export_sdp_file = true;
666
667
668
669
670
671
672
673
674
675
        psz_url = &psz_url[5];
        if( psz_url[0] == '/' && psz_url[1] == '/' )
            psz_url += 2;
        p_sys->psz_sdp_file = strdup( psz_url );
    }
    else
    {
        msg_Warn( p_stream, "unknown protocol for SDP (%s)",
                  url.psz_protocol );
    }
676
677

out:
678
679
680
    vlc_UrlClean( &url );
}

681
/*****************************************************************************
682
 * SDPGenerate
683
 *****************************************************************************/
684
685
/*static*/
char *SDPGenerate( const sout_stream_t *p_stream, const char *rtsp_url )
686
{
687
    const sout_stream_sys_t *p_sys = p_stream->p_sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
688
689
690
    char *psz_sdp;
    struct sockaddr_storage dst;
    socklen_t dstlen;
691
    int i;
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
    /*
     * When we have a fixed destination (typically when we do multicast),
     * we need to put the actual port numbers in the SDP.
     * When there is no fixed destination, we only support RTSP unicast
     * on-demand setup, so we should rather let the clients decide which ports
     * to use.
     * When there is both a fixed destination and RTSP unicast, we need to
     * put port numbers used by the fixed destination, otherwise the SDP would
     * become totally incorrect for multicast use. It should be noted that
     * port numbers from SDP with RTSP are only "recommendation" from the
     * server to the clients (per RFC2326), so only broken clients will fail
     * to handle this properly. There is no solution but to use two differents
     * output chain with two different RTSP URLs if you need to handle this
     * scenario.
     */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
707
708
709
    int inclport;

    if( p_sys->psz_destination != NULL )
710
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
711
        inclport = 1;
712

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
713
714
        /* Oh boy, this is really ugly! (+ race condition on lock_es) */
        dstlen = sizeof( dst );
715
716
717
718
719
720
        if( p_sys->es[0]->listen_fd != NULL )
            getsockname( p_sys->es[0]->listen_fd[0],
                         (struct sockaddr *)&dst, &dstlen );
        else
            getpeername( p_sys->es[0]->sinkv[0].rtp_fd,
                         (struct sockaddr *)&dst, &dstlen );
721
    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
722
    else
723
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
724
725
726
727
728
729
730
731
732
        inclport = 0;

        /* Dummy destination address for RTSP */
        memset (&dst, 0, sizeof( struct sockaddr_in ) );
        dst.ss_family = AF_INET;
#ifdef HAVE_SA_LEN
        dst.ss_len =
#endif
        dstlen = sizeof( struct sockaddr_in );
733
    }
734

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
735
736
    psz_sdp = vlc_sdp_Start( VLC_OBJECT( p_stream ), SOUT_CFG_PREFIX,
                             NULL, 0, (struct sockaddr *)&dst, dstlen );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
737
738
739
740
    if( psz_sdp == NULL )
        return NULL;

    /* TODO: a=source-filter */
741
742
    if( p_sys->rtcp_mux )
        sdp_AddAttribute( &psz_sdp, "rtcp-mux", NULL );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
743

744
745
746
    if( rtsp_url != NULL )
        sdp_AddAttribute ( &psz_sdp, "control", "%s", rtsp_url );

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
747
    /* FIXME: locking?! */
748
749
750
    for( i = 0; i < p_sys->i_es; i++ )
    {
        sout_stream_id_t *id = p_sys->es[i];
751
        const char *mime_major; /* major MIME type */
752
        const char *proto = "RTP/AVP"; /* protocol */
753

754
755
756
757
758
759
760
761
762
763
764
765
        switch( id->i_cat )
        {
            case VIDEO_ES:
                mime_major = "video";
                break;
            case AUDIO_ES:
                mime_major = "audio";
                break;
            case SPU_ES:
                mime_major = "text";
                break;
            default:
766
                continue;
767
        }
768

769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
        if( rtsp_url == NULL )
        {
            switch( p_sys->proto )
            {
                case IPPROTO_UDP:
                    break;
                case IPPROTO_TCP:
                    proto = "TCP/RTP/AVP";
                    break;
                case IPPROTO_DCCP:
                    proto = "DCCP/RTP/AVP";
                    break;
                case IPPROTO_UDPLITE:
                    continue;
            }
        }

        sdp_AddMedia( &psz_sdp, mime_major, proto, inclport * id->i_port,
787
                      id->i_payload_type, false, id->i_bitrate,
788
789
                      id->psz_enc, id->i_clock_rate, id->i_channels,
                      id->psz_fmtp);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
790

791
        if( rtsp_url != NULL )
792
793
        {
            assert( strlen( rtsp_url ) > 0 );
794
            bool addslash = ( rtsp_url[strlen( rtsp_url ) - 1] != '/' );
795
796
            sdp_AddAttribute ( &psz_sdp, "control",
                               addslash ? "%s/trackID=%u" : "%strackID=%u",
797
                               rtsp_url, i );
798
        }
799
800
801
802
803
        else
        {
            if( id->listen_fd != NULL )
                sdp_AddAttribute( &psz_sdp, "setup", "passive" );
            if( p_sys->proto == IPPROTO_DCCP )
804
805
                sdp_AddAttribute( &psz_sdp, "dccp-service-code", 
                                  "SC:RTP%c", toupper( mime_major[0] ) );
806
        }
807
808
    }

Laurent Aimar's avatar
Laurent Aimar committed
809
    return psz_sdp;
810
811
812
}

/*****************************************************************************
813
 * RTP mux
814
815
816
817
 *****************************************************************************/

static void sprintf_hexa( char *s, uint8_t *p_data, int i_data )
{
818
    static const char hex[16] = "0123456789abcdef";
819
820
821
822
    int i;

    for( i = 0; i < i_data; i++ )
    {
823
        s[2*i+0] = hex[(p_data[i]>>4)&0xf];
824
825
826
827
        s[2*i+1] = hex[(p_data[i]   )&0xf];
    }
    s[2*i_data] = '\0';
}
828

829
830
831
832
833
834
835
836
837
838
839
840
841
842
/**
 * Shrink the MTU down to a fixed packetization time (for audio).
 */
static void
rtp_set_ptime (sout_stream_id_t *id, unsigned ptime_ms, size_t bytes)
{
    /* Samples per second */
    size_t spl = (id->i_clock_rate - 1) * ptime_ms / 1000 + 1;
    bytes *= id->i_channels;
    spl *= bytes;

    if (spl < rtp_mtu (id)) /* MTU is big enough for ptime */
        id->i_mtu = 12 + spl;
    else /* MTU is too small for ptime, align to a sample boundary */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
843
        id->i_mtu = 12 + (((id->i_mtu - 12) / bytes) * bytes);
844
}
845
846

/** Add an ES as a new RTP stream */
gbazin's avatar
   
gbazin committed
847
static sout_stream_id_t *Add( sout_stream_t *p_stream, es_format_t *p_fmt )
848
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
849
850
    /* NOTE: As a special case, if we use a non-RTP
     * mux (TS/PS), then p_fmt is NULL. */
851
852
    sout_stream_sys_t *p_sys = p_stream->p_sys;
    sout_stream_id_t  *id;
853
    int               i_port, cscov = -1;
854
    char              *psz_sdp;
855

856
857
858
    id = vlc_object_create( p_stream, sizeof( sout_stream_id_t ) );
    if( id == NULL )
        return NULL;
859
    vlc_object_attach( id, p_stream );
860

861
862
    /* Choose the port */
    i_port = 0;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
863
864
865
    if( p_fmt == NULL )
        ;
    else
866
867
868
869
870
    if( p_fmt->i_cat == AUDIO_ES && p_sys->i_port_audio > 0 )
    {
        i_port = p_sys->i_port_audio;
        p_sys->i_port_audio = 0;
    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
871
872
    else
    if( p_fmt->i_cat == VIDEO_ES && p_sys->i_port_video > 0 )
873
874
875
876
    {
        i_port = p_sys->i_port_video;
        p_sys->i_port_video = 0;
    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
877

878
879
    while( i_port == 0 )
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
880
881
        if( p_sys->i_port != p_sys->i_port_audio
         && p_sys->i_port != p_sys->i_port_video )
882
883
884
885
886
887
888
889
        {
            i_port = p_sys->i_port;
            p_sys->i_port += 2;
            break;
        }
        p_sys->i_port += 2;
    }

890
    id->p_stream   = p_stream;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
891
892
893
894
895
896
897
898

    id->i_sequence = rand()&0xffff;
    id->i_payload_type = p_sys->i_payload_type;
    id->ssrc[0] = rand()&0xff;
    id->ssrc[1] = rand()&0xff;
    id->ssrc[2] = rand()&0xff;
    id->ssrc[3] = rand()&0xff;

899
    id->psz_enc    = NULL;
900
    id->psz_fmtp   = NULL;
901
902
    id->i_clock_rate = 90000; /* most common case for video */
    id->i_channels = 0;
903
    id->i_port     = i_port;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
904
905
906
    if( p_fmt != NULL )
    {
        id->i_cat  = p_fmt->i_cat;
907
908
909
910
911
        if( p_fmt->i_cat == AUDIO_ES )
        {
            id->i_clock_rate = p_fmt->audio.i_rate;
            id->i_channels = p_fmt->audio.i_channels;
        }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
912
913
914
915
916
917
918
919
920
921
922
923
        id->i_bitrate = p_fmt->i_bitrate/1000; /* Stream bitrate in kbps */
    }
    else
    {
        id->i_cat  = VIDEO_ES;
        id->i_bitrate = 0;
    }

    id->i_mtu = config_GetInt( p_stream, "mtu" );
    if( id->i_mtu <= 12 + 16 )
        id->i_mtu = 576 - 20 - 8; /* pessimistic */
    msg_Dbg( p_stream, "maximum RTP packet size: %d bytes", id->i_mtu );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
924

925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
    id->srtp = NULL;
    id->pf_packetize = NULL;

    char *key = var_CreateGetNonEmptyString (p_stream, SOUT_CFG_PREFIX"key");
    if (key)
    {
        id->srtp = srtp_create (SRTP_ENCR_AES_CM, SRTP_AUTH_HMAC_SHA1, 10,
                                   SRTP_PRF_AES_CM, SRTP_RCC_MODE1);
        if (id->srtp == NULL)
        {
            free (key);
            goto error;
        }

        char *salt = var_CreateGetNonEmptyString (p_stream, SOUT_CFG_PREFIX"salt");
        errno = srtp_setkeystring (id->srtp, key, salt ? salt : "");
        free (salt);
        free (key);
        if (errno)
        {
            msg_Err (p_stream, "bad SRTP key/salt combination (%m)");
            goto error;
        }