#5009 closed defect (fixed)
HLS Segmenter: Declared EXTINF duration of the last segment in the m3u8 is incorrect
Reported by: | an_ffmpeg_user | Owned by: | |
---|---|---|---|
Priority: | normal | Component: | avformat |
Version: | git-master | Keywords: | HLS |
Cc: | Blocked By: | ||
Blocking: | Reproduced by developer: | no | |
Analyzed by developer: | no |
Description
Summary. When encoding using the FFmpeg HLS output, the declared EXTINF duration of the last segment in the mediaplaylist.m3u8 is shorter than the actual duration of the last MPEG-TS file.
Unfortunately, this affects the playback on strict HLS Players that consider playback to be over when the last segment's EXTINF has expired. End credits or copyright frames do not get displayed.
It appears to be an issue with FFmpeg's generation of EXTINF duration in the m3u8 for the last segment. The duration of the last segment in the playlist should not be lower than the actual TS duration.
The FFmpeg 2.8 HLS Segmenter generates a EXT-X-VERSION:3 Playlist with integers to six decimal places. HLS Protocol version 3, Draft 5 https://tools.ietf.org/html/draft-pantos-http-live-streaming-05
#EXTINF:<duration>,<title> "duration" is an integer or floating-point number that specifies the duration of the media file in seconds. Integer durations SHOULD be rounded to the nearest integer.
Floating-point durations should be accurate. If the duration is being declared to six decimal places, it should be accurate to six decimal places.
It is acknowledged that the duration of a TS can be ambiguous as there are multiple durations reported by FFprobe and MediaInfo
- container duration
- video duration
- audio duration
Therefore to avoid ambiguity, the following example uses a video-only stream. FFprobe outputs of both 'container duration' and 'video duration' are included below.
How to replicate...
First, generate a 1080p30 mezzanine MP4 file to be used as a source for the test. Using testsrc input so that anyone is able to reproduce:
$ ffmpeg -f lavfi -i testsrc=duration=14:size=1920x1080:rate=30*1000/1001 -vf "drawtext=fontfile=/Library/Fonts/Verdana.ttf:timecode='00\:00\:00\:00':r=30*1000/1001:x=(w-tw)/2:y=h-(2*lh):fontcolor=white:box=1:boxcolor=0x00000099" -c:v libx264 -profile:v high -level 4.0 -x264opts "bitrate=3500:vbv-maxrate=3500:vbv-bufsize=3500" -pix_fmt yuv420p -f mp4 ./mezzanine.mp4
Inspect the duration of the mezzanine file. The mezzanine MP4 has a duration of 14.014000 seconds (due to 29.970 vs 30.00fps).
$ ffprobe -loglevel quiet -print_format flat -show_entries format=duration "./mezzanine.mp4" format.duration="14.014000" $ ffprobe -loglevel quiet -print_format flat -show_streams -select_streams v -show_entries stream=duration "./mezzanine.mp4" streams.stream.0.duration="14.014000"
Now, transcode and segment the source file using the HLS output (including full FFmpeg command line output):
$ ffmpeg -i "./mezzanine.mp4" -vf "scale=1280:720" -c:v libx264 -preset medium -profile:v main -level 3.1 -x264opts "bitrate=2400:vbv-maxrate=2400:vbv-bufsize=2400:min-keyint=30:keyint=60:scenecut=0" -pix_fmt yuv420p -f hls -hls_time 6 -hls_list_size 0 -hls_segment_filename 'test_1280x720_%03d.ts' ./test_mediaplaylist_1280x720.m3u8 ffmpeg version 2.8.1 Copyright (c) 2000-2015 the FFmpeg developers built with Apple LLVM version 7.0.0 (clang-700.1.76) configuration: --prefix=/usr/local/Cellar/ffmpeg/2.8.1_1 --enable-shared --enable-pthreads --enable-gpl --enable-version3 --enable-hardcoded-tables --enable-avresample --cc=clang --host-cflags= --host-ldflags= --enable-opencl --enable-libx264 --enable-libmp3lame --enable-libvo-aacenc --enable-libxvid --enable-libfontconfig --enable-libfreetype --enable-libtheora --enable-libvorbis --enable-libvpx --enable-librtmp --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libfaac --enable-libass --enable-ffplay --enable-libssh --enable-libspeex --enable-libschroedinger --enable-libfdk-aac --enable-openssl --enable-libopus --enable-frei0r --enable-libcaca --enable-libsoxr --enable-libquvi --enable-libvidstab --enable-libx265 --enable-libwebp --enable-libopenjpeg --disable-decoder=jpeg2000 --extra-cflags=-I/usr/local/Cellar/openjpeg/1.5.2_1/include/openjpeg-1.5 --enable-nonfree --enable-vda libavutil 54. 31.100 / 54. 31.100 libavcodec 56. 60.100 / 56. 60.100 libavformat 56. 40.101 / 56. 40.101 libavdevice 56. 4.100 / 56. 4.100 libavfilter 5. 40.101 / 5. 40.101 libavresample 2. 1. 0 / 2. 1. 0 libswscale 3. 1.101 / 3. 1.101 libswresample 1. 2.101 / 1. 2.101 libpostproc 53. 3.100 / 53. 3.100 Input #0, mov,mp4,m4a,3gp,3g2,mj2, from './mezzanine.mp4': Metadata: major_brand : isom minor_version : 512 compatible_brands: isomiso2avc1mp41 encoder : Lavf56.40.101 Duration: 00:00:14.01, start: 0.000000, bitrate: 394 kb/s Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 1920x1080 [SAR 1:1 DAR 16:9], 393 kb/s, 29.97 fps, 29.97 tbr, 30k tbn, 59.94 tbc (default) Metadata: handler_name : VideoHandler [libx264 @ 0x7f9fe181e600] using SAR=1/1 [libx264 @ 0x7f9fe181e600] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 [libx264 @ 0x7f9fe181e600] profile Main, level 3.1 Output #0, hls, to './test_mediaplaylist_1280x720.m3u8': Metadata: major_brand : isom minor_version : 512 compatible_brands: isomiso2avc1mp41 encoder : Lavf56.40.101 Stream #0:0(und): Video: h264 (libx264), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], q=-1--1, 2400 kb/s, 29.97 fps, 90k tbn, 29.97 tbc (default) Metadata: handler_name : VideoHandler encoder : Lavc56.60.100 libx264 Stream mapping: Stream #0:0 -> #0:0 (h264 (native) -> h264 (libx264)) Press [q] to stop, [?] for help frame= 420 fps= 43 q=-1.0 Lsize=N/A time=00:00:13.94 bitrate=N/A video:1275kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown [libx264 @ 0x7f9fe181e600] frame I:7 Avg QP: 0.82 size: 24041 [libx264 @ 0x7f9fe181e600] frame P:413 Avg QP: 1.21 size: 2754 [libx264 @ 0x7f9fe181e600] mb I I16..4: 89.6% 0.0% 10.4% [libx264 @ 0x7f9fe181e600] mb P I16..4: 8.2% 0.0% 0.1% P16..4: 6.6% 1.1% 0.7% 0.0% 0.0% skip:83.4% [libx264 @ 0x7f9fe181e600] coded y,uvDC,uvAC intra: 1.8% 7.7% 7.6% inter: 1.4% 4.9% 4.5% [libx264 @ 0x7f9fe181e600] i16 v,h,dc,p: 99% 0% 0% 0% [libx264 @ 0x7f9fe181e600] i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 60% 20% 13% 3% 1% 1% 0% 1% 0% [libx264 @ 0x7f9fe181e600] i8c dc,h,v,p: 12% 1% 85% 1% [libx264 @ 0x7f9fe181e600] Weighted P-Frames: Y:0.0% UV:0.0% [libx264 @ 0x7f9fe181e600] ref P L0: 74.9% 0.5% 17.8% 6.8% [libx264 @ 0x7f9fe181e600] kb/s:745.37
Now inspect the m3u8...
$ cat ./test_mediaplaylist_1280x720.m3u8 #EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:7 #EXT-X-MEDIA-SEQUENCE:0 #EXTINF:6.006000, test_1280x720_000.ts #EXTINF:6.006000, test_1280x720_001.ts #EXTINF:1.968633, test_1280x720_002.ts #EXT-X-ENDLIST
FFprobe sums the EXTINF values of the m3u8 and calculates a duration of 13.980633 not 14.014000
$ ffprobe -loglevel quiet -print_format flat -show_entries format=duration "./test_mediaplaylist_1280x720.m3u8" format.duration="13.980633"
Analyzing each MPEG-TS segment using FFprobe (MediaInfo also confirms FFprobe's results)
$ ffprobe -loglevel quiet -print_format flat -show_entries format=duration "./test_1280x720_000.ts" format.duration="6.006000" $ ffprobe -loglevel quiet -print_format flat -show_streams -select_streams v -show_entries stream=duration "./test_1280x720_000.ts" programs.program.0.streams.stream.0.duration="6.006000" streams.stream.0.duration="6.006000" $ ffprobe -loglevel quiet -print_format flat -show_entries format=duration "./test_1280x720_001.ts" format.duration="6.006000" $ ffprobe -loglevel quiet -print_format flat -show_streams -select_streams v -show_entries stream=duration "./test_1280x720_001.ts" programs.program.0.streams.stream.0.duration="6.006000" streams.stream.0.duration="6.006000" $ ffprobe -loglevel quiet -print_format flat -show_entries format=duration "./test_1280x720_002.ts" format.duration="2.002000" $ ffprobe -loglevel quiet -print_format flat -show_streams -select_streams v -show_entries stream=duration "./test_1280x720_002.ts" programs.program.0.streams.stream.0.duration="2.002000" streams.stream.0.duration="2.002000"
(6.006 + 6.006 + 2.002) = 14.014s, as expected.
But the sum of the EXTINF durations in the mediaplaylist.m3u8 is (6.006000 + 6.006000 + 1.968633) = 13.980633s. The discrepancy is caused by the duration of the last segment getting under-reported.
Although this seems a small difference within this example, we have seen quite significant differences depending on the source file. The example uses a 14s testsrc as it is the simplest way to reproduce the issue with the minimum number of segments. But the issue occurs with any source file.
The last segment's declared EXTINF duration in the m3u8 is always lower than the actual MPEG-TS duration.
Thanks!
Change History (4)
comment:1 by , 9 years ago
Summary: | Declared EXTINF duration of the last segment in the mediaplaylist.m3u8 is shorter than the actual duration of the last MPEG-TS file → HLS Segmenter: Declared EXTINF duration of the last segment in the m3u8 is incorrect |
---|
comment:2 by , 8 years ago
comment:3 by , 8 years ago
Resolution: | → fixed |
---|---|
Status: | new → closed |
comment:4 by , 8 years ago
Component: | ffmpeg → avformat |
---|---|
Keywords: | Segment Duration removed |
Version: | unspecified → git-master |
Fixed by http://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=2deafd98988871f25b0778059ee84a704372ec24
Install FFmpeg from latest build on OS X