Create a module dedicated to H264 parameters dataclass.

This commit is contained in:
Frédéric Tronel
2026-08-28 21:56:19 +02:00
parent a1a1ae5399
commit f6491be5b7
2 changed files with 713 additions and 686 deletions
+713
View File
@@ -0,0 +1,713 @@
import logging
from dataclasses import dataclass, field
from math import floor, log, ceil
from typeguard import typechecked
import hexdump
from tscut.h264.bitstream import (
read_bit,
read_bits,
read_boolean,
read_byte,
read_long,
read_signed_exp_golomb,
read_unsigned_exp_golomb,
read_word,
write_bit,
write_bits,
write_boolean,
write_byte,
write_long,
write_rbsp_trailing_bits,
write_scaling_list,
write_signed_exp_golomb,
write_unsigned_exp_golomb,
write_word,
parse_scaling_list,
parse_rbsp_trailing_bits,
more_rbsp_data
)
logger = logging.getLogger(__name__)
@dataclass
class HRD:
cpb_cnt_minus1: int=0
bit_rate_scale: int=0
cpb_size_scale: int=0
bit_rate_value_minus1: dict = field(default_factory=dict)
cpb_size_value_minus1: dict = field(default_factory=dict)
cbr_flag: dict = field(default_factory=dict)
initial_cpb_removal_delay_length_minus1: int=0
cpb_removal_delay_length_minus1: int=0
dpb_output_delay_length_minus1: int=0
time_offset_length: int=0
def __init__(self):
self.bit_rate_value_minus1 = {}
self.cpb_size_value_minus1 = {}
self.cbr_flag = {}
def from_bytes(self, buf, bit_position):
bit_position, self.cpb_cnt_minus1 = read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.bit_rate_scale = read_bits(buf, bit_position, 4)
bit_position, self.cpb_size_scale = read_bits(buf, bit_position, 4)
for i in range(self.cpb_cnt_minus1+1):
bit_position, v = read_unsigned_exp_golomb(buf, bit_position)
self.bit_rate_value_minus1[i] = v
bit_position, v = read_unsigned_exp_golomb(buf, bit_position)
self.cpb_size_value_minus1[i] = v
bit_position, b = read_boolean(buf, bit_position)
self.cbr_flag[i] = b
bit_position, self.initial_cpb_removal_delay_length_minus1 = read_bits(buf, bit_position, 5)
bit_position, self.cpb_removal_delay_length_minus1 = read_bits(buf, bit_position, 5)
bit_position, self.dpb_output_delay_length_minus1 = read_bits(buf, bit_position, 5)
bit_position, self.time_offset_length = read_bits(buf, bit_position, 5)
return bit_position
def to_bytes(self, buf, bit_position):
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.cpb_cnt_minus1)
bit_position = write_bits(buf, bit_position, self.bit_rate_scale, 4)
bit_position = write_bits(buf, bit_position, self.cpb_size_scale, 4)
for i in range(self.cpb_cnt_minus1+1):
v = self.bit_rate_value_minus1[i]
bit_position = write_unsigned_exp_golomb(buf, bit_position, v)
v = self.cpb_size_value_minus1[i]
bit_position = write_unsigned_exp_golomb(buf, bit_position, v)
b = self.cbr_flag[i]
bit_position = write_boolean(buf, bit_position, b)
bit_position = write_bits(buf, bit_position, self.initial_cpb_removal_delay_length_minus1,
5)
bit_position = write_bits(buf, bit_position, self.cpb_removal_delay_length_minus1, 5)
bit_position = write_bits(buf, bit_position, self.dpb_output_delay_length_minus1, 5)
bit_position = write_bits(buf, bit_position, self.time_offset_length, 5)
return bit_position
@dataclass
class VUI:
aspect_ratio_info_present_flag:bool=False
aspect_ratio_idc:int=0
sar_width:int=0
sar_height:int=0
overscan_info_present_flag:bool=False
overscan_appropriate_flag:bool=False
video_signal_type_present_flag:bool=False
video_format:int=0
video_full_range_flag:bool=False
colour_description_present_flag:bool=False
colour_primaries:int=0
transfer_characteristics:int=0
matrix_coefficients:int=0
chroma_loc_info_present_flag:bool=False
chroma_sample_loc_type_top_field:int=0
chroma_sample_loc_type_bottom_field:int=0
timing_info_present_flag:bool=False
num_units_in_tick:int=0
time_scale:int=0
fixed_frame_rate_flag:bool=False
nal_hrd_parameters_present_flag:bool=False
hrd_parameters:HRD=None
vcl_hrd_parameters_present_flag:bool=False
vcl_hrd_parameters:HRD=None
low_delay_hrd_flag:bool=False
pic_struct_present_flag:bool=False
bitstream_restriction_flag:bool=False
motion_vectors_over_pic_boundaries_flag:bool=False
max_bytes_per_pic_denom:int=0
max_bits_per_mb_denom:int=0
log2_max_mv_length_horizontal:int=0
log2_max_mv_length_vertical:int=0
max_num_reorder_frames:int=0
max_dec_frame_buffering:int=0
# This structure is not guaranteed to be located at a byte boundary.
# We must explicitely indicate bit offset.
def from_bytes(self, buf, bit_position):
bit_position, self.aspect_ratio_info_present_flag = read_boolean(buf, bit_position)
if self.aspect_ratio_info_present_flag:
bit_position, self.aspect_ratio_idc = read_byte(buf, bit_position)
if self.aspect_ratio_idc == 255: # Extended_SAR
bit_position, self.sar_width = read_word(buf, bit_position)
bit_position, self.sar_height = read_word(buf, bit_position)
bit_position, self.overscan_info_present_flag = read_boolean(buf, bit_position)
if self.overscan_info_present_flag:
bit_position, self.overscan_appropriate_flag = read_boolean(buf, bit_position)
bit_position, self.video_signal_type_present_flag = read_boolean(buf, bit_position)
if self.video_signal_type_present_flag:
bit_position, self.video_format = read_bits(buf, bit_position, 3)
bit_position, self.video_full_range_flag = read_boolean(buf, bit_position)
bit_position, self.colour_description_present_flag = read_boolean(buf, bit_position)
if self.colour_description_present_flag:
bit_position, self.colour_primaries = read_byte(buf, bit_position)
bit_position, self.transfer_characteristics = read_byte(buf, bit_position)
bit_position, self.matrix_coefficients = read_byte(buf, bit_position)
bit_position, self.chroma_loc_info_present_flag = read_boolean(buf, bit_position)
if self.chroma_loc_info_present_flag:
bit_position, self.chroma_sample_loc_type_top_field =\
read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.chroma_sample_loc_type_bottom_field =\
read_unsigned_exp_golomb(buf,bit_position)
bit_position, self.timing_info_present_flag = read_boolean(buf, bit_position)
if self.timing_info_present_flag:
bit_position, self.num_units_in_tick = read_long(buf, bit_position)
bit_position, self.time_scale = read_long(buf, bit_position)
bit_position, self.fixed_frame_rate_flag = read_boolean(buf, bit_position)
bit_position, self.nal_hrd_parameters_present_flag = read_boolean(buf, bit_position)
if self.nal_hrd_parameters_present_flag:
hrd = HRD()
bit_position = hrd.from_bytes(buf, bit_position)
self.hrd_parameters = hrd
bit_position, self.vcl_hrd_parameters_present_flag = read_boolean(buf, bit_position)
if self.vcl_hrd_parameters_present_flag:
hrd = HRD()
bit_position = hrd.from_bytes(buf, bit_position)
self.vcl_hrd_parameters = hrd
if self.nal_hrd_parameters_present_flag or self.vcl_hrd_parameters_present_flag:
bit_position, self.low_delay_hrd_flag = read_boolean(buf, bit_position)
bit_position, self.pic_struct_present_flag = read_boolean(buf, bit_position)
bit_position, self.bitstream_restriction_flag = read_boolean(buf, bit_position)
if self.bitstream_restriction_flag:
bit_position, self.motion_vectors_over_pic_boundaries_flag =\
read_boolean(buf, bit_position)
bit_position, self.max_bytes_per_pic_denom = read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.max_bits_per_mb_denom = read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.log2_max_mv_length_horizontal = read_unsigned_exp_golomb(buf,
bit_position)
bit_position, self.log2_max_mv_length_vertical = read_unsigned_exp_golomb(buf,
bit_position)
bit_position, self.max_num_reorder_frames = read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.max_dec_frame_buffering = read_unsigned_exp_golomb(buf, bit_position)
return bit_position
def to_bytes(self, buf, bit_position):
bit_position = write_boolean(buf, bit_position, self.aspect_ratio_info_present_flag)
if self.aspect_ratio_info_present_flag:
bit_position = write_byte(buf, bit_position, self.aspect_ratio_idc)
if self.aspect_ratio_idc == 255: # Extended_SAR
bit_position = write_word(buf, bit_position, self.sar_width)
bit_position = write_word(buf, bit_position, self.sar_height)
bit_position = write_boolean(buf, bit_position, self.overscan_info_present_flag)
if self.overscan_info_present_flag:
bit_position = write_boolean(buf, bit_position, self.overscan_appropriate_flag)
bit_position = write_boolean(buf, bit_position, self.video_signal_type_present_flag)
if self.video_signal_type_present_flag:
bit_position = write_bits(buf, bit_position, self.video_format, 3)
bit_position = write_boolean(buf, bit_position, self.video_full_range_flag)
bit_position = write_boolean(buf, bit_position, self.colour_description_present_flag)
if self.colour_description_present_flag:
bit_position = write_byte(buf, bit_position, self.colour_primaries)
bit_position = write_byte(buf, bit_position, self.transfer_characteristics)
bit_position = write_byte(buf, bit_position, self.matrix_coefficients)
bit_position = write_boolean(buf, bit_position, self.chroma_loc_info_present_flag)
if self.chroma_loc_info_present_flag:
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.chroma_sample_loc_type_top_field)
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.chroma_sample_loc_type_bottom_field)
bit_position = write_boolean(buf, bit_position, self.timing_info_present_flag )
if self.timing_info_present_flag:
bit_position = write_long(buf, bit_position, self.num_units_in_tick )
bit_position = write_long(buf, bit_position, self.time_scale)
bit_position = write_boolean(buf, bit_position, self.fixed_frame_rate_flag)
bit_position = write_boolean(buf, bit_position, self.nal_hrd_parameters_present_flag)
if self.nal_hrd_parameters_present_flag:
bit_position = self.hrd_parameters.to_bytes(buf, bit_position)
bit_position = write_boolean(buf, bit_position, self.vcl_hrd_parameters_present_flag)
if self.vcl_hrd_parameters_present_flag:
bit_position = self.vcl_hrd_parameters.to_bytes(buf, bit_position)
if self.nal_hrd_parameters_present_flag or self.vcl_hrd_parameters_present_flag:
bit_position = write_boolean(buf, bit_position, self.low_delay_hrd_flag)
bit_position = write_boolean(buf, bit_position, self.pic_struct_present_flag)
bit_position = write_boolean(buf, bit_position, self.bitstream_restriction_flag)
if self.bitstream_restriction_flag:
bit_position = write_boolean(buf, bit_position,
self.motion_vectors_over_pic_boundaries_flag)
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.max_bytes_per_pic_denom)
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.max_bits_per_mb_denom)
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.log2_max_mv_length_horizontal)
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.log2_max_mv_length_vertical)
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.max_num_reorder_frames)
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.max_dec_frame_buffering)
return bit_position
@dataclass
class SPS:
profile_idc:int=0 # u(8)
constraint_set0_flag:bool=False # u(1)
constraint_set1_flag:bool=False # u(1)
constraint_set2_flag:bool=False # u(1)
constraint_set3_flag:bool=False # u(1)
constraint_set4_flag:bool=False # u(1)
constraint_set5_flag:bool=False # u(1)
level_idc:int=0 # u(8)
seq_parameter_set_id:int=0 # ue(v)
chroma_format_idc:int=0 # ue(v)
separate_colour_plane_flag:bool=False # u(1)
bit_depth_luma_minus8:int=0 # ue(v)
bit_depth_chroma_minus8:int=0 # ue(v)
qpprime_y_zero_transform_bypass_flag:bool=False # u(1)
seq_scaling_matrix_present_flag:bool=False # u(1)
scaling_list: dict = field(default_factory=dict)
log2_max_frame_num_minus4:int=0 # ue(v)
pic_order_cnt_type:int=0 # ue(v)
log2_max_pic_order_cnt_lsb_minus4:int=0 # ue(v)
delta_pic_order_always_zero_flag:bool=False # ue(1)
offset_for_non_ref_pic:int=0 # se(v)
offset_for_top_to_bottom_field:int=0 # se(v)
num_ref_frames_in_pic_order_cnt_cycle:int=0 # ue(v)
offset_for_ref_frame:dict[int] = field(default_factory=dict)
max_num_ref_frames:int=9 # ue(v)
gaps_in_frame_num_value_allowed_flag:bool=False # u(1)
pic_width_in_mbs_minus1:int=0 # ue(v)
pic_height_in_map_units_minus1:int=0 # ue(v)
frame_mbs_only_flag:bool=False # u(1)
mb_adaptive_frame_field_flag:bool=False # u(1)
direct_8x8_inference_flag:bool=False # u(1)
frame_cropping_flag:bool=False # u(1)
frame_crop_left_offset:int=0 # ue(v)
frame_crop_right_offset:int=0 # ue(v)
frame_crop_top_offset:int=0 # ue(v)
frame_crop_bottom_offset:int=0 # ue(v)
vui_parameters_present_flag:bool=False # u(1)
vui:VUI=None # VUI object
def __init__(self):
self.scaling_list={}
self.offset_for_ref_frame={}
# TODO: ...
# Compute options to pass to ffmpeg so as to reproduce the same SPS.
# Very complex since some codec configuration are not provided by ffmpeg and/or libx264.
# This is only an attempt for now and it is almost impossible to mimic any profile without
# patching ffmpeg and/or libx264 to add the support for corner cases.
def ffmpeg_options(self, video_id=0):
x264opts = []
try:
profile = {0x42:'baseline', 0x4D:'main', 0x64:'high', 0x6E:'high10', 0x7A:'high422',
0xF4:'high444'}[self.profile_idc]
except KeyError:
logger.error('Unknow profile: %x', self.profile_idc)
return []
level = f'{floor(self.level_idc/10):d}.{self.level_idc % 10:d}'
x264opts.extend([f'sps-id={self.seq_parameter_set_id:d}'] )
if self.bit_depth_chroma_minus8 not in [0,1,2,4,6,8]:
logger.error('Bit depth of chrominance is not supported: %d',
self.bit_depth_chroma_minus8+8)
return []
if self.chroma_format_idc in range(4):
if self.chroma_format_idc == 0:
# Monochrome
pass
elif self.chroma_format_idc == 1:
# YUV:4:2:0
pass
elif self.chroma_format_idc == 2:
# YUV:4:2:2
pass
elif self.chroma_format_idc == 3:
# YUV:4:4:4
pass
else:
logger.error('Unknow chrominance format: %x', self.chroma_format_idc)
return []
res = [f'-profile:v:{video_id:d}', self.profile_idc, f'-level:v:{video_id:d}', level]
return res
def from_bytes(self, buf):
logger.debug('Parsing: %s', hexdump.dump(buf,sep=':'))
bit_position=0
# NAL Unit SPS
bit_position, zero = read_bit(buf, bit_position)
if zero != 0:
raise ValueError(f'Reserved bit is not equal to 0: {zero:d}')
bit_position, nal_ref_idc = read_bits(buf, bit_position,2)
if nal_ref_idc != 3:
raise ValueError(f'NAL ref idc is not equal to 3: {nal_ref_idc:d}')
bit_position, nal_unit_type = read_bits(buf, bit_position,5)
if nal_unit_type != 7:
raise ValueError(f'NAL unit type is not a SPS: {nal_unit_type:d}')
bit_position, self.profile_idc = read_byte(buf, bit_position)
bit_position, self.constraint_set0_flag = read_bit(buf,bit_position)
bit_position, self.constraint_set1_flag = read_bit(buf,bit_position)
bit_position, self.constraint_set2_flag = read_bit(buf,bit_position)
bit_position, self.constraint_set3_flag = read_bit(buf,bit_position)
bit_position, self.constraint_set4_flag = read_bit(buf,bit_position)
bit_position, self.constraint_set5_flag = read_bit(buf,bit_position)
bit_position, v = read_bits(buf, bit_position, 2)
if v!=0:
raise ValueError(f'Reserved bits different from 0b00: {v:x}')
bit_position, self.level_idc = read_byte(buf, bit_position)
bit_position, self.seq_parameter_set_id = read_unsigned_exp_golomb(buf, bit_position)
if self.profile_idc in [44, 83, 86, 100, 110, 118, 122, 128, 134, 135, 138, 139, 244]:
bit_position, self.chroma_format_idc = read_unsigned_exp_golomb(buf, bit_position)
if self.chroma_format_idc==3:
bit_position, self.separate_colour_plane_flag=read_bit(buf, bit_position)
bit_position, self.bit_depth_luma_minus8 = read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.bit_depth_chroma_minus8 = read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.qpprime_y_zero_transform_bypass_flag = read_boolean(buf,
bit_position)
bit_position, self.seq_scaling_matrix_present_flag = read_boolean(buf, bit_position)
if self.seq_scaling_matrix_present_flag:
nb_matrices = 12 if self.chroma_format_idc == 3 else 8
for i in range(nb_matrices):
bit_position, present = read_boolean(buf, bit_position)
if present:
if i<6:
bit_position, matrix = parse_scaling_list(buf, bit_position, 16)
self.scaling_list[i] = matrix
else:
bit_position, matrix = parse_scaling_list(buf, bit_position, 64)
self.scaling_list[i] = matrix
else:
self.scaling_list[i] = []
bit_position, self.log2_max_frame_num_minus4 = read_unsigned_exp_golomb(buf, bit_position)
bit_position , self.pic_order_cnt_type = read_unsigned_exp_golomb(buf, bit_position)
if self.pic_order_cnt_type == 0:
bit_position, self.log2_max_pic_order_cnt_lsb_minus4 =\
read_unsigned_exp_golomb(buf, bit_position)
elif self.pic_order_cnt_type == 1:
bit_position, self.delta_pic_order_always_zero_flag = read_boolean(buf, bit_position)
bit_position, self.offset_for_non_ref_pic = read_signed_exp_golomb(buf, bit_position)
bit_position, self.offset_for_top_to_bottom_field = read_signed_exp_golomb(buf,
bit_position)
bit_position, self.num_ref_frames_in_pic_order_cnt_cycle =\
read_unsigned_exp_golomb(buf, bit_position)
for i in range(self.num_ref_frames_in_pic_order_cnt_cycle):
bit_position, v = read_unsigned_exp_golomb(buf, bit_position)
self.offset_for_ref_frame[i]=v
bit_position, self.max_num_ref_frames = read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.gaps_in_frame_num_value_allowed_flag = read_boolean(buf, bit_position)
bit_position, self.pic_width_in_mbs_minus1 = read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.pic_height_in_map_units_minus1 = read_unsigned_exp_golomb(buf,
bit_position)
bit_position, self.frame_mbs_only_flag = read_boolean(buf, bit_position)
if not self.frame_mbs_only_flag:
bit_position, self.mb_adaptive_frame_field_flag = read_boolean(buf, bit_position)
bit_position, self.direct_8x8_inference_flag = read_boolean(buf, bit_position)
bit_position, self.frame_cropping_flag = read_boolean(buf, bit_position)
if self.frame_cropping_flag:
bit_position, self.frame_crop_left_offset = read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.frame_crop_right_offset = read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.frame_crop_top_offset = read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.frame_crop_bottom_offset = read_unsigned_exp_golomb(buf,
bit_position)
bit_position, self.vui_parameters_present_flag = read_boolean(buf, bit_position)
if self.vui_parameters_present_flag:
self.vui = VUI()
bit_position = self.vui.from_bytes(buf,bit_position)
logger.debug('VUI present: %s', self.vui)
logger.debug('Parse end of SPS. Bit position: %d. Remaining bytes: %s.', bit_position,
hexdump.dump(buf[floor(bit_position/8):], sep=':'))
bit_position = parse_rbsp_trailing_bits(buf, bit_position)
logger.debug('End of SPS: %d. Remaining bytes: %s', bit_position,
hexdump.dump(buf[floor(bit_position/8):], sep=':'))
return bit_position
def to_bytes(self):
buf = bytearray()
bit_position = 0
bit_position = write_bit(buf, bit_position,0)
bit_position = write_bits(buf, bit_position, 3, 2)
bit_position = write_bits(buf, bit_position, 7, 5)
bit_position = write_byte(buf, bit_position, self.profile_idc)
bit_position = write_bit(buf, bit_position, self.constraint_set0_flag)
bit_position = write_bit(buf, bit_position, self.constraint_set1_flag)
bit_position = write_bit(buf, bit_position, self.constraint_set2_flag)
bit_position = write_bit(buf, bit_position, self.constraint_set3_flag)
bit_position = write_bit(buf, bit_position, self.constraint_set4_flag)
bit_position = write_bit(buf, bit_position, self.constraint_set5_flag)
bit_position = write_bits(buf, bit_position, 0, 2)
bit_position = write_byte(buf, bit_position, self.level_idc)
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.seq_parameter_set_id)
if self.profile_idc in [44, 83, 86, 100, 110, 118, 122, 128, 134, 135, 138, 139, 244]:
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.chroma_format_idc)
if self.chroma_format_idc==3:
bit_position = write_bit(buf, bit_position, self.separate_colour_plane_flag)
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.bit_depth_luma_minus8)
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.bit_depth_chroma_minus8)
bit_position = write_boolean(buf, bit_position,
self.qpprime_y_zero_transform_bypass_flag)
bit_position = write_boolean(buf, bit_position, self.seq_scaling_matrix_present_flag)
if self.seq_scaling_matrix_present_flag:
nb_matrices = 12 if self.chroma_format_idc == 3 else 8
for i in range(nb_matrices):
matrix = self.scaling_list[i]
present = (len(matrix))!=0
bit_position = write_boolean(buf, bit_position, present)
if present:
if i<6:
bit_position = write_scaling_list(buf, bit_position, 16, matrix)
else:
bit_position = write_scaling_list(buf, bit_position, 64, matrix)
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.log2_max_frame_num_minus4)
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.pic_order_cnt_type)
if self.pic_order_cnt_type == 0:
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.log2_max_pic_order_cnt_lsb_minus4)
elif self.pic_order_cnt_type == 1:
bit_position = write_boolean(buf, bit_position, self.delta_pic_order_always_zero_flag)
bit_position = write_signed_exp_golomb(buf, bit_position, self.offset_for_non_ref_pic)
bit_position = write_signed_exp_golomb(buf, bit_position,
self.offset_for_top_to_bottom_field)
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.num_ref_frames_in_pic_order_cnt_cycle)
for i in range(self.num_ref_frames_in_pic_order_cnt_cycle):
v = self.offset_for_ref_frame[i]
bit_position = write_unsigned_exp_golomb(buf, bit_position, v)
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.max_num_ref_frames)
bit_position = write_boolean(buf, bit_position, self.gaps_in_frame_num_value_allowed_flag)
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.pic_width_in_mbs_minus1)
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.pic_height_in_map_units_minus1)
bit_position = write_boolean(buf, bit_position, self.frame_mbs_only_flag)
if not self.frame_mbs_only_flag:
bit_position = write_boolean(buf, bit_position, self.mb_adaptive_frame_field_flag)
bit_position = write_boolean(buf, bit_position, self.direct_8x8_inference_flag)
bit_position = write_boolean(buf, bit_position, self.frame_cropping_flag)
if self.frame_cropping_flag:
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.frame_crop_left_offset)
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.frame_crop_right_offset)
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.frame_crop_top_offset)
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.frame_crop_bottom_offset)
bit_position = write_boolean(buf, bit_position, self.vui_parameters_present_flag)
if self.vui_parameters_present_flag:
logger.debug('SPS has VUI. Writing VUI at position: %d', bit_position)
bit_position = self.vui.to_bytes(buf, bit_position)
logger.debug('VUI written. New bit position: %d', bit_position)
bit_position = write_rbsp_trailing_bits(buf, bit_position)
return buf
@dataclass
class PPS:
pic_parameter_set_id:int=0
seq_parameter_set_id:int=0
entropy_coding_mode_flag:bool=False
bottom_field_pic_order_in_frame_present_flag:bool=False
num_slice_groups_minus1:int=0
slice_group_map_type:int=0
run_length_minus1:dict = field(default_factory=dict)
top_left:dict = field(default_factory=dict)
bottom_right:dict = field(default_factory=dict)
slice_group_change_direction_flag:bool=False
slice_group_change_rate_minus1:int=0
pic_size_in_map_units_minus1:int=0
slice_group_id:dict = field(default_factory=dict)
num_ref_idx_l0_default_active_minus1:int=0
num_ref_idx_l2_default_active_minus1:int=0
weighted_pred_flag:bool=False
weighted_bipred_idc:int=0
pic_init_qp_minus26:int=0
pic_init_qs_minus26:int=0
chroma_qp_index_offset:int=0
deblocking_filter_control_present_flag:bool=False
constrained_intra_pred_flag:bool=False
redundant_pic_cnt_present_flag:bool=False
transform_8x8_mode_flag:bool=False
pic_scaling_matrix_present_flag:bool=False
pic_scaling_list:list[list[int]] = field(default_factory=list)
second_chroma_qp_index_offset:int=0
def __init__(self):
self.run_length_minus1={}
self.top_left={}
self.bottom_right={}
self.slice_group_id={}
self.pic_scaling_list=[]
# PPS are located at byte boundary
def from_bytes(self, buf, chroma_format_idc):
logger.debug('Parsing: %s', (hexdump.dump(buf,sep=':')))
bit_position=0
# NAL Unit PPS
bit_position, zero = read_bit(buf, bit_position)
if zero != 0:
raise ValueError(f'Reserved bit is not equal to 0: {zero:d}')
bit_position, nal_ref_idc = read_bits(buf, bit_position,2)
if nal_ref_idc != 3:
raise ValueError(f'NAL ref idc is not equal to 3: {nal_ref_idc:d}')
bit_position, nal_unit_type = read_bits(buf, bit_position,5)
if nal_unit_type != 8:
raise ValueError(f'NAL unit type is not a PPS: {nal_unit_type:d}')
bit_position, self.pic_parameter_set_id = read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.seq_parameter_set_id = read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.entropy_coding_mode_flag = read_boolean(buf, bit_position)
bit_position, self.bottom_field_pic_order_in_frame_present_flag =\
read_boolean(buf, bit_position)
bit_position, self.num_slice_groups_minus1 = read_unsigned_exp_golomb(buf, bit_position)
if self.num_slice_groups_minus1>0:
bit_position, self.slice_group_map_type = read_unsigned_exp_golomb(buf, bit_position)
if self.slice_group_map_type == 0:
for i in range(self.num_slice_groups_minus1):
bit_position, v = read_unsigned_exp_golomb(buf, bit_position)
self.run_length_minus1[i]=v
elif self.slice_group_map_type == 2:
for i in range(self.num_slice_groups_minus1):
bit_position, v = read_unsigned_exp_golomb(buf, bit_position)
self.top_left[i] = v
bit_position, v = read_unsigned_exp_golomb(buf, bit_position)
self.bottom_right[i] = v
elif self.slice_group_map_type in [3,4,5]:
bit_position, self.slice_group_change_direction_flag = read_boolean(buf,
bit_position)
bit_position, self.slice_group_change_rate_minus1 =\
read_unsigned_exp_golomb(buf, bit_position)
elif self.slice_group_map_type == 6:
bit_position, self.pic_size_in_map_units_minus1 =\
read_unsigned_exp_golomb(buf, bit_position)
nb_bits = ceil(log(self.num_slice_groups_minus1+1))
for i in range(self.pic_size_in_map_units_minus1):
bit_position, v = read_bits(buf, bit_position, nb_bits)
self.slice_group_id[i]=v
bit_position, self.num_ref_idx_l0_default_active_minus1 =\
read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.num_ref_idx_l2_default_active_minus1 =\
read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.weighted_pred_flag = read_boolean(buf, bit_position)
bit_position, self.weighted_bipred_idc = read_bits(buf, bit_position, 2)
bit_position, self.pic_init_qp_minus26 = read_signed_exp_golomb(buf, bit_position)
bit_position, self.pic_init_qs_minus26 = read_signed_exp_golomb(buf, bit_position)
bit_position, self.chroma_qp_index_offset = read_signed_exp_golomb(buf, bit_position)
bit_position, self.deblocking_filter_control_present_flag = read_boolean(buf, bit_position)
bit_position, self.constrained_intra_pred_flag = read_boolean(buf, bit_position)
bit_position, self.redundant_pic_cnt_present_flag = read_boolean(buf, bit_position)
if more_rbsp_data(buf, bit_position):
bit_position, self.transform_8x8_mode_flag = read_boolean(buf, bit_position)
bit_position, self.pic_scaling_matrix_present_flag = read_boolean(buf, bit_position)
if self.pic_scaling_matrix_present_flag:
nb_matrices = 6 if chroma_format_idc == 3 else 2
if self.transform_8x8_mode_flag:
nb_matrices+=6
else:
nb_matrices = 6
for i in range(nb_matrices):
bit_position, present = read_boolean(buf, bit_position)
if present:
if i<6:
bit_position, matrix = parse_scaling_list(buf, bit_position, 16)
self.pic_scaling_list.append(matrix)
else:
bit_position, matrix = parse_scaling_list(buf, bit_position, 64)
self.pic_scaling_list.append(matrix)
else:
self.pic_scaling_list.append([])
bit_position, self.second_chroma_qp_index_offset = read_signed_exp_golomb(buf,
bit_position)
logger.info("parse RBSP")
bit_position = parse_rbsp_trailing_bits(buf, bit_position)
return bit_position
def to_bytes(self, chroma_format_idc):
buf = bytearray()
bit_position = 0
# NAL Unit PPS
bit_position = write_bit(buf, bit_position, 0)
bit_position = write_bits(buf, bit_position, 3, 2)
bit_position = write_bits(buf, bit_position, 8, 5)
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.pic_parameter_set_id)
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.seq_parameter_set_id)
bit_position = write_boolean(buf, bit_position, self.entropy_coding_mode_flag)
bit_position = write_boolean(buf, bit_position,\
self.bottom_field_pic_order_in_frame_present_flag)
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.num_slice_groups_minus1)
if self.num_slice_groups_minus1>0:
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.slice_group_map_type)
if self.slice_group_map_type == 0:
for i in range(self.num_slice_groups_minus1):
v = self.run_length_minus1[i]
bit_position = write_unsigned_exp_golomb(buf, bit_position, v)
elif self.slice_group_map_type == 2:
for i in range(self.num_slice_groups_minus1):
v = self.top_left[i]
bit_position = write_unsigned_exp_golomb(buf, bit_position, v)
v = self.bottom_right[i]
bit_position = write_unsigned_exp_golomb(buf, bit_position, v)
elif self.slice_group_map_type in [3,4,5]:
bit_position = write_boolean(buf, bit_position,
self.slice_group_change_direction_flag)
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.slice_group_change_rate_minus1)
elif self.slice_group_map_type == 6:
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.pic_size_in_map_units_minus1)
nb_bits = ceil(log(self.num_slice_groups_minus1+1))
for i in range(self.pic_size_in_map_units_minus1):
v = self.slice_group_id[i]
bit_position, v = write_bits(buf, bit_position, v, nb_bits)
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.num_ref_idx_l0_default_active_minus1)
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.num_ref_idx_l2_default_active_minus1)
bit_position = write_boolean(buf, bit_position, self.weighted_pred_flag)
bit_position = write_bits(buf, bit_position, self.weighted_bipred_idc, 2)
bit_position = write_signed_exp_golomb(buf, bit_position, self.pic_init_qp_minus26)
bit_position = write_signed_exp_golomb(buf, bit_position, self.pic_init_qs_minus26)
bit_position = write_signed_exp_golomb(buf, bit_position, self.chroma_qp_index_offset)
bit_position = write_boolean(buf, bit_position, self.deblocking_filter_control_present_flag)
bit_position = write_boolean(buf, bit_position, self.constrained_intra_pred_flag)
bit_position = write_boolean(buf, bit_position, self.redundant_pic_cnt_present_flag)
bit_position = write_boolean(buf, bit_position, self.transform_8x8_mode_flag)
bit_position = write_boolean(buf, bit_position, self.pic_scaling_matrix_present_flag)
if self.pic_scaling_matrix_present_flag:
nb_matrices = 6 if chroma_format_idc == 3 else 2
if self.transform_8x8_mode_flag:
nb_matrices+=6
else:
nb_matrices = 6
for i in range(nb_matrices):
matrix = self.pic_scaling_list[i]
logger.info("Retrieved pic scaling matrix: %s %d", matrix, len(matrix))
present = len(matrix)!=0
logger.info("Matrix is present: %s", present)
bit_position = write_boolean(buf, bit_position, present)
if present:
if i<6:
logger.info("Writing matrix: %s", matrix)
bit_position = write_scaling_list(buf, bit_position, 16, matrix)
else:
logger.info("Writing matrix: %s", matrix)
bit_position = write_scaling_list(buf, bit_position, 64, matrix)
bit_position = write_signed_exp_golomb(buf, bit_position,
self.second_chroma_qp_index_offset)
bit_position = write_rbsp_trailing_bits(buf, bit_position)
return buf
-686
View File
@@ -470,692 +470,6 @@ def get_codec_private_data_from_mkv(mkvinfo_path:str,
@dataclass
class HRD:
cpb_cnt_minus1: int=0
bit_rate_scale: int=0
cpb_size_scale: int=0
bit_rate_value_minus1: dict = field(default_factory=dict)
cpb_size_value_minus1: dict = field(default_factory=dict)
cbr_flag: dict = field(default_factory=dict)
initial_cpb_removal_delay_length_minus1: int=0
cpb_removal_delay_length_minus1: int=0
dpb_output_delay_length_minus1: int=0
time_offset_length: int=0
def __init__(self):
self.bit_rate_value_minus1 = {}
self.cpb_size_value_minus1 = {}
self.cbr_flag = {}
def from_bytes(self, buf, bit_position):
bit_position, self.cpb_cnt_minus1 = read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.bit_rate_scale = read_bits(buf, bit_position, 4)
bit_position, self.cpb_size_scale = read_bits(buf, bit_position, 4)
for i in range(self.cpb_cnt_minus1+1):
bit_position, v = read_unsigned_exp_golomb(buf, bit_position)
self.bit_rate_value_minus1[i] = v
bit_position, v = read_unsigned_exp_golomb(buf, bit_position)
self.cpb_size_value_minus1[i] = v
bit_position, b = read_boolean(buf, bit_position)
self.cbr_flag[i] = b
bit_position, self.initial_cpb_removal_delay_length_minus1 = read_bits(buf, bit_position, 5)
bit_position, self.cpb_removal_delay_length_minus1 = read_bits(buf, bit_position, 5)
bit_position, self.dpb_output_delay_length_minus1 = read_bits(buf, bit_position, 5)
bit_position, self.time_offset_length = read_bits(buf, bit_position, 5)
return bit_position
def to_bytes(self, buf, bit_position):
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.cpb_cnt_minus1)
bit_position = write_bits(buf, bit_position, self.bit_rate_scale, 4)
bit_position = write_bits(buf, bit_position, self.cpb_size_scale, 4)
for i in range(self.cpb_cnt_minus1+1):
v = self.bit_rate_value_minus1[i]
bit_position = write_unsigned_exp_golomb(buf, bit_position, v)
v = self.cpb_size_value_minus1[i]
bit_position = write_unsigned_exp_golomb(buf, bit_position, v)
b = self.cbr_flag[i]
bit_position = write_boolean(buf, bit_position, b)
bit_position = write_bits(buf, bit_position, self.initial_cpb_removal_delay_length_minus1,
5)
bit_position = write_bits(buf, bit_position, self.cpb_removal_delay_length_minus1, 5)
bit_position = write_bits(buf, bit_position, self.dpb_output_delay_length_minus1, 5)
bit_position = write_bits(buf, bit_position, self.time_offset_length, 5)
return bit_position
@dataclass
class VUI:
aspect_ratio_info_present_flag:bool=False
aspect_ratio_idc:int=0
sar_width:int=0
sar_height:int=0
overscan_info_present_flag:bool=False
overscan_appropriate_flag:bool=False
video_signal_type_present_flag:bool=False
video_format:int=0
video_full_range_flag:bool=False
colour_description_present_flag:bool=False
colour_primaries:int=0
transfer_characteristics:int=0
matrix_coefficients:int=0
chroma_loc_info_present_flag:bool=False
chroma_sample_loc_type_top_field:int=0
chroma_sample_loc_type_bottom_field:int=0
timing_info_present_flag:bool=False
num_units_in_tick:int=0
time_scale:int=0
fixed_frame_rate_flag:bool=False
nal_hrd_parameters_present_flag:bool=False
hrd_parameters:HRD=None
vcl_hrd_parameters_present_flag:bool=False
vcl_hrd_parameters:HRD=None
low_delay_hrd_flag:bool=False
pic_struct_present_flag:bool=False
bitstream_restriction_flag:bool=False
motion_vectors_over_pic_boundaries_flag:bool=False
max_bytes_per_pic_denom:int=0
max_bits_per_mb_denom:int=0
log2_max_mv_length_horizontal:int=0
log2_max_mv_length_vertical:int=0
max_num_reorder_frames:int=0
max_dec_frame_buffering:int=0
# This structure is not guaranteed to be located at a byte boundary.
# We must explicitely indicate bit offset.
def from_bytes(self, buf, bit_position):
bit_position, self.aspect_ratio_info_present_flag = read_boolean(buf, bit_position)
if self.aspect_ratio_info_present_flag:
bit_position, self.aspect_ratio_idc = read_byte(buf, bit_position)
if self.aspect_ratio_idc == 255: # Extended_SAR
bit_position, self.sar_width = read_word(buf, bit_position)
bit_position, self.sar_height = read_word(buf, bit_position)
bit_position, self.overscan_info_present_flag = read_boolean(buf, bit_position)
if self.overscan_info_present_flag:
bit_position, self.overscan_appropriate_flag = read_boolean(buf, bit_position)
bit_position, self.video_signal_type_present_flag = read_boolean(buf, bit_position)
if self.video_signal_type_present_flag:
bit_position, self.video_format = read_bits(buf, bit_position, 3)
bit_position, self.video_full_range_flag = read_boolean(buf, bit_position)
bit_position, self.colour_description_present_flag = read_boolean(buf, bit_position)
if self.colour_description_present_flag:
bit_position, self.colour_primaries = read_byte(buf, bit_position)
bit_position, self.transfer_characteristics = read_byte(buf, bit_position)
bit_position, self.matrix_coefficients = read_byte(buf, bit_position)
bit_position, self.chroma_loc_info_present_flag = read_boolean(buf, bit_position)
if self.chroma_loc_info_present_flag:
bit_position, self.chroma_sample_loc_type_top_field =\
read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.chroma_sample_loc_type_bottom_field =\
read_unsigned_exp_golomb(buf,bit_position)
bit_position, self.timing_info_present_flag = read_boolean(buf, bit_position)
if self.timing_info_present_flag:
bit_position, self.num_units_in_tick = read_long(buf, bit_position)
bit_position, self.time_scale = read_long(buf, bit_position)
bit_position, self.fixed_frame_rate_flag = read_boolean(buf, bit_position)
bit_position, self.nal_hrd_parameters_present_flag = read_boolean(buf, bit_position)
if self.nal_hrd_parameters_present_flag:
hrd = HRD()
bit_position = hrd.from_bytes(buf, bit_position)
self.hrd_parameters = hrd
bit_position, self.vcl_hrd_parameters_present_flag = read_boolean(buf, bit_position)
if self.vcl_hrd_parameters_present_flag:
hrd = HRD()
bit_position = hrd.from_bytes(buf, bit_position)
self.vcl_hrd_parameters = hrd
if self.nal_hrd_parameters_present_flag or self.vcl_hrd_parameters_present_flag:
bit_position, self.low_delay_hrd_flag = read_boolean(buf, bit_position)
bit_position, self.pic_struct_present_flag = read_boolean(buf, bit_position)
bit_position, self.bitstream_restriction_flag = read_boolean(buf, bit_position)
if self.bitstream_restriction_flag:
bit_position, self.motion_vectors_over_pic_boundaries_flag =\
read_boolean(buf, bit_position)
bit_position, self.max_bytes_per_pic_denom = read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.max_bits_per_mb_denom = read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.log2_max_mv_length_horizontal = read_unsigned_exp_golomb(buf,
bit_position)
bit_position, self.log2_max_mv_length_vertical = read_unsigned_exp_golomb(buf,
bit_position)
bit_position, self.max_num_reorder_frames = read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.max_dec_frame_buffering = read_unsigned_exp_golomb(buf, bit_position)
return bit_position
def to_bytes(self, buf, bit_position):
bit_position = write_boolean(buf, bit_position, self.aspect_ratio_info_present_flag)
if self.aspect_ratio_info_present_flag:
bit_position = write_byte(buf, bit_position, self.aspect_ratio_idc)
if self.aspect_ratio_idc == 255: # Extended_SAR
bit_position = write_word(buf, bit_position, self.sar_width)
bit_position = write_word(buf, bit_position, self.sar_height)
bit_position = write_boolean(buf, bit_position, self.overscan_info_present_flag)
if self.overscan_info_present_flag:
bit_position = write_boolean(buf, bit_position, self.overscan_appropriate_flag)
bit_position = write_boolean(buf, bit_position, self.video_signal_type_present_flag)
if self.video_signal_type_present_flag:
bit_position = write_bits(buf, bit_position, self.video_format, 3)
bit_position = write_boolean(buf, bit_position, self.video_full_range_flag)
bit_position = write_boolean(buf, bit_position, self.colour_description_present_flag)
if self.colour_description_present_flag:
bit_position = write_byte(buf, bit_position, self.colour_primaries)
bit_position = write_byte(buf, bit_position, self.transfer_characteristics)
bit_position = write_byte(buf, bit_position, self.matrix_coefficients)
bit_position = write_boolean(buf, bit_position, self.chroma_loc_info_present_flag)
if self.chroma_loc_info_present_flag:
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.chroma_sample_loc_type_top_field)
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.chroma_sample_loc_type_bottom_field)
bit_position = write_boolean(buf, bit_position, self.timing_info_present_flag )
if self.timing_info_present_flag:
bit_position = write_long(buf, bit_position, self.num_units_in_tick )
bit_position = write_long(buf, bit_position, self.time_scale)
bit_position = write_boolean(buf, bit_position, self.fixed_frame_rate_flag)
bit_position = write_boolean(buf, bit_position, self.nal_hrd_parameters_present_flag)
if self.nal_hrd_parameters_present_flag:
bit_position = self.hrd_parameters.to_bytes(buf, bit_position)
bit_position = write_boolean(buf, bit_position, self.vcl_hrd_parameters_present_flag)
if self.vcl_hrd_parameters_present_flag:
bit_position = self.vcl_hrd_parameters.to_bytes(buf, bit_position)
if self.nal_hrd_parameters_present_flag or self.vcl_hrd_parameters_present_flag:
bit_position = write_boolean(buf, bit_position, self.low_delay_hrd_flag)
bit_position = write_boolean(buf, bit_position, self.pic_struct_present_flag)
bit_position = write_boolean(buf, bit_position, self.bitstream_restriction_flag)
if self.bitstream_restriction_flag:
bit_position = write_boolean(buf, bit_position,
self.motion_vectors_over_pic_boundaries_flag)
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.max_bytes_per_pic_denom)
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.max_bits_per_mb_denom)
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.log2_max_mv_length_horizontal)
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.log2_max_mv_length_vertical)
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.max_num_reorder_frames)
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.max_dec_frame_buffering)
return bit_position
@dataclass
class SPS:
profile_idc:int=0 # u(8)
constraint_set0_flag:bool=False # u(1)
constraint_set1_flag:bool=False # u(1)
constraint_set2_flag:bool=False # u(1)
constraint_set3_flag:bool=False # u(1)
constraint_set4_flag:bool=False # u(1)
constraint_set5_flag:bool=False # u(1)
level_idc:int=0 # u(8)
seq_parameter_set_id:int=0 # ue(v)
chroma_format_idc:int=0 # ue(v)
separate_colour_plane_flag:bool=False # u(1)
bit_depth_luma_minus8:int=0 # ue(v)
bit_depth_chroma_minus8:int=0 # ue(v)
qpprime_y_zero_transform_bypass_flag:bool=False # u(1)
seq_scaling_matrix_present_flag:bool=False # u(1)
scaling_list: dict = field(default_factory=dict)
log2_max_frame_num_minus4:int=0 # ue(v)
pic_order_cnt_type:int=0 # ue(v)
log2_max_pic_order_cnt_lsb_minus4:int=0 # ue(v)
delta_pic_order_always_zero_flag:bool=False # ue(1)
offset_for_non_ref_pic:int=0 # se(v)
offset_for_top_to_bottom_field:int=0 # se(v)
num_ref_frames_in_pic_order_cnt_cycle:int=0 # ue(v)
offset_for_ref_frame:dict[int] = field(default_factory=dict)
max_num_ref_frames:int=9 # ue(v)
gaps_in_frame_num_value_allowed_flag:bool=False # u(1)
pic_width_in_mbs_minus1:int=0 # ue(v)
pic_height_in_map_units_minus1:int=0 # ue(v)
frame_mbs_only_flag:bool=False # u(1)
mb_adaptive_frame_field_flag:bool=False # u(1)
direct_8x8_inference_flag:bool=False # u(1)
frame_cropping_flag:bool=False # u(1)
frame_crop_left_offset:int=0 # ue(v)
frame_crop_right_offset:int=0 # ue(v)
frame_crop_top_offset:int=0 # ue(v)
frame_crop_bottom_offset:int=0 # ue(v)
vui_parameters_present_flag:bool=False # u(1)
vui:VUI=None # VUI object
def __init__(self):
self.scaling_list={}
self.offset_for_ref_frame={}
# TODO: ...
# Compute options to pass to ffmpeg so as to reproduce the same SPS.
# Very complex since some codec configuration are not provided by ffmpeg and/or libx264.
# This is only an attempt for now and it is almost impossible to mimic any profile without
# patching ffmpeg and/or libx264 to add the support for corner cases.
def ffmpeg_options(self, video_id=0):
logger = logging.getLogger(__name__)
x264opts = []
try:
profile = {0x42:'baseline', 0x4D:'main', 0x64:'high', 0x6E:'high10', 0x7A:'high422',
0xF4:'high444'}[self.profile_idc]
except KeyError:
logger.error('Unknow profile: %x', self.profile_idc)
return []
level = f'{floor(self.level_idc/10):d}.{self.level_idc % 10:d}'
x264opts.extend([f'sps-id={self.seq_parameter_set_id:d}'] )
if self.bit_depth_chroma_minus8 not in [0,1,2,4,6,8]:
logger.error('Bit depth of chrominance is not supported: %d',
self.bit_depth_chroma_minus8+8)
return []
if self.chroma_format_idc in range(4):
if self.chroma_format_idc == 0:
# Monochrome
pass
elif self.chroma_format_idc == 1:
# YUV:4:2:0
pass
elif self.chroma_format_idc == 2:
# YUV:4:2:2
pass
elif self.chroma_format_idc == 3:
# YUV:4:4:4
pass
else:
logger.error('Unknow chrominance format: %x', self.chroma_format_idc)
return []
res = [f'-profile:v:{video_id:d}', self.profile_idc, f'-level:v:{video_id:d}', level]
return res
def from_bytes(self, buf):
logger = logging.getLogger(__name__)
logger.debug('Parsing: %s', hexdump.dump(buf,sep=':'))
bit_position=0
# NAL Unit SPS
bit_position, zero = read_bit(buf, bit_position)
if zero != 0:
raise ValueError(f'Reserved bit is not equal to 0: {zero:d}')
bit_position, nal_ref_idc = read_bits(buf, bit_position,2)
if nal_ref_idc != 3:
raise ValueError(f'NAL ref idc is not equal to 3: {nal_ref_idc:d}')
bit_position, nal_unit_type = read_bits(buf, bit_position,5)
if nal_unit_type != 7:
raise ValueError(f'NAL unit type is not a SPS: {nal_unit_type:d}')
bit_position, self.profile_idc = read_byte(buf, bit_position)
bit_position, self.constraint_set0_flag = read_bit(buf,bit_position)
bit_position, self.constraint_set1_flag = read_bit(buf,bit_position)
bit_position, self.constraint_set2_flag = read_bit(buf,bit_position)
bit_position, self.constraint_set3_flag = read_bit(buf,bit_position)
bit_position, self.constraint_set4_flag = read_bit(buf,bit_position)
bit_position, self.constraint_set5_flag = read_bit(buf,bit_position)
bit_position, v = read_bits(buf, bit_position, 2)
if v!=0:
raise ValueError(f'Reserved bits different from 0b00: {v:x}')
bit_position, self.level_idc = read_byte(buf, bit_position)
bit_position, self.seq_parameter_set_id = read_unsigned_exp_golomb(buf, bit_position)
if self.profile_idc in [44, 83, 86, 100, 110, 118, 122, 128, 134, 135, 138, 139, 244]:
bit_position, self.chroma_format_idc = read_unsigned_exp_golomb(buf, bit_position)
if self.chroma_format_idc==3:
bit_position, self.separate_colour_plane_flag=read_bit(buf, bit_position)
bit_position, self.bit_depth_luma_minus8 = read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.bit_depth_chroma_minus8 = read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.qpprime_y_zero_transform_bypass_flag = read_boolean(buf,
bit_position)
bit_position, self.seq_scaling_matrix_present_flag = read_boolean(buf, bit_position)
if self.seq_scaling_matrix_present_flag:
nb_matrices = 12 if self.chroma_format_idc == 3 else 8
for i in range(nb_matrices):
bit_position, present = read_boolean(buf, bit_position)
if present:
if i<6:
bit_position, matrix = parse_scaling_list(buf, bit_position, 16)
self.scaling_list[i] = matrix
else:
bit_position, matrix = parse_scaling_list(buf, bit_position, 64)
self.scaling_list[i] = matrix
else:
self.scaling_list[i] = []
bit_position, self.log2_max_frame_num_minus4 = read_unsigned_exp_golomb(buf, bit_position)
bit_position , self.pic_order_cnt_type = read_unsigned_exp_golomb(buf, bit_position)
if self.pic_order_cnt_type == 0:
bit_position, self.log2_max_pic_order_cnt_lsb_minus4 =\
read_unsigned_exp_golomb(buf, bit_position)
elif self.pic_order_cnt_type == 1:
bit_position, self.delta_pic_order_always_zero_flag = read_boolean(buf, bit_position)
bit_position, self.offset_for_non_ref_pic = read_signed_exp_golomb(buf, bit_position)
bit_position, self.offset_for_top_to_bottom_field = read_signed_exp_golomb(buf,
bit_position)
bit_position, self.num_ref_frames_in_pic_order_cnt_cycle =\
read_unsigned_exp_golomb(buf, bit_position)
for i in range(self.num_ref_frames_in_pic_order_cnt_cycle):
bit_position, v = read_unsigned_exp_golomb(buf, bit_position)
self.offset_for_ref_frame[i]=v
bit_position, self.max_num_ref_frames = read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.gaps_in_frame_num_value_allowed_flag = read_boolean(buf, bit_position)
bit_position, self.pic_width_in_mbs_minus1 = read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.pic_height_in_map_units_minus1 = read_unsigned_exp_golomb(buf,
bit_position)
bit_position, self.frame_mbs_only_flag = read_boolean(buf, bit_position)
if not self.frame_mbs_only_flag:
bit_position, self.mb_adaptive_frame_field_flag = read_boolean(buf, bit_position)
bit_position, self.direct_8x8_inference_flag = read_boolean(buf, bit_position)
bit_position, self.frame_cropping_flag = read_boolean(buf, bit_position)
if self.frame_cropping_flag:
bit_position, self.frame_crop_left_offset = read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.frame_crop_right_offset = read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.frame_crop_top_offset = read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.frame_crop_bottom_offset = read_unsigned_exp_golomb(buf,
bit_position)
bit_position, self.vui_parameters_present_flag = read_boolean(buf, bit_position)
if self.vui_parameters_present_flag:
self.vui = VUI()
bit_position = self.vui.from_bytes(buf,bit_position)
logger.debug('VUI present: %s', self.vui)
logger.debug('Parse end of SPS. Bit position: %d. Remaining bytes: %s.', bit_position,
hexdump.dump(buf[floor(bit_position/8):], sep=':'))
bit_position = parse_rbsp_trailing_bits(buf, bit_position)
logger.debug('End of SPS: %d. Remaining bytes: %s', bit_position,
hexdump.dump(buf[floor(bit_position/8):], sep=':'))
return bit_position
def to_bytes(self):
logger = logging.getLogger(__name__)
buf = bytearray()
bit_position = 0
bit_position = write_bit(buf, bit_position,0)
bit_position = write_bits(buf, bit_position, 3, 2)
bit_position = write_bits(buf, bit_position, 7, 5)
bit_position = write_byte(buf, bit_position, self.profile_idc)
bit_position = write_bit(buf, bit_position, self.constraint_set0_flag)
bit_position = write_bit(buf, bit_position, self.constraint_set1_flag)
bit_position = write_bit(buf, bit_position, self.constraint_set2_flag)
bit_position = write_bit(buf, bit_position, self.constraint_set3_flag)
bit_position = write_bit(buf, bit_position, self.constraint_set4_flag)
bit_position = write_bit(buf, bit_position, self.constraint_set5_flag)
bit_position = write_bits(buf, bit_position, 0, 2)
bit_position = write_byte(buf, bit_position, self.level_idc)
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.seq_parameter_set_id)
if self.profile_idc in [44, 83, 86, 100, 110, 118, 122, 128, 134, 135, 138, 139, 244]:
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.chroma_format_idc)
if self.chroma_format_idc==3:
bit_position = write_bit(buf, bit_position, self.separate_colour_plane_flag)
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.bit_depth_luma_minus8)
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.bit_depth_chroma_minus8)
bit_position = write_boolean(buf, bit_position,
self.qpprime_y_zero_transform_bypass_flag)
bit_position = write_boolean(buf, bit_position, self.seq_scaling_matrix_present_flag)
if self.seq_scaling_matrix_present_flag:
nb_matrices = 12 if self.chroma_format_idc == 3 else 8
for i in range(nb_matrices):
matrix = self.scaling_list[i]
present = (len(matrix))!=0
bit_position = write_boolean(buf, bit_position, present)
if present:
if i<6:
bit_position = write_scaling_list(buf, bit_position, 16, matrix)
else:
bit_position = write_scaling_list(buf, bit_position, 64, matrix)
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.log2_max_frame_num_minus4)
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.pic_order_cnt_type)
if self.pic_order_cnt_type == 0:
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.log2_max_pic_order_cnt_lsb_minus4)
elif self.pic_order_cnt_type == 1:
bit_position = write_boolean(buf, bit_position, self.delta_pic_order_always_zero_flag)
bit_position = write_signed_exp_golomb(buf, bit_position, self.offset_for_non_ref_pic)
bit_position = write_signed_exp_golomb(buf, bit_position,
self.offset_for_top_to_bottom_field)
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.num_ref_frames_in_pic_order_cnt_cycle)
for i in range(self.num_ref_frames_in_pic_order_cnt_cycle):
v = self.offset_for_ref_frame[i]
bit_position = write_unsigned_exp_golomb(buf, bit_position, v)
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.max_num_ref_frames)
bit_position = write_boolean(buf, bit_position, self.gaps_in_frame_num_value_allowed_flag)
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.pic_width_in_mbs_minus1)
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.pic_height_in_map_units_minus1)
bit_position = write_boolean(buf, bit_position, self.frame_mbs_only_flag)
if not self.frame_mbs_only_flag:
bit_position = write_boolean(buf, bit_position, self.mb_adaptive_frame_field_flag)
bit_position = write_boolean(buf, bit_position, self.direct_8x8_inference_flag)
bit_position = write_boolean(buf, bit_position, self.frame_cropping_flag)
if self.frame_cropping_flag:
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.frame_crop_left_offset)
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.frame_crop_right_offset)
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.frame_crop_top_offset)
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.frame_crop_bottom_offset)
bit_position = write_boolean(buf, bit_position, self.vui_parameters_present_flag)
if self.vui_parameters_present_flag:
logger.debug('SPS has VUI. Writing VUI at position: %d', bit_position)
bit_position = self.vui.to_bytes(buf, bit_position)
logger.debug('VUI written. New bit position: %d', bit_position)
bit_position = write_rbsp_trailing_bits(buf, bit_position)
return buf
@dataclass
class PPS:
pic_parameter_set_id:int=0
seq_parameter_set_id:int=0
entropy_coding_mode_flag:bool=False
bottom_field_pic_order_in_frame_present_flag:bool=False
num_slice_groups_minus1:int=0
slice_group_map_type:int=0
run_length_minus1:dict = field(default_factory=dict)
top_left:dict = field(default_factory=dict)
bottom_right:dict = field(default_factory=dict)
slice_group_change_direction_flag:bool=False
slice_group_change_rate_minus1:int=0
pic_size_in_map_units_minus1:int=0
slice_group_id:dict = field(default_factory=dict)
num_ref_idx_l0_default_active_minus1:int=0
num_ref_idx_l2_default_active_minus1:int=0
weighted_pred_flag:bool=False
weighted_bipred_idc:int=0
pic_init_qp_minus26:int=0
pic_init_qs_minus26:int=0
chroma_qp_index_offset:int=0
deblocking_filter_control_present_flag:bool=False
constrained_intra_pred_flag:bool=False
redundant_pic_cnt_present_flag:bool=False
transform_8x8_mode_flag:bool=False
pic_scaling_matrix_present_flag:bool=False
pic_scaling_list:list[list[int]] = field(default_factory=list)
second_chroma_qp_index_offset:int=0
def __init__(self):
self.run_length_minus1={}
self.top_left={}
self.bottom_right={}
self.slice_group_id={}
self.pic_scaling_list=[]
# PPS are located at byte boundary
def from_bytes(self, buf, chroma_format_idc):
logger = logging.getLogger(__name__)
logger.debug('Parsing: %s', (hexdump.dump(buf,sep=':')))
bit_position=0
# NAL Unit PPS
bit_position, zero = read_bit(buf, bit_position)
if zero != 0:
raise ValueError(f'Reserved bit is not equal to 0: {zero:d}')
bit_position, nal_ref_idc = read_bits(buf, bit_position,2)
if nal_ref_idc != 3:
raise ValueError(f'NAL ref idc is not equal to 3: {nal_ref_idc:d}')
bit_position, nal_unit_type = read_bits(buf, bit_position,5)
if nal_unit_type != 8:
raise ValueError(f'NAL unit type is not a PPS: {nal_unit_type:d}')
bit_position, self.pic_parameter_set_id = read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.seq_parameter_set_id = read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.entropy_coding_mode_flag = read_boolean(buf, bit_position)
bit_position, self.bottom_field_pic_order_in_frame_present_flag =\
read_boolean(buf, bit_position)
bit_position, self.num_slice_groups_minus1 = read_unsigned_exp_golomb(buf, bit_position)
if self.num_slice_groups_minus1>0:
bit_position, self.slice_group_map_type = read_unsigned_exp_golomb(buf, bit_position)
if self.slice_group_map_type == 0:
for i in range(self.num_slice_groups_minus1):
bit_position, v = read_unsigned_exp_golomb(buf, bit_position)
self.run_length_minus1[i]=v
elif self.slice_group_map_type == 2:
for i in range(self.num_slice_groups_minus1):
bit_position, v = read_unsigned_exp_golomb(buf, bit_position)
self.top_left[i] = v
bit_position, v = read_unsigned_exp_golomb(buf, bit_position)
self.bottom_right[i] = v
elif self.slice_group_map_type in [3,4,5]:
bit_position, self.slice_group_change_direction_flag = read_boolean(buf,
bit_position)
bit_position, self.slice_group_change_rate_minus1 =\
read_unsigned_exp_golomb(buf, bit_position)
elif self.slice_group_map_type == 6:
bit_position, self.pic_size_in_map_units_minus1 =\
read_unsigned_exp_golomb(buf, bit_position)
nb_bits = ceil(log(self.num_slice_groups_minus1+1))
for i in range(self.pic_size_in_map_units_minus1):
bit_position, v = read_bits(buf, bit_position, nb_bits)
self.slice_group_id[i]=v
bit_position, self.num_ref_idx_l0_default_active_minus1 =\
read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.num_ref_idx_l2_default_active_minus1 =\
read_unsigned_exp_golomb(buf, bit_position)
bit_position, self.weighted_pred_flag = read_boolean(buf, bit_position)
bit_position, self.weighted_bipred_idc = read_bits(buf, bit_position, 2)
bit_position, self.pic_init_qp_minus26 = read_signed_exp_golomb(buf, bit_position)
bit_position, self.pic_init_qs_minus26 = read_signed_exp_golomb(buf, bit_position)
bit_position, self.chroma_qp_index_offset = read_signed_exp_golomb(buf, bit_position)
bit_position, self.deblocking_filter_control_present_flag = read_boolean(buf, bit_position)
bit_position, self.constrained_intra_pred_flag = read_boolean(buf, bit_position)
bit_position, self.redundant_pic_cnt_present_flag = read_boolean(buf, bit_position)
if more_rbsp_data(buf, bit_position):
bit_position, self.transform_8x8_mode_flag = read_boolean(buf, bit_position)
bit_position, self.pic_scaling_matrix_present_flag = read_boolean(buf, bit_position)
if self.pic_scaling_matrix_present_flag:
nb_matrices = 6 if chroma_format_idc == 3 else 2
if self.transform_8x8_mode_flag:
nb_matrices+=6
else:
nb_matrices = 6
for i in range(nb_matrices):
bit_position, present = read_boolean(buf, bit_position)
if present:
if i<6:
bit_position, matrix = parse_scaling_list(buf, bit_position, 16)
self.pic_scaling_list.append(matrix)
else:
bit_position, matrix = parse_scaling_list(buf, bit_position, 64)
self.pic_scaling_list.append(matrix)
else:
self.pic_scaling_list.append([])
bit_position, self.second_chroma_qp_index_offset = read_signed_exp_golomb(buf,
bit_position)
logger.info("parse RBSP")
bit_position = parse_rbsp_trailing_bits(buf, bit_position)
return bit_position
def to_bytes(self, chroma_format_idc):
logger = logging.getLogger(__name__)
buf = bytearray()
bit_position = 0
# NAL Unit PPS
bit_position = write_bit(buf, bit_position, 0)
bit_position = write_bits(buf, bit_position, 3, 2)
bit_position = write_bits(buf, bit_position, 8, 5)
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.pic_parameter_set_id)
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.seq_parameter_set_id)
bit_position = write_boolean(buf, bit_position, self.entropy_coding_mode_flag)
bit_position = write_boolean(buf, bit_position,\
self.bottom_field_pic_order_in_frame_present_flag)
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.num_slice_groups_minus1)
if self.num_slice_groups_minus1>0:
bit_position = write_unsigned_exp_golomb(buf, bit_position, self.slice_group_map_type)
if self.slice_group_map_type == 0:
for i in range(self.num_slice_groups_minus1):
v = self.run_length_minus1[i]
bit_position = write_unsigned_exp_golomb(buf, bit_position, v)
elif self.slice_group_map_type == 2:
for i in range(self.num_slice_groups_minus1):
v = self.top_left[i]
bit_position = write_unsigned_exp_golomb(buf, bit_position, v)
v = self.bottom_right[i]
bit_position = write_unsigned_exp_golomb(buf, bit_position, v)
elif self.slice_group_map_type in [3,4,5]:
bit_position = write_boolean(buf, bit_position,
self.slice_group_change_direction_flag)
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.slice_group_change_rate_minus1)
elif self.slice_group_map_type == 6:
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.pic_size_in_map_units_minus1)
nb_bits = ceil(log(self.num_slice_groups_minus1+1))
for i in range(self.pic_size_in_map_units_minus1):
v = self.slice_group_id[i]
bit_position, v = write_bits(buf, bit_position, v, nb_bits)
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.num_ref_idx_l0_default_active_minus1)
bit_position = write_unsigned_exp_golomb(buf, bit_position,
self.num_ref_idx_l2_default_active_minus1)
bit_position = write_boolean(buf, bit_position, self.weighted_pred_flag)
bit_position = write_bits(buf, bit_position, self.weighted_bipred_idc, 2)
bit_position = write_signed_exp_golomb(buf, bit_position, self.pic_init_qp_minus26)
bit_position = write_signed_exp_golomb(buf, bit_position, self.pic_init_qs_minus26)
bit_position = write_signed_exp_golomb(buf, bit_position, self.chroma_qp_index_offset)
bit_position = write_boolean(buf, bit_position, self.deblocking_filter_control_present_flag)
bit_position = write_boolean(buf, bit_position, self.constrained_intra_pred_flag)
bit_position = write_boolean(buf, bit_position, self.redundant_pic_cnt_present_flag)
bit_position = write_boolean(buf, bit_position, self.transform_8x8_mode_flag)
bit_position = write_boolean(buf, bit_position, self.pic_scaling_matrix_present_flag)
if self.pic_scaling_matrix_present_flag:
nb_matrices = 6 if chroma_format_idc == 3 else 2
if self.transform_8x8_mode_flag:
nb_matrices+=6
else:
nb_matrices = 6
for i in range(nb_matrices):
matrix = self.pic_scaling_list[i]
logger.info("Retrieved pic scaling matrix: %s %d", matrix, len(matrix))
present = len(matrix)!=0
logger.info("Matrix is present: %s", present)
bit_position = write_boolean(buf, bit_position, present)
if present:
if i<6:
logger.info("Writing matrix: %s", matrix)
bit_position = write_scaling_list(buf, bit_position, 16, matrix)
else:
logger.info("Writing matrix: %s", matrix)
bit_position = write_scaling_list(buf, bit_position, 64, matrix)
bit_position = write_signed_exp_golomb(buf, bit_position,
self.second_chroma_qp_index_offset)
bit_position = write_rbsp_trailing_bits(buf, bit_position)
return buf
@dataclass
class AVCDecoderConfiguration: