From e097792627893d3891957751b84a3e33f2c1aeed Mon Sep 17 00:00:00 2001 From: Markham Date: Tue, 14 Feb 2023 21:04:58 +0100 Subject: [PATCH] add patches ffmpeg-3.3.9 --- .../3.3.9/0001-ffmpeg-hds-libroxml-3.x.patch | 2703 +++++++++++++++++ .../3.3.9/0002-ffmpeg-aac-3.x.patch | 53 + ...mpeg-increase-IO-BUFFER-SIZE-to-256k.patch | 17 + ...ep-track-of-the-original-buffer-size.patch | 55 + ...ashdec-add-dash-demuxer-base-version.patch | 1902 ++++++++++++ ...-ffmpeg-reset-compressed-header-flag.patch | 11 + .../3.3.9/0007-ffmpeg-CST-fix-pts.patch | 15 + .../3.3.9/0008-ffmpeg-CST-fix-sps-pps.patch | 29 + ...1-Annex-G-and-RCV-bitstream-filters.patch} | 141 +- ...allow-to-choose-rtmp-impl-at-runtime.patch | 112 + .../3.3.9/0011-ffmpeg-discont.patch | 125 + .../ffmpeg-cst/3.3.9/0012-ffmpeg-nolog.patch | 28 + .../3.3.9/0013-ffmpeg-disable-whitelist.patch | 19 + 13 files changed, 5137 insertions(+), 73 deletions(-) create mode 100644 archive-patches/ffmpeg-cst/3.3.9/0001-ffmpeg-hds-libroxml-3.x.patch create mode 100644 archive-patches/ffmpeg-cst/3.3.9/0002-ffmpeg-aac-3.x.patch create mode 100644 archive-patches/ffmpeg-cst/3.3.9/0003-ffmpeg-increase-IO-BUFFER-SIZE-to-256k.patch create mode 100644 archive-patches/ffmpeg-cst/3.3.9/0004-ffmpeg-revert-libavformat-aviobuf-keep-track-of-the-original-buffer-size.patch create mode 100755 archive-patches/ffmpeg-cst/3.3.9/0005-ffmpeg-devel-v14-avformat-dashdec-add-dash-demuxer-base-version.patch create mode 100644 archive-patches/ffmpeg-cst/3.3.9/0006-ffmpeg-reset-compressed-header-flag.patch create mode 100644 archive-patches/ffmpeg-cst/3.3.9/0007-ffmpeg-CST-fix-pts.patch create mode 100755 archive-patches/ffmpeg-cst/3.3.9/0008-ffmpeg-CST-fix-sps-pps.patch rename archive-patches/ffmpeg-cst/{3.3/ffmpeg34-cst-add-ASF-VC1-Annex-G-and-RCV-bitstream-filters.patch => 3.3.9/0009-ffmpeg-CST-add-ASF-VC1-Annex-G-and-RCV-bitstream-filters.patch} (78%) create mode 100644 archive-patches/ffmpeg-cst/3.3.9/0010-ffmpeg-CST-allow-to-choose-rtmp-impl-at-runtime.patch create mode 100644 archive-patches/ffmpeg-cst/3.3.9/0011-ffmpeg-discont.patch create mode 100644 archive-patches/ffmpeg-cst/3.3.9/0012-ffmpeg-nolog.patch create mode 100644 archive-patches/ffmpeg-cst/3.3.9/0013-ffmpeg-disable-whitelist.patch diff --git a/archive-patches/ffmpeg-cst/3.3.9/0001-ffmpeg-hds-libroxml-3.x.patch b/archive-patches/ffmpeg-cst/3.3.9/0001-ffmpeg-hds-libroxml-3.x.patch new file mode 100644 index 0000000..e3dfdc7 --- /dev/null +++ b/archive-patches/ffmpeg-cst/3.3.9/0001-ffmpeg-hds-libroxml-3.x.patch @@ -0,0 +1,2703 @@ +--- a/configure 2018-11-18 01:57:59.000000000 +0100 ++++ b/configure 2023-02-14 17:31:43.422907713 +0100 +@@ -293,6 +293,7 @@ External library support: + on OSX if openssl and gnutls are not used [autodetect] + --disable-xlib disable xlib [autodetect] + --disable-zlib disable zlib [autodetect] ++ --disable-libroxml disable XML parsing using the C library libroxml [autodetect] + + The following libraries provide various hardware acceleration features: + --disable-audiotoolbox disable Apple AudioToolbox code [autodetect] +@@ -1565,6 +1566,7 @@ EXTERNAL_LIBRARY_LIST=" + libopenmpt + libopus + libpulse ++ libroxml + librtmp + libschroedinger + libshine +@@ -2926,6 +2928,7 @@ eac3_demuxer_select="ac3_parser" + f4v_muxer_select="mov_muxer" + fifo_muxer_deps="threads" + flac_demuxer_select="flac_parser" ++hds_demuxer_select="libroxml" + hds_muxer_select="flv_muxer" + hls_muxer_select="mpegts_muxer" + image2_alias_pix_demuxer_select="image2_demuxer" +@@ -5735,6 +5738,8 @@ disabled zlib || check_lib zlib.h + disabled bzlib || check_lib bzlib.h BZ2_bzlibVersion -lbz2 || disable bzlib + disabled lzma || check_lib lzma.h lzma_version_number -llzma || disable lzma + ++disabled libroxml || check_lib roxml roxml.h roxml_load_buf -lroxml || disable libroxml ++ + check_lib math.h sin -lm && LIBM="-lm" + disabled crystalhd || check_lib "stdint.h libcrystalhd/libcrystalhd_if.h" DtsCrystalHDVersion -lcrystalhd || disable crystalhd + +--- a/libavformat/allformats.c 2018-11-18 01:58:00.000000000 +0100 ++++ b/libavformat/allformats.c 2023-02-14 17:27:17.200274887 +0100 +@@ -142,7 +142,7 @@ static void register_all(void) + REGISTER_MUXDEMUX(H263, h263); + REGISTER_MUXDEMUX(H264, h264); + REGISTER_MUXER (HASH, hash); +- REGISTER_MUXER (HDS, hds); ++ REGISTER_MUXDEMUX(HDS, hds); + REGISTER_MUXDEMUX(HEVC, hevc); + REGISTER_MUXDEMUX(HLS, hls); + REGISTER_DEMUXER (HNM, hnm); +--- a/libavformat/amfmetadata.c 1970-01-01 01:00:00.000000000 +0100 ++++ b/libavformat/amfmetadata.c 2023-02-14 17:27:17.200274887 +0100 +@@ -0,0 +1,290 @@ ++/* ++ * 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" ++#include "libavformat/avformat.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 flv_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("audiosamplesize", name)) { ++ metadata->audio_sample_size = (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)) { ++ switch(FLV_AUDIO_CODECID_MASK & ((int)value << FLV_AUDIO_CODECID_OFFSET)) { ++ case FLV_CODECID_PCM: ++ metadata->audio_codec_id = metadata->audio_sample_size == 8 ++ ? AV_CODEC_ID_PCM_U8 ++#if HAVE_BIGENDIAN ++ : AV_CODEC_ID_PCM_S16BE; ++#else ++ : AV_CODEC_ID_PCM_S16LE; ++#endif ++ break; ++ case FLV_CODECID_ADPCM: ++ metadata->audio_codec_id = AV_CODEC_ID_ADPCM_SWF; ++ break; ++ case FLV_CODECID_MP3: ++ metadata->audio_stream_need_parsing = AVSTREAM_PARSE_FULL; ++ metadata->audio_codec_id = AV_CODEC_ID_MP3; ++ break; ++ case FLV_CODECID_PCM_LE: ++ metadata->audio_codec_id = metadata->audio_sample_size == 8 ++ ? AV_CODEC_ID_PCM_U8 ++ : AV_CODEC_ID_PCM_S16LE; ++ break; ++ case FLV_CODECID_NELLYMOSER_16KHZ_MONO: ++ if (!metadata->audio_sample_rate) ++ metadata->audio_sample_rate = 16000; ++ metadata->nb_audio_channels = 1; ++ metadata->audio_codec_id = AV_CODEC_ID_NELLYMOSER; ++ break; ++ case FLV_CODECID_NELLYMOSER_8KHZ_MONO: ++ if (!metadata->audio_sample_rate) ++ metadata->audio_sample_rate = 8000; ++ metadata->nb_audio_channels = 1; ++ metadata->audio_codec_id = AV_CODEC_ID_NELLYMOSER; ++ break; ++ case FLV_CODECID_NELLYMOSER: ++ metadata->audio_codec_id = AV_CODEC_ID_NELLYMOSER; ++ break; ++ case FLV_CODECID_PCM_ALAW: ++ metadata->audio_sample_rate = 8000; ++ metadata->audio_codec_id = AV_CODEC_ID_PCM_ALAW; ++ break; ++ case FLV_CODECID_PCM_MULAW: ++ metadata->audio_sample_rate = 8000; ++ metadata->audio_codec_id = AV_CODEC_ID_PCM_MULAW; ++ break; ++ case FLV_CODECID_AAC: ++ metadata->audio_codec_id = AV_CODEC_ID_AAC; ++ break; ++ case FLV_CODECID_SPEEX: ++ metadata->audio_sample_rate = 16000; ++ metadata->audio_codec_id = AV_CODEC_ID_SPEEX; ++ break; ++ default: ++ break; ++ } ++ } ++ 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(".mp3", value)) { ++ metadata->audio_stream_need_parsing = AVSTREAM_PARSE_FULL; ++ metadata->audio_codec_id = AV_CODEC_ID_MP3; ++ } ++ else ++ av_log(NULL, AV_LOG_ERROR, "amfmetadata audiocodecid=%s unhandled\n", value); ++ } ++ else ++ if(!av_strcasecmp("videocodecid", name)) { ++ if(!av_strcasecmp("avc1", value)) ++ metadata->video_codec_id = AV_CODEC_ID_H264; ++ else ++ av_log(NULL, AV_LOG_ERROR, "amfmetadata videocodecid=%s unhandled\n", value); ++ } ++} ++ ++static int amf_metadata_parse_object_property(AVIOContext *in, AMFMetadata *metadata) ++{ ++ char name[INT16_MAX]; ++ int ret; ++ ++ if((ret = flv_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) ++ 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[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 = flv_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); ++ } ++ else if(type == AMF_END_OF_OBJECT) { ++ ret = -1; // EOF ++ } ++ ++ 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; ++} +--- a/libavformat/amfmetadata.h 1970-01-01 01:00:00.000000000 +0100 ++++ b/libavformat/amfmetadata.h 2023-02-14 17:27:17.200274887 +0100 +@@ -0,0 +1,47 @@ ++/* ++ * 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 audio_sample_size; ++ int nb_audio_channels; ++ int audio_data_rate; ++ int video_data_rate; ++ int audio_stream_need_parsing; ++ ++ enum AVCodecID audio_codec_id; ++ enum AVCodecID video_codec_id; ++} AMFMetadata; ++ ++int ff_parse_amf_metadata(uint8_t *buffer, int buffer_size, AMFMetadata *metadata); +--- a/libavformat/f4fbox.c 1970-01-01 01:00:00.000000000 +0100 ++++ b/libavformat/f4fbox.c 2023-02-14 17:27:17.200274887 +0100 +@@ -0,0 +1,345 @@ ++/* ++ * 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; ++ 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); ++ ++ asrt->nb_quality_entries = avio_r8(in); ++ if (asrt->nb_quality_entries) ++ asrt->quality_entries = (char **) av_malloc(asrt->nb_quality_entries * sizeof(char *)); ++ for(i = 0; i < asrt->nb_quality_entries; i++) { ++ avio_get_str(in, sizeof(url), url, sizeof(url)); ++ av_log(NULL, AV_LOG_DEBUG, "f4fbox asrt quality[%d]=%s\n", i, url); ++ asrt->quality_entries[i] = av_strdup(url); ++ } ++ ++ segment_run_entry_count = avio_rb32(in); ++ if(segment_run_entry_count > MAX_NB_SEGMENT_RUN_ENTRIES) { ++ av_log(NULL, AV_LOG_ERROR, "f4fbox segment run entry count exceeds maximum, %d > %d \n", ++ segment_run_entry_count, MAX_NB_SEGMENT_RUN_ENTRIES); ++ segment_run_entry_count = MAX_NB_SEGMENT_RUN_ENTRIES; ++ } ++ ++ 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; ++ 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); ++ ++ afrt->nb_quality_entries = avio_r8(in); ++ if (afrt->nb_quality_entries) ++ afrt->quality_entries = (char **) av_malloc(afrt->nb_quality_entries * sizeof(char *)); ++ for(i = 0; i < afrt->nb_quality_entries; i++) { ++ avio_get_str(in, sizeof(url), url, sizeof(url)); ++ av_log(NULL, AV_LOG_DEBUG, "f4fbox afrt quality[%d]=%s\n", i, url); ++ afrt->quality_entries[i] = av_strdup(url); ++ } ++ ++ fragment_run_entry_count = avio_rb32(in); ++ if(fragment_run_entry_count > MAX_NB_FRAGMENT_RUN_ENTRIES) { ++ av_log(NULL, AV_LOG_ERROR, "f4fbox fragment run entry count exceeds maximum, %d > %d \n", ++ fragment_run_entry_count, MAX_NB_FRAGMENT_RUN_ENTRIES); ++ fragment_run_entry_count = MAX_NB_FRAGMENT_RUN_ENTRIES; ++ } ++ ++ 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; ++ 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)); ++ } ++ ++ abst->nb_quality_entries = avio_r8(in); ++ if (abst->nb_quality_entries) ++ abst->quality_entries = (char **) av_malloc(abst->nb_quality_entries * sizeof(char *)); ++ for(i = 0; i < abst->nb_quality_entries; i++) { ++ avio_get_str(in, sizeof(url), url, sizeof(url)); ++ av_log(NULL, AV_LOG_DEBUG, "f4fbox abst quality[%d]=%s\n", i, url); ++ abst->quality_entries[i] = av_strdup(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(!avio_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(j = 0; j < abst->nb_quality_entries; j++) ++ av_freep(&abst->quality_entries[j]); ++ if (abst->quality_entries) ++ av_freep(&abst->quality_entries); ++ ++ for(i = 0; i < abst->nb_segment_run_table_boxes; i++) { ++ asrt = abst->segment_run_table_boxes[i]; ++ for(j = 0; j < asrt->nb_quality_entries; j++) ++ av_freep(&asrt->quality_entries[j]); ++ if (asrt->quality_entries) ++ av_freep(&asrt->quality_entries); ++ 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_quality_entries; j++) ++ av_freep(&afrt->quality_entries[j]); ++ if (afrt->quality_entries) ++ av_freep(&afrt->quality_entries); ++ 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; ++} +--- a/libavformat/f4fbox.h 1970-01-01 01:00:00.000000000 +0100 ++++ b/libavformat/f4fbox.h 2023-02-14 17:27:17.200274887 +0100 +@@ -0,0 +1,110 @@ ++/* ++ * 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; ++ ++ uint8_t nb_quality_entries; ++ char **quality_entries; ++ ++ 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; ++ ++ uint8_t nb_quality_entries; ++ char **quality_entries; ++ ++ 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_quality_entries; ++ char **quality_entries; ++ ++ 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); +--- a/libavformat/f4mmanifest.c 1970-01-01 01:00:00.000000000 +0100 ++++ b/libavformat/f4mmanifest.c 2023-02-14 17:27:17.200274887 +0100 +@@ -0,0 +1,256 @@ ++/* ++ * 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 ++ ++static int f4m_parse_bootstrap_info_node(node_t * node, F4MBootstrapInfo *bootstrap_info) ++{ ++ const char *p; ++ uint8_t *dst; ++ int ret; ++ node_t *attr; ++ ++ attr = roxml_get_attr(node, "id", 0); ++ p =roxml_get_content(attr,NULL,0,NULL); ++ if(p) { ++ av_strlcpy(bootstrap_info->id, p, sizeof(bootstrap_info->id)); ++ } ++ ++ attr = roxml_get_attr(node, "url", 0); ++ p =roxml_get_content(attr,NULL,0,NULL); ++ if(p) { ++ av_strlcpy(bootstrap_info->url, p, sizeof(bootstrap_info->url)); ++ } ++ ++ attr = roxml_get_attr(node, "profile", 0); ++ p =roxml_get_content(attr,NULL,0,NULL); ++ if(p) { ++ av_strlcpy(bootstrap_info->profile, p, sizeof(bootstrap_info->profile)); ++ } ++ ++ p = roxml_get_content(node, NULL, 0, NULL); ++ if(p) { ++ dst = av_mallocz(sizeof(uint8_t)*strlen(p)); ++ if(!dst) ++ return AVERROR(ENOMEM); ++ ++ if((ret = av_base64_decode(dst, p, strlen(p))) < 0) { ++ av_log(NULL, AV_LOG_ERROR, "f4mmanifest Failed to decode bootstrap node base64 metadata, ret: %d \n", ret); ++ 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); ++ } ++ ++ return 0; ++} ++ ++static int f4m_parse_metadata_node(node_t * node, F4MMedia *media) ++{ ++ const char *p = NULL; ++ uint8_t *dst; ++ int ret; ++ ++ node_t * metadata_node = roxml_get_chld(node, NULL, 0); ++ while(metadata_node) { ++ if(!strcmp(roxml_get_name(metadata_node, NULL, 0), "metadata")) { ++ p = roxml_get_content(metadata_node, NULL, 0, NULL); ++ break; ++ } ++ metadata_node = roxml_get_next_sibling(metadata_node); ++ } ++ ++ if(!p) ++ return 0; ++ ++ dst = av_mallocz(sizeof(uint8_t)*strlen(p)); ++ if(!dst) ++ return AVERROR(ENOMEM); ++ ++ if((ret = av_base64_decode(dst, p, strlen(p))) < 0) { ++ av_log(NULL, AV_LOG_ERROR, "f4mmanifest Failed to decode base64 metadata, ret: %d \n", ret); ++ 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); ++ ++ av_free(dst); ++ ++ return 0; ++} ++ ++static int f4m_parse_media_node(node_t * node, F4MMedia *media) ++{ ++ const char *p; ++ int ret; ++ node_t * attr; ++ ++ attr = roxml_get_attr(node, "bitrate", 0); ++ p =roxml_get_content(attr,NULL,0,NULL); ++ if(p) { ++ media->bitrate = strtoul(p, NULL, 10); ++ } ++ ++ attr = roxml_get_attr(node, "url", 0); ++ p =roxml_get_content(attr,NULL,0,NULL); ++ if(p) { ++ av_strlcpy(media->url, p, sizeof(media->url)); ++ } ++ ++ attr = roxml_get_attr(node, "bootstrapInfoId", 0); ++ p =roxml_get_content(attr,NULL,0,NULL); ++ if(p) { ++ av_strlcpy(media->bootstrap_info_id, p, sizeof(media->bootstrap_info_id)); ++ } ++ ++ if((ret = f4m_parse_metadata_node(node, media)) < 0) { ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int f4m_parse_manifest_node(node_t * root_node, F4MManifest *manifest) ++{ ++ F4MBootstrapInfo *bootstrap_info; ++ F4MMedia *media; ++ node_t * node; ++ const char *node_content; ++ int ret = 0,chld_idx=0; ++ ++ for (chld_idx=0; chld_idxid, node_content, sizeof(manifest->id)); ++ } else if(!strcmp(node_name, "streamType") && node_content) { ++ av_strlcpy(manifest->stream_type, node_content, sizeof(manifest->stream_type)); ++ } 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); ++ } else if (!strcmp(node_name, "duration")) { ++ double duration = strtod(node_content, NULL); ++ manifest->duration = duration * AV_TIME_BASE; ++ } ++ ++ if(ret < 0) ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int f4m_parse_xml_file(uint8_t *buffer, int size, F4MManifest *manifest) ++{ ++ node_t * doc; ++ node_t * root_node; ++ int ret; ++ ++ doc = roxml_load_buf(buffer); ++ if(!doc) { ++ return -1; ++ } ++ ++ doc = roxml_get_root(doc); ++ root_node = roxml_get_chld(doc, NULL, 0); ++ if(!root_node) { ++ av_log(NULL, AV_LOG_ERROR, "f4mmanifest Root element not found \n"); ++ roxml_close(doc); ++ return -1; ++ } ++ const char * root_node_name = roxml_get_name(root_node, NULL, 0); ++ 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); ++ roxml_close(doc); ++ return -1; ++ } ++ ++ ret = f4m_parse_manifest_node(root_node, manifest); ++ roxml_close(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; ++} +--- a/libavformat/f4mmanifest.h 1970-01-01 01:00:00.000000000 +0100 ++++ b/libavformat/f4mmanifest.h 2023-02-14 17:27:17.200274887 +0100 +@@ -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]; ++ int64_t duration; ++ ++ 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); +--- a/libavformat/flvdec.c 2018-11-18 01:58:00.000000000 +0100 ++++ b/libavformat/flvdec.c 2023-02-14 17:27:17.200274887 +0100 +@@ -368,6 +368,11 @@ static int amf_get_string(AVIOContext *i + return length; + } + ++int flv_amf_get_string(AVIOContext *ioc, char *buffer, int buffsize) ++{ ++ return amf_get_string(ioc, buffer, buffsize); ++} ++ + static int parse_keyframes_index(AVFormatContext *s, AVIOContext *ioc, int64_t max_pos) + { + FLVContext *flv = s->priv_data; +--- a/libavformat/flv.h 2017-12-31 23:35:49.000000000 +0100 ++++ b/libavformat/flv.h 2023-02-14 17:27:17.200274887 +0100 +@@ -135,4 +135,6 @@ typedef enum { + AMF_DATA_TYPE_UNSUPPORTED = 0x0d, + } AMFDataType; + ++int flv_amf_get_string(AVIOContext *ioc, char *buffer, int buffsize); ++ + #endif /* AVFORMAT_FLV_H */ +--- a/libavformat/flvtag.c 1970-01-01 01:00:00.000000000 +0100 ++++ b/libavformat/flvtag.c 2023-02-14 17:27:17.200274887 +0100 +@@ -0,0 +1,408 @@ ++/* ++ * Adobe FLV Tag Parser ++ * Copyright (c) 2013 Cory McCarthy ++ * Copyright (c) 2014 martii ++ * ++ * 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" ++#include "libavcodec/get_bits.h" ++#include "libavutil/channel_layout.h" ++#include "flv.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 cts; ++} 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 & FLV_AUDIO_CODECID_MASK; ++ header->sound_rate = (byte >> FLV_AUDIO_SAMPLERATE_OFFSET) & 0x03; ++ header->sound_size = (byte >> FLV_AUDIO_SAMPLESSIZE_OFFSET) & 0x01; ++ header->sound_type = byte & 0x01; ++ ++ if(header->sound_format == FLV_CODECID_AAC) { ++ 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 != FLV_CODECID_AAC) { ++ av_log(NULL, AV_LOG_ERROR, "flvtag Unhandled sound format %d\n", header->sound_format >> FLV_AUDIO_CODECID_OFFSET); ++ return 0; ++ } ++ ++ if(header->aac_packet_type == 0) { ++ av_log(NULL, AV_LOG_DEBUG, "flvtag skipped aac_packet_type %d\n", header->aac_packet_type); ++ return 0; ++ } ++ ++ if(header->aac_packet_type != 1) { ++ av_log(NULL, AV_LOG_ERROR, "flvtag Unhandled aac_packet_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) { ++ av_free(sample); ++ 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; ++ header->codec_id = byte & 0x0F; ++ ++ if(header->codec_id == 0x07) { ++ header->avc_packet_type = avio_r8(in); ++ header->cts = (avio_rb24(in) + 0xff800000) ^ 0xff800000; ++ 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; ++ int i, ret = 0; ++ ++ if(header->frame_type == 0x05) { ++ avio_r8(in); ++ return 1; ++ } ++ ++ if(header->codec_id != FLV_CODECID_H264) { ++ 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) { // AVC NALU ++ 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) { ++ av_free(sample); ++ 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) { ++ int length_size = body->length_size_minus_one + 1; ++ uint32_t nal_size = 0; ++ *p++ = 0x00; ++ *p++ = 0x00; ++ *p++ = 0x00; ++ *p++ = 0x01; ++ ++ while (length_size) { ++ nal_size <<= 8; ++ nal_size |= avio_r8(in); ++ length_size--; ++ ret++; ++ } ++ ++ 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, 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(!avio_feof(in)) { ++ byte = avio_r8(in); ++ filter = byte & 0x20; ++ tag_type = (byte & 0x01F); ++ ++ data_size = avio_rb24(in); ++ ++ dts = avio_rb24(in); ++ dts |= avio_r8(in) << 24; ++ ++ 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 == FLV_TAG_TYPE_AUDIO) { ++ data_size -= flv_tag_parse_audio_header(in, &audio_header); ++ } else if(tag_type == FLV_TAG_TYPE_VIDEO) { ++ data_size -= flv_tag_parse_video_header(in, &video_header); ++ } ++ ++#if 0 ++ if(filter) { ++ //EncryptionTagHeader ++ //FilterParams ++ } ++#endif ++ ++ sample = NULL; ++ ++ if(tag_type == FLV_TAG_TYPE_AUDIO) { ++ 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 == FLV_TAG_TYPE_VIDEO) { ++ if((ret = flv_tag_parse_video_body(in, data_size, ++ &video_header, &video_body, &sample)) < 0) { ++ ++ av_free(in); ++ return ret; ++ } ++ data_size -= ret; ++ } ++#if 0 ++ else if(tag_type == 18) { ++ //ScriptData ++ } ++#endif ++ ++ if(sample) { ++ sample->dts = dts; ++ if (tag_type == FLV_TAG_TYPE_VIDEO) ++ sample->pts = dts + video_header.cts; ++ else ++ sample->pts = AV_NOPTS_VALUE; ++ samples[nb_samples++] = sample; ++ } ++ ++ if(data_size) { ++ 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); ++} +--- a/libavformat/flvtag.h 1970-01-01 01:00:00.000000000 +0100 ++++ b/libavformat/flvtag.h 2023-02-14 17:27:17.200274887 +0100 +@@ -0,0 +1,40 @@ ++/* ++ * 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; ++ uint64_t dts; ++ uint64_t pts; ++ int data_size; ++ uint8_t *data; ++} FLVMediaSample; ++ ++int ff_decode_flv_body(uint8_t *buffer, int buffer_size, ++ FLVMediaSample **samples, int *nb_samples_out); +--- a/libavformat/hdsdec.c 1970-01-01 01:00:00.000000000 +0100 ++++ b/libavformat/hdsdec.c 2023-02-14 17:27:17.200274887 +0100 +@@ -0,0 +1,1036 @@ ++/* ++ * Adobe HTTP Dynamic Streaming (HDS) demuxer ++ * Copyright (c) 2013 Cory McCarthy ++ * Copyright (c) 2014 martii ++ * ++ * 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 ++ */ ++ ++#include "avformat.h" ++#include "internal.h" ++#include "url.h" ++#include "avio_internal.h" ++#include "libavutil/avstring.h" ++#include "libavutil/parseutils.h" ++#include "libavutil/opt.h" ++#include "libavutil/dict.h" ++#include "libavutil/time.h" ++ ++#include "amfmetadata.h" ++#include "f4mmanifest.h" ++#include "f4fbox.h" ++#include "flvtag.h" ++ ++#include ++#include ++#include ++#include ++ ++#define MAX_NB_SAMPLES 1024 ++ ++struct HDSDownloadThreadData ++{ ++ pthread_mutex_t mutex; // protects access to this struct ++ pthread_t thread; // download thread id ++ sem_t *to_thread; // send signal to thread ++ sem_t *to_caller; // send signal to caller ++ sem_t _to_thread; // send signal to thread ++ sem_t _to_caller; // send signal to caller ++ char *cookies; // cookies, if any ++ char *url; // current url ++ int run; // thread will run until == 0 ++ int abort; // setting this to !=0 aborts download ++ uint8_t *buffer; // set by thread, unset by thread or caller ++ uint32_t buflen; // set by thread, unset by thread or caller ++ AVIOInterruptCB *interrupt_callback; ++ AVIOInterruptCB *abort_callback; ++ AVIOInterruptCB download_abort_callback; ++}; ++ ++typedef struct HDSBootstrapInfo { ++ char id[MAX_URL_SIZE]; ++ char url[MAX_URL_SIZE]; ++ char profile[MAX_URL_SIZE]; ++ char *quality; ++ ++ F4FBox box; ++} HDSBootstrapInfo; ++ ++typedef struct HDSMedia { ++ int media_index; ++ int bitrate; ++ char url[MAX_URL_SIZE]; ++ HDSBootstrapInfo *bootstrap_info; ++ ++ AVStream *audio_stream; ++ AVStream *video_stream; ++ ++ int nb_samples; ++ FLVMediaSample *samples[MAX_NB_SAMPLES]; ++ int sample_index; ++ ++ unsigned int nb_fragments_read; ++ ++ struct HDSDownloadThreadData download_data; ++} HDSMedia; ++ ++typedef struct HDSContext { ++ char id[MAX_URL_SIZE]; ++ char base_url[MAX_URL_SIZE]; ++ int is_live; ++ int last_media_index; ++ ++ int nb_bootstraps; ++ HDSBootstrapInfo *bootstrap_info[MAX_NB_BOOTSTRAPS]; ++ ++ int nb_media; ++ HDSMedia *media[MAX_NB_MEDIA]; ++ ++ int64_t seek_timestamp; ++ char *cookies; ++} 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 = NULL; ++ AVDictionary *opts = NULL; ++ 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); ++ } ++ ++ av_dict_set(&opts, "cookies", c->cookies, 0); ++ ret = ffurl_open(&puc, url, AVIO_FLAG_READ, &s->interrupt_callback, &opts); ++ av_dict_free(&opts); ++ ++ if(ret < 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+AV_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 download bootstrap, ret: %d\n", ret); ++ av_free(buffer); ++ return ret; ++ } ++ ++ if (c->cookies) ++ av_freep(&c->cookies); ++ av_opt_get(puc->priv_data, "cookies", 0, (uint8_t **) &c->cookies); ++ if (c->cookies && !strlen(c->cookies)) ++ av_freep(&c->cookies); ++ ++ 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; ++ else ++ av_free(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)); ++ ++ buffer = f4m_bootstrap_info->metadata; ++ buffer_size = f4m_bootstrap_info->metadata_size; ++ ++ if(buffer_size > 0) { ++ 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); ++ return ret; ++ } ++ } ++ ++ return 0; ++} ++ ++static int create_streams(AVFormatContext *s, HDSMedia *media, AMFMetadata *metadata, int i) ++{ ++ if (metadata->video_codec_id) { ++ AVStream *st = avformat_new_stream(s, NULL); ++ if(!st) ++ return AVERROR(ENOMEM); ++ ++ media->video_stream = st; ++ avpriv_set_pts_info(st, 32, 1, 1000); ++ st->discard = AVDISCARD_ALL; ++ st->id = 0 + 2 * i; ++ 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; ++ } ++ ++ if (metadata->audio_codec_id) { ++ AVStream *st = avformat_new_stream(s, NULL); ++ if(!st) ++ return AVERROR(ENOMEM); ++ ++ media->audio_stream = st; ++ avpriv_set_pts_info(st, 32, 1, 1000); ++ st->discard = AVDISCARD_ALL; ++ st->id = 1 + 2 * i; ++ st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; ++ st->codecpar->codec_id = metadata->audio_codec_id; ++ st->codecpar->channels = metadata->nb_audio_channels; ++ st->codecpar->channel_layout = (st->codecpar->channels == 2) ? AV_CH_LAYOUT_STEREO : AV_CH_LAYOUT_MONO; ++ st->codecpar->sample_rate = metadata->audio_sample_rate; ++ st->codecpar->sample_fmt = AV_SAMPLE_FMT_S16; ++ st->codecpar->bit_rate = metadata->audio_data_rate * 1000; ++ st->need_parsing = metadata->audio_stream_need_parsing; ++ } ++ ++ return 0; ++} ++ ++static int create_media(AVFormatContext *s, F4MMedia *f4m_media, int i) ++{ ++ HDSContext *c = s->priv_data; ++ HDSMedia *media; ++ AMFMetadata metadata; ++ int ret, j; ++ ++ media = av_mallocz(sizeof(HDSMedia)); ++ if(!media) ++ return AVERROR(ENOMEM); ++ ++ c->media[c->nb_media++] = media; ++ ++ media->media_index = i; ++ media->bitrate = f4m_media->bitrate; ++ memcpy(media->url, f4m_media->url, sizeof(media->url)); ++ ++ for(j = 0; j < c->nb_bootstraps; j++) { ++ if(!av_strcasecmp(f4m_media->bootstrap_info_id, c->bootstrap_info[j]->id)) ++ continue; ++ media->bootstrap_info = c->bootstrap_info[j]; ++ break; ++ } ++ ++ memset(&metadata, 0x00, sizeof(AMFMetadata)); ++ metadata.nb_audio_channels = 1; ++ 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; ++ } ++ ++ return create_streams(s, media, &metadata, i); ++} ++ ++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("%d kbit/s", media->bitrate), 0); ++ ++ if (media->video_stream) ++ av_program_add_stream_index(s, p->id, media->video_stream->index); ++ if (media->audio_stream) ++ av_program_add_stream_index(s, p->id, media->audio_stream->index); ++ } ++ ++ return 0; ++} ++ ++static void download_thread_start(AVFormatContext *s, HDSMedia *media); ++ ++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, i)) < 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; ++ ++ s->duration = manifest->duration; ++ c->seek_timestamp = AV_NOPTS_VALUE; ++ ++ for(i = 0; i < c->nb_media; i++) ++ download_thread_start(s, c->media[i]); ++ ++ return 0; ++} ++ ++//#define HDS_ENABLE_LOG_CALLBACK ++#ifdef HDS_ENABLE_LOG_CALLBACK ++static void log_callback(void *ptr __attribute__ ((unused)), int lvl __attribute__ ((unused)), const char *format, va_list ap) ++{ ++ vfprintf(stderr, format, ap); ++} ++#endif ++ ++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; ++ int ret; ++#ifdef HDS_ENABLE_LOG_CALLBACK ++ av_log_set_callback(log_callback); ++#endif ++ ++ p = av_stristr(s->filename, "manifest.f4m"); ++ if(!p) { ++ av_log(NULL, AV_LOG_ERROR, "hds \"manifest.f4m\" is not a substring of \"%s\"\n", s->filename); ++ return -1; ++ } ++ av_strlcpy(c->base_url, s->filename, p - s->filename + 1); ++ ++ filesize = avio_size(in); ++ if(filesize <= 0) ++ return -1; ++ ++ buf = av_mallocz(filesize*sizeof(uint8_t)); ++ if(!buf) ++ return AVERROR(ENOMEM); ++ ++ avio_read(in, buf, filesize); ++ ++ memset(&manifest, 0x00, sizeof(F4MManifest)); ++ ret = ff_parse_f4m_manifest(buf, filesize, &manifest); ++ ++ av_free(buf); ++ ++ if (ret > -1) ++ ret = initialize_context(s, &manifest); ++ ++ ff_free_manifest(&manifest); ++ ++ return ret; ++} ++ ++static void construct_fragment_url(const char *base_url, const char *media_url, ++ unsigned int segment, unsigned 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%u-Frag%u", segment, fragment); ++ p += av_strlcat(p, fragment_str, url_size); ++ av_free(fragment_str); ++ ++ p += av_strlcat(p, suffix, url_size); ++} ++ ++static int get_fragment_offset(HDSMedia *media, int64_t timestamp) ++{ ++ F4FBootstrapInfoBox *abst = &(media->bootstrap_info->box.abst); ++ int fragments_max = 0; ++ int i, j; ++ ++ for(i = 0; i < abst->nb_segment_run_table_boxes; i++) { ++ F4FSegmentRunTableBox *asrt = abst->segment_run_table_boxes[i]; ++ int found = 0; ++ if (asrt->nb_quality_entries && media->bootstrap_info->quality) ++ for(j = 0; !found && j < asrt->nb_quality_entries; j++) ++ found = !strcmp(asrt->quality_entries[j], media->bootstrap_info->quality); ++ else ++ found = 1; ++ ++ if (found) { ++ F4FSegmentRunEntry *segment_entry = asrt->segment_run_entries[asrt->nb_segment_run_entries - 1]; ++ fragments_max = segment_entry->fragments_per_segment; ++ break; ++ } ++ } ++ ++ for(i = 0; i < abst->nb_fragment_run_table_boxes; i++) { ++ int found = 0; ++ F4FFragmentRunTableBox *afrt = abst->fragment_run_table_boxes[i]; ++ if (afrt->nb_quality_entries && media->bootstrap_info->quality) ++ for(j = 0; !found && j < afrt->nb_quality_entries; j++) ++ found = !strcmp(afrt->quality_entries[j], media->bootstrap_info->quality); ++ else ++ found = 1; ++ ++ if (found) { ++ for(j = 0; j < afrt->nb_fragment_run_entries; j++) { ++ F4FFragmentRunEntry *fre = afrt->fragment_run_entries[j]; ++ int fragcount; ++ if (j + 1 < afrt->nb_fragment_run_entries) { ++ fragcount = afrt->fragment_run_entries[j + 1]->first_fragment - fre->first_fragment; ++ fragments_max -= fragcount; ++ } else ++ fragcount = fragments_max; ++ ++ if (timestamp >= fre->first_fragment_time_stamp && timestamp <= fre->first_fragment_time_stamp + fragcount * fre->fragment_duration) ++ return fre->first_fragment + (timestamp - fre->first_fragment_time_stamp)/fre->fragment_duration; ++ } ++ break; ++ } ++ } ++ return 0; ++} ++ ++static int get_segment_fragment(int is_live, HDSMedia *media, unsigned int *segment_out, unsigned int *fragment_out) ++{ ++ F4FBootstrapInfoBox *abst = &(media->bootstrap_info->box.abst); ++ unsigned int segment = ~0, fragment = ~0, fragments_max = 0; ++ int i, j, skip; ++ ++ if (is_live) { ++ // FIXME. This is a crude hack. ++ *segment_out = 1; ++ *fragment_out = media->nb_fragments_read; ++ return 0; ++ } ++ ++ skip = media->nb_fragments_read; ++ for(i = 0; segment == ~0 && i < abst->nb_segment_run_table_boxes; i++) { ++ F4FSegmentRunTableBox *asrt = abst->segment_run_table_boxes[i]; ++ int found = 0; ++ if (asrt->nb_quality_entries && media->bootstrap_info->quality) ++ for(j = 0; !found && j < asrt->nb_quality_entries; j++) ++ found = !strcmp(asrt->quality_entries[j], media->bootstrap_info->quality); ++ else ++ found = 1; ++ ++ if (found) { ++ for (j = 0; segment == ~0 && j < asrt->nb_segment_run_entries; j++) { ++ F4FSegmentRunEntry *segment_entry = asrt->segment_run_entries[j]; ++ ++ if (segment_entry->fragments_per_segment < skip) { ++ skip -= segment_entry->fragments_per_segment; ++ fragments_max = segment_entry->fragments_per_segment; ++ } else ++ segment = segment_entry->first_segment; ++ } ++ break; ++ } ++ } ++ ++ if(segment == ~0) { ++ av_log(NULL, AV_LOG_ERROR, "hds segment entry for next fragment (%u) not found, skip: %d\n", media->nb_fragments_read, skip); ++ return 0; ++ } ++ ++ skip = media->nb_fragments_read; ++ for(i = 0; fragment == ~0 && i < abst->nb_fragment_run_table_boxes; i++) { ++ int found = 0; ++ F4FFragmentRunTableBox *afrt = abst->fragment_run_table_boxes[i]; ++ if (afrt->nb_quality_entries && media->bootstrap_info->quality) ++ for(j = 0; !found && j < afrt->nb_quality_entries; j++) ++ found = !strcmp(afrt->quality_entries[j], media->bootstrap_info->quality); ++ else ++ found = 1; ++ ++ if (found) { ++ for(j = 0; fragment == ~0 && j < afrt->nb_fragment_run_entries; j++) { ++ int fragcount; ++ F4FFragmentRunEntry *fre = afrt->fragment_run_entries[j]; ++ if (j + 1 < afrt->nb_fragment_run_entries) { ++ fragcount = afrt->fragment_run_entries[j + 1]->first_fragment - fre->first_fragment; ++ fragments_max -= fragcount; ++ } else ++ fragcount = fragments_max; ++ ++ if (fragcount < skip) ++ fragcount -= skip; ++ else { ++ fragment = fre->first_fragment + skip; ++ skip = 0; ++ } ++ } ++ break; ++ } ++ } ++ ++ if (!is_live && skip > 0) { ++ av_log(NULL, AV_LOG_ERROR, "hds fragment %d fragments beyond EOF\n", skip); ++ return AVERROR_EOF; ++ } ++ ++ if(fragment == ~0) { ++ av_log(NULL, AV_LOG_ERROR, "hds fragment entry not found\n"); ++ return -1; ++ } ++ ++ if(segment_out) ++ *segment_out = segment; ++ if(fragment_out) ++ *fragment_out = fragment; ++ ++ return 0; ++} ++ ++static int download_abort_callback_function(void *opaque) ++{ ++ HDSMedia *media = (HDSMedia *) opaque; ++ return media->download_data.abort || ff_check_interrupt(media->download_data.interrupt_callback); ++} ++ ++static void *download_thread(void *opaque) ++{ ++ HDSMedia *media = (HDSMedia *) opaque; ++ ++ while (media->download_data.run && !download_abort_callback_function(media)) { ++ AVDictionary *opts = NULL; ++ URLContext *puc = NULL; ++ uint8_t *buffer = NULL; ++ int buffer_size = 0, ret; ++ char *url = NULL; ++ int url_len; ++ int tries_left = 15; ++ ++ if (sem_wait(media->download_data.to_thread)) ++ break; ++ if (!media->download_data.run) ++ continue; ++ ++ pthread_mutex_lock(&media->download_data.mutex); ++ media->download_data.abort = 0; ++ if (media->download_data.buffer) ++ av_freep(&media->download_data.buffer); ++ media->download_data.buflen = 0; ++ // generate local copies from HDSDownloadThreadData ++ if (media->download_data.cookies) ++ av_dict_set(&opts, "cookies", media->download_data.cookies, 0); ++ url_len = strlen(media->download_data.url) + 1; ++ url = alloca(url_len); ++ strncpy(url, media->download_data.url, url_len); ++ pthread_mutex_unlock(&media->download_data.mutex); ++ //av_log(NULL, AV_LOG_DEBUG, "%s %d: downloading %s\n", __FILE__,__LINE__, url); ++ ++ // initiate download ++ do { ++ ret = ffurl_open(&puc, url, AVIO_FLAG_READ, &media->download_data.download_abort_callback, &opts); ++ if (ret < 0) { ++ sleep(1); ++ if (download_abort_callback_function(media)) ++ break; ++ sleep(1); ++ tries_left--; ++ } ++ } while (ret < 0 && media->download_data.run && !download_abort_callback_function(media) && tries_left > 0); ++ ++ if (opts) ++ av_dict_free(&opts); ++ ++ if(ret < 0) { ++ av_log(NULL, AV_LOG_ERROR, "hds Failed to start downloading url:%s, ret:%d\n", url, ret); ++ } else { ++ buffer_size = ffurl_size(puc); ++ if (buffer_size > -1) ++ buffer = av_mallocz(buffer_size+AV_INPUT_BUFFER_PADDING_SIZE); ++ if(!buffer) ++ av_log(NULL, AV_LOG_DEBUG, "hds Failed to allocate %d bytes buffer\n", buffer_size); ++ } ++ ++ if(buffer && (ret = ffurl_read_complete(puc, buffer, buffer_size)) < 0) { ++ av_log(NULL, AV_LOG_ERROR, "hds Failed to download fragment, ret: %d\n", ret); ++ av_freep(&buffer); ++ } ++ ++ pthread_mutex_lock(&media->download_data.mutex); ++ if (media->download_data.abort || !buffer) { ++ media->download_data.abort = 0; ++ } else { ++ //av_log(NULL, AV_LOG_DEBUG, "%s %d: downloaded %s\n", __FILE__,__LINE__, media->download_data.url); ++ if (media->download_data.cookies) ++ av_freep(&media->download_data.cookies); ++ av_opt_get(puc->priv_data, "cookies", 0, (uint8_t **) &media->download_data.cookies); ++ if (media->download_data.cookies && !strlen(media->download_data.cookies)) ++ av_freep(&media->download_data.cookies); ++ media->download_data.buffer = buffer; ++ media->download_data.buflen = buffer_size; ++ } ++ sem_post(media->download_data.to_caller); // confirm download ++ pthread_mutex_unlock(&media->download_data.mutex); ++ if (puc) ++ ffurl_close(puc); ++ } ++ pthread_exit(NULL); ++} ++ ++static void download_thread_start(AVFormatContext *s, HDSMedia *media) ++{ ++#if defined(__APPLE__) ++ char buf[40]; ++#endif ++ pthread_mutex_init(&media->download_data.mutex, NULL); ++#if defined(__APPLE__) // no unnamed semaphores on darwin ++ snprintf(buf, sizeof(buf), "sem_to_thread%d", media->media_index); ++ media->download_data.to_thread = sem_open(buf, O_CREAT, 0644, 0); ++ snprintf(buf, sizeof(buf), "sem_to_caller%d", media->media_index); ++ media->download_data.to_caller = sem_open(buf, O_CREAT, 0644, 0); ++#else ++ sem_init(&media->download_data._to_thread, 0, 0); ++ sem_init(&media->download_data._to_caller, 0, 0); ++ media->download_data.to_thread = &media->download_data._to_thread; ++ media->download_data.to_caller = &media->download_data._to_caller; ++#endif ++ media->download_data.thread = 0; ++ media->download_data.run = 1; ++ media->download_data.abort = 0; ++ media->download_data.url = NULL; ++ media->download_data.buffer = NULL; ++ media->download_data.buflen = 0; ++ media->download_data.cookies = NULL; ++ media->download_data.interrupt_callback = &s->interrupt_callback; ++ media->download_data.download_abort_callback.callback = download_abort_callback_function; ++ media->download_data.download_abort_callback.opaque = media; ++ media->download_data.abort_callback = &media->download_data.download_abort_callback; ++ if (pthread_create(&media->download_data.thread, NULL, download_thread, media)) ++ av_log(NULL, AV_LOG_ERROR, "hds: creating download thread failed\n"); ++} ++ ++static void download_thread_stop(HDSMedia *media) ++{ ++ if (media->download_data.thread) { ++#if defined(__APPLE__) ++ char buf[40]; ++#endif ++ media->download_data.run = 0; ++ media->download_data.abort = 1; ++ sem_post(media->download_data.to_thread); ++ pthread_join(media->download_data.thread, NULL); ++ media->download_data.thread = 0; ++ if (media->download_data.url) ++ av_freep(&media->download_data.url); ++ if (media->download_data.cookies) ++ av_freep(&media->download_data.cookies); ++ if (media->download_data.buffer) ++ av_freep(&media->download_data.buffer); ++ media->download_data.buflen = 0; ++#if defined(__APPLE__) ++ sem_close(media->download_data.to_thread); ++ sem_close(media->download_data.to_caller); ++ snprintf(buf, sizeof(buf), "sem_to_thread%d", media->media_index); ++ sem_unlink(buf); ++ snprintf(buf, sizeof(buf), "sem_to_caller%d", media->media_index); ++ sem_unlink(buf); ++#else ++ sem_destroy(&media->download_data._to_thread); ++ sem_destroy(&media->download_data._to_caller); ++#endif ++ pthread_mutex_destroy(&media->download_data.mutex); ++ } ++} ++ ++static int download_fragment(AVFormatContext *s, HDSMedia *media, uint8_t **buffer_out, int *buffer_size_out) ++{ ++ HDSContext *c = s->priv_data; ++ char url[MAX_URL_SIZE]; ++ unsigned int segment, fragment; ++ int ret; ++ ++ if((ret = get_segment_fragment(c->is_live, media, &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); ++ ++ pthread_mutex_lock(&media->download_data.mutex); ++ ++ if (!media->download_data.cookies) ++ media->download_data.cookies = av_strdup(c->cookies); ++ if (media->download_data.url && strcmp(media->download_data.url, url)) { ++ // download in progress or finished, but not the wanted one. abort it. ++ media->download_data.abort = 1; // initiate abort ++ pthread_mutex_unlock(&media->download_data.mutex); ++ sem_wait(media->download_data.to_caller); // wait until current transfer has finished ++ pthread_mutex_lock(&media->download_data.mutex); ++ av_freep(&media->download_data.url); ++ } ++ if (!media->download_data.url) { ++ // queue retrieval of wanted url ++ media->download_data.url = av_strdup(url); ++ sem_post(media->download_data.to_thread); // initiate download ++ } ++ if (media->download_data.url && !strcmp(media->download_data.url, url)) { ++ // download matches what we want ++ pthread_mutex_unlock(&media->download_data.mutex); ++ sem_wait(media->download_data.to_caller); // wait until finished ++ pthread_mutex_lock(&media->download_data.mutex); ++ av_freep(&media->download_data.url); ++ if (media->download_data.buffer) { ++ *buffer_out = media->download_data.buffer; ++ *buffer_size_out = media->download_data.buflen; ++ media->download_data.buffer = NULL; ++ media->download_data.buflen = 0; ++ ret = 0; ++ media->nb_fragments_read++; ++ //initiate retrieval of next url ++ if(!get_segment_fragment(c->is_live, media, &segment, &fragment)) ++ 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); ++ media->download_data.url = av_strdup(url); ++ sem_post(media->download_data.to_thread); // initiate download ++ } else { ++ // finished but failed ++ ret = AVERROR(EIO); ++ } ++ } ++ pthread_mutex_unlock(&media->download_data.mutex); ++ return ret; ++} ++ ++static int get_next_fragment(AVFormatContext *s, HDSMedia *media) ++{ ++ F4FBox box; ++ uint8_t *buffer = NULL; ++ int buffer_size = 0, ret; ++ ++ if (ff_check_interrupt(media->download_data.interrupt_callback)) ++ return AVERROR(EIO); ++ ++ if((ret = download_fragment(s, 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->dts; ++ pkt->pts = sample->pts; ++ ++ 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, HDSMedia *media, AVPacket *pkt) ++{ ++ HDSContext *c = s->priv_data; ++ int ret; ++ ++ if (c->is_live && !media->nb_fragments_read) { ++ int64_t ts = media->bootstrap_info->box.abst.current_media_time; ++ media->nb_fragments_read = get_fragment_offset(media, ts); ++ if (media->nb_fragments_read > 1) ++ media->nb_fragments_read--; ++ } ++ ++ if (c->seek_timestamp != AV_NOPTS_VALUE) { ++ int64_t ts = c->seek_timestamp; ++ c->seek_timestamp = AV_NOPTS_VALUE; ++ media->nb_fragments_read = get_fragment_offset(media, ts); ++ clear_samples(media); ++ } ++ ++ if(media->nb_samples == 0) { ++ if((ret = get_next_fragment(s, 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; ++ HDSMedia *media = NULL; ++ int i, ret; ++ ++ for (i = c->last_media_index + 1; !media && i < c->nb_media; i++) { ++ media = c->media[i]; ++ if ((!media->video_stream || (media->video_stream->discard == AVDISCARD_ALL)) ++ && (!media->audio_stream || (media->audio_stream->discard == AVDISCARD_ALL))) ++ media = NULL; ++ } ++ for (i = 0; !media && i < c->nb_media; i++) { ++ media = c->media[i]; ++ if ((!media->video_stream || (media->video_stream->discard == AVDISCARD_ALL)) ++ && (!media->audio_stream || (media->audio_stream->discard == AVDISCARD_ALL))) ++ media = NULL; ++ } ++ c->last_media_index = i; ++ ++ if (media && !media->bootstrap_info && c->nb_bootstraps) ++ media->bootstrap_info = c->bootstrap_info[0]; ++ ++ if (!media || !media->bootstrap_info) { ++ av_log(NULL, AV_LOG_ERROR, "hds Failed to find valid stream\n"); ++ return AVERROR(EIO); ++ } ++ ++ if((ret = get_next_packet(s, media, pkt)) < 0) { ++ if(ret != AVERROR_EOF) ++ 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_media; i++) ++ download_thread_stop(c->media[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]; ++ ++ if (media->download_data.buffer) ++ av_freep(&media->download_data.buffer); ++ ++ clear_samples(media); ++ av_freep(&media); ++ } ++ ++ if(c->cookies) ++ av_freep(&c->cookies); ++ ++ 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; ++} ++ ++static int hds_read_seek(AVFormatContext *s, int stream_index, ++ int64_t timestamp, int flags) ++{ ++ HDSContext *c = s->priv_data; ++ ++ if (flags & AVSEEK_FLAG_BYTE) ++ return AVERROR(ENOSYS); ++ if (s->duration < c->seek_timestamp) { ++ c->seek_timestamp = AV_NOPTS_VALUE; ++ return AVERROR(EIO); ++ } ++ c->seek_timestamp = timestamp; ++ ++ if (c->is_live) { ++ int i; ++ for(i = 0; i < c->nb_media; i++) ++ c->media[i]->nb_fragments_read = 0; ++ c->seek_timestamp = AV_NOPTS_VALUE; ++ return 0; ++ } ++ ++ 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, ++ .read_seek = hds_read_seek, ++}; +--- a/libavformat/Makefile 2018-11-18 01:58:00.000000000 +0100 ++++ b/libavformat/Makefile 2023-02-14 17:27:17.200274887 +0100 +@@ -200,6 +200,7 @@ OBJS-$(CONFIG_H263_DEMUXER) + OBJS-$(CONFIG_H263_MUXER) += rawenc.o + OBJS-$(CONFIG_H264_DEMUXER) += h264dec.o rawdec.o + OBJS-$(CONFIG_H264_MUXER) += rawenc.o ++OBJS-$(CONFIG_HDS_DEMUXER) += hdsdec.o amfmetadata.o f4mmanifest.o f4fbox.o flvtag.o + OBJS-$(CONFIG_HASH_MUXER) += hashenc.o + OBJS-$(CONFIG_HDS_MUXER) += hdsenc.o + OBJS-$(CONFIG_HEVC_DEMUXER) += hevcdec.o rawdec.o diff --git a/archive-patches/ffmpeg-cst/3.3.9/0002-ffmpeg-aac-3.x.patch b/archive-patches/ffmpeg-cst/3.3.9/0002-ffmpeg-aac-3.x.patch new file mode 100644 index 0000000..54b837d --- /dev/null +++ b/archive-patches/ffmpeg-cst/3.3.9/0002-ffmpeg-aac-3.x.patch @@ -0,0 +1,53 @@ +--- a/libavcodec/aacdec_template.c 2018-11-18 01:57:59.000000000 +0100 ++++ b/libavcodec/aacdec_template.c 2023-02-14 17:54:57.904729510 +0100 +@@ -2414,7 +2414,7 @@ static int decode_extension_payload(AACC + * @param decode 1 if tool is used normally, 0 if tool is used in LTP. + * @param coef spectral coefficients + */ +-static void apply_tns(INTFLOAT coef_param[1024], TemporalNoiseShaping *tns, ++static __attribute__((optimize(0))) void apply_tns(INTFLOAT coef_param[1024], TemporalNoiseShaping *tns, + IndividualChannelStream *ics, int decode) + { + const int mmm = FFMIN(ics->tns_max_bands, ics->max_sfb); +--- a/libavcodec/aacps.c 2018-11-18 01:57:59.000000000 +0100 ++++ b/libavcodec/aacps.c 2023-02-14 17:52:02.998962048 +0100 +@@ -654,7 +654,7 @@ static void map_val_20_to_34(INTFLOAT pa + par[ 1] = AAC_HALF_SUM(par[ 0], par[ 1]); + } + +-static void decorrelation(PSContext *ps, INTFLOAT (*out)[32][2], const INTFLOAT (*s)[32][2], int is34) ++static void __attribute__((optimize(0))) decorrelation(PSContext *ps, INTFLOAT (*out)[32][2], const INTFLOAT (*s)[32][2], int is34) + { + LOCAL_ALIGNED_16(INTFLOAT, power, [34], [PS_QMF_TIME_SLOTS]); + LOCAL_ALIGNED_16(INTFLOAT, transient_gain, [34], [PS_QMF_TIME_SLOTS]); +--- a/libavcodec/fft_template.c 2018-11-18 01:57:59.000000000 +0100 ++++ b/libavcodec/fft_template.c 2023-02-14 17:52:02.998962048 +0100 +@@ -475,7 +475,7 @@ static void fft##n(FFTComplex *z)\ + pass(z,FFT_NAME(ff_cos_##n),n4/2);\ + } + +-static void fft4(FFTComplex *z) ++static void __attribute__((optimize(0))) fft4(FFTComplex *z) + { + FFTDouble t1, t2, t3, t4, t5, t6, t7, t8; + +@@ -489,7 +489,7 @@ static void fft4(FFTComplex *z) + BF(z[2].im, z[0].im, t2, t5); + } + +-static void fft8(FFTComplex *z) ++static void __attribute__((optimize(0))) fft8(FFTComplex *z) + { + FFTDouble t1, t2, t3, t4, t5, t6; + +--- a/libavcodec/mdct_template.c 2018-07-17 11:27:40.000000000 +0200 ++++ b/libavcodec/mdct_template.c 2023-02-14 17:52:02.998962048 +0100 +@@ -102,7 +102,7 @@ av_cold int ff_mdct_init(FFTContext *s, + * @param output N/2 samples + * @param input N/2 samples + */ +-void ff_imdct_half_c(FFTContext *s, FFTSample *output, const FFTSample *input) ++void __attribute__((optimize(0))) ff_imdct_half_c(FFTContext *s, FFTSample *output, const FFTSample *input) + { + int k, n8, n4, n2, n, j; + const uint16_t *revtab = s->revtab; diff --git a/archive-patches/ffmpeg-cst/3.3.9/0003-ffmpeg-increase-IO-BUFFER-SIZE-to-256k.patch b/archive-patches/ffmpeg-cst/3.3.9/0003-ffmpeg-increase-IO-BUFFER-SIZE-to-256k.patch new file mode 100644 index 0000000..dbd6160 --- /dev/null +++ b/archive-patches/ffmpeg-cst/3.3.9/0003-ffmpeg-increase-IO-BUFFER-SIZE-to-256k.patch @@ -0,0 +1,17 @@ +diff --git a/libavformat/aviobuf.c b/libavformat/aviobuf.c +index c2681ed..711fff8 100644 +--- a/libavformat/aviobuf.c ++++ b/libavformat/aviobuf.c +@@ -33,7 +33,7 @@ + #include "url.h" + #include + +-#define IO_BUFFER_SIZE 32768 ++#define IO_BUFFER_SIZE 262144 + + /** + * Do seeks within this distance ahead of the current buffer by skipping +-- +2.7.4.1.g5468f9e + + diff --git a/archive-patches/ffmpeg-cst/3.3.9/0004-ffmpeg-revert-libavformat-aviobuf-keep-track-of-the-original-buffer-size.patch b/archive-patches/ffmpeg-cst/3.3.9/0004-ffmpeg-revert-libavformat-aviobuf-keep-track-of-the-original-buffer-size.patch new file mode 100644 index 0000000..0ab74bb --- /dev/null +++ b/archive-patches/ffmpeg-cst/3.3.9/0004-ffmpeg-revert-libavformat-aviobuf-keep-track-of-the-original-buffer-size.patch @@ -0,0 +1,55 @@ +--- a/libavformat/aviobuf.c 2018-11-18 01:58:00.000000000 +0100 ++++ b/libavformat/aviobuf.c 2023-02-14 18:10:53.082399288 +0100 +@@ -88,7 +88,6 @@ int ffio_init_context(AVIOContext *s, + int64_t (*seek)(void *opaque, int64_t offset, int whence)) + { + s->buffer = buffer; +- s->orig_buffer_size = + s->buffer_size = buffer_size; + s->buf_ptr = buffer; + s->opaque = opaque; +@@ -536,16 +535,16 @@ static void fill_buffer(AVIOContext *s) + } + + /* make buffer smaller in case it ended up large after probing */ +- if (s->read_packet && s->orig_buffer_size && s->buffer_size > s->orig_buffer_size) { ++ if (s->read_packet && s->buffer_size > max_buffer_size) { + if (dst == s->buffer) { +- int ret = ffio_set_buf_size(s, s->orig_buffer_size); ++ int ret = ffio_set_buf_size(s, max_buffer_size); + if (ret < 0) + av_log(s, AV_LOG_WARNING, "Failed to decrease buffer size\n"); + + s->checksum_ptr = dst = s->buffer; + } +- av_assert0(len >= s->orig_buffer_size); +- len = s->orig_buffer_size; ++ av_assert0(len >= max_buffer_size); ++ len = max_buffer_size; + } + + if (s->read_packet) +@@ -987,7 +986,6 @@ int ffio_set_buf_size(AVIOContext *s, in + + av_free(s->buffer); + s->buffer = buffer; +- s->orig_buffer_size = + s->buffer_size = buf_size; + s->buf_ptr = buffer; + url_resetbuf(s, s->write_flag ? AVIO_FLAG_WRITE : AVIO_FLAG_READ); +--- a/libavformat/avio.h 2018-11-18 01:58:00.000000000 +0100 ++++ b/libavformat/avio.h 2023-02-14 18:05:28.759082028 +0100 +@@ -282,13 +282,6 @@ typedef struct AVIOContext { + int writeout_count; + + /** +- * Original buffer size +- * used internally after probing and ensure seekback to reset the buffer size +- * This field is internal to libavformat and access from outside is not allowed. +- */ +- int orig_buffer_size; +- +- /** + * Threshold to favor readahead over seek. + * This is current internal only, do not use from outside. + */ diff --git a/archive-patches/ffmpeg-cst/3.3.9/0005-ffmpeg-devel-v14-avformat-dashdec-add-dash-demuxer-base-version.patch b/archive-patches/ffmpeg-cst/3.3.9/0005-ffmpeg-devel-v14-avformat-dashdec-add-dash-demuxer-base-version.patch new file mode 100755 index 0000000..46199c8 --- /dev/null +++ b/archive-patches/ffmpeg-cst/3.3.9/0005-ffmpeg-devel-v14-avformat-dashdec-add-dash-demuxer-base-version.patch @@ -0,0 +1,1902 @@ +--- a/configure 2018-11-18 01:57:59.000000000 +0100 ++++ b/configure 2023-02-14 20:19:47.770726018 +0100 +@@ -274,6 +274,7 @@ + --enable-libxcb-shape enable X11 grabbing shape rendering [autodetect] + --enable-libxvid enable Xvid encoding via xvidcore, + native MPEG-4/Xvid encoder exists [no] ++ --enable-libxml2 enable XML parsing using the C library libxml2 [no] + --enable-libzimg enable z.lib, needed for zscale filter [no] + --enable-libzmq enable message passing via libzmq [no] + --enable-libzvbi enable teletext support via libzvbi [no] +@@ -1581,6 +1582,7 @@ + libvpx + libwavpack + libwebp ++ libxml2 + libzimg + libzmq + libzvbi +@@ -2916,6 +2918,7 @@ + caf_demuxer_select="iso_media riffdec" + caf_muxer_select="iso_media" + dash_muxer_select="mp4_muxer" ++dash_demuxer_deps="libxml2" + dirac_demuxer_select="dirac_parser" + dts_demuxer_select="dca_parser" + dtshd_demuxer_select="dca_parser" +@@ -5924,6 +5927,7 @@ + check_lib openssl/ssl.h SSL_library_init -lssl -lcrypto -lws2_32 -lgdi32 || + die "ERROR: openssl not found"; } + enabled qtkit_indev && { check_header_objcc QTKit/QTKit.h || disable qtkit_indev; } ++enabled libxml2 && require_pkg_config libxml-2.0 libxml2/libxml/xmlversion.h xmlCheckVersion + + # libdc1394 check + if enabled libdc1394; then +--- a/libavformat/allformats.c 2018-11-18 01:58:00.000000000 +0100 ++++ b/libavformat/allformats.c 2023-02-14 20:19:47.774726060 +0100 +@@ -96,7 +96,7 @@ + REGISTER_DEMUXER (CINE, cine); + REGISTER_DEMUXER (CONCAT, concat); + REGISTER_MUXER (CRC, crc); +- REGISTER_MUXER (DASH, dash); ++ REGISTER_MUXDEMUX(DASH, dash); + REGISTER_MUXDEMUX(DATA, data); + REGISTER_MUXDEMUX(DAUD, daud); + REGISTER_DEMUXER (DCSTR, dcstr); +--- a/libavformat/dashdec.c 1970-01-01 01:00:00.000000000 +0100 ++++ b/libavformat/dashdec.c 2023-02-14 20:19:47.774726060 +0100 +@@ -0,0 +1,1801 @@ ++/* ++ * Dynamic Adaptive Streaming over HTTP demux ++ * Copyright (c) 2017 samsamsam@o2.pl based on HLS demux ++ * Copyright (c) 2017 Steven Liu ++ * ++ * 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 "libavutil/avstring.h" ++#include "libavutil/intreadwrite.h" ++#include "libavutil/opt.h" ++#include "libavutil/time.h" ++#include "libavutil/parseutils.h" ++#include "internal.h" ++#include "avio_internal.h" ++ ++#define INITIAL_BUFFER_SIZE 32768 ++ ++struct fragment { ++ int64_t url_offset; ++ int64_t size; ++ char *url; ++}; ++ ++/* ++ * reference to : ISO_IEC_23009-1-DASH-2012 ++ * Section: 5.3.9.6.2 ++ * Table: Table 17 — Semantics of SegmentTimeline element ++ * */ ++struct timeline { ++ /* t: Element or Attribute Name ++ * specifies the MPD start time, in @timescale units, ++ * the first Segment in the series starts relative to the beginning of the Period. ++ * The value of this attribute must be equal to or greater than the sum of the previous S ++ * element earliest presentation time and the sum of the contiguous Segment durations. ++ * If the value of the attribute is greater than what is expressed by the previous S element, ++ * it expresses discontinuities in the timeline. ++ * If not present then the value shall be assumed to be zero for the first S element ++ * and for the subsequent S elements, the value shall be assumed to be the sum of ++ * the previous S element's earliest presentation time and contiguous duration ++ * (i.e. previous S@t + @d * (@r + 1)). ++ * */ ++ int64_t t; ++ /* r: Element or Attribute Name ++ * specifies the repeat count of the number of following contiguous Segments with ++ * the same duration expressed by the value of @d. This value is zero-based ++ * (e.g. a value of three means four Segments in the contiguous series). ++ * */ ++ int64_t r; ++ /* d: Element or Attribute Name ++ * specifies the Segment duration, in units of the value of the @timescale. ++ * */ ++ int64_t d; ++}; ++ ++enum DASHTmplUrlType { ++ TMP_URL_TYPE_UNSPECIFIED, ++ TMP_URL_TYPE_NUMBER, ++ TMP_URL_TYPE_TIME, ++}; ++ ++/* ++ * Each playlist has its own demuxer. If it is currently active, ++ * it has an opened AVIOContext too, and potentially an AVPacket ++ * containing the next packet from this stream. ++ */ ++struct representation { ++ char *url_template; ++ enum DASHTmplUrlType tmp_url_type; ++ AVIOContext pb; ++ AVIOContext *input; ++ AVFormatContext *parent; ++ AVFormatContext *ctx; ++ AVPacket pkt; ++ int rep_idx; ++ int rep_count; ++ int stream_index; ++ ++ enum AVMediaType type; ++ int64_t target_duration; ++ ++ int n_fragments; ++ struct fragment **fragments; /* VOD list of fragment for profile */ ++ ++ int n_timelines; ++ struct timeline **timelines; ++ ++ int64_t first_seq_no; ++ int64_t last_seq_no; ++ ++ int64_t fragment_duration; ++ int64_t fragment_timescale; ++ ++ int64_t cur_seq_no; ++ int64_t cur_seg_offset; ++ int64_t cur_seg_size; ++ struct fragment *cur_seg; ++ ++ /* Currently active Media Initialization Section */ ++ struct fragment *init_section; ++ uint8_t *init_sec_buf; ++ uint32_t init_sec_buf_size; ++ uint32_t init_sec_data_len; ++ uint32_t init_sec_buf_read_offset; ++ int fix_multiple_stsd_order; ++ int64_t cur_timestamp; ++}; ++ ++typedef struct DASHContext { ++ const AVClass *class; ++ char *base_url; ++ struct representation *cur_video; ++ struct representation *cur_audio; ++ uint64_t media_presentation_duration_sec; ++ uint64_t suggested_presentation_delay_sec; ++ uint64_t presentation_delay_sec; ++ uint64_t availability_start_time_sec; ++ uint64_t publish_time_sec; ++ uint64_t minimum_update_period_sec; ++ uint64_t time_shift_buffer_depth_sec; ++ uint64_t min_buffer_time_sec; ++ uint64_t period_duration_sec; ++ uint64_t period_start_sec; ++ int is_live; ++ AVIOInterruptCB *interrupt_callback; ++ char *user_agent; ///< holds HTTP user agent set as an AVOption to the HTTP protocol context ++ char *cookies; ///< holds HTTP cookie values set in either the initial response or as an AVOption to the HTTP protocol context ++ char *headers; ///< holds HTTP headers set as an AVOption to the HTTP protocol context ++ AVDictionary *avio_opts; ++} DASHContext; ++ ++static uint64_t get_current_time_in_sec(void) ++{ ++ return av_gettime() / 1000000; ++} ++ ++static uint64_t get_utc_date_time_insec(AVFormatContext *s, const char *datetime) ++{ ++ struct tm timeinfo; ++ int year = 0; ++ int month = 0; ++ int day = 0; ++ int hour = 0; ++ int minute = 0; ++ int ret = 0; ++ float second = 0.0; ++ ++ /* ISO-8601 date parser */ ++ if (!datetime) ++ return 0; ++ ++ ret = sscanf(datetime, "%d-%d-%dT%d:%d:%fZ", &year, &month, &day, &hour, &minute, &second); ++ /* year, month, day, hour, minute, second 6 arguments */ ++ if (ret != 6) { ++ av_log(s, AV_LOG_WARNING, "get_utc_date_time_insec get a wrong time format\n"); ++ } ++ timeinfo.tm_year = year - 1900; ++ timeinfo.tm_mon = month - 1; ++ timeinfo.tm_mday = day; ++ timeinfo.tm_hour = hour; ++ timeinfo.tm_min = minute; ++ timeinfo.tm_sec = (int)second; ++ ++ return av_timegm(&timeinfo); ++} ++ ++static uint32_t get_duration_insec(AVFormatContext *s, const char *duration) ++{ ++ /* ISO-8601 duration parser */ ++ uint32_t days = 0; ++ uint32_t hours = 0; ++ uint32_t mins = 0; ++ uint32_t secs = 0; ++ uint32_t size = 0; ++ float value = 0; ++ uint8_t type = 0; ++ const char *ptr = duration; ++ ++ while (*ptr) { ++ if (*ptr == 'P' || *ptr == 'T') { ++ ptr++; ++ continue; ++ } ++ ++ if (sscanf(ptr, "%f%c%n", &value, &type, &size) != 2) { ++ av_log(s, AV_LOG_WARNING, "get_duration_insec get a wrong time format\n"); ++ return 0; /* parser error */ ++ } ++ switch (type) { ++ case 'D': ++ days = (uint32_t)value; ++ break; ++ case 'H': ++ hours = (uint32_t)value; ++ break; ++ case 'M': ++ mins = (uint32_t)value; ++ break; ++ case 'S': ++ secs = (uint32_t)value; ++ break; ++ default: ++ // handle invalid type ++ break; ++ } ++ ptr += size; ++ } ++ return ((days * 24 + hours) * 60 + mins) * 60 + secs; ++} ++ ++static void free_fragment(struct fragment **seg) ++{ ++ if (!(*seg)) { ++ return; ++ } ++ av_freep(&(*seg)->url); ++ av_freep(seg); ++} ++ ++static void free_fragment_list(struct representation *pls) ++{ ++ int i; ++ ++ for (i = 0; i < pls->n_fragments; i++) { ++ free_fragment(&pls->fragments[i]); ++ } ++ av_freep(&pls->fragments); ++ pls->n_fragments = 0; ++} ++ ++static void free_timelines_list(struct representation *pls) ++{ ++ int i; ++ ++ for (i = 0; i < pls->n_timelines; i++) { ++ av_freep(&pls->timelines[i]); ++ } ++ av_freep(&pls->timelines); ++ pls->n_timelines = 0; ++} ++ ++/* ++ * Used to reset a statically allocated AVPacket to a clean slate, ++ * containing no data. ++ */ ++static void reset_packet(AVPacket *pkt) ++{ ++ av_init_packet(pkt); ++ pkt->data = NULL; ++} ++ ++static void free_representation(struct representation *pls) ++{ ++ free_fragment_list(pls); ++ free_timelines_list(pls); ++ free_fragment(&pls->cur_seg); ++ free_fragment(&pls->init_section); ++ av_freep(&pls->init_sec_buf); ++ av_packet_unref(&pls->pkt); ++ reset_packet(&pls->pkt); ++ av_freep(&pls->pb.buffer); ++ if (pls->input) ++ ff_format_io_close(pls->parent, &pls->input); ++ if (pls->ctx) { ++ pls->ctx->pb = NULL; ++ avformat_close_input(&pls->ctx); ++ } ++ ++ av_free(pls->url_template); ++ av_free(pls); ++} ++ ++static void update_options(char **dest, const char *name, void *src) ++{ ++ av_freep(dest); ++ av_opt_get(src, name, AV_OPT_SEARCH_CHILDREN, (uint8_t**)dest); ++ if (*dest) ++ av_freep(dest); ++} ++ ++static int open_url(AVFormatContext *s, AVIOContext **pb, const char *url, ++ AVDictionary *opts, AVDictionary *opts2, int *is_http) ++{ ++ DASHContext *c = s->priv_data; ++ AVDictionary *tmp = NULL; ++ const char *proto_name = NULL; ++ int ret; ++ void *p = NULL; ++ ++ av_dict_copy(&tmp, opts, 0); ++ av_dict_copy(&tmp, opts2, 0); ++ ++ if (av_strstart(url, "crypto", NULL)) { ++ if (url[6] == '+' || url[6] == ':') ++ proto_name = avio_find_protocol_name(url + 7); ++ } ++ ++ if (!proto_name) ++ proto_name = avio_find_protocol_name(url); ++ ++ if (!proto_name) ++ return AVERROR_INVALIDDATA; ++ ++ // only http(s) & file are allowed ++ if (!av_strstart(proto_name, "http", NULL) && !av_strstart(proto_name, "file", NULL)) { ++ return AVERROR_INVALIDDATA; ++ } ++ if (!strncmp(proto_name, url, strlen(proto_name)) && url[strlen(proto_name)] == ':') { ++ ; ++ } else if (av_strstart(url, "crypto", NULL) && !strncmp(proto_name, url + 7, strlen(proto_name)) && url[7 + strlen(proto_name)] == ':') { ++ ; ++ } else if (strcmp(proto_name, "file") || !strncmp(url, "file,", 5)) { ++ return AVERROR_INVALIDDATA; ++ } ++ ret = s->io_open(s, pb, url, AVIO_FLAG_READ, &tmp); ++ if (ret >= 0) { ++ // update cookies on http response with setcookies. ++ p = (s->flags & AVFMT_FLAG_CUSTOM_IO) ? NULL : s->pb; ++ update_options(&c->cookies, "cookies", p); ++ av_dict_set(&opts, "cookies", c->cookies, 0); ++ } ++ ++ av_dict_free(&tmp); ++ ++ if (is_http) ++ *is_http = av_strstart(proto_name, "http", NULL); ++ ++ return ret; ++ ++} ++ ++static char *replace_template_str(const char *url, const char *marker) ++{ ++ char *prefix = NULL; ++ char *start = 0; ++ char *end = NULL; ++ char *tmp_url = NULL; ++ int marker_len; ++ ++ prefix = av_strdup(marker); ++ marker_len = strlen(prefix) - 1; ++ prefix[marker_len] = '\0'; ++ ++ start = av_stristr(url, prefix); ++ if (!start) ++ goto finish; ++ end = strchr(start + 1, '$'); ++ if (!end) ++ goto finish; ++ ++ tmp_url = av_mallocz(MAX_URL_SIZE); ++ if (!tmp_url) { ++ return NULL; ++ } ++ ++ av_strlcpy(tmp_url, url, start - url+ 1); ++ av_strlcat(tmp_url, start + marker_len, strlen(tmp_url) + end - start - marker_len + 1); ++ av_strlcat(tmp_url, end + 1, MAX_URL_SIZE); ++ ++finish: ++ av_free(prefix); ++ return tmp_url; ++} ++ ++static char *get_content_url(xmlNodePtr *baseurl_nodes, ++ int n_baseurl_nodes, ++ xmlChar *rep_id_val, ++ xmlChar *rep_bandwidth_val, ++ xmlChar *val) ++{ ++ int i; ++ xmlChar *text; ++ char *url = NULL; ++ char *tmp_str = av_mallocz(MAX_URL_SIZE); ++ char *tmp_str_2 = NULL; ++ ++ if (!tmp_str) { ++ return NULL; ++ } ++ for (i = 0; i < n_baseurl_nodes; ++i) { ++ if (baseurl_nodes[i] && ++ baseurl_nodes[i]->children && ++ baseurl_nodes[i]->children->type == XML_TEXT_NODE) { ++ text = xmlNodeGetContent(baseurl_nodes[i]->children); ++ if (text) { ++ tmp_str_2 = av_mallocz(MAX_URL_SIZE); ++ if (!tmp_str_2) { ++ av_free(tmp_str); ++ return NULL; ++ } ++ ff_make_absolute_url(tmp_str_2, MAX_URL_SIZE, tmp_str, text); ++ av_free(tmp_str); ++ tmp_str = tmp_str_2; ++ xmlFree(text); ++ } ++ } ++ } ++ if (val) ++ av_strlcat(tmp_str, (const char*)val, MAX_URL_SIZE); ++ ++ if (rep_id_val) { ++ url = av_strireplace(tmp_str, "$RepresentationID$", (const char*)rep_id_val); ++ av_free(tmp_str); ++ tmp_str = url; ++ } ++ if (rep_bandwidth_val && tmp_str) ++ url = av_strireplace(tmp_str, "$Bandwidth$", (const char*)rep_bandwidth_val); ++ if (tmp_str != url) ++ av_free(tmp_str); ++ return url; ++} ++ ++static xmlChar *get_val_from_nodes_tab(xmlNodePtr *nodes, const int n_nodes, const xmlChar *attrname) ++{ ++ int i; ++ xmlChar *val; ++ ++ for (i = 0; i < n_nodes; ++i) { ++ if (nodes[i]) { ++ val = xmlGetProp(nodes[i], attrname); ++ if (val) ++ return val; ++ } ++ } ++ ++ return NULL; ++} ++ ++static xmlNodePtr find_child_node_by_name(xmlNodePtr rootnode, const xmlChar *nodename) ++{ ++ xmlNodePtr node = rootnode; ++ if (!node) { ++ return NULL; ++ } ++ ++ node = xmlFirstElementChild(node); ++ while (node) { ++ if (!xmlStrcmp(node->name, nodename)) { ++ return node; ++ } ++ node = xmlNextElementSibling(node); ++ } ++ return NULL; ++} ++ ++static void check_full_number(struct representation *rep) ++{ ++ char *temp_string = NULL; ++ ++ if (av_stristr(rep->url_template, "$Number$")) { ++ temp_string = av_strireplace(rep->url_template, "$Number$", "$Number%d$"); ++ av_free(rep->url_template); ++ rep->url_template = temp_string; ++ } ++} ++ ++static enum AVMediaType get_content_type(xmlNodePtr node) ++{ ++ enum AVMediaType type = AVMEDIA_TYPE_UNKNOWN; ++ int i = 0; ++ const char *attr; ++ ++ if (node) { ++ while (type == AVMEDIA_TYPE_UNKNOWN && i < 2) { ++ attr = (i) ? "mimeType" : "contentType"; ++ xmlChar *val = xmlGetProp(node, attr); ++ if (val) { ++ if (av_stristr((const char *)val, "video")) { ++ type = AVMEDIA_TYPE_VIDEO; ++ } else if (av_stristr((const char *)val, "audio")) { ++ type = AVMEDIA_TYPE_AUDIO; ++ } ++ xmlFree(val); ++ } ++ i++; ++ } ++ } ++ return type; ++} ++ ++static int parse_manifest_segmenturlnode(AVFormatContext *s, struct representation *rep, ++ xmlNodePtr fragmenturl_node, ++ xmlNodePtr *baseurl_nodes, ++ xmlChar *rep_id_val, ++ xmlChar *rep_bandwidth_val) ++{ ++ xmlChar *initialization_val = NULL; ++ xmlChar *media_val = NULL; ++ ++ if (!xmlStrcmp(fragmenturl_node->name, (const xmlChar *)"Initialization")) { ++ initialization_val = xmlGetProp(fragmenturl_node, "sourceURL"); ++ if (initialization_val) { ++ rep->init_section = av_mallocz(sizeof(struct fragment)); ++ if (!rep->init_section) { ++ xmlFree(initialization_val); ++ return AVERROR(ENOMEM); ++ } ++ rep->init_section->url = get_content_url(baseurl_nodes, 4, ++ rep_id_val, ++ rep_bandwidth_val, ++ initialization_val); ++ if (!rep->init_section->url) { ++ av_free(rep->init_section); ++ xmlFree(initialization_val); ++ return AVERROR(ENOMEM); ++ } ++ rep->init_section->size = -1; ++ xmlFree(initialization_val); ++ } ++ } else if (!xmlStrcmp(fragmenturl_node->name, (const xmlChar *)"SegmentURL")) { ++ media_val = xmlGetProp(fragmenturl_node, "media"); ++ if (media_val) { ++ struct fragment *seg = av_mallocz(sizeof(struct fragment)); ++ if (!seg) { ++ xmlFree(media_val); ++ return AVERROR(ENOMEM); ++ } ++ seg->url = get_content_url(baseurl_nodes, 4, ++ rep_id_val, ++ rep_bandwidth_val, ++ media_val); ++ if (!seg->url) { ++ av_free(seg); ++ xmlFree(media_val); ++ return AVERROR(ENOMEM); ++ } ++ seg->size = -1; ++ dynarray_add(&rep->fragments, &rep->n_fragments, seg); ++ xmlFree(media_val); ++ } ++ } ++ ++ return 0; ++} ++ ++static int parse_manifest_segmenttimeline(AVFormatContext *s, struct representation *rep, ++ xmlNodePtr fragment_timeline_node) ++{ ++ xmlAttrPtr attr = NULL; ++ xmlChar *val = NULL; ++ ++ if (!xmlStrcmp(fragment_timeline_node->name, (const xmlChar *)"S")) { ++ struct timeline *tml = av_mallocz(sizeof(struct timeline)); ++ if (!tml) { ++ return AVERROR(ENOMEM); ++ } ++ attr = fragment_timeline_node->properties; ++ while (attr) { ++ val = xmlGetProp(fragment_timeline_node, attr->name); ++ ++ if (!val) { ++ av_log(s, AV_LOG_WARNING, "parse_manifest_segmenttimeline attr->name = %s val is NULL\n", attr->name); ++ continue; ++ } ++ ++ if (!xmlStrcmp(attr->name, (const xmlChar *)"t")) { ++ tml->t = (int64_t)strtoll(val, NULL, 10); ++ } else if (!xmlStrcmp(attr->name, (const xmlChar *)"r")) { ++ tml->r =(int64_t) strtoll(val, NULL, 10); ++ } else if (!xmlStrcmp(attr->name, (const xmlChar *)"d")) { ++ tml->d = (int64_t)strtoll(val, NULL, 10); ++ rep->fragment_duration = (int64_t) strtoll(val, NULL, 10); ++ } ++ attr = attr->next; ++ xmlFree(val); ++ } ++ dynarray_add(&rep->timelines, &rep->n_timelines, tml); ++ } ++ ++ return 0; ++} ++ ++static int parse_manifest_representation(AVFormatContext *s, const char *url, ++ xmlNodePtr node, ++ xmlNodePtr adaptionset_node, ++ xmlNodePtr mpd_baseurl_node, ++ xmlNodePtr period_baseurl_node, ++ xmlNodePtr fragment_template_node, ++ xmlNodePtr content_component_node, ++ xmlNodePtr adaptionset_baseurl_node) ++{ ++ int32_t ret = 0; ++ int32_t audio_rep_idx = 0; ++ int32_t video_rep_idx = 0; ++ char *temp_string = NULL; ++ DASHContext *c = s->priv_data; ++ struct representation *rep = NULL; ++ struct fragment *seg = NULL; ++ xmlNodePtr representation_segmenttemplate_node = NULL; ++ xmlNodePtr representation_baseurl_node = NULL; ++ xmlNodePtr representation_segmentlist_node = NULL; ++ xmlNodePtr fragment_timeline_node = NULL; ++ xmlNodePtr fragment_templates_tab[2]; ++ xmlChar *duration_val = NULL; ++ xmlChar *startnumber_val = NULL; ++ xmlChar *timescale_val = NULL; ++ xmlChar *initialization_val = NULL; ++ xmlChar *media_val = NULL; ++ xmlNodePtr baseurl_nodes[4]; ++ xmlNodePtr representation_node = node; ++ xmlChar *rep_id_val = xmlGetProp(representation_node, "id"); ++ xmlChar *rep_bandwidth_val = xmlGetProp(representation_node, "bandwidth"); ++ enum AVMediaType type = AVMEDIA_TYPE_UNKNOWN; ++ ++ // try get information from representation ++ if (type == AVMEDIA_TYPE_UNKNOWN) ++ type = get_content_type(representation_node); ++ // try get information from contentComponen ++ if (type == AVMEDIA_TYPE_UNKNOWN) ++ type = get_content_type(content_component_node); ++ // try get information from adaption set ++ if (type == AVMEDIA_TYPE_UNKNOWN) ++ type = get_content_type(adaptionset_node); ++ if (type == AVMEDIA_TYPE_UNKNOWN) { ++ av_log(s, AV_LOG_VERBOSE, "Parsing '%s' - skipp not supported representation type\n", url); ++ } else if ((type == AVMEDIA_TYPE_VIDEO && !c->cur_video) || (type == AVMEDIA_TYPE_AUDIO && !c->cur_audio)) { ++ // convert selected representation to our internal struct ++ rep = av_mallocz(sizeof(struct representation)); ++ if (!rep) { ++ ret = AVERROR(ENOMEM); ++ goto end; ++ } ++ representation_segmenttemplate_node = find_child_node_by_name(representation_node, "SegmentTemplate"); ++ representation_baseurl_node = find_child_node_by_name(representation_node, "BaseURL"); ++ representation_segmentlist_node = find_child_node_by_name(representation_node, "SegmentList"); ++ reset_packet(&rep->pkt); ++ ++ baseurl_nodes[0] = mpd_baseurl_node; ++ baseurl_nodes[1] = period_baseurl_node; ++ baseurl_nodes[2] = adaptionset_baseurl_node; ++ baseurl_nodes[3] = representation_baseurl_node; ++ ++ if (representation_segmenttemplate_node || fragment_template_node) { ++ fragment_timeline_node = NULL; ++ fragment_templates_tab[0] = representation_segmenttemplate_node; ++ fragment_templates_tab[1] = fragment_template_node; ++ ++ duration_val = get_val_from_nodes_tab(fragment_templates_tab, 2, "duration"); ++ startnumber_val = get_val_from_nodes_tab(fragment_templates_tab, 2, "startNumber"); ++ timescale_val = get_val_from_nodes_tab(fragment_templates_tab, 2, "timescale"); ++ initialization_val = get_val_from_nodes_tab(fragment_templates_tab, 2, "initialization"); ++ media_val = get_val_from_nodes_tab(fragment_templates_tab, 2, "media"); ++ ++ if (initialization_val) { ++ rep->init_section = av_mallocz(sizeof(struct fragment)); ++ if (!rep->init_section) { ++ av_free(rep); ++ ret = AVERROR(ENOMEM); ++ goto end; ++ } ++ rep->init_section->url = get_content_url(baseurl_nodes, 4, ++ rep_id_val, ++ rep_bandwidth_val, ++ initialization_val); ++ if (!rep->init_section->url) { ++ av_free(rep->init_section); ++ av_free(rep); ++ ret = AVERROR(ENOMEM); ++ goto end; ++ } ++ rep->init_section->size = -1; ++ xmlFree(initialization_val); ++ } ++ ++ if (media_val) { ++ rep->url_template = get_content_url(baseurl_nodes, 4, ++ rep_id_val, ++ rep_bandwidth_val, ++ media_val); ++ temp_string = rep->url_template; ++ if (temp_string) { ++ if (av_stristr(temp_string, "$Number")) { ++ rep->tmp_url_type = TMP_URL_TYPE_NUMBER; /* Number-Based. */ ++ check_full_number(rep); ++ } else if (av_stristr(temp_string, "$Time")) { ++ rep->tmp_url_type = TMP_URL_TYPE_TIME; /* Time-Based. */ ++ } else { ++ temp_string = NULL; ++ } ++ } ++ xmlFree(media_val); ++ } ++ if (duration_val) { ++ rep->fragment_duration = (int64_t) strtoll(duration_val, NULL, 10); ++ xmlFree(duration_val); ++ } ++ if (timescale_val) { ++ rep->fragment_timescale = (int64_t) strtoll(timescale_val, NULL, 10); ++ xmlFree(timescale_val); ++ } ++ if (startnumber_val) { ++ rep->first_seq_no = (int64_t) strtoll(startnumber_val, NULL, 10); ++ xmlFree(startnumber_val); ++ } ++ ++ fragment_timeline_node = find_child_node_by_name(representation_segmenttemplate_node, "SegmentTimeline"); ++ ++ if (!fragment_timeline_node) ++ fragment_timeline_node = find_child_node_by_name(fragment_template_node, "SegmentTimeline"); ++ if (fragment_timeline_node) { ++ fragment_timeline_node = xmlFirstElementChild(fragment_timeline_node); ++ while (fragment_timeline_node) { ++ ret = parse_manifest_segmenttimeline(s, rep, fragment_timeline_node); ++ if (ret < 0) { ++ return ret; ++ } ++ fragment_timeline_node = xmlNextElementSibling(fragment_timeline_node); ++ } ++ } ++ } else if (representation_baseurl_node && !representation_segmentlist_node) { ++ seg = av_mallocz(sizeof(struct fragment)); ++ if (!seg) { ++ ret = AVERROR(ENOMEM); ++ goto end; ++ } ++ seg->url = get_content_url(baseurl_nodes, 4, rep_id_val, rep_bandwidth_val, NULL); ++ if (!seg->url) { ++ av_free(seg); ++ ret = AVERROR(ENOMEM); ++ goto end; ++ } ++ seg->size = -1; ++ dynarray_add(&rep->fragments, &rep->n_fragments, seg); ++ } else if (!representation_baseurl_node && representation_segmentlist_node) { ++ // TODO: https://www.brendanlong.com/the-structure-of-an-mpeg-dash-mpd.html ++ // http://www-itec.uni-klu.ac.at/dash/ddash/mpdGenerator.php?fragmentlength=15&type=full ++ xmlNodePtr fragmenturl_node = NULL; ++ duration_val = xmlGetProp(representation_segmentlist_node, "duration"); ++ timescale_val = xmlGetProp(representation_segmentlist_node, "timescale"); ++ if (duration_val) { ++ rep->fragment_duration = (int64_t) strtoll(duration_val, NULL, 10); ++ xmlFree(duration_val); ++ } ++ if (timescale_val) { ++ rep->fragment_timescale = (int64_t) strtoll(timescale_val, NULL, 10); ++ xmlFree(timescale_val); ++ } ++ fragmenturl_node = xmlFirstElementChild(representation_segmentlist_node); ++ while (fragmenturl_node) { ++ ret = parse_manifest_segmenturlnode(s, rep, fragmenturl_node, ++ baseurl_nodes, ++ rep_id_val, ++ rep_bandwidth_val); ++ if (ret < 0) { ++ return ret; ++ } ++ fragmenturl_node = xmlNextElementSibling(fragmenturl_node); ++ } ++ } else { ++ free_representation(rep); ++ rep = NULL; ++ av_log(s, AV_LOG_ERROR, "Unknown format of Representation node id[%s] \n", (const char *)rep_id_val); ++ } ++ ++ if (rep) { ++ if (rep->fragment_duration > 0 && !rep->fragment_timescale) ++ rep->fragment_timescale = 1; ++ if (type == AVMEDIA_TYPE_VIDEO) { ++ rep->rep_idx = video_rep_idx; ++ c->cur_video = rep; ++ } else { ++ rep->rep_idx = audio_rep_idx; ++ c->cur_audio = rep; ++ } ++ } ++ } ++ ++ video_rep_idx += type == AVMEDIA_TYPE_VIDEO; ++ audio_rep_idx += type == AVMEDIA_TYPE_AUDIO; ++ ++end: ++ if (rep_id_val) ++ xmlFree(rep_id_val); ++ if (rep_bandwidth_val) ++ xmlFree(rep_bandwidth_val); ++ ++ return ret; ++} ++ ++static int parse_manifest_adaptationset(AVFormatContext *s, const char *url, ++ xmlNodePtr adaptionset_node, ++ xmlNodePtr mpd_baseurl_node, ++ xmlNodePtr period_baseurl_node) ++{ ++ int ret = 0; ++ xmlNodePtr fragment_template_node = NULL; ++ xmlNodePtr content_component_node = NULL; ++ xmlNodePtr adaptionset_baseurl_node = NULL; ++ xmlNodePtr node = NULL; ++ ++ node = xmlFirstElementChild(adaptionset_node); ++ while (node) { ++ if (!xmlStrcmp(node->name, (const xmlChar *)"SegmentTemplate")) { ++ fragment_template_node = node; ++ } else if (!xmlStrcmp(node->name, (const xmlChar *)"ContentComponent")) { ++ content_component_node = node; ++ } else if (!xmlStrcmp(node->name, (const xmlChar *)"BaseURL")) { ++ adaptionset_baseurl_node = node; ++ } else if (!xmlStrcmp(node->name, (const xmlChar *)"Representation")) { ++ ret = parse_manifest_representation(s, url, node, ++ adaptionset_node, ++ mpd_baseurl_node, ++ period_baseurl_node, ++ fragment_template_node, ++ content_component_node, ++ adaptionset_baseurl_node); ++ if (ret < 0) { ++ return ret; ++ } ++ } ++ node = xmlNextElementSibling(node); ++ } ++ return 0; ++} ++ ++ ++static int parse_manifest(AVFormatContext *s, const char *url, AVIOContext *in) ++{ ++ DASHContext *c = s->priv_data; ++ int ret = 0; ++ int close_in = 0; ++ uint8_t *new_url = NULL; ++ int64_t filesize = 0; ++ char *buffer = NULL; ++ AVDictionary *opts = NULL; ++ xmlDoc *doc = NULL; ++ xmlNodePtr root_element = NULL; ++ xmlNodePtr node = NULL; ++ xmlNodePtr period_node = NULL; ++ xmlNodePtr mpd_baseurl_node = NULL; ++ xmlNodePtr period_baseurl_node = NULL; ++ xmlNodePtr adaptionset_node = NULL; ++ xmlAttrPtr attr = NULL; ++ xmlChar *val = NULL; ++ uint32_t perdiod_duration_sec = 0; ++ uint32_t perdiod_start_sec = 0; ++ int32_t audio_rep_idx = 0; ++ int32_t video_rep_idx = 0; ++ ++ if (!in) { ++ close_in = 1; ++ /* This is XML manifest there is no need to set range header */ ++ av_dict_set(&opts, "seekable", "0", 0); ++ // broker prior HTTP options that should be consistent across requests ++ av_dict_set(&opts, "user-agent", c->user_agent, 0); ++ av_dict_set(&opts, "cookies", c->cookies, 0); ++ av_dict_set(&opts, "headers", c->headers, 0); ++ ++ ret = avio_open2(&in, url, AVIO_FLAG_READ, c->interrupt_callback, &opts); ++ av_dict_free(&opts); ++ if (ret < 0) ++ return ret; ++ } ++ ++ if (av_opt_get(in, "location", AV_OPT_SEARCH_CHILDREN, &new_url) >= 0) { ++ c->base_url = av_strdup(new_url); ++ } else { ++ c->base_url = av_strdup(url); ++ } ++ ++ filesize = avio_size(in); ++ if (filesize <= 0) { ++ filesize = 8 * 1024; ++ } ++ ++ buffer = av_mallocz(filesize); ++ if (!buffer) { ++ return AVERROR(ENOMEM); ++ } ++ ++ filesize = avio_read(in, buffer, filesize); ++ if (filesize > 0) { ++ LIBXML_TEST_VERSION ++ ++ doc = xmlReadMemory(buffer, filesize, c->base_url, NULL, 0); ++ root_element = xmlDocGetRootElement(doc); ++ node = root_element; ++ ++ if (!node) { ++ ret = AVERROR_INVALIDDATA; ++ av_log(s, AV_LOG_ERROR, "Unable to parse '%s' - missing root node\n", url); ++ goto cleanup; ++ } ++ ++ if (node->type != XML_ELEMENT_NODE || ++ xmlStrcmp(node->name, (const xmlChar *)"MPD")) { ++ ret = AVERROR_INVALIDDATA; ++ av_log(s, AV_LOG_ERROR, "Unable to parse '%s' - wrong root node name[%s] type[%d]\n", url, node->name, (int)node->type); ++ goto cleanup; ++ } ++ ++ val = xmlGetProp(node, "type"); ++ if (!val) { ++ av_log(s, AV_LOG_ERROR, "Unable to parse '%s' - missing type attrib\n", url); ++ ret = AVERROR_INVALIDDATA; ++ goto cleanup; ++ } ++ if (!xmlStrcmp(val, (const xmlChar *)"dynamic")) ++ c->is_live = 1; ++ xmlFree(val); ++ ++ attr = node->properties; ++ while (attr) { ++ val = xmlGetProp(node, attr->name); ++ ++ if (!xmlStrcmp(attr->name, (const xmlChar *)"availabilityStartTime")) { ++ c->availability_start_time_sec = get_utc_date_time_insec(s, (const char *)val); ++ } else if (!xmlStrcmp(attr->name, (const xmlChar *)"publishTime")) { ++ c->publish_time_sec = get_utc_date_time_insec(s, (const char *)val); ++ } else if (!xmlStrcmp(attr->name, (const xmlChar *)"minimumUpdatePeriod")) { ++ c->minimum_update_period_sec = get_duration_insec(s, (const char *)val); ++ } else if (!xmlStrcmp(attr->name, (const xmlChar *)"timeShiftBufferDepth")) { ++ c->time_shift_buffer_depth_sec = get_duration_insec(s, (const char *)val); ++ } else if (!xmlStrcmp(attr->name, (const xmlChar *)"minBufferTime")) { ++ c->min_buffer_time_sec = get_duration_insec(s, (const char *)val); ++ } else if (!xmlStrcmp(attr->name, (const xmlChar *)"suggestedPresentationDelay")) { ++ c->suggested_presentation_delay_sec = get_duration_insec(s, (const char *)val); ++ } else if (!xmlStrcmp(attr->name, (const xmlChar *)"mediaPresentationDuration")) { ++ c->media_presentation_duration_sec = get_duration_insec(s, (const char *)val); ++ } ++ attr = attr->next; ++ xmlFree(val); ++ } ++ ++ mpd_baseurl_node = find_child_node_by_name(node, "BaseURL"); ++ ++ // at now we can handle only one period, with the longest duration ++ node = xmlFirstElementChild(node); ++ while (node) { ++ if (!xmlStrcmp(node->name, (const xmlChar *)"Period")) { ++ perdiod_duration_sec = 0; ++ perdiod_start_sec = 0; ++ attr = node->properties; ++ while (attr) { ++ val = xmlGetProp(node, attr->name); ++ if (!xmlStrcmp(attr->name, (const xmlChar *)"duration")) { ++ perdiod_duration_sec = get_duration_insec(s, (const char *)val); ++ } else if (!xmlStrcmp(attr->name, (const xmlChar *)"start")) { ++ perdiod_start_sec = get_duration_insec(s, (const char *)val); ++ } ++ attr = attr->next; ++ xmlFree(val); ++ } ++ if ((perdiod_duration_sec) >= (c->period_duration_sec)) { ++ period_node = node; ++ c->period_duration_sec = perdiod_duration_sec; ++ c->period_start_sec = perdiod_start_sec; ++ if (c->period_start_sec > 0) ++ c->media_presentation_duration_sec = c->period_duration_sec; ++ } ++ } ++ node = xmlNextElementSibling(node); ++ } ++ if (!period_node) { ++ av_log(s, AV_LOG_ERROR, "Unable to parse '%s' - missing Period node\n", url); ++ ret = AVERROR_INVALIDDATA; ++ goto cleanup; ++ } ++ ++ adaptionset_node = xmlFirstElementChild(period_node); ++ while (adaptionset_node) { ++ if (!xmlStrcmp(adaptionset_node->name, (const xmlChar *)"BaseURL")) { ++ period_baseurl_node = adaptionset_node; ++ } else if (!xmlStrcmp(adaptionset_node->name, (const xmlChar *)"AdaptationSet")) { ++ parse_manifest_adaptationset(s, url, adaptionset_node, mpd_baseurl_node, period_baseurl_node); ++ } ++ adaptionset_node = xmlNextElementSibling(adaptionset_node); ++ } ++ if (c->cur_video) { ++ c->cur_video->rep_count = video_rep_idx; ++ c->cur_video->fix_multiple_stsd_order = 1; ++ av_log(s, AV_LOG_VERBOSE, "rep_idx[%d]\n", (int)c->cur_video->rep_idx); ++ av_log(s, AV_LOG_VERBOSE, "rep_count[%d]\n", (int)video_rep_idx); ++ } ++ if (c->cur_audio) { ++ c->cur_audio->rep_count = audio_rep_idx; ++ } ++ ++cleanup: ++ /*free the document */ ++ xmlFreeDoc(doc); ++ xmlCleanupParser(); ++ } else { ++ av_log(s, AV_LOG_ERROR, "Unable to read to offset '%s'\n", url); ++ ret = AVERROR_INVALIDDATA; ++ } ++ ++ av_free(new_url); ++ av_free(buffer); ++ if (close_in) { ++ avio_close(in); ++ } ++ return ret; ++} ++ ++static int64_t calc_cur_seg_no(AVFormatContext *s, struct representation *pls) ++{ ++ DASHContext *c = pls->parent->priv_data; ++ int64_t num = 0; ++ ++ if (c->is_live) { ++ num = pls->first_seq_no + (((get_current_time_in_sec() - c->availability_start_time_sec) - c->presentation_delay_sec) * pls->fragment_timescale) / pls->fragment_duration; ++ } else { ++ num = pls->first_seq_no; ++ } ++ return num; ++} ++ ++static int64_t calc_min_seg_no(AVFormatContext *s, struct representation *pls) ++{ ++ DASHContext *c = pls->parent->priv_data; ++ int64_t num = 0; ++ ++ if (c->is_live) { ++ num = pls->first_seq_no + (((get_current_time_in_sec() - c->availability_start_time_sec) - c->time_shift_buffer_depth_sec) * pls->fragment_timescale) / pls->fragment_duration; ++ } else { ++ num = pls->first_seq_no; ++ } ++ return num; ++} ++ ++static int64_t calc_max_seg_no(AVFormatContext *s, struct representation *pls) ++{ ++ DASHContext *c = pls->parent->priv_data; ++ int64_t num = 0; ++ int64_t i = 0; ++ ++ if (c->is_live) { ++ num = pls->first_seq_no + (((get_current_time_in_sec() - c->availability_start_time_sec)) * pls->fragment_timescale) / pls->fragment_duration; ++ } else { ++ if (pls->n_fragments) { ++ num = pls->first_seq_no + pls->n_fragments - 1; ++ } else if (pls->n_timelines) { ++ num = pls->first_seq_no + pls->n_timelines - 1; ++ for (i = 0; i < pls->n_timelines; ++i) { ++ num += pls->timelines[i]->r; ++ } ++ } else { ++ num = pls->first_seq_no + (c->media_presentation_duration_sec * pls->fragment_timescale) / pls->fragment_duration; ++ } ++ } ++ ++ return num; ++} ++ ++static int64_t get_fragment_start_time(struct representation *pls, int64_t cur_seq_no) ++{ ++ int64_t i = 0; ++ int64_t j = 0; ++ int64_t num = 0; ++ int64_t startTime = 0; ++ ++ if (pls->n_timelines) { ++ for (i = 0; i < pls->n_timelines; ++i) { ++ if (pls->timelines[i]->t > 0) { ++ startTime = pls->timelines[i]->t; ++ } ++ if (num == cur_seq_no) ++ goto finish; ++ startTime += pls->timelines[i]->d; ++ for (j = 0; j < pls->timelines[i]->r; ++j) { ++ num++; ++ if (num == cur_seq_no) ++ goto finish; ++ startTime += pls->timelines[i]->d; ++ } ++ num++; ++ } ++ } ++ ++finish: ++ return startTime; ++} ++ ++static struct fragment *get_current_fragment(struct representation *pls) ++{ ++ int64_t tmp_val = 0; ++ int64_t min_seq_no = 0; ++ int64_t max_seq_no = 0; ++ char buffer[1024]; ++ char *tmp_str = NULL; ++ struct fragment *seg = NULL; ++ struct fragment *seg_ptr = NULL; ++ DASHContext *c = pls->parent->priv_data; ++ ++ if (pls->n_fragments > 0) { ++ if (pls->cur_seq_no < pls->n_fragments) { ++ seg_ptr = pls->fragments[pls->cur_seq_no]; ++ seg = av_mallocz(sizeof(struct fragment)); ++ if (!seg) { ++ return NULL; ++ } ++ seg->url = av_strdup(seg_ptr->url); ++ if (!seg->url) { ++ av_free(seg); ++ return NULL; ++ } ++ seg->size = seg_ptr->size; ++ seg->url_offset = seg_ptr->url_offset; ++ return seg; ++ } ++ } ++ if (c->is_live) { ++ while (1) { ++ min_seq_no = calc_min_seg_no(pls->parent, pls); ++ max_seq_no = calc_max_seg_no(pls->parent, pls); ++ ++ if (pls->cur_seq_no <= min_seq_no) { ++ av_log(pls->parent, AV_LOG_VERBOSE, "old fragment: cur[%"PRId64"] min[%"PRId64"] max[%"PRId64"], playlist %d\n", ++ (int64_t)pls->cur_seq_no, min_seq_no, max_seq_no, (int)pls->rep_idx); ++ pls->cur_seq_no = calc_cur_seg_no(pls->parent, pls); ++ } else if (pls->cur_seq_no > max_seq_no) { ++ av_log(pls->parent, AV_LOG_VERBOSE, "new fragment: min[%"PRId64"] max[%"PRId64"], playlist %d\n", ++ min_seq_no, max_seq_no, (int)pls->rep_idx); ++ av_usleep(1000); ++ continue; ++ } ++ break; ++ } ++ seg = av_mallocz(sizeof(struct fragment)); ++ if (!seg) { ++ return NULL; ++ } ++ } else if (pls->cur_seq_no <= pls->last_seq_no) { ++ seg = av_mallocz(sizeof(struct fragment)); ++ if (!seg) { ++ return NULL; ++ } ++ } ++ if (seg) { ++ if (pls->tmp_url_type != TMP_URL_TYPE_UNSPECIFIED) { ++ tmp_val = pls->tmp_url_type == TMP_URL_TYPE_NUMBER ? pls->cur_seq_no : get_fragment_start_time(pls, pls->cur_seq_no); ++ if (av_get_frame_filename(buffer, sizeof(buffer), pls->url_template, (int64_t)tmp_val) < 0) { ++ av_log(pls->parent, AV_LOG_ERROR, "Invalid segment filename template %s\n", pls->url_template); ++ return NULL; ++ } ++ ++ if (pls->tmp_url_type == TMP_URL_TYPE_NUMBER) { ++ tmp_str = replace_template_str(buffer, "$Number$"); ++ } else if (pls->tmp_url_type == TMP_URL_TYPE_TIME) { ++ tmp_str = replace_template_str(buffer, "$Time$"); ++ } else { ++ av_log(pls->parent, AV_LOG_ERROR, "Invalid tmp_url_type\n"); ++ return NULL; ++ } ++ ++ seg->url = av_strdup(tmp_str); ++ if (!seg->url) { ++ av_free(tmp_str); ++ return NULL; ++ } ++ } else { ++ av_log(pls->parent, AV_LOG_ERROR, "Unable to unable to resolve template url '%s'\n", pls->url_template); ++ seg->url = av_strdup(pls->url_template); ++ if (!seg->url) { ++ return NULL; ++ } ++ } ++ seg->size = -1; ++ } ++ ++ av_free(tmp_str); ++ return seg; ++} ++ ++enum ReadFromURLMode { ++ READ_NORMAL, ++ READ_COMPLETE, ++}; ++ ++static int read_from_url(struct representation *pls, struct fragment *seg, ++ uint8_t *buf, int buf_size, ++ enum ReadFromURLMode mode) ++{ ++ int ret; ++ ++ /* limit read if the fragment was only a part of a file */ ++ if (seg->size >= 0) ++ buf_size = FFMIN(buf_size, pls->cur_seg_size - pls->cur_seg_offset); ++ ++ if (mode == READ_COMPLETE) { ++ ret = avio_read(pls->input, buf, buf_size); ++ if (ret < buf_size) { ++ av_log(pls->parent, AV_LOG_WARNING, "Could not read complete fragment.\n"); ++ } ++ } else { ++ ret = avio_read(pls->input, buf, buf_size); ++ } ++ if (ret > 0) ++ pls->cur_seg_offset += ret; ++ ++ return ret; ++} ++ ++static int open_input(DASHContext *c, struct representation *pls, struct fragment *seg) ++{ ++ AVDictionary *opts = NULL; ++ char url[MAX_URL_SIZE]; ++ int ret; ++ ++ // broker prior HTTP options that should be consistent across requests ++ av_dict_set(&opts, "user-agent", c->user_agent, 0); ++ av_dict_set(&opts, "cookies", c->cookies, 0); ++ av_dict_set(&opts, "headers", c->headers, 0); ++ if (c->is_live) { ++ av_dict_set(&opts, "seekable", "0", 0); ++ } ++ ++ if (seg->size >= 0) { ++ /* try to restrict the HTTP request to the part we want ++ * (if this is in fact a HTTP request) */ ++ av_dict_set_int(&opts, "offset", seg->url_offset, 0); ++ av_dict_set_int(&opts, "end_offset", seg->url_offset + seg->size, 0); ++ } ++ ++ ff_make_absolute_url(url, MAX_URL_SIZE, c->base_url, seg->url); ++ av_log(pls->parent, AV_LOG_VERBOSE, "DASH request for url '%s', offset %"PRId64", playlist %d\n", ++ url, seg->url_offset, pls->rep_idx); ++ ret = open_url(pls->parent, &pls->input, url, c->avio_opts, opts, NULL); ++ if (ret < 0) { ++ goto cleanup; ++ } ++ ++ /* Seek to the requested position. If this was a HTTP request, the offset ++ * should already be where want it to, but this allows e.g. local testing ++ * without a HTTP server. */ ++ if (!ret && seg->url_offset) { ++ int64_t seekret = avio_seek(pls->input, seg->url_offset, SEEK_SET); ++ if (seekret < 0) { ++ av_log(pls->parent, AV_LOG_ERROR, "Unable to seek to offset %"PRId64" of DASH fragment '%s'\n", seg->url_offset, seg->url); ++ ret = (int) seekret; ++ ff_format_io_close(pls->parent, &pls->input); ++ } ++ } ++ ++cleanup: ++ av_dict_free(&opts); ++ pls->cur_seg_offset = 0; ++ pls->cur_seg_size = seg->size; ++ return ret; ++} ++ ++static int update_init_section(struct representation *pls) ++{ ++ static const int max_init_section_size = 1024*1024; ++ DASHContext *c = pls->parent->priv_data; ++ int64_t sec_size = 0; ++ int64_t urlsize = 0; ++ int ret = 0; ++ ++ /* read init section only once per representation */ ++ if (!pls->init_section || pls->init_sec_buf) { ++ return 0; ++ } ++ ++ ret = open_input(c, pls, pls->init_section); ++ if (ret < 0) { ++ av_log(pls->parent, AV_LOG_WARNING, "Failed to open an initialization section in playlist %d\n", pls->rep_idx); ++ return ret; ++ } ++ ++ if (pls->init_section->size >= 0) { ++ sec_size = pls->init_section->size; ++ } else if ((urlsize = avio_size(pls->input)) >= 0) { ++ sec_size = urlsize; ++ } else { ++ sec_size = max_init_section_size; ++ } ++ av_log(pls->parent, AV_LOG_DEBUG, "Downloading an initialization section of size %"PRId64"\n", sec_size); ++ sec_size = FFMIN(sec_size, max_init_section_size); ++ av_fast_malloc(&pls->init_sec_buf, &pls->init_sec_buf_size, sec_size); ++ ret = read_from_url(pls, pls->init_section, pls->init_sec_buf, pls->init_sec_buf_size, READ_COMPLETE); ++ ff_format_io_close(pls->parent, &pls->input); ++ if (ret < 0) ++ return ret; ++ ++ if (pls->fix_multiple_stsd_order && pls->rep_idx > 0) { ++ uint8_t **stsd_entries = NULL; ++ int *stsd_entries_size = NULL; ++ int i = 4; ++ ++ while (i <= (ret - 4)) { ++ // find start stsd atom ++ if (!memcmp(pls->init_sec_buf + i, "stsd", 4)) { ++ /* 1B version ++ * 3B flags ++ * 4B num of entries */ ++ int stsd_first_offset = i + 8; ++ int stsd_offset = 0; ++ int j = 0; ++ uint32_t stsd_count = AV_RB32(pls->init_sec_buf + stsd_first_offset); ++ stsd_first_offset += 4; ++ if (stsd_count != pls->rep_count) { ++ i++; ++ continue; ++ } ++ // find all stsd entries ++ stsd_entries = av_mallocz_array(stsd_count, sizeof(*stsd_entries)); ++ stsd_entries_size = av_mallocz_array(stsd_count, sizeof(*stsd_entries_size)); ++ for (j = 0; j < stsd_count; ++j) { ++ /* 4B - size ++ * 4B - format */ ++ stsd_entries_size[j] = AV_RB32(pls->init_sec_buf + stsd_first_offset + stsd_offset); ++ stsd_entries[j] = av_malloc(stsd_entries_size[j]); ++ memcpy(stsd_entries[j], pls->init_sec_buf + stsd_first_offset + stsd_offset, stsd_entries_size[j]); ++ stsd_offset += stsd_entries_size[j]; ++ } ++ // reorder stsd entries ++ // as first put stsd entry for current representation ++ j = pls->rep_idx; ++ stsd_offset = stsd_first_offset; ++ memcpy(pls->init_sec_buf + stsd_offset, stsd_entries[j], stsd_entries_size[j]); ++ stsd_offset += stsd_entries_size[j]; ++ for (j = 0; j < stsd_count; ++j) { ++ if (j != pls->rep_idx) { ++ memcpy(pls->init_sec_buf + stsd_offset, stsd_entries[j], stsd_entries_size[j]); ++ stsd_offset += stsd_entries_size[j]; ++ } ++ av_free(stsd_entries[j]); ++ } ++ av_freep(&stsd_entries); ++ av_freep(&stsd_entries_size); ++ break; ++ } ++ i++; ++ } ++ } ++ ++ av_log(pls->parent, AV_LOG_TRACE, "pls[%p] init section size[%d]\n", pls, (int)ret); ++ pls->init_sec_data_len = ret; ++ pls->init_sec_buf_read_offset = 0; ++ ++ return 0; ++} ++ ++static int64_t seek_data(void *opaque, int64_t offset, int whence) ++{ ++ struct representation *v = opaque; ++ if (v->n_fragments && !v->init_sec_data_len) { ++ return avio_seek(v->input, offset, whence); ++ } ++ ++ return AVERROR(ENOSYS); ++} ++ ++static int read_data(void *opaque, uint8_t *buf, int buf_size) ++{ ++ int ret = 0; ++ struct representation *v = opaque; ++ DASHContext *c = v->parent->priv_data; ++ ++restart: ++ if (!v->input) { ++ free_fragment(&v->cur_seg); ++ v->cur_seg = get_current_fragment(v); ++ if (!v->cur_seg) { ++ ret = AVERROR_EOF; ++ goto end; ++ } ++ ++ /* load/update Media Initialization Section, if any */ ++ ret = update_init_section(v); ++ if (ret) ++ goto end; ++ ++ ret = open_input(c, v, v->cur_seg); ++ if (ret < 0) { ++ if (ff_check_interrupt(c->interrupt_callback)) { ++ goto end; ++ ret = AVERROR_EXIT; ++ } ++ av_log(v->parent, AV_LOG_WARNING, "Failed to open fragment of playlist %d\n", v->rep_idx); ++ v->cur_seq_no++; ++ goto restart; ++ } ++ } ++ ++ if (v->init_sec_buf_read_offset < v->init_sec_data_len) { ++ /* Push init section out first before first actual fragment */ ++ int copy_size = FFMIN(v->init_sec_data_len - v->init_sec_buf_read_offset, buf_size); ++ memcpy(buf, v->init_sec_buf, copy_size); ++ v->init_sec_buf_read_offset += copy_size; ++ ret = copy_size; ++ goto end; ++ } ++ ++ /* check the v->cur_seg, if it is null, get current and double check if the new v->cur_seg*/ ++ if (!v->cur_seg) { ++ v->cur_seg = get_current_fragment(v); ++ } ++ if (!v->cur_seg) { ++ ret = AVERROR_EOF; ++ goto end; ++ } ++ ret = read_from_url(v, v->cur_seg, buf, buf_size, READ_NORMAL); ++ if (ret > 0) ++ goto end; ++ ff_format_io_close(v->parent, &v->input); ++ v->cur_seq_no++; ++ goto restart; ++ ++end: ++ return ret; ++} ++ ++static int save_avio_options(AVFormatContext *s) ++{ ++ DASHContext *c = s->priv_data; ++ const char *opts[] = { "headers", "user_agent", "user-agent", "cookies", NULL }, **opt = opts; ++ uint8_t *buf = NULL; ++ int ret = 0; ++ ++ while (*opt) { ++ if (av_opt_get(s->pb, *opt, AV_OPT_SEARCH_CHILDREN, &buf) >= 0) { ++ if (buf[0] != '\0') { ++ ret = av_dict_set(&c->avio_opts, *opt, buf, AV_DICT_DONT_STRDUP_VAL); ++ if (ret < 0) ++ return ret; ++ } ++ } ++ opt++; ++ } ++ ++ return ret; ++} ++ ++static int nested_io_open(AVFormatContext *s, AVIOContext **pb, const char *url, ++ int flags, AVDictionary **opts) ++{ ++ av_log(s, AV_LOG_ERROR, ++ "A HDS playlist item '%s' referred to an external file '%s'. " ++ "Opening this file was forbidden for security reasons\n", ++ s->filename, url); ++ return AVERROR(EPERM); ++} ++ ++static int reopen_demux_for_component(AVFormatContext *s, struct representation *pls) ++{ ++ DASHContext *c = s->priv_data; ++ AVInputFormat *in_fmt = NULL; ++ AVDictionary *in_fmt_opts = NULL; ++ uint8_t *avio_ctx_buffer = NULL; ++ int ret = 0; ++ ++ if (pls->ctx) { ++ /* note: the internal buffer could have changed, and be != avio_ctx_buffer */ ++ av_freep(&pls->pb.buffer); ++ memset(&pls->pb, 0x00, sizeof(AVIOContext)); ++ pls->ctx->pb = NULL; ++ avformat_close_input(&pls->ctx); ++ pls->ctx = NULL; ++ } ++ if (!(pls->ctx = avformat_alloc_context())) { ++ ret = AVERROR(ENOMEM); ++ goto fail; ++ } ++ ++ avio_ctx_buffer = av_malloc(INITIAL_BUFFER_SIZE); ++ if (!avio_ctx_buffer ) { ++ ret = AVERROR(ENOMEM); ++ avformat_free_context(pls->ctx); ++ pls->ctx = NULL; ++ goto fail; ++ } ++ if (c->is_live) { ++ ffio_init_context(&pls->pb, avio_ctx_buffer , INITIAL_BUFFER_SIZE, 0, pls, read_data, NULL, NULL); ++ } else { ++ ffio_init_context(&pls->pb, avio_ctx_buffer , INITIAL_BUFFER_SIZE, 0, pls, read_data, NULL, seek_data); ++ } ++ pls->pb.seekable = 0; ++ ++ if ((ret = ff_copy_whiteblacklists(pls->ctx, s)) < 0) ++ goto fail; ++ ++ pls->ctx->flags = AVFMT_FLAG_CUSTOM_IO; ++ pls->ctx->probesize = 1024 * 4; ++ pls->ctx->max_analyze_duration = 4 * AV_TIME_BASE; ++ ret = av_probe_input_buffer(&pls->pb, &in_fmt, "", NULL, 0, 0); ++ if (ret < 0) { ++ av_log(s, AV_LOG_ERROR, "Error when loading first fragment, playlist %d\n", (int)pls->rep_idx); ++ avformat_free_context(pls->ctx); ++ pls->ctx = NULL; ++ goto fail; ++ } ++ ++ pls->ctx->pb = &pls->pb; ++ pls->ctx->io_open = nested_io_open; ++ ++ // provide additional information from mpd if available ++ ret = avformat_open_input(&pls->ctx, "", in_fmt, &in_fmt_opts); //pls->init_section->url ++ av_dict_free(&in_fmt_opts); ++ if (ret < 0) ++ goto fail; ++ if (pls->n_fragments) { ++ ret = avformat_find_stream_info(pls->ctx, NULL); ++ if (ret < 0) ++ goto fail; ++ } ++ ++fail: ++ return ret; ++} ++ ++static int open_demux_for_component(AVFormatContext *s, struct representation *pls) ++{ ++ int ret = 0; ++ int i; ++ ++ pls->parent = s; ++ pls->cur_seq_no = calc_cur_seg_no(s, pls); ++ pls->last_seq_no = calc_max_seg_no(s, pls); ++ ++ ret = reopen_demux_for_component(s, pls); ++ if (ret < 0) { ++ goto fail; ++ } ++ for (i = 0; i < pls->ctx->nb_streams; i++) { ++ AVStream *st = avformat_new_stream(s, NULL); ++ AVStream *ist = pls->ctx->streams[i]; ++ if (!st) { ++ ret = AVERROR(ENOMEM); ++ goto fail; ++ } ++ st->id = i; ++ avcodec_parameters_copy(st->codecpar, pls->ctx->streams[i]->codecpar); ++ avpriv_set_pts_info(st, ist->pts_wrap_bits, ist->time_base.num, ist->time_base.den); ++ } ++ ++ return 0; ++fail: ++ return ret; ++} ++ ++static int dash_read_header(AVFormatContext *s) ++{ ++ void *u = (s->flags & AVFMT_FLAG_CUSTOM_IO) ? NULL : s->pb; ++ DASHContext *c = s->priv_data; ++ int ret = 0; ++ int stream_index = 0; ++ ++ c->interrupt_callback = &s->interrupt_callback; ++ // if the URL context is good, read important options we must broker later ++ if (u) { ++ update_options(&c->user_agent, "user-agent", u); ++ update_options(&c->cookies, "cookies", u); ++ update_options(&c->headers, "headers", u); ++ } ++ ++ if ((ret = parse_manifest(s, s->filename, s->pb)) < 0) ++ goto fail; ++ ++ if ((ret = save_avio_options(s)) < 0) ++ goto fail; ++ ++ /* If this isn't a live stream, fill the total duration of the ++ * stream. */ ++ if (!c->is_live) { ++ s->duration = (int64_t) c->media_presentation_duration_sec * AV_TIME_BASE; ++ } ++ ++ /* Open the demuxer for curent video and current audio components if available */ ++ if (!ret && c->cur_video) { ++ ret = open_demux_for_component(s, c->cur_video); ++ if (!ret) { ++ c->cur_video->stream_index = stream_index; ++ ++stream_index; ++ } else { ++ free_representation(c->cur_video); ++ c->cur_video = NULL; ++ } ++ } ++ ++ if (!ret && c->cur_audio) { ++ ret = open_demux_for_component(s, c->cur_audio); ++ if (!ret) { ++ c->cur_audio->stream_index = stream_index; ++ ++stream_index; ++ } else { ++ free_representation(c->cur_audio); ++ c->cur_audio = NULL; ++ } ++ } ++ ++ if (!stream_index) { ++ ret = AVERROR_INVALIDDATA; ++ goto fail; ++ } ++ ++ /* Create a program */ ++ if (!ret) { ++ AVProgram *program; ++ program = av_new_program(s, 0); ++ if (!program) { ++ goto fail; ++ } ++ ++ if (c->cur_video) { ++ av_program_add_stream_index(s, 0, c->cur_video->stream_index); ++ } ++ ++ if (c->cur_audio) { ++ av_program_add_stream_index(s, 0, c->cur_audio->stream_index); ++ } ++ } ++ ++ return 0; ++fail: ++ return ret; ++} ++ ++static int dash_read_packet(AVFormatContext *s, AVPacket *pkt) ++{ ++ DASHContext *c = s->priv_data; ++ int ret = 0; ++ struct representation *cur = NULL; ++ ++ if (!c->cur_audio && !c->cur_video) { ++ return AVERROR_INVALIDDATA; ++ } ++ if (c->cur_audio && !c->cur_video) { ++ cur = c->cur_audio; ++ } else if (!c->cur_audio && c->cur_video) { ++ cur = c->cur_video; ++ } else if (c->cur_video->cur_timestamp < c->cur_audio->cur_timestamp) { ++ cur = c->cur_video; ++ } else { ++ cur = c->cur_audio; ++ } ++ ++ if (cur->ctx) { ++ ret = av_read_frame(cur->ctx, &cur->pkt); ++ if (ret < 0) { ++ av_packet_unref(&cur->pkt); ++ } else { ++ /* If we got a packet, return it */ ++ *pkt = cur->pkt; ++ cur->cur_timestamp = av_rescale(pkt->pts, (int64_t)cur->ctx->streams[0]->time_base.num * 90000, cur->ctx->streams[0]->time_base.den); ++ pkt->stream_index = cur->stream_index; ++ reset_packet(&cur->pkt); ++ return 0; ++ } ++ } ++ return AVERROR_EOF; ++} ++ ++static int dash_close(AVFormatContext *s) ++{ ++ DASHContext *c = s->priv_data; ++ ++ if (c->cur_audio) { ++ free_representation(c->cur_audio); ++ } ++ ++ if (c->cur_video) { ++ free_representation(c->cur_video); ++ } ++ ++ av_freep(&c->cookies); ++ av_freep(&c->user_agent); ++ av_dict_free(&c->avio_opts); ++ av_freep(&c->base_url); ++ return 0; ++} ++ ++static int dash_seek(AVFormatContext *s, struct representation *pls, int64_t seek_pos_msec, int flags) ++{ ++ int ret = 0; ++ int i = 0; ++ int j = 0; ++ int64_t duration = 0; ++ ++ av_log(pls->parent, AV_LOG_VERBOSE, "DASH seek pos[%"PRId64"ms], playlist %d\n", seek_pos_msec, pls->rep_idx); ++ ++ // single fragment mode ++ if (pls->n_fragments == 1) { ++ pls->cur_timestamp = 0; ++ pls->cur_seg_offset = 0; ++ ff_read_frame_flush(pls->ctx); ++ return av_seek_frame(pls->ctx, -1, seek_pos_msec*1000, flags); ++ } ++ ++ if (pls->input) ++ ff_format_io_close(pls->parent, &pls->input); ++ ++ // find the nearest fragment ++ if (pls->n_timelines > 0 && pls->fragment_timescale > 0) { ++ int64_t num = pls->first_seq_no; ++ av_log(pls->parent, AV_LOG_VERBOSE, "dash_seek with SegmentTimeline start n_timelines[%d] last_seq_no[%"PRId64"], playlist %d.\n", ++ (int)pls->n_timelines, (int64_t)pls->last_seq_no, (int)pls->rep_idx); ++ for (i = 0; i < pls->n_timelines; i++) { ++ if (pls->timelines[i]->t > 0) { ++ duration = pls->timelines[i]->t; ++ } ++ duration += pls->timelines[i]->d; ++ if (seek_pos_msec < ((duration * 1000) / pls->fragment_timescale)) { ++ goto set_seq_num; ++ } ++ for (j = 0; j < pls->timelines[i]->r; j++) { ++ duration += pls->timelines[i]->d; ++ num++; ++ if (seek_pos_msec < ((duration * 1000) / pls->fragment_timescale)) { ++ goto set_seq_num; ++ } ++ } ++ num++; ++ } ++ ++set_seq_num: ++ pls->cur_seq_no = num > pls->last_seq_no ? pls->last_seq_no : num; ++ av_log(pls->parent, AV_LOG_VERBOSE, "dash_seek with SegmentTimeline end cur_seq_no[%"PRId64"], playlist %d.\n", ++ (int64_t)pls->cur_seq_no, (int)pls->rep_idx); ++ } else if (pls->fragment_duration > 0) { ++ pls->cur_seq_no = pls->first_seq_no + ((seek_pos_msec * pls->fragment_timescale) / pls->fragment_duration) / 1000; ++ } else { ++ av_log(pls->parent, AV_LOG_ERROR, "dash_seek missing fragment_duration\n"); ++ pls->cur_seq_no = pls->first_seq_no; ++ } ++ pls->cur_timestamp = 0; ++ pls->cur_seg_offset = 0; ++ pls->init_sec_buf_read_offset = 0; ++ ret = reopen_demux_for_component(s, pls); ++ ++ return ret; ++} ++ ++static int dash_read_seek(AVFormatContext *s, int stream_index, int64_t timestamp, int flags) ++{ ++ int ret = 0; ++ DASHContext *c = s->priv_data; ++ int64_t seek_pos_msec = av_rescale_rnd(timestamp, 1000, ++ s->streams[stream_index]->time_base.den, ++ flags & AVSEEK_FLAG_BACKWARD ? ++ AV_ROUND_DOWN : AV_ROUND_UP); ++ if ((flags & AVSEEK_FLAG_BYTE) || c->is_live) ++ return AVERROR(ENOSYS); ++ if (c->cur_audio) { ++ ret = dash_seek(s, c->cur_audio, seek_pos_msec, flags); ++ } ++ if (!ret && c->cur_video) { ++ ret = dash_seek(s, c->cur_video, seek_pos_msec, flags); ++ } ++ return ret; ++} ++ ++static int dash_probe(AVProbeData *p) ++{ ++ if (!av_stristr(p->buf, "buf, "dash:profile:isoff-on-demand:2011") || ++ av_stristr(p->buf, "dash:profile:isoff-live:2011") || ++ av_stristr(p->buf, "dash:profile:isoff-live:2012") || ++ av_stristr(p->buf, "dash:profile:isoff-main:2011")) { ++ return AVPROBE_SCORE_MAX; ++ } ++ if (av_stristr(p->buf, "dash:profile")) { ++ return AVPROBE_SCORE_MAX >> 1; ++ } ++ ++ return 0; ++} ++ ++#define OFFSET(x) offsetof(DASHContext, x) ++#define FLAGS AV_OPT_FLAG_DECODING_PARAM ++static const AVOption dash_options[] = { ++ {NULL} ++}; ++ ++static const AVClass dash_class = { ++ .class_name = "dash", ++ .item_name = av_default_item_name, ++ .option = dash_options, ++ .version = LIBAVUTIL_VERSION_INT, ++}; ++ ++AVInputFormat ff_dash_demuxer = { ++ .name = "dash", ++ .long_name = NULL_IF_CONFIG_SMALL("Dynamic Adaptive Streaming over HTTP"), ++ .priv_class = &dash_class, ++ .priv_data_size = sizeof(DASHContext), ++ .read_probe = dash_probe, ++ .read_header = dash_read_header, ++ .read_packet = dash_read_packet, ++ .read_close = dash_close, ++ .read_seek = dash_read_seek, ++ .flags = AVFMT_NO_BYTE_SEEK, ++}; +--- a/libavformat/Makefile 2018-11-18 01:58:00.000000000 +0100 ++++ b/libavformat/Makefile 2023-02-14 20:19:47.774726060 +0100 +@@ -134,6 +134,7 @@ + OBJS-$(CONFIG_DATA_DEMUXER) += rawdec.o + OBJS-$(CONFIG_DATA_MUXER) += rawenc.o + OBJS-$(CONFIG_DASH_MUXER) += dashenc.o ++OBJS-$(CONFIG_DASH_DEMUXER) += dashdec.o + OBJS-$(CONFIG_DAUD_DEMUXER) += dauddec.o + OBJS-$(CONFIG_DAUD_MUXER) += daudenc.o + OBJS-$(CONFIG_DCSTR_DEMUXER) += dcstr.o +--- a/libavutil/avstring.c 2018-11-18 01:58:00.000000000 +0100 ++++ b/libavutil/avstring.c 2023-02-14 20:19:47.778726097 +0100 +@@ -231,6 +231,29 @@ + return c1 - c2; + } + ++char *av_strireplace(const char *str, const char *from, const char *to) ++{ ++ char *ret = NULL; ++ const char *pstr2, *pstr = str; ++ size_t tolen = strlen(to), fromlen = strlen(from); ++ AVBPrint pbuf; ++ ++ av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED); ++ while ((pstr2 = av_stristr(pstr, from))) { ++ av_bprint_append_data(&pbuf, pstr, pstr2 - pstr); ++ pstr = pstr2 + fromlen; ++ av_bprint_append_data(&pbuf, to, tolen); ++ } ++ av_bprint_append_data(&pbuf, pstr, strlen(pstr)); ++ if (!av_bprint_is_complete(&pbuf)) { ++ av_bprint_finalize(&pbuf, NULL); ++ } else { ++ av_bprint_finalize(&pbuf, &ret); ++ } ++ ++ return ret; ++} ++ + const char *av_basename(const char *path) + { + char *p = strrchr(path, '/'); +--- a/libavutil/avstring.h 2018-11-18 01:58:00.000000000 +0100 ++++ b/libavutil/avstring.h 2023-02-14 20:19:47.778726097 +0100 +@@ -67,6 +67,8 @@ + */ + char *av_stristr(const char *haystack, const char *needle); + ++char *av_strireplace(const char *str, const char *from, const char *to); ++ + /** + * Locate the first occurrence of the string needle in the string haystack + * where not more than hay_length characters are searched. A zero-length diff --git a/archive-patches/ffmpeg-cst/3.3.9/0006-ffmpeg-reset-compressed-header-flag.patch b/archive-patches/ffmpeg-cst/3.3.9/0006-ffmpeg-reset-compressed-header-flag.patch new file mode 100644 index 0000000..7d94b3d --- /dev/null +++ b/archive-patches/ffmpeg-cst/3.3.9/0006-ffmpeg-reset-compressed-header-flag.patch @@ -0,0 +1,11 @@ +# reset compressed header flag, fix http 302 request +--- a/libavformat/http.c 2017-05-04 15:49:41.890902000 +0200 ++++ b/libavformat/http.c 2017-07-30 11:30:47.502581448 +0200 +@@ -1248,6 +1248,7 @@ + s->willclose = 0; + s->end_chunked_post = 0; + s->end_header = 0; ++ s->compressed = 0; + if (post && !s->post_data && !send_expect_100) { + /* Pretend that it did work. We didn't read any header yet, since + * we've still to send the POST data, but the code calling this diff --git a/archive-patches/ffmpeg-cst/3.3.9/0007-ffmpeg-CST-fix-pts.patch b/archive-patches/ffmpeg-cst/3.3.9/0007-ffmpeg-CST-fix-pts.patch new file mode 100644 index 0000000..bad6105 --- /dev/null +++ b/archive-patches/ffmpeg-cst/3.3.9/0007-ffmpeg-CST-fix-pts.patch @@ -0,0 +1,15 @@ +--- a/libavformat/mpegts.c 2016-06-27 02:54:30.000000000 +0300 ++++ b/libavformat/mpegts.c 2016-08-20 01:39:36.668873256 +0300 +@@ -904,10 +904,10 @@ + pes->buffer = NULL; + reset_pes_packet_state(pes); + +- sd = av_packet_new_side_data(pkt, AV_PKT_DATA_MPEGTS_STREAM_ID, 1); ++ /*sd = av_packet_new_side_data(pkt, AV_PKT_DATA_MPEGTS_STREAM_ID, 1); + if (!sd) + return AVERROR(ENOMEM); +- *sd = pes->stream_id; ++ *sd = pes->stream_id;*/ + + return 0; + } diff --git a/archive-patches/ffmpeg-cst/3.3.9/0008-ffmpeg-CST-fix-sps-pps.patch b/archive-patches/ffmpeg-cst/3.3.9/0008-ffmpeg-CST-fix-sps-pps.patch new file mode 100755 index 0000000..8d87f3a --- /dev/null +++ b/archive-patches/ffmpeg-cst/3.3.9/0008-ffmpeg-CST-fix-sps-pps.patch @@ -0,0 +1,29 @@ +From 1c7b83f945e710a17a41ad9feb7dc929f26f2b0e Mon Sep 17 00:00:00 2001 +From: Jacek Jendrzej +Date: Wed, 28 Jun 2017 11:38:20 +0200 +Subject: [PATCH] fix sps/pps for cooli;This is commit that breaks seek in some + mkv.break with commit 6d2219e9f950b96279fd8464cc11c4d02518b629 + +--- + libavcodec/h264_mp4toannexb_bsf.c | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/libavcodec/h264_mp4toannexb_bsf.c b/libavcodec/h264_mp4toannexb_bsf.c +index 163d0f59ce..c895cf2829 100644 +--- a/libavcodec/h264_mp4toannexb_bsf.c ++++ b/libavcodec/h264_mp4toannexb_bsf.c +@@ -235,6 +235,11 @@ static int h264_mp4toannexb_filter(AVBSFContext *ctx, AVPacket *out) + if (!s->new_idr && unit_type == 5 && (buf[1] & 0x80)) + s->new_idr = 1; + ++ if (s->new_idr && unit_type == 6 && s->idr_sps_seen && s->idr_pps_seen){ ++ s->idr_sps_seen= 0; ++ s->idr_pps_seen= 0; ++ } ++ + /* prepend only to the first type 5 NAL unit of an IDR picture, if no sps/pps are already present */ + if (s->new_idr && unit_type == 5 && !s->idr_sps_seen && !s->idr_pps_seen) { + if ((ret=alloc_and_copy(out, +-- +2.13.2 + diff --git a/archive-patches/ffmpeg-cst/3.3/ffmpeg34-cst-add-ASF-VC1-Annex-G-and-RCV-bitstream-filters.patch b/archive-patches/ffmpeg-cst/3.3.9/0009-ffmpeg-CST-add-ASF-VC1-Annex-G-and-RCV-bitstream-filters.patch similarity index 78% rename from archive-patches/ffmpeg-cst/3.3/ffmpeg34-cst-add-ASF-VC1-Annex-G-and-RCV-bitstream-filters.patch rename to archive-patches/ffmpeg-cst/3.3.9/0009-ffmpeg-CST-add-ASF-VC1-Annex-G-and-RCV-bitstream-filters.patch index b028809..37df0fc 100644 --- a/archive-patches/ffmpeg-cst/3.3/ffmpeg34-cst-add-ASF-VC1-Annex-G-and-RCV-bitstream-filters.patch +++ b/archive-patches/ffmpeg-cst/3.3.9/0009-ffmpeg-CST-add-ASF-VC1-Annex-G-and-RCV-bitstream-filters.patch @@ -1,35 +1,45 @@ -diff --git a/libavcodec/Makefile b/libavcodec/Makefile -index c4ec09b1c4..4462d98ff9 100644 ---- a/libavcodec/Makefile -+++ b/libavcodec/Makefile -@@ -1024,6 +1024,8 @@ OBJS-$(CONFIG_NOISE_BSF) += noise_bsf.o - OBJS-$(CONFIG_NULL_BSF) += null_bsf.o +From c6f8769255ec6c4bb4ce02879d2332461ef1e24e Mon Sep 17 00:00:00 2001 +From: Jacek Jendrzej +Date: Sat, 12 Aug 2017 17:05:08 +0200 +Subject: [PATCH 7/7] add ASF VC1 Annex-G and RCV bitstream filters. Originally + done by Google but modified by us to convert certain VC1 in MKV without + startcodes. port fot 3.x + +Signed-off-by: Jacek Jendrzej +--- + libavcodec/Makefile | 2 + + libavcodec/bitstream_filters.c | 2 + + libavcodec/vc1_asftoannexg_bsf.c | 182 +++++++++++++++++++++++++++++++++++++++ + libavcodec/vc1_asftorcv_bsf.c | 95 ++++++++++++++++++++ + 4 files changed, 281 insertions(+) + create mode 100644 libavcodec/vc1_asftoannexg_bsf.c + create mode 100644 libavcodec/vc1_asftorcv_bsf.c + +--- a/libavcodec/bitstream_filters.c 2018-11-18 01:58:03.000000000 +0100 ++++ b/libavcodec/bitstream_filters.c 2023-02-14 18:26:27.787945035 +0100 +@@ -41,6 +41,8 @@ extern const AVBitStreamFilter ff_noise_ + extern const AVBitStreamFilter ff_remove_extradata_bsf; + extern const AVBitStreamFilter ff_text2movsub_bsf; + extern const AVBitStreamFilter ff_vp9_superframe_bsf; ++extern const AVBitStreamFilter ff_vc1_asftoannexg_bsf; ++extern const AVBitStreamFilter ff_vc1_asftorcv_bsf; + + #include "libavcodec/bsf_list.c" + +--- a/libavcodec/Makefile 2018-11-18 01:57:59.000000000 +0100 ++++ b/libavcodec/Makefile 2023-02-14 18:31:48.627199402 +0100 +@@ -987,6 +987,8 @@ OBJS-$(CONFIG_MP3_HEADER_DECOMPRESS_BSF) + OBJS-$(CONFIG_NOISE_BSF) += noise_bsf.o OBJS-$(CONFIG_REMOVE_EXTRADATA_BSF) += remove_extradata_bsf.o OBJS-$(CONFIG_TEXT2MOVSUB_BSF) += movsub_bsf.o +OBJS-$(CONFIG_VC1_ASFTORCV_BSF) += vc1_asftorcv_bsf.o +OBJS-$(CONFIG_VC1_ASFTOANNEXG_BSF) += vc1_asftoannexg_bsf.o vc1.o - OBJS-$(CONFIG_VP9_RAW_REORDER_BSF) += vp9_raw_reorder_bsf.o OBJS-$(CONFIG_VP9_SUPERFRAME_BSF) += vp9_superframe_bsf.o - OBJS-$(CONFIG_VP9_SUPERFRAME_SPLIT_BSF) += vp9_superframe_split_bsf.o -diff --git a/libavcodec/bitstream_filters.c b/libavcodec/bitstream_filters.c -index ace2a1e01f..ac8be8c90b 100644 ---- a/libavcodec/bitstream_filters.c -+++ b/libavcodec/bitstream_filters.c -@@ -41,6 +41,8 @@ extern const AVBitStreamFilter ff_noise_bsf; - extern const AVBitStreamFilter ff_null_bsf; - extern const AVBitStreamFilter ff_remove_extradata_bsf; - extern const AVBitStreamFilter ff_text2movsub_bsf; -+extern const AVBitStreamFilter ff_vc1_asftoannexg_bsf; -+extern const AVBitStreamFilter ff_vc1_asftorcv_bsf; - extern const AVBitStreamFilter ff_vp9_raw_reorder_bsf; - extern const AVBitStreamFilter ff_vp9_superframe_bsf; - extern const AVBitStreamFilter ff_vp9_superframe_split_bsf; -diff --git a/libavcodec/vc1_asftoannexg_bsf.c b/libavcodec/vc1_asftoannexg_bsf.c -new file mode 100644 -index 0000000000..ba68346118 ---- /dev/null -+++ b/libavcodec/vc1_asftoannexg_bsf.c -@@ -0,0 +1,189 @@ + + # thread libraries +--- a/libavcodec/vc1_asftoannexg_bsf.c 1970-01-01 01:00:00.000000000 +0100 ++++ b/libavcodec/vc1_asftoannexg_bsf.c 2023-02-14 18:21:36.120977196 +0100 +@@ -0,0 +1,182 @@ +/* + * copyright (c) 2010 Google Inc. + * copyright (c) 2013 CoolStream International Ltd. @@ -142,8 +152,8 @@ index 0000000000..ba68346118 +{ + ASFTOANNEXGBSFContext* bsfctx = ctx->priv_data; + AVPacket *in; -+ int keyframe; -+ int ret; ++ int keyframe = 0; ++ int ret = 0; + uint8_t* bs = NULL; + + ret = ff_bsf_get_packet(ctx, &in); @@ -153,29 +163,28 @@ index 0000000000..ba68346118 + keyframe = in->flags & AV_PKT_FLAG_KEY; + if(in->size >= 1 && !find_codec_data(bsfctx, in->data, in->size, keyframe)) { +// av_log(NULL, AV_LOG_INFO, "Nothing to do: %i\n",in->size); -+ av_packet_move_ref(out, in); -+ av_packet_free(&in); ++ out->data = in->data; ++ out->size = in->size; + return 0; + } + + if(!ctx->par_in->extradata || ctx->par_in->extradata_size < 16) { + av_log(NULL, AV_LOG_INFO, "Extradata size too small: %i\n", ctx->par_in->extradata_size); -+ av_packet_move_ref(out, in); -+ av_packet_free(&in); ++ out->data = in->data; ++ out->size = in->size; + return 0; + } + -+ if (!bsfctx->frames && parse_extradata(bsfctx, ctx->par_in->extradata , ctx->par_in->extradata_size) < 0) { -+ av_packet_free(&in); ++ if (!bsfctx->frames && parse_extradata(bsfctx, ctx->par_in->extradata , ctx->par_in->extradata_size ) < 0) { ++ av_freep(in); + av_log(NULL, AV_LOG_ERROR, "Cannot parse extra data!\n"); + return -1; + } + + if (keyframe) { + // If this is the keyframe, need to put sequence header and entry point header. -+ ret = av_new_packet(out, bsfctx->seq_header_size + bsfctx->ep_header_size + 4 + in->size); -+ if (ret < 0) -+ goto exit; ++ out->size = bsfctx->seq_header_size + bsfctx->ep_header_size + 4 + in->size; ++ out->data = av_malloc(out->size); + bs = out->data; + + memcpy(bs, bsfctx->seq_header, bsfctx->seq_header_size); @@ -183,9 +192,8 @@ index 0000000000..ba68346118 + memcpy(bs, bsfctx->ep_header, bsfctx->ep_header_size); + bs += bsfctx->ep_header_size; + } else { -+ ret = av_new_packet(out, 4 + in->size); -+ if (ret < 0) -+ goto exit; ++ out->size = 4 + in->size; ++ out->data = av_malloc(out->size); + bs = out->data; + } + @@ -193,14 +201,9 @@ index 0000000000..ba68346118 + bytestream_put_be32(&bs, VC1_CODE_FRAME); + memcpy(bs, in->data, in->size); + ++bsfctx->frames; ++ return 1; + -+ ret = av_packet_copy_props(out, in); -+ -+exit: -+ av_packet_free(&in); -+ -+ return ret; -+} ++ } + +static void asftoannexg_close(AVBSFContext *bsfc) { + ASFTOANNEXGBSFContext *bsfctx = bsfc->priv_data; @@ -217,14 +220,11 @@ index 0000000000..ba68346118 + .priv_data_size = sizeof(ASFTOANNEXGBSFContext), + .filter = asftoannexg_filter, + .close = asftoannexg_close, -+ .codec_ids = codec_ids, ++ .codec_ids = codec_ids +}; -diff --git a/libavcodec/vc1_asftorcv_bsf.c b/libavcodec/vc1_asftorcv_bsf.c -new file mode 100644 -index 0000000000..bdce5b5003 ---- /dev/null -+++ b/libavcodec/vc1_asftorcv_bsf.c -@@ -0,0 +1,104 @@ +--- a/libavcodec/vc1_asftorcv_bsf.c 1970-01-01 01:00:00.000000000 +0100 ++++ b/libavcodec/vc1_asftorcv_bsf.c 2023-02-14 18:21:36.120977196 +0100 +@@ -0,0 +1,95 @@ +/* + * copyright (c) 2010 Google Inc. + * copyright (c) 2017 Jacek Jendrzej port to 3.x @@ -259,8 +259,8 @@ index 0000000000..bdce5b5003 +static int asftorcv_filter(AVBSFContext *ctx, AVPacket *out){ + ASFTORCVBSFContext* bsfctx = ctx->priv_data; + AVPacket *in; -+ int keyframe; -+ int ret; ++ int keyframe = 0; ++ int ret = 0; + uint8_t* bs = NULL; + + ret = ff_bsf_get_packet(ctx, &in); @@ -271,9 +271,8 @@ index 0000000000..bdce5b5003 + + if (!bsfctx->frames) { + // Write the header if this is the first frame. -+ ret = av_new_packet(out, RCV_STREAM_HEADER_SIZE + RCV_PICTURE_HEADER_SIZE + in->size); -+ if (ret < 0) -+ goto exit; ++ out->data = av_malloc(RCV_STREAM_HEADER_SIZE + RCV_PICTURE_HEADER_SIZE + in->size); ++ out->size = RCV_STREAM_HEADER_SIZE + RCV_PICTURE_HEADER_SIZE + in->size; + bs = out->data; + + // The following structure of stream header comes from libavformat/vc1testenc.c. @@ -296,9 +295,8 @@ index 0000000000..bdce5b5003 + // See: libavformat/vc1testenc.c + bytestream_put_le32(&bs, 0xFFFFFFFF); + } else { -+ ret = av_new_packet(out, RCV_PICTURE_HEADER_SIZE + in->size); -+ if (ret < 0) -+ goto exit; ++ out->data = av_malloc(RCV_PICTURE_HEADER_SIZE + in->size); ++ out->size = RCV_PICTURE_HEADER_SIZE + in->size; + bs = out->data; + } + @@ -310,13 +308,7 @@ index 0000000000..bdce5b5003 + memcpy(bs, in->data, in->size); + + ++bsfctx->frames; -+ -+ ret = av_packet_copy_props(out, in); -+ -+exit: -+ av_packet_free(&in); -+ -+ return ret; ++ return 0; +} + +static const enum AVCodecID codec_ids[] = { @@ -324,8 +316,11 @@ index 0000000000..bdce5b5003 +}; + +AVBitStreamFilter ff_vc1_asftorcv_bsf = { -+ .name = "vc1_asftorcv", ++ .name = "vc1_asftorcv", + .priv_data_size = sizeof(ASFTORCVBSFContext), -+ .filter = asftorcv_filter, -+ .codec_ids = codec_ids, ++ .filter = asftorcv_filter +}; + +-- +2.14.1 + diff --git a/archive-patches/ffmpeg-cst/3.3.9/0010-ffmpeg-CST-allow-to-choose-rtmp-impl-at-runtime.patch b/archive-patches/ffmpeg-cst/3.3.9/0010-ffmpeg-CST-allow-to-choose-rtmp-impl-at-runtime.patch new file mode 100644 index 0000000..14225bc --- /dev/null +++ b/archive-patches/ffmpeg-cst/3.3.9/0010-ffmpeg-CST-allow-to-choose-rtmp-impl-at-runtime.patch @@ -0,0 +1,112 @@ +--- ffmpeg-3.2.2/configure ++++ ffmpeg-3.2.2/configure +@@ -3033,10 +3033,8 @@ + # protocols + async_protocol_deps="threads" + bluray_protocol_deps="libbluray" +-ffrtmpcrypt_protocol_deps="!librtmp_protocol" + ffrtmpcrypt_protocol_deps_any="gcrypt gmp openssl" + ffrtmpcrypt_protocol_select="tcp_protocol" +-ffrtmphttp_protocol_deps="!librtmp_protocol" + ffrtmphttp_protocol_select="http_protocol" + ftp_protocol_select="tcp_protocol" + gopher_protocol_select="network" +@@ -3053,14 +3051,12 @@ + libssh_protocol_deps="libssh" + mmsh_protocol_select="http_protocol" + mmst_protocol_select="network" +-rtmp_protocol_deps="!librtmp_protocol" +-rtmp_protocol_select="tcp_protocol" +-rtmpe_protocol_select="ffrtmpcrypt_protocol" +-rtmps_protocol_deps="!librtmp_protocol" +-rtmps_protocol_select="tls_protocol" +-rtmpt_protocol_select="ffrtmphttp_protocol" +-rtmpte_protocol_select="ffrtmpcrypt_protocol ffrtmphttp_protocol" +-rtmpts_protocol_select="ffrtmphttp_protocol https_protocol" ++ffrtmp_protocol_select="tcp_protocol" ++ffrtmpe_protocol_select="ffrtmpcrypt_protocol" ++ffrtmps_protocol_select="tls_protocol" ++ffrtmpt_protocol_select="ffrtmphttp_protocol" ++ffrtmpte_protocol_select="ffrtmpcrypt_protocol ffrtmphttp_protocol" ++ffrtmpts_protocol_select="ffrtmphttp_protocol https_protocol" + rtp_protocol_select="udp_protocol" + sctp_protocol_deps="struct_sctp_event_subscribe struct_msghdr_msg_flags" + sctp_protocol_select="network" +--- ffmpeg-3.2.2/libavformat/rtmpproto.c ++++ ffmpeg-3.2.2/libavformat/rtmpproto.c +@@ -2612,7 +2612,7 @@ + static int rtmp_open(URLContext *s, const char *uri, int flags, AVDictionary **opts) + { + RTMPContext *rt = s->priv_data; +- char proto[8], hostname[256], path[1024], auth[100], *fname; ++ char *proto, tmpProto[10], hostname[256], path[1024], auth[100], *fname; + char *old_app, *qmark, *n, fname_buffer[1024]; + uint8_t buf[2048]; + int port; +@@ -2623,7 +2623,7 @@ + + rt->is_input = !(flags & AVIO_FLAG_WRITE); + +- av_url_split(proto, sizeof(proto), auth, sizeof(auth), ++ memset(tmpProto, 0, sizeof(tmpProto)); proto = &tmpProto[2]; av_url_split(tmpProto, sizeof(tmpProto), auth, sizeof(auth), + hostname, sizeof(hostname), &port, + path, sizeof(path), s->filename); + +@@ -3157,9 +3157,9 @@ + }; + + +-RTMP_PROTOCOL(rtmp) +-RTMP_PROTOCOL(rtmpe) +-RTMP_PROTOCOL(rtmps) +-RTMP_PROTOCOL(rtmpt) +-RTMP_PROTOCOL(rtmpte) +-RTMP_PROTOCOL(rtmpts) ++RTMP_PROTOCOL(ffrtmp) ++RTMP_PROTOCOL(ffrtmpe) ++RTMP_PROTOCOL(ffrtmps) ++RTMP_PROTOCOL(ffrtmpt) ++RTMP_PROTOCOL(ffrtmpte) ++RTMP_PROTOCOL(ffrtmpts) +--- a/libavformat/Makefile ++++ b/libavformat/Makefile +@@ -566,12 +566,12 @@ + OBJS-$(CONFIG_MMST_PROTOCOL) += mmst.o mms.o asf.o + OBJS-$(CONFIG_PIPE_PROTOCOL) += file.o + OBJS-$(CONFIG_PROMPEG_PROTOCOL) += prompeg.o +-OBJS-$(CONFIG_RTMP_PROTOCOL) += rtmpproto.o rtmppkt.o +-OBJS-$(CONFIG_RTMPE_PROTOCOL) += rtmpproto.o rtmppkt.o +-OBJS-$(CONFIG_RTMPS_PROTOCOL) += rtmpproto.o rtmppkt.o +-OBJS-$(CONFIG_RTMPT_PROTOCOL) += rtmpproto.o rtmppkt.o +-OBJS-$(CONFIG_RTMPTE_PROTOCOL) += rtmpproto.o rtmppkt.o +-OBJS-$(CONFIG_RTMPTS_PROTOCOL) += rtmpproto.o rtmppkt.o ++OBJS-$(CONFIG_FFRTMP_PROTOCOL) += rtmpproto.o rtmppkt.o ++OBJS-$(CONFIG_FFRTMPE_PROTOCOL) += rtmpproto.o rtmppkt.o ++OBJS-$(CONFIG_FFRTMPS_PROTOCOL) += rtmpproto.o rtmppkt.o ++OBJS-$(CONFIG_FFRTMPT_PROTOCOL) += rtmpproto.o rtmppkt.o ++OBJS-$(CONFIG_FFRTMPTE_PROTOCOL) += rtmpproto.o rtmppkt.o ++OBJS-$(CONFIG_FFRTMPTS_PROTOCOL) += rtmpproto.o rtmppkt.o + OBJS-$(CONFIG_RTP_PROTOCOL) += rtpproto.o + OBJS-$(CONFIG_SCTP_PROTOCOL) += sctp.o + OBJS-$(CONFIG_SRTP_PROTOCOL) += srtpproto.o srtp.o +--- a/libavformat/protocols.c ++++ b/libavformat/protocols.c +@@ -44,12 +44,12 @@ + extern const URLProtocol ff_md5_protocol; + extern const URLProtocol ff_pipe_protocol; + extern const URLProtocol ff_prompeg_protocol; +-extern const URLProtocol ff_rtmp_protocol; +-extern const URLProtocol ff_rtmpe_protocol; +-extern const URLProtocol ff_rtmps_protocol; +-extern const URLProtocol ff_rtmpt_protocol; +-extern const URLProtocol ff_rtmpte_protocol; +-extern const URLProtocol ff_rtmpts_protocol; ++extern const URLProtocol ff_ffrtmp_protocol; ++extern const URLProtocol ff_ffrtmpe_protocol; ++extern const URLProtocol ff_ffrtmps_protocol; ++extern const URLProtocol ff_ffrtmpt_protocol; ++extern const URLProtocol ff_ffrtmpte_protocol; ++extern const URLProtocol ff_ffrtmpts_protocol; + extern const URLProtocol ff_rtp_protocol; + extern const URLProtocol ff_sctp_protocol; + extern const URLProtocol ff_srtp_protocol; diff --git a/archive-patches/ffmpeg-cst/3.3.9/0011-ffmpeg-discont.patch b/archive-patches/ffmpeg-cst/3.3.9/0011-ffmpeg-discont.patch new file mode 100644 index 0000000..67b86b0 --- /dev/null +++ b/archive-patches/ffmpeg-cst/3.3.9/0011-ffmpeg-discont.patch @@ -0,0 +1,125 @@ +diff -u -pr a/ffmpeg.c b/ffmpeg.c +--- a/ffmpeg.c 2021-10-02 14:03:12.000000000 +0200 ++++ b/ffmpeg.c 2021-11-30 17:52:38.171328331 +0100 +@@ -4332,7 +4332,7 @@ static int process_input(int file_index) + if ((ist->dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO || + ist->dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) && + pkt_dts != AV_NOPTS_VALUE && ist->next_dts == AV_NOPTS_VALUE && !copy_ts +- && (is->iformat->flags & AVFMT_TS_DISCONT) && ifile->last_ts != AV_NOPTS_VALUE) { ++ && (is->iformat->flags & AVFMT_TS_DISCONT) && ifile->last_ts != AV_NOPTS_VALUE && !force_dts_monotonicity) { + int64_t delta = pkt_dts - ifile->last_ts; + if (delta < -1LL*dts_delta_threshold*AV_TIME_BASE || + delta > 1LL*dts_delta_threshold*AV_TIME_BASE){ +@@ -4360,7 +4360,7 @@ static int process_input(int file_index) + if ((ist->dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO || + ist->dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) && + pkt_dts != AV_NOPTS_VALUE && ist->next_dts != AV_NOPTS_VALUE && +- !copy_ts) { ++ !copy_ts && !force_dts_monotonicity) { + int64_t delta = pkt_dts - ist->next_dts; + if (is->iformat->flags & AVFMT_TS_DISCONT) { + if (delta < -1LL*dts_delta_threshold*AV_TIME_BASE || +@@ -4395,6 +4395,43 @@ static int process_input(int file_index) + if (pkt.dts != AV_NOPTS_VALUE) + ifile->last_ts = av_rescale_q(pkt.dts, ist->st->time_base, AV_TIME_BASE_Q); + ++ if ((ist->dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO || ++ ist->dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) && ++ (pkt.pts != AV_NOPTS_VALUE || pkt.dts != AV_NOPTS_VALUE) && ++ force_dts_monotonicity) { ++ int64_t ff_pts_error = 0; ++ int64_t ff_dts_error = 0; ++ int64_t ff_dts_threshold = av_rescale_q(dts_monotonicity_threshold, AV_TIME_BASE_Q, ist->st->time_base); ++ ++ // adjust the incoming packet by the accumulated monotonicity error ++ if (pkt.pts != AV_NOPTS_VALUE) { ++ pkt.pts += ifile->ff_timestamp_monotonicity_offset; ++ if (ist->next_pts != AV_NOPTS_VALUE) { ++ ff_pts_error = av_rescale_q(ist->next_pts, AV_TIME_BASE_Q, ist->st->time_base) - pkt.pts; ++ } ++ } ++ if (pkt.dts != AV_NOPTS_VALUE) { ++ pkt.dts += ifile->ff_timestamp_monotonicity_offset; ++ if (ist->next_dts != AV_NOPTS_VALUE) { ++ ff_dts_error = av_rescale_q(ist->next_dts, AV_TIME_BASE_Q, ist->st->time_base) - pkt.dts; ++ } ++ } ++ ++ if (ff_dts_error > 0 || ff_dts_error < (-ff_dts_threshold) || ff_pts_error < (-ff_dts_threshold)) { ++ if (pkt.dts == AV_NOPTS_VALUE /*|| ist->next_dts != AV_NOPTS_VALUE*/) { ++ pkt.pts += ff_pts_error; ++ ifile->ff_timestamp_monotonicity_offset += ff_pts_error; ++ av_log(is, AV_LOG_INFO, "Incoming PTS error %"PRId64", offsetting subsequent timestamps by %"PRId64" to correct\n", ff_pts_error, ifile->ff_timestamp_monotonicity_offset); ++ } ++ else { ++ pkt.pts += ff_dts_error; ++ pkt.dts += ff_dts_error; ++ ifile->ff_timestamp_monotonicity_offset += ff_dts_error; ++ av_log(is, AV_LOG_INFO, "Incoming DTS error %"PRId64", offsetting subsequent timestamps by %"PRId64" to correct\n", ff_dts_error, ifile->ff_timestamp_monotonicity_offset); ++ } ++ } ++ } ++ + if (debug_ts) { + av_log(NULL, AV_LOG_INFO, "demuxer+ffmpeg -> ist_index:%d type:%s pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s off:%s off_time:%s\n", + ifile->ist_index + pkt.stream_index, av_get_media_type_string(ist->dec_ctx->codec_type), +Nur in _ffmpeg-git-2ba896f: ffmpeg.c.orig. +Nur in _ffmpeg-git-2ba896f: ffmpeg.c.rej. +diff -u -pr a/ffmpeg.h b/ffmpeg.h +--- a/ffmpeg.h 2021-10-02 14:03:12.000000000 +0200 ++++ b/ffmpeg.h 2021-11-30 17:47:55.128659787 +0100 +@@ -417,6 +417,10 @@ typedef struct InputFile { + int joined; /* the thread has been joined */ + int thread_queue_size; /* maximum number of queued packets */ + #endif ++ ++ // A value added to inbound timestamps to prevent them from going "backward" in cases such as HLS discontinuities ++ int64_t ff_timestamp_monotonicity_offset; ++ + } InputFile; + + enum forced_keyframes_const { +@@ -584,6 +588,9 @@ extern float audio_drift_threshold; + extern float dts_delta_threshold; + extern float dts_error_threshold; + ++extern int dts_monotonicity_threshold; ++extern int force_dts_monotonicity; ++ + extern int audio_volume; + extern int audio_sync_method; + extern int video_sync_method; +Nur in _ffmpeg-git-2ba896f: ffmpeg.h.orig. +diff -u -pr a/ffmpeg_opt.c b/ffmpeg_opt.c +--- a/ffmpeg_opt.c 2021-10-02 14:03:12.000000000 +0200 ++++ b/ffmpeg_opt.c 2021-11-30 17:47:55.128659787 +0100 +@@ -99,6 +99,9 @@ float audio_drift_threshold = 0.1; + float dts_delta_threshold = 10; + float dts_error_threshold = 3600*30; + ++int dts_monotonicity_threshold = AV_TIME_BASE; ++int force_dts_monotonicity = 0; ++ + int audio_volume = 256; + int audio_sync_method = 0; + int video_sync_method = VSYNC_AUTO; +@@ -1070,6 +1073,7 @@ static int open_input_file(OptionsContex + f->recording_time = o->recording_time; + f->input_ts_offset = o->input_ts_offset; + f->ts_offset = o->input_ts_offset - (copy_ts ? (start_at_zero && ic->start_time != AV_NOPTS_VALUE ? ic->start_time : 0) : timestamp); ++ f->ff_timestamp_monotonicity_offset = 0; + f->nb_streams = ic->nb_streams; + f->rate_emu = o->rate_emu; + f->accurate_seek = o->accurate_seek; +@@ -3404,6 +3408,10 @@ const OptionDef options[] = { + { "apad", OPT_STRING | HAS_ARG | OPT_SPEC | + OPT_OUTPUT, { .off = OFFSET(apad) }, + "audio pad", "" }, ++ { "force_dts_monotonicity", OPT_BOOL | OPT_EXPERT, { &force_dts_monotonicity }, ++ "correct dts monotonicity errors" }, ++ { "dts_monotonicity_threshold", HAS_ARG | OPT_INT | OPT_EXPERT, { &dts_monotonicity_threshold }, ++ "dts correction threshold for forward jumps", "microseconds" }, + { "dts_delta_threshold", HAS_ARG | OPT_FLOAT | OPT_EXPERT, { &dts_delta_threshold }, + "timestamp discontinuity delta threshold", "threshold" }, + { "dts_error_threshold", HAS_ARG | OPT_FLOAT | OPT_EXPERT, { &dts_error_threshold }, +Nur in _ffmpeg-git-2ba896f: ffmpeg_opt.c.orig. diff --git a/archive-patches/ffmpeg-cst/3.3.9/0012-ffmpeg-nolog.patch b/archive-patches/ffmpeg-cst/3.3.9/0012-ffmpeg-nolog.patch new file mode 100644 index 0000000..b8add3f --- /dev/null +++ b/archive-patches/ffmpeg-cst/3.3.9/0012-ffmpeg-nolog.patch @@ -0,0 +1,28 @@ +diff --git a/libavutil/log.c b/libavutil/log.c +index 5948e50467..528eaa728c 100644 +--- a/libavutil/log.c ++++ b/libavutil/log.c +@@ -53,7 +53,7 @@ static AVMutex mutex = AV_MUTEX_INITIALIZER; + #define BACKTRACE_LOGLEVEL AV_LOG_ERROR + #endif + +-static int av_log_level = AV_LOG_INFO; ++static int av_log_level = AV_LOG_ERROR; // NOT WORKING for libs + static int flags; + + #define NB_LEVELS 8 +@@ -366,14 +366,6 @@ static void (*av_log_callback)(void*, int, const char*, va_list) = + + void av_log(void* avcl, int level, const char *fmt, ...) + { +- AVClass* avc = avcl ? *(AVClass **) avcl : NULL; +- va_list vl; +- va_start(vl, fmt); +- if (avc && avc->version >= (50 << 16 | 15 << 8 | 2) && +- avc->log_level_offset_offset && level >= AV_LOG_FATAL) +- level += *(int *) (((uint8_t *) avcl) + avc->log_level_offset_offset); +- av_vlog(avcl, level, fmt, vl); +- va_end(vl); + } + + void av_log_once(void* avcl, int initial_level, int subsequent_level, int *state, const char *fmt, ...) diff --git a/archive-patches/ffmpeg-cst/3.3.9/0013-ffmpeg-disable-whitelist.patch b/archive-patches/ffmpeg-cst/3.3.9/0013-ffmpeg-disable-whitelist.patch new file mode 100644 index 0000000..df1560f --- /dev/null +++ b/archive-patches/ffmpeg-cst/3.3.9/0013-ffmpeg-disable-whitelist.patch @@ -0,0 +1,19 @@ +diff --git a/libavformat/avio.c b/libavformat/avio.c +index 4846bbd8c6..978bf72994 100644 +--- a/libavformat/avio.c ++++ b/libavformat/avio.c +@@ -180,12 +180,12 @@ int ffurl_connect(URLContext *uc, AVDictionary **options) + (uc->protocol_whitelist && !strcmp(uc->protocol_whitelist, e->value))); + av_assert0(!(e=av_dict_get(*options, "protocol_blacklist", NULL, 0)) || + (uc->protocol_blacklist && !strcmp(uc->protocol_blacklist, e->value))); +- ++/* + if (uc->protocol_whitelist && av_match_list(uc->prot->name, uc->protocol_whitelist, ',') <= 0) { + av_log(uc, AV_LOG_ERROR, "Protocol '%s' not on whitelist '%s'!\n", uc->prot->name, uc->protocol_whitelist); + return AVERROR(EINVAL); + } +- ++*/ + if (uc->protocol_blacklist && av_match_list(uc->prot->name, uc->protocol_blacklist, ',') > 0) { + av_log(uc, AV_LOG_ERROR, "Protocol '%s' on blacklist '%s'!\n", uc->prot->name, uc->protocol_blacklist); + return AVERROR(EINVAL); -- 2.39.5