[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