]> git.sesse.net Git - vlc/blob - modules/misc/rtsp.c
1a7a416e1b58c97fd7d4e3552906806ef0754e59
[vlc] / modules / misc / rtsp.c
1 /*****************************************************************************
2  * rtsp.c: rtsp VoD server module
3  *****************************************************************************
4  * Copyright (C) 2003-2006 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Laurent Aimar <fenrir@via.ecp.fr>
8  *          Gildas Bazin <gbazin@videolan.org>
9  *
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.
14  *
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.
19  *
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  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
32
33 #include <vlc_common.h>
34 #include <vlc_plugin.h>
35 #include <vlc_input.h>
36 #include <vlc_sout.h>
37 #include <vlc_block.h>
38
39 #include "vlc_httpd.h"
40 #include "vlc_vod.h"
41 #include "vlc_url.h"
42 #include <vlc_network.h>
43 #include <vlc_charset.h>
44 #include <vlc_strings.h>
45
46 #include <errno.h>
47 #ifndef WIN32
48 # include <locale.h>
49 #endif
50
51 /*****************************************************************************
52  * Module descriptor
53  *****************************************************************************/
54 static int  Open ( vlc_object_t * );
55 static void Close( vlc_object_t * );
56
57 #define HOST_TEXT N_( "RTSP host address" )
58 #define HOST_LONGTEXT N_( \
59     "This defines the address, port and path the RTSP VOD server will listen " \
60     "on.\nSyntax is address:port/path. The default is to listen on all "\
61     "interfaces (address 0.0.0.0), on port 554, with no path.\nTo listen " \
62     "only on the local interface, use \"localhost\" as address." )
63
64 #define THROTLE_TEXT N_( "Maximum number of connections" )
65 #define THROTLE_LONGTEXT N_( "This limits the maximum number of clients " \
66     "that can connect to the RTSP VOD. 0 means no limit."  )
67
68 #define RAWMUX_TEXT N_( "MUX for RAW RTSP transport" )
69
70 #define SESSION_TIMEOUT_TEXT N_( "Sets the timeout option in the RTSP " \
71     "session string" )
72 #define SESSION_TIMEOUT_LONGTEXT N_( "Defines what timeout option to add " \
73     "to the RTSP session ID string. Setting it to a negative number removes " \
74     "the timeout option entirely. This is needed by some IPTV STBs (such as " \
75     "those made by HansunTech) which get confused by it. The default is 5." )
76
77 vlc_module_begin();
78     set_shortname( N_("RTSP VoD" ) );
79     set_description( N_("RTSP VoD server") );
80     set_category( CAT_SOUT );
81     set_subcategory( SUBCAT_SOUT_VOD );
82     set_capability( "vod server", 1 );
83     set_callbacks( Open, Close );
84     add_shortcut( "rtsp" );
85     add_string ( "rtsp-host", NULL, NULL, HOST_TEXT, HOST_LONGTEXT, true );
86     add_string( "rtsp-raw-mux", "ts", NULL, RAWMUX_TEXT,
87                 RAWMUX_TEXT, true );
88     add_integer( "rtsp-throttle-users", 0, NULL, THROTLE_TEXT,
89                                            THROTLE_LONGTEXT, true );
90     add_integer( "rtsp-session-timeout", 5, NULL, SESSION_TIMEOUT_TEXT,
91                  SESSION_TIMEOUT_LONGTEXT, true );
92 vlc_module_end();
93
94 /*****************************************************************************
95  * Exported prototypes
96  *****************************************************************************/
97
98 typedef struct media_es_t media_es_t;
99
100 typedef struct
101 {
102     media_es_t *p_media_es;
103     char *psz_ip;
104     int i_port;
105
106 } rtsp_client_es_t;
107
108 typedef struct
109 {
110     char *psz_session;
111     int64_t i_last; /* for timeout */
112
113     bool b_playing; /* is it in "play" state */
114     bool b_paused; /* is it in "pause" state */
115
116     int i_es;
117     rtsp_client_es_t **es;
118
119 } rtsp_client_t;
120
121 struct media_es_t
122 {
123     /* VoD server */
124     vod_t *p_vod;
125
126     /* RTSP server */
127     httpd_url_t *p_rtsp_url;
128
129     vod_media_t *p_media;
130
131     es_format_t fmt;
132     int         i_port;
133     uint8_t     i_payload_type;
134     char        *psz_rtpmap;
135     char        *psz_fmtp;
136
137 };
138
139 struct vod_media_t
140 {
141     int id;
142
143     /* VoD server */
144     vod_t *p_vod;
145
146     /* RTSP server */
147     httpd_url_t  *p_rtsp_url;
148     char         *psz_rtsp_control_v4;
149     char         *psz_rtsp_control_v6;
150     char         *psz_rtsp_path;
151
152     int  i_port;
153     int  i_port_audio;
154     int  i_port_video;
155     int  i_ttl;
156     int  i_payload_type;
157
158     int64_t i_sdp_id;
159     int     i_sdp_version;
160
161     bool b_multicast;
162
163     vlc_mutex_t lock;
164
165     /* ES list */
166     int        i_es;
167     media_es_t **es;
168     char       *psz_mux;
169     bool  b_raw;
170
171     /* RTSP client */
172     int           i_rtsp;
173     rtsp_client_t **rtsp;
174
175     /* Infos */
176     char *psz_session_name;
177     char *psz_session_description;
178     char *psz_session_url;
179     char *psz_session_email;
180     mtime_t i_length;
181 };
182
183 struct vod_sys_t
184 {
185     /* RTSP server */
186     httpd_host_t *p_rtsp_host;
187     char *psz_path;
188     int i_port;
189     int i_throttle_users;
190     int i_connections;
191
192     char *psz_raw_mux;
193
194     int i_session_timeout;
195
196     /* List of media */
197     vlc_mutex_t lock_media;
198     int i_media_id;
199     int i_media;
200     vod_media_t **media;
201
202     /* */
203     block_fifo_t *p_fifo_cmd;
204 };
205
206 /* rtsp delayed command (to avoid deadlock between vlm/httpd) */
207 typedef enum
208 {
209     RTSP_CMD_TYPE_NONE,  /* Exit requested */
210
211     RTSP_CMD_TYPE_PLAY,
212     RTSP_CMD_TYPE_PAUSE,
213     RTSP_CMD_TYPE_STOP,
214     RTSP_CMD_TYPE_SEEK,
215     RTSP_CMD_TYPE_REWIND,
216     RTSP_CMD_TYPE_FORWARD,
217 } rtsp_cmd_type_t;
218
219 static vod_media_t *MediaNew( vod_t *, const char *, input_item_t * );
220 static void         MediaDel( vod_t *, vod_media_t * );
221 static int          MediaAddES( vod_t *, vod_media_t *, es_format_t * );
222 static void         MediaDelES( vod_t *, vod_media_t *, es_format_t * );
223
224 static void CommandThread( vlc_object_t *p_this );
225 static void CommandPush( vod_t *, rtsp_cmd_type_t, vod_media_t *, const char *psz_session,
226                          double f_arg, const char *psz_arg );
227
228 static rtsp_client_t *RtspClientNew( vod_media_t *, char * );
229 static rtsp_client_t *RtspClientGet( vod_media_t *, const char * );
230 static void           RtspClientDel( vod_media_t *, rtsp_client_t * );
231
232 static int RtspCallback( httpd_callback_sys_t *, httpd_client_t *,
233                          httpd_message_t *, const httpd_message_t * );
234 static int RtspCallbackES( httpd_callback_sys_t *, httpd_client_t *,
235                            httpd_message_t *, const httpd_message_t * );
236
237 static char *SDPGenerate( const vod_media_t *, httpd_client_t *cl );
238
239 static void sprintf_hexa( char *s, uint8_t *p_data, int i_data )
240 {
241     static const char hex[16] = "0123456789abcdef";
242     int i;
243
244     for( i = 0; i < i_data; i++ )
245     {
246         s[2*i+0] = hex[(p_data[i]>>4)&0xf];
247         s[2*i+1] = hex[(p_data[i]   )&0xf];
248     }
249     s[2*i_data] = '\0';
250 }
251
252 /*****************************************************************************
253  * Open: Starts the RTSP server module
254  *****************************************************************************/
255 static int Open( vlc_object_t *p_this )
256 {
257     vod_t *p_vod = (vod_t *)p_this;
258     vod_sys_t *p_sys = 0;
259     char *psz_url = 0;
260     vlc_url_t url;
261
262     psz_url = config_GetPsz( p_vod, "rtsp-host" );
263     vlc_UrlParse( &url, psz_url, 0 );
264     free( psz_url );
265
266     if( url.i_port <= 0 ) url.i_port = 554;
267
268     p_vod->p_sys = p_sys = malloc( sizeof( vod_sys_t ) );
269     if( !p_sys ) goto error;
270     p_sys->p_rtsp_host = 0;
271
272     p_sys->i_session_timeout = var_CreateGetInteger( p_this, "rtsp-session-timeout" );
273
274     p_sys->i_throttle_users = var_CreateGetInteger( p_this, "rtsp-throttle-users" );
275     msg_Dbg( p_this, "allowing up to %d connections", p_sys->i_throttle_users );
276     p_sys->i_connections = 0;
277
278     p_sys->psz_raw_mux = var_CreateGetString( p_this, "rtsp-raw-mux" );
279
280     p_sys->p_rtsp_host =
281         httpd_HostNew( VLC_OBJECT(p_vod), url.psz_host, url.i_port );
282     if( !p_sys->p_rtsp_host )
283     {
284         msg_Err( p_vod, "cannot create RTSP server (%s:%i)",
285                  url.psz_host, url.i_port );
286         goto error;
287     }
288
289     p_sys->psz_path = strdup( url.psz_path ? url.psz_path : "/" );
290     p_sys->i_port = url.i_port;
291
292     vlc_UrlClean( &url );
293
294     vlc_mutex_init( &p_sys->lock_media );
295
296     TAB_INIT( p_sys->i_media, p_sys->media );
297     p_sys->i_media_id = 0;
298
299     p_vod->pf_media_new = MediaNew;
300     p_vod->pf_media_del = MediaDel;
301     p_vod->pf_media_add_es = MediaAddES;
302     p_vod->pf_media_del_es = MediaDelES;
303
304     p_sys->p_fifo_cmd = block_FifoNew();
305     if( vlc_thread_create( p_vod, "rtsp vod thread", CommandThread,
306                            VLC_THREAD_PRIORITY_LOW, false ) )
307     {
308         msg_Err( p_vod, "cannot spawn rtsp vod thread" );
309         block_FifoRelease( p_sys->p_fifo_cmd );
310         free( p_sys->psz_path );
311         goto error;
312     }
313
314     return VLC_SUCCESS;
315
316 error:
317     if( p_sys )
318     {
319         if( p_sys->p_rtsp_host ) httpd_HostDelete( p_sys->p_rtsp_host );
320         free( p_sys->psz_raw_mux );
321         free( p_sys );
322     }
323     vlc_UrlClean( &url );
324
325     return VLC_EGENERIC;
326 }
327
328 /*****************************************************************************
329  * Close:
330  *****************************************************************************/
331 static void Close( vlc_object_t * p_this )
332 {
333     vod_t *p_vod = (vod_t *)p_this;
334     vod_sys_t *p_sys = p_vod->p_sys;
335
336     /* Stop command thread */
337     vlc_object_kill( p_vod );
338     CommandPush( p_vod, RTSP_CMD_TYPE_NONE, NULL, NULL, 0.0, NULL );
339     vlc_thread_join( p_vod );
340
341     block_FifoRelease( p_sys->p_fifo_cmd );
342
343     httpd_HostDelete( p_sys->p_rtsp_host );
344     var_Destroy( p_this, "rtsp-session-timeout" );
345     var_Destroy( p_this, "rtsp-throttle-users" );
346     var_Destroy( p_this, "rtsp-raw-mux" );
347
348     /* Check VLM is not buggy */
349     if( p_sys->i_media > 0 )
350         msg_Err( p_vod, "rtsp vod leaking %d medias", p_sys->i_media );
351     TAB_CLEAN( p_sys->i_media, p_sys->media );
352
353     vlc_mutex_destroy( &p_sys->lock_media );
354
355     free( p_sys->psz_path );
356     free( p_sys->psz_raw_mux );
357     free( p_sys );
358 }
359
360 /*****************************************************************************
361  * Media handling
362  *****************************************************************************/
363 static vod_media_t *MediaNew( vod_t *p_vod, const char *psz_name,
364                               input_item_t *p_item )
365 {
366     vod_sys_t *p_sys = p_vod->p_sys;
367     vod_media_t *p_media = malloc( sizeof(vod_media_t) );
368     int i;
369
370     if( !p_media )
371         return NULL;
372
373     memset( p_media, 0, sizeof(vod_media_t) );
374     p_media->id = p_sys->i_media_id++;
375     TAB_INIT( p_media->i_es, p_media->es );
376     p_media->psz_mux = 0;
377     TAB_INIT( p_media->i_rtsp, p_media->rtsp );
378     p_media->b_raw = false;
379
380     if( asprintf( &p_media->psz_rtsp_path, "%s%s",
381                   p_sys->psz_path, psz_name ) <0 )
382         return NULL;
383     p_media->p_rtsp_url =
384         httpd_UrlNewUnique( p_sys->p_rtsp_host, p_media->psz_rtsp_path, NULL,
385                             NULL, NULL );
386
387     if( !p_media->p_rtsp_url )
388     {
389         msg_Err( p_vod, "cannot create RTSP url (%s)", p_media->psz_rtsp_path);
390         free( p_media->psz_rtsp_path );
391         free( p_media );
392         return NULL;
393     }
394
395     msg_Dbg( p_vod, "created RTSP url: %s", p_media->psz_rtsp_path );
396
397     if( asprintf( &p_media->psz_rtsp_control_v4,
398                "a=control:rtsp://%%s:%d%s/trackID=%%d\r\n",
399                p_sys->i_port, p_media->psz_rtsp_path ) < 0 )
400         return NULL;
401     if( asprintf( &p_media->psz_rtsp_control_v6,
402                "a=control:rtsp://[%%s]:%d%s/trackID=%%d\r\n",
403               p_sys->i_port, p_media->psz_rtsp_path ) < 0 )
404         return NULL;
405
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 );
418
419     p_media->p_vod = p_vod;
420
421     vlc_mutex_lock( &p_sys->lock_media );
422     TAB_APPEND( p_sys->i_media, p_sys->media, p_media );
423     vlc_mutex_unlock( &p_sys->lock_media );
424
425     vlc_mutex_init( &p_media->lock );
426     p_media->psz_session_name = strdup("");
427     p_media->psz_session_description = strdup("");
428     p_media->psz_session_url = strdup("");
429     p_media->psz_session_email = strdup("");
430
431     p_media->i_port_audio = 1234;
432     p_media->i_port_video = 1236;
433     p_media->i_port       = 1238;
434     p_media->i_payload_type = 96;
435
436     p_media->i_sdp_id = mdate();
437     p_media->i_sdp_version = 1;
438     p_media->i_length = input_item_GetDuration( p_item );
439
440     vlc_mutex_lock( &p_item->lock );
441     msg_Dbg( p_vod, "media has %i declared ES", p_item->i_es );
442     for( i = 0; i < p_item->i_es; i++ )
443     {
444         MediaAddES( p_vod, p_media, p_item->es[i] );
445     }
446     vlc_mutex_unlock( &p_item->lock );
447
448     return p_media;
449 }
450
451 static void MediaDel( vod_t *p_vod, vod_media_t *p_media )
452 {
453     vod_sys_t *p_sys = p_vod->p_sys;
454
455     msg_Dbg( p_vod, "deleting media: %s", p_media->psz_rtsp_path );
456
457     vlc_mutex_lock( &p_sys->lock_media );
458     TAB_REMOVE( p_sys->i_media, p_sys->media, p_media );
459     vlc_mutex_unlock( &p_sys->lock_media );
460
461     while( p_media->i_rtsp > 0 )
462         RtspClientDel( p_media, p_media->rtsp[0] );
463     TAB_CLEAN( p_media->i_rtsp, p_media->rtsp );
464
465     httpd_UrlDelete( p_media->p_rtsp_url );
466     free( p_media->psz_rtsp_path );
467     free( p_media->psz_rtsp_control_v6 );
468     free( p_media->psz_rtsp_control_v4 );
469
470     while( p_media->i_es )
471         MediaDelES( p_vod, p_media, &p_media->es[0]->fmt );
472     TAB_CLEAN( p_media->i_es, p_media->es );
473
474     vlc_mutex_destroy( &p_media->lock );
475
476     free( p_media->psz_session_name );
477     free( p_media->psz_session_description );
478     free( p_media->psz_session_url );
479     free( p_media->psz_session_email );
480     free( p_media->psz_mux );
481     free( p_media );
482 }
483
484 static int MediaAddES( vod_t *p_vod, vod_media_t *p_media, es_format_t *p_fmt )
485 {
486     media_es_t *p_es = malloc( sizeof(media_es_t) );
487     char *psz_urlc;
488
489     if( !p_es ) return VLC_ENOMEM;
490     memset( p_es, 0, sizeof(media_es_t) );
491
492     free( p_media->psz_mux );
493     p_media->psz_mux = NULL;
494
495     /* TODO: update SDP, etc... */
496     if( asprintf( &psz_urlc, "%s/trackID=%d",
497               p_media->psz_rtsp_path, p_media->i_es ) < 0 )
498     {
499         free( p_es );
500         return VLC_ENOMEM;
501     }
502     msg_Dbg( p_vod, "  - ES %4.4s (%s)", (char *)&p_fmt->i_codec, psz_urlc );
503
504     switch( p_fmt->i_codec )
505     {
506         case VLC_FOURCC( 's', '1', '6', 'b' ):
507             if( p_fmt->audio.i_channels == 1 && p_fmt->audio.i_rate == 44100 )
508             {
509                 p_es->i_payload_type = 11;
510             }
511             else if( p_fmt->audio.i_channels == 2 &&
512                      p_fmt->audio.i_rate == 44100 )
513             {
514                 p_es->i_payload_type = 10;
515             }
516             else
517             {
518                 p_es->i_payload_type = p_media->i_payload_type++;
519             }
520             p_es->psz_rtpmap = malloc( strlen( "L16/*/*" ) + 20+1 );
521             sprintf( p_es->psz_rtpmap, "L16/%d/%d", p_fmt->audio.i_rate,
522                     p_fmt->audio.i_channels );
523             break;
524         case VLC_FOURCC( 'u', '8', ' ', ' ' ):
525             p_es->i_payload_type = p_media->i_payload_type++;
526             p_es->psz_rtpmap = malloc( strlen( "L8/*/*" ) + 20+1 );
527             sprintf( p_es->psz_rtpmap, "L8/%d/%d", p_fmt->audio.i_rate,
528                     p_fmt->audio.i_channels );
529             break;
530         case VLC_FOURCC( 'm', 'p', 'g', 'a' ):
531         case VLC_FOURCC( 'm', 'p', '3', ' ' ):
532             p_es->i_payload_type = 14;
533             p_es->psz_rtpmap = strdup( "MPA/90000" );
534             break;
535         case VLC_FOURCC( 'm', 'p', 'g', 'v' ):
536             p_es->i_payload_type = 32;
537             p_es->psz_rtpmap = strdup( "MPV/90000" );
538             break;
539         case VLC_FOURCC( 'a', '5', '2', ' ' ):
540             p_es->i_payload_type = p_media->i_payload_type++;
541             asprintf( &p_es->psz_rtpmap, "ac3/%d", p_fmt->audio.i_rate );
542             break;
543         case VLC_FOURCC( 'H', '2', '6', '3' ):
544             p_es->i_payload_type = p_media->i_payload_type++;
545             p_es->psz_rtpmap = strdup( "H263-1998/90000" );
546             break;
547         case VLC_FOURCC( 'h', '2', '6', '4' ):
548             p_es->i_payload_type = p_media->i_payload_type++;
549             p_es->psz_rtpmap = strdup( "H264/90000" );
550             p_es->psz_fmtp = NULL;
551             /* FIXME AAAAAAAAAAAARRRRRRRRGGGG copied from stream_out/rtp.c */
552             if( p_fmt->i_extra > 0 )
553             {
554                 uint8_t *p_buffer = p_fmt->p_extra;
555                 int     i_buffer = p_fmt->i_extra;
556                 char    *p_64_sps = NULL;
557                 char    *p_64_pps = NULL;
558                 char    hexa[6+1];
559
560                 while( i_buffer > 4 &&
561                        p_buffer[0] == 0 && p_buffer[1] == 0 &&
562                        p_buffer[2] == 0 && p_buffer[3] == 1 )
563                 {
564                     const int i_nal_type = p_buffer[4]&0x1f;
565                     int i_offset;
566                     int i_size      = 0;
567
568                     i_size = i_buffer;
569                     for( i_offset = 4; i_offset+3 < i_buffer ; i_offset++)
570                     {
571                         if( p_buffer[i_offset] == 0 && p_buffer[i_offset+1] == 0 && p_buffer[i_offset+2] == 0 && p_buffer[i_offset+3] == 1 )
572                         {
573                             /* we found another startcode */
574                             i_size = i_offset;
575                             break;
576                         }
577                     }
578                     if( i_nal_type == 7 )
579                     {
580                         p_64_sps = vlc_b64_encode_binary( &p_buffer[4], i_size - 4 );
581                         sprintf_hexa( hexa, &p_buffer[5], 3 );
582                     }
583                     else if( i_nal_type == 8 )
584                     {
585                         p_64_pps = vlc_b64_encode_binary( &p_buffer[4], i_size - 4 );
586                     }
587                     i_buffer -= i_size;
588                     p_buffer += i_size;
589                 }
590                 /* */
591                 if( p_64_sps && p_64_pps )
592                 {
593                     if( asprintf( &p_es->psz_fmtp,
594                                   "packetization-mode=1;profile-level-id=%s;"
595                                   "sprop-parameter-sets=%s,%s;", hexa, p_64_sps,
596                                   p_64_pps ) < 0 )
597                     {
598                         free( p_64_sps );
599                         free( p_64_pps );
600                         free( psz_urlc );
601                         free( p_es );
602                         return VLC_ENOMEM;
603                     }
604                 }
605                 free( p_64_sps );
606                 free( p_64_pps );
607             }
608             if( !p_es->psz_fmtp )
609                 p_es->psz_fmtp = strdup( "packetization-mode=1" );
610             break;
611         case VLC_FOURCC( 'm', 'p', '4', 'v' ):
612             p_es->i_payload_type = p_media->i_payload_type++;
613             p_es->psz_rtpmap = strdup( "MP4V-ES/90000" );
614             if( p_fmt->i_extra > 0 )
615             {
616                 char *p_hexa = malloc( 2 * p_fmt->i_extra + 1 );
617                 p_es->psz_fmtp = malloc( 100 + 2 * p_fmt->i_extra );
618                 sprintf_hexa( p_hexa, p_fmt->p_extra, p_fmt->i_extra );
619                 sprintf( p_es->psz_fmtp,
620                         "profile-level-id=3; config=%s;", p_hexa );
621                 free( p_hexa );
622             }
623             break;
624         case VLC_FOURCC( 'm', 'p', '4', 'a' ):
625             p_es->i_payload_type = p_media->i_payload_type++;
626             p_es->psz_rtpmap = malloc( strlen( "mpeg4-generic/" ) + 12 );
627             sprintf( p_es->psz_rtpmap, "mpeg4-generic/%d", p_fmt->audio.i_rate );
628             if( p_fmt->i_extra > 0 )
629             {
630                 char *p_hexa = malloc( 2 * p_fmt->i_extra + 1 );
631                 p_es->psz_fmtp = malloc( 200 + 2 * p_fmt->i_extra );
632                 sprintf_hexa( p_hexa, p_fmt->p_extra, p_fmt->i_extra );
633                 sprintf( p_es->psz_fmtp,
634                         "streamtype=5; profile-level-id=15; mode=AAC-hbr; "
635                         "config=%s; SizeLength=13;IndexLength=3; "
636                         "IndexDeltaLength=3; Profile=1;", p_hexa );
637                 free( p_hexa );
638             }
639             break;
640         case VLC_FOURCC( 'm', 'p', '2', 't' ):
641             p_media->psz_mux = strdup("ts");
642             p_es->i_payload_type = 33;
643             p_es->psz_rtpmap = strdup( "MP2T/90000" );
644             break;
645         case VLC_FOURCC( 'm', 'p', '2', 'p' ):
646             p_media->psz_mux = strdup("ps");
647             p_es->i_payload_type = p_media->i_payload_type++;
648             p_es->psz_rtpmap = strdup( "MP2P/90000" );
649             break;
650         case VLC_FOURCC( 's', 'a', 'm', 'r' ):
651             p_es->i_payload_type = p_media->i_payload_type++;
652             p_es->psz_rtpmap = strdup( p_fmt->audio.i_channels == 2 ?
653                                     "AMR/8000/2" : "AMR/8000" );
654             p_es->psz_fmtp = strdup( "octet-align=1" );
655             break;
656         case VLC_FOURCC( 's', 'a', 'w', 'b' ):
657             p_es->i_payload_type = p_media->i_payload_type++;
658             p_es->psz_rtpmap = strdup( p_fmt->audio.i_channels == 2 ?
659                                     "AMR-WB/16000/2" : "AMR-WB/16000" );
660             p_es->psz_fmtp = strdup( "octet-align=1" );
661             break;
662
663         default:
664             msg_Err( p_vod, "cannot add this stream (unsupported "
665                     "codec: %4.4s)", (char*)&p_fmt->i_codec );
666             free( psz_urlc );
667             free( p_es );
668             return VLC_EGENERIC;
669     }
670
671     p_es->p_rtsp_url =
672         httpd_UrlNewUnique( p_vod->p_sys->p_rtsp_host, psz_urlc, NULL, NULL,
673                             NULL );
674
675     if( !p_es->p_rtsp_url )
676     {
677         msg_Err( p_vod, "cannot create RTSP url (%s)", psz_urlc );
678         free( psz_urlc );
679         free( p_es );
680         return VLC_EGENERIC;
681     }
682     free( psz_urlc );
683
684     httpd_UrlCatch( p_es->p_rtsp_url, HTTPD_MSG_SETUP,
685                     RtspCallbackES, (void*)p_es );
686     httpd_UrlCatch( p_es->p_rtsp_url, HTTPD_MSG_TEARDOWN,
687                     RtspCallbackES, (void*)p_es );
688     httpd_UrlCatch( p_es->p_rtsp_url, HTTPD_MSG_PLAY,
689                     RtspCallbackES, (void*)p_es );
690     httpd_UrlCatch( p_es->p_rtsp_url, HTTPD_MSG_PAUSE,
691                     RtspCallbackES, (void*)p_es );
692
693     es_format_Copy( &p_es->fmt, p_fmt );
694     p_es->p_vod = p_vod;
695     p_es->p_media = p_media;
696
697 #if 0
698     /* Choose the port */
699     if( p_fmt->i_cat == AUDIO_ES && p_media->i_port_audio > 0 )
700     {
701         p_es->i_port = p_media->i_port_audio;
702         p_media->i_port_audio = 0;
703     }
704     else if( p_fmt->i_cat == VIDEO_ES && p_media->i_port_video > 0 )
705     {
706         p_es->i_port = p_media->i_port_video;
707         p_media->i_port_video = 0;
708     }
709     while( !p_es->i_port )
710     {
711         if( p_media->i_port != p_media->i_port_audio &&
712             p_media->i_port != p_media->i_port_video )
713         {
714             p_es->i_port = p_media->i_port;
715             p_media->i_port += 2;
716             break;
717         }
718         p_media->i_port += 2;
719     }
720 #else
721
722     p_es->i_port = 0;
723 #endif
724
725     vlc_mutex_lock( &p_media->lock );
726     TAB_APPEND( p_media->i_es, p_media->es, p_es );
727     vlc_mutex_unlock( &p_media->lock );
728
729     p_media->i_sdp_version++;
730
731     return VLC_SUCCESS;
732 }
733
734 static void MediaDelES( vod_t *p_vod, vod_media_t *p_media, es_format_t *p_fmt)
735 {
736     media_es_t *p_es = NULL;
737     int i;
738
739     /* Find the ES */
740     for( i = 0; i < p_media->i_es; i++ )
741     {
742         if( p_media->es[i]->fmt.i_cat == p_fmt->i_cat &&
743             p_media->es[i]->fmt.i_codec == p_fmt->i_codec &&
744             p_media->es[i]->fmt.i_id == p_fmt->i_id )
745         {
746             p_es = p_media->es[i];
747         }
748     }
749     if( !p_es ) return;
750
751     msg_Dbg( p_vod, "  - Removing ES %4.4s", (char *)&p_fmt->i_codec );
752
753     vlc_mutex_lock( &p_media->lock );
754     TAB_REMOVE( p_media->i_es, p_media->es, p_es );
755     vlc_mutex_unlock( &p_media->lock );
756
757     free( p_es->psz_rtpmap );
758     free( p_es->psz_fmtp );
759     p_media->i_sdp_version++;
760
761     if( p_es->p_rtsp_url ) httpd_UrlDelete( p_es->p_rtsp_url );
762     es_format_Clean( &p_es->fmt );
763     free( p_es );
764 }
765
766 /* */
767 typedef struct
768 {
769     int i_type;
770     int i_media_id;
771     //vod_media_t *p_media;
772     char *psz_session;
773     char *psz_arg;
774     double f_arg;
775 } rtsp_cmd_t;
776
777 static void CommandPush( vod_t *p_vod, rtsp_cmd_type_t i_type, vod_media_t *p_media, const char *psz_session,
778                          double f_arg, const char *psz_arg )
779 {
780     rtsp_cmd_t cmd;
781     block_t *p_cmd;
782
783     memset( &cmd, 0, sizeof(cmd) );
784     cmd.i_type = i_type;
785     if( p_media )
786         cmd.i_media_id = p_media->id;
787     if( psz_session )
788         cmd.psz_session = strdup(psz_session);
789     cmd.f_arg = f_arg;
790     if( psz_arg )
791         cmd.psz_arg = strdup(psz_arg);
792
793     p_cmd = block_New( p_vod, sizeof(rtsp_cmd_t) );
794     memcpy( p_cmd->p_buffer, &cmd, sizeof(cmd) );
795
796     block_FifoPut( p_vod->p_sys->p_fifo_cmd, p_cmd );
797 }
798
799 static void CommandThread( vlc_object_t *p_this )
800 {
801     vod_t *p_vod = (vod_t*)p_this;
802     vod_sys_t *p_sys = p_vod->p_sys;
803
804     while( vlc_object_alive (p_vod) )
805     {
806         block_t *p_block_cmd = block_FifoGet( p_sys->p_fifo_cmd );
807         rtsp_cmd_t cmd;
808         vod_media_t *p_media = NULL;
809         int i;
810
811         if( !p_block_cmd )
812             break;
813
814         memcpy( &cmd, p_block_cmd->p_buffer, sizeof(cmd) );
815         block_Release( p_block_cmd );
816
817         if( cmd.i_type == RTSP_CMD_TYPE_NONE )
818             break;
819
820         /* */
821         vlc_mutex_lock( &p_sys->lock_media );
822         for( i = 0; i < p_sys->i_media; i++ )
823         {
824             if( p_sys->media[i]->id == cmd.i_media_id )
825                 break;
826         }
827         if( i >= p_sys->i_media )
828             goto next;
829         p_media = p_sys->media[i];
830
831         switch( cmd.i_type )
832         {
833         case RTSP_CMD_TYPE_PLAY:
834             vod_MediaControl( p_vod, p_media, cmd.psz_session,
835                               VOD_MEDIA_PLAY, cmd.psz_arg );
836             break;
837         case RTSP_CMD_TYPE_PAUSE:
838             vod_MediaControl( p_vod, p_media, cmd.psz_session,
839                               VOD_MEDIA_PAUSE );
840             break;
841
842         case RTSP_CMD_TYPE_STOP:
843             vod_MediaControl( p_vod, p_media, cmd.psz_session, VOD_MEDIA_STOP );
844             break;
845
846         case RTSP_CMD_TYPE_SEEK:
847             vod_MediaControl( p_vod, p_media, cmd.psz_session,
848                               VOD_MEDIA_SEEK, cmd.f_arg );
849             break;
850
851         case RTSP_CMD_TYPE_REWIND:
852             vod_MediaControl( p_vod, p_media, cmd.psz_session,
853                               VOD_MEDIA_REWIND, cmd.f_arg );
854             break;
855
856         case RTSP_CMD_TYPE_FORWARD:
857             vod_MediaControl( p_vod, p_media, cmd.psz_session,
858                               VOD_MEDIA_FORWARD, cmd.f_arg );
859             break;
860
861         default:
862             break;
863         }
864
865     next:
866         vlc_mutex_unlock( &p_sys->lock_media );
867         free( cmd.psz_session );
868         free( cmd.psz_arg );
869     }
870 }
871
872 /****************************************************************************
873  * RTSP server implementation
874  ****************************************************************************/
875 static rtsp_client_t *RtspClientNew( vod_media_t *p_media, char *psz_session )
876 {
877     rtsp_client_t *p_rtsp = malloc( sizeof(rtsp_client_t) );
878
879     if( !p_rtsp ) return NULL;
880     memset( p_rtsp, 0, sizeof(rtsp_client_t) );
881     p_rtsp->es = 0;
882
883     p_rtsp->psz_session = psz_session;
884     TAB_APPEND( p_media->i_rtsp, p_media->rtsp, p_rtsp );
885
886     p_media->p_vod->p_sys->i_connections++;
887     msg_Dbg( p_media->p_vod, "new session: %s, connections: %d",
888              psz_session, p_media->p_vod->p_sys->i_throttle_users );
889
890     return p_rtsp;
891 }
892
893 static rtsp_client_t *RtspClientGet( vod_media_t *p_media, const char *psz_session )
894 {
895     int i;
896
897     for( i = 0; psz_session && i < p_media->i_rtsp; i++ )
898     {
899         if( !strcmp( p_media->rtsp[i]->psz_session, psz_session ) )
900             return p_media->rtsp[i];
901     }
902
903     return NULL;
904 }
905
906 static void RtspClientDel( vod_media_t *p_media, rtsp_client_t *p_rtsp )
907 {
908     p_media->p_vod->p_sys->i_connections--;
909     msg_Dbg( p_media->p_vod, "closing session: %s, connections: %d",
910              p_rtsp->psz_session, p_media->p_vod->p_sys->i_throttle_users );
911
912     while( p_rtsp->i_es-- )
913     {
914         free( p_rtsp->es[p_rtsp->i_es]->psz_ip );
915         free( p_rtsp->es[p_rtsp->i_es] );
916         if( !p_rtsp->i_es ) free( p_rtsp->es );
917     }
918
919     TAB_REMOVE( p_media->i_rtsp, p_media->rtsp, p_rtsp );
920
921     free( p_rtsp->psz_session );
922     free( p_rtsp );
923 }
924
925
926 static float ParseNPT (const char *str)
927 {
928      locale_t loc = newlocale (LC_NUMERIC_MASK, "C", NULL);
929      locale_t oldloc = uselocale (loc);
930      unsigned hour, min;
931      float sec;
932
933      if (sscanf (str, "%u:%u:%f", &hour, &min, &sec) == 3)
934          sec += ((hour * 60) + min) * 60;
935      else
936      if (sscanf (str, "%f", &sec) != 1)
937          sec = 0.;
938
939      if (loc != (locale_t)0)
940      {
941          uselocale (oldloc);
942          freelocale (loc);
943      }
944      return sec;
945 }
946
947
948 static int RtspCallback( httpd_callback_sys_t *p_args, httpd_client_t *cl,
949                          httpd_message_t *answer, const httpd_message_t *query )
950 {
951     vod_media_t *p_media = (vod_media_t*)p_args;
952     vod_t *p_vod = p_media->p_vod;
953     const char *psz_transport = NULL;
954     const char *psz_playnow = NULL; /* support option: x-playNow */
955     const char *psz_session = NULL;
956     const char *psz_cseq = NULL;
957     rtsp_client_t *p_rtsp;
958     int i_port = 0;
959     int i_cseq = 0;
960
961     if( answer == NULL || query == NULL ) return VLC_SUCCESS;
962
963     msg_Dbg( p_vod, "RtspCallback query: type=%d", query->i_type );
964
965     answer->i_proto   = HTTPD_PROTO_RTSP;
966     answer->i_version = query->i_version;
967     answer->i_type    = HTTPD_MSG_ANSWER;
968     answer->i_body    = 0;
969     answer->p_body    = NULL;
970
971     switch( query->i_type )
972     {
973         case HTTPD_MSG_SETUP:
974         {
975             psz_playnow = httpd_MsgGet( query, "x-playNow" );
976             psz_transport = httpd_MsgGet( query, "Transport" );
977             if( psz_transport == NULL )
978             {
979                 answer->i_status = 400;
980                 break;
981             }
982             msg_Dbg( p_vod, "HTTPD_MSG_SETUP: transport=%s", psz_transport );
983
984             if( strstr( psz_transport, "unicast" ) &&
985                 strstr( psz_transport, "client_port=" ) )
986             {
987                 rtsp_client_t *p_rtsp = NULL;
988                 char ip[NI_MAXNUMERICHOST];
989                 i_port = atoi( strstr( psz_transport, "client_port=" ) +
990                                 strlen("client_port=") );
991
992                 if( strstr( psz_transport, "MP2T/H2221/UDP" ) ||
993                     strstr( psz_transport, "RAW/RAW/UDP" ) )
994                 {
995                     free( p_media->psz_mux );
996                     p_media->psz_mux = NULL;
997                     p_media->psz_mux = strdup( p_vod->p_sys->psz_raw_mux );
998                     p_media->b_raw = true;
999                 }
1000
1001                 if( httpd_ClientIP( cl, ip ) == NULL )
1002                 {
1003                     answer->i_status = 500;
1004                     answer->i_body = 0;
1005                     answer->p_body = NULL;
1006                     break;
1007                 }
1008
1009                 msg_Dbg( p_vod, "HTTPD_MSG_SETUP: unicast ip=%s port=%d",
1010                          ip, i_port );
1011
1012                 psz_session = httpd_MsgGet( query, "Session" );
1013                 if( !psz_session || !*psz_session )
1014                 {
1015                     char *psz_new;
1016                     if( ( p_vod->p_sys->i_throttle_users > 0 ) &&
1017                         ( p_vod->p_sys->i_connections >= p_vod->p_sys->i_throttle_users ) )
1018                     {
1019                         answer->i_status = 503;
1020                         answer->i_body = 0;
1021                         answer->p_body = NULL;
1022                         break;
1023                     }
1024                     if( asprintf( &psz_new, "%d", rand() ) < 0 )
1025                         return VLC_ENOMEM;
1026                     psz_session = psz_new;
1027
1028                     p_rtsp = RtspClientNew( p_media, psz_new );
1029                     if( !p_rtsp )
1030                     {
1031                         answer->i_status = 454;
1032                         answer->i_body = 0;
1033                         answer->p_body = NULL;
1034                         break;
1035                     }
1036                 }
1037                 else
1038                 {
1039                     p_rtsp = RtspClientGet( p_media, psz_session );
1040                     if( !p_rtsp )
1041                     {
1042                         answer->i_status = 454;
1043                         answer->i_body = 0;
1044                         answer->p_body = NULL;
1045                         break;
1046                     }
1047                 }
1048
1049                 answer->i_status = 200;
1050                 answer->i_body = 0;
1051                 answer->p_body = NULL;
1052
1053                 if( p_media->b_raw )
1054                 {
1055                     if( strstr( psz_transport, "MP2T/H2221/UDP" ) )
1056                     {
1057                         httpd_MsgAdd( answer, "Transport",
1058                                       "MP2T/H2221/UDP;unicast;client_port=%d-%d",
1059                                       i_port, i_port + 1 );
1060                     }
1061                     else if( strstr( psz_transport, "RAW/RAW/UDP" ) )
1062                     {
1063                         httpd_MsgAdd( answer, "Transport",
1064                                       "RAW/RAW/UDP;unicast;client_port=%d-%d",
1065                                       i_port, i_port + 1 );
1066                     }
1067                 }
1068                 else
1069                     httpd_MsgAdd( answer, "Transport",
1070                                   "RTP/AVP/UDP;unicast;client_port=%d-%d",
1071                                   i_port, i_port + 1 );
1072             }
1073             else /* TODO  strstr( psz_transport, "interleaved" ) ) */
1074             {
1075                 answer->i_status = 461;
1076                 answer->i_body = 0;
1077                 answer->p_body = NULL;
1078             }
1079
1080             /* Intentional fall-through on x-playNow option in RTSP request */
1081             if( !psz_playnow )
1082                 break;
1083         }
1084
1085         case HTTPD_MSG_PLAY:
1086         {
1087             char *psz_output, ip[NI_MAXNUMERICHOST];
1088             int i, i_port_audio = 0, i_port_video = 0;
1089
1090             /* for now only multicast so easy */
1091             if( !psz_playnow )
1092             {
1093                 answer->i_status = 200;
1094                 answer->i_body = 0;
1095                 answer->p_body = NULL;
1096             }
1097
1098             if( !psz_session )
1099                 psz_session = httpd_MsgGet( query, "Session" );
1100             msg_Dbg( p_vod, "HTTPD_MSG_PLAY for session: %s", psz_session );
1101
1102             p_rtsp = RtspClientGet( p_media, psz_session );
1103             if( !p_rtsp )
1104             {
1105                 answer->i_status = 500;
1106                 answer->i_body = 0;
1107                 answer->p_body = NULL;
1108                 break;
1109             }
1110
1111             if( p_rtsp->b_playing )
1112             {
1113                 const char *psz_position = httpd_MsgGet( query, "Range" );
1114                 const char *psz_scale = httpd_MsgGet( query, "Scale" );
1115                 if( psz_position )
1116                     psz_position = strstr( psz_position, "npt=" );
1117                 if( psz_position && !psz_scale )
1118                 {
1119                     double f_pos = ParseNPT (psz_position + 4);
1120                     msg_Dbg( p_vod, "seeking request: %s", psz_position );
1121                     f_pos /= ((double)(p_media->i_length))/1000 /1000 / 100;
1122                     CommandPush( p_vod, RTSP_CMD_TYPE_SEEK, p_media,
1123                                  psz_session, f_pos, NULL );
1124                     break;
1125                 }
1126                 if( psz_scale )
1127                 {
1128                     double f_scale = 0.0;
1129                     char *end;
1130
1131                     f_scale = us_strtod( psz_scale, &end );
1132                     if( end > psz_scale )
1133                     {
1134                         f_scale = (f_scale * 30.0);
1135                         if( psz_scale[0] == '-' ) /* rewind */
1136                         {
1137                             msg_Dbg( p_vod, "rewind request: %s", psz_scale );
1138                             CommandPush( p_vod, RTSP_CMD_TYPE_REWIND, p_media,
1139                                          psz_session, f_scale, NULL );
1140                         }
1141                         else if(psz_scale[0] != '1' ) /* fast-forward */
1142                         {
1143                             msg_Dbg( p_vod, "fastforward request: %s",
1144                                      psz_scale );
1145                             CommandPush( p_vod, RTSP_CMD_TYPE_FORWARD, p_media,
1146                                          psz_session, f_scale, NULL );
1147                         }
1148
1149                         if( p_rtsp->b_paused == true )
1150                         {
1151                             p_rtsp->b_paused = false;
1152                             CommandPush( p_vod, RTSP_CMD_TYPE_PAUSE, p_media,
1153                                          psz_session, 0, NULL );
1154                         }
1155                     }
1156                     break;
1157                 }
1158             }
1159
1160             if( p_rtsp->b_playing && p_rtsp->b_paused )
1161             {
1162                 CommandPush( p_vod, RTSP_CMD_TYPE_PAUSE, p_media,
1163                              psz_session, 0, NULL );
1164                 p_rtsp->b_paused = false;
1165                 break;
1166             }
1167             else if( p_rtsp->b_playing ) break;
1168
1169             if( httpd_ClientIP( cl, ip ) == NULL ) break;
1170
1171             p_rtsp->b_playing = true;
1172
1173             /* FIXME for != 1 video and 1 audio */
1174             for( i = 0; i < p_rtsp->i_es; i++ )
1175             {
1176                 if( p_rtsp->es[i]->p_media_es->fmt.i_cat == AUDIO_ES )
1177                     i_port_audio = p_rtsp->es[i]->i_port;
1178                 if( p_rtsp->es[i]->p_media_es->fmt.i_cat == VIDEO_ES )
1179                     i_port_video = p_rtsp->es[i]->i_port;
1180             }
1181
1182             if( p_media->psz_mux )
1183             {
1184                 if( p_media->b_raw )
1185                 {
1186                     if( asprintf( &psz_output,
1187                               "std{access=udp,dst=%s:%i,mux=%s}",
1188                               ip, i_port, p_media->psz_mux ) < 0 )
1189                         return VLC_ENOMEM;
1190                 }
1191                 else
1192                 {
1193                     if( asprintf( &psz_output,
1194                               "rtp{dst=%s,port=%i,mux=%s}",
1195                               ip, i_port_video, p_media->psz_mux ) < 0 )
1196                         return VLC_ENOMEM;
1197                 }
1198             }
1199             else
1200             {
1201                 if( asprintf( &psz_output,
1202                               "rtp{dst=%s,port-video=%i,port-audio=%i}",
1203                               ip, i_port_video, i_port_audio ) < 0 )
1204                     return VLC_ENOMEM;
1205             }
1206
1207             CommandPush( p_vod, RTSP_CMD_TYPE_PLAY, p_media, psz_session,
1208                          0, psz_output );
1209             free( psz_output );
1210             break;
1211         }
1212
1213         case HTTPD_MSG_DESCRIBE:
1214         {
1215             char *psz_sdp =
1216                 SDPGenerate( p_media, cl );
1217
1218             if( psz_sdp != NULL )
1219             {
1220                 answer->i_status = 200;
1221                 httpd_MsgAdd( answer, "Content-type",  "%s",
1222                               "application/sdp" );
1223
1224                 answer->p_body = (uint8_t *)psz_sdp;
1225                 answer->i_body = strlen( psz_sdp );
1226             }
1227             else
1228             {
1229                 answer->i_status = 500;
1230                 answer->p_body = NULL;
1231                 answer->i_body = 0;
1232             }
1233             break;
1234         }
1235
1236         case HTTPD_MSG_PAUSE:
1237             psz_session = httpd_MsgGet( query, "Session" );
1238             msg_Dbg( p_vod, "HTTPD_MSG_PAUSE for session: %s", psz_session );
1239
1240             p_rtsp = RtspClientGet( p_media, psz_session );
1241             if( !p_rtsp ) break;
1242
1243             CommandPush( p_vod, RTSP_CMD_TYPE_PAUSE, p_media, psz_session,
1244                          0, NULL );
1245             p_rtsp->b_paused = true;
1246
1247             answer->i_status = 200;
1248             answer->i_body = 0;
1249             answer->p_body = NULL;
1250             break;
1251
1252         case HTTPD_MSG_TEARDOWN:
1253             /* for now only multicast so easy again */
1254             answer->i_status = 200;
1255             answer->i_body = 0;
1256             answer->p_body = NULL;
1257
1258             psz_session = httpd_MsgGet( query, "Session" );
1259             msg_Dbg( p_vod, "HTTPD_MSG_TEARDOWN for session: %s", psz_session);
1260
1261             p_rtsp = RtspClientGet( p_media, psz_session );
1262             if( !p_rtsp ) break;
1263
1264             CommandPush( p_vod, RTSP_CMD_TYPE_STOP, p_media, psz_session,
1265                          0, NULL );
1266             RtspClientDel( p_media, p_rtsp );
1267             break;
1268
1269         case HTTPD_MSG_GETPARAMETER:
1270             answer->i_status = 200;
1271             answer->i_body = 0;
1272             answer->p_body = NULL;
1273             break;
1274
1275         default:
1276             return VLC_EGENERIC;
1277     }
1278
1279     httpd_MsgAdd( answer, "Server", "VLC Server" );
1280     httpd_MsgAdd( answer, "Content-Length", "%d", answer->i_body );
1281     psz_cseq = httpd_MsgGet( query, "Cseq" );
1282     psz_cseq ? i_cseq = atoi( psz_cseq ) : 0;
1283     httpd_MsgAdd( answer, "CSeq", "%d", i_cseq );
1284     httpd_MsgAdd( answer, "Cache-Control", "%s", "no-cache" );
1285
1286     if( psz_session )
1287     {
1288          if( p_media->p_vod->p_sys->i_session_timeout >= 0 )
1289              httpd_MsgAdd( answer, "Session", "%s;timeout=%i", psz_session,
1290                p_media->p_vod->p_sys->i_session_timeout );
1291          else
1292               httpd_MsgAdd( answer, "Session", "%s", psz_session );
1293     }
1294
1295     return VLC_SUCCESS;
1296 }
1297
1298 static int RtspCallbackES( httpd_callback_sys_t *p_args, httpd_client_t *cl,
1299                            httpd_message_t *answer,
1300                            const httpd_message_t *query )
1301 {
1302     media_es_t *p_es = (media_es_t*)p_args;
1303     vod_media_t *p_media = p_es->p_media;
1304     vod_t *p_vod = p_media->p_vod;
1305     rtsp_client_t *p_rtsp = NULL;
1306     const char *psz_transport = NULL;
1307     const char *psz_playnow = NULL; /* support option: x-playNow */
1308     const char *psz_session = NULL;
1309     const char *psz_position = NULL;
1310     const char *psz_cseq = NULL;
1311     int i_cseq = 0;
1312     int i;
1313
1314     if( answer == NULL || query == NULL ) return VLC_SUCCESS;
1315
1316     msg_Dbg( p_vod, "RtspCallback query: type=%d", query->i_type );
1317
1318     answer->i_proto   = HTTPD_PROTO_RTSP;
1319     answer->i_version = query->i_version;
1320     answer->i_type    = HTTPD_MSG_ANSWER;
1321     answer->i_body    = 0;
1322     answer->p_body      = NULL;
1323
1324     switch( query->i_type )
1325     {
1326         case HTTPD_MSG_SETUP:
1327             psz_playnow = httpd_MsgGet( query, "x-playNow" );
1328             psz_transport = httpd_MsgGet( query, "Transport" );
1329
1330             msg_Dbg( p_vod, "HTTPD_MSG_SETUP: transport=%s", psz_transport );
1331
1332             if( strstr( psz_transport, "unicast" ) &&
1333                 strstr( psz_transport, "client_port=" ) )
1334             {
1335                 rtsp_client_t *p_rtsp = NULL;
1336                 rtsp_client_es_t *p_rtsp_es = NULL;
1337                 char ip[NI_MAXNUMERICHOST];
1338                 int i_port = atoi( strstr( psz_transport, "client_port=" ) +
1339                                    strlen("client_port=") );
1340
1341                 if( httpd_ClientIP( cl, ip ) == NULL )
1342                 {
1343                     answer->i_status = 500;
1344                     answer->i_body = 0;
1345                     answer->p_body = NULL;
1346                     break;
1347                 }
1348
1349                 msg_Dbg( p_vod, "HTTPD_MSG_SETUP: unicast ip=%s port=%d",
1350                         ip, i_port );
1351
1352                 psz_session = httpd_MsgGet( query, "Session" );
1353                 if( !psz_session || !*psz_session )
1354                 {
1355                     char *psz_new;
1356                     if( ( p_vod->p_sys->i_throttle_users > 0 ) &&
1357                         ( p_vod->p_sys->i_connections >= p_vod->p_sys->i_throttle_users ) )
1358                     {
1359                         answer->i_status = 503;
1360                         answer->i_body = 0;
1361                         answer->p_body = NULL;
1362                         break;
1363                     }
1364                     if( asprintf( &psz_new, "%d", rand() ) < 0 )
1365                         return VLC_ENOMEM;
1366                     psz_session = psz_new;
1367
1368                     p_rtsp = RtspClientNew( p_media, psz_new );
1369                 }
1370                 else
1371                 {
1372                     p_rtsp = RtspClientGet( p_media, psz_session );
1373                     if( !p_rtsp )
1374                     {
1375                         answer->i_status = 454;
1376                         answer->i_body = 0;
1377                         answer->p_body = NULL;
1378                         break;
1379                     }
1380                 }
1381
1382                 p_rtsp_es = malloc( sizeof(rtsp_client_es_t) );
1383                 if( !p_rtsp_es )
1384                 {
1385                     answer->i_status = 500;
1386                     answer->i_body = 0;
1387                     answer->p_body = NULL;
1388                     break;
1389                 }
1390                 p_rtsp_es->i_port = i_port;
1391                 p_rtsp_es->psz_ip = strdup( ip );
1392                 p_rtsp_es->p_media_es = p_es;
1393                 TAB_APPEND( p_rtsp->i_es, p_rtsp->es, p_rtsp_es );
1394
1395                 answer->i_status = 200;
1396                 answer->i_body = 0;
1397                 answer->p_body = NULL;
1398
1399                 if( p_media->b_raw )
1400                 {
1401                     if( strstr( psz_transport, "MP2T/H2221/UDP" ) )
1402                     {
1403                         httpd_MsgAdd( answer, "Transport",
1404                                      "MP2T/H2221/UDP;client_port=%d-%d",
1405                                      p_rtsp_es->i_port, p_rtsp_es->i_port + 1 );
1406                     }
1407                     else if( strstr( psz_transport, "RAW/RAW/UDP" ) )
1408                     {
1409                         httpd_MsgAdd( answer, "Transport",
1410                                      "RAW/RAW/UDP;client_port=%d-%d",
1411                                      p_rtsp_es->i_port, p_rtsp_es->i_port + 1 );
1412                     }
1413                 }
1414                 else
1415                 {
1416                     httpd_MsgAdd( answer, "Transport",
1417                                   "RTP/AVP/UDP;client_port=%d-%d",
1418                                   p_rtsp_es->i_port, p_rtsp_es->i_port + 1 );
1419                 }
1420             }
1421             else /* TODO  strstr( psz_transport, "interleaved" ) ) */
1422             {
1423                 answer->i_status = 461;
1424                 answer->i_body = 0;
1425                 answer->p_body = NULL;
1426             }
1427
1428             /* Intentional fall-through on x-playNow option in RTSP request */
1429             if( !psz_playnow )
1430                 break;
1431
1432         case HTTPD_MSG_PLAY:
1433             /* This is kind of a kludge. Should we only support Aggregate
1434              * Operations ? */
1435             psz_session = httpd_MsgGet( query, "Session" );
1436             msg_Dbg( p_vod, "HTTPD_MSG_PLAY for session: %s", psz_session );
1437
1438             p_rtsp = RtspClientGet( p_media, psz_session );
1439
1440             psz_position = httpd_MsgGet( query, "Range" );
1441             if( psz_position ) psz_position = strstr( psz_position, "npt=" );
1442             if( psz_position )
1443             {
1444                 double f_pos = ParseNPT (psz_position + 4);
1445                 msg_Dbg( p_vod, "seeking request: %s", psz_position );
1446                 f_pos /= ((double)(p_media->i_length))/1000 /1000 / 100;
1447                 CommandPush( p_vod, RTSP_CMD_TYPE_SEEK, p_media,
1448                              psz_session, f_pos, NULL );
1449             }
1450
1451             if( !psz_playnow )
1452             {
1453                 answer->i_status = 200;
1454                 answer->i_body = 0;
1455                 answer->p_body = NULL;
1456             }
1457             break;
1458
1459         case HTTPD_MSG_TEARDOWN:
1460             answer->i_status = 200;
1461             answer->i_body = 0;
1462             answer->p_body = NULL;
1463
1464             psz_session = httpd_MsgGet( query, "Session" );
1465             msg_Dbg( p_vod, "HTTPD_MSG_TEARDOWN for session: %s", psz_session);
1466
1467             p_rtsp = RtspClientGet( p_media, psz_session );
1468             if( !p_rtsp ) break;
1469
1470             for( i = 0; i < p_rtsp->i_es; i++ )
1471             {
1472                 if( p_rtsp->es[i]->p_media_es == p_es )
1473                 {
1474                     free( p_rtsp->es[i]->psz_ip );
1475                     TAB_REMOVE( p_rtsp->i_es, p_rtsp->es, p_rtsp->es[i] );
1476                     break;
1477                 }
1478             }
1479
1480             if( !p_rtsp->i_es )
1481             {
1482                 CommandPush( p_vod, RTSP_CMD_TYPE_STOP, p_media, psz_session,
1483                              0, NULL );
1484                 RtspClientDel( p_media, p_rtsp );
1485             }
1486             break;
1487
1488         case HTTPD_MSG_PAUSE:
1489             /* This is kind of a kludge. Should we only support Aggregate
1490              * Operations ? */
1491             psz_session = httpd_MsgGet( query, "Session" );
1492             msg_Dbg( p_vod, "HTTPD_MSG_PAUSE for session: %s", psz_session );
1493
1494             p_rtsp = RtspClientGet( p_media, psz_session );
1495             if( !p_rtsp ) break;
1496
1497             CommandPush( p_vod, RTSP_CMD_TYPE_PAUSE, p_media, psz_session,
1498                          0, NULL );
1499             p_rtsp->b_paused = true;
1500
1501             answer->i_status = 200;
1502             answer->i_body = 0;
1503             answer->p_body = NULL;
1504             break;
1505
1506         default:
1507             return VLC_EGENERIC;
1508             break;
1509     }
1510
1511     httpd_MsgAdd( answer, "Server", "VLC Server" );
1512     httpd_MsgAdd( answer, "Content-Length", "%d", answer->i_body );
1513     psz_cseq = httpd_MsgGet( query, "Cseq" );
1514     if (psz_cseq)
1515         i_cseq = atoi( psz_cseq );
1516     else
1517         i_cseq = 0;
1518     httpd_MsgAdd( answer, "Cseq", "%d", i_cseq );
1519     httpd_MsgAdd( answer, "Cache-Control", "%s", "no-cache" );
1520
1521     if( psz_session )
1522         httpd_MsgAdd( answer, "Session", "%s"/*;timeout=5*/, psz_session );
1523
1524     return VLC_SUCCESS;
1525 }
1526
1527 /*****************************************************************************
1528  * SDPGenerate: TODO
1529  * FIXME: need to be moved to a common place ?
1530  *****************************************************************************/
1531 static char *SDPGenerate( const vod_media_t *p_media, httpd_client_t *cl )
1532 {
1533     int i, i_size;
1534     char *p, *psz_sdp, ip[NI_MAXNUMERICHOST], ipv;
1535     const char *psz_control;
1536
1537     if( httpd_ServerIP( cl, ip ) == NULL )
1538         return NULL;
1539
1540     p = strchr( ip, '%' );
1541     if( p != NULL )
1542         *p = '\0'; /* remove scope if present */
1543
1544     ipv = ( strchr( ip, ':' ) != NULL ) ? '6' : '4';
1545
1546     /* Calculate size */
1547     i_size = sizeof( "v=0\r\n" ) +
1548         sizeof( "o=- * * IN IP4 \r\n" ) + 10 + NI_MAXNUMERICHOST +
1549         sizeof( "s=*\r\n" ) + strlen( p_media->psz_session_name ) +
1550         sizeof( "i=*\r\n" ) + strlen( p_media->psz_session_description ) +
1551         sizeof( "u=*\r\n" ) + strlen( p_media->psz_session_url ) +
1552         sizeof( "e=*\r\n" ) + strlen( p_media->psz_session_email ) +
1553         sizeof( "c=IN IP4 0.0.0.0\r\n" ) + 20 + 10 +
1554         sizeof( "t=0 0\r\n" ) + /* FIXME */
1555         sizeof( "a=tool:"PACKAGE_STRING"\r\n" ) +
1556         sizeof( "a=range:npt=0-1000000000.000\r\n" );
1557
1558     psz_control = (ipv == '6') ? p_media->psz_rtsp_control_v6
1559                                : p_media->psz_rtsp_control_v4;
1560     for( i = 0; i < p_media->i_es; i++ )
1561     {
1562         media_es_t *p_es = p_media->es[i];
1563
1564         i_size += sizeof( "m=**d*o * RTP/AVP *\r\n" ) + 19;
1565         if( p_es->psz_rtpmap )
1566         {
1567             i_size += sizeof( "a=rtpmap:* *\r\n" ) +
1568                 strlen( p_es->psz_rtpmap ) + 9;
1569         }
1570         if( p_es->psz_fmtp )
1571         {
1572             i_size += sizeof( "a=fmtp:* *\r\n" ) +
1573                 strlen( p_es->psz_fmtp ) + 9;
1574         }
1575     }
1576     i_size += (strlen( psz_control ) + strlen( ip ) + 9) * p_media->i_es;
1577
1578     p = psz_sdp = malloc( i_size );
1579     p += sprintf( p, "v=0\r\n" );
1580     p += sprintf( p, "o=- %"PRId64" %d IN IP%c %s\r\n",
1581                   p_media->i_sdp_id, p_media->i_sdp_version, ipv, ip );
1582     if( *p_media->psz_session_name )
1583         p += sprintf( p, "s=%s\r\n", p_media->psz_session_name );
1584     if( *p_media->psz_session_description )
1585         p += sprintf( p, "i=%s\r\n", p_media->psz_session_description );
1586     if( *p_media->psz_session_url )
1587         p += sprintf( p, "u=%s\r\n", p_media->psz_session_url );
1588     if( *p_media->psz_session_email )
1589         p += sprintf( p, "e=%s\r\n", p_media->psz_session_email );
1590
1591     p += sprintf( p, "c=IN IP%c %s\r\n", ipv, ipv == '6' ? "::" : "0.0.0.0" );
1592     p += sprintf( p, "t=0 0\r\n" ); /* FIXME */
1593     p += sprintf( p, "a=tool:"PACKAGE_STRING"\r\n" );
1594
1595     if( p_media->i_length > 0 )
1596     {
1597         lldiv_t d = lldiv( p_media->i_length / 1000, 1000 );
1598         p += sprintf( p, "a=range:npt=0-%lld.%03u\r\n", d.quot,
1599                       (unsigned)d.rem );
1600     }
1601
1602     for( i = 0; i < p_media->i_es; i++ )
1603     {
1604         media_es_t *p_es = p_media->es[i];
1605
1606         if( p_es->fmt.i_cat == AUDIO_ES )
1607         {
1608             p += sprintf( p, "m=audio %d RTP/AVP %d\r\n",
1609                           p_es->i_port, p_es->i_payload_type );
1610         }
1611         else if( p_es->fmt.i_cat == VIDEO_ES )
1612         {
1613             p += sprintf( p, "m=video %d RTP/AVP %d\r\n",
1614                           p_es->i_port, p_es->i_payload_type );
1615         }
1616         else
1617         {
1618             continue;
1619         }
1620
1621         if( p_es->psz_rtpmap )
1622         {
1623             p += sprintf( p, "a=rtpmap:%d %s\r\n", p_es->i_payload_type,
1624                           p_es->psz_rtpmap );
1625         }
1626         if( p_es->psz_fmtp )
1627         {
1628             p += sprintf( p, "a=fmtp:%d %s\r\n", p_es->i_payload_type,
1629                           p_es->psz_fmtp );
1630         }
1631
1632         p += sprintf( p, psz_control, ip, i );
1633     }
1634
1635     return psz_sdp;
1636 }