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 HOST_TEXT N_( "RTSP host address" )
62 #define HOST_LONGTEXT N_( \
63 "This defines the address, port and path the RTSP VOD server will listen " \
64 "on.\nSyntax is address:port/path. The default is to listen on all "\
65 "interfaces (address 0.0.0.0), on port 554, with no path.\nTo listen " \
66 "only on the local interface, use \"localhost\" as address." )
68 #define THROTTLE_TEXT N_( "Maximum number of connections" )
69 #define THROTTLE_LONGTEXT N_( "This limits the maximum number of clients " \
70 "that can connect to the RTSP VOD. 0 means no limit." )
72 #define RAWMUX_TEXT N_( "MUX for RAW RTSP transport" )
74 #define SESSION_TIMEOUT_TEXT N_( "Sets the timeout option in the RTSP " \
76 #define SESSION_TIMEOUT_LONGTEXT N_( "Defines what timeout option to add " \
77 "to the RTSP session ID string. Setting it to a negative number removes " \
78 "the timeout option entirely. This is needed by some IPTV STBs (such as " \
79 "those made by HansunTech) which get confused by it. The default is 5." )
82 set_shortname( N_("RTSP VoD" ) )
83 set_description( N_("RTSP VoD server") )
84 set_category( CAT_SOUT
)
85 set_subcategory( SUBCAT_SOUT_VOD
)
86 set_capability( "vod server", 1 )
87 set_callbacks( Open
, Close
)
88 add_shortcut( "rtsp" )
89 add_string ( "rtsp-host", NULL
, HOST_TEXT
, HOST_LONGTEXT
, true )
90 add_string( "rtsp-raw-mux", "ts", RAWMUX_TEXT
,
92 add_integer( "rtsp-throttle-users", 0, THROTTLE_TEXT
,
93 THROTTLE_LONGTEXT
, true )
94 add_integer( "rtsp-session-timeout", 5, SESSION_TIMEOUT_TEXT
,
95 SESSION_TIMEOUT_LONGTEXT
, true )
98 /*****************************************************************************
100 *****************************************************************************/
102 typedef struct media_es_t media_es_t
;
106 media_es_t
*p_media_es
;
115 bool b_playing
; /* is it in "play" state */
119 rtsp_client_es_t
**es
;
129 httpd_url_t
*p_rtsp_url
;
131 vod_media_t
*p_media
;
134 uint8_t i_payload_type
;
135 const char *psz_ptname
;
136 unsigned i_clock_rate
;
150 httpd_url_t
*p_rtsp_url
;
151 char *psz_rtsp_control_v4
;
152 char *psz_rtsp_control_v6
;
165 rtsp_client_t
**rtsp
;
174 httpd_host_t
*p_rtsp_host
;
177 int i_throttle_users
;
182 int i_session_timeout
;
191 block_fifo_t
*p_fifo_cmd
;
194 /* rtsp delayed command (to avoid deadlock between vlm/httpd) */
197 RTSP_CMD_TYPE_NONE
, /* Exit requested */
203 RTSP_CMD_TYPE_REWIND
,
204 RTSP_CMD_TYPE_FORWARD
,
215 vod_media_t
*p_media
;
222 static vod_media_t
*MediaNew( vod_t
*, const char *, input_item_t
* );
223 static void MediaDel( vod_t
*, vod_media_t
* );
224 static void MediaAskDel ( vod_t
*, vod_media_t
* );
225 static int MediaAddES( vod_t
*, vod_media_t
*, es_format_t
* );
226 static void MediaDelES( vod_t
*, vod_media_t
*, es_format_t
* );
228 static void* CommandThread( void * );
229 static void CommandPush( vod_t
*, rtsp_cmd_type_t
, vod_media_t
*,
230 const char *psz_session
, int64_t i_arg
,
231 double f_arg
, const char *psz_arg
);
233 static rtsp_client_t
*RtspClientNew( vod_media_t
*, char * );
234 static rtsp_client_t
*RtspClientGet( vod_media_t
*, const char * );
235 static void RtspClientDel( vod_media_t
*, rtsp_client_t
* );
237 static int RtspCallback( httpd_callback_sys_t
*, httpd_client_t
*,
238 httpd_message_t
*, const httpd_message_t
* );
239 static int RtspCallbackES( httpd_callback_sys_t
*, httpd_client_t
*,
240 httpd_message_t
*, const httpd_message_t
* );
242 static char *SDPGenerate( const vod_media_t
*, httpd_client_t
*cl
);
244 static void sprintf_hexa( char *s
, uint8_t *p_data
, int i_data
)
246 static const char hex
[16] = "0123456789abcdef";
248 for( int i
= 0; i
< i_data
; i
++ )
250 s
[2*i
+0] = hex
[(p_data
[i
]>>4)&0xf];
251 s
[2*i
+1] = hex
[(p_data
[i
] )&0xf];
256 /*****************************************************************************
257 * Open: Starts the RTSP server module
258 *****************************************************************************/
259 static int Open( vlc_object_t
*p_this
)
261 vod_t
*p_vod
= (vod_t
*)p_this
;
262 vod_sys_t
*p_sys
= NULL
;
263 char *psz_url
= NULL
;
266 psz_url
= var_InheritString( p_vod
, "rtsp-host" );
267 vlc_UrlParse( &url
, psz_url
, 0 );
270 if( url
.i_port
<= 0 ) url
.i_port
= 554;
272 p_vod
->p_sys
= p_sys
= malloc( sizeof( vod_sys_t
) );
273 if( !p_sys
) goto error
;
274 p_sys
->p_rtsp_host
= 0;
276 p_sys
->i_session_timeout
= var_CreateGetInteger( p_this
, "rtsp-session-timeout" );
278 p_sys
->i_throttle_users
= var_CreateGetInteger( p_this
, "rtsp-throttle-users" );
279 msg_Dbg( p_this
, "allowing up to %d connections", p_sys
->i_throttle_users
);
280 p_sys
->i_connections
= 0;
282 p_sys
->psz_raw_mux
= var_CreateGetString( p_this
, "rtsp-raw-mux" );
285 httpd_HostNew( VLC_OBJECT(p_vod
), url
.psz_host
, url
.i_port
);
286 if( !p_sys
->p_rtsp_host
)
288 msg_Err( p_vod
, "cannot create RTSP server (%s:%i)",
289 url
.psz_host
, url
.i_port
);
293 p_sys
->psz_path
= strdup( url
.psz_path
? url
.psz_path
: "/" );
294 p_sys
->i_port
= url
.i_port
;
296 vlc_UrlClean( &url
);
298 TAB_INIT( p_sys
->i_media
, p_sys
->media
);
299 p_sys
->i_media_id
= 0;
301 p_vod
->pf_media_new
= MediaNew
;
302 p_vod
->pf_media_del
= MediaAskDel
;
304 p_sys
->p_fifo_cmd
= block_FifoNew();
305 if( vlc_clone( &p_sys
->thread
, CommandThread
, p_vod
, VLC_THREAD_PRIORITY_LOW
) )
307 msg_Err( p_vod
, "cannot spawn rtsp vod thread" );
308 block_FifoRelease( p_sys
->p_fifo_cmd
);
309 free( p_sys
->psz_path
);
318 if( p_sys
->p_rtsp_host
) httpd_HostDelete( p_sys
->p_rtsp_host
);
319 free( p_sys
->psz_raw_mux
);
322 vlc_UrlClean( &url
);
327 /*****************************************************************************
329 *****************************************************************************/
330 static void Close( vlc_object_t
* p_this
)
332 vod_t
*p_vod
= (vod_t
*)p_this
;
333 vod_sys_t
*p_sys
= p_vod
->p_sys
;
335 /* Stop command thread */
336 CommandPush( p_vod
, RTSP_CMD_TYPE_NONE
, NULL
, NULL
, 0, 0.0, NULL
);
337 vlc_join( p_sys
->thread
, NULL
);
339 while( block_FifoCount( p_sys
->p_fifo_cmd
) > 0 )
342 block_t
*p_block_cmd
= block_FifoGet( p_sys
->p_fifo_cmd
);
343 memcpy( &cmd
, p_block_cmd
->p_buffer
, sizeof(cmd
) );
344 block_Release( p_block_cmd
);
345 if ( cmd
.i_type
== RTSP_CMD_TYPE_DEL
)
346 MediaDel(p_vod
, cmd
.p_media
);
347 free( cmd
.psz_session
);
350 block_FifoRelease( p_sys
->p_fifo_cmd
);
352 httpd_HostDelete( p_sys
->p_rtsp_host
);
353 var_Destroy( p_this
, "rtsp-session-timeout" );
354 var_Destroy( p_this
, "rtsp-throttle-users" );
355 var_Destroy( p_this
, "rtsp-raw-mux" );
357 /* Check VLM is not buggy */
358 if( p_sys
->i_media
> 0 )
359 msg_Err( p_vod
, "rtsp vod leaking %d medias", p_sys
->i_media
);
360 TAB_CLEAN( p_sys
->i_media
, p_sys
->media
);
362 free( p_sys
->psz_path
);
363 free( p_sys
->psz_raw_mux
);
367 /*****************************************************************************
369 *****************************************************************************/
370 static vod_media_t
*MediaNew( vod_t
*p_vod
, const char *psz_name
,
371 input_item_t
*p_item
)
373 vod_sys_t
*p_sys
= p_vod
->p_sys
;
375 vod_media_t
*p_media
= calloc( 1, sizeof(vod_media_t
) );
379 p_media
->id
= p_sys
->i_media_id
++;
380 TAB_INIT( p_media
->i_es
, p_media
->es
);
381 p_media
->psz_mux
= NULL
;
382 TAB_INIT( p_media
->i_rtsp
, p_media
->rtsp
);
383 p_media
->b_raw
= false;
385 if( asprintf( &p_media
->psz_rtsp_path
, "%s%s",
386 p_sys
->psz_path
, psz_name
) <0 )
388 p_media
->p_rtsp_url
=
389 httpd_UrlNewUnique( p_sys
->p_rtsp_host
, p_media
->psz_rtsp_path
, NULL
,
392 if( !p_media
->p_rtsp_url
)
394 msg_Err( p_vod
, "cannot create RTSP url (%s)", p_media
->psz_rtsp_path
);
395 free( p_media
->psz_rtsp_path
);
400 msg_Dbg( p_vod
, "created RTSP url: %s", p_media
->psz_rtsp_path
);
402 if( asprintf( &p_media
->psz_rtsp_control_v4
,
403 "rtsp://%%s:%d%s/trackID=%%d",
404 p_sys
->i_port
, p_media
->psz_rtsp_path
) < 0 )
406 httpd_UrlDelete( p_media
->p_rtsp_url
);
407 free( p_media
->psz_rtsp_path
);
411 if( asprintf( &p_media
->psz_rtsp_control_v6
,
412 "rtsp://[%%s]:%d%s/trackID=%%d",
413 p_sys
->i_port
, p_media
->psz_rtsp_path
) < 0 )
415 httpd_UrlDelete( p_media
->p_rtsp_url
);
416 free( p_media
->psz_rtsp_path
);
421 httpd_UrlCatch( p_media
->p_rtsp_url
, HTTPD_MSG_SETUP
,
422 RtspCallback
, (void*)p_media
);
423 httpd_UrlCatch( p_media
->p_rtsp_url
, HTTPD_MSG_DESCRIBE
,
424 RtspCallback
, (void*)p_media
);
425 httpd_UrlCatch( p_media
->p_rtsp_url
, HTTPD_MSG_PLAY
,
426 RtspCallback
, (void*)p_media
);
427 httpd_UrlCatch( p_media
->p_rtsp_url
, HTTPD_MSG_PAUSE
,
428 RtspCallback
, (void*)p_media
);
429 httpd_UrlCatch( p_media
->p_rtsp_url
, HTTPD_MSG_GETPARAMETER
,
430 RtspCallback
, (void*)p_media
);
431 httpd_UrlCatch( p_media
->p_rtsp_url
, HTTPD_MSG_TEARDOWN
,
432 RtspCallback
, (void*)p_media
);
434 p_media
->p_vod
= p_vod
;
436 vlc_mutex_init( &p_media
->lock
);
438 p_media
->i_length
= input_item_GetDuration( p_item
);
440 vlc_mutex_lock( &p_item
->lock
);
441 msg_Dbg( p_vod
, "media has %i declared ES", p_item
->i_es
);
442 for( int i
= 0; i
< p_item
->i_es
; i
++ )
444 MediaAddES( p_vod
, p_media
, p_item
->es
[i
] );
446 vlc_mutex_unlock( &p_item
->lock
);
448 CommandPush( p_vod
, RTSP_CMD_TYPE_ADD
, p_media
, NULL
, 0, 0.0, NULL
);
452 static void MediaAskDel ( vod_t
*p_vod
, vod_media_t
*p_media
)
454 CommandPush( p_vod
, RTSP_CMD_TYPE_DEL
, p_media
, NULL
, 0, 0.0, NULL
);
457 static void MediaDel( vod_t
*p_vod
, vod_media_t
*p_media
)
459 vod_sys_t
*p_sys
= p_vod
->p_sys
;
461 msg_Dbg( p_vod
, "deleting media: %s", p_media
->psz_rtsp_path
);
463 TAB_REMOVE( p_sys
->i_media
, p_sys
->media
, p_media
);
465 httpd_UrlDelete( p_media
->p_rtsp_url
);
467 while( p_media
->i_rtsp
> 0 )
468 RtspClientDel( p_media
, p_media
->rtsp
[0] );
469 TAB_CLEAN( p_media
->i_rtsp
, p_media
->rtsp
);
471 free( p_media
->psz_rtsp_path
);
472 free( p_media
->psz_rtsp_control_v6
);
473 free( p_media
->psz_rtsp_control_v4
);
475 while( p_media
->i_es
)
476 MediaDelES( p_vod
, p_media
, &p_media
->es
[0]->fmt
);
477 TAB_CLEAN( p_media
->i_es
, p_media
->es
);
479 vlc_mutex_destroy( &p_media
->lock
);
484 static int MediaAddES( vod_t
*p_vod
, vod_media_t
*p_media
, es_format_t
*p_fmt
)
488 media_es_t
*p_es
= calloc( 1, sizeof(media_es_t
) );
492 p_media
->psz_mux
= NULL
;
494 /* TODO: update SDP, etc... */
495 if( asprintf( &psz_urlc
, "%s/trackID=%d",
496 p_media
->psz_rtsp_path
, p_media
->i_es
) < 0 )
501 msg_Dbg( p_vod
, " - ES %4.4s (%s)", (char *)&p_fmt
->i_codec
, psz_urlc
);
503 /* Dynamic payload. No conflict since we put each ES in its own
505 p_es
->i_payload_type
= 96;
506 p_es
->i_clock_rate
= 90000;
507 p_es
->i_channels
= 1;
509 switch( p_fmt
->i_codec
)
512 if( p_fmt
->audio
.i_channels
== 1 && p_fmt
->audio
.i_rate
== 44100 )
514 p_es
->i_payload_type
= 11;
516 else if( p_fmt
->audio
.i_channels
== 2 &&
517 p_fmt
->audio
.i_rate
== 44100 )
519 p_es
->i_payload_type
= 10;
521 p_es
->psz_ptname
= "L16";
522 p_es
->i_clock_rate
= p_fmt
->audio
.i_rate
;
523 p_es
->i_channels
= p_fmt
->audio
.i_channels
;
526 p_es
->psz_ptname
= "L8";
527 p_es
->i_clock_rate
= p_fmt
->audio
.i_rate
;
528 p_es
->i_channels
= p_fmt
->audio
.i_channels
;
531 p_es
->i_payload_type
= 14;
532 p_es
->psz_ptname
= "MPA";
535 p_es
->i_payload_type
= 32;
536 p_es
->psz_ptname
= "MPV";
539 p_es
->psz_ptname
= "ac3";
540 p_es
->i_clock_rate
= p_fmt
->audio
.i_rate
;
543 p_es
->psz_ptname
= "H263-1998";
546 p_es
->psz_ptname
= "H264";
547 p_es
->psz_fmtp
= NULL
;
548 /* FIXME AAAAAAAAAAAARRRRRRRRGGGG copied from stream_out/rtp.c */
549 if( p_fmt
->i_extra
> 0 )
551 uint8_t *p_buffer
= p_fmt
->p_extra
;
552 int i_buffer
= p_fmt
->i_extra
;
553 char *p_64_sps
= NULL
;
554 char *p_64_pps
= NULL
;
557 while( i_buffer
> 4 )
562 while( p_buffer
[0] != 0 || p_buffer
[1] != 0 ||
567 if( i_buffer
== 0 ) break;
570 if( i_buffer
< 4 || memcmp(p_buffer
, "\x00\x00\x01", 3 ) )
572 /* No startcode found.. */
578 const int i_nal_type
= p_buffer
[0]&0x1f;
581 for( i_offset
= 0; i_offset
+2 < i_buffer
; i_offset
++)
583 if( !memcmp(p_buffer
+ i_offset
, "\x00\x00\x01", 3 ) )
585 /* we found another startcode */
586 while( i_offset
> 0 && 0 == p_buffer
[ i_offset
- 1 ] )
595 /* No-info found in nal */
599 if( i_nal_type
== 7 )
602 p_64_sps
= vlc_b64_encode_binary( p_buffer
, i_size
);
603 /* XXX: nothing ensures that i_size >= 4 ?? */
604 sprintf_hexa( hexa
, &p_buffer
[1], 3 );
606 else if( i_nal_type
== 8 )
609 p_64_pps
= vlc_b64_encode_binary( p_buffer
, i_size
);
615 if( p_64_sps
&& p_64_pps
)
617 if( asprintf( &p_es
->psz_fmtp
,
618 "packetization-mode=1;profile-level-id=%s;"
619 "sprop-parameter-sets=%s,%s;", hexa
, p_64_sps
,
632 if( !p_es
->psz_fmtp
)
633 p_es
->psz_fmtp
= strdup( "packetization-mode=1" );
636 p_es
->psz_ptname
= "MP4V-ES";
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 "profile-level-id=3; config=%s;", p_hexa
) == -1 )
643 p_es
->psz_fmtp
= NULL
;
648 p_es
->psz_ptname
= "mpeg4-generic";
649 p_es
->i_clock_rate
= p_fmt
->audio
.i_rate
;
650 if( p_fmt
->i_extra
> 0 )
652 char *p_hexa
= malloc( 2 * p_fmt
->i_extra
+ 1 );
653 sprintf_hexa( p_hexa
, p_fmt
->p_extra
, p_fmt
->i_extra
);
654 if( asprintf( &p_es
->psz_fmtp
,
655 "streamtype=5; profile-level-id=15; mode=AAC-hbr; "
656 "config=%s; SizeLength=13;IndexLength=3; "
657 "IndexDeltaLength=3; Profile=1;", p_hexa
) == -1 )
658 p_es
->psz_fmtp
= NULL
;
662 case VLC_FOURCC( 'm', 'p', '2', 't' ):
663 p_media
->psz_mux
= "ts";
664 p_es
->i_payload_type
= 33;
665 p_es
->psz_ptname
= "MP2T";
667 case VLC_FOURCC( 'm', 'p', '2', 'p' ):
668 p_media
->psz_mux
= "ps";
669 p_es
->psz_ptname
= "MP2P";
671 case VLC_CODEC_AMR_NB
:
672 p_es
->psz_ptname
= "AMR";
673 p_es
->i_clock_rate
= 8000;
674 if(p_fmt
->audio
.i_channels
== 2 )
675 p_es
->i_channels
= 2;
676 p_es
->psz_fmtp
= strdup( "octet-align=1" );
678 case VLC_CODEC_AMR_WB
:
679 p_es
->psz_ptname
= "AMR-WB";
680 p_es
->i_clock_rate
= 16000;
681 if(p_fmt
->audio
.i_channels
== 2 )
682 p_es
->i_channels
= 2;
683 p_es
->psz_fmtp
= strdup( "octet-align=1" );
687 msg_Err( p_vod
, "cannot add this stream (unsupported "
688 "codec: %4.4s)", (char*)&p_fmt
->i_codec
);
695 httpd_UrlNewUnique( p_vod
->p_sys
->p_rtsp_host
, psz_urlc
, NULL
, NULL
,
698 if( !p_es
->p_rtsp_url
)
700 msg_Err( p_vod
, "cannot create RTSP url (%s)", psz_urlc
);
707 httpd_UrlCatch( p_es
->p_rtsp_url
, HTTPD_MSG_SETUP
,
708 RtspCallbackES
, (void*)p_es
);
709 httpd_UrlCatch( p_es
->p_rtsp_url
, HTTPD_MSG_TEARDOWN
,
710 RtspCallbackES
, (void*)p_es
);
711 httpd_UrlCatch( p_es
->p_rtsp_url
, HTTPD_MSG_PLAY
,
712 RtspCallbackES
, (void*)p_es
);
713 httpd_UrlCatch( p_es
->p_rtsp_url
, HTTPD_MSG_PAUSE
,
714 RtspCallbackES
, (void*)p_es
);
716 es_format_Copy( &p_es
->fmt
, p_fmt
);
718 p_es
->p_media
= p_media
;
720 vlc_mutex_lock( &p_media
->lock
);
721 TAB_APPEND( p_media
->i_es
, p_media
->es
, p_es
);
722 vlc_mutex_unlock( &p_media
->lock
);
727 static void MediaDelES( vod_t
*p_vod
, vod_media_t
*p_media
, es_format_t
*p_fmt
)
729 media_es_t
*p_es
= NULL
;
732 for( int i
= 0; i
< p_media
->i_es
; i
++ )
734 if( p_media
->es
[i
]->fmt
.i_cat
== p_fmt
->i_cat
&&
735 p_media
->es
[i
]->fmt
.i_codec
== p_fmt
->i_codec
&&
736 p_media
->es
[i
]->fmt
.i_id
== p_fmt
->i_id
)
738 p_es
= p_media
->es
[i
];
743 msg_Dbg( p_vod
, " - Removing ES %4.4s", (char *)&p_fmt
->i_codec
);
745 vlc_mutex_lock( &p_media
->lock
);
746 TAB_REMOVE( p_media
->i_es
, p_media
->es
, p_es
);
747 vlc_mutex_unlock( &p_media
->lock
);
749 free( p_es
->psz_fmtp
);
751 if( p_es
->p_rtsp_url
) httpd_UrlDelete( p_es
->p_rtsp_url
);
752 es_format_Clean( &p_es
->fmt
);
756 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
,
757 double f_arg
, const char *psz_arg
)
762 memset( &cmd
, 0, sizeof(cmd
) );
764 cmd
.p_media
= p_media
;
766 cmd
.i_media_id
= p_media
->id
;
768 cmd
.psz_session
= strdup(psz_session
);
772 cmd
.psz_arg
= strdup(psz_arg
);
774 p_cmd
= block_New( p_vod
, sizeof(rtsp_cmd_t
) );
775 memcpy( p_cmd
->p_buffer
, &cmd
, sizeof(cmd
) );
777 block_FifoPut( p_vod
->p_sys
->p_fifo_cmd
, p_cmd
);
780 static void* CommandThread( void *obj
)
782 vod_t
*p_vod
= (vod_t
*)obj
;
783 vod_sys_t
*p_sys
= p_vod
->p_sys
;
784 int canc
= vlc_savecancel ();
788 block_t
*p_block_cmd
= block_FifoGet( p_sys
->p_fifo_cmd
);
790 vod_media_t
*p_media
= NULL
;
796 memcpy( &cmd
, p_block_cmd
->p_buffer
, sizeof(cmd
) );
797 block_Release( p_block_cmd
);
799 if( cmd
.i_type
== RTSP_CMD_TYPE_NONE
)
802 if ( cmd
.i_type
== RTSP_CMD_TYPE_ADD
)
804 TAB_APPEND( p_sys
->i_media
, p_sys
->media
, cmd
.p_media
);
808 if ( cmd
.i_type
== RTSP_CMD_TYPE_DEL
)
810 MediaDel(p_vod
, cmd
.p_media
);
815 for( i
= 0; i
< p_sys
->i_media
; i
++ )
817 if( p_sys
->media
[i
]->id
== cmd
.i_media_id
)
820 if( i
>= p_sys
->i_media
)
824 p_media
= p_sys
->media
[i
];
828 case RTSP_CMD_TYPE_PLAY
:
830 vod_MediaControl( p_vod
, p_media
, cmd
.psz_session
,
831 VOD_MEDIA_PLAY
, cmd
.psz_arg
, &cmd
.i_arg
);
833 case RTSP_CMD_TYPE_PAUSE
:
835 vod_MediaControl( p_vod
, p_media
, cmd
.psz_session
,
836 VOD_MEDIA_PAUSE
, &cmd
.i_arg
);
839 case RTSP_CMD_TYPE_STOP
:
840 vod_MediaControl( p_vod
, p_media
, cmd
.psz_session
, VOD_MEDIA_STOP
);
843 case RTSP_CMD_TYPE_SEEK
:
844 vod_MediaControl( p_vod
, p_media
, cmd
.psz_session
,
845 VOD_MEDIA_SEEK
, cmd
.i_arg
);
848 case RTSP_CMD_TYPE_REWIND
:
849 vod_MediaControl( p_vod
, p_media
, cmd
.psz_session
,
850 VOD_MEDIA_REWIND
, cmd
.f_arg
);
853 case RTSP_CMD_TYPE_FORWARD
:
854 vod_MediaControl( p_vod
, p_media
, cmd
.psz_session
,
855 VOD_MEDIA_FORWARD
, cmd
.f_arg
);
863 free( cmd
.psz_session
);
867 vlc_restorecancel (canc
);
871 /****************************************************************************
872 * RTSP server implementation
873 ****************************************************************************/
874 static rtsp_client_t
*RtspClientNew( vod_media_t
*p_media
, char *psz_session
)
876 rtsp_client_t
*p_rtsp
= calloc( 1, sizeof(rtsp_client_t
) );
882 p_rtsp
->psz_session
= psz_session
;
883 TAB_APPEND( p_media
->i_rtsp
, p_media
->rtsp
, p_rtsp
);
885 p_media
->p_vod
->p_sys
->i_connections
++;
886 msg_Dbg( p_media
->p_vod
, "new session: %s, connections: %d",
887 psz_session
, p_media
->p_vod
->p_sys
->i_throttle_users
);
892 static rtsp_client_t
*RtspClientGet( vod_media_t
*p_media
, const char *psz_session
)
894 for( int i
= 0; psz_session
&& i
< p_media
->i_rtsp
; i
++ )
896 if( !strcmp( p_media
->rtsp
[i
]->psz_session
, psz_session
) )
897 return p_media
->rtsp
[i
];
903 static void RtspClientDel( vod_media_t
*p_media
, rtsp_client_t
*p_rtsp
)
905 p_media
->p_vod
->p_sys
->i_connections
--;
906 msg_Dbg( p_media
->p_vod
, "closing session: %s, connections: %d",
907 p_rtsp
->psz_session
, p_media
->p_vod
->p_sys
->i_throttle_users
);
909 while( p_rtsp
->i_es
)
912 free( p_rtsp
->es
[p_rtsp
->i_es
] );
916 TAB_REMOVE( p_media
->i_rtsp
, p_media
->rtsp
, p_rtsp
);
918 free( p_rtsp
->psz_session
);
923 static int64_t ParseNPT (const char *str
)
925 locale_t loc
= newlocale (LC_NUMERIC_MASK
, "C", NULL
);
926 locale_t oldloc
= uselocale (loc
);
930 if (sscanf (str
, "%u:%u:%f", &hour
, &min
, &sec
) == 3)
931 sec
+= ((hour
* 60) + min
) * 60;
933 if (sscanf (str
, "%f", &sec
) != 1)
936 if (loc
!= (locale_t
)0)
941 return sec
* CLOCK_FREQ
;
945 static int RtspCallback( httpd_callback_sys_t
*p_args
, httpd_client_t
*cl
,
946 httpd_message_t
*answer
, const httpd_message_t
*query
)
948 vod_media_t
*p_media
= (vod_media_t
*)p_args
;
949 vod_t
*p_vod
= p_media
->p_vod
;
950 const char *psz_transport
= NULL
;
951 const char *psz_playnow
= NULL
; /* support option: x-playNow */
952 const char *psz_session
= NULL
;
953 const char *psz_cseq
= NULL
;
954 rtsp_client_t
*p_rtsp
;
957 if( answer
== NULL
|| query
== NULL
) return VLC_SUCCESS
;
959 msg_Dbg( p_vod
, "RtspCallback query: type=%d", query
->i_type
);
961 answer
->i_proto
= HTTPD_PROTO_RTSP
;
962 answer
->i_version
= query
->i_version
;
963 answer
->i_type
= HTTPD_MSG_ANSWER
;
965 answer
->p_body
= NULL
;
967 switch( query
->i_type
)
969 case HTTPD_MSG_SETUP
:
971 psz_playnow
= httpd_MsgGet( query
, "x-playNow" );
972 psz_transport
= httpd_MsgGet( query
, "Transport" );
973 if( psz_transport
== NULL
)
975 answer
->i_status
= 400;
978 msg_Dbg( p_vod
, "HTTPD_MSG_SETUP: transport=%s", psz_transport
);
980 if( strstr( psz_transport
, "unicast" ) &&
981 strstr( psz_transport
, "client_port=" ) )
983 rtsp_client_t
*p_rtsp
= NULL
;
984 char ip
[NI_MAXNUMERICHOST
];
985 int i_port
= atoi( strstr( psz_transport
, "client_port=" ) +
986 strlen("client_port=") );
988 if( strstr( psz_transport
, "MP2T/H2221/UDP" ) ||
989 strstr( psz_transport
, "RAW/RAW/UDP" ) )
991 p_media
->psz_mux
= p_vod
->p_sys
->psz_raw_mux
;
992 p_media
->b_raw
= true;
995 if( httpd_ClientIP( cl
, ip
) == NULL
)
997 answer
->i_status
= 500;
999 answer
->p_body
= NULL
;
1003 msg_Dbg( p_vod
, "HTTPD_MSG_SETUP: unicast ip=%s port=%d",
1006 psz_session
= httpd_MsgGet( query
, "Session" );
1007 if( !psz_session
|| !*psz_session
)
1010 if( ( p_vod
->p_sys
->i_throttle_users
> 0 ) &&
1011 ( p_vod
->p_sys
->i_connections
>= p_vod
->p_sys
->i_throttle_users
) )
1013 answer
->i_status
= 503;
1015 answer
->p_body
= NULL
;
1018 #warning Should use secure randomness here! (spoofing risk)
1019 if( asprintf( &psz_new
, "%lu", vlc_mrand48() ) < 0 )
1021 psz_session
= psz_new
;
1023 p_rtsp
= RtspClientNew( p_media
, psz_new
);
1026 answer
->i_status
= 454;
1028 answer
->p_body
= NULL
;
1034 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1037 answer
->i_status
= 454;
1039 answer
->p_body
= NULL
;
1044 answer
->i_status
= 200;
1046 answer
->p_body
= NULL
;
1048 if( p_media
->b_raw
)
1050 p_rtsp
->i_port_raw
= i_port
;
1052 if( strstr( psz_transport
, "MP2T/H2221/UDP" ) )
1054 httpd_MsgAdd( answer
, "Transport",
1055 "MP2T/H2221/UDP;unicast;client_port=%d-%d",
1056 i_port
, i_port
+ 1 );
1058 else if( strstr( psz_transport
, "RAW/RAW/UDP" ) )
1060 httpd_MsgAdd( answer
, "Transport",
1061 "RAW/RAW/UDP;unicast;client_port=%d-%d",
1062 i_port
, i_port
+ 1 );
1066 httpd_MsgAdd( answer
, "Transport",
1067 "RTP/AVP/UDP;unicast;client_port=%d-%d",
1068 i_port
, i_port
+ 1 );
1070 else /* TODO strstr( psz_transport, "interleaved" ) ) */
1072 answer
->i_status
= 461;
1074 answer
->p_body
= NULL
;
1077 /* Intentional fall-through on x-playNow option in RTSP request */
1082 case HTTPD_MSG_PLAY
:
1084 char *psz_output
, ip
[NI_MAXNUMERICHOST
];
1085 int i_port_audio
= 0, i_port_video
= 0;
1087 /* for now only multicast so easy */
1090 answer
->i_status
= 200;
1092 answer
->p_body
= NULL
;
1096 psz_session
= httpd_MsgGet( query
, "Session" );
1097 msg_Dbg( p_vod
, "HTTPD_MSG_PLAY for session: %s", psz_session
);
1099 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1102 answer
->i_status
= 500;
1104 answer
->p_body
= NULL
;
1108 if( p_rtsp
->b_playing
)
1110 const char *psz_position
= httpd_MsgGet( query
, "Range" );
1111 const char *psz_scale
= httpd_MsgGet( query
, "Scale" );
1113 psz_position
= strstr( psz_position
, "npt=" );
1114 if( psz_position
&& !psz_scale
)
1116 int64_t i_time
= ParseNPT (psz_position
+ 4);
1117 msg_Dbg( p_vod
, "seeking request: %s", psz_position
);
1118 CommandPush( p_vod
, RTSP_CMD_TYPE_SEEK
, p_media
,
1119 psz_session
, i_time
, 0.0, NULL
);
1121 else if( psz_scale
)
1123 double f_scale
= 0.0;
1126 f_scale
= us_strtod( psz_scale
, &end
);
1127 if( end
> psz_scale
)
1129 f_scale
= (f_scale
* 30.0);
1130 if( psz_scale
[0] == '-' ) /* rewind */
1132 msg_Dbg( p_vod
, "rewind request: %s", psz_scale
);
1133 CommandPush( p_vod
, RTSP_CMD_TYPE_REWIND
, p_media
,
1134 psz_session
, 0, f_scale
, NULL
);
1136 else if(psz_scale
[0] != '1' ) /* fast-forward */
1138 msg_Dbg( p_vod
, "fastforward request: %s",
1140 CommandPush( p_vod
, RTSP_CMD_TYPE_FORWARD
, p_media
,
1141 psz_session
, 0, f_scale
, NULL
);
1145 /* unpause, in case it's paused */
1146 CommandPush( p_vod
, RTSP_CMD_TYPE_PLAY
, p_media
, psz_session
,
1151 if( httpd_ClientIP( cl
, ip
) == NULL
) break;
1153 p_rtsp
->b_playing
= true;
1155 /* FIXME for != 1 video and 1 audio */
1156 for( int i
= 0; i
< p_rtsp
->i_es
; i
++ )
1158 if( p_rtsp
->es
[i
]->p_media_es
->fmt
.i_cat
== AUDIO_ES
)
1159 i_port_audio
= p_rtsp
->es
[i
]->i_port
;
1160 if( p_rtsp
->es
[i
]->p_media_es
->fmt
.i_cat
== VIDEO_ES
)
1161 i_port_video
= p_rtsp
->es
[i
]->i_port
;
1164 if( p_media
->psz_mux
)
1166 if( p_media
->b_raw
)
1168 if( asprintf( &psz_output
,
1169 "std{access=udp,dst=%s:%i,mux=%s}",
1170 ip
, p_rtsp
->i_port_raw
, p_media
->psz_mux
) < 0 )
1175 if( asprintf( &psz_output
,
1176 "rtp{dst=%s,port=%i,mux=%s}",
1177 ip
, i_port_video
, p_media
->psz_mux
) < 0 )
1183 if( asprintf( &psz_output
,
1184 "rtp{dst=%s,port-video=%i,port-audio=%i}",
1185 ip
, i_port_video
, i_port_audio
) < 0 )
1189 CommandPush( p_vod
, RTSP_CMD_TYPE_PLAY
, p_media
, psz_session
,
1190 0, 0.0, psz_output
);
1195 case HTTPD_MSG_DESCRIBE
:
1198 SDPGenerate( p_media
, cl
);
1200 if( psz_sdp
!= NULL
)
1202 answer
->i_status
= 200;
1203 httpd_MsgAdd( answer
, "Content-type", "%s",
1204 "application/sdp" );
1206 answer
->p_body
= (uint8_t *)psz_sdp
;
1207 answer
->i_body
= strlen( psz_sdp
);
1211 answer
->i_status
= 500;
1212 answer
->p_body
= NULL
;
1218 case HTTPD_MSG_PAUSE
:
1219 psz_session
= httpd_MsgGet( query
, "Session" );
1220 msg_Dbg( p_vod
, "HTTPD_MSG_PAUSE for session: %s", psz_session
);
1222 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1223 if( !p_rtsp
) break;
1225 CommandPush( p_vod
, RTSP_CMD_TYPE_PAUSE
, p_media
, psz_session
,
1228 answer
->i_status
= 200;
1230 answer
->p_body
= NULL
;
1233 case HTTPD_MSG_TEARDOWN
:
1234 /* for now only multicast so easy again */
1235 answer
->i_status
= 200;
1237 answer
->p_body
= NULL
;
1239 psz_session
= httpd_MsgGet( query
, "Session" );
1240 msg_Dbg( p_vod
, "HTTPD_MSG_TEARDOWN for session: %s", psz_session
);
1242 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1243 if( !p_rtsp
) break;
1245 CommandPush( p_vod
, RTSP_CMD_TYPE_STOP
, p_media
, psz_session
,
1247 RtspClientDel( p_media
, p_rtsp
);
1250 case HTTPD_MSG_GETPARAMETER
:
1251 answer
->i_status
= 200;
1253 answer
->p_body
= NULL
;
1257 return VLC_EGENERIC
;
1260 httpd_MsgAdd( answer
, "Server", "VLC/%s", VERSION
);
1261 httpd_MsgAdd( answer
, "Content-Length", "%d", answer
->i_body
);
1262 psz_cseq
= httpd_MsgGet( query
, "Cseq" );
1263 psz_cseq
? i_cseq
= atoi( psz_cseq
) : 0;
1264 httpd_MsgAdd( answer
, "CSeq", "%d", i_cseq
);
1265 httpd_MsgAdd( answer
, "Cache-Control", "%s", "no-cache" );
1269 if( p_media
->p_vod
->p_sys
->i_session_timeout
>= 0 )
1270 httpd_MsgAdd( answer
, "Session", "%s;timeout=%i", psz_session
,
1271 p_media
->p_vod
->p_sys
->i_session_timeout
);
1273 httpd_MsgAdd( answer
, "Session", "%s", psz_session
);
1279 static int RtspCallbackES( httpd_callback_sys_t
*p_args
, httpd_client_t
*cl
,
1280 httpd_message_t
*answer
,
1281 const httpd_message_t
*query
)
1283 media_es_t
*p_es
= (media_es_t
*)p_args
;
1284 vod_media_t
*p_media
= p_es
->p_media
;
1285 vod_t
*p_vod
= p_media
->p_vod
;
1286 rtsp_client_t
*p_rtsp
= NULL
;
1287 const char *psz_transport
= NULL
;
1288 const char *psz_playnow
= NULL
; /* support option: x-playNow */
1289 const char *psz_session
= NULL
;
1290 const char *psz_position
= NULL
;
1291 const char *psz_cseq
= NULL
;
1294 if( answer
== NULL
|| query
== NULL
) return VLC_SUCCESS
;
1296 msg_Dbg( p_vod
, "RtspCallback query: type=%d", query
->i_type
);
1298 answer
->i_proto
= HTTPD_PROTO_RTSP
;
1299 answer
->i_version
= query
->i_version
;
1300 answer
->i_type
= HTTPD_MSG_ANSWER
;
1302 answer
->p_body
= NULL
;
1304 switch( query
->i_type
)
1306 case HTTPD_MSG_SETUP
:
1307 psz_playnow
= httpd_MsgGet( query
, "x-playNow" );
1308 psz_transport
= httpd_MsgGet( query
, "Transport" );
1310 msg_Dbg( p_vod
, "HTTPD_MSG_SETUP: transport=%s", psz_transport
);
1312 if( strstr( psz_transport
, "unicast" ) &&
1313 strstr( psz_transport
, "client_port=" ) )
1315 rtsp_client_t
*p_rtsp
= NULL
;
1316 rtsp_client_es_t
*p_rtsp_es
= NULL
;
1317 char ip
[NI_MAXNUMERICHOST
];
1318 int i_port
= atoi( strstr( psz_transport
, "client_port=" ) +
1319 strlen("client_port=") );
1321 if( httpd_ClientIP( cl
, ip
) == NULL
)
1323 answer
->i_status
= 500;
1325 answer
->p_body
= NULL
;
1329 msg_Dbg( p_vod
, "HTTPD_MSG_SETUP: unicast ip=%s port=%d",
1332 psz_session
= httpd_MsgGet( query
, "Session" );
1333 if( !psz_session
|| !*psz_session
)
1336 if( ( p_vod
->p_sys
->i_throttle_users
> 0 ) &&
1337 ( p_vod
->p_sys
->i_connections
>= p_vod
->p_sys
->i_throttle_users
) )
1339 answer
->i_status
= 503;
1341 answer
->p_body
= NULL
;
1344 #warning Session ID should be securely random (spoofing risk)
1345 if( asprintf( &psz_new
, "%lu", vlc_mrand48() ) < 0 )
1347 psz_session
= psz_new
;
1349 p_rtsp
= RtspClientNew( p_media
, psz_new
);
1352 answer
->i_status
= 454;
1354 answer
->p_body
= NULL
;
1360 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1363 answer
->i_status
= 454;
1365 answer
->p_body
= NULL
;
1370 p_rtsp_es
= malloc( sizeof(rtsp_client_es_t
) );
1373 answer
->i_status
= 500;
1375 answer
->p_body
= NULL
;
1378 p_rtsp_es
->i_port
= i_port
;
1379 p_rtsp_es
->p_media_es
= p_es
;
1380 TAB_APPEND( p_rtsp
->i_es
, p_rtsp
->es
, p_rtsp_es
);
1382 answer
->i_status
= 200;
1384 answer
->p_body
= NULL
;
1386 if( p_media
->b_raw
)
1388 if( strstr( psz_transport
, "MP2T/H2221/UDP" ) )
1390 httpd_MsgAdd( answer
, "Transport",
1391 "MP2T/H2221/UDP;unicast;client_port=%d-%d",
1392 p_rtsp_es
->i_port
, p_rtsp_es
->i_port
+ 1 );
1394 else if( strstr( psz_transport
, "RAW/RAW/UDP" ) )
1396 httpd_MsgAdd( answer
, "Transport",
1397 "RAW/RAW/UDP;unicast;client_port=%d-%d",
1398 p_rtsp_es
->i_port
, p_rtsp_es
->i_port
+ 1 );
1403 httpd_MsgAdd( answer
, "Transport",
1404 "RTP/AVP/UDP;unicast;client_port=%d-%d",
1405 p_rtsp_es
->i_port
, p_rtsp_es
->i_port
+ 1 );
1408 else /* TODO strstr( psz_transport, "interleaved" ) ) */
1410 answer
->i_status
= 461;
1412 answer
->p_body
= NULL
;
1415 /* Intentional fall-through on x-playNow option in RTSP request */
1419 case HTTPD_MSG_PLAY
:
1420 /* This is kind of a kludge. Should we only support Aggregate
1422 psz_session
= httpd_MsgGet( query
, "Session" );
1423 msg_Dbg( p_vod
, "HTTPD_MSG_PLAY for session: %s", psz_session
);
1425 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1427 psz_position
= httpd_MsgGet( query
, "Range" );
1428 if( psz_position
) psz_position
= strstr( psz_position
, "npt=" );
1431 int64_t i_time
= ParseNPT (psz_position
+ 4);
1432 msg_Dbg( p_vod
, "seeking request: %s", psz_position
);
1433 CommandPush( p_vod
, RTSP_CMD_TYPE_SEEK
, p_media
,
1434 psz_session
, i_time
, 0.0, NULL
);
1439 answer
->i_status
= 200;
1441 answer
->p_body
= NULL
;
1445 case HTTPD_MSG_TEARDOWN
:
1446 answer
->i_status
= 200;
1448 answer
->p_body
= NULL
;
1450 psz_session
= httpd_MsgGet( query
, "Session" );
1451 msg_Dbg( p_vod
, "HTTPD_MSG_TEARDOWN for session: %s", psz_session
);
1453 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1454 if( !p_rtsp
) break;
1456 for( int i
= 0; i
< p_rtsp
->i_es
; i
++ )
1458 if( p_rtsp
->es
[i
]->p_media_es
== p_es
)
1460 TAB_REMOVE( p_rtsp
->i_es
, p_rtsp
->es
, p_rtsp
->es
[i
] );
1467 CommandPush( p_vod
, RTSP_CMD_TYPE_STOP
, p_media
, psz_session
,
1469 RtspClientDel( p_media
, p_rtsp
);
1473 case HTTPD_MSG_PAUSE
:
1474 /* This is kind of a kludge. Should we only support Aggregate
1476 psz_session
= httpd_MsgGet( query
, "Session" );
1477 msg_Dbg( p_vod
, "HTTPD_MSG_PAUSE for session: %s", psz_session
);
1479 p_rtsp
= RtspClientGet( p_media
, psz_session
);
1480 if( !p_rtsp
) break;
1482 CommandPush( p_vod
, RTSP_CMD_TYPE_PAUSE
, p_media
, psz_session
,
1485 answer
->i_status
= 200;
1487 answer
->p_body
= NULL
;
1491 return VLC_EGENERIC
;
1495 httpd_MsgAdd( answer
, "Server", "VLC/%s", VERSION
);
1496 httpd_MsgAdd( answer
, "Content-Length", "%d", answer
->i_body
);
1497 psz_cseq
= httpd_MsgGet( query
, "Cseq" );
1499 i_cseq
= atoi( psz_cseq
);
1502 httpd_MsgAdd( answer
, "Cseq", "%d", i_cseq
);
1503 httpd_MsgAdd( answer
, "Cache-Control", "%s", "no-cache" );
1506 httpd_MsgAdd( answer
, "Session", "%s"/*;timeout=5*/, psz_session
);
1511 /*****************************************************************************
1513 * FIXME: need to be moved to a common place ?
1514 *****************************************************************************/
1515 static char *SDPGenerate( const vod_media_t
*p_media
, httpd_client_t
*cl
)
1517 char *psz_sdp
, ip
[NI_MAXNUMERICHOST
];
1518 const char *psz_control
;
1520 if( httpd_ServerIP( cl
, ip
) == NULL
)
1523 bool ipv6
= ( strchr( ip
, ':' ) != NULL
);
1525 psz_control
= ipv6
? p_media
->psz_rtsp_control_v6
1526 : p_media
->psz_rtsp_control_v4
;
1528 /* Dummy destination address for RTSP */
1529 struct sockaddr_storage dst
;
1530 socklen_t dstlen
= ipv6
? sizeof( struct sockaddr_in6
)
1531 : sizeof( struct sockaddr_in
);
1532 memset (&dst
, 0, dstlen
);
1533 dst
.ss_family
= ipv6
? AF_INET6
: AF_INET
;
1535 dst
.ss_len
= dstlen
;
1538 psz_sdp
= vlc_sdp_Start( VLC_OBJECT( p_media
->p_vod
), "sout-rtp-",
1539 NULL
, 0, (struct sockaddr
*)&dst
, dstlen
);
1540 if( psz_sdp
== NULL
)
1543 if( p_media
->i_length
> 0 )
1545 lldiv_t d
= lldiv( p_media
->i_length
/ 1000, 1000 );
1546 sdp_AddAttribute( &psz_sdp
, "range","npt=0-%lld.%03u", d
.quot
,
1550 for( int i
= 0; i
< p_media
->i_es
; i
++ )
1552 media_es_t
*p_es
= p_media
->es
[i
];
1553 const char *mime_major
; /* major MIME type */
1555 switch( p_es
->fmt
.i_cat
)
1558 mime_major
= "video";
1561 mime_major
= "audio";
1564 mime_major
= "text";
1570 sdp_AddMedia( &psz_sdp
, mime_major
, "RTP/AVP", 0 /* p_es->i_port */,
1571 p_es
->i_payload_type
, false, 0,
1572 p_es
->psz_ptname
, p_es
->i_clock_rate
, p_es
->i_channels
,
1575 sdp_AddAttribute( &psz_sdp
, "control", psz_control
, ip
, i
);