[vlc-devel] [PATCH] Add Smooth Streaming module
F. Yhuel
fyhuel at viotech.net
Thu Nov 17 17:42:04 CET 2011
From: F. Yhuel <yhuelf at gmail.com>
This is a "pre-alpha" version. No adaptation, VOD only, no DRMs, and others
limitations.
As you can see, it is heavily inpired by HLS module of Jean-Paul Saman.
Moreover, you need to have a Python script installed in
/usr/local/share/ to test it. Non Linux users may adapt the code. The
script is available in a public git repository:
git clone http://46.105.6.148:81/vlcScriptsViotech.git
Note: this script is quite ugly, at least for some parts. Patches and
comments are welcome, though it will be eventually moved into C code.
If all goes well, it should work with Anevia Smooth Streaming demo
server:
./vlc http://demo.anevia.com:3130/html/player/smooth/vod/content1.ism/Manifest
---
modules/stream_filter/Modules.am | 2 +
modules/stream_filter/smooth.c | 1197 ++++++++++++++++++++++++++++++++++++++
2 files changed, 1199 insertions(+), 0 deletions(-)
create mode 100644 modules/stream_filter/smooth.c
diff --git a/modules/stream_filter/Modules.am b/modules/stream_filter/Modules.am
index 741e8f4..9c4dad5 100644
--- a/modules/stream_filter/Modules.am
+++ b/modules/stream_filter/Modules.am
@@ -3,10 +3,12 @@ DIST_SUBDIRS = dash
SOURCES_decomp = decomp.c
SOURCES_stream_filter_record = record.c
SOURCES_stream_filter_httplive = httplive.c
+SOURCES_stream_filter_smooth = smooth.c
libvlc_LTLIBRARIES += \
libstream_filter_record_plugin.la \
libstream_filter_httplive_plugin.la \
+ libstream_filter_smooth_plugin.la \
$(NULL)
if !HAVE_WIN32
if !HAVE_WINCE
diff --git a/modules/stream_filter/smooth.c b/modules/stream_filter/smooth.c
new file mode 100644
index 0000000..4854278
--- /dev/null
+++ b/modules/stream_filter/smooth.c
@@ -0,0 +1,1197 @@
+/*****************************************************************************
+ * smooth.c: Smooth Streaming stream filter
+ *****************************************************************************
+ * Copyright (C) 1996-2011 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
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Preamble
+ *****************************************************************************/
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <limits.h>
+#include <errno.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_threads.h>
+#include <vlc_arrays.h>
+#include <vlc_stream.h>
+#include <vlc_url.h>
+#include <vlc_memory.h>
+
+/* In all the file, "segment" and "chunk" refer to the same thing,
+ * and used intechangeably */
+
+/*****************************************************************************
+ * 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 stream filter"))
+ set_capability("stream_filter", 20)
+ set_callbacks(Open, Close)
+vlc_module_end()
+
+/*****************************************************************************
+ *
+ *****************************************************************************/
+
+/* Time Scale, I've never seen another value, but it may happen */
+#define TS 10000000
+
+enum Type {
+ VIDEO=0,
+ AUDIO,
+ TEXT,
+};
+
+enum FourCC {
+ H264=0,
+ AACL,
+ WVC1,
+ WMAP,
+ TTML,
+};
+
+typedef struct segment_s
+{
+ uint64_t duration; /* segment duration (seconds) */
+ uint64_t start_time;
+ uint64_t size; /* segment size in bytes */
+ uint64_t bandwidth; /* size / duration (bits per second)*/
+ uint32_t sequence; /* unique sequence number */
+ enum Type type;
+
+ vlc_mutex_t lock;
+ block_t *data; /* data */
+} segment_t;
+
+typedef struct quality_level_s
+{
+ int Index; /* An ordinal that identifies the Track and MUST be
+ unique for each Track in the Stream */
+ enum FourCC FourCC;
+ uint64_t Bitrate;
+ uint32_t MaxWidth;
+ uint32_t MaxHeight;
+ uint32_t SamplingRate;
+ uint32_t Channels;
+ uint32_t BitsPerSample;
+ uint32_t PacketSize;
+ uint32_t AudioTag;
+ uint8_t *CodecPrivateData
+
+} quality_level_t;
+
+typedef struct sms_stream_s
+{
+ vlc_array_t *qlevels;
+ vlc_array_t *segments; /* list of segments */
+ uint32_t vod_chunks_nb; /* total num of chunks of the VOD media */
+ uint32_t qlevel_nb; /* number of quality levels */
+ char *name;
+ char *url_template;
+ enum Type type;
+ quality_level_t *playback_qlvl;
+ quality_level_t *download_qlvl;
+
+} sms_stream_t;
+
+
+struct stream_sys_t
+{
+ vlc_url_t ismc; /* Manifest url */
+ char *base_url;
+ vlc_thread_t thread; /* HLS segment download thread */
+
+ block_t *peeked;
+
+ /* */
+ segment_t *init_sgmt;
+ vlc_array_t *sms_streams; /* array of sms_stream_t */
+ vlc_array_t *dld_chunks; /* segments that have been downloaded*/
+ sms_stream_t *vstream; /* current video sms_stream */
+ sms_stream_t *astream; /* current audio sms_stream */
+ sms_stream_t *tstream; /* current text sms_stream */
+ int stream_nb;
+ uint64_t bandwidth; /* measured bandwidth (bits per second) */
+ uint64_t vod_duration; /* total duration of the VOD media */
+
+ /* 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^-1 */
+
+ uint32_t aindex; /* current audio segment for download */
+ uint32_t vindex;
+ uint32_t tindex;
+ vlc_mutex_t lock_wait; /* protect segment 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 segment for playback */
+ } playback;
+
+ /* state */
+ bool b_cache; /* can cache files */
+ bool b_live; /* live stream? or vod? */
+ bool b_error; /* parsing error */
+};
+
+
+/****************************************************************************
+ * Local prototypes
+ ****************************************************************************/
+static int Read (stream_t *, void *p_read, unsigned int i_read);
+static int Peek (stream_t *, const uint8_t **pp_peek, unsigned int i_peek);
+static int Control(stream_t *, int i_query, va_list);
+
+static char *ConstructUrl(const char *template, const char *base_url,
+ uint64_t bandwidth, uint64_t start_time);
+
+static char *inverse_string(const char *str);
+static int sms_Download(stream_t *s, uint64_t start_time, sms_stream_t *sms);
+static int Download(stream_t *s, uint64_t start_time, sms_stream_t *sms,\
+ quality_level_t *qlevel);
+static void* sms_Thread(void *p_this);
+/****************************************************************************
+ *
+ ****************************************************************************/
+
+static bool isSmoothStreaming(stream_t *s)
+{
+ const uint8_t *peek;
+ const char *conv_peek;
+ const char *needle = "<SmoothStreamingMedia";
+
+ int i_size = stream_Peek(s->p_source, &peek, 1024);
+ if (i_size < 1)
+ return false;
+
+ if (strstr((const char*)peek, needle) != NULL)
+ return true;
+ else
+ /* maybe it's utf-16 encoding, should we also test other encodings? */
+ {
+ conv_peek = FromCharset("UTF-16", peek, 1024);
+ if (conv_peek != NULL)
+ if (strstr(conv_peek, needle) != NULL)
+ return true;
+ }
+ return false;
+}
+
+static quality_level_t * ql_New(void)
+{
+ quality_level_t *ql = (quality_level_t *)malloc(sizeof(quality_level_t));
+ if (ql == NULL)
+ return NULL;
+ ql->Index = -1;
+ ql->FourCC = H264;
+ ql->Bitrate = 0;
+ return ql;
+}
+
+static void ql_Free(quality_level_t *qlevel)
+{
+ free(qlevel);
+ qlevel = NULL;
+}
+
+/* Segment */
+static segment_t *segment_New(sms_stream_t* sms, uint64_t duration,\
+ uint64_t start_time)
+{
+ segment_t *segment = (segment_t *)malloc(sizeof(segment_t));
+ if (segment == NULL)
+ return NULL;
+
+ segment->duration = duration; /* seconds */
+ segment->start_time = start_time; /* seconds */
+ segment->size = 0; /* bytes */
+ segment->bandwidth = 0;
+ segment->data = NULL;
+ segment->sequence = vlc_array_count(sms->segments);
+ vlc_array_append(sms->segments, segment);
+ vlc_mutex_init(&segment->lock);
+ return segment;
+}
+
+static void segment_Free(segment_t *segment)
+{
+ vlc_mutex_destroy(&segment->lock);
+
+ if (segment->data)
+ block_Release(segment->data);
+ free(segment);
+ segment = NULL;
+}
+
+static sms_stream_t * sms_New(void)
+{
+ sms_stream_t *sms = (sms_stream_t *)malloc(sizeof(sms_stream_t));
+ if (sms == NULL)
+ return NULL;
+ sms->qlevels = vlc_array_new();
+ sms->segments = vlc_array_new();
+ sms->vod_chunks_nb = 0;
+ sms->qlevel_nb = 0;
+ sms->name = NULL;
+ sms->url_template = NULL;
+ sms->type = VIDEO;
+ sms->playback_qlvl = NULL;
+ return sms;
+}
+
+static void sms_Free(sms_stream_t *sms)
+{
+ if (sms->segments)
+ {
+ 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);
+
+ for (int n = 0; n < vlc_array_count(sms->segments); n++)
+ {
+ segment_t *segment =\
+ (segment_t *)vlc_array_item_at_index(sms->segments, n);
+ if (segment) segment_Free(segment);
+ }
+ vlc_array_destroy(sms->segments);
+ }
+
+ free(sms);
+ sms = NULL;
+}
+
+static int parse_Manifest(stream_t *s, vlc_array_t *streams)
+{
+ stream_sys_t *p_sys = s->p_sys;
+ xml_t *vlc_xml = NULL;
+ xml_reader_t *vlc_reader = NULL;
+ int type = -1;
+ const char *name, *value;
+
+ assert(streams);
+
+ msg_Dbg(s, "parse_Manifest\n");
+
+ vlc_xml = xml_Create(s->p_source);
+ if( !vlc_xml )
+ {
+ msg_Err(s, "Failed to open XML parser");
+ return VLC_EGENERIC;
+ }
+
+ vlc_reader = xml_ReaderCreate(vlc_xml, s->p_source);
+ if( !vlc_reader )
+ {
+ msg_Err(s, "Failed to open source for parsing");
+ }
+
+ const char *node;
+ int count = 0;
+ sms_stream_t *sms = NULL;
+ quality_level_t *ql = NULL;
+ segment_t *sgmt = NULL;
+ uint64_t start_time = 0;
+ uint64_t duration = 0;
+ uint64_t computed_start_time = 0;
+ uint64_t computed_duration = 0;
+
+ while ( (type = xml_ReaderNextNode(vlc_reader, &node)) > 0)
+ {
+ count++;
+ switch (type)
+ {
+ case XML_READER_STARTELEM:
+ if (!strcmp(node, "SmoothStreamingMedia"))
+ while( (name = xml_ReaderNextAttr( vlc_reader, &value )) !=
+ NULL )
+ if (!strcmp(name, "Duration"))
+ {
+ p_sys->vod_duration = strtoull(value, NULL, 10);
+ p_sys->b_live = false;
+ }
+
+ if (!strcmp(node, "StreamIndex"))
+ {
+ sms = sms_New();
+
+ while( (name = xml_ReaderNextAttr( vlc_reader, &value )) !=
+ NULL )
+ {
+ if (!strcmp(name, "Type"))
+ {
+ if (!strcmp(value, "video"))
+ sms->type = VIDEO;
+ else if (!strcmp(value, "audio"))
+ sms->type = AUDIO;
+ else if (!strcmp(value, "text"))
+ sms->type = TEXT;
+ }
+
+ if (!strcmp(name, "Chunks"))
+ sms->vod_chunks_nb = strtol(value, NULL, 10);
+ if (!strcmp(name, "QualityLevels"))
+ sms->qlevel_nb = strtoul(value, NULL, 10);
+ if (!strcmp(name, "Name"))
+ sms->name = strdup(value);
+
+ if (!strcmp(name, "Url"))
+ sms->url_template = strdup(value);
+ }
+ vlc_array_append(p_sys->sms_streams, sms);
+ }
+
+ if (!strcmp(node, "QualityLevel"))
+ {
+ ql = ql_New();
+
+ while( (name = xml_ReaderNextAttr( vlc_reader, &value )) !=
+ NULL )
+ {
+ if (!strcmp(name, "Index"))
+ ql->Index = strtol(value, NULL, 10);
+ if (!strcmp(name, "Bitrate"))
+ ql->Bitrate = strtoull(value, NULL, 10);
+ if (!strcmp(name, "FourCC"))
+ {
+ if (!strcmp(value, "H264"))
+ ql->FourCC = H264;
+ if (!strcmp(value, "AACL"))
+ ql->FourCC = AACL;
+ if (!strcmp(value, "WVC1"))
+ ql->FourCC = WVC1;
+ if (!strcmp(value, "WMAP"))
+ ql->FourCC = WMAP;
+ if (!strcmp(value, "TTML"))
+ ql->FourCC = TTML;
+ }
+ }
+ vlc_array_append(sms->qlevels, ql);
+ }
+
+ if (!strcmp(node, "c"))
+ {
+ start_time = duration = 0;
+ while( (name = xml_ReaderNextAttr( vlc_reader, &value )) !=
+ NULL )
+ {
+ if (!strcmp(name, "t"))
+ start_time = strtoull(value, NULL, 10);
+ if (!strcmp(name, "d"))
+ duration = strtoull(value, NULL, 10);
+ }
+ if (!start_time)
+ {
+ assert(duration);
+ sgmt = segment_New(sms, duration, computed_start_time);
+ computed_start_time += duration;
+ }
+ else if (!duration)
+ {
+ computed_duration = start_time - computed_start_time;
+ sgmt = segment_New(sms, computed_duration, start_time);
+ }
+ else
+ sgmt = segment_New(sms, duration, start_time);
+
+ if (sms->qlevel_nb == 0 && start_time == 0)
+ sms->qlevel_nb = vlc_array_count(sms->qlevels);
+ }
+ break;
+
+ case XML_READER_ENDELEM:
+ if (!strcmp(node, "StreamIndex"))
+ {
+ computed_start_time = 0;
+ computed_duration = 0;
+ }
+ break;
+ case XML_READER_NONE:
+ break;
+ case XML_READER_TEXT:
+ break;
+ default:
+ return VLC_EGENERIC;
+ }
+ }
+
+ return VLC_SUCCESS;
+}
+
+/****************************************************************************
+ * Open
+ ****************************************************************************/
+
+static int build_init_segment(stream_t *s, char *psz_uri)
+{
+ stream_sys_t *p_sys = s->p_sys;
+ char *cmd = NULL;
+ asprintf(&cmd, "/usr/local/share/build_moov_box.py %s %li",
+ psz_uri, 0L);
+ int ret = system(cmd);
+ if (ret)
+ {
+ msg_Err(s, "Building of initialization segment failed\n");
+ return VLC_EGENERIC;
+ }
+ p_sys->init_sgmt = (segment_t *)malloc(sizeof(segment_t));
+ if (p_sys->init_sgmt == NULL)
+ return VLC_ENOMEM;
+
+ const char *spath = "/tmp/piff_initialization_segment";
+ FILE *fp = fopen(spath, "rb");
+ fseek(fp, 0L, SEEK_END);
+ p_sys->init_sgmt->size = (uint64_t)ftell(fp);
+ fseek(fp, 0L, SEEK_SET);
+
+ p_sys->init_sgmt->duration = 0;
+ p_sys->init_sgmt->start_time = 0;
+ p_sys->init_sgmt->bandwidth = 0;
+ p_sys->init_sgmt->sequence = 0;
+ vlc_mutex_init(&p_sys->init_sgmt->lock);
+ p_sys->init_sgmt->data = block_Alloc(p_sys->init_sgmt->size);
+ if (p_sys->init_sgmt->data == NULL)
+ return VLC_ENOMEM;
+
+ assert(p_sys->init_sgmt->data->i_buffer == p_sys->init_sgmt->size);
+ size_t read = fread(p_sys->init_sgmt->data->p_buffer, 1,
+ (size_t) p_sys->init_sgmt->size, fp);
+ if (read != (size_t)p_sys->init_sgmt->size)
+ return VLC_EGENERIC;
+ fclose(fp);
+
+ 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))
+ {
+ msg_Info(p_this, "Smooth Streaming: this is not a valid manifest");
+ return VLC_EGENERIC;
+ }
+
+ msg_Info(p_this, "Smooth Streaming (%s)", s->psz_path);
+
+ /* */
+ s->p_sys = p_sys = calloc(1, sizeof(*p_sys));
+ if (p_sys == NULL)
+ return VLC_ENOMEM;
+
+ char *psz_uri = NULL;
+ if (asprintf(&psz_uri,"%s://%s", s->psz_access, s->psz_path) < 0)
+ {
+ free(p_sys);
+ return VLC_ENOMEM;
+ }
+ vlc_UrlParse(&p_sys->ismc, psz_uri, 0);
+
+ /* remove the last part of the url */
+ char *inv = inverse_string(psz_uri);
+ char *tmp = strtok(inv, "/");
+ tmp = strtok(NULL, "");
+ p_sys->base_url = inverse_string(tmp);
+
+ p_sys->download.vlead = 0;
+ p_sys->download.alead = 0;
+ p_sys->download.tlead = 0;
+ p_sys->playback.boffset = 0;
+ p_sys->playback.index = 0xffffffff;
+ p_sys->download.vindex = 0;
+ p_sys->download.aindex = 0;
+ p_sys->bandwidth = 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->dld_chunks = vlc_array_new();
+ if (p_sys->sms_streams == NULL)
+ {
+ vlc_UrlClean(&p_sys->ismc);
+ free(p_sys);
+ return VLC_ENOMEM;
+ }
+
+ /* */
+ s->pf_read = Read;
+ s->pf_peek = Peek;
+ s->pf_control = Control;
+
+ build_init_segment(s, psz_uri);
+ free(psz_uri);
+
+ /* Parse SMS ismc content. */
+ uint8_t *buffer = NULL;
+ if (parse_Manifest(s, p_sys->sms_streams) != VLC_SUCCESS)
+ {
+ free(buffer);
+ return VLC_EGENERIC;
+ }
+ free(buffer);
+
+ p_sys->stream_nb = vlc_array_count(p_sys->sms_streams);
+
+ /* Choose first video stream available (TO FIX) */
+ sms_stream_t *vsms = NULL;
+ for (int i=0; i<p_sys->stream_nb; i++)
+ {
+ vsms = vlc_array_item_at_index(p_sys->sms_streams, i);
+ if (vsms->type == VIDEO)
+ {
+ msg_Info(s, "Video stream chosen is %s", vsms->name);
+ break;
+ }
+ }
+ p_sys->vstream = vsms;
+
+ /* Choose first audio stream available (TO FIX) */
+ sms_stream_t *asms = NULL;
+ for (int i=0; i<p_sys->stream_nb; i++)
+ {
+ asms = vlc_array_item_at_index(p_sys->sms_streams, i);
+ //if (asms->type == AUDIO && !strcmp(asms->name, "audio_eng"))
+ if (asms->type == AUDIO)
+ {
+ msg_Info(s, "Audio stream chosen is %s", asms->name);
+ break;
+ }
+ }
+ p_sys->astream = asms;
+
+ /* Choose SMS quality to start with */
+ quality_level_t *lowest = 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 < lowest->Bitrate)
+ lowest = qlvl;
+ }
+ vsms->playback_qlvl = lowest;
+ vsms->download_qlvl = lowest;
+
+ lowest = 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 < lowest->Bitrate)
+ lowest = qlvl;
+ }
+ asms->playback_qlvl = lowest;
+ asms->download_qlvl = lowest;
+
+
+
+ p_sys->playback.toffset = 0;
+
+ vlc_mutex_init(&p_sys->download.lock_wait);
+ vlc_cond_init(&p_sys->download.wait);
+
+ 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;
+}
+
+
+
+/****************************************************************************
+ * Close
+ ****************************************************************************/
+static void Close(vlc_object_t *p_this)
+{
+ stream_t *s = (stream_t*)p_this;
+ stream_sys_t *p_sys = s->p_sys;
+
+ vlc_mutex_lock(&p_sys->download.lock_wait);
+ vlc_cond_signal(&p_sys->download.wait);
+ vlc_mutex_unlock(&p_sys->download.lock_wait);
+
+ vlc_join(p_sys->thread, NULL);
+ vlc_mutex_destroy(&p_sys->download.lock_wait);
+ vlc_cond_destroy(&p_sys->download.wait);
+
+ /* Free sms streams */
+ segment_Free(p_sys->init_sgmt);
+ 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_UrlClean(&p_sys->ismc);
+ if (p_sys->peeked)
+ block_Release (p_sys->peeked);
+ free(p_sys);
+}
+
+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)
+ return 0;
+
+ uint32_t video_chunk_nb = p_sys->vstream->vod_chunks_nb;
+ segment_t *first_video_chunk = vlc_array_item_at_index(
+ p_sys->vstream->segments, 0);
+ uint64_t chunk_duration = first_video_chunk->duration;
+ uint64_t total_duration = video_chunk_nb * chunk_duration;
+ uint64_t bitrate = p_sys->vstream->download_qlvl->Bitrate;
+ uint64_t size = bitrate * total_duration;
+
+ return size;
+}
+
+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_GET_POSITION:
+ //*(va_arg (args, uint64_t *)) = p_sys->playback.boffset;
+ //break;
+ case STREAM_SET_POSITION:
+ return VLC_EGENERIC;
+ case STREAM_GET_SIZE:
+ *(va_arg (args, uint64_t *)) = GetStreamSize(s);
+ break;
+ default:
+ return VLC_EGENERIC;
+ }
+ return VLC_SUCCESS;
+}
+
+static ssize_t sms_Read(stream_t *s, uint8_t *p_read, unsigned int i_read)
+{
+ stream_sys_t *p_sys = s->p_sys;
+ ssize_t copied = 0;
+ segment_t *segment = NULL;
+ int count;
+
+ do
+ {
+ if (p_sys->playback.index == 0xffffffff)
+ {
+ segment = p_sys->init_sgmt;
+ p_sys->playback.index = 0;
+ }
+ else
+ {
+ vlc_mutex_lock(&p_sys->download.lock_wait);
+ count = vlc_array_count(p_sys->dld_chunks);
+ if (count == 0 || (int)p_sys->playback.index >= count-1)
+ {
+ vlc_cond_wait(&p_sys->download.wait,
+ &p_sys->download.lock_wait);
+ vlc_mutex_unlock(&p_sys->download.lock_wait);
+ continue;
+ }
+ segment = vlc_array_item_at_index(
+ p_sys->dld_chunks, p_sys->playback.index);
+ vlc_mutex_unlock(&p_sys->download.lock_wait);
+ }
+
+ vlc_mutex_lock(&segment->lock);
+ if (segment->data->i_buffer == 0)
+ {
+ msg_Warn(s, "End of the buffer !!!");
+ if (segment->type == VIDEO)
+ p_sys->playback.toffset += segment->duration;
+ if (!p_sys->b_cache || p_sys->b_live)
+ {
+ block_Release(segment->data);
+ segment->data = NULL;
+ }
+ else
+ { /* reset playback pointer to start of buffer */
+ if (segment->size > 0)
+ {
+ segment->data->i_buffer += segment->size;
+ segment->data->p_buffer -= segment->size;
+ }
+ }
+ p_sys->playback.index++;
+ vlc_mutex_unlock(&segment->lock);
+
+ /* 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);
+ continue;
+ }
+
+ if (segment->size == segment->data->i_buffer)
+ msg_Info(s, "playing segment %"PRIu32", bandwidth %"PRIu64"",
+ segment->sequence, segment->bandwidth);
+
+ ssize_t len = -1;
+ if (i_read <= segment->data->i_buffer)
+ len = i_read;
+ else
+ len = segment->data->i_buffer;
+
+ if (len > 0)
+ {
+ memcpy(p_read + copied, segment->data->p_buffer, len);
+ segment->data->i_buffer -= len;
+ segment->data->p_buffer += len;
+ copied += len;
+ i_read -= len;
+ }
+ vlc_mutex_unlock(&segment->lock);
+
+ } while (i_read > 0);
+
+ return copied;
+}
+
+static int Read(stream_t *s, void *buffer, unsigned int i_read)
+{
+ msg_Warn(s, "Demuxer want to read %u bytes", i_read);
+ stream_sys_t *p_sys = s->p_sys;
+ ssize_t length = 0;
+
+ if (p_sys->b_error)
+ return 0;
+
+ if (buffer == NULL)
+ {
+ /* caller skips data, get big enough buffer */
+ msg_Warn(s, "buffer is NULL (allocate %d)", i_read);
+ buffer = calloc(1, i_read);
+ if (buffer == NULL)
+ return 0; /* NO MEMORY left */
+ }
+
+ length = sms_Read(s, (uint8_t*) buffer, i_read);
+ if (length < 0)
+ return 0;
+
+ p_sys->playback.boffset += 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;
+ segment_t *segment;
+ unsigned int len = i_peek;
+
+ segment = p_sys->init_sgmt;
+ if (segment == NULL)
+ {
+ msg_Err(s, "Initialization segment should have been available");
+ return 0;
+ }
+
+ vlc_mutex_lock(&segment->lock);
+
+ size_t i_buff = segment->data->i_buffer;
+ uint8_t *p_buff = segment->data->p_buffer;
+
+ if (i_peek < i_buff)
+ {
+ *pp_peek = p_buff;
+ vlc_mutex_unlock(&segment->lock);
+ return i_peek;
+ }
+
+ else
+ {
+ size_t curlen = 0;
+ segment_t *nsegment;
+ block_t *peeked = p_sys->peeked;
+ vlc_array_t *dld_chunks = p_sys->dld_chunks;
+ int count = 0;
+
+ if (peeked == NULL)
+ peeked = block_Alloc (i_peek);
+ else if (peeked->i_buffer < i_peek)
+ peeked = block_Realloc (peeked, 0, i_peek);
+ if (peeked == NULL)
+ return 0;
+
+ memcpy(peeked->p_buffer, p_buff, i_buff);
+ curlen = i_buff;
+ len -= i_buff;
+ vlc_mutex_unlock(&segment->lock);
+
+ i_buff = peeked->i_buffer;
+ p_buff = peeked->p_buffer;
+ *pp_peek = p_buff;
+
+ while (curlen < i_peek)
+ {
+ while (count >= vlc_array_count(dld_chunks))
+ {
+ /*msg_Dbg(s, "count is %d and size of dld_chunks is %d", count,
+ vlc_array_count(dld_chunks));*/
+ vlc_mutex_lock(&p_sys->download.lock_wait);
+ vlc_cond_wait(&p_sys->download.wait, &p_sys->download.lock_wait);
+ vlc_mutex_unlock(&p_sys->download.lock_wait);
+ }
+ vlc_mutex_lock(&p_sys->download.lock_wait);
+ nsegment = vlc_array_item_at_index(dld_chunks, count);
+ vlc_mutex_unlock(&p_sys->download.lock_wait);
+ count++;
+
+ vlc_mutex_lock(&nsegment->lock);
+
+ if (len < nsegment->data->i_buffer)
+ {
+ memcpy(p_buff + curlen, nsegment->data->p_buffer, len);
+ curlen += len;
+ }
+ else
+ {
+ size_t i_nbuff = nsegment->data->i_buffer;
+ memcpy(p_buff + curlen, nsegment->data->p_buffer, i_nbuff);
+ curlen += i_nbuff;
+ len -= i_nbuff;
+ }
+
+ vlc_mutex_unlock(&nsegment->lock);
+ }
+
+ return curlen;
+ }
+}
+
+static char *inverse_string(const char *str)
+{
+ size_t len = strlen(str);
+ char *ret = (char *)malloc(len+1);
+ for (unsigned int i=0; i<len; i++)
+ ret[i] = str[len-i-1];
+ ret[len] = '\0';
+ return ret;
+}
+
+/******************************************************************************
+ * Download thread
+ *****************************************************************************/
+
+static char *ConstructUrl(const char *template, const char *base_url,
+ uint64_t bandwidth, uint64_t start_time)
+{
+ char *frag;
+ char *qual;
+ char *tmp;
+ char *url_template = strdup(template);
+ qual = strtok(url_template, "{");
+ tmp = strtok(NULL, "}");
+ frag = strtok(NULL, "{");
+ char *url = NULL;
+
+ if (asprintf(&url, "%s/%s%"PRIu64"%s%"PRIu64")", base_url, qual,
+ bandwidth, frag, start_time) < 0)
+ return NULL;
+
+ free(url_template);
+ return url;
+}
+
+static segment_t * chunk_Get(stream_t *s,
+ sms_stream_t *sms, uint64_t start_time)
+{
+ int len = vlc_array_count(sms->segments);
+ for (int i=0; i<len; i++)
+ {
+ segment_t * chunk = vlc_array_item_at_index(sms->segments, i);
+ if (chunk->start_time <= start_time &&
+ chunk->start_time + chunk->duration > start_time)
+ return chunk;
+ }
+ msg_Warn(s, "Could not find a chunk for stream %s, start time = %"PRIu64"",
+ sms->name, start_time);
+ return NULL;
+}
+
+static int sms_Download(stream_t *s, uint64_t start_time, sms_stream_t *sms)
+{
+ assert(sms);
+ stream_sys_t *p_sys = s->p_sys;
+
+ segment_t *chunk = chunk_Get(s, sms, start_time);
+ chunk->bandwidth = sms->download_qlvl->Bitrate;
+ chunk->type = sms->type;
+ if (!chunk)
+ return VLC_EGENERIC;
+
+ /* Construct URL */
+ char *psz_url = ConstructUrl(sms->url_template,
+ p_sys->base_url,
+ sms->download_qlvl->Bitrate,
+ chunk->start_time);
+ msg_Dbg(s, "chunk url is %s\n", psz_url);
+ if (psz_url == NULL)
+ return VLC_ENOMEM;
+
+ stream_t *p_ts = stream_UrlNew(s, psz_url);
+ free(psz_url);
+ if (p_ts == NULL)
+ return VLC_EGENERIC;
+
+ chunk->size = stream_Size(p_ts);
+ assert(chunk->size > 0);
+
+ chunk->data = block_Alloc(chunk->size);
+ if (chunk->data == NULL)
+ {
+ stream_Delete(p_ts);
+ return VLC_ENOMEM;
+ }
+
+ assert(chunk->data->i_buffer == chunk->size);
+
+ ssize_t length = 0, curlen = 0;
+ uint64_t size;
+ do
+ {
+ size = stream_Size(p_ts);
+ if (size > chunk->size)
+ {
+ msg_Dbg(s, "size changed %"PRIu64"", chunk->size);
+ block_t *p_block = block_Realloc(chunk->data, 0, size);
+ if (p_block == NULL)
+ {
+ stream_Delete(p_ts);
+ block_Release(chunk->data);
+ chunk->data = NULL;
+ return VLC_ENOMEM;
+ }
+ chunk->data = p_block;
+ chunk->size = size;
+ assert(chunk->data->i_buffer == chunk->size);
+ p_block = NULL;
+ }
+ length = stream_Read(p_ts, chunk->data->p_buffer + curlen,
+ chunk->size - curlen);
+ if (length <= 0)
+ break;
+ curlen += length;
+ } while (1);
+
+ vlc_mutex_lock(&p_sys->download.lock_wait);
+ vlc_array_append(p_sys->dld_chunks, chunk);
+ vlc_mutex_unlock(&p_sys->download.lock_wait);
+ if (sms->type == AUDIO)
+ p_sys->download.alead += chunk->duration;
+ else if (sms->type == VIDEO)
+ p_sys->download.vlead += chunk->duration;
+ else if (sms->type == TEXT)
+ p_sys->download.tlead += chunk->duration;
+ stream_Delete(p_ts);
+
+ return VLC_SUCCESS;
+}
+
+static quality_level_t * BandwidthAdaptation(stream_t *s,
+ sms_stream_t *sms, uint64_t bandwidth)
+{
+ return sms->download_qlvl;
+}
+
+static int Download(stream_t *s, uint64_t start_time, sms_stream_t *sms,\
+ quality_level_t *qlevel)
+{
+ stream_sys_t *p_sys = s->p_sys;
+
+ assert(sms);
+ assert(qlevel);
+
+ sms->download_qlvl = qlevel;
+ segment_t *segment = chunk_Get(s, sms, start_time);
+
+ vlc_mutex_lock(&segment->lock);
+ if (segment->data != NULL)
+ {
+ /* Segment already downloaded */
+ vlc_mutex_unlock(&segment->lock);
+ return VLC_SUCCESS;
+ }
+
+ /* sanity check - can we download this segment on time? */
+ if ((p_sys->bandwidth > 0) && (qlevel->Bitrate > 0))
+ {
+ uint32_t sgmt_duration = segment->duration / TS; /* duration in seconds */
+ uint64_t size = (sgmt_duration * qlevel->Bitrate); /* bits */
+ uint32_t estimated = (uint32_t)(size / p_sys->bandwidth);
+ if (estimated > sgmt_duration)
+ {
+ msg_Warn(s,"downloading of segment %d takes %ds,\
+ which is longer than its playback (%ds)",
+ segment->sequence, estimated, sgmt_duration);
+ }
+ }
+
+ mtime_t start = mdate();
+ if (sms_Download(s, start_time, sms) != VLC_SUCCESS)
+ {
+ msg_Err(s, "downloaded segment %"PRIu32" from stream %s at quality\
+ %"PRIu64" failed", segment->sequence, sms->name, qlevel->Bitrate);
+ vlc_mutex_unlock(&segment->lock);
+ return VLC_EGENERIC;
+ }
+ mtime_t duration = mdate() - start;
+
+ vlc_mutex_unlock(&segment->lock);
+
+ msg_Info(s, "downloaded segment %d from stream %s at quality %"PRIu64"",
+ segment->sequence, sms->name, qlevel->Bitrate);
+ if (sms->type == AUDIO)
+ p_sys->download.aindex++;
+ if (sms->type == VIDEO)
+ p_sys->download.vindex++;
+
+ /* check for division by zero */
+ double ms = (double)duration / 1000.0; /* ms */
+ if (ms <= 0.0)
+ return VLC_SUCCESS;
+
+ uint64_t bw = ((double)(segment->size * 8) / ms) * 1000; /* bits / s */
+ p_sys->bandwidth = bw;
+ if (qlevel->Bitrate != bw)
+ {
+ quality_level_t *new_qlevel = BandwidthAdaptation(s, sms, bw);
+
+ /* FIXME: we need an average here */
+ if ((new_qlevel) && (new_qlevel->Bitrate != qlevel->Bitrate))
+ {
+ msg_Info(s, "detected %s bandwidth (%"PRIu64") stream",
+ (bw >= qlevel->Bitrate) ? "faster" : "lower", bw);
+ sms->download_qlvl = new_qlevel;
+ }
+ }
+ return VLC_SUCCESS;
+}
+
+static void* sms_Thread(void *p_this)
+{
+ stream_t *s = (stream_t *)p_this;
+ stream_sys_t *p_sys = s->p_sys;
+ uint64_t lead=0, time_left=0;
+ sms_stream_t *vsms, *asms;
+ vsms = p_sys->vstream;
+ asms = p_sys->astream;
+ assert(vsms);
+ assert(asms);
+ int count = 0;
+
+ int canc = vlc_savecancel();
+
+ if (Download(s, 0, vsms, vsms->download_qlvl) != VLC_SUCCESS)
+ {
+ p_sys->b_error = true;
+ goto cancel;
+ }
+
+ while (vlc_object_alive(s))
+ {
+ /* Is there a new segment to process? */
+ if (p_sys->download.aindex >= (asms->vod_chunks_nb - 1) &&
+ p_sys->download.vindex >= (vsms->vod_chunks_nb - 1))
+ break;
+ time_left = p_sys->vod_duration - p_sys->playback.toffset;
+ if (time_left > 60*TS)
+ {
+ /* wait */
+ vlc_mutex_lock(&p_sys->download.lock_wait);
+ lead = p_sys->download.vlead - p_sys->playback.toffset;
+ while (lead > 60*TS)
+ {
+ vlc_cond_wait(&p_sys->download.wait,
+ &p_sys->download.lock_wait);
+ lead = p_sys->download.vlead - p_sys->playback.toffset;
+ }
+ vlc_mutex_unlock(&p_sys->download.lock_wait);
+ }
+
+ if (p_sys->download.alead <= p_sys->download.vlead)
+ if (Download(s, p_sys->download.alead, asms,
+ asms->download_qlvl) != VLC_SUCCESS)
+ {
+ p_sys->b_error = true;
+ break;
+ }
+
+ if (p_sys->download.vlead < p_sys->download.alead)
+ if (Download(s, p_sys->download.vlead, vsms,
+ vsms->download_qlvl) != 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:
+ vlc_restorecancel(canc);
+ return NULL;
+}
--
1.7.4.1
More information about the vlc-devel
mailing list