[vlc-devel] [PATCH 2/2] Add Smooth Streaming module

Frédéric Yhuel fyhuel at viotech.net
Thu Sep 6 11:38:55 CEST 2012


Test it:

***** Live *****
- http://demo.anevia.com:3128/live/disk1/live1/ss-ss/manifest
- http://demo.anevia.com:3129/live/disk1/live2/ss-ss/manifest

***** VOD *****
- http://demo.anevia.com:3131/vod/disk1/content2/ss-ss/Manifest
- http://demo.anevia.com:3130/vod/disk1/content1/ss-ss/Manifest

This one is WVC1/WMAP content, it kinda works (one over two video frame
has wrong PTS, as if sample-composition-time-offsets information was
missing):
- http://mediadl.microsoft.com/mediadl/iisnet/smoothmedia/Experience/BigBuckBunny_720p.ism/Manifest
---
 modules/stream_filter/Modules.am          |    4 +
 modules/stream_filter/smooth/downloader.c |  690 +++++++++++++++++++++++++
 modules/stream_filter/smooth/smooth.c     |  776 +++++++++++++++++++++++++++++
 modules/stream_filter/smooth/smooth.h     |  181 +++++++
 modules/stream_filter/smooth/utils.c      |  201 ++++++++
 5 files changed, 1852 insertions(+)
 create mode 100644 modules/stream_filter/smooth/downloader.c
 create mode 100644 modules/stream_filter/smooth/smooth.c
 create mode 100644 modules/stream_filter/smooth/smooth.h
 create mode 100644 modules/stream_filter/smooth/utils.c

diff --git a/modules/stream_filter/Modules.am b/modules/stream_filter/Modules.am
index c258add..71453d2 100644
--- a/modules/stream_filter/Modules.am
+++ b/modules/stream_filter/Modules.am
@@ -2,9 +2,13 @@ SUBDIRS = dash
 
 SOURCES_decomp = decomp.c
 SOURCES_stream_filter_record = record.c
+SOURCES_stream_filter_smooth = libmp4.h smooth/smooth.c \
+    smooth/utils.c smooth/smooth.h smooth/downloader.c \
+    $(NULL)
 
 libvlc_LTLIBRARIES += \
    libstream_filter_record_plugin.la \
+   libstream_filter_smooth_plugin.la \
    $(NULL)
 
 if HAVE_GCRYPT
diff --git a/modules/stream_filter/smooth/downloader.c b/modules/stream_filter/smooth/downloader.c
new file mode 100644
index 0000000..37dd8d1
--- /dev/null
+++ b/modules/stream_filter/smooth/downloader.c
@@ -0,0 +1,690 @@
+/*****************************************************************************
+ * downloader.c: download thread
+ *****************************************************************************
+ * Copyright (C) 1996-2012 VLC authors and VideoLAN
+ * $Id$
+ *
+ * Author: Frédéric Yhuel <fyhuel _AT_ viotech _DOT_ net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include <vlc_common.h>
+#include <assert.h>
+#include <vlc_stream.h>
+#include <vlc_es.h>
+
+#include "smooth.h"
+#include "../../demux/mp4/libmp4.h"
+
+static char *ConstructUrl( const char *template, const char *base_url,
+        const uint64_t bandwidth, const uint64_t start_time )
+{
+    char *frag, *end, *qual;
+    char *url_template = strdup( template );
+    char *saveptr = NULL;
+
+    qual = strtok_r( url_template, "{", &saveptr );
+    strtok_r( NULL, "}", &saveptr );
+    frag = strtok_r( NULL, "{", &saveptr );
+    strtok_r( NULL, "}", &saveptr );
+    end = strtok_r( NULL, "", &saveptr );
+    char *url = NULL;
+
+    if( asprintf( &url, "%s/%s%"PRIu64"%s%"PRIu64"%s", base_url, qual,
+                bandwidth, frag, start_time, end) < 0 )
+    {
+       return NULL;
+    }
+
+    free( url_template );
+    return url;
+}
+
+static chunk_t * chunk_Get( sms_stream_t *sms, const int64_t start_time )
+{
+    int len = vlc_array_count( sms->chunks );
+    for( int i = 0; i < len; i++ )
+    {
+        chunk_t * chunk = vlc_array_item_at_index( sms->chunks, i );
+        if( !chunk ) return NULL;
+
+        if( chunk->start_time <= start_time &&
+                chunk->start_time + chunk->duration > start_time )
+        {
+            return chunk;
+        }
+    }
+    return NULL;
+}
+
+static unsigned set_track_id( chunk_t *chunk, const unsigned tid )
+{
+    uint32_t size, type;
+    assert( chunk->data );
+    uint8_t *slice = chunk->data->p_buffer;
+    assert( slice );
+
+    SMS_GET4BYTES( size );
+    SMS_GETFOURCC( type );
+    assert( type == ATOM_moof );
+
+    SMS_GET4BYTES( size );
+    SMS_GETFOURCC( type );
+    assert( type == ATOM_mfhd );
+    slice += size - 8;
+
+    SMS_GET4BYTES( size );
+    SMS_GETFOURCC( type );
+    assert( type == ATOM_traf );
+
+    SMS_GET4BYTES( size );
+    SMS_GETFOURCC( type );
+    assert( type == ATOM_tfhd );
+
+    unsigned ret = bswap32( ((uint32_t *)slice)[1] );
+    ((uint32_t *)slice)[1] = bswap32( tid );
+
+    return ret;
+}
+
+static int sms_Download( stream_t *s, chunk_t *chunk, char *url )
+{
+    stream_sys_t *p_sys = s->p_sys;
+
+    stream_t *p_ts = stream_UrlNew( s, url );
+    free( url );
+    if( p_ts == NULL )
+        return VLC_EGENERIC;
+
+    int64_t size = stream_Size( p_ts );
+
+    chunk->size = size;
+    chunk->offset = p_sys->download.next_chunk_offset;
+    p_sys->download.next_chunk_offset += chunk->size;
+
+    chunk->data = block_Alloc( size );
+
+    if( chunk->data == NULL )
+    {
+        stream_Delete( p_ts );
+        return VLC_ENOMEM;
+    }
+
+    int read = stream_Read( p_ts, chunk->data->p_buffer, size );
+    if( read < size )
+    {
+        msg_Warn( s, "sms_Download: I requested %"PRIi64" bytes, "\
+                "but I got only %i", size, read );
+        chunk->data = block_Realloc( chunk->data, 0, read );
+    }
+
+    stream_Delete( p_ts );
+
+    vlc_mutex_lock( &p_sys->download.lock_wait );
+
+    if( chunk->type == AUDIO_ES )
+        p_sys->download.alead += chunk->duration;
+    else if( chunk->type == VIDEO_ES )
+        p_sys->download.vlead += chunk->duration;
+    else if( chunk->type == SPU_ES )
+        p_sys->download.tlead += chunk->duration;
+
+    vlc_mutex_unlock( &p_sys->download.lock_wait );
+
+    return VLC_SUCCESS;
+}
+
+#ifdef DISABLE_BANDWIDTH_ADAPTATION
+static unsigned BandwidthAdaptation( stream_t *s,
+        sms_stream_t *sms, uint64_t *bandwidth )
+{
+    return sms->download_qlvl;
+}
+#else
+
+static unsigned BandwidthAdaptation( stream_t *s,
+        sms_stream_t *sms, uint64_t bandwidth )
+{
+    if( sms->type != VIDEO_ES )
+        return sms->download_qlvl;
+
+    uint64_t bw_candidate = 0;
+    quality_level_t *qlevel;
+    unsigned ret = sms->download_qlvl;
+    //msg_Dbg( s, "bw is %"PRIu64"", *bandwidth );
+
+    for( unsigned i = 0; i < sms->qlevel_nb; i++ )
+    {
+        qlevel = vlc_array_item_at_index( sms->qlevels, i );
+        assert( qlevel );
+
+        if( qlevel->Bitrate < (bandwidth - bandwidth / 3) &&
+                qlevel->Bitrate > bw_candidate )
+        {
+            bw_candidate = qlevel->Bitrate;
+            ret = qlevel->id;
+        }
+    }
+
+    return ret;
+}
+#endif
+
+static int get_new_chunks( stream_t *s, chunk_t *ck )
+{
+    stream_sys_t *p_sys = s->p_sys;
+
+    uint8_t *slice = ck->data->p_buffer;
+    assert( slice );
+    uint8_t version, fragment_count;
+    uint32_t size, type, flags;
+    sms_stream_t *sms;
+    UUID_t uuid;
+    TfrfBoxDataFields_t *tfrf_df;
+
+    if( ck->type == AUDIO_ES )
+        sms = p_sys->astream;
+    else if ( ck->type == VIDEO_ES )
+        sms = p_sys->vstream;
+    else
+        return 0;
+
+    SMS_GET4BYTES( size );
+    SMS_GETFOURCC( type );
+    assert( type == ATOM_moof );
+
+    SMS_GET4BYTES( size );
+    SMS_GETFOURCC( type );
+    assert( type == ATOM_mfhd );
+    slice += size - 8;
+
+    SMS_GET4BYTES( size );
+    SMS_GETFOURCC( type );
+    assert( type == ATOM_traf );
+
+    for(;;)
+    {
+        SMS_GET4BYTES( size );
+        assert( size > 1 );
+        SMS_GETFOURCC( type );
+        if( type == ATOM_mdat )
+        {
+            msg_Err( s, "No uuid box found :-(" );
+            return VLC_EGENERIC;
+        }
+        else if( type == ATOM_uuid )
+        {
+            GetUUID( &uuid, slice);
+            if( !CmpUUID( &uuid, &TfrfBoxUUID ) )
+                break;
+        }
+        slice += size - 8;
+    }
+
+    slice += 16;
+    SMS_GET1BYTE( version );
+    SMS_GET3BYTES( flags );
+    SMS_GET1BYTE( fragment_count );
+
+    tfrf_df = calloc( fragment_count, sizeof( TfrfBoxDataFields_t ) );
+    if( unlikely( tfrf_df == NULL ) )
+        return VLC_EGENERIC;
+
+    for( uint8_t i = 0; i < fragment_count; i++ )
+    {
+        SMS_GET4or8BYTES( tfrf_df[i].i_fragment_abs_time );
+        SMS_GET4or8BYTES( tfrf_df[i].i_fragment_duration );
+    }
+
+    msg_Dbg( s, "read box: \"tfrf\" version %d, flags 0x%x, "\
+            "fragment count %"PRIu8, version, flags, fragment_count );
+
+    for( uint8_t i = 0; i < fragment_count; i++ )
+    {
+        int64_t dur = tfrf_df[i].i_fragment_duration;
+        int64_t stime = tfrf_df[i].i_fragment_abs_time;
+        msg_Dbg( s, "\"tfrf\" fragment duration %"PRIu64", "\
+                    "fragment abs time %"PRIu64, dur, stime);
+
+        if( !chunk_Get( sms, stime + dur ) )
+            chunk_New( sms, dur, stime );
+    }
+    free( tfrf_df );
+
+    return VLC_SUCCESS;
+}
+
+#define STRA_SIZE 338
+#define SMOO_SIZE (STRA_SIZE * 3 + 24) /* 1038 */
+
+/* SmooBox is a very simple MP4 box, used only to pass information
+ * to the demux layer. As this box is not aimed to travel accross networks,
+ * simplicity of the design is better than compactness */
+static int build_smoo_box( stream_t *s, uint8_t *smoo_box )
+{
+    stream_sys_t *p_sys = s->p_sys;
+    sms_stream_t *sms = NULL;
+    bool disable_audio = var_InheritBool( s, "smooth-disable-audio" );
+
+    /* smoo */
+    memset( smoo_box, 0, SMOO_SIZE );
+    smoo_box[2] = (SMOO_SIZE & 0xff00)>>8;
+    smoo_box[3] = SMOO_SIZE & 0xff;
+    smoo_box[4] = 'u';
+    smoo_box[5] = 'u';
+    smoo_box[6] = 'i';
+    smoo_box[7] = 'd';
+
+    /* UUID is e1da72ba-24d7-43c3-a6a5-1b5759a1a92c */
+    ((uint32_t *)smoo_box)[2] = bswap32( 0xe1da72ba );
+    ((uint32_t *)smoo_box)[3] = bswap32( 0x24d743c3 );
+    ((uint32_t *)smoo_box)[4] = bswap32( 0xa6a51b57 );
+    ((uint32_t *)smoo_box)[5] = bswap32( 0x59a1a92c );
+
+    uint8_t *stra_box;
+    for( int i = 0; i < 3; i++ )
+    {
+        sms = NULL;
+        stra_box = smoo_box + i * STRA_SIZE;
+
+        stra_box[26] = (STRA_SIZE & 0xff00)>>8;
+        stra_box[27] = STRA_SIZE & 0xff;
+        stra_box[28] = 'u';
+        stra_box[29] = 'u';
+        stra_box[30] = 'i';
+        stra_box[31] = 'd';
+
+        /* UUID is b03ef770-33bd-4bac-96c7-bf25f97e2447 */
+        ((uint32_t *)stra_box)[8] = bswap32( 0xb03ef770 );
+        ((uint32_t *)stra_box)[9] = bswap32( 0x33bd4bac );
+        ((uint32_t *)stra_box)[10] = bswap32( 0x96c7bf25 );
+        ((uint32_t *)stra_box)[11] = bswap32( 0xf97e2447 );
+
+        if( i == 0)
+        {
+            stra_box[48] = VIDEO_ES;
+            sms = p_sys->vstream;
+        }
+        else if( i == 1 )
+        {
+            stra_box[48] = AUDIO_ES;
+            if( !disable_audio )
+                sms = p_sys->astream;
+        }
+        else if( i == 2 )
+        {
+            stra_box[48] = SPU_ES;
+            sms = p_sys->tstream;
+        }
+
+        stra_box[49] = 0; /* reserved */
+        if( sms == NULL )
+            continue;
+        stra_box[50] = (sms->id & 0xff00)>>8;
+        stra_box[51] = sms->id & 0xff;
+
+        ((uint32_t *)stra_box)[13] = bswap32( sms->timescale );
+        ((uint64_t *)stra_box)[7] = bswap64( p_sys->vod_duration );
+
+        quality_level_t * qlvl = get_qlevel( sms, sms->download_qlvl );
+
+        ((uint32_t *)stra_box)[16] = bswap32( qlvl->FourCC );
+        ((uint32_t *)stra_box)[17] = bswap32( qlvl->Bitrate );
+        ((uint32_t *)stra_box)[18] = bswap32( qlvl->MaxWidth );
+        ((uint32_t *)stra_box)[19] = bswap32( qlvl->MaxHeight );
+        ((uint32_t *)stra_box)[20] = bswap32( qlvl->SamplingRate );
+        ((uint32_t *)stra_box)[21] = bswap32( qlvl->Channels );
+        ((uint32_t *)stra_box)[22] = bswap32( qlvl->BitsPerSample );
+        ((uint32_t *)stra_box)[23] = bswap32( qlvl->PacketSize );
+        ((uint32_t *)stra_box)[24] = bswap32( qlvl->AudioTag );
+        ((uint16_t *)stra_box)[50] = bswap16( qlvl->nBlockAlign );
+
+        stra_box[102] = stra_box[103] = stra_box[104] = 0; /* reserved */
+        assert( strlen( qlvl->CodecPrivateData ) < 512 );
+        stra_box[105] = strlen( qlvl->CodecPrivateData ) / 2;
+        uint8_t *binary_cpd = decode_string_hex_to_binary( qlvl->CodecPrivateData );
+        memcpy( stra_box + 106, binary_cpd, stra_box[105] );
+        free( binary_cpd );
+    }
+
+    return VLC_SUCCESS;
+}
+
+static chunk_t *build_init_chunk( stream_t *s )
+{
+    chunk_t *ret = calloc( 1, sizeof( chunk_t ) );
+    if( unlikely( ret == NULL ) )
+        return NULL;
+
+    ret->size = SMOO_SIZE;
+    ret->data = block_Alloc( SMOO_SIZE );
+    if( !ret->data )
+        return NULL;
+
+    build_smoo_box( s, ret->data->p_buffer );
+
+    return ret;
+}
+
+static int Download( stream_t *s, sms_stream_t *sms )
+{
+    stream_sys_t *p_sys = s->p_sys;
+
+    int64_t start_time;
+
+    if( sms->type == AUDIO_ES )
+        start_time = p_sys->download.alead;
+    else if ( sms->type == VIDEO_ES )
+        start_time = p_sys->download.vlead;
+    else
+    {
+        return VLC_EGENERIC;
+    }
+
+    quality_level_t *qlevel = get_qlevel( sms, sms->download_qlvl );
+    assert( qlevel );
+
+    chunk_t *chunk = chunk_Get( sms, start_time );
+    if( !chunk )
+    {
+        msg_Warn( s, "Could not find a chunk for stream %s, "\
+                "start time = %"PRIu64"", sms->name, start_time );
+        return VLC_EGENERIC;
+    }
+    if( chunk->data != NULL )
+    {
+        /* Segment already downloaded */
+        msg_Warn( s, "Segment already downloaded" );
+        return VLC_SUCCESS;
+    }
+
+    chunk->type = sms->type;
+
+    char *url = ConstructUrl( sms->url_template, p_sys->base_url,
+                                  qlevel->Bitrate, chunk->start_time );
+
+    /* sanity check - can we download this chunk on time? */
+    uint64_t avg_bw = sms_queue_avg( p_sys->bws );
+    if( (avg_bw > 0) && (qlevel->Bitrate > 0) )
+    {
+        /* duration in ms */
+        unsigned chunk_duration = chunk->duration * 1000 / sms->timescale;
+        uint64_t size = chunk_duration * qlevel->Bitrate / 1000; /* bits */
+        unsigned estimated = size * 1000 / avg_bw;
+        if( estimated > chunk_duration )
+        {
+            msg_Warn( s,"downloading of chunk %d would take %d ms, "\
+                    "which is longer than its playback (%d ms)",
+                        chunk->sequence, estimated, chunk_duration );
+        }
+    }
+
+    mtime_t start = mdate();
+    if( sms_Download( s, chunk, url ) != VLC_SUCCESS )
+    {
+        msg_Err( s, "downloaded chunk %u from stream %s at quality\
+            %u failed", chunk->sequence, sms->name, qlevel->Bitrate );
+        return VLC_EGENERIC;
+    }
+    mtime_t duration = mdate() - start;
+
+    unsigned real_id = set_track_id( chunk, sms->id );
+    if( real_id == 0)
+    {
+        msg_Err( s, "tfhd box not found or invalid chunk" );
+        return VLC_EGENERIC;
+    }
+
+    //msg_Dbg( s, "chunk ID was %i and is now %i", real_id, sms->id );
+
+    if( p_sys->b_live )
+        get_new_chunks( s, chunk );
+
+    vlc_mutex_lock( &p_sys->download.lock_wait );
+    vlc_array_append( p_sys->download.chunks, chunk );
+    vlc_cond_signal( &p_sys->download.wait );
+    vlc_mutex_unlock( &p_sys->download.lock_wait );
+
+    msg_Info( s, "downloaded chunk %d from stream %s at quality %u",
+                chunk->sequence, sms->name, qlevel->Bitrate );
+
+    uint64_t actual_lead = chunk->start_time + chunk->duration;
+    if( sms->type == AUDIO_ES )
+    {
+        p_sys->download.aindex = chunk->sequence;
+        p_sys->download.alead = __MIN( p_sys->download.alead, actual_lead );
+    }
+    else if( sms->type == VIDEO_ES )
+    {
+        p_sys->download.vindex = chunk->sequence;
+        p_sys->download.vlead = __MIN( p_sys->download.vlead, actual_lead );
+        p_sys->playback.toffset = __MIN( p_sys->playback.toffset, (uint64_t)chunk->start_time );
+    }
+    else if( sms->type == SPU_ES )
+    {
+        p_sys->download.sindex = chunk->sequence;
+        p_sys->download.tlead = __MIN( p_sys->download.tlead, actual_lead );
+    }
+
+    unsigned dur_ms = __MAX( 1, duration / 1000 );
+    uint64_t bw = chunk->size * 8 * 1000 / dur_ms; /* bits / s */
+    sms_queue_put( p_sys->bws, bw );
+    avg_bw = sms_queue_avg( p_sys->bws );
+
+    if( sms->type != VIDEO_ES )
+        return VLC_SUCCESS;
+
+    /* Track could get disabled in mp4 demux if we trigger adaption too soon. */
+    if( chunk->sequence <= 1 )
+        return VLC_SUCCESS;
+
+    unsigned new_qlevel_id = BandwidthAdaptation( s, sms, avg_bw );
+    quality_level_t *new_qlevel = get_qlevel( sms, new_qlevel_id );
+    assert( new_qlevel );
+
+    if( new_qlevel->Bitrate != qlevel->Bitrate )
+    {
+        msg_Warn( s, "detected %s bandwidth (%u) stream",
+                 (new_qlevel->Bitrate >= qlevel->Bitrate) ? "faster" : "lower",
+                 new_qlevel->Bitrate );
+
+        sms->download_qlvl = new_qlevel_id;
+    }
+
+    if( new_qlevel->MaxWidth != qlevel->MaxWidth ||
+        new_qlevel->MaxHeight != qlevel->MaxHeight )
+    {
+        chunk_t *new_init_ck = build_init_chunk( s );
+        assert( new_init_ck );
+
+        new_init_ck->offset = p_sys->download.next_chunk_offset;
+        p_sys->download.next_chunk_offset += new_init_ck->size;
+
+        vlc_mutex_lock( &p_sys->download.lock_wait );
+        vlc_array_append( p_sys->download.chunks, new_init_ck );
+        vlc_mutex_unlock( &p_sys->download.lock_wait );
+    }
+    return VLC_SUCCESS;
+}
+
+void* sms_Thread( void *p_this )
+{
+    stream_t *s = (stream_t *)p_this;
+    stream_sys_t *p_sys = s->p_sys;
+
+    sms_stream_t *vsms = p_sys->vstream;
+    sms_stream_t *asms = p_sys->astream;
+    assert( vsms ); assert( asms );
+
+    /* We compute the average bandwidth of the 4 last downloaded
+     * chunks, but feel free to replace '4' by whatever you wish */
+    p_sys->bws = sms_queue_init( 4 );
+
+    chunk_t *init_ck = build_init_chunk( s );
+    assert( init_ck );
+
+    vlc_mutex_lock( &p_sys->download.lock_wait );
+    vlc_array_append( p_sys->download.chunks, init_ck );
+    vlc_mutex_unlock( &p_sys->download.lock_wait );
+
+    p_sys->download.next_chunk_offset = init_ck->size;
+
+    int canc = vlc_savecancel();
+
+    chunk_t *video_chunk = vlc_array_item_at_index( vsms->chunks, 0 );
+    chunk_t *audio_chunk = vlc_array_item_at_index( asms->chunks, 0 );
+    assert( video_chunk ); assert( audio_chunk );
+
+    /* Sometimes, the video stream is cut into pieces of one exact length,
+     * while the audio stream fragments can't be made to match exactly,
+     * and for some reason the n^th advertised video fragment is related to
+     * the n+1^th advertised audio chunk or vice versa */
+    int64_t amid = audio_chunk->duration / 2;
+    int64_t vmid = video_chunk->duration / 2;
+
+    if( audio_chunk->start_time > video_chunk->start_time + vmid )
+    {
+        video_chunk = vlc_array_item_at_index( vsms->chunks, 1 );
+    }
+    else if ( video_chunk->start_time > audio_chunk->start_time + amid )
+    {
+        audio_chunk = vlc_array_item_at_index( asms->chunks, 1 );
+    }
+
+    bool disable_audio = var_InheritBool( s, "smooth-disable-audio" );
+    if( p_sys->b_live )
+    {
+        p_sys->download.vlead = video_chunk->start_time + p_sys->timescale / 1000;
+        if( disable_audio )
+            p_sys->download.alead = UINT64_MAX;
+        else
+            p_sys->download.alead = audio_chunk->start_time + p_sys->timescale / 1000;
+    }
+
+    if( Download( s, vsms ) != VLC_SUCCESS )
+    {
+        goto cancel;
+    }
+    if( !disable_audio )
+    {
+        if( Download( s, asms ) != VLC_SUCCESS )
+            goto cancel;
+    }
+
+    int64_t lead = 0;
+
+    while( 1 )
+    {
+        /* XXX replace magic number 20 by a value depending on
+         * LookAheadFragmentCount and DVRWindowLength */
+        vlc_mutex_lock( &p_sys->download.lock_wait );
+
+        if( p_sys->b_close )
+        {
+            vlc_mutex_unlock( &p_sys->download.lock_wait );
+            break;
+        }
+
+        lead = __MIN( p_sys->download.vlead, p_sys->download.alead )
+            - p_sys->playback.toffset;
+
+        while( (lead > 10 * p_sys->timescale + video_chunk->start_time) ||
+                /* If there is no new chunk to process, we wait */
+                (
+                    !p_sys->b_live &&
+                    p_sys->download.aindex >= (asms->vod_chunks_nb -1) &&
+                    p_sys->download.vindex >= (vsms->vod_chunks_nb - 1)
+                )
+             )
+        {
+#if 0
+            msg_Info( s, "sms_Thread is waiting!" );
+            msg_Info( s, "toffset is %"PRIu64" vlead is %"PRIu64", alead is %"PRIu64", "\
+                    "and lead is %"PRIi64,
+                    p_sys->playback.toffset,
+                    p_sys->download.vlead - video_chunk->start_time,
+                    p_sys->download.alead - video_chunk->start_time,
+                    lead );
+#endif
+            vlc_cond_wait( &p_sys->download.wait, &p_sys->download.lock_wait );
+            lead = __MIN( p_sys->download.vlead, p_sys->download.alead )
+                - p_sys->playback.toffset;
+
+            if( p_sys->b_close )
+                break;
+        }
+
+        if( p_sys->b_tseek )
+        {
+            int count = vlc_array_count( p_sys->download.chunks );
+            chunk_t *ck = NULL;
+            for( int i = 0; i < count; i++ )
+            {
+                ck = vlc_array_item_at_index( p_sys->download.chunks, i );
+                assert( ck );
+                ck->read_pos = 0;
+                if( ck->data == NULL )
+                    continue;
+                block_Release( ck->data );
+                ck->data = NULL;
+            }
+
+            vlc_array_destroy( p_sys->download.chunks );
+            p_sys->download.chunks = vlc_array_new();
+
+            p_sys->playback.toffset = p_sys->time_pos;
+            p_sys->download.vlead = p_sys->download.alead = p_sys->time_pos;
+            p_sys->download.aindex = p_sys->download.vindex = 0;
+            p_sys->download.next_chunk_offset = 0;
+
+            p_sys->playback.boffset = 0;
+            p_sys->playback.index = 0;
+
+            chunk_t *new_init_ck = build_init_chunk( s );
+            assert( new_init_ck );
+
+            new_init_ck->offset = p_sys->download.next_chunk_offset;
+            p_sys->download.next_chunk_offset += new_init_ck->size;
+
+            vlc_array_append( p_sys->download.chunks, new_init_ck );
+            p_sys->b_tseek = false;
+        }
+        vlc_mutex_unlock( &p_sys->download.lock_wait );
+
+        if( p_sys->download.alead < p_sys->download.vlead )
+        {
+            if( Download( s, asms ) != VLC_SUCCESS )
+                    break;
+        }
+        else if( p_sys->download.vlead <= p_sys->download.alead )
+        {
+            if( Download( s, vsms ) != VLC_SUCCESS )
+                    break;
+        }
+        /* let reader thread breath. That avoids video track
+         * to get de-selected in certain cases */
+        //msleep( 100000 );
+    }
+
+cancel:
+    p_sys->b_error = true;
+    msg_Warn(s, "Canceling download thread!");
+    vlc_restorecancel( canc );
+    return NULL;
+}
diff --git a/modules/stream_filter/smooth/smooth.c b/modules/stream_filter/smooth/smooth.c
new file mode 100644
index 0000000..9296fd7
--- /dev/null
+++ b/modules/stream_filter/smooth/smooth.c
@@ -0,0 +1,776 @@
+/*****************************************************************************
+ * smooth.c: Smooth Streaming stream filter
+ *****************************************************************************
+ * Copyright (C) 1996-2012 VLC authors and VideoLAN
+ * $Id$
+ *
+ * Author: Frédéric Yhuel <fyhuel _AT_ viotech _DOT_ net>
+ * Heavily inspired by HLS module of Jean-Paul Saman
+ * <jpsaman _AT_ videolan _DOT_ org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <limits.h>
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+
+#include <assert.h>
+#include <inttypes.h>
+
+#include <vlc_xml.h>
+#include <vlc_charset.h>
+#include <vlc_stream.h>
+#include <vlc_es.h>
+#include <vlc_codecs.h>
+
+#include "smooth.h"
+#include "../../demux/mp4/libmp4.h"
+
+#define DA_TEXT N_("Disable audio stream")
+#define DA_LONGTEXT N_("Disable audio stream")
+
+/* I make the assumption that when the demux want to do a *time* seek,
+ * then p_sys->download->boffset > FAKE_STREAM_SIZE, and thus FAKE_STREAM_SIZE
+ * should be small enough. 1000 seems to be a sensible choice. See also
+ * chunk_Seek() comments to understand properly */
+#define FAKE_STREAM_SIZE 1000
+/*****************************************************************************
+ * Module descriptor
+ *****************************************************************************/
+static int  Open( vlc_object_t * );
+static void Close( vlc_object_t * );
+
+vlc_module_begin()
+    set_category( CAT_INPUT )
+    set_subcategory( SUBCAT_INPUT_STREAM_FILTER )
+    set_description( N_("Smooth Streaming") )
+    set_shortname( "Smooth Streaming")
+    add_shortcut( "smooth" )
+    set_capability( "stream_filter", 30 )
+    set_callbacks( Open, Close )
+    add_bool( "smooth-disable-audio", false, DA_TEXT, DA_LONGTEXT, false )
+        change_safe()
+vlc_module_end()
+
+static int   Read( stream_t *, void *, unsigned );
+static int   Peek( stream_t *, const uint8_t **, unsigned );
+static int   Control( stream_t *, int , va_list );
+
+static bool isSmoothStreaming( stream_t *s )
+{
+    const uint8_t *peek;
+    char *peeked = malloc( 512 );
+    if( unlikely( !peeked ) )
+        return false;
+    const char *needle = "<SmoothStreamingMedia";
+    const char *encoding = NULL;
+    bool ret = false;
+
+    int i_size = stream_Peek( s->p_source, &peek, 512 );
+    if(  i_size < 512 )
+    {
+        free( peeked );
+        return false;
+    }
+
+    memcpy( peeked, peek, 512 );
+    peeked[511] = peeked[510] = '\0';
+
+    if( strstr( (const char *)peeked, needle ) != NULL )
+        ret = true;
+    else
+    /* maybe it's utf-16 encoding, should we also test other encodings? */
+    {
+        if( !memcmp( peeked, "\xFF\xFE", 2 ) )
+            encoding = "UTF-16LE";
+        else if( !memcmp( peeked, "\xFE\xFF", 2 ) )
+            encoding = "UTF-16BE";
+        else
+        {
+            free( peeked );
+            return false;
+        }
+        peeked = FromCharset( encoding, peeked, 512 );
+
+        if( strstr( peeked, needle ) != NULL )
+            ret = true;
+    }
+    free( peeked );
+    return ret;
+}
+
+#if 0
+static void print_chunk( stream_t *s, chunk_t *ck )
+{
+    msg_Info( s, "chunk %u type %i: duration is %"PRIu64", stime is %"PRIu64", "\
+            "size is %i, offset is %"PRIu64", read_pos is %i.",
+            ck->sequence, ck->type, ck->duration,
+            ck->start_time, ck->size, ck->offset, ck->read_pos );
+}
+#endif
+
+static int parse_Manifest( stream_t *s )
+{
+    stream_sys_t *p_sys = s->p_sys;
+    xml_t *vlc_xml = NULL;
+    xml_reader_t *vlc_reader = NULL;
+    int type = UNKNOWN_ES;
+    const char *name, *value;
+    stream_t *st = s->p_source;
+    msg_Dbg( s, "Manifest parsing\n" );
+
+    vlc_xml = xml_Create( st );
+    if( !vlc_xml )
+    {
+        msg_Err( s, "Failed to open XML parser" );
+        return VLC_EGENERIC;
+    }
+
+    vlc_reader = xml_ReaderCreate( vlc_xml, st );
+    if( !vlc_reader )
+    {
+        msg_Err( s, "Failed to open source for parsing" );
+        xml_Delete( vlc_xml );
+        return VLC_EGENERIC;
+    }
+
+    const char *node;
+    char *stream_name = NULL;
+    uint8_t *WaveFormatEx;
+    int stream_type = UNKNOWN_ES;
+    sms_stream_t *sms = NULL;
+    quality_level_t *ql = NULL;
+    int64_t start_time = 0, duration = 0;
+    int64_t computed_start_time = 0, computed_duration = 0;
+    unsigned next_track_id = 1;
+    unsigned next_qid = 1;
+    int loop_count = 0;
+    bool b_weird = false;
+
+#define TIMESCALE 10000000
+    while( (type = xml_ReaderNextNode( vlc_reader, &node )) > 0 )
+    {
+        switch( type )
+        {
+            case XML_READER_STARTELEM:
+
+                if( !strcmp( node, "SmoothStreamingMedia" ) )
+                {
+                    while( (name = xml_ReaderNextAttr( vlc_reader, &value )) )
+                    {
+                        if( !strcmp( name, "Duration" ) )
+                            p_sys->vod_duration = strtoull( value, NULL, 10 );
+                        if( !strcmp( name, "TimeScale" ) )
+                            p_sys->timescale = strtoull( value, NULL, 10 );
+                    }
+                    if( !p_sys->timescale )
+                        p_sys->timescale = TIMESCALE;
+                }
+
+
+                if( !strcmp( node, "StreamIndex" ) )
+                {
+                    sms = sms_New();
+                    if( unlikely( !sms ) )
+                        return VLC_ENOMEM;
+                    sms->id = next_track_id;
+                    next_track_id++;
+
+                    while( (name = xml_ReaderNextAttr( vlc_reader, &value )) )
+                    {
+                        if( !strcmp( name, "Type" ) )
+                        {
+                            if( !strcmp( value, "video" ) )
+                                stream_type = VIDEO_ES;
+                            else if( !strcmp( value, "audio" ) )
+                                stream_type = AUDIO_ES;
+                            else if( !strcmp( value, "text" ) )
+                                stream_type = SPU_ES;
+                        }
+
+                        if( !strcmp( name, "Name" ) )
+                            stream_name = strdup( value );
+                        if( !strcmp( name, "TimeScale" ) )
+                            sms->timescale = strtoull( value, NULL, 10 );
+
+                        if( !strcmp( name, "Chunks" ) )
+                        {
+                            sms->vod_chunks_nb = strtol( value, NULL, 10 );
+                            if( sms->vod_chunks_nb == 0 ) /* live */
+                                sms->vod_chunks_nb = UINT32_MAX;
+                        }
+
+                        if( !strcmp( name, "QualityLevels" ) )
+                            sms->qlevel_nb = strtoul( value, NULL, 10 );
+                        if( !strcmp( name, "Url" ) )
+                            sms->url_template = strdup(value);
+                    }
+
+                    if( sms && !sms->timescale )
+                        sms->timescale = TIMESCALE;
+                    if( !stream_name )
+                    {
+                        if( stream_type == VIDEO_ES )
+                            stream_name = strdup( "video" );
+                        else if( stream_type == AUDIO_ES )
+                            stream_name = strdup( "audio" );
+                        else if( stream_type == SPU_ES )
+                            stream_name = strdup( "text" );
+                    }
+
+                    sms->name = stream_name;
+                    sms->type = stream_type;
+                    vlc_array_append( p_sys->sms_streams, sms );
+                }
+
+                if( !strcmp( node, "QualityLevel" ) )
+                {
+                    ql = ql_New();
+                    if( !ql )
+                        return VLC_ENOMEM;
+                    ql->id = next_qid;
+                    next_qid++;
+                    while( (name = xml_ReaderNextAttr( vlc_reader, &value )) )
+                    {
+                        if( !strcmp( name, "Index" ) )
+                            ql->Index = strtol( value, NULL, 10 );
+                        if( !strcmp( name, "Bitrate" ) )
+                            ql->Bitrate = strtoull( value, NULL, 10 );
+                        if( !strcmp( name, "FourCC" ) )
+                            ql->FourCC = VLC_FOURCC( value[0], value[1],
+                                                     value[2], value[3] );
+                        if( !strcmp( name, "CodecPrivateData" ) )
+                            ql->CodecPrivateData = strdup( value );
+                        if( !strcmp( name, "WaveFormatEx" ) )
+                        {
+                            WaveFormatEx = decode_string_hex_to_binary( value );
+                            uint16_t data_len = ((uint16_t *)WaveFormatEx)[8];
+                            ql->CodecPrivateData = strndup( value + 36, data_len * 2 );
+
+                            uint16_t wf_tag = ((uint16_t *)WaveFormatEx)[0];
+                            wf_tag_to_fourcc( wf_tag, &ql->FourCC, NULL );
+
+                            ql->Channels = ((uint16_t *)WaveFormatEx)[1];
+                            ql->SamplingRate = ((uint32_t *)WaveFormatEx)[1];
+                            ql->nBlockAlign = ((uint16_t *)WaveFormatEx)[6];
+                            ql->BitsPerSample = ((uint16_t *)WaveFormatEx)[7];
+                            free( WaveFormatEx );
+                        }
+                        if( !strcmp( name, "MaxWidth" ) || !strcmp( name, "Width" ) )
+                            ql->MaxWidth = strtoul( value, NULL, 10 );
+                        if( !strcmp( name, "MaxHeight" ) || !strcmp( name, "Height" ) )
+                            ql->MaxHeight = strtoul( value, NULL, 10 );
+                        if( !strcmp( name, "Channels" ) )
+                            ql->Channels = strtoul( value, NULL, 10 );
+                        if( !strcmp( name, "SamplingRate" ) )
+                            ql->SamplingRate = strtoul( value, NULL, 10 );
+                        if( !strcmp( name, "BitsPerSample" ) )
+                            ql->BitsPerSample = strtoul( value, NULL, 10 );
+                    }
+                    vlc_array_append( sms->qlevels, ql );
+                }
+
+                if( !strcmp( node, "c" ) )
+                {
+                    loop_count++;
+                    start_time = duration = -1;
+                    while( (name = xml_ReaderNextAttr( vlc_reader, &value )) )
+                    {
+                        if( !strcmp( name, "t" ) )
+                            start_time = strtoull( value, NULL, 10 );
+                        if( !strcmp( name, "d" ) )
+                            duration = strtoull( value, NULL, 10 );
+                    }
+                    if( start_time == -1 )
+                    {
+                        assert( duration != -1 );
+                        computed_start_time += computed_duration;
+                        computed_duration = duration;
+                    }
+                    else if( duration == -1 )
+                    {
+                        assert( start_time != -1 );
+                        /* Handle weird Manifests which give only the start time
+                         * of the first segment. In those cases, we have to look
+                         * at the start time of the second segment to compute
+                         * the duration of the first one. */
+                        if( loop_count == 1 )
+                        {
+                            b_weird = true;
+                            computed_start_time = start_time;
+                            continue;
+                        }
+
+                        computed_duration = start_time - computed_start_time;
+                        if( !b_weird )
+                            computed_start_time = start_time;
+                    }
+                    else
+                    {
+                        if( b_weird )
+                            computed_duration = start_time - computed_start_time;
+                        else
+                        {
+                            computed_start_time = start_time;
+                            computed_duration = duration;
+                        }
+                    }
+
+                    if( unlikely( chunk_New( sms, computed_duration,
+                                        computed_start_time ) == NULL ) )
+                    {
+                        return VLC_ENOMEM;
+                    }
+                    if( b_weird && start_time != -1 )
+                        computed_start_time = start_time;
+                }
+                break;
+
+            case XML_READER_ENDELEM:
+                if( strcmp( node, "StreamIndex" ) )
+                    break;
+
+                stream_name = NULL;
+                stream_type = UNKNOWN_ES;
+                computed_start_time = 0;
+                computed_duration = 0;
+                loop_count = 0;
+                if( b_weird && !chunk_New( sms, computed_duration, computed_start_time ) )
+                    return VLC_ENOMEM;
+
+                b_weird = false;
+                next_qid = 1;
+
+                if( sms->qlevel_nb == 0 )
+                    sms->qlevel_nb = vlc_array_count( sms->qlevels );
+                break;
+
+            case XML_READER_NONE:
+                break;
+            case XML_READER_TEXT:
+                break;
+            default:
+                return VLC_EGENERIC;
+        }
+    }
+#undef TIMESCALE
+
+    xml_ReaderDelete( vlc_reader );
+    xml_Delete( vlc_xml );
+
+    return VLC_SUCCESS;
+}
+
+static int Open( vlc_object_t *p_this )
+{
+    stream_t *s = (stream_t*)p_this;
+    stream_sys_t *p_sys;
+
+    if( !isSmoothStreaming( s ) )
+        return VLC_EGENERIC;
+
+    msg_Info( p_this, "Smooth Streaming (%s)", s->psz_path );
+
+    s->p_sys = p_sys = calloc( 1, sizeof(*p_sys ) );
+    if( unlikely( p_sys == NULL ) )
+        return VLC_ENOMEM;
+
+    char *uri = NULL;
+    if( unlikely( asprintf( &uri, "%s://%s", s->psz_access, s->psz_path ) < 0 ) )
+    {
+        free( p_sys );
+        return VLC_ENOMEM;
+    }
+
+    /* remove the last part of the url */
+    char *pos = strrchr( uri, '/');
+    *pos = '\0';
+    p_sys->base_url = uri;
+
+    bool disable_audio = var_InheritBool( s, "smooth-disable-audio" );
+    p_sys->download.alead = disable_audio ? UINT64_MAX : 0;
+
+    /* XXX I don't know wether or not we should allow caching */
+    p_sys->b_cache = false;
+
+    p_sys->sms_streams = vlc_array_new();
+    p_sys->download.chunks = vlc_array_new();
+    if( unlikely( !p_sys->sms_streams || !p_sys->download.chunks ) )
+    {
+        free( p_sys );
+        return VLC_ENOMEM;
+    }
+
+    /* Parse SMS ismc content. */
+    if( parse_Manifest( s ) != VLC_SUCCESS )
+    {
+        free( p_sys );
+        return VLC_EGENERIC;
+    }
+
+    if( !p_sys->vod_duration )
+       p_sys->b_live = true;
+
+    p_sys->i_tracks = vlc_array_count( p_sys->sms_streams );
+
+    /* FIXME */
+    p_sys->i_selected_tracks = 2; /* one video track and one audio track */
+    if( disable_audio )
+        p_sys->i_selected_tracks -= 1;
+
+    /* Choose first video stream available */
+    sms_stream_t *vsms = NULL;
+    for( unsigned i = 0; i < p_sys->i_tracks; i++ )
+    {
+        vsms = vlc_array_item_at_index( p_sys->sms_streams, i );
+        if( vsms->type == VIDEO_ES )
+        {
+            msg_Dbg( s, "Video stream chosen is %s", vsms->name );
+            break;
+        }
+    }
+    p_sys->vstream = vsms;
+
+    /* Choose first audio stream available */
+    sms_stream_t *asms = NULL;
+    for( unsigned i = 0; i < p_sys->i_tracks; i++ )
+    {
+        asms = vlc_array_item_at_index( p_sys->sms_streams, i );
+        //if( asms->type == AUDIO_ES && !strcmp( asms->name, "audio_eng" ) )
+        if( asms->type == AUDIO_ES )
+        {
+            msg_Dbg( s, "Audio stream chosen is %s", asms->name );
+            break;
+        }
+    }
+    p_sys->astream = asms;
+
+    /* Choose lowest quality for the first chunks */
+    quality_level_t *wanted, *qlvl;
+    sms_stream_t *sms = NULL;
+
+    for( unsigned i = 0; i < p_sys->i_tracks; i++ )
+    {
+        wanted = qlvl = NULL;
+        sms = vlc_array_item_at_index( p_sys->sms_streams, i );
+        wanted = vlc_array_item_at_index( sms->qlevels, 0 );
+        for( unsigned i=1; i < sms->qlevel_nb; i++ )
+        {
+            qlvl = vlc_array_item_at_index( sms->qlevels, i );
+            if( qlvl->Bitrate < wanted->Bitrate )
+                wanted = qlvl;
+        }
+        sms->download_qlvl = wanted->id;
+    }
+
+    vlc_mutex_init( &p_sys->download.lock_wait );
+    vlc_cond_init( &p_sys->download.wait );
+
+    /* */
+    s->pf_read = Read;
+    s->pf_peek = Peek;
+    s->pf_control = Control;
+
+    if( vlc_clone( &p_sys->thread, sms_Thread, s, VLC_THREAD_PRIORITY_INPUT ) )
+    {
+        free( p_sys );
+        vlc_mutex_destroy( &p_sys->download.lock_wait );
+        vlc_cond_destroy( &p_sys->download.wait );
+        return VLC_EGENERIC;
+    }
+
+    return VLC_SUCCESS;
+}
+
+static void Close( vlc_object_t *p_this )
+{
+    stream_t *s = (stream_t*)p_this;
+    stream_sys_t *p_sys = s->p_sys;
+
+    vlc_mutex_lock( &p_sys->download.lock_wait );
+    p_sys->b_close = true;
+    /* Negate the condition variable's predicate */
+    p_sys->download.vlead = p_sys->download.alead = 0;
+    p_sys->playback.toffset = 0;
+    vlc_cond_signal(&p_sys->download.wait);
+    vlc_mutex_unlock( &p_sys->download.lock_wait );
+
+    vlc_join( p_sys->thread, NULL );
+    vlc_mutex_destroy( &p_sys->download.lock_wait );
+    vlc_cond_destroy( &p_sys->download.wait );
+
+    /* Free sms streams */
+    sms_stream_t *sms;
+    for( int i = 0; i < vlc_array_count( p_sys->sms_streams ); i++ )
+    {
+        sms = vlc_array_item_at_index( p_sys->sms_streams, i );
+        if( sms)
+            sms_Free( sms );
+    }
+
+    vlc_array_destroy( p_sys->sms_streams );
+    vlc_array_destroy( p_sys->download.chunks );
+
+    free( p_sys->base_url );
+    free( p_sys );
+}
+
+static chunk_t *get_chunk( stream_t *s, const bool wait )
+{
+    stream_sys_t *p_sys = s->p_sys;
+    unsigned count;
+    chunk_t *chunk = NULL;
+
+    vlc_mutex_lock( &p_sys->download.lock_wait );
+    count = vlc_array_count( p_sys->download.chunks );
+    while( p_sys->playback.index >= count || p_sys->b_tseek )
+    {
+        /* Yes I know, checking for p_sys->b_die is not reliable,
+         * that's why vlc_object_alive() has been deprecated. But if I
+         * understood well, there is no good solution with a stream_filter
+         * module anyaway. */
+        if( !wait || s->b_die || p_sys->b_error )
+        {
+            vlc_mutex_unlock( &p_sys->download.lock_wait );
+            msg_Warn( s, "get_chunk failed! (playback index %u)",
+                    p_sys->playback.index );
+            return NULL;
+        }
+        if( !p_sys->b_live &&
+            p_sys->download.aindex >= (p_sys->vstream->vod_chunks_nb -1) &&
+            p_sys->download.vindex >= (p_sys->astream->vod_chunks_nb -1) )
+        {
+            vlc_mutex_unlock( &p_sys->download.lock_wait );
+            msg_Info( s, "No more chunks, end of the VOD" );
+            return NULL;
+        }
+
+        msg_Dbg( s, "get_chunk is waiting !!!" );
+        vlc_cond_timedwait( &p_sys->download.wait,
+                &p_sys->download.lock_wait, mdate() + 500000 );
+        count = vlc_array_count( p_sys->download.chunks );
+        msg_Dbg( s, "count is %u, and index is %u", count, p_sys->playback.index );
+    }
+    chunk = vlc_array_item_at_index( p_sys->download.chunks, p_sys->playback.index );
+
+    vlc_mutex_unlock( &p_sys->download.lock_wait );
+
+    return chunk;
+}
+
+static int sms_Read( stream_t *s, uint8_t *p_read, int i_read )
+{
+    stream_sys_t *p_sys = s->p_sys;
+    int copied = 0;
+    chunk_t *chunk = NULL;
+
+    do
+    {
+        chunk = get_chunk( s, true );
+        if( !chunk )
+            return copied;
+
+        if( chunk->read_pos >= (int)chunk->size )
+        {
+            if( chunk->type == VIDEO_ES )
+            {
+                vlc_mutex_lock( &p_sys->download.lock_wait );
+                p_sys->playback.toffset += chunk->duration;
+                vlc_mutex_unlock( &p_sys->download.lock_wait );
+                vlc_cond_signal( &p_sys->download.wait);
+            }
+            if( !p_sys->b_cache || p_sys->b_live )
+            {
+                block_Release( chunk->data );
+                chunk->data = NULL;
+            }
+
+            chunk->read_pos = 0;
+
+            p_sys->playback.index += 1;
+            msg_Dbg( s, "Incrementing playback index" );
+
+            continue;
+        }
+
+        if( chunk->read_pos == 0 )
+        {
+            const char *verb = p_read == NULL ? "skipping" : "reading";
+            msg_Dbg( s, "%s chunk %u (%u bytes), type %i",
+                        verb, chunk->sequence, i_read, chunk->type );
+            /* check integrity */
+            uint32_t type;
+            uint8_t *slice = chunk->data->p_buffer;
+            SMS_GET4BYTES( type );
+            SMS_GETFOURCC( type );
+            assert( type == ATOM_moof || type == ATOM_uuid );
+        }
+
+        int len = -1;
+        uint8_t *src = chunk->data->p_buffer + chunk->read_pos;
+        if( i_read <= chunk->size - chunk->read_pos )
+            len = i_read;
+        else
+            len = chunk->size - chunk->read_pos;
+
+        if( len > 0 )
+        {
+            if( p_read ) /* otherwise caller skips data */
+                memcpy( p_read + copied, src, len );
+            chunk->read_pos += len;
+            copied += len;
+            i_read -= len;
+        }
+
+
+    } while ( i_read > 0 );
+
+    return copied;
+}
+
+static int Read( stream_t *s, void *buffer, unsigned i_read )
+{
+    stream_sys_t *p_sys = s->p_sys;
+    int length = 0;
+
+    if( p_sys->b_error )
+        return 0;
+
+    length = sms_Read( s, (uint8_t*) buffer, i_read );
+    if( length < 0 )
+        return 0;
+
+    /* This call to sms_Read will increment p_sys->playback.index
+     * in case the last chunk we read into is entirely read */
+    sms_Read( s, NULL, 0 );
+
+    p_sys->playback.boffset += length;
+    if( (unsigned)length < i_read )
+        msg_Warn( s, "could not read %i bytes, only %i!", i_read, length );
+
+    return length;
+}
+
+/* The MP4 demux should never have to to peek outside the current chunk */
+static int Peek( stream_t *s, const uint8_t **pp_peek, unsigned i_peek )
+{
+    chunk_t *chunk = get_chunk( s, true );
+    if( chunk )
+        assert( chunk->data );
+    else
+        return 0;
+
+    int bytes = chunk->size - chunk->read_pos;
+    assert( bytes > 0 );
+
+    if( (unsigned)bytes < i_peek )
+    {
+        msg_Err( s, "could not peek %u bytes, only %i!", i_peek, bytes );
+    }
+    msg_Dbg( s, "peeking at chunk %u!", chunk->sequence );
+    *pp_peek = chunk->data->p_buffer + chunk->read_pos;
+
+    return bytes;
+}
+
+/* Not being able to distinguish between a time seek and a byte seek is
+ * annoying. However, if FAKE_STREAM_SIZE is small enough, say 1000,
+ * then we can reasonably assume that pos > 1000 means that the demux
+ * wants to *byte* seek. This is important because if we treat a time seek
+ * as a byte seek, then the demux would get bytes of the current chunk, during
+ * the next call to Peek() or Read(), instead of a new initialization chunk,
+ * followed by the new downloaded chunks, as it is normally the case for a time
+ * seek. And that would result in a bug */
+static int chunk_Seek( stream_t *s, const uint64_t pos )
+{
+    stream_sys_t *p_sys = s->p_sys;
+
+    if( pos == p_sys->playback.boffset )
+        return VLC_SUCCESS;
+
+    chunk_t *chunk = get_chunk( s, false );
+    if( chunk == NULL )
+        return VLC_EGENERIC;
+
+    bool inside_chunk = pos >= chunk->offset &&
+            pos < (chunk->offset + chunk->size) ? true : false;
+
+    if( inside_chunk )
+    {
+        chunk->read_pos = pos - chunk->offset;
+        p_sys->playback.boffset = pos;
+        return VLC_SUCCESS;
+    }
+    else
+    {
+        if( p_sys->b_live )
+        {
+            msg_Err( s, "Cannot seek outside the current chunk for a live stream" );
+            return VLC_EGENERIC;
+        }
+
+        msg_Info( s, "Seeking outside the current chunk" );
+        assert( pos <= FAKE_STREAM_SIZE );
+
+        vlc_mutex_lock( &p_sys->download.lock_wait );
+
+        p_sys->b_tseek = true;
+        p_sys->time_pos = p_sys->vod_duration * pos / FAKE_STREAM_SIZE;
+        p_sys->download.vlead = p_sys->download.alead = 0;
+        p_sys->playback.toffset = 0;
+
+        vlc_cond_signal( &p_sys->download.wait);
+        vlc_mutex_unlock( &p_sys->download.lock_wait );
+
+        return VLC_SUCCESS;
+    }
+}
+
+static int Control( stream_t *s, int i_query, va_list args )
+{
+    stream_sys_t *p_sys = s->p_sys;
+
+    switch( i_query )
+    {
+        case STREAM_CAN_SEEK:
+        case STREAM_CAN_FASTSEEK:
+            *(va_arg( args, bool * )) = true;
+            break;
+        case STREAM_GET_POSITION:
+            *(va_arg( args, uint64_t * )) = p_sys->playback.boffset;
+            break;
+        case STREAM_SET_POSITION:
+            {
+                uint64_t pos = (uint64_t)va_arg(args, uint64_t);
+                int ret = chunk_Seek(s, pos);
+                if( ret == VLC_SUCCESS )
+                    break;
+                else
+                    return VLC_EGENERIC;
+            }
+        case STREAM_GET_SIZE:
+            *(va_arg( args, uint64_t * )) = FAKE_STREAM_SIZE;
+            break;
+        default:
+            return VLC_EGENERIC;
+    }
+    return VLC_SUCCESS;
+}
diff --git a/modules/stream_filter/smooth/smooth.h b/modules/stream_filter/smooth/smooth.h
new file mode 100644
index 0000000..19b81e3
--- /dev/null
+++ b/modules/stream_filter/smooth/smooth.h
@@ -0,0 +1,181 @@
+/*****************************************************************************
+ * smooth.h: misc. stuff
+ *****************************************************************************
+ * Copyright (C) 1996-2012 VLC authors and VideoLAN
+ * $Id$
+ *
+ * Author: Frédéric Yhuel <fyhuel _AT_ viotech _DOT_ net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+ *****************************************************************************/
+
+#ifndef _VLC_SMOOTH_H
+#define _VLC_SMOOTH_H 1
+
+//#define DISABLE_BANDWIDTH_ADAPTATION
+
+typedef struct item_s
+{
+    uint64_t value;
+    struct item_s *next;
+
+} item_t;
+
+typedef struct sms_queue_s
+{
+    int length;
+    item_t *first;
+} sms_queue_t;
+
+typedef struct chunk_s
+{
+    int64_t     duration;   /* chunk duration (seconds / TimeScale) */
+    int64_t     start_time; /* PTS (seconds / TimeScale) */
+    int         size;       /* chunk size in bytes */
+    unsigned    sequence;   /* unique sequence number */
+    uint64_t    offset;     /* offset in the media */
+    int         read_pos;   /* position in the chunk */
+    int         type;       /* video, audio, or subtitles */
+
+    block_t     *data;
+} chunk_t;
+
+typedef struct quality_level_s
+{
+    int             Index;
+    uint32_t        FourCC;
+    unsigned        Bitrate;
+    unsigned        MaxWidth;
+    unsigned        MaxHeight;
+    unsigned        SamplingRate;
+    unsigned        Channels;
+    unsigned        BitsPerSample;
+    unsigned        PacketSize;
+    unsigned        AudioTag;
+    unsigned        nBlockAlign;
+    char            *CodecPrivateData; /* hex encoded string */
+    unsigned        id;
+
+} quality_level_t;
+
+typedef struct sms_stream_s
+{
+    vlc_array_t    *qlevels;       /* list of available Quality Levels */
+    vlc_array_t    *chunks;        /* list of chunks */
+    unsigned       vod_chunks_nb;  /* total num of chunks of the VOD stream */
+    unsigned       timescale;
+    unsigned       qlevel_nb;      /* number of quality levels */
+    unsigned       id;             /* track id, will be set arbitrarily */
+    char           *name;
+    char           *url_template;
+    int            type;
+    unsigned       download_qlvl; /* current quality level ID for Download() */
+
+} sms_stream_t;
+
+struct stream_sys_t
+{
+    char         *base_url;    /* URL common part for chunks */
+    vlc_thread_t thread;       /* SMS chunk download thread */
+
+    vlc_array_t  *sms_streams; /* array of sms_stream_t */
+    sms_stream_t *vstream;     /* current video stream  */
+    sms_stream_t *astream;     /* current audio stream  */
+    sms_stream_t *tstream;     /* current text stream  */
+    unsigned     i_tracks;     /* Total number of tracks in the Manifest */
+    unsigned     i_selected_tracks;
+    sms_queue_t  *bws;         /* Measured bandwidths of the N last chunks */
+    uint64_t     vod_duration; /* total duration of the VOD media */
+    int64_t      time_pos;
+    unsigned     timescale;
+
+    /* Download */
+    struct sms_download_s
+    {
+        uint64_t     alead;       // how much audio/video/text data is
+        uint64_t     vlead;       // available (downloaded),
+        uint64_t     tlead;       // in seconds / TimeScale
+
+        unsigned     aindex;      /* current audio chunk for download */
+        unsigned     vindex;      /*         video                    */
+        unsigned     sindex;      /*         spu                      */
+
+        uint64_t     next_chunk_offset;
+        vlc_array_t  *chunks;     /* chunks that have been downloaded */
+        vlc_mutex_t  lock_wait;   /* protect chunk download counter. */
+        vlc_cond_t   wait;        /* some condition to wait on */
+    } download;
+
+    /* Playback */
+    struct sms_playback_s
+    {
+        uint64_t    boffset;     /* current byte offset in media */
+        uint64_t    toffset;     /* current time offset in media */
+        unsigned    index;       /* current chunk for playback */
+    } playback;
+
+    /* state */
+    bool        b_cache;     /* can cache files */
+    bool        b_live;      /* live stream? or vod? */
+    bool        b_error;     /* parsing error */
+    bool        b_close;     /* set by Close() */
+    bool        b_tseek;     /* time seeking */
+};
+
+#define SMS_GET4BYTES( dst ) do { \
+    dst = U32_AT( slice ); \
+    slice += 4; \
+  } while(0)
+
+#define SMS_GET1BYTE( dst ) do { \
+    dst = *slice; \
+    slice += 1; \
+  } while(0)
+
+#define SMS_GET3BYTES( dst ) do { \
+    dst = Get24bBE( slice ); \
+    slice += 3; \
+  } while(0)
+
+#define SMS_GET8BYTES( dst ) do { \
+    dst = U64_AT( slice ); \
+    slice += 8; \
+  } while(0)
+
+#define SMS_GET4or8BYTES( dst ) \
+    if( (version) == 0 ) \
+        SMS_GET4BYTES( dst ); \
+    else \
+        SMS_GET8BYTES( dst ); \
+
+#define SMS_GETFOURCC( dst ) do { \
+    memcpy( &dst, slice, 4 ); \
+    slice += 4; \
+  } while(0)
+
+sms_queue_t *sms_queue_init( const int );
+void sms_queue_put( sms_queue_t *, const uint64_t );
+uint64_t sms_queue_avg( sms_queue_t *);
+quality_level_t *get_qlevel( sms_stream_t *, const unsigned );
+void* sms_Thread( void *);
+quality_level_t * ql_New( void );
+void ql_Free( quality_level_t *);
+chunk_t *chunk_New( sms_stream_t* , uint64_t , uint64_t );
+void chunk_Free( chunk_t *);
+sms_stream_t * sms_New( void );
+void sms_Free( sms_stream_t *);
+uint8_t *decode_string_hex_to_binary( const char * );
+
+#endif
diff --git a/modules/stream_filter/smooth/utils.c b/modules/stream_filter/smooth/utils.c
new file mode 100644
index 0000000..1a0db1f
--- /dev/null
+++ b/modules/stream_filter/smooth/utils.c
@@ -0,0 +1,201 @@
+/*****************************************************************************
+ * utils.c: misc. stuff
+ *****************************************************************************
+ * Copyright (C) 1996-2012 VLC authors and VideoLAN
+ * $Id$
+ *
+ * Author: Frédéric Yhuel <fyhuel _AT_ viotech _DOT_ net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include <vlc_common.h>
+#include <vlc_es.h>
+#include <vlc_block.h>
+#include <assert.h>
+
+#include "smooth.h"
+
+static int hex_digit( const char c )
+{
+    if (c >= 'A' && c <= 'F')
+        return c - 'A' + 10;
+    else if (c >= 'a' && c <= 'f')
+        return c - 'a' + 10;
+    else if (c >= '0' && c<= '9')
+        return c - '0';
+    else
+        return -1;
+}
+
+uint8_t *decode_string_hex_to_binary( const char *psz_src )
+{
+    int i = 0, j = 0, first_digit, second_digit;
+    int i_len = strlen( psz_src );
+    assert( i_len % 2 == 0 );
+    uint8_t *p_data = malloc( i_len / 2 );
+
+    if( !p_data )
+        return NULL;
+
+    while( i < i_len )
+    {
+        first_digit = hex_digit( psz_src[i++] );
+        second_digit = hex_digit( psz_src[i++] );
+        assert( first_digit >= 0 && second_digit >= 0 );
+        p_data[j++] = ( first_digit << 4 ) | second_digit;
+    }
+
+    return p_data;
+}
+
+quality_level_t * ql_New( void )
+{
+    quality_level_t *ql = calloc( 1, sizeof( quality_level_t ) );
+    if( unlikely( !ql ) ) return NULL;
+
+    ql->Index = -1;
+    return ql;
+}
+
+void ql_Free( quality_level_t *qlevel )
+{
+    free( qlevel->CodecPrivateData );
+    free( qlevel );
+    qlevel = NULL;
+}
+
+chunk_t *chunk_New( sms_stream_t* sms, uint64_t duration,\
+        uint64_t start_time )
+{
+    chunk_t *chunk = calloc( 1, sizeof( chunk_t ) );
+    if( unlikely( chunk == NULL ) )
+        return NULL;
+
+    chunk->duration = duration;
+    chunk->start_time = start_time;
+    chunk->type = UNKNOWN_ES;
+    chunk->sequence = vlc_array_count( sms->chunks );
+    vlc_array_append( sms->chunks, chunk );
+    return chunk;
+}
+
+void chunk_Free( chunk_t *chunk )
+{
+    if( chunk->data )
+        block_Release( chunk->data );
+    free( chunk );
+    chunk = NULL;
+}
+
+sms_stream_t * sms_New( void )
+{
+    sms_stream_t *sms = calloc( 1, sizeof( sms_stream_t ) );
+    if( unlikely( !sms ) ) return NULL;
+
+    sms->qlevels = vlc_array_new();
+    sms->chunks = vlc_array_new();
+    sms->type = UNKNOWN_ES;
+    return sms;
+}
+
+void sms_Free( sms_stream_t *sms )
+{
+    if( sms->qlevels )
+    {
+        for( int n = 0; n < vlc_array_count( sms->qlevels ); n++ )
+        {
+            quality_level_t *qlevel = vlc_array_item_at_index( sms->qlevels, n );
+            if( qlevel ) ql_Free( qlevel );
+        }
+        vlc_array_destroy( sms->qlevels );
+    }
+
+    if( sms->chunks )
+    {
+        for( int n = 0; n < vlc_array_count( sms->chunks ); n++ )
+        {
+            chunk_t *chunk = vlc_array_item_at_index( sms->chunks, n );
+            if( chunk) chunk_Free( chunk );
+        }
+        vlc_array_destroy( sms->chunks );
+    }
+
+    free( sms );
+    sms = NULL;
+}
+
+quality_level_t *get_qlevel( sms_stream_t *sms, const unsigned qid )
+{
+    quality_level_t *qlevel = NULL;
+    for( unsigned i = 0; i < sms->qlevel_nb; i++ )
+    {
+        qlevel = vlc_array_item_at_index( sms->qlevels, i );
+        if( qlevel->id == qid )
+            break;
+        else if( i == sms->qlevel_nb - 1 )
+            return NULL;
+    }
+    return qlevel;
+}
+
+sms_queue_t *sms_queue_init( const int length )
+{
+    sms_queue_t *ret = malloc( sizeof( sms_queue_t ) );
+    ret->length = length;
+    ret->first = NULL;
+    return ret;
+}
+
+void sms_queue_put( sms_queue_t *queue, const uint64_t value )
+{
+    item_t *last = queue->first;
+    int i = 0;
+    for( i = 0; i < queue->length - 1; i++ )
+    {
+        if( last )
+            last = last->next;
+    }
+    if( i == queue->length - 1 )
+        FREENULL( last );
+
+    item_t *new = malloc( sizeof( item_t ) );
+    assert( new );
+
+    new->value = value;
+    new->next = queue->first;
+    queue->first = new;
+}
+
+uint64_t sms_queue_avg( sms_queue_t *queue )
+{
+    item_t *last = queue->first;
+    if( last == NULL )
+        return 0;
+    uint64_t sum = queue->first->value;
+    for( int i = 0; i < queue->length - 1; i++ )
+    {
+        if( last )
+        {
+            last = last->next;
+            if( last )
+                sum += last->value;
+        }
+    }
+    return sum / queue->length;
+}
-- 
1.7.9.5




More information about the vlc-devel mailing list