diff --git a/src/tscut/h264/parameters.py b/src/tscut/h264/parameters.py new file mode 100644 index 0000000..4e918a5 --- /dev/null +++ b/src/tscut/h264/parameters.py @@ -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 diff --git a/src/tscut/tscut.py b/src/tscut/tscut.py index 13d0bfd..39e1a6e 100755 --- a/src/tscut/tscut.py +++ b/src/tscut/tscut.py @@ -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: