Summary
When set to use CBR rate control, QSV's HEVC encoder can generate slice_segment_layer()
NALs that have trailing zeroes, which can cause MediaInfoLib to miscalculate its position when it moves to the next block of NALs. And if PPS repeating is enabled, causes it to have cascading parsing errors, which in turn corrupts the reporting output.
Data and Affected Files
MediaInfoLib version: ca37f16b7f6a2e210722e10d5a78084dbc10584b (master as of this writing)
MediaInfo version: 9c1a6e8fbd2f206805054ffcaa1c761d5f09b0ac (master as of this writing)
ZenLib version: 94ea0fd07edb43237e8e219fe2b2624a4287bd3e (master as of this writing)
Example file (post-mux) causing parsing errors: https://obsproject.com/temp/cbr_packets.mkv
Example raw HEVC packet (pre-mux) which causes parsing errors: https://obsproject.com/temp/cbr_packet.bin
HEX view of HEVC packet showing trailing zeroes: https://gist.github.com/jp9000/98fea8dfe6a045d70aa022c8ff745101
The offset to that packet (the first packet with trailing zeroes) within the MKV file is 624388
(0x98704
), or frame 168(?) I believe.
This was tested with the in-progress pull request for obs-studio: https://github.com/obsproject/obs-studio/pull/6522
It was also tested with FFmpeg, with parameters: ffmpeg -i [input file] -c:v hevc_qsv -preset:v medium -profile:v main -g:v 180 -b:v 2500k -maxrate 2500k -c:a copy [output file].mkv
-- if you want to test FFmpeg yourself, don't use blank video, use a video that has details but is moving only marginally, such as my example video. But my video file should be sufficient to show the parsing issue.
Note about MKV
MKV container is required, because for some reason MediaInfoLib will only parse all frames in a file if MKV container is used; other file containers will only parse the first dozen or so frames, which stops before the frame where it occurs (usually around 150 frames in or so).
Details of parsing issue
I'll speak relative to that first example packet. The first packet with trailing zeroes, the example packet, has 3 NAL units:
pic_parameter_set()
(because repeating headers is on; note that it doesn't matter if they're on or off, the parsing errors still occur)
sei()
slice_segment_layer()
with the aforementioned trailing zeroes
When MediaInfoLib encounters the third NAL of the example packet, the slice_segment_layer()
NAL with trailing zeroes, the first thing that happens is that it'll misinterpret the first three bytes of the trailing zeroes as a delimiter to for a new NAL, via this code:
https://github.com/MediaArea/MediaInfoLib/blob/master/Source/MediaInfo/Video/File_Hevc.cpp#L1063-L1082
Then, after it parses that NAL, it'll mistakenly parse the trailing zeroes as a fourth NAL here:
https://github.com/MediaArea/MediaInfoLib/blob/master/Source/MediaInfo/File__Analyze.cpp#L1115-L1118
It silently fails parsing the zeroes and erroneously treats it as a slice_segment_layer()
. When it finishes parsing that NAL, the Buffer_Offset
will be equal to Buffer_Size-1
, so one byte will be remaining. It'll then try to treat that last byte as a fifth NAL in the block. But Buffer_Parse()
fails, and it leaves Buffer_Offset
equal to Buffer_Size-1
.
Then, because Buffer_Offset
is left with the value of Buffer_Size-1
, when Open_Buffer_Continue_Loop()
returns to its calling function, Open_Buffer_Continue()
, it'll calculate Buffer_Size
which will now equal just 1
, here:
https://github.com/MediaArea/MediaInfoLib/blob/master/Source/MediaInfo/File__Analyze.cpp#L861
Because of that, when the next block of NALs is parsed, it will erroneously subtract 1
from the file position of that File_Offset
, here:
https://github.com/MediaArea/MediaInfoLib/blob/master/Source/MediaInfo/File__Analyze.cpp#L976-L978
Because of that, when it starts parsing NALs, the File_Offset
will be incorrect, one byte less than what it's supposed to be, causing the reading of the Size
value for the next NAL to be read as 0 instead of its correct size, here:
https://github.com/MediaArea/MediaInfoLib/blob/master/Source/MediaInfo/Video/File_Hevc.cpp#L1052

This then causes it to try to calculate the size when it tries to parse the next header here:
https://github.com/MediaArea/MediaInfoLib/blob/master/Source/MediaInfo/Video/File_Hevc.cpp#L1060-L1061
Which makes it use a much larger value than it was supposed to be (5578 versus the intended value of 8). This causes cascading corrupt parsing, especially in this case when the encoder is set to use repeating PPS. You'll get bad SEI values, and on this file in particular, you'll see this:
SEI_rbsp_stop_one_bit : Missing
Which is part of its erroneous analysis of the file due to the parsing issue.
Other thoughts
That's basically the situation. I am not very familiar with the HEVC spec, I had to wing it and look through the spec as I investigated this issue as thoroughly as I could. The trailing zeroes I was told are related to CBR's HRD compliance, or something like that (paraphrasing, can't remember exactly). I think it's CBR bitrate padding or something.
This file appears to decode properly with normal decoders.
The intent is that I'm just trying to confirm whether it's a bug in this parser, or whether it's a bug with Intel's QSV encoder. I doubt it's a bug in the QSV encoder, but I could be wrong. MediaInfoLib appears to use a custom parser, so I feel like I'd be more likely to assume it's just some bug in the parser, especially considering this is a very niche rate control, and usually parsing cuts off at a dozen or so frames on other container formats.
If you can verify whether this is a bug with the MediaInfoLib parser or not, I'd be most grateful. If it's a bug in the code, hopefully my investigation will help you pinpoint this niche bug and improve the parser. I'll relay this issue report so that you can talk with the relevant people around QSV if necessary.