def test_byte_for_byte_identical(self): sh1 = serialise_to_bytes( bitstream.SequenceHeader( parse_parameters=bitstream.ParseParameters( major_version=1, profile=tables.Profiles.low_delay, ), )) sh2 = serialise_to_bytes( bitstream.SequenceHeader( parse_parameters=bitstream.ParseParameters(major_version=2), )) state = bytes_to_state(sh1 + sh1 + sh2) decoder.sequence_header(state) decoder.byte_align(state) decoder.sequence_header(state) decoder.byte_align(state) with pytest.raises( decoder.SequenceHeaderChangedMidSequence) as exc_info: decoder.sequence_header(state) assert exc_info.value.last_sequence_header_offset == len(sh1) assert exc_info.value.last_sequence_header_bytes == sh1 assert exc_info.value.this_sequence_header_offset == len(sh1) * 2 assert exc_info.value.this_sequence_header_bytes == sh2
def test_level_must_be_valid(self): state = bytes_to_state( serialise_to_bytes( bitstream.ParseParameters(level=tables.Levels.unconstrained))) decoder.parse_parameters(state) state = bytes_to_state( serialise_to_bytes(bitstream.ParseParameters(level=9999))) with pytest.raises(decoder.BadLevel) as exc_info: decoder.parse_parameters(state) assert exc_info.value.level == 9999
def test_profile_must_be_valid(self): state = bytes_to_state( serialise_to_bytes( bitstream.ParseParameters( profile=tables.Profiles.high_quality))) decoder.parse_parameters(state) state = bytes_to_state( serialise_to_bytes(bitstream.ParseParameters(profile=9999))) with pytest.raises(decoder.BadProfile) as exc_info: decoder.parse_parameters(state) assert exc_info.value.profile == 9999
def test_profile_version_restriction(self): # A sequence with no pictures but (incorrectly) major_version 1 and # profile high quality. seq = bitstream.Sequence(data_units=[ bitstream.DataUnit( parse_info=bitstream.ParseInfo( parse_code=tables.ParseCodes.sequence_header, ), sequence_header=bitstream.SequenceHeader( parse_parameters=bitstream.ParseParameters( major_version=1, profile=tables.Profiles.high_quality, ), video_parameters=bitstream.SourceParameters( frame_size=bitstream.FrameSize( # Don't waste time on full-sized frames custom_dimensions_flag=True, frame_width=4, frame_height=4, ), clean_area=bitstream.CleanArea( custom_clean_area_flag=True, clean_width=4, clean_height=4, ), ), ), ), bitstream.DataUnit(parse_info=bitstream.ParseInfo( parse_code=tables.ParseCodes.end_of_sequence, ), ), ]) populate_parse_offsets(seq) state = bytes_to_state(serialise_to_bytes(seq)) with pytest.raises(decoder.ProfileNotSupportedByVersion): decoder.parse_stream(state)
def test_high_quality_profile_requires_version_two(self): state = bytes_to_state( serialise_to_bytes( bitstream.ParseParameters( level=0, profile=tables.Profiles.high_quality, major_version=1, minor_version=0, ))) with pytest.raises(decoder.ProfileNotSupportedByVersion): decoder.parse_parameters(state)
def test_minor_version_must_be_zero(self): state = bytes_to_state( serialise_to_bytes( bitstream.ParseParameters( level=0, profile=0, major_version=3, minor_version=1, ))) with pytest.raises(decoder.MinorVersionNotZero): decoder.parse_parameters(state)
def test_major_version_must_be_at_least_one(self): state = bytes_to_state( serialise_to_bytes( bitstream.ParseParameters( level=0, profile=0, major_version=0, minor_version=0, ))) with pytest.raises(decoder.MajorVersionTooLow): decoder.parse_parameters(state)
def test_parse_code_version_restriction(self): # A sequence with 1 HQ picture fragment but (incorrectly) major_version 2 seq = bitstream.Sequence(data_units=[ bitstream.DataUnit( parse_info=bitstream.ParseInfo( parse_code=tables.ParseCodes.sequence_header, ), sequence_header=bitstream.SequenceHeader( parse_parameters=bitstream.ParseParameters(major_version=2, ), video_parameters=bitstream.SourceParameters( frame_size=bitstream.FrameSize( # Don't waste time on full-sized frames custom_dimensions_flag=True, frame_width=4, frame_height=4, ), clean_area=bitstream.CleanArea( custom_clean_area_flag=True, clean_width=4, clean_height=4, ), ), ), ), bitstream.DataUnit( parse_info=bitstream.ParseInfo( parse_code=tables.ParseCodes.high_quality_picture_fragment, ), fragment_parse=bitstream.FragmentParse( fragment_header=bitstream.FragmentHeader( fragment_slice_count=0, ), transform_parameters=bitstream.TransformParameters( slice_parameters=bitstream.SliceParameters( slices_x=1, slices_y=1, ), ), ), ), bitstream.DataUnit( parse_info=bitstream.ParseInfo( parse_code=tables.ParseCodes.high_quality_picture_fragment, ), fragment_parse=bitstream.FragmentParse( fragment_header=bitstream.FragmentHeader( fragment_slice_count=1, ), ), ), bitstream.DataUnit(parse_info=bitstream.ParseInfo( parse_code=tables.ParseCodes.end_of_sequence, ), ), ]) populate_parse_offsets(seq) state = bytes_to_state(serialise_to_bytes(seq)) with pytest.raises(decoder.ParseCodeNotSupportedByVersion): decoder.parse_stream(state)
def test_minimal_major_version_requirement( self, num_pictures, major_version, expected_major_version, exp_fail, ): # A sequence with 1 (or zero) HQ pictures seq = bitstream.Sequence(data_units=[ bitstream.DataUnit( parse_info=bitstream.ParseInfo( parse_code=tables.ParseCodes.sequence_header, ), sequence_header=bitstream.SequenceHeader( parse_parameters=bitstream.ParseParameters( major_version=major_version, ), video_parameters=bitstream.SourceParameters( frame_size=bitstream.FrameSize( # Don't waste time on full-sized frames custom_dimensions_flag=True, frame_width=4, frame_height=4, ), clean_area=bitstream.CleanArea( custom_clean_area_flag=True, clean_width=4, clean_height=4, ), ), ), ), ] + [ bitstream.DataUnit( parse_info=bitstream.ParseInfo( parse_code=tables.ParseCodes.high_quality_picture, ), picture_parse=bitstream.PictureParse( picture_header=bitstream.PictureHeader(picture_number=n, ), ), ) for n in range(num_pictures) ] + [ bitstream.DataUnit(parse_info=bitstream.ParseInfo( parse_code=tables.ParseCodes.end_of_sequence, ), ), ]) populate_parse_offsets(seq) state = bytes_to_state(serialise_to_bytes(seq)) if exp_fail: with pytest.raises(decoder.MajorVersionTooHigh): decoder.parse_stream(state) else: decoder.parse_stream(state) assert state["_expected_major_version"] == expected_major_version
def test_odd_number_of_fields_disallowed(self, picture_coding_mode, num_pictures, exp_fail): # A sequence with num_pictures HQ pictures seq = bitstream.Sequence(data_units=[ bitstream.DataUnit( parse_info=bitstream.ParseInfo( parse_code=tables.ParseCodes.sequence_header, ), sequence_header=bitstream.SequenceHeader( picture_coding_mode=picture_coding_mode, parse_parameters=bitstream.ParseParameters( major_version=2), video_parameters=bitstream.SourceParameters( frame_size=bitstream.FrameSize( # Don't waste time on full-sized frames custom_dimensions_flag=True, frame_width=4, frame_height=4, ), clean_area=bitstream.CleanArea( custom_clean_area_flag=True, clean_width=4, clean_height=4, ), ), ), ), ] + [ bitstream.DataUnit( parse_info=bitstream.ParseInfo( parse_code=tables.ParseCodes.high_quality_picture, ), picture_parse=bitstream.PictureParse( picture_header=bitstream.PictureHeader(picture_number=n, ), ), ) for n in range(num_pictures) ] + [ bitstream.DataUnit(parse_info=bitstream.ParseInfo( parse_code=tables.ParseCodes.end_of_sequence, ), ), ]) populate_parse_offsets(seq) state = bytes_to_state(serialise_to_bytes(seq)) if exp_fail: with pytest.raises( decoder.OddNumberOfFieldsInSequence) as exc_info: decoder.parse_stream(state) assert exc_info.value.num_fields_in_sequence == num_pictures else: decoder.parse_stream(state)
def test_constraints(self): state = bytes_to_state( serialise_to_bytes( bitstream.ParseParameters( level=0, profile=0, major_version=3, minor_version=0, ))) decoder.parse_parameters(state) assert state["_level_constrained_values"] == { "level": 0, "profile": 0, "major_version": 3, "minor_version": 0, }
def test_level_sequence_matcher(self): # This level requires alternating HQ pictures and sequence headers. state = bytes_to_state( serialise_to_bytes( bitstream.ParseParameters( level=tables.Levels.uhd_over_hd_sdi, profile=tables.Profiles.high_quality, major_version=3, minor_version=0, ))) decoder.parse_parameters(state) # The matcher should start *after* the sequence header this # parse_parameters was in assert state["_level_sequence_matcher"].match_symbol( "high_quality_picture") assert state["_level_sequence_matcher"].match_symbol("sequence_header") assert not state["_level_sequence_matcher"].match_symbol( "sequence_header")
def valid_bitstream(self, filename): """ Creates a valid bitstream in the file whose name is returned by this fixture. This bitstream consists of two small pictures. A minimal bitstream with a pair of 4x4 pixel 4:2:0 8-bit pictures with picture numbers 100 and 101. """ with open(filename, "wb") as f: w = bitstream.BitstreamWriter(f) seq = bitstream.Sequence( data_units=[ bitstream.DataUnit( parse_info=bitstream.ParseInfo( parse_code=tables.ParseCodes.sequence_header, next_parse_offset=19, ), sequence_header=bitstream.SequenceHeader( parse_parameters=bitstream.ParseParameters( major_version=2, ), video_parameters=bitstream.SourceParameters( frame_size=bitstream.FrameSize( custom_dimensions_flag=True, frame_width=4, frame_height=4, ), clean_area=bitstream.CleanArea( custom_clean_area_flag=True, left_offset=0, top_offset=0, clean_width=4, clean_height=4, ), color_diff_sampling_format=bitstream.ColorDiffSamplingFormat( # noqa: E501 custom_color_diff_format_flag=True, color_diff_format_index=tables.ColorDifferenceSamplingFormats.color_4_2_0, # noqa: E501 ), ), picture_coding_mode=tables.PictureCodingModes.pictures_are_frames, # noqa: E501 ), ), bitstream.DataUnit( parse_info=bitstream.ParseInfo( parse_code=tables.ParseCodes.high_quality_picture, next_parse_offset=24, previous_parse_offset=19, ), picture_parse=bitstream.PictureParse( picture_header=bitstream.PictureHeader( picture_number=100, ), ), ), bitstream.DataUnit( parse_info=bitstream.ParseInfo( parse_code=tables.ParseCodes.high_quality_picture, next_parse_offset=24, previous_parse_offset=24, ), picture_parse=bitstream.PictureParse( picture_header=bitstream.PictureHeader( picture_number=101, ), ), ), bitstream.DataUnit( parse_info=bitstream.ParseInfo( parse_code=tables.ParseCodes.end_of_sequence, previous_parse_offset=24, ) ), ] ) with bitstream.Serialiser(w, seq, bitstream.vc2_default_values) as ser: bitstream.parse_sequence(ser, State()) w.flush() return filename
def test_level_constraints(): # Simply check that all constrainted level values get asserted. We check # this by making sure assert_level_constraint has added the relevant values # to # state["_level_constrained_values"]. state = bytes_to_state( serialise_to_bytes( bitstream.SequenceHeader( base_video_format=tables.BaseVideoFormats.custom_format, picture_coding_mode=tables.PictureCodingModes. pictures_are_frames, parse_parameters=bitstream.ParseParameters( level=tables.Levels.unconstrained, profile=tables.Profiles.high_quality, major_version=3, minor_version=0, ), video_parameters=bitstream.SourceParameters( frame_size=bitstream.FrameSize( custom_dimensions_flag=True, frame_width=1280, frame_height=1080, ), color_diff_sampling_format=bitstream. ColorDiffSamplingFormat( custom_color_diff_format_flag=True, color_diff_format_index=tables. ColorDifferenceSamplingFormats.color_4_4_4, ), scan_format=bitstream.ScanFormat( custom_scan_format_flag=True, source_sampling=tables.SourceSamplingModes.progressive, ), frame_rate=bitstream.FrameRate( custom_frame_rate_flag=True, index=0, frame_rate_numer=25, frame_rate_denom=1, ), pixel_aspect_ratio=bitstream.PixelAspectRatio( custom_pixel_aspect_ratio_flag=True, index=0, pixel_aspect_ratio_numer=1, pixel_aspect_ratio_denom=1, ), clean_area=bitstream.CleanArea( custom_clean_area_flag=True, clean_width=1, clean_height=2, left_offset=3, top_offset=4, ), signal_range=bitstream.SignalRange( custom_signal_range_flag=True, index=0, luma_offset=1, luma_excursion=2, color_diff_offset=3, color_diff_excursion=4, ), color_spec=bitstream.ColorSpec( custom_color_spec_flag=True, index=0, color_primaries=bitstream.ColorPrimaries( custom_color_primaries_flag=True, index=tables.PresetColorPrimaries.uhdtv, ), color_matrix=bitstream.ColorMatrix( custom_color_matrix_flag=True, index=tables.PresetColorMatrices.uhdtv, ), transfer_function=bitstream.TransferFunction( custom_transfer_function_flag=True, index=tables.PresetTransferFunctions. hybrid_log_gamma, ), ), ), ))) decoder.sequence_header(state) assert state["_level_constrained_values"] == { # sequence_header "base_video_format": tables.BaseVideoFormats.custom_format, "picture_coding_mode": tables.PictureCodingModes.pictures_are_frames, # parse_parameters "level": tables.Levels.unconstrained, "profile": tables.Profiles.high_quality, "major_version": 3, "minor_version": 0, # frame_size "custom_dimensions_flag": True, "frame_width": 1280, "frame_height": 1080, # color_diff_sampling_format "custom_color_diff_format_flag": True, "color_diff_format_index": tables.ColorDifferenceSamplingFormats.color_4_4_4, # scan_format "custom_scan_format_flag": True, "source_sampling": tables.SourceSamplingModes.progressive, # frame_rate "custom_frame_rate_flag": True, "frame_rate_index": 0, "frame_rate_numer": 25, "frame_rate_denom": 1, # pixel_aspect_ratio "custom_pixel_aspect_ratio_flag": True, "pixel_aspect_ratio_index": 0, "pixel_aspect_ratio_numer": 1, "pixel_aspect_ratio_denom": 1, # clean_area "custom_clean_area_flag": True, "clean_width": 1, "clean_height": 2, "left_offset": 3, "top_offset": 4, # signal_range "custom_signal_range_flag": True, "custom_signal_range_index": 0, "luma_offset": 1, "luma_excursion": 2, "color_diff_offset": 3, "color_diff_excursion": 4, # color_spec "custom_color_spec_flag": True, "color_spec_index": 0, # color_primaries "custom_color_primaries_flag": True, "color_primaries_index": tables.PresetColorPrimaries.uhdtv, # color_matrix "custom_color_matrix_flag": True, "color_matrix_index": tables.PresetColorMatrices.uhdtv, # transfer_function "custom_transfer_function_flag": True, "transfer_function_index": tables.PresetTransferFunctions.hybrid_log_gamma, }
def test_output_picture(self): # This test adds a callback for output_picture and makes sure that both # fragments and pictures call it correctly (and that sanity-checks very # loosely that decoding etc. is happening). Finally, it also checks # that two concatenated sequences are read one after another. # A sequence with a HQ picture followed by a HQ fragment (both all # zeros) seq1 = bitstream.Sequence(data_units=[ bitstream.DataUnit( parse_info=bitstream.ParseInfo( parse_code=tables.ParseCodes.sequence_header, ), sequence_header=bitstream.SequenceHeader( video_parameters=bitstream.SourceParameters( frame_size=bitstream.FrameSize( # Don't waste time on full-sized frames custom_dimensions_flag=True, frame_width=4, frame_height=2, ), clean_area=bitstream.CleanArea( custom_clean_area_flag=True, clean_width=4, clean_height=2, ), color_diff_sampling_format=bitstream. ColorDiffSamplingFormat( custom_color_diff_format_flag=True, color_diff_format_index=tables. ColorDifferenceSamplingFormats. color_4_2_2, # noqa: E501 ), # Output values will be treated as 8-bit (and thus all # decode to 128) signal_range=bitstream.SignalRange( custom_signal_range_flag=True, index=tables.PresetSignalRanges. video_8bit_full_range, ), ), picture_coding_mode=tables.PictureCodingModes. pictures_are_frames, ), ), # A HQ picture bitstream.DataUnit( parse_info=bitstream.ParseInfo( parse_code=tables.ParseCodes.high_quality_picture, ), picture_parse=bitstream.PictureParse( picture_header=bitstream.PictureHeader(picture_number=10, ), ), ), # A fragmented HQ picture (sent over two fragments to ensure the # callback only fires after a whole picture arrives) bitstream.DataUnit( parse_info=bitstream.ParseInfo( parse_code=tables.ParseCodes.high_quality_picture_fragment, ), fragment_parse=bitstream.FragmentParse( fragment_header=bitstream.FragmentHeader( picture_number=11, fragment_slice_count=0, ), transform_parameters=bitstream.TransformParameters( slice_parameters=bitstream.SliceParameters( slices_x=2, slices_y=1, ), ), ), ), bitstream.DataUnit( parse_info=bitstream.ParseInfo( parse_code=tables.ParseCodes.high_quality_picture_fragment, ), fragment_parse=bitstream.FragmentParse( fragment_header=bitstream.FragmentHeader( picture_number=11, fragment_slice_count=1, fragment_x_offset=0, fragment_y_offset=0, ), ), ), bitstream.DataUnit( parse_info=bitstream.ParseInfo( parse_code=tables.ParseCodes.high_quality_picture_fragment, ), fragment_parse=bitstream.FragmentParse( fragment_header=bitstream.FragmentHeader( picture_number=11, fragment_slice_count=1, fragment_x_offset=1, fragment_y_offset=0, ), ), ), bitstream.DataUnit(parse_info=bitstream.ParseInfo( parse_code=tables.ParseCodes.end_of_sequence, ), ), ]) # Another (HQ) picture in a separate sequence seq2 = bitstream.Sequence(data_units=[ bitstream.DataUnit( parse_info=bitstream.ParseInfo( parse_code=tables.ParseCodes.sequence_header, ), sequence_header=bitstream.SequenceHeader( parse_parameters=bitstream.ParseParameters( major_version=2), video_parameters=bitstream.SourceParameters( frame_size=bitstream.FrameSize( # Don't waste time on full-sized frames custom_dimensions_flag=True, frame_width=4, frame_height=2, ), clean_area=bitstream.CleanArea( custom_clean_area_flag=True, clean_width=4, clean_height=2, ), color_diff_sampling_format=bitstream. ColorDiffSamplingFormat( custom_color_diff_format_flag=True, color_diff_format_index=tables. ColorDifferenceSamplingFormats. color_4_2_2, # noqa: E501 ), # Output values will be treated as 8-bit (and thus all # decode to 128) signal_range=bitstream.SignalRange( custom_signal_range_flag=True, index=tables.PresetSignalRanges. video_8bit_full_range, ), ), picture_coding_mode=tables.PictureCodingModes. pictures_are_frames, ), ), bitstream.DataUnit( parse_info=bitstream.ParseInfo( parse_code=tables.ParseCodes.high_quality_picture, ), picture_parse=bitstream.PictureParse( picture_header=bitstream.PictureHeader( picture_number=12), ), ), bitstream.DataUnit(parse_info=bitstream.ParseInfo( parse_code=tables.ParseCodes.end_of_sequence, ), ), ]) populate_parse_offsets(seq1) populate_parse_offsets(seq2) state = bytes_to_state( serialise_to_bytes(seq1) + serialise_to_bytes(seq2)) state["_output_picture_callback"] = Mock() decoder.parse_stream(state) assert state["_output_picture_callback"].call_count == 3 for i, (args, kwargs) in enumerate( state["_output_picture_callback"].call_args_list): assert kwargs == {} # Should get a 4x2 mid-gray frame with 4:2:2 color difference sampling assert args[0] == { "pic_num": 10 + i, "Y": [[128, 128, 128, 128], [128, 128, 128, 128]], "C1": [[128, 128], [128, 128]], "C2": [[128, 128], [128, 128]], } # Just sanity check the second argument looks like a set of video parameters assert args[1]["frame_width"] == 4 assert args[1]["frame_height"] == 2 assert args[1]["luma_offset"] == 0 assert args[1]["luma_offset"] == 0 assert args[1]["luma_excursion"] == 255 assert args[1]["color_diff_offset"] == 128 assert args[1]["color_diff_excursion"] == 255 # And the picture coding mode too... assert args[2] == tables.PictureCodingModes.pictures_are_frames