[FFmpeg-devel] [PATCH] avformat/hlsenc: Port support for LHLS from lavf/dashenc

Zenon Mousmoulas zmousm at noc.grnet.gr
Wed Mar 27 11:08:02 EET 2019


Add experimental support for LHLS (low-latency HLS), following what
was introduced to lavf/dashenc in f22fcd4483f.
---
 doc/muxers.texi           |  6 +++++
 libavformat/dashenc.c     |  2 +-
 libavformat/hlsenc.c      | 63 ++++++++++++++++++++++++++++++++++++++---------
 libavformat/hlsplaylist.c | 28 +++++++++++++--------
 libavformat/hlsplaylist.h |  3 ++-
 5 files changed, 79 insertions(+), 23 deletions(-)

diff --git a/doc/muxers.texi b/doc/muxers.texi
index aac7d94edf..4ebaf559c5 100644
--- a/doc/muxers.texi
+++ b/doc/muxers.texi
@@ -1067,6 +1067,12 @@ Set timeout for socket I/O operations. Applicable only for HTTP output.
 @item -ignore_io_errors
 Ignore IO errors during open, write and delete. Useful for long-duration runs with network output.
 
+ at item -lhls @var{lhls}
+Enable low-latency HLS (LHLS). Adds #EXT-X-PREFETCH tag with current segment's URI.
+Apple does not have an official spec for LHLS. Meanwhile hls.js player folks are
+trying to standardize an open LHLS spec. The draft spec is available in https://github.com/video-dev/hlsjs-rfcs/blob/lhls-spec/proposals/0001-lhls.md
+This is an experimental feature.
+
 @end table
 
 @anchor{ico}
diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c
index 1b74bce060..04950222d3 100644
--- a/libavformat/dashenc.c
+++ b/libavformat/dashenc.c
@@ -483,7 +483,7 @@ static void write_hls_media_playlist(OutputStream *os, AVFormatContext *s,
                                 (double) seg->duration / timescale, 0,
                                 seg->range_length, seg->start_pos, NULL,
                                 c->single_file ? os->initfile : seg->file,
-                                &prog_date_time);
+                                &prog_date_time, 0);
         if (ret < 0) {
             av_log(os->ctx, AV_LOG_WARNING, "ff_hls_write_file_entry get error
");
         }
diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c
index 5f9a200c6e..4a427fc514 100644
--- a/libavformat/hlsenc.c
+++ b/libavformat/hlsenc.c
@@ -158,6 +158,7 @@ typedef struct VariantStream {
     char *agroup; /* audio group name */
     char *ccgroup; /* closed caption group name */
     char *baseurl;
+    int m3u8_got_prefetch;
 } VariantStream;
 
 typedef struct ClosedCaptionsStream {
@@ -230,6 +231,7 @@ typedef struct HLSContext {
     AVIOContext *sub_m3u8_out;
     int64_t timeout;
     int ignore_io_errors;
+    int lhls;
     int has_default_key; /* has DEFAULT field of var_stream_map */
     int has_video_m3u8; /* has video stream m3u8 list */
 } HLSContext;
@@ -1360,7 +1362,7 @@ fail:
     return ret;
 }
 
-static int hls_window(AVFormatContext *s, int last, VariantStream *vs)
+static int hls_window(AVFormatContext *s, int last, VariantStream *vs, char *prefetch_url)
 {
     HLSContext *hls = s->priv_data;
     HLSSegment *en;
@@ -1439,12 +1441,23 @@ static int hls_window(AVFormatContext *s, int last, VariantStream *vs)
         ret = ff_hls_write_file_entry(hls->m3u8_out, en->discont, byterange_mode,
                                       en->duration, hls->flags & HLS_ROUND_DURATIONS,
                                       en->size, en->pos, vs->baseurl,
-                                      en->filename, prog_date_time_p);
+                                      en->filename, prog_date_time_p, 0);
         if (ret < 0) {
             av_log(s, AV_LOG_WARNING, "ff_hls_write_file_entry get error
");
         }
     }
 
+    if (prefetch_url)
+        ret = en ?
+              ff_hls_write_file_entry(hls->m3u8_out, en->discont, byterange_mode,
+                                      en->duration, hls->flags & HLS_ROUND_DURATIONS,
+                                      en->size, en->pos, vs->baseurl,
+                                      prefetch_url, prog_date_time_p, 1) :
+              ff_hls_write_file_entry(hls->m3u8_out, 0, byterange_mode,
+                                      0, hls->flags & HLS_ROUND_DURATIONS,
+                                      0, 0, vs->baseurl,
+                                      prefetch_url, prog_date_time_p, 1);
+
     if (last && (hls->flags & HLS_OMIT_ENDLIST)==0)
         ff_hls_write_end_list(hls->m3u8_out);
 
@@ -1459,7 +1472,7 @@ static int hls_window(AVFormatContext *s, int last, VariantStream *vs)
         for (en = vs->segments; en; en = en->next) {
             ret = ff_hls_write_file_entry(hls->sub_m3u8_out, 0, byterange_mode,
                                           en->duration, 0, en->size, en->pos,
-                                          vs->baseurl, en->sub_filename, NULL);
+                                          vs->baseurl, en->sub_filename, NULL, 0);
             if (ret < 0) {
                 av_log(s, AV_LOG_WARNING, "ff_hls_write_file_entry get error
");
             }
@@ -2158,6 +2171,7 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
     int64_t end_pts = 0;
     int is_ref_pkt = 1;
     int ret = 0, can_split = 1, i, j;
+    int byterange_mode;
     int stream_index = 0;
     int range_length = 0;
     const char *proto = NULL;
@@ -2232,10 +2246,16 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
 
     }
 
+    byterange_mode = (hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size > 0);
+
+    if (oc->url[0]) {
+        proto = avio_find_protocol_name(oc->url);
+        use_temp_file = proto && !strcmp(proto, "file") && (hls->flags & HLS_TEMP_FILE);
+    }
+
     if (vs->packets_written && can_split && av_compare_ts(pkt->pts - vs->start_pts, st->time_base,
                                                           end_pts, AV_TIME_BASE_Q) >= 0) {
         int64_t new_start_pos;
-        int byterange_mode = (hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size > 0);
 
         av_write_frame(vs->avf, NULL); /* Flush any buffered data */
 
@@ -2272,11 +2292,6 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
             }
         }
 
-        if (oc->url[0]) {
-            proto = avio_find_protocol_name(oc->url);
-            use_temp_file = proto && !strcmp(proto, "file") && (hls->flags & HLS_TEMP_FILE);
-        }
-
         // look to rename the asset name
         if (use_temp_file) {
             if (!(hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size <= 0))
@@ -2358,9 +2373,18 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
 
         // if we're building a VOD playlist, skip writing the manifest multiple times, and just wait until the end
         if (hls->pl_type != PLAYLIST_TYPE_VOD) {
-            if ((ret = hls_window(s, 0, vs)) < 0) {
+            if ((ret = hls_window(s, 0, vs, NULL)) < 0) {
+                return ret;
+            }
+            vs->m3u8_got_prefetch = 0;
+        }
+    } else if (hls->lhls && !use_temp_file && !byterange_mode &&
+               !vs->m3u8_got_prefetch) {
+        if (hls->pl_type != PLAYLIST_TYPE_VOD) {
+            if ((ret = hls_window(s, 0, vs, (char *)av_basename(oc->url))) < 0) {
                 return ret;
             }
+            vs->m3u8_got_prefetch = 1;
         }
     }
 
@@ -2504,7 +2528,7 @@ failed:
         avformat_free_context(oc);
 
         vs->avf = NULL;
-        hls_window(s, 1, vs);
+        hls_window(s, 1, vs, NULL);
         av_free(old_filename);
     }
 
@@ -2588,6 +2612,22 @@ static int hls_init(AVFormatContext *s)
         }
     }
 
+    if (hls->lhls && s->strict_std_compliance > FF_COMPLIANCE_EXPERIMENTAL) {
+        av_log(s, AV_LOG_ERROR,
+               "LHLS is experimental, please set -strict experimental in order to enable it.
");
+        return AVERROR_EXPERIMENTAL;
+    }
+    if (hls->lhls && hls->flags & HLS_TEMP_FILE) {
+        av_log(s, AV_LOG_WARNING,
+               "'lhls' will be disabled because 'temp_file' flag is enabled'
");
+        hls->lhls = 0;
+    }
+    if (hls->lhls && hls->flags & HLS_SINGLE_FILE) {
+        av_log(s, AV_LOG_WARNING,
+               "'lhls' will be disabled because 'single_file' flag is enabled'
");
+        hls->lhls = 0;
+    }
+
     if (hls->segment_type == SEGMENT_TYPE_FMP4) {
         pattern = "%d.m4s";
     }
@@ -2942,6 +2982,7 @@ static const AVOption options[] = {
     {"http_persistent", "Use persistent HTTP connections", OFFSET(http_persistent), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, E },
     {"timeout", "set timeout for socket I/O operations", OFFSET(timeout), AV_OPT_TYPE_DURATION, { .i64 = -1 }, -1, INT_MAX, .flags = E },
     {"ignore_io_errors", "Ignore IO errors for stable long-duration runs with network output", OFFSET(ignore_io_errors), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E },
+    { "lhls", "Enable low-latency HLS (experimental). Adds #EXT-X-PREFETCH tag with current segment's URI", OFFSET(lhls), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E },
     { NULL },
 };
 
diff --git a/libavformat/hlsplaylist.c b/libavformat/hlsplaylist.c
index 0537049a97..8db0c93c12 100644
--- a/libavformat/hlsplaylist.c
+++ b/libavformat/hlsplaylist.c
@@ -108,21 +108,27 @@ int ff_hls_write_file_entry(AVIOContext *out, int insert_discont,
                              double duration, int round_duration,
                              int64_t size, int64_t pos, //Used only if HLS_SINGLE_FILE flag is set
                              char *baseurl, //Ignored if NULL
-                             char *filename, double *prog_date_time) {
+                             char *filename, double *prog_date_time,
+                             int prefetch) {
     if (!out || !filename)
         return AVERROR(EINVAL);
 
     if (insert_discont) {
-        avio_printf(out, "#EXT-X-DISCONTINUITY
");
+        if (prefetch)
+            avio_printf(out, "#EXT-X-PREFETCH-DISCONTINUITY
");
+        else
+            avio_printf(out, "#EXT-X-DISCONTINUITY
");
     }
-    if (round_duration)
-        avio_printf(out, "#EXTINF:%ld,
",  lrint(duration));
-    else
-        avio_printf(out, "#EXTINF:%f,
", duration);
-    if (byterange_mode)
-        avio_printf(out, "#EXT-X-BYTERANGE:%"PRId64"@%"PRId64"
", size, pos);
-
-    if (prog_date_time) {
+    if (!prefetch) {
+        if (round_duration)
+            avio_printf(out, "#EXTINF:%ld,
",  lrint(duration));
+        else
+            avio_printf(out, "#EXTINF:%f,
", duration);
+        if (byterange_mode)
+            avio_printf(out, "#EXT-X-BYTERANGE:%"PRId64"@%"PRId64"
", size, pos);
+    }
+
+    if (!prefetch && prog_date_time) {
         time_t tt, wrongsecs;
         int milli;
         struct tm *tm, tmpbuf;
@@ -149,6 +155,8 @@ int ff_hls_write_file_entry(AVIOContext *out, int insert_discont,
         avio_printf(out, "#EXT-X-PROGRAM-DATE-TIME:%s.%03d%s
", buf0, milli, buf1);
         *prog_date_time += duration;
     }
+    if (prefetch)
+        avio_printf(out, "#EXT-X-PREFETCH:");
     if (baseurl)
         avio_printf(out, "%s", baseurl);
     avio_printf(out, "%s
", filename);
diff --git a/libavformat/hlsplaylist.h b/libavformat/hlsplaylist.h
index 54c93a3963..af35162e08 100644
--- a/libavformat/hlsplaylist.h
+++ b/libavformat/hlsplaylist.h
@@ -52,7 +52,8 @@ int ff_hls_write_file_entry(AVIOContext *out, int insert_discont,
                              double duration, int round_duration,
                              int64_t size, int64_t pos, //Used only if HLS_SINGLE_FILE flag is set
                              char *baseurl, //Ignored if NULL
-                             char *filename, double *prog_date_time);
+                             char *filename, double *prog_date_time,
+                             int prefetch);
 void ff_hls_write_end_list (AVIOContext *out);
 
 #endif /* AVFORMAT_HLSPLAYLIST_H_ */
-- 
2.11.0




More information about the ffmpeg-devel mailing list