From patchwork Wed Mar 27 09:08:02 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Zenon Mousmoulas X-Patchwork-Id: 12509 Return-Path: X-Original-To: patchwork@ffaux-bg.ffmpeg.org Delivered-To: patchwork@ffaux-bg.ffmpeg.org Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100]) by ffaux.localdomain (Postfix) with ESMTP id 0CDD2448E7B for ; Thu, 28 Mar 2019 09:08:10 +0200 (EET) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id DFB3A68A757; Thu, 28 Mar 2019 09:08:09 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail.noc.grnet.gr (mail.noc.grnet.gr [83.212.9.5]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id B3131689D14 for ; Thu, 28 Mar 2019 09:08:02 +0200 (EET) Received: from localhost (ip6-localhost [127.0.0.1]) by mail.noc.grnet.gr (Postfix) with ESMTP id 8BA84D8044 for ; Thu, 28 Mar 2019 09:08:01 +0200 (EET) X-Virus-Scanned: Debian amavisd-new at mail.noc.grnet.gr Received: from mail.noc.grnet.gr ([127.0.0.1]) by localhost (mail.noc.grnet.gr [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id WcIuZ--Hp8AW for ; Thu, 28 Mar 2019 09:08:00 +0200 (EET) Received: from mail.noc.grnet.gr (mail.noc.grnet.gr [IPv6:2001:648:2340:4::5]) by mail.noc.grnet.gr (Postfix) with ESMTP id 8C3ADD8001 for ; Thu, 28 Mar 2019 09:08:00 +0200 (EET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=noc.grnet.gr; s=vrf; t=1553756880; bh=kTSACOB5yLPx15IFMK6g/3/BkBTIwmIv7remTHKIAlk=; h=From:Date:Subject:To; b=OJ0FWyi4XXKgu50AmuYq8KSpw3mzFvMbAfDgOfV5ntJEM3Npr12V0NlffoRETcFmg fPAoiXEToq7t8r1EnWB0A3TJBO9BF9pwvhPG3oX6MyoCWmCnXelZseJ1FeJRDd+K2c dbRBM/wWKrqjmvC+rDfQhIKQU8k6afh0LtSR5+dQ= From: Zenon Mousmoulas Date: Wed, 27 Mar 2019 11:08:02 +0200 To: ffmpeg-devel@ffmpeg.org Message-Id: <20190328070800.8C3ADD8001@mail.noc.grnet.gr> Subject: [FFmpeg-devel] [PATCH] avformat/hlsenc: Port support for LHLS from lavf/dashenc X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches MIME-Version: 1.0 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" 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) { 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_ */