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 #include <vlc_common.h>
34 #include <vlc_plugin.h>
35 #include <vlc_input.h>
37 #include <vlc_block.h>
39 #include <vlc_httpd.h>
42 #include <vlc_network.h>
43 #include <vlc_charset.h>
44 #include <vlc_strings.h>
55 /*****************************************************************************
57 *****************************************************************************/
58 static int Open ( vlc_object_t
* );
59 static void Close( vlc_object_t
* );
61 #define THROTTLE_TEXT N_( "Maximum number of connections" )
62 #define THROTTLE_LONGTEXT N_( "This limits the maximum number of clients " \
63 "that can connect to the RTSP VOD. 0 means no limit." )
65 #define RAWMUX_TEXT N_( "MUX for RAW RTSP transport" )
67 #define SESSION_TIMEOUT_TEXT N_( "Sets the timeout option in the RTSP " \
69 #define SESSION_TIMEOUT_LONGTEXT N_( "Defines what timeout option to add " \
70 "to the RTSP session ID string. Setting it to a negative number removes " \
71 "the timeout option entirely. This is needed by some IPTV STBs (such as " \
72 "those made by HansunTech) which get confused by it. The default is 5." )
75 set_shortname( N_("RTSP VoD" ) )
76 set_description( N_("RTSP VoD server") )
77 set_category( CAT_SOUT
)
78 set_subcategory( SUBCAT_SOUT_VOD
)
79 set_capability( "vod server", 1 )
80 set_callbacks( Open
, Close
)
81 add_shortcut( "rtsp" )
82 add_string( "rtsp-raw-mux", "ts", RAWMUX_TEXT
,
84 add_integer( "rtsp-throttle-users", 0, THROTTLE_TEXT
,
85 THROTTLE_LONGTEXT
, true )
86 add_integer( "rtsp-session-timeout", 5, SESSION_TIMEOUT_TEXT
,
87 SESSION_TIMEOUT_LONGTEXT
, true )
90 /*****************************************************************************
92 *****************************************************************************/
94 typedef struct media_es_t media_es_t
;
98 media_es_t
*p_media_es
;
107 bool b_playing
; /* is it in "play" state */
111 rtsp_client_es_t
**es
;
121 httpd_url_t
*p_rtsp_url
;
123 vod_media_t
*p_media
;
126 uint8_t i_payload_type
;
127 const char *psz_ptname
;
128 unsigned i_clock_rate
;
142 httpd_url_t
*p_rtsp_url
;
143 char *psz_rtsp_control_v4
;
144 char *psz_rtsp_control_v6
;
157 rtsp_client_t
**rtsp
;
166 httpd_host_t
*p_rtsp_host
;
168 int i_throttle_users
;
173 int i_session_timeout
;
182 block_fifo_t
*p_fifo_cmd
;
185 /* rtsp delayed command (to avoid deadlock between vlm/httpd) */
188 RTSP_CMD_TYPE_NONE
, /* Exit requested */
194 RTSP_CMD_TYPE_REWIND
,
195 RTSP_CMD_TYPE_FORWARD
,
206 vod_media_t
*p_media
;
213 static vod_media_t
*MediaNew( vod_t
*, const char *, input_item_t
* );
214 static void MediaDel( vod_t
*, vod_media_t
* );
215 static void MediaAskDel ( vod_t
*, vod_media_t
* );
216 static int MediaAddES( vod_t
*, vod_media_t
*, es_format_t
* );
217 static void MediaDelES( vod_t
*, vod_media_t
*, es_format_t
* );
219 static void* CommandThread( void * );
220 static void CommandPush( vod_t
*, rtsp_cmd_type_t
, vod_media_t
*,
221 const char *psz_session
, int64_t i_arg
,
222 double f_arg
, const char *psz_arg
);
224 static rtsp_client_t
*RtspClientNew( vod_media_t
*, char * );
225 static rtsp_client_t
*RtspClientGet( vod_media_t
*, const char * );
226 static void RtspClientDel( vod_media_t
*, rtsp_client_t
* );
228 static int RtspCallback( httpd_callback_sys_t
*, httpd_client_t
*,
229 httpd_message_t
*, const httpd_message_t
* );
230 static int RtspCallbackES( httpd_callback_sys_t
*, httpd_client_t
*,
231 httpd_message_t
*, const httpd_message_t
* );
233 static char *SDPGenerate( const vod_media_t
*, httpd_client_t
*cl
);
235 static void sprintf_hexa( char *s
, uint8_t *p_data
, int i_data
)
237 static const char hex
[16] = "0123456789abcdef";
239 for( int i
= 0; i
< i_data
; i
++ )
241 s
[2*i
+0] = hex
[(p_data
[i
]>>4)&0xf];
242 s
[2*i
+1] = hex
[(p_data
[i
] )&0xf];
247 /*****************************************************************************
248 * Open: Starts the RTSP server module
249 *****************************************************************************/
250 static int Open( vlc_object_t
*p_this
)
252 vod_t
*p_vod
= (vod_t
*)p_this
;
253 vod_sys_t
*p_sys
= NULL
;
254 char *psz_url
= NULL
;
257 psz_url
= var_InheritString( p_vod
, "rtsp-host" );
258 vlc_UrlParse( &url
, psz_url
, 0 );
261 p_vod
->p_sys
= p_sys
= malloc( sizeof( vod_sys_t
) );
262 if( !p_sys
) goto error
;
263 p_sys
->p_rtsp_host
= 0;
265 p_sys
->i_session_timeout
= var_CreateGetInteger( p_this
, "rtsp-session-timeout" );
267 p_sys
->i_throttle_users
= var_CreateGetInteger( p_this
, "rtsp-throttle-users" );
268 msg_Dbg( p_this
, "allowing up to %d connections", p_sys
->i_throttle_users
);
269 p_sys
->i_connections
= 0;
271 p_sys
->psz_raw_mux
= var_CreateGetString( p_this
, "rtsp-raw-mux" );
273 p_sys
->p_rtsp_host
= vlc_rtsp_HostNew( VLC_OBJECT(p_vod
) );
274 if( !p_sys
->p_rtsp_host
)
276 msg_Err( p_vod
, "cannot create RTSP server" );
280 p_sys
->psz_path
= strdup( url
.psz_path
? url
.psz_path
: "/" );
282 vlc_UrlClean( &url
);
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
);
304 if( p_sys
->p_rtsp_host
) httpd_HostDelete( p_sys
->p_rtsp_host
);
305 free( p_sys
->psz_raw_mux
);
308 vlc_UrlClean( &url
);
313 /*****************************************************************************
315 *****************************************************************************/
316 static void Close( vlc_object_t
* p_this
)
318 vod_t
*p_vod
= (vod_t
*)p_this
;
319 vod_sys_t
*p_sys
= p_vod
->p_sys
;
321 /* Stop command thread */
322 CommandPush( p_vod
, RTSP_CMD_TYPE_NONE
, NULL
, NULL
, 0, 0.0, NULL
);
323 vlc_join( p_sys
->thread
, NULL
);
325 while( block_FifoCount( p_sys
->p_fifo_cmd
) > 0 )
328 block_t
*p_block_cmd
= block_FifoGet( p_sys
->p_fifo_cmd
);
329 memcpy( &cmd
, p_block_cmd
->p_buffer
, sizeof(cmd
) );
330 block_Release( p_block_cmd
);
331 if ( cmd
.i_type
== RTSP_CMD_TYPE_DEL
)
332 MediaDel(p_vod
, cmd
.p_media
);
333 free( cmd
.psz_session
);
336 block_FifoRelease( p_sys
->p_fifo_cmd
);
338 httpd_HostDelete( p_sys
->p_rtsp_host
);
339 var_Destroy( p_this
, "rtsp-session-timeout" );
340 var_Destroy( p_this
, "rtsp-throttle-users" );
341 var_Destroy( p_this
, "rtsp-raw-mux" );
343 /* Check VLM is not buggy */
344 if( p_sys
->i_media
> 0 )
345 msg_Err( p_vod
, "rtsp vod leaking %d medias", p_sys
->i_media
);
346 TAB_CLEAN( p_sys
->i_media
, p_sys
->media
);
348 free( p_sys
->psz_path
);
349 free( p_sys
->psz_raw_mux
);
353 /*****************************************************************************
355 *****************************************************************************/
356 static vod_media_t
*MediaNew( vod_t
*p_vod
, const char *psz_name
,
357 input_item_t
*p_item
)
359 vod_sys_t
*p_sys
= p_vod
->p_sys
;
361 vod_media_t
*p_media
= calloc( 1, sizeof(vod_media_t
) );
365 p_media
->id
= p_sys
->i_media_id
++;
366 TAB_INIT( p_media
->i_es
, p_media
->es
);
367 p_media
->psz_mux
= NULL
;
368 TAB_INIT( p_media
->i_rtsp
, p_media
->rtsp
);
369 p_media
->b_raw
= false;
371 if( asprintf( &p_media
->psz_rtsp_path
, "%s%s",
372 p_sys
->psz_path
, psz_name
) <0 )
374 p_media
->p_rtsp_url
=
375 httpd_UrlNew( p_sys
->p_rtsp_host
, p_media
->psz_rtsp_path
, NULL
, NULL
);
377 if( !p_media
->p_rtsp_url
)
379 msg_Err( p_vod
, "cannot create RTSP url (%s)", p_media
->psz_rtsp_path
);
380 free( 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 )
391 httpd_UrlDelete( p_media
->p_rtsp_url
);
392 free( p_media
->psz_rtsp_path
);
396 if( asprintf( &p_media
->psz_rtsp_control_v6
,
397 "rtsp://[%%s]:%%d%s/trackID=%%d",
398 p_media
->psz_rtsp_path
) < 0 )
400 httpd_UrlDelete( p_media
->p_rtsp_url
);
401 free( p_media
->psz_rtsp_path
);
406 httpd_UrlCatch( p_media
->p_rtsp_url
, HTTPD_MSG_SETUP
,
407 RtspCallback
, (void*)p_media
);
408 httpd_UrlCatch( p_media
->p_rtsp_url
, HTTPD_MSG_DESCRIBE
,
409 RtspCallback
, (void*)p_media
);
410 httpd_UrlCatch( p_media
->p_rtsp_url
, HTTPD_MSG_PLAY
,
411 RtspCallback
, (void*)p_media
);
412 httpd_UrlCatch( p_media
->p_rtsp_url
, HTTPD_MSG_PAUSE
,
413 RtspCallback
, (void*)p_media
);
414 httpd_UrlCatch( p_media
->p_rtsp_url
, HTTPD_MSG_GETPARAMETER
,
415 RtspCallback
, (void*)p_media
);
416 httpd_UrlCatch( p_media
->p_rtsp_url
, HTTPD_MSG_TEARDOWN
,
417 RtspCallback
, (void*)p_media
);
419 p_media
->p_vod
= p_vod
;
421 vlc_mutex_init( &p_media
->lock
);
423 p_media
->i_length
= input_item_GetDuration( p_item
);
425 vlc_mutex_lock( &p_item
->lock
);
426 msg_Dbg( p_vod
, "media has %i declared ES", p_item
->i_es
);
427 for( int i
= 0; i
< p_item
->i_es
; i
++ )
429 MediaAddES( p_vod
, p_media
, p_item
->es
[i
] );
431 vlc_mutex_unlock( &p_item
->lock
);
433 CommandPush( p_vod
, RTSP_CMD_TYPE_ADD
, p_media
, NULL
, 0, 0.0, NULL
);
437 static void MediaAskDel ( vod_t
*p_vod
, vod_media_t
*p_media
)
439 CommandPush( p_vod
, RTSP_CMD_TYPE_DEL
, p_media
, NULL
, 0, 0.0, NULL
);
442 static void MediaDel( vod_t
*p_vod
, vod_media_t
*p_media
)
444 vod_sys_t
*p_sys
= p_vod
->p_sys
;
446 msg_Dbg( p_vod
, "deleting media: %s", p_media
->psz_rtsp_path
);
448 TAB_REMOVE( p_sys
->i_media
, p_sys
->media
, p_media
);
450 httpd_UrlDelete( p_media
->p_rtsp_url
);
452 while( p_media
->i_rtsp
> 0 )
453 RtspClientDel( p_media
, p_media
->rtsp
[0] );
454 TAB_CLEAN( p_media
->i_rtsp
, p_media
->rtsp
);
456 free( p_media
->psz_rtsp_path
);
457 free( p_media
->psz_rtsp_control_v6
);
458 free( p_media
->psz_rtsp_control_v4
);
460 while( p_media
->i_es
)
461 MediaDelES( p_vod
, p_media
, &p_media
->es
[0]->fmt
);
462 TAB_CLEAN( p_media
->i_es
, p_media
->es
);
464 vlc_mutex_destroy( &p_media
->lock
);
469 static int MediaAddES( vod_t
*p_vod
, vod_media_t
*p_media
, es_format_t
*p_fmt
)
473 media_es_t
*p_es
= calloc( 1, sizeof(media_es_t
) );
477 p_media
->psz_mux
= NULL
;
479 /* TODO: update SDP, etc... */
480 if( asprintf( &psz_urlc
, "%s/trackID=%d",
481 p_media
->psz_rtsp_path
, p_media
->i_es
) < 0 )
486 msg_Dbg( p_vod
, " - ES %4.4s (%s)", (char *)&p_fmt
->i_codec
, psz_urlc
);
488 /* Dynamic payload. No conflict since we put each ES in its own
490 p_es
->i_payload_type
= 96;
491 p_es
->i_clock_rate
= 90000;
492 p_es
->i_channels
= 1;
494 switch( p_fmt
->i_codec
)
497 if( p_fmt
->audio
.i_channels
== 1 && p_fmt
->audio
.i_rate
== 44100 )
499 p_es
->i_payload_type
= 11;
501 else if( p_fmt
->audio
.i_channels
== 2 &&
502 p_fmt
->audio
.i_rate
== 44100 )
504 p_es
->i_payload_type
= 10;
506 p_es
->psz_ptname
= "L16";
507 p_es
->i_clock_rate
= p_fmt
->audio
.i_rate
;
508 p_es
->i_channels
= p_fmt
->audio
.i_channels
;
511 p_es
->psz_ptname
= "L8";
512 p_es
->i_clock_rate
= p_fmt
->audio
.i_rate
;
513 p_es
->i_channels
= p_fmt
->audio
.i_channels
;
516 p_es
->i_payload_type
= 14;
517 p_es
->psz_ptname
= "MPA";
520 p_es
->i_payload_type
= 32;
521 p_es
->psz_ptname
= "MPV";
524 p_es
->psz_ptname
= "ac3";
525 p_es
->i_clock_rate
= p_fmt
->audio
.i_rate
;
528 p_es
->psz_ptname
= "H263-1998";
531 p_es
->psz_ptname
= "H264";
532 p_es
->psz_fmtp
= NULL
;
533 /* FIXME AAAAAAAAAAAARRRRRRRRGGGG copied from stream_out/rtp.c */
534 if( p_fmt
->i_extra
> 0 )
536 uint8_t *p_buffer
= p_fmt
->p_extra
;
537 int i_buffer
= p_fmt
->i_extra
;
538 char *p_64_sps
= NULL
;
539 char *p_64_pps
= NULL
;
542 while( i_buffer
> 4 )
547 while( p_buffer
[0] != 0 || p_buffer
[1] != 0 ||
552 if( i_buffer
== 0 ) break;
555 if( i_buffer
< 4 || memcmp(p_buffer
, "\x00\x00\x01", 3 ) )
557 /* No startcode found.. */
563 const int i_nal_type
= p_buffer
[0]&0x1f;
566 for( i_offset
= 0; i_offset
+2 < i_buffer
; i_offset
++)
568 if( !memcmp(p_buffer
+ i_offset
, "\x00\x00\x01", 3 ) )
570 /* we found another startcode */
571 while( i_offset
> 0 && 0 == p_buffer
[ i_offset
- 1 ] )
580 /* No-info found in nal */
584 if( i_nal_type
== 7 )
587 p_64_sps
= vlc_b64_encode_binary( p_buffer
, i_size
);
588 /* XXX: nothing ensures that i_size >= 4 ?? */
589 sprintf_hexa( hexa
, &p_buffer
[1], 3 );
591 else if( i_nal_type
== 8 )
594 p_64_pps
= vlc_b64_encode_binary( p_buffer
, i_size
);
600 if( p_64_sps
&& p_64_pps
)
602 if( asprintf( &p_es
->psz_fmtp
,
603 "packetization-mode=1;profile-level-id=%s;"
604 "sprop-parameter-sets=%s,%s;", hexa
, p_64_sps
,
617 if( !p_es
->psz_fmtp
)
618 p_es
->psz_fmtp
= strdup( "packetization-mode=1" );
621 p_es
->psz_ptname
= "MP4V-ES";
622 if( p_fmt
->i_extra
> 0 )
624 char *p_hexa
= malloc( 2 * p_fmt
->i_extra
+ 1 );
625 sprintf_hexa( p_hexa
, p_fmt
->p_extra
, p_fmt
->i_extra
);
626 if( asprintf( &p_es
->psz_fmtp
,
627 "profile-level-id=3; config=%s;", p_hexa
) == -1 )
628 p_es
->psz_fmtp
= NULL
;
633 p_es
->psz_ptname
= "mpeg4-generic";
634 p_es
->i_clock_rate
= p_fmt
->audio
.i_rate
;
635 if( p_fmt
->i_extra
> 0 )
637 char *p_hexa
= malloc( 2 * p_fmt
->i_extra
+ 1 );
638 sprintf_hexa( p_hexa
, p_fmt
->p_extra
, p_fmt
->i_extra
);
639 if( asprintf( &p_es
->psz_fmtp
,
640 "streamtype=5; profile-level-id=15; mode=AAC-hbr; "
641 "config=%s; SizeLength=13;IndexLength=3; "
642 "IndexDeltaLength=3; Profile=1;", p_hexa
) == -1 )
643 p_es
->psz_fmtp
= NULL
;
647 case VLC_FOURCC( 'm', 'p', '2', 't' ):
648 p_media
->psz_mux
= "ts";
649 p_es
->i_payload_type
= 33;
650 p_es
->psz_ptname
= "MP2T";
652 case VLC_FOURCC( 'm', 'p', '2', 'p' ):
653 p_media
->psz_mux
= "ps";
654 p_es
->psz_ptname
= "MP2P";
656 case VLC_CODEC_AMR_NB
:
657 p_es
->psz_ptname
= "AMR";
658 p_es
->i_clock_rate
= 8000;
659 if(p_fmt
->audio
.i_channels
== 2 )
660 p_es
->i_channels
= 2;
661 p_es
->psz_fmtp
= strdup( "octet-align=1" );
663 case VLC_CODEC_AMR_WB
:
664 p_es
->psz_ptname
= "AMR-WB";
665 p_es
->i_clock_rate
= 16000;
666 if(p_fmt
->audio
.i_channels
== 2 )
667 p_es
->i_channels
= 2;
668 p_es
->psz_fmtp
= strdup( "octet-align=1" );
672 msg_Err( p_vod
, "cannot add this stream (unsupported "
673 "codec: %4.4s)", (char*)&p_fmt
->i_codec
);
680 httpd_UrlNew( p_vod
->p_sys
->p_rtsp_host
, psz_urlc
, NULL
, NULL
);
682 if( !p_es
->p_rtsp_url
)
684 msg_Err( p_vod
, "cannot create RTSP url (%s)", psz_urlc
);
691 httpd_UrlCatch( p_es
->p_rtsp_url
, HTTPD_MSG_SETUP
,
692 RtspCallbackES
, (void*)p_es
);
693 httpd_UrlCatch( p_es
->p_rtsp_url
, HTTPD_MSG_TEARDOWN
,
694 RtspCallbackES
, (void*)p_es
);
695 httpd_UrlCatch( p_es
->p_rtsp_url
, HTTPD_MSG_PLAY
,
696 RtspCallbackES
, (void*)p_es
);
697 httpd_UrlCatch( p_es
->p_rtsp_url
, HTTPD_MSG_PAUSE
,
698 RtspCallbackES
, (void*)p_es
);
700 es_format_Copy( &p_es
->fmt
, p_fmt
);
702 p_es
->p_media
= p_media
;
704 vlc_mutex_lock( &p_media
->lock
);
705 TAB_APPEND( p_media
->i_es
, p_media
->es
, p_es
);
706 vlc_mutex_unlock( &p_media
->lock
);
711 static void MediaDelES( vod_t
*p_vod
, vod_media_t
*p_media
, es_format_t
*p_fmt
)
713 media_es_t
*p_es
= NULL
;
716 for( int i
= 0; i
< p_media
->i_es
; i
++ )
718 if( p_media
->es
[i
]->fmt
.i_cat
== p_fmt
->i_cat
&&
719 p_media
->es
[i
]->fmt
.i_codec
== p_fmt
->i_codec
&&
720 p_media
->es
[i
]->fmt
.i_id
== p_fmt
->i_id
)
722 p_es
= p_media
->es
[i
];
727 msg_Dbg( p_vod
, " - Removing ES %4.4s", (char *)&p_fmt
->i_codec
);
729 vlc_mutex_lock( &p_media
->lock
);
730 TAB_REMOVE( p_media
->i_es
, p_media
->es
, p_es
);
731 vlc_mutex_unlock( &p_media
->lock
);
733 free( p_es
->psz_fmtp
);
735 if( p_es
->p_rtsp_url
) httpd_UrlDelete( p_es
->p_rtsp_url
);
736 es_format_Clean( &p_es
->fmt
);
740 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
,
741 double f_arg
, const char *psz_arg
)
746 memset( &cmd
, 0, sizeof(cmd
) );
748 cmd
.p_media
= p_media
;
750 cmd
.i_media_id
= p_media
->id
;
752 cmd
.psz_session
= strdup(psz_session
);
756 cmd
.psz_arg
= strdup(psz_arg
);
758 p_cmd
= block_Alloc( sizeof(rtsp_cmd_t
) );
759 memcpy( p_cmd
->p_buffer
, &cmd
, sizeof(cmd
) );
761 block_FifoPut( p_vod
->p_sys
->p_fifo_cmd
, p_cmd
);
764 static void* CommandThread( void *obj
)
766 vod_t
*p_vod
= (vod_t
*)obj
;
767 vod_sys_t
*p_sys
= p_vod
->p_sys
;
768 int canc
= vlc_savecancel ();
772 block_t
*p_block_cmd
= block_FifoGet( p_sys
->p_fifo_cmd
);
774 vod_media_t
*p_media
= NULL
;
780 memcpy( &cmd
, p_block_cmd
->p_buffer
, sizeof(cmd
) );
781 block_Release( p_block_cmd
);
783 if( cmd
.i_type
== RTSP_CMD_TYPE_NONE
)
786 if ( cmd
.i_type
== RTSP_CMD_TYPE_ADD
)
788 TAB_APPEND( p_sys
->i_media
, p_sys
->media
, cmd
.p_media
);
792 if ( cmd
.i_type
== RTSP_CMD_TYPE_DEL
)
794 MediaDel(p_vod
, cmd
.p_media
);
799 for( i
= 0; i
< p_sys
->i_media
; i
++ )
801 if( p_sys
->media
[i
]->id
== cmd
.i_media_id
)
804 if( i
>= p_sys
->i_media
)
808 p_media
= p_sys
->media
[i
];
812 case RTSP_CMD_TYPE_PLAY
:
814 vod_MediaControl( p_vod
, p_media
, cmd
.psz_session
,
815 VOD_MEDIA_PLAY
, cmd
.psz_arg
, &cmd
.i_arg
);
817 case RTSP_CMD_TYPE_PAUSE
:
819 vod_MediaControl( p_vod
, p_media
, cmd
.psz_session
,
820 VOD_MEDIA_PAUSE
, &cmd
.i_arg
);
823 case RTSP_CMD_TYPE_STOP
:
824 vod_MediaControl( p_vod
, p_media
, cmd
.psz_session
, VOD_MEDIA_STOP
);
827 case RTSP_CMD_TYPE_SEEK
:
828 vod_MediaControl( p_vod
, p_media
, cmd
.psz_session
,
829 VOD_MEDIA_SEEK
, cmd
.i_arg
);
832 case RTSP_CMD_TYPE_REWIND
:
833 vod_MediaControl( p_vod
, p_media
, cmd
.psz_session
,
834 VOD_MEDIA_REWIND
, cmd
.f_arg
);
837 case RTSP_CMD_TYPE_FORWARD
:
838 vod_MediaControl( p_vod
, p_media
, cmd
.psz_session
,
839 VOD_MEDIA_FORWARD
, cmd
.f_arg
);
847 free( cmd
.psz_session
);
851 vlc_restorecancel (canc
);
855 /****************************************************************************
856 * RTSP server implementation
857 ****************************************************************************/
858 static rtsp_client_t
*RtspClientNew( vod_media_t
*p_media
, char *psz_session
)
860 rtsp_client_t
*p_rtsp
= calloc( 1, sizeof(rtsp_client_t
) );
866 p_rtsp
->psz_session
= psz_session
;
867 TAB_APPEND( p_media
->i_rtsp
, p_media
->rtsp
, p_rtsp
);
869 p_media
->p_vod
->p_sys
->i_connections
++;
870 msg_Dbg( p_media
->p_vod
, "new session: %s, connections: %d",
871 psz_session
, p_media
->p_vod
->p_sys
->i_throttle_users
);
876 static rtsp_client_t
*RtspClientGet( vod_media_t
*p_media
, const char *psz_session
)
878 for( int i
= 0; psz_session
&& i
< p_media
->i_rtsp
; i
++ )
880 if( !strcmp( p_media
->rtsp
[i
]->psz_session
, psz_session
) )
881 return p_media
->rtsp
[i
];
887 static void RtspClientDel( vod_media_t
*p_media
, rtsp_client_t
*p_rtsp
)
889 p_media
->p_vod
->p_sys
->i_connections
--;
890 msg_Dbg( p_media
->p_vod
, "closing session: %s, connections: %d",
891 p_rtsp
->psz_session
, p_media
->p_vod
->p_sys
->i_throttle_users
);
893 while( p_rtsp
->i_es
)
896 free( p_rtsp
->es
[p_rtsp
->i_es
] );
900 TAB_REMOVE( p_media
->i_rtsp
, p_media
->rtsp
, p_rtsp
);
902 free( p_rtsp
->psz_session
);
907 static int64_t ParseNPT (const char *str
)
909 locale_t loc
= newlocale (LC_NUMERIC_MASK
, "C", NULL
);
910 locale_t oldloc
= uselocale (loc
);
914 if (sscanf (str
, "%u:%u:%f", &hour
, &min
, &sec
) == 3)
915 sec
+= ((hour
* 60) + min
) * 60;
917 if (sscanf (str
, "%f", &sec
) != 1)
920 if (loc
!= (locale_t
)0)
925 return sec
* CLOCK_FREQ
;
929 static int RtspCallback( httpd_callback_sys_t
*p_args
, httpd_client_t
*cl
,
930 httpd_message_t
*answer
, const httpd_message_t
*query
)
932 vod_media_t
*p_media
= (vod_media_t
*)p_args
;
933 vod_t
*p_vod
= p_media
->p_vod
;
934 const char *psz_transport
= NULL
;
935 const char *psz_playnow
= NULL
; /* support option: x-playNow */
936 const char *psz_session
= NULL
;
937 const char *psz_cseq
= NULL
;
938 rtsp_client_t
*p_rtsp
;
941 if( answer
== NULL
|| query
== NULL
) return VLC_SUCCESS
;
943 msg_Dbg( p_vod
, "RtspCallback query: type=%d", query
->i_type
);
945 answer
->i_proto
= HTTPD_PROTO_RTSP
;
946 answer
->i_version
= query
->i_version
;
947 answer
->i_type
= HTTPD_MSG_ANSWER
;
949 answer
->p_body
= NULL
;
951 switch( query
->i_type
)
953 case HTTPD_MSG_SETUP
:
955 psz_playnow
= httpd_MsgGet( query
, "x-playNow" );
956 psz_transport
= httpd_MsgGet( query
, "Transport" );
957 if( psz_transport
== NULL
)
959 answer
->i_status
= 400;
962 msg_Dbg( p_vod
, "HTTPD_MSG_SETUP: transport=%s", psz_transport
);
964 if( strstr( psz_transport
, "unicast" ) &&
965 strstr( psz_transport
, "client_port=" ) )
967 rtsp_client_t
*p_rtsp
= NULL
;
968 char ip
[NI_MAXNUMERICHOST
];
969 int i_port
= atoi( strstr( psz_transport
, "client_port=" ) +
970 strlen("client_port=") );
972 if( strstr( psz_transport
, "MP2T/H2221/UDP" ) ||
973 strstr( psz_transport
, "RAW/RAW/UDP" ) )
975 p_media
->psz_mux
= p_vod
->p_sys
->psz_raw_mux
;
976 p_media
->b_raw
= true;
979 if( httpd_ClientIP( cl
, ip
, NULL
) == NULL
)
981 answer
->i_status
= 500;
983 answer
->p_body
= NULL
;
987 msg_Dbg( p_vod
, "HTTPD_MSG_SETUP: unicast ip=%s port=%d",
990 psz_session
= httpd_MsgGet( query
, "Session" );
991 if( !psz_session
|| !*psz_session
)
994 if( ( p_vod
->p_sys
->i_throttle_users
> 0 ) &&
995 ( p_vod
->p_sys
->i_connections
>= p_vod
->p_sys
->i_throttle_users
) )
997 answer
->i_status
= 503;
999 answer
->p_body
= NULL
;
1002 #warning Should use secure randomness here! (spoofing risk)
1003 if( asprintf( &psz_new
, "%lu", vlc_mrand48() ) < 0 )
1005 psz_session
= psz_new
;
1007 p_rtsp
= RtspClientNew( p_media
, psz_new
);
1010 answer
->i_status
= 454;
1012 answer
->p_body
= NULL
;
1018 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1021 answer
->i_status
= 454;
1023 answer
->p_body
= NULL
;
1028 answer
->i_status
= 200;
1030 answer
->p_body
= NULL
;
1032 if( p_media
->b_raw
)
1034 p_rtsp
->i_port_raw
= i_port
;
1036 if( strstr( psz_transport
, "MP2T/H2221/UDP" ) )
1038 httpd_MsgAdd( answer
, "Transport",
1039 "MP2T/H2221/UDP;unicast;client_port=%d-%d",
1040 i_port
, i_port
+ 1 );
1042 else if( strstr( psz_transport
, "RAW/RAW/UDP" ) )
1044 httpd_MsgAdd( answer
, "Transport",
1045 "RAW/RAW/UDP;unicast;client_port=%d-%d",
1046 i_port
, i_port
+ 1 );
1050 httpd_MsgAdd( answer
, "Transport",
1051 "RTP/AVP/UDP;unicast;client_port=%d-%d",
1052 i_port
, i_port
+ 1 );
1054 else /* TODO strstr( psz_transport, "interleaved" ) ) */
1056 answer
->i_status
= 461;
1058 answer
->p_body
= NULL
;
1061 /* Intentional fall-through on x-playNow option in RTSP request */
1066 case HTTPD_MSG_PLAY
:
1068 char *psz_output
, ip
[NI_MAXNUMERICHOST
];
1069 int i_port_audio
= 0, i_port_video
= 0;
1071 /* for now only multicast so easy */
1074 answer
->i_status
= 200;
1076 answer
->p_body
= NULL
;
1080 psz_session
= httpd_MsgGet( query
, "Session" );
1081 msg_Dbg( p_vod
, "HTTPD_MSG_PLAY for session: %s", psz_session
);
1083 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1086 answer
->i_status
= 500;
1088 answer
->p_body
= NULL
;
1092 if( p_rtsp
->b_playing
)
1094 const char *psz_position
= httpd_MsgGet( query
, "Range" );
1095 const char *psz_scale
= httpd_MsgGet( query
, "Scale" );
1097 psz_position
= strstr( psz_position
, "npt=" );
1098 if( psz_position
&& !psz_scale
)
1100 int64_t i_time
= ParseNPT (psz_position
+ 4);
1101 msg_Dbg( p_vod
, "seeking request: %s", psz_position
);
1102 CommandPush( p_vod
, RTSP_CMD_TYPE_SEEK
, p_media
,
1103 psz_session
, i_time
, 0.0, NULL
);
1105 else if( psz_scale
)
1107 double f_scale
= 0.0;
1110 f_scale
= us_strtod( psz_scale
, &end
);
1111 if( end
> psz_scale
)
1113 f_scale
= (f_scale
* 30.0);
1114 if( psz_scale
[0] == '-' ) /* rewind */
1116 msg_Dbg( p_vod
, "rewind request: %s", psz_scale
);
1117 CommandPush( p_vod
, RTSP_CMD_TYPE_REWIND
, p_media
,
1118 psz_session
, 0, f_scale
, NULL
);
1120 else if(psz_scale
[0] != '1' ) /* fast-forward */
1122 msg_Dbg( p_vod
, "fastforward request: %s",
1124 CommandPush( p_vod
, RTSP_CMD_TYPE_FORWARD
, p_media
,
1125 psz_session
, 0, f_scale
, NULL
);
1129 /* unpause, in case it's paused */
1130 CommandPush( p_vod
, RTSP_CMD_TYPE_PLAY
, p_media
, psz_session
,
1135 if( httpd_ClientIP( cl
, ip
, NULL
) == NULL
) break;
1137 p_rtsp
->b_playing
= true;
1139 /* FIXME for != 1 video and 1 audio */
1140 for( int i
= 0; i
< p_rtsp
->i_es
; i
++ )
1142 if( p_rtsp
->es
[i
]->p_media_es
->fmt
.i_cat
== AUDIO_ES
)
1143 i_port_audio
= p_rtsp
->es
[i
]->i_port
;
1144 if( p_rtsp
->es
[i
]->p_media_es
->fmt
.i_cat
== VIDEO_ES
)
1145 i_port_video
= p_rtsp
->es
[i
]->i_port
;
1148 if( p_media
->psz_mux
)
1150 if( p_media
->b_raw
)
1152 if( asprintf( &psz_output
,
1153 "std{access=udp,dst=%s:%i,mux=%s}",
1154 ip
, p_rtsp
->i_port_raw
, p_media
->psz_mux
) < 0 )
1159 if( asprintf( &psz_output
,
1160 "rtp{dst=%s,port=%i,mux=%s}",
1161 ip
, i_port_video
, p_media
->psz_mux
) < 0 )
1167 if( asprintf( &psz_output
,
1168 "rtp{dst=%s,port-video=%i,port-audio=%i}",
1169 ip
, i_port_video
, i_port_audio
) < 0 )
1173 CommandPush( p_vod
, RTSP_CMD_TYPE_PLAY
, p_media
, psz_session
,
1174 0, 0.0, psz_output
);
1179 case HTTPD_MSG_DESCRIBE
:
1182 SDPGenerate( p_media
, cl
);
1184 if( psz_sdp
!= NULL
)
1186 answer
->i_status
= 200;
1187 httpd_MsgAdd( answer
, "Content-type", "%s",
1188 "application/sdp" );
1190 answer
->p_body
= (uint8_t *)psz_sdp
;
1191 answer
->i_body
= strlen( psz_sdp
);
1195 answer
->i_status
= 500;
1196 answer
->p_body
= NULL
;
1202 case HTTPD_MSG_PAUSE
:
1203 psz_session
= httpd_MsgGet( query
, "Session" );
1204 msg_Dbg( p_vod
, "HTTPD_MSG_PAUSE for session: %s", psz_session
);
1206 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1207 if( !p_rtsp
) break;
1209 CommandPush( p_vod
, RTSP_CMD_TYPE_PAUSE
, p_media
, psz_session
,
1212 answer
->i_status
= 200;
1214 answer
->p_body
= NULL
;
1217 case HTTPD_MSG_TEARDOWN
:
1218 /* for now only multicast so easy again */
1219 answer
->i_status
= 200;
1221 answer
->p_body
= NULL
;
1223 psz_session
= httpd_MsgGet( query
, "Session" );
1224 msg_Dbg( p_vod
, "HTTPD_MSG_TEARDOWN for session: %s", psz_session
);
1226 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1227 if( !p_rtsp
) break;
1229 CommandPush( p_vod
, RTSP_CMD_TYPE_STOP
, p_media
, psz_session
,
1231 RtspClientDel( p_media
, p_rtsp
);
1234 case HTTPD_MSG_GETPARAMETER
:
1235 answer
->i_status
= 200;
1237 answer
->p_body
= NULL
;
1241 return VLC_EGENERIC
;
1244 httpd_MsgAdd( answer
, "Server", "VLC/%s", VERSION
);
1245 httpd_MsgAdd( answer
, "Content-Length", "%d", answer
->i_body
);
1246 psz_cseq
= httpd_MsgGet( query
, "Cseq" );
1247 psz_cseq
? i_cseq
= atoi( psz_cseq
) : 0;
1248 httpd_MsgAdd( answer
, "CSeq", "%d", i_cseq
);
1249 httpd_MsgAdd( answer
, "Cache-Control", "%s", "no-cache" );
1253 if( p_media
->p_vod
->p_sys
->i_session_timeout
>= 0 )
1254 httpd_MsgAdd( answer
, "Session", "%s;timeout=%i", psz_session
,
1255 p_media
->p_vod
->p_sys
->i_session_timeout
);
1257 httpd_MsgAdd( answer
, "Session", "%s", psz_session
);
1263 static int RtspCallbackES( httpd_callback_sys_t
*p_args
, httpd_client_t
*cl
,
1264 httpd_message_t
*answer
,
1265 const httpd_message_t
*query
)
1267 media_es_t
*p_es
= (media_es_t
*)p_args
;
1268 vod_media_t
*p_media
= p_es
->p_media
;
1269 vod_t
*p_vod
= p_media
->p_vod
;
1270 rtsp_client_t
*p_rtsp
= NULL
;
1271 const char *psz_transport
= NULL
;
1272 const char *psz_playnow
= NULL
; /* support option: x-playNow */
1273 const char *psz_session
= NULL
;
1274 const char *psz_position
= NULL
;
1275 const char *psz_cseq
= NULL
;
1278 if( answer
== NULL
|| query
== NULL
) return VLC_SUCCESS
;
1280 msg_Dbg( p_vod
, "RtspCallback query: type=%d", query
->i_type
);
1282 answer
->i_proto
= HTTPD_PROTO_RTSP
;
1283 answer
->i_version
= query
->i_version
;
1284 answer
->i_type
= HTTPD_MSG_ANSWER
;
1286 answer
->p_body
= NULL
;
1288 switch( query
->i_type
)
1290 case HTTPD_MSG_SETUP
:
1291 psz_playnow
= httpd_MsgGet( query
, "x-playNow" );
1292 psz_transport
= httpd_MsgGet( query
, "Transport" );
1294 msg_Dbg( p_vod
, "HTTPD_MSG_SETUP: transport=%s", psz_transport
);
1296 if( strstr( psz_transport
, "unicast" ) &&
1297 strstr( psz_transport
, "client_port=" ) )
1299 rtsp_client_t
*p_rtsp
= NULL
;
1300 rtsp_client_es_t
*p_rtsp_es
= NULL
;
1301 char ip
[NI_MAXNUMERICHOST
];
1302 int i_port
= atoi( strstr( psz_transport
, "client_port=" ) +
1303 strlen("client_port=") );
1305 if( httpd_ClientIP( cl
, ip
, NULL
) == NULL
)
1307 answer
->i_status
= 500;
1309 answer
->p_body
= NULL
;
1313 msg_Dbg( p_vod
, "HTTPD_MSG_SETUP: unicast ip=%s port=%d",
1316 psz_session
= httpd_MsgGet( query
, "Session" );
1317 if( !psz_session
|| !*psz_session
)
1320 if( ( p_vod
->p_sys
->i_throttle_users
> 0 ) &&
1321 ( p_vod
->p_sys
->i_connections
>= p_vod
->p_sys
->i_throttle_users
) )
1323 answer
->i_status
= 503;
1325 answer
->p_body
= NULL
;
1328 #warning Session ID should be securely random (spoofing risk)
1329 if( asprintf( &psz_new
, "%lu", vlc_mrand48() ) < 0 )
1331 psz_session
= psz_new
;
1333 p_rtsp
= RtspClientNew( p_media
, psz_new
);
1336 answer
->i_status
= 454;
1338 answer
->p_body
= NULL
;
1344 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1347 answer
->i_status
= 454;
1349 answer
->p_body
= NULL
;
1354 p_rtsp_es
= malloc( sizeof(rtsp_client_es_t
) );
1357 answer
->i_status
= 500;
1359 answer
->p_body
= NULL
;
1362 p_rtsp_es
->i_port
= i_port
;
1363 p_rtsp_es
->p_media_es
= p_es
;
1364 TAB_APPEND( p_rtsp
->i_es
, p_rtsp
->es
, p_rtsp_es
);
1366 answer
->i_status
= 200;
1368 answer
->p_body
= NULL
;
1370 if( p_media
->b_raw
)
1372 if( strstr( psz_transport
, "MP2T/H2221/UDP" ) )
1374 httpd_MsgAdd( answer
, "Transport",
1375 "MP2T/H2221/UDP;unicast;client_port=%d-%d",
1376 p_rtsp_es
->i_port
, p_rtsp_es
->i_port
+ 1 );
1378 else if( strstr( psz_transport
, "RAW/RAW/UDP" ) )
1380 httpd_MsgAdd( answer
, "Transport",
1381 "RAW/RAW/UDP;unicast;client_port=%d-%d",
1382 p_rtsp_es
->i_port
, p_rtsp_es
->i_port
+ 1 );
1387 httpd_MsgAdd( answer
, "Transport",
1388 "RTP/AVP/UDP;unicast;client_port=%d-%d",
1389 p_rtsp_es
->i_port
, p_rtsp_es
->i_port
+ 1 );
1392 else /* TODO strstr( psz_transport, "interleaved" ) ) */
1394 answer
->i_status
= 461;
1396 answer
->p_body
= NULL
;
1399 /* Intentional fall-through on x-playNow option in RTSP request */
1403 case HTTPD_MSG_PLAY
:
1404 /* This is kind of a kludge. Should we only support Aggregate
1406 psz_session
= httpd_MsgGet( query
, "Session" );
1407 msg_Dbg( p_vod
, "HTTPD_MSG_PLAY for session: %s", psz_session
);
1409 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1411 psz_position
= httpd_MsgGet( query
, "Range" );
1412 if( psz_position
) psz_position
= strstr( psz_position
, "npt=" );
1415 int64_t i_time
= ParseNPT (psz_position
+ 4);
1416 msg_Dbg( p_vod
, "seeking request: %s", psz_position
);
1417 CommandPush( p_vod
, RTSP_CMD_TYPE_SEEK
, p_media
,
1418 psz_session
, i_time
, 0.0, NULL
);
1423 answer
->i_status
= 200;
1425 answer
->p_body
= NULL
;
1429 case HTTPD_MSG_TEARDOWN
:
1430 answer
->i_status
= 200;
1432 answer
->p_body
= NULL
;
1434 psz_session
= httpd_MsgGet( query
, "Session" );
1435 msg_Dbg( p_vod
, "HTTPD_MSG_TEARDOWN for session: %s", psz_session
);
1437 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1438 if( !p_rtsp
) break;
1440 for( int i
= 0; i
< p_rtsp
->i_es
; i
++ )
1442 rtsp_client_es_t
*es
= p_rtsp
->es
[i
];
1443 if( es
->p_media_es
== p_es
)
1445 TAB_REMOVE( p_rtsp
->i_es
, p_rtsp
->es
, es
);
1452 CommandPush( p_vod
, RTSP_CMD_TYPE_STOP
, p_media
, psz_session
,
1454 RtspClientDel( p_media
, p_rtsp
);
1458 case HTTPD_MSG_PAUSE
:
1459 /* This is kind of a kludge. Should we only support Aggregate
1461 psz_session
= httpd_MsgGet( query
, "Session" );
1462 msg_Dbg( p_vod
, "HTTPD_MSG_PAUSE for session: %s", psz_session
);
1464 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1465 if( !p_rtsp
) break;
1467 CommandPush( p_vod
, RTSP_CMD_TYPE_PAUSE
, p_media
, psz_session
,
1470 answer
->i_status
= 200;
1472 answer
->p_body
= NULL
;
1476 return VLC_EGENERIC
;
1480 httpd_MsgAdd( answer
, "Server", "VLC/%s", VERSION
);
1481 httpd_MsgAdd( answer
, "Content-Length", "%d", answer
->i_body
);
1482 psz_cseq
= httpd_MsgGet( query
, "Cseq" );
1484 i_cseq
= atoi( psz_cseq
);
1487 httpd_MsgAdd( answer
, "Cseq", "%d", i_cseq
);
1488 httpd_MsgAdd( answer
, "Cache-Control", "%s", "no-cache" );
1491 httpd_MsgAdd( answer
, "Session", "%s"/*;timeout=5*/, psz_session
);
1496 /*****************************************************************************
1498 * FIXME: need to be moved to a common place ?
1499 *****************************************************************************/
1500 static char *SDPGenerate( const vod_media_t
*p_media
, httpd_client_t
*cl
)
1502 char *psz_sdp
, ip
[NI_MAXNUMERICHOST
];
1503 const char *psz_control
;
1506 if( httpd_ServerIP( cl
, ip
, &port
) == NULL
)
1509 bool ipv6
= ( strchr( ip
, ':' ) != NULL
);
1511 psz_control
= ipv6
? p_media
->psz_rtsp_control_v6
1512 : p_media
->psz_rtsp_control_v4
;
1514 /* Dummy destination address for RTSP */
1515 struct sockaddr_storage dst
;
1516 socklen_t dstlen
= ipv6
? sizeof( struct sockaddr_in6
)
1517 : sizeof( struct sockaddr_in
);
1518 memset (&dst
, 0, dstlen
);
1519 dst
.ss_family
= ipv6
? AF_INET6
: AF_INET
;
1521 dst
.ss_len
= dstlen
;
1524 psz_sdp
= vlc_sdp_Start( VLC_OBJECT( p_media
->p_vod
), "sout-rtp-",
1525 NULL
, 0, (struct sockaddr
*)&dst
, dstlen
);
1526 if( psz_sdp
== NULL
)
1529 if( p_media
->i_length
> 0 )
1531 lldiv_t d
= lldiv( p_media
->i_length
/ 1000, 1000 );
1532 sdp_AddAttribute( &psz_sdp
, "range","npt=0-%lld.%03u", d
.quot
,
1536 for( int i
= 0; i
< p_media
->i_es
; i
++ )
1538 media_es_t
*p_es
= p_media
->es
[i
];
1539 const char *mime_major
; /* major MIME type */
1541 switch( p_es
->fmt
.i_cat
)
1544 mime_major
= "video";
1547 mime_major
= "audio";
1550 mime_major
= "text";
1556 sdp_AddMedia( &psz_sdp
, mime_major
, "RTP/AVP", 0 /* p_es->i_port */,
1557 p_es
->i_payload_type
, false, 0,
1558 p_es
->psz_ptname
, p_es
->i_clock_rate
, p_es
->i_channels
,
1561 sdp_AddAttribute( &psz_sdp
, "control", psz_control
, ip
, port
, i
);