diff mbox series

[FFmpeg-devel,GSoC,v3,1/7] avformat/abr: Adaptive Bitrate support

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
Related show

Checks

Context Check Description
andriy/default pending
andriy/make success Make finished
andriy/make_fate success Make fate finished

Commit Message

Hongcheng Zhong Aug. 23, 2020, 12:23 p.m. UTC
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

Comments

Hongcheng Zhong Aug. 23, 2020, 12:36 p.m. UTC | #1
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
Steven Liu Sept. 3, 2020, 2:59 p.m. UTC | #2
> 在 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 mbox series

Patch

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;