After contacting the original author of the the patch posted in:
http://lists.ffmpeg.org/pipermail/ffmpeg-devel/2014-February/154612.html
I attach a new patch with improvements:
- Better codec detection in amfmetata
- Better live streams handling (Get current segment, calculate next
segment..etc)
- Parse tabs in raw xml (0x09)
Tested streams:
http://livehds.rasset.ie/hds-live/_definst_/rte1/rte1_288p.f4m
http://livehds.rasset.ie/hds-live/_definst_/rte2/rte2_288p.f4m
http://livehds.rasset.ie/hds-live/_definst_/newsnow/newsnow_288p.f4m
TODO:
- Parse child manifest, ex:
http://www.rte.ie/manifests/rte1.f4m
http://www.rte.ie/manifests/newsnow.f4m
- Implement fragment caching.
diff --git Changelog Changelog
index 6260d0c..053d36d 100644
--- Changelog
+++ Changelog
@@ -7,6 +7,7 @@ version <next>:
- large optimizations in dctdnoiz to make it usable
- request icecast metadata by default
- support for using metadata in stream specifiers in fftools
+- Adobe HTTP Dynamic Streaming (HDS) demuxer
version 2.3:
diff --git configure configure
index f355dad..f8645bf 100755
--- configure
+++ configure
@@ -261,6 +261,7 @@ External library support:
--enable-x11grab enable X11 grabbing [no]
--disable-xlib disable xlib [autodetect]
--disable-zlib disable zlib [autodetect]
+ --disable-xml2 disable XML parsing using the C library libxml2 [autodetect]
Toolchain options:
--arch=ARCH select architecture [$arch]
@@ -1386,6 +1387,7 @@ EXTERNAL_LIBRARY_LIST="
openssl
x11grab
xlib
+ xml2
zlib
"
@@ -2386,6 +2388,7 @@ f4v_muxer_select="mov_muxer"
flac_demuxer_select="flac_parser"
hds_muxer_select="flv_muxer"
hls_muxer_select="mpegts_muxer"
+hds_demuxer_select="xml2"
image2_alias_pix_demuxer_select="image2_demuxer"
image2_brender_pix_demuxer_select="image2_demuxer"
ipod_muxer_select="mov_muxer"
@@ -4756,6 +4759,12 @@ fi
disabled zlib || check_lib zlib.h zlibVersion -lz || disable zlib
disabled bzlib || check_lib2 bzlib.h BZ2_bzlibVersion -lbz2 || disable bzlib
+disabled xml2 || {
+ check_pkg_config libxml-2.0 libxml2/libxml/xmlversion.h xmlCheckVersion &&
+ require_pkg_config libxml-2.0 libxml2/libxml/xmlversion.h xmlCheckVersion
+ } || disable xml2
+
+
check_lib math.h sin -lm && LIBM="-lm"
disabled crystalhd || check_lib libcrystalhd/libcrystalhd_if.h DtsCrystalHDVersion -lcrystalhd || disable crystalhd
diff --git libavformat/Makefile libavformat/Makefile
index 11d3e9c..30fe33c 100644
--- libavformat/Makefile
+++ libavformat/Makefile
@@ -170,6 +170,7 @@ OBJS-$(CONFIG_H263_MUXER) += rawenc.o
OBJS-$(CONFIG_H264_DEMUXER) += h264dec.o rawdec.o
OBJS-$(CONFIG_H264_MUXER) += rawenc.o
OBJS-$(CONFIG_HDS_MUXER) += hdsenc.o
+OBJS-$(CONFIG_HDS_DEMUXER) += hdsdec.o amfmetadata.o f4mmanifest.o f4fbox.o flvtag.o
OBJS-$(CONFIG_HEVC_DEMUXER) += hevcdec.o rawdec.o
OBJS-$(CONFIG_HEVC_MUXER) += rawenc.o
OBJS-$(CONFIG_HLS_DEMUXER) += hls.o
diff --git libavformat/allformats.c libavformat/allformats.c
index c3b9ba5..5489cc4 100644
--- libavformat/allformats.c
+++ libavformat/allformats.c
@@ -137,7 +137,7 @@ void av_register_all(void)
REGISTER_MUXDEMUX(H261, h261);
REGISTER_MUXDEMUX(H263, h263);
REGISTER_MUXDEMUX(H264, h264);
- REGISTER_MUXER (HDS, hds);
+ REGISTER_MUXDEMUX(HDS, hds);
REGISTER_MUXDEMUX(HEVC, hevc);
REGISTER_MUXDEMUX(HLS, hls);
REGISTER_DEMUXER (HNM, hnm);
diff --git libavformat/version.h libavformat/version.h
index c09bd88..8b8a690 100644
--- libavformat/version.h
+++ libavformat/version.h
@@ -30,7 +30,7 @@
#include "libavutil/version.h"
#define LIBAVFORMAT_VERSION_MAJOR 56
-#define LIBAVFORMAT_VERSION_MINOR 1
+#define LIBAVFORMAT_VERSION_MINOR 2
#define LIBAVFORMAT_VERSION_MICRO 100
#define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
diff --git libavformat/amfmetadata.c libavformat/amfmetadata.c
new file mode 100644
index 0000000..7068e87
--- /dev/null
+++ libavformat/amfmetadata.c
@@ -0,0 +1,232 @@
+/*
+ * Adobe Action Message Format Parser
+ * Copyright (c) 2013 Cory McCarthy
+ *
+ * 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
+ * @brief Adobe Action Message Format Parser
+ * @author Cory McCarthy
+ * @see http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf
+ * @see http://www.adobe.com/content/dam/Adobe/en/devnet/amf/pdf/amf-file-format-spec.pdf
+ */
+
+#include "amfmetadata.h"
+#include "avio_internal.h"
+#include "flv.h"
+#include "libavutil/avstring.h"
+#include "libavutil/intfloat.h"
+
+static int amf_metadata_parse_value(AVIOContext *in, AMFMetadata *metadata, const char *name);
+
+static int amf_metadata_read_string_value(AVIOContext *in, char *str, int str_size)
+{
+ uint8_t type;
+
+ type = avio_r8(in);
+ if(type != 0x02) {
+ av_log(NULL, AV_LOG_ERROR, "amfmetadata Expected type 2, type = %d \n", type);
+ return -1;
+ }
+
+ return ff_amf_get_string(in, str, str_size);
+}
+
+static void amf_metadata_assign_property_number(AMFMetadata *metadata,
+ const char *name, double value)
+{
+ if(!av_strcasecmp("width", name)) {
+ metadata->width = (int)value;
+ }
+ else
+ if(!av_strcasecmp("height", name)) {
+ metadata->height = (int)value;
+ }
+ else
+ if(!av_strcasecmp("framerate", name)) {
+ metadata->frame_rate = (int)value;
+ }
+ else
+ if(!av_strcasecmp("videodatarate", name)) {
+ metadata->video_data_rate = (int)value;
+ }
+ else
+ if(!av_strcasecmp("audiosamplerate", name)) {
+ metadata->audio_sample_rate = (int)value;
+ }
+ else
+ if(!av_strcasecmp("audiochannels", name)) {
+ metadata->nb_audio_channels = (int)value;
+ }
+ else
+ if(!av_strcasecmp("stereo", name)) {
+ metadata->nb_audio_channels = ((int)value) ? 2 : 1;
+ }
+ else
+ if(!av_strcasecmp("audiodatarate", name)) {
+ metadata->audio_data_rate = (int)value;
+ }
+ else
+ if(!av_strcasecmp("audiocodecid", name)) {
+ if((int)value == 10)
+ metadata->audio_codec_id = AV_CODEC_ID_AAC;
+ }
+ else
+ if(!av_strcasecmp("videocodecid", name)) {
+ if((int)value == 7)
+ metadata->video_codec_id = AV_CODEC_ID_H264;
+ }
+}
+
+static void amf_metadata_assign_property_string(AMFMetadata *metadata,
+ const char *name, const char *value)
+{
+ if(!av_strcasecmp("audiocodecid", name)) {
+ if(!av_strcasecmp("mp4a", value))
+ metadata->audio_codec_id = AV_CODEC_ID_AAC;
+ else if(!av_strcasecmp("aac", value))
+ metadata->audio_codec_id = AV_CODEC_ID_AAC;
+ }
+ else
+ if(!av_strcasecmp("videocodecid", name)) {
+ if(!av_strcasecmp("avc1", value))
+ metadata->video_codec_id = AV_CODEC_ID_H264;
+ else if(!av_strcasecmp("h264", value))
+ metadata->video_codec_id = AV_CODEC_ID_H264;
+ }
+}
+
+static int amf_metadata_parse_object_property(AVIOContext *in, AMFMetadata *metadata)
+{
+ char name[INT16_MAX];
+ int ret;
+
+ if((ret = ff_amf_get_string(in, name, sizeof(name))) < 0)
+ return ret;
+
+ if(!strlen(name))
+ return -1;
+
+ return amf_metadata_parse_value(in, metadata, name);
+}
+
+static int amf_metadata_parse_object(AVIOContext *in, AMFMetadata *metadata)
+{
+ int ret;
+
+ while(!url_feof(in)) {
+ if((ret = amf_metadata_parse_object_property(in, metadata)) < 0) {
+ if(avio_r8(in) != AMF_END_OF_OBJECT)
+ return ret;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int amf_metadata_parse_strict_array(AVIOContext *in, AMFMetadata *metadata)
+{
+ int length;
+ int ret;
+
+ length = avio_rb32(in);
+ while(!url_feof(in) && length > 0) {
+ if((ret = amf_metadata_parse_value(in, metadata, NULL)) < 0)
+ return ret;
+ length--;
+ }
+
+ return 0;
+}
+
+static int amf_metadata_parse_value(AVIOContext *in, AMFMetadata *metadata, const char *name)
+{
+ uint8_t type;
+ char value_str[INT16_MAX];
+ double value_number;
+ int ret = 0;
+
+ type = avio_r8(in);
+
+ if(type == AMF_DATA_TYPE_NUMBER) {
+ value_number = av_int2double(avio_rb64(in));
+ amf_metadata_assign_property_number(metadata, name, value_number);
+ }
+ else if(type == AMF_DATA_TYPE_BOOL) {
+ value_number = avio_r8(in);
+ amf_metadata_assign_property_number(metadata, name, value_number);
+ }
+ else if(type == AMF_DATA_TYPE_STRING) {
+ if((ret = ff_amf_get_string(in, value_str, sizeof(value_str))) < 0)
+ return ret;
+ amf_metadata_assign_property_string(metadata, name, value_str);
+ }
+ else if(type == AMF_DATA_TYPE_OBJECT) {
+ ret = amf_metadata_parse_object(in, metadata);
+ }
+ else if(type == AMF_DATA_TYPE_MIXEDARRAY) {
+ avio_skip(in, 4);
+ ret = amf_metadata_parse_object(in, metadata);
+ }
+ else if(type == AMF_DATA_TYPE_ARRAY) {
+ ret = amf_metadata_parse_strict_array(in, metadata);
+ }
+
+ return ret;
+}
+
+static int amf_metadata_parse(AVIOContext *in, AMFMetadata *metadata)
+{
+ char name[INT16_MAX];
+ int ret;
+
+ if((ret = amf_metadata_read_string_value(in, name, sizeof(name))) < 0) {
+ av_log(NULL, AV_LOG_ERROR, "amfmetadata Failed to read onMetadata string, ret: %d \n", ret);
+ return ret;
+ }
+
+ if(av_strcasecmp(name, "onMetaData")) {
+ av_log(NULL, AV_LOG_ERROR, "amfmetadata Expected onMetadata, str = %s \n", name);
+ return -1;
+ }
+
+ return amf_metadata_parse_value(in, metadata, name);
+}
+
+int ff_parse_amf_metadata(uint8_t *buffer, int buffer_size, AMFMetadata *metadata)
+{
+ AVIOContext *in;
+ int ret;
+
+ if(!buffer)
+ return 0;
+ if(buffer_size <= 0)
+ return 0;
+
+ in = avio_alloc_context(buffer, buffer_size,
+ 0, NULL, NULL, NULL, NULL);
+ if(!in)
+ return AVERROR(ENOMEM);
+
+ ret = amf_metadata_parse(in, metadata);
+ av_free(in);
+
+ return ret;
+}
diff --git libavformat/amfmetadata.h libavformat/amfmetadata.h
new file mode 100644
index 0000000..0b3e48d
--- /dev/null
+++ libavformat/amfmetadata.h
@@ -0,0 +1,45 @@
+/*
+ * Adobe Action Message Format Parser
+ * Copyright (c) 2013 Cory McCarthy
+ *
+ * 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
+ * @brief Adobe Action Message Format Parser
+ * @author Cory McCarthy
+ * @see http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf
+ * @see http://www.adobe.com/content/dam/Adobe/en/devnet/amf/pdf/amf-file-format-spec.pdf
+ */
+
+#include "libavcodec/avcodec.h"
+
+typedef struct AMFMetadata {
+ int width;
+ int height;
+ int frame_rate;
+ int audio_sample_rate;
+ int nb_audio_channels;
+ int audio_data_rate;
+ int video_data_rate;
+
+ enum AVCodecID audio_codec_id;
+ enum AVCodecID video_codec_id;
+} AMFMetadata;
+
+int ff_parse_amf_metadata(uint8_t *buffer, int buffer_size, AMFMetadata *metadata);
diff --git libavformat/f4fbox.c libavformat/f4fbox.c
new file mode 100644
index 0000000..ec269ef
--- /dev/null
+++ libavformat/f4fbox.c
@@ -0,0 +1,311 @@
+/*
+ * Adobe Fragmented F4V File (F4F) Parser
+ * Copyright (c) 2013 Cory McCarthy
+ *
+ * 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
+ * @brief Adobe Fragmented F4V File (F4F) Parser for Adobe HDS
+ * @author Cory McCarthy
+ * @see http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf
+ */
+
+#include "f4fbox.h"
+#include "avformat.h"
+
+static int f4fbox_parse_single_box(AVIOContext *in, void *opague);
+
+static int f4fbox_parse_asrt(AVIOContext *in, int64_t data_size, void *opague)
+{
+ F4FBootstrapInfoBox *parent = (F4FBootstrapInfoBox*)opague;
+ F4FSegmentRunTableBox *asrt;
+ F4FSegmentRunEntry *entry;
+ uint8_t quality_entry_count;
+ uint32_t segment_run_entry_count;
+ char url[1024];
+ int i;
+
+ asrt = av_mallocz(sizeof(F4FSegmentRunTableBox));
+ if(!asrt)
+ return AVERROR(ENOMEM);
+
+ parent->segment_run_table_boxes[parent->nb_segment_run_table_boxes] = asrt;
+ parent->nb_segment_run_table_boxes++;
+
+ asrt->version = avio_r8(in);
+ asrt->flags = avio_rb24(in);
+
+ quality_entry_count = avio_r8(in);
+ for(i = 0; i < quality_entry_count; i++) {
+ avio_get_str(in, sizeof(url), url, sizeof(url));
+ }
+
+ segment_run_entry_count = avio_rb32(in);
+ for(i = 0; i < segment_run_entry_count; i++) {
+ entry = av_mallocz(sizeof(F4FSegmentRunEntry));
+ if(!entry)
+ return AVERROR(ENOMEM);
+
+ asrt->segment_run_entries[asrt->nb_segment_run_entries] = entry;
+ asrt->nb_segment_run_entries++;
+
+ entry->first_segment = avio_rb32(in);
+
+ entry->fragments_per_segment = avio_rb32(in);
+ }
+
+ return 0;
+}
+
+static int f4fbox_parse_afrt(AVIOContext *in, int64_t data_size, void *opague)
+{
+ F4FBootstrapInfoBox *parent = (F4FBootstrapInfoBox*)opague;
+ F4FFragmentRunTableBox *afrt;
+ F4FFragmentRunEntry *entry;
+ uint8_t quality_entry_count;
+ uint32_t fragment_run_entry_count;
+ char url[1024];
+ int i;
+
+ afrt = av_mallocz(sizeof(F4FFragmentRunTableBox));
+ if(!afrt)
+ return AVERROR(ENOMEM);
+
+ parent->fragment_run_table_boxes[parent->nb_fragment_run_table_boxes] = afrt;
+ parent->nb_fragment_run_table_boxes++;
+
+ afrt->version = avio_r8(in);
+ afrt->flags = avio_rb24(in);
+
+ afrt->timescale = avio_rb32(in);
+
+ quality_entry_count = avio_r8(in);
+ for(i = 0; i < quality_entry_count; i++) {
+ avio_get_str(in, sizeof(url), url, sizeof(url));
+ }
+
+ fragment_run_entry_count = avio_rb32(in);
+ for(i = 0; i < fragment_run_entry_count; i++) {
+ entry = av_mallocz(sizeof(F4FFragmentRunEntry));
+ if(!entry)
+ return AVERROR(ENOMEM);
+
+ afrt->fragment_run_entries[afrt->nb_fragment_run_entries] = entry;
+ afrt->nb_fragment_run_entries++;
+
+ entry->first_fragment = avio_rb32(in);
+ entry->first_fragment_time_stamp = avio_rb64(in);
+ entry->fragment_duration = avio_rb32(in);
+ if(entry->fragment_duration == 0) {
+ entry->discontinuity_indicator = avio_r8(in);
+ }
+ }
+
+ return 0;
+}
+
+
+static int f4fbox_parse_abst(AVIOContext *in, int64_t data_size, void *opague)
+{
+ F4FBox *parent = (F4FBox*)opague;
+ F4FBootstrapInfoBox *abst = &(parent->abst);
+ uint8_t server_entry_count, quality_entry_count;
+ uint8_t segment_run_table_count, fragment_run_table_count;
+ uint8_t byte;
+ char url[1024];
+ int i, ret;
+
+ abst->version = avio_r8(in);
+ abst->flags = avio_rb24(in);
+ abst->bootstrap_info_version = avio_rb32(in);
+
+ byte = avio_r8(in);
+ abst->profile = (byte >> 6) & 0x03;
+ abst->is_live = (byte >> 5) & 0x01;
+ abst->is_update = (byte >> 4) & 0x01;
+
+ abst->timescale = avio_rb32(in);
+ abst->current_media_time = avio_rb64(in);
+ abst->smpte_time_code_offset = avio_rb64(in);
+
+ avio_get_str(in, sizeof(abst->movie_id), abst->movie_id, sizeof(abst->movie_id));
+
+ server_entry_count = avio_r8(in);
+ for(i = 0; i < server_entry_count; i++) {
+ avio_get_str(in, sizeof(url), url, sizeof(url));
+ }
+
+ quality_entry_count = avio_r8(in);
+ for(i = 0; i < quality_entry_count; i++) {
+ avio_get_str(in, sizeof(url), url, sizeof(url));
+ }
+
+ avio_get_str(in, sizeof(abst->drm_data), abst->drm_data, sizeof(abst->drm_data));
+ avio_get_str(in, sizeof(abst->metadata), abst->metadata, sizeof(abst->metadata));
+
+ segment_run_table_count = avio_r8(in);
+ for(i = 0; i < segment_run_table_count; i++) {
+ if((ret = f4fbox_parse_single_box(in, abst)) < 0) {
+ av_log(NULL, AV_LOG_ERROR, "f4fbox Failed to parse asrt box, ret: %d \n", ret);
+ return ret;
+ }
+ }
+
+ fragment_run_table_count = avio_r8(in);
+ for(i = 0; i < fragment_run_table_count; i++) {
+ if((ret = f4fbox_parse_single_box(in, abst)) < 0) {
+ av_log(NULL, AV_LOG_ERROR, "f4fbox Failed to parse afrt box, ret: %d \n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int f4fbox_parse_mdat(AVIOContext *in, int64_t data_size, void *opague)
+{
+ F4FBox *parent = (F4FBox*)opague;
+ F4FMediaDataBox *mdat = &(parent->mdat);
+
+ mdat->data = av_mallocz(sizeof(uint8_t)*data_size);
+ if(!mdat->data)
+ return AVERROR(ENOMEM);
+
+ mdat->size = data_size;
+ avio_read(in, mdat->data, mdat->size);
+
+ return 0;
+}
+
+static int f4fbox_parse_single_box(AVIOContext *in, void *opague)
+{
+ int64_t bytes_read, bytes_left, start_pos, end_pos;
+ uint64_t size;
+ uint32_t type;
+ int ret = 0;
+
+ bytes_read = 0;
+ start_pos = avio_tell(in);
+
+ size = avio_rb32(in);
+ type = avio_rl32(in);
+ bytes_read += 8;
+
+ if(size == 1) {/* 64 bit extended size */
+ size = avio_rb64(in) - 8;
+ bytes_read += 8;
+ }
+
+ if(size == 0)
+ return -1;
+
+ if(type == MKTAG('a', 'b', 's', 't')) {
+ ret = f4fbox_parse_abst(in, size, opague);
+ }
+ if(type == MKTAG('a', 's', 'r', 't')) {
+ ret = f4fbox_parse_asrt(in, size, opague);
+ }
+ if(type == MKTAG('a', 'f', 'r', 't')) {
+ ret = f4fbox_parse_afrt(in, size, opague);
+ }
+ if(type == MKTAG('m', 'd', 'a', 't')) {
+ ret = f4fbox_parse_mdat(in, size, opague);
+ }
+
+ if(ret < 0)
+ return ret;
+
+ end_pos = avio_tell(in);
+ bytes_left = size - (end_pos - start_pos);
+ if(bytes_left > 0)
+ avio_skip(in, bytes_left);
+
+ bytes_read += size;
+
+ return bytes_read;
+}
+
+static int f4fbox_parse(AVIOContext *in, int64_t data_size, void *opague)
+{
+ int64_t bytes_read = 0;
+ int ret;
+
+ while(!url_feof(in) && bytes_read + 8 < data_size) {
+ if((ret = f4fbox_parse_single_box(in, opague)) < 0) {
+ av_log(NULL, AV_LOG_ERROR, "f4fbox Failed to parse box, ret: %d \n", ret);
+ return ret;
+ }
+ bytes_read += ret;
+ }
+
+ return 0;
+}
+
+int ff_parse_f4f_box(uint8_t *buffer, int buffer_size, F4FBox *box)
+{
+ AVIOContext *in;
+ int ret;
+
+ in = avio_alloc_context(buffer, buffer_size, 0, NULL, NULL, NULL, NULL);
+ if(!in)
+ return AVERROR(ENOMEM);
+
+ ret = f4fbox_parse(in, buffer_size, box);
+ av_free(in);
+
+ return ret;
+}
+
+int ff_free_f4f_box(F4FBox *box)
+{
+ F4FBootstrapInfoBox *abst;
+ F4FSegmentRunTableBox *asrt;
+ F4FSegmentRunEntry *sre;
+ F4FFragmentRunTableBox *afrt;
+ F4FFragmentRunEntry *fre;
+ F4FMediaDataBox *mdat;
+ int i, j;
+
+ abst = &(box->abst);
+ for(i = 0; i < abst->nb_segment_run_table_boxes; i++) {
+ asrt = abst->segment_run_table_boxes[i];
+ for(j = 0; j < asrt->nb_segment_run_entries; j++) {
+ sre = asrt->segment_run_entries[j];
+ av_freep(&sre);
+ }
+ av_freep(&asrt);
+ }
+
+ for(i = 0; i < abst->nb_fragment_run_table_boxes; i++) {
+ afrt = abst->fragment_run_table_boxes[i];
+ for(j = 0; j < afrt->nb_fragment_run_entries; j++) {
+ fre = afrt->fragment_run_entries[j];
+ av_freep(&fre);
+ }
+ av_freep(&afrt);
+ }
+
+ mdat = &(box->mdat);
+ if(mdat->size > 0)
+ av_freep(&mdat->data);
+
+ memset(box, 0x00, sizeof(F4FBox));
+
+ return 0;
+}
diff --git libavformat/f4fbox.h libavformat/f4fbox.h
new file mode 100644
index 0000000..2f35911
--- /dev/null
+++ libavformat/f4fbox.h
@@ -0,0 +1,101 @@
+/*
+ * Adobe Fragmented F4V File (F4F) Parser
+ * Copyright (c) 2013 Cory McCarthy
+ *
+ * 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
+ * @brief Adobe Fragmented F4V File (F4F) Parser for Adobe HDS
+ * @author Cory McCarthy
+ * @see http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf
+ */
+
+#include "avio_internal.h"
+
+#define MAX_NB_SEGMENT_RUN_TABLE_BOXES 256
+#define MAX_NB_FRAGMENT_RUN_TABLE_BOXES 256
+
+#define MAX_NB_SEGMENT_RUN_ENTRIES 1024
+#define MAX_NB_FRAGMENT_RUN_ENTRIES 1024
+
+typedef struct F4FFragmentRunEntry {
+ uint32_t first_fragment;
+ uint64_t first_fragment_time_stamp;
+ uint32_t fragment_duration;
+ uint8_t discontinuity_indicator;
+} F4FFragmentRunEntry;
+
+typedef struct F4FFragmentRunTableBox {
+ uint8_t version;
+ uint32_t flags;
+ uint32_t timescale;
+
+ uint32_t nb_fragment_run_entries;
+ F4FFragmentRunEntry *fragment_run_entries[MAX_NB_FRAGMENT_RUN_ENTRIES];
+} F4FFragmentRunTableBox;
+
+typedef struct F4FSegmentRunEntry {
+ uint32_t first_segment;
+ uint32_t fragments_per_segment;
+} F4FSegmentRunEntry;
+
+typedef struct F4FSegmentRunTableBox {
+ uint8_t version;
+ uint32_t flags;
+
+ uint32_t nb_segment_run_entries;
+ F4FSegmentRunEntry *segment_run_entries[MAX_NB_SEGMENT_RUN_ENTRIES];
+} F4FSegmentRunTableBox;
+
+typedef struct F4FBootstrapInfoBox {
+ uint8_t version;
+ uint32_t flags;
+ uint32_t bootstrap_info_version;
+
+ uint8_t profile;
+ uint8_t is_live;
+ uint8_t is_update;
+
+ uint32_t timescale;
+ uint64_t current_media_time;
+ uint64_t smpte_time_code_offset;
+
+ char movie_id[1024];
+ char drm_data[1024];
+ char metadata[1024];
+
+ uint8_t nb_segment_run_table_boxes;
+ F4FSegmentRunTableBox *segment_run_table_boxes[MAX_NB_SEGMENT_RUN_TABLE_BOXES];
+
+ uint8_t nb_fragment_run_table_boxes;
+ F4FFragmentRunTableBox *fragment_run_table_boxes[MAX_NB_FRAGMENT_RUN_TABLE_BOXES];
+} F4FBootstrapInfoBox;
+
+typedef struct F4FMediaDataBox {
+ uint32_t size;
+ uint8_t *data;
+} F4FMediaDataBox;
+
+typedef struct F4FBox {
+ F4FBootstrapInfoBox abst;
+ F4FMediaDataBox mdat;
+} F4FBox;
+
+int ff_parse_f4f_box(uint8_t *buffer, int buffer_size, F4FBox *box);
+int ff_free_f4f_box(F4FBox *box);
diff --git libavformat/f4mmanifest.c libavformat/f4mmanifest.c
new file mode 100644
index 0000000..f28eb26
--- /dev/null
+++ libavformat/f4mmanifest.c
@@ -0,0 +1,330 @@
+/*
+ * Adobe Media Manifest (F4M) File Parser
+ * Copyright (c) 2013 Cory McCarthy
+ *
+ * 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
+ * @brief Adobe Media Manifest (F4M) File Parser
+ * @author Cory McCarthy
+ * @see http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/hds/pdfs/adobe-media-manifest-specification.pdf
+ */
+
+#include "f4mmanifest.h"
+#include "libavutil/avstring.h"
+#include "libavutil/base64.h"
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+
+
+static int f4m_get_content_padding(xmlChar *p){
+
+ int result = 0;
+ if(p){
+ int len = strlen(p);
+ int i;
+
+ for(i = 0; i < len; i++){
+ if(p[i] == 0x0a || p[i] == 0x09)
+ result++;
+ else
+ break;
+ }
+
+ }
+ return result;
+}
+
+static int f4m_get_content_length(xmlChar *p){
+
+ int result = 0;
+ if(p){
+
+ int len = strlen(p);
+ int i;
+
+ for(i = 0; i < len; i++)
+ if(p[i] != 0x0a && p[i] != 0x09)
+ result++;
+
+ result++;
+ }
+
+ return result <= MAX_URL_SIZE ? result : MAX_URL_SIZE;
+
+}
+
+static int f4m_parse_bootstrap_info_node(xmlNodePtr node, F4MBootstrapInfo *bootstrap_info)
+{
+ xmlChar *p;
+ uint8_t *dst;
+ int ret;
+ int p_len;
+ int padding;
+
+
+ p = xmlGetProp(node, "id");
+ if(p) {
+ av_strlcpy(bootstrap_info->id, p, sizeof(bootstrap_info->id));
+ xmlFree(p);
+ }
+
+ p = xmlGetProp(node, "url");
+ if(p) {
+ av_strlcpy(bootstrap_info->url, p, sizeof(bootstrap_info->url));
+ xmlFree(p);
+ }
+
+ p = xmlGetProp(node, "profile");
+ if(p) {
+ av_strlcpy(bootstrap_info->profile, p, sizeof(bootstrap_info->profile));
+ xmlFree(p);
+ }
+
+ p = xmlNodeGetContent(node);
+ if(p) {
+ p_len = f4m_get_content_length(p);
+ padding = f4m_get_content_padding(p);
+ if(p_len > 1){
+ dst = av_mallocz(sizeof(uint8_t)*p_len);
+ if(!dst)
+ return AVERROR(ENOMEM);
+
+ if((ret = av_base64_decode(dst, p + padding, p_len)) < 0) {
+ av_log(NULL, AV_LOG_ERROR, "f4mmanifest Failed to decode bootstrap node base64 metadata, ret: %d \n", ret);
+ xmlFree(p);
+ av_free(dst);
+ return ret;
+ }
+
+ bootstrap_info->metadata = av_mallocz(sizeof(uint8_t)*ret);
+ if(!bootstrap_info->metadata)
+ return AVERROR(ENOMEM);
+
+ bootstrap_info->metadata_size = ret;
+ memcpy(bootstrap_info->metadata, dst, ret);
+
+ av_free(dst);
+ }
+ xmlFree(p);
+
+ }
+
+ return 0;
+}
+
+static int f4m_parse_metadata_node(xmlNodePtr node, F4MMedia *media)
+{
+ xmlNodePtr metadata_node;
+ xmlChar *p;
+ uint8_t *dst;
+ int p_len;
+ int padding;
+ int ret;
+
+
+ p = NULL;
+ metadata_node = node->children;
+ while(metadata_node) {
+ if(!strcmp(metadata_node->name, "metadata")) {
+ p = xmlNodeGetContent(metadata_node);
+ break;
+ }
+
+ metadata_node = metadata_node->next;
+ }
+
+ if(!p)
+ return 0;
+
+
+ p_len = f4m_get_content_length(p);
+ padding = f4m_get_content_padding(p);
+
+ dst = av_mallocz(sizeof(uint8_t)*p_len);
+
+ if(!dst)
+ return AVERROR(ENOMEM);
+
+
+ if((ret = av_base64_decode(dst, p + padding, p_len)) < 0) {
+ av_log(NULL, AV_LOG_ERROR, "f4mmanifest Failed to decode base64 metadata, ret: %d \n", ret);
+ xmlFree(p);
+ av_free(dst);
+ return ret;
+ }
+
+ media->metadata = av_mallocz(sizeof(uint8_t)*ret);
+ if(!media->metadata)
+ return AVERROR(ENOMEM);
+
+ media->metadata_size = ret;
+ memcpy(media->metadata, dst, ret);
+
+ xmlFree(p);
+ av_free(dst);
+
+ return 0;
+}
+
+static int f4m_parse_media_node(xmlNodePtr node, F4MMedia *media, F4MManifest *manifest)
+{
+ xmlChar *p;
+ int ret;
+
+ p = xmlGetProp(node, "bitrate");
+ if(p) {
+ media->bitrate = strtoul(p, NULL, 10);
+ xmlFree(p);
+ }
+
+ p = xmlGetProp(node, "url");
+ if(p) {
+ av_strlcpy(media->url, p, sizeof(media->url));
+ xmlFree(p);
+ }
+
+
+ p = xmlGetProp(node, "bootstrapInfoId");
+ if(p) {
+ av_strlcpy(media->bootstrap_info_id, p, sizeof(media->bootstrap_info_id));
+ xmlFree(p);
+ }
+
+ if((ret = f4m_parse_metadata_node(node, media)) < 0) {
+ return ret;
+ }
+
+ return 0;
+}
+
+static int f4m_parse_manifest_node(xmlNodePtr root_node, F4MManifest *manifest)
+{
+ F4MBootstrapInfo *bootstrap_info;
+ F4MMedia *media;
+ xmlNodePtr node;
+ xmlChar *node_content;
+ int ret;
+ int length;
+ int padding;
+
+ for(node = root_node->children; node != root_node->last; node = node->next) {
+
+ if(!strcmp(node->name, "text"))
+ continue;
+
+ node_content = xmlNodeGetContent(node);
+
+ padding = f4m_get_content_padding(node_content);
+ length = f4m_get_content_length(node_content);
+
+ if(!strcmp(node->name, "id") && node_content) {
+ av_strlcpy(manifest->id, node_content + padding, length);
+ }
+
+ if(!strcmp(node->name, "streamType") && node_content) {
+ av_strlcpy(manifest->stream_type, node_content + padding, length);
+ }
+ else
+ if(!strcmp(node->name, "bootstrapInfo")) {
+ bootstrap_info = av_mallocz(sizeof(F4MBootstrapInfo));
+ if(!bootstrap_info)
+ return AVERROR(ENOMEM);
+
+ manifest->bootstraps[manifest->nb_bootstraps++] = bootstrap_info;
+ ret = f4m_parse_bootstrap_info_node(node, bootstrap_info);
+ }
+ else
+ if(!strcmp(node->name, "media")) {
+
+ media = av_mallocz(sizeof(F4MMedia));
+ if(!media)
+ return AVERROR(ENOMEM);
+
+ manifest->media[manifest->nb_media++] = media;
+ ret = f4m_parse_media_node(node, media, manifest);
+ }
+
+ if(node_content)
+ xmlFree(node_content);
+ if(ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int f4m_parse_xml_file(uint8_t *buffer, int size, F4MManifest *manifest)
+{
+ xmlDocPtr doc;
+ xmlNodePtr root_node;
+ int ret;
+
+
+ doc = xmlReadMemory(buffer, size, "noname.xml", NULL, 0);
+ if(!doc) {
+ return -1;
+ }
+
+ root_node = xmlDocGetRootElement(doc);
+ if(!root_node) {
+ av_log(NULL, AV_LOG_ERROR, "f4mmanifest Root element not found \n");
+ xmlFreeDoc(doc);
+ return -1;
+ }
+
+ if(strcmp(root_node->name, "manifest")) {
+ av_log(NULL, AV_LOG_ERROR, "f4mmanifest Root element is not named manifest, name = %s \n", root_node->name);
+ xmlFreeDoc(doc);
+ return -1;
+ }
+
+ ret = f4m_parse_manifest_node(root_node, manifest);
+ xmlFreeDoc(doc);
+
+ return ret;
+}
+
+int ff_parse_f4m_manifest(uint8_t *buffer, int size, F4MManifest *manifest)
+{
+ return f4m_parse_xml_file(buffer, size, manifest);
+}
+
+int ff_free_manifest(F4MManifest *manifest)
+{
+ F4MBootstrapInfo *bootstrap_info;
+ F4MMedia *media;
+ int i;
+
+ for(i = 0; i < manifest->nb_bootstraps; i++) {
+ bootstrap_info = manifest->bootstraps[i];
+ av_freep(&bootstrap_info->metadata);
+ av_freep(&bootstrap_info);
+ }
+
+ for(i = 0; i < manifest->nb_media; i++) {
+ media = manifest->media[i];
+ av_freep(&media->metadata);
+ av_freep(&media);
+ }
+
+ memset(manifest, 0x00, sizeof(F4MManifest));
+
+ return 0;
+}
diff --git libavformat/f4mmanifest.h libavformat/f4mmanifest.h
new file mode 100644
index 0000000..6912126
--- /dev/null
+++ libavformat/f4mmanifest.h
@@ -0,0 +1,65 @@
+/*
+ * Adobe Media Manifest (F4M) File Parser
+ * Copyright (c) 2013 Cory McCarthy
+ *
+ * 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
+ * @brief Adobe Media Manifest (F4M) File Parser
+ * @author Cory McCarthy
+ * @see http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/hds/pdfs/adobe-media-manifest-specification.pdf
+ */
+
+#include "internal.h"
+
+#define MAX_NB_BOOTSTRAPS 32
+#define MAX_NB_MEDIA 32
+
+
+typedef struct F4MBootstrapInfo {
+ char id[MAX_URL_SIZE];
+ char url[MAX_URL_SIZE];
+ char profile[MAX_URL_SIZE];
+
+ int metadata_size;
+ uint8_t *metadata;
+} F4MBootstrapInfo;
+
+typedef struct F4MMedia {
+ int bitrate;
+ char url[MAX_URL_SIZE];
+ char bootstrap_info_id[MAX_URL_SIZE];
+
+ int metadata_size;
+ uint8_t *metadata;
+} F4MMedia;
+
+
+typedef struct F4MManifest {
+ char id[MAX_URL_SIZE];
+ char stream_type[MAX_URL_SIZE];
+ int nb_bootstraps;
+ F4MBootstrapInfo *bootstraps[MAX_NB_BOOTSTRAPS];
+
+ int nb_media;
+ F4MMedia *media[MAX_NB_MEDIA];
+} F4MManifest;
+
+int ff_parse_f4m_manifest(uint8_t *buffer, int size, F4MManifest *manifest);
+int ff_free_manifest(F4MManifest *manifest);
diff --git libavformat/flvtag.c libavformat/flvtag.c
new file mode 100644
index 0000000..b62a0c6
--- /dev/null
+++ libavformat/flvtag.c
@@ -0,0 +1,392 @@
+/*
+ * Adobe FLV Tag Parser
+ * Copyright (c) 2013 Cory McCarthy
+ *
+ * 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
+ * @brief FLV Tag Parser for Adobe HDS F4F Files
+ * @author Cory McCarthy
+ * @see http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf
+ */
+
+#include "flvtag.h"
+#include "libavformat/avio.h"
+
+typedef struct FLVTagAudioHeader {
+ uint8_t sound_format;
+ uint8_t sound_rate;
+ uint8_t sound_size;
+ uint8_t sound_type;
+
+ uint8_t aac_packet_type;
+} FLVTagAudioHeader;
+
+typedef struct FLVTagAudioBody {
+ uint8_t sound_format;
+ uint8_t sound_rate;
+ uint8_t sound_size;
+ uint8_t sound_type;
+
+ uint8_t aac_packet_type;
+} FLVTagAudioBody;
+
+typedef struct FLVTagVideoHeader {
+ uint8_t frame_type;
+ uint8_t codec_id;
+
+ uint8_t avc_packet_type;
+ int32_t composition_time;
+} FLVTagVideoHeader;
+
+typedef struct FLVTagVideoBody {
+ //AVCDecoderConfigurationRecord
+ uint8_t configuration_version;
+ uint8_t avc_profile_indication;
+ uint8_t profile_compatibility;
+ uint8_t avc_level_indication;
+
+ uint8_t length_size_minus_one;
+
+ uint8_t *sps_data;
+ int sps_data_size;
+
+ uint8_t *pps_data;
+ int pps_data_size;
+} FLVTagVideoBody;
+
+static int flv_tag_parse_audio_header(AVIOContext *in,
+ FLVTagAudioHeader *header)
+{
+ int ret = 0;
+ uint8_t byte;
+
+ byte = avio_r8(in);
+ ret++;
+
+ header->sound_format = (byte >> 4) & 0x0F;
+ header->sound_rate = (byte >> 2) & 0x03;
+ header->sound_size = (byte >> 1) & 0x01;
+ header->sound_type = (byte >> 0) & 0x01;
+
+ if(header->sound_format == 10) {
+ header->aac_packet_type = avio_r8(in);
+ ret++;
+ }
+
+ return ret;
+}
+
+static int flv_tag_parse_audio_body(AVIOContext *in, uint32_t data_size,
+ FLVTagAudioHeader *header, FLVTagAudioBody *body, FLVMediaSample **sample_out)
+{
+ FLVMediaSample *sample;
+
+ if(header->sound_format != 10) {
+ av_log(NULL, AV_LOG_ERROR, "flvtag Unhandled sound format, fmt: %d \n", header->sound_format);
+ return 0;
+ }
+
+ if(header->aac_packet_type == 0)
+ return 0;//skip AudioSpecificConfig
+
+ if(header->aac_packet_type != 1) {
+ av_log(NULL, AV_LOG_ERROR, "flvtag Unhandled aac_packet_type, type: %d \n", header->aac_packet_type);
+ return 0;
+ }
+
+ sample = av_mallocz(sizeof(FLVMediaSample));
+ if(!sample)
+ return AVERROR(ENOMEM);
+
+ sample->type = AVMEDIA_TYPE_AUDIO;
+
+ sample->data = av_mallocz(sizeof(uint8_t) * data_size);
+ if(!sample->data)
+ return AVERROR(ENOMEM);
+
+ sample->data_size = data_size;
+ avio_read(in, sample->data, sample->data_size);
+
+ if(sample_out)
+ *sample_out = sample;
+
+ return data_size;
+}
+
+static int flv_tag_parse_video_header(AVIOContext *in,
+ FLVTagVideoHeader *header)
+{
+ int ret = 0;
+ uint8_t byte;
+
+ byte = avio_r8(in);
+ ret++;
+
+ header->frame_type = (byte >> 4) & 0x0F;
+ header->codec_id = byte & 0x0F;
+
+ if(header->codec_id == 0x07) {
+ header->avc_packet_type = avio_r8(in);
+ header->composition_time = avio_rb24(in);
+ ret += 4;
+ }
+
+ return ret;
+}
+
+static int flv_tag_parse_video_body(AVIOContext *in, uint32_t data_size,
+ FLVTagVideoHeader *header, FLVTagVideoBody *body, FLVMediaSample **sample_out)
+{
+ FLVMediaSample *sample = NULL;
+ uint8_t *p;
+ uint8_t nb_sps, nb_pps;
+ uint16_t sps_length, pps_length;
+ uint32_t nal_size;
+ int i, ret = 0;
+
+ if(header->frame_type == 0x05) {
+ avio_r8(in);
+ return 1;
+ }
+
+ if(header->codec_id != 0x07) {
+ av_log(NULL, AV_LOG_ERROR, "flvtag Unhandled video codec id, id: %d \n", header->codec_id);
+ return 0;
+ }
+
+ if(header->avc_packet_type == 0x00) {
+ body->configuration_version = avio_r8(in);
+ body->avc_profile_indication = avio_r8(in);
+ body->profile_compatibility = avio_r8(in);
+ body->avc_level_indication = avio_r8(in);
+ ret += 4;
+
+ body->length_size_minus_one = avio_r8(in) & 0x03;
+ ret++;
+
+ if(body->sps_data_size > 0)
+ av_freep(&body->sps_data);
+ if(body->pps_data_size > 0)
+ av_freep(&body->pps_data);
+
+ nb_sps = avio_r8(in) & 0x1F;
+ ret++;
+
+ for(i = 0; i < nb_sps; i++) {
+ sps_length = avio_rb16(in);
+ ret += 2;
+
+ body->sps_data = av_realloc(body->sps_data,
+ body->sps_data_size + sps_length + 4);
+ if(!body->sps_data)
+ return AVERROR(ENOMEM);
+
+ p = body->sps_data + body->sps_data_size;
+
+ *p++ = 0x00;
+ *p++ = 0x00;
+ *p++ = 0x00;
+ *p++ = 0x01;
+ body->sps_data_size += 4;
+
+ avio_read(in, p, sps_length);
+ body->sps_data_size += sps_length;
+
+ ret += sps_length;
+ }
+
+ nb_pps = avio_r8(in);
+ ret++;
+
+ for(i = 0; i < nb_pps; i++) {
+ pps_length = avio_rb16(in);
+ ret += 2;
+
+ body->pps_data = av_realloc(body->pps_data,
+ body->pps_data_size + pps_length + 4);
+ if(!body->pps_data)
+ return AVERROR(ENOMEM);
+
+ p = body->pps_data + body->pps_data_size;
+
+ *p++ = 0x00;
+ *p++ = 0x00;
+ *p++ = 0x00;
+ *p++ = 0x01;
+ body->pps_data_size += 4;
+
+ avio_read(in, p, pps_length);
+ body->pps_data_size += pps_length;
+
+ ret += pps_length;
+ }
+ }
+ else if(header->avc_packet_type == 0x01) {
+ sample = av_mallocz(sizeof(FLVMediaSample));
+ if(!sample)
+ return AVERROR(ENOMEM);
+
+ sample->type = AVMEDIA_TYPE_VIDEO;
+
+ sample->data_size = body->sps_data_size + body->pps_data_size;
+ sample->data_size += 4 + data_size;
+
+ sample->data = av_mallocz(sizeof(uint8_t) * sample->data_size);
+ if(!sample->data)
+ return AVERROR(ENOMEM);
+
+ p = sample->data;
+
+ memcpy(p, body->sps_data, body->sps_data_size);
+ p += body->sps_data_size;
+
+ memcpy(p, body->pps_data, body->pps_data_size);
+ p += body->pps_data_size;
+
+ while(ret < data_size) {
+ *p++ = 0x00;
+ *p++ = 0x00;
+ *p++ = 0x00;
+ *p++ = 0x01;
+
+ nal_size = avio_rb32(in);
+ ret += 4;
+
+ avio_read(in, p, nal_size);
+ p += nal_size;
+ ret += nal_size;
+ }
+ }
+
+ if(sample_out)
+ *sample_out = sample;
+
+ return ret;
+}
+
+static int flv_tag_decode_body(uint8_t *buffer, int buffer_size,
+ FLVMediaSample **samples, int *nb_samples_out)
+{
+ FLVMediaSample *sample;
+ AVIOContext *in;
+ FLVTagAudioHeader audio_header;
+ FLVTagAudioBody audio_body;
+ FLVTagVideoHeader video_header;
+ FLVTagVideoBody video_body;
+ uint8_t byte, filter, tag_type;
+ uint32_t data_size, timestamp, timestamp_extended, dts, stream_id;
+ int nb_samples = 0;
+ int ret;
+
+ memset(&audio_header, 0x00, sizeof(FLVTagAudioHeader));
+ memset(&audio_body, 0x00, sizeof(FLVTagAudioBody));
+ memset(&video_header, 0x00, sizeof(FLVTagVideoHeader));
+ memset(&video_body, 0x00, sizeof(FLVTagVideoBody));
+
+
+ in = avio_alloc_context(buffer, buffer_size, 0, NULL, NULL, NULL, NULL);
+ if(!in)
+ return AVERROR(ENOMEM);
+
+ while(!url_feof(in)) {
+ byte = avio_r8(in);
+ filter = (byte >> 5) & 0x01;
+ tag_type = (byte & 0x01F);
+
+ data_size = avio_rb24(in);
+
+ timestamp = avio_rb24(in);
+ timestamp_extended = avio_r8(in);
+ dts = ((timestamp_extended << 24) & 0xFF000000) | timestamp;
+
+ stream_id = avio_rb24(in);
+ if(stream_id != 0) {
+ av_log(NULL, AV_LOG_ERROR, "flvtag Invalid stream_id %d \n", stream_id);
+ return -1;
+ }
+
+ if(tag_type == 8) {
+ data_size -= flv_tag_parse_audio_header(in, &audio_header);
+ }
+ else
+ if(tag_type == 9) {
+ data_size -= flv_tag_parse_video_header(in, &video_header);
+ }
+
+ if(filter == 0x01) {
+ //EncryptionTagHeader
+ //FilterParams
+ }
+
+ sample = NULL;
+
+ if(tag_type == 8) {
+ if((ret = flv_tag_parse_audio_body(in, data_size,
+ &audio_header, &audio_body, &sample)) < 0) {
+
+ av_free(in);
+ return ret;
+ }
+ data_size -= ret;
+ }
+ else
+ if(tag_type == 9) {
+ if((ret = flv_tag_parse_video_body(in, data_size,
+ &video_header, &video_body, &sample)) < 0) {
+
+ av_free(in);
+ return ret;
+ }
+ data_size -= ret;
+ }
+ else
+ if(tag_type == 18) {
+ //ScriptData
+ }
+
+ if(sample) {
+ sample->timestamp = dts;
+ samples[nb_samples++] = sample;
+ }
+
+ if(data_size != 0) {
+ avio_skip(in, data_size);
+ }
+ avio_rb32(in);
+ }
+
+ av_free(in);
+
+ if(video_body.sps_data_size > 0)
+ av_free(video_body.sps_data);
+ if(video_body.pps_data_size > 0)
+ av_free(video_body.pps_data);
+
+ if(nb_samples_out)
+ *nb_samples_out = nb_samples;
+
+ return 0;
+}
+
+int ff_decode_flv_body(uint8_t *buffer, int buffer_size,
+ FLVMediaSample **samples, int *nb_samples_out)
+{
+ return flv_tag_decode_body(buffer, buffer_size, samples, nb_samples_out);
+}
diff --git libavformat/flvtag.h libavformat/flvtag.h
new file mode 100644
index 0000000..bc44be2
--- /dev/null
+++ libavformat/flvtag.h
@@ -0,0 +1,39 @@
+/*
+ * Adobe FLV Tag Parser
+ * Copyright (c) 2013 Cory McCarthy
+ *
+ * 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
+ * @brief Adobe FLV Tag Parser for Adobe HDS F4F Files
+ * @author Cory McCarthy
+ * @see http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf
+ */
+
+#include "libavutil/avutil.h"
+
+typedef struct FLVMediaSample {
+ enum AVMediaType type;
+ uint32_t timestamp;
+ int data_size;
+ uint8_t *data;
+} FLVMediaSample;
+
+int ff_decode_flv_body(uint8_t *buffer, int buffer_size,
+ FLVMediaSample **samples, int *nb_samples_out);
diff --git libavformat/hdsdec.c libavformat/hdsdec.c
new file mode 100644
index 0000000..bcfbb15
--- /dev/null
+++ libavformat/hdsdec.c
@@ -0,0 +1,812 @@
+/*
+ * Adobe HTTP Dynamic Streaming (HDS) demuxer
+ * Copyright (c) 2013 Cory McCarthy
+ *
+ * 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
+ * @brief Adobe HTTP Dynamic Streaming (HDS) demuxer
+ * @author Cory McCarthy
+ * @see http://www.adobe.com/devnet/hds.html
+ * @see http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/hds/pdfs/adobe-hds-specification.pdf
+ * @see http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/hds/pdfs/adobe-media-manifest-specification.pdf
+ * @see http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf
+ *
+ * @note Link for a HDS test player below:
+ * @see http://mediapm.edgesuite.net/edgeflash/public/zeri/debug/Main.html
+ *
+ * @note Test streams are below:
+ * @test http://multiplatform-f.akamaihd.net/z/multi/april11/hdworld/hdworld_,512x288_450_b,640x360_700_b,768x432_1000_b,1024x576_1400_m,1280x720_1900_m,1280x720_2500_m,1280x720_3500_m,.mp4.csmil/manifest.f4m?hdcore
+ * @test http://multiplatform-f.akamaihd.net/z/multi/april11/cctv/cctv_,512x288_450_b,640x360_700_b,768x432_1000_b,1024x576_1400_m,1280x720_1900_m,1280x720_2500_m,1280x720_3500_m,.mp4.csmil/manifest.f4m?hdcore
+ * @test http://multiplatform-f.akamaihd.net/z/multi/april11/sintel/sintel-hd_,512x288_450_b,640x360_700_b,768x432_1000_b,1024x576_1400_m,1280x720_1900_m,1280x720_2500_m,1280x720_3500_m,.mp4.csmil/manifest.f4m?hdcore
+ * @test http://multiplatform-f.akamaihd.net/z/multi/akamai10year/Akamai_10_Year_,200,300,600,800,1000,1500,2500,4000,k.mp4.csmil/manifest.f4m?hdcore
+ * @test http://zerihdndemo-f.akamaihd.net/z/h264/seeker/LegendofSeeker_16x9_24fps_H264_,400K,650K,1Mbps,1.4Mbps,1.8Mbps,2.5Mbps,.mp4.csmil/manifest.f4m?hdcore
+ * @test http://multiplatform-f.akamaihd.net/z/multi/will/bunny/big_buck_bunny_,640x360_400,640x360_700,640x360_1000,950x540_1500,1280x720_2000,1280x720_3000,.f4v.csmil/manifest.f4m?hdcore
+ * @test http://multiplatform-f.akamaihd.net/z/multi/companion/nba_game/nba_game.mov_,300,600,800,1000,2500,4000,9000,k.mp4.csmil/manifest.f4m?hdcore
+ * @test http://multiplatform-f.akamaihd.net/z/multi/companion/big_bang_theory/big_bang_theory.mov_,300,600,800,1000,2500,4000,9000,k.mp4.csmil/manifest.f4m?hdcore
+ * @test http://multiplatform-f.akamaihd.net/z/multi/shuttle/shuttle_,300,600,800,1000,k.mp4.csmil/manifest.f4m?hdcore
+ * @test http://multiplatform-f.akamaihd.net/z/multi/up_trailer/up_trailer_720p_,300,600,800,1000,k.mp4.csmil/manifest.f4m?hdcore
+ * @test http://multiformatlive-f.akamaihd.net/z/demostream_1@2131/manifest.f4m?hdcore
+ * @test http://zerihdndemo-f.akamaihd.net/z/h264/darkknight/darkknight.smil/manifest.f4m?hdcore
+ * @test http://zerihdndemo-f.akamaihd.net/z/h264/amours/amours.smil/manifest.f4m?hdcore
+ * @test http://zerihdndemo-f.akamaihd.net/z/h264/robinhood/robinhood.smil/manifest.f4m?hdcore
+ * @test http://zerihdndemo-f.akamaihd.net/z/h264/wallstreet/wallstreet.smil/manifest.f4m?hdcore
+ * @test http://zerihdndemo-f.akamaihd.net/z/h264/rockandroll/rockandroll.smil/manifest.f4m?hdcore
+ * @test http://184.72.239.149/vod/smil:bigbuckbunny.smil/manifest.f4m
+ *
+ * @test http://livehds.rasset.ie/hds-live/_definst_/newsnow/newsnow_540p.f4m
+ * @test http://livehds.rasset.ie/hds-live/_definst_/rte1/rte1_288p.f4m
+ * @test http://livehds.rasset.ie/hds-live/_definst_/rte2/rte2_288p.f4m
+ * @test http://ooyalahd2-f.akamaihd.net/z/godtv02_delivery@17351/manifest.f4m?hdcore=2.10.3&g=ILYQWQWFPMLW
+ */
+
+#include "avformat.h"
+#include "internal.h"
+#include "url.h"
+#include "avio_internal.h"
+#include "libavutil/avstring.h"
+#include "libavutil/parseutils.h"
+#include "libavutil/time.h"
+
+#include "amfmetadata.h"
+#include "f4mmanifest.h"
+#include "f4fbox.h"
+#include "flvtag.h"
+
+#define MAX_NB_SAMPLES 1024
+
+typedef struct HDSBootstrapInfo {
+ char id[MAX_URL_SIZE];
+ char url[MAX_URL_SIZE];
+ char profile[MAX_URL_SIZE];
+
+ F4FBox box;
+} HDSBootstrapInfo;
+
+typedef struct HDSMedia {
+ int bitrate;
+ char url[MAX_URL_SIZE];
+ char bootstrap_info_id[MAX_URL_SIZE];
+
+ AVStream *audio_stream;
+ AVStream *video_stream;
+
+ int nb_total;
+ int nb_offset;
+ int nb_samples;
+ FLVMediaSample *samples[MAX_NB_SAMPLES];
+ int sample_index;
+
+ int nb_fragments_read;
+} HDSMedia;
+
+typedef struct HDSContext {
+ char id[MAX_URL_SIZE];
+ int is_live;
+ char base_url[MAX_URL_SIZE];
+
+ int nb_bootstraps;
+ HDSBootstrapInfo *bootstrap_info[MAX_NB_BOOTSTRAPS];
+
+ int nb_media;
+ HDSMedia *media[MAX_NB_MEDIA];
+} HDSContext;
+
+static void construct_bootstrap_url(const char *base_url, const char *bootstrap_url,
+ const char *suffix, char *url_out, size_t url_size)
+{
+ char *p;
+
+ p = url_out;
+ p += av_strlcat(p, base_url, url_size);
+ p += av_strlcat(p, bootstrap_url, url_size);
+ p += av_strlcat(p, suffix, url_size);
+}
+
+static int download_bootstrap(AVFormatContext *s, HDSBootstrapInfo *bootstrap,
+ uint8_t **buffer_out, int *buffer_size_out)
+{
+ HDSContext *c = s->priv_data;
+ URLContext *puc;
+ char url[MAX_URL_SIZE];
+ uint8_t *buffer;
+ int buffer_size;
+ int ret;
+
+ memset(url, 0x00, sizeof(url));
+
+ if(!av_stristr(bootstrap->url, "?") && av_stristr(s->filename, "?")) {
+ construct_bootstrap_url(c->base_url, bootstrap->url, av_stristr(s->filename, "?"), url, MAX_URL_SIZE);
+ } else {
+ construct_bootstrap_url(c->base_url, bootstrap->url, "", url, MAX_URL_SIZE);
+ }
+
+ if((ret = ffurl_open(&puc, url, AVIO_FLAG_READ, &s->interrupt_callback, NULL)) < 0) {
+ av_log(NULL, AV_LOG_ERROR, "hds Failed to start downloading bootstrap, ret: %d \n", ret);
+ return ret;
+ }
+
+ buffer_size = ffurl_size(puc);
+ buffer = av_mallocz(buffer_size+FF_INPUT_BUFFER_PADDING_SIZE);
+ if(!buffer)
+ return AVERROR(ENOMEM);
+
+ if((ret = ffurl_read_complete(puc, buffer, buffer_size)) < 0) {
+ av_log(NULL, AV_LOG_ERROR, "hds Failed to downloaded bootstrap, ret: %d \n", ret);
+ av_free(buffer);
+ return ret;
+ }
+
+ if((ret = ffurl_close(puc)) < 0) {
+ av_log(NULL, AV_LOG_ERROR, "hds Failed to finish downloading bootstrap, ret: %d \n", ret);
+ av_free(buffer);
+ return ret;
+ }
+
+ if(buffer_out)
+ *buffer_out = buffer;
+ if(buffer_size_out)
+ *buffer_size_out = buffer_size;
+
+ return 0;
+}
+
+static int create_bootstrap_info(AVFormatContext *s, F4MBootstrapInfo *f4m_bootstrap_info)
+{
+ HDSContext *c = s->priv_data;
+ HDSBootstrapInfo *bootstrap_info;
+ uint8_t *buffer;
+ int buffer_size, ret;
+
+ bootstrap_info = av_mallocz(sizeof(HDSBootstrapInfo));
+ if(!bootstrap_info)
+ return AVERROR(ENOMEM);
+
+ c->bootstrap_info[c->nb_bootstraps++] = bootstrap_info;
+
+ memcpy(bootstrap_info->id, f4m_bootstrap_info->id, sizeof(bootstrap_info->id));
+ memcpy(bootstrap_info->url, f4m_bootstrap_info->url, sizeof(bootstrap_info->url));
+ memcpy(bootstrap_info->profile, f4m_bootstrap_info->profile, sizeof(bootstrap_info->profile));
+
+ if(f4m_bootstrap_info->metadata_size > 0) {
+ buffer = f4m_bootstrap_info->metadata;
+ buffer_size = f4m_bootstrap_info->metadata_size;
+
+ if((ret = ff_parse_f4f_box(buffer, buffer_size, &(bootstrap_info->box))) < 0) {
+ av_log(NULL, AV_LOG_ERROR, "hds Failed to parse metadata bootstrap box, ret: %d \n", ret);
+ return ret;
+ }
+ }
+ else {
+ if((ret = download_bootstrap(s, bootstrap_info, &buffer, &buffer_size)) < 0) {
+ av_log(NULL, AV_LOG_ERROR, "hds Failed to download bootstrap, ret: %d \n", ret);
+ return ret;
+ }
+
+ if((ret = ff_parse_f4f_box(buffer, buffer_size, &(bootstrap_info->box))) < 0) {
+ av_log(NULL, AV_LOG_ERROR, "hds Failed to parse downloaded bootstrap box, ret: %d \n", ret);
+ av_free(buffer);
+ return ret;
+ }
+
+ av_free(buffer);
+ }
+
+ return 0;
+}
+
+static int create_streams(AVFormatContext *s, HDSMedia *media, AMFMetadata *metadata)
+{
+ AVStream *st;
+
+ st = avformat_new_stream(s, NULL);
+ if(!st)
+ return AVERROR(ENOMEM);
+
+ media->video_stream = st;
+
+ st->id = 0;
+ avpriv_set_pts_info(st, 32, 1, 1000);
+
+ st->codec->codec_type = AVMEDIA_TYPE_VIDEO;
+ st->codec->codec_id = metadata->video_codec_id;
+ st->codec->width = metadata->width;
+ st->codec->height = metadata->height;
+ st->codec->bit_rate = metadata->video_data_rate * 1000;
+
+ st = avformat_new_stream(s, NULL);
+ if(!st)
+ return AVERROR(ENOMEM);
+
+ media->audio_stream = st;
+
+ st->id = 0;
+ avpriv_set_pts_info(st, 32, 1, 1000);
+
+ st->codec->codec_type = AVMEDIA_TYPE_AUDIO;
+ st->codec->codec_id = metadata->audio_codec_id;
+ st->codec->channels = metadata->nb_audio_channels;
+ st->codec->sample_rate = metadata->audio_sample_rate;
+ st->codec->sample_fmt = AV_SAMPLE_FMT_S16;
+ st->codec->bit_rate = metadata->audio_data_rate * 1000;
+
+ return 0;
+}
+
+static int create_media(AVFormatContext *s, F4MMedia *f4m_media)
+{
+ HDSContext *c = s->priv_data;
+ HDSMedia *media;
+ AMFMetadata metadata;
+ int ret;
+
+ media = av_mallocz(sizeof(HDSMedia));
+ if(!media)
+ return AVERROR(ENOMEM);
+
+ c->media[c->nb_media++] = media;
+
+ media->bitrate = f4m_media->bitrate;
+ memcpy(media->url, f4m_media->url, sizeof(media->url));
+ memcpy(media->bootstrap_info_id, f4m_media->bootstrap_info_id, sizeof(media->bootstrap_info_id));
+
+ memset(&metadata, 0x00, sizeof(AMFMetadata));
+ if((ret = ff_parse_amf_metadata(f4m_media->metadata, f4m_media->metadata_size, &metadata)) < 0) {
+ av_log(NULL, AV_LOG_ERROR, "hds Failed to parse metadata, ret: %d \n", ret);
+ return ret;
+ }
+
+ if((ret = create_streams(s, media, &metadata)) < 0)
+ return ret;
+
+ return 0;
+}
+
+static int create_pmt(AVFormatContext *s)
+{
+ HDSContext *c = s->priv_data;
+ HDSMedia *media;
+ AVProgram *p;
+ int i, j;
+
+ j = 0;
+ for(i = 0; i < c->nb_media; i++) {
+ media = c->media[i];
+
+ p = av_new_program(s, j++);
+ if(!p)
+ return AVERROR(ENOMEM);
+
+ av_dict_set(&p->metadata,"name",
+ av_asprintf("Bandwidth: %dKbps", media->bitrate), 0);
+
+ ff_program_add_stream_index(s, p->id, media->video_stream->index);
+ ff_program_add_stream_index(s, p->id, media->audio_stream->index);
+ }
+
+ return 0;
+}
+
+static int initialize_context(AVFormatContext *s, F4MManifest *manifest)
+{
+ HDSContext *c = s->priv_data;
+ F4MBootstrapInfo *f4m_bootstrap_info;
+ F4MMedia *f4m_media;
+ int i, ret;
+
+ for(i = 0; i < manifest->nb_bootstraps; i++) {
+ f4m_bootstrap_info = manifest->bootstraps[i];
+ if((ret = create_bootstrap_info(s, f4m_bootstrap_info)) < 0) {
+ av_log(NULL, AV_LOG_ERROR, "hds Failed to create bootstrap_info, ret: %d \n", ret);
+ return ret;
+ }
+ }
+
+ for(i = 0; i < manifest->nb_media; i++) {
+ f4m_media = manifest->media[i];
+ if((ret = create_media(s, f4m_media)) < 0) {
+ av_log(NULL, AV_LOG_ERROR, "hds Failed to create media, ret: %d \n", ret);
+ return ret;
+ }
+ }
+
+ if((ret = create_pmt(s)) < 0) {
+ av_log(NULL, AV_LOG_ERROR, "hds Failed to create PMT, ret: %d \n", ret);
+ return ret;
+ }
+
+ if(!av_strcasecmp(manifest->stream_type, "live"))
+ c->is_live = 1;
+
+ return 0;
+}
+
+static int hds_read_header(AVFormatContext *s)
+{
+ HDSContext *c = s->priv_data;
+ AVIOContext *in = s->pb;
+ F4MManifest manifest;
+ int64_t filesize;
+ uint8_t *buf;
+ char *p, *pch;
+ int ret;
+
+ p = av_stristr(s->filename, ".f4m");
+
+ pch = strrchr(s->filename, '/');
+
+ if(!p || !pch) {
+ av_log(NULL, AV_LOG_ERROR, "hds Failed to build base url, url: %s \n", s->filename);
+ return -1;
+ }
+
+ av_strlcpy(c->base_url, s->filename, pch - s->filename + 2);
+
+ av_log(NULL, AV_LOG_DEBUG, "hds build base url: %s \n", c->base_url);
+
+ filesize = avio_size(in);
+
+ if(filesize <= 0){
+ filesize = 8 * 1024;
+ }
+
+ buf = av_mallocz(filesize*sizeof(uint8_t));
+ if(!buf)
+ return AVERROR(ENOMEM);
+
+ avio_read(in, buf, filesize);
+
+ memset(&manifest, 0x00, sizeof(F4MManifest));
+ if((ret = ff_parse_f4m_manifest(buf, filesize, &manifest)) < 0) {
+ av_free(buf);
+ ff_free_manifest(&manifest);
+ return ret;
+ }
+
+ av_free(buf);
+
+ ret = initialize_context(s, &manifest);
+ ff_free_manifest(&manifest);
+
+ return ret;
+}
+
+static void construct_fragment_url(const char *base_url, const char *media_url,
+ int segment, int fragment, const char *suffix, char *url_out, size_t url_size)
+{
+ char *p;
+ char *fragment_str;
+
+ p = url_out;
+ p += av_strlcat(p, base_url, url_size);
+ p += av_strlcat(p, media_url, url_size);
+
+ fragment_str = av_asprintf("Seg%d-Frag%d", segment, fragment);
+ p += av_strlcat(p, fragment_str, url_size);
+ av_free(fragment_str);
+
+ p += av_strlcat(p, suffix, url_size);
+}
+
+static int calculate_fragment_offset(HDSBootstrapInfo *bootstrap_info){
+
+ F4FBootstrapInfoBox *abst = &(bootstrap_info->box.abst);
+ F4FSegmentRunTableBox *asrt;
+ F4FSegmentRunEntry *segment_entry;
+ int offset = 0;
+ int i;
+
+ asrt = abst->segment_run_table_boxes[abst->nb_segment_run_table_boxes -1];
+
+ if( asrt && asrt->nb_segment_run_entries > 1){
+
+ for(i = 0; i < asrt->nb_segment_run_entries; i++) {
+
+ segment_entry = asrt->segment_run_entries[i];
+
+ offset += segment_entry->fragments_per_segment;
+
+ }
+ }
+
+ return offset;
+
+}
+
+static int calculate_fragment_total(HDSBootstrapInfo *bootstrap_info){
+
+ F4FBootstrapInfoBox *abst = &(bootstrap_info->box.abst);
+ F4FSegmentRunTableBox *asrt;
+ F4FSegmentRunEntry *segment_entry;
+ int total = 0;
+
+ asrt = abst->segment_run_table_boxes[abst->nb_segment_run_table_boxes -1];
+
+ if(asrt){
+
+ segment_entry = asrt->segment_run_entries[0];
+
+ total = (asrt->nb_segment_run_entries) * segment_entry->fragments_per_segment;
+
+ }
+
+ return total;
+
+}
+
+
+static int calculate_next_segment(HDSBootstrapInfo *bootstrap_info, int *fragments_per_segment){
+
+ F4FBootstrapInfoBox *abst = &(bootstrap_info->box.abst);
+ F4FSegmentRunTableBox *asrt;
+ F4FSegmentRunEntry *segment_entry;
+ int segment;
+ int i, j;
+
+
+ for(i = 0; i < abst->nb_segment_run_table_boxes; i++) {
+ asrt = abst->segment_run_table_boxes[i];
+ for(j = 0; j < asrt->nb_segment_run_entries; j++) {
+ segment_entry = asrt->segment_run_entries[j];
+
+ segment = segment_entry->first_segment;
+
+ *fragments_per_segment = segment_entry->fragments_per_segment;
+
+ }
+ }
+
+ return segment;
+
+
+}
+
+
+static int calculate_next_fragment(HDSBootstrapInfo *bootstrap_info, int is_live){
+
+ F4FBootstrapInfoBox *abst = &(bootstrap_info->box.abst);
+ F4FFragmentRunTableBox *afrt;
+ F4FFragmentRunEntry *fragment_entry;
+
+ int fragment;
+ int i, j;
+
+ for(i = 0; i < abst->nb_fragment_run_table_boxes; i++) {
+ afrt = abst->fragment_run_table_boxes[i];
+ for(j = 0; j < afrt->nb_fragment_run_entries; j++) {
+
+ fragment_entry = afrt->fragment_run_entries[j];
+
+ if(fragment_entry->first_fragment > 0)
+ fragment = fragment_entry->first_fragment;
+
+ if(!is_live)
+ break;
+ }
+ }
+
+ return fragment;
+
+}
+
+static int get_segment_fragment(AVFormatContext *s, HDSBootstrapInfo *bootstrap_info,
+ HDSMedia *media, int is_live, int *segment_out, int *fragment_out)
+{
+ uint8_t *buffer;
+
+ int segment, fragment;
+ int first_fragment;
+
+ int fragments_per_segment;
+ int buffer_size;
+ int ret = 0;
+
+
+ segment = calculate_next_segment(bootstrap_info, &fragments_per_segment);
+
+ first_fragment = calculate_next_fragment(bootstrap_info, is_live);
+
+ fragment = first_fragment + media->nb_fragments_read;
+
+ if(is_live){
+
+ if(!media->nb_offset)
+ media->nb_offset = calculate_fragment_offset(bootstrap_info);
+
+ if(!media->nb_total)
+ media->nb_total = calculate_fragment_total(bootstrap_info);
+
+ fragment += media->nb_offset - 1;
+
+
+ if(fragment >= (first_fragment + media->nb_total)){
+
+ if((ret = download_bootstrap(s, bootstrap_info, &buffer, &buffer_size)) < 0){
+ av_log(NULL, AV_LOG_ERROR, "hds Failed to download bootstrap, ret: %d \n", ret);
+ return ret;
+ }
+
+ if((ret = ff_parse_f4f_box(buffer, buffer_size, &(bootstrap_info->box))) < 0) {
+ av_log(NULL, AV_LOG_ERROR, "hds Failed to parse downloaded bootstrap box, ret: %d \n", ret);
+ av_free(buffer);
+ return ret;
+ }
+
+ segment = calculate_next_segment(bootstrap_info, &fragments_per_segment);
+
+ media->nb_fragments_read = 0;
+
+ media->nb_offset = 0;
+
+ media->nb_total = 0;
+
+ av_free(buffer);
+ }
+ }
+
+
+ if(!is_live && fragment >= (first_fragment + fragments_per_segment)) {
+ return AVERROR_EOF;
+ }
+
+
+ if(segment_out)
+ *segment_out = segment;
+ if(fragment_out)
+ *fragment_out = fragment;
+
+ return ret;
+}
+
+static int download_fragment(AVFormatContext *s,
+ HDSBootstrapInfo *bootstrap_info, HDSMedia *media,
+ uint8_t **buffer_out, int *buffer_size_out)
+{
+ HDSContext *c = s->priv_data;
+ URLContext *puc;
+ char url[MAX_URL_SIZE];
+ uint8_t *buffer;
+ int buffer_size;
+ int segment, fragment;
+ int ret;
+
+ if((ret = get_segment_fragment(s, bootstrap_info, media, c->is_live, &segment, &fragment)) < 0) {
+ return ret;
+ }
+
+
+ memset(url, 0x00, sizeof(url));
+
+ if(!av_stristr(media->url, "?") && av_stristr(s->filename, "?")) {
+ construct_fragment_url(c->base_url, media->url,
+ segment, fragment, av_stristr(s->filename, "?"), url, MAX_URL_SIZE);
+ } else {
+ construct_fragment_url(c->base_url, media->url,
+ segment, fragment, "", url, MAX_URL_SIZE);
+ }
+
+ if((ret = ffurl_open(&puc, url, AVIO_FLAG_READ, &s->interrupt_callback, NULL)) < 0) {
+ if(ret != AVERROR(EIO))
+ av_log(NULL, AV_LOG_ERROR, "hds Failed to start downloading fragment, url:%s, ret:%d \n", url, ret);
+ return ret;
+ }
+
+ buffer_size = ffurl_size(puc);
+ buffer = av_mallocz(buffer_size+FF_INPUT_BUFFER_PADDING_SIZE);
+ if(!buffer)
+ return AVERROR(ENOMEM);
+
+ if((ret = ffurl_read_complete(puc, buffer, buffer_size)) < 0) {
+ av_log(NULL, AV_LOG_ERROR, "hds Failed to downloaded fragment, ret: %d \n", ret);
+ av_free(buffer);
+ return ret;
+ }
+
+ if((ret = ffurl_close(puc)) < 0) {
+ av_log(NULL, AV_LOG_ERROR, "hds Failed to finish downloading fragment, ret: %d \n", ret);
+ av_free(buffer);
+ return ret;
+ }
+
+ media->nb_fragments_read++;
+
+ if(buffer_out)
+ *buffer_out = buffer;
+ if(buffer_size_out)
+ *buffer_size_out = buffer_size;
+
+ return 0;
+}
+
+static int get_next_fragment(AVFormatContext *s,
+ HDSBootstrapInfo *bootstrap_info, HDSMedia *media)
+{
+ F4FBox box;
+ uint8_t *buffer;
+ int buffer_size, ret;
+
+ if((ret = download_fragment(s, bootstrap_info, media, &buffer, &buffer_size)) < 0) {
+ return ret;
+ }
+
+ memset(&box, 0x00, sizeof(F4FBox));
+ if((ret = ff_parse_f4f_box(buffer, buffer_size, &box)) < 0) {
+ av_log(NULL, AV_LOG_ERROR, "hds Failed to parse bootstrap box, ret: %d \n", ret);
+ av_free(buffer);
+ ff_free_f4f_box(&box);
+ return ret;
+ }
+ av_free(buffer);
+
+ if((ret = ff_decode_flv_body(box.mdat.data, box.mdat.size, media->samples, &media->nb_samples)) < 0) {
+ av_log(NULL, AV_LOG_ERROR, "hds Failed to decode FLV body, ret: %d \n", ret);
+ ff_free_f4f_box(&box);
+ return ret;
+ }
+
+ ff_free_f4f_box(&box);
+
+ return 0;
+}
+
+static void read_next_sample(HDSMedia *media, AVPacket *pkt)
+{
+ FLVMediaSample *sample;
+
+ sample = media->samples[media->sample_index];
+ media->sample_index++;
+
+ av_new_packet(pkt, sample->data_size);
+ memcpy(pkt->data, sample->data, sample->data_size);
+
+ pkt->dts = sample->timestamp;
+ if(sample->type == AVMEDIA_TYPE_VIDEO && media->video_stream) {
+ pkt->stream_index = media->video_stream->index;
+ }
+ else
+ if(sample->type == AVMEDIA_TYPE_AUDIO && media->audio_stream) {
+ pkt->stream_index = media->audio_stream->index;
+ }
+}
+
+static void clear_samples(HDSMedia *media)
+{
+ FLVMediaSample *sample;
+ int i;
+
+ for(i = 0; i < media->nb_samples; i++) {
+ sample = media->samples[i];
+ av_freep(&sample->data);
+ av_freep(&sample);
+ media->samples[i] = NULL;
+ }
+
+ media->nb_samples = 0;
+ media->sample_index = 0;
+}
+
+static int get_next_packet(AVFormatContext *s,
+ HDSBootstrapInfo *bootstrap_info, HDSMedia *media, AVPacket *pkt)
+{
+ int ret;
+
+ if(media->nb_samples == 0) {
+ if((ret = get_next_fragment(s, bootstrap_info, media)) < 0) {
+ return ret;
+ }
+ }
+
+ if(media->nb_samples > 0) {
+ read_next_sample(media, pkt);
+ }
+
+ if(media->sample_index >= media->nb_samples) {
+ clear_samples(media);
+ }
+
+ return 0;
+}
+
+static int hds_read_packet(AVFormatContext *s, AVPacket *pkt)
+{
+ HDSContext *c = s->priv_data;
+ HDSBootstrapInfo *bootstrap_info = NULL;
+ HDSMedia *media = NULL;
+ int i, j, ret;
+
+ for(i = 0; i < c->nb_media; i++) {
+ media = c->media[i];
+ bootstrap_info = NULL;
+
+ if(media->video_stream->discard == AVDISCARD_ALL
+ && media->audio_stream->discard == AVDISCARD_ALL)
+ continue;
+
+ for(j = 0; j < c->nb_bootstraps; j++) {
+ if(av_strcasecmp(media->bootstrap_info_id, c->bootstrap_info[j]->id))
+ continue;
+
+ bootstrap_info = c->bootstrap_info[j];
+ break;
+ }
+
+ if(!bootstrap_info)
+ continue;
+
+ break;
+ }
+
+ if(!bootstrap_info)
+ return 0;
+ if(!media)
+ return 0;
+
+ if((ret = get_next_packet(s, bootstrap_info, media, pkt)) < 0) {
+ if(ret == AVERROR(EIO))
+ return 0;
+
+ av_log(NULL, AV_LOG_ERROR, "hds Failed to get next packet, ret: %d \n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int hds_close(AVFormatContext *s)
+{
+ HDSContext *c = s->priv_data;
+ HDSBootstrapInfo *bootstrap_info;
+ HDSMedia *media;
+ int i;
+
+ for(i = 0; i < c->nb_bootstraps; i++) {
+ bootstrap_info = c->bootstrap_info[i];
+
+ ff_free_f4f_box(&bootstrap_info->box);
+ av_freep(&bootstrap_info);
+ }
+
+ for(i = 0; i < c->nb_media; i++) {
+ media = c->media[i];
+
+ clear_samples(media);
+ av_freep(&media);
+ }
+
+ memset(c, 0x00, sizeof(HDSContext));
+
+ return 0;
+}
+
+static int hds_probe(AVProbeData *p)
+{
+ if(p->filename && av_stristr(p->filename, ".f4m"))
+ return AVPROBE_SCORE_MAX;
+ return 0;
+}
+
+AVInputFormat ff_hds_demuxer = {
+ .name = "hds",
+ .long_name = NULL_IF_CONFIG_SMALL("Adobe HTTP Dynamic Streaming Demuxer"),
+ .priv_data_size = sizeof(HDSContext),
+ .read_probe = hds_probe,
+ .read_header = hds_read_header,
+ .read_packet = hds_read_packet,
+ .read_close = hds_close,
+};
_______________________________________________
ffmpeg-devel mailing list
[email protected]
http://ffmpeg.org/mailman/listinfo/ffmpeg-devel