rtp.c 73.2 KB
Newer Older
1
/*****************************************************************************
Gildas Bazin's avatar
 
Gildas Bazin 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
Antoine Cellerier's avatar
Antoine Cellerier 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 "network.h"
37
#include "charset.h"
38

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

#define MTU_REDUCE 50

45 46 47 48 49
#define DST_TEXT N_("Destination")
#define DST_LONGTEXT N_( \
    "Allows you to specify the output URL used for the streaming output." )
#define SDP_TEXT N_("SDP")
#define SDP_LONGTEXT N_( \
Clément Stenac's avatar
Clément Stenac committed
50 51 52
    "Allows you to specify the SDP used for the streaming output. " \
    "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 " \
53
    "announced via SAP." )
54 55 56
#define MUX_TEXT N_("Muxer")
#define MUX_LONGTEXT N_( \
    "Allows you to specify the muxer used for the streaming output." )
57 58 59 60 61 62 63 64 65 66 67 68 69 70

#define NAME_TEXT N_("Session name")
#define NAME_LONGTEXT N_( \
    "Allows you to specify the session name used for the streaming output." )
#define DESC_TEXT N_("Session description")
#define DESC_LONGTEXT N_( \
    "Allows you to give a broader description of the stream." )
#define URL_TEXT N_("Session URL")
#define URL_LONGTEXT N_( \
    "Allows you to specify a URL with additional information on the stream." )
#define EMAIL_TEXT N_("Session email")
#define EMAIL_LONGTEXT N_( \
    "Allows you to specify contact e-mail address for this session." )

71 72
#define PORT_TEXT N_("Port")
#define PORT_LONGTEXT N_( \
73
    "Allows you to specify the base port used for the RTP streaming." )
74 75 76 77 78 79 80
#define PORT_AUDIO_TEXT N_("Audio port")
#define PORT_AUDIO_LONGTEXT N_( \
    "Allows you to specify the default audio port used for the RTP streaming." )
#define PORT_VIDEO_TEXT N_("Video port")
#define PORT_VIDEO_LONGTEXT N_( \
    "Allows you to specify the default video port used for the RTP streaming." )

Felix Paul Kühne's avatar
Felix Paul Kühne committed
81
#define TTL_TEXT N_("Time-To-Live (TTL)")
82
#define TTL_LONGTEXT N_( \
Felix Paul Kühne's avatar
Felix Paul Kühne committed
83
    "Allows you to specify the Time-To-Live for the output stream." )
84

85 86 87
static int  Open ( vlc_object_t * );
static void Close( vlc_object_t * );

88 89
#define SOUT_CFG_PREFIX "sout-rtp-"

90
vlc_module_begin();
91
    set_shortname( _("RTP"));
Gildas Bazin's avatar
 
Gildas Bazin committed
92
    set_description( _("RTP stream output") );
93 94
    set_capability( "sout stream", 0 );
    add_shortcut( "rtp" );
Clément Stenac's avatar
Clément Stenac committed
95 96
    set_category( CAT_SOUT );
    set_subcategory( SUBCAT_SOUT_STREAM );
97

98 99 100 101 102 103
    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 );
104 105

    add_string( SOUT_CFG_PREFIX "name", "NONE", NULL, NAME_TEXT,
106 107 108 109 110 111 112
                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 );
113

114
    add_integer( SOUT_CFG_PREFIX "port", 1234, NULL, PORT_TEXT,
115
                 PORT_LONGTEXT, VLC_TRUE );
116 117 118 119
    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 );
120

Marian Durkovic's avatar
Marian Durkovic committed
121
    add_integer( SOUT_CFG_PREFIX "ttl", 0, NULL, TTL_TEXT,
122
                 TTL_LONGTEXT, VLC_TRUE );
123

124 125 126 127 128 129
    set_callbacks( Open, Close );
vlc_module_end();

/*****************************************************************************
 * Exported prototypes
 *****************************************************************************/
130
static const char *ppsz_sout_options[] = {
131
    "dst", "name", "port", "port-audio", "port-video", "*sdp", "ttl", "mux",
132
    "description", "url","email", NULL
133 134
};

135
static sout_stream_id_t *Add ( sout_stream_t *, es_format_t * );
136
static int               Del ( sout_stream_t *, sout_stream_id_t * );
Gildas Bazin's avatar
 
Gildas Bazin committed
137
static int               Send( sout_stream_t *, sout_stream_id_t *,
138
                               block_t* );
139

140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
/* 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;

156 157
struct sout_stream_sys_t
{
158 159 160
    /* sdp */
    int64_t i_sdp_id;
    int     i_sdp_version;
161 162 163
    char    *psz_sdp;
    vlc_mutex_t  lock_sdp;

164
    char        *psz_session_name;
165 166 167
    char        *psz_session_description;
    char        *psz_session_url;
    char        *psz_session_email;
168

169 170
    /* */
    vlc_bool_t b_export_sdp_file;
171
    char *psz_sdp_file;
172 173 174 175
    /* sap */
    vlc_bool_t b_export_sap;
    session_descriptor_t *p_session;

176 177
    httpd_host_t *p_httpd_host;
    httpd_file_t *p_httpd_file;
178

Laurent Aimar's avatar
Laurent Aimar committed
179 180 181 182 183
    httpd_host_t *p_rtsp_host;
    httpd_url_t  *p_rtsp_url;
    char         *psz_rtsp_control;
    char         *psz_rtsp_path;

184 185 186
    /* */
    char *psz_destination;
    int  i_port;
187 188
    int  i_port_audio;
    int  i_port_video;
189 190
    int  i_ttl;

191
    /* when need to use a private one or when using muxer */
192 193 194
    int i_payload_type;

    /* in case we do TS/PS over rtp */
195 196 197 198 199 200 201
    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];
202
    block_t           *packet;
203 204

    /* */
205
    vlc_mutex_t      lock_es;
206 207
    int              i_es;
    sout_stream_id_t **es;
208 209 210 211

    /* */
    int              i_rtsp;
    rtsp_client_t    **rtsp;
212 213
};

Gildas Bazin's avatar
 
Gildas Bazin committed
214
typedef int (*pf_rtp_packetizer_t)( sout_stream_t *, sout_stream_id_t *,
215
                                    block_t * );
Gildas Bazin's avatar
 
Gildas Bazin committed
216

217 218
struct sout_stream_id_t
{
219
    sout_stream_t *p_stream;
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
    /* 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;

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

    /* for sending the packets */
    sout_access_out_t *p_access;
240 241 242 243 244 245

    vlc_mutex_t       lock_rtsp;
    int               i_rtsp_access;
    sout_access_out_t **rtsp_access;

    /* */
246
    sout_input_t      *p_input;
Laurent Aimar's avatar
Laurent Aimar committed
247 248 249

    /* RTSP url control */
    httpd_url_t  *p_rtsp_url;
250
};
Laurent Aimar's avatar
Laurent Aimar committed
251

252
static int AccessOutGrabberWrite( sout_access_out_t *, block_t * );
253

254 255
static void SDPHandleUrl( sout_stream_t *, char * );

256
static int SapSetup( sout_stream_t *p_stream );
257
static int FileSetup( sout_stream_t *p_stream );
Laurent Aimar's avatar
Laurent Aimar committed
258 259 260 261 262
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 * );
263 264
static int  RtspCallbackId( httpd_callback_sys_t *, httpd_client_t *,
                            httpd_message_t *, httpd_message_t * );
Laurent Aimar's avatar
Laurent Aimar committed
265

266

267 268 269 270
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 * );

271 272 273 274 275 276
/*****************************************************************************
 * Open:
 *****************************************************************************/
static int Open( vlc_object_t *p_this )
{
    sout_stream_t       *p_stream = (sout_stream_t*)p_this;
277
    sout_instance_t     *p_sout = p_stream->p_sout;
278
    sout_stream_sys_t   *p_sys;
279
    vlc_value_t         val;
280

Laurent Aimar's avatar
Laurent Aimar committed
281
    sout_CfgParse( p_stream, SOUT_CFG_PREFIX, ppsz_sout_options, p_stream->p_cfg );
282 283

    p_sys = malloc( sizeof( sout_stream_sys_t ) );
284

285 286 287 288 289 290 291 292 293 294 295
    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" );
296

297 298 299
    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" );
300

301 302
    p_sys->psz_sdp_file = NULL;

303 304 305 306 307 308
    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;
    }
309

310 311 312 313 314 315 316 317
    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" );
    }

318
    if( !p_sys->psz_destination || *p_sys->psz_destination == '\0' )
319
    {
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
        sout_cfg_t *p_cfg;
        vlc_bool_t b_ok = VLC_FALSE;

        for( p_cfg = p_stream->p_cfg; p_cfg != NULL; p_cfg = p_cfg->p_next )
        {
            if( !strcmp( p_cfg->psz_name, "sdp" ) )
            {
                if( p_cfg->psz_value && !strncasecmp( p_cfg->psz_value, "rtsp", 4 ) )
                {
                    b_ok = VLC_TRUE;
                    break;
                }
            }
        }
        if( !b_ok )
        {
            vlc_value_t val2;
            var_Get( p_stream, SOUT_CFG_PREFIX "sdp", &val2 );
            if( !strncasecmp( val2.psz_string, "rtsp", 4 ) )
                b_ok = VLC_TRUE;
            free( val2.psz_string );
        }
342

343
        if( !b_ok )
344 345 346 347 348 349 350 351 352 353
        {
            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" );
354 355 356
        free( p_sys );
        return VLC_EGENERIC;
    }
357

358
    var_Get( p_stream, SOUT_CFG_PREFIX "ttl", &val );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
359 360 361 362 363 364
    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. */
365
        val.i_int = config_GetInt( p_stream, "ttl" );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
366 367 368 369 370
    }
    if( p_sys->i_ttl > 255 ) p_sys->i_ttl = 255;
    /* must not exceed 999 once formatted */

    if( p_sys->i_ttl < 0 )
371 372 373 374 375
    {
        msg_Err( p_stream, "illegal TTL %d", val.i_int );
        free( p_sys );
        return VLC_EGENERIC;
    }
376
    p_sys->i_ttl = val.i_int;
377 378

    p_sys->i_payload_type = 96;
379 380
    p_sys->i_es = 0;
    p_sys->es   = NULL;
381 382
    p_sys->i_rtsp = 0;
    p_sys->rtsp   = NULL;
383 384 385 386
    p_sys->psz_sdp = NULL;

    p_sys->i_sdp_id = mdate();
    p_sys->i_sdp_version = 1;
387
    p_sys->psz_sdp = NULL;
388 389

    p_sys->b_export_sap = VLC_FALSE;
390
    p_sys->b_export_sdp_file = VLC_FALSE;
391 392
    p_sys->p_session = NULL;

393 394
    p_sys->p_httpd_host = NULL;
    p_sys->p_httpd_file = NULL;
Laurent Aimar's avatar
Laurent Aimar committed
395 396 397 398 399 400
    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 );
401
    vlc_mutex_init( p_stream, &p_sys->lock_es );
Laurent Aimar's avatar
Laurent Aimar committed
402 403 404 405 406 407

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

    p_stream->p_sys     = p_sys;
408

409 410
    var_Get( p_stream, SOUT_CFG_PREFIX "mux", &val );
    if( *val.psz_string )
411 412
    {
        sout_access_out_t *p_grab;
413
        char *psz_rtpmap, url[NI_MAXHOST + 8], access[17], psz_ttl[5], ipv;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
414

Marian Durkovic's avatar
Marian Durkovic committed
415
        if( !p_sys->psz_destination || *p_sys->psz_destination == '\0' )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
416 417 418 419 420
        {
            msg_Err( p_stream, "rtp needs a destination when muxing" );
            free( p_sys );
            return VLC_EGENERIC;
        }
421 422

        /* Check muxer type */
423
        if( !strncasecmp( val.psz_string, "ps", 2 ) || !strncasecmp( val.psz_string, "mpeg1", 5 ) )
424 425 426
        {
            psz_rtpmap = "MP2P/90000";
        }
427
        else if( !strncasecmp( val.psz_string, "ts", 2 ) )
428 429 430 431 432 433 434
        {
            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
435
            free( p_sys );
436 437 438 439 440 441 442 443 444 445 446 447
            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
448 449 450 451 452 453 454 455

        /* 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';
456 457
        /* FIXME: we should check that url is a numerical address, otherwise
         * the SDP will be quite broken (regardless of the IP protocol version)
458
         * Also it might be IPv6 with no ':' if it is a DNS name.
459 460
         */
        ipv = ( strchr( p_sys->psz_destination, ':' ) != NULL ) ? '6' : '4';
461

Gildas Bazin's avatar
 
Gildas Bazin committed
462
        if( !( p_sys->p_access = sout_AccessOutNew( p_sout, access, url ) ) )
463 464 465 466 467 468 469
        {
            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 */
470
        if( p_sys->i_mtu <= 16 + MTU_REDUCE )
471 472 473 474
        {
            /* better than nothing */
            p_sys->i_mtu = 1500;
        }
475
        p_sys->i_mtu -= MTU_REDUCE;
476

477
        /* the access out grabber TODO export it as sout_AccessOutGrabberNew */
Gildas Bazin's avatar
 
Gildas Bazin committed
478 479
        p_grab = p_sys->p_grab =
            vlc_object_create( p_sout, sizeof( sout_access_out_t ) );
480 481 482 483 484 485 486 487 488 489
        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 */
490
        if( !( p_sys->p_mux = sout_MuxNew( p_sout, val.psz_string, p_sys->p_grab ) ) )
491
        {
492
            msg_Err( p_stream, "cannot create the muxer (%s)", val.psz_string );
493 494 495 496 497 498
            sout_AccessOutDelete( p_sys->p_grab );
            sout_AccessOutDelete( p_sys->p_access );
            free( p_sys );
            return VLC_EGENERIC;
        }

499 500 501 502 503 504 505 506 507 508 509 510 511 512
        /* 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  */
513 514
        if( net_AddressIsMulticast( (vlc_object_t *)p_stream, p_sys->psz_destination ) )
        {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
515
            snprintf( psz_ttl, sizeof( psz_ttl ), "/%d", p_sys->i_ttl );
516
            psz_ttl[sizeof( psz_ttl ) - 1] = '\0';
517 518 519 520 521 522
        }
        else
        {
            psz_ttl[0] = '\0'; 
        }

523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544
        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 );
545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561
        fprintf( stderr, "sdp=%s", p_sys->psz_sdp );

        /* 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;
    }
562 563
    free( val.psz_string );

564

565 566
    var_Get( p_stream, SOUT_CFG_PREFIX "sdp", &val );
    if( *val.psz_string )
567
    {
568
        sout_cfg_t *p_cfg;
569

570 571 572
        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
573
        {
574
            if( !strcmp( p_cfg->psz_name, "sdp" ) )
575
            {
576 577 578 579 580 581 582
                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 );
583 584 585
            }
        }
    }
586
    free( val.psz_string );
587

588 589 590
    /* update p_sout->i_out_pace_nocontrol */
    p_stream->p_sout->i_out_pace_nocontrol++;

591 592 593 594 595 596 597 598 599 600 601
    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;

602 603 604
    /* update p_sout->i_out_pace_nocontrol */
    p_stream->p_sout->i_out_pace_nocontrol--;

605 606 607 608 609 610 611
    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 )
        {
612
            block_Release( p_sys->packet );
613
        }
614 615 616 617 618
        if( p_sys->b_export_sap )
        {   
            p_sys->p_mux = NULL;
            SapSetup( p_stream );
        }
619 620
    }

621 622 623 624 625
    while( p_sys->i_rtsp > 0 )
    {
        RtspClientDel( p_stream, p_sys->rtsp[0] );
    }

626 627 628 629
    vlc_mutex_destroy( &p_sys->lock_sdp );

    if( p_sys->p_httpd_file )
    {
Laurent Aimar's avatar
Laurent Aimar committed
630
        httpd_FileDelete( p_sys->p_httpd_file );
631 632 633
    }
    if( p_sys->p_httpd_host )
    {
Laurent Aimar's avatar
Laurent Aimar committed
634
        httpd_HostDelete( p_sys->p_httpd_host );
635
    }
Laurent Aimar's avatar
Laurent Aimar committed
636
    if( p_sys->p_rtsp_url )
637
    {
Laurent Aimar's avatar
Laurent Aimar committed
638 639 640 641 642
        httpd_UrlDelete( p_sys->p_rtsp_url );
    }
    if( p_sys->p_rtsp_host )
    {
        httpd_HostDelete( p_sys->p_rtsp_host );
643
    }
644
#if 0
645
    /* why? is this disabled? */
646 647 648 649 650
    if( p_sys->psz_session_name )
    {
        free( p_sys->psz_session_name );
        p_sys->psz_session_name = NULL;
    }
651 652 653 654 655 656 657 658 659 660 661 662 663 664 665
    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;
    }
666
#endif
667 668 669 670
    if( p_sys->psz_sdp )
    {
        free( p_sys->psz_sdp );
    }
671 672 673
    free( p_sys );
}

674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709
/*****************************************************************************
 * 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 )
        {
            msg_Err( p_stream, "You can used sdp=http:// only once" );
            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 )
        {
            msg_Err( p_stream, "You can used sdp=rtsp:// only once" );
            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" );
        }
    }
710 711
    else if( ( url.psz_protocol && !strcasecmp( url.psz_protocol, "sap" ) ) || 
             ( url.psz_host && !strcasecmp( url.psz_host, "sap" ) ) )
712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736
    {
        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 )
        {
            msg_Err( p_stream, "You can used sdp=file:// only once" );
            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 );
}

737
/*****************************************************************************
738
 * SDPGenerate
739
 *****************************************************************************/
740 741 742 743 744 745 746 747 748 749 750 751 752
        /* 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  */
753 754
static char *SDPGenerate( const sout_stream_t *p_stream,
                          const char *psz_destination, vlc_bool_t b_rtsp )
755
{
756
    sout_stream_sys_t *p_sys = p_stream->p_sys;
Marian Durkovic's avatar
Marian Durkovic committed
757
    sout_instance_t  *p_sout = p_stream->p_sout;
758
    int i_size;
759
    char *psz_sdp, *p, ipv;
760 761
    int i;

762 763 764 765 766 767 768 769 770 771 772 773 774 775
    /* 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 ) ;
776 777 778
    for( i = 0; i < p_sys->i_es; i++ )
    {
        sout_stream_id_t *id = p_sys->es[i];
779

780
        i_size += strlen( "m=**d*o * RTP/AVP *\r\n" ) + 10 + 10;
781 782
        if( id->psz_rtpmap )
        {
783
            i_size += strlen( "a=rtpmap:* *\r\n" ) + strlen( id->psz_rtpmap )+10;
784 785 786
        }
        if( id->psz_fmtp )
        {
787
            i_size += strlen( "a=fmtp:* *\r\n" ) + strlen( id->psz_fmtp ) + 10;
788
        }
Laurent Aimar's avatar
Laurent Aimar committed
789 790
        if( b_rtsp )
        {
791
            i_size += strlen( "a=control:*/trackid=*\r\n" ) + strlen( p_sys->psz_rtsp_control ) + 10;
Laurent Aimar's avatar
Laurent Aimar committed
792
        }
793
    }
794

795 796
    ipv = ( strchr( psz_destination, ':' ) != NULL ) ? '6' : '4';

797
    p = psz_sdp = malloc( i_size );
798
    p += sprintf( p, "v=0\r\n" );
799 800 801
    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" );
802 803 804
    if( *p_sys->psz_session_name )
        p += sprintf( p, "s=%s\r\n", p_sys->psz_session_name );
    if( *p_sys->psz_session_description )
805
        p += sprintf( p, "i=%s\r\n", p_sys->psz_session_description );
806
    if( *p_sys->psz_session_url )
807
        p += sprintf( p, "u=%s\r\n", p_sys->psz_session_url );
808
    if( *p_sys->psz_session_email )
809
        p += sprintf( p, "e=%s\r\n", p_sys->psz_session_email );
810

811 812 813
    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" );

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

816
    if( net_AddressIsMulticast( (vlc_object_t *)p_stream, psz_destination ) )
817 818
    {
        /* Add the ttl if it is a multicast address */
819 820
        /* FIXME: 1 is not a correct default value in the case of IPv6 */
        p += sprintf( p, "/%d\r\n", p_sys->i_ttl ?: 1 );
821 822 823 824 825
    }
    else
    {
        p += sprintf( p, "\r\n" );
    }
826

827 828 829 830 831 832
    for( i = 0; i < p_sys->i_es; i++ )
    {
        sout_stream_id_t *id = p_sys->es[i];

        if( id->i_cat == AUDIO_ES )
        {
833
            p += sprintf( p, "m=audio %d RTP/AVP %d\r\n",
834 835 836 837
                          id->i_port, id->i_payload_type );
        }
        else if( id->i_cat == VIDEO_ES )
        {
838
            p += sprintf( p, "m=video %d RTP/AVP %d\r\n",
839 840 841 842 843 844 845 846
                          id->i_port, id->i_payload_type );
        }
        else
        {
            continue;
        }
        if( id->psz_rtpmap )
        {
847
            p += sprintf( p, "a=rtpmap:%d %s\r\n", id->i_payload_type,
Gildas Bazin's avatar
 
Gildas Bazin committed
848
                          id->psz_rtpmap );
849 850 851
        }
        if( id->psz_fmtp )
        {
852
            p += sprintf( p, "a=fmtp:%d %s\r\n", id->i_payload_type,
Gildas Bazin's avatar
 
Gildas Bazin committed
853
                          id->psz_fmtp );
854
        }
Laurent Aimar's avatar
Laurent Aimar committed
855 856
        if( b_rtsp )
        {
857
            p += sprintf( p, "a=control:%s/trackid=%d\r\n", p_sys->psz_rtsp_control, i );
Laurent Aimar's avatar
Laurent Aimar committed
858
        }
859 860
    }

Laurent Aimar's avatar
Laurent Aimar committed
861
    return psz_sdp;
862 863 864 865 866
}

/*****************************************************************************
 *
 *****************************************************************************/
867 868 869 870 871 872 873
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 * );
874
static int rtp_packetize_h263 ( sout_stream_t *, sout_stream_id_t *, block_t * );
875
static int rtp_packetize_amr  ( sout_stream_t *, sout_stream_id_t *, block_t * );
876 877 878

static void sprintf_hexa( char *s, uint8_t *p_data, int i_data )
{
879
    static const char hex[16] = "0123456789abcdef";
880 881 882 883
    int i;

    for( i = 0; i < i_data; i++ )
    {
884
        s[2*i+0] = hex[(p_data[i]>>4)&0xf];
885 886 887 888
        s[2*i+1] = hex[(p_data[i]   )&0xf];
    }
    s[2*i_data] = '\0';
}
889

Gildas Bazin's avatar
 
Gildas Bazin committed
890
static sout_stream_id_t *Add( sout_stream_t *p_stream, es_format_t *p_fmt )
891 892 893 894
{
    sout_instance_t   *p_sout = p_stream->p_sout;
    sout_stream_sys_t *p_sys = p_stream->p_sys;
    sout_stream_id_t  *id;
895
    sout_access_out_t *p_access = NULL;
896 897
    int               i_port;
    char              *psz_sdp;
898

899 900 901 902 903 904 905 906 907 908
    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 ) );
909
        memset( id, 0, sizeof( sout_stream_id_t ) );
910 911 912
        id->p_access    = NULL;
        id->p_input     = p_input;
        id->pf_packetize= NULL;
913
        id->p_rtsp_url  = NULL;
914
        id->i_port      = 0;
915 916 917
        return id;
    }

918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941

    /* 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;
    }

942
    if( p_sys->psz_destination )
943
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
944
        char access[17];
945
        char url[NI_MAXHOST + 8];
946 947

        /* first try to create the access out */
948
        if( p_sys->i_ttl )
949
        {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
950 951 952
            snprintf( access, sizeof( access ), "udp{raw,ttl=%d}",
                      p_sys->i_ttl );
            access[sizeof( access ) - 1] = '\0';
953 954
        }
        else
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
955 956
            strcpy( access, "udp{raw}" );

957 958
        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
959 960 961
                 p_sys->psz_destination, i_port );
        url[sizeof( url ) - 1] = '\0';

962 963 964 965 966 967 968
        if( ( p_access = sout_AccessOutNew( p_sout, access, url ) ) == NULL )
        {
            msg_Err( p_stream, "cannot create the access out for %s://%s",
                     access, url );
            return NULL;
        }
        msg_Dbg( p_stream, "access out %s:%s", access, url );
969 970 971 972
    }

    /* not create the rtp specific stuff */
    id = malloc( sizeof( sout_stream_id_t ) );
973
    memset( id, 0, sizeof( sout_stream_id_t ) );
974
    id->p_stream   = p_stream;
975
    id->p_access   = p_access;
976
    id->p_input    = NULL;
977 978
    id->psz_rtpmap = NULL;
    id->psz_fmtp   = NULL;
979
    id->psz_destination = p_sys->psz_destination ? strdup( p_sys->psz_destination ) : NULL;
980
    id->i_port = i_port;
Laurent Aimar's avatar
Laurent Aimar committed
981
    id->p_rtsp_url = NULL;
982 983 984
    vlc_mutex_init( p_stream, &id->lock_rtsp );
    id->i_rtsp_access = 0;
    id->rtsp_access = NULL;
985

986
    switch( p_fmt->i_codec )
987
    {
988
        case VLC_FOURCC( 's', '1', '6', 'b' ):
989
            if( p_fmt->audio.i_channels == 1 && p_fmt->audio.i_rate == 44100 )
990 991 992
            {
                id->i_payload_type = 11;
            }
Gildas Bazin's avatar
 
Gildas Bazin committed
993 994
            else if( p_fmt->audio.i_channels == 2 &&
                     p_fmt->audio.i_rate == 44100 )
995 996 997 998 999 1000 1001 1002
            {
                id->i_payload_type = 10;
            }
            else
            {
                id->i_payload_type = p_sys->i_payload_type++;
            }
            id->psz_rtpmap = malloc( strlen( "L16/*/*" ) + 20+1 );
Gildas Bazin's avatar
 
Gildas Bazin committed
1003 1004
            sprintf( id->psz_rtpmap, "L16/%d/%d", p_fmt->audio.i_rate,
                     p_fmt->audio.i_channels );
1005
            id->i_clock_rate = p_fmt->audio.i_rate;
1006 1007 1008 1009 1010
            id->pf_packetize = rtp_packetize_l16;
            break;
        case VLC_FOURCC( 'u', '8', ' ', ' ' ):
            id->i_payload_type = p_sys->i_payload_type++;
            id->psz_rtpmap = malloc( strlen( "L8/*/*" ) + 20+1 );
Gildas Bazin's avatar
 
Gildas Bazin committed
1011 1012
            sprintf( id->psz_rtpmap, "L8/%d/%d", p_fmt->audio.i_rate,
                     p_fmt->audio.i_channels );
1013
            id->i_clock_rate = p_fmt->audio.i_rate;
1014 1015
            id->pf_packetize = rtp_packetize_l8;
            break;
1016 1017 1018 1019 1020 1021
        case VLC_FOURCC( 'm', 'p', 'g', 'a' ):
            id->i_payload_type = 14;
            id->i_clock_rate = 90000;
            id->psz_rtpmap = strdup( "MPA/90000" );
            id->pf_packetize = rtp_packetize_mpa;
            break;
1022 1023 1024 1025 1026 1027
        case VLC_FOURCC( 'm', 'p', 'g', 'v' ):
            id->i_payload_type = 32;
            id->i_clock_rate = 90000;
            id->psz_rtpmap = strdup( "MPV/90000" );
            id->pf_packetize = rtp_packetize_mpv;
            break;
1028 1029 1030 1031 1032 1033
        case VLC_FOURCC( 'a', '5', '2', ' ' ):
            id->i_payload_type = p_sys->i_payload_type++;
            id->i_clock_rate = 90000;
            id->psz_rtpmap = strdup( "ac3/90000" );
            id->pf_packetize = rtp_packetize_ac3;
            break;
1034
        case VLC_FOURCC( 'H', '2', '6', '3' ):
1035
            id->i_payload_type = p_sys->i_payload_type++;
1036 1037 1038 1039 1040
            id->i_clock_rate = 90000;
            id->psz_rtpmap = strdup( "H263-1998/90000" );
            id->pf_packetize = rtp_packetize_h263;
            break;

1041 1042
        case VLC_FOURCC( 'm', 'p', '4', 'v' ):
        {
1043
            char hexa[2*p_fmt->i_extra +1];
1044 1045 1046 1047 1048

            id->i_payload_type = p_sys->i_payload_type++;
            id->i_clock_rate = 90000;
            id->psz_rtpmap = strdup( "MP4V-ES/90000" );
            id->pf_packetize = rtp_packetize_split;