[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