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. > > +@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_ */
_______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".