[vlc-devel] [PATCH 5/5] Add Smooth Streaming module
Frédéric Yhuel
fyhuel at viotech.net
Tue May 22 17:11:12 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 if you disable the audio
stream:
- http://mediadl.microsoft.com/mediadl/iisnet/smoothmedia/Experience/BigBuckBunny_720p.ism/Manifest
---
configure.ac | 1 +
modules/stream_filter/Modules.am | 1 +
modules/stream_filter/smooth/Modules.am | 3 +
modules/stream_filter/smooth/downloader.c | 624 +++++++++++++++++++++++
modules/stream_filter/smooth/smooth.c | 792 +++++++++++++++++++++++++++++
modules/stream_filter/smooth/smooth.h | 182 +++++++
modules/stream_filter/smooth/utils.c | 173 +++++++
7 files changed, 1776 insertions(+)
create mode 100644 modules/stream_filter/smooth/Modules.am
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/configure.ac b/configure.ac
index fb83def..e9413fe 100644
--- a/configure.ac
+++ b/configure.ac
@@ -4258,6 +4258,7 @@ AC_CONFIG_FILES([
modules/services_discovery/Makefile
modules/stream_filter/Makefile
modules/stream_filter/dash/Makefile
+ modules/stream_filter/smooth/Makefile
modules/stream_out/Makefile
modules/stream_out/transcode/Makefile
modules/text_renderer/Makefile
diff --git a/modules/stream_filter/Modules.am b/modules/stream_filter/Modules.am
index 78efb7d..6d8d94a 100644
--- a/modules/stream_filter/Modules.am
+++ b/modules/stream_filter/Modules.am
@@ -1,4 +1,5 @@
SUBDIRS = dash
+SUBDIRS = smooth
SOURCES_decomp = decomp.c
SOURCES_stream_filter_record = record.c
diff --git a/modules/stream_filter/smooth/Modules.am b/modules/stream_filter/smooth/Modules.am
new file mode 100644
index 0000000..ea9d505
--- /dev/null
+++ b/modules/stream_filter/smooth/Modules.am
@@ -0,0 +1,3 @@
+SOURCES_stream_filter_smooth = smooth.c libmp4.h utils.c smooth.h downloader.c
+
+libvlc_LTLIBRARIES += libstream_filter_smooth_plugin.la
diff --git a/modules/stream_filter/smooth/downloader.c b/modules/stream_filter/smooth/downloader.c
new file mode 100644
index 0000000..57d58dc
--- /dev/null
+++ b/modules/stream_filter/smooth/downloader.c
@@ -0,0 +1,624 @@
+/*****************************************************************************
+ * 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,
+ uint64_t bandwidth, uint64_t start_time )
+{
+ char *frag;
+ char *end;
+ char *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, int64_t start_time )
+{
+ vlc_mutex_lock( &sms->lock );
+ 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->start_time <= start_time &&
+ chunk->start_time + chunk->duration > start_time )
+ {
+ vlc_mutex_unlock( &sms->lock );
+ return chunk;
+ }
+ }
+ vlc_mutex_unlock( &sms->lock );
+ return NULL;
+}
+
+static int sms_Download( stream_t *s, chunk_t *chunk, char *url )
+{
+ assert( chunk );
+ stream_sys_t *p_sys = s->p_sys;
+
+ if( url == NULL )
+ return VLC_ENOMEM;
+ msg_Dbg( s, "chunk url is %s\n", url );
+
+ 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->data = block_Alloc( size );
+ if( chunk->data == NULL )
+ {
+ stream_Delete( p_ts );
+ return VLC_ENOMEM;
+ }
+ msg_Dbg( s, "sms_Download: gonna dl %i bytes", (int)chunk->data->i_size );
+
+ 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 );
+ }
+
+ vlc_mutex_lock( &p_sys->download.lock_wait );
+ vlc_array_append( p_sys->download.dld_chunks, chunk );
+ vlc_mutex_unlock( &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;
+ stream_Delete( p_ts );
+
+ return VLC_SUCCESS;
+}
+
+#ifdef DISABLE_BANDWIDTH_ADAPTATION
+static uint16_t BandwidthAdaptation( stream_t *s,
+ sms_stream_t *sms, uint64_t *bandwidth )
+{
+ return sms->download_qlvl;
+}
+#else
+
+static uint16_t 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;
+ uint16_t 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 );
+ if( qlevel->Bitrate < (*bandwidth - *bandwidth / 3) &&
+ qlevel->Bitrate > bw_candidate )
+ {
+ bw_candidate = qlevel->Bitrate;
+ ret = qlevel->id;
+ }
+ }
+ if( bw_candidate )
+ *bandwidth = bw_candidate;
+ 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;
+ uint8_t version, fragment_count;
+ uint32_t size, type, flags;
+ sms_stream_t *sms;
+ if( ck->type == AUDIO_ES )
+ sms = p_sys->astream;
+ else if ( ck->type == VIDEO_ES )
+ sms = p_sys->vstream;
+ else
+ return 0;
+ UUID_t uuid;
+ TfrfBoxDataFields_t *tfrf_df;
+
+ 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 ) )
+ chunk_New( sms, dur, stime );
+ }
+ free( tfrf_df );
+
+ return VLC_SUCCESS;
+}
+
+/* Set track ID */
+static uint16_t set_track_id( chunk_t *chunk, uint32_t tid )
+{
+ uint8_t *slice = chunk->data->p_buffer;
+ uint32_t type;
+ for( size_t stop = chunk->data->i_buffer; stop > 0; )
+ {
+ slice = memchr( slice , 't', stop );
+ if( slice == NULL )
+ return 0;
+ SMS_GETFOURCC( type );
+ if( type == ATOM_tfhd )
+ break;
+ else
+ {
+ slice = __MAX( slice - 4 + 1, chunk->data->p_buffer + 1 );
+ stop = (size_t)chunk->data->i_buffer -
+ (size_t)(slice - chunk->data->p_buffer);
+ }
+ }
+ if( slice > chunk->data->p_buffer + chunk->data->i_buffer - 12 )
+ return 0;
+
+ uint32_t ret = bswap32( ((uint32_t *)slice)[1] );
+ ((uint32_t *)slice)[1] = bswap32( tid );
+ return ret;
+}
+
+#define STRA_SIZE 328
+#define SMOO_SIZE (STRA_SIZE * 3 + 24) /* 1008 */
+
+/* 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 );
+
+ quality_level_t * qlvl = get_qlevel( sms, sms->download_qlvl );
+
+ ((uint32_t *)stra_box)[14] = bswap32( qlvl->FourCC );
+ ((uint32_t *)stra_box)[15] = bswap32( qlvl->Bitrate );
+ ((uint32_t *)stra_box)[16] = bswap32( qlvl->MaxWidth );
+ ((uint32_t *)stra_box)[17] = bswap32( qlvl->MaxHeight );
+ ((uint32_t *)stra_box)[18] = bswap32( qlvl->SamplingRate );
+ ((uint32_t *)stra_box)[19] = bswap32( qlvl->Channels );
+ ((uint32_t *)stra_box)[20] = bswap32( qlvl->BitsPerSample );
+ ((uint32_t *)stra_box)[21] = bswap32( qlvl->PacketSize );
+ ((uint32_t *)stra_box)[22] = bswap32( qlvl->AudioTag );
+
+ stra_box[92] = stra_box[93] = stra_box[94] = 0; /* reserved */
+ stra_box[95] = strlen( qlvl->CodecPrivateData );
+ char *dst = (char *)(stra_box + 96);
+ strncpy( dst, qlvl->CodecPrivateData, 256 );
+ }
+
+ 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;
+ vlc_mutex_init( &ret->lock );
+ 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, uint64_t *next_chunk_offset )
+{
+ stream_sys_t *p_sys = s->p_sys;
+
+ assert( sms );
+
+ 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 );
+ chunk_t *chunk = chunk_Get( sms, start_time );
+ while( !chunk )
+ {
+ if( !vlc_object_alive( s ) )
+ return VLC_EGENERIC;
+ msg_Warn( s, "Could not find a chunk for stream %s, "\
+ "start time = %"PRIu64"", sms->name, start_time );
+ sleep( 1 );
+ chunk = chunk_Get( sms, start_time );
+ }
+
+ vlc_mutex_lock( &chunk->lock );
+ if( chunk->data != NULL )
+ {
+ /* Segment already downloaded */
+ vlc_mutex_unlock( &chunk->lock );
+ 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 */
+ uint32_t chunk_duration = chunk->duration * 1000 / sms->timescale;
+ uint64_t size = chunk_duration * qlevel->Bitrate / 1000; /* bits */
+ uint32_t estimated = (uint32_t)(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 %"PRIu32" from stream %s at quality\
+ %"PRIu32" failed", chunk->sequence, sms->name, qlevel->Bitrate );
+ vlc_mutex_unlock( &chunk->lock );
+ return VLC_EGENERIC;
+ }
+ mtime_t duration = mdate() - start;
+
+ chunk->offset = *next_chunk_offset;
+ *next_chunk_offset += chunk->size;
+ if( p_sys->b_live )
+ get_new_chunks( s, chunk );
+ uint16_t real_id = set_track_id( chunk, sms->id );
+ if( real_id == 0)
+ {
+ vlc_mutex_unlock( &chunk->lock );
+ 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 );
+ vlc_mutex_unlock( &chunk->lock );
+
+ msg_Dbg( s, "downloaded chunk %d from stream %s at quality %"PRIu32"",
+ chunk->sequence, sms->name, qlevel->Bitrate );
+ if( sms->type == AUDIO_ES )
+ p_sys->download.aindex++;
+ else if( sms->type == VIDEO_ES )
+ p_sys->download.vindex++;
+ else if( sms->type == SPU_ES )
+ p_sys->download.sindex++;
+
+ if( sms->type != VIDEO_ES )
+ return VLC_SUCCESS;
+
+ 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( ( (qlevel->Bitrate < avg_bw - avg_bw / 3) || (qlevel->Bitrate > avg_bw) )
+ && chunk->sequence > 1 )
+ {
+ uint16_t new_qlevel_id = BandwidthAdaptation( s, sms, &avg_bw );
+ quality_level_t *new_qlevel = get_qlevel( sms, new_qlevel_id );
+
+ /* FIXME: we need an average here */
+ if( (new_qlevel) && (new_qlevel->Bitrate != qlevel->Bitrate) )
+ {
+ msg_Warn( s, "detected %s bandwidth (%"PRIu64") stream",
+ (avg_bw >= qlevel->Bitrate) ? "faster" : "lower", avg_bw );
+ sms->download_qlvl = new_qlevel_id;
+
+ chunk_t *new_init_ck = build_init_chunk( s );
+ new_init_ck->offset = *next_chunk_offset;
+ *next_chunk_offset += new_init_ck->size;
+ vlc_mutex_lock( &p_sys->download.lock_wait );
+ vlc_array_append( p_sys->download.dld_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;
+ int64_t lead=0, time_left=0;
+ uint64_t next_chunk_offset = 0;
+ sms_stream_t *vsms, *asms;
+ vsms = p_sys->vstream;
+ 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 );
+ vlc_mutex_lock( &p_sys->download.lock_wait );
+ vlc_array_append( p_sys->download.dld_chunks, init_ck );
+ vlc_mutex_unlock( &p_sys->download.lock_wait );
+ next_chunk_offset += init_ck->size;
+
+ int canc = vlc_savecancel();
+
+ vlc_mutex_lock( &vsms->lock );
+ chunk_t *third_video_chunk = vlc_array_item_at_index( vsms->chunks, 0 );
+ chunk_t *third_audio_chunk = vlc_array_item_at_index( asms->chunks, 0 );
+
+ /* 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 = third_audio_chunk->duration / 2;
+ int64_t vmid = third_video_chunk->duration / 2;
+
+ if( third_audio_chunk->start_time > third_video_chunk->start_time + vmid )
+ {
+ third_video_chunk = vlc_array_item_at_index( vsms->chunks, 1 );
+ }
+ else if ( third_video_chunk->start_time >
+ third_audio_chunk->start_time + amid )
+ {
+ third_audio_chunk = vlc_array_item_at_index( asms->chunks, 1 );
+ }
+ vlc_mutex_unlock( &vsms->lock );
+ bool disable_audio = var_InheritBool( s, "smooth-disable-audio" );
+ if( p_sys->b_live )
+ {
+ p_sys->download.vlead = third_video_chunk->start_time +
+ p_sys->timescale / 1000;
+ if( disable_audio )
+ p_sys->download.alead = UINT64_MAX;
+ else
+ p_sys->download.alead = third_audio_chunk->start_time +
+ p_sys->timescale / 1000;
+ }
+
+ if( Download( s, vsms, &next_chunk_offset ) != VLC_SUCCESS )
+ {
+ p_sys->b_error = true;
+ goto cancel;
+ }
+ if( !disable_audio )
+ {
+ if( Download( s, asms, &next_chunk_offset ) != VLC_SUCCESS )
+ {
+ p_sys->b_error = true;
+ goto cancel;
+ }
+ }
+
+ while( vlc_object_alive( s ) )
+ {
+ /* Is there a new chunk to process? */
+ if( p_sys->download.aindex >= (asms->vod_chunks_nb - 1) &&
+ p_sys->download.vindex >= (vsms->vod_chunks_nb - 1) &&
+ !p_sys->b_live )
+ break;
+ time_left = p_sys->vod_duration - p_sys->playback.toffset;
+ if( time_left > 60 * p_sys->timescale || p_sys->b_live )
+ {
+ /* wait */
+ vlc_mutex_lock( &p_sys->download.lock_wait );
+ if( p_sys->download.vlead >= p_sys->playback.toffset )
+ lead = p_sys->download.vlead - p_sys->playback.toffset;
+ else
+ lead = 0;
+ while( lead > 20 * p_sys->timescale + third_video_chunk->start_time )
+ {
+#if 0
+ msg_Dbg( s, "sms_Thread is waiting!" );
+ msg_Dbg( s, "toffset is %"PRIu64" vlead is %"PRIu64", "\
+ "and lead is %"PRIi64,
+ p_sys->playback.toffset,
+ p_sys->download.vlead - third_video_chunk->start_time,
+ lead );
+#endif
+ vlc_cond_timedwait( &p_sys->download.wait,
+ &p_sys->download.lock_wait, mdate() + 100000 );
+ lead = p_sys->download.vlead - p_sys->playback.toffset;
+ if( !vlc_object_alive( s ) )
+ break;
+ }
+ vlc_mutex_unlock( &p_sys->download.lock_wait );
+ }
+
+ msg_Dbg( s, "I escaped the waiting cond !!!" );
+ if( p_sys->download.alead < p_sys->download.vlead )
+ if( Download( s, asms, &next_chunk_offset ) != VLC_SUCCESS )
+ {
+ p_sys->b_error = true;
+ break;
+ }
+
+ if( p_sys->download.vlead <= p_sys->download.alead )
+ if( Download( s, vsms, &next_chunk_offset) != VLC_SUCCESS )
+ {
+ p_sys->b_error = true;
+ break;
+ }
+
+ /* download succeeded */
+ vlc_mutex_lock( &p_sys->download.lock_wait );
+ vlc_cond_signal( &p_sys->download.wait );
+ vlc_mutex_unlock( &p_sys->download.lock_wait );
+ }
+
+cancel:
+ 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..20f498c
--- /dev/null
+++ b/modules/stream_filter/smooth/smooth.c
@@ -0,0 +1,792 @@
+/*****************************************************************************
+ * 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 "smooth.h"
+
+#define DA_TEXT N_("Disable audio stream")
+#define DA_LONGTEXT N_("Disable audio stream")
+
+/*****************************************************************************
+ * 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 int );
+static int Peek( stream_t *, const uint8_t **, unsigned int );
+static int Control( stream_t *, int , va_list );
+
+static bool isSmoothStreaming( stream_t *s )
+{
+ const uint8_t *peek;
+ 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 )
+ return false;
+ char *peeked = malloc( 512 );
+ if( unlikely( !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;
+}
+
+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" );
+ }
+
+ const char *node;
+ char *stream_name = NULL;
+ 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;
+ uint32_t next_track_id = 1;
+ uint32_t next_qid = 1;
+ enum{ TIMESCALE = 10000000 };
+ uint32_t Subtype = 0;
+ int loop_count = 0;
+ bool b_weird = false;
+
+ 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" ) )
+ {
+ Subtype = 0;
+ 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( !strcmp( name, "Subtype" ) )
+ if( !strcmp( value, "WmaPro" ) )
+ Subtype = VLC_FOURCC('W', 'M', 'A', 'P' );
+ }
+
+ 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 );
+
+ for( unsigned i = 0; i < p_sys->i_tracks; i++ )
+ {
+ sms = vlc_array_item_at_index( p_sys->sms_streams, i );
+ if( (sms->type == stream_type) &&
+ !(strcmp( stream_name, sms->name )) )
+ break;
+ else if( i == p_sys->i_tracks - 1 )
+ msg_Err( s, "Updating of the manifest failed" );
+ }
+ }
+
+ 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" ) )
+ ql->CodecPrivateData = strdup( value );
+ if( !strcmp( name, "MaxWidth" ) )
+ ql->MaxWidth = strtoul( value, NULL, 10 );
+ if( !strcmp( name, "MaxHeight" ) )
+ 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 );
+ }
+ if( !ql->FourCC )
+ ql->FourCC = Subtype;
+ 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 );
+ 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;
+ }
+ }
+
+ msg_Dbg( s, "duration is %lli, and stime is %lli",
+ computed_duration, computed_start_time );
+ 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" ) )
+ {
+ 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;
+ }
+ }
+
+ 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( asprintf( &uri,"%s://%s", s->psz_access, s->psz_path ) < 0)
+ {
+ free( p_sys );
+ return VLC_ENOMEM;
+ }
+ p_sys->ismc = uri;
+
+ /* remove the last part of the url */
+ char *base_url = strdup( uri );
+ char *pos = strrchr( base_url, '/');
+ *pos = '\0';
+ p_sys->base_url = base_url;
+
+ p_sys->download.vlead = 0;
+ bool disable_audio = var_InheritBool( s, "smooth-disable-audio" );
+ if( disable_audio )
+ p_sys->download.alead = UINT64_MAX;
+ else
+ p_sys->download.alead = 0;
+ p_sys->download.tlead = 0;
+ p_sys->playback.boffset = 0;
+ /* UINT32_MAX is the index value of the initialization chunk */
+ p_sys->download.vindex = 0;
+ p_sys->download.aindex = 0;
+ p_sys->b_live = false;
+ p_sys->b_cache = true;
+ p_sys->b_error = false;
+
+ p_sys->sms_streams = vlc_array_new();
+ p_sys->download.dld_chunks = vlc_array_new();
+ if( p_sys->sms_streams == NULL )
+ {
+ free( p_sys );
+ return VLC_ENOMEM;
+ }
+
+ p_sys->playback.peek_index = p_sys->playback.index = 0;
+
+ /* 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 );
+ p_sys->i_selected_tracks = 2;
+ if( disable_audio )
+ p_sys->i_selected_tracks -= 1;
+
+ /* Choose first video stream available FIXME */
+ 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 FIXME */
+ 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 SMS quality to start with */
+ quality_level_t *wanted = vlc_array_item_at_index( vsms->qlevels, 0 );
+ quality_level_t *qlvl = NULL;
+ for( uint32_t i=1; i < vsms->qlevel_nb; i++ )
+ {
+ qlvl = vlc_array_item_at_index( vsms->qlevels, i );
+ if( qlvl->Bitrate < wanted->Bitrate )
+ wanted = qlvl;
+ }
+ vsms->download_qlvl = wanted->id;
+
+ wanted = vlc_array_item_at_index( asms->qlevels, 0 );
+ for( uint32_t i = 1; i < asms->qlevel_nb; i++)
+ {
+ qlvl = vlc_array_item_at_index( asms->qlevels, i );
+ if( qlvl->Bitrate < wanted->Bitrate )
+ wanted = qlvl;
+ }
+ asms->download_qlvl = wanted->id;
+
+
+
+ p_sys->playback.toffset = 0;
+
+ 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 ) )
+ {
+ vlc_mutex_destroy( &p_sys->download.lock_wait );
+ vlc_cond_destroy( &p_sys->download.wait );
+ return VLC_EGENERIC;
+ }
+
+ return VLC_SUCCESS;
+}
+
+static void flush_dld_cks( stream_t *s )
+{
+ stream_sys_t *p_sys = s->p_sys;
+
+ vlc_mutex_lock( &p_sys->download.lock_wait );
+ int count = vlc_array_count( p_sys->download.dld_chunks );
+ for( int i = 0; i < count; i++ )
+ {
+ chunk_t *ck = vlc_array_item_at_index( p_sys->download.dld_chunks, i );
+ if( ck )
+ chunk_Free( ck );
+ }
+ vlc_mutex_unlock( &p_sys->download.lock_wait );
+}
+
+static void Close( vlc_object_t *p_this )
+{
+ stream_t *s = (stream_t*)p_this;
+ stream_sys_t *p_sys = s->p_sys;
+
+ msg_Dbg( s, "Joining dl thread" );
+ vlc_join( p_sys->thread, NULL );
+ vlc_mutex_destroy( &p_sys->download.lock_wait );
+ vlc_cond_destroy( &p_sys->download.wait );
+ msg_Dbg( s, "dl thread joined" );
+
+ /* Free sms streams */
+ for( int i = 0; i < vlc_array_count( p_sys->sms_streams ); i++ )
+ {
+ sms_stream_t *sms;
+ sms = (sms_stream_t *)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.dld_chunks );
+
+ if( p_sys->peeked )
+ block_Release( p_sys->peeked );
+ free( p_sys );
+}
+
+static chunk_t *get_chunk( stream_t *s, uint32_t chunk_index, bool wait )
+{
+ stream_sys_t *p_sys = s->p_sys;
+ int count;
+ chunk_t *chunk = NULL;
+
+ vlc_mutex_lock( &p_sys->download.lock_wait );
+ count = vlc_array_count( p_sys->download.dld_chunks );
+ while( chunk_index >= (uint32_t)count )
+ {
+ if( !wait || !vlc_object_alive( s ) )
+ goto fail;
+
+ msg_Dbg( s, "get_chunk is waiting !!!" );
+ vlc_cond_timedwait( &p_sys->download.wait,
+ &p_sys->download.lock_wait, mdate() + 100000 );
+ count = vlc_array_count( p_sys->download.dld_chunks );
+ }
+ chunk = vlc_array_item_at_index( p_sys->download.dld_chunks, chunk_index );
+ vlc_mutex_unlock( &p_sys->download.lock_wait );
+ return chunk;
+
+fail:
+ vlc_mutex_unlock( &p_sys->download.lock_wait );
+ msg_Warn( s, "get_chunk failed! (chunk_index %"PRIu32")", chunk_index );
+ return NULL;
+}
+
+static uint64_t GetStreamSize( stream_t *s )
+{
+ /* The returned size will be very approximative, but bitrate adaptation
+ * make computations a tad tricky anyway */
+ stream_sys_t *p_sys = s->p_sys;
+
+ if( p_sys->b_live )
+ {
+ chunk_t *chunk = get_chunk( s, p_sys->playback.index, false );
+ return chunk->size;
+ }
+
+ uint32_t video_chunk_nb = p_sys->vstream->vod_chunks_nb;
+ vlc_mutex_lock( &p_sys->vstream->lock );
+ chunk_t *first_video_chunk = vlc_array_item_at_index(
+ p_sys->vstream->chunks, 0 );
+ vlc_mutex_unlock( &p_sys->vstream->lock );
+ uint64_t chunk_duration = first_video_chunk->duration;
+ uint64_t total_duration = video_chunk_nb * chunk_duration /
+ p_sys->timescale;
+ uint64_t bitrate = sms_queue_avg( p_sys->bws );
+ uint64_t size = bitrate * total_duration;
+
+ return size;
+}
+
+static int sms_Read( stream_t *s, uint8_t *p_read, unsigned int i_read,
+ bool peek )
+{
+ stream_sys_t *p_sys = s->p_sys;
+ int copied = 0;
+ chunk_t *chunk = NULL;
+ int loop_count = 0;
+ uint32_t *chunk_index;
+ int *position;
+ int peek_pos = 0;
+
+ if( peek )
+ chunk_index = &p_sys->playback.peek_index;
+ else
+ chunk_index = &p_sys->playback.index;
+
+ do
+ {
+ chunk = get_chunk( s, *chunk_index, true );
+ if( !chunk )
+ return copied;
+
+ if( peek )
+ {
+ if( loop_count == 0 ) /* we start at read position */
+ peek_pos = chunk->read_pos;
+ position = &peek_pos;
+ }
+ else
+ position = &chunk->read_pos;
+
+ vlc_mutex_lock( &chunk->lock );
+
+ loop_count++;
+ if( *position >= (int)chunk->size )
+ {
+ if( chunk->type == VIDEO_ES && !peek )
+ {
+ p_sys->playback.toffset += chunk->duration;
+ /* signal download thread */
+ vlc_mutex_lock( &p_sys->download.lock_wait );
+ vlc_cond_signal( &p_sys->download.wait );
+ vlc_mutex_unlock( &p_sys->download.lock_wait );
+ }
+
+ if( !peek && (!p_sys->b_cache || p_sys->b_live) )
+ {
+ block_Release( chunk->data );
+ chunk->data = NULL;
+ }
+ else if( chunk->size > 0 )
+ *position = 0;
+
+ *chunk_index += 1;
+ msg_Dbg( s, "Incrementing playback index" );
+ vlc_mutex_unlock( &chunk->lock );
+
+ continue;
+ }
+
+ if( *position == 0 )
+ {
+ const char *verb = p_read == NULL ? "skipping" : "reading";
+ msg_Dbg( s, "%s chunk %"PRIu32" (%u bytes), type %i",
+ peek ? "peeking at" : verb,
+ chunk->sequence, i_read, chunk->type );
+ }
+
+ int len = -1;
+ uint8_t *src = chunk->data->p_buffer + *position;
+ if( i_read <= (chunk->size - *position) )
+ len = i_read;
+ else
+ len = chunk->size - *position;
+
+ if( len > 0 )
+ {
+ if( p_read ) /* otherwise caller skips data */
+ memcpy( p_read + copied, src, len );
+ *position += len;
+ copied += len;
+ i_read -= len;
+ }
+ vlc_mutex_unlock( &chunk->lock );
+
+ } 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;
+
+ if( !buffer ) msg_Dbg(s, "Caller skips data (%i bytes)", i_read );
+ else msg_Dbg( s, "Demuxer wants to read %i bytes", i_read );
+
+ length = sms_Read( s, (uint8_t*) buffer, i_read, false );
+ if( length < 0 )
+ return 0;
+
+ p_sys->playback.boffset += length;
+ if( (unsigned)length < i_read )
+ msg_Warn( s, "could not read %u bytes, only %i!", i_read, length );
+ return length;
+}
+
+static int Peek( stream_t *s, const uint8_t **pp_peek, unsigned int i_peek )
+{
+ stream_sys_t *p_sys = s->p_sys;
+ msg_Dbg( s, "Demuxer wants to peek %i bytes", i_peek );
+ block_t *peeked = p_sys->peeked;
+ p_sys->playback.peek_index = p_sys->playback.index;
+
+ if( peeked == NULL )
+ peeked = block_Alloc ( i_peek );
+ else if( peeked->i_buffer < i_peek)
+ peeked = block_Realloc( peeked, 0, i_peek );
+
+ if( (p_sys->peeked = peeked) == NULL )
+ return 0;
+
+ int val = sms_Read( s, peeked->p_buffer, i_peek, true );
+
+ *pp_peek = peeked->p_buffer;
+ if( (unsigned)val < i_peek )
+ msg_Warn( s, "could not peek %u bytes, only %i!", i_peek, val );
+
+ return val;
+}
+
+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, p_sys->playback.index, false );
+ if( chunk == NULL )
+ return VLC_EGENERIC;
+
+ if( chunk->read_pos == chunk->size )
+ {
+ sms_Read( s, NULL, 0, false );
+ return chunk_Seek( s, pos );
+ }
+
+ vlc_mutex_lock( &chunk->lock );
+ if( (pos >= chunk->offset) && (pos < chunk->offset + chunk->size) )
+ {
+ chunk->read_pos = pos - chunk->offset;
+ }
+ else
+ {
+ msg_Warn( s, "Seeking outside the current chunk" );
+#if 0
+ msg_Info( s, "pos is %llu", pos );
+ chunk->read_pos = 0;
+ uint64_t time_pos = pos / sms_queue_avg( p_sys->bws );
+ p_sys->playback.toffset = time_pos;
+ p_sys->download.vlead = p_sys->download.alead = time_pos;
+ flush_dld_cks( s );
+ p_sys->playback.index = 0;
+#endif
+ }
+
+ vlc_mutex_unlock( &chunk->lock );
+ 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:
+ *(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);
+ if (chunk_Seek(s, pos) == VLC_SUCCESS)
+ {
+ p_sys->playback.boffset = pos;
+ break;
+ }
+ }
+ return VLC_EGENERIC;
+ case STREAM_GET_SIZE:
+ *(va_arg( args, uint64_t * )) = GetStreamSize( s );
+ 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..2499960
--- /dev/null
+++ b/modules/stream_filter/smooth/smooth.h
@@ -0,0 +1,182 @@
+/*****************************************************************************
+ * 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) */
+ int64_t size; /* chunk size in bytes */
+ uint32_t sequence; /* unique sequence number */
+ uint64_t offset; /* offset in the media */
+ int read_pos; /* position in the chunk */
+ int type; /* video, audio, or subtitles */
+
+ vlc_mutex_t lock;
+ block_t *data;
+} chunk_t;
+
+typedef struct quality_level_s
+{
+ int Index;
+ uint32_t FourCC;
+ uint32_t Bitrate;
+ uint32_t MaxWidth;
+ uint32_t MaxHeight;
+ unsigned SamplingRate;
+ unsigned Channels;
+ unsigned BitsPerSample;
+ uint32_t PacketSize;
+ uint32_t AudioTag;
+ char *CodecPrivateData; /* hex encoded string */
+ uint16_t 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 */
+ uint32_t vod_chunks_nb; /* total num of chunks of the VOD stream */
+ uint32_t timescale;
+ uint32_t qlevel_nb; /* number of quality levels */
+ uint32_t id; /* track id, will be set arbitrarily */
+ char *name;
+ char *url_template;
+ int type;
+ uint16_t download_qlvl; /* current quality level ID for Download() */
+ vlc_mutex_t lock;
+
+} sms_stream_t;
+
+struct stream_sys_t
+{
+ char *ismc; /* Manifest url */
+ char *base_url; /* URL common part for chunks */
+ vlc_thread_t thread; /* SMS chunk download thread */
+
+ block_t *peeked;
+
+ /* */
+ 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 */
+ uint32_t 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
+
+ uint32_t aindex; /* current audio chunk for download */
+ uint32_t vindex; /* video */
+ uint32_t sindex; /* spu */
+
+ vlc_array_t *dld_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 */
+ uint32_t index; /* current chunk for playback */
+ uint32_t peek_index;
+ } playback;
+
+ /* state */
+ bool b_cache; /* can cache files */
+ bool b_live; /* live stream? or vod? */
+ bool b_error; /* parsing error */
+};
+
+#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( int );
+void sms_queue_put( sms_queue_t *, uint64_t );
+uint64_t sms_queue_avg( sms_queue_t *);
+quality_level_t *get_qlevel( sms_stream_t *, uint16_t );
+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 *);
+
+#endif
diff --git a/modules/stream_filter/smooth/utils.c b/modules/stream_filter/smooth/utils.c
new file mode 100644
index 0000000..3eeac36
--- /dev/null
+++ b/modules/stream_filter/smooth/utils.c
@@ -0,0 +1,173 @@
+/*****************************************************************************
+ * 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 "smooth.h"
+
+quality_level_t * ql_New( void )
+{
+ quality_level_t *ql = calloc( 1, sizeof( quality_level_t ) );
+ if( unlikely( ql == NULL ) )
+ 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;
+ vlc_mutex_lock( &sms->lock );
+ chunk->sequence = vlc_array_count( sms->chunks );
+ vlc_array_append( sms->chunks, chunk );
+ vlc_mutex_unlock( &sms->lock );
+ vlc_mutex_init( &chunk->lock );
+ return chunk;
+}
+
+void chunk_Free( chunk_t *chunk )
+{
+ vlc_mutex_destroy( &chunk->lock );
+
+ 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 == NULL ) )
+ return NULL;
+ sms->qlevels = vlc_array_new();
+ sms->chunks = vlc_array_new();
+ sms->type = UNKNOWN_ES;
+ vlc_mutex_init( &sms->lock );
+ 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 =\
+ (quality_level_t *)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 =\
+ (chunk_t *)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, uint16_t 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( 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, 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 ) );
+ 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