From patchwork Wed Oct 12 07:56:49 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Liu Steven X-Patchwork-Id: 968 Delivered-To: ffmpegpatchwork@gmail.com Received: by 10.103.140.66 with SMTP id o63csp203627vsd; Wed, 12 Oct 2016 00:57:08 -0700 (PDT) X-Received: by 10.194.78.42 with SMTP id y10mr7263600wjw.104.1476259028448; Wed, 12 Oct 2016 00:57:08 -0700 (PDT) Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id b63si1477592wme.26.2016.10.12.00.57.06; Wed, 12 Oct 2016 00:57:08 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id E9D9B680368; Wed, 12 Oct 2016 10:57:03 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from smtpproxy19.qq.com (smtpproxy19.qq.com [184.105.206.84]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 35396689214 for ; Wed, 12 Oct 2016 10:56:55 +0300 (EEST) X-QQ-mid: bizesmtp1t1476259009t97br64xy Received: from localhost (unknown [58.96.181.81]) by esmtp4.qq.com (ESMTP) with id ; Wed, 12 Oct 2016 15:56:48 +0800 (CST) X-QQ-SSF: 01100000008000F0F511B00A0000000 X-QQ-FEAT: UwW+5R6BrEPr3xU89uPJ1OioBssolPuG76GbJRDXtK5sZMs9/n2j84mdYjSzz /6TgBJAdQvno+ndZR3v08fVTttLYK87Jqd7OBTGPSm4N/t5KA6AZvhLoBL/Jx9GHNsrjyPr 6pGbUKnsF72VNG4ro0uOvOzmyLyvxgn+mlMN3+T+PYBmprzPulg8fWwiEKW6bH2/48qkhUf 6PF4a+RigsKxEyBqhlQyS1BmPw9pPO0GbU83pFT+ltP4tz9wpXq7A64yqaDuVG0ypPumxd6 i+KIFaHRWO/hzZ X-QQ-GoodBg: 0 From: Steven Liu To: ffmpeg-devel@ffmpeg.org Date: Wed, 12 Oct 2016 15:56:49 +0800 Message-Id: <20161012075649.8814-1-lq@chinaffmpeg.org> X-Mailer: git-send-email 2.10.1.382.ga23ca1b.dirty X-QQ-SENDSIZE: 520 X-QQ-Bgrelay: 1 Subject: [FFmpeg-devel] [PATCH] add hds demuxer X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Steven Liu MIME-Version: 1.0 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" init add hds demuxer Based-on: patch by CORY MCCARTHY Based-on: patch by Gorilla Maguila Signed-off-by: Steven Liu --- configure | 5 + libavformat/Makefile | 1 + libavformat/allformats.c | 2 +- libavformat/amfmetadata.c | 219 +++++++++++++ libavformat/amfmetadata.h | 39 +++ libavformat/f4fbox.c | 381 +++++++++++++++++++++++ libavformat/f4fbox.h | 95 ++++++ libavformat/f4mmanifest.c | 324 +++++++++++++++++++ libavformat/f4mmanifest.h | 59 ++++ libavformat/flvtag.c | 370 ++++++++++++++++++++++ libavformat/flvtag.h | 32 ++ libavformat/hdsdec.c | 759 +++++++++++++++++++++++++++++++++++++++++++++ 12 files changed, 2285 insertions(+), 1 deletions(-) create mode 100644 libavformat/amfmetadata.c create mode 100644 libavformat/amfmetadata.h create mode 100644 libavformat/f4fbox.c create mode 100644 libavformat/f4fbox.h create mode 100644 libavformat/f4mmanifest.c create mode 100644 libavformat/f4mmanifest.h create mode 100644 libavformat/flvtag.c create mode 100644 libavformat/flvtag.h create mode 100644 libavformat/hdsdec.c diff --git a/configure b/configure index 8fc71fb..d695e29 100755 --- a/configure +++ b/configure @@ -295,6 +295,7 @@ External library support: on OSX if openssl and gnutls are not used [autodetect] --enable-x11grab enable X11 grabbing (legacy) [no] --disable-xlib disable xlib [autodetect] + --disable-xml2 disable XML parsing using the C library libxml2 [autodetect] --disable-zlib disable zlib [autodetect] The following libraries provide various hardware acceleration features: @@ -1552,6 +1553,7 @@ EXTERNAL_LIBRARY_LIST=" videotoolbox x11grab xlib + xml2 zlib " @@ -2854,6 +2856,7 @@ eac3_demuxer_select="ac3_parser" f4v_muxer_select="mov_muxer" fifo_muxer_deps="threads" flac_demuxer_select="flac_parser" +hds_demuxer_select="xml2" hds_muxer_select="flv_muxer" hls_muxer_select="mpegts_muxer" image2_alias_pix_demuxer_select="image2_demuxer" @@ -5627,6 +5630,8 @@ disabled zlib || check_lib zlib.h zlibVersion -lz || disable zlib disabled bzlib || check_lib2 bzlib.h BZ2_bzlibVersion -lbz2 || disable bzlib disabled lzma || check_lib2 lzma.h lzma_version_number -llzma || disable lzma +disabled xml2 || 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 a/libavformat/Makefile b/libavformat/Makefile index 5d827d3..e2b4dd4 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -202,6 +202,7 @@ OBJS-$(CONFIG_H264_DEMUXER) += h264dec.o rawdec.o OBJS-$(CONFIG_H264_MUXER) += rawenc.o OBJS-$(CONFIG_HASH_MUXER) += hashenc.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 a/libavformat/allformats.c b/libavformat/allformats.c index 6a216ef..39505c3 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -146,9 +146,9 @@ void av_register_all(void) REGISTER_MUXDEMUX(H263, h263); REGISTER_MUXDEMUX(H264, h264); REGISTER_MUXER (HASH, hash); - REGISTER_MUXER (HDS, hds); REGISTER_MUXDEMUX(HEVC, hevc); REGISTER_MUXDEMUX(HLS, hls); + REGISTER_MUXDEMUX(HDS, hds); REGISTER_DEMUXER (HNM, hnm); REGISTER_MUXDEMUX(ICO, ico); REGISTER_DEMUXER (IDCIN, idcin); diff --git a/libavformat/amfmetadata.c b/libavformat/amfmetadata.c new file mode 100644 index 0000000..0e7a2ea --- /dev/null +++ b/libavformat/amfmetadata.c @@ -0,0 +1,219 @@ +/* + * 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 + */ + + +#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_get_string(AVIOContext *in, char *buffer, int buffsize) +{ + int length; + + length = avio_rb16(in); + if (length >= buffsize) { + avio_skip(in, length); + return AVERROR_INVALIDDATA; + } + avio_read(in, buffer, length); + buffer[length] = '\0'; + + return length; +} + +static int amf_metadata_read_string_value(AVIOContext *in, char *str, int str_size) +{ + uint8_t type; + + type = avio_r8(in); + if (type != AMF_DATA_TYPE_STRING){ + return AVERROR_INVALIDDATA; + } + + return 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 = value; + } else if (!av_strcasecmp("height", name)){ + metadata->height = value; + } else if (!av_strcasecmp("framerate", name)){ + metadata->frame_rate = value; + } else if (!av_strcasecmp("videodatarate", name)){ + metadata->video_data_rate = value; + } else if (!av_strcasecmp("audiosamplerate", name)){ + metadata->audio_sample_rate = value; + } else if (!av_strcasecmp("audiochannels", name)){ + metadata->nb_audio_channels = value; + } else if (!av_strcasecmp("stereo", name)){ + metadata->nb_audio_channels = (value) ? 2 : 1; + } else if (!av_strcasecmp("audiodatarate", name)){ + metadata->audio_data_rate = value; + } else if (!av_strcasecmp("audiocodecid", name)){ + if (value == 10) + metadata->audio_codec_id = AV_CODEC_ID_AAC; + } else if (!av_strcasecmp("videocodecid", name)){ + if (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[1024]; + int ret; + + if ((ret = amf_get_string(in, name, sizeof(name))) < 0) + return ret; + + return amf_metadata_parse_value(in, metadata, name); +} + +static int amf_metadata_parse_object(AVIOContext *in, AMFMetadata *metadata) +{ + int ret; + + while (!avio_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 (!avio_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[1024]; + double value_number; + int ret = 0; + + type = avio_r8(in); + switch (type){ + case AMF_DATA_TYPE_NUMBER: + value_number = av_int2double(avio_rb64(in)); + amf_metadata_assign_property_number(metadata, name, value_number); + break; + case AMF_DATA_TYPE_BOOL: + value_number = avio_r8(in); + amf_metadata_assign_property_number(metadata, name, value_number); + break; + case AMF_DATA_TYPE_STRING: + if ((ret = amf_get_string(in, value_str, sizeof(value_str))) < 0){ + return ret; + } + amf_metadata_assign_property_string(metadata, name, value_str); + break; + case AMF_DATA_TYPE_OBJECT: + ret = amf_metadata_parse_object(in, metadata); + break; + case AMF_DATA_TYPE_MIXEDARRAY: + avio_skip(in, 4); + ret = amf_metadata_parse_object(in, metadata); + break; + case AMF_DATA_TYPE_ARRAY: + ret = amf_metadata_parse_strict_array(in, metadata); + break; + default: + break; + } + return ret; +} + +static int amf_metadata_parse(AVFormatContext *s, AVIOContext *in, AMFMetadata *metadata) +{ + char name[1024]; + int ret; + + if ((ret = amf_metadata_read_string_value(in, name, sizeof(name))) < 0) { + av_log(s, AV_LOG_ERROR, "amfmetadata Failed to read onMetadata string, ret: %d \n", ret); + return ret; + } + + if (av_strcasecmp(name, "onMetaData")) { + av_log(s, AV_LOG_ERROR, "amfmetadata Expected onMetadata, str = %s \n", name); + return AVERROR_INVALIDDATA; + } + + return amf_metadata_parse_value(in, metadata, name); +} + +int ff_parse_amf_metadata(AVFormatContext *s, AMFMetadata *metadata, uint8_t *buffer, int buffer_size) +{ + 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(s, in, metadata); + av_freep(&in); + + return ret; +} diff --git a/libavformat/amfmetadata.h b/libavformat/amfmetadata.h new file mode 100644 index 0000000..f0c85c3 --- /dev/null +++ b/libavformat/amfmetadata.h @@ -0,0 +1,39 @@ +/* + * 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 + */ + + +#include "avformat.h" +#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(AVFormatContext *s ,AMFMetadata *metadata, uint8_t *buffer, int buffer_size); diff --git a/libavformat/f4fbox.c b/libavformat/f4fbox.c new file mode 100644 index 0000000..21d4eb0 --- /dev/null +++ b/libavformat/f4fbox.c @@ -0,0 +1,381 @@ +/* + * 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 + */ + + +#include "f4fbox.h" +#include "avformat.h" + +static int f4fbox_parse_asrt(AVFormatContext *s, AVIOContext *in, int64_t data_size, void *opaque) +{ + F4FBootstrapInfoBox *parent = (F4FBootstrapInfoBox*)opaque; + F4FSegmentRunTableBox *asrt; + F4FSegmentRunEntry *entry; + uint8_t quality_entry_count; + uint32_t segment_run_entry_count; + char url[1024]; + int i; + + asrt = av_mallocz(sizeof(*asrt)); + 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(*entry)); + 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(AVFormatContext *s, AVIOContext *in, int64_t data_size, void *opaque) +{ + F4FBootstrapInfoBox *parent = (F4FBootstrapInfoBox*)opaque; + F4FFragmentRunTableBox *afrt; + F4FFragmentRunEntry *entry; + uint8_t quality_entry_count; + uint32_t fragment_run_entry_count; + char url[1024] = {0, }; + int i; + + afrt = av_mallocz(sizeof(*afrt)); + 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(*entry)); + 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) { + entry->discontinuity_indicator = avio_r8(in); + } + } + + return 0; +} + + +static int f4fbox_parse_single_afrt(AVFormatContext *s, AVIOContext *in, void *opaque) +{ + 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){ + return AVERROR_INVALIDDATA; + } + if (type == MKTAG('a', 'f', 'r', 't')) { + ret = f4fbox_parse_afrt(s, in, size, opaque); + } else { + return AVERROR_INVALIDDATA; + } + 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_single_asrt(AVFormatContext *s, AVIOContext *in, void *opaque) +{ + 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){ + return AVERROR_INVALIDDATA; + } + if (type == MKTAG('a', 's', 'r', 't')) { + ret = f4fbox_parse_asrt(s, in, size, opaque); + } else { + return AVERROR_INVALIDDATA; + } + 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_abst(AVFormatContext *s, AVIOContext *in, int64_t data_size, void *opaque) +{ + F4FBox *parent = (F4FBox*)opaque; + 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] = {0, }; + int i; + int ret = 0; + + 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_asrt(s, in, abst)) < 0) { + av_log(s, 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_afrt(s, in, abst)) < 0) { + av_log(s, AV_LOG_ERROR, "f4fbox Failed to parse afrt box, ret: %d \n", ret); + return ret; + } + } + + return ret; +} + +static int f4fbox_parse_mdat(AVFormatContext *s, AVIOContext *in, int64_t data_size, void *opaque) +{ + F4FBox *parent = (F4FBox*)opaque; + F4FMediaDataBox *mdat = &(parent->mdat); + + mdat->data = av_malloc(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(AVFormatContext *s, AVIOContext *in, void *opaque) +{ + 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){ + return AVERROR_INVALIDDATA; + } + + switch (type) { + case MKTAG('a', 'b', 's', 't'): + ret = f4fbox_parse_abst(s, in, size, opaque); + break; + case MKTAG('a', 's', 'r', 't'): + ret = f4fbox_parse_asrt(s, in, size, opaque); + break; + case MKTAG('a', 'f', 'r', 't'): + ret = f4fbox_parse_afrt(s, in, size, opaque); + break; + case MKTAG('m', 'd', 'a', 't'): + ret = f4fbox_parse_mdat(s, in, size, opaque); + break; + default: + break; + } + + 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(AVFormatContext *s, AVIOContext *in, int64_t data_size, void *opaque) +{ + int64_t bytes_read = 0; + int ret; + + while (!avio_feof(in) && bytes_read + 8 < data_size) { + if ((ret = f4fbox_parse_single_box(s, in, opaque)) < 0) { + av_log(s, AV_LOG_ERROR, "f4fbox Failed to parse box, ret: %d \n", ret); + return ret; + } + bytes_read += ret; + } + + return 0; +} + +int ff_parse_f4f_box(AVFormatContext *s, uint8_t *buffer, int buffer_size, void *opaque) +{ + AVIOContext *in; + int ret; + + in = avio_alloc_context(buffer, buffer_size, 0, NULL, NULL, NULL, NULL); + if (!in) + return AVERROR(ENOMEM); + + ret = f4fbox_parse(s, in, buffer_size, opaque); + av_freep(&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(*box)); + + return 0; +} diff --git a/libavformat/f4fbox.h b/libavformat/f4fbox.h new file mode 100644 index 0000000..c0aa4f2 --- /dev/null +++ b/libavformat/f4fbox.h @@ -0,0 +1,95 @@ +/* + * 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 + */ + +#include "avformat.h" +#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(AVFormatContext *s, uint8_t *buffer, int buffer_size, void *opaque); +int ff_free_f4f_box(F4FBox *box); diff --git a/libavformat/f4mmanifest.c b/libavformat/f4mmanifest.c new file mode 100644 index 0000000..7dd51be --- /dev/null +++ b/libavformat/f4mmanifest.c @@ -0,0 +1,324 @@ +/* + * 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 + */ + +#include +#include + +#include "f4mmanifest.h" +#include "libavutil/avstring.h" +#include "libavutil/base64.h" + +#define XML_FORMATIC_TAB 0x0a +#define XML_FORMATIC_LF 0x09 +#define XML_FORMATIC_CR 0x0d +#define XML_FORMATIC_WHITE 0x20 + + +static int f4m_get_xml_content_offset(xmlChar *p) +{ + int result = 0; + int len = strlen(p); + int i; + + for (i = 0; i < len; i++) { + if (p[i] == XML_FORMATIC_LF || + p[i] == XML_FORMATIC_TAB || + p[i] == XML_FORMATIC_CR || + p[i] == XML_FORMATIC_WHITE) { + result++; + } else { + break; + } + } + + if (result > len) + result = 0; + + return result; +} + +static int f4m_get_xml_content_length(xmlChar *p) +{ + int result = 0; + int len = strlen(p); + int i; + + for (i = 0; i < len; i++) { + if (p[i] != XML_FORMATIC_LF && + p[i] != XML_FORMATIC_TAB && + p[i] != XML_FORMATIC_CR && + p[i] != XML_FORMATIC_WHITE) { + result++; + } + } + result++; + result = FFMIN(result, MAX_URL_SIZE); + + return result; +} + +static int f4m_parse_bootstrap_info_node(AVFormatContext *s, xmlNodePtr node, F4MBootstrapInfo *bootstrap_info) +{ + xmlChar *p; + uint8_t *dst; + int ret; + int len; + int offset; + + 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) { + len = f4m_get_xml_content_length(p); + offset = f4m_get_xml_content_offset(p); + dst = av_mallocz(len); + if (!dst) { + xmlFree(p); + return AVERROR(ENOMEM); + } + if ((ret = av_base64_decode(dst, p + offset, len)) < 0) { + av_log(s, AV_LOG_ERROR, "f4mmanifest Failed to decode bootstrap node base64 metadata, ret: %d \n", ret); + xmlFree(p); + av_freep(&dst); + return ret; + } + + bootstrap_info->metadata = av_mallocz(ret); + if (!bootstrap_info->metadata) { + xmlFree(p); + av_freep(&dst); + return AVERROR(ENOMEM); + } + + bootstrap_info->metadata_size = ret; + memcpy(bootstrap_info->metadata, dst, ret); + av_freep(&dst); + xmlFree(p); + } + + return 0; +} + +static int f4m_parse_metadata_node(AVFormatContext *s, xmlNodePtr node, F4MMedia *media) +{ + xmlNodePtr metadata_node; + xmlChar *p; + uint8_t *dst; + int len; + int offset; + int ret; + + metadata_node = node->children; + while (metadata_node) { + if (!av_strcasecmp(metadata_node->name, "metadata")) { + p = xmlNodeGetContent(metadata_node); + break; + } + metadata_node = metadata_node->next; + } + + if (!p) + return 0; + + len = f4m_get_xml_content_length(p); + offset = f4m_get_xml_content_offset(p); + + dst = av_mallocz(len); + if (!dst) { + xmlFree(p); + return AVERROR(ENOMEM); + } + + if ((ret = av_base64_decode(dst, p + offset, len)) < 0) { + av_log(s, AV_LOG_ERROR, "f4mmanifest Failed to decode base64 metadata, ret: %d \n", ret); + xmlFree(p); + av_freep(&dst); + return ret; + } + + media->metadata = av_mallocz(ret); + if (!media->metadata) { + xmlFree(p); + av_freep(&dst); + return AVERROR(ENOMEM); + } + + media->metadata_size = ret; + memcpy(media->metadata, dst, ret); + + xmlFree(p); + av_freep(&dst); + + return 0; +} + +static int f4m_parse_media_node(AVFormatContext *s, 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(s, node, media)) < 0) { + return ret; + } + return 0; +} + +static int f4m_parse_manifest_node(AVFormatContext *s, xmlNodePtr root_node, F4MManifest *manifest) +{ + F4MBootstrapInfo *bootstrap_info; + F4MMedia *media; + xmlNodePtr node; + xmlChar *node_content; + int ret; + int len; + int offset; + + for (node = root_node->children; node != root_node->last; node = node->next) { + if (!av_strcasecmp(node->name, "text")) + continue; + + node_content = xmlNodeGetContent(node); + if (!node_content) + return AVERROR(ENOMEM); + + offset = f4m_get_xml_content_offset(node_content); + len = f4m_get_xml_content_length(node_content); + if (!av_strcasecmp(node->name, "id") && node_content) { + av_strlcpy(manifest->id, node_content + offset, len); + } + if (!av_strcasecmp(node->name, "streamType") && node_content) { + av_strlcpy(manifest->stream_type, node_content + offset, len); + } else if (!av_strcasecmp(node->name, "bootstrapInfo")) { + bootstrap_info = av_mallocz(sizeof(*bootstrap_info)); + if (!bootstrap_info) { + xmlFree(node_content); + return AVERROR(ENOMEM); + } + manifest->bootstraps[manifest->nb_bootstraps++] = bootstrap_info; + ret = f4m_parse_bootstrap_info_node(s, node, bootstrap_info); + } else if (!av_strcasecmp(node->name, "media")) { + media = av_mallocz(sizeof(*media)); + if (!media) { + xmlFree(node_content); + return AVERROR(ENOMEM); + } + manifest->media[manifest->nb_media++] = media; + ret = f4m_parse_media_node(s, node, media, manifest); + } + + if (node_content) + xmlFree(node_content); + if (ret < 0) + return ret; + } + + return 0; +} + +static int f4m_parse_xml_file(AVFormatContext *s, 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 AVERROR_INVALIDDATA; + } + + root_node = xmlDocGetRootElement(doc); + if (!root_node) { + av_log(s, AV_LOG_ERROR, "f4mmanifest Root element not found \n"); + xmlFreeDoc(doc); + return AVERROR_INVALIDDATA; + } + + if (av_strcasecmp(root_node->name, "manifest")) { + av_log(s, AV_LOG_ERROR, "f4mmanifest Root element is not named manifest, name = %s \n", + root_node->name); + xmlFreeDoc(doc); + return AVERROR_INVALIDDATA; + } + + ret = f4m_parse_manifest_node(s, root_node, manifest); + xmlFreeDoc(doc); + + return ret; +} + +int ff_parse_f4m_manifest(AVFormatContext *s, uint8_t *buffer, int size, F4MManifest *manifest) +{ + return f4m_parse_xml_file(s, 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(*manifest)); + + return 0; +} diff --git a/libavformat/f4mmanifest.h b/libavformat/f4mmanifest.h new file mode 100644 index 0000000..0986c3f --- /dev/null +++ b/libavformat/f4mmanifest.h @@ -0,0 +1,59 @@ +/* + * 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 + */ + +#include "avformat.h" +#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(AVFormatContext *s, uint8_t *buffer, int size, F4MManifest *manifest); +int ff_free_manifest(F4MManifest *manifest); diff --git a/libavformat/flvtag.c b/libavformat/flvtag.c new file mode 100644 index 0000000..af080b7 --- /dev/null +++ b/libavformat/flvtag.c @@ -0,0 +1,370 @@ +/* + * 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 + */ + +#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 { + 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(AVFormatContext *s, AVIOContext *in, uint32_t data_size, + FLVTagAudioHeader *header, FLVTagAudioBody *body, FLVMediaSample **sample_out) +{ + FLVMediaSample *sample; + + if (header->sound_format != 10) { + av_log(s, AV_LOG_ERROR, "flvtag Unhandled sound format, fmt: %d \n", header->sound_format); + return 0; + } + + if (!header->aac_packet_type) + return 0;//skip AudioSpecificConfig + + if (header->aac_packet_type != 1) { + av_log(s, AV_LOG_ERROR, "flvtag Unhandled aac_packet_type, type: %d \n", header->aac_packet_type); + return 0; + } + + sample = av_mallocz(sizeof(*sample)); + if (!sample) + return AVERROR(ENOMEM); + + sample->type = AVMEDIA_TYPE_AUDIO; + sample->data = av_malloc(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(AVFormatContext *s, AVIOContext *in, uint32_t data_size, + FLVTagVideoHeader *header, FLVTagVideoBody *body, FLVMediaSample **sample_out) +{ + FLVMediaSample *sample; + 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(s, 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(*sample)); + 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_malloc(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(AVFormatContext *s, 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(audio_header)); + memset(&audio_body, 0x00, sizeof(audio_body)); + memset(&video_header, 0x00, sizeof(video_header)); + memset(&video_body, 0x00, sizeof(video_body)); + + in = avio_alloc_context(buffer, buffer_size, 0, NULL, NULL, NULL, NULL); + if (!in) + return AVERROR(ENOMEM); + + while (!avio_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) { + av_log(s, AV_LOG_ERROR, "flvtag Invalid stream_id %d \n", stream_id); + return -1; + } + + if (tag_type == 0x08) { + data_size -= flv_tag_parse_audio_header(in, &audio_header); + } else if (tag_type == 0x09) { + 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(s, in, data_size, + &audio_header, &audio_body, &sample)) < 0) { + av_freep(&in); + return ret; + } + data_size -= ret; + } else if (tag_type == 9) { + if ((ret = flv_tag_parse_video_body(s, in, data_size, + &video_header, &video_body, &sample)) < 0) { + + av_freep(&in); + return ret; + } + data_size -= ret; + } else if (tag_type == 18) { + //ScriptData + } + + if (sample) { + sample->timestamp = dts; + samples[nb_samples++] = sample; + } + + if (data_size) { + avio_skip(in, data_size); + } + avio_rb32(in); + } + + av_freep(&in); + + if (video_body.sps_data_size > 0) + av_freep(&video_body.sps_data); + if (video_body.pps_data_size > 0) + av_freep(&video_body.pps_data); + + if (nb_samples_out) + *nb_samples_out = nb_samples; + + return 0; +} + +int ff_decode_flv_body(AVFormatContext *s, uint8_t *buffer, int buffer_size, + FLVMediaSample **samples, int *nb_samples_out) +{ + return flv_tag_decode_body(s, buffer, buffer_size, samples, nb_samples_out); +} diff --git a/libavformat/flvtag.h b/libavformat/flvtag.h new file mode 100644 index 0000000..7c468af --- /dev/null +++ b/libavformat/flvtag.h @@ -0,0 +1,32 @@ +/* + * 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 + */ + +#include "avformat.h" +#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(AVFormatContext *s, uint8_t *buffer, int buffer_size, FLVMediaSample **samples, int *nb_samples_out); diff --git a/libavformat/hdsdec.c b/libavformat/hdsdec.c new file mode 100644 index 0000000..c99cb9c --- /dev/null +++ b/libavformat/hdsdec.c @@ -0,0 +1,759 @@ +/* + * 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_samples; + FLVMediaSample *samples[MAX_NB_SAMPLES]; + int sample_index; + + int nb_first_fragment; + int nb_offset_fragment; + int nb_fragments_read; + int nb_fragments_total; +} 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 = 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 = 0; + + 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(s, 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(s, AV_LOG_ERROR, "hds Failed to downloaded bootstrap, ret: %d \n", ret); + av_freep(&buffer); + return ret; + } + + if ((ret = ffurl_close(puc)) < 0) { + av_log(s, AV_LOG_ERROR, "hds Failed to finish downloading bootstrap, ret: %d \n", ret); + av_freep(&buffer); + return ret; + } + + if (buffer_out) + *buffer_out = buffer; + if (buffer_size_out) + *buffer_size_out = buffer_size; + + return ret; +} + +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(s, buffer, buffer_size, &(bootstrap_info->box))) < 0) { + av_log(s, 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(s, AV_LOG_ERROR, "hds Failed to download bootstrap, ret: %d \n", ret); + return ret; + } + + if ((ret = ff_parse_f4f_box(s, buffer, buffer_size, &(bootstrap_info->box))) < 0) { + av_log(s, AV_LOG_ERROR, "hds Failed to parse downloaded bootstrap box, ret: %d \n", ret); + av_freep(&buffer); + return ret; + } + + av_freep(&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->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; + st->codecpar->codec_id = metadata->video_codec_id; + st->codecpar->width = metadata->width; + st->codecpar->height = metadata->height; + st->codecpar->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->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; + st->codecpar->codec_id = metadata->audio_codec_id; + st->codecpar->channels = metadata->nb_audio_channels; + st->codecpar->sample_rate = metadata->audio_sample_rate; + st->codecpar->bits_per_raw_sample = AV_SAMPLE_FMT_S16; + st->codecpar->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(*media)); + 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(metadata)); + if ((ret = ff_parse_amf_metadata(s, &metadata, f4m_media->metadata, f4m_media->metadata_size)) < 0) { + av_log(s, 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; + uint8_t value[1024]; + int i, 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); + snprintf(value, sizeof(value),"Bandwidth: %dKbps", media->bitrate); + av_dict_set(&p->metadata,"name", value, 0); + av_program_add_stream_index(s, p->id, media->video_stream->index); + av_program_add_stream_index(s, p->id, media->audio_stream->index); + } + + return 0; +} + +static int hds_create_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(s, 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(s, AV_LOG_ERROR, "hds Failed to create media, ret: %d \n", ret); + return ret; + } + } + + if ((ret = create_pmt(s)) < 0) { + av_log(s, 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; + av_log(s, AV_LOG_DEBUG, "hds stream_type live %s \n", c->is_live ? "TRUE" : "FALSE"); + + 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(s, 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(s, 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); + if (!buf) + return AVERROR(ENOMEM); + + avio_read(in, buf, filesize); + memset(&manifest, 0x00, sizeof(manifest)); + if ((ret = ff_parse_f4m_manifest(s, buf, filesize, &manifest)) < 0) { + av_freep(&buf); + ff_free_manifest(&manifest); + return ret; + } + + av_freep(&buf); + + ret = hds_create_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[1024]; + + p = url_out; + p += av_strlcat(p, base_url, url_size); + p += av_strlcat(p, media_url, url_size); + + snprintf(fragment_str, sizeof (fragment_str), "Seg%d-Frag%d", segment, fragment); + p += av_strlcat(p, fragment_str, url_size); + p += av_strlcat(p, suffix, url_size); +} + +static int get_offset_fragment(HDSBootstrapInfo *bootstrap_info) +{ + F4FBootstrapInfoBox *abst = &(bootstrap_info->box.abst); + F4FSegmentRunTableBox *asrt; + F4FSegmentRunEntry *segment_entry; + int offset = 0; + 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]; + offset += segment_entry->fragments_per_segment; + } + } + + return offset; +} + +static int get_total_fragment(HDSBootstrapInfo *bootstrap_info) +{ + F4FBootstrapInfoBox *abst = &(bootstrap_info->box.abst); + F4FSegmentRunTableBox *asrt; + F4FSegmentRunEntry *segment_entry; + int total = 0; + int i; + + for (i = 0; i < abst->nb_segment_run_table_boxes; i++) { + asrt = abst->segment_run_table_boxes[i]; + segment_entry = asrt->segment_run_entries[0]; + total = asrt->nb_segment_run_entries * segment_entry->fragments_per_segment; + } + + return total; +} + + +static int get_current_segment(HDSBootstrapInfo *bootstrap_info) +{ + F4FBootstrapInfoBox *abst = &(bootstrap_info->box.abst); + F4FSegmentRunTableBox *asrt; + F4FSegmentRunEntry *segment_entry; + int segment = 0; + 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; + } + } + return segment; +} + + +static int get_first_fragment(HDSBootstrapInfo *bootstrap_info) +{ + F4FBootstrapInfoBox *abst = &(bootstrap_info->box.abst); + F4FFragmentRunTableBox *afrt; + F4FFragmentRunEntry *fragment_entry; + int fragment = 0; + 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; + return fragment; + } + } + } + + 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 buffer_size; + int ret = 0; + + segment = get_current_segment(bootstrap_info); + if (!media->nb_first_fragment) + media->nb_first_fragment = get_first_fragment(bootstrap_info); + if (!media->nb_fragments_total) + media->nb_fragments_total = get_total_fragment(bootstrap_info); + + fragment = media->nb_first_fragment + media->nb_fragments_read; + av_log(s, AV_LOG_DEBUG, "hds current_segment: %d current_fragment: %d\n", segment, fragment); + if (is_live) { + if (!media->nb_offset_fragment) + media->nb_offset_fragment = get_offset_fragment(bootstrap_info); + fragment += media->nb_offset_fragment - 1; + + av_log(s, AV_LOG_DEBUG, "hds live current_fragment: %d total_fragments: %d\n", + fragment, media->nb_first_fragment + media->nb_fragments_total); + + if (fragment >= (media->nb_first_fragment + media->nb_fragments_total)) { + /* Update bootstrap info*/ + av_log(s, AV_LOG_DEBUG, "update bootstrap_info\n"); + if ((ret = ff_free_f4f_box(&(bootstrap_info->box))) < 0) { + av_log(s, AV_LOG_ERROR, "hds Failed to free bootstrap box, ret: %d \n", ret); + return ret; + } + if ((ret = download_bootstrap(s, bootstrap_info, &buffer, &buffer_size)) < 0) { + av_log(s, AV_LOG_ERROR, "hds Failed to download bootstrap, ret: %d \n", ret); + return ret; + } + if ((ret = ff_parse_f4f_box(s, buffer, buffer_size, &(bootstrap_info->box))) < 0) { + av_log(s, AV_LOG_ERROR, "hds Failed to parse downloaded bootstrap box, ret: %d \n", ret); + av_freep(&buffer); + return ret; + } + segment = get_current_segment(bootstrap_info); + media->nb_first_fragment = 0; + media->nb_fragments_read = 0; + media->nb_fragments_total = 0; + media->nb_offset_fragment = 0; + av_freep(&buffer); + } + } + + if (!is_live && fragment >= (media->nb_first_fragment + media->nb_fragments_total)) { + 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(s, 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(s, AV_LOG_ERROR, "hds Failed to downloaded fragment, ret: %d \n", ret); + av_freep(&buffer); + return ret; + } + + if ((ret = ffurl_close(puc)) < 0) { + av_log(s, AV_LOG_ERROR, "hds Failed to finish downloading fragment, ret: %d \n", ret); + av_freep(&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(box)); + if ((ret = ff_parse_f4f_box(s, buffer, buffer_size, &box)) < 0) { + av_log(s, AV_LOG_ERROR, "hds Failed to parse bootstrap box, ret: %d \n", ret); + av_freep(&buffer); + ff_free_f4f_box(&box); + return ret; + } + av_freep(&buffer); + + if ((ret = ff_decode_flv_body(s ,box.mdat.data, box.mdat.size, media->samples, &media->nb_samples)) < 0) { + av_log(s, 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 = 0; + + if (!media->nb_samples) { + 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 ret; +} + +static int hds_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + HDSContext *c = s->priv_data; + HDSBootstrapInfo *bootstrap_info; + HDSMedia *media; + int i, j; + + 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) { + av_log(s, AV_LOG_ERROR, "cannot find bootstrap info"); + return AVERROR(EINVAL); + } + + if (!media) { + av_log(s, AV_LOG_ERROR, "cannot find media"); + return AVERROR(EINVAL); + } + + return get_next_packet(s, bootstrap_info, media, pkt); +} + +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(*c)); + + return 0; +} + +static int hds_probe(AVProbeData *p) +{ + if (strncmp(p->buf, "buf, "buf, "buf, "