1 /*****************************************************************************
2 * rtsp.c: rtsp VoD server module
3 *****************************************************************************
4 * Copyright (C) 2003-2006 the VideoLAN team
7 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
8 * Gildas Bazin <gbazin@videolan.org>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 *****************************************************************************/
25 /*****************************************************************************
27 *****************************************************************************/
33 #define VLC_MODULE_LICENSE VLC_LICENSE_GPL_2_PLUS
34 #include <vlc_common.h>
35 #include <vlc_plugin.h>
36 #include <vlc_input.h>
38 #include <vlc_block.h>
40 #include <vlc_httpd.h>
43 #include <vlc_network.h>
44 #include <vlc_charset.h>
45 #include <vlc_strings.h>
47 #include <vlc_memstream.h>
57 /*****************************************************************************
59 *****************************************************************************/
60 static int Open ( vlc_object_t
* );
61 static void Close( vlc_object_t
* );
63 #define THROTTLE_TEXT N_( "Maximum number of connections" )
64 #define THROTTLE_LONGTEXT N_( "This limits the maximum number of clients " \
65 "that can connect to the RTSP VOD. 0 means no limit." )
67 #define RAWMUX_TEXT N_( "MUX for RAW RTSP transport" )
69 #define SESSION_TIMEOUT_TEXT N_( "Sets the timeout option in the RTSP " \
71 #define SESSION_TIMEOUT_LONGTEXT N_( "Defines what timeout option to add " \
72 "to the RTSP session ID string. Setting it to a negative number removes " \
73 "the timeout option entirely. This is needed by some IPTV STBs (such as " \
74 "those made by HansunTech) which get confused by it. The default is 5." )
77 set_shortname( N_("RTSP VoD" ) )
78 set_description( N_("Legacy RTSP VoD server") )
79 set_category( CAT_SOUT
)
80 set_subcategory( SUBCAT_SOUT_VOD
)
81 set_capability( "vod server", 1 )
82 set_callbacks( Open
, Close
)
83 add_shortcut( "rtsp" )
84 add_string( "rtsp-raw-mux", "ts", RAWMUX_TEXT
,
86 add_integer( "rtsp-throttle-users", 0, THROTTLE_TEXT
,
87 THROTTLE_LONGTEXT
, true )
88 add_integer( "rtsp-session-timeout", 5, SESSION_TIMEOUT_TEXT
,
89 SESSION_TIMEOUT_LONGTEXT
, true )
92 /*****************************************************************************
94 *****************************************************************************/
96 typedef struct media_es_t media_es_t
;
100 media_es_t
*p_media_es
;
109 bool b_playing
; /* is it in "play" state */
113 rtsp_client_es_t
**es
;
123 httpd_url_t
*p_rtsp_url
;
125 vod_media_t
*p_media
;
128 uint8_t i_payload_type
;
129 const char *psz_ptname
;
130 unsigned i_clock_rate
;
144 httpd_url_t
*p_rtsp_url
;
145 char *psz_rtsp_control_v4
;
146 char *psz_rtsp_control_v6
;
159 rtsp_client_t
**rtsp
;
168 httpd_host_t
*p_rtsp_host
;
170 int i_throttle_users
;
175 int i_session_timeout
;
184 block_fifo_t
*p_fifo_cmd
;
187 /* rtsp delayed command (to avoid deadlock between vlm/httpd) */
190 RTSP_CMD_TYPE_NONE
, /* Exit requested */
196 RTSP_CMD_TYPE_REWIND
,
197 RTSP_CMD_TYPE_FORWARD
,
208 vod_media_t
*p_media
;
215 static vod_media_t
*MediaNew( vod_t
*, const char *, input_item_t
* );
216 static void MediaDel( vod_t
*, vod_media_t
* );
217 static void MediaAskDel ( vod_t
*, vod_media_t
* );
218 static int MediaAddES( vod_t
*, vod_media_t
*, es_format_t
* );
219 static void MediaDelES( vod_t
*, vod_media_t
*, es_format_t
* );
221 static void* CommandThread( void * );
222 static void CommandPush( vod_t
*, rtsp_cmd_type_t
, vod_media_t
*,
223 const char *psz_session
, int64_t i_arg
,
224 double f_arg
, const char *psz_arg
);
226 static rtsp_client_t
*RtspClientNew( vod_media_t
*, char * );
227 static rtsp_client_t
*RtspClientGet( vod_media_t
*, const char * );
228 static void RtspClientDel( vod_media_t
*, rtsp_client_t
* );
230 static int RtspCallback( httpd_callback_sys_t
*, httpd_client_t
*,
231 httpd_message_t
*, const httpd_message_t
* );
232 static int RtspCallbackES( httpd_callback_sys_t
*, httpd_client_t
*,
233 httpd_message_t
*, const httpd_message_t
* );
235 static char *SDPGenerate( const vod_media_t
*, httpd_client_t
*cl
);
237 static void sprintf_hexa( char *s
, uint8_t *p_data
, int i_data
)
239 static const char hex
[16] = "0123456789abcdef";
241 for( int i
= 0; i
< i_data
; i
++ )
243 s
[2*i
+0] = hex
[(p_data
[i
]>>4)&0xf];
244 s
[2*i
+1] = hex
[(p_data
[i
] )&0xf];
249 /*****************************************************************************
250 * Open: Starts the RTSP server module
251 *****************************************************************************/
252 static int Open( vlc_object_t
*p_this
)
254 vod_t
*p_vod
= (vod_t
*)p_this
;
255 vod_sys_t
*p_sys
= NULL
;
256 char *psz_url
= NULL
;
259 psz_url
= var_InheritString( p_vod
, "rtsp-host" );
260 vlc_UrlParse( &url
, psz_url
);
263 p_vod
->p_sys
= p_sys
= malloc( sizeof( vod_sys_t
) );
264 if( !p_sys
) goto error
;
265 p_sys
->p_rtsp_host
= 0;
267 p_sys
->i_session_timeout
= var_CreateGetInteger( p_this
, "rtsp-session-timeout" );
269 p_sys
->i_throttle_users
= var_CreateGetInteger( p_this
, "rtsp-throttle-users" );
270 msg_Dbg( p_this
, "allowing up to %d connections", p_sys
->i_throttle_users
);
271 p_sys
->i_connections
= 0;
273 p_sys
->psz_raw_mux
= var_CreateGetString( p_this
, "rtsp-raw-mux" );
275 p_sys
->p_rtsp_host
= vlc_rtsp_HostNew( VLC_OBJECT(p_vod
) );
276 if( !p_sys
->p_rtsp_host
)
278 msg_Err( p_vod
, "cannot create RTSP server" );
282 p_sys
->psz_path
= strdup( url
.psz_path
? url
.psz_path
: "/" );
284 TAB_INIT( p_sys
->i_media
, p_sys
->media
);
285 p_sys
->i_media_id
= 0;
287 p_vod
->pf_media_new
= MediaNew
;
288 p_vod
->pf_media_del
= MediaAskDel
;
290 p_sys
->p_fifo_cmd
= block_FifoNew();
291 if( vlc_clone( &p_sys
->thread
, CommandThread
, p_vod
, VLC_THREAD_PRIORITY_LOW
) )
293 msg_Err( p_vod
, "cannot spawn rtsp vod thread" );
294 block_FifoRelease( p_sys
->p_fifo_cmd
);
295 free( p_sys
->psz_path
);
299 vlc_UrlClean( &url
);
305 if( p_sys
->p_rtsp_host
) httpd_HostDelete( p_sys
->p_rtsp_host
);
306 free( p_sys
->psz_raw_mux
);
309 vlc_UrlClean( &url
);
314 /*****************************************************************************
316 *****************************************************************************/
317 static void Close( vlc_object_t
* p_this
)
319 vod_t
*p_vod
= (vod_t
*)p_this
;
320 vod_sys_t
*p_sys
= p_vod
->p_sys
;
322 /* Stop command thread */
323 CommandPush( p_vod
, RTSP_CMD_TYPE_NONE
, NULL
, NULL
, 0, 0.0, NULL
);
324 vlc_join( p_sys
->thread
, NULL
);
326 while( block_FifoCount( p_sys
->p_fifo_cmd
) > 0 )
329 block_t
*p_block_cmd
= block_FifoGet( p_sys
->p_fifo_cmd
);
330 memcpy( &cmd
, p_block_cmd
->p_buffer
, sizeof(cmd
) );
331 block_Release( p_block_cmd
);
332 if ( cmd
.i_type
== RTSP_CMD_TYPE_DEL
)
333 MediaDel(p_vod
, cmd
.p_media
);
334 free( cmd
.psz_session
);
337 block_FifoRelease( p_sys
->p_fifo_cmd
);
339 httpd_HostDelete( p_sys
->p_rtsp_host
);
340 var_Destroy( p_this
, "rtsp-session-timeout" );
341 var_Destroy( p_this
, "rtsp-throttle-users" );
342 var_Destroy( p_this
, "rtsp-raw-mux" );
344 /* Check VLM is not buggy */
345 if( p_sys
->i_media
> 0 )
346 msg_Err( p_vod
, "rtsp vod leaking %d medias", p_sys
->i_media
);
347 TAB_CLEAN( p_sys
->i_media
, p_sys
->media
);
349 free( p_sys
->psz_path
);
350 free( p_sys
->psz_raw_mux
);
354 /*****************************************************************************
356 *****************************************************************************/
357 static vod_media_t
*MediaNew( vod_t
*p_vod
, const char *psz_name
,
358 input_item_t
*p_item
)
360 vod_sys_t
*p_sys
= p_vod
->p_sys
;
362 vod_media_t
*p_media
= calloc( 1, sizeof(vod_media_t
) );
366 p_media
->id
= p_sys
->i_media_id
++;
367 TAB_INIT( p_media
->i_es
, p_media
->es
);
368 p_media
->psz_mux
= NULL
;
369 TAB_INIT( p_media
->i_rtsp
, p_media
->rtsp
);
370 p_media
->b_raw
= false;
372 if( asprintf( &p_media
->psz_rtsp_path
, "%s%s",
373 p_sys
->psz_path
, psz_name
) <0 )
376 p_media
->p_rtsp_url
=
377 httpd_UrlNew( p_sys
->p_rtsp_host
, p_media
->psz_rtsp_path
, NULL
, NULL
);
379 if( !p_media
->p_rtsp_url
)
381 msg_Err( p_vod
, "cannot create RTSP url (%s)", p_media
->psz_rtsp_path
);
385 msg_Dbg( p_vod
, "created RTSP url: %s", p_media
->psz_rtsp_path
);
387 if( asprintf( &p_media
->psz_rtsp_control_v4
,
388 "rtsp://%%s:%%d%s/trackID=%%d",
389 p_media
->psz_rtsp_path
) < 0 )
392 if( asprintf( &p_media
->psz_rtsp_control_v6
,
393 "rtsp://[%%s]:%%d%s/trackID=%%d",
394 p_media
->psz_rtsp_path
) < 0 )
397 httpd_UrlCatch( p_media
->p_rtsp_url
, HTTPD_MSG_SETUP
,
398 RtspCallback
, (void*)p_media
);
399 httpd_UrlCatch( p_media
->p_rtsp_url
, HTTPD_MSG_DESCRIBE
,
400 RtspCallback
, (void*)p_media
);
401 httpd_UrlCatch( p_media
->p_rtsp_url
, HTTPD_MSG_PLAY
,
402 RtspCallback
, (void*)p_media
);
403 httpd_UrlCatch( p_media
->p_rtsp_url
, HTTPD_MSG_PAUSE
,
404 RtspCallback
, (void*)p_media
);
405 httpd_UrlCatch( p_media
->p_rtsp_url
, HTTPD_MSG_GETPARAMETER
,
406 RtspCallback
, (void*)p_media
);
407 httpd_UrlCatch( p_media
->p_rtsp_url
, HTTPD_MSG_TEARDOWN
,
408 RtspCallback
, (void*)p_media
);
410 p_media
->p_vod
= p_vod
;
412 vlc_mutex_init( &p_media
->lock
);
414 p_media
->i_length
= input_item_GetDuration( p_item
);
416 vlc_mutex_lock( &p_item
->lock
);
417 msg_Dbg( p_vod
, "media has %i declared ES", p_item
->i_es
);
418 for( int i
= 0; i
< p_item
->i_es
; i
++ )
420 MediaAddES( p_vod
, p_media
, p_item
->es
[i
] );
422 vlc_mutex_unlock( &p_item
->lock
);
424 CommandPush( p_vod
, RTSP_CMD_TYPE_ADD
, p_media
, NULL
, 0, 0.0, NULL
);
430 free( p_media
->psz_rtsp_control_v4
);
431 if( p_media
->p_rtsp_url
)
432 httpd_UrlDelete( p_media
->p_rtsp_url
);
433 free( p_media
->psz_rtsp_path
);
439 static void MediaAskDel ( vod_t
*p_vod
, vod_media_t
*p_media
)
441 CommandPush( p_vod
, RTSP_CMD_TYPE_DEL
, p_media
, NULL
, 0, 0.0, NULL
);
444 static void MediaDel( vod_t
*p_vod
, vod_media_t
*p_media
)
446 vod_sys_t
*p_sys
= p_vod
->p_sys
;
448 msg_Dbg( p_vod
, "deleting media: %s", p_media
->psz_rtsp_path
);
450 TAB_REMOVE( p_sys
->i_media
, p_sys
->media
, p_media
);
452 httpd_UrlDelete( p_media
->p_rtsp_url
);
454 while( p_media
->i_rtsp
> 0 )
455 RtspClientDel( p_media
, p_media
->rtsp
[0] );
456 TAB_CLEAN( p_media
->i_rtsp
, p_media
->rtsp
);
458 free( p_media
->psz_rtsp_path
);
459 free( p_media
->psz_rtsp_control_v6
);
460 free( p_media
->psz_rtsp_control_v4
);
462 while( p_media
->i_es
)
463 MediaDelES( p_vod
, p_media
, &p_media
->es
[0]->fmt
);
464 TAB_CLEAN( p_media
->i_es
, p_media
->es
);
466 vlc_mutex_destroy( &p_media
->lock
);
471 static int MediaAddES( vod_t
*p_vod
, vod_media_t
*p_media
, es_format_t
*p_fmt
)
475 media_es_t
*p_es
= calloc( 1, sizeof(media_es_t
) );
479 p_media
->psz_mux
= NULL
;
481 /* TODO: update SDP, etc... */
482 if( asprintf( &psz_urlc
, "%s/trackID=%d",
483 p_media
->psz_rtsp_path
, p_media
->i_es
) < 0 )
488 msg_Dbg( p_vod
, " - ES %4.4s (%s)", (char *)&p_fmt
->i_codec
, psz_urlc
);
490 /* Dynamic payload. No conflict since we put each ES in its own
492 p_es
->i_payload_type
= 96;
493 p_es
->i_clock_rate
= 90000;
494 p_es
->i_channels
= 1;
496 switch( p_fmt
->i_codec
)
499 if( p_fmt
->audio
.i_channels
== 1 && p_fmt
->audio
.i_rate
== 44100 )
501 p_es
->i_payload_type
= 11;
503 else if( p_fmt
->audio
.i_channels
== 2 &&
504 p_fmt
->audio
.i_rate
== 44100 )
506 p_es
->i_payload_type
= 10;
508 p_es
->psz_ptname
= "L16";
509 p_es
->i_clock_rate
= p_fmt
->audio
.i_rate
;
510 p_es
->i_channels
= p_fmt
->audio
.i_channels
;
513 p_es
->psz_ptname
= "L8";
514 p_es
->i_clock_rate
= p_fmt
->audio
.i_rate
;
515 p_es
->i_channels
= p_fmt
->audio
.i_channels
;
518 p_es
->i_payload_type
= 14;
519 p_es
->psz_ptname
= "MPA";
522 p_es
->i_payload_type
= 32;
523 p_es
->psz_ptname
= "MPV";
526 p_es
->psz_ptname
= "ac3";
527 p_es
->i_clock_rate
= p_fmt
->audio
.i_rate
;
530 p_es
->psz_ptname
= "H263-1998";
533 p_es
->psz_ptname
= "H264";
534 p_es
->psz_fmtp
= NULL
;
535 /* FIXME AAAAAAAAAAAARRRRRRRRGGGG copied from stream_out/rtp.c */
536 if( p_fmt
->i_extra
> 0 )
538 uint8_t *p_buffer
= p_fmt
->p_extra
;
539 int i_buffer
= p_fmt
->i_extra
;
540 char *p_64_sps
= NULL
;
541 char *p_64_pps
= NULL
;
544 while( i_buffer
> 4 )
549 while( p_buffer
[0] != 0 || p_buffer
[1] != 0 ||
554 if( i_buffer
== 0 ) break;
557 if( i_buffer
< 4 || memcmp(p_buffer
, "\x00\x00\x01", 3 ) )
559 /* No startcode found.. */
565 const int i_nal_type
= p_buffer
[0]&0x1f;
568 for( i_offset
= 0; i_offset
+2 < i_buffer
; i_offset
++)
570 if( !memcmp(p_buffer
+ i_offset
, "\x00\x00\x01", 3 ) )
572 /* we found another startcode */
573 while( i_offset
> 0 && 0 == p_buffer
[ i_offset
- 1 ] )
582 /* No-info found in nal */
586 if( i_nal_type
== 7 )
589 p_64_sps
= vlc_b64_encode_binary( p_buffer
, i_size
);
590 /* XXX: nothing ensures that i_size >= 4 ?? */
591 sprintf_hexa( hexa
, &p_buffer
[1], 3 );
593 else if( i_nal_type
== 8 )
596 p_64_pps
= vlc_b64_encode_binary( p_buffer
, i_size
);
602 if( p_64_sps
&& p_64_pps
)
604 if( asprintf( &p_es
->psz_fmtp
,
605 "packetization-mode=1;profile-level-id=%s;"
606 "sprop-parameter-sets=%s,%s;", hexa
, p_64_sps
,
619 if( !p_es
->psz_fmtp
)
620 p_es
->psz_fmtp
= strdup( "packetization-mode=1" );
623 p_es
->psz_ptname
= "MP4V-ES";
624 if( p_fmt
->i_extra
> 0 )
626 char *p_hexa
= malloc( 2 * p_fmt
->i_extra
+ 1 );
627 sprintf_hexa( p_hexa
, p_fmt
->p_extra
, p_fmt
->i_extra
);
628 if( asprintf( &p_es
->psz_fmtp
,
629 "profile-level-id=3; config=%s;", p_hexa
) == -1 )
630 p_es
->psz_fmtp
= NULL
;
635 p_es
->psz_ptname
= "mpeg4-generic";
636 p_es
->i_clock_rate
= p_fmt
->audio
.i_rate
;
637 if( p_fmt
->i_extra
> 0 )
639 char *p_hexa
= malloc( 2 * p_fmt
->i_extra
+ 1 );
640 sprintf_hexa( p_hexa
, p_fmt
->p_extra
, p_fmt
->i_extra
);
641 if( asprintf( &p_es
->psz_fmtp
,
642 "streamtype=5; profile-level-id=15; mode=AAC-hbr; "
643 "config=%s; SizeLength=13;IndexLength=3; "
644 "IndexDeltaLength=3; Profile=1;", p_hexa
) == -1 )
645 p_es
->psz_fmtp
= NULL
;
649 case VLC_FOURCC( 'm', 'p', '2', 't' ):
650 p_media
->psz_mux
= "ts";
651 p_es
->i_payload_type
= 33;
652 p_es
->psz_ptname
= "MP2T";
654 case VLC_FOURCC( 'm', 'p', '2', 'p' ):
655 p_media
->psz_mux
= "ps";
656 p_es
->psz_ptname
= "MP2P";
658 case VLC_CODEC_AMR_NB
:
659 p_es
->psz_ptname
= "AMR";
660 p_es
->i_clock_rate
= 8000;
661 if(p_fmt
->audio
.i_channels
== 2 )
662 p_es
->i_channels
= 2;
663 p_es
->psz_fmtp
= strdup( "octet-align=1" );
665 case VLC_CODEC_AMR_WB
:
666 p_es
->psz_ptname
= "AMR-WB";
667 p_es
->i_clock_rate
= 16000;
668 if(p_fmt
->audio
.i_channels
== 2 )
669 p_es
->i_channels
= 2;
670 p_es
->psz_fmtp
= strdup( "octet-align=1" );
674 msg_Err( p_vod
, "cannot add this stream (unsupported "
675 "codec: %4.4s)", (char*)&p_fmt
->i_codec
);
682 httpd_UrlNew( p_vod
->p_sys
->p_rtsp_host
, psz_urlc
, NULL
, NULL
);
684 if( !p_es
->p_rtsp_url
)
686 msg_Err( p_vod
, "cannot create RTSP url (%s)", psz_urlc
);
693 httpd_UrlCatch( p_es
->p_rtsp_url
, HTTPD_MSG_SETUP
,
694 RtspCallbackES
, (void*)p_es
);
695 httpd_UrlCatch( p_es
->p_rtsp_url
, HTTPD_MSG_TEARDOWN
,
696 RtspCallbackES
, (void*)p_es
);
697 httpd_UrlCatch( p_es
->p_rtsp_url
, HTTPD_MSG_PLAY
,
698 RtspCallbackES
, (void*)p_es
);
699 httpd_UrlCatch( p_es
->p_rtsp_url
, HTTPD_MSG_PAUSE
,
700 RtspCallbackES
, (void*)p_es
);
702 es_format_Copy( &p_es
->fmt
, p_fmt
);
704 p_es
->p_media
= p_media
;
706 vlc_mutex_lock( &p_media
->lock
);
707 TAB_APPEND( p_media
->i_es
, p_media
->es
, p_es
);
708 vlc_mutex_unlock( &p_media
->lock
);
713 static void MediaDelES( vod_t
*p_vod
, vod_media_t
*p_media
, es_format_t
*p_fmt
)
715 media_es_t
*p_es
= NULL
;
718 for( int i
= 0; i
< p_media
->i_es
; i
++ )
720 if( p_media
->es
[i
]->fmt
.i_cat
== p_fmt
->i_cat
&&
721 p_media
->es
[i
]->fmt
.i_codec
== p_fmt
->i_codec
&&
722 p_media
->es
[i
]->fmt
.i_id
== p_fmt
->i_id
)
724 p_es
= p_media
->es
[i
];
729 msg_Dbg( p_vod
, " - Removing ES %4.4s", (char *)&p_fmt
->i_codec
);
731 vlc_mutex_lock( &p_media
->lock
);
732 TAB_REMOVE( p_media
->i_es
, p_media
->es
, p_es
);
733 vlc_mutex_unlock( &p_media
->lock
);
735 free( p_es
->psz_fmtp
);
737 if( p_es
->p_rtsp_url
) httpd_UrlDelete( p_es
->p_rtsp_url
);
738 es_format_Clean( &p_es
->fmt
);
742 static void CommandPush( vod_t
*p_vod
, rtsp_cmd_type_t i_type
, vod_media_t
*p_media
, const char *psz_session
, int64_t i_arg
,
743 double f_arg
, const char *psz_arg
)
748 memset( &cmd
, 0, sizeof(cmd
) );
750 cmd
.p_media
= p_media
;
752 cmd
.i_media_id
= p_media
->id
;
754 cmd
.psz_session
= strdup(psz_session
);
758 cmd
.psz_arg
= strdup(psz_arg
);
760 p_cmd
= block_Alloc( sizeof(rtsp_cmd_t
) );
761 memcpy( p_cmd
->p_buffer
, &cmd
, sizeof(cmd
) );
763 block_FifoPut( p_vod
->p_sys
->p_fifo_cmd
, p_cmd
);
766 static void* CommandThread( void *obj
)
768 vod_t
*p_vod
= (vod_t
*)obj
;
769 vod_sys_t
*p_sys
= p_vod
->p_sys
;
770 int canc
= vlc_savecancel ();
774 block_t
*p_block_cmd
= block_FifoGet( p_sys
->p_fifo_cmd
);
776 vod_media_t
*p_media
= NULL
;
782 memcpy( &cmd
, p_block_cmd
->p_buffer
, sizeof(cmd
) );
783 block_Release( p_block_cmd
);
785 if( cmd
.i_type
== RTSP_CMD_TYPE_NONE
)
788 if ( cmd
.i_type
== RTSP_CMD_TYPE_ADD
)
790 TAB_APPEND( p_sys
->i_media
, p_sys
->media
, cmd
.p_media
);
794 if ( cmd
.i_type
== RTSP_CMD_TYPE_DEL
)
796 MediaDel(p_vod
, cmd
.p_media
);
801 for( i
= 0; i
< p_sys
->i_media
; i
++ )
803 if( p_sys
->media
[i
]->id
== cmd
.i_media_id
)
806 if( i
>= p_sys
->i_media
)
810 p_media
= p_sys
->media
[i
];
814 case RTSP_CMD_TYPE_PLAY
:
816 vod_MediaControl( p_vod
, p_media
, cmd
.psz_session
,
817 VOD_MEDIA_PLAY
, cmd
.psz_arg
, &cmd
.i_arg
);
819 case RTSP_CMD_TYPE_PAUSE
:
821 vod_MediaControl( p_vod
, p_media
, cmd
.psz_session
,
822 VOD_MEDIA_PAUSE
, &cmd
.i_arg
);
825 case RTSP_CMD_TYPE_STOP
:
826 vod_MediaControl( p_vod
, p_media
, cmd
.psz_session
, VOD_MEDIA_STOP
);
829 case RTSP_CMD_TYPE_SEEK
:
830 vod_MediaControl( p_vod
, p_media
, cmd
.psz_session
,
831 VOD_MEDIA_SEEK
, cmd
.i_arg
);
834 case RTSP_CMD_TYPE_REWIND
:
835 vod_MediaControl( p_vod
, p_media
, cmd
.psz_session
,
836 VOD_MEDIA_REWIND
, cmd
.f_arg
);
839 case RTSP_CMD_TYPE_FORWARD
:
840 vod_MediaControl( p_vod
, p_media
, cmd
.psz_session
,
841 VOD_MEDIA_FORWARD
, cmd
.f_arg
);
849 free( cmd
.psz_session
);
853 vlc_restorecancel (canc
);
857 /****************************************************************************
858 * RTSP server implementation
859 ****************************************************************************/
860 static rtsp_client_t
*RtspClientNew( vod_media_t
*p_media
, char *psz_session
)
862 rtsp_client_t
*p_rtsp
= calloc( 1, sizeof(rtsp_client_t
) );
868 p_rtsp
->psz_session
= psz_session
;
869 TAB_APPEND( p_media
->i_rtsp
, p_media
->rtsp
, p_rtsp
);
871 p_media
->p_vod
->p_sys
->i_connections
++;
872 msg_Dbg( p_media
->p_vod
, "new session: %s, connections: %d",
873 psz_session
, p_media
->p_vod
->p_sys
->i_throttle_users
);
878 static rtsp_client_t
*RtspClientGet( vod_media_t
*p_media
, const char *psz_session
)
880 for( int i
= 0; psz_session
&& i
< p_media
->i_rtsp
; i
++ )
882 if( !strcmp( p_media
->rtsp
[i
]->psz_session
, psz_session
) )
883 return p_media
->rtsp
[i
];
889 static void RtspClientDel( vod_media_t
*p_media
, rtsp_client_t
*p_rtsp
)
891 p_media
->p_vod
->p_sys
->i_connections
--;
892 msg_Dbg( p_media
->p_vod
, "closing session: %s, connections: %d",
893 p_rtsp
->psz_session
, p_media
->p_vod
->p_sys
->i_throttle_users
);
895 while( p_rtsp
->i_es
)
898 free( p_rtsp
->es
[p_rtsp
->i_es
] );
902 TAB_REMOVE( p_media
->i_rtsp
, p_media
->rtsp
, p_rtsp
);
904 free( p_rtsp
->psz_session
);
909 static int64_t ParseNPT (const char *str
)
911 locale_t loc
= newlocale (LC_NUMERIC_MASK
, "C", NULL
);
912 locale_t oldloc
= uselocale (loc
);
916 if (sscanf (str
, "%u:%u:%f", &hour
, &min
, &sec
) == 3)
917 sec
+= ((hour
* 60) + min
) * 60;
919 if (sscanf (str
, "%f", &sec
) != 1)
922 if (loc
!= (locale_t
)0)
927 return sec
* CLOCK_FREQ
;
931 static int RtspCallback( httpd_callback_sys_t
*p_args
, httpd_client_t
*cl
,
932 httpd_message_t
*answer
, const httpd_message_t
*query
)
934 vod_media_t
*p_media
= (vod_media_t
*)p_args
;
935 vod_t
*p_vod
= p_media
->p_vod
;
936 const char *psz_transport
= NULL
;
937 const char *psz_playnow
= NULL
; /* support option: x-playNow */
938 const char *psz_session
= NULL
;
939 const char *psz_cseq
= NULL
;
940 rtsp_client_t
*p_rtsp
;
943 if( answer
== NULL
|| query
== NULL
) return VLC_SUCCESS
;
945 msg_Dbg( p_vod
, "RtspCallback query: type=%d", query
->i_type
);
947 answer
->i_proto
= HTTPD_PROTO_RTSP
;
948 answer
->i_version
= query
->i_version
;
949 answer
->i_type
= HTTPD_MSG_ANSWER
;
951 answer
->p_body
= NULL
;
953 switch( query
->i_type
)
955 case HTTPD_MSG_SETUP
:
957 psz_playnow
= httpd_MsgGet( query
, "x-playNow" );
958 psz_transport
= httpd_MsgGet( query
, "Transport" );
959 if( psz_transport
== NULL
)
961 answer
->i_status
= 400;
964 msg_Dbg( p_vod
, "HTTPD_MSG_SETUP: transport=%s", psz_transport
);
966 if( strstr( psz_transport
, "unicast" ) &&
967 strstr( psz_transport
, "client_port=" ) )
969 rtsp_client_t
*p_rtsp
= NULL
;
970 char ip
[NI_MAXNUMERICHOST
];
971 int i_port
= atoi( strstr( psz_transport
, "client_port=" ) +
972 strlen("client_port=") );
974 if( strstr( psz_transport
, "MP2T/H2221/UDP" ) ||
975 strstr( psz_transport
, "RAW/RAW/UDP" ) )
977 p_media
->psz_mux
= p_vod
->p_sys
->psz_raw_mux
;
978 p_media
->b_raw
= true;
981 if( httpd_ClientIP( cl
, ip
, NULL
) == NULL
)
983 answer
->i_status
= 500;
985 answer
->p_body
= NULL
;
989 msg_Dbg( p_vod
, "HTTPD_MSG_SETUP: unicast ip=%s port=%d",
992 psz_session
= httpd_MsgGet( query
, "Session" );
993 if( !psz_session
|| !*psz_session
)
996 if( ( p_vod
->p_sys
->i_throttle_users
> 0 ) &&
997 ( p_vod
->p_sys
->i_connections
>= p_vod
->p_sys
->i_throttle_users
) )
999 answer
->i_status
= 503;
1001 answer
->p_body
= NULL
;
1004 #warning Should use secure randomness here! (spoofing risk)
1005 if( asprintf( &psz_new
, "%lu", vlc_mrand48() ) < 0 )
1007 psz_session
= psz_new
;
1009 p_rtsp
= RtspClientNew( p_media
, psz_new
);
1012 answer
->i_status
= 454;
1014 answer
->p_body
= NULL
;
1020 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1023 answer
->i_status
= 454;
1025 answer
->p_body
= NULL
;
1030 answer
->i_status
= 200;
1032 answer
->p_body
= NULL
;
1034 if( p_media
->b_raw
)
1036 p_rtsp
->i_port_raw
= i_port
;
1038 if( strstr( psz_transport
, "MP2T/H2221/UDP" ) )
1040 httpd_MsgAdd( answer
, "Transport",
1041 "MP2T/H2221/UDP;unicast;client_port=%d-%d",
1042 i_port
, i_port
+ 1 );
1044 else if( strstr( psz_transport
, "RAW/RAW/UDP" ) )
1046 httpd_MsgAdd( answer
, "Transport",
1047 "RAW/RAW/UDP;unicast;client_port=%d-%d",
1048 i_port
, i_port
+ 1 );
1052 httpd_MsgAdd( answer
, "Transport",
1053 "RTP/AVP/UDP;unicast;client_port=%d-%d",
1054 i_port
, i_port
+ 1 );
1056 else /* TODO strstr( psz_transport, "interleaved" ) ) */
1058 answer
->i_status
= 461;
1060 answer
->p_body
= NULL
;
1063 /* Intentional fall-through on x-playNow option in RTSP request */
1066 } /* fall through */
1068 case HTTPD_MSG_PLAY
:
1070 char *psz_output
, ip
[NI_MAXNUMERICHOST
];
1071 int i_port_audio
= 0, i_port_video
= 0;
1073 /* for now only multicast so easy */
1076 answer
->i_status
= 200;
1078 answer
->p_body
= NULL
;
1082 psz_session
= httpd_MsgGet( query
, "Session" );
1083 msg_Dbg( p_vod
, "HTTPD_MSG_PLAY for session: %s", psz_session
);
1085 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1088 answer
->i_status
= 500;
1090 answer
->p_body
= NULL
;
1094 if( p_rtsp
->b_playing
)
1096 const char *psz_position
= httpd_MsgGet( query
, "Range" );
1097 const char *psz_scale
= httpd_MsgGet( query
, "Scale" );
1099 psz_position
= strstr( psz_position
, "npt=" );
1100 if( psz_position
&& !psz_scale
)
1102 int64_t i_time
= ParseNPT (psz_position
+ 4);
1103 msg_Dbg( p_vod
, "seeking request: %s", psz_position
);
1104 CommandPush( p_vod
, RTSP_CMD_TYPE_SEEK
, p_media
,
1105 psz_session
, i_time
, 0.0, NULL
);
1107 else if( psz_scale
)
1109 double f_scale
= 0.0;
1112 f_scale
= us_strtod( psz_scale
, &end
);
1113 if( end
> psz_scale
)
1115 f_scale
= (f_scale
* 30.0);
1116 if( psz_scale
[0] == '-' ) /* rewind */
1118 msg_Dbg( p_vod
, "rewind request: %s", psz_scale
);
1119 CommandPush( p_vod
, RTSP_CMD_TYPE_REWIND
, p_media
,
1120 psz_session
, 0, f_scale
, NULL
);
1122 else if(psz_scale
[0] != '1' ) /* fast-forward */
1124 msg_Dbg( p_vod
, "fastforward request: %s",
1126 CommandPush( p_vod
, RTSP_CMD_TYPE_FORWARD
, p_media
,
1127 psz_session
, 0, f_scale
, NULL
);
1131 /* unpause, in case it's paused */
1132 CommandPush( p_vod
, RTSP_CMD_TYPE_PLAY
, p_media
, psz_session
,
1137 if( httpd_ClientIP( cl
, ip
, NULL
) == NULL
) break;
1139 p_rtsp
->b_playing
= true;
1141 /* FIXME for != 1 video and 1 audio */
1142 for( int i
= 0; i
< p_rtsp
->i_es
; i
++ )
1144 if( p_rtsp
->es
[i
]->p_media_es
->fmt
.i_cat
== AUDIO_ES
)
1145 i_port_audio
= p_rtsp
->es
[i
]->i_port
;
1146 if( p_rtsp
->es
[i
]->p_media_es
->fmt
.i_cat
== VIDEO_ES
)
1147 i_port_video
= p_rtsp
->es
[i
]->i_port
;
1150 if( p_media
->psz_mux
)
1152 if( p_media
->b_raw
)
1154 if( asprintf( &psz_output
,
1155 "std{access=udp,dst=%s:%i,mux=%s}",
1156 ip
, p_rtsp
->i_port_raw
, p_media
->psz_mux
) < 0 )
1161 if( asprintf( &psz_output
,
1162 "rtp{dst=%s,port=%i,mux=%s}",
1163 ip
, i_port_video
, p_media
->psz_mux
) < 0 )
1169 if( asprintf( &psz_output
,
1170 "rtp{dst=%s,port-video=%i,port-audio=%i}",
1171 ip
, i_port_video
, i_port_audio
) < 0 )
1175 CommandPush( p_vod
, RTSP_CMD_TYPE_PLAY
, p_media
, psz_session
,
1176 0, 0.0, psz_output
);
1181 case HTTPD_MSG_DESCRIBE
:
1184 SDPGenerate( p_media
, cl
);
1186 if( psz_sdp
!= NULL
)
1188 answer
->i_status
= 200;
1189 httpd_MsgAdd( answer
, "Content-type", "%s",
1190 "application/sdp" );
1192 answer
->p_body
= (uint8_t *)psz_sdp
;
1193 answer
->i_body
= strlen( psz_sdp
);
1197 answer
->i_status
= 500;
1198 answer
->p_body
= NULL
;
1204 case HTTPD_MSG_PAUSE
:
1205 psz_session
= httpd_MsgGet( query
, "Session" );
1206 msg_Dbg( p_vod
, "HTTPD_MSG_PAUSE for session: %s", psz_session
);
1208 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1209 if( !p_rtsp
) break;
1211 CommandPush( p_vod
, RTSP_CMD_TYPE_PAUSE
, p_media
, psz_session
,
1214 answer
->i_status
= 200;
1216 answer
->p_body
= NULL
;
1219 case HTTPD_MSG_TEARDOWN
:
1220 /* for now only multicast so easy again */
1221 answer
->i_status
= 200;
1223 answer
->p_body
= NULL
;
1225 psz_session
= httpd_MsgGet( query
, "Session" );
1226 msg_Dbg( p_vod
, "HTTPD_MSG_TEARDOWN for session: %s", psz_session
);
1228 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1229 if( !p_rtsp
) break;
1231 CommandPush( p_vod
, RTSP_CMD_TYPE_STOP
, p_media
, psz_session
,
1233 RtspClientDel( p_media
, p_rtsp
);
1236 case HTTPD_MSG_GETPARAMETER
:
1237 answer
->i_status
= 200;
1239 answer
->p_body
= NULL
;
1243 return VLC_EGENERIC
;
1246 httpd_MsgAdd( answer
, "Server", "VLC/%s", VERSION
);
1247 httpd_MsgAdd( answer
, "Content-Length", "%d", answer
->i_body
);
1248 psz_cseq
= httpd_MsgGet( query
, "Cseq" );
1249 psz_cseq
? i_cseq
= atoi( psz_cseq
) : 0;
1250 httpd_MsgAdd( answer
, "CSeq", "%d", i_cseq
);
1251 httpd_MsgAdd( answer
, "Cache-Control", "%s", "no-cache" );
1255 if( p_media
->p_vod
->p_sys
->i_session_timeout
>= 0 )
1256 httpd_MsgAdd( answer
, "Session", "%s;timeout=%i", psz_session
,
1257 p_media
->p_vod
->p_sys
->i_session_timeout
);
1259 httpd_MsgAdd( answer
, "Session", "%s", psz_session
);
1265 static int RtspCallbackES( httpd_callback_sys_t
*p_args
, httpd_client_t
*cl
,
1266 httpd_message_t
*answer
,
1267 const httpd_message_t
*query
)
1269 media_es_t
*p_es
= (media_es_t
*)p_args
;
1270 vod_media_t
*p_media
= p_es
->p_media
;
1271 vod_t
*p_vod
= p_media
->p_vod
;
1272 rtsp_client_t
*p_rtsp
= NULL
;
1273 const char *psz_transport
= NULL
;
1274 const char *psz_playnow
= NULL
; /* support option: x-playNow */
1275 const char *psz_session
= NULL
;
1276 const char *psz_position
= NULL
;
1277 const char *psz_cseq
= NULL
;
1280 if( answer
== NULL
|| query
== NULL
) return VLC_SUCCESS
;
1282 msg_Dbg( p_vod
, "RtspCallback query: type=%d", query
->i_type
);
1284 answer
->i_proto
= HTTPD_PROTO_RTSP
;
1285 answer
->i_version
= query
->i_version
;
1286 answer
->i_type
= HTTPD_MSG_ANSWER
;
1288 answer
->p_body
= NULL
;
1290 switch( query
->i_type
)
1292 case HTTPD_MSG_SETUP
:
1293 psz_playnow
= httpd_MsgGet( query
, "x-playNow" );
1294 psz_transport
= httpd_MsgGet( query
, "Transport" );
1296 msg_Dbg( p_vod
, "HTTPD_MSG_SETUP: transport=%s", psz_transport
);
1298 if( strstr( psz_transport
, "unicast" ) &&
1299 strstr( psz_transport
, "client_port=" ) )
1301 rtsp_client_t
*p_rtsp
= NULL
;
1302 rtsp_client_es_t
*p_rtsp_es
= NULL
;
1303 char ip
[NI_MAXNUMERICHOST
];
1304 int i_port
= atoi( strstr( psz_transport
, "client_port=" ) +
1305 strlen("client_port=") );
1307 if( httpd_ClientIP( cl
, ip
, NULL
) == NULL
)
1309 answer
->i_status
= 500;
1311 answer
->p_body
= NULL
;
1315 msg_Dbg( p_vod
, "HTTPD_MSG_SETUP: unicast ip=%s port=%d",
1318 psz_session
= httpd_MsgGet( query
, "Session" );
1319 if( !psz_session
|| !*psz_session
)
1322 if( ( p_vod
->p_sys
->i_throttle_users
> 0 ) &&
1323 ( p_vod
->p_sys
->i_connections
>= p_vod
->p_sys
->i_throttle_users
) )
1325 answer
->i_status
= 503;
1327 answer
->p_body
= NULL
;
1330 #warning Session ID should be securely random (spoofing risk)
1331 if( asprintf( &psz_new
, "%lu", vlc_mrand48() ) < 0 )
1333 psz_session
= psz_new
;
1335 p_rtsp
= RtspClientNew( p_media
, psz_new
);
1338 answer
->i_status
= 454;
1340 answer
->p_body
= NULL
;
1346 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1349 answer
->i_status
= 454;
1351 answer
->p_body
= NULL
;
1356 p_rtsp_es
= malloc( sizeof(rtsp_client_es_t
) );
1359 answer
->i_status
= 500;
1361 answer
->p_body
= NULL
;
1364 p_rtsp_es
->i_port
= i_port
;
1365 p_rtsp_es
->p_media_es
= p_es
;
1366 TAB_APPEND( p_rtsp
->i_es
, p_rtsp
->es
, p_rtsp_es
);
1368 answer
->i_status
= 200;
1370 answer
->p_body
= NULL
;
1372 if( p_media
->b_raw
)
1374 if( strstr( psz_transport
, "MP2T/H2221/UDP" ) )
1376 httpd_MsgAdd( answer
, "Transport",
1377 "MP2T/H2221/UDP;unicast;client_port=%d-%d",
1378 p_rtsp_es
->i_port
, p_rtsp_es
->i_port
+ 1 );
1380 else if( strstr( psz_transport
, "RAW/RAW/UDP" ) )
1382 httpd_MsgAdd( answer
, "Transport",
1383 "RAW/RAW/UDP;unicast;client_port=%d-%d",
1384 p_rtsp_es
->i_port
, p_rtsp_es
->i_port
+ 1 );
1389 httpd_MsgAdd( answer
, "Transport",
1390 "RTP/AVP/UDP;unicast;client_port=%d-%d",
1391 p_rtsp_es
->i_port
, p_rtsp_es
->i_port
+ 1 );
1394 else /* TODO strstr( psz_transport, "interleaved" ) ) */
1396 answer
->i_status
= 461;
1398 answer
->p_body
= NULL
;
1401 /* Intentional fall-through on x-playNow option in RTSP request */
1406 case HTTPD_MSG_PLAY
:
1407 /* This is kind of a kludge. Should we only support Aggregate
1409 psz_session
= httpd_MsgGet( query
, "Session" );
1410 msg_Dbg( p_vod
, "HTTPD_MSG_PLAY for session: %s", psz_session
);
1412 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1414 psz_position
= httpd_MsgGet( query
, "Range" );
1415 if( psz_position
) psz_position
= strstr( psz_position
, "npt=" );
1418 int64_t i_time
= ParseNPT (psz_position
+ 4);
1419 msg_Dbg( p_vod
, "seeking request: %s", psz_position
);
1420 CommandPush( p_vod
, RTSP_CMD_TYPE_SEEK
, p_media
,
1421 psz_session
, i_time
, 0.0, NULL
);
1426 answer
->i_status
= 200;
1428 answer
->p_body
= NULL
;
1432 case HTTPD_MSG_TEARDOWN
:
1433 answer
->i_status
= 200;
1435 answer
->p_body
= NULL
;
1437 psz_session
= httpd_MsgGet( query
, "Session" );
1438 msg_Dbg( p_vod
, "HTTPD_MSG_TEARDOWN for session: %s", psz_session
);
1440 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1441 if( !p_rtsp
) break;
1443 for( int i
= 0; i
< p_rtsp
->i_es
; i
++ )
1445 rtsp_client_es_t
*es
= p_rtsp
->es
[i
];
1446 if( es
->p_media_es
== p_es
)
1448 TAB_REMOVE( p_rtsp
->i_es
, p_rtsp
->es
, es
);
1455 CommandPush( p_vod
, RTSP_CMD_TYPE_STOP
, p_media
, psz_session
,
1457 RtspClientDel( p_media
, p_rtsp
);
1461 case HTTPD_MSG_PAUSE
:
1462 /* This is kind of a kludge. Should we only support Aggregate
1464 psz_session
= httpd_MsgGet( query
, "Session" );
1465 msg_Dbg( p_vod
, "HTTPD_MSG_PAUSE for session: %s", psz_session
);
1467 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1468 if( !p_rtsp
) break;
1470 CommandPush( p_vod
, RTSP_CMD_TYPE_PAUSE
, p_media
, psz_session
,
1473 answer
->i_status
= 200;
1475 answer
->p_body
= NULL
;
1479 return VLC_EGENERIC
;
1482 httpd_MsgAdd( answer
, "Server", "VLC/%s", VERSION
);
1483 httpd_MsgAdd( answer
, "Content-Length", "%d", answer
->i_body
);
1484 psz_cseq
= httpd_MsgGet( query
, "Cseq" );
1486 i_cseq
= atoi( psz_cseq
);
1489 httpd_MsgAdd( answer
, "Cseq", "%d", i_cseq
);
1490 httpd_MsgAdd( answer
, "Cache-Control", "%s", "no-cache" );
1493 httpd_MsgAdd( answer
, "Session", "%s"/*;timeout=5*/, psz_session
);
1498 /*****************************************************************************
1500 * FIXME: need to be moved to a common place ?
1501 *****************************************************************************/
1502 static char *SDPGenerate( const vod_media_t
*p_media
, httpd_client_t
*cl
)
1504 struct vlc_memstream sdp
;
1505 char ip
[NI_MAXNUMERICHOST
];
1506 const char *psz_control
;
1509 if( httpd_ServerIP( cl
, ip
, &port
) == NULL
)
1512 bool ipv6
= ( strchr( ip
, ':' ) != NULL
);
1514 psz_control
= ipv6
? p_media
->psz_rtsp_control_v6
1515 : p_media
->psz_rtsp_control_v4
;
1517 /* Dummy destination address for RTSP */
1518 struct sockaddr_storage dst
;
1519 socklen_t dstlen
= ipv6
? sizeof( struct sockaddr_in6
)
1520 : sizeof( struct sockaddr_in
);
1521 memset (&dst
, 0, dstlen
);
1522 dst
.ss_family
= ipv6
? AF_INET6
: AF_INET
;
1524 dst
.ss_len
= dstlen
;
1527 if( vlc_sdp_Start( &sdp
, VLC_OBJECT( p_media
->p_vod
), "sout-rtp-",
1528 NULL
, 0, (struct sockaddr
*)&dst
, dstlen
) )
1531 if( p_media
->i_length
> 0 )
1533 lldiv_t d
= lldiv( p_media
->i_length
/ 1000, 1000 );
1534 sdp_AddAttribute( &sdp
, "range","npt=0-%lld.%03u", d
.quot
,
1538 for( int i
= 0; i
< p_media
->i_es
; i
++ )
1540 media_es_t
*p_es
= p_media
->es
[i
];
1541 const char *mime_major
; /* major MIME type */
1543 switch( p_es
->fmt
.i_cat
)
1546 mime_major
= "video";
1549 mime_major
= "audio";
1552 mime_major
= "text";
1558 sdp_AddMedia( &sdp
, mime_major
, "RTP/AVP", 0 /* p_es->i_port */,
1559 p_es
->i_payload_type
, false, 0,
1560 p_es
->psz_ptname
, p_es
->i_clock_rate
, p_es
->i_channels
,
1563 sdp_AddAttribute( &sdp
, "control", psz_control
, ip
, port
, i
);
1566 return vlc_memstream_close( &sdp
) ? NULL
: sdp
.ptr
;