[vlc-devel] [PATCH] Support for Http Dynamic Streaming
Jonathan Thambidurai
jonathan at fastly.com
Thu May 29 00:12:32 CEST 2014
---
NEWS | 1 +
modules/MODULES_LIST | 1 +
modules/stream_filter/Makefile.am | 8 +
modules/stream_filter/hds/hds.c | 1556 +++++++++++++++++++++++++++++++++++++
modules/stream_filter/hds/hds.h | 127 +++
po/POTFILES.in | 1 +
6 files changed, 1694 insertions(+)
create mode 100644 modules/stream_filter/hds/hds.c
create mode 100644 modules/stream_filter/hds/hds.h
diff --git a/NEWS b/NEWS
index 67f2819..92c9511 100644
--- a/NEWS
+++ b/NEWS
@@ -86,6 +86,7 @@ Streaming:
:std{access=http{mime=video/webm},mux=webm,dst=:4212}'
* GSM, VP8, Opus and JPEG RTP packetization
* HLS: Allow setting the first segment number and numerous improvements
+ * HDS: Http Dynamic Streaming - based on public specs from Adobe (f4m, f4v, etc.)
* new stats module to output block timing values and md5 sums
* transcode module access fps values as rationals now, eg 30000/1001
* VLC now streams all elementary streams, you can revert to previous behaviour
diff --git a/modules/MODULES_LIST b/modules/MODULES_LIST
index effd694..082176f 100644
--- a/modules/MODULES_LIST
+++ b/modules/MODULES_LIST
@@ -152,6 +152,7 @@ $Id$
* grey_yuv: grayscale to others conversion module
* growl: announce currently playing stream to growl
* h264: H264 decoder
+ * hds: HTTP Dynamic Streaming, per Adobe's specs
* headphone_channel_mixer: headphone channel mixer with virtual spatialization effect
* hevc: HEVC demuxer
* hotkeys: hotkeys control module
diff --git a/modules/stream_filter/Makefile.am b/modules/stream_filter/Makefile.am
index 158b46a..cbf8751 100644
--- a/modules/stream_filter/Makefile.am
+++ b/modules/stream_filter/Makefile.am
@@ -104,6 +104,14 @@ libsmooth_plugin_la_SOURCES = \
libsmooth_plugin_la_CFLAGS = $(AM_CFLAGS)
stream_filter_LTLIBRARIES += libsmooth_plugin.la
+libhds_plugin_la_SOURCES = \
+ stream_filter/hds/hds.c \
+ stream_filter/hds/hds.h
+
+libhds_plugin_la_CFLAGS = $(AM_CFLAGS)
+stream_filter_LTLIBRARIES += libhds_plugin.la
+
+
libhttplive_plugin_la_SOURCES = stream_filter/httplive.c
libhttplive_plugin_la_CFLAGS = $(AM_CFLAGS) $(GCRYPT_CFLAGS)
libhttplive_plugin_la_LIBADD = $(GCRYPT_LIBS) -lgpg-error
diff --git a/modules/stream_filter/hds/hds.c b/modules/stream_filter/hds/hds.c
new file mode 100644
index 0000000..84f0210
--- /dev/null
+++ b/modules/stream_filter/hds/hds.c
@@ -0,0 +1,1556 @@
+/*****************************************************************************
+ * hds.c: Http Dynamic Streaming (HDS) stream filter
+ *****************************************************************************
+ *
+ * Author: Jonathan Thambidurai <jonathan _AT_ fastly _DOT_ com>
+ * Heavily inspired by HDS module of 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 <inttypes.h>
+
+#include <vlc_common.h>
+#include <vlc_strings.h>
+#include <vlc_plugin.h>
+#include <vlc_xml.h>
+#include <vlc_charset.h>
+#include <vlc_stream.h>
+#include <vlc_es.h>
+
+#include "hds.h"
+
+/*****************************************************************************
+ * 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_("HTTP Dynamic Streaming") )
+ set_shortname( "Dynamic Streaming")
+ add_shortcut( "hds" )
+ set_capability( "stream_filter", 30 )
+ set_callbacks( Open, Close )
+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 isFQUrl( char* url )
+{
+ return ( NULL != vlc_strcasestr( url, "https://") ||
+ NULL != vlc_strcasestr( url, "http://" ) );
+}
+
+static bool isHDS( stream_t *s )
+{
+ const uint8_t *peek;
+ int i_size = stream_Peek( s->p_source, &peek, 512 );
+ if( i_size < 512 )
+ return false;
+
+ char *str;
+
+ if( !memcmp( peek, "\xFF\xFE", 2 ) )
+ {
+ str = FromCharset( "UTF-16LE", peek, 512 );
+ }
+ else if( !memcmp( peek, "\xFE\xFF", 2 ) )
+ {
+ str = FromCharset( "UTF-16BE", peek, 512 );
+ }
+ else
+ str = peek;
+
+ if( str == NULL )
+ return false;
+
+ bool ret = strstr( str, "<manifest" ) != NULL;
+ return ret;
+}
+
+typedef struct _bootstrap_info {
+ char* data;
+ char* id;
+ char* url;
+ char* profile;
+ int data_len;
+} bootstrap_info;
+
+typedef struct _media_info {
+ char* stream_id;
+ char* media_url;
+ char* bootstrap_id;
+} media_info;
+
+static char* parse_asrt( vlc_object_t* p_this,
+ hds_stream_t* s,
+ char* data,
+ char* data_end )
+{
+ char* data_p = data;
+
+ uint32_t asrt_len = 0;
+ asrt_len = U32_AT( data_p );
+ if( asrt_len > data_end - data ||
+ data_end - data < 14 )
+ {
+ msg_Err( p_this, "Not enough asrt data (%u, %u)", asrt_len, data_end - data );
+ return NULL;
+ }
+
+ data_p += sizeof(asrt_len);
+
+ if( 0 != memcmp( "asrt", data_p, 4 ) )
+ {
+ msg_Err( p_this, "Cant find asrt in bootstrap" );
+ return NULL;
+ }
+ data_p += 4;
+
+ /* ignore flags and versions (we don't handle multiple updates) */
+ data_p += 4;
+
+ uint8_t quality_entry_count = *data_p;
+ bool quality_found = false;
+ data_p++;
+
+ if( ! s->quality_segment_modifier )
+ {
+ quality_found = true;
+ }
+
+ while( quality_entry_count-- > 0 )
+ {
+ char* str_start = data_p;
+ data_p = memchr( data_p, '\0', data_end - data_p );
+ if( ! data_p )
+ {
+ msg_Err( p_this, "Couldn't find quality entry string in asrt" );
+ return NULL;
+ }
+ data_p++;
+
+ if( ! quality_found )
+ {
+ if( ! strncmp( str_start, s->quality_segment_modifier,
+ strlen(s->quality_segment_modifier) ) )
+ {
+ quality_found = true;
+ }
+ }
+
+ if( data_p >= data_end )
+ {
+ msg_Err( p_this, "Premature end of asrt in quality entries" );
+ return NULL;
+ }
+ }
+
+ if( data_end - data_p < 4 )
+ {
+ msg_Err( p_this, "Premature end of asrt after quality entries" );
+ return NULL;
+ }
+
+ uint32_t segment_run_entry_count = U32_AT( data_p );
+ data_p += sizeof(segment_run_entry_count);
+
+ if( data_end - data_p < 8*segment_run_entry_count )
+ {
+ msg_Err( p_this, "Not enough data in asrt for segment run entries" );
+ return NULL;
+ }
+
+ if( segment_run_entry_count >= MAX_HDS_SEGMENT_RUNS )
+ {
+ msg_Err( p_this, "Too many segment runs" );
+ return NULL;
+ }
+
+ while( segment_run_entry_count-- > 0 )
+ {
+ if( quality_found )
+ {
+ s->segment_runs[s->segment_run_count].first_segment = U32_AT(data_p);
+ }
+ data_p+=4;
+
+ if( quality_found )
+ {
+ s->segment_runs[s->segment_run_count].fragments_per_segment = U32_AT(data_p);
+ }
+ data_p+=4;
+
+ s->segment_run_count++;
+ }
+
+ return data_p;
+}
+
+static char* parse_afrt( vlc_object_t* p_this,
+ hds_stream_t* s,
+ char* data,
+ char* data_end )
+{
+ char* data_p = data;
+
+ uint32_t afrt_len = U32_AT( data_p );
+ if( afrt_len > data_end - data ||
+ data_end - data < 9 )
+ {
+ msg_Err( p_this, "Not enough afrt data %u, %u", afrt_len, data_end - data );
+ return NULL;
+ }
+ data_p += sizeof(afrt_len);
+
+ if( 0 != memcmp( data_p, "afrt", 4 ) )
+ {
+ msg_Err( p_this, "Cant find afrt in bootstrap" );
+ return NULL;
+ }
+ data_p += 4;
+
+ /* ignore flags and versions (we don't handle multiple updates) */
+ data_p += 4;
+
+ if( data_end - data_p < 9 )
+ {
+ msg_Err( p_this, "afrt is too short" );
+ return NULL;
+ }
+
+ s->afrt_timescale = U32_AT( data_p );
+ data_p += 4;
+
+ bool quality_found = false;
+ if( ! s->quality_segment_modifier )
+ {
+ quality_found = true;
+ }
+
+ uint32_t quality_entry_count = *data_p;
+ data_p++;
+ while( quality_entry_count-- > 0 )
+ {
+ char* str_start = data_p;
+ data_p = memchr( data_p, '\0', data_end - data_p );
+ if( ! data_p )
+ {
+ msg_Err( p_this, "Couldn't find quality entry string in afrt" );
+ return NULL;
+ }
+ data_p++;
+
+ if( ! quality_found )
+ {
+ if( ! strncmp( str_start, s->quality_segment_modifier,
+ strlen(s->quality_segment_modifier) ) )
+ {
+ quality_found = true;
+ }
+ }
+ }
+
+ if( data_end - data_p < 5 )
+ {
+ msg_Err( p_this, "No more space in afrt after quality entries" );
+ return NULL;
+ }
+
+ uint32_t fragment_run_entry_count = U32_AT( data_p );
+ data_p += sizeof(uint32_t);
+
+ while(fragment_run_entry_count-- > 0)
+ {
+ if( data_end - data_p < 16 )
+ {
+ msg_Err( p_this, "Not enough data in afrt" );
+ return NULL;
+ }
+
+ if( s->fragment_run_count >= MAX_HDS_FRAGMENT_RUNS )
+ {
+ msg_Err( p_this, "Too many fragment runs, exiting" );
+ return NULL;
+ }
+
+ s->fragment_runs[s->fragment_run_count].fragment_number_start = U32_AT(data_p);
+ data_p += 4;
+
+ s->fragment_runs[s->fragment_run_count].fragment_timestamp = U64_AT( data_p );
+ data_p += 8;
+
+ s->fragment_runs[s->fragment_run_count].fragment_duration = U32_AT( data_p );
+ data_p += 4;
+
+ s->fragment_runs[s->fragment_run_count].discont = 0;
+ if( s->fragment_runs[s->fragment_run_count].fragment_duration == 0 )
+ {
+ /* discontinuity flag */
+ s->fragment_runs[s->fragment_run_count].discont = *(data_p++);
+ }
+
+ s->fragment_run_count++;
+ }
+
+ return data_p;
+}
+
+static volatile chunk_t* chunk_new()
+{
+ volatile chunk_t* chunk = malloc(sizeof(chunk_t));
+ memset( chunk, 0, sizeof(chunk_t) );
+ return chunk;
+}
+
+static void chunk_free( volatile chunk_t * chunk )
+{
+ FREENULL( chunk->data );
+ free( chunk );
+}
+
+static void parse_BootstrapData( vlc_object_t* p_this,
+ hds_stream_t * s,
+ char* data,
+ char* data_end )
+{
+ char* data_p = data;
+
+ uint32_t abst_len = U32_AT( data_p );
+ if( abst_len > data_end - data
+ || data_end - data < 29 /* min size of data */ )
+ {
+ msg_Warn( p_this, "Not enough bootstrap data" );
+ return;
+ }
+ data_p += sizeof(abst_len);
+
+ if( 0 != memcmp( data_p, "abst", 4 ) )
+ {
+ msg_Warn( p_this, "Cant find abst in bootstrap" );
+ return;
+ }
+ data_p += 4;
+
+ /* version, flags*/
+ data_p += 4;
+
+ /* we ignore the version */
+ data_p += 4;
+
+ /* some flags we don't care about here because they are
+ * in the manifest
+ */
+ data_p += 1;
+
+ /* timescale */
+ s->timescale = U32_AT( data_p );
+ data_p += sizeof(s->timescale);
+
+ s->live_current_time = U64_AT( data_p );
+ data_p += sizeof(s->live_current_time);
+
+ /* smtpe time code offset */
+ data_p += 8;
+
+ s->movie_id = strndup( data_p, data_end - data_p );
+ data_p += ( strlen( s->movie_id ) + 1 );
+
+ if( data_end - data_p < 4 ) {
+ msg_Warn( p_this, "Not enough bootstrap after Movie Identifier" );
+ return;
+ }
+
+ uint8_t server_entry_count = 0;
+ server_entry_count = (uint8_t) *data_p;
+ data_p++;
+
+ s->server_entry_count = 0;
+ while( server_entry_count-- > 0 )
+ {
+ if( s->server_entry_count < MAX_HDS_SERVERS )
+ {
+ s->server_entries[s->server_entry_count++] = strndup( data_p,
+ data_end - data_p );
+ data_p += strlen( s->server_entries[s->server_entry_count-1] ) + 1;
+ }
+ else
+ {
+ msg_Warn( p_this, "Too many servers" );
+ data_p = memchr( data_p, '\0', data_end );
+ if( ! data_p )
+ {
+ msg_Err( p_this, "Couldn't find server entry" );
+ return NULL;
+ }
+ data_p++;
+ }
+
+ if( data_p >= data_end )
+ {
+ msg_Warn( p_this, "Premature end of bootstrap info while reading servers" );
+ return;
+ }
+ }
+
+ if( data_end - data_p < 3 ) {
+ msg_Warn( p_this, "Not enough bootstrap after Servers" );
+ return;
+ }
+
+ s->quality_segment_modifier = 0;
+
+ uint8_t quality_entry_count = *data_p;
+ data_p++;
+
+ if( quality_entry_count > 1 )
+ {
+ msg_Err( p_this, "I don't know what to do with multiple quality levels in the bootstrap - shouldn't this be handled at the manifest level?" );
+ return;
+ }
+
+ s->quality_segment_modifier = 0;
+ while( quality_entry_count-- > 0 )
+ {
+ if( s->quality_segment_modifier != 0 )
+ {
+ s->quality_segment_modifier = strndup( data_p, data_end - data_p );
+ }
+ data_p += strnlen( data_p, data_end - data_p ) + 1;
+ }
+
+ if( data_end - data_p < 2 ) {
+ msg_Warn( p_this, "Not enough bootstrap after quality entries" );
+ return;
+ }
+
+ /* ignoring "DrmData" */
+ data_p = memchr( data_p, '\0', data_end - data_p );
+ if( ! data_p )
+ {
+ msg_Err( p_this, "Couldn't find DRM Data" );
+ return NULL;
+ }
+ data_p++;
+
+ if( data_end - data_p < 2 ) {
+ msg_Warn( p_this, "Not enough bootstrap after drm data" );
+ return;
+ }
+
+ /* ignoring "metadata" */
+ data_p = memchr( data_p, '\0', data_end - data_p );
+ if( ! data_p )
+ {
+ msg_Err( p_this, "Couldn't find metadata");
+ return NULL;
+ }
+ data_p++;
+
+ if( data_end - data_p < 2 ) {
+ msg_Warn( p_this, "Not enough bootstrap after drm data" );
+ return;
+ }
+
+ uint8_t asrt_count = *data_p;
+ data_p++;
+
+ s->segment_run_count = 0;
+ while( asrt_count-- > 0 &&
+ data_end > data_p &&
+ (data_p = parse_asrt( p_this, s, data_p, data_end )) );
+
+ uint8_t afrt_count = *data_p;
+ data_p++;
+
+ s->fragment_run_count = 0;
+ while( afrt_count-- > 0 &&
+ data_end > data_p &&
+ (data_p = parse_afrt( p_this, s, data_p, data_end )) );
+}
+
+/* this only works with ANSI characters - this is ok
+ for the bootstrapinfo field which this function is
+ exclusively used for since it is merely a base64 encoding
+*/
+static bool is_whitespace( char c )
+{
+ return ( ' ' == c ||
+ '\t' == c ||
+ '\n' == c ||
+ '\v' == c ||
+ '\f' == c ||
+ '\r' == c );
+}
+
+/* see above note for is_whitespace */
+static void whitespace_substr( char** start,
+ char** end )
+{
+ while( is_whitespace( **start ) && *start != *end ) {
+ (*start)++;
+ }
+
+ if( *start == *end )
+ return;
+
+ while( is_whitespace(*(*end - 1) ) ) {
+ end--;
+ }
+}
+
+/* returns length (could be zero, indicating all of remaining data) */
+/* ptr is to start of data, right after 'mdat' string */
+static uint32_t find_chunk_mdat( vlc_object_t* p_this,
+ uint8_t* chunkdata, uint8_t* chunkdata_end,
+ uint8_t** mdatptr )
+{
+ char* boxname = 0;
+ char* boxdata = 0;
+ uint64_t boxsize = 0;
+
+ do {
+ if( chunkdata_end < chunkdata ||
+ chunkdata_end - chunkdata < 8 )
+ {
+ msg_Err( p_this, "Couldn't find mdat in box 1!" );
+ *mdatptr = 0;
+ return 0;
+ }
+
+ boxsize = U32_AT( chunkdata );
+ chunkdata += 4;
+
+ boxname = chunkdata;
+ chunkdata += 4;
+
+ if( boxsize == 1 )
+ {
+ if( chunkdata_end - chunkdata >= 12 )
+ {
+ boxsize = U64_AT(chunkdata);
+ chunkdata += 8;
+ }
+ else
+ {
+ msg_Err( p_this, "Couldn't find mdat in box 2!");
+ *mdatptr = 0;
+ return 0;
+ }
+ boxdata = chunkdata;
+ chunkdata += (boxsize - 16);
+ }
+ else
+ {
+ boxdata = chunkdata;
+ chunkdata += (boxsize - 8);
+ }
+ } while ( 0 != memcmp( boxname, "mdat", 4 ) );
+
+ *mdatptr = boxdata;
+
+ return chunkdata_end - ((uint8_t*)boxdata);
+}
+
+/* returns data ptr if valid (updating the chunk itself
+ tells the reader that the chunk is safe to read, which is not yet correct)*/
+static uint8_t* download_chunk( stream_t *s,
+ stream_sys_t* sys,
+ hds_stream_t* stream, volatile chunk_t* chunk )
+{
+ const char* quality = "";
+ char* server_base = sys->base_url;
+ if( stream->server_entry_count > 0 &&
+ strlen(stream->server_entries[0]) > 0 )
+ {
+ server_base = stream->server_entries[0];
+ }
+
+ if( stream->quality_segment_modifier )
+ {
+ quality = stream->quality_segment_modifier;
+ }
+
+ const char* movie_id = "";
+ bool free_movie_id = false;
+ if( stream->url && strlen(stream->url) > 0 )
+ {
+ if( isFQUrl( stream->url ) )
+ {
+ server_base = stream->url;
+ }
+ else
+ {
+ movie_id = stream->url;
+ }
+ }
+
+ char* fragment_url;
+ asprintf( &fragment_url, "%s/%s%sSeg%u-Frag%u",
+ server_base,
+ movie_id,
+ quality,
+ chunk->seg_num,
+ chunk->frag_num );
+ if( ! fragment_url )
+ {
+ msg_Err(s, "Failed to allocate memory for fragment url" );
+ return NULL;
+ }
+
+ msg_Info(s, "Downloading fragment %s",
+ fragment_url );
+
+ stream_t* download_stream = stream_UrlNew( s, fragment_url );
+ if( ! download_stream )
+ {
+ msg_Err(s, "Failed to download fragment %s", fragment_url );
+ free( fragment_url );
+ chunk->failed = true;
+ return NULL;
+ }
+ free( fragment_url );
+
+ int64_t size = stream_Size( download_stream );
+ chunk->data_len = (uint32_t) size;
+
+ if( size > 50*1024*1024 )
+ {
+ msg_Err(s, "Strangely-large chunk of %"PRIi64" Bytes" );
+ return NULL;
+ }
+
+ uint8_t* data = malloc( size );
+ if( ! data )
+ {
+ msg_Err(s, "Couldn't allocate chunk" );
+ return NULL;
+ }
+
+ int read = stream_Read( download_stream, data,
+ size );
+ chunk->data_len = read;
+
+ if( read < size )
+ {
+ msg_Err( s, "Requested %"PRIi64" bytes, "\
+ "but only got %d", size, read );
+ data = realloc( chunk->data, read );
+ chunk->failed = true;
+ return NULL;
+ }
+ else
+ {
+ chunk->failed = false;
+ }
+
+ stream_Delete( download_stream );
+ return data;
+}
+
+static void* download_thread( void* p )
+{
+ vlc_object_t* p_this = (vlc_object_t*)p;
+ stream_t* s = (stream_t*) p_this;
+ stream_sys_t* sys = s->p_sys;
+
+ // TODO: Change here for selectable stream
+ hds_stream_t* hds_stream = sys->hds_streams->pp_elems[0];
+
+ int canc = vlc_savecancel();
+
+ vlc_mutex_lock( & hds_stream->dl_lock );
+
+ while( ! sys->closed )
+ {
+ if( ! hds_stream->chunks_downloadpos )
+ {
+ volatile chunk_t* chunk = hds_stream->chunks_head;
+ while(chunk && chunk->data )
+ {
+ chunk = chunk->next;
+ }
+
+ if( chunk && ! chunk->data )
+ hds_stream->chunks_downloadpos = chunk;
+ }
+
+ while( hds_stream->chunks_downloadpos )
+ {
+ volatile chunk_t *chunk = hds_stream->chunks_downloadpos;
+
+ uint8_t *data = download_chunk( (stream_t*)p_this,
+ sys,
+ hds_stream,
+ chunk );
+
+ if( ! chunk->failed )
+ {
+ chunk->mdat_len =
+ find_chunk_mdat( p_this,
+ data,
+ data + chunk->data_len,
+ & chunk->mdat_data );
+ if( chunk->mdat_len == 0 ) {
+ chunk->mdat_len = chunk->data_len - (chunk->mdat_data - data);
+ }
+ hds_stream->chunks_downloadpos = chunk->next;
+ chunk->data = data;
+
+ sys->chunk_count++;
+ }
+ }
+
+ vlc_cond_wait( & hds_stream->dl_cond,
+ & hds_stream->dl_lock );
+ }
+
+ vlc_mutex_unlock( & hds_stream->dl_lock );
+
+ vlc_restorecancel( canc );
+ return NULL;
+}
+
+static volatile chunk_t* generate_new_chunk(
+ vlc_object_t* p_this,
+ volatile chunk_t* last_chunk,
+ hds_stream_t* hds_stream )
+{
+ stream_t* s = (stream_t*) p_this;
+ stream_sys_t *sys = s->p_sys;
+ volatile chunk_t *chunk = chunk_new();
+ int frun_entry = 0;
+
+ if( last_chunk )
+ {
+ chunk->timestamp = last_chunk->timestamp + last_chunk->duration;
+ chunk->frag_num = last_chunk->frag_num + 1;
+
+ if( ! sys->live )
+ {
+ frun_entry = last_chunk->frun_entry;
+ }
+ }
+ else
+ {
+ fragment_run_t* first_frun = hds_stream->fragment_runs;
+ fragment_run_t* latest_frun = hds_stream->fragment_runs + ( hds_stream->fragment_run_count - 1 );
+ if( sys->live )
+ {
+ chunk->timestamp = (hds_stream->live_current_time * ((uint64_t)hds_stream->afrt_timescale)) / ((uint64_t)hds_stream->timescale);
+ }
+ else
+ {
+ chunk->timestamp = first_frun->fragment_timestamp;
+ chunk->frag_num = first_frun->fragment_number_start;
+ }
+ }
+
+ for( frun_entry; frun_entry < hds_stream->fragment_run_count;
+ frun_entry++ )
+ {
+ /* check for discontinuity first */
+ if( hds_stream->fragment_runs[frun_entry].fragment_duration == 0 )
+ {
+ if( frun_entry == hds_stream->fragment_run_count - 1 )
+ {
+ msg_Err( p_this, "Discontinuity but can't find next timestamp!");
+ return NULL;
+ }
+
+ chunk->frag_num = hds_stream->fragment_runs[frun_entry+1].fragment_number_start;
+ chunk->duration = hds_stream->fragment_runs[frun_entry+1].fragment_duration;
+ chunk->timestamp = hds_stream->fragment_runs[frun_entry+1].fragment_timestamp;
+
+ frun_entry++;
+ break;
+ }
+
+ if( chunk->frag_num == 0 )
+ {
+ if( frun_entry == hds_stream->fragment_run_count - 1 ||
+ ( chunk->timestamp >= hds_stream->fragment_runs[frun_entry].fragment_timestamp &&
+ chunk->timestamp < hds_stream->fragment_runs[frun_entry+1].fragment_timestamp )
+ )
+ {
+ fragment_run_t* frun = hds_stream->fragment_runs + frun_entry;
+ chunk->frag_num = frun->fragment_number_start + ( (chunk->timestamp - frun->fragment_timestamp) /
+ frun->fragment_duration );
+ chunk->duration = frun->fragment_duration;
+ }
+
+ }
+
+ if( hds_stream->fragment_runs[frun_entry].fragment_number_start <=
+ chunk->frag_num &&
+ (frun_entry == hds_stream->fragment_run_count - 1 ||
+ hds_stream->fragment_runs[frun_entry+1].fragment_number_start > chunk->frag_num ) )
+ {
+ chunk->duration = hds_stream->fragment_runs[frun_entry].fragment_duration;
+ chunk->timestamp = hds_stream->fragment_runs[frun_entry].fragment_timestamp +
+ chunk->duration * (chunk->frag_num - hds_stream->fragment_runs[frun_entry].fragment_number_start);
+ break;
+ }
+ }
+
+ if( frun_entry == hds_stream->fragment_run_count )
+ {
+ msg_Err( p_this, "Couldn'd find the fragment run!" );
+ return NULL;
+ }
+
+ int srun_entry = 0;
+ int segment = 0;
+ uint64_t fragments_accum = chunk->frag_num;
+ for( srun_entry = 0; srun_entry < hds_stream->segment_run_count;
+ srun_entry++ )
+ {
+ segment = hds_stream->segment_runs[srun_entry].first_segment +
+ (chunk->frag_num - fragments_accum ) / hds_stream->segment_runs[srun_entry].fragments_per_segment;
+
+ if( srun_entry + 1 == hds_stream->segment_run_count ||
+ hds_stream->segment_runs[srun_entry+1].first_segment > segment )
+ {
+ break;
+ }
+
+ fragments_accum += (
+ (hds_stream->segment_runs[srun_entry+1].first_segment -
+ hds_stream->segment_runs[srun_entry].first_segment) *
+ hds_stream->segment_runs[srun_entry].fragments_per_segment );
+ }
+
+ chunk->seg_num = segment;
+ chunk->frun_entry = frun_entry;
+
+ if( ! sys->live )
+ {
+ if( (chunk->timestamp + chunk->duration) / hds_stream->afrt_timescale >= sys->duration_seconds )
+ {
+ chunk->eof = true;
+ }
+ }
+
+ return chunk;
+}
+
+static void* maintain_live_chunks(
+ vlc_object_t* p_this,
+ hds_stream_t* hds_stream
+ )
+{
+ if( ! hds_stream->chunks_head )
+ {
+ /* just start with the earliest in the abst
+ * maybe it would be better to use the currentMediaTime?
+ * but then we are right on the edge of buffering, esp for
+ * small fragments */
+ hds_stream->chunks_head = generate_new_chunk( p_this, 0, hds_stream );
+ hds_stream->chunks_livereadpos = hds_stream->chunks_head;
+ }
+
+ volatile chunk_t* chunk = hds_stream->chunks_head;
+ bool dl = false;
+ while( ( chunk->timestamp * ((uint64_t)hds_stream->timescale) )
+ / ((uint64_t)hds_stream->afrt_timescale)
+ <= hds_stream->live_current_time )
+ {
+ if( chunk->next )
+ {
+ chunk = chunk->next;
+ }
+ else
+ {
+ chunk->next = generate_new_chunk( p_this, chunk, hds_stream );
+ chunk = chunk->next;
+ dl = true;
+ }
+ }
+
+ if( dl )
+ vlc_cond_signal( & hds_stream->dl_cond );
+
+ chunk = hds_stream->chunks_head;
+ while( chunk && chunk->data && chunk->mdat_pos >= chunk->mdat_len && chunk->next )
+ {
+ volatile chunk_t* next_chunk = chunk->next;
+ chunk_free( chunk );
+ chunk = chunk->next;
+ }
+
+ if( ! hds_stream->chunks_livereadpos )
+ hds_stream->chunks_livereadpos = hds_stream->chunks_head;
+
+ hds_stream->chunks_head = chunk;
+}
+
+static void* live_thread( void* p )
+{
+ vlc_object_t* p_this = (vlc_object_t*)p;
+ stream_t* s = (stream_t*) p_this;
+ stream_sys_t* sys = s->p_sys;
+
+ // TODO: Change here for selectable stream
+ hds_stream_t* hds_stream = sys->hds_streams->pp_elems[0];
+
+ int canc = vlc_savecancel();
+
+ char* abst_url;
+
+ if( hds_stream->abst_url &&
+ ( isFQUrl( hds_stream->abst_url ) ) )
+ {
+ abst_url = strdup ( hds_stream->abst_url );
+ }
+ else
+ {
+ char* server_base = sys->base_url;
+
+ asprintf( &abst_url, "%s/%s",
+ server_base,
+ hds_stream->abst_url );
+ }
+
+ mtime_t last_dl_start_time;
+
+ while( ! sys->closed )
+ {
+ last_dl_start_time = mdate();
+ stream_t* download_stream = stream_UrlNew( p_this, abst_url );
+ if( ! download_stream )
+ {
+ msg_Err( p_this, "Failed to download abst %s", abst_url );
+ }
+ else
+ {
+ int64_t size = stream_Size( download_stream );
+ uint8_t* data = malloc( size );
+ int read = stream_Read( download_stream, data,
+ size );
+ if( read < size )
+ {
+ msg_Err( p_this, "Requested %"PRIi64" bytes, " \
+ "but only got %d", size, read );
+
+ }
+ else
+ {
+ vlc_mutex_lock( & hds_stream->abst_lock );
+ parse_BootstrapData( p_this, hds_stream,
+ data, data + read );
+ vlc_mutex_unlock( & hds_stream->abst_lock );
+ maintain_live_chunks( p_this, hds_stream );
+ }
+
+ free( data );
+
+ stream_Delete( download_stream );
+ }
+
+ mwait( last_dl_start_time + ( ((uint64_t)hds_stream->fragment_runs[hds_stream->fragment_run_count-1].fragment_duration) * 1000000ULL) / ((uint64_t)hds_stream->afrt_timescale) );
+
+
+ }
+
+ free( abst_url );
+
+ vlc_restorecancel( canc );
+ return NULL;
+}
+
+static int parse_Manifest( stream_t *s )
+{
+ xml_t *vlc_xml = NULL;
+ xml_reader_t *vlc_reader = NULL;
+ int type = UNKNOWN_ES;
+ 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;
+ }
+
+ char *node;
+
+ stream_sys_t *sys = s->p_sys;
+
+ sys->duration_seconds = 0;
+
+#define MAX_BOOTSTRAP_INFO 10
+ bootstrap_info bootstraps[MAX_BOOTSTRAP_INFO];
+ uint8_t bootstrap_idx = 0;
+ memset( bootstraps, 0, sizeof(bootstrap_info) * MAX_BOOTSTRAP_INFO );
+
+#define MAX_MEDIA_ELEMENTS 10
+ media_info medias[MAX_MEDIA_ELEMENTS];
+ uint8_t media_idx = 0;
+ memset( medias, 0, sizeof(media_info) * MAX_MEDIA_ELEMENTS );
+
+#define MAX_XML_DEPTH 256
+ char* element_stack[256];
+ uint8_t current_element_idx = 0;
+ char* current_element = 0;
+ memset( element_stack, 0, sizeof(char*) * MAX_XML_DEPTH );
+
+ const char* attr_name;
+ const char* attr_value;
+
+ char* media_id = 0;
+
+#define TIMESCALE 10000000
+ while( (type = xml_ReaderNextNode( vlc_reader, &node )) > 0 )
+ {
+ switch( type )
+ {
+ case XML_READER_STARTELEM:
+ if( current_element_idx == 0 && element_stack[current_element_idx] == 0 ) {
+ element_stack[current_element_idx] = strdup( node );
+ } else {
+ element_stack[++current_element_idx] = strdup( node );
+ }
+ break;
+ case XML_READER_ENDELEM:
+ if( ! strcmp( current_element, "bootstrapInfo") ) {
+ if( bootstrap_idx + 1 == MAX_BOOTSTRAP_INFO ) {
+ msg_Warn( (vlc_object_t*) s, "Too many bootstraps, ignoring" );
+ } else {
+ bootstrap_idx++;
+ }
+ }
+
+ free( current_element );
+ element_stack[current_element_idx--] = 0;
+ break;
+ }
+
+ if( ! element_stack[current_element_idx] ) {
+ continue;
+ }
+
+ current_element = element_stack[current_element_idx];
+
+ if( type == XML_READER_STARTELEM && ! strcmp( current_element, "media") )
+ {
+ if( media_idx == MAX_MEDIA_ELEMENTS )
+ {
+ msg_Err( (vlc_object_t*) s, "Too many media elements, quitting" );
+ return VLC_EGENERIC;
+ }
+
+ while( attr_name = xml_ReaderNextAttr( vlc_reader, &attr_value ) )
+ {
+ if( !strcmp(attr_name, "streamId" ) )
+ {
+ medias[media_idx].stream_id = strdup( attr_value );
+ }
+ if( !strcmp(attr_name, "url" ) )
+ {
+ medias[media_idx].media_url = strdup( attr_value );
+ }
+ if( !strcmp(attr_name, "bootstrapInfoId" ) )
+ {
+ medias[media_idx].bootstrap_id = strdup( attr_value );
+ }
+ }
+
+ media_idx++;
+ }
+
+ if( type == XML_READER_STARTELEM && ! strcmp( current_element, "bootstrapInfo") )
+ {
+ while( attr_name = xml_ReaderNextAttr( vlc_reader, &attr_value ) )
+ {
+ if( !strcmp(attr_name, "url" ) )
+ {
+ bootstraps[bootstrap_idx].url = strdup( attr_value );
+ }
+ if( !strcmp(attr_name, "id" ) )
+ {
+ bootstraps[bootstrap_idx].id = strdup( attr_value );
+ }
+ if( !strcmp(attr_name, "profile" ) )
+ {
+ bootstraps[bootstrap_idx].profile = strdup( attr_value );
+ }
+ }
+ }
+
+ if( type == XML_READER_TEXT )
+ {
+ if( ! strcmp( current_element, "bootstrapInfo" ) )
+ {
+ char* start = node;
+ char* end = start + strlen(start);
+ whitespace_substr( &start, &end );
+ *end = '\0';
+
+ bootstraps[bootstrap_idx].data_len =
+ vlc_b64_decode_binary( (uint8_t**)&bootstraps[bootstrap_idx].data, start );
+ if( ! bootstraps[bootstrap_idx].data )
+ {
+ msg_Err( (vlc_object_t*) s, "Couldn't decode bootstrap info" );
+ }
+ }
+ if( ! strcmp( current_element, "duration" ) )
+ {
+ sys->duration_seconds = (int)atof( node );
+ }
+ if( ! strcmp( current_element, "id" ) )
+ {
+ if( current_element != 0 &&
+ ! strcmp( element_stack[current_element_idx-1], "manifest" ) )
+ {
+ media_id = strdup( node );
+ }
+ }
+ }
+ }
+
+ xml_ReaderDelete( vlc_reader );
+ xml_Delete( vlc_xml );
+
+ for( int i = 0; i <= media_idx; i++ )
+ {
+ for( int j = 0; j < bootstrap_idx; j++ )
+ {
+ if( ( ! medias[i].bootstrap_id && ! bootstraps[j].id ) ||
+ (medias[i].bootstrap_id && bootstraps[j].id &&
+ ! strcmp( medias[i].bootstrap_id, bootstraps[j].id ) ) )
+ {
+ hds_stream_t* new_stream = malloc(sizeof(hds_stream_t));
+ memset( new_stream, 0, sizeof(hds_stream_t));
+
+ vlc_mutex_init( & new_stream->abst_lock );
+ vlc_mutex_init( & new_stream->dl_lock );
+ vlc_cond_init( & new_stream->dl_cond );
+
+ if( sys->duration_seconds )
+ {
+ sys->live = false;
+ }
+ else
+ {
+ sys->live = true;
+ }
+
+ if( medias[i].media_url )
+ {
+ new_stream->url = strdup( medias[i].media_url );
+ }
+
+ if( ! sys->live )
+ {
+ parse_BootstrapData( (vlc_object_t*)s,
+ new_stream,
+ bootstraps[j].data,
+ bootstraps[j].data + bootstraps[j].data_len );
+
+ new_stream->download_leadtime = 15;
+
+ new_stream->chunks_head = generate_new_chunk( s, 0, new_stream );
+ volatile chunk_t* chunk = new_stream->chunks_head;
+ uint64_t total_duration = chunk->duration;
+ while( chunk && total_duration/new_stream->afrt_timescale < new_stream->download_leadtime )
+ {
+ chunk->next = generate_new_chunk( s, chunk, new_stream );
+ chunk = chunk->next;
+ if( chunk )
+ total_duration += chunk->duration;
+ }
+ }
+ else
+ {
+ new_stream->abst_url = strdup( bootstraps[j].url );
+ }
+
+ vlc_array_append( sys->hds_streams, new_stream );
+
+ msg_Info( (vlc_object_t*)s, "New track with quality_segment(%s), timescale(%u), movie_id(%s), segment_run_count(%d), fragment_run_count(%u)",
+ new_stream->quality_segment_modifier?"":new_stream->quality_segment_modifier, new_stream->timescale,
+ new_stream->movie_id, new_stream->segment_run_count, new_stream->fragment_run_count );
+
+ }
+ }
+ }
+
+ for( int i = 0; i < MAX_MEDIA_ELEMENTS; i++ )
+ {
+ FREENULL( medias[media_idx].stream_id );
+ FREENULL( medias[media_idx].media_url );
+ FREENULL( medias[media_idx].bootstrap_id );
+ }
+
+ for( int i = 0; i < MAX_BOOTSTRAP_INFO; i++ )
+ {
+ FREENULL( bootstraps[i].data );
+ FREENULL( bootstraps[i].id );
+ FREENULL( bootstraps[i].url );
+ FREENULL( bootstraps[i].profile );
+ }
+
+ FREENULL( media_id );
+
+ return VLC_SUCCESS;
+}
+
+static void hds_free( hds_stream_t *p_stream )
+{
+ FREENULL( p_stream->quality_segment_modifier );
+
+ FREENULL( p_stream->abst_url );
+
+ FREENULL( p_stream->url );
+ FREENULL( p_stream->movie_id );
+ for( int i = 0; i < p_stream->server_entry_count; i++ )
+ {
+ FREENULL( p_stream->server_entries[i] );
+ }
+
+ volatile chunk_t* chunk = p_stream->chunks_head;
+ while( chunk )
+ {
+ volatile chunk_t* next = chunk->next;
+ chunk_free( chunk );
+ chunk = next;
+ }
+
+ free( p_stream );
+}
+
+static void SysCleanup( stream_sys_t *p_sys )
+{
+ if ( p_sys->hds_streams )
+ {
+ for ( int i=0; i< p_sys->hds_streams->i_count ; i++ )
+ hds_free( p_sys->hds_streams->pp_elems[i] );
+ vlc_array_destroy( p_sys->hds_streams );
+ }
+ free( p_sys->base_url );
+}
+
+static int Open( vlc_object_t *p_this )
+{
+ stream_t *s = (stream_t*)p_this;
+ stream_sys_t *p_sys;
+
+ if( !isHDS( s ) )
+ return VLC_EGENERIC;
+
+ msg_Info( p_this, "HTTP Dynamic 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;
+
+ p_sys->flv_header_bytes_sent = 0;
+
+ p_sys->hds_streams = vlc_array_new();
+
+ if( parse_Manifest( s ) != VLC_SUCCESS )
+ {
+ SysCleanup( p_sys );
+ free( p_sys );
+ return VLC_EGENERIC;
+ }
+
+ s->pf_read = Read;
+ s->pf_peek = Peek;
+ s->pf_control = Control;
+
+ if( vlc_clone( &p_sys->dl_thread, download_thread, s, VLC_THREAD_PRIORITY_INPUT ) )
+ {
+ SysCleanup( p_sys );
+ free( p_sys );
+ return VLC_EGENERIC;
+ }
+
+ if( p_sys->live ) {
+ msg_Info( p_this, "Live stream detected" );
+
+ if( vlc_clone( &p_sys->live_thread, live_thread, s, VLC_THREAD_PRIORITY_INPUT ) )
+ {
+ SysCleanup( p_sys );
+ free( p_sys );
+ 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;
+
+ // TODO: Change here for selectable stream
+ hds_stream_t *stream = s->p_sys->hds_streams->pp_elems[0];
+
+ p_sys->closed = true;
+ vlc_cond_signal( & stream->dl_cond );
+
+ vlc_join( p_sys->dl_thread, NULL );
+ vlc_mutex_destroy( &stream->dl_lock );
+ vlc_cond_destroy( &stream->dl_cond );
+ vlc_mutex_destroy( &stream->abst_lock );
+
+ if( p_sys->live )
+ {
+ vlc_join( p_sys->live_thread, NULL );
+ }
+
+ SysCleanup( p_sys );
+ free( p_sys );
+}
+
+static unsigned char flv_header[] = {
+ 'F',
+ 'L',
+ 'V',
+ 0x1, //version
+ 0x5, //indicates audio and video
+ 0x0, // length
+ 0x0, // length
+ 0x0, // length
+ 0x9, // length of header
+ 0x0,
+ 0x0,
+ 0x0,
+ 0x0, // initial "trailer"
+};
+
+static int send_flv_header( stream_sys_t* p_sys, void* buffer, unsigned i_read,
+ bool peek )
+{
+ uint32_t to_be_read = i_read;
+ if( to_be_read > 13 - p_sys->flv_header_bytes_sent ) {
+ to_be_read = 13 - p_sys->flv_header_bytes_sent;
+ }
+
+ memcpy( buffer, flv_header + p_sys->flv_header_bytes_sent, to_be_read );
+
+ if( ! peek )
+ {
+ p_sys->flv_header_bytes_sent += to_be_read;
+ }
+ return to_be_read;
+}
+
+static unsigned read_chunk_data(
+ vlc_object_t* p_this,
+ uint8_t* buffer, unsigned read_len,
+ hds_stream_t* stream,
+ bool* eof
+ )
+{
+ stream_t* s = (stream_t*) p_this;
+ stream_sys_t* sys = s->p_sys;
+ volatile chunk_t* chunk = stream->chunks_head;
+ uint8_t* buffer_start = buffer;
+ bool dl = false;
+
+ while( chunk && chunk->data && read_len > 0 )
+ {
+ /* in the live case, it is necessary to store the next
+ * pointer here, since as soon as we increment the mdat_pos, that
+ * chunk may be deleted */
+ volatile chunk_t* next = chunk->next;
+
+ if( chunk->mdat_pos < chunk->mdat_len )
+ {
+ unsigned cp_len = chunk->mdat_len - chunk->mdat_pos;
+ if( cp_len > read_len )
+ cp_len = read_len;
+ memcpy( buffer, chunk->mdat_data + chunk->mdat_pos,
+ cp_len );
+
+ read_len -= cp_len;
+ buffer += cp_len;
+ chunk->mdat_pos += cp_len;
+ }
+
+ if( ! sys->live && (chunk->mdat_pos >= chunk->mdat_len || chunk->failed) )
+ {
+ if( chunk->eof )
+ {
+ *eof = true;
+ }
+
+ /* make sure there is at least one chunk in the queue */
+ if( ! chunk->next && ! chunk->eof )
+ {
+ chunk->next = generate_new_chunk( p_this, chunk, stream );
+ dl = true;
+ }
+
+ chunk_free( chunk );
+ chunk = next;
+ stream->chunks_head = chunk;
+ }
+ else if( sys->live && (chunk->mdat_pos >= chunk->mdat_len || chunk->failed) )
+ {
+ chunk = next;
+ }
+ }
+
+ if( sys->live )
+ {
+ stream->chunks_livereadpos = chunk;
+ }
+
+ /* new chunk generation is handled by a different thread in live case */
+ if( ! sys->live )
+ {
+ chunk = stream->chunks_head;
+ if( chunk )
+ {
+ uint64_t total_duration = chunk->duration;
+ while( chunk && total_duration/stream->afrt_timescale < stream->download_leadtime )
+ {
+ if( ! chunk->next )
+ {
+ chunk->next = generate_new_chunk( p_this, chunk, stream );
+ dl = true;
+ }
+ chunk = chunk->next;
+ total_duration += chunk->duration;
+ }
+ }
+
+ if( dl )
+ vlc_cond_signal( & stream->dl_cond );
+ }
+
+ return ( ((uint8_t*)buffer) - ((uint8_t*)buffer_start));
+}
+
+static int Read( stream_t *s, void *buffer, unsigned i_read )
+{
+ stream_sys_t *p_sys = s->p_sys;
+
+ // TODO: change here for selectable stream
+ hds_stream_t *stream = s->p_sys->hds_streams->pp_elems[0];
+ int length = 0;
+
+ uint8_t *buffer_uint8 = (uint8_t*) buffer;
+
+ unsigned hdr_bytes = send_flv_header( p_sys, buffer, i_read, false );
+ length += hdr_bytes;
+ i_read -= hdr_bytes;
+ buffer_uint8 += hdr_bytes;
+
+ bool eof = false;
+ while( i_read > 0 && ! eof )
+ {
+ int tmp_length = read_chunk_data( s, buffer_uint8, i_read, stream, &eof );
+ buffer_uint8 += tmp_length;
+ i_read -= tmp_length;
+ length += tmp_length;
+ }
+
+ return length;
+}
+
+static int Peek( stream_t *s, const uint8_t **pp_peek, unsigned i_peek )
+{
+ stream_sys_t *p_sys = s->p_sys;
+
+ // TODO: change here for selectable stream
+ hds_stream_t *stream = p_sys->hds_streams->pp_elems[0];
+
+ if( p_sys->flv_header_bytes_sent < 13 )
+ {
+ *pp_peek = flv_header + p_sys->flv_header_bytes_sent;
+ return 13 - p_sys->flv_header_bytes_sent;
+ }
+
+ if( stream->chunks_head && ! stream->chunks_head->failed && stream->chunks_head->data )
+ {
+ // TODO: change here for selectable stream
+ volatile chunk_t* chunk = stream->chunks_head;
+ *pp_peek = chunk->mdat_data + chunk->mdat_pos;
+ if( chunk->mdat_len - chunk->mdat_pos < i_peek )
+ {
+ return chunk->mdat_len - chunk->mdat_pos;
+ }
+ else
+ {
+ return i_peek;
+ }
+ } else
+ {
+ return 0;
+ }
+}
+
+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 * )) = false;
+ break;
+ case STREAM_CAN_FASTSEEK:
+ case STREAM_CAN_PAUSE: /* TODO */
+ *(va_arg( args, bool * )) = false;
+ break;
+ case STREAM_CAN_CONTROL_PACE:
+ *(va_arg( args, bool * )) = true;
+ break;
+ case STREAM_GET_PTS_DELAY:
+ *va_arg (args, int64_t *) = INT64_C(1000) *
+ var_InheritInteger(s, "network-caching");
+ break;
+ default:
+ return VLC_EGENERIC;
+ }
+ return VLC_SUCCESS;
+}
diff --git a/modules/stream_filter/hds/hds.h b/modules/stream_filter/hds/hds.h
new file mode 100644
index 0000000..5a3b825
--- /dev/null
+++ b/modules/stream_filter/hds/hds.h
@@ -0,0 +1,127 @@
+/*****************************************************************************
+ * hds.h: misc. stuff
+ *****************************************************************************
+ *
+ * Author: Jonathan Thambidurai <jonathan AT fastly DOT com>
+ *
+ * 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_HDS_H
+#define _VLC_HDS_H 1
+
+typedef struct chunk_s
+{
+ int64_t duration; /* chunk duration in afrt timescale units */
+ int64_t timestamp;
+ int32_t frag_num;
+ uint32_t seg_num;
+ uint32_t frun_entry; /* Used to speed things up in vod situations */
+
+ uint32_t data_len;
+
+ uint32_t mdat_pos; /* position in the mdat */
+ uint32_t mdat_len;
+
+ void *next;
+
+ uint8_t *mdat_data;
+ uint8_t *data;
+ bool failed;
+ bool eof;
+} chunk_t;
+
+typedef struct segment_run_s
+{
+ uint32_t first_segment;
+ uint32_t fragments_per_segment;
+} segment_run_t;
+
+typedef struct fragment_run_s
+{
+ uint32_t fragment_number_start;
+ uint32_t fragment_duration;
+ uint64_t fragment_timestamp;
+ uint8_t discont;
+} fragment_run_t;
+
+typedef struct hds_stream_s
+{
+ /* linked-list of chunks */
+ volatile chunk_t *chunks_head;
+ volatile chunk_t *chunks_livereadpos;
+ volatile chunk_t *chunks_downloadpos;
+
+ char* quality_segment_modifier;
+
+ /* we can make this configurable */
+ uint64_t download_leadtime;
+
+ /* in timescale units */
+ uint32_t total_duration;
+
+ uint32_t afrt_timescale;
+
+ /* these two values come from the abst */
+ uint32_t timescale;
+ uint64_t live_current_time;
+
+ vlc_mutex_t abst_lock;
+
+ vlc_mutex_t dl_lock;
+ vlc_cond_t dl_cond;
+
+ /* can be left as null */
+ char* abst_url;
+
+ /* this comes from the manifest media section */
+ char* url;
+
+ /* this comes from the bootstrap info */
+ char* movie_id;
+
+#define MAX_HDS_SERVERS 10
+ char* server_entries[MAX_HDS_SERVERS];
+ uint8_t server_entry_count;
+
+#define MAX_HDS_SEGMENT_RUNS 256
+ segment_run_t segment_runs[MAX_HDS_SEGMENT_RUNS];
+ uint8_t segment_run_count;
+
+#define MAX_HDS_FRAGMENT_RUNS 10000
+ fragment_run_t fragment_runs[MAX_HDS_FRAGMENT_RUNS];
+ uint32_t fragment_run_count;
+} hds_stream_t;
+
+struct stream_sys_t
+{
+ char *base_url; /* URL common part for chunks */
+ vlc_thread_t live_thread;
+ vlc_thread_t dl_thread;
+
+ /* we pend on peek until some number of segments arrives; otherwise
+ * the downstream system dies in case of playback */
+ volatile uint64_t chunk_count;
+
+ vlc_array_t *hds_streams; /* available streams */
+
+ uint32_t flv_header_bytes_sent;
+ uint32_t duration_seconds;
+
+ bool live;
+ bool closed;
+};
+
+#endif
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 0a86072..cacff6a 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1009,6 +1009,7 @@ modules/services_discovery/windrive.c
modules/services_discovery/xcb_apps.c
modules/stream_filter/dash/dash.cpp
modules/stream_filter/decomp.c
+modules/stream_filter/hds/hds.c
modules/stream_filter/httplive.c
modules/stream_filter/record.c
modules/stream_filter/smooth/smooth.c
--
1.9.1
More information about the vlc-devel
mailing list