From 1212e3468e7b66007a3a3f0363af5fd92718835a Mon Sep 17 00:00:00 2001 From: Steven Liu Date: Mon, 19 Sep 2016 07:00:42 +0800 Subject: [PATCH] avformat/hlsenc: refine EXT-X-BYTERANGE support for segments refine EXT-X-BYTERANGE tag, the spec link: https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.2.2 the apple doc: https://developer.apple.com/library/ios/technotes/tn2288/_index.html# //apple_ref/doc/uid/DTS40012238-CH1-BYTE_RANGE_SUPPORT_FOR_SEGMENTS command line: ./ffmpeg -i ~/Movies/objectC/a.mp4 -c copy -f hls -hls_time 7 -hls_list_size 0 -hls_segment_size 2500000 -t 40 output-test.m3u8 output: localhost:ffmpeg liuqi$ ll *.ts ;cat output-test.m3u8 -rw-r--r-- 1 liuqi staff 2792176 9 12 14:44 output-test0.ts -rw-r--r-- 1 liuqi staff 3112528 9 12 14:44 output-test3.ts -rw-r--r-- 1 liuqi staff 3377420 9 12 14:44 output-test6.ts -rw-r--r-- 1 liuqi staff 1228016 9 12 14:44 output-test7.ts #EXTM3U #EXT-X-VERSION:4 #EXT-X-TARGETDURATION:10 #EXT-X-MEDIA-SEQUENCE:0 #EXTINF:9.021000, #EXT-X-BYTERANGE:1334988@0 output-test0.ts #EXTINF:3.000000, #EXT-X-BYTERANGE:721356@1334988 output-test0.ts #EXTINF:3.000000, #EXT-X-BYTERANGE:735832@2056344 output-test0.ts #EXTINF:6.000000, #EXT-X-BYTERANGE:1645940@0 output-test3.ts #EXTINF:3.000000, #EXT-X-BYTERANGE:715152@1645940 output-test3.ts #EXTINF:3.000000, #EXT-X-BYTERANGE:751436@2361092 output-test3.ts #EXTINF:9.000000, #EXT-X-BYTERANGE:3377420@0 output-test6.ts #EXTINF:3.960000, #EXT-X-BYTERANGE:1228016@0 output-test7.ts #EXT-X-ENDLIST localhost:ffmpeg liuqi$ ticket-id: #5839 Signed-off-by: Steven Liu Signed-off-by: Michael Niedermayer --- libavformat/hlsenc.c | 49 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c index 0ca59329b6..428bae4377 100644 --- a/libavformat/hlsenc.c +++ b/libavformat/hlsenc.c @@ -104,6 +104,7 @@ typedef struct HLSContext { double duration; // last segment duration computed so far, in seconds int64_t start_pos; // last segment starting position int64_t size; // last segment size + int64_t max_seg_size; // every segment file max size int nb_entries; int discontinuity_set; @@ -396,6 +397,9 @@ static int hls_append_segment(struct AVFormatContext *s, HLSContext *hls, double } else hls->nb_entries++; + if (hls->max_seg_size > 0) { + return 0; + } hls->sequence++; return 0; @@ -476,7 +480,7 @@ static int hls_window(AVFormatContext *s, int last) AVIOContext *sub_out = NULL; char temp_filename[1024]; int64_t sequence = FFMAX(hls->start_sequence, hls->sequence - hls->nb_entries); - int version = hls->flags & HLS_SINGLE_FILE ? 4 : 3; + int version = 3; const char *proto = avio_find_protocol_name(s->filename); int use_rename = proto && !strcmp(proto, "file"); static unsigned warned_non_file; @@ -484,6 +488,12 @@ static int hls_window(AVFormatContext *s, int last) char *iv_string = NULL; AVDictionary *options = NULL; double prog_date_time = hls->initial_prog_date_time; + int byterange_mode = (hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size > 0); + + if (byterange_mode) { + version = 4; + sequence = 0; + } if (!use_rename && !warned_non_file++) av_log(s, AV_LOG_ERROR, "Cannot use rename on non file protocol, this may lead to races and temporarly partial files\n"); @@ -533,7 +543,7 @@ static int hls_window(AVFormatContext *s, int last) avio_printf(out, "#EXTINF:%ld,\n", lrint(en->duration)); else avio_printf(out, "#EXTINF:%f,\n", en->duration); - if (hls->flags & HLS_SINGLE_FILE) + if (byterange_mode) avio_printf(out, "#EXT-X-BYTERANGE:%"PRIi64"@%"PRIi64"\n", en->size, en->pos); if (hls->flags & HLS_PROGRAM_DATE_TIME) { @@ -584,7 +594,7 @@ static int hls_window(AVFormatContext *s, int last) for (en = hls->segments; en; en = en->next) { avio_printf(sub_out, "#EXTINF:%f,\n", en->duration); - if (hls->flags & HLS_SINGLE_FILE) + if (byterange_mode) avio_printf(sub_out, "#EXT-X-BYTERANGE:%"PRIi64"@%"PRIi64"\n", en->size, en->pos); if (hls->baseurl) @@ -621,6 +631,13 @@ static int hls_start(AVFormatContext *s) if (c->vtt_basename) av_strlcpy(vtt_oc->filename, c->vtt_basename, sizeof(vtt_oc->filename)); + } else if (c->max_seg_size > 0) { + if (av_get_frame_filename2(oc->filename, sizeof(oc->filename), + c->basename, c->wrap ? c->sequence % c->wrap : c->sequence, + AV_FRAME_FILENAME_FLAGS_MULTIPLE) < 0) { + av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s', you can try to use -use_localtime 1 with it\n", c->basename); + return AVERROR(EINVAL); + } } else { if (c->use_localtime) { time_t now0; @@ -867,6 +884,16 @@ static int hls_write_header(AVFormatContext *s) for (i = 0; i < s->nb_streams; i++) { AVStream *inner_st; AVStream *outer_st = s->streams[i]; + + if (hls->max_seg_size > 0) { + if ((outer_st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) && + (outer_st->codecpar->bit_rate > hls->max_seg_size)) { + av_log(s, AV_LOG_WARNING, "Your video bitrate is bigger than hls_segment_size, " + "(%"PRId64 " > %"PRId64 "), the result maybe not be what you want.", + outer_st->codecpar->bit_rate, hls->max_seg_size); + } + } + if (outer_st->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) inner_st = hls->avf->streams[i]; else if (hls->vtt_avf) @@ -954,6 +981,21 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) if (hls->avf->oformat->priv_class && hls->avf->priv_data) av_opt_set(hls->avf->priv_data, "mpegts_flags", "resend_headers", 0); hls->number++; + } else if (hls->max_seg_size > 0) { + if (hls->avf->oformat->priv_class && hls->avf->priv_data) + av_opt_set(hls->avf->priv_data, "mpegts_flags", "resend_headers", 0); + if (hls->start_pos >= hls->max_seg_size) { + hls->sequence++; + ff_format_io_close(s, &oc->pb); + if (hls->vtt_avf) + ff_format_io_close(s, &hls->vtt_avf->pb); + ret = hls_start(s); + hls->start_pos = 0; + /* When split segment by byte, the duration is short than hls_time, + * so it is not enough one segment duration as hls_time, */ + hls->number--; + } + hls->number++; } else { ff_format_io_close(s, &oc->pb); if (hls->vtt_avf) @@ -1028,6 +1070,7 @@ static const AVOption options[] = { {"hls_allow_cache", "explicitly set whether the client MAY (1) or MUST NOT (0) cache media segments", OFFSET(allowcache), AV_OPT_TYPE_INT, {.i64 = -1}, INT_MIN, INT_MAX, E}, {"hls_base_url", "url to prepend to each playlist entry", OFFSET(baseurl), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, {"hls_segment_filename", "filename template for segment files", OFFSET(segment_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, + {"hls_segment_size", "maximum size per segment file, (in bytes)", OFFSET(max_seg_size), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E}, {"hls_key_info_file", "file with key URI and key file path", OFFSET(key_info_file), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, {"hls_subtitle_path", "set path of hls subtitles", OFFSET(subtitle_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, {"hls_flags", "set flags affecting HLS playlist and media file generation", OFFSET(flags), AV_OPT_TYPE_FLAGS, {.i64 = 0 }, 0, UINT_MAX, E, "flags"},