[FFmpeg-devel] [PATCH] avformat/hlsenc: Port support for LHLS from lavf/dashenc
Jeyapal, Karthick
kjeyapal at akamai.com
Thu Mar 28 10:40:25 EET 2019
On 3/27/19 2:38 PM, Zenon Mousmoulas wrote:
> 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) {
Can we create a separate function like below for writing prefetch segments?
int ff_hls_write_prefetch(AVIOContext *out, int insert_discont,
char *baseurl, //Ignored if NULL
char *filename)
Because the current function(ff_hls_write_file_entry) is already pretty complex with multiple arguments, most of which are not applicable for PREFETCH.
Anyways, the condition if(prefetch) is anyways there on the calling side(hlsenc.c and dashenc.c).
A separate function like above called by both hlsenc.c and dashenc.c will be much cleaner.
> 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_ */
More information about the ffmpeg-devel
mailing list