[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