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
);
681 vod_sys_t
*p_sys
= p_vod
->p_sys
;
683 httpd_UrlNew( p_sys
->p_rtsp_host
, psz_urlc
, NULL
, NULL
);
685 if( !p_es
->p_rtsp_url
)
687 msg_Err( p_vod
, "cannot create RTSP url (%s)", psz_urlc
);
694 httpd_UrlCatch( p_es
->p_rtsp_url
, HTTPD_MSG_SETUP
,
695 RtspCallbackES
, (void*)p_es
);
696 httpd_UrlCatch( p_es
->p_rtsp_url
, HTTPD_MSG_TEARDOWN
,
697 RtspCallbackES
, (void*)p_es
);
698 httpd_UrlCatch( p_es
->p_rtsp_url
, HTTPD_MSG_PLAY
,
699 RtspCallbackES
, (void*)p_es
);
700 httpd_UrlCatch( p_es
->p_rtsp_url
, HTTPD_MSG_PAUSE
,
701 RtspCallbackES
, (void*)p_es
);
703 es_format_Copy( &p_es
->fmt
, p_fmt
);
705 p_es
->p_media
= p_media
;
707 vlc_mutex_lock( &p_media
->lock
);
708 TAB_APPEND( p_media
->i_es
, p_media
->es
, p_es
);
709 vlc_mutex_unlock( &p_media
->lock
);
714 static void MediaDelES( vod_t
*p_vod
, vod_media_t
*p_media
, es_format_t
*p_fmt
)
716 media_es_t
*p_es
= NULL
;
719 for( int i
= 0; i
< p_media
->i_es
; i
++ )
721 if( p_media
->es
[i
]->fmt
.i_cat
== p_fmt
->i_cat
&&
722 p_media
->es
[i
]->fmt
.i_codec
== p_fmt
->i_codec
&&
723 p_media
->es
[i
]->fmt
.i_id
== p_fmt
->i_id
)
725 p_es
= p_media
->es
[i
];
730 msg_Dbg( p_vod
, " - Removing ES %4.4s", (char *)&p_fmt
->i_codec
);
732 vlc_mutex_lock( &p_media
->lock
);
733 TAB_REMOVE( p_media
->i_es
, p_media
->es
, p_es
);
734 vlc_mutex_unlock( &p_media
->lock
);
736 free( p_es
->psz_fmtp
);
738 if( p_es
->p_rtsp_url
) httpd_UrlDelete( p_es
->p_rtsp_url
);
739 es_format_Clean( &p_es
->fmt
);
743 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
,
744 double f_arg
, const char *psz_arg
)
749 memset( &cmd
, 0, sizeof(cmd
) );
751 cmd
.p_media
= p_media
;
753 cmd
.i_media_id
= p_media
->id
;
755 cmd
.psz_session
= strdup(psz_session
);
759 cmd
.psz_arg
= strdup(psz_arg
);
761 p_cmd
= block_Alloc( sizeof(rtsp_cmd_t
) );
762 memcpy( p_cmd
->p_buffer
, &cmd
, sizeof(cmd
) );
764 vod_sys_t
*p_sys
= p_vod
->p_sys
;
765 block_FifoPut( p_sys
->p_fifo_cmd
, p_cmd
);
768 static void* CommandThread( void *obj
)
770 vod_t
*p_vod
= (vod_t
*)obj
;
771 vod_sys_t
*p_sys
= p_vod
->p_sys
;
772 int canc
= vlc_savecancel ();
776 block_t
*p_block_cmd
= block_FifoGet( p_sys
->p_fifo_cmd
);
778 vod_media_t
*p_media
= NULL
;
784 memcpy( &cmd
, p_block_cmd
->p_buffer
, sizeof(cmd
) );
785 block_Release( p_block_cmd
);
787 if( cmd
.i_type
== RTSP_CMD_TYPE_NONE
)
790 if ( cmd
.i_type
== RTSP_CMD_TYPE_ADD
)
792 TAB_APPEND( p_sys
->i_media
, p_sys
->media
, cmd
.p_media
);
796 if ( cmd
.i_type
== RTSP_CMD_TYPE_DEL
)
798 MediaDel(p_vod
, cmd
.p_media
);
803 for( i
= 0; i
< p_sys
->i_media
; i
++ )
805 if( p_sys
->media
[i
]->id
== cmd
.i_media_id
)
808 if( i
>= p_sys
->i_media
)
812 p_media
= p_sys
->media
[i
];
816 case RTSP_CMD_TYPE_PLAY
:
818 vod_MediaControl( p_vod
, p_media
, cmd
.psz_session
,
819 VOD_MEDIA_PLAY
, cmd
.psz_arg
, &cmd
.i_arg
);
821 case RTSP_CMD_TYPE_PAUSE
:
823 vod_MediaControl( p_vod
, p_media
, cmd
.psz_session
,
824 VOD_MEDIA_PAUSE
, &cmd
.i_arg
);
827 case RTSP_CMD_TYPE_STOP
:
828 vod_MediaControl( p_vod
, p_media
, cmd
.psz_session
, VOD_MEDIA_STOP
);
831 case RTSP_CMD_TYPE_SEEK
:
832 vod_MediaControl( p_vod
, p_media
, cmd
.psz_session
,
833 VOD_MEDIA_SEEK
, cmd
.i_arg
);
836 case RTSP_CMD_TYPE_REWIND
:
837 vod_MediaControl( p_vod
, p_media
, cmd
.psz_session
,
838 VOD_MEDIA_REWIND
, cmd
.f_arg
);
841 case RTSP_CMD_TYPE_FORWARD
:
842 vod_MediaControl( p_vod
, p_media
, cmd
.psz_session
,
843 VOD_MEDIA_FORWARD
, cmd
.f_arg
);
851 free( cmd
.psz_session
);
855 vlc_restorecancel (canc
);
859 /****************************************************************************
860 * RTSP server implementation
861 ****************************************************************************/
862 static rtsp_client_t
*RtspClientNew( vod_media_t
*p_media
, char *psz_session
)
864 rtsp_client_t
*p_rtsp
= calloc( 1, sizeof(rtsp_client_t
) );
870 p_rtsp
->psz_session
= psz_session
;
871 TAB_APPEND( p_media
->i_rtsp
, p_media
->rtsp
, p_rtsp
);
873 vod_sys_t
*p_sys
= p_media
->p_vod
->p_sys
;
874 p_sys
->i_connections
++;
875 msg_Dbg( p_media
->p_vod
, "new session: %s, connections: %d",
876 psz_session
, p_sys
->i_throttle_users
);
881 static rtsp_client_t
*RtspClientGet( vod_media_t
*p_media
, const char *psz_session
)
883 for( int i
= 0; psz_session
&& i
< p_media
->i_rtsp
; i
++ )
885 if( !strcmp( p_media
->rtsp
[i
]->psz_session
, psz_session
) )
886 return p_media
->rtsp
[i
];
892 static void RtspClientDel( vod_media_t
*p_media
, rtsp_client_t
*p_rtsp
)
894 vod_sys_t
*p_sys
= p_media
->p_vod
->p_sys
;
895 p_sys
->i_connections
--;
896 msg_Dbg( p_media
->p_vod
, "closing session: %s, connections: %d",
897 p_rtsp
->psz_session
, p_sys
->i_throttle_users
);
899 while( p_rtsp
->i_es
)
902 free( p_rtsp
->es
[p_rtsp
->i_es
] );
906 TAB_REMOVE( p_media
->i_rtsp
, p_media
->rtsp
, p_rtsp
);
908 free( p_rtsp
->psz_session
);
913 static int64_t ParseNPT (const char *str
)
915 locale_t loc
= newlocale (LC_NUMERIC_MASK
, "C", NULL
);
916 locale_t oldloc
= uselocale (loc
);
920 if (sscanf (str
, "%u:%u:%f", &hour
, &min
, &sec
) == 3)
921 sec
+= ((hour
* 60) + min
) * 60;
923 if (sscanf (str
, "%f", &sec
) != 1)
926 if (loc
!= (locale_t
)0)
931 return vlc_tick_from_sec( sec
);
935 static int RtspCallback( httpd_callback_sys_t
*p_args
, httpd_client_t
*cl
,
936 httpd_message_t
*answer
, const httpd_message_t
*query
)
938 vod_media_t
*p_media
= (vod_media_t
*)p_args
;
939 vod_t
*p_vod
= p_media
->p_vod
;
940 vod_sys_t
*p_sys
= p_vod
->p_sys
;
941 const char *psz_transport
= NULL
;
942 const char *psz_playnow
= NULL
; /* support option: x-playNow */
943 const char *psz_session
= NULL
;
944 const char *psz_cseq
= NULL
;
945 rtsp_client_t
*p_rtsp
;
948 if( answer
== NULL
|| query
== NULL
) return VLC_SUCCESS
;
950 msg_Dbg( p_vod
, "RtspCallback query: type=%d", query
->i_type
);
952 answer
->i_proto
= HTTPD_PROTO_RTSP
;
953 answer
->i_version
= query
->i_version
;
954 answer
->i_type
= HTTPD_MSG_ANSWER
;
956 answer
->p_body
= NULL
;
958 switch( query
->i_type
)
960 case HTTPD_MSG_SETUP
:
962 psz_playnow
= httpd_MsgGet( query
, "x-playNow" );
963 psz_transport
= httpd_MsgGet( query
, "Transport" );
964 if( psz_transport
== NULL
)
966 answer
->i_status
= 400;
969 msg_Dbg( p_vod
, "HTTPD_MSG_SETUP: transport=%s", psz_transport
);
971 if( strstr( psz_transport
, "unicast" ) &&
972 strstr( psz_transport
, "client_port=" ) )
974 rtsp_client_t
*p_rtsp
= NULL
;
975 char ip
[NI_MAXNUMERICHOST
];
976 int i_port
= atoi( strstr( psz_transport
, "client_port=" ) +
977 strlen("client_port=") );
979 if( strstr( psz_transport
, "MP2T/H2221/UDP" ) ||
980 strstr( psz_transport
, "RAW/RAW/UDP" ) )
982 p_media
->psz_mux
= p_sys
->psz_raw_mux
;
983 p_media
->b_raw
= true;
986 if( httpd_ClientIP( cl
, ip
, NULL
) == NULL
)
988 answer
->i_status
= 500;
990 answer
->p_body
= NULL
;
994 msg_Dbg( p_vod
, "HTTPD_MSG_SETUP: unicast ip=%s port=%d",
997 psz_session
= httpd_MsgGet( query
, "Session" );
998 if( !psz_session
|| !*psz_session
)
1001 if( ( p_sys
->i_throttle_users
> 0 ) &&
1002 ( p_sys
->i_connections
>= p_sys
->i_throttle_users
) )
1004 answer
->i_status
= 503;
1006 answer
->p_body
= NULL
;
1009 #warning Should use secure randomness here! (spoofing risk)
1010 if( asprintf( &psz_new
, "%lu", vlc_mrand48() ) < 0 )
1012 psz_session
= psz_new
;
1014 p_rtsp
= RtspClientNew( p_media
, psz_new
);
1017 answer
->i_status
= 454;
1019 answer
->p_body
= NULL
;
1025 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1028 answer
->i_status
= 454;
1030 answer
->p_body
= NULL
;
1035 answer
->i_status
= 200;
1037 answer
->p_body
= NULL
;
1039 if( p_media
->b_raw
)
1041 p_rtsp
->i_port_raw
= i_port
;
1043 if( strstr( psz_transport
, "MP2T/H2221/UDP" ) )
1045 httpd_MsgAdd( answer
, "Transport",
1046 "MP2T/H2221/UDP;unicast;client_port=%d-%d",
1047 i_port
, i_port
+ 1 );
1049 else if( strstr( psz_transport
, "RAW/RAW/UDP" ) )
1051 httpd_MsgAdd( answer
, "Transport",
1052 "RAW/RAW/UDP;unicast;client_port=%d-%d",
1053 i_port
, i_port
+ 1 );
1057 httpd_MsgAdd( answer
, "Transport",
1058 "RTP/AVP/UDP;unicast;client_port=%d-%d",
1059 i_port
, i_port
+ 1 );
1061 else /* TODO strstr( psz_transport, "interleaved" ) ) */
1063 answer
->i_status
= 461;
1065 answer
->p_body
= NULL
;
1068 /* Intentional fall-through on x-playNow option in RTSP request */
1071 } /* fall through */
1073 case HTTPD_MSG_PLAY
:
1075 char *psz_output
, ip
[NI_MAXNUMERICHOST
];
1076 int i_port_audio
= 0, i_port_video
= 0;
1078 /* for now only multicast so easy */
1081 answer
->i_status
= 200;
1083 answer
->p_body
= NULL
;
1087 psz_session
= httpd_MsgGet( query
, "Session" );
1088 msg_Dbg( p_vod
, "HTTPD_MSG_PLAY for session: %s", psz_session
);
1090 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1093 answer
->i_status
= 500;
1095 answer
->p_body
= NULL
;
1099 if( p_rtsp
->b_playing
)
1101 const char *psz_position
= httpd_MsgGet( query
, "Range" );
1102 const char *psz_scale
= httpd_MsgGet( query
, "Scale" );
1104 psz_position
= strstr( psz_position
, "npt=" );
1105 if( psz_position
&& !psz_scale
)
1107 int64_t i_time
= ParseNPT (psz_position
+ 4);
1108 msg_Dbg( p_vod
, "seeking request: %s", psz_position
);
1109 CommandPush( p_vod
, RTSP_CMD_TYPE_SEEK
, p_media
,
1110 psz_session
, i_time
, 0.0, NULL
);
1112 else if( psz_scale
)
1114 double f_scale
= 0.0;
1117 f_scale
= us_strtod( psz_scale
, &end
);
1118 if( end
> psz_scale
)
1120 f_scale
= (f_scale
* 30.0);
1121 if( psz_scale
[0] == '-' ) /* rewind */
1123 msg_Dbg( p_vod
, "rewind request: %s", psz_scale
);
1124 CommandPush( p_vod
, RTSP_CMD_TYPE_REWIND
, p_media
,
1125 psz_session
, 0, f_scale
, NULL
);
1127 else if(psz_scale
[0] != '1' ) /* fast-forward */
1129 msg_Dbg( p_vod
, "fastforward request: %s",
1131 CommandPush( p_vod
, RTSP_CMD_TYPE_FORWARD
, p_media
,
1132 psz_session
, 0, f_scale
, NULL
);
1136 /* unpause, in case it's paused */
1137 CommandPush( p_vod
, RTSP_CMD_TYPE_PLAY
, p_media
, psz_session
,
1142 if( httpd_ClientIP( cl
, ip
, NULL
) == NULL
) break;
1144 p_rtsp
->b_playing
= true;
1146 /* FIXME for != 1 video and 1 audio */
1147 for( int i
= 0; i
< p_rtsp
->i_es
; i
++ )
1149 if( p_rtsp
->es
[i
]->p_media_es
->fmt
.i_cat
== AUDIO_ES
)
1150 i_port_audio
= p_rtsp
->es
[i
]->i_port
;
1151 if( p_rtsp
->es
[i
]->p_media_es
->fmt
.i_cat
== VIDEO_ES
)
1152 i_port_video
= p_rtsp
->es
[i
]->i_port
;
1155 if( p_media
->psz_mux
)
1157 if( p_media
->b_raw
)
1159 if( asprintf( &psz_output
,
1160 "std{access=udp,dst=%s:%i,mux=%s}",
1161 ip
, p_rtsp
->i_port_raw
, p_media
->psz_mux
) < 0 )
1166 if( asprintf( &psz_output
,
1167 "rtp{dst=%s,port=%i,mux=%s}",
1168 ip
, i_port_video
, p_media
->psz_mux
) < 0 )
1174 if( asprintf( &psz_output
,
1175 "rtp{dst=%s,port-video=%i,port-audio=%i}",
1176 ip
, i_port_video
, i_port_audio
) < 0 )
1180 CommandPush( p_vod
, RTSP_CMD_TYPE_PLAY
, p_media
, psz_session
,
1181 0, 0.0, psz_output
);
1186 case HTTPD_MSG_DESCRIBE
:
1189 SDPGenerate( p_media
, cl
);
1191 if( psz_sdp
!= NULL
)
1193 answer
->i_status
= 200;
1194 httpd_MsgAdd( answer
, "Content-type", "%s",
1195 "application/sdp" );
1197 answer
->p_body
= (uint8_t *)psz_sdp
;
1198 answer
->i_body
= strlen( psz_sdp
);
1202 answer
->i_status
= 500;
1203 answer
->p_body
= NULL
;
1209 case HTTPD_MSG_PAUSE
:
1210 psz_session
= httpd_MsgGet( query
, "Session" );
1211 msg_Dbg( p_vod
, "HTTPD_MSG_PAUSE for session: %s", psz_session
);
1213 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1214 if( !p_rtsp
) break;
1216 CommandPush( p_vod
, RTSP_CMD_TYPE_PAUSE
, p_media
, psz_session
,
1219 answer
->i_status
= 200;
1221 answer
->p_body
= NULL
;
1224 case HTTPD_MSG_TEARDOWN
:
1225 /* for now only multicast so easy again */
1226 answer
->i_status
= 200;
1228 answer
->p_body
= NULL
;
1230 psz_session
= httpd_MsgGet( query
, "Session" );
1231 msg_Dbg( p_vod
, "HTTPD_MSG_TEARDOWN for session: %s", psz_session
);
1233 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1234 if( !p_rtsp
) break;
1236 CommandPush( p_vod
, RTSP_CMD_TYPE_STOP
, p_media
, psz_session
,
1238 RtspClientDel( p_media
, p_rtsp
);
1241 case HTTPD_MSG_GETPARAMETER
:
1242 answer
->i_status
= 200;
1244 answer
->p_body
= NULL
;
1248 return VLC_EGENERIC
;
1251 httpd_MsgAdd( answer
, "Server", "VLC/%s", VERSION
);
1252 httpd_MsgAdd( answer
, "Content-Length", "%d", answer
->i_body
);
1253 psz_cseq
= httpd_MsgGet( query
, "Cseq" );
1254 psz_cseq
? i_cseq
= atoi( psz_cseq
) : 0;
1255 httpd_MsgAdd( answer
, "CSeq", "%d", i_cseq
);
1256 httpd_MsgAdd( answer
, "Cache-Control", "%s", "no-cache" );
1260 if( p_sys
->i_session_timeout
>= 0 )
1261 httpd_MsgAdd( answer
, "Session", "%s;timeout=%i", psz_session
,
1262 p_sys
->i_session_timeout
);
1264 httpd_MsgAdd( answer
, "Session", "%s", psz_session
);
1270 static int RtspCallbackES( httpd_callback_sys_t
*p_args
, httpd_client_t
*cl
,
1271 httpd_message_t
*answer
,
1272 const httpd_message_t
*query
)
1274 media_es_t
*p_es
= (media_es_t
*)p_args
;
1275 vod_media_t
*p_media
= p_es
->p_media
;
1276 vod_t
*p_vod
= p_media
->p_vod
;
1277 vod_sys_t
*p_sys
= p_vod
->p_sys
;
1278 rtsp_client_t
*p_rtsp
= NULL
;
1279 const char *psz_transport
= NULL
;
1280 const char *psz_playnow
= NULL
; /* support option: x-playNow */
1281 const char *psz_session
= NULL
;
1282 const char *psz_position
= NULL
;
1283 const char *psz_cseq
= NULL
;
1286 if( answer
== NULL
|| query
== NULL
) return VLC_SUCCESS
;
1288 msg_Dbg( p_vod
, "RtspCallback query: type=%d", query
->i_type
);
1290 answer
->i_proto
= HTTPD_PROTO_RTSP
;
1291 answer
->i_version
= query
->i_version
;
1292 answer
->i_type
= HTTPD_MSG_ANSWER
;
1294 answer
->p_body
= NULL
;
1296 switch( query
->i_type
)
1298 case HTTPD_MSG_SETUP
:
1299 psz_playnow
= httpd_MsgGet( query
, "x-playNow" );
1300 psz_transport
= httpd_MsgGet( query
, "Transport" );
1302 msg_Dbg( p_vod
, "HTTPD_MSG_SETUP: transport=%s", psz_transport
);
1304 if( strstr( psz_transport
, "unicast" ) &&
1305 strstr( psz_transport
, "client_port=" ) )
1307 rtsp_client_t
*p_rtsp
= NULL
;
1308 rtsp_client_es_t
*p_rtsp_es
= NULL
;
1309 char ip
[NI_MAXNUMERICHOST
];
1310 int i_port
= atoi( strstr( psz_transport
, "client_port=" ) +
1311 strlen("client_port=") );
1313 if( httpd_ClientIP( cl
, ip
, NULL
) == NULL
)
1315 answer
->i_status
= 500;
1317 answer
->p_body
= NULL
;
1321 msg_Dbg( p_vod
, "HTTPD_MSG_SETUP: unicast ip=%s port=%d",
1324 psz_session
= httpd_MsgGet( query
, "Session" );
1325 if( !psz_session
|| !*psz_session
)
1328 if( ( p_sys
->i_throttle_users
> 0 ) &&
1329 ( p_sys
->i_connections
>= p_sys
->i_throttle_users
) )
1331 answer
->i_status
= 503;
1333 answer
->p_body
= NULL
;
1336 #warning Session ID should be securely random (spoofing risk)
1337 if( asprintf( &psz_new
, "%lu", vlc_mrand48() ) < 0 )
1339 psz_session
= psz_new
;
1341 p_rtsp
= RtspClientNew( p_media
, psz_new
);
1344 answer
->i_status
= 454;
1346 answer
->p_body
= NULL
;
1352 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1355 answer
->i_status
= 454;
1357 answer
->p_body
= NULL
;
1362 p_rtsp_es
= malloc( sizeof(rtsp_client_es_t
) );
1365 answer
->i_status
= 500;
1367 answer
->p_body
= NULL
;
1370 p_rtsp_es
->i_port
= i_port
;
1371 p_rtsp_es
->p_media_es
= p_es
;
1372 TAB_APPEND( p_rtsp
->i_es
, p_rtsp
->es
, p_rtsp_es
);
1374 answer
->i_status
= 200;
1376 answer
->p_body
= NULL
;
1378 if( p_media
->b_raw
)
1380 if( strstr( psz_transport
, "MP2T/H2221/UDP" ) )
1382 httpd_MsgAdd( answer
, "Transport",
1383 "MP2T/H2221/UDP;unicast;client_port=%d-%d",
1384 p_rtsp_es
->i_port
, p_rtsp_es
->i_port
+ 1 );
1386 else if( strstr( psz_transport
, "RAW/RAW/UDP" ) )
1388 httpd_MsgAdd( answer
, "Transport",
1389 "RAW/RAW/UDP;unicast;client_port=%d-%d",
1390 p_rtsp_es
->i_port
, p_rtsp_es
->i_port
+ 1 );
1395 httpd_MsgAdd( answer
, "Transport",
1396 "RTP/AVP/UDP;unicast;client_port=%d-%d",
1397 p_rtsp_es
->i_port
, p_rtsp_es
->i_port
+ 1 );
1400 else /* TODO strstr( psz_transport, "interleaved" ) ) */
1402 answer
->i_status
= 461;
1404 answer
->p_body
= NULL
;
1407 /* Intentional fall-through on x-playNow option in RTSP request */
1412 case HTTPD_MSG_PLAY
:
1413 /* This is kind of a kludge. Should we only support Aggregate
1415 psz_session
= httpd_MsgGet( query
, "Session" );
1416 msg_Dbg( p_vod
, "HTTPD_MSG_PLAY for session: %s", psz_session
);
1418 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1420 psz_position
= httpd_MsgGet( query
, "Range" );
1421 if( psz_position
) psz_position
= strstr( psz_position
, "npt=" );
1424 int64_t i_time
= ParseNPT (psz_position
+ 4);
1425 msg_Dbg( p_vod
, "seeking request: %s", psz_position
);
1426 CommandPush( p_vod
, RTSP_CMD_TYPE_SEEK
, p_media
,
1427 psz_session
, i_time
, 0.0, NULL
);
1432 answer
->i_status
= 200;
1434 answer
->p_body
= NULL
;
1438 case HTTPD_MSG_TEARDOWN
:
1439 answer
->i_status
= 200;
1441 answer
->p_body
= NULL
;
1443 psz_session
= httpd_MsgGet( query
, "Session" );
1444 msg_Dbg( p_vod
, "HTTPD_MSG_TEARDOWN for session: %s", psz_session
);
1446 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1447 if( !p_rtsp
) break;
1449 for( int i
= 0; i
< p_rtsp
->i_es
; i
++ )
1451 rtsp_client_es_t
*es
= p_rtsp
->es
[i
];
1452 if( es
->p_media_es
== p_es
)
1454 TAB_REMOVE( p_rtsp
->i_es
, p_rtsp
->es
, es
);
1461 CommandPush( p_vod
, RTSP_CMD_TYPE_STOP
, p_media
, psz_session
,
1463 RtspClientDel( p_media
, p_rtsp
);
1467 case HTTPD_MSG_PAUSE
:
1468 /* This is kind of a kludge. Should we only support Aggregate
1470 psz_session
= httpd_MsgGet( query
, "Session" );
1471 msg_Dbg( p_vod
, "HTTPD_MSG_PAUSE for session: %s", psz_session
);
1473 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1474 if( !p_rtsp
) break;
1476 CommandPush( p_vod
, RTSP_CMD_TYPE_PAUSE
, p_media
, psz_session
,
1479 answer
->i_status
= 200;
1481 answer
->p_body
= NULL
;
1485 return VLC_EGENERIC
;
1488 httpd_MsgAdd( answer
, "Server", "VLC/%s", VERSION
);
1489 httpd_MsgAdd( answer
, "Content-Length", "%d", answer
->i_body
);
1490 psz_cseq
= httpd_MsgGet( query
, "Cseq" );
1492 i_cseq
= atoi( psz_cseq
);
1495 httpd_MsgAdd( answer
, "Cseq", "%d", i_cseq
);
1496 httpd_MsgAdd( answer
, "Cache-Control", "%s", "no-cache" );
1499 httpd_MsgAdd( answer
, "Session", "%s"/*;timeout=5*/, psz_session
);
1504 /*****************************************************************************
1506 * FIXME: need to be moved to a common place ?
1507 *****************************************************************************/
1508 static char *SDPGenerate( const vod_media_t
*p_media
, httpd_client_t
*cl
)
1510 struct vlc_memstream sdp
;
1511 char ip
[NI_MAXNUMERICHOST
];
1512 const char *psz_control
;
1515 if( httpd_ServerIP( cl
, ip
, &port
) == NULL
)
1518 bool ipv6
= ( strchr( ip
, ':' ) != NULL
);
1520 psz_control
= ipv6
? p_media
->psz_rtsp_control_v6
1521 : p_media
->psz_rtsp_control_v4
;
1523 /* Dummy destination address for RTSP */
1524 struct sockaddr_storage dst
;
1525 socklen_t dstlen
= ipv6
? sizeof( struct sockaddr_in6
)
1526 : sizeof( struct sockaddr_in
);
1527 memset (&dst
, 0, dstlen
);
1528 dst
.ss_family
= ipv6
? AF_INET6
: AF_INET
;
1530 dst
.ss_len
= dstlen
;
1533 if( vlc_sdp_Start( &sdp
, VLC_OBJECT( p_media
->p_vod
), "sout-rtp-",
1534 NULL
, 0, (struct sockaddr
*)&dst
, dstlen
) )
1537 if( p_media
->i_length
> 0 )
1539 lldiv_t d
= lldiv( MS_FROM_VLC_TICK(p_media
->i_length
), VLC_TICK_FROM_MS(1) );
1540 sdp_AddAttribute( &sdp
, "range","npt=0-%lld.%03u", d
.quot
,
1544 for( int i
= 0; i
< p_media
->i_es
; i
++ )
1546 media_es_t
*p_es
= p_media
->es
[i
];
1547 const char *mime_major
; /* major MIME type */
1549 switch( p_es
->fmt
.i_cat
)
1552 mime_major
= "video";
1555 mime_major
= "audio";
1558 mime_major
= "text";
1564 sdp_AddMedia( &sdp
, mime_major
, "RTP/AVP", 0 /* p_es->i_port */,
1565 p_es
->i_payload_type
, false, 0,
1566 p_es
->psz_ptname
, p_es
->i_clock_rate
, p_es
->i_channels
,
1569 sdp_AddAttribute( &sdp
, "control", psz_control
, ip
, port
, i
);
1572 return vlc_memstream_close( &sdp
) ? NULL
: sdp
.ptr
;