Message ID | 20200823122355.188611-1-sj.hc_Zhong@sjtu.edu.cn |
---|---|
State | New |
Headers | show |
Series | [FFmpeg-devel,GSoC,v3,1/7] avformat/abr: Adaptive Bitrate support | expand |
Context | Check | Description |
---|---|---|
andriy/default | pending | |
andriy/make | success | Make finished |
andriy/make_fate | success | Make fate finished |
My GSoC project is “ABR meets FFmpeg” which aim to add an ABR module to FFmpeg. Report in brief: Describe my work briefly - Implement ABR module for FFmpeg: ffabr protocol. - Use ffabr protocol in hls, support a switch mechanism. - Test the hls with abr in ffplay. What is done - Added a internal protocol ffabr to make switch request according to throughput. - Enhanced hls.c using ffabr. - Implement a switch mechanism to deal with general switch problem. - Implement abr support in ffplay. Further work could be done - Test more hls manifest examples. - Have dash support. - Add more abr algorithms to ffabr. Describe my work in detail I have written some more details about my work on this posted in my blog: https://spartazhc.github.io/2020/08/23/ABR-meets-FFmpeg-in-detail/. You could read it if you are interested. Regards, Hongcheng Zhong
> 在 2020年8月23日,20:23,Hongcheng Zhong <sj.hc_zhong@sjtu.edu.cn> 写道: > > From: spartazhc <spartazhc@gmail.com> > > Add abr module for hls/dash. > > Signed-off-by: spartazhc <spartazhc@gmail.com> > > v1 fixed: > 1. add an "ff" prefix to the protocol name to mark it internal. > 2. use 1.2f for float constant 1.2. > 3. simplify abr_seek for we just need AVSEEK_SIZE only. > > v2 fixed: > 1. fix error return > 2. simplify abr_seek > > v3 fixed: > 1. rewrite hls_param_parse function > 2. fix error code return > 3. use unsigned type and const prefix > 4. fix documentation > 5. fix abr_rule > 6. rename cur_pls to cur_var > 7. add type input and output > --- > doc/protocols.texi | 7 ++ > libavformat/Makefile | 1 + > libavformat/ffabr.c | 271 ++++++++++++++++++++++++++++++++++++++++ > libavformat/protocols.c | 1 + > 4 files changed, 280 insertions(+) > create mode 100644 libavformat/ffabr.c > > diff --git a/doc/protocols.texi b/doc/protocols.texi > index 7b3df96fda..e31de80ab6 100644 > --- a/doc/protocols.texi > +++ b/doc/protocols.texi > @@ -232,6 +232,13 @@ For example, to convert a GIF file given inline with @command{ffmpeg}: > ffmpeg -i "data:image/gif;base64,R0lGODdhCAAIAMIEAAAAAAAA//8AAP//AP///////////////ywAAAAACAAIAAADF0gEDLojDgdGiJdJqUX02iB4E8Q9jUMkADs=" smiley.png > @end example > > +@section ffabr > + > +Adaptive bitrate sub-protocol work for hls/dash, ffabr is internal. > + > +The ffabr protocol takes stream information from hls/dash as input, > +use bandwidth estimation to decide whether to switch or not. > + > @section file > > File access protocol. > diff --git a/libavformat/Makefile b/libavformat/Makefile > index cbb33fe37c..68b004eee0 100644 > --- a/libavformat/Makefile > +++ b/libavformat/Makefile > @@ -598,6 +598,7 @@ OBJS-$(CONFIG_CACHE_PROTOCOL) += cache.o > OBJS-$(CONFIG_CONCAT_PROTOCOL) += concat.o > OBJS-$(CONFIG_CRYPTO_PROTOCOL) += crypto.o > OBJS-$(CONFIG_DATA_PROTOCOL) += data_uri.o > +OBJS-$(CONFIG_FFABR_PROTOCOL) += ffabr.o > OBJS-$(CONFIG_FFRTMPCRYPT_PROTOCOL) += rtmpcrypt.o rtmpdigest.o rtmpdh.o > OBJS-$(CONFIG_FFRTMPHTTP_PROTOCOL) += rtmphttp.o > OBJS-$(CONFIG_FILE_PROTOCOL) += file.o > diff --git a/libavformat/ffabr.c b/libavformat/ffabr.c > new file mode 100644 > index 0000000000..1785ef5643 > --- /dev/null > +++ b/libavformat/ffabr.c > @@ -0,0 +1,271 @@ > +/* > + * Adaptive Bitrate Module for HLS / DASH > + * Copyright (c) 2020 > + * > + * 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 <math.h> > +#include "avformat.h" > +#include "libavutil/avassert.h" > +#include "libavutil/avstring.h" > +#include "libavutil/common.h" > +#include "libavutil/opt.h" > +#include "libavutil/time.h" > +#include "url.h" > + > +#define ABR_NOT_SWITCH -1 > + > +enum ABRFormatType { > + ABR_TYPE_HLS, > + ABR_TYPE_DASH > +}; > + > +typedef struct Variant { > + uint32_t bitrate; > + size_t index; > +} variant; > + > +typedef struct ABRContext { > + const AVClass *class; > + URLContext *hd; > + AVDictionary *abr_params; > + AVDictionary *abr_metadata; > + enum ABRFormatType format; > + uint8_t cur_var; > + uint8_t type; > + int8_t can_switch; > + size_t n_variants; > + variant *variants; > + size_t n_throughputs; > + float *throughputs; > +} ABRContext; > + > +static float harmonic_mean(const float *arr, size_t num) > +{ > + float tmp = 0; > + > + if (!num) return 0; > + > + for (size_t i = 0; i < num; i++) { > + tmp += 1 / arr[i]; > + } > + > + return num / tmp; > +} > + > +static int hls_param_parse(ABRContext *c, const AVDictionaryEntry *entry) > +{ > + AVDictionaryEntry *en; > + size_t index; > + char key_tmp[20]; > + > + > + en = av_dict_get(c->abr_params, "cur_var", entry, AV_DICT_IGNORE_SUFFIX); > + if (en) { > + c->cur_var = strtol(en->value, NULL, 10); I saw you define it with uint8_t, maybe this will overflow if use long strtol or define it int64_t and strtoll here? > + } > + en = av_dict_get(c->abr_params, "type", entry, AV_DICT_IGNORE_SUFFIX); > + if (en) { > + c->type = strtol(en->value, NULL, 10); I saw you define it with uint8_t, maybe this will overflow if use long strtol > + } > + en = av_dict_get(c->abr_params, "can_switch", entry, AV_DICT_IGNORE_SUFFIX); > + if (en) { > + c->can_switch = strtol(en->value, NULL, 10); I saw you define it with int8_t, maybe this will overflow if use long strtol > + } > + en = av_dict_get(c->abr_params, "n_variants", entry, AV_DICT_IGNORE_SUFFIX); > + if (en) { > + c->n_variants = strtol(en->value, NULL, 10); > + c->variants = av_mallocz(sizeof(variant) * c->n_variants); > + if (!c->variants) > + return AVERROR(ENOMEM); > + index = 0; > + snprintf(key_tmp, sizeof(key_tmp), "variant_bitrate%ld", index); > + while ((en = av_dict_get(c->abr_params, key_tmp, entry, AV_DICT_IGNORE_SUFFIX)) > + && index < c->n_variants) { > + c->variants[index].bitrate = strtol(en->value, NULL, 10); > + c->variants[index].index = index; > + index++; > + snprintf(key_tmp, sizeof(key_tmp), "variant_bitrate%ld", index); > + } > + } > + en = av_dict_get(c->abr_params, "n_throughputs", entry, AV_DICT_IGNORE_SUFFIX); > + if (en) { > + c->n_throughputs = strtol(en->value, NULL, 10); > + if (!c->n_throughputs) > + return 0; > + c->throughputs = av_malloc(sizeof(float) * c->n_throughputs); > + if (!c->throughputs) > + return AVERROR(ENOMEM); maybe will memleak if return here, because you call av_mallocz and above for the c->variants. what about use goto fail: and free them in fail label at the end of this function? > + index = 0; > + snprintf(key_tmp, sizeof(key_tmp), "throughputs%ld", index); > + while ((en = av_dict_get(c->abr_params, key_tmp, entry, AV_DICT_IGNORE_SUFFIX)) > + && index < c->n_throughputs) { > + c->throughputs[index++] = strtol(en->value, NULL, 10); > + snprintf(key_tmp, sizeof(key_tmp), "throughputs%ld", index); > + } > + } > + > + return 0; > +} > + > +static int abr_param_parse(ABRContext *c, enum ABRFormatType type, const AVDictionaryEntry *en) > +{ > + int ret; > + if (type == ABR_TYPE_HLS) { > + ret = hls_param_parse(c, en); > + } > + return ret; > +} > + > +static int compare_vb(const void *a, const void *b) > +{ > + return FFDIFFSIGN((*(const variant *)b).bitrate, (*(const variant *)a).bitrate); > +} > + > +static int abr_rule(URLContext *h, float bw_estimate) > +{ > + int ret = ABR_NOT_SWITCH; > + ABRContext *c = h->priv_data; > + > + if (bw_estimate < c->variants[c->cur_var].bitrate / 1000 * 1.2f && > + bw_estimate > c->variants[c->cur_var].bitrate / 1000 * 0.8f) > + return ABR_NOT_SWITCH; > + qsort(c->variants, c->n_variants, sizeof(variant), compare_vb); > + for (int i = 0; i < c->n_variants; i++) { > + if (bw_estimate > c->variants[i].bitrate / 1000) { maybe you want 1000.0 for float? > + ret = c->variants[i].index; > + break; > + } > + } > + if (ret == ABR_NOT_SWITCH) > + ret = c->variants[c->n_variants - 1].index; > + else if (ret == c->cur_var) > + ret = ABR_NOT_SWITCH; > + > + av_log(h, AV_LOG_VERBOSE, "[switch] bwe=%.2fkbps, cur=%d, switch=%d\n", bw_estimate, c->cur_var, ret); what about use AV_LOG_DEBUG? > + return ret; > +} > + > +static int abr_open(URLContext *h, const char *uri, int flags, AVDictionary **options) > +{ > + const char *nested_url; > + int64_t start, end; > + float bw_estimation; > + int switch_request = ABR_NOT_SWITCH; > + int ret = 0; > + ABRContext *c = h->priv_data; > + AVDictionaryEntry *en = NULL; > + > + if (!av_strstart(uri, "ffabr+", &nested_url) && > + !av_strstart(uri, "ffabr:", &nested_url)) { > + av_log(h, AV_LOG_ERROR, "Unsupported url %s\n", uri); > + return AVERROR(EINVAL); > + } > + > + en = av_dict_get(c->abr_params, "format", en, AV_DICT_IGNORE_SUFFIX); > + if (en) { > + if (!av_strcasecmp(en->value, "hls")) { > + c->format = ABR_TYPE_HLS; > + } else if (!av_strcasecmp(en->value, "dash")) { > + c->format = ABR_TYPE_DASH; > + } > + av_log(h, AV_LOG_VERBOSE, "%s is using ABR\n", en->value); > + } else { > + return AVERROR(EINVAL); > + } > + > + if (ret = abr_param_parse(c, c->format, en) < 0) { > + av_log(h, AV_LOG_ERROR,"Error parsing abr params.\n"); > + return ret; > + } > + > + start = av_gettime(); > + if ((ret = ffurl_open_whitelist(&c->hd, nested_url, flags, > + &h->interrupt_callback, options, > + h->protocol_whitelist, h->protocol_blacklist, h)) < 0) { > + av_log(h, AV_LOG_ERROR, "Unable to open resource: %s\n", nested_url); > + return ret; > + } > + end = av_gettime(); > + > + bw_estimation = harmonic_mean(c->throughputs, c->n_throughputs); > + > + if (c->can_switch == 1) > + switch_request = abr_rule(h, bw_estimation); > + > + av_dict_set_int(&c->abr_metadata, "download_time", (end - start), 0); > + av_dict_set_int(&c->abr_metadata, "switch_request", switch_request, 0); > + av_dict_set_int(&c->abr_metadata, "type", c->type, 0); > + > + return ret; > +} > + > + > +static int abr_read(URLContext *h, uint8_t *buf, int size) > +{ > + ABRContext *c = h->priv_data; > + > + return ffurl_read(c->hd, buf, size); > +} > + > +static int64_t abr_seek(URLContext *h, int64_t pos, int whence) > +{ > + ABRContext *c = h->priv_data; > + > + if (whence == AVSEEK_SIZE) { > + return ffurl_seek(c->hd, pos, AVSEEK_SIZE); > + } else { > + return AVERROR(errno); > + } > +} > + > +static int abr_close(URLContext *h) > +{ > + ABRContext *c = h->priv_data; > + int ret = 0; > + > + ffurl_closep(&c->hd); > + av_free(c->variants); > + av_free(c->throughputs); > + return ret; > +} > + > +#define OFFSET(x) offsetof(ABRContext, x) > +#define D AV_OPT_FLAG_DECODING_PARAM > +static const AVOption ffabr_options[] = { > + { "abr-params", "Informations ABR needed, using a :-separated list of key=value parameters", OFFSET(abr_params), AV_OPT_TYPE_DICT, { 0 }, 0, 0, D }, > + { "abr-metadata", "Metadata return from abr, including switch signal and network bandwidth", OFFSET(abr_metadata), AV_OPT_TYPE_DICT, { 0 }, 0, 0, D }, > + { NULL } > +}; > + > +static const AVClass ffabr_class = { > + .class_name = "ffabr", > + .item_name = av_default_item_name, > + .option = ffabr_options, > + .version = LIBAVUTIL_VERSION_INT, > +}; > + > +const URLProtocol ff_ffabr_protocol = { > + .name = "ffabr", > + .url_open2 = abr_open, > + .url_read = abr_read, > + .url_seek = abr_seek, > + .url_close = abr_close, > + .priv_data_size = sizeof(ABRContext), > + .priv_data_class = &ffabr_class, > +}; > diff --git a/libavformat/protocols.c b/libavformat/protocols.c > index 7df18fbb3b..1d6af8e380 100644 > --- a/libavformat/protocols.c > +++ b/libavformat/protocols.c > @@ -29,6 +29,7 @@ extern const URLProtocol ff_cache_protocol; > extern const URLProtocol ff_concat_protocol; > extern const URLProtocol ff_crypto_protocol; > extern const URLProtocol ff_data_protocol; > +extern const URLProtocol ff_ffabr_protocol; > extern const URLProtocol ff_ffrtmpcrypt_protocol; > extern const URLProtocol ff_ffrtmphttp_protocol; > extern const URLProtocol ff_file_protocol; > -- > 2.28.0 > > _______________________________________________ > ffmpeg-devel mailing list > ffmpeg-devel@ffmpeg.org > https://ffmpeg.org/mailman/listinfo/ffmpeg-devel > > To unsubscribe, visit link above, or email > ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". Thanks Steven
diff --git a/doc/protocols.texi b/doc/protocols.texi index 7b3df96fda..e31de80ab6 100644 --- a/doc/protocols.texi +++ b/doc/protocols.texi @@ -232,6 +232,13 @@ For example, to convert a GIF file given inline with @command{ffmpeg}: ffmpeg -i "data:image/gif;base64,R0lGODdhCAAIAMIEAAAAAAAA//8AAP//AP///////////////ywAAAAACAAIAAADF0gEDLojDgdGiJdJqUX02iB4E8Q9jUMkADs=" smiley.png @end example +@section ffabr + +Adaptive bitrate sub-protocol work for hls/dash, ffabr is internal. + +The ffabr protocol takes stream information from hls/dash as input, +use bandwidth estimation to decide whether to switch or not. + @section file File access protocol. diff --git a/libavformat/Makefile b/libavformat/Makefile index cbb33fe37c..68b004eee0 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -598,6 +598,7 @@ OBJS-$(CONFIG_CACHE_PROTOCOL) += cache.o OBJS-$(CONFIG_CONCAT_PROTOCOL) += concat.o OBJS-$(CONFIG_CRYPTO_PROTOCOL) += crypto.o OBJS-$(CONFIG_DATA_PROTOCOL) += data_uri.o +OBJS-$(CONFIG_FFABR_PROTOCOL) += ffabr.o OBJS-$(CONFIG_FFRTMPCRYPT_PROTOCOL) += rtmpcrypt.o rtmpdigest.o rtmpdh.o OBJS-$(CONFIG_FFRTMPHTTP_PROTOCOL) += rtmphttp.o OBJS-$(CONFIG_FILE_PROTOCOL) += file.o diff --git a/libavformat/ffabr.c b/libavformat/ffabr.c new file mode 100644 index 0000000000..1785ef5643 --- /dev/null +++ b/libavformat/ffabr.c @@ -0,0 +1,271 @@ +/* + * Adaptive Bitrate Module for HLS / DASH + * Copyright (c) 2020 + * + * 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 <math.h> +#include "avformat.h" +#include "libavutil/avassert.h" +#include "libavutil/avstring.h" +#include "libavutil/common.h" +#include "libavutil/opt.h" +#include "libavutil/time.h" +#include "url.h" + +#define ABR_NOT_SWITCH -1 + +enum ABRFormatType { + ABR_TYPE_HLS, + ABR_TYPE_DASH +}; + +typedef struct Variant { + uint32_t bitrate; + size_t index; +} variant; + +typedef struct ABRContext { + const AVClass *class; + URLContext *hd; + AVDictionary *abr_params; + AVDictionary *abr_metadata; + enum ABRFormatType format; + uint8_t cur_var; + uint8_t type; + int8_t can_switch; + size_t n_variants; + variant *variants; + size_t n_throughputs; + float *throughputs; +} ABRContext; + +static float harmonic_mean(const float *arr, size_t num) +{ + float tmp = 0; + + if (!num) return 0; + + for (size_t i = 0; i < num; i++) { + tmp += 1 / arr[i]; + } + + return num / tmp; +} + +static int hls_param_parse(ABRContext *c, const AVDictionaryEntry *entry) +{ + AVDictionaryEntry *en; + size_t index; + char key_tmp[20]; + + + en = av_dict_get(c->abr_params, "cur_var", entry, AV_DICT_IGNORE_SUFFIX); + if (en) { + c->cur_var = strtol(en->value, NULL, 10); + } + en = av_dict_get(c->abr_params, "type", entry, AV_DICT_IGNORE_SUFFIX); + if (en) { + c->type = strtol(en->value, NULL, 10); + } + en = av_dict_get(c->abr_params, "can_switch", entry, AV_DICT_IGNORE_SUFFIX); + if (en) { + c->can_switch = strtol(en->value, NULL, 10); + } + en = av_dict_get(c->abr_params, "n_variants", entry, AV_DICT_IGNORE_SUFFIX); + if (en) { + c->n_variants = strtol(en->value, NULL, 10); + c->variants = av_mallocz(sizeof(variant) * c->n_variants); + if (!c->variants) + return AVERROR(ENOMEM); + index = 0; + snprintf(key_tmp, sizeof(key_tmp), "variant_bitrate%ld", index); + while ((en = av_dict_get(c->abr_params, key_tmp, entry, AV_DICT_IGNORE_SUFFIX)) + && index < c->n_variants) { + c->variants[index].bitrate = strtol(en->value, NULL, 10); + c->variants[index].index = index; + index++; + snprintf(key_tmp, sizeof(key_tmp), "variant_bitrate%ld", index); + } + } + en = av_dict_get(c->abr_params, "n_throughputs", entry, AV_DICT_IGNORE_SUFFIX); + if (en) { + c->n_throughputs = strtol(en->value, NULL, 10); + if (!c->n_throughputs) + return 0; + c->throughputs = av_malloc(sizeof(float) * c->n_throughputs); + if (!c->throughputs) + return AVERROR(ENOMEM); + index = 0; + snprintf(key_tmp, sizeof(key_tmp), "throughputs%ld", index); + while ((en = av_dict_get(c->abr_params, key_tmp, entry, AV_DICT_IGNORE_SUFFIX)) + && index < c->n_throughputs) { + c->throughputs[index++] = strtol(en->value, NULL, 10); + snprintf(key_tmp, sizeof(key_tmp), "throughputs%ld", index); + } + } + + return 0; +} + +static int abr_param_parse(ABRContext *c, enum ABRFormatType type, const AVDictionaryEntry *en) +{ + int ret; + if (type == ABR_TYPE_HLS) { + ret = hls_param_parse(c, en); + } + return ret; +} + +static int compare_vb(const void *a, const void *b) +{ + return FFDIFFSIGN((*(const variant *)b).bitrate, (*(const variant *)a).bitrate); +} + +static int abr_rule(URLContext *h, float bw_estimate) +{ + int ret = ABR_NOT_SWITCH; + ABRContext *c = h->priv_data; + + if (bw_estimate < c->variants[c->cur_var].bitrate / 1000 * 1.2f && + bw_estimate > c->variants[c->cur_var].bitrate / 1000 * 0.8f) + return ABR_NOT_SWITCH; + qsort(c->variants, c->n_variants, sizeof(variant), compare_vb); + for (int i = 0; i < c->n_variants; i++) { + if (bw_estimate > c->variants[i].bitrate / 1000) { + ret = c->variants[i].index; + break; + } + } + if (ret == ABR_NOT_SWITCH) + ret = c->variants[c->n_variants - 1].index; + else if (ret == c->cur_var) + ret = ABR_NOT_SWITCH; + + av_log(h, AV_LOG_VERBOSE, "[switch] bwe=%.2fkbps, cur=%d, switch=%d\n", bw_estimate, c->cur_var, ret); + return ret; +} + +static int abr_open(URLContext *h, const char *uri, int flags, AVDictionary **options) +{ + const char *nested_url; + int64_t start, end; + float bw_estimation; + int switch_request = ABR_NOT_SWITCH; + int ret = 0; + ABRContext *c = h->priv_data; + AVDictionaryEntry *en = NULL; + + if (!av_strstart(uri, "ffabr+", &nested_url) && + !av_strstart(uri, "ffabr:", &nested_url)) { + av_log(h, AV_LOG_ERROR, "Unsupported url %s\n", uri); + return AVERROR(EINVAL); + } + + en = av_dict_get(c->abr_params, "format", en, AV_DICT_IGNORE_SUFFIX); + if (en) { + if (!av_strcasecmp(en->value, "hls")) { + c->format = ABR_TYPE_HLS; + } else if (!av_strcasecmp(en->value, "dash")) { + c->format = ABR_TYPE_DASH; + } + av_log(h, AV_LOG_VERBOSE, "%s is using ABR\n", en->value); + } else { + return AVERROR(EINVAL); + } + + if (ret = abr_param_parse(c, c->format, en) < 0) { + av_log(h, AV_LOG_ERROR,"Error parsing abr params.\n"); + return ret; + } + + start = av_gettime(); + if ((ret = ffurl_open_whitelist(&c->hd, nested_url, flags, + &h->interrupt_callback, options, + h->protocol_whitelist, h->protocol_blacklist, h)) < 0) { + av_log(h, AV_LOG_ERROR, "Unable to open resource: %s\n", nested_url); + return ret; + } + end = av_gettime(); + + bw_estimation = harmonic_mean(c->throughputs, c->n_throughputs); + + if (c->can_switch == 1) + switch_request = abr_rule(h, bw_estimation); + + av_dict_set_int(&c->abr_metadata, "download_time", (end - start), 0); + av_dict_set_int(&c->abr_metadata, "switch_request", switch_request, 0); + av_dict_set_int(&c->abr_metadata, "type", c->type, 0); + + return ret; +} + + +static int abr_read(URLContext *h, uint8_t *buf, int size) +{ + ABRContext *c = h->priv_data; + + return ffurl_read(c->hd, buf, size); +} + +static int64_t abr_seek(URLContext *h, int64_t pos, int whence) +{ + ABRContext *c = h->priv_data; + + if (whence == AVSEEK_SIZE) { + return ffurl_seek(c->hd, pos, AVSEEK_SIZE); + } else { + return AVERROR(errno); + } +} + +static int abr_close(URLContext *h) +{ + ABRContext *c = h->priv_data; + int ret = 0; + + ffurl_closep(&c->hd); + av_free(c->variants); + av_free(c->throughputs); + return ret; +} + +#define OFFSET(x) offsetof(ABRContext, x) +#define D AV_OPT_FLAG_DECODING_PARAM +static const AVOption ffabr_options[] = { + { "abr-params", "Informations ABR needed, using a :-separated list of key=value parameters", OFFSET(abr_params), AV_OPT_TYPE_DICT, { 0 }, 0, 0, D }, + { "abr-metadata", "Metadata return from abr, including switch signal and network bandwidth", OFFSET(abr_metadata), AV_OPT_TYPE_DICT, { 0 }, 0, 0, D }, + { NULL } +}; + +static const AVClass ffabr_class = { + .class_name = "ffabr", + .item_name = av_default_item_name, + .option = ffabr_options, + .version = LIBAVUTIL_VERSION_INT, +}; + +const URLProtocol ff_ffabr_protocol = { + .name = "ffabr", + .url_open2 = abr_open, + .url_read = abr_read, + .url_seek = abr_seek, + .url_close = abr_close, + .priv_data_size = sizeof(ABRContext), + .priv_data_class = &ffabr_class, +}; diff --git a/libavformat/protocols.c b/libavformat/protocols.c index 7df18fbb3b..1d6af8e380 100644 --- a/libavformat/protocols.c +++ b/libavformat/protocols.c @@ -29,6 +29,7 @@ extern const URLProtocol ff_cache_protocol; extern const URLProtocol ff_concat_protocol; extern const URLProtocol ff_crypto_protocol; extern const URLProtocol ff_data_protocol; +extern const URLProtocol ff_ffabr_protocol; extern const URLProtocol ff_ffrtmpcrypt_protocol; extern const URLProtocol ff_ffrtmphttp_protocol; extern const URLProtocol ff_file_protocol;