[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