I am currently working on an H.264 decoder. I have already studied the documentation for MP4 and NALUs, and I have a NALU list as well as the so-called DecoderConfigurationRecord, from which I read the Sequence Parameter Set. I am having issues with this SPS.
From my previous question, I know that everything up to the variable max_frame_num is correct—both the bit positions and the corresponding values in the variables. Now, I've done some additional programming and noticed that the values for pic_width_in_mbs_minus1
and pic_height_in_map_units_minus1
are incorrect for all videos. These are videos from several recorders with different dimensions. I assume my DecodeExpGolomb function is correct. The new function I've been using since last time is the DecodeExpGolombSigned function, and I suspect the error lies there. In one video, the bit position is progressing too far, causing the byte array of the SPS to be exceeded.
I've created a test project for you (WinForms application)—as small as possible but containing everything necessary. The reading of the NALU header/extension is not included here. By the way, this time I've added all the bytes of the SPS.
The values I expect are 1800px for the width and 2700px for the height. Because the video in this example only has 3 frames, I also expect a plausible value for max_num_ref_frames
.
I would appreciate any help, as I find these byte functions a bit challenging.
Friend NotInheritable Class BitStream
Friend Property BytePos As Integer
Get
Return _BytePos
End Get
Private Set
_BytePos = Value
UpdateAbsBitPos()
End Set
End Property
Friend Property BitPos As Integer
Get
Return _BitPos
End Get
Private Set
_BitPos = Value
UpdateAbsBitPos()
End Set
End Property
''' <summary>
''' as index 0–7
''' </summary>
''' <returns></returns>
Friend Property AbsBitPos As Integer
Get
Return _AbsBitPos
End Get
Private Set
_AbsBitPos = Value
End Set
End Property
Private ReadOnly _paddingLength As Integer
Private _BytePos As Integer = 0
Private _BitPos As Integer = 0
Private _AbsBitPos As Integer
Friend Sub New(paddingLength As Integer)
Me._paddingLength = paddingLength
UpdateAbsBitPos()
End Sub
Friend Sub SetInitialPositions(byteNumber As Integer, bitNumber As Integer)
Me.BytePos = byteNumber
Me.BitPos = bitNumber
End Sub
Friend Sub SetBitPosition(bitPosition As Integer)
Me.BytePos = bitPosition \ 8
Me.BitPos = bitPosition Mod 8
End Sub
Friend Sub IncrementBytePosition(byteNumber As Integer)
Dim totalBits As Integer = (Me.BytePos + byteNumber) * 8 + Me.BitPos
SetBitPosition(totalBits)
End Sub
Friend Sub IncrementBitPosition(bitNumber As Integer)
Dim totalBits As Integer = (Me.BytePos * 8) + Me.BitPos + bitNumber
SetBitPosition(totalBits)
End Sub
Public Overrides Function ToString() As String
Return $"Byte: {Me.BytePos.ToString(New Globalization.CultureInfo("en-GB")).PadLeft(_paddingLength, " "c)}, Bit: {Me.BitPos}, Absolute BitPos: {Me.AbsBitPos}"
End Function
''' <summary>
''' Updates the current bit position to a new absolute bit position.
''' Automatically adjusts the byte position as necessary.
''' </summary>
''' <param name="newBitPos"></param>
Friend Sub UpdateBitPosition(newBitPos As Integer)
SetBitPosition(newBitPos)
End Sub
''' <summary>
''' Updates the current bit position to a new absolute bit position.
''' Automatically adjusts the byte position as necessary.
''' </summary>
''' <param name="newBitPos"></param>
Friend Sub UpdateBitPosition(newBitPos As UInteger)
SetBitPosition(CInt(newBitPos))
End Sub
''' <summary>
''' Updates the absolute bit position based on the current byte and bit positions.
''' </summary>
Private Sub UpdateAbsBitPos()
Me.AbsBitPos = (Me.BytePos * 8) + Me.BitPos
End Sub
End Class
Friend NotInheritable Class SequenceParameterSet
Friend ReadOnly Property SequenceParameterSetLength As UInt16
Friend ReadOnly Property SequenceParameterSetNALUnit As Byte()
Friend Sub New(sequenceParameterSetLength As UInt16, sequenceParameterSetNALUnit As Byte())
Me.SequenceParameterSetLength = sequenceParameterSetLength
Me.SequenceParameterSetNALUnit = sequenceParameterSetNALUnit
End Sub
End Class
Friend NotInheritable Class ByteTools
Friend Shared Function ReadBitsWithinOneByte(value As Byte, startPositionIndexFrom As Integer, numBitsToRead As Integer) As Byte
Return value << startPositionIndexFrom >> (8 - numBitsToRead) << (8 - numBitsToRead - startPositionIndexFrom)
End Function
Friend Shared Function DecodeExpGolomb(byteStream As Byte(), ByRef nextPos As UInteger) As UInt32
'https://stackoverflow.com/a/39841215/13936657
If byteStream Is Nothing Then
Throw New ArgumentException("The byte array must not be null.", NameOf(byteStream))
ElseIf nextPos = 0UI Then
Throw New ArgumentException("Index must not be 0.", NameOf(nextPos))
End If
Dim leadingZeroBits As UInteger = UInt32.MaxValue
Dim pos As UInteger = nextPos
Dim b As Integer = 0
While b = 0
b = GetBitByPosArray(byteStream, CInt(pos))
pos += 1UI
If leadingZeroBits < UInt32.MaxValue Then ' In the original C++ code, leadingZeroBits is an uint32 (as here) but is initialized with -1 (so, UInt32.MaxValue), and overflows without exception.
leadingZeroBits += 1UI
Else
leadingZeroBits = 0UI
End If
End While
' In the original C++ code, there was a for loop with bit-shifting on this line, but it was incorrect. In fact, that answer was intended to correct a previous one.
nextPos = pos
Dim bitCount As Integer = Math.Max(1, CInt(leadingZeroBits)) ' Always read at least 1 bit
Dim readBitsResult As UInteger = ReadBits(byteStream, CInt(pos) - 1, bitCount) ' Starting from the position with the "1" where the while loop stops counting the leading zeros
If leadingZeroBits > 0UI Then
nextPos += CUInt(bitCount)
End If
Dim extractedValue As UInteger = CUInt(Math.Pow(2, leadingZeroBits)) - 1UI + readBitsResult
Return extractedValue
End Function
Friend Shared Function DecodeExpGolombSigned(byteStream() As Byte, ByRef nextPos As UInteger) As Integer
If byteStream Is Nothing Then
Throw New ArgumentException("The byte array must not be null.", NameOf(byteStream))
ElseIf nextPos = 0UI Then
Throw New ArgumentException("Index must not be 0.", NameOf(nextPos))
End If
Dim leadingZeroBits As UInteger = UInt32.MaxValue
Dim pos As UInteger = nextPos
Dim b As Integer = 0
While b = 0
b = GetBitByPosArray(byteStream, CInt(pos))
pos += 1UI
If leadingZeroBits < UInt32.MaxValue Then ' In the original C++ code, leadingZeroBits is an uint32 (as here) but is initialized with -1 (so, UInt32.MaxValue), and overflows without exception.
leadingZeroBits += 1UI
Else
leadingZeroBits = 0UI
End If
End While
' In the original C++ code, there was a for loop with bit-shifting on this line, but it was incorrect. In fact, that answer was intended to correct a previous one.
nextPos = pos
Dim bitCount As Integer = Math.Max(1, CInt(leadingZeroBits)) ' Always read at least 1 bit
Dim tmpResult As Integer = CInt(ReadBits(byteStream, CInt(pos) - 1, bitCount)) ' Starting from the position with the "1" where the while loop stops counting the leading zeros
If leadingZeroBits > 0UI Then
nextPos += CUInt(bitCount)
End If
Dim result As Integer
If (tmpResult And 1) > 0 Then
result = (tmpResult + 1) >> 1
Else
result = -(tmpResult >> 1)
End If
Return result
End Function
Friend Shared Function ReadBits(bytes As Byte(), bitStartPositionAsIndex As Integer, bitCount As Integer) As UInteger
If bitCount = 0 Then
Throw New ArgumentException($"{NameOf(bitCount)} must not be 0.")
End If
If bytes Is Nothing Then
Throw New ArgumentException("The byte array must not be null.", NameOf(bytes))
End If
If bitStartPositionAsIndex >= bytes.Length * 8 Then
Throw New ArgumentException($"{NameOf(bitStartPositionAsIndex)} is too large ({bitStartPositionAsIndex}) for the byte array with a size of {bytes.Length} bytes.")
End If
If bitCount > 32 Then
Throw New ArgumentException("UInt32 can only hold up to 32 bits.")
End If
Dim startIndex As Integer = bitStartPositionAsIndex \ 8
Dim endIndex As Integer = (bitStartPositionAsIndex + bitCount - 1) \ 8
Dim remE As Integer = (bitStartPositionAsIndex + bitCount) Mod 8
Dim a As UInt32 = bytes.Skip(startIndex).Take(1 + endIndex - startIndex).Select(AddressOf Convert.ToUInt32).Aggregate(Function(x, y) x << 8 Or y)
Return a >> ((8 - remE) Mod 8) And (1UI << bitCount) - 1UI
End Function
Private Shared Function GetBitByPosArray(buffer As Byte(), pos As Integer) As Byte
If buffer Is Nothing Then
Throw New ArgumentException("The byte array must not be null.", NameOf(buffer))
End If
pos = pos Mod (buffer.Length * 8)
Return CByte((buffer(pos \ 8) >> (7 - pos Mod 8)) And 1)
End Function
''' <summary>
''' Retrieves a single bit (0 or 1) from a given byte at a specified position, and returns a boolean.
''' </summary>
''' <param name="value"></param>
''' <param name="index"></param>
''' <returns></returns>
Friend Shared Function GetBitB(value As Byte, index As Integer) As Boolean
If index < 0 Then
Throw New ArgumentException($"{NameOf(index)} cannot be less than 0.", NameOf(index))
ElseIf index >= 8 Then
Throw New ArgumentException($"{NameOf(index)} cannot be greater than or equal to 8.", NameOf(index))
End If
index = index Mod 8
Return CByte((value >> (7 - index)) And 1) > 0
End Function
Friend Shared Function GetBit(value As Byte, index As Integer) As Byte
If index < 0 Then
Throw New ArgumentException($"{NameOf(index)} cannot be less than 0.", NameOf(index))
ElseIf index >= 8 Then
Throw New ArgumentException($"{NameOf(index)} cannot be greater than or equal to 8.", NameOf(index))
End If
index = index Mod 8
Return CByte((value >> (7 - index)) And 1)
End Function
End Class
Friend NotInheritable Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
' Some things here, e.g. NALU header
'...
Dim spsList As New List(Of SequenceParameterSet)()
Dim spsTest As New SequenceParameterSet(27US, New Byte() {103, 100, 0, 51, 172, 217, 0, 113, 1, 83, 229, 188, 4, 64, 0, 0, 3, 0, 64, 0, 0, 15, 3, 198, 12, 101, 128})
spsList.Add(spsTest)
ReadSps(spsList, 3UI)
End Sub
''' <summary>
''' The SPS contains a special profile (HDR, etc) und thus more fields.
''' </summary>
''' <param name="id"></param>
''' <returns></returns>
Private Shared Function IsSpecialSpsProfile(id As UInt32) As Boolean
Dim ids As New List(Of UInt32) From {100, 110, 122, 244, 44, 83, 86, 118, 128, 138, 139, 134, 135}
Return ids.Contains(id)
End Function
Private Sub ScalingList(ByRef scalingList As Integer(), sizeOfScalingList As Integer, ByRef useDefaultScalingMatrixFlag As Boolean, byteStream As Byte(), ByRef nextPos As UInteger)
Dim lastScale As Integer = 8
Dim nextScale As Integer = 8
Dim delta_scale As Integer
For j As Integer = 0 To sizeOfScalingList - 1
If nextScale <> 0 Then
delta_scale = ByteTools.DecodeExpGolombSigned(byteStream, nextPos)
nextScale = (lastScale + delta_scale + 256) Mod 256
useDefaultScalingMatrixFlag = (j = 0 AndAlso nextScale = 0)
End If
scalingList(j) = If(nextScale = 0, lastScale, nextScale)
lastScale = scalingList(j)
Next
End Sub
Private Sub ReadSps(spsList As List(Of SequenceParameterSet), numberOfFramesInTrack As UInteger)
Dim bs As New BitStream(7)
' ===================
' Start of SPS data
' ===================
Dim hasFinishedSps As Boolean = False
If spsList.Count > 0 AndAlso Not hasFinishedSps Then ' SPS from DecoderConfigurationRecord → SPS List
Dim sps As SequenceParameterSet = spsList.First()
Dim profile_idc As Byte = sps.SequenceParameterSetNALUnit(1)
Dim constraint_set0_flag As Boolean = ByteTools.GetBitB(sps.SequenceParameterSetNALUnit(2), 0)
Dim constraint_set1_flag As Boolean = ByteTools.GetBitB(sps.SequenceParameterSetNALUnit(2), 1)
Dim constraint_set2_flag As Boolean = ByteTools.GetBitB(sps.SequenceParameterSetNALUnit(2), 2)
Dim constraint_set3_flag As Boolean = ByteTools.GetBitB(sps.SequenceParameterSetNALUnit(2), 3)
Dim constraint_set4_flag As Boolean = ByteTools.GetBitB(sps.SequenceParameterSetNALUnit(2), 4)
Dim constraint_set5_flag As Boolean = ByteTools.GetBitB(sps.SequenceParameterSetNALUnit(2), 5)
Dim reserved_zero_2bits As Byte = ByteTools.ReadBitsWithinOneByte(sps.SequenceParameterSetNALUnit(2), 6, 2)
#If DEBUG Then
If reserved_zero_2bits <> CByte(0) Then
System.Diagnostics.Debug.WriteLine($"{NameOf(reserved_zero_2bits)}: {reserved_zero_2bits}. Supposed to be 0, but has to be ignored anyway.")
End If
#End If
Dim level_idc As Byte = sps.SequenceParameterSetNALUnit(3)
bs.SetInitialPositions(4, 0) '5. Byte
Dim absoluteBitPos As UInteger = CUInt(bs.AbsBitPos)
'0–31
Dim seq_Parameter_set_id As UInt32 = ByteTools.DecodeExpGolomb(sps.SequenceParameterSetNALUnit, absoluteBitPos)
bs.UpdateBitPosition(absoluteBitPos)
#If DEBUG Then
If seq_Parameter_set_id > 31UI Then
System.Diagnostics.Debug.WriteLine($"{NameOf(seq_Parameter_set_id)}: {seq_Parameter_set_id}")
End If
#End If
Dim ChromaArrayType As Byte = 0
Dim SubWidthC As UInt32? = Nothing
Dim SubHeightC As UInt32? = Nothing
If IsSpecialSpsProfile(profile_idc) Then
'0–3
Dim chroma_format_idc As UInt32 = ByteTools.DecodeExpGolomb(sps.SequenceParameterSetNALUnit, absoluteBitPos)
bs.UpdateBitPosition(absoluteBitPos)
#If DEBUG Then
If chroma_format_idc > 3UI Then
System.Diagnostics.Debug.WriteLine($"{NameOf(chroma_format_idc)} should not be greater than 3, but is: {chroma_format_idc}.")
End If
#End If
Dim separate_colour_plane_flag As Byte = 0
If chroma_format_idc = CByte(3) Then
separate_colour_plane_flag = ByteTools.GetBit(sps.SequenceParameterSetNALUnit(bs.BytePos), bs.BitPos)
bs.IncrementBitPosition(1)
End If
If chroma_format_idc = CByte(1) OrElse chroma_format_idc = CByte(2) Then
SubWidthC = 2UI
SubHeightC = 2UI
ElseIf chroma_format_idc = CByte(3) AndAlso separate_colour_plane_flag = CByte(0) Then
SubWidthC = 1UI
SubHeightC = 1UI
End If
If separate_colour_plane_flag = CByte(0) Then
ChromaArrayType = CByte(chroma_format_idc)
Else
ChromaArrayType = 0
End If
'0–6
Dim bit_depth_luma_minus8 As UInt32 = ByteTools.DecodeExpGolomb(sps.SequenceParameterSetNALUnit, absoluteBitPos)
bs.UpdateBitPosition(absoluteBitPos)
#If DEBUG Then
If bit_depth_luma_minus8 > 6UI Then
System.Diagnostics.Debug.WriteLine($"{NameOf(bit_depth_luma_minus8)}: {bit_depth_luma_minus8}")
End If
#End If
' Bit depth 0–6
Dim bit_depth_chroma_minus8 As UInt32 = ByteTools.DecodeExpGolomb(sps.SequenceParameterSetNALUnit, absoluteBitPos)
bs.UpdateBitPosition(absoluteBitPos)
#If DEBUG Then
If bit_depth_chroma_minus8 > 6 Then
System.Diagnostics.Debug.WriteLine($"{NameOf(bit_depth_chroma_minus8)}: {bit_depth_chroma_minus8 }")
End If
#End If
Dim qpprime_y_zero_transform_bypass_flag As Boolean = ByteTools.GetBitB(sps.SequenceParameterSetNALUnit(bs.BytePos), bs.BitPos)
bs.IncrementBitPosition(1)
Dim seq_scaling_matrix_present_flag As Boolean = ByteTools.GetBitB(sps.SequenceParameterSetNALUnit(bs.BytePos), bs.BitPos)
bs.IncrementBitPosition(1)
Dim Flat_4x4_16 As Integer() = Enumerable.Repeat(16, 16).ToArray() ' 4×4 Matrix, each value is 16
Dim Flat_8x8_16 As Integer() = Enumerable.Repeat(16, 64).ToArray() ' 8×8 Matrix, each value is 16
Dim limit As Integer = If(chroma_format_idc <> 3, 8, 12)
Dim ScalingList4x4 As Integer() = New Integer(15) {}
Dim ScalingList8x8 As Integer() = New Integer(63) {}
Dim UseDefaultScalingMatrix4x4Flag As Boolean = False
Dim UseDefaultScalingMatrix8x8Flag As Boolean = False
If seq_scaling_matrix_present_flag Then
Dim seq_scaling_list_present_flag As Boolean() = New Boolean(limit - 1) {}
For i As Integer = 0 To limit - 1
seq_scaling_list_present_flag(i) = ByteTools.GetBitB(sps.SequenceParameterSetNALUnit(bs.BytePos), bs.BitPos)
bs.IncrementBitPosition(1)
If seq_scaling_list_present_flag(i) Then
absoluteBitPos = CUInt(bs.AbsBitPos)
If i < 6 Then
' If seq_scaling_list_present_flag(i) is set, use ScalingList4x4
ScalingList(ScalingList4x4, 16, UseDefaultScalingMatrix4x4Flag, sps.SequenceParameterSetNALUnit, absoluteBitPos)
Else
' Use ScalingList8x8 if i >= 6
ScalingList(ScalingList8x8, 64, UseDefaultScalingMatrix8x8Flag, sps.SequenceParameterSetNALUnit, absoluteBitPos)
End If
bs.UpdateBitPosition(absoluteBitPos)
Else
' If seq_scaling_list_present_flag(i) is not set, use the default matrices
If i < 6 Then
ScalingList4x4(i) = Flat_4x4_16(i) ' Set the default 4×4 matrix for i = 0..5
Else
ScalingList8x8(i - 6) = Flat_8x8_16(i - 6) ' Set the default 8×8-Matrix for i = 6..11
End If
End If
Next
Else
' If seq_scaling_matrix_present_flag = 0, use only the default matrices
For i As Integer = 0 To 5
ScalingList4x4(i) = Flat_4x4_16(i)
Next
For i As Integer = 6 To 11
ScalingList8x8(i - 6) = Flat_8x8_16(i - 6)
Next
End If
End If
' – – – – – – – –
absoluteBitPos = CUInt(bs.AbsBitPos)
' supposed to equal to 0–12
Dim log2_max_frame_num_minus4 As UInt32 = ByteTools.DecodeExpGolomb(sps.SequenceParameterSetNALUnit, absoluteBitPos)
bs.UpdateBitPosition(absoluteBitPos)
#If DEBUG Then
If log2_max_frame_num_minus4 > 12UI Then
System.Diagnostics.Debug.WriteLine($"{NameOf(log2_max_frame_num_minus4)} is greater than 12: {String.Format("{0:n0}", log2_max_frame_num_minus4)}")
End If
#End If
Dim max_frame_num As UInt32 = CUInt(Math.Pow(2, log2_max_frame_num_minus4 + 4))
Dim fallback_max_frame_num As UInt32 = CUInt(Math.Pow(2, Math.Ceiling(Math.Log(numberOfFramesInTrack) / Math.Log(2))))
' Validate max_frame_num
If max_frame_num < numberOfFramesInTrack OrElse max_frame_num > fallback_max_frame_num * 2 Then
' Fallback if max_frame_num is unreasonably low or too high
'System.Diagnostics.Debug.WriteLine($"Fallback triggered: Parsed max_frame_num = {max_frame_num}, Fallback (german numbering) = {String.Format(New System.Globalization.CultureInfo("de-DE"), "{0:n0}", fallback_max_frame_num)}")
max_frame_num = fallback_max_frame_num
End If
' – – – – – – – –
' 0–2
Dim pic_order_cnt_type As UInt32 = ByteTools.DecodeExpGolomb(sps.SequenceParameterSetNALUnit, absoluteBitPos)
bs.UpdateBitPosition(absoluteBitPos)
If pic_order_cnt_type = 0UI Then
' 0–12
Dim log2_max_pic_order_cnt_lsb_minus4 As UInt32 = ByteTools.DecodeExpGolomb(sps.SequenceParameterSetNALUnit, absoluteBitPos)
bs.UpdateBitPosition(absoluteBitPos)
Dim MaxPicOrderCntLsb As UInt32 = CUInt(Math.Pow(2, log2_max_pic_order_cnt_lsb_minus4 + 4))
ElseIf pic_order_cnt_type = 1UI Then
Dim delta_pic_order_always_zero_flag As Boolean = ByteTools.GetBitB(sps.SequenceParameterSetNALUnit(bs.BytePos), bs.BitPos)
bs.IncrementBitPosition(1)
absoluteBitPos = CUInt(bs.AbsBitPos)
' -(2^31) to (2^31)-1
Dim offset_for_non_ref_pic As Integer = ByteTools.DecodeExpGolombSigned(sps.SequenceParameterSetNALUnit, absoluteBitPos)
bs.UpdateBitPosition(absoluteBitPos)
' -(2^31) to (2^31)-1
Dim offset_for_top_to_bottom_field As Integer = ByteTools.DecodeExpGolombSigned(sps.SequenceParameterSetNALUnit, absoluteBitPos)
bs.UpdateBitPosition(absoluteBitPos)
' 0–255
Dim num_ref_frames_in_pic_order_cnt_cycle As UInt32 = ByteTools.DecodeExpGolomb(sps.SequenceParameterSetNALUnit, absoluteBitPos)
bs.UpdateBitPosition(absoluteBitPos)
Dim offsets_for_ref_frame As New List(Of Integer)()
For i As Integer = 0 To CInt(num_ref_frames_in_pic_order_cnt_cycle) - 1 Step 1
Dim offset As Integer = ByteTools.DecodeExpGolombSigned(sps.SequenceParameterSetNALUnit, absoluteBitPos)
offsets_for_ref_frame.Add(offset)
bs.UpdateBitPosition(absoluteBitPos)
Next
End If
Dim max_num_ref_frames As UInt32 = ByteTools.DecodeExpGolomb(sps.SequenceParameterSetNALUnit, absoluteBitPos)
bs.UpdateBitPosition(absoluteBitPos)
Dim gaps_in_frame_num_value_allowed_flag As Boolean = ByteTools.GetBitB(sps.SequenceParameterSetNALUnit(bs.BytePos), bs.BitPos)
bs.IncrementBitPosition(1)
absoluteBitPos = CUInt(bs.AbsBitPos)
' in Macro blocks (16×16 Pixel)
Dim pic_width_in_mbs_minus1 As UInt32 = ByteTools.DecodeExpGolomb(sps.SequenceParameterSetNALUnit, absoluteBitPos)
bs.UpdateBitPosition(absoluteBitPos)
Dim pic_height_in_map_units_minus1 As UInt32 = ByteTools.DecodeExpGolomb(sps.SequenceParameterSetNALUnit, absoluteBitPos)
bs.UpdateBitPosition(absoluteBitPos)
Dim frame_mbs_only_flag As Byte = ByteTools.GetBit(sps.SequenceParameterSetNALUnit(bs.BytePos), bs.BitPos)
bs.IncrementBitPosition(1)
absoluteBitPos = CUInt(bs.AbsBitPos)
If frame_mbs_only_flag = CByte(0) Then
Dim mb_adaptive_frame_field_flag As Boolean = ByteTools.GetBitB(sps.SequenceParameterSetNALUnit(bs.BytePos), bs.BitPos)
bs.IncrementBitPosition(1)
absoluteBitPos = CUInt(bs.AbsBitPos)
End If
Dim direct_8x8_inference_flag As Boolean = ByteTools.GetBitB(sps.SequenceParameterSetNALUnit(bs.BytePos), bs.BitPos)
bs.IncrementBitPosition(1)
absoluteBitPos = CUInt(bs.AbsBitPos)
Dim frame_cropping_flag As Boolean = ByteTools.GetBitB(sps.SequenceParameterSetNALUnit(bs.BytePos), bs.BitPos)
bs.IncrementBitPosition(1)
absoluteBitPos = CUInt(bs.AbsBitPos)
Dim frame_crop_left_offset As UInt32 = 0UI
Dim frame_crop_right_offset As UInt32 = 0UI
Dim frame_crop_top_offset As UInt32 = 0UI
Dim frame_crop_bottom_offset As UInt32 = 0UI
If frame_cropping_flag Then
frame_crop_left_offset = ByteTools.DecodeExpGolomb(sps.SequenceParameterSetNALUnit, absoluteBitPos)
bs.UpdateBitPosition(absoluteBitPos)
frame_crop_right_offset = ByteTools.DecodeExpGolomb(sps.SequenceParameterSetNALUnit, absoluteBitPos)
bs.UpdateBitPosition(absoluteBitPos)
frame_crop_top_offset = ByteTools.DecodeExpGolomb(sps.SequenceParameterSetNALUnit, absoluteBitPos)
bs.UpdateBitPosition(absoluteBitPos)
frame_crop_bottom_offset = ByteTools.DecodeExpGolomb(sps.SequenceParameterSetNALUnit, absoluteBitPos)
bs.UpdateBitPosition(absoluteBitPos)
End If
Dim CropUnitX As UInt32 = 0UI
Dim CropUnitY As UInt32 = 0UI
Dim PicWidthInSamples As UInt32 = (pic_width_in_mbs_minus1 + 1UI) * 16UI
Dim PicHeightInMapUnits As UInt32 = pic_height_in_map_units_minus1 + 1UI
Dim FrameHeightInMbs As UInt32 = (2UI - frame_mbs_only_flag) * PicHeightInMapUnits
If ChromaArrayType = CByte(0) Then
CropUnitX = 1UI
CropUnitY = 2UI - frame_mbs_only_flag
Else
If SubWidthC.HasValue Then
CropUnitX = SubWidthC.Value
End If
If SubHeightC.HasValue Then
CropUnitY = SubHeightC.Value * (2UI - frame_mbs_only_flag)
End If
End If
Dim finalWidth As UInt32 = PicWidthInSamples - CropUnitX * (frame_crop_left_offset + frame_crop_right_offset)
System.Diagnostics.Debug.WriteLine($"Width: {finalWidth} px")
Dim finalHeight As UInt32 = 16UI * FrameHeightInMbs - CropUnitY * (frame_crop_top_offset + frame_crop_bottom_offset)
hasFinishedSps = True
'ElseIf currentNalu.Type = CByte(7) Then ' SPS from Nalu
End If
End Sub
End Class
"I noticed that the values for
pic_width_in_mbs_minus1
andpic_height_in_map_units_minus1
are incorrect for all videos."
"The values I expect are 1800px for the width and 2700px for the height. Because the video in this example only has 3 frames, I also expect a plausible value formax_num_ref_frames
."
If you are getting 112 for pic_width_in_mbs_minus1
then your code is correct.
An MB (or MacroBlock) is a rectangular block of pixels, width is 16px and height is 16px.
112 * mb_width_16px == (112 * 16) == 1792px width //# 112 is mbs_minus1
113 * mb_width_16px == (113 * 16) == 1808px width //# 113 is mbs_minus1 +1
Your picture width is 1800px so it needs 113 MBs. The minus1, as encoded in SPS, is 112.
max_num_ref_frames
is for configuring the H264 decoder. It has nothing to do with any total video frame count. Even a 1 frame video can have a reference frame
count of 4.
Your max_num_ref_frames
seems to be encoded as 3. Do you get that number from your code?
"I would appreciate any help, as I find these byte functions a bit challenging."
One way for doing some faster double-checking is to use an H264 analysis tool.
Such tools will show you the values of the existing H264 variables according to your NALU's bytes structure then now it's faster to confirm if the code is reading correctly (or if not, then adjust code to match results).
I like CodecVisa (with 30-day free testing) but you can get the same NALU header results (minus the beautiful GUI) for free using FFmpeg. I suspect you already have an FFmpeg executable (runs in commandline)?
Did you know FFmpeg can print details about a NALU's header variables?
ffmpeg -i test.h264 -c copy -bsf:v trace_headers -f null - 2> nalu_results.txt
example:
0, 0, 0, 1
start code followed by the values of your SPStestnalu.h264
Run this FFmpeg command:
ffmpeg -i testnalu.h264 -c copy -bsf:v trace_headers -f null - 2> nalu_result_sps.txt
Then the print out looks like:
where layout is: [ bit-pos, H264 variable name, bits of variable, =, decimal value]
0 forbidden_zero_bit 0 = 0
1 nal_ref_idc 11 = 3
3 nal_unit_type 00111 = 7
8 profile_idc 01100100 = 100
16 constraint_set0_flag 0 = 0
17 constraint_set1_flag 0 = 0
18 constraint_set2_flag 0 = 0
19 constraint_set3_flag 0 = 0
20 constraint_set4_flag 0 = 0
21 constraint_set5_flag 0 = 0
22 reserved_zero_2bits 00 = 0
24 level_idc 00110011 = 51
32 seq_parameter_set_id 1 = 0
33 chroma_format_idc 010 = 1
36 bit_depth_luma_minus8 1 = 0
37 bit_depth_chroma_minus8 1 = 0
38 qpprime_y_zero_transform_bypass_flag 0 = 0
39 seq_scaling_matrix_present_flag 0 = 0
40 log2_max_frame_num_minus4 1 = 0
41 pic_order_cnt_type 1 = 0
42 log2_max_pic_order_cnt_lsb_minus4 011 = 2
45 max_num_ref_frames 00100 = 3
50 gaps_in_frame_num_allowed_flag 0 = 0
51 pic_width_in_mbs_minus1 0000001110001 = 112
64 pic_height_in_map_units_minus1 000000010101001 = 168
79 frame_mbs_only_flag 1 = 1
80 direct_8x8_inference_flag 1 = 1
81 frame_cropping_flag 1 = 1
82 frame_crop_left_offset 1 = 0
83 frame_crop_right_offset 00101 = 4
88 frame_crop_top_offset 1 = 0
89 frame_crop_bottom_offset 011 = 2
92 vui_parameters_present_flag 1 = 1
93 aspect_ratio_info_present_flag 1 = 1
94 aspect_ratio_idc 00000001 = 1
102 overscan_info_present_flag 0 = 0
103 video_signal_type_present_flag 0 = 0
104 chroma_loc_info_present_flag 0 = 0
105 timing_info_present_flag 1 = 1
106 num_units_in_tick 00000000000000000000000000000001 = 1
138 time_scale 00000000000000000000000000111100 = 60
170 fixed_frame_rate_flag 0 = 0
171 nal_hrd_parameters_present_flag 0 = 0
172 vcl_hrd_parameters_present_flag 0 = 0
173 pic_struct_present_flag 0 = 0
174 bitstream_restriction_flag 1 = 1
175 motion_vectors_over_pic_boundaries_flag 1 = 1
176 max_bytes_per_pic_denom 1 = 0
177 max_bits_per_mb_denom 1 = 0
178 log2_max_mv_length_horizontal 0001100 = 11
185 log2_max_mv_length_vertical 0001100 = 11
192 max_num_reorder_frames 011 = 2
195 max_dec_frame_buffering 00101 = 4
200 rbsp_stop_one_bit 1 = 1
201 rbsp_alignment_zero_bit 0 = 0
202 rbsp_alignment_zero_bit 0 = 0
203 rbsp_alignment_zero_bit 0 = 0
204 rbsp_alignment_zero_bit 0 = 0
205 rbsp_alignment_zero_bit 0 = 0
206 rbsp_alignment_zero_bit 0 = 0
207 rbsp_alignment_zero_bit 0 = 0