rtp.c 81 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
 * $Id$
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
27
28
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
#include <stdlib.h>

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
29
30
#include <errno.h>

31
32
33
34
#include <vlc/vlc.h>
#include <vlc/input.h>
#include <vlc/sout.h>

Laurent Aimar's avatar
Laurent Aimar committed
35
#include "vlc_httpd.h"
36
#include "vlc_url.h"
37
#include "network.h"
38
#include "charset.h"
39

40
41
42
/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
43
44
45

#define MTU_REDUCE 50

46
47
#define DST_TEXT N_("Destination")
#define DST_LONGTEXT N_( \
48
    "This is the output URL that will be used." )
49
50
#define SDP_TEXT N_("SDP")
#define SDP_LONGTEXT N_( \
51
52
53
54
    "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." )
55
56
#define MUX_TEXT N_("Muxer")
#define MUX_LONGTEXT N_( \
57
58
    "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
59
60
61

#define NAME_TEXT N_("Session name")
#define NAME_LONGTEXT N_( \
62
63
    "This is the name of the session that will be announced in the SDP " \
    "(Session Descriptor)." )
hartman's avatar
hartman committed
64
65
#define DESC_TEXT N_("Session description")
#define DESC_LONGTEXT N_( \
66
67
    "This allows you to give a broader description of the stream, that will " \
    "be announced in the SDP (Session Descriptor)." )
hartman's avatar
hartman committed
68
69
#define URL_TEXT N_("Session URL")
#define URL_LONGTEXT N_( \
70
71
72
    "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
73
74
#define EMAIL_TEXT N_("Session email")
#define EMAIL_LONGTEXT N_( \
75
76
   "This allows you to give a contact mail address for the stream, that will " \
   "be announced in the SDP (Session Descriptor)." )
77
78
#define PORT_TEXT N_("Port")
#define PORT_LONGTEXT N_( \
79
    "This allows you to specify the base port for the RTP streaming." )
80
81
#define PORT_AUDIO_TEXT N_("Audio port")
#define PORT_AUDIO_LONGTEXT N_( \
82
    "This allows you to specify the default audio port for the RTP streaming." )
83
84
#define PORT_VIDEO_TEXT N_("Video port")
#define PORT_VIDEO_LONGTEXT N_( \
85
    "This allows you to specify the default video port for the RTP streaming." )
86

Felix Paul Kühne's avatar
Felix Paul Kühne committed
87
#define TTL_TEXT N_("Time-To-Live (TTL)")
88
#define TTL_LONGTEXT N_( \
89
    "This allows you to specify the Time-To-Live for the output stream." )
90

91
#define RFC3016_TEXT N_("MP4A LATM")
92
#define RFC3016_LONGTEXT N_( \
93
    "This allows you to stream MPEG4 LATM audio streams (see RFC3016)." )
94

95
96
97
static int  Open ( vlc_object_t * );
static void Close( vlc_object_t * );

98
99
#define SOUT_CFG_PREFIX "sout-rtp-"

100
vlc_module_begin();
101
    set_shortname( _("RTP"));
gbazin's avatar
   
gbazin committed
102
    set_description( _("RTP stream output") );
103
104
    set_capability( "sout stream", 0 );
    add_shortcut( "rtp" );
zorglub's avatar
zorglub committed
105
106
    set_category( CAT_SOUT );
    set_subcategory( SUBCAT_SOUT_STREAM );
107

108
109
110
111
112
113
    add_string( SOUT_CFG_PREFIX "dst", "", NULL, DST_TEXT,
                DST_LONGTEXT, VLC_TRUE );
    add_string( SOUT_CFG_PREFIX "sdp", "", NULL, SDP_TEXT,
                SDP_LONGTEXT, VLC_TRUE );
    add_string( SOUT_CFG_PREFIX "mux", "", NULL, MUX_TEXT,
                MUX_LONGTEXT, VLC_TRUE );
114
115

    add_string( SOUT_CFG_PREFIX "name", "NONE", NULL, NAME_TEXT,
hartman's avatar
hartman committed
116
117
118
119
120
121
122
                NAME_LONGTEXT, VLC_TRUE );
    add_string( SOUT_CFG_PREFIX "description", "", NULL, DESC_TEXT,
                DESC_LONGTEXT, VLC_TRUE );
    add_string( SOUT_CFG_PREFIX "url", "", NULL, URL_TEXT,
                URL_LONGTEXT, VLC_TRUE );
    add_string( SOUT_CFG_PREFIX "email", "", NULL, EMAIL_TEXT,
                EMAIL_LONGTEXT, VLC_TRUE );
123

124
    add_integer( SOUT_CFG_PREFIX "port", 1234, NULL, PORT_TEXT,
125
                 PORT_LONGTEXT, VLC_TRUE );
126
127
128
129
    add_integer( SOUT_CFG_PREFIX "port-audio", 1230, NULL, PORT_AUDIO_TEXT,
                 PORT_AUDIO_LONGTEXT, VLC_TRUE );
    add_integer( SOUT_CFG_PREFIX "port-video", 1232, NULL, PORT_VIDEO_TEXT,
                 PORT_VIDEO_LONGTEXT, VLC_TRUE );
130

Marian Durkovic's avatar
Marian Durkovic committed
131
    add_integer( SOUT_CFG_PREFIX "ttl", 0, NULL, TTL_TEXT,
132
                 TTL_LONGTEXT, VLC_TRUE );
133

134
    add_bool( SOUT_CFG_PREFIX "mp4a-latm", 0, NULL, RFC3016_TEXT,
135
                 RFC3016_LONGTEXT, VLC_FALSE );
136

137
138
139
140
141
142
    set_callbacks( Open, Close );
vlc_module_end();

/*****************************************************************************
 * Exported prototypes
 *****************************************************************************/
143
static const char *ppsz_sout_options[] = {
144
    "dst", "name", "port", "port-audio", "port-video", "*sdp", "ttl", "mux",
145
    "description", "url","email", "mp4a-latm", NULL
146
147
};

148
static sout_stream_id_t *Add ( sout_stream_t *, es_format_t * );
149
static int               Del ( sout_stream_t *, sout_stream_id_t * );
gbazin's avatar
   
gbazin committed
150
static int               Send( sout_stream_t *, sout_stream_id_t *,
151
                               block_t* );
152

153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
/* For unicast/interleaved streaming */
typedef struct
{
    char    *psz_session;
    int64_t i_last; /* for timeout */

    /* is it in "play" state */
    vlc_bool_t b_playing;

    /* output (id-access) */
    int               i_id;
    sout_stream_id_t  **id;
    int               i_access;
    sout_access_out_t **access;
} rtsp_client_t;

169
170
struct sout_stream_sys_t
{
171
172
173
    /* sdp */
    int64_t i_sdp_id;
    int     i_sdp_version;
174
175
176
    char    *psz_sdp;
    vlc_mutex_t  lock_sdp;

zorglub's avatar
zorglub committed
177
    char        *psz_session_name;
hartman's avatar
hartman committed
178
179
180
    char        *psz_session_description;
    char        *psz_session_url;
    char        *psz_session_email;
zorglub's avatar
zorglub committed
181

182
183
    /* */
    vlc_bool_t b_export_sdp_file;
184
    char *psz_sdp_file;
zorglub's avatar
zorglub committed
185
186
187
188
    /* sap */
    vlc_bool_t b_export_sap;
    session_descriptor_t *p_session;

189
190
    httpd_host_t *p_httpd_host;
    httpd_file_t *p_httpd_file;
191

Laurent Aimar's avatar
Laurent Aimar committed
192
193
194
195
196
    httpd_host_t *p_rtsp_host;
    httpd_url_t  *p_rtsp_url;
    char         *psz_rtsp_control;
    char         *psz_rtsp_path;

197
198
199
    /* */
    char *psz_destination;
    int  i_port;
200
201
    int  i_port_audio;
    int  i_port_video;
202
    int  i_ttl;
203
    vlc_bool_t b_latm;
204

205
    /* when need to use a private one or when using muxer */
206
207
208
    int i_payload_type;

    /* in case we do TS/PS over rtp */
209
210
211
212
213
214
215
    sout_mux_t        *p_mux;
    sout_access_out_t *p_access;
    int               i_mtu;
    sout_access_out_t *p_grab;
    uint16_t          i_sequence;
    uint32_t          i_timestamp_start;
    uint8_t           ssrc[4];
hartman's avatar
hartman committed
216
    block_t           *packet;
217
218

    /* */
219
    vlc_mutex_t      lock_es;
220
221
    int              i_es;
    sout_stream_id_t **es;
222
223
224
225

    /* */
    int              i_rtsp;
    rtsp_client_t    **rtsp;
226
227
};

gbazin's avatar
   
gbazin committed
228
typedef int (*pf_rtp_packetizer_t)( sout_stream_t *, sout_stream_id_t *,
229
                                    block_t * );
gbazin's avatar
   
gbazin committed
230

231
232
struct sout_stream_id_t
{
233
    sout_stream_t *p_stream;
234
235
236
237
238
239
240
241
242
243
244
245
246
    /* rtp field */
    uint8_t     i_payload_type;
    uint16_t    i_sequence;
    uint32_t    i_timestamp_start;
    uint8_t     ssrc[4];

    /* for sdp */
    int         i_clock_rate;
    char        *psz_rtpmap;
    char        *psz_fmtp;
    char        *psz_destination;
    int         i_port;
    int         i_cat;
247
    int         i_bitrate;
248
249
250
251
252
253
254

    /* Packetizer specific fields */
    pf_rtp_packetizer_t pf_packetize;
    int           i_mtu;

    /* for sending the packets */
    sout_access_out_t *p_access;
255
256
257
258
259
260

    vlc_mutex_t       lock_rtsp;
    int               i_rtsp_access;
    sout_access_out_t **rtsp_access;

    /* */
261
    sout_input_t      *p_input;
Laurent Aimar's avatar
Laurent Aimar committed
262
263
264

    /* RTSP url control */
    httpd_url_t  *p_rtsp_url;
265
};
Laurent Aimar's avatar
Laurent Aimar committed
266

267
static int AccessOutGrabberWrite( sout_access_out_t *, block_t * );
268

269
270
static void SDPHandleUrl( sout_stream_t *, char * );

zorglub's avatar
zorglub committed
271
static int SapSetup( sout_stream_t *p_stream );
272
static int FileSetup( sout_stream_t *p_stream );
Laurent Aimar's avatar
Laurent Aimar committed
273
274
275
276
277
static int HttpSetup( sout_stream_t *p_stream, vlc_url_t * );
static int RtspSetup( sout_stream_t *p_stream, vlc_url_t * );

static int  RtspCallback( httpd_callback_sys_t *, httpd_client_t *,
                          httpd_message_t *, httpd_message_t * );
278
279
static int  RtspCallbackId( httpd_callback_sys_t *, httpd_client_t *,
                            httpd_message_t *, httpd_message_t * );
Laurent Aimar's avatar
Laurent Aimar committed
280

281

282
283
284
285
static rtsp_client_t *RtspClientNew( sout_stream_t *, char *psz_session );
static rtsp_client_t *RtspClientGet( sout_stream_t *, char *psz_session );
static void           RtspClientDel( sout_stream_t *, rtsp_client_t * );

286
287
288
289
290
291
/*****************************************************************************
 * Open:
 *****************************************************************************/
static int Open( vlc_object_t *p_this )
{
    sout_stream_t       *p_stream = (sout_stream_t*)p_this;
292
    sout_instance_t     *p_sout = p_stream->p_sout;
293
294
    sout_stream_sys_t   *p_sys = NULL;
    sout_cfg_t          *p_cfg = NULL;
295
    vlc_value_t         val;
296
    vlc_bool_t          b_rtsp = VLC_FALSE;
297

Laurent Aimar's avatar
Laurent Aimar committed
298
    sout_CfgParse( p_stream, SOUT_CFG_PREFIX, ppsz_sout_options, p_stream->p_cfg );
299
300

    p_sys = malloc( sizeof( sout_stream_sys_t ) );
301

302
303
304
305
306
307
308
309
310
311
312
    p_sys->psz_destination = var_GetString( p_stream, SOUT_CFG_PREFIX "dst" );
    if( *p_sys->psz_destination == '\0' )
    {
        free( p_sys->psz_destination );
        p_sys->psz_destination = NULL;
    }

    p_sys->psz_session_name = var_GetString( p_stream, SOUT_CFG_PREFIX "name" );
    p_sys->psz_session_description = var_GetString( p_stream, SOUT_CFG_PREFIX "description" );
    p_sys->psz_session_url = var_GetString( p_stream, SOUT_CFG_PREFIX "url" );
    p_sys->psz_session_email = var_GetString( p_stream, SOUT_CFG_PREFIX "email" );
313

314
315
316
    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" );
317

318
319
    p_sys->psz_sdp_file = NULL;

320
321
322
323
324
325
    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;
    }
326

zorglub's avatar
zorglub committed
327
328
329
330
331
332
333
    if( !p_sys->psz_session_name )
    {
        if( p_sys->psz_destination )
            p_sys->psz_session_name = strdup( p_sys->psz_destination );
        else
           p_sys->psz_session_name = strdup( "NONE" );
    }
334

335
    for( p_cfg = p_stream->p_cfg; p_cfg != NULL; p_cfg = p_cfg->p_next )
336
    {
337
        if( !strcmp( p_cfg->psz_name, "sdp" ) )
338
        {
339
            if( p_cfg->psz_value && !strncasecmp( p_cfg->psz_value, "rtsp", 4 ) )
340
            {
341
342
                b_rtsp = VLC_TRUE;
                break;
343
344
            }
        }
345
346
347
348
349
350
351
352
353
    }
    if( !b_rtsp )
    {
        vlc_value_t val2;
        var_Get( p_stream, SOUT_CFG_PREFIX "sdp", &val2 );
        if( !strncasecmp( val2.psz_string, "rtsp", 4 ) )
            b_rtsp = VLC_TRUE;
        free( val2.psz_string );
    }
354

355
356
357
    if( !p_sys->psz_destination || *p_sys->psz_destination == '\0' )
    {
        if( !b_rtsp )
358
359
360
361
362
363
364
365
366
367
        {
            msg_Err( p_stream, "missing destination and not in rtsp mode" );
            free( p_sys );
            return VLC_EGENERIC;
        }
        p_sys->psz_destination = NULL;
    }
    else if( p_sys->i_port <= 0 )
    {
        msg_Err( p_stream, "invalid port" );
368
369
370
        free( p_sys );
        return VLC_EGENERIC;
    }
371

372
    var_Get( p_stream, SOUT_CFG_PREFIX "ttl", &val );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
373
374
375
376
377
378
    if( val.i_int == 0 )
    {
        /* 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. */
379
        val.i_int = config_GetInt( p_stream, "ttl" );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
380
    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
381
    if( val.i_int > 255 ) val.i_int = 255;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
382
383
    /* must not exceed 999 once formatted */

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
384
    if( val.i_int < 0 )
385
386
387
388
389
    {
        msg_Err( p_stream, "illegal TTL %d", val.i_int );
        free( p_sys );
        return VLC_EGENERIC;
    }
390
    p_sys->i_ttl = val.i_int;
391

392

393
394
    var_Get( p_stream, SOUT_CFG_PREFIX "mp4a-latm", &val );
    p_sys->b_latm = val.b_bool;
395

396
    p_sys->i_payload_type = 96;
397
398
    p_sys->i_es = 0;
    p_sys->es   = NULL;
399
400
    p_sys->i_rtsp = 0;
    p_sys->rtsp   = NULL;
401
402
403
404
    p_sys->psz_sdp = NULL;

    p_sys->i_sdp_id = mdate();
    p_sys->i_sdp_version = 1;
405
    p_sys->psz_sdp = NULL;
zorglub's avatar
zorglub committed
406
407

    p_sys->b_export_sap = VLC_FALSE;
408
    p_sys->b_export_sdp_file = VLC_FALSE;
zorglub's avatar
zorglub committed
409
410
    p_sys->p_session = NULL;

411
412
    p_sys->p_httpd_host = NULL;
    p_sys->p_httpd_file = NULL;
Laurent Aimar's avatar
Laurent Aimar committed
413
414
415
416
417
418
    p_sys->p_rtsp_host  = NULL;
    p_sys->p_rtsp_url   = NULL;
    p_sys->psz_rtsp_control = NULL;
    p_sys->psz_rtsp_path = NULL;

    vlc_mutex_init( p_stream, &p_sys->lock_sdp );
419
    vlc_mutex_init( p_stream, &p_sys->lock_es );
Laurent Aimar's avatar
Laurent Aimar committed
420
421
422
423
424
425

    p_stream->pf_add    = Add;
    p_stream->pf_del    = Del;
    p_stream->pf_send   = Send;

    p_stream->p_sys     = p_sys;
426

427
428
    var_Get( p_stream, SOUT_CFG_PREFIX "mux", &val );
    if( *val.psz_string )
429
430
    {
        sout_access_out_t *p_grab;
431
        char *psz_rtpmap, url[NI_MAXHOST + 8], access[17], psz_ttl[5], ipv;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
432

433
434
        if( b_rtsp )
        {
435
436
437
            msg_Err( p_stream, "muxing is not supported in RTSP mode" );
            free( p_sys );
            return VLC_EGENERIC;
438
439
        }
        else if( !p_sys->psz_destination || *p_sys->psz_destination == '\0' )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
440
441
442
443
444
        {
            msg_Err( p_stream, "rtp needs a destination when muxing" );
            free( p_sys );
            return VLC_EGENERIC;
        }
445
446

        /* Check muxer type */
447
        if( !strncasecmp( val.psz_string, "ps", 2 ) || !strncasecmp( val.psz_string, "mpeg1", 5 ) )
448
449
450
        {
            psz_rtpmap = "MP2P/90000";
        }
451
        else if( !strncasecmp( val.psz_string, "ts", 2 ) )
452
453
454
455
456
457
458
        {
            psz_rtpmap = "MP2T/90000";
            p_sys->i_payload_type = 33;
        }
        else
        {
            msg_Err( p_stream, "unsupported muxer type with rtp (only ts/ps)" );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
459
            free( p_sys );
460
461
462
463
464
465
466
467
468
469
470
471
            return VLC_EGENERIC;
        }

        /* create the access out */
        if( p_sys->i_ttl > 0 )
        {
            sprintf( access, "udp{raw,ttl=%d}", p_sys->i_ttl );
        }
        else
        {
            sprintf( access, "udp{raw}" );
        }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
472
473
474
475
476
477
478
479

        /* IPv6 needs brackets if not already present */
        snprintf( url, sizeof( url ),
                  ( ( p_sys->psz_destination[0] != '[' ) 
                 && ( strchr( p_sys->psz_destination, ':' ) != NULL ) )
                  ? "[%s]:%d" : "%s:%d", p_sys->psz_destination,
                  p_sys->i_port );
        url[sizeof( url ) - 1] = '\0';
480
481
        /* FIXME: we should check that url is a numerical address, otherwise
         * the SDP will be quite broken (regardless of the IP protocol version)
482
         * Also it might be IPv6 with no ':' if it is a DNS name.
483
484
         */
        ipv = ( strchr( p_sys->psz_destination, ':' ) != NULL ) ? '6' : '4';
485

gbazin's avatar
   
gbazin committed
486
        if( !( p_sys->p_access = sout_AccessOutNew( p_sout, access, url ) ) )
487
488
489
490
491
492
493
        {
            msg_Err( p_stream, "cannot create the access out for %s://%s",
                     access, url );
            free( p_sys );
            return VLC_EGENERIC;
        }
        p_sys->i_mtu = config_GetInt( p_stream, "mtu" );  /* XXX beurk */
494
        if( p_sys->i_mtu <= 16 + MTU_REDUCE )
495
496
497
498
        {
            /* better than nothing */
            p_sys->i_mtu = 1500;
        }
499
        p_sys->i_mtu -= MTU_REDUCE;
500

501
        /* the access out grabber TODO export it as sout_AccessOutGrabberNew */
gbazin's avatar
   
gbazin committed
502
503
        p_grab = p_sys->p_grab =
            vlc_object_create( p_sout, sizeof( sout_access_out_t ) );
504
505
506
507
508
509
510
511
512
513
        p_grab->p_module    = NULL;
        p_grab->p_sout      = p_sout;
        p_grab->psz_access  = strdup( "grab" );
        p_grab->p_cfg       = NULL;
        p_grab->psz_name    = strdup( "" );
        p_grab->p_sys       = (sout_access_out_sys_t*)p_stream;
        p_grab->pf_seek     = NULL;
        p_grab->pf_write    = AccessOutGrabberWrite;

        /* the muxer */
514
        if( !( p_sys->p_mux = sout_MuxNew( p_sout, val.psz_string, p_sys->p_grab ) ) )
515
        {
516
            msg_Err( p_stream, "cannot create the muxer (%s)", val.psz_string );
517
518
519
520
521
522
            sout_AccessOutDelete( p_sys->p_grab );
            sout_AccessOutDelete( p_sys->p_access );
            free( p_sys );
            return VLC_EGENERIC;
        }

hartman's avatar
hartman committed
523
524
525
526
527
528
529
530
531
532
533
534
535
536
        /* create the SDP for a muxed stream (only once) */
        /* FIXME  http://www.faqs.org/rfcs/rfc2327.html
           All text fields should be UTF-8 encoded. Use global a:charset to announce this.
           o= - should be local username (no spaces allowed)
           o= time should be hashed with some other value to garantue uniqueness
           o= we need IP6 support?
           o= don't use the localhost address. use fully qualified domain name or IP4 address
           p= international phone number (pass via vars?)
           c= IP6 support
           a= recvonly (missing)
           a= type:broadcast (missing)
           a= charset: (normally charset should be UTF-8, this can be used to override s= and i=)
           a= x-plgroup: (missing)
           RTP packets need to get the correct src IP address  */
537
538
        if( net_AddressIsMulticast( (vlc_object_t *)p_stream, p_sys->psz_destination ) )
        {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
539
            snprintf( psz_ttl, sizeof( psz_ttl ), "/%d", p_sys->i_ttl );
540
            psz_ttl[sizeof( psz_ttl ) - 1] = '\0';
541
542
543
544
545
546
        }
        else
        {
            psz_ttl[0] = '\0'; 
        }

547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
        asprintf( &p_sys->psz_sdp,
                  "v=0\r\n"
                  /* FIXME: source address not known :( */
                  "o=- "I64Fd" %d IN IP%c %s\r\n"
                  "s=%s\r\n"
                  "i=%s\r\n"
                  "u=%s\r\n"
                  "e=%s\r\n"
                  "t=0 0\r\n" /* permanent stream */ /* when scheduled from vlm, we should set this info correctly */
                  "a=tool:"PACKAGE_STRING"\r\n"
                  "c=IN IP%c %s%s\r\n"
                  "m=video %d RTP/AVP %d\r\n"
                  "a=rtpmap:%d %s\r\n",
                  p_sys->i_sdp_id, p_sys->i_sdp_version,
                  ipv, ipv == '6' ? "::1" : "127.0.0.1" /* FIXME */,
                  p_sys->psz_session_name,
                  p_sys->psz_session_description,
                  p_sys->psz_session_url,
                  p_sys->psz_session_email,
                  ipv, p_sys->psz_destination, psz_ttl,
                  p_sys->i_port, p_sys->i_payload_type,
                  p_sys->i_payload_type, psz_rtpmap );
569
        msg_Dbg( p_stream, "sdp=%s", p_sys->psz_sdp );
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585

        /* create the rtp context */
        p_sys->ssrc[0] = rand()&0xff;
        p_sys->ssrc[1] = rand()&0xff;
        p_sys->ssrc[2] = rand()&0xff;
        p_sys->ssrc[3] = rand()&0xff;
        p_sys->i_sequence = rand()&0xffff;
        p_sys->i_timestamp_start = rand()&0xffffffff;
        p_sys->packet = NULL;
    }
    else
    {
        p_sys->p_mux    = NULL;
        p_sys->p_access = NULL;
        p_sys->p_grab   = NULL;
    }
586
587
    free( val.psz_string );

588

589
590
    var_Get( p_stream, SOUT_CFG_PREFIX "sdp", &val );
    if( *val.psz_string )
591
    {
592
        sout_cfg_t *p_cfg;
593

594
595
596
        SDPHandleUrl( p_stream, val.psz_string );

        for( p_cfg = p_stream->p_cfg; p_cfg != NULL; p_cfg = p_cfg->p_next )
Laurent Aimar's avatar
Laurent Aimar committed
597
        {
598
            if( !strcmp( p_cfg->psz_name, "sdp" ) )
599
            {
600
601
602
603
604
605
606
                if( p_cfg->psz_value == NULL || *p_cfg->psz_value == '\0' )
                    continue;

                if( !strcmp( p_cfg->psz_value, val.psz_string ) )   /* needed both :sout-rtp-sdp= and rtp{sdp=} can be used */
                    continue;

                SDPHandleUrl( p_stream, p_cfg->psz_value );
607
608
609
            }
        }
    }
610
    free( val.psz_string );
611

612
613
614
    /* update p_sout->i_out_pace_nocontrol */
    p_stream->p_sout->i_out_pace_nocontrol++;

615
616
617
618
619
620
621
622
623
624
625
    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;

626
627
628
    /* update p_sout->i_out_pace_nocontrol */
    p_stream->p_sout->i_out_pace_nocontrol--;

629
630
631
632
633
634
635
    if( p_sys->p_mux )
    {
        sout_MuxDelete( p_sys->p_mux );
        sout_AccessOutDelete( p_sys->p_access );
        sout_AccessOutDelete( p_sys->p_grab );
        if( p_sys->packet )
        {
636
            block_Release( p_sys->packet );
637
        }
638
639
640
641
642
        if( p_sys->b_export_sap )
        {   
            p_sys->p_mux = NULL;
            SapSetup( p_stream );
        }
643
644
    }

645
646
647
648
649
    while( p_sys->i_rtsp > 0 )
    {
        RtspClientDel( p_stream, p_sys->rtsp[0] );
    }

650
651
652
653
    vlc_mutex_destroy( &p_sys->lock_sdp );

    if( p_sys->p_httpd_file )
    {
Laurent Aimar's avatar
Laurent Aimar committed
654
        httpd_FileDelete( p_sys->p_httpd_file );
655
656
657
    }
    if( p_sys->p_httpd_host )
    {
Laurent Aimar's avatar
Laurent Aimar committed
658
        httpd_HostDelete( p_sys->p_httpd_host );
659
    }
Laurent Aimar's avatar
Laurent Aimar committed
660
    if( p_sys->p_rtsp_url )
661
    {
Laurent Aimar's avatar
Laurent Aimar committed
662
663
664
665
666
        httpd_UrlDelete( p_sys->p_rtsp_url );
    }
    if( p_sys->p_rtsp_host )
    {
        httpd_HostDelete( p_sys->p_rtsp_host );
667
    }
zorglub's avatar
zorglub committed
668
669
670
671
672
    if( p_sys->psz_session_name )
    {
        free( p_sys->psz_session_name );
        p_sys->psz_session_name = NULL;
    }
hartman's avatar
hartman committed
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
    if( p_sys->psz_session_description )
    {
        free( p_sys->psz_session_description );
        p_sys->psz_session_description = NULL;
    }
    if( p_sys->psz_session_url )
    {
        free( p_sys->psz_session_url );
        p_sys->psz_session_url = NULL;
    }
    if( p_sys->psz_session_email )
    {
        free( p_sys->psz_session_email );
        p_sys->psz_session_email = NULL;
    }
688
689
690
    if( p_sys->psz_sdp )
    {
        free( p_sys->psz_sdp );
691
        p_sys->psz_sdp = NULL;
692
    }
693
694
695
    free( p_sys );
}

696
697
698
699
700
701
702
703
704
705
706
707
708
/*****************************************************************************
 * SDPHandleUrl:
 *****************************************************************************/
static void SDPHandleUrl( sout_stream_t *p_stream, char *psz_url )
{
    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 )
        {
709
            msg_Err( p_stream, "you can use sdp=http:// only once" );
710
711
712
713
714
715
716
717
718
719
720
721
            return;
        }

        if( HttpSetup( p_stream, &url ) )
        {
            msg_Err( p_stream, "cannot export sdp as http" );
        }
    }
    else if( url.psz_protocol && !strcasecmp( url.psz_protocol, "rtsp" ) )
    {
        if( p_sys->p_rtsp_url )
        {
722
            msg_Err( p_stream, "you can use sdp=rtsp:// only once" );
723
724
725
726
727
728
729
730
731
            return;
        }

        /* FIXME test if destination is multicast or no destination at all FIXME */
        if( RtspSetup( p_stream, &url ) )
        {
            msg_Err( p_stream, "cannot export sdp as rtsp" );
        }
    }
732
733
    else if( ( url.psz_protocol && !strcasecmp( url.psz_protocol, "sap" ) ) || 
             ( url.psz_host && !strcasecmp( url.psz_host, "sap" ) ) )
734
735
736
737
738
739
740
741
    {
        p_sys->b_export_sap = VLC_TRUE;
        SapSetup( p_stream );
    }
    else if( url.psz_protocol && !strcasecmp( url.psz_protocol, "file" ) )
    {
        if( p_sys->b_export_sdp_file )
        {
742
            msg_Err( p_stream, "you can use sdp=file:// only once" );
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
            return;
        }
        p_sys->b_export_sdp_file = VLC_TRUE;
        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 );
    }
    vlc_UrlClean( &url );
}

759
/*****************************************************************************
760
 * SDPGenerate
761
 *****************************************************************************/
hartman's avatar
hartman committed
762
763
764
765
766
767
768
769
770
771
772
773
774
        /* FIXME  http://www.faqs.org/rfcs/rfc2327.html
           All text fields should be UTF-8 encoded. Use global a:charset to announce this.
           o= - should be local username (no spaces allowed)
           o= time should be hashed with some other value to garantue uniqueness
           o= we need IP6 support?
           o= don't use the localhost address. use fully qualified domain name or IP4 address
           p= international phone number (pass via vars?)
           c= IP6 support
           a= recvonly (missing)
           a= type:broadcast (missing)
           a= charset: (normally charset should be UTF-8, this can be used to override s= and i=)
           a= x-plgroup: (missing)
           RTP packets need to get the correct src IP address  */
775
776
static char *SDPGenerate( const sout_stream_t *p_stream,
                          const char *psz_destination, vlc_bool_t b_rtsp )
777
{
778
779
    sout_stream_sys_t *p_sys = p_stream->p_sys;
    int i_size;
780
    char *psz_sdp, *p, ipv;
781
782
    int i;

783
784
785
786
787
788
789
790
791
792
793
794
795
796
    /* FIXME: breaks IP version check on unknown destination */
    if( psz_destination == NULL )
        psz_destination = "0.0.0.0";

    i_size = sizeof( "v=0\r\n" ) +
             sizeof( "o=- * * IN IP4 127.0.0.1\r\n" ) + 10 + 10 +
             sizeof( "s=*\r\n" ) + strlen( p_sys->psz_session_name ) +
             sizeof( "i=*\r\n" ) + strlen( p_sys->psz_session_description ) +
             sizeof( "u=*\r\n" ) + strlen( p_sys->psz_session_url ) +
             sizeof( "e=*\r\n" ) + strlen( p_sys->psz_session_email ) +
             sizeof( "t=0 0\r\n" ) + /* permanent stream */ /* when scheduled from vlm, we should set this info correctly */
             sizeof( "a=tool:"PACKAGE_STRING"\r\n" ) +
             sizeof( "c=IN IP4 */*\r\n" ) + 20 + 10 +
             strlen( psz_destination ) ;
797
798
799
    for( i = 0; i < p_sys->i_es; i++ )
    {
        sout_stream_id_t *id = p_sys->es[i];
800

hartman's avatar
hartman committed
801
        i_size += strlen( "m=**d*o * RTP/AVP *\r\n" ) + 10 + 10;
802
803
        if( id->psz_rtpmap )
        {
hartman's avatar
hartman committed
804
            i_size += strlen( "a=rtpmap:* *\r\n" ) + strlen( id->psz_rtpmap )+10;
805
806
807
        }
        if( id->psz_fmtp )
        {
hartman's avatar
hartman committed
808
            i_size += strlen( "a=fmtp:* *\r\n" ) + strlen( id->psz_fmtp ) + 10;
809
        }
810
811
812
813
        if ( id->i_bitrate)
        {
            i_size += strlen( "b=AS: *\r\n") + 10;
        }
Laurent Aimar's avatar
Laurent Aimar committed
814
815
        if( b_rtsp )
        {
816
            i_size += strlen( "a=control:*/trackID=*\r\n" ) + strlen( p_sys->psz_rtsp_control ) + 10;
Laurent Aimar's avatar
Laurent Aimar committed
817
        }
818
    }
819
820
821
822
    if( p_sys->p_mux )
    {
        i_size += strlen( "m=video %d RTP/AVP %d\r\n" ) +10 +10;
    }
823

824
825
    ipv = ( strchr( psz_destination, ':' ) != NULL ) ? '6' : '4';

826
    p = psz_sdp = malloc( i_size );
hartman's avatar
hartman committed
827
    p += sprintf( p, "v=0\r\n" );
828
829
830
    p += sprintf( p, "o=- "I64Fd" %d IN IP%c %s\r\n",
                  p_sys->i_sdp_id, p_sys->i_sdp_version,
                  ipv, ipv == '6' ? "::" : "127.0.0.1" );
831
832
833
    if( *p_sys->psz_session_name )
        p += sprintf( p, "s=%s\r\n", p_sys->psz_session_name );
    if( *p_sys->psz_session_description )
hartman's avatar
hartman committed
834
        p += sprintf( p, "i=%s\r\n", p_sys->psz_session_description );
835
    if( *p_sys->psz_session_url )
hartman's avatar
hartman committed
836
        p += sprintf( p, "u=%s\r\n", p_sys->psz_session_url );
837
    if( *p_sys->psz_session_email )
hartman's avatar
hartman committed
838
        p += sprintf( p, "e=%s\r\n", p_sys->psz_session_email );
839

hartman's avatar
hartman committed
840
841
842
    p += sprintf( p, "t=0 0\r\n" ); /* permanent stream */ /* when scheduled from vlm, we should set this info correctly */
    p += sprintf( p, "a=tool:"PACKAGE_STRING"\r\n" );

843
    p += sprintf( p, "c=IN IP%c %s", ipv, psz_destination );
844

845
    if( net_AddressIsMulticast( (vlc_object_t *)p_stream, psz_destination ) )
846
847
    {
        /* Add the ttl if it is a multicast address */
848
849
        /* FIXME: 1 is not a correct default value in the case of IPv6 */
        p += sprintf( p, "/%d\r\n", p_sys->i_ttl ?: 1 );
850
851
852
853
854
    }
    else
    {
        p += sprintf( p, "\r\n" );
    }
855

856
857
858
859
860
861
    for( i = 0; i < p_sys->i_es; i++ )
    {
        sout_stream_id_t *id = p_sys->es[i];

        if( id->i_cat == AUDIO_ES )
        {
hartman's avatar
hartman committed
862
            p += sprintf( p, "m=audio %d RTP/AVP %d\r\n",
863
864
865
866
                          id->i_port, id->i_payload_type );
        }
        else if( id->i_cat == VIDEO_ES )
        {
hartman's avatar
hartman committed
867
            p += sprintf( p, "m=video %d RTP/AVP %d\r\n",
868
869
870
871
872
873
874
875
                          id->i_port, id->i_payload_type );
        }
        else
        {
            continue;
        }
        if( id->psz_rtpmap )
        {
hartman's avatar
hartman committed
876
            p += sprintf( p, "a=rtpmap:%d %s\r\n", id->i_payload_type,
gbazin's avatar
   
gbazin committed
877
                          id->psz_rtpmap );
878
879
880
        }
        if( id->psz_fmtp )
        {
hartman's avatar
hartman committed
881
            p += sprintf( p, "a=fmtp:%d %s\r\n", id->i_payload_type,
gbazin's avatar
   
gbazin committed
882
                          id->psz_fmtp );
883
        }
884
885
886
887
        if ( id->i_bitrate)
        {
            p += sprintf(p,"b=AS:%d\r\n",id->i_bitrate);
        }
Laurent Aimar's avatar
Laurent Aimar committed
888
889
        if( b_rtsp )
        {
890
            p += sprintf( p, "a=control:trackID=%d\r\n", i );
Laurent Aimar's avatar
Laurent Aimar committed
891
        }
892
    }
893
894
895
896
897
    if( p_sys->p_mux )
    {
       p += sprintf( p, "m=video %d RTP/AVP %d\r\n",
                 p_sys->i_port, p_sys->i_payload_type );
    }
898

Laurent Aimar's avatar
Laurent Aimar committed
899
    return psz_sdp;
900
901
902
903
904
}

/*****************************************************************************
 *
 *****************************************************************************/
905
906
907
908
909
910
911
static int rtp_packetize_l16  ( sout_stream_t *, sout_stream_id_t *, block_t * );
static int rtp_packetize_l8   ( sout_stream_t *, sout_stream_id_t *, block_t * );
static int rtp_packetize_mpa  ( sout_stream_t *, sout_stream_id_t *, block_t * );
static int rtp_packetize_mpv  ( sout_stream_t *, sout_stream_id_t *, block_t * );
static int rtp_packetize_ac3  ( sout_stream_t *, sout_stream_id_t *, block_t * );
static int rtp_packetize_split( sout_stream_t *, sout_stream_id_t *, block_t * );
static int rtp_packetize_mp4a ( sout_stream_t *, sout_stream_id_t *, block_t * );
912
static int rtp_packetize_mp4a_latm ( sout_stream_t *, sout_stream_id_t *, block_t * );
913
static int rtp_packetize_h263 ( sout_stream_t *, sout_stream_id_t *, block_t * );
914
static int rtp_packetize_h264 ( sout_stream_t *, sout_stream_id_t *, block_t * );
915
static int rtp_packetize_amr  ( sout_stream_t *, sout_stream_id_t *, block_t * );
916
917
918

static void sprintf_hexa( char *s, uint8_t *p_data, int i_data )
{
919
    static const char hex[16] = "0123456789abcdef";
920
921
922
923
    int i;

    for( i = 0; i < i_data; i++ )
    {
924
        s[2*i+0] = hex[(p_data[i]>>4)&0xf];
925
926
927
928
        s[2*i+1] = hex[(p_data[i]   )&0xf];
    }
    s[2*i_data] = '\0';
}
929

gbazin's avatar
   
gbazin committed
930
static sout_stream_id_t *Add( sout_stream_t *p_stream, es_format_t *p_fmt )
931
932
933
934
{
    sout_instance_t   *p_sout = p_stream->p_sout;
    sout_stream_sys_t *p_sys = p_stream->p_sys;
    sout_stream_id_t  *id;
935
    sout_access_out_t *p_access = NULL;
936
937
    int               i_port;
    char              *psz_sdp;
938

939
940
941
942
943
944
945
946
947
948
    if( p_sys->p_mux != NULL )
    {
        sout_input_t      *p_input  = NULL;
        if( ( p_input = sout_MuxAddStream( p_sys->p_mux, p_fmt ) ) == NULL )
        {
            msg_Err( p_stream, "cannot add this stream to the muxer" );
            return NULL;
        }

        id = malloc( sizeof( sout_stream_id_t ) );
949
        memset( id, 0, sizeof( sout_stream_id_t ) );
950
951
952
        id->p_access    = NULL;
        id->p_input     = p_input;
        id->pf_packetize= NULL;
953
        id->p_rtsp_url  = NULL;
954
        id->i_port      = 0;
955
956
957
        return id;
    }

958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981

    /* Choose the port */
    i_port = 0;
    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;
    }
    else if( p_fmt->i_cat == VIDEO_ES && p_sys->i_port_video > 0 )
    {
        i_port = p_sys->i_port_video;
        p_sys->i_port_video = 0;
    }
    while( i_port == 0 )
    {
        if( p_sys->i_port != p_sys->i_port_audio && p_sys->i_port != p_sys->i_port_video )
        {
            i_port = p_sys->i_port;
            p_sys->i_port += 2;
            break;
        }
        p_sys->i_port += 2;
    }

982
    if( p_sys->psz_destination )
983
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
984
        char access[17];
985
        char url[NI_MAXHOST + 8];
986
987

        /* first try to create the access out */
988
        if( p_sys->i_ttl )
989
        {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
990
991
992
            snprintf( access, sizeof( access ), "udp{raw,ttl=%d}",
                      p_sys->i_ttl );
            access[sizeof( access ) - 1] = '\0';
993
994
        }
        else
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
995
996
            strcpy( access, "udp{raw}" );

997
998
        snprintf( url, sizeof( url ), (( p_sys->psz_destination[0] != '[' ) &&
                 strchr( p_sys->psz_destination, ':' )) ? "[%s]:%d" : "%s:%d",
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
999
1000
                 p_sys->psz_destination, i_port );
        url[sizeof( url ) - 1] = '\0';