[vlc-devel] [PATCHv3] Merge VoD module into RTP stream output module

Pierre Ynard linkfanel at yahoo.fr
Tue Nov 2 20:15:58 CET 2010


Hello,

This patch is aimed at bringing a cleaner and better working solution
for VoD than the bad hack that we currently have. Instead of (badly)
duplicating large portions of codes, it reuses the existing RTSP stack
and factorizes the RTP payload code, and provides support for a number
of ES not limited to one audio and one video tracks, as well as for
missing features in RTSP headers.

The VoD module is responsible for starting and running the RTSP stack.
When a VoD media instance is started, the VLM passes down media and
session parameters as variables to the RTP stream output object, that
uses them to hook back into the VoD code (and through it, into the RTSP
stack), to retrieve accurate, already set up parameters, and provide
playback data.

It works provided that input options and variables are passed down to
input resources (which is currently broken).


diff --git a/modules/stream_out/Modules.am b/modules/stream_out/Modules.am
index 1bfafeb..8c757ba 100644
--- a/modules/stream_out/Modules.am
+++ b/modules/stream_out/Modules.am
@@ -34,7 +34,7 @@ libvlc_LTLIBRARIES += \
 libvlc_LTLIBRARIES += \
 	libstream_out_rtp_plugin.la
 libstream_out_rtp_plugin_la_SOURCES = \
-	rtp.c rtp.h rtpfmt.c rtcp.c rtsp.c
+	rtp.c rtp.h rtpfmt.c rtcp.c rtsp.c vod.c
 libstream_out_rtp_plugin_la_CFLAGS = $(AM_CFLAGS)
 libstream_out_rtp_plugin_la_LIBADD = $(AM_LIBADD)
 libstream_out_rtp_plugin_la_DEPENDENCIES =
diff --git a/modules/stream_out/rtp.c b/modules/stream_out/rtp.c
index 623b0df..a2095ed 100644
--- a/modules/stream_out/rtp.c
+++ b/modules/stream_out/rtp.c
@@ -1,10 +1,11 @@
 /*****************************************************************************
  * rtp.c: rtp stream output module
  *****************************************************************************
- * Copyright (C) 2003-2004 the VideoLAN team
+ * Copyright (C) 2003-2004, 2010 the VideoLAN team
  * Copyright © 2007-2008 Rémi Denis-Courmont
  *
  * Authors: Laurent Aimar <fenrir at via.ecp.fr>
+ *          Pierre Ynard
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -38,7 +39,6 @@
 #include <vlc_url.h>
 #include <vlc_network.h>
 #include <vlc_fs.h>
-#include <vlc_strings.h>
 #include <vlc_rand.h>
 #ifdef HAVE_SRTP
 # include <srtp.h>
@@ -160,6 +160,13 @@ static const char *const ppsz_protocols[] = {
 #define RFC3016_LONGTEXT N_( \
     "This allows you to stream MPEG4 LATM audio streams (see RFC3016)." )
 
+#define RTSP_HOST_TEXT N_( "RTSP host address" )
+#define RTSP_HOST_LONGTEXT N_( \
+    "This defines the address, port and path the RTSP VOD server will listen " \
+    "on.\nSyntax is address:port/path. The default is to listen on all "\
+    "interfaces (address 0.0.0.0), on port 554, with no path.\nTo listen " \
+    "only on the local interface, use \"localhost\" as address." )
+
 static int  Open ( vlc_object_t * );
 static void Close( vlc_object_t * );
 
@@ -170,7 +177,7 @@ vlc_module_begin ()
     set_shortname( N_("RTP"))
     set_description( N_("RTP stream output") )
     set_capability( "sout stream", 0 )
-    add_shortcut( "rtp" )
+    add_shortcut( "rtp", "vod" )
     set_category( CAT_SOUT )
     set_subcategory( SUBCAT_SOUT_STREAM )
 
@@ -222,6 +229,18 @@ vlc_module_begin ()
                  RFC3016_LONGTEXT, false )
 
     set_callbacks( Open, Close )
+
+    add_submodule ()
+    set_shortname( N_("RTSP VoD" ) )
+    set_description( N_("RTSP VoD server") )
+    set_category( CAT_SOUT )
+    set_subcategory( SUBCAT_SOUT_VOD )
+    set_capability( "vod server", 0 )
+    set_callbacks( OpenVoD, CloseVoD )
+    add_shortcut( "rtsp" )
+    add_string ( "rtsp-host", NULL, RTSP_HOST_TEXT,
+                 RTSP_HOST_LONGTEXT, true )
+
 vlc_module_end ()
 
 /*****************************************************************************
@@ -253,6 +272,9 @@ static int SapSetup( sout_stream_t *p_stream );
 static int FileSetup( sout_stream_t *p_stream );
 static int HttpSetup( sout_stream_t *p_stream, const vlc_url_t * );
 
+static int64_t rtp_init_ts( const vod_media_t *p_media,
+                            const char *psz_vod_session );
+
 struct sout_stream_sys_t
 {
     /* SDP */
@@ -289,6 +311,10 @@ struct sout_stream_sys_t
     int       i_ttl:9;
     bool      b_latm;
 
+    /* VoD */
+    vod_media_t *p_vod_media;
+    char     *psz_vod_session;
+
     /* in case we do TS/PS over rtp */
     sout_mux_t        *p_mux;
     sout_access_out_t *p_grab;
@@ -313,7 +339,6 @@ struct sout_stream_id_t
     sout_stream_t *p_stream;
     /* rtp field */
     uint16_t    i_sequence;
-    uint8_t     i_payload_type;
     bool        b_ts_init;
     uint32_t    i_ts_offset;
     uint8_t     ssrc[4];
@@ -322,13 +347,8 @@ struct sout_stream_id_t
     uint16_t    i_seq_sent_next;
 
     /* for sdp */
-    const char  *psz_enc;
-    char        *psz_fmtp;
-    int          i_clock_rate;
+    rtp_format_t rtp_fmt;
     int          i_port;
-    int          i_cat;
-    int          i_channels;
-    int          i_bitrate;
 
     /* Packetizer specific fields */
     int                 i_mtu;
@@ -444,7 +464,33 @@ static int Open( vlc_object_t *p_this )
     free (psz);
     var_Create (p_this, "dccp-service", VLC_VAR_STRING);
 
-    if( ( p_sys->psz_destination == NULL ) && !b_rtsp )
+    p_sys->p_vod_media = NULL;
+    p_sys->psz_vod_session = NULL;
+
+    if (! strcmp(p_stream->psz_name, "vod"))
+    {
+        /* The VLM stops all instances before deleting a media, so this
+         * reference will remain valid during the lifetime of the rtp
+         * stream output. */
+        p_sys->p_vod_media = var_InheritAddress(p_stream, "vod-media");
+
+        if (p_sys->p_vod_media != NULL)
+        {
+            p_sys->psz_vod_session = var_InheritString(p_stream, "vod-session");
+            if (p_sys->psz_vod_session == NULL)
+            {
+                msg_Err(p_stream, "missing VoD session");
+                free(p_sys);
+                return VLC_EGENERIC;
+            }
+
+            const char *mux = vod_get_mux(p_sys->p_vod_media);
+            var_SetString(p_stream, SOUT_CFG_PREFIX "mux", mux);
+        }
+    }
+
+    if( p_sys->psz_destination == NULL && !b_rtsp
+        && p_sys->p_vod_media == NULL )
     {
         msg_Err( p_stream, "missing destination and not in RTSP mode" );
         free( p_sys );
@@ -465,12 +511,12 @@ static int Open( vlc_object_t *p_this )
 
     /* NPT=0 time will be determined when we packetize the first packet
      * (of any ES). But we want to be able to report rtptime in RTSP
-     * without waiting. So until then, we use an arbitrary reference
-     * PTS for timestamp computations, and then actual PTS will catch
-     * up using offsets. */
+     * without waiting (and already did in the VoD case). So until then,
+     * we use an arbitrary reference PTS for timestamp computations, and
+     * then actual PTS will catch up using offsets. */
     p_sys->i_npt_zero = VLC_TS_INVALID;
-    p_sys->i_pts_zero = mdate(); /* arbitrary value, could probably be
-                                  * random */
+    p_sys->i_pts_zero = rtp_init_ts(p_sys->p_vod_media,
+                                    p_sys->psz_vod_session); 
     p_sys->i_es = 0;
     p_sys->es   = NULL;
     p_sys->rtsp = NULL;
@@ -503,6 +549,7 @@ static int Open( vlc_object_t *p_this )
             free( psz );
             vlc_mutex_destroy( &p_sys->lock_sdp );
             vlc_mutex_destroy( &p_sys->lock_es );
+            free( p_sys->psz_vod_session );
             free( p_sys->psz_destination );
             free( p_sys );
             return VLC_EGENERIC;
@@ -518,6 +565,7 @@ static int Open( vlc_object_t *p_this )
             sout_AccessOutDelete( p_sys->p_grab );
             vlc_mutex_destroy( &p_sys->lock_sdp );
             vlc_mutex_destroy( &p_sys->lock_es );
+            free( p_sys->psz_vod_session );
             free( p_sys->psz_destination );
             free( p_sys );
             return VLC_EGENERIC;
@@ -530,6 +578,7 @@ static int Open( vlc_object_t *p_this )
             sout_AccessOutDelete( p_sys->p_grab );
             vlc_mutex_destroy( &p_sys->lock_sdp );
             vlc_mutex_destroy( &p_sys->lock_es );
+            free( p_sys->psz_vod_session );
             free( p_sys->psz_destination );
             free( p_sys );
             return VLC_EGENERIC;
@@ -636,6 +685,7 @@ static void Close( vlc_object_t * p_this )
 #endif
         free( p_sys->psz_sdp_file );
     }
+    free( p_sys->psz_vod_session );
     free( p_sys->psz_destination );
     free( p_sys );
 }
@@ -671,16 +721,17 @@ static void SDPHandleUrl( sout_stream_t *p_stream, const char *psz_url )
         }
 
         /* FIXME test if destination is multicast or no destination at all */
-        p_sys->rtsp = RtspSetup( p_stream, &url );
+        p_sys->rtsp = RtspSetup( VLC_OBJECT(p_stream), NULL, &url );
         if( p_sys->rtsp == NULL )
             msg_Err( p_stream, "cannot export SDP as RTSP" );
         else
         if( p_sys->p_mux != NULL )
         {
             sout_stream_id_t *id = p_sys->es[0];
-            rtsp_stream_id_t *rtsp_id = RtspAddId( p_sys->rtsp, id, GetDWBE( id->ssrc ),
-                                                   p_sys->psz_destination, p_sys->i_ttl,
-                                                   id->i_port, id->i_port + 1 );
+            rtsp_stream_id_t *rtsp_id = RtspAddId( p_sys->rtsp, id,
+                                GetDWBE( id->ssrc ), id->rtp_fmt.clock_rate,
+                                p_sys->psz_destination, p_sys->i_ttl,
+                                id->i_port, id->i_port + 1 );
             vlc_mutex_lock( &p_sys->lock_es );
             id->rtsp_id = rtsp_id;
             vlc_mutex_unlock( &p_sys->lock_es );
@@ -810,9 +861,10 @@ char *SDPGenerate( sout_stream_t *p_stream, const char *rtsp_url )
     for( i = 0; i < p_sys->i_es; i++ )
     {
         sout_stream_id_t *id = p_sys->es[i];
+        rtp_format_t *rtp_fmt = &id->rtp_fmt;
         const char *mime_major; /* major MIME type */
 
-        switch( id->i_cat )
+        switch( rtp_fmt->cat )
         {
             case VIDEO_ES:
                 mime_major = "video";
@@ -828,9 +880,9 @@ char *SDPGenerate( sout_stream_t *p_stream, const char *rtsp_url )
         }
 
         sdp_AddMedia( &psz_sdp, mime_major, proto, inclport * id->i_port,
-                      id->i_payload_type, false, id->i_bitrate,
-                      id->psz_enc, id->i_clock_rate, id->i_channels,
-                      id->psz_fmtp);
+                      rtp_fmt->payload_type, false, rtp_fmt->bitrate,
+                      rtp_fmt->ptname, rtp_fmt->clock_rate, rtp_fmt->channels,
+                      rtp_fmt->fmtp);
 
         /* cf RFC4566 §5.14 */
         if( inclport && !p_sys->rtcp_mux && (id->i_port & 1) )
@@ -863,18 +915,6 @@ out:
  * RTP mux
  *****************************************************************************/
 
-static void sprintf_hexa( char *s, uint8_t *p_data, int i_data )
-{
-    static const char hex[16] = "0123456789abcdef";
-
-    for( int i = 0; i < i_data; i++ )
-    {
-        s[2*i+0] = hex[(p_data[i]>>4)&0xf];
-        s[2*i+1] = hex[(p_data[i]   )&0xf];
-    }
-    s[2*i_data] = '\0';
-}
-
 /**
  * Shrink the MTU down to a fixed packetization time (for audio).
  */
@@ -882,8 +922,8 @@ static void
 rtp_set_ptime (sout_stream_id_t *id, unsigned ptime_ms, size_t bytes)
 {
     /* Samples per second */
-    size_t spl = (id->i_clock_rate - 1) * ptime_ms / 1000 + 1;
-    bytes *= id->i_channels;
+    size_t spl = (id->rtp_fmt.clock_rate - 1) * ptime_ms / 1000 + 1;
+    bytes *= id->rtp_fmt.channels;
     spl *= bytes;
 
     if (spl < rtp_mtu (id)) /* MTU is big enough for ptime */
@@ -892,11 +932,11 @@ rtp_set_ptime (sout_stream_id_t *id, unsigned ptime_ms, size_t bytes)
         id->i_mtu = 12 + (((id->i_mtu - 12) / bytes) * bytes);
 }
 
-uint32_t rtp_compute_ts( const sout_stream_id_t *id, int64_t i_pts )
+uint32_t rtp_compute_ts( unsigned i_clock_rate, int64_t i_pts )
 {
     /* NOTE: this plays nice with offsets because the calculations are
      * linear. */
-    return i_pts * (int64_t)id->i_clock_rate / CLOCK_FREQ;
+    return i_pts * (int64_t)i_clock_rate / CLOCK_FREQ;
 }
 
 /** Add an ES as a new RTP stream */
@@ -912,34 +952,6 @@ static sout_stream_id_t *Add( sout_stream_t *p_stream, es_format_t *p_fmt )
         return NULL;
     id->p_stream   = p_stream;
 
-    /* Dynamic payload type. Payload types are scoped to the RTP
-     * session, and we put each ES in its own session, so no risk of
-     * conflict. */
-    id->i_payload_type = 96;
-
-    vlc_rand_bytes (&id->i_sequence, sizeof (id->i_sequence));
-    vlc_rand_bytes (id->ssrc, sizeof (id->ssrc));
-
-    id->psz_enc    = NULL;
-    id->psz_fmtp   = NULL;
-    id->i_clock_rate = 90000; /* most common case for video */
-    id->i_channels = 0;
-    if( p_fmt != NULL )
-    {
-        id->i_cat  = p_fmt->i_cat;
-        if( p_fmt->i_cat == AUDIO_ES )
-        {
-            id->i_clock_rate = p_fmt->audio.i_rate;
-            id->i_channels = p_fmt->audio.i_channels;
-        }
-        id->i_bitrate = p_fmt->i_bitrate/1000; /* Stream bitrate in kbps */
-    }
-    else
-    {
-        id->i_cat  = VIDEO_ES;
-        id->i_bitrate = 0;
-    }
-
     id->i_mtu = var_InheritInteger( p_stream, "mtu" );
     if( id->i_mtu <= 12 + 16 )
         id->i_mtu = 576 - 20 - 8; /* pessimistic */
@@ -960,6 +972,41 @@ static sout_stream_id_t *Add( sout_stream_t *p_stream, es_format_t *p_fmt )
     id->i_caching =
         (int64_t)1000 * var_GetInteger( p_stream, SOUT_CFG_PREFIX "caching");
 
+    vlc_rand_bytes (&id->i_sequence, sizeof (id->i_sequence));
+    vlc_rand_bytes (id->ssrc, sizeof (id->ssrc));
+
+    id->rtp_fmt.ptname = NULL;
+
+    if (p_sys->p_vod_media != NULL)
+    {
+        uint32_t ssrc;
+        int val = vod_init_id(p_sys->p_vod_media, p_sys->psz_vod_session,
+                              p_fmt ? p_fmt->i_id : 0, id, &id->rtp_fmt,
+                              &ssrc, &id->i_seq_sent_next);
+        if (val == VLC_SUCCESS)
+        {
+            memcpy(id->ssrc, &ssrc, sizeof(id->ssrc));
+            /* This is ugly, but id->i_seq_sent_next needs to be
+             * initialized inside vod_init_id() to avoid race
+             * conditions. */
+            id->i_sequence = id->i_seq_sent_next;
+        }
+
+        /* vod_init_id() may fail either because the ES wasn't found in
+         * the VoD media, or because that track wasn't SETUP. In the
+         * former case, id->rtp_fmt was left untouched, so we fill it
+         * in right after this. */
+    }
+
+    if (id->rtp_fmt.ptname == NULL)
+    {
+        char *psz = var_GetNonEmptyString( p_stream, SOUT_CFG_PREFIX "mux" );
+        if (p_fmt == NULL && psz == NULL)
+            goto error;
+        rtp_get_fmt(VLC_OBJECT(p_stream), p_fmt, psz, &id->rtp_fmt);
+        free( psz );
+    }
+
 #ifdef HAVE_SRTP
     char *key = var_CreateGetNonEmptyString (p_stream, SOUT_CFG_PREFIX"key");
     if (key)
@@ -1030,7 +1077,7 @@ static sout_stream_id_t *Add( sout_stream_t *p_stream, es_format_t *p_fmt )
             case IPPROTO_DCCP:
             {
                 const char *code;
-                switch (id->i_cat)
+                switch (id->rtp_fmt.cat)
                 {
                     case VIDEO_ES: code = "RTPV";     break;
                     case AUDIO_ES: code = "RTPARTPV"; break;
@@ -1078,92 +1125,42 @@ static sout_stream_id_t *Add( sout_stream_t *p_stream, es_format_t *p_fmt )
         }
     }
 
-    if( p_fmt == NULL )
-    {
-        char *psz = var_GetNonEmptyString( p_stream, SOUT_CFG_PREFIX "mux" );
-
-        if( psz == NULL ) /* Uho! */
-            ;
-        else
-        if( strncmp( psz, "ts", 2 ) == 0 )
-        {
-            id->i_payload_type = 33;
-            id->psz_enc = "MP2T";
-        }
-        else
-        {
-            id->psz_enc = "MP2P";
-        }
-        free( psz );
-    }
-    else
+    if( p_fmt != NULL )
     switch( p_fmt->i_codec )
     {
         case VLC_CODEC_MULAW:
-            if( p_fmt->audio.i_channels == 1 && p_fmt->audio.i_rate == 8000 )
-                id->i_payload_type = 0;
-            id->psz_enc = "PCMU";
-            id->pf_packetize = rtp_packetize_split;
-            rtp_set_ptime (id, 20, 1);
-            break;
         case VLC_CODEC_ALAW:
-            if( p_fmt->audio.i_channels == 1 && p_fmt->audio.i_rate == 8000 )
-                id->i_payload_type = 8;
-            id->psz_enc = "PCMA";
+        case VLC_CODEC_U8:
             id->pf_packetize = rtp_packetize_split;
             rtp_set_ptime (id, 20, 1);
             break;
         case VLC_CODEC_S16B:
         case VLC_CODEC_S16L:
-            if( p_fmt->audio.i_channels == 1 && p_fmt->audio.i_rate == 44100 )
-            {
-                id->i_payload_type = 11;
-            }
-            else if( p_fmt->audio.i_channels == 2 &&
-                     p_fmt->audio.i_rate == 44100 )
-            {
-                id->i_payload_type = 10;
-            }
-            id->psz_enc = "L16";
             if( p_fmt->i_codec == VLC_CODEC_S16B )
                 id->pf_packetize = rtp_packetize_split;
             else
                 id->pf_packetize = rtp_packetize_swab;
             rtp_set_ptime (id, 20, 2);
             break;
-        case VLC_CODEC_U8:
-            id->psz_enc = "L8";
-            id->pf_packetize = rtp_packetize_split;
-            rtp_set_ptime (id, 20, 1);
-            break;
         case VLC_CODEC_MPGA:
-            id->i_payload_type = 14;
-            id->psz_enc = "MPA";
-            id->i_clock_rate = 90000; /* not 44100 */
             id->pf_packetize = rtp_packetize_mpa;
             break;
         case VLC_CODEC_MPGV:
-            id->i_payload_type = 32;
-            id->psz_enc = "MPV";
             id->pf_packetize = rtp_packetize_mpv;
             break;
         case VLC_CODEC_ADPCM_G726:
             switch( p_fmt->i_bitrate / 1000 )
             {
             case 16:
-                id->psz_enc = "G726-16";
                 id->pf_packetize = rtp_packetize_g726_16;
                 break;
             case 24:
-                id->psz_enc = "G726-24";
                 id->pf_packetize = rtp_packetize_g726_24;
                 break;
             case 32:
-                id->psz_enc = "G726-32";
                 id->pf_packetize = rtp_packetize_g726_32;
                 break;
             case 40:
-                id->psz_enc = "G726-40";
                 id->pf_packetize = rtp_packetize_g726_40;
                 break;
             default:
@@ -1173,175 +1170,33 @@ static sout_stream_id_t *Add( sout_stream_t *p_stream, es_format_t *p_fmt )
             }
             break;
         case VLC_CODEC_A52:
-            id->psz_enc = "ac3";
             id->pf_packetize = rtp_packetize_ac3;
             break;
         case VLC_CODEC_H263:
-            id->psz_enc = "H263-1998";
             id->pf_packetize = rtp_packetize_h263;
             break;
         case VLC_CODEC_H264:
-            id->psz_enc = "H264";
             id->pf_packetize = rtp_packetize_h264;
-            id->psz_fmtp = NULL;
-
-            if( p_fmt->i_extra > 0 )
-            {
-                uint8_t *p_buffer = p_fmt->p_extra;
-                int     i_buffer = p_fmt->i_extra;
-                char    *p_64_sps = NULL;
-                char    *p_64_pps = NULL;
-                char    hexa[6+1];
-
-                while( i_buffer > 4 )
-                {
-                    int i_offset    = 0;
-                    int i_size      = 0;
-
-                    while( p_buffer[0] != 0 || p_buffer[1] != 0 ||
-                           p_buffer[2] != 1 )
-                    {
-                        p_buffer++;
-                        i_buffer--;
-                        if( i_buffer == 0 ) break;
-                    }
-
-                    if( i_buffer < 4 || memcmp(p_buffer, "\x00\x00\x01", 3 ) )
-                    {
-                        msg_Dbg( p_stream, "No startcode found..");
-                        break;
-                    }
-                    p_buffer += 3;
-                    i_buffer -= 3;
-
-                    const int i_nal_type = p_buffer[0]&0x1f;
-
-                    msg_Dbg( p_stream, "we found a startcode for NAL with TYPE:%d", i_nal_type );
-
-                    i_size = i_buffer;
-                    for( i_offset = 0; i_offset+2 < i_buffer ; i_offset++)
-                    {
-                        if( !memcmp(p_buffer + i_offset, "\x00\x00\x01", 3 ) )
-                        {
-                            /* we found another startcode */
-                            while( i_offset > 0 && 0 == p_buffer[ i_offset - 1 ] )
-                                i_offset--;
-                            i_size = i_offset;
-                            break;
-                        }
-                    }
-
-                    if( i_size == 0 )
-                    {
-                        msg_Dbg( p_stream, "No-info found in nal ");
-                        continue;
-                    }
-
-                    if( i_nal_type == 7 )
-                    {
-                        free( p_64_sps );
-                        p_64_sps = vlc_b64_encode_binary( p_buffer, i_size );
-                        /* XXX: nothing ensures that i_size >= 4 ?? */
-                        sprintf_hexa( hexa, &p_buffer[1], 3 );
-                    }
-                    else if( i_nal_type == 8 )
-                    {
-                        free( p_64_pps );
-                        p_64_pps = vlc_b64_encode_binary( p_buffer, i_size );
-                    }
-                    i_buffer -= i_size;
-                    p_buffer += i_size;
-                }
-                /* */
-                if( p_64_sps && p_64_pps &&
-                    ( asprintf( &id->psz_fmtp,
-                                "packetization-mode=1;profile-level-id=%s;"
-                                "sprop-parameter-sets=%s,%s;", hexa, p_64_sps,
-                                p_64_pps ) == -1 ) )
-                    id->psz_fmtp = NULL;
-                free( p_64_sps );
-                free( p_64_pps );
-            }
-            if( !id->psz_fmtp )
-                id->psz_fmtp = strdup( "packetization-mode=1" );
             break;
-
         case VLC_CODEC_MP4V:
-        {
-            id->psz_enc = "MP4V-ES";
             id->pf_packetize = rtp_packetize_split;
-            if( p_fmt->i_extra > 0 )
-            {
-                char hexa[2*p_fmt->i_extra +1];
-                sprintf_hexa( hexa, p_fmt->p_extra, p_fmt->i_extra );
-                if( asprintf( &id->psz_fmtp,
-                              "profile-level-id=3; config=%s;", hexa ) == -1 )
-                    id->psz_fmtp = NULL;
-            }
             break;
-        }
         case VLC_CODEC_MP4A:
         {
             if(!p_sys->b_latm)
-            {
-                char hexa[2*p_fmt->i_extra +1];
-
-                id->psz_enc = "mpeg4-generic";
                 id->pf_packetize = rtp_packetize_mp4a;
-                sprintf_hexa( hexa, p_fmt->p_extra, p_fmt->i_extra );
-                if( asprintf( &id->psz_fmtp,
-                              "streamtype=5; profile-level-id=15; "
-                              "mode=AAC-hbr; config=%s; SizeLength=13; "
-                              "IndexLength=3; IndexDeltaLength=3; Profile=1;",
-                              hexa ) == -1 )
-                    id->psz_fmtp = NULL;
-            }
             else
-            {
-                char hexa[13];
-                int i;
-                unsigned char config[6];
-                unsigned int aacsrates[15] = {
-                    96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
-                    16000, 12000, 11025, 8000, 7350, 0, 0 };
-
-                for( i = 0; i < 15; i++ )
-                    if( p_fmt->audio.i_rate == aacsrates[i] )
-                        break;
-
-                config[0]=0x40;
-                config[1]=0;
-                config[2]=0x20|i;
-                config[3]=p_fmt->audio.i_channels<<4;
-                config[4]=0x3f;
-                config[5]=0xc0;
-
-                id->psz_enc = "MP4A-LATM";
                 id->pf_packetize = rtp_packetize_mp4a_latm;
-                sprintf_hexa( hexa, config, 6 );
-                if( asprintf( &id->psz_fmtp, "profile-level-id=15; "
-                              "object=2; cpresent=0; config=%s", hexa ) == -1 )
-                    id->psz_fmtp = NULL;
-            }
             break;
         }
         case VLC_CODEC_AMR_NB:
-            id->psz_enc = "AMR";
-            id->psz_fmtp = strdup( "octet-align=1" );
-            id->pf_packetize = rtp_packetize_amr;
-            break;
         case VLC_CODEC_AMR_WB:
-            id->psz_enc = "AMR-WB";
-            id->psz_fmtp = strdup( "octet-align=1" );
             id->pf_packetize = rtp_packetize_amr;
             break;
         case VLC_CODEC_SPEEX:
-            id->psz_enc = "SPEEX";
             id->pf_packetize = rtp_packetize_spx;
             break;
         case VLC_CODEC_ITU_T140:
-            id->psz_enc = "t140" ;
-            id->i_clock_rate = 1000;
             id->pf_packetize = rtp_packetize_t140;
             break;
 
@@ -1363,11 +1218,13 @@ static sout_stream_id_t *Add( sout_stream_t *p_stream, es_format_t *p_fmt )
     id->b_ts_init = ( p_sys->i_npt_zero != VLC_TS_INVALID );
     vlc_mutex_unlock( &p_sys->lock_ts );
     if( id->b_ts_init )
-        id->i_ts_offset = rtp_compute_ts( id, p_sys->i_pts_offset );
+        id->i_ts_offset = rtp_compute_ts( id->rtp_fmt.clock_rate,
+                                          p_sys->i_pts_offset );
 
     if( p_sys->rtsp != NULL )
         id->rtsp_id = RtspAddId( p_sys->rtsp, id,
                                  GetDWBE( id->ssrc ),
+                                 id->rtp_fmt.clock_rate,
                                  p_sys->psz_destination,
                                  p_sys->i_ttl, id->i_port, id->i_port + 1 );
 
@@ -1421,8 +1278,10 @@ static int Del( sout_stream_t *p_stream, sout_stream_id_t *id )
         block_FifoRelease( id->p_fifo );
     }
 
-    free( id->psz_fmtp );
+    free( id->rtp_fmt.fmtp );
 
+    if (p_sys->p_vod_media != NULL)
+        vod_detach_id(p_sys->p_vod_media, p_sys->psz_vod_session, id);
     if( id->rtsp_id )
         RtspDelId( p_sys->rtsp, id->rtsp_id );
     if( id->listen.fd != NULL )
@@ -1749,10 +1608,41 @@ uint16_t rtp_get_seq( sout_stream_id_t *id )
     return seq;
 }
 
+/* Return an arbitrary initial timestamp for RTP timestamp computations.
+ * RFC 3550 states that the resulting initial RTP timestamps SHOULD be
+ * random (although we use the same reference for all the ES as a
+ * feature). In the VoD case, this function is called independently
+ * from several parts of the code, so we need to always return the same
+ * value. */
+static int64_t rtp_init_ts( const vod_media_t *p_media,
+                            const char *psz_vod_session )
+{
+    if (p_media == NULL || psz_vod_session == NULL)
+        return mdate();
+
+    uint64_t i_ts_init;
+    /* As per RFC 2326, session identifiers are at least 8 bytes long */
+    strncpy((char *)&i_ts_init, psz_vod_session, sizeof(uint64_t));
+    i_ts_init ^= (uint64_t) p_media;
+    /* Limit the timestamp to 48 bytes, this is enough and allows us
+     * to stay away from overflows */
+    i_ts_init &= 0xFFFFFFFFFFFF;
+    return i_ts_init;
+}
+
 /* Return a timestamp corresponding to packets being sent now, and that
- * can be passed to rtp_compute_ts() to get rtptime values for each ES. */
-int64_t rtp_get_ts( const sout_stream_t *p_stream )
+ * can be passed to rtp_compute_ts() to get rtptime values for each ES.
+ * If the stream output is not started, the initial timestamp that will
+ * be used with the first packets is returned instead. */
+int64_t rtp_get_ts( const sout_stream_t *p_stream, const sout_stream_id_t *id,
+                    const vod_media_t *p_media, const char *psz_vod_session )
 {
+    if (id != NULL)
+        p_stream = id->p_stream;
+
+    if (p_stream == NULL)
+        return rtp_init_ts(p_media, psz_vod_session);
+
     sout_stream_sys_t *p_sys = p_stream->p_sys;
     mtime_t i_npt_zero;
     vlc_mutex_lock( &p_sys->lock_ts );
@@ -1788,14 +1678,16 @@ void rtp_packetize_common( sout_stream_id_t *id, block_t *out,
 
         /* And in any case this is the first packet of this ES, so we
          * initialize the offset for this ES. */
-        id->i_ts_offset = rtp_compute_ts( id, p_sys->i_pts_offset );
+        id->i_ts_offset = rtp_compute_ts( id->rtp_fmt.clock_rate,
+                                          p_sys->i_pts_offset );
         id->b_ts_init = true;
     }
 
-    uint32_t i_timestamp = rtp_compute_ts( id, i_pts ) + id->i_ts_offset;
+    uint32_t i_timestamp = rtp_compute_ts( id->rtp_fmt.clock_rate, i_pts )
+                           + id->i_ts_offset;
 
     out->p_buffer[0] = 0x80;
-    out->p_buffer[1] = (b_marker?0x80:0x00)|id->i_payload_type;
+    out->p_buffer[1] = (b_marker?0x80:0x00)|id->rtp_fmt.payload_type;
     out->p_buffer[2] = ( id->i_sequence >> 8)&0xff;
     out->p_buffer[3] = ( id->i_sequence     )&0xff;
     out->p_buffer[4] = ( i_timestamp >> 24 )&0xff;
diff --git a/modules/stream_out/rtp.h b/modules/stream_out/rtp.h
index 01e7288..85b8b0b 100644
--- a/modules/stream_out/rtp.h
+++ b/modules/stream_out/rtp.h
@@ -25,24 +25,33 @@
 typedef struct rtsp_stream_t rtsp_stream_t;
 typedef struct rtsp_stream_id_t rtsp_stream_id_t;
 
-rtsp_stream_t *RtspSetup( sout_stream_t *p_stream, const vlc_url_t *url );
+rtsp_stream_t *RtspSetup( vlc_object_t *owner, vod_media_t *media,
+                          const vlc_url_t *url );
 void RtspUnsetup( rtsp_stream_t *rtsp );
 
 rtsp_stream_id_t *RtspAddId( rtsp_stream_t *rtsp, sout_stream_id_t *sid,
-                             uint32_t ssrc,
+                             uint32_t ssrc, unsigned clock_rate,
                              const char *dst, int ttl,
                              unsigned loport, unsigned hiport );
 void RtspDelId( rtsp_stream_t *rtsp, rtsp_stream_id_t * );
 
 char *RtspAppendTrackPath( rtsp_stream_id_t *id, const char *base );
 
+int RtspTrackAttach( rtsp_stream_t *rtsp, const char *name,
+                     rtsp_stream_id_t *id, sout_stream_id_t *sout_id,
+                     uint32_t *ssrc, uint16_t *seq_init );
+void RtspTrackDetach( rtsp_stream_t *rtsp, const char *name,
+                      sout_stream_id_t *sout_id);
+
 char *SDPGenerate( sout_stream_t *p_stream, const char *rtsp_url );
+char *SDPGenerateVoD( const vod_media_t *p_media, const char *rtsp_url );
 
-uint32_t rtp_compute_ts( const sout_stream_id_t *id, int64_t i_pts );
+uint32_t rtp_compute_ts( unsigned i_clock_rate, int64_t i_pts );
 int rtp_add_sink( sout_stream_id_t *id, int fd, bool rtcp_mux, uint16_t *seq );
 void rtp_del_sink( sout_stream_id_t *id, int fd );
 uint16_t rtp_get_seq( sout_stream_id_t *id );
-int64_t rtp_get_ts( const sout_stream_t *p_stream );
+int64_t rtp_get_ts( const sout_stream_t *p_stream, const sout_stream_id_t *id,
+                    const vod_media_t *p_media, const char *psz_vod_session );
 
 /* RTP packetization */
 void rtp_packetize_common (sout_stream_id_t *id, block_t *out,
@@ -73,3 +82,36 @@ rtcp_sender_t *OpenRTCP (vlc_object_t *obj, int rtp_fd, int proto,
                          bool mux);
 void CloseRTCP (rtcp_sender_t *rtcp);
 void SendRTCP (rtcp_sender_t *restrict rtcp, const block_t *rtp);
+
+typedef struct rtp_format_t
+{
+    /* Used for SDP and packetization */
+    uint8_t      payload_type;
+    unsigned     clock_rate;
+    unsigned     channels;
+    int          cat;
+    /* Used in SDP only */
+    unsigned     bitrate;
+    const char  *ptname;
+    char        *fmtp;
+} rtp_format_t;
+
+void rtp_get_fmt( vlc_object_t *obj, es_format_t *p_fmt, const char *mux,
+                  rtp_format_t *p_rtp_fmt );
+
+/* VoD */
+int  OpenVoD ( vlc_object_t * );
+void CloseVoD( vlc_object_t * );
+
+void vod_start(vod_media_t *p_media, const char *psz_session);
+void vod_toggle_pause(vod_media_t *p_media, const char *psz_session);
+void vod_stop(vod_media_t *p_media, const char *psz_session);
+void vod_seek(vod_media_t *p_media, const char *psz_session, float time);
+
+const char *vod_get_mux(const vod_media_t *p_media);
+int vod_init_id(vod_media_t *p_media, const char *psz_session, int es_id,
+                sout_stream_id_t *sout_id, rtp_format_t *rtp_fmt,
+                uint32_t *ssrc, uint16_t *seq_init);
+void vod_detach_id(vod_media_t *p_media, const char *psz_session,
+                   sout_stream_id_t *sout_id);
+
diff --git a/modules/stream_out/rtpfmt.c b/modules/stream_out/rtpfmt.c
index 482db7d..d633111 100644
--- a/modules/stream_out/rtpfmt.c
+++ b/modules/stream_out/rtpfmt.c
@@ -29,9 +29,286 @@
 #include <vlc_common.h>
 #include <vlc_sout.h>
 #include <vlc_block.h>
+#include <vlc_strings.h>
 
 #include "rtp.h"
 
+#include <assert.h>
+
+static void sprintf_hexa( char *s, uint8_t *p_data, int i_data )
+{
+    static const char hex[16] = "0123456789abcdef";
+
+    for( int i = 0; i < i_data; i++ )
+    {
+        s[2*i+0] = hex[(p_data[i]>>4)&0xf];
+        s[2*i+1] = hex[(p_data[i]   )&0xf];
+    }
+    s[2*i_data] = '\0';
+}
+
+void rtp_get_fmt( vlc_object_t *obj, es_format_t *p_fmt, const char *mux,
+                  rtp_format_t *rtp_fmt )
+{
+    assert( p_fmt != NULL || mux != NULL );
+
+    /* Dynamic payload type. Payload types are scoped to the RTP
+     * session, and we put each ES in its own session, so no risk of
+     * conflict. */
+    rtp_fmt->payload_type = 96;
+    rtp_fmt->ptname = NULL;
+    rtp_fmt->cat = mux != NULL ? VIDEO_ES : p_fmt->i_cat;
+    if( rtp_fmt->cat == AUDIO_ES )
+    {
+        rtp_fmt->clock_rate = p_fmt->audio.i_rate;
+        rtp_fmt->channels = p_fmt->audio.i_channels;
+    }
+    else
+        rtp_fmt->clock_rate = 90000; /* most common case for video */
+    /* Stream bitrate in kbps */
+    rtp_fmt->bitrate = p_fmt != NULL ? p_fmt->i_bitrate/1000 : 0;
+    rtp_fmt->fmtp = NULL;
+
+    if( mux != NULL )
+    {
+        if( strncmp( mux, "ts", 2 ) == 0 )
+        {
+            rtp_fmt->payload_type = 33;
+            rtp_fmt->ptname = "MP2T";
+        }
+        else
+            rtp_fmt->ptname = "MP2P";
+        return;
+    }
+
+    switch( p_fmt->i_codec )
+    {
+        case VLC_CODEC_MULAW:
+            if( p_fmt->audio.i_channels == 1 && p_fmt->audio.i_rate == 8000 )
+                rtp_fmt->payload_type = 0;
+            rtp_fmt->ptname = "PCMU";
+            break;
+        case VLC_CODEC_ALAW:
+            if( p_fmt->audio.i_channels == 1 && p_fmt->audio.i_rate == 8000 )
+                rtp_fmt->payload_type = 8;
+            rtp_fmt->ptname = "PCMA";
+            break;
+        case VLC_CODEC_S16B:
+        case VLC_CODEC_S16L:
+            if( p_fmt->audio.i_channels == 1 && p_fmt->audio.i_rate == 44100 )
+            {
+                rtp_fmt->payload_type = 11;
+            }
+            else if( p_fmt->audio.i_channels == 2 &&
+                     p_fmt->audio.i_rate == 44100 )
+            {
+                rtp_fmt->payload_type = 10;
+            }
+            rtp_fmt->ptname = "L16";
+            break;
+        case VLC_CODEC_U8:
+            rtp_fmt->ptname = "L8";
+            break;
+        case VLC_CODEC_MPGA:
+            rtp_fmt->payload_type = 14;
+            rtp_fmt->ptname = "MPA";
+            rtp_fmt->clock_rate = 90000; /* not 44100 */
+            break;
+        case VLC_CODEC_MPGV:
+            rtp_fmt->payload_type = 32;
+            rtp_fmt->ptname = "MPV";
+            break;
+        case VLC_CODEC_ADPCM_G726:
+            switch( p_fmt->i_bitrate / 1000 )
+            {
+            case 16:
+                rtp_fmt->ptname = "G726-16";
+                break;
+            case 24:
+                rtp_fmt->ptname = "G726-24";
+                break;
+            case 32:
+                rtp_fmt->ptname = "G726-32";
+                break;
+            case 40:
+                rtp_fmt->ptname = "G726-40";
+                break;
+            default:
+                msg_Err( obj, "cannot add this stream (unsupported "
+                         "G.726 bit rate: %u)", p_fmt->i_bitrate );
+                return;
+            }
+            break;
+        case VLC_CODEC_A52:
+            rtp_fmt->ptname = "ac3";
+            break;
+        case VLC_CODEC_H263:
+            rtp_fmt->ptname = "H263-1998";
+            break;
+        case VLC_CODEC_H264:
+            rtp_fmt->ptname = "H264";
+            rtp_fmt->fmtp = NULL;
+
+            if( p_fmt->i_extra > 0 )
+            {
+                uint8_t *p_buffer = p_fmt->p_extra;
+                int     i_buffer = p_fmt->i_extra;
+                char    *p_64_sps = NULL;
+                char    *p_64_pps = NULL;
+                char    hexa[6+1];
+
+                while( i_buffer > 4 )
+                {
+                    int i_offset    = 0;
+                    int i_size      = 0;
+
+                    while( p_buffer[0] != 0 || p_buffer[1] != 0 ||
+                           p_buffer[2] != 1 )
+                    {
+                        p_buffer++;
+                        i_buffer--;
+                        if( i_buffer == 0 ) break;
+                    }
+
+                    if( i_buffer < 4 || memcmp(p_buffer, "\x00\x00\x01", 3 ) )
+                    {
+                        msg_Dbg( obj, "No startcode found..");
+                        break;
+                    }
+                    p_buffer += 3;
+                    i_buffer -= 3;
+
+                    const int i_nal_type = p_buffer[0]&0x1f;
+
+                    msg_Dbg( obj, "we found a startcode for NAL with TYPE:%d", i_nal_type );
+
+                    i_size = i_buffer;
+                    for( i_offset = 0; i_offset+2 < i_buffer ; i_offset++)
+                    {
+                        if( !memcmp(p_buffer + i_offset, "\x00\x00\x01", 3 ) )
+                        {
+                            /* we found another startcode */
+                            while( i_offset > 0 && 0 == p_buffer[ i_offset - 1 ] )
+                                i_offset--;
+                            i_size = i_offset;
+                            break;
+                        }
+                    }
+
+                    if( i_size == 0 )
+                    {
+                        msg_Dbg( obj, "No-info found in nal ");
+                        continue;
+                    }
+
+                    if( i_nal_type == 7 )
+                    {
+                        free( p_64_sps );
+                        p_64_sps = vlc_b64_encode_binary( p_buffer, i_size );
+                        /* XXX: nothing ensures that i_size >= 4 ?? */
+                        sprintf_hexa( hexa, &p_buffer[1], 3 );
+                    }
+                    else if( i_nal_type == 8 )
+                    {
+                        free( p_64_pps );
+                        p_64_pps = vlc_b64_encode_binary( p_buffer, i_size );
+                    }
+                    i_buffer -= i_size;
+                    p_buffer += i_size;
+                }
+                /* */
+                if( p_64_sps && p_64_pps &&
+                    ( asprintf( &rtp_fmt->fmtp,
+                                "packetization-mode=1;profile-level-id=%s;"
+                                "sprop-parameter-sets=%s,%s;", hexa, p_64_sps,
+                                p_64_pps ) == -1 ) )
+                    rtp_fmt->fmtp = NULL;
+                free( p_64_sps );
+                free( p_64_pps );
+            }
+            if( rtp_fmt->fmtp == NULL )
+                rtp_fmt->fmtp = strdup( "packetization-mode=1" );
+            break;
+
+        case VLC_CODEC_MP4V:
+        {
+            rtp_fmt->ptname = "MP4V-ES";
+            if( p_fmt->i_extra > 0 )
+            {
+                char hexa[2*p_fmt->i_extra +1];
+                sprintf_hexa( hexa, p_fmt->p_extra, p_fmt->i_extra );
+                if( asprintf( &rtp_fmt->fmtp,
+                              "profile-level-id=3; config=%s;", hexa ) == -1 )
+                    rtp_fmt->fmtp = NULL;
+            }
+            break;
+        }
+        case VLC_CODEC_MP4A:
+        {
+            if( ! var_InheritBool( obj, "sout-rtp-mp4a-latm" ) )
+            {
+                char hexa[2*p_fmt->i_extra +1];
+
+                rtp_fmt->ptname = "mpeg4-generic";
+                sprintf_hexa( hexa, p_fmt->p_extra, p_fmt->i_extra );
+                if( asprintf( &rtp_fmt->fmtp,
+                              "streamtype=5; profile-level-id=15; "
+                              "mode=AAC-hbr; config=%s; SizeLength=13; "
+                              "IndexLength=3; IndexDeltaLength=3; Profile=1;",
+                              hexa ) == -1 )
+                    rtp_fmt->fmtp = NULL;
+            }
+            else
+            {
+                char hexa[13];
+                int i;
+                unsigned char config[6];
+                unsigned int aacsrates[15] = {
+                    96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
+                    16000, 12000, 11025, 8000, 7350, 0, 0 };
+
+                for( i = 0; i < 15; i++ )
+                    if( p_fmt->audio.i_rate == aacsrates[i] )
+                        break;
+
+                config[0]=0x40;
+                config[1]=0;
+                config[2]=0x20|i;
+                config[3]=p_fmt->audio.i_channels<<4;
+                config[4]=0x3f;
+                config[5]=0xc0;
+
+                rtp_fmt->ptname = "MP4A-LATM";
+                sprintf_hexa( hexa, config, 6 );
+                if( asprintf( &rtp_fmt->fmtp, "profile-level-id=15; "
+                              "object=2; cpresent=0; config=%s", hexa ) == -1 )
+                    rtp_fmt->fmtp = NULL;
+            }
+            break;
+        }
+        case VLC_CODEC_AMR_NB:
+            rtp_fmt->ptname = "AMR";
+            rtp_fmt->fmtp = strdup( "octet-align=1" );
+            break;
+        case VLC_CODEC_AMR_WB:
+            rtp_fmt->ptname = "AMR-WB";
+            rtp_fmt->fmtp = strdup( "octet-align=1" );
+            break;
+        case VLC_CODEC_SPEEX:
+            rtp_fmt->ptname = "SPEEX";
+            break;
+        case VLC_CODEC_ITU_T140:
+            rtp_fmt->ptname = "t140" ;
+            rtp_fmt->clock_rate = 1000;
+            break;
+
+        default:
+            msg_Err( obj, "cannot add this stream (unsupported "
+                     "codec: %4.4s)", (char*)&p_fmt->i_codec );
+    }
+}
+
+
 int
 rtp_packetize_h264_nal( sout_stream_id_t *id,
                         const uint8_t *p_data, int i_data, int64_t i_pts,
diff --git a/modules/stream_out/rtsp.c b/modules/stream_out/rtsp.c
index f737862..7ecf6e0 100644
--- a/modules/stream_out/rtsp.c
+++ b/modules/stream_out/rtsp.c
@@ -1,12 +1,13 @@
 /*****************************************************************************
  * rtsp.c: RTSP support for RTP stream output module
  *****************************************************************************
- * Copyright (C) 2003-2004 the VideoLAN team
+ * Copyright (C) 2003-2004, 2010 the VideoLAN team
  * Copyright © 2007 Rémi Denis-Courmont
  *
  * $Id$
  *
  * Authors: Laurent Aimar <fenrir at via.ecp.fr>
+ *          Pierre Ynard
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -35,12 +36,21 @@
 
 #include <vlc_httpd.h>
 #include <vlc_url.h>
+#include <vlc_charset.h>
+#include <vlc_fs.h>
 #include <vlc_network.h>
 #include <vlc_rand.h>
 #include <assert.h>
 #include <errno.h>
 #include <stdlib.h>
 
+#ifndef WIN32
+# include <locale.h>
+#endif
+#ifdef HAVE_XLOCALE_H
+# include <xlocale.h>
+#endif
+
 #include "rtp.h"
 
 typedef struct rtsp_session_t rtsp_session_t;
@@ -48,7 +58,8 @@ typedef struct rtsp_session_t rtsp_session_t;
 struct rtsp_stream_t
 {
     vlc_mutex_t     lock;
-    sout_stream_t  *owner;
+    vlc_object_t   *owner;
+    vod_media_t    *vod_media;
     httpd_host_t   *host;
     httpd_url_t    *url;
     char           *psz_path;
@@ -68,7 +79,8 @@ static int  RtspCallbackId( httpd_callback_sys_t *p_args,
                             const httpd_message_t *query );
 static void RtspClientDel( rtsp_stream_t *rtsp, rtsp_session_t *session );
 
-rtsp_stream_t *RtspSetup( sout_stream_t *p_stream, const vlc_url_t *url )
+rtsp_stream_t *RtspSetup( vlc_object_t *owner, vod_media_t *media,
+                          const vlc_url_t *url )
 {
     rtsp_stream_t *rtsp = malloc( sizeof( *rtsp ) );
 
@@ -78,7 +90,8 @@ rtsp_stream_t *RtspSetup( sout_stream_t *p_stream, const vlc_url_t *url )
         return NULL;
     }
 
-    rtsp->owner = p_stream;
+    rtsp->owner = owner;
+    rtsp->vod_media = media;
     rtsp->sessionc = 0;
     rtsp->sessionv = NULL;
     rtsp->host = NULL;
@@ -92,10 +105,10 @@ rtsp_stream_t *RtspSetup( sout_stream_t *p_stream, const vlc_url_t *url )
     if( rtsp->psz_path == NULL )
         goto error;
 
-    msg_Dbg( p_stream, "RTSP stream: host %s port %d at %s",
+    msg_Dbg( owner, "RTSP stream: host %s port %d at %s",
              url->psz_host, rtsp->port, rtsp->psz_path );
 
-    rtsp->host = httpd_HostNew( VLC_OBJECT(p_stream), url->psz_host,
+    rtsp->host = httpd_HostNew( VLC_OBJECT(owner), url->psz_host,
                                 rtsp->port );
     if( rtsp->host == NULL )
         goto error;
@@ -142,6 +155,7 @@ struct rtsp_stream_id_t
 {
     rtsp_stream_t    *stream;
     sout_stream_id_t *sout_id;
+    unsigned          clock_rate; /* needed to compute rtptime in RTP-Info */
     httpd_url_t      *url;
     const char       *dst;
     int               ttl;
@@ -158,6 +172,8 @@ struct rtsp_session_t
 {
     rtsp_stream_t *stream;
     uint64_t       id;
+    bool           vod_started; /* true if the VoD media instance was created */
+    bool           paused;      /* true if the client paused the VoD instance */
 
     /* output (id-access) */
     int            trackc;
@@ -169,10 +185,15 @@ struct rtsp_session_t
 struct rtsp_strack_t
 {
     rtsp_stream_id_t  *id;
-    int                fd;
+    sout_stream_id_t  *sout_id;
+    int          setup_fd;       /* socket created by the SETUP request */
+    int          rtp_fd;         /* socket used by the RTP output */
+    uint32_t     ssrc;
+    uint16_t     seq_init;
     bool         playing;
 };
 
+static void RtspTrackClose( rtsp_strack_t *tr );
 
 char *RtspAppendTrackPath( rtsp_stream_id_t *id, const char *base )
 {
@@ -187,7 +208,7 @@ char *RtspAppendTrackPath( rtsp_stream_id_t *id, const char *base )
 
 
 rtsp_stream_id_t *RtspAddId( rtsp_stream_t *rtsp, sout_stream_id_t *sid,
-                             uint32_t ssrc,
+                             uint32_t ssrc, unsigned clock_rate,
                              /* Multicast stuff - TODO: cleanup */
                              const char *dst, int ttl,
                              unsigned loport, unsigned hiport )
@@ -203,6 +224,7 @@ rtsp_stream_id_t *RtspAddId( rtsp_stream_t *rtsp, sout_stream_id_t *sid,
     id->sout_id = sid;
     id->track_id = rtsp->track_id;
     id->ssrc = ssrc;
+    id->clock_rate = clock_rate;
     /* TODO: can we assume that this need not be strdup'd? */
     id->dst = dst;
     if( id->dst != NULL )
@@ -256,7 +278,7 @@ void RtspDelId( rtsp_stream_t *rtsp, rtsp_stream_id_t *id )
             if( ses->trackv[j].id == id )
             {
                 rtsp_strack_t *tr = ses->trackv + j;
-                rtp_del_sink( tr->id->sout_id, tr->fd );
+                RtspTrackClose( tr );
                 REMOVE_ELEM( ses->trackv, ses->trackc, j );
             }
         }
@@ -277,6 +299,8 @@ rtsp_session_t *RtspClientNew( rtsp_stream_t *rtsp )
 
     s->stream = rtsp;
     vlc_rand_bytes (&s->id, sizeof (s->id));
+    s->vod_started = false;
+    s->paused = false;
     s->trackc = 0;
     s->trackv = NULL;
 
@@ -320,13 +344,114 @@ void RtspClientDel( rtsp_stream_t *rtsp, rtsp_session_t *session )
     TAB_REMOVE( rtsp->sessionc, rtsp->sessionv, session );
 
     for( i = 0; i < session->trackc; i++ )
-        rtp_del_sink( session->trackv[i].id->sout_id, session->trackv[i].fd );
+        RtspTrackClose( &session->trackv[i] );
 
     free( session->trackv );
     free( session );
 }
 
 
+/* Attach a starting VoD RTP id to its RTSP track, and let it
+ * initialize with the parameters of the SETUP request */
+int RtspTrackAttach( rtsp_stream_t *rtsp, const char *name,
+                     rtsp_stream_id_t *id, sout_stream_id_t *sout_id,
+                     uint32_t *ssrc, uint16_t *seq_init )
+{
+    int val = VLC_EGENERIC;
+    rtsp_session_t *session;
+
+    vlc_mutex_lock(&rtsp->lock);
+    session = RtspClientGet(rtsp, name);
+
+    if (session == NULL)
+        goto out;
+
+    for (int i = 0; session->trackc; i++)
+    {
+        rtsp_strack_t *tr = session->trackv + i;
+        if (tr->id == id)
+        {
+            int rtp_fd;
+#if !defined(WIN32) || defined(UNDER_CE)
+            rtp_fd = vlc_dup(tr->setup_fd);
+#else
+            WSAPROTOCOL_INFO info;
+            WSADuplicateSocket (tr->setup_fd, GetCurrentProcessId (), &info);
+            rtp_fd = WSASocket (info.iAddressFamily, info.iSocketType,
+                                info.iProtocol, &info, 0, 0);
+#endif
+            if (rtp_fd == -1)
+                break;
+
+            /* Ignore any unexpected incoming packet */
+            /* XXX: is this needed again? */
+            setsockopt (rtp_fd, SOL_SOCKET, SO_RCVBUF, &(int){ 0 },
+                        sizeof (int));
+
+            uint16_t seq;
+            *ssrc = ntohl(tr->ssrc);
+            *seq_init = tr->seq_init;
+            rtp_add_sink(sout_id, rtp_fd, false, &seq);
+            /* To avoid race conditions, sout_id->i_seq_sent_next must
+             * be set here and now. Make sure the caller did its job
+             * properly when passing seq_init. */
+            assert(tr->seq_init == seq);
+
+            tr->rtp_fd = rtp_fd;
+            tr->sout_id = sout_id;
+            tr->playing = true;
+
+            val = VLC_SUCCESS;
+            break;
+        }
+    }
+
+out:
+    vlc_mutex_unlock(&rtsp->lock);
+    return val;
+}
+
+
+/* Remove references to the RTP id when it is stopped */
+void RtspTrackDetach( rtsp_stream_t *rtsp, const char *name,
+                      sout_stream_id_t *sout_id )
+{
+    rtsp_session_t *session;
+
+    vlc_mutex_lock(&rtsp->lock);
+    session = RtspClientGet(rtsp, name);
+
+    if (session == NULL)
+        goto out;
+
+    for (int i = 0; session->trackc; i++)
+    {
+        rtsp_strack_t *tr = session->trackv + i;
+        if (tr->sout_id == sout_id)
+        {
+            tr->sout_id = NULL;
+            tr->playing = false;
+            rtp_del_sink(sout_id, tr->rtp_fd);
+            break;
+        }
+    }
+
+out:
+    vlc_mutex_unlock(&rtsp->lock);
+}
+
+
+/** rtsp must be locked */
+static void RtspTrackClose( rtsp_strack_t *tr )
+{
+    if (tr->sout_id != NULL)
+        rtp_del_sink(tr->sout_id, tr->rtp_fd);
+    /* rtp_fd is duplicated from setup_fd only in VoD mode. */
+    if (tr->id->stream->vod_media != NULL)
+        net_Close(tr->setup_fd);
+}
+
+
 /** Finds the next transport choice */
 static inline const char *transport_next( const char *str )
 {
@@ -353,6 +478,28 @@ static inline const char *parameter_next( const char *str )
 }
 
 
+static float ParseNPT (const char *str)
+{
+    locale_t loc = newlocale (LC_NUMERIC_MASK, "C", NULL);
+    locale_t oldloc = uselocale (loc);
+    unsigned hour, min;
+    float sec;
+
+    if (sscanf (str, "%u:%u:%f", &hour, &min, &sec) == 3)
+        sec += ((hour * 60) + min) * 60;
+    else
+    if (sscanf (str, "%f", &sec) != 1)
+        sec = 0.;
+
+    if (loc != (locale_t)0)
+    {
+        uselocale (oldloc);
+        freelocale (loc);
+    }
+    return sec;
+}
+
+
 /** RTSP requests handler
  * @param id selected track for non-aggregate URLs,
  *           NULL for aggregate URLs
@@ -362,11 +509,12 @@ static int RtspHandler( rtsp_stream_t *rtsp, rtsp_stream_id_t *id,
                         httpd_message_t *answer,
                         const httpd_message_t *query )
 {
-    sout_stream_t *p_stream = rtsp->owner;
+    vlc_object_t *owner = rtsp->owner;
     char psz_sesbuf[17];
     const char *psz_session = NULL, *psz;
     char control[sizeof("rtsp://[]:12345") + NI_MAXNUMERICHOST
                   + strlen( rtsp->psz_path )];
+    bool vod = rtsp->vod_media != NULL;
     time_t now;
 
     time (&now);
@@ -439,7 +587,10 @@ static int RtspHandler( rtsp_stream_t *rtsp, rtsp_stream_id_t *id,
             answer->i_status = 200;
             httpd_MsgAdd( answer, "Content-Type",  "%s", "application/sdp" );
             httpd_MsgAdd( answer, "Content-Base",  "%s", control );
-            answer->p_body = (uint8_t *)SDPGenerate( p_stream, control );
+
+            answer->p_body = (uint8_t *) ( vod ?
+                SDPGenerateVoD( rtsp->vod_media, control ) :
+                SDPGenerate( (sout_stream_t *)owner, control ) );
             if( answer->p_body != NULL )
                 answer->i_body = strlen( (char *)answer->p_body );
             else
@@ -554,8 +705,8 @@ static int RtspHandler( rtsp_stream_t *rtsp, rtsp_stream_id_t *id,
                 {
                     char ip[NI_MAXNUMERICHOST], src[NI_MAXNUMERICHOST];
                     rtsp_session_t *ses = NULL;
-                    rtsp_strack_t track = { id, -1, false };
-                    int sport;
+                    int fd, sport;
+                    uint32_t ssrc;
 
                     if( httpd_ClientIP( cl, ip ) == NULL )
                     {
@@ -563,11 +714,11 @@ static int RtspHandler( rtsp_stream_t *rtsp, rtsp_stream_id_t *id,
                         continue;
                     }
 
-                    track.fd = net_ConnectDgram( p_stream, ip, loport, -1,
-                                                 IPPROTO_UDP );
-                    if( track.fd == -1 )
+                    fd = net_ConnectDgram( owner, ip, loport, -1,
+                                           IPPROTO_UDP );
+                    if( fd == -1 )
                     {
-                        msg_Err( p_stream,
+                        msg_Err( owner,
                                  "cannot create RTP socket for %s port %u",
                                  ip, loport );
                         answer->i_status = 500;
@@ -575,9 +726,24 @@ static int RtspHandler( rtsp_stream_t *rtsp, rtsp_stream_id_t *id,
                     }
 
                     /* Ignore any unexpected incoming packet */
-                    setsockopt (track.fd, SOL_SOCKET, SO_RCVBUF, &(int){ 0 },
+                    setsockopt (fd, SOL_SOCKET, SO_RCVBUF, &(int){ 0 },
                                 sizeof (int));
-                    net_GetSockAddress( track.fd, src, &sport );
+                    net_GetSockAddress( fd, src, &sport );
+
+                    rtsp_strack_t track = { .id = id, .sout_id = id->sout_id,
+                                            .setup_fd = fd, .playing = false };
+
+                    if (vod)
+                    {
+                        vlc_rand_bytes (&track.seq_init, sizeof (track.seq_init));
+                        vlc_rand_bytes (&track.ssrc, sizeof (track.ssrc));
+                        ssrc = track.ssrc;
+                    }
+                    else
+                    {
+                        track.rtp_fd = track.setup_fd;
+                        ssrc = id->ssrc;
+                    }
 
                     vlc_mutex_lock( &rtsp->lock );
                     if( psz_session == NULL )
@@ -596,7 +762,7 @@ static int RtspHandler( rtsp_stream_t *rtsp, rtsp_stream_id_t *id,
                         {
                             answer->i_status = 454;
                             vlc_mutex_unlock( &rtsp->lock );
-                            net_Close( track.fd );
+                            net_Close( fd );
                             continue;
                         }
                     }
@@ -619,7 +785,7 @@ static int RtspHandler( rtsp_stream_t *rtsp, rtsp_stream_id_t *id,
                                       "client_port=%u-%u;server_port=%u-%u;"
                                       "ssrc=%08X;mode=play",
                                       src, loport, loport + 1, sport,
-                                      sport + 1, id->ssrc );
+                                      sport + 1, ssrc );
                     }
                     else
                     {
@@ -628,7 +794,7 @@ static int RtspHandler( rtsp_stream_t *rtsp, rtsp_stream_id_t *id,
                                       "client_port=%u-%u;server_port=%u-%u;"
                                       "ssrc=%08X;mode=play",
                                       loport, loport + 1, sport, sport + 1,
-                                      id->ssrc );
+                                      ssrc );
                     }
 
                     answer->i_status = 200;
@@ -644,7 +810,7 @@ static int RtspHandler( rtsp_stream_t *rtsp, rtsp_stream_id_t *id,
 
             psz_session = httpd_MsgGet( query, "Session" );
             const char *range = httpd_MsgGet (query, "Range");
-            if (range && strncmp (range, "npt=", 4))
+            if (range != NULL && strncmp (range, "npt=", 4))
             {
                 answer->i_status = 501;
                 break;
@@ -661,7 +827,24 @@ static int RtspHandler( rtsp_stream_t *rtsp, rtsp_stream_id_t *id,
                               + sizeof("url=/trackID=123;seq=65535;"
                                        "rtptime=4294967295, ") ) + 1];
                 size_t infolen = 0;
-                int64_t ts = rtp_get_ts( p_stream );
+
+                sout_stream_id_t *sout_id = NULL;
+                if (vod)
+                {
+                    /* We don't keep a reference to the sout_stream_t,
+                     * so we check if a sout_id is available instead.
+                     * FIXME: this is broken if the stream is still
+                     * running but with no track set up; but this case
+                     * is already broken anyway (see below). */
+                    for (int i = 0; i < ses->trackc; i++)
+                    {
+                        sout_id = ses->trackv[i].sout_id;
+                        if (sout_id != NULL)
+                            break;
+                    }
+                }
+                int64_t ts = rtp_get_ts(vod ? NULL : (sout_stream_t *)owner,
+                                        sout_id, rtsp->vod_media, psz_session);
 
                 for( int i = 0; i < ses->trackc; i++ )
                 {
@@ -671,17 +854,29 @@ static int RtspHandler( rtsp_stream_t *rtsp, rtsp_stream_id_t *id,
                         uint16_t seq;
                         if( !tr->playing )
                         {
-                            tr->playing = true;
-                            rtp_add_sink( tr->id->sout_id, tr->fd, false,
-                                          &seq );
+                            if (vod)
+                                /* TODO: if the RTP stream output is already
+                                 * started, it won't pick up newly set-up
+                                 * tracks, so we need to call rtp_add_sink()
+                                 * or something. */
+                                seq = tr->seq_init;
+                            else
+                            {
+                                tr->playing = true;
+                                rtp_add_sink( tr->sout_id, tr->rtp_fd,
+                                              false, &seq );
+                            }
                         }
                         else
-                            seq = rtp_get_seq( tr->id->sout_id );
+                        {
+                            assert( tr->sout_id != NULL );
+                            seq = rtp_get_seq( tr->sout_id );
+                        }
                         char *url = RtspAppendTrackPath( tr->id, control );
                         infolen += sprintf( info + infolen,
                                     "url=%s;seq=%u;rtptime=%u, ",
                                     url != NULL ? url : "", seq,
-                                    rtp_compute_ts( tr->id->sout_id, ts ) );
+                                    rtp_compute_ts( tr->id->clock_rate, ts ) );
                         free( url );
                     }
                 }
@@ -690,6 +885,28 @@ static int RtspHandler( rtsp_stream_t *rtsp, rtsp_stream_id_t *id,
                     info[infolen - 2] = '\0'; /* remove trailing ", " */
                     httpd_MsgAdd( answer, "RTP-Info", "%s", info );
                 }
+                if (vod)
+                {
+                    /* TODO: fix that crap, this is barely RTSP */
+                    if (!ses->vod_started)
+                    {
+                        vod_start(rtsp->vod_media, psz_session);
+                        ses->vod_started = true;
+                    }
+                    else
+                    {
+                        if (range != NULL)
+                        {
+                            float time = ParseNPT (range + 4);
+                            vod_seek(rtsp->vod_media, psz_session, time);
+                        }
+                        if (ses->paused)
+                        {
+                            vod_toggle_pause(rtsp->vod_media, psz_session);
+                            ses->paused = false;
+                        }
+                    }
+                }
             }
             vlc_mutex_unlock( &rtsp->lock );
 
@@ -699,11 +916,29 @@ static int RtspHandler( rtsp_stream_t *rtsp, rtsp_stream_id_t *id,
         }
 
         case HTTPD_MSG_PAUSE:
-            answer->i_status = 405;
-            httpd_MsgAdd( answer, "Allow",
-                          "%s, TEARDOWN, PLAY, GET_PARAMETER",
-                          ( id != NULL ) ? "SETUP" : "DESCRIBE" );
+        {
+            if (!vod)
+            {
+                answer->i_status = 405;
+                httpd_MsgAdd( answer, "Allow",
+                              "%s, TEARDOWN, PLAY, GET_PARAMETER",
+                              ( id != NULL ) ? "SETUP" : "DESCRIBE" );
+                break;
+            }
+
+            rtsp_session_t *ses;
+            answer->i_status = 200;
+            psz_session = httpd_MsgGet( query, "Session" );
+            vlc_mutex_lock( &rtsp->lock );
+            ses = RtspClientGet( rtsp, psz_session );
+            if (ses != NULL && !ses->paused)
+            {
+                vod_toggle_pause(rtsp->vod_media, psz_session);
+                ses->paused = true;
+            }
+            vlc_mutex_unlock( &rtsp->lock );
             break;
+        }
 
         case HTTPD_MSG_GETPARAMETER:
             if( query->i_body > 0 )
@@ -729,13 +964,17 @@ static int RtspHandler( rtsp_stream_t *rtsp, rtsp_stream_id_t *id,
             if( ses != NULL )
             {
                 if( id == NULL ) /* Delete the entire session */
+                {
                     RtspClientDel( rtsp, ses );
+                    if (vod)
+                        vod_stop(rtsp->vod_media, psz_session);
+                }
                 else /* Delete one track from the session */
                 for( int i = 0; i < ses->trackc; i++ )
                 {
                     if( ses->trackv[i].id == id )
                     {
-                        rtp_del_sink( id->sout_id, ses->trackv[i].fd );
+                        RtspTrackClose( &ses->trackv[i] );
                         REMOVE_ELEM( ses->trackv, ses->trackc, i );
                     }
                 }
diff --git a/modules/stream_out/vod.c b/modules/stream_out/vod.c
new file mode 100644
index 0000000..643c91d
--- /dev/null
+++ b/modules/stream_out/vod.c
@@ -0,0 +1,628 @@
+/*****************************************************************************
+ * vod.c: rtsp VoD server module
+ *****************************************************************************
+ * Copyright (C) 2003-2006, 2010 the VideoLAN team
+ * $Id$
+ *
+ * Authors: Laurent Aimar <fenrir at via.ecp.fr>
+ *          Gildas Bazin <gbazin at videolan.org>
+ *          Pierre Ynard
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Preamble
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_input.h>
+#include <vlc_sout.h>
+#include <vlc_block.h>
+
+#include <vlc_vod.h>
+#include <vlc_url.h>
+#include <vlc_network.h>
+
+#include <assert.h>
+
+#include "rtp.h"
+
+/*****************************************************************************
+ * Exported prototypes
+ *****************************************************************************/
+
+typedef struct media_es_t media_es_t;
+
+struct media_es_t
+{
+    int es_id;
+    rtp_format_t rtp_fmt;
+    rtsp_stream_id_t *rtsp_id;
+};
+
+struct vod_media_t
+{
+    int id;
+
+    /* VoD server */
+    vod_t *p_vod;
+
+    /* RTSP server */
+    rtsp_stream_t *rtsp;
+
+    /* ES list */
+    int        i_es;
+    media_es_t **es;
+    const char *psz_mux;
+
+    /* Infos */
+    mtime_t i_length;
+};
+
+struct vod_sys_t
+{
+    char *psz_rtsp_url;
+
+    /* List of media */
+    int i_media_id;
+    int i_media;
+    vod_media_t **media;
+
+    /* */
+    block_fifo_t *p_fifo_cmd;
+};
+
+/* rtsp delayed command (to avoid deadlock between vlm/httpd) */
+typedef enum
+{
+    RTSP_CMD_TYPE_NONE,  /* Exit requested */
+
+    RTSP_CMD_TYPE_PLAY,
+    RTSP_CMD_TYPE_PAUSE,
+    RTSP_CMD_TYPE_STOP,
+    RTSP_CMD_TYPE_SEEK,
+#if 0
+    RTSP_CMD_TYPE_REWIND,
+    RTSP_CMD_TYPE_FORWARD,
+#endif
+
+    RTSP_CMD_TYPE_ADD,
+    RTSP_CMD_TYPE_DEL,
+} rtsp_cmd_type_t;
+
+/* */
+typedef struct
+{
+    int i_type;
+    int i_media_id;
+    vod_media_t *p_media;
+    char *psz_session;
+    char *psz_arg;
+    double f_arg;
+} rtsp_cmd_t;
+
+static vod_media_t *MediaNew( vod_t *, const char *, input_item_t * );
+static void         MediaDel( vod_t *, vod_media_t * );
+static void         MediaAskDel ( vod_t *, vod_media_t * );
+
+static void* CommandThread( vlc_object_t *p_this );
+static void  CommandPush( vod_t *, rtsp_cmd_type_t, vod_media_t *, const char *psz_session,
+                          double f_arg, const char *psz_arg );
+
+/*****************************************************************************
+ * Open: Starts the RTSP server module
+ *****************************************************************************/
+int OpenVoD( vlc_object_t *p_this )
+{
+    vod_t *p_vod = (vod_t *)p_this;
+    vod_sys_t *p_sys = NULL;
+    char *psz_url;
+
+    p_vod->p_sys = p_sys = malloc( sizeof( vod_sys_t ) );
+    if( !p_sys ) goto error;
+
+    psz_url = var_InheritString( p_vod, "rtsp-host" );
+
+    if( psz_url == NULL )
+        p_sys->psz_rtsp_url = strdup( "/" );
+    else
+    if( !( strlen( psz_url ) > 0 && psz_url[strlen( psz_url ) - 1] == '/' ) )
+    {
+         if( asprintf( &p_sys->psz_rtsp_url, "%s/", psz_url ) == -1 )
+         {
+             p_sys->psz_rtsp_url = NULL;
+             free( psz_url );
+             goto error;
+         }
+         free( psz_url );
+    }
+    else
+        p_sys->psz_rtsp_url = psz_url;
+
+    TAB_INIT( p_sys->i_media, p_sys->media );
+    p_sys->i_media_id = 0;
+
+    p_vod->pf_media_new = MediaNew;
+    p_vod->pf_media_del = MediaAskDel;
+    /* These are never used in the core, wonder why they're in the API */
+    p_vod->pf_media_add_es = NULL;
+    p_vod->pf_media_del_es = NULL;
+
+    p_sys->p_fifo_cmd = block_FifoNew();
+    if( vlc_thread_create( p_vod, "rtsp vod thread", CommandThread,
+                           VLC_THREAD_PRIORITY_LOW ) )
+    {
+        msg_Err( p_vod, "cannot spawn rtsp vod thread" );
+        block_FifoRelease( p_sys->p_fifo_cmd );
+        goto error;
+    }
+
+    return VLC_SUCCESS;
+
+error:
+    if( p_sys )
+    {
+        free( p_sys->psz_rtsp_url );
+        free( p_sys );
+    }
+
+    return VLC_EGENERIC;
+}
+
+/*****************************************************************************
+ * Close:
+ *****************************************************************************/
+void CloseVoD( vlc_object_t * p_this )
+{
+    vod_t *p_vod = (vod_t *)p_this;
+    vod_sys_t *p_sys = p_vod->p_sys;
+
+    /* Stop command thread */
+    vlc_object_kill( p_vod );
+    CommandPush( p_vod, RTSP_CMD_TYPE_NONE, NULL, NULL, 0.0, NULL );
+    vlc_thread_join( p_vod );
+
+    while( block_FifoCount( p_sys->p_fifo_cmd ) > 0 )
+    {
+        rtsp_cmd_t cmd;
+        block_t *p_block_cmd = block_FifoGet( p_sys->p_fifo_cmd );
+        memcpy( &cmd, p_block_cmd->p_buffer, sizeof(cmd) );
+        block_Release( p_block_cmd );
+        if ( cmd.i_type == RTSP_CMD_TYPE_DEL )
+            MediaDel(p_vod, cmd.p_media);
+        free( cmd.psz_session );
+        free( cmd.psz_arg );
+    }
+    block_FifoRelease( p_sys->p_fifo_cmd );
+
+    /* Check VLM is not buggy */
+    if( p_sys->i_media > 0 )
+        msg_Err( p_vod, "rtsp vod leaking %d medias", p_sys->i_media );
+    TAB_CLEAN( p_sys->i_media, p_sys->media );
+
+    free( p_sys->psz_rtsp_url );
+    free( p_sys );
+}
+
+/*****************************************************************************
+ * Media handling
+ *****************************************************************************/
+static vod_media_t *MediaNew( vod_t *p_vod, const char *psz_name,
+                              input_item_t *p_item )
+{
+    vod_sys_t *p_sys = p_vod->p_sys;
+
+    vod_media_t *p_media = calloc( 1, sizeof(vod_media_t) );
+    if( !p_media )
+        return NULL;
+
+    p_media->p_vod = p_vod;
+    p_media->rtsp = NULL;
+    TAB_INIT( p_media->i_es, p_media->es );
+    p_media->psz_mux = NULL;
+    p_media->i_length = input_item_GetDuration( p_item );
+
+    vlc_mutex_lock( &p_item->lock );
+    msg_Dbg( p_vod, "media '%s' has %i declared ES", psz_name, p_item->i_es );
+    for( int i = 0; i < p_item->i_es; i++ )
+    {
+        es_format_t *p_fmt = p_item->es[i];
+
+        switch( p_fmt->i_codec )
+        {
+            case VLC_FOURCC( 'm', 'p', '2', 't' ):
+                p_media->psz_mux = "ts";
+                break;
+            case VLC_FOURCC( 'm', 'p', '2', 'p' ):
+                p_media->psz_mux = "ps";
+                break;
+        }
+        assert(p_media->psz_mux == NULL || p_item->i_es == 1);
+
+        media_es_t *p_es = calloc( 1, sizeof(media_es_t) );
+        if( !p_es )
+            continue;
+
+        p_es->es_id = p_fmt->i_id;
+        p_es->rtsp_id = NULL;
+
+        rtp_get_fmt(VLC_OBJECT(p_vod), p_fmt, p_media->psz_mux,
+                    &p_es->rtp_fmt);
+
+        if (p_es->rtp_fmt.ptname == NULL)
+        {
+            free(p_es);
+            continue;
+        }
+
+        TAB_APPEND( p_media->i_es, p_media->es, p_es );
+        msg_Dbg(p_vod, "  - added ES %u %s (%4.4s)",
+                p_es->rtp_fmt.payload_type, p_es->rtp_fmt.ptname,
+                (char *)&p_fmt->i_codec);
+    }
+    vlc_mutex_unlock( &p_item->lock );
+
+    if (p_media->i_es == 0)
+    {
+        msg_Err(p_vod, "no ES was added to the media, aborting");
+        goto error;
+    }
+
+    char *psz_url;
+
+    if( asprintf( &psz_url, "%s%s", p_sys->psz_rtsp_url, psz_name ) < 0 )
+        goto error;
+
+    vlc_url_t url;
+    vlc_UrlParse( &url, psz_url, 0 );
+    free( psz_url );
+
+    p_media->rtsp = RtspSetup(VLC_OBJECT(p_vod), p_media, &url);
+
+    vlc_UrlClean( &url );
+
+    if (p_media->rtsp == NULL)
+        goto error;
+
+    for (int i = 0; i < p_media->i_es; i++)
+    {
+        media_es_t *p_es = p_media->es[i];
+        p_es->rtsp_id = RtspAddId(p_media->rtsp, NULL, 0,
+                                  p_es->rtp_fmt.clock_rate, NULL, 0, 0, 0);
+        if (p_es->rtsp_id == NULL)
+            goto error;
+    }
+
+    p_media->id = p_sys->i_media_id++;
+
+    msg_Dbg(p_vod, "adding media '%s', id %i", psz_name, p_media->id);
+
+    CommandPush( p_vod, RTSP_CMD_TYPE_ADD, p_media, NULL, 0.0, NULL );
+    return p_media;
+
+error:
+    MediaDel(p_vod, p_media);
+    return NULL;
+}
+
+static void MediaAskDel ( vod_t *p_vod, vod_media_t *p_media )
+{
+    msg_Dbg( p_vod, "deleting media id %i", p_media->id );
+    CommandPush( p_vod, RTSP_CMD_TYPE_DEL, p_media, NULL, 0.0, NULL );
+}
+
+static void MediaDel( vod_t *p_vod, vod_media_t *p_media )
+{
+    vod_sys_t *p_sys = p_vod->p_sys;
+
+    TAB_REMOVE( p_sys->i_media, p_sys->media, p_media );
+
+    if (p_media->rtsp != NULL)
+    {
+        for (int i = 0; i < p_media->i_es; i++)
+        {
+            media_es_t *p_es = p_media->es[i];
+            if (p_es->rtsp_id != NULL)
+                RtspDelId(p_media->rtsp, p_es->rtsp_id);
+        }
+        RtspUnsetup(p_media->rtsp);
+    }
+
+    while( p_media->i_es )
+    {
+        media_es_t *p_es = p_media->es[0];
+        TAB_REMOVE( p_media->i_es, p_media->es, p_es );
+        free( p_es->rtp_fmt.fmtp );
+        free( p_es );
+    }
+
+    TAB_CLEAN( p_media->i_es, p_media->es );
+    free( p_media );
+}
+
+static void CommandPush( vod_t *p_vod, rtsp_cmd_type_t i_type, vod_media_t *p_media, const char *psz_session,
+                         double f_arg, const char *psz_arg )
+{
+    rtsp_cmd_t cmd;
+    block_t *p_cmd;
+
+    memset( &cmd, 0, sizeof(cmd) );
+    cmd.i_type = i_type;
+    cmd.p_media = p_media;
+    if( p_media )
+        cmd.i_media_id = p_media->id;
+    if( psz_session )
+        cmd.psz_session = strdup(psz_session);
+    cmd.f_arg = f_arg;
+    if( psz_arg )
+        cmd.psz_arg = strdup(psz_arg);
+
+    p_cmd = block_New( p_vod, sizeof(rtsp_cmd_t) );
+    memcpy( p_cmd->p_buffer, &cmd, sizeof(cmd) );
+
+    block_FifoPut( p_vod->p_sys->p_fifo_cmd, p_cmd );
+}
+
+static void* CommandThread( vlc_object_t *p_this )
+{
+    vod_t *p_vod = (vod_t*)p_this;
+    vod_sys_t *p_sys = p_vod->p_sys;
+    int canc = vlc_savecancel ();
+
+    while( vlc_object_alive (p_vod) )
+    {
+        block_t *p_block_cmd = block_FifoGet( p_sys->p_fifo_cmd );
+        rtsp_cmd_t cmd;
+        vod_media_t *p_media = NULL;
+        int i;
+
+        if( !p_block_cmd )
+            break;
+
+        memcpy( &cmd, p_block_cmd->p_buffer, sizeof(cmd) );
+        block_Release( p_block_cmd );
+
+        if( cmd.i_type == RTSP_CMD_TYPE_NONE )
+            break;
+
+        if ( cmd.i_type == RTSP_CMD_TYPE_ADD )
+        {
+            TAB_APPEND( p_sys->i_media, p_sys->media, cmd.p_media );
+            goto next;
+        }
+
+        if ( cmd.i_type == RTSP_CMD_TYPE_DEL )
+        {
+            MediaDel(p_vod, cmd.p_media);
+            goto next;
+        }
+
+        /* */
+        for( i = 0; i < p_sys->i_media; i++ )
+        {
+            if( p_sys->media[i]->id == cmd.i_media_id )
+                break;
+        }
+        if( i >= p_sys->i_media )
+        {
+            goto next;
+        }
+        p_media = p_sys->media[i];
+
+        switch( cmd.i_type )
+        {
+        case RTSP_CMD_TYPE_PLAY:
+            vod_MediaControl( p_vod, p_media, cmd.psz_session,
+                              VOD_MEDIA_PLAY, cmd.psz_arg );
+            break;
+        case RTSP_CMD_TYPE_PAUSE:
+            vod_MediaControl( p_vod, p_media, cmd.psz_session,
+                              VOD_MEDIA_PAUSE );
+            break;
+
+        case RTSP_CMD_TYPE_STOP:
+            vod_MediaControl( p_vod, p_media, cmd.psz_session, VOD_MEDIA_STOP );
+            break;
+
+        case RTSP_CMD_TYPE_SEEK:
+            vod_MediaControl( p_vod, p_media, cmd.psz_session,
+                              VOD_MEDIA_SEEK, cmd.f_arg );
+            break;
+
+#if 0
+        case RTSP_CMD_TYPE_REWIND:
+            vod_MediaControl( p_vod, p_media, cmd.psz_session,
+                              VOD_MEDIA_REWIND, cmd.f_arg );
+            break;
+
+        case RTSP_CMD_TYPE_FORWARD:
+            vod_MediaControl( p_vod, p_media, cmd.psz_session,
+                              VOD_MEDIA_FORWARD, cmd.f_arg );
+            break;
+#endif
+
+        default:
+            break;
+        }
+
+    next:
+        free( cmd.psz_session );
+        free( cmd.psz_arg );
+    }
+
+    vlc_restorecancel (canc);
+    return NULL;
+}
+
+/*****************************************************************************
+ * SDPGenerateVoD
+ * FIXME: needs to be merged more?
+ *****************************************************************************/
+char *SDPGenerateVoD( const vod_media_t *p_media, const char *rtsp_url )
+{
+    char *psz_sdp;
+
+    assert(rtsp_url != NULL);
+    /* Check against URL format rtsp://[<ipv6>]:<port>/<path> */
+    bool ipv6 = strlen( rtsp_url ) > 7 && rtsp_url[7] == '[';
+
+    /* Dummy destination address for RTSP */
+    struct sockaddr_storage dst;
+    socklen_t dstlen = ipv6 ? sizeof( struct sockaddr_in6 )
+                            : sizeof( struct sockaddr_in );
+    memset (&dst, 0, dstlen);
+    dst.ss_family = ipv6 ? AF_INET6 : AF_INET;
+#ifdef HAVE_SA_LEN
+    dst.ss_len = dstlen;
+#endif
+
+    psz_sdp = vlc_sdp_Start( VLC_OBJECT( p_media->p_vod ), "sout-rtp-",
+                             NULL, 0, (struct sockaddr *)&dst, dstlen );
+    if( psz_sdp == NULL )
+        return NULL;
+
+    if( p_media->i_length > 0 )
+    {
+        lldiv_t d = lldiv( p_media->i_length / 1000, 1000 );
+        sdp_AddAttribute( &psz_sdp, "range"," npt=0-%lld.%03u", d.quot,
+                          (unsigned)d.rem );
+    }
+
+    sdp_AddAttribute ( &psz_sdp, "control", "%s", rtsp_url );
+
+    /* No locking needed, the ES table can't be modified now */
+    for( int i = 0; i < p_media->i_es; i++ )
+    {
+        media_es_t *p_es = p_media->es[i];
+        rtp_format_t *rtp_fmt = &p_es->rtp_fmt;
+        const char *mime_major; /* major MIME type */
+
+        switch( rtp_fmt->cat )
+        {
+            case VIDEO_ES:
+                mime_major = "video";
+                break;
+            case AUDIO_ES:
+                mime_major = "audio";
+                break;
+            case SPU_ES:
+                mime_major = "text";
+                break;
+            default:
+                continue;
+        }
+
+        sdp_AddMedia( &psz_sdp, mime_major, "RTP/AVP", 0,
+                      rtp_fmt->payload_type, false, 0,
+                      rtp_fmt->ptname, rtp_fmt->clock_rate, rtp_fmt->channels,
+                      rtp_fmt->fmtp );
+
+        char *track_url = RtspAppendTrackPath( p_es->rtsp_id, rtsp_url );
+        if( track_url != NULL )
+        {
+            sdp_AddAttribute ( &psz_sdp, "control", "%s", track_url );
+            free( track_url );
+        }
+    }
+
+    return psz_sdp;
+}
+
+void vod_start(vod_media_t *p_media, const char *psz_session)
+{
+    /* We're passing the #vod{} sout chain here */
+    CommandPush(p_media->p_vod, RTSP_CMD_TYPE_PLAY, p_media,
+                psz_session, 0.0, "vod");
+}
+
+/* FIXME: this sucks, RTSP doesn't really toggle the pause state, it
+ * either PAUSEs or PLAYs */
+void vod_toggle_pause(vod_media_t *p_media, const char *psz_session)
+{
+    CommandPush(p_media->p_vod, RTSP_CMD_TYPE_PAUSE, p_media,
+                psz_session, 0.0, NULL);
+}
+
+void vod_stop(vod_media_t *p_media, const char *psz_session)
+{
+    CommandPush(p_media->p_vod, RTSP_CMD_TYPE_STOP, p_media,
+                psz_session, 0.0, NULL);
+}
+
+void vod_seek(vod_media_t *p_media, const char *psz_session, float time)
+{
+    /* FIXME: why do we even bother converting from time to position??? */
+    double position = time / (((double)(p_media->i_length)) / CLOCK_FREQ / 100);
+    CommandPush(p_media->p_vod, RTSP_CMD_TYPE_SEEK, p_media,
+                psz_session, position, NULL);
+}
+
+
+const char *vod_get_mux(const vod_media_t *p_media)
+{
+    return p_media->psz_mux;
+}
+
+
+/* Match an RTP id to a VoD media ES and RTSP track to initialize it
+ * with the data that was already set up */
+int vod_init_id(vod_media_t *p_media, const char *psz_session, int es_id,
+                sout_stream_id_t *sout_id, rtp_format_t *rtp_fmt,
+                uint32_t *ssrc, uint16_t *seq_init)
+{
+    media_es_t *p_es;
+
+    if (p_media->psz_mux != NULL)
+    {
+        assert(p_media->i_es == 1);
+        p_es = p_media->es[0];
+    }
+    else
+    {
+        p_es = NULL;
+        /* No locking needed, the ES table can't be modified now */
+        for (int i = 0; i < p_media->i_es; i++)
+        {
+            if (p_media->es[i]->es_id == es_id)
+            {
+                p_es = p_media->es[i];
+                break;
+            }
+        }
+        if (p_es == NULL)
+            return VLC_EGENERIC;
+    }
+
+    memcpy(rtp_fmt, &p_es->rtp_fmt, sizeof(*rtp_fmt));
+    if (p_es->rtp_fmt.fmtp != NULL)
+        rtp_fmt->fmtp = strdup(p_es->rtp_fmt.fmtp);
+
+    return RtspTrackAttach(p_media->rtsp, psz_session, p_es->rtsp_id,
+                           sout_id, ssrc, seq_init);
+}
+
+/* Remove references to the RTP id from its RTSP track */
+void vod_detach_id(vod_media_t *p_media, const char *psz_session,
+                   sout_stream_id_t *sout_id)
+{
+    RtspTrackDetach(p_media->rtsp, psz_session, sout_id);
+}
+
diff --git a/src/input/vlm.c b/src/input/vlm.c
index efec710..36f778d 100644
--- a/src/input/vlm.c
+++ b/src/input/vlm.c
@@ -974,6 +974,12 @@ static int vlm_ControlMediaInstanceStart( vlm_t *p_vlm, int64_t id, const char *
         if( p_instance->p_input )
         {
             var_AddCallback( p_instance->p_input, "intf-event", InputEvent, p_media );
+            var_Create( p_instance->p_input, "vod-media", VLC_VAR_ADDRESS );
+            var_SetAddress( p_instance->p_input, "vod-media",
+                            p_media->vod.p_media );
+            var_Create( p_instance->p_input, "vod-session", VLC_VAR_STRING );
+            var_SetString( p_instance->p_input, "vod-session", psz_id );
+
             if( input_Start( p_instance->p_input ) != VLC_SUCCESS )
             {
                 var_DelCallback( p_instance->p_input, "intf-event", InputEvent, p_media );


Regards,

-- 
Pierre Ynard
"Une âme dans un corps, c'est comme un dessin sur une feuille de papier."



More information about the vlc-devel mailing list