[FFmpeg-devel] [PATCH 3/3] Add the smoothstreaming support

Florent Tribouilloy florent.tribouilloy at smartjog.com
Thu Jul 18 17:36:34 CEST 2013


The Manifest is parsed with the libexpat in smoothstreaming_parse.c
Then, the demuxer try to find the corresponding codec and use the
demuxer mov to read it. Adaptative bitrate is not coded yet.

Signed-off-by: Florent Tribouilloy <florent.tribouilloy at smartjog.com>
---
 configure                           |    1 +
 libavformat/Makefile                |    1 +
 libavformat/allformats.c            |    2 +-
 libavformat/smoothstreaming.c       |  713 +++++++++++++++++++++++++++++++++++
 libavformat/smoothstreaming.h       |  144 +++++++
 libavformat/smoothstreaming_parse.c |  646 +++++++++++++++++++++++++++++++
 6 files changed, 1506 insertions(+), 1 deletion(-)
 create mode 100644 libavformat/smoothstreaming.c
 create mode 100644 libavformat/smoothstreaming.h
 create mode 100644 libavformat/smoothstreaming_parse.c

diff --git a/configure b/configure
index dcd1732..9bba8b1 100755
--- a/configure
+++ b/configure
@@ -2065,6 +2065,7 @@ rtsp_muxer_select="rtp_muxer http_protocol rtp_protocol rtpenc_chain"
 sap_demuxer_select="sdp_demuxer"
 sap_muxer_select="rtp_muxer rtp_protocol rtpenc_chain"
 sdp_demuxer_select="rtpdec"
+smoothstreaming_demuxer_select="mov_demuxer libexpat"
 smoothstreaming_muxer_select="ismv_muxer"
 spdif_muxer_select="aac_parser"
 tak_demuxer_select="tak_parser"
diff --git a/libavformat/Makefile b/libavformat/Makefile
index cc4a4cb..4ecdc40 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -352,6 +352,7 @@ OBJS-$(CONFIG_SMACKER_DEMUXER)           += smacker.o
 OBJS-$(CONFIG_SMJPEG_DEMUXER)            += smjpegdec.o smjpeg.o
 OBJS-$(CONFIG_SMJPEG_MUXER)              += smjpegenc.o smjpeg.o
 OBJS-$(CONFIG_SMOOTHSTREAMING_MUXER)     += smoothstreamingenc.o isom.o
+OBJS-$(CONFIG_SMOOTHSTREAMING_MUXER)     += smoothstreaming.o smoothstreaming_parse.o isom.o
 OBJS-$(CONFIG_SMUSH_DEMUXER)             += smush.o
 OBJS-$(CONFIG_SOL_DEMUXER)               += sol.o pcm.o
 OBJS-$(CONFIG_SOX_DEMUXER)               += soxdec.o pcm.o
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index 6bba812..bd17917 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -257,7 +257,7 @@ void av_register_all(void)
     REGISTER_DEMUXER (SIFF,             siff);
     REGISTER_DEMUXER (SMACKER,          smacker);
     REGISTER_MUXDEMUX(SMJPEG,           smjpeg);
-    REGISTER_MUXER   (SMOOTHSTREAMING,  smoothstreaming);
+    REGISTER_MUXDEMUX(SMOOTHSTREAMING,  smoothstreaming);
     REGISTER_DEMUXER (SMUSH,            smush);
     REGISTER_DEMUXER (SOL,              sol);
     REGISTER_MUXDEMUX(SOX,              sox);
diff --git a/libavformat/smoothstreaming.c b/libavformat/smoothstreaming.c
new file mode 100644
index 0000000..37f9c59
--- /dev/null
+++ b/libavformat/smoothstreaming.c
@@ -0,0 +1,713 @@
+/*
+ * Microsoft Smooth Streaming (mss) demuxer
+ * Copyright (c) 2013 Florent Tribouilloy
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg 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.
+ *
+ * FFmpeg 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 FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * Used the hls demuxer as example. (libavformat/hls.c)
+ * And piff specification
+ * (http://www.iis.net/learn/media/smooth-streaming/protected-interoperable-file-format)
+ */
+#include "libavutil/dict.h"
+
+#include "libavutil/avstring.h"
+#include "libavutil/time.h"
+
+#include "avformat.h"
+#include "internal.h"
+#include "avio_internal.h"
+#include "url.h"
+#include "isom.h"
+#include "avc.h"
+#include "smoothstreaming.h"
+#include "riff.h"
+
+#include <string.h>
+
+static int make_frag_url(StreamIndex* si, uint64_t bit_rate, uint64_t start_ts,
+                         char *frag_url, int frag_url_size)
+{
+    uint64_t diff, pos, wr = 0;
+    char *find_pos;
+    int ret = 0;
+    int len = 0;
+
+    find_pos = av_stristr(si->url, "{bitrate}");
+    if (!find_pos)
+        return AVERROR_INVALIDDATA;
+    diff = find_pos - si->url;
+    len = FFMIN(frag_url_size, diff + 1);
+    snprintf(frag_url, len, "%s", si->url);
+    pos = len - 1;
+    wr = pos;
+
+    wr += snprintf(frag_url + wr, frag_url_size - wr, "%"PRIu64"", bit_rate);
+    pos += 9;
+
+    find_pos = av_stristr(si->url + pos, "{start time}");
+    if (!find_pos)
+        return AVERROR_INVALIDDATA;
+    diff = find_pos - (si->url + pos);
+    len = FFMIN(frag_url_size - wr, diff + 1);
+    snprintf(frag_url + wr, len, "%s", si->url + pos);
+    pos += len - 1;
+    wr += len - 1;
+
+    wr += snprintf(frag_url + wr, frag_url_size - wr, "%"PRIu64"", start_ts);
+    pos += 12;
+    wr += snprintf(frag_url + wr, frag_url_size - wr, "%s", si->url + pos);
+    return ret;
+}
+
+
+static int read_data(void *opaque, uint8_t *buf, int buf_size)
+{
+    StreamIndex *si = opaque;
+    MSSContext *c = si->parent->priv_data;
+    Fragment *frag = NULL;
+    AVDictionary *opts = NULL;
+    char url[MAX_URL_SIZE];
+    int ret = 0;
+
+ restart:
+    if (!si->input) {
+        int64_t reload_interval = 0;
+        ++si->cur_frag;
+        if (!c->is_live && si->cur_frag >= si->nb_fragments)
+            return AVERROR_EOF;
+
+        reload_interval = si->nb_fragments > 0 && c->is_live ?
+            si->frags[si->cur_frag].duration :
+            c->duration;
+    reload:
+        if (c->is_live &&
+            av_gettime() - si->last_load_time >= reload_interval) {
+            if ((ret = smoothstreaming_parse_manifest(si->parent, c->url, si->parent->pb)) < 0)
+                return ret;
+            reload_interval = c->duration * 500000LL;
+        }
+        if (si->cur_frag >= si->nb_fragments) {
+            if (si->cur_frag == si->nb_fragments)
+                return AVERROR_EOF;
+            while (av_gettime() - si->last_load_time < reload_interval)
+                {
+                    if (ff_check_interrupt(c->interrupt_callback))
+                        return AVERROR_EXIT;
+                    av_usleep(100*1000);
+                }
+            /* Enough time has elapsed since the last reload */
+            goto reload;
+        }
+
+        if (si->cur_frag < si->nb_fragments) {
+            av_dict_set(&opts, "seekable", "0", 0);
+            frag = &si->frags[si->cur_frag];
+            make_frag_url(si, si->qualities[si->cur_quality].bit_rate, frag->start_ts, &url[0], sizeof(url));
+            ret = ffurl_open(&si->input, url, AVIO_FLAG_READ,
+                             &si->parent->interrupt_callback, &opts);
+            av_dict_free(&opts);
+            if (ret < 0)
+                return ret;
+        }
+        else
+            return AVERROR_EXIT;
+    }
+
+    ret = ffurl_read(si->input, buf, buf_size);
+    if (ret > 0) {
+        return ret;
+    }
+
+    ffurl_close(si->input);
+    si->input = NULL;
+    if (ret < 0)
+        return ret;
+    goto restart;
+}
+
+static int smoothstreaming_set_extradata(AVCodecContext *codec, const char *extra)
+{
+    int size = 0;
+    uint8_t *buf = NULL;
+    int new_size;
+
+    new_size = strlen(extra) / 2;
+    if (new_size >= INT_MAX)
+        return AVERROR_INVALIDDATA;
+    buf = av_mallocz((new_size + FF_INPUT_BUFFER_PADDING_SIZE) * sizeof(*buf));
+    if (!buf)
+        return AVERROR(ENOMEM);
+    size = ff_hex_to_data(buf, extra);
+    codec->extradata_size = size;
+    codec->extradata = buf;
+    return codec->extradata_size;
+}
+
+static int smoothstreaming_set_extradata_h264(AVCodecContext *codec, const char *extra)
+{
+    int size = 0, ret = 0;
+    int i, count;
+    uint8_t *buf = NULL;
+    int new_size;
+    AVIOContext *bio = NULL;
+
+    new_size = strlen(extra) / 2;
+    if (new_size >= INT_MAX)
+        return AVERROR_INVALIDDATA;
+    buf = av_mallocz((new_size + FF_INPUT_BUFFER_PADDING_SIZE) * sizeof(*buf));
+    if (!buf)
+        return AVERROR(ENOMEM);
+    size = ff_hex_to_data(buf, extra);
+    codec->extradata_size = size;
+    codec->extradata = buf;
+
+    for (i = 0, count=0; i + 3 < size; ++i) {
+        if (buf[i] == 0
+            && buf[i + 1] == 0
+            && buf[i + 2] == 0
+            && buf[i + 3] == 1) {
+            ++count;
+            i += 3;
+        }
+    }
+
+    new_size = size + count * 4;
+    buf = av_mallocz((new_size + FF_INPUT_BUFFER_PADDING_SIZE) * sizeof(*buf));
+    if (!buf)
+        return AVERROR(ENOMEM);
+
+    bio = avio_alloc_context(buf, new_size, 0, NULL, NULL, NULL, NULL);
+    if (!bio)
+        return AVERROR(ENOMEM);
+    if ((ret = ff_isom_write_avcc(bio, codec->extradata, codec->extradata_size)) < 0)
+        return ret;
+    codec->extradata_size = bio->buf_ptr - bio->buffer;
+    codec->extradata = bio->buffer;
+
+    return codec->extradata_size;
+}
+
+static int open_audio_demuxer(StreamIndex *si, AVStream *st)
+{
+    Quality *q = &si->qualities[si->cur_quality];
+    QualityAudio *qa = q->qa;
+    AVStream *ist = NULL;
+    int ret = 0;
+
+    if (qa->wave_format_ex != 0) {
+        int  len = 0;
+        uint8_t *buf = NULL;
+        AVIOContext *bio = NULL;
+
+        len = strlen(q->private_str) / 2;
+        if (len >= INT_MAX)
+            return AVERROR_INVALIDDATA;
+        buf = av_mallocz((len + FF_INPUT_BUFFER_PADDING_SIZE) * sizeof(*buf));
+        if (!buf)
+            return AVERROR(ENOMEM);
+        len = ff_hex_to_data(buf, q->private_str);
+        bio = avio_alloc_context(buf, len, 0, NULL, NULL, NULL, NULL);
+        if (!bio)
+            return AVERROR(ENOMEM);
+        ret = ff_get_wav_header(bio, st->codec, len);
+        if (ret < 0)
+            return ret;
+        st->need_parsing = AVSTREAM_PARSE_FULL_RAW;
+        avpriv_set_pts_info(st, 64, 1, st->codec->sample_rate);
+        av_free(buf);
+
+    } else {
+        ist = si->ctx->streams[0]; /* only one stream by fragment */
+        avpriv_set_pts_info(st, ist->pts_wrap_bits, ist->time_base.num, ist->time_base.den);
+        avcodec_copy_context(st->codec, ist->codec);
+        st->codec->codec_type = AVMEDIA_TYPE_AUDIO;
+        st->codec->codec_id = ff_codec_get_id(ff_codec_movaudio_tags, q->fourcc);
+        if (q->fourcc == MKTAG('a', 'a', 'c', 'l'))
+            st->codec->codec_id = AV_CODEC_ID_AAC;
+        else if (q->fourcc == MKTAG('w', 'm', 'a', 'p'))
+            st->codec->codec_id = AV_CODEC_ID_WMAPRO;
+
+        st->codec->sample_rate = qa->sample_rate;
+        st->codec->bits_per_coded_sample = qa->bit_per_sample;
+        st->codec->channels = qa->nb_channels;
+        if (qa->bit_per_sample == 16)
+            st->codec->sample_fmt = AV_SAMPLE_FMT_S16;
+        st->time_base.den = qa->sample_rate;
+        st->time_base.num = 1;
+        st->codec->time_base.den = st->time_base.den;
+        st->codec->time_base.num = st->time_base.num;
+        st->codec->block_align = qa->packet_size;
+
+        if ((ret = smoothstreaming_set_extradata(st->codec, q->private_str)) < 0)
+            return ret;
+        st->codec->bit_rate = q->bit_rate;
+    }
+    si->parent->bit_rate += q->bit_rate;
+
+    return 0;
+}
+
+static int open_video_demuxer(StreamIndex *si, AVStream *st)
+{
+    Quality *q = &si->qualities[si->cur_quality];
+    QualityVideo *qv = q->qv;
+    AVStream *ist = NULL;
+    int ret = 0;
+
+    ist = si->ctx->streams[0]; /* only one stream by fragment */
+    avcodec_copy_context(st->codec, ist->codec);
+    /* FIXME : the pts is not correct, video going to fast */
+    avpriv_set_pts_info(st, ist->pts_wrap_bits, ist->time_base.num, ist->time_base.den);
+
+    st->codec->codec_type = AVMEDIA_TYPE_VIDEO;
+    if (q->fourcc == MKTAG('h', '2', '6', '4')
+        || q->fourcc == MKTAG('a', 'v', 'c', '1')) {
+        st->codec->codec_id = AV_CODEC_ID_H264;
+        st->codec->pix_fmt = AV_PIX_FMT_YUV420P;
+        if ((ret = smoothstreaming_set_extradata_h264(st->codec, q->private_str)) < 0)
+            return ret;
+    }
+    else if (q->fourcc == MKTAG('w', 'v', 'c', '1')) {
+        st->codec->codec_id = AV_CODEC_ID_VC1;
+        if ((ret = smoothstreaming_set_extradata(st->codec, q->private_str)) < 0)
+            return ret;
+    }
+
+    st->codec->bit_rate = q->bit_rate;
+    st->codec->width = qv->width != -1 ? qv->width : qv->max_width;
+    st->codec->height = qv->height != -1 ? qv->height : qv->max_height;
+    st->codec->flags &= ~CODEC_FLAG_GLOBAL_HEADER;
+
+    return 0;
+}
+
+static int open_demux_codec(StreamIndex *si, AVStream *st)
+{
+    Quality *q = &si->qualities[si->cur_quality];
+    int ret = 0;
+
+    if (!q || !st)
+        return AVERROR_INVALIDDATA;
+
+    st->codec->codec_tag = q->fourcc;
+    if (si->is_video != 0) {
+        ret = open_video_demuxer(si, st);
+    } else if (si->is_audio != 0) {
+        ret = open_audio_demuxer(si, st);
+    }
+    return ret;
+}
+
+static int open_demuxer_io(StreamIndex *si)
+{
+    AVDictionary *opts = NULL;
+    char url[MAX_URL_SIZE];
+    int ret = 0;
+
+    si->read_buffer = av_malloc(INITIAL_BUFFER_SIZE);
+    if (!si->read_buffer)
+        return AVERROR(ENOMEM);
+
+    ffio_init_context(&si->pb, si->read_buffer, INITIAL_BUFFER_SIZE, 0, si,
+                      read_data, NULL, NULL);
+    si->pb.seekable = 0;
+
+    make_frag_url(si, si->qualities[si->cur_quality].bit_rate,
+                  si->frags[si->cur_frag].start_ts, &url[0], sizeof(url));
+
+    si->ctx->pb = &si->pb;
+    ret = av_probe_input_buffer(&si->pb, &si->fmt, url, si->parent, 0, 0);
+    if (ret < 0) {
+        av_log(si->parent, AV_LOG_ERROR, "Error when loading first fragment"
+               " '%s'\n", url);
+        avformat_free_context(si->ctx);
+        si->ctx = NULL;
+        return ret;
+    }
+
+    av_dict_set(&opts, "movdflags", "smooth", 0);
+    av_dict_set(&opts, "seekable", "0", 0);
+    ret = avformat_open_input(&si->ctx, url, si->fmt, &opts);
+    if (ret < 0) {
+        av_log(si->parent, AV_LOG_ERROR, "Error when opening the first fragment"
+               " '%s'\n", url);
+        avformat_free_context(si->ctx);
+        si->ctx = NULL;
+        return ret;
+    }
+
+    return ret;
+}
+
+static int open_demuxer(AVFormatContext *s, StreamIndex *si)
+{
+    AVStream *st = NULL;
+    int ret = 0;
+
+    if (!si || si->nb_fragments == 0)
+        return AVERROR_INVALIDDATA;
+
+    /* initilize the format context */
+    if (!(si->ctx = avformat_alloc_context())) {
+        return AVERROR(ENOMEM);
+    }
+    si->ctx->interrupt_callback = s->interrupt_callback;
+
+    /* Create new AVStreams the stream of this fragments */
+    st = avformat_new_stream(s, NULL);
+    if (!st) {
+        return AVERROR(ENOMEM);
+    }
+    si->qualities[si->cur_quality].stream_id = st->index;
+
+    si->parent = s;
+    if ((ret = open_demuxer_io(si)) < 0)
+        return ret;
+
+    si->ctx->ctx_flags &= ~CODEC_FLAG_GLOBAL_HEADER;
+    if ((ret = avformat_find_stream_info(si->ctx, NULL)) < 0)
+        return ret;
+
+    if ((ret = open_demux_codec(si, st)) < 0)
+        return ret;
+
+    return ret;
+}
+
+/* make a function to initilize video_id and audio_id */
+static int get_init_streams_id(MSSContext *c)
+{
+    unsigned int i = 0, j = 0;
+
+    c->video_id = -1;
+    c->audio_id = -1;
+    for (i=0; i < c->nb_stream_index; ++i) {
+        StreamIndex *si = &c->stream_index[i];
+
+        if (si && si->is_video != 0) {
+            c->video_id = i;
+            for (j = 0; j < si->nb_qualities; ++j) {
+                Quality *q = &si->qualities[j];
+                if (c->video_id == -1) {
+                    si->cur_quality = j;
+                } else if (si->display_width == q->qv->width && si->display_height == q->qv->height) {
+                    si->cur_quality = j;
+                    break;
+                } else if (si->display_width == q->qv->max_width && si->display_height == q->qv->max_height) {
+                    si->cur_quality = j;
+                    break;
+                }
+            }
+            si->cur_frag = 0;
+        } else if (si && si->is_audio != 0) {
+            c->audio_id = i;
+            for (j = 0; j < si->nb_qualities; ++j) {
+                if (si->cur_quality == -1) {
+                    si->cur_quality = j;
+                    break;
+                }
+            }
+            si->cur_frag = 0;
+        }
+    }
+    return 0;
+}
+
+static int smoothstreaming_read_header(AVFormatContext *s)
+{
+    MSSContext *c = s->priv_data;
+    int ret = 0;
+
+    /* Manifest is already here; copy the url to reach */
+    snprintf(c->url, sizeof(c->url), "%s", s->filename);
+
+    c->interrupt_callback = &s->interrupt_callback;
+
+    if ((ret = smoothstreaming_parse_manifest(s, c->url, s->pb)) < 0)
+        goto fail;
+
+    if (c->nb_stream_index == 0 || c->duration == -1) {
+        av_log(s, AV_LOG_ERROR, "No streams in the Manifest\n");
+        ret = AVERROR_EOF;
+        goto fail;
+    }
+
+    if (c->major != 2 || c->minor != 0)
+        av_log(s, AV_LOG_WARNING, "Manifest : MajorVersion should be 2, MinorVersion should be 0\n");
+
+    get_init_streams_id(c);
+
+    av_log(s, AV_LOG_INFO, "Stream index for video : %d, audio : %d\n",
+           c->video_id, c->audio_id);
+
+    /* Open demuxer for video stream */
+    if (c->video_id != -1 && (ret = open_demuxer(s, &c->stream_index[c->video_id])) < 0)
+        goto fail;
+
+    /* Open demuxer for audio stream */
+    if (c->audio_id != -1 && (ret = open_demuxer(s, &c->stream_index[c->audio_id])) < 0)
+        goto fail;
+
+    c->first_timestamp = AV_NOPTS_VALUE;
+    c->seek_timestamp  = AV_NOPTS_VALUE;
+
+    av_log(s, AV_LOG_INFO, "Stream index for video : %d, audio : %d\n",
+           c->video_id, c->audio_id);
+
+
+    /* register the total duration for non live streams */
+    if (!c->is_live)
+        s->duration = c->duration / 10;
+
+    return 0;
+
+fail:
+    return ret;
+}
+
+static int get_minstream_dts(MSSContext *c, int id, int minstream)
+{
+    StreamIndex *last_is = NULL;
+    StreamIndex *si = &c->stream_index[id];
+    int64_t this_dts = 0;
+    int64_t last_dts = 0;
+    AVStream *st = NULL;
+    AVStream *last_st = NULL;
+
+    /* Check if this stream has the packet with the lowest dts */
+    if (si->pkt.data) {
+        if (minstream < 0) {
+            return id;
+        } else {
+            this_dts = si->pkt.dts;
+            if (id == c->audio_id)
+                last_is = &c->stream_index[c->video_id];
+            else
+                last_is = &c->stream_index[c->audio_id];
+            last_dts = last_is->pkt.dts;
+            st = si->ctx->streams[si->pkt.stream_index];
+            last_st = last_is->ctx->streams[last_is->pkt.stream_index];
+
+            if(st->start_time != AV_NOPTS_VALUE)
+                this_dts -= st->start_time;
+            if(last_st->start_time != AV_NOPTS_VALUE)
+                last_dts -= last_st->start_time;
+
+            if (av_compare_ts(this_dts, st->time_base, last_dts, last_st->time_base) < 0)
+                minstream = id;
+        }
+    }
+    return minstream;
+}
+
+static int treat_packet(MSSContext *c, AVPacket *pkt, StreamIndex *si, int *minvariant)
+{
+    int ret = 0;
+
+    if (!si->pkt.data) {
+        while (1) {
+            int64_t ts_diff;
+            AVStream *st = NULL;
+
+            ret = av_read_frame(si->ctx, &si->pkt);
+
+            if (ret < 0) {
+                if (!url_feof(&si->pb) && ret != AVERROR_EOF)
+                    return ret;
+                av_init_packet(&si->pkt);
+                si->pkt.data = NULL;
+                break;
+            } else {
+                if (c->first_timestamp == AV_NOPTS_VALUE)
+                    c->first_timestamp = si->pkt.dts;
+            }
+
+            if (c->seek_timestamp == AV_NOPTS_VALUE)
+                break;
+
+            if (si->pkt.dts == AV_NOPTS_VALUE) {
+                c->seek_timestamp = AV_NOPTS_VALUE;
+                break;
+            }
+
+            st = si->ctx->streams[si->pkt.stream_index];
+            ts_diff = av_rescale_rnd(si->pkt.dts, AV_TIME_BASE,
+                                     st->time_base.den, AV_ROUND_DOWN) -
+                c->seek_timestamp;
+            if (ts_diff >= 0 && (c->seek_flags  & AVSEEK_FLAG_ANY ||
+                                 si->pkt.flags & AV_PKT_FLAG_KEY)) {
+                c->seek_timestamp = AV_NOPTS_VALUE;
+                break;
+            }
+        }
+    }
+    return 0;
+}
+
+static int smoothstreaming_read_packet(AVFormatContext *s, AVPacket *pkt)
+{
+    MSSContext *c = s->priv_data;
+    StreamIndex *si = NULL;
+    int ret, minstream = -1;
+
+    if (c->video_id != -1) {
+        ret = treat_packet(c, pkt, &c->stream_index[c->video_id], &minstream);
+        if (ret < 0)
+            return ret;
+    }
+    if (c->audio_id != -1) {
+        ret = treat_packet(c, pkt, &c->stream_index[c->audio_id], &minstream);
+        if (ret < 0) {
+            return ret;
+        }
+    }
+
+    if (c->video_id != -1) {
+        minstream = get_minstream_dts(c, c->video_id, minstream);
+        si = &c->stream_index[c->video_id];
+    }
+
+    if (c->audio_id != -1) {
+        if (minstream != get_minstream_dts(c, c->audio_id, minstream)) {
+            si = &c->stream_index[c->audio_id];
+            minstream = c->audio_id;
+        }
+    }
+    /* If we have a packet, return it */
+    if (si && minstream >= 0) {
+        *pkt = si->pkt;
+        pkt->stream_index = si->qualities[si->cur_quality].stream_id;
+        av_init_packet(&si->pkt);
+        si->pkt.data = NULL;
+        return 0;
+    }
+    return AVERROR_EOF;
+}
+
+static int smoothstreaming_close(AVFormatContext *s)
+{
+    MSSContext *c = s->priv_data;
+    uint64_t i, j;
+
+    for (i=0; i < c->nb_stream_index; ++i)
+    {
+        for (j=0; j < c->stream_index[i].nb_qualities; ++j) {
+            free(c->stream_index[i].qualities[j].private_str);
+        }
+        av_free(c->stream_index[i].qualities);
+        av_free(c->stream_index[i].frags);
+    }
+    av_free(c->stream_index);
+    return 0;
+}
+
+static int find_fragments_ts(AVFormatContext *s, StreamIndex *si, int stream_index, int flags, int64_t timestamp, int *ret)
+{
+    MSSContext *c = s->priv_data;
+    int j;
+
+    /* Reset reading */
+    int64_t pos = c->first_timestamp == AV_NOPTS_VALUE ? 0 :
+        av_rescale_rnd(c->first_timestamp, 10, stream_index >= 0 ?
+                       s->streams[stream_index]->time_base.den :
+                       AV_TIME_BASE, flags & AVSEEK_FLAG_BACKWARD ?
+                       AV_ROUND_DOWN : AV_ROUND_UP);
+
+    if (si->input) {
+        ffurl_close(si->input);
+        si->input = NULL;
+    }
+    av_free_packet(&si->pkt);
+    av_init_packet(&si->pkt);
+    si->pkt.data = NULL;
+    si->pb.eof_reached = 0;
+    /* Clear any buffered data */
+    si->pb.buf_end = si->pb.buf_ptr = si->pb.buffer;
+    /* Reset the pos, to let the mpegts demuxer know we've seeked. */
+    si->pb.pos = 0;
+
+    /* Locate the segment that contains the target timestamp */
+    for (j = 0; j < si->nb_fragments; ++j) {
+        if (timestamp >= pos &&
+            timestamp < pos + si->frags[j].duration) {
+            si->cur_frag = j;
+            *ret = 0;
+            break;
+        }
+        pos += si->frags[j].duration;
+    }
+    return *ret;
+}
+
+static int smoothstreaming_seek(AVFormatContext *s, int stream_index,
+                               int64_t timestamp, int flags)
+{
+    MSSContext *c = s->priv_data;
+    StreamIndex *si = NULL;
+    int i, ret = 0;
+
+    if ((flags & AVSEEK_FLAG_BYTE) || c->is_live)
+        return AVERROR(ENOSYS);
+
+    c->seek_flags     = flags;
+    c->seek_timestamp = stream_index < 0 ? timestamp :
+                        av_rescale_rnd(timestamp, AV_TIME_BASE,
+                                       s->streams[stream_index]->time_base.den,
+                                       flags & AVSEEK_FLAG_BACKWARD ?
+                                       AV_ROUND_DOWN : AV_ROUND_UP);
+    timestamp = av_rescale_rnd(timestamp, 10, stream_index >= 0 ?
+                               s->streams[stream_index]->time_base.den :
+                               AV_TIME_BASE, flags & AVSEEK_FLAG_BACKWARD ?
+                               AV_ROUND_DOWN : AV_ROUND_UP);
+
+    ret = AVERROR(EIO);
+    for (i = 0; i < c->nb_stream_index; ++i) {
+        si = &c->stream_index[i];
+        if (si->qualities[si->cur_quality].stream_id == stream_index) {
+            find_fragments_ts(s, &c->stream_index[i], stream_index, flags, timestamp, &ret);
+            if (ret)
+                c->seek_timestamp = AV_NOPTS_VALUE;
+        }
+    }
+
+    return ret;
+}
+
+static int smoothstreaming_read_probe(AVProbeData *pd)
+{
+    int ret = 0;
+
+    if (pd->filename && !strcasecmp(pd->filename + strlen(pd->filename) - 9, "/manifest"))
+        ret += AVPROBE_SCORE_MAX / 2;
+    if (pd->buf && pd->buf_size > 19 && !strncasecmp(pd->buf, "<?xml version=\"1.0\"", 19))
+        ret += AVPROBE_SCORE_MAX / 4;
+    /* TODO: check for SmoothStreamingMedia */
+    return ret;
+}
+
+AVInputFormat ff_smoothstreaming_demuxer = {
+    .name           = "smoothstreaming,mss",
+    .long_name      = NULL_IF_CONFIG_SMALL("Microsoft Smooth Streaming"),
+    .priv_data_size = sizeof(MSSContext),
+    .read_probe     = smoothstreaming_read_probe,
+    .read_header    = smoothstreaming_read_header,
+    .read_packet    = smoothstreaming_read_packet,
+    .read_close     = smoothstreaming_close,
+    .read_seek      = smoothstreaming_seek,
+};
diff --git a/libavformat/smoothstreaming.h b/libavformat/smoothstreaming.h
new file mode 100644
index 0000000..8e3f5dd
--- /dev/null
+++ b/libavformat/smoothstreaming.h
@@ -0,0 +1,144 @@
+/*
+ * Microsoft Smooth Streaming (mss) demuxer
+ * Copyright (c) 2013 Florent Tribouilloy
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg 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.
+ *
+ * FFmpeg 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 FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * header for smoothstreaming demuxer
+ */
+
+#ifndef AVFORMAT_SMOOTHSTREAMING_H
+#define AVFORMAT_SMOOTHSTREAMING_H
+#include "isom.h"
+
+typedef struct Fragment
+{
+    uint64_t duration;
+    int index;
+    uint64_t start_ts;
+} Fragment;
+
+typedef struct QualityVideo
+{
+    int max_width;
+    int max_height;
+    int width;
+    int height;
+} QualityVideo;
+
+typedef struct QualityAudio
+{
+    int sample_rate;
+    int nb_channels;
+    int bit_per_sample;
+    int packet_size;
+    int audio_tag;
+    int wave_format_ex;
+} QualityAudio;
+
+/* QualityLevel */
+typedef struct Quality
+{
+    int is_video;
+    int is_audio;
+
+    int index; /* stream index */
+    uint32_t fourcc;
+    uint64_t bit_rate; /* bit rate of this fragments stream */
+    char *private_str; /* Codec private data */
+
+    int stream_id;
+
+    AVFormatContext *ctx; /* context of the video/audio stream */
+    AVFormatContext *parent; /* needed when reading data from fragment */
+    URLContext *input; /* current fragment */
+    AVInputFormat *fmt; /* input format, fragment format */
+    AVPacket pkt; /* packet to send to the demuxer */
+
+    uint8_t *read_buffer; /* buffer needed by read_data */
+    AVIOContext pb;
+    QualityVideo *qv;
+    QualityAudio *qa;
+} Quality;
+
+/* StreamIndex */
+typedef struct StreamIndex {
+    int is_video;
+    int is_audio;
+    int is_text;
+
+    int index;
+    char url[MAX_URL_SIZE];
+    int64_t last_load_time;
+
+    int max_width;
+    int max_height;
+
+    int display_width;
+    int display_height;
+
+    int nb_qualities;
+    int cur_quality;
+    Quality *qualities;
+
+    int nb_fragments;
+    int cur_frag; /* fragment to add */
+    Fragment *frags;
+
+    AVFormatContext *parent;
+    AVFormatContext *ctx;
+    AVInputFormat *fmt;
+    AVIOContext pb;
+    AVPacket pkt;
+    uint8_t *read_buffer;
+    URLContext *input;
+} StreamIndex;
+
+/* SmoothStreamingMedia */
+typedef struct MSSContext {
+    char url[MAX_URL_SIZE];
+
+    int is_live;
+    uint64_t duration;
+
+    int major;
+    int minor;
+    int xml_error;
+
+    int nb_stream_index;
+    StreamIndex *stream_index;
+
+    int64_t first_timestamp;
+    int64_t seek_timestamp;
+    int seek_flags;
+
+    int video_id;
+    int audio_id;
+
+    AVIOInterruptCB *interrupt_callback;
+} MSSContext;
+
+#define SMOOTH_BUFF_SIZE 4096
+#define INITIAL_BUFFER_SIZE 32768
+#define NB_DIGIT_UINT64 20
+
+int smoothstreaming_parse_manifest(AVFormatContext *s, const char *url, AVIOContext *in);
+
+#endif /* AVFORMAT_SMOOTHSTREAMING_H */
diff --git a/libavformat/smoothstreaming_parse.c b/libavformat/smoothstreaming_parse.c
new file mode 100644
index 0000000..4fec1f9
--- /dev/null
+++ b/libavformat/smoothstreaming_parse.c
@@ -0,0 +1,646 @@
+/*
+ * Microsoft Smooth Streaming (mss) demuxer
+ * Copyright (c) 2013 Florent Tribouilloy
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg 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.
+ *
+ * FFmpeg 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 FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "libavutil/time.h"
+
+#include "avformat.h"
+#include "url.h"
+#include "smoothstreaming.h"
+
+#include <expat.h>
+#include <ctype.h>
+
+static void print_context(AVFormatContext *s)
+{
+    uint64_t i,j;
+    MSSContext *c = s->priv_data;
+
+    av_log(s, AV_LOG_VERBOSE, "is live : %s\n", c->is_live ? "yes" : "no");
+    av_log(s, AV_LOG_VERBOSE, "duration : %"PRIu64"\n", c->duration);
+    av_log(s, AV_LOG_VERBOSE, "major : %d\n", c->major);
+    av_log(s, AV_LOG_VERBOSE, "minor : %d\n", c->minor);
+    av_log(s, AV_LOG_VERBOSE, "number of stream index : %d\n", c->nb_stream_index);
+
+
+    for (i = 0; i < c->nb_stream_index ; ++i) {
+        StreamIndex *si = &c->stream_index[i];
+        const char *type = si->is_video ? "video" : si->is_audio ? "audio" : "unknown";
+
+        av_log(s, AV_LOG_VERBOSE, "\tStream %s index : %d\n",
+               type, si->index);
+        av_log(s, AV_LOG_VERBOSE, "\tUrl : %s\n", si->url);
+
+        if (si->is_video) {
+            av_log(s, AV_LOG_VERBOSE, "\tMaximum widthXheight : %dX%d\n",
+                   si->max_width, si->max_height);
+
+            av_log(s, AV_LOG_VERBOSE, "\tDisplay widthXheight : %dX%d\n",
+                   si->display_width, si->display_height);
+        }
+
+        av_log(s, AV_LOG_VERBOSE, "\t%d qualities for this stream\n", si->nb_qualities);
+
+        for (j = 0; j < si->nb_qualities ; ++j) {
+            Quality *q = &si->qualities[j];
+
+            av_log(s, AV_LOG_VERBOSE, "\t\tIndex of this %s quality: %d\n", type, q->index);
+            av_log(s, AV_LOG_VERBOSE, "\t\tbit_rate : %"PRIu64"\n", q->bit_rate);
+            av_log(s, AV_LOG_VERBOSE, "\t\tfourcc : %.4s\n", (char*)&q->fourcc);
+            av_log(s, AV_LOG_VERBOSE, "\t\tprivate data : %s\n", q->private_str);
+            if (q->is_video) {
+                av_log(s, AV_LOG_VERBOSE, "\t\tvideo widthXheight : %dX%d\n",
+                       q->qv->width, q->qv->height);
+                av_log(s, AV_LOG_VERBOSE, "\t\tvideo maxwidthXmaxheight : %dX%d\n",
+                       q->qv->max_width, q->qv->max_height);
+            } else if (q->is_audio) {
+                av_log(s, AV_LOG_VERBOSE, "\t\tsample_rate : %d\n", q->qa->sample_rate);
+                av_log(s, AV_LOG_VERBOSE, "\t\tnb of channels : %d\n", q->qa->nb_channels);
+                av_log(s, AV_LOG_VERBOSE, "\t\tbit per sample : %d\n", q->qa->bit_per_sample);
+                av_log(s, AV_LOG_VERBOSE, "\t\tpacket size : %d\n", q->qa->packet_size);
+                av_log(s, AV_LOG_VERBOSE, "\t\ttag audio : %d\n", q->qa->audio_tag);
+            }
+        }
+
+        av_log(s, AV_LOG_VERBOSE, "\t%d fragments for this stream\n", si->nb_fragments);
+        for (j=0; j < si->nb_fragments ; ++j)
+        {
+            av_log(s, AV_LOG_VERBOSE, "\t\tfragment duration : %"PRIu64"\n", si->frags[j].duration);
+            av_log(s, AV_LOG_VERBOSE, "\t\tfragment index : %d\n", si->frags[j].index);
+            av_log(s, AV_LOG_VERBOSE, "\t\tfragment start timestamp : %"PRIu64"\n", si->frags[j].start_ts);
+        }
+    }
+
+}
+
+static int parse_media(AVFormatContext *s, const char **attribute)
+{
+    MSSContext *c = s->priv_data;
+    char *tmp = NULL;
+    int i = 0;
+
+    c->is_live = 0;
+    c->nb_stream_index = 0;
+    c->stream_index = NULL;
+    c->duration = -1;
+    c->major = -1;
+    c->minor = -1;
+
+    for (i = 0; attribute[i] && attribute[i + 1]; i += 2) {
+        if (strcmp(attribute[i], "isLive") == 0
+            && strcmp(attribute[i + 1], "true") == 0)
+            c->is_live = 1;
+        else if (strcasecmp(attribute[i], "Duration") == 0) {
+            c->duration = strtoll(attribute[i + 1], &tmp, 10);
+            if (!tmp || tmp == attribute[i + 1] || *tmp != '\0') {
+                c->duration = 0;
+                return AVERROR_INVALIDDATA;
+            }
+        } else if (!strcasecmp(attribute[i], "MajorVersion")) {
+            c->major = strtol(attribute[i + 1], &tmp, 10);
+            if (!tmp || tmp == attribute[i + 1] || *tmp != '\0') {
+                c->major = -1;
+                return AVERROR_INVALIDDATA;
+            }
+        } else if (!strcasecmp(attribute[i], "MinorVersion")) {
+            c->minor = strtol(attribute[i + 1], &tmp, 10);
+            if (!tmp || tmp == attribute[i + 1] || *tmp != '\0') {
+                c->minor = -1;
+                return AVERROR_INVALIDDATA;
+            }
+        } else if (!strcasecmp(attribute[i], "TimeScale")) {
+            /* TODO */
+        } else if (!strcasecmp(attribute[i], "LookAheadCount")) {
+            /* TODO */
+        } else if (!strcasecmp(attribute[i], "DVRWindowLength")) {
+            /* TODO */
+        } else {
+            av_log(s, AV_LOG_ERROR, "Manifest : SmoothStreamingMedia: field %s"
+                   " is not recognized\n", attribute[i]);
+            return AVERROR_INVALIDDATA;
+        }
+    }
+    if (c->duration == -1 || c->major == -1 || c->minor == -1) {
+        av_log(s, AV_LOG_ERROR, "Manifest : SmoothStreamingMedia needs all its mandatory fields\n");
+        return AVERROR_INVALIDDATA;
+    }
+    return 0;
+}
+
+static int make_stream_url(AVFormatContext *s, StreamIndex* si, const char *url)
+{
+    MSSContext *c = s->priv_data;
+    char *find_pos;
+    uint64_t diff;
+    int len;
+
+    find_pos = strcasestr(c->url, "/manifest");
+    if (!find_pos)
+        diff = strlen(c->url);
+    else
+        diff = find_pos - c->url;
+    len = FFMIN(sizeof(si->url), diff + 1);
+    snprintf(si->url, len, "%s", c->url);
+
+    len -= 1;
+
+    snprintf(si->url + len, sizeof(si->url) - len, "/%s", url);
+
+    return 0;
+}
+
+static int parse_index(AVFormatContext *s, const char **attribute)
+{
+    MSSContext *c = s->priv_data;
+    int stream_i = c->nb_stream_index;
+    int i = 0;
+    char *tmp = NULL;
+    const char *url = NULL;
+    StreamIndex *si = NULL;
+
+    ++c->nb_stream_index;
+    c->stream_index = av_realloc(c->stream_index, c->nb_stream_index * sizeof(*c->stream_index));
+    if (!c->stream_index) {
+        goto not_enought_memory;
+    }
+    si = &c->stream_index[stream_i];
+
+    si->display_width = -1;
+    si->display_height = -1;
+    si->max_width = -1;
+    si->max_height = -1;
+    si->is_video = 0;
+    si->is_audio = 0;
+    si->index = -1;
+    si->nb_qualities = -1;
+    si->fmt = NULL;
+    si->pb.av_class = NULL;
+    si->input = NULL;
+    si->pkt.data = NULL;
+
+    for (i = 0; attribute[i] && attribute[i + 1]; i += 2) {
+        if (!strcasecmp(attribute[i], "Type")) {
+            if (strcasecmp(attribute[i + 1], "video") == 0)
+                si->is_video = 1;
+            else if (strcasecmp(attribute[i + 1], "audio") == 0)
+                si->is_audio = 1;
+            else if (strcasecmp(attribute[i + 1], "text") == 0)
+                si->is_text = 1; /* TODO: subtitles */
+            else
+                return AVERROR_INVALIDDATA;
+        } else if (!strcasecmp(attribute[i], "QualityLevels")) {
+            /* Some server use this value for fun and make a segmentation fault */
+            /* si->nb_qualities = strtoll(attribute[i + 1], &tmp, 10); */
+            /* if (!tmp || tmp == attribute[i + 1] || *tmp != '\0') */
+            /*     { */
+            /*         return AVERROR_INVALIDDATA; */
+            /*     } */
+        } else if (!strcasecmp(attribute[i], "Chunks")) {
+            si->nb_fragments = strtoll(attribute[i + 1], &tmp, 10);
+            if (!tmp || tmp == attribute[i + 1] || *tmp != '\0') {
+                return AVERROR_INVALIDDATA;
+            }
+        } else if (!strcasecmp(attribute[i], "Index")) {
+            si->index = strtoll(attribute[i + 1], &tmp, 10);
+            if (!tmp || tmp == attribute[i + 1] || *tmp != '\0') {
+                return AVERROR_INVALIDDATA;
+            }
+        } else if (!strcasecmp(attribute[i], "MaxWidth")) {
+            si->max_width = strtoll(attribute[i + 1], &tmp, 10);
+            if (!tmp || tmp == attribute[i + 1] || *tmp != '\0') {
+                return AVERROR_INVALIDDATA;
+            }
+        } else if (!strcasecmp(attribute[i], "MaxHeight")) {
+            si->max_height = strtoll(attribute[i + 1], &tmp, 10);
+            if (!tmp || tmp == attribute[i + 1] || *tmp != '\0') {
+                return AVERROR_INVALIDDATA;
+            }
+        } else if (!strcasecmp(attribute[i], "DisplayWidth")) {
+            si->display_width = strtoll(attribute[i + 1], &tmp, 10);
+            if (!tmp || tmp == attribute[i + 1] || *tmp != '\0') {
+                return AVERROR_INVALIDDATA;
+            }
+        } else if (!strcasecmp(attribute[i], "DisplayHeight")) {
+            si->display_height = strtoll(attribute[i + 1], &tmp, 10);
+            if (!tmp || tmp == attribute[i + 1] || *tmp != '\0') {
+                return AVERROR_INVALIDDATA;
+            }
+        } else if (!strcasecmp(attribute[i], "Url")) {
+            url = attribute[i + 1];
+        } else if (!strcasecmp(attribute[i], "Subtype")) {
+            av_log(s, AV_LOG_INFO, "Subtype : %s\n", attribute[i + 1]);
+        } else if (!strcasecmp(attribute[i], "SubtypeEventControl")) {
+            av_log(s, AV_LOG_INFO, "SubtypeEventControl : %s\n", attribute[i + 1]);
+        } else if (!strcasecmp(attribute[i], "ParentStream")) {
+            av_log(s, AV_LOG_INFO, "ParentStream : %s\n", attribute[i + 1]);
+        } else if (!strcasecmp(attribute[i], "Name")) {
+            av_log(s, AV_LOG_INFO, "name : %s\n", attribute[i + 1]);
+        } else {
+            av_log(s, AV_LOG_WARNING, "Manifest : StreamIndex : option %s is not recognized\n", attribute[i]);
+        }
+    }
+
+    if (si->index == -1) {
+        si->index = 0;
+    }
+
+    make_stream_url(s, si, url);
+
+    if (si->nb_qualities != -1) {
+        si->qualities = av_mallocz(si->nb_qualities * sizeof(*si->qualities));
+        if (!si->qualities) {
+            goto not_enought_memory;
+        }
+    } else {
+        si->qualities = NULL;
+        si->nb_qualities = 0;
+    }
+    si->cur_quality = -1;
+
+    si->frags = av_mallocz(si->nb_fragments * sizeof(*si->frags));
+    if (!si->frags) {
+        goto not_enought_memory;
+    }
+    si->cur_frag = -1;
+    si->last_load_time = av_gettime();
+
+    return 0;
+
+ not_enought_memory:
+    return AVERROR(ENOMEM);
+}
+
+static int parse_quality(AVFormatContext *s, const char **attribute)
+{
+    MSSContext *c = s->priv_data;
+    int stream_i = c->nb_stream_index - 1;
+    StreamIndex *si = NULL;
+    Quality *q = NULL;
+
+    int i = 0;
+    char *tmp = NULL;
+    int64_t bit_rate = -1;
+    int index = -1;
+    int max_width = -1, max_height = -1;
+    int width = -1, height = -1;
+    uint64_t audio_tag = 0, packet_size = 0, channels = 0;
+    uint64_t sample_rate = 0, b_p_sample = 0;
+    const char *fourcc = NULL, *private_data = NULL;
+    int wave_format_ex = 0;
+
+    if (stream_i == -1)
+        return AVERROR_INVALIDDATA;
+
+    si = &c->stream_index[stream_i];
+    ++si->cur_quality;
+
+    if (si->cur_quality == si->nb_qualities) {
+        ++si->nb_qualities;
+        si->qualities = av_realloc(si->qualities, si->nb_qualities * sizeof(*si->qualities));
+        if (!si->qualities)
+            return AVERROR(ENOMEM);
+    }
+    q = &si->qualities[si->cur_quality];
+
+    for (i = 0; attribute[i] && attribute[i + 1]; i += 2)
+    {
+        if (strcasecmp(attribute[i], "Index") == 0)
+        {
+            index = strtoll(attribute[i + 1], &tmp, 10);
+            if (!tmp || tmp == attribute[i + 1] || *tmp != '\0')
+            {
+                return AVERROR_INVALIDDATA;
+            }
+        }
+        else if (strcasecmp(attribute[i], "Bitrate") == 0)
+        {
+            bit_rate = strtoll(attribute[i + 1], &tmp, 10);
+            if (!tmp || tmp == attribute[i + 1] || *tmp != '\0')
+            {
+                return AVERROR_INVALIDDATA;
+            }
+        }
+        else if (strcasecmp(attribute[i], "MaxWidth") == 0)
+        {
+            max_width = strtoll(attribute[i + 1], &tmp, 10);
+            if (!tmp || tmp == attribute[i + 1] || *tmp != '\0')
+            {
+                return AVERROR_INVALIDDATA;
+            }
+        }
+        else if (strcasecmp(attribute[i], "MaxHeight") == 0)
+        {
+            max_height = strtoll(attribute[i + 1], &tmp, 10);
+            if (!tmp || tmp == attribute[i + 1] || *tmp != '\0')
+            {
+                return AVERROR_INVALIDDATA;
+            }
+        }
+        else if (strcasecmp(attribute[i], "Width") == 0)
+        {
+            width = strtoll(attribute[i + 1], &tmp, 10);
+            if (!tmp || tmp == attribute[i + 1] || *tmp != '\0')
+            {
+                return AVERROR_INVALIDDATA;
+            }
+        }
+        else if (strcasecmp(attribute[i], "Height") == 0)
+        {
+            height = strtoll(attribute[i + 1], &tmp, 10);
+            if (!tmp || tmp == attribute[i + 1] || *tmp != '\0')
+            {
+                return AVERROR_INVALIDDATA;
+            }
+        }
+        else if (strcasecmp(attribute[i], "AudioTag") == 0)
+        {
+            audio_tag = strtoll(attribute[i + 1], &tmp, 10);
+            if (!tmp || tmp == attribute[i + 1] || *tmp != '\0')
+            {
+                return AVERROR_INVALIDDATA;
+            }
+        }
+        else if (strcasecmp(attribute[i], "BitsPerSample") == 0)
+        {
+            b_p_sample = strtoll(attribute[i + 1], &tmp, 10);
+            if (!tmp || tmp == attribute[i + 1] || *tmp != '\0')
+            {
+                return AVERROR_INVALIDDATA;
+            }
+        }
+        else if (strcasecmp(attribute[i], "SamplingRate") == 0)
+        {
+            sample_rate = strtoll(attribute[i + 1], &tmp, 10);
+            if (!tmp || tmp == attribute[i + 1] || *tmp != '\0')
+            {
+                return AVERROR_INVALIDDATA;
+            }
+        }
+        else if (strcasecmp(attribute[i], "PacketSize") == 0)
+        {
+            packet_size = strtoll(attribute[i + 1], &tmp, 10);
+            if (!tmp || tmp == attribute[i + 1] || *tmp != '\0')
+            {
+                return AVERROR_INVALIDDATA;
+            }
+        }
+        else if (strcasecmp(attribute[i], "Channels") == 0)
+        {
+            channels = strtoll(attribute[i + 1], &tmp, 10);
+            if (!tmp || tmp == attribute[i + 1] || *tmp != '\0')
+            {
+                return AVERROR_INVALIDDATA;
+            }
+        }
+        else if (strcasecmp(attribute[i], "FourCC") == 0)
+        {
+            fourcc = attribute[i + 1];
+        }
+        else if (strcasecmp(attribute[i], "CodecPrivateData") == 0)
+        {
+            private_data = attribute[i + 1];
+        }
+        else if (strcasecmp(attribute[i], "WaveFormatEx") == 0)
+        {
+            fourcc = "WMAP";
+            private_data = attribute[i + 1];
+            wave_format_ex = 1;
+        }
+        else
+            av_log(NULL, AV_LOG_WARNING, "Unreconized %s='%s'\n", attribute[i], attribute[i + 1]);
+    }
+
+    /* No field Index in the QualityLevel, only one is supported */
+    if (index == -1)
+        index = 0;
+
+    if (bit_rate == -1)
+        return AVERROR_INVALIDDATA;
+
+    q->bit_rate = bit_rate;
+    q->index = index;
+    if (!fourcc || strlen(fourcc) != 4)
+        return AVERROR_INVALIDDATA;
+    q->fourcc = MKTAG(tolower(fourcc[0]), tolower(fourcc[1]), tolower(fourcc[2]), tolower(fourcc[3]));
+
+    if (private_data) {
+        q->private_str = strdup(private_data);
+        if (!q->private_str)
+            return AVERROR(ENOMEM);
+    } else
+        q->private_str = NULL;
+    q->is_video = si->is_video;
+    q->is_audio = si->is_audio;
+    if (q->is_audio) {
+        QualityAudio *qa = av_mallocz(sizeof(*qa));
+        if (!qa)
+            return AVERROR(ENOMEM);
+
+        qa->nb_channels = channels;
+        qa->sample_rate = sample_rate;
+        qa->bit_per_sample = b_p_sample;
+        qa->audio_tag = audio_tag;
+        qa->packet_size = packet_size;
+        q->qa = qa;
+        qa->wave_format_ex = wave_format_ex;
+    } else if (q->is_video) {
+        QualityVideo *qv = av_mallocz(sizeof(*qv));
+        if (!qv)
+            return AVERROR(ENOMEM);
+
+        qv->max_width = max_width;
+        qv->max_height = max_height;
+        qv->width = width;
+        qv->height = height;
+        q->qv = qv;
+    }
+    return 0;
+}
+
+static int parse_frags(AVFormatContext *s, const char **attribute)
+{
+    MSSContext *c = s->priv_data;
+    int stream_i = c->nb_stream_index - 1;
+    StreamIndex *si = NULL;
+    Fragment *f = NULL;
+    int i = 0, j;
+    char *tmp = NULL;
+    int64_t start_ts = -1;
+
+    if (stream_i == -1)
+        return AVERROR_INVALIDDATA;
+
+    si = &c->stream_index[stream_i];
+    ++si->cur_frag;
+    f = &si->frags[si->cur_frag];
+
+    for (i = 0; attribute[i] && attribute[i + 1]; i += 2)
+    {
+        if (strcmp(attribute[i], "n") == 0)
+        {
+            f->index = strtoll(attribute[i + 1], &tmp, 10);
+            if (!tmp || tmp == attribute[i + 1] || *tmp != '\0')
+            {
+                return AVERROR_INVALIDDATA;
+            }
+            /* start_ts = 0; */
+            /* for (j = 0; j < f->index - 1; ++j) */
+            /*     start_ts += si->frags[j].duration; */
+        }
+        else if (strcasecmp(attribute[i], "d") == 0)
+        {
+            f->duration = strtoll(attribute[i + 1], &tmp, 10);
+            if (!tmp || tmp == attribute[i + 1] || *tmp != '\0')
+            {
+                return AVERROR_INVALIDDATA;
+            }
+        }
+        else if (strcasecmp(attribute[i], "t") == 0)
+        {
+            f->start_ts = strtoll(attribute[i + 1], &tmp, 10);
+            if (!tmp || tmp == attribute[i + 1] || *tmp != '\0')
+            {
+                return AVERROR_INVALIDDATA;
+            }
+        }
+        else
+        {
+            av_log(NULL, AV_LOG_WARNING, "Unrecognized %s='%s'\n", attribute[i], attribute[i + 1]);
+            return AVERROR_INVALIDDATA;
+        }
+    }
+
+    if (f->index == 0) {
+        f->index = si->cur_frag;
+    }
+    if (start_ts == -1 && f->start_ts == 0) {
+        start_ts = 0;
+        for (j = 0; j < f->index - 1; ++j)
+            start_ts += si->frags[j].duration;
+        f->start_ts = start_ts;
+    }
+
+    return 0;
+}
+
+/* first when start element is encountered */
+static void start_element(void *data, const char *element, const char **attribute)
+{
+    AVFormatContext *s = data;
+    MSSContext *c = s->priv_data;
+    int error = 0;
+
+    if (strcasecmp(element, "SmoothStreamingMedia") == 0)
+    {
+        if ((error = parse_media(s, attribute)) < 0)
+            goto fail;
+    }
+
+    else if (strcasecmp(element, "StreamIndex") == 0)
+    {
+        if ((error = parse_index(s, attribute)) < 0)
+            goto fail;
+    }
+
+    else if (strcasecmp(element, "QualityLevel") == 0)
+    {
+        if ((error = parse_quality(s, attribute)) < 0)
+            goto fail;
+    }
+
+    else if (strcasecmp(element, "c") == 0)
+    {
+        if ((error = parse_frags(s, attribute)) < 0)
+            goto fail;
+    }
+
+    else
+    {
+        av_log(s, AV_LOG_WARNING, "Unrecognized element %s", element);
+    }
+
+    return ;
+
+ fail:
+    c->xml_error = error;
+}
+
+static void end_element(void *data, const char *element)
+{
+}
+
+static void handle_data(void *data, const char *content, int length)
+{
+}
+
+int smoothstreaming_parse_manifest(AVFormatContext *s, const char *url, AVIOContext *in)
+{
+    MSSContext *c = s->priv_data;
+    int ret = 0;
+    char *line = NULL;
+    int pos = 0;
+    int len = 0;
+    XML_Parser      parser;
+
+    while (!url_feof(in))
+    {
+        line = av_realloc(line, pos + SMOOTH_BUFF_SIZE);
+        if (!line)
+        {
+            return AVERROR(ENOMEM);
+        }
+        len = avio_read(in, line + pos, SMOOTH_BUFF_SIZE - 1);
+        if (len >= 0)
+        {
+            pos += len;
+            line[pos] = '\0';
+        }
+        else
+        {
+            break;
+        }
+    }
+
+    parser = XML_ParserCreate(NULL);
+
+    if (!parser)
+    {
+        av_log(s, AV_LOG_ERROR, "Unable to allocate memory for the libexpat XML parser\n");
+        return AVERROR(ENOMEM);
+    }
+
+    c->xml_error = 0;
+    XML_SetUserData(parser, s);
+    XML_SetElementHandler(parser, start_element, end_element);
+    XML_SetCharacterDataHandler(parser, handle_data);
+
+    if (XML_Parse(parser, line, pos, XML_TRUE) == XML_STATUS_ERROR)
+    {
+        av_log(s, AV_LOG_ERROR, "Error: %s\n", XML_ErrorString(XML_GetErrorCode(parser)));
+    }
+
+    XML_ParserFree(parser);
+    av_free(line);
+
+    if (c->xml_error >= 0)
+        print_context(s);
+
+    return c->xml_error;
+}
-- 
1.7.10.4



More information about the ffmpeg-devel mailing list