[FFmpeg-devel] [PATCH 10/18] avformat/hls: add support for byte-ranged segments

Anssi Hannula anssi.hannula at iki.fi
Mon Dec 30 12:14:24 CET 2013


Add support for EXT-X-BYTERANGE added in HLS protocol v4.

Signed-off-by: Anssi Hannula <anssi.hannula at iki.fi>
---
 libavformat/hls.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 61 insertions(+), 2 deletions(-)

diff --git a/libavformat/hls.c b/libavformat/hls.c
index 2a337fb..4c23d51 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -61,6 +61,8 @@ enum KeyType {
 
 struct segment {
     int64_t duration;
+    int64_t url_offset;
+    int64_t size;
     char url[MAX_URL_SIZE];
     char key[MAX_URL_SIZE];
     enum KeyType key_type;
@@ -92,6 +94,7 @@ struct playlist {
     struct segment **segments;
     int needed, cur_needed;
     int cur_seq_no;
+    int64_t cur_seq_offset;
     int64_t last_load_time;
 
     char key_url[MAX_URL_SIZE];
@@ -443,6 +446,8 @@ static int parse_playlist(HLSContext *c, const char *url,
     char line[MAX_URL_SIZE];
     const char *ptr;
     int close_in = 0;
+    int64_t seg_offset = 0;
+    int64_t seg_size = -1;
     uint8_t *new_url = NULL;
     struct variant_info variant_info;
 
@@ -526,6 +531,11 @@ static int parse_playlist(HLSContext *c, const char *url,
         } else if (av_strstart(line, "#EXTINF:", &ptr)) {
             is_segment = 1;
             duration   = atof(ptr) * AV_TIME_BASE;
+        } else if (av_strstart(line, "#EXT-X-BYTERANGE:", &ptr)) {
+            seg_size = atoi(ptr);
+            ptr = strchr(ptr, '@');
+            if (ptr)
+                seg_offset = atoi(ptr+1);
         } else if (av_strstart(line, "#", NULL)) {
             continue;
         } else if (line[0]) {
@@ -563,6 +573,16 @@ static int parse_playlist(HLSContext *c, const char *url,
                 ff_make_absolute_url(seg->url, sizeof(seg->url), url, line);
                 dynarray_add(&pls->segments, &pls->n_segments, seg);
                 is_segment = 0;
+
+                seg->size = seg_size;
+                if (seg_size >= 0) {
+                    seg->url_offset = seg_offset;
+                    seg_offset += seg_size;
+                    seg_size = -1;
+                } else {
+                    seg->url_offset = 0;
+                    seg_offset = 0;
+                }
             }
         }
     }
@@ -579,9 +599,23 @@ fail:
 static int open_input(HLSContext *c, struct playlist *pls)
 {
     AVDictionary *opts = NULL;
+    AVDictionary *offset_opts = NULL;
     int ret;
     struct segment *seg = pls->segments[pls->cur_seq_no - pls->start_seq_no];
 
+    if (seg->size >= 0) {
+        /* try to restrict the HTTP request to the part we want
+         * (if this is in fact a HTTP request) */
+        char offset[24] = { 0 };
+        char end_offset[24] = { 0 };
+        snprintf(offset, sizeof(offset) - 1, "%"PRId64,
+                 seg->url_offset);
+        snprintf(end_offset, sizeof(end_offset) - 1, "%"PRId64,
+                 seg->url_offset + seg->size);
+        av_dict_set(&offset_opts, "offset", offset, 0);
+        av_dict_set(&offset_opts, "end_offset", end_offset, 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);
@@ -589,9 +623,10 @@ static int open_input(HLSContext *c, struct playlist *pls)
     av_dict_set(&opts, "seekable", "0", 0);
 
     if (seg->key_type == KEY_NONE) {
+        av_dict_copy(&opts, offset_opts, 0);
         ret = ffurl_open(&pls->input, seg->url, AVIO_FLAG_READ,
                           &pls->parent->interrupt_callback, &opts);
-        goto cleanup;
+
     } else if (seg->key_type == KEY_AES_128) {
         char iv[33], key[33], url[MAX_URL_SIZE];
         if (strcmp(seg->key, pls->key_url)) {
@@ -625,6 +660,7 @@ static int open_input(HLSContext *c, struct playlist *pls)
         /* Need to repopulate options */
         av_dict_free(&opts);
         av_dict_set(&opts, "seekable", "0", 0);
+        av_dict_copy(&opts, offset_opts, 0);
         if ((ret = ffurl_connect(pls->input, &opts)) < 0) {
             ffurl_close(pls->input);
             pls->input = NULL;
@@ -635,8 +671,22 @@ static int open_input(HLSContext *c, struct playlist *pls)
     else
       ret = AVERROR(ENOSYS);
 
+    /* Seek to the requested position. If this was a HTTP request,
+     * the offset should already be there. */
+    if (ret == 0) {
+        int seekret = ffurl_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 HLS segment '%s'\n", seg->url_offset, seg->url);
+            ret = seekret;
+            ffurl_close(pls->input);
+            pls->input = NULL;
+        }
+    }
+
 cleanup:
     av_dict_free(&opts);
+    av_dict_free(&offset_opts);
+    pls->cur_seq_offset = 0;
     return ret;
 }
 
@@ -645,6 +695,8 @@ static int read_data(void *opaque, uint8_t *buf, int buf_size)
     struct playlist *v = opaque;
     HLSContext *c = v->parent->priv_data;
     int ret, i;
+    int actual_read_size;
+    struct segment *seg;
 
     if (!v->needed)
         return AVERROR_EOF;
@@ -689,7 +741,14 @@ reload:
         if (ret < 0)
             return ret;
     }
-    ret = ffurl_read(v->input, buf, buf_size);
+    /* limit read if the segment was only a part of a file */
+    seg = v->segments[v->cur_seq_no - v->start_seq_no];
+    if (seg->size >= 0)
+        actual_read_size = FFMIN(buf_size, seg->size - v->cur_seq_offset);
+    else
+        actual_read_size = buf_size;
+
+    ret = ffurl_read(v->input, buf, actual_read_size);
     if (ret > 0)
         return ret;
     ffurl_close(v->input);
-- 
1.8.1.5



More information about the ffmpeg-devel mailing list