[FFmpeg-trac] #5009(ffmpeg:new): Declared EXTINF duration of the last segment in the mediaplaylist.m3u8 is shorter than the actual duration of the last MPEG-TS file
FFmpeg
trac at avcodec.org
Sun Nov 15 09:15:01 CET 2015
#5009: Declared EXTINF duration of the last segment in the mediaplaylist.m3u8 is
shorter than the actual duration of the last MPEG-TS file
-------------------------------------+-------------------------------------
Reporter: | Type: defect
an_ffmpeg_user | Priority: normal
Status: new | Version:
Component: ffmpeg | unspecified
Keywords: HLS Segment | Blocked By:
Duration | Reproduced by developer: 0
Blocking: |
Analyzed by developer: 0 |
-------------------------------------+-------------------------------------
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!
--
Ticket URL: <https://trac.ffmpeg.org/ticket/5009>
FFmpeg <https://ffmpeg.org>
FFmpeg issue tracker
More information about the FFmpeg-trac
mailing list