From d5ea625272c03c9c53745df6deaf440d86563104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Sat, 13 Jun 2026 23:49:15 -0700 Subject: [PATCH] Added video decoder support. (#3756) --- 3rdparty/h264/h264.h | 1029 ++++++ 3rdparty/l-smash/LICENSE | 13 + 3rdparty/l-smash/codecs/a52.c | 768 +++++ 3rdparty/l-smash/codecs/a52.h | 94 + 3rdparty/l-smash/codecs/alac.c | 160 + 3rdparty/l-smash/codecs/description.c | 3257 +++++++++++++++++++ 3rdparty/l-smash/codecs/description.h | 79 + 3rdparty/l-smash/codecs/dts.c | 1834 +++++++++++ 3rdparty/l-smash/codecs/dts.h | 135 + 3rdparty/l-smash/codecs/h264.c | 2588 +++++++++++++++ 3rdparty/l-smash/codecs/h264.h | 369 +++ 3rdparty/l-smash/codecs/hevc.c | 3150 ++++++++++++++++++ 3rdparty/l-smash/codecs/hevc.h | 447 +++ 3rdparty/l-smash/codecs/id.c | 28 + 3rdparty/l-smash/codecs/mp4a.c | 1160 +++++++ 3rdparty/l-smash/codecs/mp4a.h | 136 + 3rdparty/l-smash/codecs/mp4sys.c | 1610 ++++++++++ 3rdparty/l-smash/codecs/mp4sys.h | 191 ++ 3rdparty/l-smash/codecs/mp4v.c | 46 + 3rdparty/l-smash/codecs/nalu.c | 267 ++ 3rdparty/l-smash/codecs/nalu.h | 145 + 3rdparty/l-smash/codecs/qt_wfex.c | 79 + 3rdparty/l-smash/codecs/vc1.c | 965 ++++++ 3rdparty/l-smash/codecs/vc1.h | 134 + 3rdparty/l-smash/codecs/wma.c | 86 + 3rdparty/l-smash/common/alloc.c | 73 + 3rdparty/l-smash/common/bits.c | 194 ++ 3rdparty/l-smash/common/bits.h | 42 + 3rdparty/l-smash/common/bytes.c | 707 +++++ 3rdparty/l-smash/common/bytes.h | 163 + 3rdparty/l-smash/common/internal.h | 39 + 3rdparty/l-smash/common/list.c | 252 ++ 3rdparty/l-smash/common/list.h | 117 + 3rdparty/l-smash/common/memint.h | 108 + 3rdparty/l-smash/common/multibuf.c | 90 + 3rdparty/l-smash/common/multibuf.h | 33 + 3rdparty/l-smash/common/osdep.c | 67 + 3rdparty/l-smash/common/osdep.h | 61 + 3rdparty/l-smash/common/utils.c | 179 ++ 3rdparty/l-smash/common/utils.h | 232 ++ 3rdparty/l-smash/core/box.c | 2071 ++++++++++++ 3rdparty/l-smash/core/box.h | 2669 ++++++++++++++++ 3rdparty/l-smash/core/box_default.c | 276 ++ 3rdparty/l-smash/core/box_default.h | 151 + 3rdparty/l-smash/core/box_type.c | 27 + 3rdparty/l-smash/core/box_type.h | 263 ++ 3rdparty/l-smash/core/chapter.c | 440 +++ 3rdparty/l-smash/core/file.c | 718 +++++ 3rdparty/l-smash/core/file.h | 43 + 3rdparty/l-smash/core/fragment.c | 1783 +++++++++++ 3rdparty/l-smash/core/fragment.h | 54 + 3rdparty/l-smash/core/isom.c | 4220 +++++++++++++++++++++++++ 3rdparty/l-smash/core/meta.c | 500 +++ 3rdparty/l-smash/core/print.c | 2625 +++++++++++++++ 3rdparty/l-smash/core/print.h | 30 + 3rdparty/l-smash/core/read.c | 2996 ++++++++++++++++++ 3rdparty/l-smash/core/read.h | 29 + 3rdparty/l-smash/core/summary.c | 359 +++ 3rdparty/l-smash/core/timeline.c | 2081 ++++++++++++ 3rdparty/l-smash/core/timeline.h | 138 + 3rdparty/l-smash/core/write.c | 1761 +++++++++++ 3rdparty/l-smash/core/write.h | 29 + 3rdparty/l-smash/importer/a52_imp.c | 655 ++++ 3rdparty/l-smash/importer/adts_imp.c | 524 +++ 3rdparty/l-smash/importer/als_imp.c | 383 +++ 3rdparty/l-smash/importer/amr_imp.c | 288 ++ 3rdparty/l-smash/importer/dts_imp.c | 366 +++ 3rdparty/l-smash/importer/importer.c | 323 ++ 3rdparty/l-smash/importer/importer.h | 168 + 3rdparty/l-smash/importer/isobm_imp.c | 242 ++ 3rdparty/l-smash/importer/mp3_imp.c | 513 +++ 3rdparty/l-smash/importer/nalu_imp.c | 1566 +++++++++ 3rdparty/l-smash/importer/vc1_imp.c | 547 ++++ 3rdparty/l-smash/importer/wave_imp.c | 446 +++ 3rdparty/l-smash/lsmash.h | 4198 ++++++++++++++++++++++++ bindings/bf/bgfx.bf | 177 +- bindings/c3/bgfx.c3 | 183 +- bindings/cs/bgfx.cs | 174 +- bindings/d/impl.d | 5 + bindings/d/package.d | 174 +- bindings/zig/bgfx.zig | 120 +- docs/bgfx.rst | 11 + include/bgfx/bgfx.h | 121 + include/bgfx/c99/bgfx.h | 127 + include/bgfx/defines.h | 59 +- scripts/bgfx.idl | 143 +- scripts/bgfx.lua | 3 + scripts/genie.lua | 6 + scripts/geometryv.lua | 3 + scripts/texturev.lua | 57 + src/amalgamated.cpp | 4 + src/bgfx.cpp | 237 +- src/bgfx.idl.inl | 6 + src/bgfx_p.h | 37 +- src/config.h | 27 + src/cs_yuv_to_rgb.bin.h | 1305 ++++++++ src/cs_yuv_to_rgb.sc | 41 + src/makefile | 4 + src/renderer_d3d11.cpp | 77 +- src/renderer_d3d11.h | 4 + src/renderer_d3d12.cpp | 133 +- src/renderer_d3d12.h | 8 + src/renderer_mtl.cpp | 91 +- src/renderer_mtl.h | 6 +- src/renderer_vk.cpp | 197 +- src/renderer_vk.h | 44 + src/video.h | 1175 +++++++ src/video_d3d11.cpp | 1209 +++++++ src/video_d3d11.h | 32 + src/video_d3d12.cpp | 1840 +++++++++++ src/video_d3d12.h | 37 + src/video_mtl.cpp | 873 +++++ src/video_mtl.h | 24 + src/video_vk.cpp | 2830 +++++++++++++++++ src/video_vk.h | 37 + tools/texturev/texturev.cpp | 486 ++- tools/texturev/video_player.cpp | 1139 +++++++ tools/texturev/video_player.h | 92 + 118 files changed, 67776 insertions(+), 191 deletions(-) create mode 100644 3rdparty/h264/h264.h create mode 100644 3rdparty/l-smash/LICENSE create mode 100644 3rdparty/l-smash/codecs/a52.c create mode 100644 3rdparty/l-smash/codecs/a52.h create mode 100644 3rdparty/l-smash/codecs/alac.c create mode 100644 3rdparty/l-smash/codecs/description.c create mode 100644 3rdparty/l-smash/codecs/description.h create mode 100644 3rdparty/l-smash/codecs/dts.c create mode 100644 3rdparty/l-smash/codecs/dts.h create mode 100644 3rdparty/l-smash/codecs/h264.c create mode 100644 3rdparty/l-smash/codecs/h264.h create mode 100644 3rdparty/l-smash/codecs/hevc.c create mode 100644 3rdparty/l-smash/codecs/hevc.h create mode 100644 3rdparty/l-smash/codecs/id.c create mode 100644 3rdparty/l-smash/codecs/mp4a.c create mode 100644 3rdparty/l-smash/codecs/mp4a.h create mode 100644 3rdparty/l-smash/codecs/mp4sys.c create mode 100644 3rdparty/l-smash/codecs/mp4sys.h create mode 100644 3rdparty/l-smash/codecs/mp4v.c create mode 100644 3rdparty/l-smash/codecs/nalu.c create mode 100644 3rdparty/l-smash/codecs/nalu.h create mode 100644 3rdparty/l-smash/codecs/qt_wfex.c create mode 100644 3rdparty/l-smash/codecs/vc1.c create mode 100644 3rdparty/l-smash/codecs/vc1.h create mode 100644 3rdparty/l-smash/codecs/wma.c create mode 100644 3rdparty/l-smash/common/alloc.c create mode 100644 3rdparty/l-smash/common/bits.c create mode 100644 3rdparty/l-smash/common/bits.h create mode 100644 3rdparty/l-smash/common/bytes.c create mode 100644 3rdparty/l-smash/common/bytes.h create mode 100644 3rdparty/l-smash/common/internal.h create mode 100644 3rdparty/l-smash/common/list.c create mode 100644 3rdparty/l-smash/common/list.h create mode 100644 3rdparty/l-smash/common/memint.h create mode 100644 3rdparty/l-smash/common/multibuf.c create mode 100644 3rdparty/l-smash/common/multibuf.h create mode 100644 3rdparty/l-smash/common/osdep.c create mode 100644 3rdparty/l-smash/common/osdep.h create mode 100644 3rdparty/l-smash/common/utils.c create mode 100644 3rdparty/l-smash/common/utils.h create mode 100644 3rdparty/l-smash/core/box.c create mode 100644 3rdparty/l-smash/core/box.h create mode 100644 3rdparty/l-smash/core/box_default.c create mode 100644 3rdparty/l-smash/core/box_default.h create mode 100644 3rdparty/l-smash/core/box_type.c create mode 100644 3rdparty/l-smash/core/box_type.h create mode 100644 3rdparty/l-smash/core/chapter.c create mode 100644 3rdparty/l-smash/core/file.c create mode 100644 3rdparty/l-smash/core/file.h create mode 100644 3rdparty/l-smash/core/fragment.c create mode 100644 3rdparty/l-smash/core/fragment.h create mode 100644 3rdparty/l-smash/core/isom.c create mode 100644 3rdparty/l-smash/core/meta.c create mode 100644 3rdparty/l-smash/core/print.c create mode 100644 3rdparty/l-smash/core/print.h create mode 100644 3rdparty/l-smash/core/read.c create mode 100644 3rdparty/l-smash/core/read.h create mode 100644 3rdparty/l-smash/core/summary.c create mode 100644 3rdparty/l-smash/core/timeline.c create mode 100644 3rdparty/l-smash/core/timeline.h create mode 100644 3rdparty/l-smash/core/write.c create mode 100644 3rdparty/l-smash/core/write.h create mode 100644 3rdparty/l-smash/importer/a52_imp.c create mode 100644 3rdparty/l-smash/importer/adts_imp.c create mode 100644 3rdparty/l-smash/importer/als_imp.c create mode 100644 3rdparty/l-smash/importer/amr_imp.c create mode 100644 3rdparty/l-smash/importer/dts_imp.c create mode 100644 3rdparty/l-smash/importer/importer.c create mode 100644 3rdparty/l-smash/importer/importer.h create mode 100644 3rdparty/l-smash/importer/isobm_imp.c create mode 100644 3rdparty/l-smash/importer/mp3_imp.c create mode 100644 3rdparty/l-smash/importer/nalu_imp.c create mode 100644 3rdparty/l-smash/importer/vc1_imp.c create mode 100644 3rdparty/l-smash/importer/wave_imp.c create mode 100644 3rdparty/l-smash/lsmash.h create mode 100644 src/cs_yuv_to_rgb.bin.h create mode 100644 src/cs_yuv_to_rgb.sc create mode 100644 src/video.h create mode 100644 src/video_d3d11.cpp create mode 100644 src/video_d3d11.h create mode 100644 src/video_d3d12.cpp create mode 100644 src/video_d3d12.h create mode 100644 src/video_mtl.cpp create mode 100644 src/video_mtl.h create mode 100644 src/video_vk.cpp create mode 100644 src/video_vk.h create mode 100644 tools/texturev/video_player.cpp create mode 100644 tools/texturev/video_player.h diff --git a/3rdparty/h264/h264.h b/3rdparty/h264/h264.h new file mode 100644 index 000000000..7c2b9b712 --- /dev/null +++ b/3rdparty/h264/h264.h @@ -0,0 +1,1029 @@ +// Minimal cross-platform H264 video parser utility created by Turánszki János for Wicked Engine: https://github.com/turanszkij/WickedEngine +// This is not using any includes or memory allocations +// Based on the H264 specification: Rec. ITU-T H.264 (08/2021) +// The following library was also used as reference: https://github.com/aizvorski/h264bitstream/ +// +// What this library does: +// - extract descriptor structures from a H264 bitstream +// - NAL headers +// - Sequence Parameter Set (SPS) +// - Picture Parameter Set (PPS) +// - Slices (SliceHeader) +// +// What this library doesn't do: +// - read from MP4 file +// - decode video +// - encode video +// - display video +// +// How to use: +// 1) Create a bitstream to consume binary data, the data pointer must point to data that contains raw H264 data: +// Bitstream bs; +// bs.init(data, size); +// +// 2) Find a NAL header in the bitstream: +// find_next_nal(&bs); +// +// 2) Read the NAL header from the bitstream: +// NALHeader nal; +// read_nal_header(&nal, &bs); +// +// 3) Depending on nal.type, you can extract the important structures from the bitstream: +// if (nal.type == NAL_UNIT_TYPE_SPS) +// { +// PPS pps = {}; +// read_pps(&pps, &bs); +// } +// else if (nal.type == NAL_UNIT_TYPE_SPS) +// { +// SPS sps = {}; +// read_sps(&sps, &bs); +// } +// else if (nal.type == NAL_UNIT_TYPE_CODED_SLICE_IDR || nal.type == NAL_UNIT_TYPE_CODED_SLICE_NON_IDR) +// { +// SliceHeader slice_header = {}; +// read_slice_header(&slice_header, &nal, &pps, &sps, &bs); +// } +// +// 4) repeat until you have extracted all that you require or the data ends +// +// +// MIT License (see the end of this file) + +#ifndef H264_H +#define H264_H + +namespace h264 { + + static constexpr unsigned char nal_start_code[] = { 0,0,1 }; + + struct SPS + { + int profile_idc; + int constraint_set0_flag; + int constraint_set1_flag; + int constraint_set2_flag; + int constraint_set3_flag; + int constraint_set4_flag; + int constraint_set5_flag; + int reserved_zero_2bits; + int level_idc; + int seq_parameter_set_id; + int chroma_format_idc; + int separate_colour_plane_flag; + int bit_depth_luma_minus8; + int bit_depth_chroma_minus8; + int qpprime_y_zero_transform_bypass_flag; + int seq_scaling_matrix_present_flag; + int seq_scaling_list_present_flag[8]; + int ScalingList4x4[6][16]; + int UseDefaultScalingMatrix4x4Flag[6]; + int ScalingList8x8[2][64]; + int UseDefaultScalingMatrix8x8Flag[2]; + int log2_max_frame_num_minus4; + int pic_order_cnt_type; + int log2_max_pic_order_cnt_lsb_minus4; + int delta_pic_order_always_zero_flag; + int offset_for_non_ref_pic; + int offset_for_top_to_bottom_field; + int num_ref_frames_in_pic_order_cnt_cycle; + int offset_for_ref_frame[256]; + int num_ref_frames; + int gaps_in_frame_num_value_allowed_flag; + int pic_width_in_mbs_minus1; + int pic_height_in_map_units_minus1; + int frame_mbs_only_flag; + int mb_adaptive_frame_field_flag; + int direct_8x8_inference_flag; + int frame_cropping_flag; + int frame_crop_left_offset; + int frame_crop_right_offset; + int frame_crop_top_offset; + int frame_crop_bottom_offset; + int vui_parameters_present_flag; + + struct + { + int aspect_ratio_info_present_flag; + int aspect_ratio_idc; + int sar_width; + int sar_height; + int overscan_info_present_flag; + int overscan_appropriate_flag; + int video_signal_type_present_flag; + int video_format; + int video_full_range_flag; + int colour_description_present_flag; + int colour_primaries; + int transfer_characteristics; + int matrix_coefficients; + int chroma_loc_info_present_flag; + int chroma_sample_loc_type_top_field; + int chroma_sample_loc_type_bottom_field; + int timing_info_present_flag; + int num_units_in_tick; + int time_scale; + int fixed_frame_rate_flag; + int nal_hrd_parameters_present_flag; + int vcl_hrd_parameters_present_flag; + int low_delay_hrd_flag; + int pic_struct_present_flag; + int bitstream_restriction_flag; + int motion_vectors_over_pic_boundaries_flag; + int max_bytes_per_pic_denom; + int max_bits_per_mb_denom; + int log2_max_mv_length_horizontal; + int log2_max_mv_length_vertical; + int num_reorder_frames; + int max_dec_frame_buffering; + } vui; + + struct + { + int cpb_cnt_minus1; + int bit_rate_scale; + int cpb_size_scale; + int bit_rate_value_minus1[32]; + int cpb_size_value_minus1[32]; + int cbr_flag[32]; + int initial_cpb_removal_delay_length_minus1; + int cpb_removal_delay_length_minus1; + int dpb_output_delay_length_minus1; + int time_offset_length; + } hrd; + }; + + struct PPS + { + int pic_parameter_set_id; + int seq_parameter_set_id; + int entropy_coding_mode_flag; + int pic_order_present_flag; + int num_slice_groups_minus1; + int slice_group_map_type; + int run_length_minus1[8]; + int top_left[8]; + int bottom_right[8]; + int slice_group_change_direction_flag; + int slice_group_change_rate_minus1; + int pic_size_in_map_units_minus1; + int slice_group_id[256]; + int num_ref_idx_l0_active_minus1; + int num_ref_idx_l1_active_minus1; + int weighted_pred_flag; + int weighted_bipred_idc; + int pic_init_qp_minus26; + int pic_init_qs_minus26; + int chroma_qp_index_offset; + int deblocking_filter_control_present_flag; + int constrained_intra_pred_flag; + int redundant_pic_cnt_present_flag; + + int _more_rbsp_data_present; + + int transform_8x8_mode_flag; + int pic_scaling_matrix_present_flag; + int pic_scaling_list_present_flag[8]; + int ScalingList4x4[6][16]; + int UseDefaultScalingMatrix4x4Flag[6]; + int ScalingList8x8[2][64]; + int UseDefaultScalingMatrix8x8Flag[2]; + int second_chroma_qp_index_offset; + }; + + enum SH_SLICE_TYPE + { + SH_SLICE_TYPE_P = 0, + SH_SLICE_TYPE_B = 1, + SH_SLICE_TYPE_I = 2, + SH_SLICE_TYPE_SP = 3, + SH_SLICE_TYPE_SI = 4, + + // The *_ONLY slice types indicate that all other slices in that picture are of the same type + SH_SLICE_TYPE_P_ONLY = 5, + SH_SLICE_TYPE_B_ONLY = 6, + SH_SLICE_TYPE_I_ONLY = 7, + SH_SLICE_TYPE_SP_ONLY = 8, + SH_SLICE_TYPE_SI_ONLY = 9, + }; + struct SliceHeader + { + int first_mb_in_slice; + int slice_type; + int pic_parameter_set_id; + int frame_num; + int field_pic_flag; + int bottom_field_flag; + int idr_pic_id; + int pic_order_cnt_lsb; + int delta_pic_order_cnt_bottom; + int delta_pic_order_cnt[2]; + int redundant_pic_cnt; + int direct_spatial_mv_pred_flag; + int num_ref_idx_active_override_flag; + int num_ref_idx_l0_active_minus1; + int num_ref_idx_l1_active_minus1; + int cabac_init_idc; + int slice_qp_delta; + int sp_for_switch_flag; + int slice_qs_delta; + int disable_deblocking_filter_idc; + int slice_alpha_c0_offset_div2; + int slice_beta_offset_div2; + int slice_group_change_cycle; + + struct + { + int luma_log2_weight_denom; + int chroma_log2_weight_denom; + int luma_weight_l0_flag[64]; + int luma_weight_l0[64]; + int luma_offset_l0[64]; + int chroma_weight_l0_flag[64]; + int chroma_weight_l0[64][2]; + int chroma_offset_l0[64][2]; + int luma_weight_l1_flag[64]; + int luma_weight_l1[64]; + int luma_offset_l1[64]; + int chroma_weight_l1_flag[64]; + int chroma_weight_l1[64][2]; + int chroma_offset_l1[64][2]; + } pwt; // predictive weight table + + struct + { + int ref_pic_list_reordering_flag_l0; + struct + { + int reordering_of_pic_nums_idc[64]; + int abs_diff_pic_num_minus1[64]; + int long_term_pic_num[64]; + } reorder_l0; + int ref_pic_list_reordering_flag_l1; + struct + { + int reordering_of_pic_nums_idc[64]; + int abs_diff_pic_num_minus1[64]; + int long_term_pic_num[64]; + } reorder_l1; + } rplr; // ref pic list reorder + + struct + { + int no_output_of_prior_pics_flag; + int long_term_reference_flag; + int adaptive_ref_pic_marking_mode_flag; + int memory_management_control_operation[64]; + int difference_of_pic_nums_minus1[64]; + int long_term_pic_num[64]; + int long_term_frame_idx[64]; + int max_long_term_frame_idx_plus1[64]; + } drpm; // decoded ref pic marking + + }; + + enum NAL_REF_IDC + { + NAL_REF_IDC_PRIORITY_HIGHEST = 3, + NAL_REF_IDC_PRIORITY_HIGH = 2, + NAL_REF_IDC_PRIORITY_LOW = 1, + NAL_REF_IDC_PRIORITY_DISPOSABLE = 0, + }; + enum NAL_UNIT_TYPE + { + NAL_UNIT_TYPE_UNSPECIFIED = 0, // Unspecified + NAL_UNIT_TYPE_CODED_SLICE_NON_IDR = 1, // Coded slice of a non-IDR picture + NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_A = 2, // Coded slice data partition A + NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_B = 3, // Coded slice data partition B + NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_C = 4, // Coded slice data partition C + NAL_UNIT_TYPE_CODED_SLICE_IDR = 5, // Coded slice of an IDR picture + NAL_UNIT_TYPE_SEI = 6, // Supplemental enhancement information (SEI) + NAL_UNIT_TYPE_SPS = 7, // Sequence parameter set + NAL_UNIT_TYPE_PPS = 8, // Picture parameter set + NAL_UNIT_TYPE_AUD = 9, // Access unit delimiter + NAL_UNIT_TYPE_END_OF_SEQUENCE = 10, // End of sequence + NAL_UNIT_TYPE_END_OF_STREAM = 11, // End of stream + NAL_UNIT_TYPE_FILLER = 12, // Filler data + NAL_UNIT_TYPE_SPS_EXT = 13, // Sequence parameter set extension + NAL_UNIT_TYPE_CODED_SLICE_AUX = 19, // Coded slice of an auxiliary coded picture without partitioning + }; + struct NALHeader + { + NAL_REF_IDC idc; + NAL_UNIT_TYPE type; + }; + + struct Bitstream + { + const unsigned char* start; + const unsigned char* p; + const unsigned char* end; + int bits_left; + + constexpr void init(const unsigned char* buf, unsigned long long size) + { + start = buf; + p = buf; + end = buf + size; + bits_left = 8; + } + constexpr unsigned long long byte_offset() { return (unsigned long long)(p - start); } + constexpr bool byte_aligned() { return bits_left == 8; } + constexpr bool eof() { if (p >= end) { return true; } else { return false; } } + constexpr void back_1byte() + { + bits_left = 8; + p--; + } + constexpr unsigned u1() + { + unsigned r = 0; + bits_left--; + if (!eof()) + { + r = ((*(p)) >> bits_left) & 0x01; + } + if (bits_left == 0) + { + p++; + bits_left = 8; + } + return r; + } + constexpr unsigned u(int n) + { + unsigned r = 0; + for (int i = 0; i < n; i++) + { + r |= (u1() << (n - i - 1)); + } + return r; + } + constexpr unsigned ue() + { + int r = 0; + int i = 0; + + while ((u1() == 0) && (i < 32) && (!eof())) + { + i++; + } + r = u(i); + r += (1 << i) - 1; + return r; + } + constexpr int se() + { + int r = ue(); + if (r & 0x01) + { + r = (r + 1) / 2; + } + else + { + r = -(r / 2); + } + return r; + } + }; + + // returns true if a nal header is found, false otherwise + constexpr bool find_next_nal(Bitstream* b) + { + while (!b->eof()) + { + // Read in 3 byte increments because the nal header is 3 bytes normally: + const unsigned int b0 = b->u(8); + const unsigned int b1 = b->u(8); + const unsigned int b2 = b->u(8); + if (b0 == 0 && b1 == 0 && b2 == 1) + return true; + // step back 2 bytes, so the next iteration has a chance to succeed if extended NAL start code is being used which is 4 bytes: + b->back_1byte(); + b->back_1byte(); + } + return false; + } + // returns true if a valid nal header was read, false otherwise + constexpr bool read_nal_header(NALHeader* nal, Bitstream* b) + { + unsigned forbidden_zero_bit = b->u(1); + if (forbidden_zero_bit != 0) + return false; + nal->idc = (h264::NAL_REF_IDC)b->u(2); + nal->type = (h264::NAL_UNIT_TYPE)b->u(5); + return true; + } + constexpr void read_scaling_list(Bitstream* b, int* scalingList, int sizeOfScalingList, int* useDefaultScalingMatrixFlag) + { + int lastScale = 8; + int nextScale = 8; + int delta_scale = 0; + for (int j = 0; j < sizeOfScalingList; j++) + { + if (nextScale != 0) + { + if (0) + { + nextScale = scalingList[j]; + if (*useDefaultScalingMatrixFlag) { nextScale = 0; } + delta_scale = (nextScale - lastScale) % 256; + } + + delta_scale = b->se(); + + if (1) + { + nextScale = (lastScale + delta_scale + 256) % 256; + *useDefaultScalingMatrixFlag = (j == 0 && nextScale == 0); + } + } + if (1) + { + scalingList[j] = (nextScale == 0) ? lastScale : nextScale; + } + lastScale = scalingList[j]; + } + } + constexpr void read_hrd_parameters(SPS* sps, Bitstream* b) + { + sps->hrd.cpb_cnt_minus1 = b->ue(); + sps->hrd.bit_rate_scale = b->u(4); + sps->hrd.cpb_size_scale = b->u(4); + for (int SchedSelIdx = 0; SchedSelIdx <= sps->hrd.cpb_cnt_minus1; SchedSelIdx++) + { + sps->hrd.bit_rate_value_minus1[SchedSelIdx] = b->ue(); + sps->hrd.cpb_size_value_minus1[SchedSelIdx] = b->ue(); + sps->hrd.cbr_flag[SchedSelIdx] = b->u1(); + } + sps->hrd.initial_cpb_removal_delay_length_minus1 = b->u(5); + sps->hrd.cpb_removal_delay_length_minus1 = b->u(5); + sps->hrd.dpb_output_delay_length_minus1 = b->u(5); + sps->hrd.time_offset_length = b->u(5); + } + constexpr void read_rbsp_trailing_bits(Bitstream* b) + { + /* rbsp_stop_one_bit */ b->u(1); + + while (!b->byte_aligned()) + { + /* rbsp_alignment_zero_bit */ b->u(1); + } + } + constexpr void read_vui_parameters(SPS* sps, Bitstream* b) + { + sps->vui.aspect_ratio_info_present_flag = b->u1(); + if (sps->vui.aspect_ratio_info_present_flag) + { + sps->vui.aspect_ratio_idc = b->u(8); + if (sps->vui.aspect_ratio_idc == 255) // Extended_SAR + { + sps->vui.sar_width = b->u(16); + sps->vui.sar_height = b->u(16); + } + } + sps->vui.overscan_info_present_flag = b->u1(); + if (sps->vui.overscan_info_present_flag) + { + sps->vui.overscan_appropriate_flag = b->u1(); + } + sps->vui.video_signal_type_present_flag = b->u1(); + if (sps->vui.video_signal_type_present_flag) + { + sps->vui.video_format = b->u(3); + sps->vui.video_full_range_flag = b->u1(); + sps->vui.colour_description_present_flag = b->u1(); + if (sps->vui.colour_description_present_flag) + { + sps->vui.colour_primaries = b->u(8); + sps->vui.transfer_characteristics = b->u(8); + sps->vui.matrix_coefficients = b->u(8); + } + } + sps->vui.chroma_loc_info_present_flag = b->u1(); + if (sps->vui.chroma_loc_info_present_flag) + { + sps->vui.chroma_sample_loc_type_top_field = b->ue(); + sps->vui.chroma_sample_loc_type_bottom_field = b->ue(); + } + sps->vui.timing_info_present_flag = b->u1(); + if (sps->vui.timing_info_present_flag) + { + sps->vui.num_units_in_tick = b->u(32); + sps->vui.time_scale = b->u(32); + sps->vui.fixed_frame_rate_flag = b->u1(); + } + sps->vui.nal_hrd_parameters_present_flag = b->u1(); + if (sps->vui.nal_hrd_parameters_present_flag) + { + read_hrd_parameters(sps, b); + } + sps->vui.vcl_hrd_parameters_present_flag = b->u1(); + if (sps->vui.vcl_hrd_parameters_present_flag) + { + read_hrd_parameters(sps, b); + } + if (sps->vui.nal_hrd_parameters_present_flag || sps->vui.vcl_hrd_parameters_present_flag) + { + sps->vui.low_delay_hrd_flag = b->u1(); + } + sps->vui.pic_struct_present_flag = b->u1(); + sps->vui.bitstream_restriction_flag = b->u1(); + if (sps->vui.bitstream_restriction_flag) + { + sps->vui.motion_vectors_over_pic_boundaries_flag = b->u1(); + sps->vui.max_bytes_per_pic_denom = b->ue(); + sps->vui.max_bits_per_mb_denom = b->ue(); + sps->vui.log2_max_mv_length_horizontal = b->ue(); + sps->vui.log2_max_mv_length_vertical = b->ue(); + sps->vui.num_reorder_frames = b->ue(); + sps->vui.max_dec_frame_buffering = b->ue(); + } + } + constexpr int intlog2(int x) + { + int log = 0; + if (x < 0) { x = 0; } + while ((x >> log) > 0) + { + log++; + } + if (log > 0 && x == 1 << (log - 1)) { log--; } + return log; + } + constexpr int more_rbsp_data(Bitstream* b) + { + // no more data + if (b->eof()) { return 0; } + + Bitstream bs_tmp = *b; // make copy + + // no rbsp_stop_bit yet + if (bs_tmp.u1() == 0) { return 1; } + + while (!bs_tmp.eof()) + { + // A later bit was 1, it wasn't the rsbp_stop_bit + if (bs_tmp.u1() == 1) { return 1; } + } + + // All following bits were 0, it was the rsbp_stop_bit + return 0; + } + constexpr void read_sps(SPS* sps, Bitstream* b) + { + sps->profile_idc = b->u(8); + sps->constraint_set0_flag = b->u1(); + sps->constraint_set1_flag = b->u1(); + sps->constraint_set2_flag = b->u1(); + sps->constraint_set3_flag = b->u1(); + sps->constraint_set4_flag = b->u1(); + sps->constraint_set5_flag = b->u1(); + /* reserved_zero_2bits */ b->u(2); + sps->level_idc = b->u(8); + sps->seq_parameter_set_id = b->ue(); + + if (sps->profile_idc == 100 || sps->profile_idc == 110 || + sps->profile_idc == 122 || sps->profile_idc == 144) + { + sps->chroma_format_idc = b->ue(); + if (sps->chroma_format_idc == 3) + { + sps->separate_colour_plane_flag = b->u1(); + } + sps->bit_depth_luma_minus8 = b->ue(); + sps->bit_depth_chroma_minus8 = b->ue(); + sps->qpprime_y_zero_transform_bypass_flag = b->u1(); + sps->seq_scaling_matrix_present_flag = b->u1(); + if (sps->seq_scaling_matrix_present_flag) + { + for (int i = 0; i < 8; i++) + { + sps->seq_scaling_list_present_flag[i] = b->u1(); + if (sps->seq_scaling_list_present_flag[i]) + { + if (i < 6) + { + read_scaling_list(b, sps->ScalingList4x4[i], 16, + &(sps->UseDefaultScalingMatrix4x4Flag[i])); + } + else + { + read_scaling_list(b, sps->ScalingList8x8[i - 6], 64, + &(sps->UseDefaultScalingMatrix8x8Flag[i - 6])); + } + } + } + } + } + else + { + // H.264 spec 7.4.2.1.1: when chroma_format_idc is not present + // (i.e. Baseline/Main/Extended profile) it shall be inferred + // to be equal to 1 (4:2:0 chroma format). + sps->chroma_format_idc = 1; + } + sps->log2_max_frame_num_minus4 = b->ue(); + sps->pic_order_cnt_type = b->ue(); + if (sps->pic_order_cnt_type == 0) + { + sps->log2_max_pic_order_cnt_lsb_minus4 = b->ue(); + } + else if (sps->pic_order_cnt_type == 1) + { + sps->delta_pic_order_always_zero_flag = b->u1(); + sps->offset_for_non_ref_pic = b->se(); + sps->offset_for_top_to_bottom_field = b->se(); + sps->num_ref_frames_in_pic_order_cnt_cycle = b->ue(); + for (int i = 0; i < sps->num_ref_frames_in_pic_order_cnt_cycle; i++) + { + sps->offset_for_ref_frame[i] = b->se(); + } + } + sps->num_ref_frames = b->ue(); + sps->gaps_in_frame_num_value_allowed_flag = b->u1(); + sps->pic_width_in_mbs_minus1 = b->ue(); + sps->pic_height_in_map_units_minus1 = b->ue(); + sps->frame_mbs_only_flag = b->u1(); + if (!sps->frame_mbs_only_flag) + { + sps->mb_adaptive_frame_field_flag = b->u1(); + } + sps->direct_8x8_inference_flag = b->u1(); + sps->frame_cropping_flag = b->u1(); + if (sps->frame_cropping_flag) + { + sps->frame_crop_left_offset = b->ue(); + sps->frame_crop_right_offset = b->ue(); + sps->frame_crop_top_offset = b->ue(); + sps->frame_crop_bottom_offset = b->ue(); + } + sps->vui_parameters_present_flag = b->u1(); + if (sps->vui_parameters_present_flag) + { + read_vui_parameters(sps, b); + } + read_rbsp_trailing_bits(b); + } + constexpr void read_pps(PPS* pps, Bitstream* b) + { + pps->pic_parameter_set_id = b->ue(); + pps->seq_parameter_set_id = b->ue(); + pps->entropy_coding_mode_flag = b->u1(); + pps->pic_order_present_flag = b->u1(); + pps->num_slice_groups_minus1 = b->ue(); + + if (pps->num_slice_groups_minus1 > 0) + { + pps->slice_group_map_type = b->ue(); + if (pps->slice_group_map_type == 0) + { + for (int i_group = 0; i_group <= pps->num_slice_groups_minus1; i_group++) + { + pps->run_length_minus1[i_group] = b->ue(); + } + } + else if (pps->slice_group_map_type == 2) + { + for (int i_group = 0; i_group < pps->num_slice_groups_minus1; i_group++) + { + pps->top_left[i_group] = b->ue(); + pps->bottom_right[i_group] = b->ue(); + } + } + else if (pps->slice_group_map_type == 3 || + pps->slice_group_map_type == 4 || + pps->slice_group_map_type == 5) + { + pps->slice_group_change_direction_flag = b->u1(); + pps->slice_group_change_rate_minus1 = b->ue(); + } + else if (pps->slice_group_map_type == 6) + { + pps->pic_size_in_map_units_minus1 = b->ue(); + for (int i = 0; i <= pps->pic_size_in_map_units_minus1; i++) + { + int v = intlog2(pps->num_slice_groups_minus1 + 1); + pps->slice_group_id[i] = b->u(v); + } + } + } + pps->num_ref_idx_l0_active_minus1 = b->ue(); + pps->num_ref_idx_l1_active_minus1 = b->ue(); + pps->weighted_pred_flag = b->u1(); + pps->weighted_bipred_idc = b->u(2); + pps->pic_init_qp_minus26 = b->se(); + pps->pic_init_qs_minus26 = b->se(); + pps->chroma_qp_index_offset = b->se(); + pps->deblocking_filter_control_present_flag = b->u1(); + pps->constrained_intra_pred_flag = b->u1(); + pps->redundant_pic_cnt_present_flag = b->u1(); + + int have_more_data = 0; + if (1) { have_more_data = more_rbsp_data(b); } + if (0) + { + have_more_data = (pps->transform_8x8_mode_flag | pps->pic_scaling_matrix_present_flag | pps->second_chroma_qp_index_offset) != 0; + } + + if (have_more_data) + { + pps->transform_8x8_mode_flag = b->u1(); + pps->pic_scaling_matrix_present_flag = b->u1(); + if (pps->pic_scaling_matrix_present_flag) + { + for (int i = 0; i < 6 + 2 * pps->transform_8x8_mode_flag; i++) + { + pps->pic_scaling_list_present_flag[i] = b->u1(); + if (pps->pic_scaling_list_present_flag[i]) + { + if (i < 6) + { + read_scaling_list(b, pps->ScalingList4x4[i], 16, + &(pps->UseDefaultScalingMatrix4x4Flag[i])); + } + else + { + read_scaling_list(b, pps->ScalingList8x8[i - 6], 64, + &(pps->UseDefaultScalingMatrix8x8Flag[i - 6])); + } + } + } + } + pps->second_chroma_qp_index_offset = b->se(); + } + read_rbsp_trailing_bits(b); + } + constexpr int is_slice_type(int slice_type, int cmp_type) + { + if (slice_type >= 5) { slice_type -= 5; } + if (cmp_type >= 5) { cmp_type -= 5; } + if (slice_type == cmp_type) { return 1; } + else { return 0; } + } + constexpr void read_ref_pic_list_reordering(SliceHeader* sh, Bitstream* b) + { + if (!is_slice_type(sh->slice_type, SH_SLICE_TYPE_I) && !is_slice_type(sh->slice_type, SH_SLICE_TYPE_SI)) + { + sh->rplr.ref_pic_list_reordering_flag_l0 = b->u1(); + if (sh->rplr.ref_pic_list_reordering_flag_l0) + { + int n = -1; + do + { + n++; + sh->rplr.reorder_l0.reordering_of_pic_nums_idc[n] = b->ue(); + if (sh->rplr.reorder_l0.reordering_of_pic_nums_idc[n] == 0 || + sh->rplr.reorder_l0.reordering_of_pic_nums_idc[n] == 1) + { + sh->rplr.reorder_l0.abs_diff_pic_num_minus1[n] = b->ue(); + } + else if (sh->rplr.reorder_l0.reordering_of_pic_nums_idc[n] == 2) + { + sh->rplr.reorder_l0.long_term_pic_num[n] = b->ue(); + } + } while (sh->rplr.reorder_l0.reordering_of_pic_nums_idc[n] != 3 && !b->eof()); + } + } + if (is_slice_type(sh->slice_type, SH_SLICE_TYPE_B)) + { + sh->rplr.ref_pic_list_reordering_flag_l1 = b->u1(); + if (sh->rplr.ref_pic_list_reordering_flag_l1) + { + int n = -1; + do + { + n++; + sh->rplr.reorder_l1.reordering_of_pic_nums_idc[n] = b->ue(); + if (sh->rplr.reorder_l1.reordering_of_pic_nums_idc[n] == 0 || + sh->rplr.reorder_l1.reordering_of_pic_nums_idc[n] == 1) + { + sh->rplr.reorder_l1.abs_diff_pic_num_minus1[n] = b->ue(); + } + else if (sh->rplr.reorder_l1.reordering_of_pic_nums_idc[n] == 2) + { + sh->rplr.reorder_l1.long_term_pic_num[n] = b->ue(); + } + } while (sh->rplr.reorder_l1.reordering_of_pic_nums_idc[n] != 3 && !b->eof()); + } + } + } + constexpr void read_pred_weight_table(SliceHeader* sh, const SPS* sps, const PPS* pps, Bitstream* b) + { + (void)pps; + sh->pwt.luma_log2_weight_denom = b->ue(); + if (sps->chroma_format_idc != 0) + { + sh->pwt.chroma_log2_weight_denom = b->ue(); + } + for (int i = 0; i <= sh->num_ref_idx_l0_active_minus1; i++) + { + sh->pwt.luma_weight_l0_flag[i] = b->u1(); + if (sh->pwt.luma_weight_l0_flag[i]) + { + sh->pwt.luma_weight_l0[i] = b->se(); + sh->pwt.luma_offset_l0[i] = b->se(); + } + if (sps->chroma_format_idc != 0) + { + sh->pwt.chroma_weight_l0_flag[i] = b->u1(); + if (sh->pwt.chroma_weight_l0_flag[i]) + { + for (int j = 0; j < 2; j++) + { + sh->pwt.chroma_weight_l0[i][j] = b->se(); + sh->pwt.chroma_offset_l0[i][j] = b->se(); + } + } + } + } + if (is_slice_type(sh->slice_type, SH_SLICE_TYPE_B)) + { + for (int i = 0; i <= sh->num_ref_idx_l1_active_minus1; i++) + { + sh->pwt.luma_weight_l1_flag[i] = b->u1(); + if (sh->pwt.luma_weight_l1_flag[i]) + { + sh->pwt.luma_weight_l1[i] = b->se(); + sh->pwt.luma_offset_l1[i] = b->se(); + } + if (sps->chroma_format_idc != 0) + { + sh->pwt.chroma_weight_l1_flag[i] = b->u1(); + if (sh->pwt.chroma_weight_l1_flag[i]) + { + for (int j = 0; j < 2; j++) + { + sh->pwt.chroma_weight_l1[i][j] = b->se(); + sh->pwt.chroma_offset_l1[i][j] = b->se(); + } + } + } + } + } + } + constexpr void read_dec_ref_pic_marking(SliceHeader* sh, NALHeader* nal, Bitstream* b) + { + if (nal->type == 5) + { + sh->drpm.no_output_of_prior_pics_flag = b->u1(); + sh->drpm.long_term_reference_flag = b->u1(); + } + else + { + sh->drpm.adaptive_ref_pic_marking_mode_flag = b->u1(); + if (sh->drpm.adaptive_ref_pic_marking_mode_flag) + { + int n = -1; + do + { + n++; + sh->drpm.memory_management_control_operation[n] = b->ue(); + if (sh->drpm.memory_management_control_operation[n] == 1 || + sh->drpm.memory_management_control_operation[n] == 3) + { + sh->drpm.difference_of_pic_nums_minus1[n] = b->ue(); + } + if (sh->drpm.memory_management_control_operation[n] == 2) + { + sh->drpm.long_term_pic_num[n] = b->ue(); + } + if (sh->drpm.memory_management_control_operation[n] == 3 || + sh->drpm.memory_management_control_operation[n] == 6) + { + sh->drpm.long_term_frame_idx[n] = b->ue(); + } + if (sh->drpm.memory_management_control_operation[n] == 4) + { + sh->drpm.max_long_term_frame_idx_plus1[n] = b->ue(); + } + } while (sh->drpm.memory_management_control_operation[n] != 0 && !b->eof()); + } + } + } + constexpr void read_slice_header(SliceHeader* sh, NALHeader* nal, const PPS* pps_array, const SPS* sps_array, Bitstream* b) + { + sh->first_mb_in_slice = b->ue(); + sh->slice_type = b->ue(); + sh->pic_parameter_set_id = b->ue(); + + const PPS* pps = pps_array + sh->pic_parameter_set_id; + const SPS* sps = sps_array + pps->seq_parameter_set_id; + + sh->frame_num = b->u(sps->log2_max_frame_num_minus4 + 4); // was u(v) + if (!sps->frame_mbs_only_flag) + { + sh->field_pic_flag = b->u1(); + if (sh->field_pic_flag) + { + sh->bottom_field_flag = b->u1(); + } + } + if (nal->type == 5) + { + sh->idr_pic_id = b->ue(); + } + if (sps->pic_order_cnt_type == 0) + { + sh->pic_order_cnt_lsb = b->u(sps->log2_max_pic_order_cnt_lsb_minus4 + 4); // was u(v) + if (pps->pic_order_present_flag && !sh->field_pic_flag) + { + sh->delta_pic_order_cnt_bottom = b->se(); + } + } + if (sps->pic_order_cnt_type == 1 && !sps->delta_pic_order_always_zero_flag) + { + sh->delta_pic_order_cnt[0] = b->se(); + if (pps->pic_order_present_flag && !sh->field_pic_flag) + { + sh->delta_pic_order_cnt[1] = b->se(); + } + } + if (pps->redundant_pic_cnt_present_flag) + { + sh->redundant_pic_cnt = b->ue(); + } + if (is_slice_type(sh->slice_type, SH_SLICE_TYPE_B)) + { + sh->direct_spatial_mv_pred_flag = b->u1(); + } + if (is_slice_type(sh->slice_type, SH_SLICE_TYPE_P) || is_slice_type(sh->slice_type, SH_SLICE_TYPE_SP) || is_slice_type(sh->slice_type, SH_SLICE_TYPE_B)) + { + // H.264 spec 7.4.3: when num_ref_idx_active_override_flag is + // zero, the slice's effective num_ref_idx_l[01]_active_minus1 + // defaults to the PPS num_ref_idx_l[01]_default_active_minus1. + sh->num_ref_idx_l0_active_minus1 = pps->num_ref_idx_l0_active_minus1; + sh->num_ref_idx_l1_active_minus1 = pps->num_ref_idx_l1_active_minus1; + sh->num_ref_idx_active_override_flag = b->u1(); + if (sh->num_ref_idx_active_override_flag) + { + sh->num_ref_idx_l0_active_minus1 = b->ue(); + if (is_slice_type(sh->slice_type, SH_SLICE_TYPE_B)) + { + sh->num_ref_idx_l1_active_minus1 = b->ue(); + } + } + } + read_ref_pic_list_reordering(sh, b); + if ((pps->weighted_pred_flag && (is_slice_type(sh->slice_type, SH_SLICE_TYPE_P) || is_slice_type(sh->slice_type, SH_SLICE_TYPE_SP))) || + (pps->weighted_bipred_idc == 1 && is_slice_type(sh->slice_type, SH_SLICE_TYPE_B))) + { + read_pred_weight_table(sh, sps, pps, b); + } + if (nal->idc != 0) + { + read_dec_ref_pic_marking(sh, nal, b); + } + if (pps->entropy_coding_mode_flag && !is_slice_type(sh->slice_type, SH_SLICE_TYPE_I) && !is_slice_type(sh->slice_type, SH_SLICE_TYPE_SI)) + { + sh->cabac_init_idc = b->ue(); + } + sh->slice_qp_delta = b->se(); + if (is_slice_type(sh->slice_type, SH_SLICE_TYPE_SP) || is_slice_type(sh->slice_type, SH_SLICE_TYPE_SI)) + { + if (is_slice_type(sh->slice_type, SH_SLICE_TYPE_SP)) + { + sh->sp_for_switch_flag = b->u1(); + } + sh->slice_qs_delta = b->se(); + } + if (pps->deblocking_filter_control_present_flag) + { + sh->disable_deblocking_filter_idc = b->ue(); + if (sh->disable_deblocking_filter_idc != 1) + { + sh->slice_alpha_c0_offset_div2 = b->se(); + sh->slice_beta_offset_div2 = b->se(); + } + } + if (pps->num_slice_groups_minus1 > 0 && + pps->slice_group_map_type >= 3 && pps->slice_group_map_type <= 5) + { + const int v = intlog2(pps->pic_size_in_map_units_minus1 + pps->slice_group_change_rate_minus1 + 1); + sh->slice_group_change_cycle = b->u(v); + } + } + +} + +#endif // H264_H + +//Copyright(c) 2025 Turánszki János +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files(the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and /or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions : +// +//The above copyright notice and this permission notice shall be included in +//all copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +//THE SOFTWARE. diff --git a/3rdparty/l-smash/LICENSE b/3rdparty/l-smash/LICENSE new file mode 100644 index 000000000..80614e2f7 --- /dev/null +++ b/3rdparty/l-smash/LICENSE @@ -0,0 +1,13 @@ +Copyright (C) 2010-2017 L-SMASH project + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/3rdparty/l-smash/codecs/a52.c b/3rdparty/l-smash/codecs/a52.c new file mode 100644 index 000000000..3a03f58cb --- /dev/null +++ b/3rdparty/l-smash/codecs/a52.c @@ -0,0 +1,768 @@ +/***************************************************************************** + * a52.c + ***************************************************************************** + * Copyright (C) 2012-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include +#include +#include + +#include "core/box.h" + +static const char *bit_stream_mode[] = + { + "Main audio service: complete main (CM)", + "Main audio service: music and effects (ME)", + "Associated service: visually impaired (VI)", + "Associated service: hearing impaired (HI)", + "Associated service: dialogue (D)", + "Associated service: commentary (C)", + "Associated service: emergency (E)", + "Undefined service", + "Associated service: voice over (VO)", /* only if acmod == 0b001 */ + "Main audio service: karaoke" + }; + +/* For karaoke mode, C->M, S->V1, SL->V1 and SR->V2. */ +static const char *audio_coding_mode[] = + { + "1 + 1: Dual mono", + "1/0: C", + "2/0: L, R", + "3/0: L, C, R", + "2/1: L, R, S", + "3/1: L, C, R, S", + "2/2: L, R, SL, SR", + "3/2: L, C, R, SL, SR", + "Undefined audio coding mode", + "Undefined audio coding mode", + "2/0: L, R", + "3/0: L, M, R", + "2/1: L, R, V1", + "3/1: L, M, R, V1", + "2/2: L, R, V1, V2", + "3/2: L, M, R, V1, V2" + }; + +/*************************************************************************** + AC-3 tools + ETSI TS 102 366 V1.2.1 (2008-08) +***************************************************************************/ +#include "a52.h" + +#define AC3_SPECIFIC_BOX_LENGTH 11 + +const uint32_t ac3_sample_rate_table [4] = { 48000, 44100, 32000, 0 }; +const uint32_t ac3_channel_count_table[8] = { 2, 1, 2, 3, 3, 4, 4, 5 }; + +uint32_t ac3_get_sample_rate( lsmash_ac3_specific_parameters_t *dac3_param ) +{ + /* The value 3 (or 0b11) of fscod is reserved. */ + uint32_t samplerate = ac3_sample_rate_table[ dac3_param->fscod ]; + if( samplerate == 0 ) + lsmash_log( NULL, LSMASH_LOG_WARNING, "Unknown sampling rate is detected.\n" ); + return samplerate; +} + +#if 0 +/* FIXME: though this table is for AC-3 in QTFF, we don't support it yet since the structure of CODEC specific info is unknown. + * ChannelLayout is given by ac3_channel_layout_table[ acmod ][ lfeon ]. */ +static const lsmash_channel_layout_tag ac3_channel_layout_table[8][2] = +{ + /* LFE: off LFE: on */ + { QT_CHANNEL_LAYOUT_UNKNOWN, QT_CHANNEL_LAYOUT_UNKNOWN }, /* FIXME: dual mono */ + { QT_CHANNEL_LAYOUT_MONO, QT_CHANNEL_LAYOUT_AC3_1_0_1 }, + { QT_CHANNEL_LAYOUT_STEREO, QT_CHANNEL_LAYOUT_DVD_4 }, + { QT_CHANNEL_LAYOUT_AC3_3_0, QT_CHANNEL_LAYOUT_AC3_3_0_1 }, + { QT_CHANNEL_LAYOUT_DVD_2, QT_CHANNEL_LAYOUT_AC3_2_1_1 }, + { QT_CHANNEL_LAYOUT_AC3_3_1, QT_CHANNEL_LAYOUT_AC3_3_1_1 }, + { QT_CHANNEL_LAYOUT_DVD_3, QT_CHANNEL_LAYOUT_DVD_18 }, + { QT_CHANNEL_LAYOUT_MPEG_5_0_C, QT_CHANNEL_LAYOUT_MPEG_5_1_C } +}; +#endif + +uint8_t *lsmash_create_ac3_specific_info( lsmash_ac3_specific_parameters_t *param, uint32_t *data_length ) +{ + lsmash_bits_t bits = { 0 }; + lsmash_bs_t bs = { 0 }; + lsmash_bits_init( &bits, &bs ); + uint8_t buffer[AC3_SPECIFIC_BOX_LENGTH] = { 0 }; + bs.buffer.data = buffer; + bs.buffer.alloc = AC3_SPECIFIC_BOX_LENGTH; + lsmash_bits_put( &bits, 32, AC3_SPECIFIC_BOX_LENGTH ); /* box size */ + lsmash_bits_put( &bits, 32, ISOM_BOX_TYPE_DAC3.fourcc ); /* box type: 'dac3' */ + lsmash_bits_put( &bits, 2, param->fscod ); + lsmash_bits_put( &bits, 5, param->bsid ); + lsmash_bits_put( &bits, 3, param->bsmod ); + lsmash_bits_put( &bits, 3, param->acmod ); + lsmash_bits_put( &bits, 1, param->lfeon ); + lsmash_bits_put( &bits, 5, param->frmsizecod >> 1 ); + lsmash_bits_put( &bits, 5, 0 ); + uint8_t *data = lsmash_bits_export_data( &bits, data_length ); + lsmash_bits_empty( &bits ); + return data; +} + +int lsmash_setup_ac3_specific_parameters_from_syncframe( lsmash_ac3_specific_parameters_t *param, uint8_t *data, uint32_t data_length ) +{ + if( !data || data_length < AC3_MIN_SYNCFRAME_LENGTH ) + return LSMASH_ERR_FUNCTION_PARAM; + /* Check a syncword. */ + if( data[0] != 0x0b + || data[1] != 0x77 ) + return LSMASH_ERR_INVALID_DATA; + lsmash_bits_t bits = { 0 }; + lsmash_bs_t bs = { 0 }; + uint8_t buffer[AC3_MAX_SYNCFRAME_LENGTH] = { 0 }; + bs.buffer.data = buffer; + bs.buffer.store = data_length; + bs.buffer.alloc = AC3_MAX_SYNCFRAME_LENGTH; + ac3_info_t info = { .bits = &bits }; + lsmash_bits_init( &bits, &bs ); + memcpy( buffer, data, LSMASH_MIN( data_length, AC3_MAX_SYNCFRAME_LENGTH ) ); + int err = ac3_parse_syncframe_header( &info ); + if( err < 0 ) + return err; + *param = info.dac3_param; + return 0; +} + +static int ac3_check_syncframe_header( lsmash_ac3_specific_parameters_t *param ) +{ + if( param->fscod == 0x3 ) + return LSMASH_ERR_INVALID_DATA; /* unknown Sample Rate Code */ + if( param->frmsizecod > 0x25 ) + return LSMASH_ERR_INVALID_DATA; /* unknown Frame Size Code */ + if( param->bsid >= 10 ) + return LSMASH_ERR_INVALID_DATA; /* might be EAC-3 */ + return 0; +} + +int ac3_parse_syncframe_header( ac3_info_t *info ) +{ + lsmash_bits_t *bits = info->bits; + lsmash_ac3_specific_parameters_t *param = &info->dac3_param; + lsmash_bits_get( bits, 32 ); /* syncword + crc1 */ + param->fscod = lsmash_bits_get( bits, 2 ); + param->frmsizecod = lsmash_bits_get( bits, 6 ); + param->bsid = lsmash_bits_get( bits, 5 ); + param->bsmod = lsmash_bits_get( bits, 3 ); + param->acmod = lsmash_bits_get( bits, 3 ); + if( (param->acmod & 0x01) && (param->acmod != 0x01) ) + lsmash_bits_get( bits, 2 ); /* cmixlev */ + if( param->acmod & 0x04 ) + lsmash_bits_get( bits, 2 ); /* surmixlev */ + if( param->acmod == 0x02 ) + lsmash_bits_get( bits, 2 ); /* dsurmod */ + param->lfeon = lsmash_bits_get( bits, 1 ); + lsmash_bits_get_align( bits ); + return ac3_check_syncframe_header( param ); +} + +int ac3_construct_specific_parameters( lsmash_codec_specific_t *dst, lsmash_codec_specific_t *src ) +{ + assert( dst && dst->data.structured && src && src->data.unstructured ); + if( src->size < AC3_SPECIFIC_BOX_LENGTH ) + return LSMASH_ERR_INVALID_DATA; + lsmash_ac3_specific_parameters_t *param = (lsmash_ac3_specific_parameters_t *)dst->data.structured; + uint8_t *data = src->data.unstructured; + uint64_t size = LSMASH_GET_BE32( data ); + data += ISOM_BASEBOX_COMMON_SIZE; + if( size == 1 ) + { + size = LSMASH_GET_BE64( data ); + data += 8; + } + if( size != src->size ) + return LSMASH_ERR_INVALID_DATA; + param->fscod = (data[0] >> 6) & 0x03; /* XXxx xxxx xxxx xxxx xxxx xxxx */ + param->bsid = (data[0] >> 1) & 0x1F; /* xxXX XXXx xxxx xxxx xxxx xxxx */ + param->bsmod = ((data[0] & 0x01) << 2) | ((data[2] >> 6) & 0x03); /* xxxx xxxX XXxx xxxx xxxx xxxx */ + param->acmod = (data[1] >> 3) & 0x07; /* xxxx xxxx xxXX Xxxx xxxx xxxx */ + param->lfeon = (data[1] >> 2) & 0x01; /* xxxx xxxx xxxx xXxx xxxx xxxx */ + param->frmsizecod = ((data[1] & 0x03) << 3) | ((data[3] >> 5) & 0x07); /* xxxx xxxx xxxx xxXX XXXx xxxx */ + param->frmsizecod <<= 1; + return 0; +} + +int ac3_print_codec_specific( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + assert( box->manager & LSMASH_BINARY_CODED_BOX ); + int indent = level; + lsmash_ifprintf( fp, indent++, "[%s: AC3 Specific Box]\n", isom_4cc2str( box->type.fourcc ) ); + lsmash_ifprintf( fp, indent, "position = %"PRIu64"\n", box->pos ); + lsmash_ifprintf( fp, indent, "size = %"PRIu64"\n", box->size ); + if( box->size < AC3_SPECIFIC_BOX_LENGTH ) + return LSMASH_ERR_INVALID_DATA; + uint8_t *data = box->binary; + isom_skip_box_common( &data ); + uint8_t fscod = (data[0] >> 6) & 0x03; + uint8_t bsid = (data[0] >> 1) & 0x1F; + uint8_t bsmod = ((data[0] & 0x01) << 2) | ((data[1] >> 6) & 0x03); + uint8_t acmod = (data[1] >> 3) & 0x07; + uint8_t lfeon = (data[1] >> 2) & 0x01; + uint8_t bit_rate_code = ((data[1] & 0x03) << 3) | ((data[2] >> 5) & 0x07); + if( fscod != 0x03 ) + lsmash_ifprintf( fp, indent, "fscod = %"PRIu8" (%"PRIu32" Hz)\n", fscod, ac3_sample_rate_table[fscod] ); + else + lsmash_ifprintf( fp, indent, "fscod = 0x03 (reserved)\n" ); + lsmash_ifprintf( fp, indent, "bsid = %"PRIu8"\n", bsid ); + lsmash_ifprintf( fp, indent, "bsmod = %"PRIu8" (%s)\n", bsmod, bit_stream_mode[bsmod + (acmod == 0x01 ? 1 : acmod > 0x01 ? 2 : 0)] ); + lsmash_ifprintf( fp, indent, "acmod = %"PRIu8" (%s)\n", acmod, audio_coding_mode[acmod + (bsmod == 0x07 ? 8 : 0)] ); + lsmash_ifprintf( fp, indent, "lfeon = %s\n", lfeon ? "1 (LFE)" : "0" ); + static const uint32_t bit_rate[] = + { + 32, 40, 48, 56, 64, 80, 96, 112, 128, + 160, 192, 224, 256, 320, 384, 448, 512, 576, 640, + 0 /* undefined */ + }; + lsmash_ifprintf( fp, indent, "bit_rate_code = 0x%02"PRIx8" (%"PRIu32" kbit/s)\n", bit_rate_code, bit_rate[bit_rate_code] ); + lsmash_ifprintf( fp, indent, "reserved = 0x%02"PRIx8"\n", data[2] & 0x1F ); + return 0; +} + +#undef AC3_SPECIFIC_BOX_LENGTH + +/*************************************************************************** + Enhanced AC-3 tools + ETSI TS 102 366 V1.2.1 (2008-08) +***************************************************************************/ + +const uint8_t eac3_audio_block_table[4] = { 1, 2, 3, 6 }; + +uint8_t *lsmash_create_eac3_specific_info( lsmash_eac3_specific_parameters_t *param, uint32_t *data_length ) +{ +#define EAC3_SPECIFIC_BOX_MAX_LENGTH 42 + if( param->num_ind_sub > 7 ) + return NULL; + lsmash_bits_t bits = { 0 }; + lsmash_bs_t bs = { 0 }; + lsmash_bits_init( &bits, &bs ); + uint8_t buffer[EAC3_SPECIFIC_BOX_MAX_LENGTH] = { 0 }; + bs.buffer.data = buffer; + bs.buffer.alloc = EAC3_SPECIFIC_BOX_MAX_LENGTH; + lsmash_bits_put( &bits, 32, 0 ); /* box size */ + lsmash_bits_put( &bits, 32, ISOM_BOX_TYPE_DEC3.fourcc ); /* box type: 'dec3' */ + lsmash_bits_put( &bits, 13, param->data_rate ); /* data_rate; setup by isom_update_bitrate_description */ + lsmash_bits_put( &bits, 3, param->num_ind_sub ); + /* Apparently, the condition of this loop defined in ETSI TS 102 366 V1.2.1 (2008-08) is wrong. */ + for( int i = 0; i <= param->num_ind_sub; i++ ) + { + lsmash_eac3_substream_info_t *independent_info = ¶m->independent_info[i]; + lsmash_bits_put( &bits, 2, independent_info->fscod ); + lsmash_bits_put( &bits, 5, independent_info->bsid ); + lsmash_bits_put( &bits, 5, independent_info->bsmod ); + lsmash_bits_put( &bits, 3, independent_info->acmod ); + lsmash_bits_put( &bits, 1, independent_info->lfeon ); + lsmash_bits_put( &bits, 3, 0 ); /* reserved */ + lsmash_bits_put( &bits, 4, independent_info->num_dep_sub ); + if( independent_info->num_dep_sub > 0 ) + lsmash_bits_put( &bits, 9, independent_info->chan_loc ); + else + lsmash_bits_put( &bits, 1, 0 ); /* reserved */ + } + uint8_t *data = lsmash_bits_export_data( &bits, data_length ); + lsmash_bits_empty( &bits ); + /* Update box size. */ + LSMASH_SET_BE32( data, *data_length ); + return data; +#undef EAC3_SPECIFIC_BOX_MAX_LENGTH +} + +/* Return -1 if incomplete Enhanced AC-3 sample is given. */ +int lsmash_setup_eac3_specific_parameters_from_frame( lsmash_eac3_specific_parameters_t *param, uint8_t *data, uint32_t data_length ) +{ + if( !data || data_length < 5 ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_bits_t bits = { 0 }; + lsmash_bs_t bs = { 0 }; + uint8_t buffer[EAC3_MAX_SYNCFRAME_LENGTH] = { 0 }; + bs.buffer.data = buffer; + bs.buffer.store = data_length; + bs.buffer.alloc = EAC3_MAX_SYNCFRAME_LENGTH; + eac3_info_t *info = &(eac3_info_t){ .bits = &bits }; + lsmash_bits_init( &bits, &bs ); + memcpy( buffer, data, LSMASH_MIN( data_length, EAC3_MAX_SYNCFRAME_LENGTH ) ); + uint64_t next_frame_pos = 0; + while( 1 ) + { + /* Seek to the head of the next syncframe. */ + bs.buffer.pos = LSMASH_MIN( data_length, next_frame_pos ); + /* Check the remainder length of the input data. + * If there is enough length, then parse the syncframe in it. + * The length 5 is the required byte length to get frame size. */ + uint64_t remain_size = lsmash_bs_get_remaining_buffer_size( &bs ); + if( bs.eob || (bs.eof && remain_size < 5) ) + goto setup_param; /* No more valid data. */ + /* Check the syncword. */ + if( lsmash_bs_show_byte( &bs, 0 ) != 0x0b + || lsmash_bs_show_byte( &bs, 1 ) != 0x77 ) + goto setup_param; + /* Parse syncframe. */ + info->frame_size = 0; + if( eac3_parse_syncframe( info ) < 0 ) + goto setup_param; + if( remain_size < info->frame_size ) + goto setup_param; + int independent = info->strmtyp != 0x1; + if( independent && info->substreamid == 0x0 ) + { + if( info->number_of_audio_blocks == 6 ) + { + /* Encountered the first syncframe of the next access unit. */ + info->number_of_audio_blocks = 0; + goto setup_param; + } + else if( info->number_of_audio_blocks > 6 ) + goto setup_param; + info->number_of_audio_blocks += eac3_audio_block_table[ info->numblkscod ]; + info->number_of_independent_substreams = 0; + } + else if( info->syncframe_count == 0 ) + /* The first syncframe in an AU must be independent and assigned substream ID 0. */ + return LSMASH_ERR_INVALID_DATA; + if( independent ) + info->independent_info[info->number_of_independent_substreams ++].num_dep_sub = 0; + else + ++ info->independent_info[info->number_of_independent_substreams - 1].num_dep_sub; + next_frame_pos += info->frame_size; + ++ info->syncframe_count; + } +setup_param: + if( info->number_of_independent_substreams == 0 || info->number_of_independent_substreams > 8 ) + return LSMASH_ERR_INVALID_DATA; + if( !info->dec3_param_initialized ) + eac3_update_specific_param( info ); + *param = info->dec3_param; + return info->number_of_audio_blocks == 6 ? 0 : LSMASH_ERR_INVALID_DATA; +} + +uint16_t lsmash_eac3_get_chan_loc_from_chanmap( uint16_t chanmap ) +{ + return ((chanmap & 0x7f8) >> 2) | ((chanmap & 0x2) >> 1); +} + +static int eac3_check_syncframe_header( eac3_info_t *info ) +{ + if( info->strmtyp == 0x3 ) + return LSMASH_ERR_INVALID_DATA; /* unknown Stream type */ + lsmash_eac3_substream_info_t *substream_info; + if( info->strmtyp != 0x1 ) + substream_info = &info->independent_info[ info->current_independent_substream_id ]; + else + substream_info = &info->dependent_info; + if( substream_info->fscod == 0x3 && info->fscod2 == 0x3 ) + return LSMASH_ERR_INVALID_DATA; /* unknown Sample Rate Code */ + if( substream_info->bsid < 10 || substream_info->bsid > 16 ) + return LSMASH_ERR_INVALID_DATA; /* not EAC-3 */ + return 0; +} + +int eac3_parse_syncframe( eac3_info_t *info ) +{ + lsmash_bits_t *bits = info->bits; + lsmash_bits_get( bits, 16 ); /* syncword (16) */ + info->strmtyp = lsmash_bits_get( bits, 2 ); /* strmtyp (2) */ + info->substreamid = lsmash_bits_get( bits, 3 ); /* substreamid (3) */ + lsmash_eac3_substream_info_t *substream_info; + if( info->strmtyp != 0x1 ) + { + if( info->substreamid == 0x0 && info->number_of_independent_substreams ) + eac3_update_specific_param( info ); + info->current_independent_substream_id = info->substreamid; + substream_info = &info->independent_info[ info->current_independent_substream_id ]; + substream_info->chan_loc = 0; + } + else + substream_info = &info->dependent_info; + info->frame_size = 2 * (lsmash_bits_get( bits, 11 ) + 1); /* frmsiz (11) */ + substream_info->fscod = lsmash_bits_get( bits, 2 ); /* fscod (2) */ + if( substream_info->fscod == 0x3 ) + { + info->fscod2 = lsmash_bits_get( bits, 2 ); /* fscod2 (2) */ + info->numblkscod = 0x3; + } + else + info->numblkscod = lsmash_bits_get( bits, 2 ); /* numblkscod (2) */ + substream_info->acmod = lsmash_bits_get( bits, 3 ); /* acmod (3) */ + substream_info->lfeon = lsmash_bits_get( bits, 1 ); /* lfeon (1) */ + substream_info->bsid = lsmash_bits_get( bits, 5 ); /* bsid (5) */ + lsmash_bits_get( bits, 5 ); /* dialnorm (5) */ + if( lsmash_bits_get( bits, 1 ) ) /* compre (1) */ + lsmash_bits_get( bits, 8 ); /* compr (8) */ + if( substream_info->acmod == 0x0 ) + { + lsmash_bits_get( bits, 5 ); /* dialnorm2 (5) */ + if( lsmash_bits_get( bits, 1 ) ) /* compre2 (1) */ + lsmash_bits_get( bits, 8 ); /* compr2 (8) */ + } + if( info->strmtyp == 0x1 && lsmash_bits_get( bits, 1 ) ) /* chanmape (1) */ + { + uint16_t chanmap = lsmash_bits_get( bits, 16 ); /* chanmap (16) */ + info->independent_info[ info->current_independent_substream_id ].chan_loc |= lsmash_eac3_get_chan_loc_from_chanmap( chanmap ); + } + if( lsmash_bits_get( bits, 1 ) ) /* mixmdate (1) */ + { + if( substream_info->acmod > 0x2 ) + lsmash_bits_get( bits, 2 ); /* dmixmod (2) */ + if( ((substream_info->acmod & 0x1) && (substream_info->acmod > 0x2)) || (substream_info->acmod & 0x4) ) + lsmash_bits_get( bits, 6 ); /* ltrt[c/sur]mixlev (3) + * loro[c/sur]mixlev (3) */ + if( substream_info->lfeon && lsmash_bits_get( bits, 1 ) ) /* lfemixlevcode (1) */ + lsmash_bits_get( bits, 5 ); /* lfemixlevcod (5) */ + if( info->strmtyp == 0x0 ) + { + if( lsmash_bits_get( bits, 1 ) ) /* pgmscle (1) */ + lsmash_bits_get( bits, 6 ); /* pgmscl (6) */ + if( substream_info->acmod == 0x0 && lsmash_bits_get( bits, 1 ) ) /* pgmscle2 (1) */ + lsmash_bits_get( bits, 6 ); /* pgmscl2 (6) */ + if( lsmash_bits_get( bits, 1 ) ) /* extpgmscle (1) */ + lsmash_bits_get( bits, 6 ); /* extpgmscl (6) */ + uint8_t mixdef = lsmash_bits_get( bits, 2 ); /* mixdef (2) */ + if( mixdef == 0x1 ) + lsmash_bits_get( bits, 5 ); /* premixcmpsel (1) + * drcsrc (1) + * premixcmpscl (3) */ + else if( mixdef == 0x2 ) + lsmash_bits_get( bits, 12 ); /* mixdata (12) */ + else if( mixdef == 0x3 ) + { + uint8_t mixdeflen = lsmash_bits_get( bits, 5 ); /* mixdeflen (5) */ + lsmash_bits_get( bits, 8 * (mixdeflen + 2) ); /* mixdata (8 * (mixdeflen + 2)) + * mixdatafill (0-7) */ + } + if( substream_info->acmod < 0x2 ) + { + if( lsmash_bits_get( bits, 1 ) ) /* paninfoe (1) */ + lsmash_bits_get( bits, 14 ); /* panmean (8) + * paninfo (6) */ + if( substream_info->acmod == 0x0 && lsmash_bits_get( bits, 1 ) ) /* paninfo2e (1) */ + lsmash_bits_get( bits, 14 ); /* panmean2 (8) + * paninfo2 (6) */ + } + if( lsmash_bits_get( bits, 1 ) ) /* frmmixcfginfoe (1) */ + { + if( info->numblkscod == 0x0 ) + lsmash_bits_get( bits, 5 ); /* blkmixcfginfo[0] (5) */ + else + { + int number_of_blocks_per_syncframe = ((int []){ 1, 2, 3, 6 })[ info->numblkscod ]; + for( int blk = 0; blk < number_of_blocks_per_syncframe; blk++ ) + if( lsmash_bits_get( bits, 1 ) ) /* blkmixcfginfoe (1)*/ + lsmash_bits_get( bits, 5 ); /* blkmixcfginfo[blk] (5) */ + } + } + } + } + if( lsmash_bits_get( bits, 1 ) ) /* infomdate (1) */ + { + substream_info->bsmod = lsmash_bits_get( bits, 3 ); /* bsmod (3) */ + lsmash_bits_get( bits, 1 ); /* copyrightb (1) */ + lsmash_bits_get( bits, 1 ); /* origbs (1) */ + if( substream_info->acmod == 0x2 ) + lsmash_bits_get( bits, 4 ); /* dsurmod (2) + * dheadphonmod (2) */ + else if( substream_info->acmod >= 0x6 ) + lsmash_bits_get( bits, 2 ); /* dsurexmod (2) */ + if( lsmash_bits_get( bits, 1 ) ) /* audprodie (1) */ + lsmash_bits_get( bits, 8 ); /* mixlevel (5) + * roomtyp (2) + * adconvtyp (1) */ + if( substream_info->acmod == 0x0 && lsmash_bits_get( bits, 1 ) ) /* audprodie2 (1) */ + lsmash_bits_get( bits, 8 ); /* mixlevel2 (5) + * roomtyp2 (2) + * adconvtyp2 (1) */ + if( substream_info->fscod < 0x3 ) + lsmash_bits_get( bits, 1 ); /* sourcefscod (1) */ + } + else + substream_info->bsmod = 0; + if( info->strmtyp == 0x0 && info->numblkscod != 0x3 ) + lsmash_bits_get( bits, 1 ); /* convsync (1) */ + if( info->strmtyp == 0x2 ) + { + int blkid; + if( info->numblkscod == 0x3 ) + blkid = 1; + else + blkid = lsmash_bits_get( bits, 1 ); /* blkid (1) */ + if( blkid ) + lsmash_bits_get( bits, 6 ); /* frmsizecod (6) */ + } + if( lsmash_bits_get( bits, 1 ) ) /* addbsie (1) */ + { + uint8_t addbsil = lsmash_bits_get( bits, 6 ); /* addbsil (6) */ + lsmash_bits_get( bits, (addbsil + 1) * 8 ); /* addbsi ((addbsil + 1) * 8) */ + } + lsmash_bits_get_align( bits ); + return eac3_check_syncframe_header( info ); +} + +void eac3_update_specific_param( eac3_info_t *info ) +{ + lsmash_eac3_specific_parameters_t *param = &info->dec3_param; + param->data_rate = 0; + param->num_ind_sub = info->number_of_independent_substreams - 1; + for( uint8_t i = 0; i <= param->num_ind_sub; i++ ) + param->independent_info[i] = info->independent_info[i]; + info->dec3_param_initialized = 1; +} + +#define EAC3_SPECIFIC_BOX_MIN_LENGTH 13 + +int eac3_construct_specific_parameters( lsmash_codec_specific_t *dst, lsmash_codec_specific_t *src ) +{ + assert( dst && dst->data.structured && src && src->data.unstructured ); + if( src->size < EAC3_SPECIFIC_BOX_MIN_LENGTH ) + return LSMASH_ERR_INVALID_DATA; + lsmash_eac3_specific_parameters_t *param = (lsmash_eac3_specific_parameters_t *)dst->data.structured; + uint8_t *data = src->data.unstructured; + uint64_t size = LSMASH_GET_BE32( data ); + data += ISOM_BASEBOX_COMMON_SIZE; + if( size == 1 ) + { + size = LSMASH_GET_BE64( data ); + data += 8; + } + if( size != src->size ) + return LSMASH_ERR_INVALID_DATA; + param->data_rate = (data[0] << 5) | ((data[1] >> 3) & 0x1F); /* XXXX XXXX XXXX Xxxx */ + param->num_ind_sub = data[1] & 0x07; /* xxxx xxxx xxxx xXXX */ + data += 2; + size -= 2; + for( int i = 0; i <= param->num_ind_sub; i++ ) + { + if( size < 3 ) + return LSMASH_ERR_INVALID_DATA; + lsmash_eac3_substream_info_t *independent_info = ¶m->independent_info[i]; + independent_info->fscod = (data[0] >> 6) & 0x03; /* XXxx xxxx xxxx xxxx xxxx xxxx */ + independent_info->bsid = (data[0] >> 1) & 0x1F; /* xxXX XXXx xxxx xxxx xxxx xxxx */ + independent_info->bsmod = ((data[0] & 0x01) << 4) | ((data[1] >> 4) & 0x0F); /* xxxx xxxX XXXX xxxx xxxx xxxx */ + independent_info->acmod = (data[1] >> 1) & 0x07; /* xxxx xxxx xxxx XXXx xxxx xxxx */ + independent_info->lfeon = data[1] & 0x01; /* xxxx xxxx xxxx xxxX xxxx xxxx */ + independent_info->num_dep_sub = (data[2] >> 1) & 0x0F; /* xxxx xxxx xxxx xxxx xxxX XXXx */ + data += 3; + size -= 3; + if( independent_info->num_dep_sub > 0 ) + { + if( size < 1 ) + return LSMASH_ERR_INVALID_DATA; + independent_info->chan_loc = ((data[-1] & 0x01) << 8) | data[0]; /* xxxx xxxX XXXX XXXX */ + data += 1; + size -= 1; + } + } + return 0; +} + +void eac3_update_sample_rate +( + uint32_t *frequency, + lsmash_eac3_specific_parameters_t *dec3_param, + uint8_t *fscod2 +) +{ + /* Additional independent substreams 1 to 7 must be encoded at the same sample rate as independent substream 0. */ + uint32_t samplerate = ac3_sample_rate_table[ dec3_param->independent_info[0].fscod ]; + if( samplerate == 0 && fscod2 ) + /* The value 3 (or 0b11) of fscod2 is reserved. */ + samplerate = ac3_sample_rate_table[ *fscod2 ] / 2; + if( samplerate != 0 ) + *frequency = samplerate; + else + lsmash_log( NULL, LSMASH_LOG_WARNING, "Unknown sampling rate is detected.\n" ); +} + +#if 0 +/* FIXME: though this table is for EAC-3 in QTFF, we don't support it yet since the structure of CODEC specific info is unknown. */ +static void eac3_update_channel_layout +( + lsmash_channel_layout_tag *layout_tag, + lsmash_eac3_substream_info_t *independent_info +) +{ + if( independent_info->chan_loc == 0 ) + { + *layout_tag = ac3_channel_layout_table[ independent_info->acmod ][ independent_info->lfeon ]; + return; + } + else if( independent_info->acmod != 0x7 ) + { + *layout_tag = QT_CHANNEL_LAYOUT_UNKNOWN; + return; + } + /* OK. All L, C, R, Ls and Rs exsist. */ + if( !independent_info->lfeon ) + { + if( independent_info->chan_loc == 0x80 ) + *layout_tag = QT_CHANNEL_LAYOUT_EAC_7_0_A; + else if( independent_info->chan_loc == 0x40 ) + *layout_tag = QT_CHANNEL_LAYOUT_EAC_6_0_A; + else + *layout_tag = QT_CHANNEL_LAYOUT_UNKNOWN; + return; + } + /* Also LFE exsists. */ + static const struct + { + uint16_t chan_loc; + lsmash_channel_layout_tag tag; + } eac3_channel_layout_table[] + = { + { 0x100, QT_CHANNEL_LAYOUT_EAC3_7_1_B }, + { 0x80, QT_CHANNEL_LAYOUT_EAC3_7_1_A }, + { 0x40, QT_CHANNEL_LAYOUT_EAC3_6_1_A }, + { 0x20, QT_CHANNEL_LAYOUT_EAC3_6_1_B }, + { 0x10, QT_CHANNEL_LAYOUT_EAC3_7_1_C }, + { 0x10, QT_CHANNEL_LAYOUT_EAC3_7_1_D }, + { 0x4, QT_CHANNEL_LAYOUT_EAC3_7_1_E }, + { 0x2, QT_CHANNEL_LAYOUT_EAC3_6_1_C }, + { 0x60, QT_CHANNEL_LAYOUT_EAC3_7_1_F }, + { 0x42, QT_CHANNEL_LAYOUT_EAC3_7_1_G }, + { 0x22, QT_CHANNEL_LAYOUT_EAC3_7_1_H }, + { 0 } + }; + for( int i = 0; eac3_channel_layout_table[i].chan_loc; i++ ) + if( independent_info->chan_loc == eac3_channel_layout_table[i].chan_loc ) + { + *layout_tag = eac3_channel_layout_table[i].tag; + return; + } + *layout_tag = QT_CHANNEL_LAYOUT_UNKNOWN; +} +#endif + +void eac3_update_channel_count +( + uint32_t *channels, + lsmash_eac3_specific_parameters_t *dec3_param +) +{ + /* The default programme selection should always be Programme 1. + * Thus, pick the number of channels of Programme 1. */ + lsmash_eac3_substream_info_t *independent_info = &dec3_param->independent_info[0]; + *channels = ac3_channel_count_table[ independent_info->acmod ] /* L/C/R/Ls/Rs combination */ + + 2 * !!(independent_info->chan_loc & 0x100) /* Lc/Rc pair */ + + 2 * !!(independent_info->chan_loc & 0x80) /* Lrs/Rrs pair */ + + !!(independent_info->chan_loc & 0x40) /* Cs */ + + !!(independent_info->chan_loc & 0x20) /* Ts */ + + 2 * !!(independent_info->chan_loc & 0x10) /* Lsd/Rsd pair */ + + 2 * !!(independent_info->chan_loc & 0x8) /* Lw/Rw pair */ + + 2 * !!(independent_info->chan_loc & 0x4) /* Lvh/Rvh pair */ + + !!(independent_info->chan_loc & 0x2) /* Cvh */ + + !!(independent_info->chan_loc & 0x1) /* LFE2 */ + + independent_info->lfeon; /* LFE */ +} + +int eac3_print_codec_specific( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + assert( box->manager & LSMASH_BINARY_CODED_BOX ); + int indent = level; + lsmash_ifprintf( fp, indent++, "[%s: EC3 Specific Box]\n", isom_4cc2str( box->type.fourcc ) ); + lsmash_ifprintf( fp, indent, "position = %"PRIu64"\n", box->pos ); + lsmash_ifprintf( fp, indent, "size = %"PRIu64"\n", box->size ); + if( box->size < EAC3_SPECIFIC_BOX_MIN_LENGTH ) + return LSMASH_ERR_INVALID_DATA; + uint8_t *data = box->binary; + isom_skip_box_common( &data ); + lsmash_ifprintf( fp, indent, "data_rate = %"PRIu16" kbit/s\n", (data[0] << 5) | ((data[1] >> 3) & 0x1F) ); + uint8_t num_ind_sub = data[1] & 0x07; + lsmash_ifprintf( fp, indent, "num_ind_sub = %"PRIu8"\n", num_ind_sub ); + data += 2; + for( int i = 0; i <= num_ind_sub; i++ ) + { + lsmash_ifprintf( fp, indent, "independent_substream[%d]\n", i ); + int sub_indent = indent + 1; + uint8_t fscod = (data[0] >> 6) & 0x03; + uint8_t bsid = (data[0] >> 1) & 0x1F; + uint8_t bsmod = ((data[0] & 0x01) << 4) | ((data[1] >> 4) & 0x0F); + uint8_t acmod = (data[1] >> 1) & 0x07; + uint8_t lfeon = data[1] & 0x01; + uint8_t num_dep_sub = (data[2] >> 1) & 0x0F; + if( fscod != 0x03 ) + lsmash_ifprintf( fp, sub_indent, "fscod = %"PRIu8" (%"PRIu32" Hz)\n", fscod, ac3_sample_rate_table[fscod] ); + else + lsmash_ifprintf( fp, sub_indent, "fscod = 0x03 (reduced sample rate)\n" ); + lsmash_ifprintf( fp, sub_indent, "bsid = %"PRIu8"\n", bsid ); + if( bsmod < 0x08 ) + lsmash_ifprintf( fp, sub_indent, "bsmod = %"PRIu8" (%s)\n", bsmod, bit_stream_mode[bsmod + (acmod == 0x01 ? 1 : acmod > 0x01 ? 2 : 0)] ); + else + lsmash_ifprintf( fp, sub_indent, "bsmod = %"PRIu8" (Undefined service)\n" ); + lsmash_ifprintf( fp, sub_indent, "acmod = %"PRIu8" (%s)\n", acmod, audio_coding_mode[acmod + (bsmod == 0x07 ? 8 : 0)] ); + lsmash_ifprintf( fp, sub_indent, "lfeon = %s\n", lfeon ? "1 (LFE)" : "0" ); + lsmash_ifprintf( fp, sub_indent, "num_dep_sub = %"PRIu8"\n", num_dep_sub ); + data += 3; + if( num_dep_sub > 0 ) + { + static const char *channel_location[] = + { + "LFE2", + "Cvh", + "Lvh/Rvh pair", + "Lw/Rw pair", + "Lsd/Rsd pair", + "Ts", + "Cs", + "Lrs/Rrs pair", + "Lc/Rc pair" + }; + uint16_t chan_loc = ((data[-1] & 0x01) << 8) | data[0]; + lsmash_ifprintf( fp, sub_indent, "chan_loc = 0x%04"PRIx16"\n", chan_loc ); + for( int j = 0; j < 9; j++ ) + if( (chan_loc >> j & 0x01) ) + lsmash_ifprintf( fp, sub_indent + 1, "%s\n", channel_location[j] ); + data += 1; + } + else + lsmash_ifprintf( fp, sub_indent, "reserved = %"PRIu8"\n", data[2] & 0x01 ); + } + return 0; +} + +int eac3_update_bitrate( isom_stbl_t *stbl, isom_mdhd_t *mdhd, uint32_t sample_description_index ) +{ + isom_audio_entry_t *eac3 = (isom_audio_entry_t *)lsmash_list_get_entry_data( &stbl->stsd->list, sample_description_index ); + if( LSMASH_IS_NON_EXISTING_BOX( eac3 ) ) + return LSMASH_ERR_INVALID_DATA; + isom_box_t *ext = isom_get_extension_box( &eac3->extensions, ISOM_BOX_TYPE_DEC3 ); + if( !((ext->manager & LSMASH_BINARY_CODED_BOX) && ext->binary && ext->size >= 10) ) + return LSMASH_ERR_INVALID_DATA; + uint16_t bitrate; + if( isom_is_variable_size( stbl ) ) + { + uint32_t bufferSizeDB; + uint32_t maxBitrate; + uint32_t avgBitrate; + int err = isom_calculate_bitrate_description( stbl, mdhd, &bufferSizeDB, &maxBitrate, &avgBitrate, sample_description_index ); + if( err < 0 ) + return err; + bitrate = maxBitrate / 1000; /* Use maximum bitrate if VBR. */ + } + else + bitrate = isom_get_first_sample_size( stbl ) * (eac3->samplerate >> 16) / 192000; /* 192000 == 1536 * 1000 / 8 */ + uint8_t *exdata = ext->binary + 8; + exdata[0] = (bitrate >> 5) & 0xff; + exdata[1] = (bitrate & 0x1f) << 3; + return 0; +} + +#undef EAC3_SPECIFIC_BOX_MIN_LENGTH diff --git a/3rdparty/l-smash/codecs/a52.h b/3rdparty/l-smash/codecs/a52.h new file mode 100644 index 000000000..2ba216afd --- /dev/null +++ b/3rdparty/l-smash/codecs/a52.h @@ -0,0 +1,94 @@ +/***************************************************************************** + * a52.h + ***************************************************************************** + * Copyright (C) 2012-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#define AC3_MIN_SYNCFRAME_LENGTH 128 +#define AC3_MAX_SYNCFRAME_LENGTH 3840 +#define EAC3_MAX_SYNCFRAME_LENGTH 4096 + +typedef struct +{ + lsmash_ac3_specific_parameters_t dac3_param; + lsmash_bits_t *bits; +} ac3_info_t; + +typedef struct +{ + lsmash_eac3_specific_parameters_t dec3_param; + lsmash_eac3_substream_info_t independent_info[8]; + lsmash_eac3_substream_info_t dependent_info; + uint8_t dec3_param_initialized; + uint8_t strmtyp; + uint8_t substreamid; + uint8_t current_independent_substream_id; + uint8_t fscod2; + uint8_t numblkscod; + uint8_t number_of_audio_blocks; + uint8_t number_of_independent_substreams; + uint32_t syncframe_count; + uint32_t frame_size; + lsmash_bits_t *bits; +} eac3_info_t; + +extern const uint32_t ac3_sample_rate_table [4]; +extern const uint32_t ac3_channel_count_table[8]; +extern const uint8_t eac3_audio_block_table [4]; + +static inline uint32_t ac3_get_channel_count +( + lsmash_ac3_specific_parameters_t *dac3_param +) +{ + return ac3_channel_count_table[ dac3_param->acmod ] + dac3_param->lfeon; +} + +uint32_t ac3_get_sample_rate +( + lsmash_ac3_specific_parameters_t *dac3_param +); + +int ac3_parse_syncframe_header +( + ac3_info_t *info +); + +int eac3_parse_syncframe +( + eac3_info_t *info +); + +void eac3_update_specific_param +( + eac3_info_t *info +); + +void eac3_update_sample_rate +( + uint32_t *frequency, + lsmash_eac3_specific_parameters_t *dec3_param, + uint8_t *fscod2 +); + +void eac3_update_channel_count +( + uint32_t *channels, + lsmash_eac3_specific_parameters_t *dec3_param +); diff --git a/3rdparty/l-smash/codecs/alac.c b/3rdparty/l-smash/codecs/alac.c new file mode 100644 index 000000000..7aa95c89c --- /dev/null +++ b/3rdparty/l-smash/codecs/alac.c @@ -0,0 +1,160 @@ +/***************************************************************************** + * alac.c + ***************************************************************************** + * Copyright (C) 2012-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include +#include +#include + +#include "core/box.h" + +#define ALAC_SPECIFIC_BOX_LENGTH 36 + +uint8_t *lsmash_create_alac_specific_info( lsmash_alac_specific_parameters_t *param, uint32_t *data_length ) +{ + uint8_t buffer[ALAC_SPECIFIC_BOX_LENGTH]; + lsmash_bs_t bs = { 0 }; + bs.buffer.data = buffer; + bs.buffer.alloc = ALAC_SPECIFIC_BOX_LENGTH; + lsmash_bs_put_be32( &bs, ALAC_SPECIFIC_BOX_LENGTH ); /* box size */ + lsmash_bs_put_be32( &bs, ISOM_BOX_TYPE_ALAC.fourcc ); /* box type: 'alac' */ + lsmash_bs_put_be32( &bs, 0 ); /* version + flags */ + lsmash_bs_put_be32( &bs, param->frameLength ); + lsmash_bs_put_byte( &bs, 0 ); /* compatibleVersion */ + lsmash_bs_put_byte( &bs, param->bitDepth ); + lsmash_bs_put_byte( &bs, 40 ); /* pb */ + lsmash_bs_put_byte( &bs, 14 ); /* mb */ + lsmash_bs_put_byte( &bs, 10 ); /* kb */ + lsmash_bs_put_byte( &bs, param->numChannels ); + lsmash_bs_put_be16( &bs, 255 ); /* maxRun */ + lsmash_bs_put_be32( &bs, param->maxFrameBytes ); + lsmash_bs_put_be32( &bs, param->avgBitrate ); + lsmash_bs_put_be32( &bs, param->sampleRate ); + return lsmash_bs_export_data( &bs, data_length ); +} + +int alac_construct_specific_parameters( lsmash_codec_specific_t *dst, lsmash_codec_specific_t *src ) +{ + assert( dst && dst->data.structured && src && src->data.unstructured ); + if( src->size < ALAC_SPECIFIC_BOX_LENGTH ) + return LSMASH_ERR_INVALID_DATA; + lsmash_alac_specific_parameters_t *param = (lsmash_alac_specific_parameters_t *)dst->data.structured; + uint8_t *data = src->data.unstructured; + uint64_t size = LSMASH_GET_BE32( data ); + data += ISOM_BASEBOX_COMMON_SIZE; + if( size == 1 ) + { + size = LSMASH_GET_BE64( data ); + data += 8; + } + if( size != src->size ) + return LSMASH_ERR_INVALID_DATA; + data += 4; /* Skip version and flags. */ + param->frameLength = LSMASH_GET_BE32( &data[0] ); + param->bitDepth = LSMASH_GET_BYTE( &data[5] ); + param->numChannels = LSMASH_GET_BYTE( &data[9] ); + param->maxFrameBytes = LSMASH_GET_BE32( &data[12] ); + param->avgBitrate = LSMASH_GET_BE32( &data[16] ); + param->sampleRate = LSMASH_GET_BE32( &data[20] ); + return 0; +} + +int alac_print_codec_specific( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + assert( box->manager & LSMASH_BINARY_CODED_BOX ); + int indent = level; + lsmash_ifprintf( fp, indent++, "[%s: ALAC Specific Box]\n", isom_4cc2str( box->type.fourcc ) ); + lsmash_ifprintf( fp, indent, "position = %"PRIu64"\n", box->pos ); + lsmash_ifprintf( fp, indent, "size = %"PRIu64"\n", box->size ); + if( box->size < ALAC_SPECIFIC_BOX_LENGTH ) + return LSMASH_ERR_INVALID_DATA; + uint8_t *data = box->binary; + isom_skip_box_common( &data ); + lsmash_ifprintf( fp, indent, "version = %"PRIu8"\n", LSMASH_GET_BYTE( &data[0] ) ); + lsmash_ifprintf( fp, indent, "flags = 0x%06"PRIx32"\n", LSMASH_GET_BE24( &data[1] ) ); + data += 4; + lsmash_ifprintf( fp, indent, "frameLength = %"PRIu32"\n", LSMASH_GET_BE32( &data[0] ) ); + lsmash_ifprintf( fp, indent, "compatibleVersion = %"PRIu8"\n", LSMASH_GET_BYTE( &data[4] ) ); + lsmash_ifprintf( fp, indent, "bitDepth = %"PRIu8"\n", LSMASH_GET_BYTE( &data[5] ) ); + lsmash_ifprintf( fp, indent, "pb = %"PRIu8"\n", LSMASH_GET_BYTE( &data[6] ) ); + lsmash_ifprintf( fp, indent, "mb = %"PRIu8"\n", LSMASH_GET_BYTE( &data[7] ) ); + lsmash_ifprintf( fp, indent, "kb = %"PRIu8"\n", LSMASH_GET_BYTE( &data[8] ) ); + lsmash_ifprintf( fp, indent, "numChannels = %"PRIu8"\n", LSMASH_GET_BYTE( &data[9] ) ); + lsmash_ifprintf( fp, indent, "maxRun = %"PRIu16"\n", LSMASH_GET_BE16( &data[10] ) ); + lsmash_ifprintf( fp, indent, "maxFrameBytes = %"PRIu32"\n", LSMASH_GET_BE32( &data[12] ) ); + lsmash_ifprintf( fp, indent, "avgBitrate = %"PRIu32"\n", LSMASH_GET_BE32( &data[16] ) ); + lsmash_ifprintf( fp, indent, "sampleRate = %"PRIu32"\n", LSMASH_GET_BE32( &data[20] ) ); + return 0; +} + +int alac_update_bitrate( isom_stbl_t *stbl, isom_mdhd_t *mdhd, uint32_t sample_description_index ) +{ + isom_audio_entry_t *alac = (isom_audio_entry_t *)lsmash_list_get_entry_data( &stbl->stsd->list, sample_description_index ); + if( LSMASH_IS_NON_EXISTING_BOX( alac ) ) + return LSMASH_ERR_INVALID_DATA; + uint8_t *exdata = NULL; + uint32_t exdata_size = 0; + isom_box_t *alac_ext = isom_get_extension_box( &alac->extensions, QT_BOX_TYPE_WAVE ); + if( LSMASH_IS_EXISTING_BOX( alac_ext ) ) + { + /* Apple Lossless Audio inside QuickTime file format + * Though average bitrate field we found is always set to 0 apparently, + * we set up maxFrameBytes and avgBitRate fields. */ + if( alac_ext->manager & LSMASH_BINARY_CODED_BOX ) + exdata = isom_get_child_box_position( alac_ext->binary, alac_ext->size, QT_BOX_TYPE_ALAC, &exdata_size ); + else + { + isom_wave_t *wave = (isom_wave_t *)alac_ext; + isom_box_t *wave_ext = isom_get_extension_box( &wave->extensions, QT_BOX_TYPE_ALAC ); + if( !(wave_ext->manager & LSMASH_BINARY_CODED_BOX) ) + return LSMASH_ERR_INVALID_DATA; + exdata = wave_ext->binary; + exdata_size = wave_ext->size; + } + } + else + { + /* Apple Lossless Audio inside ISO Base Media file format */ + isom_box_t *ext = isom_get_extension_box( &alac->extensions, ISOM_BOX_TYPE_ALAC ); + if( !(ext->manager & LSMASH_BINARY_CODED_BOX) ) + return LSMASH_ERR_INVALID_DATA; + exdata = ext->binary; + exdata_size = ext->size; + } + if( !exdata || exdata_size < 36 ) + return LSMASH_ERR_INVALID_DATA; + uint32_t bufferSizeDB; + uint32_t maxBitrate; + uint32_t avgBitrate; + int err = isom_calculate_bitrate_description( stbl, mdhd, &bufferSizeDB, &maxBitrate, &avgBitrate, sample_description_index ); + if( err < 0 ) + return err; + exdata += 24; + /* maxFrameBytes */ + LSMASH_SET_BE32( &exdata[0], bufferSizeDB ); + /* avgBitRate */ + LSMASH_SET_BE32( &exdata[4], avgBitrate ); + return 0; +} + +#undef ALAC_SPECIFIC_BOX_LENGTH diff --git a/3rdparty/l-smash/codecs/description.c b/3rdparty/l-smash/codecs/description.c new file mode 100644 index 000000000..fac2e3b6e --- /dev/null +++ b/3rdparty/l-smash/codecs/description.c @@ -0,0 +1,3257 @@ +/***************************************************************************** + * description.c + ***************************************************************************** + * Copyright (C) 2012-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include +#include + +#include "core/box.h" + +#include "a52.h" +#include "mp4a.h" +#include "mp4sys.h" +#include "description.h" + +typedef isom_wave_t lsmash_qt_decoder_parameters_t; + +static void global_destruct_specific_data( void *data ) +{ + if( !data ) + return; + lsmash_codec_global_header_t *global = (lsmash_codec_global_header_t *)data; + lsmash_free( global->header_data ); + lsmash_free( global ); +} + +static int isom_is_qt_video( lsmash_codec_type_t type ) +{ + return lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_2VUY_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_APCH_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_APCN_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_APCS_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_APCO_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_AP4H_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_AP4X_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_CFHD_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_CIVD_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_DVC_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_DVCP_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_DVPP_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_DV5N_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_DV5P_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_DVH2_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_DVH3_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_DVH5_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_DVH6_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_DVHP_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_DVHQ_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_DV10_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_DVOO_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_DVOR_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_DVTV_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_DVVT_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_FLIC_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_GIF_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_H261_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_H263_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_HD10_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_JPEG_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_M105_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_MJPA_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_MJPB_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_PNG_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_PNTG_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_RAW_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_RLE_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_RPZA_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_SHR0_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_SHR1_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_SHR2_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_SHR3_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_SHR4_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_SVQ1_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_SVQ3_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_TGA_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_TIFF_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_ULRA_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_ULRG_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_ULY2_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_ULY0_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_ULH2_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_ULH0_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_UQY2_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_V210_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_V216_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_V308_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_V408_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_V410_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_YUV2_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_WRLE_VIDEO ); +} + +static int isom_is_nalff( lsmash_codec_type_t type ) +{ + return lsmash_check_codec_type_identical( type, ISOM_CODEC_TYPE_AVC1_VIDEO ) + || lsmash_check_codec_type_identical( type, ISOM_CODEC_TYPE_AVC2_VIDEO ) + || lsmash_check_codec_type_identical( type, ISOM_CODEC_TYPE_AVC3_VIDEO ) + || lsmash_check_codec_type_identical( type, ISOM_CODEC_TYPE_AVC4_VIDEO ) + || lsmash_check_codec_type_identical( type, ISOM_CODEC_TYPE_AVCP_VIDEO ) + || lsmash_check_codec_type_identical( type, ISOM_CODEC_TYPE_HVC1_VIDEO ) + || lsmash_check_codec_type_identical( type, ISOM_CODEC_TYPE_HEV1_VIDEO ); +} + +static int isom_is_dts_audio( lsmash_codec_type_t type ) +{ + return lsmash_check_codec_type_identical( type, ISOM_CODEC_TYPE_DTSC_AUDIO ) + || lsmash_check_codec_type_identical( type, ISOM_CODEC_TYPE_DTSE_AUDIO ) + || lsmash_check_codec_type_identical( type, ISOM_CODEC_TYPE_DTSH_AUDIO ) + || lsmash_check_codec_type_identical( type, ISOM_CODEC_TYPE_DTSL_AUDIO ) + || lsmash_check_codec_type_identical( type, ISOM_CODEC_TYPE_DTSX_AUDIO ) + || lsmash_check_codec_type_identical( type, ISOM_CODEC_TYPE_DTSEL_AUDIO ) + || lsmash_check_codec_type_identical( type, ISOM_CODEC_TYPE_DTSDL_AUDIO ); +} + +int lsmash_convert_crop_into_clap( lsmash_crop_t crop, uint32_t width, uint32_t height, lsmash_clap_t *clap ) +{ + if( !clap || crop.top.d == 0 || crop.bottom.d == 0 || crop.left.d == 0 || crop.right.d == 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + uint64_t vertical_crop_lcm = lsmash_get_lcm( crop.top.d, crop.bottom.d ); + uint64_t horizontal_crop_lcm = lsmash_get_lcm( crop.left.d, crop.right.d ); + lsmash_rational_u64_t clap_height; + lsmash_rational_u64_t clap_width; + lsmash_rational_s64_t clap_horizontal_offset; + lsmash_rational_s64_t clap_vertical_offset; + clap_height.d = vertical_crop_lcm; + clap_width.d = horizontal_crop_lcm; + clap_horizontal_offset.d = 2 * vertical_crop_lcm; + clap_vertical_offset.d = 2 * horizontal_crop_lcm; + clap_height.n = height * vertical_crop_lcm + - (crop.top.n * (vertical_crop_lcm / crop.top.d) + crop.bottom.n * (vertical_crop_lcm / crop.bottom.d)); + clap_width.n = width * horizontal_crop_lcm + - (crop.left.n * (horizontal_crop_lcm / crop.left.d) + crop.right.n * (horizontal_crop_lcm / crop.right.d)); + clap_horizontal_offset.n = (int64_t)(crop.left.n * (horizontal_crop_lcm / crop.left.d)) + - crop.right.n * (horizontal_crop_lcm / crop.right.d); + clap_vertical_offset.n = (int64_t)(crop.top.n * (vertical_crop_lcm / crop.top.d)) + - crop.bottom.n * (vertical_crop_lcm / crop.bottom.d); + lsmash_reduce_fraction( &clap_height.n, &clap_height.d ); + lsmash_reduce_fraction( &clap_width.n, &clap_width.d ); + lsmash_reduce_fraction_su( &clap_vertical_offset.n, &clap_vertical_offset.d ); + lsmash_reduce_fraction_su( &clap_horizontal_offset.n, &clap_horizontal_offset.d ); + clap->height = (lsmash_rational_u32_t){ clap_height.n, clap_height.d }; + clap->width = (lsmash_rational_u32_t){ clap_width.n, clap_width.d }; + clap->vertical_offset = (lsmash_rational_s32_t){ clap_vertical_offset.n, clap_vertical_offset.d }; + clap->horizontal_offset = (lsmash_rational_s32_t){ clap_horizontal_offset.n, clap_horizontal_offset.d }; + return 0; +} + +int lsmash_convert_clap_into_crop( lsmash_clap_t clap, uint32_t width, uint32_t height, lsmash_crop_t *crop ) +{ + if( !crop || clap.height.d == 0 || clap.vertical_offset.d == 0 || clap.width.d == 0 || clap.horizontal_offset.d == 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + uint64_t clap_vertical_lcm = lsmash_get_lcm( clap.height.d, clap.vertical_offset.d ); + uint64_t clap_horizontal_lcm = lsmash_get_lcm( clap.width.d, clap.horizontal_offset.d ); + lsmash_rational_u64_t crop_top; + lsmash_rational_u64_t crop_bottom; + lsmash_rational_u64_t crop_left; + lsmash_rational_u64_t crop_right; + crop_top.d = 2 * clap_vertical_lcm; + crop_bottom.d = 2 * clap_vertical_lcm; + crop_left.d = 2 * clap_horizontal_lcm; + crop_right.d = 2 * clap_horizontal_lcm; + crop_top.n = (height * crop_top.d - clap.height.n * (crop_top.d / clap.height.d)) / 2 + + clap.vertical_offset.n * (crop_top.d / clap.vertical_offset.d); + crop_bottom.n = (height * crop_bottom.d - clap.height.n * (crop_bottom.d / clap.height.d)) / 2 + - clap.vertical_offset.n * (crop_bottom.d / clap.vertical_offset.d); + crop_left.n = (width * crop_left.d - clap.width.n * (crop_left.d / clap.width.d)) / 2 + + clap.horizontal_offset.n * (crop_left.d / clap.horizontal_offset.d); + crop_right.n = (width * crop_right.d - clap.width.n * (crop_right.d / clap.width.d)) / 2 + - clap.horizontal_offset.n * (crop_right.d / clap.horizontal_offset.d); + lsmash_reduce_fraction( &crop_top.n, &crop_top.d ); + lsmash_reduce_fraction( &crop_bottom.n, &crop_bottom.d ); + lsmash_reduce_fraction( &crop_left.n, &crop_left.d ); + lsmash_reduce_fraction( &crop_right.n, &crop_right.d ); + crop->top = (lsmash_rational_u32_t){ crop_top.n, crop_top.d }; + crop->bottom = (lsmash_rational_u32_t){ crop_bottom.n, crop_bottom.d }; + crop->left = (lsmash_rational_u32_t){ crop_left.n, crop_left.d }; + crop->right = (lsmash_rational_u32_t){ crop_right.n, crop_right.d }; + return 0; +} + +static void isom_destruct_nothing( void *data ) +{ + /* Do nothing. */; +} + +static int isom_initialize_structured_codec_specific_data( lsmash_codec_specific_t *specific ) +{ + extern void mp4sys_destruct_decoder_config( void * ); + extern void h264_destruct_specific_data( void * ); + extern void hevc_destruct_specific_data( void * ); + extern void vc1_destruct_specific_data( void * ); + extern void dts_destruct_specific_data( void * ); + switch( specific->type ) + { + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_MP4SYS_DECODER_CONFIG : + specific->size = sizeof(lsmash_mp4sys_decoder_parameters_t); + specific->destruct = mp4sys_destruct_decoder_config; + break; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_H264 : + specific->size = sizeof(lsmash_h264_specific_parameters_t); + specific->destruct = h264_destruct_specific_data; + break; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_HEVC : + specific->size = sizeof(lsmash_hevc_specific_parameters_t); + specific->destruct = hevc_destruct_specific_data; + break; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_VC_1 : + specific->size = sizeof(lsmash_vc1_specific_parameters_t); + specific->destruct = vc1_destruct_specific_data; + break; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_AC_3 : + specific->size = sizeof(lsmash_ac3_specific_parameters_t); + specific->destruct = lsmash_free; + break; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_EC_3 : + specific->size = sizeof(lsmash_eac3_specific_parameters_t); + specific->destruct = lsmash_free; + break; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_DTS : + specific->size = sizeof(lsmash_dts_specific_parameters_t); + specific->destruct = dts_destruct_specific_data; + break; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_ALAC : + specific->size = sizeof(lsmash_alac_specific_parameters_t); + specific->destruct = lsmash_free; + break; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_SAMPLE_SCALE : + specific->size = sizeof(lsmash_isom_sample_scale_t); + specific->destruct = lsmash_free; + break; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_H264_BITRATE : + specific->size = sizeof(lsmash_h264_bitrate_t); + specific->destruct = lsmash_free; + break; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_COMMON : + specific->size = sizeof(lsmash_qt_video_common_t); + specific->destruct = lsmash_free; + break; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_COMMON : + specific->size = sizeof(lsmash_qt_audio_common_t); + specific->destruct = lsmash_free; + break; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_FORMAT_SPECIFIC_FLAGS : + specific->size = sizeof(lsmash_qt_audio_format_specific_flags_t); + specific->destruct = lsmash_free; + break; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_CODEC_GLOBAL_HEADER : + specific->size = sizeof(lsmash_codec_global_header_t); + specific->destruct = global_destruct_specific_data; + break; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_FIELD_INFO : + specific->size = sizeof(lsmash_qt_field_info_t); + specific->destruct = lsmash_free; + break; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_PIXEL_FORMAT : + specific->size = sizeof(lsmash_qt_pixel_format_t); + specific->destruct = lsmash_free; + break; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_SIGNIFICANT_BITS : + specific->size = sizeof(lsmash_qt_significant_bits_t); + specific->destruct = lsmash_free; + break; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_CHANNEL_LAYOUT : + specific->size = sizeof(lsmash_qt_audio_channel_layout_t); + specific->destruct = lsmash_free; + break; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_RTP_HINT_COMMON: + specific->size = sizeof(lsmash_isom_rtp_reception_hint_t); + specific->destruct = lsmash_free; + break; + default : + specific->size = 0; + specific->destruct = isom_destruct_nothing; + return 0; + } + specific->data.structured = lsmash_malloc_zero( specific->size ); + if( !specific->data.structured ) + { + specific->size = 0; + specific->destruct = NULL; + return LSMASH_ERR_MEMORY_ALLOC; + } + return 0; +} + +static inline int isom_initialize_codec_specific_data( lsmash_codec_specific_t *specific, + lsmash_codec_specific_data_type type, + lsmash_codec_specific_format format ) +{ + specific->type = type; + specific->format = format; + if( format == LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ) + { + int err = isom_initialize_structured_codec_specific_data( specific ); + if( err < 0 ) + return err; + } + else + { + specific->data.unstructured = NULL; + specific->size = 0; + specific->destruct = (lsmash_codec_specific_destructor_t)lsmash_free; + } + return 0; +} + +void lsmash_destroy_codec_specific_data( lsmash_codec_specific_t *specific ) +{ + if( !specific ) + return; + if( specific->destruct ) + { + if( specific->format == LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ) + { + if( specific->data.structured ) + specific->destruct( specific->data.structured ); + } + else + { + if( specific->data.unstructured ) + specific->destruct( specific->data.unstructured ); + } + } + lsmash_free( specific ); +} + +lsmash_codec_specific_t *lsmash_create_codec_specific_data( lsmash_codec_specific_data_type type, lsmash_codec_specific_format format ) +{ + lsmash_codec_specific_t *specific = lsmash_malloc( sizeof(lsmash_codec_specific_t) ); + if( !specific ) + return NULL; + if( isom_initialize_codec_specific_data( specific, type, format ) < 0 ) + { + lsmash_destroy_codec_specific_data( specific ); + return NULL; + } + return specific; +} + +static int isom_duplicate_structured_specific_data( lsmash_codec_specific_t *dst, lsmash_codec_specific_t *src ) +{ + extern int mp4sys_copy_decoder_config( lsmash_codec_specific_t *, lsmash_codec_specific_t * ); + extern int h264_copy_codec_specific( lsmash_codec_specific_t *, lsmash_codec_specific_t * ); + extern int hevc_copy_codec_specific( lsmash_codec_specific_t *, lsmash_codec_specific_t * ); + extern int vc1_copy_codec_specific( lsmash_codec_specific_t *, lsmash_codec_specific_t * ); + extern int dts_copy_codec_specific( lsmash_codec_specific_t *, lsmash_codec_specific_t * ); + void *src_data = src->data.structured; + void *dst_data = dst->data.structured; + switch( src->type ) + { + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_MP4SYS_DECODER_CONFIG : + return mp4sys_copy_decoder_config( dst, src ); + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_H264 : + return h264_copy_codec_specific( dst, src ); + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_HEVC : + return hevc_copy_codec_specific( dst, src ); + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_VC_1 : + return vc1_copy_codec_specific( dst, src ); + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_AC_3 : + *(lsmash_ac3_specific_parameters_t *)dst_data = *(lsmash_ac3_specific_parameters_t *)src_data; + return 0; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_EC_3 : + *(lsmash_eac3_specific_parameters_t *)dst_data = *(lsmash_eac3_specific_parameters_t *)src_data; + return 0; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_DTS : + return dts_copy_codec_specific( dst, src ); + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_ALAC : + *(lsmash_alac_specific_parameters_t *)dst_data = *(lsmash_alac_specific_parameters_t *)src_data; + return 0; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_SAMPLE_SCALE : + *(lsmash_isom_sample_scale_t *)dst_data = *(lsmash_isom_sample_scale_t *)src_data; + return 0; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_H264_BITRATE : + *(lsmash_h264_bitrate_t *)dst_data = *(lsmash_h264_bitrate_t *)src_data; + return 0; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_COMMON : + *(lsmash_qt_video_common_t *)dst_data = *(lsmash_qt_video_common_t *)src_data; + return 0; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_COMMON : + *(lsmash_qt_audio_common_t *)dst_data = *(lsmash_qt_audio_common_t *)src_data; + return 0; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_FORMAT_SPECIFIC_FLAGS : + *(lsmash_qt_audio_format_specific_flags_t *)dst_data = *(lsmash_qt_audio_format_specific_flags_t *)src_data; + return 0; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_CODEC_GLOBAL_HEADER : + { + lsmash_codec_global_header_t *src_global = (lsmash_codec_global_header_t *)src_data; + if( src_global->header_data && src_global->header_size ) + { + lsmash_codec_global_header_t *dst_global = (lsmash_codec_global_header_t *)dst_data; + dst_global->header_data = lsmash_memdup( src_global->header_data, src_global->header_size ); + if( !dst_global->header_data ) + return LSMASH_ERR_MEMORY_ALLOC; + dst_global->header_size = src_global->header_size; + } + return 0; + } + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_FIELD_INFO : + *(lsmash_qt_field_info_t *)dst_data = *(lsmash_qt_field_info_t *)src_data; + return 0; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_PIXEL_FORMAT : + *(lsmash_qt_pixel_format_t *)dst_data = *(lsmash_qt_pixel_format_t *)src_data; + return 0; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_SIGNIFICANT_BITS : + *(lsmash_qt_significant_bits_t *)dst_data = *(lsmash_qt_significant_bits_t *)src_data; + return 0; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_GAMMA_LEVEL : + *(lsmash_qt_gamma_t *)dst_data = *(lsmash_qt_gamma_t *)src_data; + return 0; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_CONTENT_LIGHT_LEVEL_INFO : + *(lsmash_qt_content_light_level_info_t *)dst_data = *(lsmash_qt_content_light_level_info_t *)src_data; + return 0; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_MASTERING_DISPLAY_COLOR_VOLUME : + *(lsmash_qt_mastering_display_color_volume_t *)dst_data = *(lsmash_qt_mastering_display_color_volume_t *)src_data; + return 0; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_CHANNEL_LAYOUT : + *(lsmash_qt_audio_channel_layout_t *)dst_data = *(lsmash_qt_audio_channel_layout_t *)src_data; + return 0; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_RTP_HINT_COMMON: + *(lsmash_isom_rtp_reception_hint_t *)dst_data = *(lsmash_isom_rtp_reception_hint_t *)src_data; + return 0; + default : + return LSMASH_ERR_NAMELESS; + } +} + +lsmash_codec_specific_t *isom_duplicate_codec_specific_data( lsmash_codec_specific_t *specific ) +{ + if( !specific ) + return NULL; + lsmash_codec_specific_t *dup = lsmash_create_codec_specific_data( specific->type, specific->format ); + if( !dup ) + return NULL; + if( specific->format == LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ) + { + if( isom_duplicate_structured_specific_data( dup, specific ) < 0 ) + { + lsmash_destroy_codec_specific_data( dup ); + return NULL; + } + } + else + { + dup->data.unstructured = lsmash_memdup( specific->data.unstructured, specific->size ); + if( !dup->data.unstructured ) + { + lsmash_destroy_codec_specific_data( dup ); + return NULL; + } + } + dup->size = specific->size; + return dup; +} + +static int isom_construct_global_specific_header( lsmash_codec_specific_t *dst, lsmash_codec_specific_t *src ) +{ + if( src->size < ISOM_BASEBOX_COMMON_SIZE ) + return LSMASH_ERR_INVALID_DATA; + lsmash_codec_global_header_t *global = (lsmash_codec_global_header_t *)dst->data.structured; + uint8_t *data = src->data.unstructured; + uint64_t size = LSMASH_GET_BE32( data ); + data += ISOM_BASEBOX_COMMON_SIZE; + if( size == 1 ) + { + size = LSMASH_GET_BE64( data ); + data += 8; + } + if( size != src->size ) + return LSMASH_ERR_INVALID_DATA; + global->header_size = size - ISOM_BASEBOX_COMMON_SIZE; + if( data != src->data.unstructured + ISOM_BASEBOX_COMMON_SIZE ) + global->header_size -= 8; /* largesize */ + if( global->header_size ) + { + global->header_data = lsmash_memdup( data, global->header_size ); + if( !global->header_data ) + return LSMASH_ERR_MEMORY_ALLOC; + } + return 0; +} + +static int isom_construct_audio_channel_layout( lsmash_codec_specific_t *dst, lsmash_codec_specific_t *src ) +{ + if( src->size < ISOM_FULLBOX_COMMON_SIZE + 12 ) + return LSMASH_ERR_INVALID_DATA; + lsmash_qt_audio_channel_layout_t *layout = (lsmash_qt_audio_channel_layout_t *)dst->data.structured; + uint8_t *data = src->data.unstructured; + uint64_t size = LSMASH_GET_BE32( data ); + data += ISOM_FULLBOX_COMMON_SIZE; + if( size == 1 ) + { + size = LSMASH_GET_BE64( data ); + data += 8; + } + if( size != src->size ) + return LSMASH_ERR_INVALID_DATA; + layout->channelLayoutTag = LSMASH_GET_BE32( &data[0] ); + layout->channelBitmap = LSMASH_GET_BE32( &data[4] ); + return 0; +} + +#if 0 +static int codec_construct_qt_audio_decompression_info( lsmash_codec_specific_t *dst, lsmash_codec_specific_t *src ) +{ + if( src->size < ISOM_BASEBOX_COMMON_SIZE ) + return LSMASH_ERR_INVALID_DATA; + uint8_t *data = src->data.unstructured; + uint64_t size; + uint32_t type; + uint32_t offset = isom_read_box_size_and_type_from_binary_string( &data, &size, &type ); + if( size != src->size ) + return LSMASH_ERR_INVALID_DATA; + uint8_t *end = src->data.unstructured + src->size; + isom_wave_t *wave = isom_add_wave( isom_non_existing_audio_entry() ); + if( LSMASH_IS_NON_EXISTING_BOX( wave ) ) + return LSMASH_ERR_MEMORY_ALLOC; + wave->type = QT_BOX_TYPE_WAVE; + for( uint8_t *pos = data; pos + ISOM_BASEBOX_COMMON_SIZE <= end; ) + { + offset = isom_read_box_size_and_type_from_binary_string( &pos, &size, &type ); + switch( type ) + { + case QT_BOX_TYPE_FRMA : + { + if( pos + 4 > end ) + return LSMASH_ERR_INVALID_DATA; + isom_frma_t *frma = isom_add_frma( wave ); + if( LSMASH_IS_NON_EXISTING_BOX( frma ) ) + return LSMASH_ERR_NAMELESS; + frma->data_format = LSMASH_GET_BE32( pos ); + pos += 4; + break; + } + case QT_BOX_TYPE_ENDA : + { + if( pos + 2 > end ) + return LSMASH_ERR_INVALID_DATA; + isom_enda_t *enda = isom_add_enda( wave ); + if( LSMASH_IS_NON_EXISTING_BOX( enda ) ) + return LSMASH_ERR_NAMELESS; + enda->littleEndian = LSMASH_GET_BE16( pos ); + break; + } + case QT_BOX_TYPE_MP4A : + { + if( pos + 4 > end ) + return LSMASH_ERR_INVALID_DATA; + isom_mp4a_t *mp4a = isom_add_mp4a( wave ); + if( LSMASH_IS_NON_EXISTING_BOX( mp4a ) ) + return LSMASH_ERR_NAMELESS; + mp4a->unknown = LSMASH_GET_BE32( pos ); + pos += 4; + break; + } + case QT_BOX_TYPE_TERMINATOR : + { + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_terminator( wave ) ) ) + return LSMASH_ERR_NAMELESS; + break; + } + default : + { + isom_unknown_box_t *box = lsmash_malloc_zero( sizeof(isom_unknown_box_t) ); + if( LSMASH_IS_NON_EXISTING_BOX( box ) ) + return LSMASH_ERR_MEMORY_ALLOC; + isom_init_box_common( box, wave, type, isom_remove_unknown_box ); + box->unknown_size = size - offset; + box->unknown_field = lsmash_memdup( pos, box->unknown_size ); + if( !box->unknown_field ) + { + isom_remove_box_by_itself( box ); + return LSMASH_ERR_MEMORY_ALLOC; + } + if( lsmash_list_add_entry( &wave->extensions, box ) < 0 ) + { + isom_remove_unknown_box( box ); + return LSMASH_ERR_MEMORY_ALLOC; + } + pos += box->unknown_size; + break; + } + } + } + return 0; +} +#endif + +/* structured <-> unstructured conversion might be irreversible by CODEC + * since structured formats we defined don't always have all contents included in unstructured data. */ +lsmash_codec_specific_t *lsmash_convert_codec_specific_format( lsmash_codec_specific_t *specific, lsmash_codec_specific_format format ) +{ + if( !specific || format == LSMASH_CODEC_SPECIFIC_FORMAT_UNSPECIFIED ) + return NULL; + if( format == specific->format ) + return isom_duplicate_codec_specific_data( specific ); + lsmash_codec_specific_t *dst = lsmash_create_codec_specific_data( specific->type, format ); + if( !dst ) + return NULL; + if( format == LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED ) + /* structured -> unstructured */ + switch( specific->type ) + { + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_MP4SYS_DECODER_CONFIG : + dst->data.unstructured = lsmash_create_mp4sys_decoder_config( (lsmash_mp4sys_decoder_parameters_t *)specific->data.structured, &dst->size ); + if( !dst->data.unstructured ) + goto fail; + return dst; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_H264 : + dst->data.unstructured = lsmash_create_h264_specific_info( (lsmash_h264_specific_parameters_t *)specific->data.structured, &dst->size ); + if( !dst->data.unstructured ) + goto fail; + return dst; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_HEVC : + dst->data.unstructured = lsmash_create_hevc_specific_info( (lsmash_hevc_specific_parameters_t *)specific->data.structured, &dst->size ); + if( !dst->data.unstructured ) + goto fail; + return dst; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_VC_1 : + dst->data.unstructured = lsmash_create_vc1_specific_info( (lsmash_vc1_specific_parameters_t *)specific->data.structured, &dst->size ); + if( !dst->data.unstructured ) + goto fail; + return dst; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_AC_3 : + dst->data.unstructured = lsmash_create_ac3_specific_info( (lsmash_ac3_specific_parameters_t *)specific->data.structured, &dst->size ); + if( !dst->data.unstructured ) + goto fail; + return dst; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_EC_3 : + dst->data.unstructured = lsmash_create_eac3_specific_info( (lsmash_eac3_specific_parameters_t *)specific->data.structured, &dst->size ); + if( !dst->data.unstructured ) + goto fail; + return dst; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_DTS : + dst->data.unstructured = lsmash_create_dts_specific_info( (lsmash_dts_specific_parameters_t *)specific->data.structured, &dst->size ); + if( !dst->data.unstructured ) + goto fail; + return dst; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_ALAC : + dst->data.unstructured = lsmash_create_alac_specific_info( (lsmash_alac_specific_parameters_t *)specific->data.structured, &dst->size ); + if( !dst->data.unstructured ) + goto fail; + return dst; + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_CODEC_GLOBAL_HEADER : + { + lsmash_bs_t *bs = lsmash_bs_create(); + if( !bs ) + goto fail; + lsmash_codec_global_header_t *global = specific->data.structured; + lsmash_bs_put_be32( bs, ISOM_BASEBOX_COMMON_SIZE + global->header_size ); + lsmash_bs_put_be32( bs, QT_BOX_TYPE_GLBL.fourcc ); + lsmash_bs_put_bytes( bs, global->header_size, global->header_data ); + dst->data.unstructured = lsmash_bs_export_data( bs, &dst->size ); + lsmash_bs_cleanup( bs ); + if( !dst->data.unstructured || dst->size != (ISOM_BASEBOX_COMMON_SIZE + global->header_size) ) + goto fail; + return dst; + } + default : + break; + } + else if( format == LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ) + { + /* unstructured -> structured */ + extern int mp4sys_construct_decoder_config( lsmash_codec_specific_t *, lsmash_codec_specific_t * ); + extern int h264_construct_specific_parameters( lsmash_codec_specific_t *, lsmash_codec_specific_t * ); + extern int hevc_construct_specific_parameters( lsmash_codec_specific_t *, lsmash_codec_specific_t * ); + extern int vc1_construct_specific_parameters( lsmash_codec_specific_t *, lsmash_codec_specific_t * ); + extern int ac3_construct_specific_parameters( lsmash_codec_specific_t *, lsmash_codec_specific_t * ); + extern int eac3_construct_specific_parameters( lsmash_codec_specific_t *, lsmash_codec_specific_t * ); + extern int dts_construct_specific_parameters( lsmash_codec_specific_t *, lsmash_codec_specific_t * ); + extern int alac_construct_specific_parameters( lsmash_codec_specific_t *, lsmash_codec_specific_t * ); + static const struct + { + lsmash_codec_specific_data_type data_type; + int (*constructor)( lsmash_codec_specific_t *, lsmash_codec_specific_t * ); + } codec_specific_format_constructor_table[] = + { + { LSMASH_CODEC_SPECIFIC_DATA_TYPE_MP4SYS_DECODER_CONFIG, mp4sys_construct_decoder_config }, + { LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_H264, h264_construct_specific_parameters }, + { LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_HEVC, hevc_construct_specific_parameters }, + { LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_VC_1, vc1_construct_specific_parameters }, + { LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_AC_3, ac3_construct_specific_parameters }, + { LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_EC_3, eac3_construct_specific_parameters }, + { LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_DTS, dts_construct_specific_parameters }, + { LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_ALAC, alac_construct_specific_parameters }, + { LSMASH_CODEC_SPECIFIC_DATA_TYPE_CODEC_GLOBAL_HEADER, isom_construct_global_specific_header }, + { LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_CHANNEL_LAYOUT, isom_construct_audio_channel_layout }, + { LSMASH_CODEC_SPECIFIC_DATA_TYPE_UNKNOWN, NULL } + }; + int (*constructor)( lsmash_codec_specific_t *, lsmash_codec_specific_t * ) = NULL; + for( int i = 0; codec_specific_format_constructor_table[i].constructor; i++ ) + if( specific->type == codec_specific_format_constructor_table[i].data_type ) + { + constructor = codec_specific_format_constructor_table[i].constructor; + break; + } + if( constructor && !constructor( dst, specific ) ) + return dst; + } +fail: + lsmash_destroy_codec_specific_data( dst ); + return NULL; +} + +static inline void isom_set_default_compressorname( char *compressorname, lsmash_codec_type_t sample_type ) +{ + static struct compressorname_table_tag + { + lsmash_codec_type_t type; + char name[33]; + } compressorname_table[33] = { { LSMASH_CODEC_TYPE_INITIALIZER, { '\0' } } }; + if( compressorname_table[0].name[0] == '\0' ) + { + int i = 0; +#define ADD_COMPRESSORNAME_TABLE( type, name ) compressorname_table[i++] = (struct compressorname_table_tag){ type, name } + ADD_COMPRESSORNAME_TABLE( ISOM_CODEC_TYPE_AVC1_VIDEO, "\012AVC Coding" ); + ADD_COMPRESSORNAME_TABLE( ISOM_CODEC_TYPE_AVC2_VIDEO, "\012AVC Coding" ); + ADD_COMPRESSORNAME_TABLE( ISOM_CODEC_TYPE_AVC3_VIDEO, "\012AVC Coding" ); + ADD_COMPRESSORNAME_TABLE( ISOM_CODEC_TYPE_AVC4_VIDEO, "\012AVC Coding" ); + ADD_COMPRESSORNAME_TABLE( ISOM_CODEC_TYPE_AVCP_VIDEO, "\016AVC Parameters" ); + ADD_COMPRESSORNAME_TABLE( ISOM_CODEC_TYPE_HVC1_VIDEO, "\013HEVC Coding" ); + ADD_COMPRESSORNAME_TABLE( ISOM_CODEC_TYPE_HEV1_VIDEO, "\013HEVC Coding" ); + ADD_COMPRESSORNAME_TABLE( ISOM_CODEC_TYPE_SVC1_VIDEO, "\012SVC Coding" ); + ADD_COMPRESSORNAME_TABLE( ISOM_CODEC_TYPE_MVC1_VIDEO, "\012MVC Coding" ); + ADD_COMPRESSORNAME_TABLE( ISOM_CODEC_TYPE_MVC2_VIDEO, "\012MVC Coding" ); + ADD_COMPRESSORNAME_TABLE( QT_CODEC_TYPE_APCH_VIDEO, "\023Apple ProRes 422 (HQ)" ); + ADD_COMPRESSORNAME_TABLE( QT_CODEC_TYPE_APCN_VIDEO, "\023Apple ProRes 422 (SD)" ); + ADD_COMPRESSORNAME_TABLE( QT_CODEC_TYPE_APCS_VIDEO, "\023Apple ProRes 422 (LT)" ); + ADD_COMPRESSORNAME_TABLE( QT_CODEC_TYPE_APCO_VIDEO, "\026Apple ProRes 422 (Proxy)" ); + ADD_COMPRESSORNAME_TABLE( QT_CODEC_TYPE_AP4H_VIDEO, "\019Apple ProRes 4444" ); + ADD_COMPRESSORNAME_TABLE( QT_CODEC_TYPE_AP4X_VIDEO, "\022Apple ProRes 4444 XQ" ); + ADD_COMPRESSORNAME_TABLE( QT_CODEC_TYPE_DVPP_VIDEO, "\014DVCPRO - PAL" ); + ADD_COMPRESSORNAME_TABLE( QT_CODEC_TYPE_DV5N_VIDEO, "\017DVCPRO50 - NTSC" ); + ADD_COMPRESSORNAME_TABLE( QT_CODEC_TYPE_DV5P_VIDEO, "\016DVCPRO50 - PAL" ); + ADD_COMPRESSORNAME_TABLE( QT_CODEC_TYPE_DVH2_VIDEO, "\019DVCPRO HD 1080p25" ); + ADD_COMPRESSORNAME_TABLE( QT_CODEC_TYPE_DVH3_VIDEO, "\019DVCPRO HD 1080p30" ); + ADD_COMPRESSORNAME_TABLE( QT_CODEC_TYPE_DVH5_VIDEO, "\019DVCPRO HD 1080i50" ); + ADD_COMPRESSORNAME_TABLE( QT_CODEC_TYPE_DVH6_VIDEO, "\019DVCPRO HD 1080i60" ); + ADD_COMPRESSORNAME_TABLE( QT_CODEC_TYPE_DVHP_VIDEO, "\018DVCPRO HD 720p60" ); + ADD_COMPRESSORNAME_TABLE( QT_CODEC_TYPE_DVHQ_VIDEO, "\018DVCPRO HD 720p50" ); + ADD_COMPRESSORNAME_TABLE( QT_CODEC_TYPE_ULRA_VIDEO, "\017Ut Video (ULRA)" ); + ADD_COMPRESSORNAME_TABLE( QT_CODEC_TYPE_ULRG_VIDEO, "\017Ut Video (ULRG)" ); + ADD_COMPRESSORNAME_TABLE( QT_CODEC_TYPE_ULY0_VIDEO, "\017Ut Video (ULY0)" ); + ADD_COMPRESSORNAME_TABLE( QT_CODEC_TYPE_ULY2_VIDEO, "\017Ut Video (ULY2)" ); + ADD_COMPRESSORNAME_TABLE( QT_CODEC_TYPE_ULH0_VIDEO, "\017Ut Video (ULH0)" ); + ADD_COMPRESSORNAME_TABLE( QT_CODEC_TYPE_ULH2_VIDEO, "\017Ut Video (ULH2)" ); + ADD_COMPRESSORNAME_TABLE( QT_CODEC_TYPE_UQY2_VIDEO, "\021Ut Video Pro (UQY2)" ); + ADD_COMPRESSORNAME_TABLE( LSMASH_CODEC_TYPE_UNSPECIFIED, { '\0' } ); +#undef ADD_COMPRESSORNAME_TABLE + } + for( int i = 0; compressorname_table[i].name[0] != '\0'; i++ ) + if( lsmash_check_codec_type_identical( sample_type, compressorname_table[i].type ) ) + { + strcpy( compressorname, compressorname_table[i].name ); + return; + } +} + +lsmash_codec_specific_t *isom_get_codec_specific( lsmash_codec_specific_list_t *opaque, lsmash_codec_specific_data_type type ) +{ + for( lsmash_entry_t *entry = opaque->list.head; entry; entry = entry->next ) + { + lsmash_codec_specific_t *specific = (lsmash_codec_specific_t *)entry->data; + if( !specific || specific->type != type ) + continue; + return specific; + } + return NULL; +} + +static int isom_check_valid_summary( lsmash_summary_t *summary ) +{ + if( !summary ) + return LSMASH_ERR_NAMELESS; + isom_box_t temp_box; + temp_box.type = summary->sample_type; + temp_box.manager = summary->summary_type == LSMASH_SUMMARY_TYPE_AUDIO ? LSMASH_AUDIO_DESCRIPTION: 0; + if( isom_is_lpcm_audio( &temp_box ) ) + { + if( isom_get_codec_specific( summary->opaque, LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_FORMAT_SPECIFIC_FLAGS ) ) + return 0; + return LSMASH_ERR_INVALID_DATA; + } + if( isom_is_uncompressed_ycbcr( summary->sample_type ) ) + { + if( isom_get_codec_specific( summary->opaque, LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_FIELD_INFO ) ) + { + if( !lsmash_check_codec_type_identical( summary->sample_type, QT_CODEC_TYPE_V216_VIDEO ) ) + return 0; + } + else + return LSMASH_ERR_INVALID_DATA; + } + lsmash_codec_type_t sample_type = summary->sample_type; + lsmash_codec_specific_data_type required_data_type = LSMASH_CODEC_SPECIFIC_DATA_TYPE_UNSPECIFIED; + if( lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_AVC1_VIDEO ) + || lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_AVC2_VIDEO ) + || lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_AVC3_VIDEO ) + || lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_AVC4_VIDEO ) ) + required_data_type = LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_H264; + else if( lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_HVC1_VIDEO ) + || lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_HEV1_VIDEO ) ) + required_data_type = LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_HEVC; + else if( lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_VC_1_VIDEO ) ) + required_data_type = LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_VC_1 ; + else if( lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_ULRA_VIDEO ) + || lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_ULRG_VIDEO ) + || lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_ULY0_VIDEO ) + || lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_ULY2_VIDEO ) + || lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_ULH0_VIDEO ) + || lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_ULH2_VIDEO ) + || lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_UQY2_VIDEO ) ) + required_data_type = LSMASH_CODEC_SPECIFIC_DATA_TYPE_CODEC_GLOBAL_HEADER; + else if( lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_V216_VIDEO ) ) + required_data_type = LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_SIGNIFICANT_BITS; + else if( lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_MP4V_VIDEO ) + || lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_MP4A_AUDIO ) + || lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_MP4A_AUDIO ) ) + required_data_type = LSMASH_CODEC_SPECIFIC_DATA_TYPE_MP4SYS_DECODER_CONFIG; + else if( lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_AC_3_AUDIO ) ) + required_data_type = LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_AC_3; + else if( lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_EC_3_AUDIO ) ) + required_data_type = LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_EC_3; + else if( isom_is_dts_audio( sample_type ) ) + required_data_type = LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_DTS; + else if( lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_ALAC_AUDIO ) + || lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_ALAC_AUDIO ) ) + required_data_type = LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_ALAC; + else if( lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_RRTP_HINT ) ) + required_data_type = LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_RTP_HINT_COMMON; + if( required_data_type == LSMASH_CODEC_SPECIFIC_DATA_TYPE_UNSPECIFIED ) + return 0; + return isom_get_codec_specific( summary->opaque, required_data_type ) ? 0 : LSMASH_ERR_INVALID_DATA; +} + +static lsmash_box_type_t isom_guess_video_codec_specific_box_type( lsmash_codec_type_t active_codec_type, lsmash_compact_box_type_t fourcc ) +{ + lsmash_box_type_t box_type = LSMASH_BOX_TYPE_INITIALIZER; + box_type.fourcc = fourcc; +#define GUESS_VIDEO_CODEC_SPECIFIC_BOX_TYPE( codec_type, predefined_box_type ) \ + else if( (codec_type.user.fourcc == 0 \ + || lsmash_check_codec_type_identical( active_codec_type, codec_type )) \ + && box_type.fourcc == predefined_box_type.fourcc ) \ + box_type = predefined_box_type + if( 0 ); + GUESS_VIDEO_CODEC_SPECIFIC_BOX_TYPE( ISOM_CODEC_TYPE_AVC1_VIDEO, ISOM_BOX_TYPE_AVCC ); + GUESS_VIDEO_CODEC_SPECIFIC_BOX_TYPE( ISOM_CODEC_TYPE_AVC2_VIDEO, ISOM_BOX_TYPE_AVCC ); + GUESS_VIDEO_CODEC_SPECIFIC_BOX_TYPE( ISOM_CODEC_TYPE_AVC3_VIDEO, ISOM_BOX_TYPE_AVCC ); + GUESS_VIDEO_CODEC_SPECIFIC_BOX_TYPE( ISOM_CODEC_TYPE_AVC4_VIDEO, ISOM_BOX_TYPE_AVCC ); + GUESS_VIDEO_CODEC_SPECIFIC_BOX_TYPE( ISOM_CODEC_TYPE_AVCP_VIDEO, ISOM_BOX_TYPE_AVCC ); + GUESS_VIDEO_CODEC_SPECIFIC_BOX_TYPE( ISOM_CODEC_TYPE_HVC1_VIDEO, ISOM_BOX_TYPE_HVCC ); + GUESS_VIDEO_CODEC_SPECIFIC_BOX_TYPE( ISOM_CODEC_TYPE_HEV1_VIDEO, ISOM_BOX_TYPE_HVCC ); + GUESS_VIDEO_CODEC_SPECIFIC_BOX_TYPE( ISOM_CODEC_TYPE_VC_1_VIDEO, ISOM_BOX_TYPE_DVC1 ); + GUESS_VIDEO_CODEC_SPECIFIC_BOX_TYPE( ISOM_CODEC_TYPE_MP4V_VIDEO, ISOM_BOX_TYPE_ESDS ); + GUESS_VIDEO_CODEC_SPECIFIC_BOX_TYPE( LSMASH_CODEC_TYPE_UNSPECIFIED, ISOM_BOX_TYPE_BTRT ); + GUESS_VIDEO_CODEC_SPECIFIC_BOX_TYPE( LSMASH_CODEC_TYPE_UNSPECIFIED, QT_BOX_TYPE_FIEL ); + GUESS_VIDEO_CODEC_SPECIFIC_BOX_TYPE( LSMASH_CODEC_TYPE_UNSPECIFIED, QT_BOX_TYPE_CSPC ); + GUESS_VIDEO_CODEC_SPECIFIC_BOX_TYPE( LSMASH_CODEC_TYPE_UNSPECIFIED, QT_BOX_TYPE_SGBT ); + GUESS_VIDEO_CODEC_SPECIFIC_BOX_TYPE( LSMASH_CODEC_TYPE_UNSPECIFIED, QT_BOX_TYPE_GAMA ); + GUESS_VIDEO_CODEC_SPECIFIC_BOX_TYPE( LSMASH_CODEC_TYPE_UNSPECIFIED, QT_BOX_TYPE_GLBL ); +#undef GUESS_VIDEO_CODEC_SPECIFIC_BOX_TYPE + return box_type; +} + +static int isom_setup_visual_description( isom_stsd_t *stsd, lsmash_video_summary_t *summary ) +{ + if( !summary || LSMASH_IS_NON_EXISTING_BOX( stsd->parent->parent->parent->parent ) ) + return LSMASH_ERR_NAMELESS; + int err = isom_check_valid_summary( (lsmash_summary_t *)summary ); + if( err < 0 ) + return err; + isom_visual_entry_t *visual = isom_add_visual_description( stsd, summary->sample_type ); + if( LSMASH_IS_NON_EXISTING_BOX( visual ) ) + return LSMASH_ERR_NAMELESS; + visual->data_reference_index = summary->data_ref_index; + visual->version = 0; + visual->revision_level = 0; + visual->vendor = 0; + visual->temporalQuality = 0; + visual->spatialQuality = 0; + visual->width = (uint16_t)summary->width; + visual->height = (uint16_t)summary->height; + visual->horizresolution = 0x00480000; + visual->vertresolution = 0x00480000; + visual->dataSize = 0; + visual->frame_count = 1; + visual->depth = isom_is_qt_video( visual->type ) || isom_is_nalff( visual->type ) + ? summary->depth : 0x0018; + visual->color_table_ID = -1; + if( summary->compressorname[0] == '\0' ) + isom_set_default_compressorname( visual->compressorname, visual->type ); + else + { + memcpy( visual->compressorname, summary->compressorname, 32 ); + visual->compressorname[32] = '\0'; + } + err = LSMASH_ERR_NAMELESS; + for( lsmash_entry_t *entry = summary->opaque->list.head; entry; entry = entry->next ) + { + lsmash_codec_specific_t *specific = (lsmash_codec_specific_t *)entry->data; + if( !specific ) + goto fail; + if( specific->type == LSMASH_CODEC_SPECIFIC_DATA_TYPE_UNKNOWN + && specific->format == LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ) + continue; /* LSMASH_CODEC_SPECIFIC_DATA_TYPE_UNKNOWN + LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED is not supported. */ + switch( specific->type ) + { + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_COMMON : + { + if( specific->format == LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED ) + continue; + lsmash_qt_video_common_t *data = (lsmash_qt_video_common_t *)specific->data.structured; + visual->revision_level = data->revision_level; + visual->vendor = data->vendor; + visual->temporalQuality = data->temporalQuality; + visual->spatialQuality = data->spatialQuality; + visual->horizresolution = data->horizontal_resolution; + visual->vertresolution = data->vertical_resolution; + visual->dataSize = data->dataSize; + visual->frame_count = data->frame_count; + visual->color_table_ID = data->color_table_ID; + if( data->color_table_ID == 0 ) + { + lsmash_qt_color_table_t *src_ct = &data->color_table; + uint16_t element_count = LSMASH_MIN( src_ct->size + 1, 256 ); + isom_qt_color_array_t *dst_array = lsmash_malloc_zero( element_count * sizeof(isom_qt_color_array_t) ); + if( !dst_array ) + { + err = LSMASH_ERR_MEMORY_ALLOC; + goto fail; + } + isom_qt_color_table_t *dst_ct = &visual->color_table; + dst_ct->array = dst_array; + dst_ct->seed = src_ct->seed; + dst_ct->flags = src_ct->flags; + dst_ct->size = src_ct->size; + for( uint16_t i = 0; i < element_count; i++ ) + { + dst_array[i].value = src_ct->array[i].unused; + dst_array[i].r = src_ct->array[i].r; + dst_array[i].g = src_ct->array[i].g; + dst_array[i].b = src_ct->array[i].b; + } + } + break; + } + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_SAMPLE_SCALE : + { + lsmash_codec_specific_t *cs = lsmash_convert_codec_specific_format( specific, LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !cs ) + goto fail; + lsmash_isom_sample_scale_t *data = (lsmash_isom_sample_scale_t *)cs->data.structured; + isom_stsl_t *stsl = isom_add_stsl( visual ); + if( LSMASH_IS_NON_EXISTING_BOX( stsl ) ) + { + lsmash_destroy_codec_specific_data( cs ); + goto fail; + } + stsl->constraint_flag = data->constraint_flag; + stsl->scale_method = data->scale_method; + stsl->display_center_x = data->display_center_x; + stsl->display_center_y = data->display_center_y; + lsmash_destroy_codec_specific_data( cs ); + break; + } + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_H264_BITRATE : + { + lsmash_codec_specific_t *cs = lsmash_convert_codec_specific_format( specific, LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !cs ) + goto fail; + lsmash_h264_bitrate_t *data = (lsmash_h264_bitrate_t *)cs->data.structured; + isom_btrt_t *btrt = isom_add_btrt( visual ); + if( LSMASH_IS_NON_EXISTING_BOX( btrt ) ) + { + lsmash_destroy_codec_specific_data( cs ); + goto fail; + } + btrt->bufferSizeDB = data->bufferSizeDB; + btrt->maxBitrate = data->maxBitrate; + btrt->avgBitrate = data->avgBitrate; + lsmash_destroy_codec_specific_data( cs ); + break; + } + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_FIELD_INFO : + { + lsmash_codec_specific_t *cs = lsmash_convert_codec_specific_format( specific, LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !cs ) + goto fail; + lsmash_qt_field_info_t *data = (lsmash_qt_field_info_t *)cs->data.structured; + isom_fiel_t *fiel = isom_add_fiel( visual ); + if( LSMASH_IS_NON_EXISTING_BOX( fiel ) ) + { + lsmash_destroy_codec_specific_data( cs ); + goto fail; + } + fiel->fields = data->fields; + fiel->detail = data->detail; + lsmash_destroy_codec_specific_data( cs ); + break; + } + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_PIXEL_FORMAT : + { + lsmash_codec_specific_t *cs = lsmash_convert_codec_specific_format( specific, LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !cs ) + goto fail; + lsmash_qt_pixel_format_t *data = (lsmash_qt_pixel_format_t *)cs->data.structured; + isom_cspc_t *cspc = isom_add_cspc( visual ); + if( LSMASH_IS_NON_EXISTING_BOX( cspc ) ) + { + lsmash_destroy_codec_specific_data( cs ); + goto fail; + } + cspc->pixel_format = data->pixel_format; + lsmash_destroy_codec_specific_data( cs ); + break; + } + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_SIGNIFICANT_BITS : + { + lsmash_codec_specific_t *cs = lsmash_convert_codec_specific_format( specific, LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !cs ) + goto fail; + lsmash_qt_significant_bits_t *data = (lsmash_qt_significant_bits_t *)cs->data.structured; + isom_sgbt_t *sgbt = isom_add_sgbt( visual ); + if( LSMASH_IS_NON_EXISTING_BOX( sgbt ) ) + { + lsmash_destroy_codec_specific_data( cs ); + goto fail; + } + sgbt->significantBits = data->significantBits; + lsmash_destroy_codec_specific_data( cs ); + break; + } + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_GAMMA_LEVEL : + { + lsmash_codec_specific_t *cs = lsmash_convert_codec_specific_format( specific, LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !cs ) + goto fail; + lsmash_qt_gamma_t *data = (lsmash_qt_gamma_t *)cs->data.structured; + isom_gama_t *gama = isom_add_gama( visual ); + if( LSMASH_IS_NON_EXISTING_BOX( gama ) ) + { + lsmash_destroy_codec_specific_data( cs ); + goto fail; + } + gama->level = data->level; + lsmash_destroy_codec_specific_data( cs ); + break; + } + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_CONTENT_LIGHT_LEVEL_INFO : + { + lsmash_codec_specific_t *cs = lsmash_convert_codec_specific_format( specific, LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !cs ) + goto fail; + lsmash_qt_content_light_level_info_t *data = (lsmash_qt_content_light_level_info_t *)cs->data.structured; + isom_clli_t *clli = isom_add_clli( visual ); + if( LSMASH_IS_NON_EXISTING_BOX( clli ) ) + { + lsmash_destroy_codec_specific_data( cs ); + goto fail; + } + clli->max_content_light_level = data->max_content_light_level; + clli->max_pic_average_light_level = data->max_pic_average_light_level; + lsmash_destroy_codec_specific_data( cs ); + break; + } + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_MASTERING_DISPLAY_COLOR_VOLUME : + { + lsmash_codec_specific_t *cs = lsmash_convert_codec_specific_format( specific, LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !cs ) + goto fail; + lsmash_qt_mastering_display_color_volume_t *data = (lsmash_qt_mastering_display_color_volume_t *)cs->data.structured; + isom_mdcv_t *mdcv = isom_add_mdcv( visual ); + if( LSMASH_IS_NON_EXISTING_BOX( mdcv ) ) + { + lsmash_destroy_codec_specific_data( cs ); + goto fail; + } + mdcv->display_primaries_g_x = data->display_primaries_g_x; + mdcv->display_primaries_g_y = data->display_primaries_g_y; + mdcv->display_primaries_b_x = data->display_primaries_b_x; + mdcv->display_primaries_b_y = data->display_primaries_b_y; + mdcv->display_primaries_r_x = data->display_primaries_r_x; + mdcv->display_primaries_r_y = data->display_primaries_r_y; + mdcv->white_point_x = data->white_point_x; + mdcv->white_point_y = data->white_point_y; + mdcv->max_display_mastering_luminance = data->max_display_mastering_luminance; + mdcv->min_display_mastering_luminance = data->min_display_mastering_luminance; + lsmash_destroy_codec_specific_data( cs ); + break; + } + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_CODEC_GLOBAL_HEADER : + { + lsmash_codec_specific_t *cs = lsmash_convert_codec_specific_format( specific, LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !cs ) + goto fail; + lsmash_codec_global_header_t *data = (lsmash_codec_global_header_t *)cs->data.structured; + isom_glbl_t *glbl = isom_add_glbl( visual ); + if( LSMASH_IS_NON_EXISTING_BOX( glbl ) ) + { + lsmash_destroy_codec_specific_data( cs ); + goto fail; + } + glbl->header_size = data->header_size; + glbl->header_data = lsmash_memdup( data->header_data, data->header_size ); + lsmash_destroy_codec_specific_data( cs ); + if( !glbl->header_data ) + { + isom_remove_box_by_itself( glbl ); + err = LSMASH_ERR_MEMORY_ALLOC; + goto fail; + } + break; + } + default : + { + lsmash_codec_specific_t *cs = lsmash_convert_codec_specific_format( specific, LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED ); + if( !cs ) + goto fail; + if( cs->size < ISOM_BASEBOX_COMMON_SIZE ) + { + lsmash_destroy_codec_specific_data( cs ); + err = LSMASH_ERR_INVALID_DATA; + goto fail; + } + uint8_t *data = cs->data.unstructured; + lsmash_compact_box_type_t fourcc = LSMASH_4CC( data[4], data[5], data[6], data[7] ); + lsmash_box_type_t box_type = isom_guess_video_codec_specific_box_type( visual->type, fourcc ); + /* Append the extension. */ + err = isom_add_extension_binary( visual, box_type, LSMASH_BOX_PRECEDENCE_HM, cs->data.unstructured, cs->size ); + cs->data.unstructured = NULL; /* Avoid freeing the binary data of the extension. */ + lsmash_destroy_codec_specific_data( cs ); + if( err < 0 ) + goto fail; + break; + } + } + } + isom_trak_t *trak = (isom_trak_t *)visual->parent->parent->parent->parent->parent; + int qt_compatible = trak->file->qt_compatible; + isom_tapt_t *tapt = trak->tapt; + isom_stsl_t *stsl = (isom_stsl_t *)isom_get_extension_box_format( &visual->extensions, ISOM_BOX_TYPE_STSL ); + int set_aperture_modes = qt_compatible /* Track Aperture Modes is only available under QuickTime file format. */ + && (LSMASH_IS_NON_EXISTING_BOX( stsl ) || stsl->scale_method == 0) /* Sample scaling method might conflict with this feature. */ + && LSMASH_IS_EXISTING_BOX( tapt->clef ) + && LSMASH_IS_EXISTING_BOX( tapt->prof ) + && LSMASH_IS_EXISTING_BOX( tapt->enof ) /* Check if required boxes exist. */ + && ((isom_stsd_t *)visual->parent)->list.entry_count == 1; /* Multiple sample description might conflict with this, so in that case, disable this feature. */ + if( !set_aperture_modes ) + isom_remove_box_by_itself( trak->tapt ); + int uncompressed_ycbcr = qt_compatible && isom_is_uncompressed_ycbcr( visual->type ); + /* Set up Clean Aperture. */ + if( set_aperture_modes || uncompressed_ycbcr + || (summary->clap.width.d && summary->clap.height.d && summary->clap.horizontal_offset.d && summary->clap.vertical_offset.d) ) + { + isom_clap_t *clap = isom_add_clap( visual ); + if( LSMASH_IS_NON_EXISTING_BOX( clap ) ) + goto fail; + if( summary->clap.width.d && summary->clap.height.d && summary->clap.horizontal_offset.d && summary->clap.vertical_offset.d ) + { + clap->cleanApertureWidthN = summary->clap.width.n; + clap->cleanApertureWidthD = summary->clap.width.d; + clap->cleanApertureHeightN = summary->clap.height.n; + clap->cleanApertureHeightD = summary->clap.height.d; + clap->horizOffN = summary->clap.horizontal_offset.n; + clap->horizOffD = summary->clap.horizontal_offset.d; + clap->vertOffN = summary->clap.vertical_offset.n; + clap->vertOffD = summary->clap.vertical_offset.d; + } + else + { + clap->cleanApertureWidthN = summary->width; + clap->cleanApertureWidthD = 1; + clap->cleanApertureHeightN = summary->height; + clap->cleanApertureHeightD = 1; + clap->horizOffN = 0; + clap->horizOffD = 1; + clap->vertOffN = 0; + clap->vertOffD = 1; + } + } + /* Set up Pixel Aspect Ratio. */ + if( set_aperture_modes || (summary->par_h && summary->par_v) ) + { + isom_pasp_t *pasp = isom_add_pasp( visual ); + if( LSMASH_IS_NON_EXISTING_BOX( pasp ) ) + goto fail; + pasp->hSpacing = LSMASH_MAX( summary->par_h, 1 ); + pasp->vSpacing = LSMASH_MAX( summary->par_v, 1 ); + } + /* Set up Color Parameter. */ + if( uncompressed_ycbcr + || summary->color.primaries_index + || summary->color.transfer_index + || summary->color.matrix_index + || (trak->file->isom_compatible && summary->color.full_range) ) + { + isom_colr_t *colr = isom_add_colr( visual ); + if( LSMASH_IS_NON_EXISTING_BOX( colr ) ) + goto fail; + /* Set 'nclc' to parameter type, we don't support 'prof'. */ + uint16_t primaries = summary->color.primaries_index; + uint16_t transfer = summary->color.transfer_index; + uint16_t matrix = summary->color.matrix_index; + if( qt_compatible && !trak->file->isom_compatible ) + { + colr->manager |= LSMASH_QTFF_BASE; + colr->type = QT_BOX_TYPE_COLR; + colr->color_parameter_type = QT_COLOR_PARAMETER_TYPE_NCLC; + colr->primaries_index = (primaries == 1 || primaries == 5 || primaries == 6) + ? primaries : QT_PRIMARIES_INDEX_UNSPECIFIED; + colr->transfer_function_index = (transfer == 1 || transfer == 7) + ? transfer : QT_TRANSFER_INDEX_UNSPECIFIED; + colr->matrix_index = (matrix == 1 || matrix == 6 || matrix == 7) + ? matrix : QT_MATRIX_INDEX_UNSPECIFIED; + } + else + { + colr->type = ISOM_BOX_TYPE_COLR; + colr->color_parameter_type = ISOM_COLOR_PARAMETER_TYPE_NCLX; + colr->primaries_index = (primaries == 1 || (primaries >= 4 && primaries <= 7)) + ? primaries : ISOM_PRIMARIES_INDEX_UNSPECIFIED; + colr->transfer_function_index = (transfer == 1 || (transfer >= 4 && transfer <= 8) || (transfer >= 11 && transfer <= 13)) + ? transfer : ISOM_TRANSFER_INDEX_UNSPECIFIED; + colr->matrix_index = (matrix == 1 || (matrix >= 4 && matrix <= 8)) + ? matrix : ISOM_MATRIX_INDEX_UNSPECIFIED; + colr->full_range_flag = summary->color.full_range; + } + } + /* Set up Track Apeture Modes. */ + if( set_aperture_modes ) + { + uint32_t width = visual->width << 16; + uint32_t height = visual->height << 16; + isom_clap_t *clap = (isom_clap_t *)isom_get_extension_box_format( &visual->extensions, ISOM_BOX_TYPE_CLAP ); + isom_pasp_t *pasp = (isom_pasp_t *)isom_get_extension_box_format( &visual->extensions, ISOM_BOX_TYPE_PASP ); + double clap_width = ((double)clap->cleanApertureWidthN / clap->cleanApertureWidthD) * (1<<16); + double clap_height = ((double)clap->cleanApertureHeightN / clap->cleanApertureHeightD) * (1<<16); + double par = (double)pasp->hSpacing / pasp->vSpacing; + if( par >= 1.0 ) + { + tapt->clef->width = clap_width * par; + tapt->clef->height = clap_height; + tapt->prof->width = width * par; + tapt->prof->height = height; + } + else + { + tapt->clef->width = clap_width; + tapt->clef->height = clap_height / par; + tapt->prof->width = width; + tapt->prof->height = height / par; + } + tapt->enof->width = width; + tapt->enof->height = height; + } + return 0; +fail: + isom_remove_box_by_itself( visual ); + return err; +} + +static int isom_append_audio_es_descriptor_extension( isom_box_t *box, lsmash_audio_summary_t *summary ) +{ + uint32_t esds_size = 0; + uint8_t *esds_data = NULL; + lsmash_codec_specific_t *specific = isom_get_codec_specific( summary->opaque, LSMASH_CODEC_SPECIFIC_DATA_TYPE_MP4SYS_DECODER_CONFIG ); + if( !specific ) + return LSMASH_ERR_NAMELESS; + if( specific->format == LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED ) + { + esds_size = specific->size; + esds_data = lsmash_memdup( specific->data.unstructured, specific->size ); + if( !esds_data ) + return LSMASH_ERR_MEMORY_ALLOC; + } + else + { + esds_data = lsmash_create_mp4sys_decoder_config( (lsmash_mp4sys_decoder_parameters_t *)specific->data.structured, &esds_size ); + if( !esds_data ) + return LSMASH_ERR_NAMELESS; + } + isom_esds_t *esds = isom_add_esds( box ); + if( LSMASH_IS_NON_EXISTING_BOX( esds ) ) + { + lsmash_free( esds_data ); + return LSMASH_ERR_NAMELESS; + } + lsmash_bs_t bs = { 0 }; + bs.buffer.data = esds_data + ISOM_FULLBOX_COMMON_SIZE; + bs.buffer.alloc = esds_size - ISOM_FULLBOX_COMMON_SIZE; + bs.buffer.store = bs.buffer.alloc; + esds->ES = mp4sys_get_descriptor( &bs, NULL ); + lsmash_free( esds_data ); + if( !esds->ES ) + { + isom_remove_box_by_itself( esds ); + return LSMASH_ERR_NAMELESS; + } + return 0; +} + +static int isom_append_channel_layout_extension( lsmash_codec_specific_t *specific, void *parent, uint32_t channels ) +{ + assert( LSMASH_IS_EXISTING_BOX( (isom_box_t *)parent ) ); + if( isom_get_extension_box( &((isom_box_t *)parent)->extensions, QT_BOX_TYPE_CHAN ) ) + return 0; /* Audio Channel Layout Box is already present. */ + lsmash_codec_specific_t *cs = lsmash_convert_codec_specific_format( specific, LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !cs ) + return LSMASH_ERR_NAMELESS; + lsmash_qt_audio_channel_layout_t *data = (lsmash_qt_audio_channel_layout_t *)cs->data.structured; + lsmash_channel_layout_tag channelLayoutTag = data->channelLayoutTag; + lsmash_channel_bitmap channelBitmap = data->channelBitmap; + if( channelLayoutTag == QT_CHANNEL_LAYOUT_USE_CHANNEL_DESCRIPTIONS /* We don't support the feature of Channel Descriptions. */ + || (channelLayoutTag == QT_CHANNEL_LAYOUT_USE_CHANNEL_BITMAP && (!channelBitmap || channelBitmap > QT_CHANNEL_BIT_FULL)) ) + { + channelLayoutTag = QT_CHANNEL_LAYOUT_UNKNOWN | channels; + channelBitmap = 0; + } + lsmash_destroy_codec_specific_data( cs ); + /* Don't create Audio Channel Layout Box if the channel layout is unknown. */ + if( (channelLayoutTag ^ QT_CHANNEL_LAYOUT_UNKNOWN) >> 16 ) + { + isom_chan_t *chan = isom_add_chan( parent ); + if( LSMASH_IS_NON_EXISTING_BOX( chan ) ) + return LSMASH_ERR_NAMELESS; + chan->channelLayoutTag = channelLayoutTag; + chan->channelBitmap = channelBitmap; + chan->numberChannelDescriptions = 0; + chan->channelDescriptions = NULL; + } + return 0; +} + +static int isom_set_qtff_mp4a_description( isom_audio_entry_t *audio, lsmash_audio_summary_t *summary ) +{ + isom_wave_t *wave; + isom_frma_t *frma; + isom_mp4a_t *mp4a; + isom_terminator_t *terminator; + if( (wave = isom_add_wave( audio ), LSMASH_IS_NON_EXISTING_BOX( wave )) + || (frma = isom_add_frma( wave ), LSMASH_IS_NON_EXISTING_BOX( frma )) + || (mp4a = isom_add_mp4a( wave ), LSMASH_IS_NON_EXISTING_BOX( mp4a )) + || (terminator = isom_add_terminator( wave ), LSMASH_IS_NON_EXISTING_BOX( terminator )) ) + { + lsmash_list_remove_entry_tail( &audio->extensions ); + return LSMASH_ERR_NAMELESS; + } + frma->data_format = audio->type.fourcc; + /* Add ES Descriptor Box. */ + int err = isom_append_audio_es_descriptor_extension( (isom_box_t *)wave, summary ); + if( err < 0 ) + return err; + /* */ + audio->type = QT_CODEC_TYPE_MP4A_AUDIO; + audio->version = (summary->channels > 2 || summary->frequency > UINT16_MAX) ? 2 : 1; + audio->channelcount = audio->version == 2 ? 3 : LSMASH_MIN( summary->channels, 2 ); + audio->samplesize = 16; + audio->compression_ID = QT_AUDIO_COMPRESSION_ID_VARIABLE_COMPRESSION; + audio->packet_size = 0; + if( audio->version == 1 ) + { + audio->samplerate = summary->frequency << 16; + audio->samplesPerPacket = summary->samples_in_frame; + audio->bytesPerPacket = 1; /* Apparently, this field is set to 1. */ + audio->bytesPerFrame = audio->bytesPerPacket * summary->channels; + audio->bytesPerSample = 2; + } + else /* audio->version == 2 */ + { + audio->samplerate = 0x00010000; + audio->sizeOfStructOnly = 72; + audio->audioSampleRate = (union {double d; uint64_t i;}){summary->frequency}.i; + audio->numAudioChannels = summary->channels; + audio->always7F000000 = 0x7F000000; + audio->constBitsPerChannel = 0; /* compressed audio */ + audio->formatSpecificFlags = 0; + audio->constBytesPerAudioPacket = 0; /* variable */ + audio->constLPCMFramesPerAudioPacket = summary->samples_in_frame; + } + return 0; +} + +static int isom_set_isom_mp4a_description( isom_audio_entry_t *audio, lsmash_audio_summary_t *summary ) +{ + if( summary->summary_type != LSMASH_SUMMARY_TYPE_AUDIO ) + return LSMASH_ERR_NAMELESS; + /* Check objectTypeIndication. */ + lsmash_mp4sys_object_type_indication objectTypeIndication = lsmash_mp4sys_get_object_type_indication( (lsmash_summary_t *)summary ); + switch( objectTypeIndication ) + { + case MP4SYS_OBJECT_TYPE_Audio_ISO_14496_3: + case MP4SYS_OBJECT_TYPE_Audio_ISO_13818_7_Main_Profile: + case MP4SYS_OBJECT_TYPE_Audio_ISO_13818_7_LC_Profile: + case MP4SYS_OBJECT_TYPE_Audio_ISO_13818_7_SSR_Profile: + case MP4SYS_OBJECT_TYPE_Audio_ISO_13818_3: /* Legacy Interface */ + case MP4SYS_OBJECT_TYPE_Audio_ISO_11172_3: /* Legacy Interface */ + break; + default: + return LSMASH_ERR_NAMELESS; + } + /* Add ES Descriptor Box. */ + int err = isom_append_audio_es_descriptor_extension( (isom_box_t *)audio, summary ); + if( err < 0 ) + return err; + /* In pure mp4 file, these "template" fields shall be default values according to the spec. + But not pure - hybrid with other spec - mp4 file can take other values. + Which is to say, these template values shall be ignored in terms of mp4, except some object_type_indications. + see 14496-14, "Template fields used". */ + audio->type = ISOM_CODEC_TYPE_MP4A_AUDIO; + audio->version = 0; + audio->revision_level = 0; + audio->vendor = 0; + audio->channelcount = 2; + audio->samplesize = 16; + audio->compression_ID = 0; + audio->packet_size = 0; + /* WARNING: This field cannot retain frequency above 65535Hz. + This is not "FIXME", I just honestly implemented what the spec says. + BTW, who ever expects sampling frequency takes fixed-point decimal??? */ + audio->samplerate = summary->frequency <= UINT16_MAX ? summary->frequency << 16 : 0; + return 0; +} + +static int isom_set_qtff_lpcm_description( isom_audio_entry_t *audio, lsmash_audio_summary_t *summary ) +{ + lsmash_qt_audio_format_specific_flags_t *lpcm = NULL; + for( lsmash_entry_t *entry = summary->opaque->list.head; entry; entry = entry->next ) + { + lsmash_codec_specific_t *specific = (lsmash_codec_specific_t *)entry->data; + if( !specific ) + continue; + if( specific->type == LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_FORMAT_SPECIFIC_FLAGS + && specific->format == LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ) + { + lpcm = (lsmash_qt_audio_format_specific_flags_t *)specific->data.structured; + break; + } + } + if( !lpcm ) + return LSMASH_ERR_NAMELESS; + audio->manager |= LSMASH_QTFF_BASE; + lsmash_codec_type_t sample_type = audio->type; + /* Convert the sample type into 'lpcm' if the description doesn't match the format or version = 2 fields are needed. */ + if( (lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_RAW_AUDIO ) + && (summary->sample_size != 8 || (lpcm->format_flags & QT_LPCM_FORMAT_FLAG_FLOAT))) + || (lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_FL32_AUDIO ) + && (summary->sample_size != 32 || !(lpcm->format_flags & QT_LPCM_FORMAT_FLAG_FLOAT))) + || (lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_FL64_AUDIO ) + && (summary->sample_size != 64 || !(lpcm->format_flags & QT_LPCM_FORMAT_FLAG_FLOAT))) + || (lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_IN24_AUDIO ) + && (summary->sample_size != 24 || (lpcm->format_flags & QT_LPCM_FORMAT_FLAG_FLOAT))) + || (lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_IN32_AUDIO ) + && (summary->sample_size != 32 || (lpcm->format_flags & QT_LPCM_FORMAT_FLAG_FLOAT))) + || (lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_23NI_AUDIO ) + && (summary->sample_size != 32 || (lpcm->format_flags & QT_LPCM_FORMAT_FLAG_FLOAT) || (lpcm->format_flags & QT_LPCM_FORMAT_FLAG_BIG_ENDIAN))) + || (lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_SOWT_AUDIO ) + && (summary->sample_size != 16 || (lpcm->format_flags & QT_LPCM_FORMAT_FLAG_FLOAT) || (lpcm->format_flags & QT_LPCM_FORMAT_FLAG_BIG_ENDIAN))) + || (lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_TWOS_AUDIO ) + && ((summary->sample_size != 16 && summary->sample_size != 8) || (lpcm->format_flags & QT_LPCM_FORMAT_FLAG_FLOAT) || !(lpcm->format_flags & QT_LPCM_FORMAT_FLAG_BIG_ENDIAN))) + || (lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_NONE_AUDIO ) + && ((summary->sample_size != 16 && summary->sample_size != 8) || (lpcm->format_flags & QT_LPCM_FORMAT_FLAG_FLOAT) || !(lpcm->format_flags & QT_LPCM_FORMAT_FLAG_BIG_ENDIAN))) + || (lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_NOT_SPECIFIED ) + && ((summary->sample_size != 16 && summary->sample_size != 8) || (lpcm->format_flags & QT_LPCM_FORMAT_FLAG_FLOAT) || !(lpcm->format_flags & QT_LPCM_FORMAT_FLAG_BIG_ENDIAN))) + || (summary->channels > 2 || summary->frequency > UINT16_MAX || summary->sample_size % 8) ) + { + audio->type = QT_CODEC_TYPE_LPCM_AUDIO; + audio->version = 2; + } + else if( lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_LPCM_AUDIO ) ) + audio->version = 2; + else if( summary->sample_size > 16 + || (!lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_RAW_AUDIO ) + && !lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_TWOS_AUDIO ) + && !lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_NONE_AUDIO ) + && !lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_NOT_SPECIFIED )) ) + audio->version = 1; + /* Set up constBytesPerAudioPacket field. + * We use constBytesPerAudioPacket as the actual size of LPCM audio frame even when version is not 2. */ + audio->constBytesPerAudioPacket = (summary->sample_size * summary->channels) / 8; + /* Set up other fields in this description by its version. */ + if( audio->version == 2 ) + { + audio->channelcount = 3; + audio->samplesize = 16; + audio->compression_ID = -2; + audio->samplerate = 0x00010000; + audio->sizeOfStructOnly = 72; + audio->audioSampleRate = (union {double d; uint64_t i;}){summary->frequency}.i; + audio->numAudioChannels = summary->channels; + audio->always7F000000 = 0x7F000000; + audio->constBitsPerChannel = summary->sample_size; + audio->constLPCMFramesPerAudioPacket = 1; + audio->formatSpecificFlags = lpcm->format_flags; + if( lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_TWOS_AUDIO ) && summary->sample_size != 8 ) + audio->formatSpecificFlags |= QT_LPCM_FORMAT_FLAG_BIG_ENDIAN; + if( lpcm->format_flags & QT_LPCM_FORMAT_FLAG_FLOAT ) + audio->formatSpecificFlags &= ~QT_LPCM_FORMAT_FLAG_SIGNED_INTEGER; + if( lpcm->format_flags & QT_LPCM_FORMAT_FLAG_PACKED ) + audio->formatSpecificFlags &= ~QT_LPCM_FORMAT_FLAG_ALIGNED_HIGH; + } + else if( audio->version == 1 ) + { + audio->channelcount = summary->channels; + audio->samplesize = 16; + /* Audio formats other than 'raw ' and 'twos' are treated as compressed audio. */ + if( lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_RAW_AUDIO ) + || lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_TWOS_AUDIO ) ) + audio->compression_ID = QT_AUDIO_COMPRESSION_ID_NOT_COMPRESSED; + else + audio->compression_ID = QT_AUDIO_COMPRESSION_ID_FIXED_COMPRESSION; + audio->samplerate = summary->frequency << 16; + audio->samplesPerPacket = 1; + audio->bytesPerPacket = summary->sample_size / 8; + audio->bytesPerFrame = audio->bytesPerPacket * summary->channels; /* sample_size field in stsz box is NOT used. */ + audio->bytesPerSample = 1 + (summary->sample_size != 8); + if( lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_FL32_AUDIO ) + || lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_FL64_AUDIO ) + || lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_IN24_AUDIO ) + || lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_IN32_AUDIO ) ) + { + isom_wave_t *wave; + isom_frma_t *frma; + isom_enda_t *enda; + isom_terminator_t *terminator; + if( (wave = isom_add_wave( audio ), LSMASH_IS_NON_EXISTING_BOX( wave )) + || (frma = isom_add_frma( wave ), LSMASH_IS_NON_EXISTING_BOX( frma )) + || (enda = isom_add_enda( wave ), LSMASH_IS_NON_EXISTING_BOX( enda )) + || (terminator = isom_add_terminator( wave ), LSMASH_IS_NON_EXISTING_BOX( terminator )) ) + { + lsmash_list_remove_entry_tail( &audio->extensions ); + return LSMASH_ERR_NAMELESS; + } + frma->data_format = sample_type.fourcc; + enda->littleEndian = !(lpcm->format_flags & QT_LPCM_FORMAT_FLAG_BIG_ENDIAN); + } + } + else /* audio->version == 0 */ + { + audio->channelcount = summary->channels; + audio->samplesize = summary->sample_size; + audio->compression_ID = QT_AUDIO_COMPRESSION_ID_NOT_COMPRESSED; + audio->samplerate = summary->frequency << 16; + } + return 0; +} + +static int isom_set_isom_dts_audio_description( isom_audio_entry_t *audio, lsmash_audio_summary_t *summary ) +{ + audio->version = 0; + audio->revision_level = 0; + audio->vendor = 0; + audio->channelcount = summary->channels; + audio->samplesize = 16; + audio->compression_ID = 0; + audio->packet_size = 0; + switch( summary->frequency ) + { + case 12000 : /* Invalid? (No reference in the spec) */ + case 24000 : + case 48000 : + case 96000 : + case 192000 : + case 384000 : /* Invalid? (No reference in the spec) */ + audio->samplerate = 48000 << 16; + break; + case 22050 : + case 44100 : + case 88200 : + case 176400 : + case 352800 : /* Invalid? (No reference in the spec) */ + audio->samplerate = 44100 << 16; + break; + case 8000 : /* Invalid? (No reference in the spec) */ + case 16000 : + case 32000 : + case 64000 : + case 128000 : + audio->samplerate = 32000 << 16; + break; + default : + audio->samplerate = 0; + break; + } + return 0; +} + +static int isom_set_hint_summary( isom_hint_entry_t *hint, lsmash_hint_summary_t *summary ) +{ + hint->hinttrackversion = summary->version; + hint->highestcompatibleversion = summary->highestcompatibleversion; + hint->maxpacketsize = summary->maxpacketsize; + return 0; +} + +static lsmash_box_type_t isom_guess_audio_codec_specific_box_type( lsmash_codec_type_t active_codec_type, lsmash_compact_box_type_t fourcc ) +{ + lsmash_box_type_t box_type = LSMASH_BOX_TYPE_INITIALIZER; + box_type.fourcc = fourcc; +#define GUESS_AUDIO_CODEC_SPECIFIC_BOX_TYPE( codec_type, predefined_box_type ) \ + else if( (codec_type.user.fourcc == 0 \ + || lsmash_check_codec_type_identical( active_codec_type, codec_type )) \ + && box_type.fourcc == predefined_box_type.fourcc ) \ + box_type = predefined_box_type + if( 0 ); + GUESS_AUDIO_CODEC_SPECIFIC_BOX_TYPE( ISOM_CODEC_TYPE_AC_3_AUDIO, ISOM_BOX_TYPE_DAC3 ); + GUESS_AUDIO_CODEC_SPECIFIC_BOX_TYPE( ISOM_CODEC_TYPE_EC_3_AUDIO, ISOM_BOX_TYPE_DEC3 ); + GUESS_AUDIO_CODEC_SPECIFIC_BOX_TYPE( ISOM_CODEC_TYPE_DTSC_AUDIO, ISOM_BOX_TYPE_DDTS ); + GUESS_AUDIO_CODEC_SPECIFIC_BOX_TYPE( ISOM_CODEC_TYPE_DTSE_AUDIO, ISOM_BOX_TYPE_DDTS ); + GUESS_AUDIO_CODEC_SPECIFIC_BOX_TYPE( ISOM_CODEC_TYPE_DTSH_AUDIO, ISOM_BOX_TYPE_DDTS ); + GUESS_AUDIO_CODEC_SPECIFIC_BOX_TYPE( ISOM_CODEC_TYPE_DTSL_AUDIO, ISOM_BOX_TYPE_DDTS ); + GUESS_AUDIO_CODEC_SPECIFIC_BOX_TYPE( ISOM_CODEC_TYPE_DTSX_AUDIO, ISOM_BOX_TYPE_DDTS ); + GUESS_AUDIO_CODEC_SPECIFIC_BOX_TYPE( ISOM_CODEC_TYPE_DTSEL_AUDIO, ISOM_BOX_TYPE_DDTS ); + GUESS_AUDIO_CODEC_SPECIFIC_BOX_TYPE( ISOM_CODEC_TYPE_DTSDL_AUDIO, ISOM_BOX_TYPE_DDTS ); + GUESS_AUDIO_CODEC_SPECIFIC_BOX_TYPE( ISOM_CODEC_TYPE_ALAC_AUDIO, ISOM_BOX_TYPE_ALAC ); + GUESS_AUDIO_CODEC_SPECIFIC_BOX_TYPE( ISOM_CODEC_TYPE_MP4A_AUDIO, ISOM_BOX_TYPE_ESDS ); + GUESS_AUDIO_CODEC_SPECIFIC_BOX_TYPE( QT_CODEC_TYPE_ALAC_AUDIO, QT_BOX_TYPE_ALAC ); + GUESS_AUDIO_CODEC_SPECIFIC_BOX_TYPE( QT_CODEC_TYPE_MP4A_AUDIO, QT_BOX_TYPE_ESDS ); + GUESS_AUDIO_CODEC_SPECIFIC_BOX_TYPE( QT_CODEC_TYPE_FULLMP3_AUDIO, QT_CODEC_TYPE_MP3_AUDIO ); + GUESS_AUDIO_CODEC_SPECIFIC_BOX_TYPE( QT_CODEC_TYPE_ADPCM2_AUDIO, QT_CODEC_TYPE_ADPCM2_AUDIO ); + GUESS_AUDIO_CODEC_SPECIFIC_BOX_TYPE( QT_CODEC_TYPE_ADPCM17_AUDIO, QT_CODEC_TYPE_ADPCM17_AUDIO ); + GUESS_AUDIO_CODEC_SPECIFIC_BOX_TYPE( QT_CODEC_TYPE_GSM49_AUDIO, QT_CODEC_TYPE_GSM49_AUDIO ); + GUESS_AUDIO_CODEC_SPECIFIC_BOX_TYPE( LSMASH_CODEC_TYPE_UNSPECIFIED, QT_BOX_TYPE_CHAN ); + GUESS_AUDIO_CODEC_SPECIFIC_BOX_TYPE( LSMASH_CODEC_TYPE_UNSPECIFIED, QT_BOX_TYPE_GLBL ); + GUESS_AUDIO_CODEC_SPECIFIC_BOX_TYPE( LSMASH_CODEC_TYPE_UNSPECIFIED, QT_BOX_TYPE_WAVE ); +#undef GUESS_AUDIO_CODEC_SPECIFIC_BOX_TYPE + return box_type; +} + +typedef struct +{ + uint16_t wFormatTag; + uint16_t nChannels; + uint32_t nSamplesPerSec; + uint32_t nAvgBytesPerSec; + uint16_t nBlockAlign; + uint16_t wBitsPerSample; + uint16_t cbSize; +} wave_format_ex_t; + +static lsmash_bs_t *isom_create_waveform_audio_info +( + wave_format_ex_t *wfx, + lsmash_box_type_t type +) +{ + lsmash_bs_t *bs = lsmash_bs_create(); + if( !bs ) + return NULL; + lsmash_bs_put_be32( bs, ISOM_BASEBOX_COMMON_SIZE + 18 + wfx->cbSize ); + lsmash_bs_put_be32( bs, type.fourcc ); + lsmash_bs_put_le16( bs, wfx->wFormatTag ); + lsmash_bs_put_le16( bs, wfx->nChannels ); + lsmash_bs_put_le32( bs, wfx->nSamplesPerSec ); + lsmash_bs_put_le32( bs, wfx->nAvgBytesPerSec ); + lsmash_bs_put_le16( bs, wfx->nBlockAlign ); + lsmash_bs_put_le16( bs, wfx->wBitsPerSample ); + lsmash_bs_put_le16( bs, wfx->cbSize ); + return bs; +} + +static int isom_setup_waveform_audio_info +( + isom_wave_t *wave, + isom_audio_entry_t *audio, + lsmash_audio_summary_t *summary, + uint32_t samples_per_packet, + uint32_t bytes_per_frame, + uint32_t sample_size +) +{ + wave_format_ex_t wfx; + wfx.wFormatTag = 0x0000; /* WAVE_FORMAT_UNKNOWN */ + wfx.nChannels = summary->channels; + wfx.nSamplesPerSec = summary->frequency; + wfx.nAvgBytesPerSec = 0; + wfx.nBlockAlign = bytes_per_frame; + wfx.wBitsPerSample = sample_size; + wfx.cbSize = 0; + lsmash_bs_t *bs = NULL; + if( lsmash_check_codec_type_identical( audio->type, QT_CODEC_TYPE_ADPCM2_AUDIO ) ) + { + /* ADPCMWAVEFORMAT */ + wfx.wFormatTag = 0x0002; /* WAVE_FORMAT_ADPCM */ + wfx.cbSize = 32; + bs = isom_create_waveform_audio_info( &wfx, audio->type ); + if( !bs ) + return LSMASH_ERR_MEMORY_ALLOC; + uint16_t wSamplesPerBlock = samples_per_packet; /* nBlockAlign * 2 / nChannels - 12 */ + uint16_t wNumCoef = 7; /* Microsoft ADPCM uses just 7 coefficients. */ + static const struct + { + int16_t iCoef1; + int16_t iCoef2; + } aCoef[7] = { { 256, 0 }, { 512, -256 }, { 0,0 }, { 192,64 }, { 240,0 }, { 460, -208 }, { 392,-232 } }; + lsmash_bs_put_le16( bs, wSamplesPerBlock ); + lsmash_bs_put_le16( bs, wNumCoef ); + for( int i = 0; i < 7; i++ ) + { + lsmash_bs_put_le16( bs, aCoef[i].iCoef1 ); + lsmash_bs_put_le16( bs, aCoef[i].iCoef2 ); + } + } + else if( lsmash_check_codec_type_identical( audio->type, QT_CODEC_TYPE_ADPCM17_AUDIO ) ) + { + /* IMAADPCMWAVEFORMAT */ + wfx.wFormatTag = 0x0011; /* WAVE_FORMAT_DVI_ADPCM / WAVE_FORMAT_IMA_ADPCM */ + wfx.cbSize = 2; + bs = isom_create_waveform_audio_info( &wfx, audio->type ); + if( !bs ) + return LSMASH_ERR_MEMORY_ALLOC; + uint16_t wSamplesPerBlock = samples_per_packet; + lsmash_bs_put_le16( bs, wSamplesPerBlock ); + } + else if( lsmash_check_codec_type_identical( audio->type, QT_CODEC_TYPE_GSM49_AUDIO ) ) + { + /* GSM610WAVEFORMAT */ + wfx.wFormatTag = 0x0031; /* WAVE_FORMAT_GSM610 */ + wfx.cbSize = 2; + bs = isom_create_waveform_audio_info( &wfx, audio->type ); + if( !bs ) + return LSMASH_ERR_MEMORY_ALLOC; + uint16_t wSamplesPerBlock = samples_per_packet; + lsmash_bs_put_le16( bs, wSamplesPerBlock ); + } + else if( lsmash_check_codec_type_identical( audio->type, QT_CODEC_TYPE_FULLMP3_AUDIO ) + || lsmash_check_codec_type_identical( audio->type, QT_CODEC_TYPE_MP3_AUDIO ) ) + { + /* MPEGLAYER3WAVEFORMAT */ + wfx.wFormatTag = 0x0055; /* WAVE_FORMAT_MPEGLAYER3 */ + wfx.nBlockAlign = 1; /* ? */ + wfx.wBitsPerSample = 0; /* undefined */ + wfx.cbSize = 12; + bs = isom_create_waveform_audio_info( &wfx, audio->type ); + if( !bs ) + return LSMASH_ERR_MEMORY_ALLOC; + uint16_t wID = 1; /* MPEGLAYER3_ID_MPEG */ + uint32_t fdwFlags = 0; /* We don't know whether the stream is padded or not here. */ + uint16_t nBlockSize = 0; /* (144 * (bitrate / nSamplesPerSec) + padding) * nFramesPerBlock */ + uint16_t nFramesPerBlock = 1; /* the number of audio frames per block */ + uint16_t nCodecDelay = 0; /* Encoder delay in samples is unknown. */ + lsmash_bs_put_le16( bs, wID ); + lsmash_bs_put_le32( bs, fdwFlags ); + lsmash_bs_put_le16( bs, nBlockSize ); + lsmash_bs_put_le16( bs, nFramesPerBlock ); + lsmash_bs_put_le16( bs, nCodecDelay ); + } + if( !bs ) + { + assert( 0 ); + return LSMASH_ERR_NAMELESS; + } + uint32_t wfx_size; + uint8_t *wfx_data = lsmash_bs_export_data( bs, &wfx_size ); + lsmash_bs_cleanup( bs ); + if( !wfx_data ) + return LSMASH_ERR_NAMELESS; + if( wfx_size != ISOM_BASEBOX_COMMON_SIZE + 18 + wfx.cbSize ) + { + lsmash_free( wfx_data ); + return LSMASH_ERR_NAMELESS; + } + int err = isom_add_extension_binary( wave, audio->type, LSMASH_BOX_PRECEDENCE_HM, wfx_data, wfx_size ); + if( err < 0 ) + { + lsmash_free( wfx_data ); + return err; + } + return 0; +} + +static int isom_set_qtff_sound_decompression_parameters +( + isom_audio_entry_t *audio, + lsmash_audio_summary_t *summary, + lsmash_qt_audio_format_specific_flag *format_flags, + uint32_t samples_per_packet, + uint32_t bytes_per_frame, + uint32_t sample_size +) +{ + /* A 'wave' extension itself shall be absent in the opaque CODEC specific info list. + * So, create a 'wave' extension here and append it as an extension to the audio sample description. */ + isom_wave_t *wave = isom_add_wave( audio ); + if( LSMASH_IS_NON_EXISTING_BOX( wave ) ) + return LSMASH_ERR_NAMELESS; + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_frma ( wave ) ) + || LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_terminator( wave ) ) ) + { + lsmash_list_remove_entry_tail( &audio->extensions ); + return LSMASH_ERR_NAMELESS; + } + wave->frma->data_format = audio->type.fourcc; + /* Append extensions from the opaque CODEC specific info list to 'wave' extension. */ + int err; + int waveform_audio_info_present = 0; + int requires_waveform_audio_info = isom_is_waveform_audio( audio->type ); + for( lsmash_entry_t *entry = summary->opaque->list.head; entry; entry = entry->next ) + { + lsmash_codec_specific_t *specific = (lsmash_codec_specific_t *)entry->data; + if( !specific ) + return LSMASH_ERR_NAMELESS; + if( specific->type == LSMASH_CODEC_SPECIFIC_DATA_TYPE_UNKNOWN + && specific->format == LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ) + continue; /* LSMASH_CODEC_SPECIFIC_DATA_TYPE_UNKNOWN + LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED is not supported. */ + switch( specific->type ) + { + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_COMMON : + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_CODEC_GLOBAL_HEADER : + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_FORMAT_SPECIFIC_FLAGS : + continue; /* These cannot be an extension for 'wave' extension. */ + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_CHANNEL_LAYOUT : + /* (Legacy?) ALAC might have an Audio Channel Layout Box inside 'wave' extension. */ +#if 1 + continue; +#else + if( lsmash_check_codec_type_identical( audio->type, QT_CODEC_TYPE_ALAC_AUDIO ) ) + continue; + if( (err = isom_append_channel_layout_extension( specific, wave, summary->channels )) < 0 ) + return err; + break; +#endif + default : + { + assert( specific->format == LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED + || specific->type == LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_DECOMPRESSION_PARAMETERS ); + lsmash_codec_specific_t *cs = lsmash_convert_codec_specific_format( specific, LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED ); + if( !cs ) + return LSMASH_ERR_NAMELESS; + if( cs->size < ISOM_BASEBOX_COMMON_SIZE ) + { + lsmash_destroy_codec_specific_data( cs ); + continue; + } + uint8_t *box_data = cs->data.unstructured; + uint64_t box_size = cs->size; + lsmash_compact_box_type_t fourcc = LSMASH_4CC( box_data[4], box_data[5], box_data[6], box_data[7] ); + if( audio->version == 2 && fourcc == QT_BOX_TYPE_ENDA.fourcc ) + { + /* Don't append a 'enda' extension if version == 2. + * Endianness is indicated in QuickTime audio format specific flags. */ + if( box_size >= ISOM_BASEBOX_COMMON_SIZE + 2 ) + { + /* Override endianness indicated in format specific flags. */ + if( box_data[9] == 1 ) + *format_flags &= ~QT_AUDIO_FORMAT_FLAG_BIG_ENDIAN; + else + *format_flags |= QT_AUDIO_FORMAT_FLAG_BIG_ENDIAN; + } + lsmash_destroy_codec_specific_data( cs ); + continue; + } + lsmash_box_type_t box_type = isom_guess_audio_codec_specific_box_type( audio->type, fourcc ); + if( lsmash_check_box_type_identical( box_type, QT_BOX_TYPE_WAVE ) ) + { + /* It is insane to appened a 'wave' extension to a 'wave' extension. */ + lsmash_destroy_codec_specific_data( cs ); + continue; + } + box_type = lsmash_form_qtff_box_type( box_type.fourcc ); + /* Determine 'precedence'. */ + uint64_t precedence; + if( lsmash_check_box_type_identical( box_type, QT_BOX_TYPE_FRMA ) ) + precedence = LSMASH_BOX_PRECEDENCE_QTFF_FRMA; + else if( lsmash_check_box_type_identical( box_type, QT_BOX_TYPE_ESDS ) ) + precedence = LSMASH_BOX_PRECEDENCE_QTFF_ESDS; + else if( lsmash_check_box_type_identical( box_type, QT_BOX_TYPE_ENDA ) ) + precedence = LSMASH_BOX_PRECEDENCE_QTFF_ENDA; + else if( lsmash_check_box_type_identical( box_type, QT_BOX_TYPE_MP4A ) ) + precedence = LSMASH_BOX_PRECEDENCE_QTFF_MP4A; + else if( lsmash_check_box_type_identical( box_type, QT_BOX_TYPE_TERMINATOR ) ) + precedence = LSMASH_BOX_PRECEDENCE_QTFF_TERMINATOR; + else + precedence = LSMASH_BOX_PRECEDENCE_HM; + /* Append the extension. */ + err = isom_add_extension_binary( wave, box_type, precedence, cs->data.unstructured, cs->size ); + cs->data.unstructured = NULL; /* Avoid freeing the binary data of the extension. */ + lsmash_destroy_codec_specific_data( cs ); + if( err < 0 ) + return err; + if( isom_is_waveform_audio( box_type ) ) + waveform_audio_info_present = 1; + break; + } + } + } + if( requires_waveform_audio_info && !waveform_audio_info_present + && (err = isom_setup_waveform_audio_info( wave, audio, summary, samples_per_packet, bytes_per_frame, sample_size )) < 0 ) + return err; + return 0; +} + +static int isom_set_qtff_template_audio_description( isom_audio_entry_t *audio, lsmash_audio_summary_t *summary ) +{ + audio->manager |= LSMASH_QTFF_BASE; + audio->type = lsmash_form_qtff_box_type( audio->type.fourcc ); + audio->version = (summary->channels > 2 || summary->frequency > UINT16_MAX) ? 2 : 1; + /* Try to get QuickTime audio format specific flags. */ + lsmash_qt_audio_format_specific_flag format_flags = QT_AUDIO_FORMAT_FLAG_BIG_ENDIAN; + for( lsmash_entry_t *entry = summary->opaque->list.head; entry; entry = entry->next ) + { + lsmash_codec_specific_t *specific = (lsmash_codec_specific_t *)entry->data; + if( !specific + || !specific->data.structured ) + continue; + if( specific->type == LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_FORMAT_SPECIFIC_FLAGS + && specific->format == LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ) + { + /* A format specific flags is found. + * Force audio sample description version == 2. */ + format_flags = ((lsmash_qt_audio_format_specific_flags_t *)specific->data.structured)->format_flags; + audio->version = 2; + break; + } + } + uint32_t samples_per_packet; + uint32_t bytes_per_frame; + uint32_t sample_size; + if( !((summary->samples_in_frame == 0 || summary->bytes_per_frame == 0 || summary->sample_size == 0) + && isom_get_implicit_qt_fixed_comp_audio_sample_quants( audio, &samples_per_packet, &bytes_per_frame, &sample_size )) ) + { + samples_per_packet = summary->samples_in_frame; + bytes_per_frame = summary->bytes_per_frame; + sample_size = summary->sample_size; + } + if( !lsmash_check_codec_type_identical( audio->type, QT_CODEC_TYPE_MAC3_AUDIO ) + && !lsmash_check_codec_type_identical( audio->type, QT_CODEC_TYPE_MAC6_AUDIO ) + && !lsmash_check_codec_type_identical( audio->type, QT_CODEC_TYPE_AGSM_AUDIO ) + && !lsmash_check_codec_type_identical( audio->type, QT_CODEC_TYPE_ALAW_AUDIO ) + && !lsmash_check_codec_type_identical( audio->type, QT_CODEC_TYPE_ULAW_AUDIO ) ) + { + int err = isom_set_qtff_sound_decompression_parameters( audio, summary, &format_flags, + samples_per_packet, bytes_per_frame, sample_size ); + if( err < 0 ) + return err; + } + /* Set up common audio description fields. */ + audio->samplesize = 16; + audio->packet_size = 0; + if( audio->version == 2 ) + { + audio->channelcount = 3; + audio->compression_ID = QT_AUDIO_COMPRESSION_ID_VARIABLE_COMPRESSION; + audio->samplerate = 0x00010000; + audio->sizeOfStructOnly = 72; + audio->audioSampleRate = (union {double d; uint64_t i;}){summary->frequency}.i; + audio->numAudioChannels = summary->channels; + audio->always7F000000 = 0x7F000000; + audio->constBitsPerChannel = 0; + audio->constBytesPerAudioPacket = bytes_per_frame; + audio->constLPCMFramesPerAudioPacket = samples_per_packet; + if( lsmash_check_codec_type_identical( audio->type, QT_CODEC_TYPE_ALAC_AUDIO ) ) + { + switch( sample_size ) + { + case 16 : + audio->formatSpecificFlags = QT_ALAC_FORMAT_FLAG_16BIT_SOURCE_DATA; + break; + case 20 : + audio->formatSpecificFlags = QT_ALAC_FORMAT_FLAG_20BIT_SOURCE_DATA; + break; + case 24 : + audio->formatSpecificFlags = QT_ALAC_FORMAT_FLAG_24BIT_SOURCE_DATA; + break; + case 32 : + audio->formatSpecificFlags = QT_ALAC_FORMAT_FLAG_32BIT_SOURCE_DATA; + break; + default : + break; + } + } + else + { + if( format_flags & QT_AUDIO_FORMAT_FLAG_FLOAT ) + format_flags &= ~QT_AUDIO_FORMAT_FLAG_SIGNED_INTEGER; + if( format_flags & QT_AUDIO_FORMAT_FLAG_PACKED ) + format_flags &= ~QT_AUDIO_FORMAT_FLAG_ALIGNED_HIGH; + audio->formatSpecificFlags = format_flags; + } + } + else /* if( audio->version == 1 ) */ + { + audio->channelcount = LSMASH_MIN( summary->channels, 2 ); + audio->compression_ID = QT_AUDIO_COMPRESSION_ID_FIXED_COMPRESSION; + audio->samplerate = summary->frequency << 16; + audio->samplesPerPacket = samples_per_packet; + audio->bytesPerPacket = bytes_per_frame / summary->channels; + audio->bytesPerFrame = bytes_per_frame; /* sample_size field in stsz box is NOT used. */ + audio->bytesPerSample = 1 + (sample_size != 8); + } + return 0; +} + +static void isom_set_samplerate_division_of_media_timescale( isom_audio_entry_t *audio, int strict ) +{ + isom_mdia_t *mdia = (isom_mdia_t *)audio->parent->parent->parent->parent; /* audio_entry->stsd->stbl->minf->mdia */ + if( lsmash_check_box_type_identical( mdia->type, ISOM_BOX_TYPE_MDIA ) + && LSMASH_IS_EXISTING_BOX( mdia->mdhd ) ) + { + /* Make an effort to match the timescale with samplerate, or be an integer multiple of it. */ + uint32_t orig_timescale = ((isom_mdia_t *)audio->parent->parent->parent->parent)->mdhd->timescale; + uint32_t timescale = orig_timescale; + uint32_t i = 2; + while( timescale > UINT16_MAX && timescale > 1 ) + { + if( timescale % i == 0 ) + timescale /= i; + else + i += i > 2 ? 2 : 1; + } + if( timescale != orig_timescale && strict ) + lsmash_log( NULL, LSMASH_LOG_WARNING, "samplerate does not match the media timescale.\n" ); + if( timescale <= UINT16_MAX && timescale > 1 ) + { + audio->samplerate = timescale << 16; + return; + } + } + audio->samplerate = 0; +} + +static int isom_set_isom_template_audio_description( isom_audio_entry_t *audio, lsmash_audio_summary_t *summary ) +{ + audio->version = 0; /* reserved */ + audio->revision_level = 0; /* reserved */ + audio->vendor = 0; /* reserved */ + audio->channelcount = 2; /* template */ + audio->samplesize = 16; /* template */ + audio->compression_ID = 0; /* pre_defined */ + audio->packet_size = 0; /* reserved */ + /* template : default output audio sampling rate at playback */ + if( summary->frequency <= UINT16_MAX ) + audio->samplerate = summary->frequency << 16; + else + isom_set_samplerate_division_of_media_timescale( audio, 0 ); + return 0; +} + +static int isom_set_isom_amr_audio_description( isom_audio_entry_t *audio, int wb ) +{ + /* For AMR-NB and AMR-WB stream, these fields are not meaningful. */ + audio->version = 0; /* always 0 */ + audio->revision_level = 0; /* always 0 */ + audio->vendor = 0; /* always 0 */ + audio->channelcount = 2; /* always 2 although the actual number of channels is always 1 */ + audio->samplesize = 16; /* always 16 */ + audio->compression_ID = 0; /* always 0 */ + audio->packet_size = 0; /* always 0 */ + /* Set samplerate by trying to copy from Media Header Box of this media though the + * actual samplerate is 8000 Hz for AMR-NB and 16000 Hz for AMR-WB. + * 3GPP and 3GPP2 has no restriction for media timescale. Therefore, users should + * set suitable media timescale by themselves within the bounds of common sense. */ + isom_set_samplerate_division_of_media_timescale( audio, 1 ); + if( audio->samplerate == 0 ) + /* Set hard-coded but correct samplerate in the CODEC level. */ + audio->samplerate = wb ? 8000 : 16000; + return 0; +} + +static int isom_set_isom_alac_audio_description( isom_audio_entry_t *audio, lsmash_audio_summary_t *summary ) +{ + return isom_set_isom_template_audio_description( audio, summary ); +} + +static int isom_set_qtff_alac_audio_description( isom_audio_entry_t *audio, lsmash_audio_summary_t *summary ) +{ + return isom_set_qtff_template_audio_description( audio, summary ); +} + +static int isom_set_isom_eac3_audio_description( isom_audio_entry_t *audio, lsmash_audio_summary_t *summary ) +{ + return isom_set_isom_template_audio_description( audio, summary ); +} + +static int isom_setup_audio_description( isom_stsd_t *stsd, lsmash_audio_summary_t *summary ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( stsd->file ) || !summary ) + return LSMASH_ERR_NAMELESS; + int err = isom_check_valid_summary( (lsmash_summary_t *)summary ); + if( err < 0 ) + return err; + isom_audio_entry_t *audio = isom_add_audio_description( stsd, summary->sample_type ); + if( LSMASH_IS_NON_EXISTING_BOX( audio ) ) + return LSMASH_ERR_NAMELESS; + audio->data_reference_index = summary->data_ref_index; + lsmash_file_t *file = stsd->file; + lsmash_codec_type_t audio_type = audio->type; + if( lsmash_check_codec_type_identical( audio_type, ISOM_CODEC_TYPE_MP4A_AUDIO ) + || lsmash_check_codec_type_identical( audio_type, QT_CODEC_TYPE_MP4A_AUDIO ) ) + { + if( (LSMASH_IS_EXISTING_BOX( file->ftyp ) && file->ftyp->major_brand == ISOM_BRAND_TYPE_QT) + || (LSMASH_IS_NON_EXISTING_BOX( file->ftyp ) + && (file->qt_compatible || (LSMASH_IS_EXISTING_BOX( file->moov ) && LSMASH_IS_NON_EXISTING_BOX( file->moov->iods )))) ) + err = isom_set_qtff_mp4a_description( audio, summary ); + else + err = isom_set_isom_mp4a_description( audio, summary ); + } + else if( isom_is_lpcm_audio( audio ) ) + err = isom_set_qtff_lpcm_description( audio, summary ); + else if( file->isom_compatible && lsmash_check_codec_type_identical( audio_type, ISOM_CODEC_TYPE_ALAC_AUDIO ) ) + err = isom_set_isom_alac_audio_description( audio, summary ); + else if( file->qt_compatible && lsmash_check_codec_type_identical( audio_type, QT_CODEC_TYPE_ALAC_AUDIO ) ) + err = isom_set_qtff_alac_audio_description( audio, summary ); + else if( audio->type.fourcc == ISOM_CODEC_TYPE_ALAC_AUDIO.fourcc ) + { + if( file->qt_compatible ) + err = isom_set_qtff_alac_audio_description( audio, summary ); + else + err = isom_set_isom_alac_audio_description( audio, summary ); + } + else if( isom_is_dts_audio( audio_type ) ) + err = isom_set_isom_dts_audio_description( audio, summary ); + else if( lsmash_check_codec_type_identical( audio_type, ISOM_CODEC_TYPE_EC_3_AUDIO ) ) + err = isom_set_isom_eac3_audio_description( audio, summary ); + else if( file->qt_compatible ) + err = isom_set_qtff_template_audio_description( audio, summary ); + else if( lsmash_check_codec_type_identical( audio_type, ISOM_CODEC_TYPE_SAMR_AUDIO ) ) + err = isom_set_isom_amr_audio_description( audio, 0 ); + else if( lsmash_check_codec_type_identical( audio_type, ISOM_CODEC_TYPE_SAWB_AUDIO ) ) + err = isom_set_isom_amr_audio_description( audio, 1 ); + else + err = isom_set_isom_template_audio_description( audio, summary ); + if( err < 0 ) + goto fail; + err = LSMASH_ERR_NAMELESS; + /* Don't use audio_type since audio->type might have changed. */ + for( lsmash_entry_t *entry = summary->opaque->list.head; entry; entry = entry->next ) + { + lsmash_codec_specific_t *specific = (lsmash_codec_specific_t *)entry->data; + if( !specific ) + goto fail; + if( specific->type == LSMASH_CODEC_SPECIFIC_DATA_TYPE_UNKNOWN + && specific->format == LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ) + continue; /* LSMASH_CODEC_SPECIFIC_DATA_TYPE_UNKNOWN + LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED is not supported. */ + switch( specific->type ) + { + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_COMMON : + { + if( specific->format == LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED ) + continue; /* Ignore since not fatal. */ + lsmash_qt_audio_common_t *data = (lsmash_qt_audio_common_t *)specific->data.structured; + audio->revision_level = data->revision_level; + audio->vendor = data->vendor; + if( audio->version == 1 + && !isom_is_lpcm_audio( audio ) + && data->compression_ID != QT_AUDIO_COMPRESSION_ID_NOT_COMPRESSED ) + { + /* Compressed audio must not be set to QT_AUDIO_COMPRESSION_ID_NOT_COMPRESSED. */ + audio->compression_ID = data->compression_ID; + if( audio->compression_ID == QT_AUDIO_COMPRESSION_ID_VARIABLE_COMPRESSION ) + { + /* For variable compression, bytesPerPacket and bytesPerFrame are reserved and should be set to 0. */ + audio->bytesPerPacket = 0; + audio->bytesPerFrame = 0; + } + } + break; + } + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_CHANNEL_LAYOUT : + { + if( !file->qt_compatible + && !lsmash_check_codec_type_identical( audio->type, ISOM_CODEC_TYPE_ALAC_AUDIO ) + && !lsmash_check_codec_type_identical( audio->type, QT_CODEC_TYPE_ALAC_AUDIO ) ) + continue; + if( (err = isom_append_channel_layout_extension( specific, audio, summary->channels )) < 0 ) + goto fail; + break; + } + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_CODEC_GLOBAL_HEADER : + { + lsmash_codec_specific_t *cs = lsmash_convert_codec_specific_format( specific, LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !cs ) + goto fail; + lsmash_codec_global_header_t *data = (lsmash_codec_global_header_t *)cs->data.structured; + isom_glbl_t *glbl = isom_add_glbl( audio ); + if( LSMASH_IS_NON_EXISTING_BOX( glbl ) ) + { + lsmash_destroy_codec_specific_data( cs ); + goto fail; + } + glbl->header_size = data->header_size; + glbl->header_data = lsmash_memdup( data->header_data, data->header_size ); + lsmash_destroy_codec_specific_data( cs ); + if( !glbl->header_data ) + { + isom_remove_box_by_itself( glbl ); + err = LSMASH_ERR_MEMORY_ALLOC; + goto fail; + } + break; + } + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_FORMAT_SPECIFIC_FLAGS : + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_DECOMPRESSION_PARAMETERS : + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_MP4SYS_DECODER_CONFIG : + break; /* shall be set up already */ + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_ALAC : + if( file->qt_compatible ) + continue; /* shall be set up already */ + default : + { + lsmash_codec_specific_t *cs = lsmash_convert_codec_specific_format( specific, LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED ); + if( !cs ) + goto fail; + if( cs->size < ISOM_BASEBOX_COMMON_SIZE ) + { + lsmash_destroy_codec_specific_data( cs ); + continue; + } + uint8_t *box_data = cs->data.unstructured; + lsmash_compact_box_type_t fourcc = LSMASH_4CC( box_data[4], box_data[5], box_data[6], box_data[7] ); + lsmash_box_type_t box_type = isom_guess_audio_codec_specific_box_type( audio->type, fourcc ); + if( lsmash_check_box_type_identical( box_type, QT_BOX_TYPE_WAVE ) ) + { + /* CODEC specific info shall be already inside 'wave' extension. */ + lsmash_destroy_codec_specific_data( cs ); + continue; + } + /* Append the extension. */ + err = isom_add_extension_binary( audio, box_type, LSMASH_BOX_PRECEDENCE_HM, cs->data.unstructured, cs->size ); + cs->data.unstructured = NULL; /* Avoid freeing the binary data of the extension. */ + lsmash_destroy_codec_specific_data( cs ); + if( err < 0 ) + goto fail; + break; + } + } + } + if( audio->version == 0 ) + audio->compression_ID = QT_AUDIO_COMPRESSION_ID_NOT_COMPRESSED; + else if( audio->version == 2 ) + audio->compression_ID = QT_AUDIO_COMPRESSION_ID_VARIABLE_COMPRESSION; + return 0; +fail: + isom_remove_box_by_itself( audio ); + return err; +} + +static int isom_setup_tx3g_description( isom_stsd_t *stsd, lsmash_summary_t *summary ) +{ + isom_tx3g_entry_t *tx3g = isom_add_tx3g_description( stsd ); + if( LSMASH_IS_NON_EXISTING_BOX( tx3g ) ) + return LSMASH_ERR_NAMELESS; + /* We create a dummy font record to make valid font_ID in the sample description. + * The specification (3GPP TS 26.245) does not forbid the value 0 for the identifier, + * but we set 1 to it as track_ID begins from 1. */ + tx3g->data_reference_index = summary->data_ref_index; + tx3g->font_ID = 1; /* ID of the default font record */ + int err = LSMASH_ERR_MEMORY_ALLOC; + isom_ftab_t *ftab = isom_add_ftab( tx3g ); + if( LSMASH_IS_NON_EXISTING_BOX( ftab ) ) + { + err = LSMASH_ERR_NAMELESS; + goto fail; + } + isom_font_record_t *font = lsmash_malloc( sizeof(isom_font_record_t) ); + if( !font ) + goto fail; + if( lsmash_list_add_entry( ftab->list, font ) < 0 ) + { + lsmash_free( font ); + goto fail; + } + const char font_names[] = "Serif,Sans-serif,Monospace"; + font->font_ID = 1; + font->font_name_length = sizeof(font_names); + font->font_name = lsmash_memdup( font_names, sizeof(font_names) ); + if( !font->font_name ) + goto fail; + return 0; +fail: + isom_remove_box_by_itself( tx3g ); + return err; +} + +static int isom_setup_qt_text_description( isom_stsd_t *stsd, lsmash_summary_t *summary ) +{ + isom_qt_text_entry_t *text = isom_add_qt_text_description( stsd ); + if( LSMASH_IS_NON_EXISTING_BOX( text ) ) + return LSMASH_ERR_NAMELESS; + text->data_reference_index = summary->data_ref_index; + return 0; +} + +static int isom_setup_text_description( isom_stsd_t *stsd, lsmash_summary_t *summary ) +{ + lsmash_codec_type_t sample_type = summary->sample_type; + if( lsmash_check_box_type_identical( sample_type, ISOM_CODEC_TYPE_TX3G_TEXT ) ) + return isom_setup_tx3g_description( stsd, summary ); + else if( lsmash_check_box_type_identical( sample_type, QT_CODEC_TYPE_TEXT_TEXT ) ) + return isom_setup_qt_text_description( stsd, summary ); + else + return LSMASH_ERR_NAMELESS; +} + +int isom_setup_hint_description( isom_stsd_t *stsd, lsmash_hint_summary_t *summary ) +{ + lsmash_codec_type_t sample_type = summary->sample_type; + if( LSMASH_IS_NON_EXISTING_BOX( stsd->file ) || !summary ) + return LSMASH_ERR_NAMELESS; + int err = isom_check_valid_summary( (lsmash_summary_t *)summary ); + if( err < 0 ) + goto fail; + isom_hint_entry_t *hint = isom_add_hint_description( stsd, sample_type ); + if( LSMASH_IS_NON_EXISTING_BOX( hint ) ) + return LSMASH_ERR_NAMELESS; + hint->data_reference_index = summary->data_ref_index; + /* configure the sample description */ + lsmash_codec_type_t hint_type = hint->type; + if( lsmash_check_codec_type_identical(hint_type, ISOM_CODEC_TYPE_RRTP_HINT ) ) + { + /* go through list of codec specific datas associated with this summary */ + for( lsmash_entry_t *entry = summary->opaque->list.head; entry; entry = entry->next ) + { + lsmash_codec_specific_t *specific = (lsmash_codec_specific_t *)entry->data; + if( !specific ) + { + err = LSMASH_ERR_NAMELESS; + goto fail; + } + if( specific->type == LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_RTP_HINT_COMMON ) + { + lsmash_isom_rtp_reception_hint_t* rtp_param; + rtp_param = (lsmash_isom_rtp_reception_hint_t *)specific->data.structured; + if( rtp_param->timescale == 0 ) + return LSMASH_ERR_INVALID_DATA; + err = isom_set_hint_summary( hint, summary ); + if( err < 0 ) + goto fail; + isom_tims_t *tims = isom_add_tims( hint ); + isom_tsro_t *tsro = isom_add_tsro( hint ); + isom_tssy_t *tssy = isom_add_tssy( hint ); + if( LSMASH_IS_NON_EXISTING_BOX( tims ) + || LSMASH_IS_NON_EXISTING_BOX( tsro ) + || LSMASH_IS_NON_EXISTING_BOX( tssy ) ) + return LSMASH_ERR_NAMELESS; + tims->timescale = rtp_param->timescale; + tsro->offset = rtp_param->time_offset; + tssy->reserved = rtp_param->reserved_timestamp_sync >> 2; + tssy->timestamp_sync = rtp_param->reserved_timestamp_sync & 0x03; + } + } + } + else + return LSMASH_ERR_PATCH_WELCOME; + return 0; +fail: + return err; +} + +int isom_setup_sample_description( isom_stsd_t *stsd, lsmash_media_type media_type, lsmash_summary_t *summary ) +{ + if( media_type == ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK ) + return isom_setup_visual_description( stsd, (lsmash_video_summary_t *)summary ); + else if( media_type == ISOM_MEDIA_HANDLER_TYPE_AUDIO_TRACK ) + return isom_setup_audio_description( stsd, (lsmash_audio_summary_t *)summary ); + else if( media_type == ISOM_MEDIA_HANDLER_TYPE_TEXT_TRACK ) + return isom_setup_text_description( stsd, (lsmash_summary_t *)summary ); + else if( media_type == ISOM_MEDIA_HANDLER_TYPE_HINT_TRACK ) + return isom_setup_hint_description( stsd, (lsmash_hint_summary_t *)summary ); + else + return LSMASH_ERR_NAMELESS; +} + +static lsmash_codec_specific_data_type isom_get_codec_specific_data_type( lsmash_compact_box_type_t extension_fourcc ) +{ + static struct codec_specific_data_type_table_tag + { + lsmash_compact_box_type_t extension_fourcc; + lsmash_codec_specific_data_type data_type; + } codec_specific_data_type_table[32] = { { 0, LSMASH_CODEC_SPECIFIC_DATA_TYPE_UNKNOWN } }; + if( codec_specific_data_type_table[0].data_type == LSMASH_CODEC_SPECIFIC_DATA_TYPE_UNKNOWN ) + { + int i = 0; +#define ADD_CODEC_SPECIFIC_DATA_TYPE_TABLE_ELEMENT( extension_type, data_type ) \ + codec_specific_data_type_table[i++] = (struct codec_specific_data_type_table_tag){ extension_type.fourcc, data_type } + ADD_CODEC_SPECIFIC_DATA_TYPE_TABLE_ELEMENT( ISOM_BOX_TYPE_AVCC, LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_H264 ); + ADD_CODEC_SPECIFIC_DATA_TYPE_TABLE_ELEMENT( ISOM_BOX_TYPE_HVCC, LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_HEVC ); + ADD_CODEC_SPECIFIC_DATA_TYPE_TABLE_ELEMENT( ISOM_BOX_TYPE_DVC1, LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_VC_1 ); + ADD_CODEC_SPECIFIC_DATA_TYPE_TABLE_ELEMENT( ISOM_BOX_TYPE_DAC3, LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_AC_3 ); + ADD_CODEC_SPECIFIC_DATA_TYPE_TABLE_ELEMENT( ISOM_BOX_TYPE_DEC3, LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_EC_3 ); + ADD_CODEC_SPECIFIC_DATA_TYPE_TABLE_ELEMENT( ISOM_BOX_TYPE_DDTS, LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_DTS ); + ADD_CODEC_SPECIFIC_DATA_TYPE_TABLE_ELEMENT( ISOM_BOX_TYPE_ALAC, LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_ALAC ); + ADD_CODEC_SPECIFIC_DATA_TYPE_TABLE_ELEMENT( ISOM_BOX_TYPE_ESDS, LSMASH_CODEC_SPECIFIC_DATA_TYPE_MP4SYS_DECODER_CONFIG ); + ADD_CODEC_SPECIFIC_DATA_TYPE_TABLE_ELEMENT( ISOM_BOX_TYPE_STSL, LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_SAMPLE_SCALE ); + ADD_CODEC_SPECIFIC_DATA_TYPE_TABLE_ELEMENT( ISOM_BOX_TYPE_BTRT, LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_H264_BITRATE ); + //ADD_CODEC_SPECIFIC_DATA_TYPE_TABLE_ELEMENT( QT_BOX_TYPE_ALAC, LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_ALAC ); + //ADD_CODEC_SPECIFIC_DATA_TYPE_TABLE_ELEMENT( QT_BOX_TYPE_ESDS, LSMASH_CODEC_SPECIFIC_DATA_TYPE_MP4SYS_DECODER_CONFIG ); + ADD_CODEC_SPECIFIC_DATA_TYPE_TABLE_ELEMENT( QT_BOX_TYPE_FIEL, LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_FIELD_INFO ); + ADD_CODEC_SPECIFIC_DATA_TYPE_TABLE_ELEMENT( QT_BOX_TYPE_CSPC, LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_PIXEL_FORMAT ); + ADD_CODEC_SPECIFIC_DATA_TYPE_TABLE_ELEMENT( QT_BOX_TYPE_SGBT, LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_SIGNIFICANT_BITS ); + ADD_CODEC_SPECIFIC_DATA_TYPE_TABLE_ELEMENT( QT_BOX_TYPE_GAMA, LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_GAMMA_LEVEL ); + ADD_CODEC_SPECIFIC_DATA_TYPE_TABLE_ELEMENT( QT_BOX_TYPE_CLLI, LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_CONTENT_LIGHT_LEVEL_INFO ); + ADD_CODEC_SPECIFIC_DATA_TYPE_TABLE_ELEMENT( QT_BOX_TYPE_MDCV, LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_MASTERING_DISPLAY_COLOR_VOLUME ); + ADD_CODEC_SPECIFIC_DATA_TYPE_TABLE_ELEMENT( QT_BOX_TYPE_CHAN, LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_CHANNEL_LAYOUT ); + ADD_CODEC_SPECIFIC_DATA_TYPE_TABLE_ELEMENT( QT_BOX_TYPE_GLBL, LSMASH_CODEC_SPECIFIC_DATA_TYPE_CODEC_GLOBAL_HEADER ); + ADD_CODEC_SPECIFIC_DATA_TYPE_TABLE_ELEMENT( LSMASH_BOX_TYPE_UNSPECIFIED, LSMASH_CODEC_SPECIFIC_DATA_TYPE_UNKNOWN ); +#undef ADD_CODEC_SPECIFIC_DATA_TYPE_TABLE_ELEMENT + } + lsmash_codec_specific_data_type data_type = LSMASH_CODEC_SPECIFIC_DATA_TYPE_UNKNOWN; + for( int i = 0; codec_specific_data_type_table[i].data_type != LSMASH_CODEC_SPECIFIC_DATA_TYPE_UNKNOWN; i++ ) + if( extension_fourcc == codec_specific_data_type_table[i].extension_fourcc ) + { + data_type = codec_specific_data_type_table[i].data_type; + break; + } + return data_type; +} + +lsmash_summary_t *isom_create_video_summary_from_description( isom_sample_entry_t *sample_entry ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( sample_entry ) ) + return NULL; + isom_visual_entry_t *visual = (isom_visual_entry_t *)sample_entry; + lsmash_video_summary_t *summary = (lsmash_video_summary_t *)lsmash_create_summary( LSMASH_SUMMARY_TYPE_VIDEO ); + if( !summary ) + return NULL; + summary->sample_type = visual->type; + summary->data_ref_index = visual->data_reference_index; + summary->width = visual->width; + summary->height = visual->height; + summary->depth = visual->depth; + memcpy( summary->compressorname, visual->compressorname, 32 ); + summary->compressorname[32] = '\0'; + if( isom_is_qt_video( summary->sample_type ) ) + { + lsmash_codec_specific_t *specific = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_COMMON, + LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !specific ) + goto fail; + lsmash_qt_video_common_t *data = (lsmash_qt_video_common_t *)specific->data.structured; + data->revision_level = visual->revision_level; + data->vendor = visual->vendor; + data->temporalQuality = visual->temporalQuality; + data->spatialQuality = visual->spatialQuality; + data->horizontal_resolution = visual->horizresolution; + data->vertical_resolution = visual->vertresolution; + data->dataSize = visual->dataSize; + data->frame_count = visual->frame_count; + data->color_table_ID = visual->color_table_ID; + if( visual->color_table_ID == 0 ) + { + isom_qt_color_table_t *src_ct = &visual->color_table; + if( !src_ct->array ) + { + lsmash_destroy_codec_specific_data( specific ); + goto fail; + } + uint16_t element_count = LSMASH_MIN( src_ct->size + 1, 256 ); + lsmash_qt_color_table_t *dst_ct = &data->color_table; + dst_ct->seed = src_ct->seed; + dst_ct->flags = src_ct->flags; + dst_ct->size = src_ct->size; + for( uint16_t i = 0; i < element_count; i++ ) + { + dst_ct->array[i].unused = src_ct->array[i].value; + dst_ct->array[i].r = src_ct->array[i].r; + dst_ct->array[i].g = src_ct->array[i].g; + dst_ct->array[i].b = src_ct->array[i].b; + } + } + if( lsmash_list_add_entry( &summary->opaque->list, specific ) < 0 ) + { + lsmash_destroy_codec_specific_data( specific ); + goto fail; + } + } + for( lsmash_entry_t *entry = visual->extensions.head; entry; entry = entry->next ) + { + isom_box_t *box = (isom_box_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( box ) ) + continue; + if( !(box->manager & LSMASH_BINARY_CODED_BOX) ) + { + lsmash_codec_specific_t *specific = NULL; + if( lsmash_check_box_type_identical( box->type, ISOM_BOX_TYPE_CLAP ) ) + { + isom_clap_t *clap = (isom_clap_t *)box; + summary->clap.width.n = clap->cleanApertureWidthN; + summary->clap.width.d = clap->cleanApertureWidthD; + summary->clap.height.n = clap->cleanApertureHeightN; + summary->clap.height.d = clap->cleanApertureHeightD; + summary->clap.horizontal_offset.n = clap->horizOffN; + summary->clap.horizontal_offset.d = clap->horizOffD; + summary->clap.vertical_offset.n = clap->vertOffN; + summary->clap.vertical_offset.d = clap->vertOffD; + continue; + } + else if( lsmash_check_box_type_identical( box->type, ISOM_BOX_TYPE_PASP ) ) + { + isom_pasp_t *pasp = (isom_pasp_t *)box; + summary->par_h = pasp->hSpacing; + summary->par_v = pasp->vSpacing; + continue; + } + else if( lsmash_check_box_type_identical( box->type, ISOM_BOX_TYPE_COLR ) + || lsmash_check_box_type_identical( box->type, QT_BOX_TYPE_COLR ) ) + { + isom_colr_t *colr = (isom_colr_t *)box; + summary->color.primaries_index = colr->primaries_index; + summary->color.transfer_index = colr->transfer_function_index; + summary->color.matrix_index = colr->matrix_index; + summary->color.full_range = colr->full_range_flag; + continue; + } + else if( lsmash_check_box_type_identical( box->type, ISOM_BOX_TYPE_STSL ) ) + { + specific = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_SAMPLE_SCALE, + LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !specific ) + goto fail; + isom_stsl_t *stsl = (isom_stsl_t *)box; + lsmash_isom_sample_scale_t *data = (lsmash_isom_sample_scale_t *)specific->data.structured; + data->constraint_flag = stsl->constraint_flag; + data->scale_method = stsl->scale_method; + data->display_center_x = stsl->display_center_x; + data->display_center_y = stsl->display_center_y; + } + else if( lsmash_check_box_type_identical( box->type, ISOM_BOX_TYPE_BTRT ) ) + { + specific = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_H264_BITRATE, + LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !specific ) + goto fail; + isom_btrt_t *btrt = (isom_btrt_t *)box; + lsmash_h264_bitrate_t *data = (lsmash_h264_bitrate_t *)specific->data.structured; + data->bufferSizeDB = btrt->bufferSizeDB; + data->maxBitrate = btrt->maxBitrate; + data->avgBitrate = btrt->avgBitrate; + } + else if( lsmash_check_box_type_identical( box->type, QT_BOX_TYPE_FIEL ) ) + { + specific = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_FIELD_INFO, + LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !specific ) + goto fail; + isom_fiel_t *fiel = (isom_fiel_t *)box; + lsmash_qt_field_info_t *data = (lsmash_qt_field_info_t *)specific->data.structured; + data->fields = fiel->fields; + data->detail = fiel->detail; + } + else if( lsmash_check_box_type_identical( box->type, QT_BOX_TYPE_CSPC ) ) + { + specific = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_PIXEL_FORMAT, + LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !specific ) + goto fail; + isom_cspc_t *cspc = (isom_cspc_t *)box; + lsmash_qt_pixel_format_t *data = (lsmash_qt_pixel_format_t *)specific->data.structured; + data->pixel_format = cspc->pixel_format; + } + else if( lsmash_check_box_type_identical( box->type, QT_BOX_TYPE_SGBT ) ) + { + specific = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_SIGNIFICANT_BITS, + LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !specific ) + goto fail; + isom_sgbt_t *sgbt = (isom_sgbt_t *)box; + lsmash_qt_significant_bits_t *data = (lsmash_qt_significant_bits_t *)specific->data.structured; + data->significantBits = sgbt->significantBits; + } + else if( lsmash_check_box_type_identical( box->type, QT_BOX_TYPE_GLBL ) ) + { + specific = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_CODEC_GLOBAL_HEADER, + LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !specific ) + goto fail; + isom_glbl_t *glbl = (isom_glbl_t *)box; + lsmash_codec_global_header_t *data = (lsmash_codec_global_header_t *)specific->data.structured; + data->header_size = glbl->header_size; + data->header_data = lsmash_memdup( glbl->header_data, glbl->header_size ); + if( !data->header_data ) + { + lsmash_destroy_codec_specific_data( specific ); + goto fail; + } + } + else if( lsmash_check_box_type_identical( box->type, QT_BOX_TYPE_CLLI ) ) + { + specific = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_CONTENT_LIGHT_LEVEL_INFO, + LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !specific ) + goto fail; + isom_clli_t *clli = (isom_clli_t *)box; + lsmash_qt_content_light_level_info_t *data = (lsmash_qt_content_light_level_info_t *)specific->data.structured; + data->max_content_light_level = clli->max_content_light_level; + data->max_pic_average_light_level = clli->max_pic_average_light_level; + } + else if( lsmash_check_box_type_identical( box->type, QT_BOX_TYPE_MDCV ) ) + { + specific = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_MASTERING_DISPLAY_COLOR_VOLUME, + LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !specific ) + goto fail; + isom_mdcv_t *mdcv = (isom_mdcv_t *)box; + lsmash_qt_mastering_display_color_volume_t *data = (lsmash_qt_mastering_display_color_volume_t *)specific->data.structured; + data->display_primaries_g_x = mdcv->display_primaries_g_x; + data->display_primaries_g_y = mdcv->display_primaries_g_y; + data->display_primaries_b_x = mdcv->display_primaries_b_x; + data->display_primaries_b_y = mdcv->display_primaries_b_y; + data->display_primaries_r_x = mdcv->display_primaries_r_x; + data->display_primaries_r_y = mdcv->display_primaries_r_y; + data->white_point_x = mdcv->white_point_x; + data->white_point_y = mdcv->white_point_y; + data->max_display_mastering_luminance = mdcv->max_display_mastering_luminance; + data->min_display_mastering_luminance = mdcv->min_display_mastering_luminance; + } + else + continue; + if( lsmash_list_add_entry( &summary->opaque->list, specific ) < 0 ) + { + lsmash_destroy_codec_specific_data( specific ); + goto fail; + } + } + else + { + if( box->size < ISOM_BASEBOX_COMMON_SIZE ) + continue; + uint8_t *data = box->binary; + lsmash_compact_box_type_t fourcc = LSMASH_4CC( data[4], data[5], data[6], data[7] ); + lsmash_codec_specific_data_type type = isom_get_codec_specific_data_type( fourcc ); + lsmash_codec_specific_t *specific = lsmash_create_codec_specific_data( type, LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED ); + if( !specific ) + goto fail; + specific->size = box->size; + specific->data.unstructured = lsmash_memdup( box->binary, box->size ); + if( !specific->data.unstructured + || lsmash_list_add_entry( &summary->opaque->list, specific ) < 0 ) + { + lsmash_destroy_codec_specific_data( specific ); + goto fail; + } + } + } + return (lsmash_summary_t *)summary; +fail: + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return NULL; +} + +static int isom_append_structured_mp4sys_decoder_config( lsmash_codec_specific_list_t *opaque, isom_esds_t *esds ) +{ + lsmash_bs_t *bs = lsmash_bs_create(); + if( !bs ) + return LSMASH_ERR_MEMORY_ALLOC; + /* Put box size, type, version and flags fields. */ + lsmash_bs_put_be32( bs, 0 ); + lsmash_bs_put_be32( bs, ISOM_BOX_TYPE_ESDS.fourcc ); + lsmash_bs_put_be32( bs, 0 ); + /* Put ES Descriptor. */ + mp4sys_update_descriptor_size( esds->ES ); + mp4sys_write_descriptor( bs, esds->ES ); + /* Export ES Descriptor Box as binary string. */ + uint32_t esds_size; + uint8_t *esds_data = lsmash_bs_export_data( bs, &esds_size ); + lsmash_bs_cleanup( bs ); + if( !esds_data ) + return LSMASH_ERR_NAMELESS; + /* Update box size. */ + LSMASH_SET_BE32( esds_data, esds_size ); + lsmash_codec_specific_data_type type = isom_get_codec_specific_data_type( ISOM_BOX_TYPE_ESDS.fourcc ); + lsmash_codec_specific_t *specific = lsmash_create_codec_specific_data( type, LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED ); + if( !specific ) + { + lsmash_free( esds_data ); + return LSMASH_ERR_NAMELESS; + } + specific->data.unstructured = esds_data; + specific->size = esds_size; + /* Convert unstructured CODEC specific data format into structured, and append it to the opaque list. */ + lsmash_codec_specific_t *conv = lsmash_convert_codec_specific_format( specific, LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + lsmash_destroy_codec_specific_data( specific ); + if( !conv ) + return LSMASH_ERR_NAMELESS; + if( lsmash_list_add_entry( &opaque->list, conv ) < 0 ) + { + lsmash_destroy_codec_specific_data( conv ); + return LSMASH_ERR_MEMORY_ALLOC; + } + return 0; +} + +lsmash_summary_t *isom_create_audio_summary_from_description( isom_sample_entry_t *sample_entry ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( sample_entry->file ) + || LSMASH_IS_NON_EXISTING_BOX( sample_entry->parent ) ) + return NULL; + isom_audio_entry_t *audio = (isom_audio_entry_t *)sample_entry; + lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_create_summary( LSMASH_SUMMARY_TYPE_AUDIO ); + if( !summary ) + return NULL; + summary->sample_type = audio->type; + summary->data_ref_index = audio->data_reference_index; + summary->sample_size = audio->samplesize; + summary->channels = audio->channelcount; + summary->frequency = audio->samplerate >> 16; + if( ((isom_stsd_t *)audio->parent)->version == 0 + && audio->file->qt_compatible + && isom_is_qt_audio( audio->type ) ) + { + if( audio->version == 0 ) + isom_get_implicit_qt_fixed_comp_audio_sample_quants( audio, &summary->samples_in_frame, &summary->bytes_per_frame, &summary->sample_size ); + else if( audio->version == 1 ) + { + summary->channels = audio->bytesPerPacket ? audio->bytesPerFrame / audio->bytesPerPacket : audio->channelcount; + summary->sample_size = audio->bytesPerPacket * 8; + summary->samples_in_frame = audio->samplesPerPacket; + summary->bytes_per_frame = audio->bytesPerFrame; + } + else if( audio->version == 2 ) + { + summary->frequency = (union {uint64_t i; double d;}){audio->audioSampleRate}.d; + summary->channels = audio->numAudioChannels; + summary->sample_size = audio->constBitsPerChannel; + summary->samples_in_frame = audio->constLPCMFramesPerAudioPacket; + summary->bytes_per_frame = audio->constBytesPerAudioPacket; + } + lsmash_codec_specific_t *specific = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_COMMON, + LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !specific ) + goto fail; + lsmash_qt_audio_common_t *common = (lsmash_qt_audio_common_t *)specific->data.structured; + common->revision_level = audio->revision_level; + common->vendor = audio->vendor; + common->compression_ID = audio->compression_ID; + if( lsmash_list_add_entry( &summary->opaque->list, specific ) < 0 ) + { + lsmash_destroy_codec_specific_data( specific ); + goto fail; + } + if( isom_is_lpcm_audio( audio ) ) + { + specific = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_FORMAT_SPECIFIC_FLAGS, + LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !specific ) + goto fail; + lsmash_qt_audio_format_specific_flags_t *data = (lsmash_qt_audio_format_specific_flags_t *)specific->data.structured; + if( audio->version == 2 ) + data->format_flags = audio->formatSpecificFlags; + else + { + data->format_flags = QT_LPCM_FORMAT_FLAG_BIG_ENDIAN | QT_LPCM_FORMAT_FLAG_SIGNED_INTEGER; + /* Here, don't override samplesize. + * We should trust samplesize field in the description for misused CODEC indentifier. */ + lsmash_codec_type_t audio_type = audio->type; + if( lsmash_check_codec_type_identical( audio_type, QT_CODEC_TYPE_TWOS_AUDIO ) + || lsmash_check_codec_type_identical( audio_type, QT_CODEC_TYPE_NONE_AUDIO ) + || lsmash_check_codec_type_identical( audio_type, QT_CODEC_TYPE_NOT_SPECIFIED ) ) + { + if( summary->sample_size <= 8 ) + data->format_flags &= ~(QT_LPCM_FORMAT_FLAG_BIG_ENDIAN | QT_LPCM_FORMAT_FLAG_SIGNED_INTEGER); + } + else + { + if( lsmash_check_codec_type_identical( audio_type, QT_CODEC_TYPE_FL32_AUDIO ) + || lsmash_check_codec_type_identical( audio_type, QT_CODEC_TYPE_FL64_AUDIO ) ) + { + data->format_flags &= ~QT_LPCM_FORMAT_FLAG_SIGNED_INTEGER; + data->format_flags |= QT_LPCM_FORMAT_FLAG_FLOAT; + } + else if( lsmash_check_codec_type_identical( audio_type, QT_CODEC_TYPE_23NI_AUDIO ) + || lsmash_check_codec_type_identical( audio_type, QT_CODEC_TYPE_SOWT_AUDIO ) ) + data->format_flags &= ~QT_LPCM_FORMAT_FLAG_BIG_ENDIAN; + } + } + isom_wave_t *wave = (isom_wave_t *)isom_get_extension_box_format( &audio->extensions, QT_BOX_TYPE_WAVE ); + if( LSMASH_IS_EXISTING_BOX( wave->enda ) ) + { + if( wave->enda->littleEndian ) + data->format_flags &= ~QT_LPCM_FORMAT_FLAG_BIG_ENDIAN; + else + data->format_flags |= QT_LPCM_FORMAT_FLAG_BIG_ENDIAN; + } + if( lsmash_list_add_entry( &summary->opaque->list, specific ) < 0 ) + { + lsmash_destroy_codec_specific_data( specific ); + goto fail; + } + } + else if( audio->version == 2 + && (lsmash_check_codec_type_identical( audio->type, ISOM_CODEC_TYPE_ALAC_AUDIO ) + || lsmash_check_codec_type_identical( audio->type, QT_CODEC_TYPE_ALAC_AUDIO )) ) + switch( audio->formatSpecificFlags ) + { + case QT_ALAC_FORMAT_FLAG_16BIT_SOURCE_DATA : + summary->sample_size = 16; + break; + case QT_ALAC_FORMAT_FLAG_20BIT_SOURCE_DATA : + summary->sample_size = 20; + break; + case QT_ALAC_FORMAT_FLAG_24BIT_SOURCE_DATA : + summary->sample_size = 24; + break; + case QT_ALAC_FORMAT_FLAG_32BIT_SOURCE_DATA : + summary->sample_size = 32; + break; + default : + break; + } + } + else if( lsmash_check_codec_type_identical( audio->type, ISOM_CODEC_TYPE_SAMR_AUDIO ) ) + { + summary->channels = 1; + summary->frequency = 8000; + } + else if( lsmash_check_codec_type_identical( audio->type, ISOM_CODEC_TYPE_SAWB_AUDIO ) ) + { + summary->channels = 1; + summary->frequency = 16000; + } + uint32_t actual_sampling_rate = 0; + for( lsmash_entry_t *entry = audio->extensions.head; entry; entry = entry->next ) + { + isom_box_t *box = (isom_box_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( box ) ) + continue; + if( !(box->manager & LSMASH_BINARY_CODED_BOX) ) + { + if( lsmash_check_box_type_identical( box->type, QT_BOX_TYPE_CHAN ) ) + { + lsmash_codec_specific_t *specific = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_CHANNEL_LAYOUT, + LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !specific ) + goto fail; + isom_chan_t *chan = (isom_chan_t *)box; + lsmash_qt_audio_channel_layout_t *data = (lsmash_qt_audio_channel_layout_t *)specific->data.structured; + data->channelLayoutTag = chan->channelLayoutTag; + data->channelBitmap = chan->channelBitmap; + if( lsmash_list_add_entry( &summary->opaque->list, specific ) < 0 ) + { + lsmash_destroy_codec_specific_data( specific ); + goto fail; + } + } + else if( lsmash_check_box_type_identical( box->type, ISOM_BOX_TYPE_ESDS ) + || lsmash_check_box_type_identical( box->type, QT_BOX_TYPE_ESDS ) ) + { + isom_esds_t *esds = (isom_esds_t *)box; + if( mp4sys_setup_summary_from_DecoderSpecificInfo( summary, esds->ES ) < 0 + || isom_append_structured_mp4sys_decoder_config( summary->opaque, esds ) < 0 ) + goto fail; + } + else if( lsmash_check_box_type_identical( box->type, ISOM_BOX_TYPE_SRAT ) ) + { + isom_srat_t *srat = (isom_srat_t *)box; + actual_sampling_rate = srat->sampling_rate; + } + else if( lsmash_check_box_type_identical( box->type, QT_BOX_TYPE_WAVE ) ) + { + /* Don't append 'wave' extension itself to the opaque CODEC specific info list. */ + isom_wave_t *wave = (isom_wave_t *)box; + lsmash_bs_t *bs = lsmash_bs_create(); + if( !bs ) + goto fail; + for( lsmash_entry_t *wave_entry = wave->extensions.head; wave_entry; wave_entry = wave_entry->next ) + { + isom_box_t *wave_ext = (isom_box_t *)wave_entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( wave_ext ) ) + continue; + lsmash_box_type_t box_type = LSMASH_BOX_TYPE_INITIALIZER; + if( !(wave_ext->manager & LSMASH_BINARY_CODED_BOX) ) + { + box_type = wave_ext->type; + if( lsmash_check_box_type_identical( box_type, QT_BOX_TYPE_ENDA ) ) + { + isom_enda_t *enda = (isom_enda_t *)wave_ext; + isom_bs_put_box_common( bs, enda ); + lsmash_bs_put_be16( bs, enda->littleEndian ); + } + else if( lsmash_check_box_type_identical( box_type, QT_BOX_TYPE_MP4A ) ) + { + isom_mp4a_t *mp4a = (isom_mp4a_t *)wave_ext; + isom_bs_put_box_common( bs, mp4a ); + lsmash_bs_put_be32( bs, mp4a->unknown ); + } + else if( lsmash_check_box_type_identical( box_type, QT_BOX_TYPE_CHAN ) ) + { + isom_chan_t *chan = (isom_chan_t *)wave_ext; + isom_bs_put_box_common( bs, chan ); + lsmash_bs_put_be32( bs, chan->channelLayoutTag ); + lsmash_bs_put_be32( bs, chan->channelBitmap ); + lsmash_bs_put_be32( bs, chan->numberChannelDescriptions ); + if( chan->channelDescriptions ) + for( uint32_t i = 0; i < chan->numberChannelDescriptions; i++ ) + { + isom_channel_description_t *channelDescriptions = (isom_channel_description_t *)(&chan->channelDescriptions[i]); + lsmash_bs_put_be32( bs, channelDescriptions->channelLabel ); + lsmash_bs_put_be32( bs, channelDescriptions->channelFlags ); + lsmash_bs_put_be32( bs, channelDescriptions->coordinates[0] ); + lsmash_bs_put_be32( bs, channelDescriptions->coordinates[1] ); + lsmash_bs_put_be32( bs, channelDescriptions->coordinates[2] ); + } + } + else if( lsmash_check_box_type_identical( box_type, QT_BOX_TYPE_ESDS ) ) + { + isom_esds_t *esds = (isom_esds_t *)wave_ext; + if( LSMASH_IS_NON_EXISTING_BOX( esds ) + || mp4sys_setup_summary_from_DecoderSpecificInfo( summary, esds->ES ) < 0 + || isom_append_structured_mp4sys_decoder_config( summary->opaque, esds ) < 0 ) + { + lsmash_bs_cleanup( bs ); + goto fail; + } + continue; + } + else + /* Skip Format Box and Terminator Box since they are mandatory and fixed structure. */ + continue; + } + else + { + if( wave_ext->size < ISOM_BASEBOX_COMMON_SIZE ) + continue; + uint8_t *data = wave_ext->binary; + box_type.fourcc = LSMASH_4CC( data[4], data[5], data[6], data[7] ); + lsmash_bs_put_bytes( bs, wave_ext->size, wave_ext->binary ); + } + /* Export as binary string. */ + uint32_t box_size; + uint8_t *box_data = lsmash_bs_export_data( bs, &box_size ); + lsmash_bs_empty( bs ); + if( !box_data ) + { + lsmash_bs_cleanup( bs ); + goto fail; + } + /* Append as an unstructured CODEC specific info. */ + lsmash_codec_specific_data_type type; + if( box_type.fourcc == QT_BOX_TYPE_CHAN.fourcc ) + /* Complete audio channel layout is stored as binary string. + * We distinguish it from one of the outside of 'wave' extension here. */ + type = LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_DECOMPRESSION_PARAMETERS; + else + { + type = isom_get_codec_specific_data_type( box_type.fourcc ); + if( type == LSMASH_CODEC_SPECIFIC_DATA_TYPE_UNKNOWN ) + type = LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_DECOMPRESSION_PARAMETERS; + } + lsmash_codec_specific_t *specific = lsmash_create_codec_specific_data( type, LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED ); + if( !specific ) + { + lsmash_free( box_data ); + lsmash_bs_cleanup( bs ); + goto fail; + } + specific->data.unstructured = box_data; + specific->size = box_size; + if( lsmash_list_add_entry( &summary->opaque->list, specific ) < 0 ) + { + lsmash_destroy_codec_specific_data( specific ); + lsmash_bs_cleanup( bs ); + goto fail; + } + } + lsmash_bs_cleanup( bs ); + } + } + else + { + if( box->size < ISOM_BASEBOX_COMMON_SIZE ) + continue; + uint8_t *data = box->binary; + lsmash_compact_box_type_t fourcc = LSMASH_4CC( data[4], data[5], data[6], data[7] ); + lsmash_codec_specific_data_type type = isom_get_codec_specific_data_type( fourcc ); + lsmash_codec_specific_t *specific = lsmash_create_codec_specific_data( type, LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED ); + if( !specific ) + goto fail; + specific->size = box->size; + specific->data.unstructured = lsmash_memdup( box->binary, box->size ); + if( !specific->data.unstructured + || lsmash_list_add_entry( &summary->opaque->list, specific ) < 0 ) + { + lsmash_destroy_codec_specific_data( specific ); + goto fail; + } + if( specific->type == LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_DTS + || specific->type == LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_AC_3 + || specific->type == LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_EC_3 ) + { + specific = lsmash_convert_codec_specific_format( specific, LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !specific ) + goto fail; + switch( specific->type ) + { + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_DTS : + { + lsmash_dts_specific_parameters_t *param = (lsmash_dts_specific_parameters_t *)specific->data.structured; + summary->sample_size = param->pcmSampleDepth; + summary->samples_in_frame = (summary->frequency * (512 << param->FrameDuration)) / param->DTSSamplingFrequency; + break; + } + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_AC_3 : + { + lsmash_ac3_specific_parameters_t *param = (lsmash_ac3_specific_parameters_t *)specific->data.structured; + summary->frequency = ac3_get_sample_rate( param ); + summary->channels = ac3_get_channel_count( param ); + summary->samples_in_frame = 1536; + break; + } + case LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_EC_3 : + { + lsmash_eac3_specific_parameters_t *param = (lsmash_eac3_specific_parameters_t *)specific->data.structured; + eac3_update_sample_rate( &summary->frequency, param, NULL ); + eac3_update_channel_count( &summary->channels, param ); + summary->samples_in_frame = 1536; + break; + } + default : + break; + } + lsmash_destroy_codec_specific_data( specific ); + } + } + } + /* Set the actual sampling rate. */ + if( actual_sampling_rate ) + summary->frequency = actual_sampling_rate; + return (lsmash_summary_t *)summary; +fail: + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return NULL; +} + +lsmash_codec_specific_t *lsmash_get_codec_specific_data( lsmash_summary_t *summary, uint32_t extension_number ) +{ + if( !summary || !summary->opaque ) + return NULL; + uint32_t i = 0; + for( lsmash_entry_t *entry = summary->opaque->list.head; entry; entry = entry->next ) + if( ++i == extension_number ) + return (lsmash_codec_specific_t *)entry->data; + return NULL; +} + +uint32_t lsmash_count_codec_specific_data( lsmash_summary_t *summary ) +{ + if( !summary || !summary->opaque ) + return 0; + return summary->opaque->list.entry_count; +} + +int isom_compare_opaque_extensions( lsmash_summary_t *a, lsmash_summary_t *b ) +{ + assert( a && b ); + uint32_t in_number_of_extensions = lsmash_count_codec_specific_data( a ); + uint32_t out_number_of_extensions = lsmash_count_codec_specific_data( b ); + if( out_number_of_extensions != in_number_of_extensions ) + return 1; + uint32_t active_number_of_extensions = in_number_of_extensions; + uint32_t identical_count = 0; + for( uint32_t j = 1; j <= in_number_of_extensions; j++ ) + { + lsmash_codec_specific_t *in_cs_orig = lsmash_get_codec_specific_data( a, j ); + lsmash_codec_specific_t *in_cs; + lsmash_codec_specific_format compare_format = LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED; + if( in_cs_orig->format == LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ) + { + if( in_cs_orig->type == LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_COMMON + || in_cs_orig->type == LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_COMMON + || in_cs_orig->type == LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_FORMAT_SPECIFIC_FLAGS ) + { + compare_format = LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED; + in_cs = in_cs_orig; + } + else + { + in_cs = lsmash_convert_codec_specific_format( in_cs_orig, LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED ); + if( !in_cs ) + { + /* We don't support the format converter of this data type. */ + --active_number_of_extensions; + continue; + } + } + } + else + in_cs = in_cs_orig; + for( uint32_t k = 1; k <= out_number_of_extensions; k++ ) + { + lsmash_codec_specific_t *out_cs_orig = lsmash_get_codec_specific_data( b, k ); + if( out_cs_orig->type != in_cs_orig->type ) + continue; + lsmash_codec_specific_t *out_cs; + if( out_cs_orig->format == LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ) + { + if( compare_format == LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ) + out_cs = out_cs_orig; + else + { + out_cs = lsmash_convert_codec_specific_format( out_cs_orig, LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED ); + if( !out_cs ) + continue; + } + } + else + out_cs = out_cs_orig; + int identical; + if( compare_format == LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED ) + identical = out_cs->size == in_cs->size && !memcmp( out_cs->data.unstructured, in_cs->data.unstructured, in_cs->size ); + else + { + if( in_cs->type == LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_COMMON ) + { + lsmash_qt_video_common_t *in_data = (lsmash_qt_video_common_t *)in_cs->data.structured; + lsmash_qt_video_common_t *out_data = (lsmash_qt_video_common_t *)out_cs->data.structured; + identical = in_data->revision_level == out_data->revision_level + && in_data->vendor == out_data->vendor + && in_data->temporalQuality == out_data->temporalQuality + && in_data->spatialQuality == out_data->spatialQuality + && in_data->horizontal_resolution == out_data->horizontal_resolution + && in_data->vertical_resolution == out_data->vertical_resolution + && in_data->dataSize == out_data->dataSize + && in_data->frame_count == out_data->frame_count + && in_data->color_table_ID == out_data->color_table_ID; + } + else if( in_cs->type == LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_COMMON ) + { + lsmash_qt_audio_common_t *in_data = (lsmash_qt_audio_common_t *)in_cs->data.structured; + lsmash_qt_audio_common_t *out_data = (lsmash_qt_audio_common_t *)out_cs->data.structured; + identical = in_data->revision_level == out_data->revision_level + && in_data->vendor == out_data->vendor + && in_data->compression_ID == out_data->compression_ID; + } + else + { + lsmash_qt_audio_format_specific_flags_t *in_data = (lsmash_qt_audio_format_specific_flags_t *)in_cs->data.structured; + lsmash_qt_audio_format_specific_flags_t *out_data = (lsmash_qt_audio_format_specific_flags_t *)out_cs->data.structured; + identical = (in_data->format_flags == out_data->format_flags); + } + } + if( out_cs != out_cs_orig ) + lsmash_destroy_codec_specific_data( out_cs ); + if( identical ) + { + ++identical_count; + break; + } + } + if( in_cs != in_cs_orig ) + lsmash_destroy_codec_specific_data( in_cs ); + } + return (identical_count != active_number_of_extensions); +} + +int isom_get_implicit_qt_fixed_comp_audio_sample_quants +( + isom_audio_entry_t *audio, + uint32_t *samples_per_packet, + uint32_t *constant_bytes_per_frame, + uint32_t *sample_size +) +{ + if( lsmash_check_codec_type_identical( audio->type, QT_CODEC_TYPE_MAC3_AUDIO ) ) + { + *samples_per_packet = 6; + *constant_bytes_per_frame = 2 * audio->channelcount; + *sample_size = 8; + } + else if( lsmash_check_codec_type_identical( audio->type, QT_CODEC_TYPE_MAC6_AUDIO ) ) + { + *samples_per_packet = 6; + *constant_bytes_per_frame = audio->channelcount; + *sample_size = 8; + } + else if( lsmash_check_codec_type_identical( audio->type, QT_CODEC_TYPE_ADPCM17_AUDIO ) ) + { + *samples_per_packet = 64; + *constant_bytes_per_frame = 34 * audio->channelcount; + *sample_size = 16; + } + else if( lsmash_check_codec_type_identical( audio->type, QT_CODEC_TYPE_AGSM_AUDIO ) ) + { + *samples_per_packet = 160; + *constant_bytes_per_frame = 33; + *sample_size = 16; + } + else if( lsmash_check_codec_type_identical( audio->type, QT_CODEC_TYPE_ALAW_AUDIO ) + || lsmash_check_codec_type_identical( audio->type, QT_CODEC_TYPE_ULAW_AUDIO ) ) + { + *samples_per_packet = 1; + *constant_bytes_per_frame = audio->channelcount; + *sample_size = 16; + } + else + return 0; + return 1; +} + +int hint_update_bitrate( isom_stbl_t *stbl, isom_mdhd_t *mdhd, uint32_t sample_description_index ) +{ + uint32_t bufferSizeDB; + uint32_t maxBitrate = 0; + uint32_t avgBitrate = 0; + int err = isom_calculate_bitrate_description( stbl, mdhd, &bufferSizeDB, &maxBitrate, &avgBitrate, sample_description_index ); + isom_hmhd_t *hmhd = ((isom_mdia_t*)(mdhd->parent))->minf->hmhd; + hmhd->maxbitrate = maxBitrate; + hmhd->avgbitrate = avgBitrate; + hmhd->avgPDUsize = hmhd->PDUcount > 0 ? (hmhd->combinedPDUsize / hmhd->PDUcount) : 0; + return err; +} + +isom_bitrate_updater_t isom_get_bitrate_updater +( + isom_sample_entry_t *sample_entry +) +{ +#define RETURN_BITRATE_UPDATER( func_name ) \ + { \ + extern int func_name( isom_stbl_t *, isom_mdhd_t *, uint32_t sample_description_index ); \ + return func_name; \ + } + lsmash_codec_type_t sample_type = sample_entry->type; + if( lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_AVC1_VIDEO ) + || lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_AVC2_VIDEO ) + || lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_AVC3_VIDEO ) + || lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_AVC4_VIDEO ) + || lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_HVC1_VIDEO ) + || lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_HEV1_VIDEO ) ) + RETURN_BITRATE_UPDATER( nalu_update_bitrate ) + else if( lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_MP4V_VIDEO ) ) + RETURN_BITRATE_UPDATER( mp4v_update_bitrate ) + else if( lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_MP4A_AUDIO ) + || lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_MP4A_AUDIO ) ) + RETURN_BITRATE_UPDATER( mp4a_update_bitrate ) + else if( lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_ALAC_AUDIO ) + || lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_ALAC_AUDIO ) ) + RETURN_BITRATE_UPDATER( alac_update_bitrate ) + else if( isom_is_dts_audio( sample_type ) ) + RETURN_BITRATE_UPDATER( dts_update_bitrate ) + else if( lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_EC_3_AUDIO ) ) + RETURN_BITRATE_UPDATER( eac3_update_bitrate ) + else if( lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_RRTP_HINT ) ) + RETURN_BITRATE_UPDATER( hint_update_bitrate ) + else if( isom_is_waveform_audio( sample_type ) ) + RETURN_BITRATE_UPDATER( waveform_audio_update_bitrate ) + else + return NULL; +#undef RETURN_BITRATE_UPDATER +} diff --git a/3rdparty/l-smash/codecs/description.h b/3rdparty/l-smash/codecs/description.h new file mode 100644 index 000000000..c4475adab --- /dev/null +++ b/3rdparty/l-smash/codecs/description.h @@ -0,0 +1,79 @@ +/***************************************************************************** + * description.h + ***************************************************************************** + * Copyright (C) 2012-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +struct lsmash_codec_specific_list_tag +{ + lsmash_entry_list_t list; +}; + +lsmash_codec_specific_t *isom_duplicate_codec_specific_data +( + lsmash_codec_specific_t *specific +); + +lsmash_codec_specific_t *isom_get_codec_specific +( + lsmash_codec_specific_list_t *opaque, + lsmash_codec_specific_data_type type +); + +int isom_setup_sample_description +( + isom_stsd_t *stsd, + lsmash_media_type media_type, + lsmash_summary_t *summary +); + +int isom_setup_rtp_hint_description +( + isom_stsd_t *stsd, + lsmash_hint_summary_t *summary +); + +lsmash_summary_t *isom_create_video_summary_from_description +( + isom_sample_entry_t *sample_entry +); + +lsmash_summary_t *isom_create_audio_summary_from_description +( + isom_sample_entry_t *sample_entry +); + +int isom_compare_opaque_extensions +( + lsmash_summary_t *a, + lsmash_summary_t *b +); + +int isom_get_implicit_qt_fixed_comp_audio_sample_quants +( + isom_audio_entry_t *audio, + uint32_t *samples_per_packet, + uint32_t *constant_bytes_per_frame, + uint32_t *sample_size +); + +isom_bitrate_updater_t isom_get_bitrate_updater +( + isom_sample_entry_t *sample_entry +); diff --git a/3rdparty/l-smash/codecs/dts.c b/3rdparty/l-smash/codecs/dts.c new file mode 100644 index 000000000..f0f8e0269 --- /dev/null +++ b/3rdparty/l-smash/codecs/dts.c @@ -0,0 +1,1834 @@ +/***************************************************************************** + * dts.c + ***************************************************************************** + * Copyright (C) 2012-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include +#include +#include + +#include "core/box.h" + +/***************************************************************************** + ETSI TS 102 114 V1.2.1 (2002-12) + ETSI TS 102 114 V1.3.1 (2011-08) + ETSI TS 102 114 V1.4.1 (2012-09) + + IMPLEMENTATION OF DTS AUDIO IN MEDIA FILES BASED ON ISO/IEC 14496 + Document No.: 9302J81100 + Revision: F + Version: 1.3 + + Common File Format & Media Formats Specification Version 2.2 31 July 2015 +*****************************************************************************/ +#include "dts.h" + +#define DTS_MIN_CORE_SIZE 96 +#define DTS_MAX_STREAM_CONSTRUCTION 21 +#define DTS_SPECIFIC_BOX_MIN_LENGTH 28 + +typedef enum +{ + DTS_SYNCWORD_CORE = 0x7FFE8001, + DTS_SYNCWORD_XCH = 0x5A5A5A5A, + DTS_SYNCWORD_XXCH = 0x47004A03, + DTS_SYNCWORD_X96K = 0x1D95F262, + DTS_SYNCWORD_XBR = 0x655E315E, + DTS_SYNCWORD_LBR = 0x0A801921, + DTS_SYNCWORD_XLL = 0x41A29547, + DTS_SYNCWORD_SUBSTREAM = 0x64582025, + DTS_SYNCWORD_SUBSTREAM_CORE = 0x02b09261, + DTS_SYNCWORD_X = 0x02000850, +} dts_syncword; + +/* Loudspeaker Masks (up to 32-bit) for + * - nuCoreSpkrActivityMask + * - nuXXChSpkrLayoutMask + * - DownMixChMapMask + * - nChMask + * - nSpkrMask */ +typedef enum +{ + DTS_LOUDSPEAKER_MASK32_C = 0x00000001, /* Centre in front of listener */ + DTS_LOUDSPEAKER_MASK32_L = 0x00000002, /* Left in front */ + DTS_LOUDSPEAKER_MASK32_R = 0x00000004, /* Right in front */ + DTS_LOUDSPEAKER_MASK32_LS = 0x00000008, /* Left surround on side in rear */ + DTS_LOUDSPEAKER_MASK32_RS = 0x00000010, /* Right surround on side in rear */ + DTS_LOUDSPEAKER_MASK32_LFE1 = 0x00000020, /* Low frequency effects subwoofer */ + DTS_LOUDSPEAKER_MASK32_CS = 0x00000040, /* Centre surround in rear */ + DTS_LOUDSPEAKER_MASK32_LSR = 0x00000080, /* Left surround in rear */ + DTS_LOUDSPEAKER_MASK32_RSR = 0x00000100, /* Right surround in rear */ + DTS_LOUDSPEAKER_MASK32_LSS = 0x00000200, /* Left surround on side */ + DTS_LOUDSPEAKER_MASK32_RSS = 0x00000400, /* Right surround on side */ + DTS_LOUDSPEAKER_MASK32_LC = 0x00000800, /* Between left and centre in front */ + DTS_LOUDSPEAKER_MASK32_RC = 0x00001000, /* Between right and centre in front */ + DTS_LOUDSPEAKER_MASK32_LH = 0x00002000, /* Left height in front */ + DTS_LOUDSPEAKER_MASK32_CH = 0x00004000, /* Centre Height in front */ + DTS_LOUDSPEAKER_MASK32_RH = 0x00008000, /* Right Height in front */ + DTS_LOUDSPEAKER_MASK32_LFE2 = 0x00010000, /* Second low frequency effects subwoofer */ + DTS_LOUDSPEAKER_MASK32_LW = 0x00020000, /* Left on side in front */ + DTS_LOUDSPEAKER_MASK32_RW = 0x00040000, /* Right on side in front */ + DTS_LOUDSPEAKER_MASK32_OH = 0x00080000, /* Over the listener's head */ + DTS_LOUDSPEAKER_MASK32_LHS = 0x00100000, /* Left height on side */ + DTS_LOUDSPEAKER_MASK32_RHS = 0x00200000, /* Right height on side */ + DTS_LOUDSPEAKER_MASK32_CHR = 0x00400000, /* Centre height in rear */ + DTS_LOUDSPEAKER_MASK32_LHR = 0x00800000, /* Left height in rear */ + DTS_LOUDSPEAKER_MASK32_RHR = 0x01000000, /* Right height in rear */ + DTS_LOUDSPEAKER_MASK32_CL = 0x02000000, /* Centre in the plane lower than listener's ears */ + DTS_LOUDSPEAKER_MASK32_LL = 0x04000000, /* Left in the plane lower than listener's ears */ + DTS_LOUDSPEAKER_MASK32_RL = 0x08000000, /* Right in the plane lower than listener's ears */ +} dts_loudspeaker_mask; + +/* Loudspeaker Masks (up to 16-bit) for + * - nuSpkrActivityMask + * - nuStndrSpkrLayoutMask + * - nuMixOutChMask + * - ChannelLayout of DTSSpecificBox */ +typedef enum +{ + DTS_CHANNEL_LAYOUT_C = 0x0001, /* Centre in front of listener */ + DTS_CHANNEL_LAYOUT_L_R = 0x0002, /* Left/Right in front */ + DTS_CHANNEL_LAYOUT_LS_RS = 0x0004, /* Left/Right surround on side in rear */ + DTS_CHANNEL_LAYOUT_LFE1 = 0x0008, /* Low frequency effects subwoofer */ + DTS_CHANNEL_LAYOUT_CS = 0x0010, /* Centre surround in rear */ + DTS_CHANNEL_LAYOUT_LH_RH = 0x0020, /* Left/Right height in front */ + DTS_CHANNEL_LAYOUT_LSR_RSR = 0x0040, /* Left/Right surround in rear */ + DTS_CHANNEL_LAYOUT_CH = 0x0080, /* Centre height in front */ + DTS_CHANNEL_LAYOUT_OH = 0x0100, /* Over the listener's head */ + DTS_CHANNEL_LAYOUT_LC_RC = 0x0200, /* Between left/right and centre in front */ + DTS_CHANNEL_LAYOUT_LW_RW = 0x0400, /* Left/Right on side in front */ + DTS_CHANNEL_LAYOUT_LSS_RSS = 0x0800, /* Left/Right surround on side */ + DTS_CHANNEL_LAYOUT_LFE2 = 0x1000, /* Second low frequency effects subwoofer */ + DTS_CHANNEL_LAYOUT_LHS_RHS = 0x2000, /* Left/Right height on side */ + DTS_CHANNEL_LAYOUT_CHR = 0x4000, /* Centre height in rear */ + DTS_CHANNEL_LAYOUT_LHR_RHR = 0x8000, /* Left/Right height in rear */ +} dts_channel_layout; + +static const lsmash_dts_construction_flag construction_info[DTS_MAX_STREAM_CONSTRUCTION + 1] = + { + 0, + DTS_CORE_SUBSTREAM_CORE_FLAG, + DTS_CORE_SUBSTREAM_CORE_FLAG | DTS_CORE_SUBSTREAM_XCH_FLAG, + DTS_CORE_SUBSTREAM_CORE_FLAG | DTS_CORE_SUBSTREAM_XXCH_FLAG, + DTS_CORE_SUBSTREAM_CORE_FLAG | DTS_CORE_SUBSTREAM_X96_FLAG, + DTS_CORE_SUBSTREAM_CORE_FLAG | DTS_EXT_SUBSTREAM_XXCH_FLAG, + DTS_CORE_SUBSTREAM_CORE_FLAG | DTS_EXT_SUBSTREAM_XBR_FLAG, + DTS_CORE_SUBSTREAM_CORE_FLAG | DTS_CORE_SUBSTREAM_XCH_FLAG | DTS_EXT_SUBSTREAM_XBR_FLAG, + DTS_CORE_SUBSTREAM_CORE_FLAG | DTS_CORE_SUBSTREAM_XXCH_FLAG | DTS_EXT_SUBSTREAM_XBR_FLAG, + DTS_CORE_SUBSTREAM_CORE_FLAG | DTS_EXT_SUBSTREAM_XXCH_FLAG | DTS_EXT_SUBSTREAM_XBR_FLAG, + DTS_CORE_SUBSTREAM_CORE_FLAG | DTS_EXT_SUBSTREAM_X96_FLAG, + DTS_CORE_SUBSTREAM_CORE_FLAG | DTS_CORE_SUBSTREAM_XCH_FLAG | DTS_EXT_SUBSTREAM_X96_FLAG, + DTS_CORE_SUBSTREAM_CORE_FLAG | DTS_CORE_SUBSTREAM_XXCH_FLAG | DTS_EXT_SUBSTREAM_X96_FLAG, + DTS_CORE_SUBSTREAM_CORE_FLAG | DTS_EXT_SUBSTREAM_XXCH_FLAG | DTS_EXT_SUBSTREAM_X96_FLAG, + DTS_CORE_SUBSTREAM_CORE_FLAG | DTS_EXT_SUBSTREAM_XLL_FLAG, + DTS_CORE_SUBSTREAM_CORE_FLAG | DTS_CORE_SUBSTREAM_XCH_FLAG | DTS_EXT_SUBSTREAM_XLL_FLAG, + DTS_CORE_SUBSTREAM_CORE_FLAG | DTS_CORE_SUBSTREAM_X96_FLAG | DTS_EXT_SUBSTREAM_XLL_FLAG, + DTS_EXT_SUBSTREAM_XLL_FLAG, + DTS_EXT_SUBSTREAM_LBR_FLAG, + DTS_EXT_SUBSTREAM_CORE_FLAG, + DTS_EXT_SUBSTREAM_CORE_FLAG | DTS_EXT_SUBSTREAM_XXCH_FLAG, + DTS_EXT_SUBSTREAM_CORE_FLAG | DTS_EXT_SUBSTREAM_XLL_FLAG , + }; + +void dts_setup_parser( dts_info_t *info ) +{ + dts_extension_info_t *exss = &info->exss[0]; + /* By default the core substream data, if present, has the nuBcCoreExtSSIndex = 0 and the nuBcCoreAssetIndex = 0. + * Therefore, we can treat as if one extension substream is there even if no extension substreams. */ + exss->nuNumAudioPresnt = 1; + exss->nuNumAssets = 1; + exss->bBcCorePresent [0] = 0; + exss->nuBcCoreExtSSIndex[0] = 0; + exss->nuBcCoreAssetIndex[0] = 0; +} + +struct lsmash_dts_reserved_box_tag +{ + uint32_t size; + uint8_t *data; +}; + +int lsmash_append_dts_reserved_box( lsmash_dts_specific_parameters_t *param, const uint8_t *box_data, uint32_t box_size ) +{ + if( !param || !box_data || box_size == 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + if( !param->box ) + { + param->box = lsmash_malloc_zero( sizeof(lsmash_dts_reserved_box_t) ); + if( !param->box ) + return LSMASH_ERR_MEMORY_ALLOC; + } + /* New a DTSExpansionBox. */ + uint32_t size = param->box->size + box_size; + uint8_t *data = lsmash_realloc( param->box->data, size ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + memcpy( data + param->box->size, box_data, box_size ); + param->box->data = data; + param->box->size = size; + return 0; +} + +void lsmash_remove_dts_reserved_box( lsmash_dts_specific_parameters_t *param ) +{ + if( !param->box ) + return; + lsmash_free( param->box->data ); + lsmash_freep( ¶m->box ); +} + +void dts_destruct_specific_data( void *data ) +{ + if( !data ) + return; + lsmash_remove_dts_reserved_box( data ); + lsmash_free( data ); +} + +uint8_t lsmash_dts_get_stream_construction( lsmash_dts_construction_flag flags ) +{ + uint8_t StreamConstruction; + for( StreamConstruction = 1; StreamConstruction <= DTS_MAX_STREAM_CONSTRUCTION; StreamConstruction++ ) + if( flags == construction_info[StreamConstruction] ) + break; + /* For any stream type not listed in the above table, + * StreamConstruction shall be set to 0 and the codingname shall default to 'dtsh'. */ + return StreamConstruction <= DTS_MAX_STREAM_CONSTRUCTION ? StreamConstruction : 0; +} + +lsmash_dts_construction_flag lsmash_dts_get_construction_flags( uint8_t stream_construction ) +{ + if( stream_construction <= DTS_MAX_STREAM_CONSTRUCTION ) + return construction_info[stream_construction]; + return 0; +} + +lsmash_codec_type_t lsmash_dts_get_codingname( lsmash_dts_specific_parameters_t *param ) +{ + assert( param->StreamConstruction <= DTS_MAX_STREAM_CONSTRUCTION ); + if( param->MultiAssetFlag ) + return ISOM_CODEC_TYPE_DTSH_AUDIO; /* Multiple asset streams shall use the 'dtsh' coding_name. */ + static lsmash_codec_type_t codingname_table[DTS_MAX_STREAM_CONSTRUCTION + 1] = { LSMASH_CODEC_TYPE_INITIALIZER }; + if( lsmash_check_codec_type_identical( codingname_table[0], LSMASH_CODEC_TYPE_UNSPECIFIED ) ) + { + int i = 0; + codingname_table[i++] = ISOM_CODEC_TYPE_DTSH_AUDIO; /* Undefined stream types shall be set to 0 and the codingname shall default to 'dtsh'. */ + codingname_table[i++] = ISOM_CODEC_TYPE_DTSC_AUDIO; + codingname_table[i++] = ISOM_CODEC_TYPE_DTSC_AUDIO; + codingname_table[i++] = ISOM_CODEC_TYPE_DTSH_AUDIO; + codingname_table[i++] = ISOM_CODEC_TYPE_DTSC_AUDIO; + codingname_table[i++] = ISOM_CODEC_TYPE_DTSH_AUDIO; + codingname_table[i++] = ISOM_CODEC_TYPE_DTSH_AUDIO; + codingname_table[i++] = ISOM_CODEC_TYPE_DTSH_AUDIO; + codingname_table[i++] = ISOM_CODEC_TYPE_DTSH_AUDIO; + codingname_table[i++] = ISOM_CODEC_TYPE_DTSH_AUDIO; + codingname_table[i++] = ISOM_CODEC_TYPE_DTSH_AUDIO; + codingname_table[i++] = ISOM_CODEC_TYPE_DTSH_AUDIO; + codingname_table[i++] = ISOM_CODEC_TYPE_DTSH_AUDIO; + codingname_table[i++] = ISOM_CODEC_TYPE_DTSH_AUDIO; + codingname_table[i++] = ISOM_CODEC_TYPE_DTSH_AUDIO; + codingname_table[i++] = ISOM_CODEC_TYPE_DTSH_AUDIO; + codingname_table[i++] = ISOM_CODEC_TYPE_DTSH_AUDIO; + codingname_table[i++] = ISOM_CODEC_TYPE_DTSL_AUDIO; + codingname_table[i++] = ISOM_CODEC_TYPE_DTSE_AUDIO; + codingname_table[i++] = ISOM_CODEC_TYPE_DTSH_AUDIO; + codingname_table[i++] = ISOM_CODEC_TYPE_DTSH_AUDIO; + codingname_table[i++] = ISOM_CODEC_TYPE_DTSH_AUDIO; + } + lsmash_codec_type_t codingname = codingname_table[ param->StreamConstruction ]; + /* Check the presence of DTSXParameters Box. */ + if( !lsmash_check_codec_type_identical( codingname, ISOM_CODEC_TYPE_DTSC_AUDIO ) + && !lsmash_check_codec_type_identical( codingname, ISOM_CODEC_TYPE_DTSE_AUDIO ) + && param->box + && param->box->data + && param->box->size >= ISOM_FULLBOX_COMMON_SIZE ) + { + uint8_t *data = param->box->data; + uint32_t pos = 0; + while( pos + ISOM_FULLBOX_COMMON_SIZE <= param->box->size ) + { + uint32_t size = LSMASH_GET_BE32( &data[0] ); + uint32_t type = LSMASH_GET_BE32( &data[4] ); + if( type == LSMASH_4CC( 'd', 'x', 'p', 'b' ) ) + return ISOM_CODEC_TYPE_DTSX_AUDIO; + pos += size; + } + } + return codingname; +} + +uint8_t *lsmash_create_dts_specific_info( lsmash_dts_specific_parameters_t *param, uint32_t *data_length ) +{ + int reserved_box_present = (param->box && param->box->data && param->box->size); + lsmash_bits_t *bits = lsmash_bits_adhoc_create(); + if( !bits ) + return NULL; + /* Create a DTSSpecificBox. */ + lsmash_bits_put( bits, 32, 0 ); /* box size */ + lsmash_bits_put( bits, 32, ISOM_BOX_TYPE_DDTS.fourcc ); /* box type: 'ddts' */ + lsmash_bits_put( bits, 32, param->DTSSamplingFrequency ); + lsmash_bits_put( bits, 32, param->maxBitrate ); /* maxBitrate; setup by isom_update_bitrate_description */ + lsmash_bits_put( bits, 32, param->avgBitrate ); /* avgBitrate; setup by isom_update_bitrate_description */ + lsmash_bits_put( bits, 8, param->pcmSampleDepth ); + lsmash_bits_put( bits, 2, param->FrameDuration ); + lsmash_bits_put( bits, 5, param->StreamConstruction ); + lsmash_bits_put( bits, 1, param->CoreLFEPresent ); + lsmash_bits_put( bits, 6, param->CoreLayout ); + lsmash_bits_put( bits, 14, param->CoreSize ); + lsmash_bits_put( bits, 1, param->StereoDownmix ); + lsmash_bits_put( bits, 3, param->RepresentationType ); + lsmash_bits_put( bits, 16, param->ChannelLayout ); + lsmash_bits_put( bits, 1, param->MultiAssetFlag ); + lsmash_bits_put( bits, 1, param->LBRDurationMod ); + lsmash_bits_put( bits, 1, reserved_box_present ); + lsmash_bits_put( bits, 5, 0 ); /* Reserved */ + /* ReservedBox */ + if( reserved_box_present ) + for( uint32_t i = 0; i < param->box->size; i++ ) + lsmash_bits_put( bits, 8, param->box->data[i] ); + /* */ + uint8_t *data = lsmash_bits_export_data( bits, data_length ); + lsmash_bits_adhoc_cleanup( bits ); + /* Update box size. */ + LSMASH_SET_BE32( data, *data_length ); + return data; +} + +int lsmash_setup_dts_specific_parameters_from_frame( lsmash_dts_specific_parameters_t *param, uint8_t *data, uint32_t data_length ) +{ + lsmash_bits_t bits = { 0 }; + lsmash_bs_t bs = { 0 }; + uint8_t buffer[DTS_MAX_EXSS_SIZE] = { 0 }; + bs.buffer.data = buffer; + bs.buffer.store = data_length; + bs.buffer.alloc = DTS_MAX_EXSS_SIZE; + dts_info_t *info = &(dts_info_t){ .bits = &bits }; + info->bits = &bits; + lsmash_bits_init( &bits, &bs ); + memcpy( buffer, data, LSMASH_MIN( data_length, DTS_MAX_EXSS_SIZE ) ); + dts_setup_parser( info ); + uint64_t next_frame_pos = 0; + while( 1 ) + { + int err; + /* Seek to the head of the next syncframe. */ + bs.buffer.pos = LSMASH_MIN( data_length, next_frame_pos ); + /* Check the remainder length of the buffer. + * If there is enough length, then continue to parse the frame in it. + * The length 10 is the required byte length to get frame size. */ + uint64_t remain_size = lsmash_bs_get_remaining_buffer_size( &bs ); + if( bs.eob || (bs.eof && remain_size < 10) ) + goto setup_param; /* No more valid data. */ + /* Parse substream frame. */ + dts_substream_type prev_substream_type = info->substream_type; + info->substream_type = dts_get_substream_type( info ); + int (*dts_parse_frame)( dts_info_t * ) = NULL; + switch( info->substream_type ) + { + /* Decide substream frame parser and check if this frame and the previous frame belong to the same AU. */ + case DTS_SUBSTREAM_TYPE_CORE : + if( prev_substream_type != DTS_SUBSTREAM_TYPE_NONE ) + goto setup_param; + dts_parse_frame = dts_parse_core_substream; + break; + case DTS_SUBSTREAM_TYPE_EXTENSION : + { + uint8_t prev_exss_index = info->exss_index; + if( (err = dts_get_exss_index( info, &info->exss_index )) < 0 ) + return err; + if( prev_substream_type == DTS_SUBSTREAM_TYPE_EXTENSION && info->exss_index <= prev_exss_index ) + goto setup_param; + dts_parse_frame = dts_parse_extension_substream; + break; + } + default : + /* An unknown stream type is detected. */ + return LSMASH_ERR_NAMELESS; + } + info->frame_size = 0; + if( (err = dts_parse_frame( info )) < 0 ) + return err; /* Failed to parse. */ + next_frame_pos += info->frame_size; + } +setup_param: + dts_update_specific_param( info ); + *param = info->ddts_param; + return 0; +} + +static inline uint64_t dts_bits_get( lsmash_bits_t *bits, uint32_t width, uint64_t *bits_pos ) +{ + *bits_pos += width; + return lsmash_bits_get( bits, width ); +} + +static inline void dts_bits_align( lsmash_bits_t *bits, uint64_t *bits_pos ) +{ + uint8_t remainder = 8 - (*bits_pos & 0x7); + (void)dts_bits_get( bits, remainder, bits_pos ); +} + +static inline void dts_bits_align4( lsmash_bits_t *bits, uint64_t *bits_pos ) +{ + uint8_t remainder = 32 - (*bits_pos & 0x1f); + (void)dts_bits_get( bits, remainder, bits_pos ); +} + +static int dts_get_channel_count_from_channel_layout( uint16_t channel_layout ) +{ +#define DTS_CHANNEL_PAIR_MASK \ + (DTS_CHANNEL_LAYOUT_L_R \ + | DTS_CHANNEL_LAYOUT_LS_RS \ + | DTS_CHANNEL_LAYOUT_LH_RH \ + | DTS_CHANNEL_LAYOUT_LSR_RSR \ + | DTS_CHANNEL_LAYOUT_LC_RC \ + | DTS_CHANNEL_LAYOUT_LW_RW \ + | DTS_CHANNEL_LAYOUT_LSS_RSS \ + | DTS_CHANNEL_LAYOUT_LHS_RHS \ + | DTS_CHANNEL_LAYOUT_LHR_RHR) + return lsmash_count_bits( channel_layout ) + + lsmash_count_bits( channel_layout & DTS_CHANNEL_PAIR_MASK ); +#undef DTS_CHANNEL_PAIR_MASK +} + +static uint32_t dts_get_channel_layout_from_ls_mask32( uint32_t mask ) +{ + uint32_t layout = 0; + if( mask & DTS_LOUDSPEAKER_MASK32_C ) + layout |= DTS_CHANNEL_LAYOUT_C; + if( mask & (DTS_LOUDSPEAKER_MASK32_L | DTS_LOUDSPEAKER_MASK32_R) ) + layout |= DTS_CHANNEL_LAYOUT_L_R; + if( mask & (DTS_LOUDSPEAKER_MASK32_LS | DTS_LOUDSPEAKER_MASK32_RS) ) + layout |= DTS_CHANNEL_LAYOUT_LS_RS; + if( mask & DTS_LOUDSPEAKER_MASK32_LFE1 ) + layout |= DTS_CHANNEL_LAYOUT_LFE1; + if( mask & DTS_LOUDSPEAKER_MASK32_CS ) + layout |= DTS_CHANNEL_LAYOUT_CS; + if( mask & (DTS_LOUDSPEAKER_MASK32_LH | DTS_LOUDSPEAKER_MASK32_RH) ) + layout |= DTS_CHANNEL_LAYOUT_LH_RH; + if( mask & (DTS_LOUDSPEAKER_MASK32_LSR | DTS_LOUDSPEAKER_MASK32_RSR) ) + layout |= DTS_CHANNEL_LAYOUT_LSR_RSR; + if( mask & DTS_LOUDSPEAKER_MASK32_CH ) + layout |= DTS_CHANNEL_LAYOUT_CH; + if( mask & DTS_LOUDSPEAKER_MASK32_OH ) + layout |= DTS_CHANNEL_LAYOUT_OH; + if( mask & (DTS_LOUDSPEAKER_MASK32_LC | DTS_LOUDSPEAKER_MASK32_RC) ) + layout |= DTS_CHANNEL_LAYOUT_LC_RC; + if( mask & (DTS_LOUDSPEAKER_MASK32_LW | DTS_LOUDSPEAKER_MASK32_RW) ) + layout |= DTS_CHANNEL_LAYOUT_LW_RW; + if( mask & (DTS_LOUDSPEAKER_MASK32_LSS | DTS_LOUDSPEAKER_MASK32_RSS) ) + layout |= DTS_CHANNEL_LAYOUT_LSS_RSS; + if( mask & DTS_LOUDSPEAKER_MASK32_LFE2 ) + layout |= DTS_CHANNEL_LAYOUT_LFE2; + if( mask & (DTS_LOUDSPEAKER_MASK32_LHS | DTS_LOUDSPEAKER_MASK32_RHS) ) + layout |= DTS_CHANNEL_LAYOUT_LHS_RHS; + if( mask & DTS_LOUDSPEAKER_MASK32_CHR ) + layout |= DTS_CHANNEL_LAYOUT_CHR; + if( mask & (DTS_LOUDSPEAKER_MASK32_LHR | DTS_LOUDSPEAKER_MASK32_RHR) ) + layout |= DTS_CHANNEL_LAYOUT_LHR_RHR; + return layout; +} + +/* for channels which cannot be expressed by ChannelLayout; CL, LL and RL */ +static inline uint8_t dts_get_lower_channels_from_ls_mask32( uint32_t mask ) +{ + return (mask >> 25) & 0x7; +} + +static void dts_parse_xll_navigation( lsmash_bits_t *bits, dts_xll_info_t *xll, int nuBits4ExSSFsize, uint64_t *bits_pos ) +{ + xll->size = dts_bits_get( bits, nuBits4ExSSFsize, bits_pos ) + 1; /* nuExSSXLLFsize (nuBits4ExSSFsize) */ + if( dts_bits_get( bits, 1, bits_pos ) ) /* bExSSXLLSyncPresent (1) */ + { + dts_bits_get( bits, 4, bits_pos ); /* nuPeakBRCntrlBuffSzkB (4) */ + int nuBitsInitDecDly = dts_bits_get( bits, 5, bits_pos ) + 1; /* nuBitsInitDecDly (5) */ + dts_bits_get( bits, nuBitsInitDecDly, bits_pos ); /* nuInitLLDecDlyFrames (nuBitsInitDecDly) */ + dts_bits_get( bits, nuBits4ExSSFsize, bits_pos ); /* nuExSSXLLSyncOffset (nuBits4ExSSFsize) */ + } +} + +static void dts_parse_lbr_navigation( lsmash_bits_t *bits, dts_lbr_info_t *lbr, uint64_t *bits_pos ) +{ + lbr->size = dts_bits_get( bits, 14, bits_pos ); /* nuExSSLBRFsize (14) */ + if( dts_bits_get( bits, 1, bits_pos ) ) /* bExSSLBRSyncPresent (1) */ + dts_bits_get( bits, 2, bits_pos ); /* nuExSSLBRSyncDistInFrames (2) */ +} + +static int dts_parse_asset_descriptor( dts_info_t *info, uint64_t *bits_pos ) +{ + lsmash_bits_t *bits = info->bits; + dts_extension_info_t *exss = &info->exss[ info->exss_index ]; + /* Audio asset descriptor */ + uint64_t asset_descriptor_pos = *bits_pos; + int nuAssetDescriptFsize = dts_bits_get( bits, 9, bits_pos ) + 1; /* nuAssetDescriptFsize (9) */ + dts_audio_asset_t *asset = &exss->asset[ dts_bits_get( bits, 3, bits_pos ) ]; /* nuAssetIndex (3) */ + /* Static metadata */ + int bEmbeddedStereoFlag = 0; + int bEmbeddedSixChFlag = 0; + int nuTotalNumChs = 0; + if( exss->bStaticFieldsPresent ) + { + if( dts_bits_get( bits, 1, bits_pos ) ) /* bAssetTypeDescrPresent (1)*/ + dts_bits_get( bits, 4, bits_pos ); /* nuAssetTypeDescriptor (4) */ + if( dts_bits_get( bits, 1, bits_pos ) ) /* bLanguageDescrPresent (1) */ + dts_bits_get( bits, 24, bits_pos ); /* LanguageDescriptor (24) */ + if( dts_bits_get( bits, 1, bits_pos ) ) + { + int nuInfoTextByteSize = dts_bits_get( bits, 10, bits_pos ) + 1; /* nuInfoTextByteSize (10) */ + dts_bits_get( bits, nuInfoTextByteSize * 8, bits_pos ); /* InfoTextString (nuInfoTextByteSize) */ + } + int nuBitResolution = dts_bits_get( bits, 5, bits_pos ) + 1; /* nuBitResolution (5) */ + exss->bit_resolution = LSMASH_MAX( exss->bit_resolution, nuBitResolution ); + int nuMaxSampleRate = dts_bits_get( bits, 4, bits_pos ); /* nuMaxSampleRate (4) */ + static const uint32_t source_sample_rate_table[16] = + { + 8000, 16000, 32000, 64000, 128000, + 22050, 44100, 88200, 176400, 352800, + 12000, 24000, 48000, 96000, 192000, 384000 + }; + exss->sampling_frequency = LSMASH_MAX( exss->sampling_frequency, source_sample_rate_table[nuMaxSampleRate] ); + nuTotalNumChs = dts_bits_get( bits, 8, bits_pos ) + 1; /* nuTotalNumChs (8) */ + asset->bOne2OneMapChannels2Speakers = dts_bits_get( bits, 1, bits_pos ); /* bOne2OneMapChannels2Speakers (1) */ + if( asset->bOne2OneMapChannels2Speakers ) + { + if( nuTotalNumChs > 2 ) + { + bEmbeddedStereoFlag = dts_bits_get( bits, 1, bits_pos ); /* bEmbeddedStereoFlag (1) */ + exss->stereo_downmix |= bEmbeddedStereoFlag; + } + if( nuTotalNumChs > 6 ) + bEmbeddedSixChFlag = dts_bits_get( bits, 1, bits_pos ); /* bEmbeddedSixChFlag (1) */ + int nuNumBits4SAMask; + if( dts_bits_get( bits, 1, bits_pos ) ) /* bSpkrMaskEnabled (1) */ + { + nuNumBits4SAMask = (dts_bits_get( bits, 2, bits_pos ) + 1) << 2; /* nuNumBits4SAMask (2) */ + asset->channel_layout |= dts_bits_get( bits, nuNumBits4SAMask, bits_pos ); /* nuSpkrActivityMask (nuNumBits4SAMask) */ + } + else + /* The specification doesn't mention the value of nuNumBits4SAMask if bSpkrMaskEnabled is set to 0. */ + nuNumBits4SAMask = 16; + int nuNumSpkrRemapSets = dts_bits_get( bits, 3, bits_pos ); + int nuStndrSpkrLayoutMask[8] = { 0 }; + for( int ns = 0; ns < nuNumSpkrRemapSets; ns++ ) + nuStndrSpkrLayoutMask[ns] = dts_bits_get( bits, nuNumBits4SAMask, bits_pos ); + for( int ns = 0; ns < nuNumSpkrRemapSets; ns++ ) + { + int nuNumSpeakers = dts_get_channel_count_from_channel_layout( nuStndrSpkrLayoutMask[ns] ); + int nuNumDecCh4Remap = dts_bits_get( bits, 5, bits_pos ) + 1; /* nuNumDecCh4Remap[ns] (5) */ + for( int nCh = 0; nCh < nuNumSpeakers; nCh++ ) + { + uint32_t nuRemapDecChMask = dts_bits_get( bits, nuNumDecCh4Remap, bits_pos ); + int nCoef = lsmash_count_bits( nuRemapDecChMask ); + for( int nc = 0; nc < nCoef; nc++ ) + dts_bits_get( bits, 5, bits_pos ); /* nuSpkrRemapCodes[ns][nCh][nc] (5) */ + } + } + } + else + { + asset->nuRepresentationType = dts_bits_get( bits, 3, bits_pos ); /* nuRepresentationType (3) */ + if( asset->nuRepresentationType == 2 + || asset->nuRepresentationType == 3 ) + nuTotalNumChs = 2; + } + } + /* Dynamic metadata */ + int bDRCCoefPresent = dts_bits_get( bits, 1, bits_pos ); /* bDRCCoefPresent (1) */ + if( bDRCCoefPresent ) + dts_bits_get( bits, 8, bits_pos ); /* nuDRCCode (8) */ + if( dts_bits_get( bits, 1, bits_pos ) ) /* bDialNormPresent (1) */ + dts_bits_get( bits, 5, bits_pos ); /* nuDialNormCode (5) */ + if( bDRCCoefPresent && bEmbeddedStereoFlag ) + dts_bits_get( bits, 8, bits_pos ); /* nuDRC2ChDmixCode (8) */ + int bMixMetadataPresent; + if( exss->bMixMetadataEnbl ) + bMixMetadataPresent = dts_bits_get( bits, 1, bits_pos ); /* bMixMetadataPresent (1) */ + else + bMixMetadataPresent = 0; + if( bMixMetadataPresent ) + { + dts_bits_get( bits, 7, bits_pos ); /* bExternalMixFlag (1) + * nuPostMixGainAdjCode (7) */ + if( dts_bits_get( bits, 2, bits_pos ) < 3 ) /* nuControlMixerDRC (2) */ + dts_bits_get( bits, 3, bits_pos ); /* nuLimit4EmbeddedDRC (3) */ + else + dts_bits_get( bits, 8, bits_pos ); /* nuCustomDRCCode (8) */ + int bEnblPerChMainAudioScale = dts_bits_get( bits, 1, bits_pos ); /* bEnblPerChMainAudioScale (1) */ + for( uint8_t ns = 0; ns < exss->nuNumMixOutConfigs; ns++ ) + if( bEnblPerChMainAudioScale ) + for( uint8_t nCh = 0; nCh < exss->nNumMixOutCh[ns]; nCh++ ) + dts_bits_get( bits, 6, bits_pos ); /* nuMainAudioScaleCode[ns][nCh] (6) */ + else + dts_bits_get( bits, 6, bits_pos ); /* nuMainAudioScaleCode[ns][0] (6) */ + int nEmDM = 1; + int nDecCh[3] = { nuTotalNumChs, 0, 0 }; + if( bEmbeddedSixChFlag ) + { + nDecCh[nEmDM] = 6; + ++nEmDM; + } + if( bEmbeddedStereoFlag ) + { + nDecCh[nEmDM] = 2; + ++nEmDM; + } + for( uint8_t ns = 0; ns < exss->nuNumMixOutConfigs; ns++ ) + for( int nE = 0; nE < nEmDM; nE++ ) + for( int nCh = 0; nCh < nDecCh[nE]; nCh++ ) + { + int nuMixMapMask = dts_bits_get( bits, exss->nNumMixOutCh[ns], bits_pos ); /* nuMixMapMask (nNumMixOutCh[ns]) */ + int nuNumMixCoefs = lsmash_count_bits( nuMixMapMask ); + for( int nC = 0; nC < nuNumMixCoefs; nC++ ) + dts_bits_get( bits, 6, bits_pos ); /* nuMixCoeffs[ns][nE][nCh][nC] (6) */ + } + } + /* Decoder navigation data */ + asset->nuCodingMode = dts_bits_get( bits, 2, bits_pos ); /* nuCodingMode (2) */ + switch( asset->nuCodingMode ) + { + case 0 : /* DTS-HD Coding Mode that may contain multiple coding components */ + { + int nuCoreExtensionMask = dts_bits_get( bits, 12, bits_pos ); /* nuCoreExtensionMask (12) */ + asset->nuCoreExtensionMask = nuCoreExtensionMask; + if( nuCoreExtensionMask & DTS_EXT_SUBSTREAM_CORE_FLAG ) + { + asset->core.frame_size = dts_bits_get( bits, 14, bits_pos ) + 1; /* nuExSSCoreFsize (14) */ + if( dts_bits_get( bits, 1, bits_pos ) ) /* bExSSCoreSyncPresent (1) */ + dts_bits_get( bits, 2, bits_pos ); /* nuExSSCoreSyncDistInFrames (2) */ + } + if( nuCoreExtensionMask & DTS_EXT_SUBSTREAM_XBR_FLAG ) + asset->xbr_size = dts_bits_get( bits, 14, bits_pos ) + 1; + if( nuCoreExtensionMask & DTS_EXT_SUBSTREAM_XXCH_FLAG ) + asset->core.xxch.size = dts_bits_get( bits, 14, bits_pos ) + 1; + if( nuCoreExtensionMask & DTS_EXT_SUBSTREAM_X96_FLAG ) + asset->x96_size = dts_bits_get( bits, 12, bits_pos ) + 1; + if( nuCoreExtensionMask & DTS_EXT_SUBSTREAM_LBR_FLAG ) + dts_parse_lbr_navigation( bits, &asset->lbr, bits_pos ); + if( nuCoreExtensionMask & DTS_EXT_SUBSTREAM_XLL_FLAG ) + dts_parse_xll_navigation( bits, &asset->xll, exss->nuBits4ExSSFsize, bits_pos ); + break; + } + case 1 : /* DTS-HD Loss-less coding mode without CBR component */ + dts_parse_xll_navigation( bits, &asset->xll, exss->nuBits4ExSSFsize, bits_pos ); + break; + case 2 : /* DTS-HD Low bit-rate mode */ + dts_parse_lbr_navigation( bits, &asset->lbr, bits_pos ); + break; + case 3 : /* Auxiliary coding mode */ + asset->aux_size = dts_bits_get( bits, 14, bits_pos ) + 1; /* nuExSSAuxFsize (14) */ + break; + default : + assert( 0 ); + break; + } + dts_bits_get( bits, nuAssetDescriptFsize * 8 - (*bits_pos - asset_descriptor_pos), bits_pos ); /* Skip remaining part of Audio asset descriptor. */ + return bits->bs->error ? LSMASH_ERR_NAMELESS : 0; +} + +static int dts_parse_xxch( dts_info_t *info, uint64_t *bits_pos, dts_xxch_info_t *xxch ) +{ + lsmash_bits_t *bits = info->bits; + /* XXCH Frame Header */ + uint64_t xxch_pos = *bits_pos - 32; /* SYNCXXCh (32) */ + uint64_t nuHeaderSizeXXCh = dts_bits_get( bits, 6, bits_pos ) + 1; /* nuHeaderSizeXXCh (6) */ + dts_bits_get( bits, 1, bits_pos ); /* bCRCPresent4ChSetHeaderXXCh (1) */ + int nuBits4SpkrMaskXXCh = dts_bits_get( bits, 5, bits_pos ) + 1; /* nuBits4SpkrMaskXXCh (5) */ + int nuNumChSetsInXXCh = dts_bits_get( bits, 2, bits_pos ) + 1; /* nuNumChSetsInXXCh (2) */ + for( int nChSet = 0; nChSet < nuNumChSetsInXXCh; nChSet++ ) + dts_bits_get( bits, 14, bits_pos ); /* pnuChSetFsizeXXCh[nChSet] - 1 (14) */ + /* A 5.1 decoder uses this AMODE to configure its decoded outputs to C, L, R, Ls and Rs layout. + * On the other hand a 7.1 decoder ignores the AMODE information from the core stream and uses + * instead the nuCoreSpkrActivityMask (C, L, R, LFE1, Lss and Rss) and the nuXXChSpkrLayoutMask + * (Lsr and Rsr) from the XXCh stream to get the original 7.1 speaker layout (C, L, R, LFE1, Lss, + * Rsr, Lsr and Rsr) and configures its outputs accordingly. */ + uint32_t xxch_mask = dts_bits_get( bits, nuBits4SpkrMaskXXCh, bits_pos ); /* nuCoreSpkrActivityMask (nuBits4SpkrMaskXXCh) */ + xxch->channel_layout |= dts_get_channel_layout_from_ls_mask32( xxch_mask ); + xxch->lower_planes = dts_get_lower_channels_from_ls_mask32( xxch_mask ); + dts_bits_get( bits, nuHeaderSizeXXCh * 8 - (*bits_pos - xxch_pos), bits_pos ); /* Skip remaining part of XXCH Frame Header. */ + for( int nChSet = 0; nChSet < nuNumChSetsInXXCh; nChSet++ ) + { + /* XXCH Channel Set Header */ + xxch_pos = *bits_pos; + uint64_t nuXXChChSetHeaderSize = dts_bits_get( bits, 7, bits_pos ) + 1; /* nuXXChChSetHeaderSize (7)*/ + dts_bits_get( bits, 3, bits_pos ); /* nuChInChSetXXCh (3) */ + if( nuBits4SpkrMaskXXCh > 6 ) + { + xxch_mask = dts_bits_get( bits, nuBits4SpkrMaskXXCh - 6, bits_pos ) << 6; /* nuXXChSpkrLayoutMask (nuBits4SpkrMaskXXCh - 6) */ + xxch->channel_layout |= dts_get_channel_layout_from_ls_mask32( xxch_mask ); + xxch->lower_planes |= dts_get_lower_channels_from_ls_mask32( xxch_mask ); + } +#if 0 /* FIXME: Can we detect stereo downmixing from only XXCH data within the core substream? */ + if( dts_bits_get( bits, 1, bits_pos ) ) /* bDownMixCoeffCodeEmbedded (1) */ + { + int bDownMixEmbedded = dts_bits_get( bits, 1, bits_pos ); /* bDownMixEmbedded (1) */ + dts_bits_get( bits, 6, bits_pos ); /* nDmixScaleFactor (6) */ + uint32_t DownMixChMapMask[8]; + for( int nCh = 0; nCh < nuChInChSetXXCh; nCh++ ) + DownMixChMapMask[nCh] = dts_bits_get( bits, nuBits4SpkrMaskXXCh, bits_pos ); + } +#endif + dts_bits_get( bits, nuXXChChSetHeaderSize * 8 - (*bits_pos - xxch_pos), bits_pos ); /* Skip remaining part of XXCH Channel Set Header. */ + } + return 0; +} + +static int dts_parse_core_xxch( dts_info_t *info, uint64_t *bits_pos, dts_core_info_t *core ) +{ + if( core->extension_audio_descriptor == 0 + || core->extension_audio_descriptor == 3 ) + return LSMASH_ERR_INVALID_DATA; + int err = dts_parse_xxch( info, bits_pos, &core->xxch ); + if( err < 0 ) + return err; + info->flags |= DTS_CORE_SUBSTREAM_XXCH_FLAG; + return info->bits->bs->error ? LSMASH_ERR_NAMELESS : 0; +} + +static int dts_parse_exss_xxch( dts_info_t *info, uint64_t *bits_pos, dts_core_info_t *core ) +{ + lsmash_bits_t *bits = info->bits; + if( DTS_SYNCWORD_XXCH != dts_bits_get( bits, 32, bits_pos ) ) + return LSMASH_ERR_INVALID_DATA; + int err = dts_parse_xxch( info, bits_pos, &core->xxch ); + if( err < 0 ) + return err; + info->flags |= DTS_EXT_SUBSTREAM_XXCH_FLAG; + return bits->bs->error ? LSMASH_ERR_NAMELESS : 0; +} + +static int dts_parse_core_x96( dts_info_t *info, uint64_t *bits_pos, dts_core_info_t *core ) +{ + if( core->extension_audio_descriptor != 2 + && core->extension_audio_descriptor != 3 ) + return 0; /* Probably this is not an X96 extension. We skip this anyway. */ + lsmash_bits_t *bits = info->bits; + /* DTS_BCCORE_X96 Frame Header */ + /* SYNCX96 (32) */ + /* To reduce the probability of false synchronization caused by the presence of pseudo sync words, it is + * imperative to check the distance between the detected sync word and the end of current frame. This + * distance in bytes shall match the value of FSIZE96. */ + uint64_t FSIZE96 = ((lsmash_bs_show_byte( bits->bs, 0 ) << 4) + | ((lsmash_bs_show_byte( bits->bs, 1 ) >> 4) & 0x0F)) + 1; + if( core->frame_size * 8 != (*bits_pos - 32 + FSIZE96 * 8) ) + return 0; /* Encountered four emulation bytes (pseudo sync word). */ + dts_bits_get( bits, 16, bits_pos ); /* FSIZE96 (12) + * REVNO (4) */ + core->sampling_frequency *= 2; + core->frame_duration *= 2; + info->flags |= DTS_CORE_SUBSTREAM_X96_FLAG; + return bits->bs->error ? LSMASH_ERR_NAMELESS : 0; +} + +static int dts_parse_core_xch( dts_info_t *info, uint64_t *bits_pos, dts_core_info_t *core ) +{ + if( core->extension_audio_descriptor != 0 + && core->extension_audio_descriptor != 3 ) + return 0; /* Probably this is not an XCh extension. We skip this anyway. */ + lsmash_bits_t *bits = info->bits; + /* XCH Frame Header */ + /* XChSYNC (32) */ + /* For compatibility reasons with legacy bitstreams the estimated distance in bytes is checked against + * the XChFSIZE+1 as well as the XChFSIZE. The XCh synchronization is pronounced if the distance matches + * either of these two values. */ + uint64_t XChFSIZE = (lsmash_bs_show_byte( bits->bs, 0 ) << 2) + | ((lsmash_bs_show_byte( bits->bs, 1 ) >> 6) & 0x03); + if( core->frame_size * 8 != (*bits_pos - 32 + (XChFSIZE + 1) * 8) + && core->frame_size * 8 != (*bits_pos - 32 + XChFSIZE * 8) ) + return 0; /* Encountered four emulation bytes (pseudo sync word). */ + if( ((lsmash_bs_show_byte( bits->bs, 1 ) >> 2) & 0xF) != 1 ) + return 0; /* A known value of AMODE is only 1. Otherwise just skip. */ + dts_bits_get( bits, 16, bits_pos ); /* XChFSIZE (10) + * AMODE (4) + * byte align (2) */ + core->channel_layout |= DTS_CHANNEL_LAYOUT_CS; + info->flags |= DTS_CORE_SUBSTREAM_XCH_FLAG; + return bits->bs->error ? LSMASH_ERR_NAMELESS : 0; +} + +static int dts_parse_exss_xbr( dts_info_t *info, uint64_t *bits_pos ) +{ + lsmash_bits_t *bits = info->bits; + /* XBR Frame Header */ + uint64_t xbr_pos = *bits_pos; + if( DTS_SYNCWORD_XBR != dts_bits_get( bits, 32, bits_pos ) ) /* SYNCXBR (32) */ + return LSMASH_ERR_INVALID_DATA; + uint64_t nHeaderSizeXBR = dts_bits_get( bits, 6, bits_pos ) + 1; /* nHeaderSizeXBR (6) */ + dts_bits_get( bits, nHeaderSizeXBR * 8 - (*bits_pos - xbr_pos), bits_pos ); /* Skip the remaining bits in XBR Frame Header. */ + info->flags |= DTS_EXT_SUBSTREAM_XBR_FLAG; + return bits->bs->error ? LSMASH_ERR_NAMELESS : 0; +} + +static int dts_parse_exss_x96( dts_info_t *info, uint64_t *bits_pos, dts_core_info_t *core ) +{ + lsmash_bits_t *bits = info->bits; + /* DTS_EXSUB_STREAM_X96 Frame Header */ + uint64_t x96_pos = *bits_pos; + if( DTS_SYNCWORD_X96K != dts_bits_get( bits, 32, bits_pos ) ) /* SYNCX96 (32) */ + return LSMASH_ERR_INVALID_DATA; + uint64_t nHeaderSizeX96 = dts_bits_get( bits, 6, bits_pos ) + 1; /* nHeaderSizeXBR (6) */ + dts_bits_get( bits, nHeaderSizeX96 * 8 - (*bits_pos - x96_pos), bits_pos ); /* Skip the remaining bits in DTS_EXSUB_STREAM_X96 Frame Header. */ + /* What the fuck! The specification drops 'if' sentence. + * We assume the same behaviour for core substream. */ + core->sampling_frequency *= 2; + core->frame_duration *= 2; + info->flags |= DTS_EXT_SUBSTREAM_X96_FLAG; + return bits->bs->error ? LSMASH_ERR_NAMELESS : 0; +} + +static int dts_parse_exss_lbr( dts_info_t *info, uint64_t *bits_pos, dts_audio_asset_t *asset ) +{ + lsmash_bits_t *bits = info->bits; + dts_lbr_info_t *lbr = &asset->lbr; + if( DTS_SYNCWORD_LBR != dts_bits_get( bits, 32, bits_pos ) ) /* SYNCEXTLBR (32) */ + return LSMASH_ERR_INVALID_DATA; + int ucFmtInfoCode = dts_bits_get( bits, 8, bits_pos ); + if( ucFmtInfoCode == 2 ) + { + /* LBR decoder initialization data */ + int nLBRSampleRateCode = dts_bits_get( bits, 8, bits_pos ); /* nLBRSampleRateCode (8) */ + int usLBRSpkrMask = dts_bits_get( bits, 16, bits_pos ); /* usLBRSpkrMask (16) */ + dts_bits_get( bits, 16, bits_pos ); /* nLBRversion (16) */ + int nLBRCompressedFlags = dts_bits_get( bits, 8, bits_pos ); /* nLBRCompressedFlags (8) */ + dts_bits_get( bits, 40, bits_pos ); /* nLBRBitRateMSnybbles (8) + * nLBROriginalBitRate_LSW (16) + * nLBRScaledBitRate_LSW (16) */ + static const uint32_t source_sample_rate_table[16] = + { + 8000, 16000, 32000, 0, 0, + 11025, 22050, 44100, 0, 0, + 12000, 24000, 48000, 0, 0, 0 + }; + enum LBRFlags + { + LBR_FLAG_24_BIT_SAMPLES = 0x01, /* 0b00000001 */ + LBR_FLAG_USE_LFE = 0x02, /* 0b00000010 */ + LBR_FLAG_BANDLMT_MASK = 0x1C, /* 0b00011100 */ + LBR_FLAG_STEREO_DOWNMIX = 0x20, /* 0b00100000 */ + LBR_FLAG_MULTICHANNEL_DOWNMIX = 0x40, /* 0b01000000 */ + }; + lbr->sampling_frequency = source_sample_rate_table[nLBRSampleRateCode]; + lbr->frame_duration = lbr->sampling_frequency < 16000 ? 1024 + : lbr->sampling_frequency < 32000 ? 2048 + : 4096; + lbr->channel_layout = ((usLBRSpkrMask >> 8) & 0xff) | ((usLBRSpkrMask << 8) & 0xff00); /* usLBRSpkrMask is little-endian. */ + lbr->stereo_downmix |= !!(nLBRCompressedFlags & LBR_FLAG_STEREO_DOWNMIX); + lbr->lfe_present |= !!(nLBRCompressedFlags & LBR_FLAG_USE_LFE); + lbr->duration_modifier |= ((nLBRCompressedFlags & LBR_FLAG_BANDLMT_MASK) == 0x04) + || ((nLBRCompressedFlags & LBR_FLAG_BANDLMT_MASK) == 0x0C); + lbr->sample_size = (nLBRCompressedFlags & LBR_FLAG_24_BIT_SAMPLES) ? 24 : 16; + } + else if( ucFmtInfoCode != 1 ) + return LSMASH_ERR_NAMELESS; /* unknown */ + info->flags |= DTS_EXT_SUBSTREAM_LBR_FLAG; + return bits->bs->error ? LSMASH_ERR_NAMELESS : 0; +} + +static int dts_parse_exss_xll( dts_info_t *info, uint64_t *bits_pos, dts_audio_asset_t *asset ) +{ + lsmash_bits_t *bits = info->bits; + dts_xll_info_t *xll = &asset->xll; + /* Common Header */ + uint64_t xll_pos = *bits_pos; + if( DTS_SYNCWORD_XLL != dts_bits_get( bits, 32, bits_pos ) ) /* SYNCXLL (32) */ + return LSMASH_ERR_INVALID_DATA; + dts_bits_get( bits, 4, bits_pos ); /* nVersion (4) */ + uint64_t nHeaderSize = dts_bits_get( bits, 8, bits_pos ) + 1; /* nHeaderSize (8) */ + int nBits4FrameFsize = dts_bits_get( bits, 5, bits_pos ) + 1; /* nBits4FrameFsize (5) */ + dts_bits_get( bits, nBits4FrameFsize, bits_pos ); /* nLLFrameSize (nBits4FrameFsize) */ + int nNumChSetsInFrame = dts_bits_get( bits, 4, bits_pos ) + 1; /* nNumChSetsInFrame (4) */ + uint16_t nSegmentsInFrame = 1 << dts_bits_get( bits, 4, bits_pos ); /* nSegmentsInFrame (4) */ + uint16_t nSmplInSeg = 1 << dts_bits_get( bits, 4, bits_pos ); /* nSmplInSeg (4) */ + int nBits4SSize = dts_bits_get( bits, 5, bits_pos ) + 1; /* nBits4SSize (5) */ + dts_bits_get( bits, 3, bits_pos ); /* nBandDataCRCEn (2) + * bScalableLSBs (1) */ + int nBits4ChMask = dts_bits_get( bits, 5, bits_pos ) + 1; /* nBits4ChMask (5) */ + dts_bits_get( bits, nHeaderSize * 8 - (*bits_pos - xll_pos), bits_pos ); /* Skip the remaining bits in Common Header. */ + int sum_nChSetLLChannel = 0; + uint32_t nFs1 = 0; + int number_of_frequency_bands = 0; /* the number of frequency bands is determined simply by the underlying maximum sampling + * frequency among all of the channel sets. + * For sampling frequency Fs, + * Number of frequency bands is 1 for Fs <= Base_Fs + * Number of frequency bands is 2 for Base_Fs < Fs <= 2 * Base_Fs + * Number of frequency bands is 2 for 2 * Base_Fs < Fs <= 4 * Base_Fs + * where Base_Fs denotes the base sampling frequency i.e. 64 kHz, 88.2 kHz, or 96 kHz. */ + int nNumFreqBands1 = 0; + int nNumFreqBands[16] = { 0 }; + xll->channel_layout = 0; + for( int nChSet = 0; nChSet < nNumChSetsInFrame; nChSet++ ) + { + /* Channel Set Sub-Header */ + xll_pos = *bits_pos; + uint64_t nChSetHeaderSize = dts_bits_get( bits, 10, bits_pos ) + 1; /* nChSetHeaderSize (10) */ + int nChSetLLChannel = dts_bits_get( bits, 4, bits_pos ) + 1; /* nChSetLLChannel (4) */ + dts_bits_get( bits, nChSetLLChannel, bits_pos ); /* nResidualChEncode (nChSetLLChannel) */ + uint8_t nBitResolution = dts_bits_get( bits, 5, bits_pos ) + 1; /* nBitResolution (5) */ + dts_bits_get( bits, 5, bits_pos ); /* nBitWidth (5) */ + xll->pcm_resolution = LSMASH_MAX( xll->pcm_resolution, nBitResolution ); + static const uint32_t source_sample_rate_table[16] = + { + 8000, 16000, 32000, 64000, 128000, + 22050, 44100, 88200, 176400, 352800, + 12000, 24000, 48000, 96000, 192000, 384000 + }; + int sFreqIndex = dts_bits_get( bits, 4, bits_pos ); /* sFreqIndex (4) */ + uint32_t nFs = source_sample_rate_table[sFreqIndex]; + dts_bits_get( bits, 2, bits_pos ); /* nFsInterpolate (2) */ + int nReplacementSet = dts_bits_get( bits, 2, bits_pos ); /* nReplacementSet (2) */ + if( nReplacementSet > 0 ) + dts_bits_get( bits, 1, bits_pos ); /* bActiveReplaceSet (1) */ + if( asset->bOne2OneMapChannels2Speakers ) + { + /* Downmix is allowed only when the encoded channel represents a signal feed to a corresponding loudspeaker. */ + int bPrimaryChSet = dts_bits_get( bits, 1, bits_pos ); /* bPrimaryChSet (1) */ + int bDownmixCoeffCodeEmbedded = dts_bits_get( bits, 1, bits_pos ); /* bDownmixCoeffCodeEmbedded (1) */ + int nLLDownmixType = 0x7; /* 0b111: Unused */ + if( bDownmixCoeffCodeEmbedded ) + { + dts_bits_get( bits, 1, bits_pos ); /* bDownmixEmbedded (1) */ + if( bPrimaryChSet ) + nLLDownmixType = dts_bits_get( bits, 3, bits_pos ); /* nLLDownmixType (3) */ + } + int bHierChSet = dts_bits_get( bits, 1, bits_pos ); /* bHierChSet (1) */ + if( bDownmixCoeffCodeEmbedded ) + { + /* N: the number of channels in the current channel set + * for non-primary channel set, adding +1 for the down scaling coefficients that prevent overflow + * M: the number of channels that the current channel set is mixed into + * Downmix coefficients are transmitted using 9-bit codes. */ + static const int downmix_channel_count_table[8] = { 1, 2, 2, 3, 3, 4, 4, 0 }; + int N = nChSetLLChannel + (bPrimaryChSet ? 0 : 1); + int M = bPrimaryChSet ? downmix_channel_count_table[nLLDownmixType] : sum_nChSetLLChannel; + int nDownmixCoeffs = N * M; + dts_bits_get( bits, nDownmixCoeffs * 9, bits_pos ); /* DownmixCoeffs (nDownmixCoeffs * 9) */ + if( bPrimaryChSet && downmix_channel_count_table[nLLDownmixType] == 2 ) + xll->stereo_downmix |= 1; + } + if( bHierChSet ) + sum_nChSetLLChannel += nChSetLLChannel; + if( dts_bits_get( bits, 1, bits_pos ) ) /* bChMaskEnabled (1) */ + { + uint32_t nChMask = dts_bits_get( bits, nBits4ChMask, bits_pos ); /* nChMask (nBits4ChMask) */ + xll->channel_layout |= dts_get_channel_layout_from_ls_mask32( nChMask ); + xll->lower_planes |= dts_get_lower_channels_from_ls_mask32( nChMask ); + } + else + dts_bits_get( bits, 25 * nChSetLLChannel, bits_pos ); /* RadiusDelta[ch] (9) + * Theta[ch] (9) + * Phi[ch] (7) + * per channel */ + } + else + { + /* No downmixing is allowed and each channel set is the primary channel set. */ + if( dts_bits_get( bits, 1, bits_pos ) ) /* bMappingCoeffsPresent (1) */ + { + int nBitsCh2SpkrCoef = 6 + 2 * dts_bits_get( bits, 3, bits_pos ); /* nBitsCh2SpkrCoef (3) */ + int nNumSpeakerConfigs = dts_bits_get( bits, 2, bits_pos ) + 1; /* nNumSpeakerConfigs (2) */ + for( int nSpkrConf = 0; nSpkrConf < nNumSpeakerConfigs; nSpkrConf++ ) + { + int pnActiveChannelMask = dts_bits_get( bits, nChSetLLChannel, bits_pos ); /* pnActiveChannelMask[nSpkrConf] (nChSetLLChannel) */ + int pnNumSpeakers = dts_bits_get( bits, 6, bits_pos ) + 1; /* pnNumSpeakers[nSpkrConf] (6) */ + int bSpkrMaskEnabled = dts_bits_get( bits, 1, bits_pos ); /* bSpkrMaskEnabled (1) */ + if( bSpkrMaskEnabled ) + { + uint32_t nSpkrMask = dts_bits_get( bits, nBits4ChMask, bits_pos ); /* nSpkrMask[nSpkrConf] (nBits4ChMask) */ + xll->channel_layout |= dts_get_channel_layout_from_ls_mask32( nSpkrMask ); + xll->lower_planes |= dts_get_lower_channels_from_ls_mask32( nSpkrMask ); + } + for( int nSpkr = 0; nSpkr < pnNumSpeakers; nSpkr++ ) + { + if( !bSpkrMaskEnabled ) + dts_bits_get( bits, 25, bits_pos ); /* ChSetSpeakerConfiguration (25) */ + for( int nCh = 0; nCh < nChSetLLChannel; nCh++ ) + if( pnActiveChannelMask & (1 << nCh) ) + dts_bits_get( bits, nBitsCh2SpkrCoef, bits_pos ); /* pnCh2SpkrMapCoeff (nBitsCh2SpkrCoef) */ + } + } + } + } + int full_bandwidth; + if( nFs > 96000 ) + { + /* When bXtraFreqBands is equal to 0, only one-half of the original bandwidth is preserved and, thus, the number + * of frequency bands is also one-half of the number in the case where full bandwidth is preserved. Apparently, + * nSmplInSeg is the number of samples in a segment per one frequency band when full bandwidth is preserved. + * Because of this, to get the correct number of samples per frame, multiply the result by 2 when bXtraFreqBands + * is equal to 0. */ + full_bandwidth = dts_bits_get( bits, 1, bits_pos ); /* bXtraFreqBands (1) */ + nNumFreqBands[nChSet] = (1 + full_bandwidth) << (nFs > 192000); + } + else + { + full_bandwidth = 1; + nNumFreqBands[nChSet] = 1; + } + uint32_t nSmplInSeg_nChSet; + if( nChSet == 0 ) + { + nFs1 = nFs; + nNumFreqBands1 = nNumFreqBands[nChSet]; + nSmplInSeg_nChSet = nSmplInSeg; + } + else + nSmplInSeg_nChSet = (nSmplInSeg * (nFs * nNumFreqBands1)) / (nFs1 * nNumFreqBands[nChSet]); + if( xll->sampling_frequency < nFs ) + { + xll->sampling_frequency = nFs; + uint32_t samples_per_band_in_frame = nSegmentsInFrame * nSmplInSeg_nChSet; + xll->frame_duration = samples_per_band_in_frame * nNumFreqBands[nChSet] * (2 - full_bandwidth); + } + if( number_of_frequency_bands < nNumFreqBands[nChSet] ) + number_of_frequency_bands = nNumFreqBands[nChSet]; + dts_bits_get( bits, nChSetHeaderSize * 8 - (*bits_pos - xll_pos), bits_pos ); /* Skip the remaining bits in Channel Set Sub-Header. */ + } + /* NAVI */ + uint64_t FreqBandDataSize = 0; + for( int Band = 0; Band < number_of_frequency_bands; Band++ ) + for( int Seg = 0; Seg < nSegmentsInFrame; Seg++ ) + { + /* The spec pseudocode extracts bits and initialize SegmentSize[Band][Seg] here. This may be one of lies in the spec. + * According to 8.3.2 Stream Navigation in ETSI TS 102 114 V1.4.1, sum of all band data for all channel set in a segments is + * the size of that segment. In addition there are no headers associated with segment and channel set of abstraction layer. + * Obviously, the extraction is meaningless and the navigation should works without it. */ + // SegmentSize[Band][Seg] = dts_bits_get( bits, nBits4SSize, bits_pos ); + for( int nChSet = 0; nChSet < nNumChSetsInFrame; nChSet++ ) + if( nNumFreqBands[nChSet] > Band ) + FreqBandDataSize += dts_bits_get( bits, nBits4SSize, bits_pos ) + 1; /* BandChSetSize[Band][Seg][nChSet] (nBits4SSize) */ + } + dts_bits_align( bits, bits_pos ); + dts_bits_get( bits, 16, bits_pos ); /* Checksum (16) */ + /* Skip band data. */ + dts_bits_get( bits, FreqBandDataSize * 8, bits_pos ); + dts_bits_align4( bits, bits_pos ); + if( lsmash_bs_show_be32( bits->bs, 0 ) == DTS_SYNCWORD_X ) + xll->dtsx_extension_present = 1; + info->flags |= DTS_EXT_SUBSTREAM_XLL_FLAG; + return bits->bs->error ? LSMASH_ERR_NAMELESS : 0; +} + +static uint16_t dts_generate_channel_layout_from_core( int channel_arrangement ) +{ + static const uint16_t channel_layout_map_table[] = + { + DTS_CHANNEL_LAYOUT_C, + DTS_CHANNEL_LAYOUT_L_R, /* dual mono */ + DTS_CHANNEL_LAYOUT_L_R, /* stereo */ + DTS_CHANNEL_LAYOUT_L_R, /* sum-difference */ + DTS_CHANNEL_LAYOUT_L_R, /* Lt/Rt */ + DTS_CHANNEL_LAYOUT_C | DTS_CHANNEL_LAYOUT_L_R, + DTS_CHANNEL_LAYOUT_L_R | DTS_CHANNEL_LAYOUT_CS, + DTS_CHANNEL_LAYOUT_C | DTS_CHANNEL_LAYOUT_L_R | DTS_CHANNEL_LAYOUT_CS, + DTS_CHANNEL_LAYOUT_L_R | DTS_CHANNEL_LAYOUT_LS_RS, + DTS_CHANNEL_LAYOUT_C | DTS_CHANNEL_LAYOUT_L_R | DTS_CHANNEL_LAYOUT_LS_RS, + DTS_CHANNEL_LAYOUT_LC_RC | DTS_CHANNEL_LAYOUT_L_R | DTS_CHANNEL_LAYOUT_LS_RS, + DTS_CHANNEL_LAYOUT_C | DTS_CHANNEL_LAYOUT_L_R | DTS_CHANNEL_LAYOUT_LSR_RSR | DTS_CHANNEL_LAYOUT_OH, + DTS_CHANNEL_LAYOUT_C | DTS_CHANNEL_LAYOUT_CS | DTS_CHANNEL_LAYOUT_L_R | DTS_CHANNEL_LAYOUT_LSR_RSR, + DTS_CHANNEL_LAYOUT_C | DTS_CHANNEL_LAYOUT_L_R | DTS_CHANNEL_LAYOUT_LC_RC | DTS_CHANNEL_LAYOUT_LS_RS, + DTS_CHANNEL_LAYOUT_L_R | DTS_CHANNEL_LAYOUT_LC_RC | DTS_CHANNEL_LAYOUT_LS_RS | DTS_CHANNEL_LAYOUT_LSR_RSR, + DTS_CHANNEL_LAYOUT_C | DTS_CHANNEL_LAYOUT_CS | DTS_CHANNEL_LAYOUT_L_R | DTS_CHANNEL_LAYOUT_LC_RC | DTS_CHANNEL_LAYOUT_LS_RS + }; + return channel_arrangement < 16 ? channel_layout_map_table[channel_arrangement] : 0; +} + +static int dts_parse_core( dts_info_t *info, uint64_t *bits_pos, dts_core_info_t *core ) +{ + lsmash_bits_t *bits = info->bits; + memset( core, 0, sizeof(dts_core_info_t) ); + /* SYNC (32) */ + int frame_type = dts_bits_get( bits, 1, bits_pos ); /* FTYPE (1) */ + int deficit_sample_count = dts_bits_get( bits, 5, bits_pos ); /* SHORT (5) */ + if( frame_type == 1 && deficit_sample_count != 31 ) + return LSMASH_ERR_INVALID_DATA; /* Any normal frame (FTYPE == 1) must have SHORT == 31. */ + int crc_present_flag = dts_bits_get( bits, 1, bits_pos ); /* CPF (1) */ + int num_of_pcm_sample_blocks = dts_bits_get( bits, 7, bits_pos ) + 1; /* NBLKS (7) */ + if( num_of_pcm_sample_blocks <= 5 ) + return LSMASH_ERR_INVALID_DATA; + core->frame_duration = 32 * num_of_pcm_sample_blocks; + if( frame_type == 1 + && core->frame_duration != 256 + && core->frame_duration != 512 && core->frame_duration != 1024 + && core->frame_duration != 2048 && core->frame_duration != 4096 ) + return LSMASH_ERR_INVALID_DATA; /* For any normal frame, the actual number of PCM core samples per channel must be + * either 4096, 2048, 1024, 512, or 256 samples per channel. */ + core->frame_size = dts_bits_get( bits, 14, bits_pos ) + 1; /* FSIZE (14) */ + if( core->frame_size < DTS_MIN_CORE_SIZE ) + return LSMASH_ERR_INVALID_DATA; + core->channel_arrangement = dts_bits_get( bits, 6, bits_pos ); /* AMODE (6) */ + core->channel_layout = dts_generate_channel_layout_from_core( core->channel_arrangement ); + int core_audio_sampling_frequency = dts_bits_get( bits, 4, bits_pos ); /* SFREQ (4) */ + static const uint32_t sampling_frequency_table[16] = + { + 0, + 8000, 16000, 32000, 0, 0, + 11025, 22050, 44100, 0, 0, + 12000, 24000, 48000, 0, 0 + }; + core->sampling_frequency = sampling_frequency_table[core_audio_sampling_frequency]; + if( core->sampling_frequency == 0 ) + return LSMASH_ERR_INVALID_DATA; /* invalid */ + dts_bits_get( bits, 10, bits_pos ); /* Skip remainder 10 bits. + * RATE (5) + * MIX (1) + * DYNF (1) + * TIMEF (1) + * AUXF (1) + * HDCD (1) */ + core->extension_audio_descriptor = dts_bits_get( bits, 3, bits_pos ); /* EXT_AUDIO_ID (3) + * Note: EXT_AUDIO_ID == 3 is defined in V1.2.1. + * However, its definition disappears and is reserved in V1.3.1. */ + int extended_coding_flag = dts_bits_get( bits, 1, bits_pos ); /* EXT_AUDIO (1) */ + dts_bits_get( bits, 1, bits_pos ); /* ASPF (1) */ + int low_frequency_effects_flag = dts_bits_get( bits, 2, bits_pos ); /* LFF (2) */ + if( low_frequency_effects_flag == 0x3 ) + return LSMASH_ERR_INVALID_DATA; /* invalid */ + if( low_frequency_effects_flag ) + core->channel_layout |= DTS_CHANNEL_LAYOUT_LFE1; + dts_bits_get( bits, 8 + crc_present_flag * 16, bits_pos ); /* HFLAG (1) + * HCRC (16) + * FILTS (1) + * VERNUM (4) + * CHIST (2) */ + int PCMR = dts_bits_get( bits, 3, bits_pos ); /* PCMR (3) */ + static const uint8_t source_resolution_table[8] = { 16, 16, 20, 20, 0, 24, 24, 0 }; + core->pcm_resolution = source_resolution_table[PCMR]; + if( core->pcm_resolution == 0 ) + return LSMASH_ERR_INVALID_DATA; /* invalid */ + dts_bits_get( bits, 6, bits_pos ); /* SUMF (1) + * SUMS (1) + * DIALNORM/UNSPEC (4) */ + if( extended_coding_flag ) + { + uint32_t syncword = dts_bits_get( bits, 24, bits_pos ); + uint64_t frame_size_bits = core->frame_size * 8; + while( (*bits_pos + 24) < frame_size_bits ) + { + int err; + syncword = ((syncword << 8) & 0xffffff00) | dts_bits_get( bits, 8, bits_pos ); + switch( syncword ) + { + case DTS_SYNCWORD_XXCH : + if( (err = dts_parse_core_xxch( info, bits_pos, core )) < 0 ) + return err; + syncword = dts_bits_get( bits, 24, bits_pos ); + break; + case DTS_SYNCWORD_X96K : + if( (err = dts_parse_core_x96( info, bits_pos, core )) < 0 ) + return err; + syncword = dts_bits_get( bits, 24, bits_pos ); + break; + case DTS_SYNCWORD_XCH : + if( (err = dts_parse_core_xch( info, bits_pos, core )) < 0 ) + return err; + break; + default : + continue; + } + } + } + return bits->bs->error ? LSMASH_ERR_NAMELESS : 0; +} + +static int dts_parse_exss_core( dts_info_t *info, uint64_t *bits_pos, dts_audio_asset_t *asset ) +{ + lsmash_bits_t *bits = info->bits; + if( DTS_SYNCWORD_SUBSTREAM_CORE != dts_bits_get( bits, 32, bits_pos ) ) + return LSMASH_ERR_INVALID_DATA; + int err = dts_parse_core( info, bits_pos, &asset->core ); + if( err < 0 ) + return err; + info->flags |= DTS_EXT_SUBSTREAM_CORE_FLAG; + return bits->bs->error ? LSMASH_ERR_NAMELESS : 0; +} + +int dts_parse_core_substream( dts_info_t *info ) +{ + lsmash_bits_t *bits = info->bits; + uint64_t bits_pos = 0; + int err; + if( DTS_SYNCWORD_CORE != dts_bits_get( bits, 32, &bits_pos ) ) + { + err = LSMASH_ERR_INVALID_DATA; + goto parse_fail; + } + /* By default the core substream data, if present, has the nuBcCoreExtSSIndex = 0 and the nuBcCoreAssetIndex = 0. */ + dts_extension_info_t *exss = &info->exss[0]; + if( (err = dts_parse_core( info, &bits_pos, &exss->asset[0].core )) < 0 ) + goto parse_fail; + exss->bBcCorePresent [0] = 1; + exss->nuBcCoreExtSSIndex[0] = 0; + exss->nuBcCoreAssetIndex[0] = 0; + info->flags |= DTS_CORE_SUBSTREAM_CORE_FLAG; + info->exss_count = 0; + info->core = exss->asset[0].core; + info->frame_size = exss->asset[0].core.frame_size; + lsmash_bits_get_align( bits ); + return 0; +parse_fail: + lsmash_bits_get_align( bits ); + return err; +} + +int dts_parse_extension_substream( dts_info_t *info ) +{ + lsmash_bits_t *bits = info->bits; + uint64_t bits_pos = 0; + dts_bits_get( bits, 40, &bits_pos ); /* SYNCEXTSSH (32) + * UserDefinedBits (8) */ + int nExtSSIndex = dts_bits_get( bits, 2, &bits_pos ); /* nExtSSIndex (2) */ + info->exss_index = nExtSSIndex; + dts_extension_info_t *exss = &info->exss[nExtSSIndex]; + memset( exss, 0, sizeof(dts_extension_info_t) ); + int bHeaderSizeType = dts_bits_get( bits, 1, &bits_pos ); /* bHeaderSizeType (1) */ + int nuBits4Header = 8 + bHeaderSizeType * 4; + int nuBits4ExSSFsize = 16 + bHeaderSizeType * 4; + exss->nuBits4ExSSFsize = nuBits4ExSSFsize; + uint32_t nuExtSSHeaderSize = dts_bits_get( bits, nuBits4Header, &bits_pos ) + 1; /* nuExtSSHeaderSize (8 or 12) */ + info->frame_size = dts_bits_get( bits, nuBits4ExSSFsize, &bits_pos ) + 1; /* nuExtSSFsize (16 or 20) */ + if( info->frame_size < 10 ) + return LSMASH_ERR_INVALID_DATA; + exss->bStaticFieldsPresent = dts_bits_get( bits, 1, &bits_pos ); /* bStaticFieldsPresent (1) */ + if( exss->bStaticFieldsPresent ) + { + dts_bits_get( bits, 2, &bits_pos ); /* nuRefClockCode (2) */ + exss->frame_duration = 512 * (dts_bits_get( bits, 3, &bits_pos ) + 1); /* nuExSSFrameDurationCode (3) */ + if( dts_bits_get( bits, 1, &bits_pos ) ) /* bTimeStampFlag (1) */ + dts_bits_get( bits, 36, &bits_pos ); /* nuTimeStamp (32) + * nLSB (4) */ + exss->nuNumAudioPresnt = dts_bits_get( bits, 3, &bits_pos ) + 1; /* nuNumAudioPresnt (3) */ + exss->nuNumAssets = dts_bits_get( bits, 3, &bits_pos ) + 1; /* nuNumAssets (3) */ + /* The extension substreams with indexes lower than or equal to the index of the current extension substream can + * be activated in the audio presentations indicated within the current extension substream. */ + for( uint8_t nAuPr = 0; nAuPr < exss->nuNumAudioPresnt; nAuPr++ ) + exss->nuActiveExSSMask[nAuPr] + = dts_bits_get( bits, nExtSSIndex + 1, &bits_pos ); /* nuActiveExSSMask[nAuPr] (nExtSSIndex + 1) */ + for( uint8_t nAuPr = 0; nAuPr < exss->nuNumAudioPresnt; nAuPr++ ) + for( uint8_t nSS = 0; nSS <= nExtSSIndex; nSS++ ) + exss->nuActiveAssetMask[nAuPr][nSS] + = ((exss->nuActiveExSSMask[nAuPr] >> nSS) & 0x1) + ? dts_bits_get( bits, 8, &bits_pos ) /* nuActiveAssetMask[nAuPr][nSS] (8) */ + : 0; + exss->bMixMetadataEnbl = dts_bits_get( bits, 1, &bits_pos ); /* bMixMetadataEnbl (1) */ + if( exss->bMixMetadataEnbl ) + { + dts_bits_get( bits, 2, &bits_pos ); /* nuMixMetadataAdjLevel (2) */ + int nuBits4MixOutMask = (dts_bits_get( bits, 2, &bits_pos ) + 1) << 2; /* nuBits4MixOutMask (2) */ + exss->nuNumMixOutConfigs = dts_bits_get( bits, 2, &bits_pos ) + 1; /* nuNumMixOutConfigs (2) */ + for( int ns = 0; ns < exss->nuNumMixOutConfigs; ns++ ) + { + int nuMixOutChMask = dts_bits_get( bits, nuBits4MixOutMask, &bits_pos ); /* nuMixOutChMask[ns] (nuBits4MixOutMask) */ + exss->nNumMixOutCh[ns] = dts_get_channel_count_from_channel_layout( nuMixOutChMask ); + } + } + } + else + { + exss->nuNumAudioPresnt = 1; + exss->nuNumAssets = 1; + exss->bMixMetadataEnbl = 0; + exss->nuNumMixOutConfigs = 0; + } + for( uint8_t nAst = 0; nAst < exss->nuNumAssets; nAst++ ) + exss->asset[nAst].size = dts_bits_get( bits, nuBits4ExSSFsize, &bits_pos ) + 1; /* nuAssetFsize[nAst] - 1 (nuBits4ExSSFsize) */ + int err; + for( uint8_t nAst = 0; nAst < exss->nuNumAssets; nAst++ ) + if( (err = dts_parse_asset_descriptor( info, &bits_pos )) < 0 ) + goto parse_fail; + for( uint8_t nAuPr = 0; nAuPr < exss->nuNumAudioPresnt; nAuPr++ ) + exss->bBcCorePresent[nAuPr] = dts_bits_get( bits, 1, &bits_pos ); + for( uint8_t nAuPr = 0; nAuPr < exss->nuNumAudioPresnt; nAuPr++ ) + if( exss->bBcCorePresent[nAuPr] ) + { + exss->nuBcCoreExtSSIndex[nAuPr] = dts_bits_get( bits, 2, &bits_pos ); + exss->nuBcCoreAssetIndex[nAuPr] = dts_bits_get( bits, 3, &bits_pos ); + } + dts_bits_get( bits, nuExtSSHeaderSize * 8 - bits_pos, &bits_pos ); + for( uint8_t nAst = 0; nAst < exss->nuNumAssets; nAst++ ) + { + /* Asset Data */ + dts_audio_asset_t *asset = &exss->asset[nAst]; + uint32_t asset_pos = bits_pos; + switch( asset->nuCodingMode ) + { + case 0 : /* DTS-HD Coding Mode that may contain multiple coding components */ + { + if( asset->nuCoreExtensionMask & DTS_EXT_SUBSTREAM_CORE_FLAG ) + { + /* Core component */ + uint64_t core_pos = bits_pos; + if( (err = dts_parse_exss_core( info, &bits_pos, asset )) < 0 ) + goto parse_fail; + dts_bits_get( bits, asset->core.frame_size * 8 - (bits_pos - core_pos), &bits_pos ); + } + if( asset->nuCoreExtensionMask & DTS_EXT_SUBSTREAM_XBR_FLAG ) + { + /* XBR extension */ + uint64_t xbr_pos = bits_pos; + if( (err = dts_parse_exss_xbr( info, &bits_pos )) < 0 ) + goto parse_fail; + dts_bits_get( bits, asset->xbr_size * 8 - (bits_pos - xbr_pos), &bits_pos ); + } + if( asset->nuCoreExtensionMask & DTS_EXT_SUBSTREAM_XXCH_FLAG ) + { + /* XXCH extension */ + uint64_t xxch_pos = bits_pos; + if( (err = dts_parse_exss_xxch( info, &bits_pos, &asset->core )) < 0 ) + goto parse_fail; + dts_bits_get( bits, asset->core.xxch.size * 8 - (bits_pos - xxch_pos), &bits_pos ); + } + if( asset->nuCoreExtensionMask & DTS_EXT_SUBSTREAM_X96_FLAG ) + { + /* X96 extension */ + uint64_t x96_pos = bits_pos; + if( (err = dts_parse_exss_x96( info, &bits_pos, &asset->core )) < 0 ) + goto parse_fail; + dts_bits_get( bits, asset->x96_size * 8 - (bits_pos - x96_pos), &bits_pos ); + } + if( asset->nuCoreExtensionMask & DTS_EXT_SUBSTREAM_LBR_FLAG ) + { + /* LBR component */ + uint64_t lbr_pos = bits_pos; + if( (err = dts_parse_exss_lbr( info, &bits_pos, asset )) < 0 ) + goto parse_fail; + dts_bits_get( bits, asset->lbr.size * 8 - (bits_pos - lbr_pos), &bits_pos ); + } + if( asset->nuCoreExtensionMask & DTS_EXT_SUBSTREAM_XLL_FLAG ) + { + /* Lossless extension */ + uint64_t xll_pos = bits_pos; + if( (err = dts_parse_exss_xll( info, &bits_pos, asset )) < 0 ) + goto parse_fail; + dts_bits_get( bits, asset->xll.size * 8 - (bits_pos - xll_pos), &bits_pos ); + } + break; + } + case 1 : /* DTS-HD Loss-less coding mode without CBR component */ + if( (err = dts_parse_exss_xll( info, &bits_pos, asset )) < 0 ) + goto parse_fail; + break; + case 2 : /* DTS-HD Low bit-rate mode */ + if( (err = dts_parse_exss_lbr( info, &bits_pos, asset )) < 0 ) + goto parse_fail; + break; + case 3 : /* Auxiliary coding mode */ + dts_bits_get( bits, asset->aux_size * 8, &bits_pos ); + break; + } + dts_bits_get( bits, asset->size * 8 - (bits_pos - asset_pos), &bits_pos ); + } + dts_bits_get( bits, info->frame_size * 8 - bits_pos, &bits_pos ); + lsmash_bits_get_align( bits ); + if( info->exss_count < DTS_MAX_NUM_EXSS ) + info->exss_count += 1; + return 0; +parse_fail: + lsmash_bits_get_align( bits ); + return err; +} + +dts_substream_type dts_get_substream_type( dts_info_t *info ) +{ + if( lsmash_bs_get_remaining_buffer_size( info->bits->bs ) < 4 ) + return DTS_SUBSTREAM_TYPE_NONE; + uint8_t *buffer = lsmash_bs_get_buffer_data( info->bits->bs ); + uint32_t syncword = LSMASH_4CC( buffer[0], buffer[1], buffer[2], buffer[3] ); + switch( syncword ) + { + case DTS_SYNCWORD_CORE : + return DTS_SUBSTREAM_TYPE_CORE; + case DTS_SYNCWORD_SUBSTREAM : + return DTS_SUBSTREAM_TYPE_EXTENSION; + default : + return DTS_SUBSTREAM_TYPE_NONE; + } +} + +int dts_get_exss_index( dts_info_t *info, uint8_t *exss_index ) +{ + if( lsmash_bs_get_remaining_buffer_size( info->bits->bs ) < 6 ) + return LSMASH_ERR_INVALID_DATA; + *exss_index = lsmash_bs_show_byte( info->bits->bs, 5 ) >> 6; + return 0; +} + +int dts_get_max_channel_count( dts_info_t *info ) +{ + int max_channel_count = 0; + for( int nExtSSIndex = 0; nExtSSIndex < DTS_MAX_NUM_EXSS; nExtSSIndex++ ) + { + dts_extension_info_t *exss = &info->exss[nExtSSIndex]; + for( uint8_t nAuPr = 0; nAuPr < exss->nuNumAudioPresnt; nAuPr++ ) + { + /* Get the channel layout of an audio presentation from a core component. */ + uint16_t channel_layout = 0; + int channel_count = 0; + if( exss->bBcCorePresent [nAuPr] + && exss->nuBcCoreAssetIndex[nAuPr] < exss->nuNumAssets ) + { + dts_core_info_t *core = &info->exss[ exss->nuBcCoreExtSSIndex[nAuPr] ].asset[ exss->nuBcCoreAssetIndex[nAuPr] ].core; + if( core->xxch.channel_layout | core->xxch.lower_planes ) + { + channel_layout = core->xxch.channel_layout; + channel_count = lsmash_count_bits( core->xxch.lower_planes ); /* FIXME: Should we count these channels? */ + } + else + channel_layout = core->channel_layout; + } + channel_count += dts_get_channel_count_from_channel_layout( channel_layout ); + max_channel_count = LSMASH_MAX( max_channel_count, channel_count ); + /* Get the channel layouts of an audio presentation from extension substreams. */ + uint16_t ext_channel_layout = 0; + uint16_t lbr_channel_layout = 0; + uint16_t xll_channel_layout = 0; + uint8_t xll_lower_channels = 0; + for( int nSS = 0; nSS <= nExtSSIndex; nSS++ ) + if( (exss->nuActiveExSSMask[nAuPr] >> nSS) & 0x1 ) + for( uint8_t nAst = 0; nAst < exss->nuNumAssets; nAst++ ) + if( (exss->nuActiveAssetMask[nAuPr][nSS] >> nAst) & 0x1 ) + { + dts_audio_asset_t *asset = &exss->asset[nAst]; + ext_channel_layout |= asset->channel_layout; + lbr_channel_layout |= asset->lbr.channel_layout; + xll_channel_layout |= asset->xll.channel_layout; + xll_lower_channels |= asset->xll.lower_planes; + } + /* Audio asset descriptors */ + channel_count = dts_get_channel_count_from_channel_layout( ext_channel_layout ); + max_channel_count = LSMASH_MAX( max_channel_count, channel_count ); + /* LBR components */ + channel_count = dts_get_channel_count_from_channel_layout( lbr_channel_layout ); + max_channel_count = LSMASH_MAX( max_channel_count, channel_count ); + /* Lossless extensions */ + channel_count = dts_get_channel_count_from_channel_layout( xll_channel_layout ) + + lsmash_count_bits( xll_lower_channels ); + max_channel_count = LSMASH_MAX( max_channel_count, channel_count ); + } + } + return max_channel_count; +} + +void dts_update_specific_param( dts_info_t *info ) +{ + lsmash_dts_specific_parameters_t *param = &info->ddts_param; + /* Find the first valid substream. + * Both nuNumAudioPresnt and nuNumAssets of any substream must not be 0. Therefore, at least one of these are 0, + * then the substream is invalid or absent. */ + int exss_index_start = 0; + for( int nExtSSIndex = 0; nExtSSIndex < DTS_MAX_NUM_EXSS; nExtSSIndex++ ) + { + dts_extension_info_t *exss = &info->exss[nExtSSIndex]; + if( exss->nuNumAudioPresnt && exss->nuNumAssets ) + { + exss_index_start = nExtSSIndex; + break; + } + } + /* DTSSamplingFrequency and FrameDuration */ + for( int nExtSSIndex = exss_index_start; nExtSSIndex < DTS_MAX_NUM_EXSS; nExtSSIndex++ ) + { + dts_extension_info_t *exss = &info->exss[nExtSSIndex]; + if( exss->nuNumAudioPresnt == 0 || exss->nuNumAssets == 0 ) + continue; + if( param->DTSSamplingFrequency <= exss->sampling_frequency ) + { + param->DTSSamplingFrequency = exss->sampling_frequency; + info->frame_duration = exss->frame_duration; + } + for( uint8_t nAst = 0; nAst < exss->nuNumAssets; nAst++ ) + { + dts_audio_asset_t *asset = &exss->asset[nAst]; + if( param->DTSSamplingFrequency <= asset->core.sampling_frequency ) + { + param->DTSSamplingFrequency = asset->core.sampling_frequency; + info->frame_duration = asset->core.frame_duration; + } + if( param->DTSSamplingFrequency <= asset->lbr.sampling_frequency ) + { + param->DTSSamplingFrequency = asset->lbr.sampling_frequency; + info->frame_duration = asset->lbr.frame_duration; + } + if( param->DTSSamplingFrequency <= asset->xll.sampling_frequency ) + { + param->DTSSamplingFrequency = asset->xll.sampling_frequency; + info->frame_duration = asset->xll.frame_duration; + } + } + } + param->FrameDuration = 0; + for( uint32_t frame_duration = info->frame_duration >> 10; frame_duration; frame_duration >>= 1 ) + ++ param->FrameDuration; + /* pcmSampleDepth */ + param->pcmSampleDepth = 0; + for( int nExtSSIndex = exss_index_start; nExtSSIndex < DTS_MAX_NUM_EXSS; nExtSSIndex++ ) + { + dts_extension_info_t *exss = &info->exss[nExtSSIndex]; + if( exss->nuNumAudioPresnt == 0 || exss->nuNumAssets == 0 ) + continue; + param->pcmSampleDepth = LSMASH_MAX( param->pcmSampleDepth, exss->bit_resolution ); + for( uint8_t nAst = 0; nAst < exss->nuNumAssets; nAst++ ) + { + dts_audio_asset_t *asset = &exss->asset[nAst]; + param->pcmSampleDepth = LSMASH_MAX( param->pcmSampleDepth, asset->core.pcm_resolution ); + param->pcmSampleDepth = LSMASH_MAX( param->pcmSampleDepth, asset->lbr.sample_size ); + param->pcmSampleDepth = LSMASH_MAX( param->pcmSampleDepth, asset->xll.pcm_resolution ); + } + } + param->pcmSampleDepth = param->pcmSampleDepth > 16 ? 24 : 16; + /* StreamConstruction */ + param->StreamConstruction = lsmash_dts_get_stream_construction( info->flags ); + /* CoreLFEPresent */ + param->CoreLFEPresent = !!(info->core.channel_layout & DTS_CHANNEL_LAYOUT_LFE1); + /* CoreLayout */ + if( param->StreamConstruction == 0 /* Unknown */ + || param->StreamConstruction >= 17 /* No core substream */ ) + /* Use ChannelLayout. */ + param->CoreLayout = 31; + else + { + if( info->core.channel_arrangement != 1 + && info->core.channel_arrangement != 3 + && info->core.channel_arrangement <= 9 ) + param->CoreLayout = info->core.channel_arrangement; + else + /* Use ChannelLayout. */ + param->CoreLayout = 31; + } + /* CoreSize + * The specification says this field is the size of a core substream AU in bytes. + * If we don't assume CoreSize is the copy of FSIZE, when FSIZE equals 0x3FFF, this field overflows and becomes 0. */ + param->CoreSize = info->core.frame_size ? LSMASH_MIN( info->core.frame_size - 1, 0x3FFF ) : 0; + /* StereoDownmix */ + param->StereoDownmix = 0; + for( int nExtSSIndex = exss_index_start; nExtSSIndex < DTS_MAX_NUM_EXSS; nExtSSIndex++ ) + { + dts_extension_info_t *exss = &info->exss[nExtSSIndex]; + param->StereoDownmix |= exss->stereo_downmix; + for( uint8_t nAst = 0; nAst < exss->nuNumAssets; nAst++ ) + { + param->StereoDownmix |= exss->asset[nAst].lbr.stereo_downmix; + param->StereoDownmix |= exss->asset[nAst].xll.stereo_downmix; + } + } + /* RepresentationType + * Available only when core substream is absent and ChannelLayout is set to 0. */ + for( int nExtSSIndex = exss_index_start; nExtSSIndex < DTS_MAX_NUM_EXSS; nExtSSIndex++ ) + { + dts_extension_info_t *exss = &info->exss[nExtSSIndex]; + if( exss->nuNumAudioPresnt == 0 || exss->nuNumAssets == 0 ) + continue; + for( uint8_t nAuPr = 0; nAuPr < exss->nuNumAudioPresnt; nAuPr++ ) + { + int asset_count = 0; + for( int nSS = 0; nSS <= nExtSSIndex; nSS++ ) + if( (exss->nuActiveExSSMask[nAuPr] >> nSS) & 0x1 ) + asset_count += lsmash_count_bits( exss->nuActiveAssetMask[nAuPr][nSS] ); + if( asset_count > 1 ) + { + /* An audio presentation has mulple audio assets. + * Audio asset designated for mixing with another audio asset. */ + param->RepresentationType = 0; + nExtSSIndex = DTS_MAX_NUM_EXSS; + break; + } + for( int nSS = 0; nSS <= nExtSSIndex; nSS++ ) + if( (exss->nuActiveExSSMask[nAuPr] >> nSS) & 0x1 ) + for( uint8_t nAst = 0; nAst < exss->nuNumAssets; nAst++ ) + if( (exss->nuActiveAssetMask[nAuPr][nSS] >> nAst) & 0x1 ) + { + dts_audio_asset_t *asset = &exss->asset[nAst]; + if( asset->nuRepresentationType == info->exss[exss_index_start].asset[0].nuRepresentationType ) + param->RepresentationType = asset->nuRepresentationType; + else + { + /* Detected different representation types. Use ChannelLayout. */ + param->RepresentationType = 0; + nAuPr = exss->nuNumAudioPresnt; + nExtSSIndex = DTS_MAX_NUM_EXSS; + break; + } + } + } + } + /* ChannelLayout + * complete information on channels coded in the audio stream including core and extensions */ + param->ChannelLayout = 0; + if( param->RepresentationType == 0 ) + for( int nExtSSIndex = exss_index_start; nExtSSIndex < DTS_MAX_NUM_EXSS; nExtSSIndex++ ) + { + dts_extension_info_t *exss = &info->exss[nExtSSIndex]; + if( exss->nuNumAudioPresnt == 0 || exss->nuNumAssets == 0 ) + continue; + for( uint8_t nAst = 0; nAst < exss->nuNumAssets; nAst++ ) + { + dts_audio_asset_t *asset = &exss->asset[nAst]; + param->ChannelLayout |= asset->channel_layout; + param->ChannelLayout |= asset->core.channel_layout; + param->ChannelLayout |= asset->core.xxch.channel_layout; + param->ChannelLayout |= asset->lbr.channel_layout; + param->ChannelLayout |= asset->xll.channel_layout; + } + } + /* MultiAssetFlag + * When multiple assets exist, the remaining parameters in the DTSSpecificBox only reflect the coding parameters of the first asset. */ + param->MultiAssetFlag = ((info->exss[0].nuNumAssets + + info->exss[1].nuNumAssets + + info->exss[2].nuNumAssets + + info->exss[3].nuNumAssets) > 1); + /* LBRDurationMod */ + param->LBRDurationMod = info->exss[exss_index_start].asset[0].lbr.duration_modifier; + /* DTSExpansionBox[] */ + for( int nExtSSIndex = exss_index_start; nExtSSIndex < DTS_MAX_NUM_EXSS; nExtSSIndex++ ) + { + dts_extension_info_t *exss = &info->exss[nExtSSIndex]; + for( uint8_t nAst = 0; nAst < exss->nuNumAssets; nAst++ ) + { + dts_audio_asset_t *asset = &exss->asset[nAst]; + if( asset->xll.dtsx_extension_present ) + { + /* Add DTSXParameters Box so that its presence indicates DTS:X extensions are present in the bitstream. + * Here, treat as unknown whether dialog level control for dialog objects in the bitstream is present or not. */ + static const uint8_t dxpb[] = + { + 0x00, 0x00, 0x00, 0x0c, /* size = 12 */ + 0x64, 0x78, 0x70, 0x62, /* type = 'dxpb' */ + 0x00, 0x00, 0x00, 0x00 /* version = 0, flags = 0x000000 (no dialog_control_info_present flag) */ + }; + lsmash_remove_dts_reserved_box( param ); + lsmash_append_dts_reserved_box( param, dxpb, sizeof(dxpb) ); + /* No error checks and just return. */ + goto param_initialized; + } + } + } +param_initialized: + info->ddts_param_initialized = 1; +} + +int dts_construct_specific_parameters( lsmash_codec_specific_t *dst, lsmash_codec_specific_t *src ) +{ + assert( dst && dst->data.structured && src && src->data.unstructured ); + if( src->size < DTS_SPECIFIC_BOX_MIN_LENGTH ) + return LSMASH_ERR_INVALID_DATA; + lsmash_dts_specific_parameters_t *param = (lsmash_dts_specific_parameters_t *)dst->data.structured; + uint8_t *data = src->data.unstructured; + uint64_t size = LSMASH_GET_BE32( data ); + int dts_specific_box_min_length = DTS_SPECIFIC_BOX_MIN_LENGTH; + data += ISOM_BASEBOX_COMMON_SIZE; + if( size == 1 ) + { + size = LSMASH_GET_BE64( data ); + dts_specific_box_min_length += 8; + data += 8; + } + if( size != src->size ) + return LSMASH_ERR_INVALID_DATA; + param->DTSSamplingFrequency = LSMASH_GET_BE32( &data[0] ); + param->maxBitrate = LSMASH_GET_BE32( &data[4] ); + param->avgBitrate = LSMASH_GET_BE32( &data[8] ); + param->pcmSampleDepth = LSMASH_GET_BYTE( &data[12] ); + param->FrameDuration = (data[13] >> 6) & 0x03; + param->StreamConstruction = (data[13] >> 1) & 0x1F; + param->CoreLFEPresent = data[13] & 0x01; + param->CoreLayout = (data[14] >> 2) & 0x3F; + param->CoreSize = ((data[14] & 0x03) << 12) | (data[15] << 4) | ((data[16] >> 4) & 0x0F); + param->StereoDownmix = (data[16] >> 3) & 0x01; + param->RepresentationType = data[16] & 0x07; + param->ChannelLayout = (data[17] << 8) | data[18]; + param->MultiAssetFlag = (data[19] >> 7) & 0x01; + param->LBRDurationMod = (data[19] >> 6) & 0x01; + int reserved_box_present = ((data[19] >> 5) & 0x01) && (size > dts_specific_box_min_length); + if( reserved_box_present ) + lsmash_append_dts_reserved_box( param, data + 20, size - dts_specific_box_min_length ); + return 0; +} + +int dts_copy_codec_specific( lsmash_codec_specific_t *dst, lsmash_codec_specific_t *src ) +{ + assert( src && src->format == LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED && src->data.structured ); + assert( dst && dst->format == LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED && dst->data.structured ); + lsmash_dts_specific_parameters_t *src_data = (lsmash_dts_specific_parameters_t *)src->data.structured; + lsmash_dts_specific_parameters_t *dst_data = (lsmash_dts_specific_parameters_t *)dst->data.structured; + lsmash_remove_dts_reserved_box( dst_data ); + *dst_data = *src_data; + if( !src_data->box || !src_data->box->data || src_data->box->size == 0 ) + { + lsmash_remove_dts_reserved_box( dst_data ); + return 0; + } + return lsmash_append_dts_reserved_box( dst_data, src_data->box->data, src_data->box->size ); +} + +int dts_print_codec_specific( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + assert( box->manager & LSMASH_BINARY_CODED_BOX ); + int indent = level; + lsmash_ifprintf( fp, indent++, "[%s: DTS Specific Box]\n", isom_4cc2str( box->type.fourcc ) ); + lsmash_ifprintf( fp, indent, "position = %"PRIu64"\n", box->pos ); + lsmash_ifprintf( fp, indent, "size = %"PRIu64"\n", box->size ); + if( box->size < DTS_SPECIFIC_BOX_MIN_LENGTH ) + return LSMASH_ERR_INVALID_DATA; + uint8_t *data = box->binary; + isom_skip_box_common( &data ); + uint32_t DTSSamplingFrequency = LSMASH_GET_BE32( &data[0] ); + uint32_t maxBitrate = LSMASH_GET_BE32( &data[4] ); + uint32_t avgBitrate = LSMASH_GET_BE32( &data[8] ); + uint8_t pcmSampleDepth = LSMASH_GET_BYTE( &data[12] ); + uint8_t FrameDuration = (data[13] >> 6) & 0x03; + uint8_t StreamConstruction = (data[13] >> 1) & 0x1F; + uint8_t CoreLFEPresent = data[13] & 0x01; + uint8_t CoreLayout = (data[14] >> 2) & 0x3F; + uint16_t CoreSize = ((data[14] & 0x03) << 12) | (data[15] << 4) | ((data[16] >> 4) & 0x0F); + uint8_t StereoDownmix = (data[16] >> 3) & 0x01; + uint8_t RepresentationType = data[16] & 0x07; + uint16_t ChannelLayout = (data[17] << 8) | data[18]; + uint8_t MultiAssetFlag = (data[19] >> 7) & 0x01; + uint8_t LBRDurationMod = (data[19] >> 6) & 0x01; + uint8_t ReservedBoxPresent = (data[19] >> 5) & 0x01; + uint8_t Reserved = data[19] & 0x1F; + uint32_t frame_duration = 512 << FrameDuration; + int construction_flags = StreamConstruction <= DTS_MAX_STREAM_CONSTRUCTION ? construction_info[StreamConstruction] : 0; + static const char *core_layout_description[64] = + { + "Mono (1/0)", + "Undefined", + "Stereo (2/0)", + "Undefined", + "LT,RT (2/0)", + "L, C, R (3/0)", + "L, R, S (2/1)", + "L, C, R, S (3/1)", + "L, R, LS, RS (2/2)", + "L, C, R, LS, RS (3/2)", + [31] = "use ChannelLayout" + }; + static const char *representation_type_description[8] = + { + "Audio asset designated for mixing with another audio asset", + "Reserved", + "Lt/Rt Encoded for matrix surround decoding", + "Audio processed for headphone playback", + "Reserved", + "Reserved", + "Reserved", + "Reserved" + }; + static const char *channel_layout_description[16] = + { + "Center in front of listener", + "Left/Right in front", + "Left/Right surround on side in rear", + "Low frequency effects subwoofer", + "Center surround in rear", + "Left/Right height in front", + "Left/Right surround in rear", + "Center Height in front", + "Over the listener's head", + "Between left/right and center in front", + "Left/Right on side in front", + "Left/Right surround on side", + "Second low frequency effects subwoofer", + "Left/Right height on side", + "Center height in rear", + "Left/Right height in rear" + }; + lsmash_ifprintf( fp, indent, "DTSSamplingFrequency = %"PRIu32" Hz\n", DTSSamplingFrequency ); + lsmash_ifprintf( fp, indent, "maxBitrate = %"PRIu32" bit/s\n", maxBitrate ); + lsmash_ifprintf( fp, indent, "avgBitrate = %"PRIu32" bit/s\n", avgBitrate ); + lsmash_ifprintf( fp, indent, "pcmSampleDepth = %"PRIu8" bits\n", pcmSampleDepth ); + lsmash_ifprintf( fp, indent, "FrameDuration = %"PRIu8" (%"PRIu32" samples)\n", FrameDuration, frame_duration ); + lsmash_ifprintf( fp, indent, "StreamConstruction = 0x%02"PRIx8"\n", StreamConstruction ); + if( construction_flags & (DTS_CORE_SUBSTREAM_CORE_FLAG | DTS_CORE_SUBSTREAM_XCH_FLAG | DTS_CORE_SUBSTREAM_X96_FLAG | DTS_CORE_SUBSTREAM_XXCH_FLAG) ) + { + lsmash_ifprintf( fp, indent + 1, "Core substream\n" ); + if( construction_flags & DTS_CORE_SUBSTREAM_CORE_FLAG ) + lsmash_ifprintf( fp, indent + 2, "Core\n" ); + if( construction_flags & DTS_CORE_SUBSTREAM_XCH_FLAG ) + lsmash_ifprintf( fp, indent + 2, "XCH\n" ); + if( construction_flags & DTS_CORE_SUBSTREAM_X96_FLAG ) + lsmash_ifprintf( fp, indent + 2, "X96\n" ); + if( construction_flags & DTS_CORE_SUBSTREAM_XXCH_FLAG ) + lsmash_ifprintf( fp, indent + 2, "XXCH\n" ); + } + if( construction_flags & (DTS_EXT_SUBSTREAM_CORE_FLAG | DTS_EXT_SUBSTREAM_XXCH_FLAG | DTS_EXT_SUBSTREAM_X96_FLAG + | DTS_EXT_SUBSTREAM_XBR_FLAG | DTS_EXT_SUBSTREAM_XLL_FLAG | DTS_EXT_SUBSTREAM_LBR_FLAG) ) + { + lsmash_ifprintf( fp, indent + 1, "Extension substream\n" ); + if( construction_flags & DTS_EXT_SUBSTREAM_CORE_FLAG ) + lsmash_ifprintf( fp, indent + 2, "Core\n" ); + if( construction_flags & DTS_EXT_SUBSTREAM_XXCH_FLAG ) + lsmash_ifprintf( fp, indent + 2, "XXCH\n" ); + if( construction_flags & DTS_EXT_SUBSTREAM_X96_FLAG ) + lsmash_ifprintf( fp, indent + 2, "X96\n" ); + if( construction_flags & DTS_EXT_SUBSTREAM_XBR_FLAG ) + lsmash_ifprintf( fp, indent + 2, "XBR\n" ); + if( construction_flags & DTS_EXT_SUBSTREAM_XLL_FLAG ) + lsmash_ifprintf( fp, indent + 2, "XLL\n" ); + if( construction_flags & DTS_EXT_SUBSTREAM_LBR_FLAG ) + lsmash_ifprintf( fp, indent + 2, "LBR\n" ); + } + lsmash_ifprintf( fp, indent, "CoreLFEPresent = %s\n", CoreLFEPresent ? "1 (LFE exists)" : "0 (no LFE)" ); + if( core_layout_description[CoreLayout] ) + lsmash_ifprintf( fp, indent, "CoreLayout = %"PRIu8" (%s)\n", CoreLayout, core_layout_description[CoreLayout] ); + else + lsmash_ifprintf( fp, indent, "CoreLayout = %"PRIu8" (Undefined)\n", CoreLayout ); + if( CoreSize ) + lsmash_ifprintf( fp, indent, "CoreSize = %"PRIu16"\n", CoreSize ); + else + lsmash_ifprintf( fp, indent, "CoreSize = 0 (no core substream exists)\n" ); + lsmash_ifprintf( fp, indent, "StereoDownmix = %s\n", StereoDownmix ? "1 (embedded downmix present)" : "0 (no embedded downmix)" ); + lsmash_ifprintf( fp, indent, "RepresentationType = %"PRIu8" (%s)\n", RepresentationType, representation_type_description[RepresentationType] ); + lsmash_ifprintf( fp, indent, "ChannelLayout = 0x%04"PRIx16"\n", ChannelLayout ); + if( ChannelLayout ) + for( int i = 0; i < 16; i++ ) + if( (ChannelLayout >> i) & 0x01 ) + lsmash_ifprintf( fp, indent + 1, "%s\n", channel_layout_description[i] ); + lsmash_ifprintf( fp, indent, "MultiAssetFlag = %s\n", MultiAssetFlag ? "1 (multiple asset)" : "0 (single asset)" ); + if( LBRDurationMod ) + lsmash_ifprintf( fp, indent, "LBRDurationMod = 1 (%"PRIu32" -> %"PRIu32" samples)\n", frame_duration, (frame_duration * 3) / 2 ); + else + lsmash_ifprintf( fp, indent, "LBRDurationMod = 0 (no LBR duration modifier)\n" ); + lsmash_ifprintf( fp, indent, "ReservedBoxPresent = %s\n", ReservedBoxPresent ? "1 (ReservedBox present)" : "0 (no ReservedBox)" ); + lsmash_ifprintf( fp, indent, "Reserved = 0x%02"PRIx8"\n", Reserved ); + return 0; +} + +int dts_update_bitrate( isom_stbl_t *stbl, isom_mdhd_t *mdhd, uint32_t sample_description_index ) +{ + isom_audio_entry_t *dts_audio = (isom_audio_entry_t *)lsmash_list_get_entry_data( &stbl->stsd->list, sample_description_index ); + if( LSMASH_IS_NON_EXISTING_BOX( dts_audio ) ) + return LSMASH_ERR_INVALID_DATA; + isom_box_t *ext = isom_get_extension_box( &dts_audio->extensions, ISOM_BOX_TYPE_DDTS ); + if( !((ext->manager & LSMASH_BINARY_CODED_BOX) && ext->binary && ext->size >= 28) ) + return LSMASH_ERR_INVALID_DATA; + uint32_t bufferSizeDB; + uint32_t maxBitrate; + uint32_t avgBitrate; + int err = isom_calculate_bitrate_description( stbl, mdhd, &bufferSizeDB, &maxBitrate, &avgBitrate, sample_description_index ); + if( err < 0 ) + return err; + if( !isom_is_variable_size( stbl ) ) + maxBitrate = avgBitrate; + uint8_t *exdata = ext->binary + 12; + LSMASH_SET_BE32( &exdata[0], maxBitrate ); + LSMASH_SET_BE32( &exdata[4], avgBitrate ); + return 0; +} diff --git a/3rdparty/l-smash/codecs/dts.h b/3rdparty/l-smash/codecs/dts.h new file mode 100644 index 000000000..45850829f --- /dev/null +++ b/3rdparty/l-smash/codecs/dts.h @@ -0,0 +1,135 @@ +/***************************************************************************** + * dts.h + ***************************************************************************** + * Copyright (C) 2012-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#define DTS_MAX_CORE_SIZE 16384 +#define DTS_MAX_EXSS_SIZE 32768 +#define DTS_MAX_NUM_EXSS 4 /* the maximum number of extension substreams */ + +typedef enum +{ + DTS_SUBSTREAM_TYPE_NONE = 0, + DTS_SUBSTREAM_TYPE_CORE = 1, + DTS_SUBSTREAM_TYPE_EXTENSION = 2, +} dts_substream_type; + +typedef struct +{ + uint16_t size; + uint16_t channel_layout; + uint8_t lower_planes; /* CL, LL and RL */ +} dts_xxch_info_t; + +typedef struct +{ + uint32_t sampling_frequency; + uint32_t frame_duration; + uint16_t frame_size; + uint16_t channel_layout; + uint8_t channel_arrangement; + uint8_t extension_audio_descriptor; + uint8_t pcm_resolution; + dts_xxch_info_t xxch; +} dts_core_info_t; + +typedef struct +{ + uint16_t size; + uint16_t channel_layout; + uint32_t sampling_frequency; + uint32_t frame_duration; + uint8_t pcm_resolution; + uint8_t stereo_downmix; + uint8_t lower_planes; /* CL, LL and RL */ + uint8_t dtsx_extension_present; +} dts_xll_info_t; + +typedef struct +{ + uint16_t size; + uint16_t channel_layout; + uint32_t sampling_frequency; + uint32_t frame_duration; + uint8_t stereo_downmix; + uint8_t lfe_present; + uint8_t duration_modifier; + uint8_t sample_size; +} dts_lbr_info_t; + +typedef struct +{ + uint32_t size; + uint16_t channel_layout; + uint8_t bOne2OneMapChannels2Speakers; + uint8_t nuRepresentationType; + uint8_t nuCodingMode; + lsmash_dts_construction_flag nuCoreExtensionMask; + dts_core_info_t core; + dts_xll_info_t xll; + dts_lbr_info_t lbr; + uint16_t xbr_size; + uint16_t x96_size; + uint16_t aux_size; +} dts_audio_asset_t; + +typedef struct +{ + uint32_t sampling_frequency; + uint32_t frame_duration; + uint8_t nuBits4ExSSFsize; + uint8_t bStaticFieldsPresent; + uint8_t bMixMetadataEnbl; + uint8_t nuNumMixOutConfigs; + uint8_t nNumMixOutCh[4]; + uint8_t nuNumAudioPresnt; + uint8_t nuNumAssets; + uint8_t nuActiveExSSMask[8]; + uint8_t nuActiveAssetMask[8][4]; + uint8_t bBcCorePresent[8]; + uint8_t nuBcCoreExtSSIndex[8]; + uint8_t nuBcCoreAssetIndex[8]; + uint8_t stereo_downmix; + uint8_t bit_resolution; + dts_audio_asset_t asset[8]; +} dts_extension_info_t; + +typedef struct +{ + dts_substream_type substream_type; + lsmash_dts_construction_flag flags; + lsmash_dts_specific_parameters_t ddts_param; + dts_core_info_t core; /* core component and its extensions in core substream */ + dts_extension_info_t exss[4]; /* extension substreams */ + uint8_t ddts_param_initialized; + uint8_t exss_index; + uint8_t exss_count; + uint32_t frame_duration; + uint32_t frame_size; /* size of substream */ + lsmash_bits_t *bits; +} dts_info_t; + +void dts_setup_parser( dts_info_t *info ); +int dts_parse_core_substream( dts_info_t *info ); +int dts_parse_extension_substream( dts_info_t *info ); +int dts_get_max_channel_count( dts_info_t *info ); +dts_substream_type dts_get_substream_type( dts_info_t *info ); +int dts_get_exss_index( dts_info_t *info, uint8_t *exss_index ); +void dts_update_specific_param( dts_info_t *info ); diff --git a/3rdparty/l-smash/codecs/h264.c b/3rdparty/l-smash/codecs/h264.c new file mode 100644 index 000000000..3ae05c68b --- /dev/null +++ b/3rdparty/l-smash/codecs/h264.c @@ -0,0 +1,2588 @@ +/***************************************************************************** + * h264.c + ***************************************************************************** + * Copyright (C) 2012-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include +#include +#include + +#include "core/box.h" + +/*************************************************************************** + ITU-T Recommendation H.264 (04/13) + ISO/IEC 14496-15:2010 +***************************************************************************/ +#include "h264.h" +#include "nalu.h" + +#define IF_EXCEED_INT32( x ) if( (x) < INT32_MIN || (x) > INT32_MAX ) +#define H264_REQUIRES_AVCC_EXTENSION( x ) ((x) == 100 || (x) == 110 || (x) == 122 || (x) == 144) +#define H264_POC_DEBUG_PRINT 0 + +typedef enum +{ + H264_SLICE_TYPE_P = 0, + H264_SLICE_TYPE_B = 1, + H264_SLICE_TYPE_I = 2, + H264_SLICE_TYPE_SP = 3, + H264_SLICE_TYPE_SI = 4 +} h264_slice_type; + +static lsmash_h264_parameter_sets_t *h264_allocate_parameter_sets( void ) +{ + lsmash_h264_parameter_sets_t *parameter_sets = lsmash_malloc_zero( sizeof(lsmash_h264_parameter_sets_t) ); + if( !parameter_sets ) + return NULL; + lsmash_list_init( parameter_sets->sps_list, isom_remove_dcr_ps ); + lsmash_list_init( parameter_sets->pps_list, isom_remove_dcr_ps ); + lsmash_list_init( parameter_sets->spsext_list, isom_remove_dcr_ps ); + return parameter_sets; +} + +static void h264_deallocate_parameter_sets +( + lsmash_h264_specific_parameters_t *param +) +{ + if( !param || !param->parameter_sets ) + return; + lsmash_list_remove_entries( param->parameter_sets->sps_list ); + lsmash_list_remove_entries( param->parameter_sets->pps_list ); + lsmash_list_remove_entries( param->parameter_sets->spsext_list ); + lsmash_freep( ¶m->parameter_sets ); +} + +void lsmash_destroy_h264_parameter_sets +( + lsmash_h264_specific_parameters_t *param +) +{ + h264_deallocate_parameter_sets( param ); +} + +void h264_destruct_specific_data +( + void *data +) +{ + if( !data ) + return; + h264_deallocate_parameter_sets( data ); + lsmash_free( data ); +} + +void h264_cleanup_parser +( + h264_info_t *info +) +{ + if( !info ) + return; + lsmash_list_remove_entries( info->sps_list ); + lsmash_list_remove_entries( info->pps_list ); + lsmash_list_remove_entries( info->slice_list ); + h264_deallocate_parameter_sets( &info->avcC_param ); + h264_deallocate_parameter_sets( &info->avcC_param_next ); + lsmash_destroy_multiple_buffers( info->buffer.bank ); + lsmash_bits_adhoc_cleanup( info->bits ); + info->bits = NULL; +} + +int h264_setup_parser +( + h264_info_t *info, + int parse_only +) +{ + assert( info ); + memset( info, 0, sizeof(h264_info_t) ); + info->avcC_param .lengthSizeMinusOne = NALU_DEFAULT_NALU_LENGTH_SIZE - 1; + info->avcC_param_next.lengthSizeMinusOne = NALU_DEFAULT_NALU_LENGTH_SIZE - 1; + h264_stream_buffer_t *sb = &info->buffer; + sb->bank = lsmash_create_multiple_buffers( parse_only ? 1 : 3, NALU_DEFAULT_BUFFER_SIZE ); + if( !sb->bank ) + return LSMASH_ERR_MEMORY_ALLOC; + sb->rbsp = lsmash_withdraw_buffer( sb->bank, 1 ); + if( !parse_only ) + { + info->au.data = lsmash_withdraw_buffer( sb->bank, 2 ); + info->au.incomplete_data = lsmash_withdraw_buffer( sb->bank, 3 ); + } + info->bits = lsmash_bits_adhoc_create(); + if( !info->bits ) + { + lsmash_destroy_multiple_buffers( sb->bank ); + return LSMASH_ERR_MEMORY_ALLOC; + } + lsmash_list_init_simple( info->sps_list ); + lsmash_list_init_simple( info->pps_list ); + lsmash_list_init_simple( info->slice_list ); + return 0; +} + +static int h264_check_nalu_header +( + lsmash_bs_t *bs, + h264_nalu_header_t *nuh, + int use_long_start_code +) +{ + uint8_t temp8 = lsmash_bs_show_byte( bs, use_long_start_code ? NALU_LONG_START_CODE_LENGTH : NALU_SHORT_START_CODE_LENGTH ); + nuh->forbidden_zero_bit = (temp8 >> 7) & 0x01; + nuh->nal_ref_idc = (temp8 >> 5) & 0x03; + nuh->nal_unit_type = temp8 & 0x1f; + nuh->length = 1; + if( nuh->nal_unit_type == H264_NALU_TYPE_PREFIX + || nuh->nal_unit_type == H264_NALU_TYPE_SLICE_EXT + || nuh->nal_unit_type == H264_NALU_TYPE_SLICE_EXT_DVC ) + { + /* We don't support these types of NALU. */ + //nuh->length += 3; + return LSMASH_ERR_PATCH_WELCOME; + } + if( nuh->forbidden_zero_bit ) + return LSMASH_ERR_INVALID_DATA; + /* SPS and PPS require long start code (0x00000001). + * Also AU delimiter requires it too because this type of NALU shall be the first NALU of any AU if present. */ + if( !use_long_start_code + && (nuh->nal_unit_type == H264_NALU_TYPE_SPS + || nuh->nal_unit_type == H264_NALU_TYPE_PPS + || nuh->nal_unit_type == H264_NALU_TYPE_AUD) ) + return LSMASH_ERR_INVALID_DATA; + if( nuh->nal_ref_idc ) + { + /* nal_ref_idc shall be equal to 0 for all NALUs having nal_unit_type equal to 6, 9, 10, 11, or 12. */ + if( nuh->nal_unit_type == H264_NALU_TYPE_SEI + || nuh->nal_unit_type == H264_NALU_TYPE_AUD + || nuh->nal_unit_type == H264_NALU_TYPE_EOS + || nuh->nal_unit_type == H264_NALU_TYPE_EOB + || nuh->nal_unit_type == H264_NALU_TYPE_FD ) + return LSMASH_ERR_INVALID_DATA; + } + else + /* nal_ref_idc shall not be equal to 0 for NALUs with nal_unit_type equal to 5. */ + if( nuh->nal_unit_type == H264_NALU_TYPE_SLICE_IDR ) + return LSMASH_ERR_INVALID_DATA; + return 0; +} + +uint64_t h264_find_next_start_code +( + lsmash_bs_t *bs, + h264_nalu_header_t *nuh, + uint64_t *start_code_length, + uint64_t *trailing_zero_bytes +) +{ + uint64_t length = 0; /* the length of the latest NALU */ + uint64_t count = 0; /* the number of the trailing zero bytes after the latest NALU */ + /* Check the type of the current start code. */ + int long_start_code + = (!lsmash_bs_is_end( bs, NALU_LONG_START_CODE_LENGTH ) && 0x00000001 == lsmash_bs_show_be32( bs, 0 )) ? 1 + : (!lsmash_bs_is_end( bs, NALU_SHORT_START_CODE_LENGTH ) && 0x000001 == lsmash_bs_show_be24( bs, 0 )) ? 0 + : -1; + if( long_start_code >= 0 && h264_check_nalu_header( bs, nuh, long_start_code ) == 0 ) + { + *start_code_length = long_start_code ? NALU_LONG_START_CODE_LENGTH : NALU_SHORT_START_CODE_LENGTH; + uint64_t distance = *start_code_length + nuh->length; + /* Find the start code of the next NALU and get the distance from the start code of the latest NALU. */ + if( !lsmash_bs_is_end( bs, distance + NALU_SHORT_START_CODE_LENGTH ) ) + { + uint32_t sync_bytes = lsmash_bs_show_be24( bs, distance ); + while( 0x000001 != sync_bytes ) + { + if( lsmash_bs_is_end( bs, ++distance + NALU_SHORT_START_CODE_LENGTH ) ) + { + distance = lsmash_bs_get_remaining_buffer_size( bs ); + break; + } + sync_bytes <<= 8; + sync_bytes |= lsmash_bs_show_byte( bs, distance + NALU_SHORT_START_CODE_LENGTH - 1 ); + sync_bytes &= 0xFFFFFF; + } + } + else + distance = lsmash_bs_get_remaining_buffer_size( bs ); + /* Any NALU has no consecutive zero bytes at the end. */ + while( 0x00 == lsmash_bs_show_byte( bs, distance - 1 ) ) + { + --distance; + ++count; + } + /* Remove the length of the start code. */ + length = distance - *start_code_length; + /* If there are one or more trailing zero bytes, we treat the last one byte as a part of the next start code. + * This makes the next start code a long start code. */ + if( count ) + --count; + } + else + { + /* No start code. */ + nuh->forbidden_zero_bit = 1; /* shall be 0, so invalid */ + nuh->nal_ref_idc = 0; /* arbitrary */ + nuh->nal_unit_type = H264_NALU_TYPE_UNSPECIFIED0; + nuh->length = 0; + *start_code_length = 0; + length = NALU_NO_START_CODE_FOUND; + } + *trailing_zero_bytes = count; + return length; +} + +static h264_sps_t *h264_get_sps +( + lsmash_entry_list_t *sps_list, + uint8_t sps_id +) +{ + if( !sps_list || sps_id > 31 ) + return NULL; + for( lsmash_entry_t *entry = sps_list->head; entry; entry = entry->next ) + { + h264_sps_t *sps = (h264_sps_t *)entry->data; + if( !sps ) + return NULL; + if( sps->seq_parameter_set_id == sps_id ) + return sps; + } + h264_sps_t *sps = lsmash_malloc_zero( sizeof(h264_sps_t) ); + if( !sps ) + return NULL; + sps->seq_parameter_set_id = sps_id; + if( lsmash_list_add_entry( sps_list, sps ) < 0 ) + { + lsmash_free( sps ); + return NULL; + } + return sps; +} + +static h264_pps_t *h264_get_pps +( + lsmash_entry_list_t *pps_list, + uint8_t pps_id +) +{ + if( !pps_list ) + return NULL; + for( lsmash_entry_t *entry = pps_list->head; entry; entry = entry->next ) + { + h264_pps_t *pps = (h264_pps_t *)entry->data; + if( !pps ) + return NULL; + if( pps->pic_parameter_set_id == pps_id ) + return pps; + } + h264_pps_t *pps = lsmash_malloc_zero( sizeof(h264_pps_t) ); + if( !pps ) + return NULL; + pps->pic_parameter_set_id = pps_id; + if( lsmash_list_add_entry( pps_list, pps ) < 0 ) + { + lsmash_free( pps ); + return NULL; + } + return pps; +} + +static h264_slice_info_t *h264_get_slice_info +( + lsmash_entry_list_t *slice_list, + uint8_t slice_id +) +{ + if( !slice_list ) + return NULL; + for( lsmash_entry_t *entry = slice_list->head; entry; entry = entry->next ) + { + h264_slice_info_t *slice = (h264_slice_info_t *)entry->data; + if( !slice ) + return NULL; + if( slice->slice_id == slice_id ) + return slice; + } + h264_slice_info_t *slice = lsmash_malloc_zero( sizeof(h264_slice_info_t) ); + if( !slice ) + return NULL; + slice->slice_id = slice_id; + if( lsmash_list_add_entry( slice_list, slice ) < 0 ) + { + lsmash_free( slice ); + return NULL; + } + return slice; +} + +int h264_calculate_poc +( + h264_info_t *info, + h264_picture_info_t *picture, + h264_picture_info_t *prev_picture +) +{ +#if H264_POC_DEBUG_PRINT + fprintf( stderr, "PictureOrderCount\n" ); +#endif + h264_pps_t *pps = h264_get_pps( info->pps_list, picture->pic_parameter_set_id ); + if( !pps ) + return LSMASH_ERR_NAMELESS; + h264_sps_t *sps = h264_get_sps( info->sps_list, pps->seq_parameter_set_id ); + if( !sps ) + return LSMASH_ERR_NAMELESS; + int64_t TopFieldOrderCnt = 0; + int64_t BottomFieldOrderCnt = 0; + if( sps->pic_order_cnt_type == 0 ) + { + int32_t prevPicOrderCntMsb; + int32_t prevPicOrderCntLsb; + if( picture->idr ) + { + prevPicOrderCntMsb = 0; + prevPicOrderCntLsb = 0; + } + else if( prev_picture->ref_pic_has_mmco5 ) + { + prevPicOrderCntMsb = 0; + prevPicOrderCntLsb = prev_picture->ref_pic_bottom_field_flag ? 0 : prev_picture->ref_pic_TopFieldOrderCnt; + } + else + { + prevPicOrderCntMsb = prev_picture->ref_pic_PicOrderCntMsb; + prevPicOrderCntLsb = prev_picture->ref_pic_PicOrderCntLsb; + } + int64_t PicOrderCntMsb; + int32_t pic_order_cnt_lsb = picture->pic_order_cnt_lsb; + uint64_t MaxPicOrderCntLsb = sps->MaxPicOrderCntLsb; + if( (pic_order_cnt_lsb < prevPicOrderCntLsb) + && ((prevPicOrderCntLsb - pic_order_cnt_lsb) >= (MaxPicOrderCntLsb / 2)) ) + PicOrderCntMsb = prevPicOrderCntMsb + MaxPicOrderCntLsb; + else if( (pic_order_cnt_lsb > prevPicOrderCntLsb) + && ((pic_order_cnt_lsb - prevPicOrderCntLsb) > (MaxPicOrderCntLsb / 2)) ) + PicOrderCntMsb = prevPicOrderCntMsb - MaxPicOrderCntLsb; + else + PicOrderCntMsb = prevPicOrderCntMsb; + IF_EXCEED_INT32( PicOrderCntMsb ) + return LSMASH_ERR_INVALID_DATA; + BottomFieldOrderCnt = TopFieldOrderCnt = PicOrderCntMsb + pic_order_cnt_lsb; + if( !picture->field_pic_flag ) + BottomFieldOrderCnt += picture->delta_pic_order_cnt_bottom; + IF_EXCEED_INT32( TopFieldOrderCnt ) + return LSMASH_ERR_INVALID_DATA; + IF_EXCEED_INT32( BottomFieldOrderCnt ) + return LSMASH_ERR_INVALID_DATA; + if( !picture->disposable ) + { + picture->ref_pic_has_mmco5 = picture->has_mmco5; + picture->ref_pic_bottom_field_flag = picture->bottom_field_flag; + picture->ref_pic_TopFieldOrderCnt = TopFieldOrderCnt; + picture->ref_pic_PicOrderCntMsb = PicOrderCntMsb; + picture->ref_pic_PicOrderCntLsb = pic_order_cnt_lsb; + } +#if H264_POC_DEBUG_PRINT + fprintf( stderr, " prevPicOrderCntMsb: %"PRId32"\n", prevPicOrderCntMsb ); + fprintf( stderr, " prevPicOrderCntLsb: %"PRId32"\n", prevPicOrderCntLsb ); + fprintf( stderr, " PicOrderCntMsb: %"PRId64"\n", PicOrderCntMsb ); + fprintf( stderr, " pic_order_cnt_lsb: %"PRId32"\n", pic_order_cnt_lsb ); + fprintf( stderr, " MaxPicOrderCntLsb: %"PRIu64"\n", MaxPicOrderCntLsb ); +#endif + } + else if( sps->pic_order_cnt_type == 1 ) + { + uint32_t frame_num = picture->frame_num; + uint32_t prevFrameNum = prev_picture->has_mmco5 ? 0 : prev_picture->frame_num; + uint32_t prevFrameNumOffset = prev_picture->has_mmco5 ? 0 : prev_picture->FrameNumOffset; + uint64_t FrameNumOffset = picture->idr ? 0 : prevFrameNumOffset + (prevFrameNum > frame_num ? sps->MaxFrameNum : 0); + if( FrameNumOffset > INT32_MAX ) + return LSMASH_ERR_INVALID_DATA; + int64_t expectedPicOrderCnt; + if( sps->num_ref_frames_in_pic_order_cnt_cycle ) + { + uint64_t absFrameNum = FrameNumOffset + frame_num; + absFrameNum -= picture->disposable && absFrameNum > 0; + if( absFrameNum ) + { + uint64_t picOrderCntCycleCnt = (absFrameNum - 1) / sps->num_ref_frames_in_pic_order_cnt_cycle; + uint8_t frameNumInPicOrderCntCycle = (absFrameNum - 1) % sps->num_ref_frames_in_pic_order_cnt_cycle; + expectedPicOrderCnt = picOrderCntCycleCnt * sps->ExpectedDeltaPerPicOrderCntCycle; + for( uint8_t i = 0; i <= frameNumInPicOrderCntCycle; i++ ) + expectedPicOrderCnt += sps->offset_for_ref_frame[i]; + } + else + expectedPicOrderCnt = 0; + } + else + expectedPicOrderCnt = 0; + if( picture->disposable ) + expectedPicOrderCnt += sps->offset_for_non_ref_pic; + TopFieldOrderCnt = expectedPicOrderCnt + picture->delta_pic_order_cnt[0]; + BottomFieldOrderCnt = TopFieldOrderCnt + sps->offset_for_top_to_bottom_field; + if( !picture->field_pic_flag ) + BottomFieldOrderCnt += picture->delta_pic_order_cnt[1]; + IF_EXCEED_INT32( TopFieldOrderCnt ) + return LSMASH_ERR_INVALID_DATA; + IF_EXCEED_INT32( BottomFieldOrderCnt ) + return LSMASH_ERR_INVALID_DATA; + picture->FrameNumOffset = FrameNumOffset; + } + else if( sps->pic_order_cnt_type == 2 ) + { + uint32_t frame_num = picture->frame_num; + uint32_t prevFrameNum = prev_picture->has_mmco5 ? 0 : prev_picture->frame_num; + int32_t prevFrameNumOffset = prev_picture->has_mmco5 ? 0 : prev_picture->FrameNumOffset; + int64_t FrameNumOffset; + int64_t tempPicOrderCnt; + if( picture->idr ) + { + FrameNumOffset = 0; + tempPicOrderCnt = 0; + } + else + { + FrameNumOffset = prevFrameNumOffset + (prevFrameNum > frame_num ? sps->MaxFrameNum : 0); + tempPicOrderCnt = 2 * (FrameNumOffset + frame_num) - picture->disposable; + IF_EXCEED_INT32( FrameNumOffset ) + return LSMASH_ERR_INVALID_DATA; + IF_EXCEED_INT32( tempPicOrderCnt ) + return LSMASH_ERR_INVALID_DATA; + } + TopFieldOrderCnt = tempPicOrderCnt; + BottomFieldOrderCnt = tempPicOrderCnt; + picture->FrameNumOffset = FrameNumOffset; + } + if( !picture->field_pic_flag ) + picture->PicOrderCnt = LSMASH_MIN( TopFieldOrderCnt, BottomFieldOrderCnt ); + else + picture->PicOrderCnt = picture->bottom_field_flag ? BottomFieldOrderCnt : TopFieldOrderCnt; +#if H264_POC_DEBUG_PRINT + if( picture->field_pic_flag ) + { + if( !picture->bottom_field_flag ) + fprintf( stderr, " TopFieldOrderCnt: %"PRId64"\n", TopFieldOrderCnt ); + else + fprintf( stderr, " BottomFieldOrderCnt: %"PRId64"\n", BottomFieldOrderCnt ); + } + fprintf( stderr, " POC: %"PRId32"\n", picture->PicOrderCnt ); +#endif + return 0; +} + +static int h264_parse_scaling_list +( + lsmash_bits_t *bits, + int sizeOfScalingList +) +{ + /* scaling_list( scalingList, sizeOfScalingList, useDefaultScalingMatrixFlag ) */ + int nextScale = 8; + for( int i = 0; i < sizeOfScalingList; i++ ) + { + int64_t delta_scale = nalu_get_exp_golomb_se( bits ); + if( delta_scale < -128 || delta_scale > 127 ) + return LSMASH_ERR_INVALID_DATA; + nextScale = (nextScale + delta_scale + 256) % 256; + if( nextScale == 0 ) + break; + } + return 0; +} + +static int h264_parse_hrd_parameters +( + lsmash_bits_t *bits, + h264_hrd_t *hrd +) +{ + /* hrd_parameters() */ + uint64_t cpb_cnt_minus1 = nalu_get_exp_golomb_ue( bits ); + if( cpb_cnt_minus1 > 31 ) + return LSMASH_ERR_INVALID_DATA; + lsmash_bits_get( bits, 4 ); /* bit_rate_scale */ + lsmash_bits_get( bits, 4 ); /* cpb_size_scale */ + for( uint64_t SchedSelIdx = 0; SchedSelIdx <= cpb_cnt_minus1; SchedSelIdx++ ) + { + nalu_get_exp_golomb_ue( bits ); /* bit_rate_value_minus1[ SchedSelIdx ] */ + nalu_get_exp_golomb_ue( bits ); /* cpb_size_value_minus1[ SchedSelIdx ] */ + lsmash_bits_get( bits, 1 ); /* cbr_flag [ SchedSelIdx ] */ + } + lsmash_bits_get( bits, 5 ); /* initial_cpb_removal_delay_length_minus1 */ + hrd->cpb_removal_delay_length = lsmash_bits_get( bits, 5 ) + 1; + hrd->dpb_output_delay_length = lsmash_bits_get( bits, 5 ) + 1; + lsmash_bits_get( bits, 5 ); /* time_offset_length */ + return 0; +} + +static int h264_parse_sps_minimally +( + lsmash_bits_t *bits, + h264_sps_t *sps, + uint8_t *rbsp_buffer, + uint8_t *ebsp, + uint64_t ebsp_size +) +{ + uint64_t rbsp_size; + int err = nalu_import_rbsp_from_ebsp( bits, rbsp_buffer, &rbsp_size, ebsp, ebsp_size ); + if( err < 0 ) + return err; + memset( sps, 0, sizeof(h264_sps_t) ); + sps->profile_idc = lsmash_bits_get( bits, 8 ); + sps->constraint_set_flags = lsmash_bits_get( bits, 8 ); + sps->level_idc = lsmash_bits_get( bits, 8 ); + uint64_t seq_parameter_set_id = nalu_get_exp_golomb_ue( bits ); + if( seq_parameter_set_id > 31 ) + return LSMASH_ERR_INVALID_DATA; + sps->seq_parameter_set_id = seq_parameter_set_id; + if( sps->profile_idc == 100 || sps->profile_idc == 110 || sps->profile_idc == 122 + || sps->profile_idc == 244 || sps->profile_idc == 44 || sps->profile_idc == 83 + || sps->profile_idc == 86 || sps->profile_idc == 118 || sps->profile_idc == 128 + || sps->profile_idc == 138 ) + { + sps->chroma_format_idc = nalu_get_exp_golomb_ue( bits ); + if( sps->chroma_format_idc == 3 ) + sps->separate_colour_plane_flag = lsmash_bits_get( bits, 1 ); + uint64_t bit_depth_luma_minus8 = nalu_get_exp_golomb_ue( bits ); + if( bit_depth_luma_minus8 > 6 ) + return LSMASH_ERR_INVALID_DATA; + uint64_t bit_depth_chroma_minus8 = nalu_get_exp_golomb_ue( bits ); + if( bit_depth_chroma_minus8 > 6 ) + return LSMASH_ERR_INVALID_DATA; + sps->bit_depth_luma_minus8 = bit_depth_luma_minus8; + sps->bit_depth_chroma_minus8 = bit_depth_chroma_minus8; + lsmash_bits_get( bits, 1 ); /* qpprime_y_zero_transform_bypass_flag */ + if( lsmash_bits_get( bits, 1 ) ) /* seq_scaling_matrix_present_flag */ + { + int num_loops = sps->chroma_format_idc != 3 ? 8 : 12; + for( int i = 0; i < num_loops; i++ ) + if( lsmash_bits_get( bits, 1 ) /* seq_scaling_list_present_flag[i] */ + && (err = h264_parse_scaling_list( bits, i < 6 ? 16 : 64 )) < 0 ) + return err; + } + } + else + { + sps->chroma_format_idc = 1; + sps->separate_colour_plane_flag = 0; + sps->bit_depth_luma_minus8 = 0; + sps->bit_depth_chroma_minus8 = 0; + } + return bits->bs->error ? LSMASH_ERR_NAMELESS : 0; +} + +int h264_parse_sps +( + h264_info_t *info, + uint8_t *rbsp_buffer, + uint8_t *ebsp, + uint64_t ebsp_size +) +{ + lsmash_bits_t *bits = info->bits; + /* seq_parameter_set_data() */ + h264_sps_t temp_sps; + int err = h264_parse_sps_minimally( bits, &temp_sps, rbsp_buffer, ebsp, ebsp_size ); + if( err < 0 ) + return err; + h264_sps_t *sps = h264_get_sps( info->sps_list, temp_sps.seq_parameter_set_id ); + if( !sps ) + return LSMASH_ERR_NAMELESS; + memset( sps, 0, sizeof(h264_sps_t) ); + sps->profile_idc = temp_sps.profile_idc; + sps->constraint_set_flags = temp_sps.constraint_set_flags; + sps->level_idc = temp_sps.level_idc; + sps->seq_parameter_set_id = temp_sps.seq_parameter_set_id; + sps->chroma_format_idc = temp_sps.chroma_format_idc; + sps->separate_colour_plane_flag = temp_sps.separate_colour_plane_flag; + sps->bit_depth_luma_minus8 = temp_sps.bit_depth_luma_minus8; + sps->bit_depth_chroma_minus8 = temp_sps.bit_depth_chroma_minus8; + sps->ChromaArrayType = sps->separate_colour_plane_flag ? 0 : sps->chroma_format_idc; + uint64_t log2_max_frame_num_minus4 = nalu_get_exp_golomb_ue( bits ); + if( log2_max_frame_num_minus4 > 12 ) + return LSMASH_ERR_INVALID_DATA; + sps->log2_max_frame_num = log2_max_frame_num_minus4 + 4; + sps->MaxFrameNum = 1 << sps->log2_max_frame_num; + uint64_t pic_order_cnt_type = nalu_get_exp_golomb_ue( bits ); + if( pic_order_cnt_type > 2 ) + return LSMASH_ERR_INVALID_DATA; + sps->pic_order_cnt_type = pic_order_cnt_type; + if( sps->pic_order_cnt_type == 0 ) + { + uint64_t log2_max_pic_order_cnt_lsb_minus4 = nalu_get_exp_golomb_ue( bits ); + if( log2_max_pic_order_cnt_lsb_minus4 > 12 ) + return LSMASH_ERR_INVALID_DATA; + sps->log2_max_pic_order_cnt_lsb = log2_max_pic_order_cnt_lsb_minus4 + 4; + sps->MaxPicOrderCntLsb = 1 << sps->log2_max_pic_order_cnt_lsb; + } + else if( sps->pic_order_cnt_type == 1 ) + { + sps->delta_pic_order_always_zero_flag = lsmash_bits_get( bits, 1 ); + static const int64_t max_value = (signed)(((uint64_t)1 << 31) - 1); + static const int64_t min_value = -(signed)(((uint64_t)1 << 31) - 1); + int64_t offset_for_non_ref_pic = nalu_get_exp_golomb_se( bits ); + if( offset_for_non_ref_pic < min_value || offset_for_non_ref_pic > max_value ) + return LSMASH_ERR_INVALID_DATA; + sps->offset_for_non_ref_pic = offset_for_non_ref_pic; + int64_t offset_for_top_to_bottom_field = nalu_get_exp_golomb_se( bits ); + if( offset_for_top_to_bottom_field < min_value || offset_for_top_to_bottom_field > max_value ) + return LSMASH_ERR_INVALID_DATA; + sps->offset_for_top_to_bottom_field = offset_for_top_to_bottom_field; + uint64_t num_ref_frames_in_pic_order_cnt_cycle = nalu_get_exp_golomb_ue( bits ); + if( num_ref_frames_in_pic_order_cnt_cycle > 255 ) + return LSMASH_ERR_INVALID_DATA; + sps->num_ref_frames_in_pic_order_cnt_cycle = num_ref_frames_in_pic_order_cnt_cycle; + sps->ExpectedDeltaPerPicOrderCntCycle = 0; + for( int i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++ ) + { + int64_t offset_for_ref_frame = nalu_get_exp_golomb_se( bits ); + if( offset_for_ref_frame < min_value || offset_for_ref_frame > max_value ) + return LSMASH_ERR_INVALID_DATA; + sps->offset_for_ref_frame[i] = offset_for_ref_frame; + sps->ExpectedDeltaPerPicOrderCntCycle += offset_for_ref_frame; + } + } + sps->max_num_ref_frames = nalu_get_exp_golomb_ue( bits ); + lsmash_bits_get( bits, 1 ); /* gaps_in_frame_num_value_allowed_flag */ + uint64_t pic_width_in_mbs_minus1 = nalu_get_exp_golomb_ue( bits ); + uint64_t pic_height_in_map_units_minus1 = nalu_get_exp_golomb_ue( bits ); + sps->frame_mbs_only_flag = lsmash_bits_get( bits, 1 ); + if( !sps->frame_mbs_only_flag ) + lsmash_bits_get( bits, 1 ); /* mb_adaptive_frame_field_flag */ + lsmash_bits_get( bits, 1 ); /* direct_8x8_inference_flag */ + uint64_t PicWidthInMbs = pic_width_in_mbs_minus1 + 1; + uint64_t PicHeightInMapUnits = pic_height_in_map_units_minus1 + 1; + sps->PicSizeInMapUnits = PicWidthInMbs * PicHeightInMapUnits; + sps->cropped_width = PicWidthInMbs * 16; + sps->cropped_height = (2 - sps->frame_mbs_only_flag) * PicHeightInMapUnits * 16; + if( lsmash_bits_get( bits, 1 ) ) /* frame_cropping_flag */ + { + uint8_t CropUnitX; + uint8_t CropUnitY; + if( sps->ChromaArrayType == 0 ) + { + CropUnitX = 1; + CropUnitY = 2 - sps->frame_mbs_only_flag; + } + else + { + static const int SubWidthC [] = { 0, 2, 2, 1 }; + static const int SubHeightC[] = { 0, 2, 1, 1 }; + CropUnitX = SubWidthC [ sps->chroma_format_idc ]; + CropUnitY = SubHeightC[ sps->chroma_format_idc ] * (2 - sps->frame_mbs_only_flag); + } + uint64_t frame_crop_left_offset = nalu_get_exp_golomb_ue( bits ); + uint64_t frame_crop_right_offset = nalu_get_exp_golomb_ue( bits ); + uint64_t frame_crop_top_offset = nalu_get_exp_golomb_ue( bits ); + uint64_t frame_crop_bottom_offset = nalu_get_exp_golomb_ue( bits ); + sps->cropped_width -= (frame_crop_left_offset + frame_crop_right_offset) * CropUnitX; + sps->cropped_height -= (frame_crop_top_offset + frame_crop_bottom_offset) * CropUnitY; + } + if( lsmash_bits_get( bits, 1 ) ) /* vui_parameters_present_flag */ + { + /* vui_parameters() */ + if( lsmash_bits_get( bits, 1 ) ) /* aspect_ratio_info_present_flag */ + { + uint8_t aspect_ratio_idc = lsmash_bits_get( bits, 8 ); + if( aspect_ratio_idc == 255 ) + { + /* Extended_SAR */ + sps->vui.sar_width = lsmash_bits_get( bits, 16 ); + sps->vui.sar_height = lsmash_bits_get( bits, 16 ); + } + else + { + static const struct + { + uint16_t sar_width; + uint16_t sar_height; + } pre_defined_sar[] + = { + { 0, 0 }, { 1, 1 }, { 12, 11 }, { 10, 11 }, { 16, 11 }, + { 40, 33 }, { 24, 11 }, { 20, 11 }, { 32, 11 }, { 80, 33 }, + { 18, 11 }, { 15, 11 }, { 64, 33 }, { 160, 99 }, { 4, 3 }, + { 3, 2 }, { 2, 1 } + }; + if( aspect_ratio_idc < (sizeof(pre_defined_sar) / sizeof(pre_defined_sar[0])) ) + { + sps->vui.sar_width = pre_defined_sar[ aspect_ratio_idc ].sar_width; + sps->vui.sar_height = pre_defined_sar[ aspect_ratio_idc ].sar_height; + } + else + { + /* Behavior when unknown aspect_ratio_idc is detected is not specified in the specification. */ + sps->vui.sar_width = 0; + sps->vui.sar_height = 0; + } + } + } + if( lsmash_bits_get( bits, 1 ) ) /* overscan_info_present_flag */ + lsmash_bits_get( bits, 1 ); /* overscan_appropriate_flag */ + if( lsmash_bits_get( bits, 1 ) ) /* video_signal_type_present_flag */ + { + lsmash_bits_get( bits, 3 ); /* video_format */ + sps->vui.video_full_range_flag = lsmash_bits_get( bits, 1 ); + if( lsmash_bits_get( bits, 1 ) ) /* colour_description_present_flag */ + { + sps->vui.colour_primaries = lsmash_bits_get( bits, 8 ); + sps->vui.transfer_characteristics = lsmash_bits_get( bits, 8 ); + sps->vui.matrix_coefficients = lsmash_bits_get( bits, 8 ); + } + } + if( lsmash_bits_get( bits, 1 ) ) /* chroma_loc_info_present_flag */ + { + nalu_get_exp_golomb_ue( bits ); /* chroma_sample_loc_type_top_field */ + nalu_get_exp_golomb_ue( bits ); /* chroma_sample_loc_type_bottom_field */ + } + if( lsmash_bits_get( bits, 1 ) ) /* timing_info_present_flag */ + { + sps->vui.num_units_in_tick = lsmash_bits_get( bits, 32 ); + sps->vui.time_scale = lsmash_bits_get( bits, 32 ); + sps->vui.fixed_frame_rate_flag = lsmash_bits_get( bits, 1 ); + } + else + { + sps->vui.num_units_in_tick = 1; /* arbitrary */ + sps->vui.time_scale = 50; /* arbitrary */ + sps->vui.fixed_frame_rate_flag = 0; + } + int nal_hrd_parameters_present_flag = lsmash_bits_get( bits, 1 ); + if( nal_hrd_parameters_present_flag + && (err = h264_parse_hrd_parameters( bits, &sps->vui.hrd )) < 0 ) + return err; + int vcl_hrd_parameters_present_flag = lsmash_bits_get( bits, 1 ); + if( vcl_hrd_parameters_present_flag + && (err = h264_parse_hrd_parameters( bits, &sps->vui.hrd )) < 0 ) + return err; + if( nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag ) + { + sps->vui.hrd.present = 1; + sps->vui.hrd.CpbDpbDelaysPresentFlag = 1; + lsmash_bits_get( bits, 1 ); /* low_delay_hrd_flag */ + } + sps->vui.pic_struct_present_flag = lsmash_bits_get( bits, 1 ); + if( lsmash_bits_get( bits, 1 ) ) /* bitstream_restriction_flag */ + { + lsmash_bits_get( bits, 1 ); /* motion_vectors_over_pic_boundaries_flag */ + nalu_get_exp_golomb_ue( bits ); /* max_bytes_per_pic_denom */ + nalu_get_exp_golomb_ue( bits ); /* max_bits_per_mb_denom */ + nalu_get_exp_golomb_ue( bits ); /* log2_max_mv_length_horizontal */ + nalu_get_exp_golomb_ue( bits ); /* log2_max_mv_length_vertical */ + nalu_get_exp_golomb_ue( bits ); /* max_num_reorder_frames */ + nalu_get_exp_golomb_ue( bits ); /* max_dec_frame_buffering */ + } + } + else + { + sps->vui.video_full_range_flag = 0; + sps->vui.num_units_in_tick = 1; /* arbitrary */ + sps->vui.time_scale = 50; /* arbitrary */ + sps->vui.fixed_frame_rate_flag = 0; + } + /* rbsp_trailing_bits() */ + if( !lsmash_bits_get( bits, 1 ) ) /* rbsp_stop_one_bit */ + return LSMASH_ERR_INVALID_DATA; + lsmash_bits_empty( bits ); + if( bits->bs->error ) + return LSMASH_ERR_NAMELESS; + sps->present = 1; + info->sps = *sps; + return 0; +} + +static int h264_parse_pps_minimally +( + lsmash_bits_t *bits, + h264_pps_t *pps, + uint8_t *rbsp_buffer, + uint8_t *ebsp, + uint64_t ebsp_size +) +{ + uint64_t rbsp_size; + int err = nalu_import_rbsp_from_ebsp( bits, rbsp_buffer, &rbsp_size, ebsp, ebsp_size ); + if( err < 0 ) + return err; + memset( pps, 0, sizeof(h264_pps_t) ); + uint64_t pic_parameter_set_id = nalu_get_exp_golomb_ue( bits ); + if( pic_parameter_set_id > 255 ) + return LSMASH_ERR_INVALID_DATA; + pps->pic_parameter_set_id = pic_parameter_set_id; + return bits->bs->error ? LSMASH_ERR_NAMELESS : 0; +} + +int h264_parse_pps +( + h264_info_t *info, + uint8_t *rbsp_buffer, + uint8_t *ebsp, + uint64_t ebsp_size +) +{ + lsmash_bits_t *bits = info->bits; + /* pic_parameter_set_rbsp */ + h264_pps_t temp_pps; + int err = h264_parse_pps_minimally( bits, &temp_pps, rbsp_buffer, ebsp, ebsp_size ); + if( err < 0 ) + return err; + h264_pps_t *pps = h264_get_pps( info->pps_list, temp_pps.pic_parameter_set_id ); + if( !pps ) + return LSMASH_ERR_NAMELESS; + memset( pps, 0, sizeof(h264_pps_t) ); + pps->pic_parameter_set_id = temp_pps.pic_parameter_set_id; + uint64_t seq_parameter_set_id = nalu_get_exp_golomb_ue( bits ); + if( seq_parameter_set_id > 31 ) + return LSMASH_ERR_INVALID_DATA; + h264_sps_t *sps = h264_get_sps( info->sps_list, seq_parameter_set_id ); + if( !sps ) + return LSMASH_ERR_NAMELESS; + pps->seq_parameter_set_id = seq_parameter_set_id; + pps->entropy_coding_mode_flag = lsmash_bits_get( bits, 1 ); + pps->bottom_field_pic_order_in_frame_present_flag = lsmash_bits_get( bits, 1 ); + uint64_t num_slice_groups_minus1 = nalu_get_exp_golomb_ue( bits ); + if( num_slice_groups_minus1 > 7 ) + return LSMASH_ERR_INVALID_DATA; + pps->num_slice_groups_minus1 = num_slice_groups_minus1; + if( num_slice_groups_minus1 ) /* num_slice_groups_minus1 */ + { + uint64_t slice_group_map_type = nalu_get_exp_golomb_ue( bits ); + if( slice_group_map_type > 6 ) + return LSMASH_ERR_INVALID_DATA; + pps->slice_group_map_type = slice_group_map_type; + if( slice_group_map_type == 0 ) + for( uint64_t iGroup = 0; iGroup <= num_slice_groups_minus1; iGroup++ ) + nalu_get_exp_golomb_ue( bits ); /* run_length_minus1[ iGroup ] */ + else if( slice_group_map_type == 2 ) + for( uint64_t iGroup = 0; iGroup < num_slice_groups_minus1; iGroup++ ) + { + nalu_get_exp_golomb_ue( bits ); /* top_left [ iGroup ] */ + nalu_get_exp_golomb_ue( bits ); /* bottom_right[ iGroup ] */ + } + else if( slice_group_map_type == 3 + || slice_group_map_type == 4 + || slice_group_map_type == 5 ) + { + lsmash_bits_get( bits, 1 ); /* slice_group_change_direction_flag */ + uint64_t slice_group_change_rate_minus1 = nalu_get_exp_golomb_ue( bits ); + if( slice_group_change_rate_minus1 > (sps->PicSizeInMapUnits - 1) ) + return LSMASH_ERR_INVALID_DATA; + pps->SliceGroupChangeRate = slice_group_change_rate_minus1 + 1; + } + else if( slice_group_map_type == 6 ) + { + uint64_t pic_size_in_map_units_minus1 = nalu_get_exp_golomb_ue( bits ); + int length = lsmash_ceil_log2( num_slice_groups_minus1 + 1 ); + for( uint64_t i = 0; i <= pic_size_in_map_units_minus1; i++ ) + /* slice_group_id */ + if( lsmash_bits_get( bits, length ) > num_slice_groups_minus1 ) + return LSMASH_ERR_INVALID_DATA; + } + } + pps->num_ref_idx_l0_default_active_minus1 = nalu_get_exp_golomb_ue( bits ); + pps->num_ref_idx_l1_default_active_minus1 = nalu_get_exp_golomb_ue( bits ); + pps->weighted_pred_flag = lsmash_bits_get( bits, 1 ); + pps->weighted_bipred_idc = lsmash_bits_get( bits, 2 ); + nalu_get_exp_golomb_se( bits ); /* pic_init_qp_minus26 */ + nalu_get_exp_golomb_se( bits ); /* pic_init_qs_minus26 */ + nalu_get_exp_golomb_se( bits ); /* chroma_qp_index_offset */ + pps->deblocking_filter_control_present_flag = lsmash_bits_get( bits, 1 ); + lsmash_bits_get( bits, 1 ); /* constrained_intra_pred_flag */ + pps->redundant_pic_cnt_present_flag = lsmash_bits_get( bits, 1 ); + if( nalu_check_more_rbsp_data( bits ) ) + { + int transform_8x8_mode_flag = lsmash_bits_get( bits, 1 ); + if( lsmash_bits_get( bits, 1 ) ) /* pic_scaling_matrix_present_flag */ + { + int num_loops = 6 + (sps->chroma_format_idc != 3 ? 2 : 6) * transform_8x8_mode_flag; + for( int i = 0; i < num_loops; i++ ) + if( lsmash_bits_get( bits, 1 ) /* pic_scaling_list_present_flag[i] */ + && (err = h264_parse_scaling_list( bits, i < 6 ? 16 : 64 )) < 0 ) + return err; + } + nalu_get_exp_golomb_se( bits ); /* second_chroma_qp_index_offset */ + } + /* rbsp_trailing_bits() */ + if( !lsmash_bits_get( bits, 1 ) ) /* rbsp_stop_one_bit */ + return LSMASH_ERR_INVALID_DATA; + lsmash_bits_empty( bits ); + if( bits->bs->error ) + return LSMASH_ERR_NAMELESS; + pps->present = 1; + info->sps = *sps; + info->pps = *pps; + return 0; +} + +int h264_parse_sei +( + lsmash_bits_t *bits, + h264_sps_t *sps, + h264_sei_t *sei, + uint8_t *rbsp_buffer, + uint8_t *ebsp, + uint64_t ebsp_size +) +{ + uint64_t rbsp_size; + int err = nalu_import_rbsp_from_ebsp( bits, rbsp_buffer, &rbsp_size, ebsp, ebsp_size ); + if( err < 0 ) + return err; + uint8_t *rbsp_start = rbsp_buffer; + uint64_t rbsp_pos = 0; + do + { + /* sei_message() */ + uint32_t payloadType = 0; + for( uint8_t temp = lsmash_bits_get( bits, 8 ); ; temp = lsmash_bits_get( bits, 8 ) ) + { + /* 0xff : ff_byte + * otherwise: last_payload_type_byte */ + payloadType += temp; + ++rbsp_pos; + if( temp != 0xff ) + break; + } + uint32_t payloadSize = 0; + for( uint8_t temp = lsmash_bits_get( bits, 8 ); ; temp = lsmash_bits_get( bits, 8 ) ) + { + /* 0xff : ff_byte + * otherwise: last_payload_size_byte */ + payloadSize += temp; + ++rbsp_pos; + if( temp != 0xff ) + break; + } + if( payloadType == 1 ) + { + /* pic_timing */ + h264_hrd_t *hrd = sps ? &sps->vui.hrd : NULL; + if( !hrd ) + goto skip_sei_message; /* Any active SPS is not found. */ + sei->pic_timing.present = 1; + if( hrd->CpbDpbDelaysPresentFlag ) + { + lsmash_bits_get( bits, hrd->cpb_removal_delay_length ); /* cpb_removal_delay */ + lsmash_bits_get( bits, hrd->dpb_output_delay_length ); /* dpb_output_delay */ + } + if( sps->vui.pic_struct_present_flag ) + { + sei->pic_timing.pic_struct = lsmash_bits_get( bits, 4 ); + /* Skip the remaining bits. */ + uint32_t remaining_bits = payloadSize * 8 - 4; + if( hrd->CpbDpbDelaysPresentFlag ) + remaining_bits -= hrd->cpb_removal_delay_length + + hrd->dpb_output_delay_length; + lsmash_bits_get( bits, remaining_bits ); + } + } + else if( payloadType == 3 ) + { + /* filler_payload + * 'avc1' and 'avc2' samples are forbidden to contain this. */ + return LSMASH_ERR_PATCH_WELCOME; + } + else if( payloadType == 6 ) + { + /* recovery_point */ + sei->recovery_point.present = 1; + sei->recovery_point.random_accessible = 1; + sei->recovery_point.recovery_frame_cnt = nalu_get_exp_golomb_ue( bits ); + lsmash_bits_get( bits, 1 ); /* exact_match_flag */ + sei->recovery_point.broken_link_flag = lsmash_bits_get( bits, 1 ); + lsmash_bits_get( bits, 2 ); /* changing_slice_group_idc */ + } + else + { +skip_sei_message: + lsmash_bits_get( bits, payloadSize * 8 ); + } + lsmash_bits_get_align( bits ); + rbsp_pos += payloadSize; + if( rbsp_pos > rbsp_size ) + { + rbsp_pos = rbsp_size; + if( *(rbsp_start + rbsp_pos) != 0x80 ) + { + lsmash_log( NULL, LSMASH_LOG_ERROR, "Invalid payloadSize is there within H.264/AVC sei_message().\n" ); + return LSMASH_ERR_INVALID_DATA; + } + else + { + /* The last payloadSize is invalid but it probably can do recovery, so just log warning and break loop here. */ + lsmash_log( NULL, LSMASH_LOG_WARNING, "Invalid payloadSize is there within H.264/AVC sei_message().\n" ); + break; /* redundant but for readability */ + } + } + } while( *(rbsp_start + rbsp_pos) != 0x80 ); /* All SEI messages are byte aligned at their end. + * Therefore, 0x80 shall be rbsp_trailing_bits(). */ + lsmash_bits_empty( bits ); + return bits->bs->error ? LSMASH_ERR_NAMELESS : 0; +} + +static int h264_parse_slice_header +( + h264_info_t *info, + h264_nalu_header_t *nuh +) +{ + h264_slice_info_t *slice = &info->slice; + memset( slice, 0, sizeof(h264_slice_info_t) ); + /* slice_header() */ + lsmash_bits_t *bits = info->bits; + nalu_get_exp_golomb_ue( bits ); /* first_mb_in_slice */ + uint8_t slice_type = slice->type = nalu_get_exp_golomb_ue( bits ); + if( (uint64_t)slice->type > 9 ) + return LSMASH_ERR_INVALID_DATA; + if( slice_type > 4 ) + slice_type = slice->type -= 5; + uint64_t pic_parameter_set_id = nalu_get_exp_golomb_ue( bits ); + if( pic_parameter_set_id > 255 ) + return LSMASH_ERR_INVALID_DATA; + slice->pic_parameter_set_id = pic_parameter_set_id; + h264_pps_t *pps = h264_get_pps( info->pps_list, pic_parameter_set_id ); + if( !pps ) + return LSMASH_ERR_NAMELESS; + h264_sps_t *sps = h264_get_sps( info->sps_list, pps->seq_parameter_set_id ); + if( !sps ) + return LSMASH_ERR_NAMELESS; + slice->seq_parameter_set_id = pps->seq_parameter_set_id; + slice->nal_ref_idc = nuh->nal_ref_idc; + slice->IdrPicFlag = (nuh->nal_unit_type == H264_NALU_TYPE_SLICE_IDR); + slice->pic_order_cnt_type = sps->pic_order_cnt_type; + if( (slice->IdrPicFlag || sps->max_num_ref_frames == 0) && slice_type != 2 && slice_type != 4 ) + return LSMASH_ERR_INVALID_DATA; + if( sps->separate_colour_plane_flag ) + lsmash_bits_get( bits, 2 ); /* colour_plane_id */ + uint64_t frame_num = lsmash_bits_get( bits, sps->log2_max_frame_num ); + if( frame_num >= (1ULL << sps->log2_max_frame_num) || (slice->IdrPicFlag && frame_num) ) + return LSMASH_ERR_INVALID_DATA; + slice->frame_num = frame_num; + if( !sps->frame_mbs_only_flag ) + { + slice->field_pic_flag = lsmash_bits_get( bits, 1 ); + if( slice->field_pic_flag ) + slice->bottom_field_flag = lsmash_bits_get( bits, 1 ); + } + if( slice->IdrPicFlag ) + { + uint64_t idr_pic_id = nalu_get_exp_golomb_ue( bits ); + if( idr_pic_id > 65535 ) + return LSMASH_ERR_INVALID_DATA; + slice->idr_pic_id = idr_pic_id; + } + if( sps->pic_order_cnt_type == 0 ) + { + uint64_t pic_order_cnt_lsb = lsmash_bits_get( bits, sps->log2_max_pic_order_cnt_lsb ); + if( pic_order_cnt_lsb >= sps->MaxPicOrderCntLsb ) + return LSMASH_ERR_INVALID_DATA; + slice->pic_order_cnt_lsb = pic_order_cnt_lsb; + if( pps->bottom_field_pic_order_in_frame_present_flag && !slice->field_pic_flag ) + slice->delta_pic_order_cnt_bottom = nalu_get_exp_golomb_se( bits ); + } + else if( sps->pic_order_cnt_type == 1 && !sps->delta_pic_order_always_zero_flag ) + { + slice->delta_pic_order_cnt[0] = nalu_get_exp_golomb_se( bits ); + if( pps->bottom_field_pic_order_in_frame_present_flag && !slice->field_pic_flag ) + slice->delta_pic_order_cnt[1] = nalu_get_exp_golomb_se( bits ); + } + if( pps->redundant_pic_cnt_present_flag ) + { + uint64_t redundant_pic_cnt = nalu_get_exp_golomb_ue( bits ); + if( redundant_pic_cnt > 127 ) + return LSMASH_ERR_INVALID_DATA; + slice->has_redundancy = !!redundant_pic_cnt; + } + if( slice_type == H264_SLICE_TYPE_B ) + lsmash_bits_get( bits, 1 ); + uint64_t num_ref_idx_l0_active_minus1 = pps->num_ref_idx_l0_default_active_minus1; + uint64_t num_ref_idx_l1_active_minus1 = pps->num_ref_idx_l1_default_active_minus1; + if( slice_type == H264_SLICE_TYPE_P || slice_type == H264_SLICE_TYPE_SP || slice_type == H264_SLICE_TYPE_B ) + { + if( lsmash_bits_get( bits, 1 ) ) /* num_ref_idx_active_override_flag */ + { + num_ref_idx_l0_active_minus1 = nalu_get_exp_golomb_ue( bits ); + if( num_ref_idx_l0_active_minus1 > 31 ) + return LSMASH_ERR_INVALID_DATA; + if( slice_type == H264_SLICE_TYPE_B ) + { + num_ref_idx_l1_active_minus1 = nalu_get_exp_golomb_ue( bits ); + if( num_ref_idx_l1_active_minus1 > 31 ) + return LSMASH_ERR_INVALID_DATA; + } + } + } + if( nuh->nal_unit_type == H264_NALU_TYPE_SLICE_EXT + || nuh->nal_unit_type == H264_NALU_TYPE_SLICE_EXT_DVC ) + { + return LSMASH_ERR_PATCH_WELCOME; /* No support of MVC yet */ +#if 0 + /* ref_pic_list_mvc_modification() */ + if( slice_type == H264_SLICE_TYPE_P || slice_type == H264_SLICE_TYPE_B || slice_type == H264_SLICE_TYPE_SP ) + { + for( int i = 0; i < 1 + (slice_type == H264_SLICE_TYPE_B); i++ ) + { + if( lsmash_bits_get( bits, 1 ) ) /* (S)P and B: ref_pic_list_modification_flag_l0 + * B: ref_pic_list_modification_flag_l1 */ + { + uint64_t modification_of_pic_nums_idc; + do + { + modification_of_pic_nums_idc = nalu_get_exp_golomb_ue( bits ); +#if 0 + if( modification_of_pic_nums_idc == 0 || modification_of_pic_nums_idc == 1 ) + nalu_get_exp_golomb_ue( bits ); /* abs_diff_pic_num_minus1 */ + else if( modification_of_pic_nums_idc == 2 ) + nalu_get_exp_golomb_ue( bits ); /* long_term_pic_num */ + else if( modification_of_pic_nums_idc == 4 || modification_of_pic_nums_idc == 5 ) + nalu_get_exp_golomb_ue( bits ); /* abs_diff_view_idx_minus1 */ +#else + if( modification_of_pic_nums_idc != 3 ) + nalu_get_exp_golomb_ue( bits ); /* abs_diff_pic_num_minus1, long_term_pic_num or abs_diff_view_idx_minus1 */ +#endif + } while( modification_of_pic_nums_idc != 3 ); + } + } + } +#endif + } + else + { + /* ref_pic_list_modification() */ + if( slice_type == H264_SLICE_TYPE_P || slice_type == H264_SLICE_TYPE_B || slice_type == H264_SLICE_TYPE_SP ) + { + for( int i = 0; i < 1 + (slice_type == H264_SLICE_TYPE_B); i++ ) + { + if( lsmash_bits_get( bits, 1 ) ) /* (S)P and B: ref_pic_list_modification_flag_l0 + * B: ref_pic_list_modification_flag_l1 */ + { + uint64_t modification_of_pic_nums_idc; + do + { + modification_of_pic_nums_idc = nalu_get_exp_golomb_ue( bits ); +#if 0 + if( modification_of_pic_nums_idc == 0 || modification_of_pic_nums_idc == 1 ) + nalu_get_exp_golomb_ue( bits ); /* abs_diff_pic_num_minus1 */ + else if( modification_of_pic_nums_idc == 2 ) + nalu_get_exp_golomb_ue( bits ); /* long_term_pic_num */ +#else + if( modification_of_pic_nums_idc != 3 ) + nalu_get_exp_golomb_ue( bits ); /* abs_diff_pic_num_minus1 or long_term_pic_num */ +#endif + } while( modification_of_pic_nums_idc != 3 ); + } + } + } + } + if( (pps->weighted_pred_flag && (slice_type == H264_SLICE_TYPE_P || slice_type == H264_SLICE_TYPE_SP)) + || (pps->weighted_bipred_idc == 1 && slice_type == H264_SLICE_TYPE_B) ) + { + /* pred_weight_table() */ + nalu_get_exp_golomb_ue( bits ); /* luma_log2_weight_denom */ + if( sps->ChromaArrayType ) + nalu_get_exp_golomb_ue( bits ); /* chroma_log2_weight_denom */ + for( uint8_t i = 0; i <= num_ref_idx_l0_active_minus1; i++ ) + { + if( lsmash_bits_get( bits, 1 ) ) /* luma_weight_l0_flag */ + { + nalu_get_exp_golomb_se( bits ); /* luma_weight_l0[i] */ + nalu_get_exp_golomb_se( bits ); /* luma_offset_l0[i] */ + } + if( sps->ChromaArrayType + && lsmash_bits_get( bits, 1 ) /* chroma_weight_l0_flag */ ) + for( int j = 0; j < 2; j++ ) + { + nalu_get_exp_golomb_se( bits ); /* chroma_weight_l0[i][j]*/ + nalu_get_exp_golomb_se( bits ); /* chroma_offset_l0[i][j] */ + } + } + if( slice_type == H264_SLICE_TYPE_B ) + for( uint8_t i = 0; i <= num_ref_idx_l1_active_minus1; i++ ) + { + if( lsmash_bits_get( bits, 1 ) ) /* luma_weight_l1_flag */ + { + nalu_get_exp_golomb_se( bits ); /* luma_weight_l1[i] */ + nalu_get_exp_golomb_se( bits ); /* luma_offset_l1[i] */ + } + if( sps->ChromaArrayType + && lsmash_bits_get( bits, 1 ) /* chroma_weight_l1_flag */ ) + for( int j = 0; j < 2; j++ ) + { + nalu_get_exp_golomb_se( bits ); /* chroma_weight_l1[i][j]*/ + nalu_get_exp_golomb_se( bits ); /* chroma_offset_l1[i][j] */ + } + } + } + if( nuh->nal_ref_idc ) + { + /* dec_ref_pic_marking() */ + if( slice->IdrPicFlag ) + { + lsmash_bits_get( bits, 1 ); /* no_output_of_prior_pics_flag */ + lsmash_bits_get( bits, 1 ); /* long_term_reference_flag */ + } + else if( lsmash_bits_get( bits, 1 ) ) /* adaptive_ref_pic_marking_mode_flag */ + { + uint64_t memory_management_control_operation; + do + { + memory_management_control_operation = nalu_get_exp_golomb_ue( bits ); + if( memory_management_control_operation ) + { + if( memory_management_control_operation == 5 ) + slice->has_mmco5 = 1; + else + { + nalu_get_exp_golomb_ue( bits ); + if( memory_management_control_operation == 3 ) + nalu_get_exp_golomb_ue( bits ); + } + } + } while( memory_management_control_operation ); + } + } + /* We needn't read more if not slice data partition A. + * Skip slice_data() and rbsp_slice_trailing_bits(). */ + if( nuh->nal_unit_type == H264_NALU_TYPE_SLICE_DP_A ) + { + if( pps->entropy_coding_mode_flag && slice_type != H264_SLICE_TYPE_I && slice_type != H264_SLICE_TYPE_SI ) + nalu_get_exp_golomb_ue( bits ); /* cabac_init_idc */ + nalu_get_exp_golomb_se( bits ); /* slice_qp_delta */ + if( slice_type == H264_SLICE_TYPE_SP || slice_type == H264_SLICE_TYPE_SI ) + { + if( slice_type == H264_SLICE_TYPE_SP ) + lsmash_bits_get( bits, 1 ); /* sp_for_switch_flag */ + nalu_get_exp_golomb_se( bits ); /* slice_qs_delta */ + } + if( pps->deblocking_filter_control_present_flag + && nalu_get_exp_golomb_ue( bits ) != 1 /* disable_deblocking_filter_idc */ ) + { + int64_t slice_alpha_c0_offset_div2 = nalu_get_exp_golomb_se( bits ); + if( slice_alpha_c0_offset_div2 < -6 || slice_alpha_c0_offset_div2 > 6 ) + return LSMASH_ERR_INVALID_DATA; + int64_t slice_beta_offset_div2 = nalu_get_exp_golomb_se( bits ); + if( slice_beta_offset_div2 < -6 || slice_beta_offset_div2 > 6 ) + return LSMASH_ERR_INVALID_DATA; + } + if( pps->num_slice_groups_minus1 + && (pps->slice_group_map_type == 3 || pps->slice_group_map_type == 4 || pps->slice_group_map_type == 5) ) + { + uint64_t temp = ((uint64_t)sps->PicSizeInMapUnits - 1) / pps->SliceGroupChangeRate + 1; + uint64_t slice_group_change_cycle = lsmash_bits_get( bits, lsmash_ceil_log2( temp + 1 ) ); + if( slice_group_change_cycle > temp ) + return LSMASH_ERR_INVALID_DATA; + } + /* end of slice_header() */ + slice->slice_id = nalu_get_exp_golomb_ue( bits ); + h264_slice_info_t *slice_part = h264_get_slice_info( info->slice_list, slice->slice_id ); + if( !slice_part ) + return LSMASH_ERR_NAMELESS; + *slice_part = *slice; + } + lsmash_bits_empty( bits ); + if( bits->bs->error ) + return LSMASH_ERR_NAMELESS; + info->sps = *sps; + info->pps = *pps; + return 0; +} + +int h264_parse_slice +( + h264_info_t *info, + h264_nalu_header_t *nuh, + uint8_t *rbsp_buffer, + uint8_t *ebsp, + uint64_t ebsp_size +) +{ + lsmash_bits_t *bits = info->bits; + uint64_t size = nuh->nal_unit_type == H264_NALU_TYPE_SLICE_IDR || nuh->nal_ref_idc == 0 + ? LSMASH_MIN( ebsp_size, 100 ) + : LSMASH_MIN( ebsp_size, 1000 ); + uint64_t rbsp_size; + int err = nalu_import_rbsp_from_ebsp( bits, rbsp_buffer, &rbsp_size, ebsp, size ); + if( err < 0 ) + return err; + if( nuh->nal_unit_type != H264_NALU_TYPE_SLICE_DP_B + && nuh->nal_unit_type != H264_NALU_TYPE_SLICE_DP_C ) + return h264_parse_slice_header( info, nuh ); + /* slice_data_partition_b_layer_rbsp() or slice_data_partition_c_layer_rbsp() */ + uint64_t slice_id = nalu_get_exp_golomb_ue( bits ); + h264_slice_info_t *slice = h264_get_slice_info( info->slice_list, slice_id ); + if( !slice ) + return LSMASH_ERR_NAMELESS; + h264_pps_t *pps = h264_get_pps( info->pps_list, slice->pic_parameter_set_id ); + if( !pps ) + return LSMASH_ERR_NAMELESS; + h264_sps_t *sps = h264_get_sps( info->sps_list, pps->seq_parameter_set_id ); + if( !sps ) + return LSMASH_ERR_NAMELESS; + slice->seq_parameter_set_id = pps->seq_parameter_set_id; + if( sps->separate_colour_plane_flag ) + lsmash_bits_get( bits, 2 ); /* colour_plane_id */ + if( pps->redundant_pic_cnt_present_flag ) + { + uint64_t redundant_pic_cnt = nalu_get_exp_golomb_ue( bits ); + if( redundant_pic_cnt > 127 ) + return LSMASH_ERR_INVALID_DATA; + slice->has_redundancy = !!redundant_pic_cnt; + } + /* Skip slice_data() and rbsp_slice_trailing_bits(). */ + lsmash_bits_empty( bits ); + if( bits->bs->error ) + return LSMASH_ERR_NAMELESS; + info->sps = *sps; + info->pps = *pps; + return 0; +} + +static int h264_get_sps_id +( + uint8_t *ps_ebsp, + uint32_t ps_ebsp_length, + uint8_t *ps_id +) +{ + /* max number of bits of sps_id = 11: 0b000001XXXXX + * (24 + 11 - 1) / 8 + 1 = 5 bytes + * Why +1? Because there might be an emulation_prevention_three_byte. */ + lsmash_bits_t bits = { 0 }; + lsmash_bs_t bs = { 0 }; + uint8_t rbsp_buffer[6]; + uint8_t buffer [6]; + bs.buffer.data = buffer; + bs.buffer.alloc = 6; + lsmash_bits_init( &bits, &bs ); + uint64_t rbsp_size; + int err = nalu_import_rbsp_from_ebsp( &bits, rbsp_buffer, &rbsp_size, ps_ebsp, LSMASH_MIN( ps_ebsp_length, 6 ) ); + if( err < 0 ) + return err; + lsmash_bits_get( &bits, 24 ); /* profile_idc, constraint_set_flags and level_idc */ + uint64_t sec_parameter_set_id = nalu_get_exp_golomb_ue( &bits ); + if( sec_parameter_set_id > 31 ) + return LSMASH_ERR_INVALID_DATA; + *ps_id = sec_parameter_set_id; + return bs.error ? LSMASH_ERR_NAMELESS : 0; +} + +static int h264_get_pps_id +( + uint8_t *ps_ebsp, + uint32_t ps_ebsp_length, + uint8_t *ps_id +) +{ + /* max number of bits of pps_id = 17: 0b000000001XXXXXXXX + * (17 - 1) / 8 + 1 = 3 bytes + * Why +1? Because there might be an emulation_prevention_three_byte. */ + lsmash_bits_t bits = { 0 }; + lsmash_bs_t bs = { 0 }; + uint8_t rbsp_buffer[4]; + uint8_t buffer [4]; + bs.buffer.data = buffer; + bs.buffer.alloc = 4; + lsmash_bits_init( &bits, &bs ); + uint64_t rbsp_size; + int err = nalu_import_rbsp_from_ebsp( &bits, rbsp_buffer, &rbsp_size, ps_ebsp, LSMASH_MIN( ps_ebsp_length, 4 ) ); + if( err < 0 ) + return err; + uint64_t pic_parameter_set_id = nalu_get_exp_golomb_ue( &bits ); + if( pic_parameter_set_id > 255 ) + return LSMASH_ERR_INVALID_DATA; + *ps_id = pic_parameter_set_id; + return bs.error ? LSMASH_ERR_NAMELESS : 0; +} + +static inline int h264_get_ps_id +( + uint8_t *ps_ebsp, + uint32_t ps_ebsp_length, + uint8_t *ps_id, + lsmash_h264_parameter_set_type ps_type +) +{ + int (*get_ps_id)( uint8_t *ps_ebsp, uint32_t ps_ebsp_length, uint8_t *ps_id ) + = ps_type == H264_PARAMETER_SET_TYPE_SPS ? h264_get_sps_id + : ps_type == H264_PARAMETER_SET_TYPE_PPS ? h264_get_pps_id + : NULL; + return get_ps_id ? get_ps_id( ps_ebsp, ps_ebsp_length, ps_id ) : LSMASH_ERR_INVALID_DATA; +} + +static inline lsmash_entry_list_t *h264_get_parameter_set_list +( + lsmash_h264_specific_parameters_t *param, + lsmash_h264_parameter_set_type ps_type +) +{ + if( !param->parameter_sets ) + return NULL; + return ps_type == H264_PARAMETER_SET_TYPE_SPS ? param->parameter_sets->sps_list + : ps_type == H264_PARAMETER_SET_TYPE_PPS ? param->parameter_sets->pps_list + : ps_type == H264_PARAMETER_SET_TYPE_SPSEXT ? param->parameter_sets->spsext_list + : NULL; +} + +static lsmash_entry_t *h264_get_ps_entry_from_param +( + lsmash_h264_specific_parameters_t *param, + lsmash_h264_parameter_set_type ps_type, + uint8_t ps_id +) +{ + int (*get_ps_id)( uint8_t *ps_ebsp, uint32_t ps_ebsp_length, uint8_t *ps_id ) + = ps_type == H264_PARAMETER_SET_TYPE_SPS ? h264_get_sps_id + : ps_type == H264_PARAMETER_SET_TYPE_PPS ? h264_get_pps_id + : NULL; + if( !get_ps_id ) + return NULL; + lsmash_entry_list_t *ps_list = h264_get_parameter_set_list( param, ps_type ); + if( !ps_list ) + return NULL; + for( lsmash_entry_t *entry = ps_list->head; entry; entry = entry->next ) + { + isom_dcr_ps_entry_t *ps = (isom_dcr_ps_entry_t *)entry->data; + if( !ps ) + return NULL; + uint8_t param_ps_id; + if( get_ps_id( ps->nalUnit + 1, ps->nalUnitLength - 1, ¶m_ps_id ) < 0 ) + return NULL; + if( ps_id == param_ps_id ) + return entry; + } + return NULL; +} + +static inline void h264_update_picture_type +( + h264_picture_info_t *picture, + h264_slice_info_t *slice +) +{ + if( picture->type == H264_PICTURE_TYPE_I_P ) + { + if( slice->type == H264_SLICE_TYPE_B ) + picture->type = H264_PICTURE_TYPE_I_P_B; + else if( slice->type == H264_SLICE_TYPE_SI || slice->type == H264_SLICE_TYPE_SP ) + picture->type = H264_PICTURE_TYPE_I_SI_P_SP; + } + else if( picture->type == H264_PICTURE_TYPE_I_P_B ) + { + if( slice->type != H264_SLICE_TYPE_P && slice->type != H264_SLICE_TYPE_B && slice->type != H264_SLICE_TYPE_I ) + picture->type = H264_PICTURE_TYPE_I_SI_P_SP_B; + } + else if( picture->type == H264_PICTURE_TYPE_I ) + { + if( slice->type == H264_SLICE_TYPE_P ) + picture->type = H264_PICTURE_TYPE_I_P; + else if( slice->type == H264_SLICE_TYPE_B ) + picture->type = H264_PICTURE_TYPE_I_P_B; + else if( slice->type == H264_SLICE_TYPE_SI ) + picture->type = H264_PICTURE_TYPE_I_SI; + else if( slice->type == H264_SLICE_TYPE_SP ) + picture->type = H264_PICTURE_TYPE_I_SI_P_SP; + } + else if( picture->type == H264_PICTURE_TYPE_SI_SP ) + { + if( slice->type == H264_SLICE_TYPE_P || slice->type == H264_SLICE_TYPE_I ) + picture->type = H264_PICTURE_TYPE_I_SI_P_SP; + else if( slice->type == H264_SLICE_TYPE_B ) + picture->type = H264_PICTURE_TYPE_I_SI_P_SP_B; + } + else if( picture->type == H264_PICTURE_TYPE_SI ) + { + if( slice->type == H264_SLICE_TYPE_P ) + picture->type = H264_PICTURE_TYPE_I_SI_P_SP; + else if( slice->type == H264_SLICE_TYPE_B ) + picture->type = H264_PICTURE_TYPE_I_SI_P_SP_B; + else if( slice->type != H264_SLICE_TYPE_I ) + picture->type = H264_PICTURE_TYPE_I_SI; + else if( slice->type == H264_SLICE_TYPE_SP ) + picture->type = H264_PICTURE_TYPE_SI_SP; + } + else if( picture->type == H264_PICTURE_TYPE_I_SI ) + { + if( slice->type == H264_SLICE_TYPE_P || slice->type == H264_SLICE_TYPE_SP ) + picture->type = H264_PICTURE_TYPE_I_SI_P_SP; + else if( slice->type == H264_SLICE_TYPE_B ) + picture->type = H264_PICTURE_TYPE_I_SI_P_SP_B; + } + else if( picture->type == H264_PICTURE_TYPE_I_SI_P_SP ) + { + if( slice->type == H264_SLICE_TYPE_B ) + picture->type = H264_PICTURE_TYPE_I_SI_P_SP_B; + } + else if( picture->type == H264_PICTURE_TYPE_NONE ) + { + if( slice->type == H264_SLICE_TYPE_P ) + picture->type = H264_PICTURE_TYPE_I_P; + else if( slice->type == H264_SLICE_TYPE_B ) + picture->type = H264_PICTURE_TYPE_I_P_B; + else if( slice->type == H264_SLICE_TYPE_I ) + picture->type = H264_PICTURE_TYPE_I; + else if( slice->type == H264_SLICE_TYPE_SI ) + picture->type = H264_PICTURE_TYPE_SI; + else if( slice->type == H264_SLICE_TYPE_SP ) + picture->type = H264_PICTURE_TYPE_SI_SP; + } +#if 0 + fprintf( stderr, "Picture type = %s\n", picture->type == H264_PICTURE_TYPE_I_P ? "P" + : picture->type == H264_PICTURE_TYPE_I_P_B ? "B" + : picture->type == H264_PICTURE_TYPE_I ? "I" + : picture->type == H264_PICTURE_TYPE_SI ? "SI" + : picture->type == H264_PICTURE_TYPE_I_SI ? "SI" + : "SP" ); +#endif +} + +/* Shall be called at least once per picture. */ +void h264_update_picture_info_for_slice +( + h264_info_t *info, + h264_picture_info_t *picture, + h264_slice_info_t *slice +) +{ + assert( info ); + picture->has_mmco5 |= slice->has_mmco5; + picture->has_redundancy |= slice->has_redundancy; + picture->has_primary |= !slice->has_redundancy; + h264_update_picture_type( picture, slice ); + /* Mark 'used' on active parameter sets. */ + uint8_t ps_id[2] = { slice->seq_parameter_set_id, slice->pic_parameter_set_id }; + for( int i = 0; i < 2; i++ ) + { + lsmash_h264_parameter_set_type ps_type = (lsmash_h264_parameter_set_type)i; + lsmash_entry_t *entry = h264_get_ps_entry_from_param( &info->avcC_param, ps_type, ps_id[i] ); + if( entry && entry->data ) + { + isom_dcr_ps_entry_t *ps = (isom_dcr_ps_entry_t *)entry->data; + if( ps->unused ) + lsmash_append_h264_parameter_set( &info->avcC_param, ps_type, ps->nalUnit, ps->nalUnitLength ); + } + } + /* Discard this slice info. */ + slice->present = 0; +} + +/* Shall be called exactly once per picture. */ +void h264_update_picture_info +( + h264_info_t *info, + h264_picture_info_t *picture, + h264_slice_info_t *slice, + h264_sei_t *sei +) +{ + picture->frame_num = slice->frame_num; + picture->pic_order_cnt_lsb = slice->pic_order_cnt_lsb; + picture->delta_pic_order_cnt_bottom = slice->delta_pic_order_cnt_bottom; + picture->delta_pic_order_cnt[0] = slice->delta_pic_order_cnt[0]; + picture->delta_pic_order_cnt[1] = slice->delta_pic_order_cnt[1]; + picture->field_pic_flag = slice->field_pic_flag; + picture->bottom_field_flag = slice->bottom_field_flag; + picture->idr = slice->IdrPicFlag; + picture->pic_parameter_set_id = slice->pic_parameter_set_id; + picture->disposable = (slice->nal_ref_idc == 0); + picture->random_accessible = slice->IdrPicFlag; + h264_update_picture_info_for_slice( info, picture, slice ); + picture->independent = picture->type == H264_PICTURE_TYPE_I || picture->type == H264_PICTURE_TYPE_I_SI; + if( sei->pic_timing.present ) + { + if( sei->pic_timing.pic_struct < 9 ) + { + static const uint8_t DeltaTfiDivisor[9] = { 2, 1, 1, 2, 2, 3, 3, 4, 6 }; + picture->delta = DeltaTfiDivisor[ sei->pic_timing.pic_struct ]; + } + else + /* Reserved values in the spec we refer to. */ + picture->delta = picture->field_pic_flag ? 1 : 2; + sei->pic_timing.present = 0; + } + else + picture->delta = picture->field_pic_flag ? 1 : 2; + if( sei->recovery_point.present ) + { + picture->random_accessible |= sei->recovery_point.random_accessible; + picture->broken_link_flag |= sei->recovery_point.broken_link_flag; + picture->recovery_frame_cnt = sei->recovery_point.recovery_frame_cnt; + sei->recovery_point.present = 0; + } +} + +int h264_find_au_delimit_by_slice_info +( + h264_slice_info_t *slice, + h264_slice_info_t *prev_slice +) +{ + if( slice->frame_num != prev_slice->frame_num + || ((slice->pic_order_cnt_type == 0 && prev_slice->pic_order_cnt_type == 0) + && (slice->pic_order_cnt_lsb != prev_slice->pic_order_cnt_lsb + || slice->delta_pic_order_cnt_bottom != prev_slice->delta_pic_order_cnt_bottom)) + || ((slice->pic_order_cnt_type == 1 && prev_slice->pic_order_cnt_type == 1) + && (slice->delta_pic_order_cnt[0] != prev_slice->delta_pic_order_cnt[0] + || slice->delta_pic_order_cnt[1] != prev_slice->delta_pic_order_cnt[1])) + || slice->field_pic_flag != prev_slice->field_pic_flag + || slice->bottom_field_flag != prev_slice->bottom_field_flag + || slice->IdrPicFlag != prev_slice->IdrPicFlag + || slice->pic_parameter_set_id != prev_slice->pic_parameter_set_id + || ((slice->nal_ref_idc == 0 || prev_slice->nal_ref_idc == 0) + && (slice->nal_ref_idc != prev_slice->nal_ref_idc)) + || (slice->IdrPicFlag == 1 && prev_slice->IdrPicFlag == 1 + && slice->idr_pic_id != prev_slice->idr_pic_id) ) + return 1; + return 0; +} + +int h264_find_au_delimit_by_nalu_type +( + uint8_t nalu_type, + uint8_t prev_nalu_type +) +{ + return ((nalu_type >= H264_NALU_TYPE_SEI && nalu_type <= H264_NALU_TYPE_AUD) + || (nalu_type >= H264_NALU_TYPE_PREFIX && nalu_type <= H264_NALU_TYPE_RSV_NVCL18)) + && ((prev_nalu_type >= H264_NALU_TYPE_SLICE_N_IDR && prev_nalu_type <= H264_NALU_TYPE_SLICE_IDR) + || prev_nalu_type == H264_NALU_TYPE_FD || prev_nalu_type == H264_NALU_TYPE_SLICE_AUX); +} + +int h264_supplement_buffer +( + h264_stream_buffer_t *sb, + h264_access_unit_t *au, + uint32_t size +) +{ + lsmash_multiple_buffers_t *bank = lsmash_resize_multiple_buffers( sb->bank, size ); + if( !bank ) + return LSMASH_ERR_MEMORY_ALLOC; + sb->bank = bank; + sb->rbsp = lsmash_withdraw_buffer( bank, 1 ); + if( au && bank->number_of_buffers == 3 ) + { + au->data = lsmash_withdraw_buffer( bank, 2 ); + au->incomplete_data = lsmash_withdraw_buffer( bank, 3 ); + } + return 0; +} + +static void h264_bs_put_parameter_sets +( + lsmash_bs_t *bs, + lsmash_entry_list_t *ps_list, + uint32_t max_ps_count +) +{ + uint32_t ps_count = 0; + for( lsmash_entry_t *entry = ps_list->head; entry && ps_count < max_ps_count; entry = entry->next ) + { + isom_dcr_ps_entry_t *ps = (isom_dcr_ps_entry_t *)entry->data; + if( ps && !ps->unused ) + { + lsmash_bs_put_be16( bs, ps->nalUnitLength ); + lsmash_bs_put_bytes( bs, ps->nalUnitLength, ps->nalUnit ); + } + else + continue; + ++ps_count; + } +} + +uint8_t *lsmash_create_h264_specific_info +( + lsmash_h264_specific_parameters_t *param, + uint32_t *data_length +) +{ + if( !param || !param->parameter_sets || !data_length ) + return NULL; + if( param->lengthSizeMinusOne != 0 && param->lengthSizeMinusOne != 1 && param->lengthSizeMinusOne != 3 ) + return NULL; + static const uint32_t max_ps_count[3] = { 31, 255, 255 }; + lsmash_entry_list_t *ps_list[3] = + { + param->parameter_sets->sps_list, /* SPS */ + param->parameter_sets->pps_list, /* PPS */ + param->parameter_sets->spsext_list /* SPSExt */ + }; + uint32_t ps_count[3] = { 0, 0, 0 }; + /* SPS and PPS are mandatory. */ + if( !ps_list[0] || !ps_list[0]->head || ps_list[0]->entry_count == 0 + || !ps_list[1] || !ps_list[1]->head || ps_list[1]->entry_count == 0 ) + return NULL; + for( int i = 0; i < 3; i++ ) + if( ps_list[i] ) + for( lsmash_entry_t *entry = ps_list[i]->head; entry && ps_count[i] < max_ps_count[i]; entry = entry->next ) + { + isom_dcr_ps_entry_t *ps = (isom_dcr_ps_entry_t *)entry->data; + if( !ps ) + return NULL; + if( ps->unused ) + continue; + ++ps_count[i]; + } + /* Create an AVCConfigurationBox */ + lsmash_bs_t *bs = lsmash_bs_create(); + if( !bs ) + return NULL; + lsmash_bs_put_be32( bs, 0 ); /* box size */ + lsmash_bs_put_be32( bs, ISOM_BOX_TYPE_AVCC.fourcc ); /* box type: 'avcC' */ + lsmash_bs_put_byte( bs, 1 ); /* configurationVersion */ + lsmash_bs_put_byte( bs, param->AVCProfileIndication ); /* AVCProfileIndication */ + lsmash_bs_put_byte( bs, param->profile_compatibility ); /* profile_compatibility */ + lsmash_bs_put_byte( bs, param->AVCLevelIndication ); /* AVCLevelIndication */ + lsmash_bs_put_byte( bs, param->lengthSizeMinusOne | 0xfc ); /* lengthSizeMinusOne */ + lsmash_bs_put_byte( bs, ps_count[0] | 0xe0 ); /* numOfSequenceParameterSets */ + h264_bs_put_parameter_sets( bs, ps_list[0], ps_count[0] ); /* sequenceParameterSetLength + * sequenceParameterSetNALUnit */ + lsmash_bs_put_byte( bs, ps_count[1] ); /* numOfPictureParameterSets */ + h264_bs_put_parameter_sets( bs, ps_list[1], ps_count[1] ); /* pictureParameterSetLength + * pictureParameterSetNALUnit */ + if( H264_REQUIRES_AVCC_EXTENSION( param->AVCProfileIndication ) ) + { + lsmash_bs_put_byte( bs, param->chroma_format | 0xfc ); /* chroma_format */ + lsmash_bs_put_byte( bs, param->bit_depth_luma_minus8 | 0xf8 ); /* bit_depth_luma_minus8 */ + lsmash_bs_put_byte( bs, param->bit_depth_chroma_minus8 | 0xf8 ); /* bit_depth_chroma_minus8 */ + if( ps_list[2] ) + { + lsmash_bs_put_byte( bs, ps_count[2] ); /* numOfSequenceParameterSetExt */ + h264_bs_put_parameter_sets( bs, ps_list[2], ps_count[2] ); /* sequenceParameterSetExtLength + * sequenceParameterSetExtNALUnit */ + } + else /* no sequence parameter set extensions */ + lsmash_bs_put_byte( bs, 0 ); /* numOfSequenceParameterSetExt */ + } + uint8_t *data = lsmash_bs_export_data( bs, data_length ); + lsmash_bs_cleanup( bs ); + /* Update box size. */ + LSMASH_SET_BE32( data, *data_length ); + return data; +} + +static inline int h264_validate_ps_type +( + lsmash_h264_parameter_set_type ps_type, + void *ps_data, + uint32_t ps_length +) +{ + if( !ps_data || ps_length < 2 ) + return LSMASH_ERR_INVALID_DATA; + if( ps_type != H264_PARAMETER_SET_TYPE_SPS + && ps_type != H264_PARAMETER_SET_TYPE_PPS + && ps_type != H264_PARAMETER_SET_TYPE_SPSEXT ) + return LSMASH_ERR_INVALID_DATA; + uint8_t nalu_type = *((uint8_t *)ps_data) & 0x1f; + if( nalu_type != H264_NALU_TYPE_SPS + && nalu_type != H264_NALU_TYPE_PPS + && nalu_type != H264_NALU_TYPE_SPS_EXT ) + return LSMASH_ERR_INVALID_DATA; + if( (ps_type == H264_PARAMETER_SET_TYPE_SPS && nalu_type != H264_NALU_TYPE_SPS) + || (ps_type == H264_PARAMETER_SET_TYPE_PPS && nalu_type != H264_NALU_TYPE_PPS) + || (ps_type == H264_PARAMETER_SET_TYPE_SPSEXT && nalu_type != H264_NALU_TYPE_SPS_EXT) ) + return LSMASH_ERR_INVALID_DATA; + return 0; +} + +static lsmash_dcr_nalu_appendable h264_check_sps_appendable +( + lsmash_bits_t *bits, + uint8_t *rbsp_buffer, + lsmash_h264_specific_parameters_t *param, + uint8_t *ps_data, + uint32_t ps_length, + lsmash_entry_list_t *ps_list +) +{ + h264_sps_t sps; + if( h264_parse_sps_minimally( bits, &sps, rbsp_buffer, ps_data + 1, ps_length - 1 ) < 0 ) + return DCR_NALU_APPEND_ERROR; + lsmash_bits_empty( bits ); + /* FIXME; If the sequence parameter sets are marked with different profiles, + * and the relevant profile compatibility flags are all zero, + * then the stream may need examination to determine which profile, if any, the stream conforms to. + * If the stream is not examined, or the examination reveals that there is no profile to which the stream conforms, + * then the stream must be split into two or more sub-streams with separate configuration records in which these rules can be met. */ +#if 0 + if( sps.profile_idc != param->AVCProfileIndication && (sps->constraint_set_flags & param->profile_compatibility) ) +#else + if( sps.profile_idc != param->AVCProfileIndication ) +#endif + return DCR_NALU_APPEND_NEW_DCR_REQUIRED; + /* The values of chroma_format_idc, bit_depth_luma_minus8 and bit_depth_chroma_minus8 + * must be identical in all SPSs in a single AVC configuration record. */ + if( H264_REQUIRES_AVCC_EXTENSION( param->AVCProfileIndication ) + && (sps.chroma_format_idc != param->chroma_format + || sps.bit_depth_luma_minus8 != param->bit_depth_luma_minus8 + || sps.bit_depth_chroma_minus8 != param->bit_depth_chroma_minus8) ) + return DCR_NALU_APPEND_NEW_DCR_REQUIRED; + /* Forbidden to duplicate SPS that has the same seq_parameter_set_id with different form within the same configuration record. */ + uint8_t sps_id = sps.seq_parameter_set_id; + for( lsmash_entry_t *entry = ps_list->head; entry; entry = entry->next ) + { + isom_dcr_ps_entry_t *ps = (isom_dcr_ps_entry_t *)entry->data; + if( !ps ) + return DCR_NALU_APPEND_ERROR; + if( ps->unused ) + continue; + uint8_t param_sps_id; + if( h264_get_sps_id( ps->nalUnit + 1, ps->nalUnitLength - 1, ¶m_sps_id ) < 0 ) + return DCR_NALU_APPEND_ERROR; + if( sps_id == param_sps_id ) + /* SPS that has the same seq_parameter_set_id already exists with different form. */ + return DCR_NALU_APPEND_NEW_DCR_REQUIRED; + if( entry == ps_list->head ) + { + /* Check if the visual presentation sizes are different. */ + h264_sps_t first_sps; + if( h264_parse_sps_minimally( bits, &first_sps, rbsp_buffer, + ps->nalUnit + 1, + ps->nalUnitLength - 1 ) < 0 ) + return DCR_NALU_APPEND_ERROR; + if( sps.cropped_width != first_sps.cropped_width + || sps.cropped_height != first_sps.cropped_height ) + return DCR_NALU_APPEND_NEW_SAMPLE_ENTRY_REQUIRED; + } + } + return DCR_NALU_APPEND_POSSIBLE; +} + +static lsmash_dcr_nalu_appendable h264_check_pps_appendable +( + uint8_t *ps_data, + uint32_t ps_length, + lsmash_entry_list_t *ps_list +) +{ + uint8_t pps_id; + if( h264_get_pps_id( ps_data + 1, ps_length - 1, &pps_id ) < 0 ) + return DCR_NALU_APPEND_ERROR; + for( lsmash_entry_t *entry = ps_list->head; entry; entry = entry->next ) + { + isom_dcr_ps_entry_t *ps = (isom_dcr_ps_entry_t *)entry->data; + if( !ps ) + return DCR_NALU_APPEND_ERROR; + if( ps->unused ) + continue; + uint8_t param_pps_id; + if( h264_get_pps_id( ps->nalUnit + 1, ps->nalUnitLength - 1, ¶m_pps_id ) < 0 ) + return DCR_NALU_APPEND_ERROR; + if( pps_id == param_pps_id ) + /* PPS that has the same pic_parameter_set_id already exists with different form. */ + return DCR_NALU_APPEND_NEW_DCR_REQUIRED; + } + return DCR_NALU_APPEND_POSSIBLE; +} + +lsmash_dcr_nalu_appendable lsmash_check_h264_parameter_set_appendable +( + lsmash_h264_specific_parameters_t *param, + lsmash_h264_parameter_set_type ps_type, + void *_ps_data, + uint32_t ps_length +) +{ + uint8_t *ps_data = _ps_data; + if( !param ) + return DCR_NALU_APPEND_ERROR; + if( h264_validate_ps_type( ps_type, ps_data, ps_length ) < 0 ) + return DCR_NALU_APPEND_ERROR; + if( ps_type == H264_PARAMETER_SET_TYPE_SPSEXT + && !H264_REQUIRES_AVCC_EXTENSION( param->AVCProfileIndication ) ) + return DCR_NALU_APPEND_ERROR; + /* Check whether the same parameter set already exsits or not. */ + lsmash_entry_list_t *ps_list = h264_get_parameter_set_list( param, ps_type ); + if( !ps_list || !ps_list->head ) + return DCR_NALU_APPEND_POSSIBLE; /* No parameter set */ + switch( nalu_check_same_ps_existence( ps_list, ps_data, ps_length ) ) + { + case 0 : break; + case 1 : return DCR_NALU_APPEND_DUPLICATED; /* The same parameter set already exists. */ + default : return DCR_NALU_APPEND_ERROR; /* An error occured. */ + } + uint32_t ps_count; + if( nalu_get_ps_count( ps_list, &ps_count ) ) + return DCR_NALU_APPEND_ERROR; + if( (ps_type == H264_PARAMETER_SET_TYPE_SPS && ps_count >= 31) + || (ps_type == H264_PARAMETER_SET_TYPE_PPS && ps_count >= 255) + || (ps_type == H264_PARAMETER_SET_TYPE_SPSEXT && ps_count >= 255) ) + return DCR_NALU_APPEND_NEW_DCR_REQUIRED; /* No more appendable parameter sets. */ + if( ps_type == H264_PARAMETER_SET_TYPE_SPSEXT ) + return DCR_NALU_APPEND_POSSIBLE; + /* Check whether a new specific info is needed or not. */ + if( ps_type == H264_PARAMETER_SET_TYPE_PPS ) + /* PPS */ + return h264_check_pps_appendable( ps_data, ps_length, ps_list ); + else + { + /* SPS + * Set up bitstream handler for parse parameter sets. */ + lsmash_bits_t *bits = lsmash_bits_adhoc_create(); + if( !bits ) + return DCR_NALU_APPEND_ERROR; + uint32_t max_ps_length; + uint8_t *rbsp_buffer; + if( nalu_get_max_ps_length( ps_list, &max_ps_length ) < 0 + || (rbsp_buffer = lsmash_malloc( LSMASH_MAX( max_ps_length, ps_length ) )) == NULL ) + { + lsmash_bits_adhoc_cleanup( bits ); + return DCR_NALU_APPEND_ERROR; + } + lsmash_dcr_nalu_appendable appendable = h264_check_sps_appendable( bits, rbsp_buffer, param, ps_data, ps_length, ps_list ); + lsmash_bits_adhoc_cleanup( bits ); + lsmash_free( rbsp_buffer ); + return appendable; + } +} + +static inline void h264_reorder_parameter_set_ascending_id +( + lsmash_h264_specific_parameters_t *param, + lsmash_h264_parameter_set_type ps_type, + lsmash_entry_list_t *ps_list, + uint8_t ps_id +) +{ + lsmash_entry_t *entry = NULL; + if( ps_id ) + for( int i = ps_id - 1; i; i-- ) + { + entry = h264_get_ps_entry_from_param( param, ps_type, i ); + if( entry ) + break; + } + int append_head = 0; + if( !entry ) + { + /* Couldn't find any parameter set with lower identifier. + * Next, find parameter set with upper identifier. */ + int max_ps_id = ps_type == H264_PARAMETER_SET_TYPE_SPS ? 31 : 255; + for( int i = ps_id + 1; i <= max_ps_id; i++ ) + { + entry = h264_get_ps_entry_from_param( param, ps_type, i ); + if( entry ) + break; + } + if( entry ) + append_head = 1; + } + if( !entry ) + return; /* The new entry was appended to the tail. */ + lsmash_entry_t *new_entry = ps_list->tail; + if( append_head ) + { + /* before: entry[i > ps_id] ... -> prev_entry -> new_entry[ps_id] + * after: new_entry[ps_id] -> entry[i > ps_id] -> ... -> prev_entry */ + if( new_entry->prev ) + new_entry->prev->next = NULL; + new_entry->prev = NULL; + entry->prev = new_entry; + new_entry->next = entry; + return; + } + /* before: entry[i < ps_id] -> next_entry -> ... -> prev_entry -> new_entry[ps_id] + * after: entry[i < ps_id] -> new_entry[ps_id] -> next_entry -> ... -> prev_entry */ + if( new_entry->prev ) + new_entry->prev->next = NULL; + new_entry->prev = entry; + new_entry->next = entry->next; + if( entry->next ) + entry->next->prev = new_entry; + entry->next = new_entry; +} + +int lsmash_append_h264_parameter_set +( + lsmash_h264_specific_parameters_t *param, + lsmash_h264_parameter_set_type ps_type, + void *_ps_data, + uint32_t ps_length +) +{ + uint8_t *ps_data = _ps_data; + if( !param || !ps_data || ps_length < 2 ) + return LSMASH_ERR_FUNCTION_PARAM; + if( ps_type != H264_PARAMETER_SET_TYPE_SPS + && ps_type != H264_PARAMETER_SET_TYPE_PPS + && ps_type != H264_PARAMETER_SET_TYPE_SPSEXT ) + return LSMASH_ERR_FUNCTION_PARAM; + if( !param->parameter_sets ) + { + param->parameter_sets = h264_allocate_parameter_sets(); + if( !param->parameter_sets ) + return LSMASH_ERR_MEMORY_ALLOC; + } + lsmash_entry_list_t *ps_list = h264_get_parameter_set_list( param, ps_type ); + if( !ps_list ) + return LSMASH_ERR_NAMELESS; + if( ps_type == H264_PARAMETER_SET_TYPE_SPSEXT ) + { + if( !H264_REQUIRES_AVCC_EXTENSION( param->AVCProfileIndication ) ) + return 0; + isom_dcr_ps_entry_t *ps = isom_create_ps_entry( ps_data, ps_length ); + if( !ps ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( ps_list, ps ) < 0 ) + { + isom_remove_dcr_ps( ps ); + return LSMASH_ERR_MEMORY_ALLOC; + } + return 0; + } + /* Check if the same parameter set identifier already exists. */ + uint8_t ps_id; + int err = h264_get_ps_id( ps_data + 1, ps_length - 1, &ps_id, ps_type ); + if( err < 0 ) + return err; + lsmash_entry_t *entry = h264_get_ps_entry_from_param( param, ps_type, ps_id ); + isom_dcr_ps_entry_t *ps = entry ? (isom_dcr_ps_entry_t *)entry->data : NULL; + if( ps && !ps->unused ) + /* The same parameter set identifier already exists. */ + return LSMASH_ERR_FUNCTION_PARAM; + int invoke_reorder; + if( ps ) + { + /* Reuse an already existed parameter set in the list. */ + ps->unused = 0; + if( ps->nalUnit != ps_data ) + { + /* The same address could be given when called by h264_update_picture_info_for_slice(). */ + lsmash_free( ps->nalUnit ); + ps->nalUnit = ps_data; + } + ps->nalUnitLength = ps_length; + invoke_reorder = 0; + } + else + { + /* Create a new parameter set and append it into the list. */ + ps = isom_create_ps_entry( ps_data, ps_length ); + if( !ps ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( ps_list, ps ) < 0 ) + { + isom_remove_dcr_ps( ps ); + return LSMASH_ERR_MEMORY_ALLOC; + } + invoke_reorder = 1; + } + if( ps_type == H264_PARAMETER_SET_TYPE_SPS ) + { + /* Update specific info with SPS. */ + lsmash_bits_t *bits = lsmash_bits_adhoc_create(); + if( !bits ) + return LSMASH_ERR_MEMORY_ALLOC; + uint8_t *rbsp_buffer = lsmash_malloc( ps_length ); + if( !rbsp_buffer ) + { + lsmash_bits_adhoc_cleanup( bits ); + return LSMASH_ERR_MEMORY_ALLOC; + } + h264_sps_t sps; + err = h264_parse_sps_minimally( bits, &sps, rbsp_buffer, ps_data + 1, ps_length - 1 ); + lsmash_bits_adhoc_cleanup( bits ); + lsmash_free( rbsp_buffer ); + if( err < 0 ) + { + lsmash_list_remove_entry_tail( ps_list ); + return err; + } + if( ps_list->entry_count == 1 ) + param->profile_compatibility = 0xff; + param->AVCProfileIndication = sps.profile_idc; + param->profile_compatibility &= sps.constraint_set_flags; + param->AVCLevelIndication = LSMASH_MAX( param->AVCLevelIndication, sps.level_idc ); + param->chroma_format = sps.chroma_format_idc; + param->bit_depth_luma_minus8 = sps.bit_depth_luma_minus8; + param->bit_depth_chroma_minus8 = sps.bit_depth_chroma_minus8; + } + if( invoke_reorder ) + /* Add a new parameter set in order of ascending parameter set identifier. */ + h264_reorder_parameter_set_ascending_id( param, ps_type, ps_list, ps_id ); + return 0; +} + +int h264_try_to_append_parameter_set +( + h264_info_t *info, + lsmash_h264_parameter_set_type ps_type, + void *_ps_data, + uint32_t ps_length +) +{ + uint8_t *ps_data = _ps_data; + lsmash_dcr_nalu_appendable ret = lsmash_check_h264_parameter_set_appendable( &info->avcC_param, ps_type, ps_data, ps_length ); + lsmash_h264_specific_parameters_t *param; + switch( ret ) + { + case DCR_NALU_APPEND_ERROR : /* Error */ + return LSMASH_ERR_NAMELESS; + case DCR_NALU_APPEND_NEW_DCR_REQUIRED : /* Mulitiple sample description is needed. */ + case DCR_NALU_APPEND_NEW_SAMPLE_ENTRY_REQUIRED : /* Mulitiple sample description is needed. */ + param = &info->avcC_param_next; + info->avcC_pending = 1; + break; + case DCR_NALU_APPEND_POSSIBLE : /* Appendable */ + param = info->avcC_pending ? &info->avcC_param_next : &info->avcC_param; + break; + default : /* No need to append */ + return 0; + } + int err; + switch( ps_type ) + { + case H264_PARAMETER_SET_TYPE_SPS : + if( (err = h264_parse_sps( info, info->buffer.rbsp, ps_data + 1, ps_length - 1 )) < 0 ) + return err; + break; + case H264_PARAMETER_SET_TYPE_PPS : + if( (err = h264_parse_pps( info, info->buffer.rbsp, ps_data + 1, ps_length - 1 )) < 0 ) + return err; + break; + default : + break; + } + return lsmash_append_h264_parameter_set( param, ps_type, ps_data, ps_length ); +} + +static inline int h264_move_dcr_nalu_entry +( + lsmash_h264_specific_parameters_t *dst_data, + lsmash_h264_specific_parameters_t *src_data, + lsmash_h264_parameter_set_type ps_type +) +{ + lsmash_entry_list_t *src_ps_list = h264_get_parameter_set_list( src_data, ps_type ); + lsmash_entry_list_t *dst_ps_list = h264_get_parameter_set_list( dst_data, ps_type ); + assert( src_ps_list && dst_ps_list ); + for( lsmash_entry_t *src_entry = src_ps_list->head; src_entry; src_entry = src_entry->next ) + { + isom_dcr_ps_entry_t *src_ps = (isom_dcr_ps_entry_t *)src_entry->data; + if( !src_ps ) + continue; + int err; + uint8_t src_ps_id; + if( (err = h264_get_ps_id( src_ps->nalUnit + 1, src_ps->nalUnitLength - 1, &src_ps_id, ps_type )) < 0 ) + return err; + lsmash_entry_t *dst_entry; + for( dst_entry = dst_ps_list->head; dst_entry; dst_entry = dst_entry->next ) + { + isom_dcr_ps_entry_t *dst_ps = (isom_dcr_ps_entry_t *)dst_entry->data; + if( !dst_ps ) + continue; + uint8_t dst_ps_id; + if( (err = h264_get_ps_id( dst_ps->nalUnit + 1, dst_ps->nalUnitLength - 1, &dst_ps_id, ps_type )) < 0 ) + return err; + if( dst_ps_id == src_ps_id ) + { + /* Replace the old parameter set with the new one. */ + assert( dst_entry->data != src_entry->data ); + isom_remove_dcr_ps( dst_ps ); + dst_entry->data = src_entry->data; + src_entry->data = NULL; + break; + } + } + if( !dst_entry ) + { + /* Move the parameter set. */ + if( lsmash_list_add_entry( dst_ps_list, src_ps ) < 0 ) + return LSMASH_ERR_MEMORY_ALLOC; + src_entry->data = NULL; + } + } + return 0; +} + +int h264_move_pending_avcC_param +( + h264_info_t *info +) +{ + assert( info ); + if( !info->avcC_pending ) + return 0; + /* Mark 'unused' on parameter sets within the decoder configuration record. */ + for( int i = 0; i < H264_PARAMETER_SET_TYPE_NUM; i++ ) + { + lsmash_entry_list_t *ps_list = h264_get_parameter_set_list( &info->avcC_param, i ); + assert( ps_list ); + for( lsmash_entry_t *entry = ps_list->head; entry; entry = entry->next ) + { + isom_dcr_ps_entry_t *ps = (isom_dcr_ps_entry_t *)entry->data; + if( !ps ) + continue; + ps->unused = 1; + } + } + /* Move the new parameter sets. */ + int err; + if( (err = h264_move_dcr_nalu_entry( &info->avcC_param, &info->avcC_param_next, H264_PARAMETER_SET_TYPE_SPS )) < 0 + || (err = h264_move_dcr_nalu_entry( &info->avcC_param, &info->avcC_param_next, H264_PARAMETER_SET_TYPE_PPS )) < 0 ) + return err; + /* Move to the pending. */ + lsmash_h264_parameter_sets_t *parameter_sets = info->avcC_param.parameter_sets; /* Back up parameter sets. */ + info->avcC_param = info->avcC_param_next; + info->avcC_param.parameter_sets = parameter_sets; + /* No pending avcC. */ + h264_deallocate_parameter_sets( &info->avcC_param_next ); + uint8_t lengthSizeMinusOne = info->avcC_param_next.lengthSizeMinusOne; + memset( &info->avcC_param_next, 0, sizeof(lsmash_h264_specific_parameters_t) ); + info->avcC_param_next.lengthSizeMinusOne = lengthSizeMinusOne; + info->avcC_pending = 0; + return 0; +} + +static int h264_parse_succeeded +( + h264_info_t *info, + lsmash_h264_specific_parameters_t *param +) +{ + int ret; + if( info->sps.present && info->pps.present ) + { + *param = info->avcC_param; + /* Avoid freeing parameter sets. */ + info->avcC_param.parameter_sets = NULL; + ret = 0; + } + else + ret = LSMASH_ERR_INVALID_DATA; + h264_cleanup_parser( info ); + return ret; +} + +static inline int h264_parse_failed +( + h264_info_t *info, + int ret +) +{ + h264_cleanup_parser( info ); + return ret; +} + +int lsmash_setup_h264_specific_parameters_from_access_unit +( + lsmash_h264_specific_parameters_t *param, + uint8_t *data, + uint32_t data_length +) +{ + if( !param || !data || data_length == 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + h264_info_t *info = &(h264_info_t){ { 0 } }; + lsmash_bs_t *bs = &(lsmash_bs_t){ 0 }; + int err = lsmash_bs_set_empty_stream( bs, data, data_length ); + if( err < 0 ) + return err; + uint64_t sc_head_pos = nalu_find_first_start_code( bs ); + if( sc_head_pos == NALU_NO_START_CODE_FOUND ) + return LSMASH_ERR_INVALID_DATA; + else if( sc_head_pos == NALU_IO_ERROR ) + return LSMASH_ERR_IO; + if( (err = h264_setup_parser( info, 1 )) < 0 ) + return h264_parse_failed( info, err ); + h264_stream_buffer_t *sb = &info->buffer; + h264_slice_info_t *slice = &info->slice; + while( 1 ) + { + h264_nalu_header_t nuh; + uint64_t start_code_length; + uint64_t trailing_zero_bytes; + uint64_t nalu_length = h264_find_next_start_code( bs, &nuh, &start_code_length, &trailing_zero_bytes ); + if( nalu_length == NALU_NO_START_CODE_FOUND ) + /* For the last NALU. This NALU already has been parsed. */ + return h264_parse_succeeded( info, param ); + uint8_t nalu_type = nuh.nal_unit_type; + uint64_t next_sc_head_pos = sc_head_pos + + start_code_length + + nalu_length + + trailing_zero_bytes; + if( nalu_type == H264_NALU_TYPE_FD ) + { + /* We don't support streams with both filler and HRD yet. + * Otherwise, just skip filler because elemental streams defined in 14496-15 are forbidden to use filler. */ + if( info->sps.vui.hrd.present ) + return h264_parse_failed( info, LSMASH_ERR_PATCH_WELCOME ); + } + else if( (nalu_type >= H264_NALU_TYPE_SLICE_N_IDR && nalu_type <= H264_NALU_TYPE_SPS_EXT) + || nalu_type == H264_NALU_TYPE_SLICE_AUX ) + { + /* Increase the buffer if needed. */ + uint64_t possible_au_length = NALU_DEFAULT_NALU_LENGTH_SIZE + nalu_length; + if( sb->bank->buffer_size < possible_au_length + && (err = h264_supplement_buffer( sb, NULL, 2 * possible_au_length )) < 0 ) + return h264_parse_failed( info, err ); + /* Get the EBSP of the current NALU here. + * AVC elemental stream defined in 14496-15 can recognize from 0 to 13, and 19 of nal_unit_type. + * We don't support SVC and MVC elemental stream defined in 14496-15 yet. */ + uint8_t *nalu = lsmash_bs_get_buffer_data( bs ) + start_code_length; + if( nalu_type >= H264_NALU_TYPE_SLICE_N_IDR && nalu_type <= H264_NALU_TYPE_SLICE_IDR ) + { + /* VCL NALU (slice) */ + h264_slice_info_t prev_slice = *slice; + if( (err = h264_parse_slice( info, &nuh, sb->rbsp, nalu + nuh.length, nalu_length - nuh.length )) < 0 ) + return h264_parse_failed( info, err ); + if( prev_slice.present ) + { + /* Check whether the AU that contains the previous VCL NALU completed or not. */ + if( h264_find_au_delimit_by_slice_info( slice, &prev_slice ) ) + /* The current NALU is the first VCL NALU of the primary coded picture of an new AU. + * Therefore, the previous slice belongs to that new AU. */ + return h264_parse_succeeded( info, param ); + } + slice->present = 1; + } + else + { + if( h264_find_au_delimit_by_nalu_type( nalu_type, info->prev_nalu_type ) ) + /* The last slice belongs to the AU you want at this time. */ + return h264_parse_succeeded( info, param ); + switch( nalu_type ) + { + case H264_NALU_TYPE_SPS : + if( (err = h264_try_to_append_parameter_set( info, H264_PARAMETER_SET_TYPE_SPS, nalu, nalu_length )) < 0 ) + return h264_parse_failed( info, err ); + break; + case H264_NALU_TYPE_PPS : + if( (err = h264_try_to_append_parameter_set( info, H264_PARAMETER_SET_TYPE_PPS, nalu, nalu_length )) < 0 ) + return h264_parse_failed( info, err ); + break; + case H264_NALU_TYPE_SPS_EXT : + if( (err = h264_try_to_append_parameter_set( info, H264_PARAMETER_SET_TYPE_SPSEXT, nalu, nalu_length )) < 0 ) + return h264_parse_failed( info, err ); + break; + default : + break; + } + } + } + /* Move to the first byte of the next start code. */ + info->prev_nalu_type = nalu_type; + if( lsmash_bs_read_seek( bs, next_sc_head_pos, SEEK_SET ) != next_sc_head_pos ) + return h264_parse_failed( info, LSMASH_ERR_NAMELESS ); + /* Check if no more data to read from the stream. */ + if( !lsmash_bs_is_end( bs, NALU_SHORT_START_CODE_LENGTH ) ) + sc_head_pos = next_sc_head_pos; + else + return h264_parse_succeeded( info, param ); + } +} + +int h264_construct_specific_parameters +( + lsmash_codec_specific_t *dst, + lsmash_codec_specific_t *src +) +{ + assert( dst && dst->data.structured && src && src->data.unstructured ); + if( src->size < ISOM_BASEBOX_COMMON_SIZE + 7 ) + return LSMASH_ERR_INVALID_DATA; + lsmash_h264_specific_parameters_t *param = (lsmash_h264_specific_parameters_t *)dst->data.structured; + uint8_t *data = src->data.unstructured; + uint64_t size = LSMASH_GET_BE32( data ); + data += ISOM_BASEBOX_COMMON_SIZE; + if( size == 1 ) + { + size = LSMASH_GET_BE64( data ); + data += 8; + } + if( size != src->size ) + return LSMASH_ERR_INVALID_DATA; + if( !param->parameter_sets ) + { + param->parameter_sets = h264_allocate_parameter_sets(); + if( !param->parameter_sets ) + return LSMASH_ERR_MEMORY_ALLOC; + } + lsmash_bs_t *bs = lsmash_bs_create(); + if( !bs ) + return LSMASH_ERR_MEMORY_ALLOC; + int err = lsmash_bs_import_data( bs, data, src->size - (data - src->data.unstructured) ); + if( err < 0 ) + goto fail; + if( lsmash_bs_get_byte( bs ) != 1 ) + { + /* We don't support configurationVersion other than 1. */ + err = LSMASH_ERR_INVALID_DATA; + goto fail; + } + param->AVCProfileIndication = lsmash_bs_get_byte( bs ); + param->profile_compatibility = lsmash_bs_get_byte( bs ); + param->AVCLevelIndication = lsmash_bs_get_byte( bs ); + param->lengthSizeMinusOne = lsmash_bs_get_byte( bs ) & 0x03; + uint8_t numOfSequenceParameterSets = lsmash_bs_get_byte( bs ) & 0x1F; + if( numOfSequenceParameterSets + && (err = nalu_get_dcr_ps( bs, param->parameter_sets->sps_list, numOfSequenceParameterSets )) < 0 ) + goto fail; + uint8_t numOfPictureParameterSets = lsmash_bs_get_byte( bs ); + if( numOfPictureParameterSets + && (err = nalu_get_dcr_ps( bs, param->parameter_sets->pps_list, numOfPictureParameterSets )) < 0 ) + goto fail; + if( H264_REQUIRES_AVCC_EXTENSION( param->AVCProfileIndication ) ) + { + param->chroma_format = lsmash_bs_get_byte( bs ) & 0x03; + param->bit_depth_luma_minus8 = lsmash_bs_get_byte( bs ) & 0x07; + param->bit_depth_chroma_minus8 = lsmash_bs_get_byte( bs ) & 0x07; + uint8_t numOfSequenceParameterSetExt = lsmash_bs_get_byte( bs ); + if( numOfSequenceParameterSetExt + && (err = nalu_get_dcr_ps( bs, param->parameter_sets->spsext_list, numOfSequenceParameterSetExt )) < 0 ) + goto fail; + } + lsmash_bs_cleanup( bs ); + return 0; +fail: + lsmash_bs_cleanup( bs ); + return err; +} + +int h264_print_codec_specific +( + FILE *fp, + lsmash_file_t *file, + isom_box_t *box, + int level +) +{ + assert( box->manager & LSMASH_BINARY_CODED_BOX ); + int indent = level; + lsmash_ifprintf( fp, indent++, "[%s: AVC Configuration Box]\n", isom_4cc2str( box->type.fourcc ) ); + lsmash_ifprintf( fp, indent, "position = %"PRIu64"\n", box->pos ); + lsmash_ifprintf( fp, indent, "size = %"PRIu64"\n", box->size ); + uint8_t *data = box->binary; + uint32_t offset = isom_skip_box_common( &data ); + lsmash_bs_t *bs = lsmash_bs_create(); + if( !bs ) + return LSMASH_ERR_MEMORY_ALLOC; + int err = lsmash_bs_import_data( bs, data, box->size - offset ); + if( err < 0 ) + { + lsmash_bs_cleanup( bs ); + return err; + } + lsmash_ifprintf( fp, indent, "configurationVersion = %"PRIu8"\n", lsmash_bs_get_byte( bs ) ); + uint8_t AVCProfileIndication = lsmash_bs_get_byte( bs ); + lsmash_ifprintf( fp, indent, "AVCProfileIndication = %"PRIu8"\n", AVCProfileIndication ); + lsmash_ifprintf( fp, indent, "profile_compatibility = 0x%02"PRIx8"\n", lsmash_bs_get_byte( bs ) ); + lsmash_ifprintf( fp, indent, "AVCLevelIndication = %"PRIu8"\n", lsmash_bs_get_byte( bs ) ); + uint8_t temp8 = lsmash_bs_get_byte( bs ); + lsmash_ifprintf( fp, indent, "reserved = 0x%02"PRIx8"\n", (temp8 >> 2) & 0x3F ); + lsmash_ifprintf( fp, indent, "lengthSizeMinusOne = %"PRIu8"\n", temp8 & 0x03 ); + temp8 = lsmash_bs_get_byte( bs ); + lsmash_ifprintf( fp, indent, "reserved = 0x%02"PRIx8"\n", (temp8 >> 5) & 0x07 ); + uint8_t numOfSequenceParameterSets = temp8 & 0x1f; + lsmash_ifprintf( fp, indent, "numOfSequenceParameterSets = %"PRIu8"\n", numOfSequenceParameterSets ); + for( uint8_t i = 0; i < numOfSequenceParameterSets; i++ ) + { + uint16_t nalUnitLength = lsmash_bs_get_be16( bs ); + lsmash_bs_skip_bytes( bs, nalUnitLength ); + } + uint8_t numOfPictureParameterSets = lsmash_bs_get_byte( bs ); + lsmash_ifprintf( fp, indent, "numOfPictureParameterSets = %"PRIu8"\n", numOfPictureParameterSets ); + for( uint8_t i = 0; i < numOfPictureParameterSets; i++ ) + { + uint16_t nalUnitLength = lsmash_bs_get_be16( bs ); + lsmash_bs_skip_bytes( bs, nalUnitLength ); + } + /* Note: there are too many files, in the world, that don't contain the following fields. */ + if( H264_REQUIRES_AVCC_EXTENSION( AVCProfileIndication ) + && (lsmash_bs_get_pos( bs ) < (box->size - offset)) ) + { + temp8 = lsmash_bs_get_byte( bs ); + lsmash_ifprintf( fp, indent, "reserved = 0x%02"PRIx8"\n", (temp8 >> 2) & 0x3F ); + lsmash_ifprintf( fp, indent, "chroma_format = %"PRIu8"\n", temp8 & 0x03 ); + temp8 = lsmash_bs_get_byte( bs ); + lsmash_ifprintf( fp, indent, "reserved = 0x%02"PRIx8"\n", (temp8 >> 3) & 0x1F ); + lsmash_ifprintf( fp, indent, "bit_depth_luma_minus8 = %"PRIu8"\n", temp8 & 0x7 ); + temp8 = lsmash_bs_get_byte( bs ); + lsmash_ifprintf( fp, indent, "reserved = 0x%02"PRIx8"\n", (temp8 >> 3) & 0x1F ); + lsmash_ifprintf( fp, indent, "bit_depth_chroma_minus8 = %"PRIu8"\n", temp8 & 0x7 ); + lsmash_ifprintf( fp, indent, "numOfSequenceParameterSetExt = %"PRIu8"\n", lsmash_bs_get_byte( bs ) ); + } + lsmash_bs_cleanup( bs ); + return 0; +} + +int h264_copy_codec_specific +( + lsmash_codec_specific_t *dst, + lsmash_codec_specific_t *src +) +{ + assert( src && src->format == LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED && src->data.structured ); + assert( dst && dst->format == LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED && dst->data.structured ); + lsmash_h264_specific_parameters_t *src_data = (lsmash_h264_specific_parameters_t *)src->data.structured; + lsmash_h264_specific_parameters_t *dst_data = (lsmash_h264_specific_parameters_t *)dst->data.structured; + h264_deallocate_parameter_sets( dst_data ); + *dst_data = *src_data; + if( !src_data->parameter_sets ) + return 0; + dst_data->parameter_sets = h264_allocate_parameter_sets(); + if( !dst_data->parameter_sets ) + return LSMASH_ERR_MEMORY_ALLOC; + for( int i = 0; i < 3; i++ ) + { + lsmash_entry_list_t *src_ps_list = h264_get_parameter_set_list( src_data, i ); + lsmash_entry_list_t *dst_ps_list = h264_get_parameter_set_list( dst_data, i ); + assert( src_ps_list && dst_ps_list ); + for( lsmash_entry_t *entry = src_ps_list->head; entry; entry = entry->next ) + { + isom_dcr_ps_entry_t *src_ps = (isom_dcr_ps_entry_t *)entry->data; + if( !src_ps || src_ps->unused ) + continue; + isom_dcr_ps_entry_t *dst_ps = isom_create_ps_entry( src_ps->nalUnit, src_ps->nalUnitLength ); + if( !dst_ps ) + { + h264_deallocate_parameter_sets( dst_data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + if( lsmash_list_add_entry( dst_ps_list, dst_ps ) < 0 ) + { + h264_deallocate_parameter_sets( dst_data ); + isom_remove_dcr_ps( dst_ps ); + return LSMASH_ERR_MEMORY_ALLOC; + } + } + } + return 0; +} + +int h264_print_bitrate +( + FILE *fp, + lsmash_file_t *file, + isom_box_t *box, + int level +) +{ + assert( fp && LSMASH_IS_EXISTING_BOX( file ) && LSMASH_IS_EXISTING_BOX( box ) ); + int indent = level; + lsmash_ifprintf( fp, indent++, "[%s: MPEG-4 Bit Rate Box]\n", isom_4cc2str( box->type.fourcc ) ); + lsmash_ifprintf( fp, indent, "position = %"PRIu64"\n", box->pos ); + lsmash_ifprintf( fp, indent, "size = %"PRIu64"\n", box->size ); + isom_btrt_t *btrt = (isom_btrt_t *)box; + lsmash_ifprintf( fp, indent, "bufferSizeDB = %"PRIu32"\n", btrt->bufferSizeDB ); + lsmash_ifprintf( fp, indent, "maxBitrate = %"PRIu32"\n", btrt->maxBitrate ); + lsmash_ifprintf( fp, indent, "avgBitrate = %"PRIu32"\n", btrt->avgBitrate ); + return 0; +} diff --git a/3rdparty/l-smash/codecs/h264.h b/3rdparty/l-smash/codecs/h264.h new file mode 100644 index 000000000..c5324900a --- /dev/null +++ b/3rdparty/l-smash/codecs/h264.h @@ -0,0 +1,369 @@ +/***************************************************************************** + * h264.h + ***************************************************************************** + * Copyright (C) 2012-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +enum +{ + H264_NALU_TYPE_UNSPECIFIED0 = 0, /* Unspecified */ + H264_NALU_TYPE_SLICE_N_IDR = 1, /* Coded slice of a non-IDR picture */ + H264_NALU_TYPE_SLICE_DP_A = 2, /* Coded slice data partition A */ + H264_NALU_TYPE_SLICE_DP_B = 3, /* Coded slice data partition B */ + H264_NALU_TYPE_SLICE_DP_C = 4, /* Coded slice data partition C */ + H264_NALU_TYPE_SLICE_IDR = 5, /* Coded slice of an IDR picture */ + H264_NALU_TYPE_SEI = 6, /* Supplemental Enhancement Information */ + H264_NALU_TYPE_SPS = 7, /* Sequence Parameter Set */ + H264_NALU_TYPE_PPS = 8, /* Picture Parameter Set */ + H264_NALU_TYPE_AUD = 9, /* Access Unit Delimiter */ + H264_NALU_TYPE_EOS = 10, /* End of Sequence */ + H264_NALU_TYPE_EOB = 11, /* End of Bitstream */ + H264_NALU_TYPE_FD = 12, /* Filler Data */ + H264_NALU_TYPE_SPS_EXT = 13, /* Sequence Parameter Set Extension */ + H264_NALU_TYPE_PREFIX = 14, /* Prefix NAL unit */ + H264_NALU_TYPE_SUBSET_SPS = 15, /* Subset Sequence Parameter Set */ + H264_NALU_TYPE_RSV_NVCL16 = 16, /* Reserved */ + H264_NALU_TYPE_RSV_NVCL17 = 17, /* Reserved */ + H264_NALU_TYPE_RSV_NVCL18 = 18, /* Reserved */ + H264_NALU_TYPE_SLICE_AUX = 19, /* Coded slice of an auxiliary coded picture without partitioning */ + H264_NALU_TYPE_SLICE_EXT = 20, /* Coded slice extension */ + H264_NALU_TYPE_SLICE_EXT_DVC = 21, /* Coded slice extension for depth view components */ + H264_NALU_TYPE_RSV22 = 22, /* Reserved */ + H264_NALU_TYPE_RSV23 = 23, /* Reserved */ + H264_NALU_TYPE_UNSPECIFIED24 = 24, /* Unspecified */ + H264_NALU_TYPE_UNSPECIFIED31 = 31, /* Unspecified */ +}; + +struct lsmash_h264_parameter_sets_tag +{ + /* Each list contains entries as isom_dcr_ps_entry_t. */ + lsmash_entry_list_t sps_list [1]; + lsmash_entry_list_t pps_list [1]; + lsmash_entry_list_t spsext_list[1]; +}; + +typedef struct +{ + unsigned forbidden_zero_bit : 1; + unsigned nal_ref_idc : 2; + unsigned nal_unit_type : 5; + uint8_t length; +} h264_nalu_header_t; + +typedef struct +{ + uint8_t present; + uint8_t CpbDpbDelaysPresentFlag; + uint8_t cpb_removal_delay_length; + uint8_t dpb_output_delay_length; +} h264_hrd_t; + +typedef struct +{ + uint16_t sar_width; + uint16_t sar_height; + uint8_t video_full_range_flag; + uint8_t colour_primaries; + uint8_t transfer_characteristics; + uint8_t matrix_coefficients; + uint32_t num_units_in_tick; + uint32_t time_scale; + uint8_t fixed_frame_rate_flag; + uint8_t pic_struct_present_flag; + h264_hrd_t hrd; +} h264_vui_t; + +typedef struct +{ + uint8_t present; + uint8_t profile_idc; + uint8_t constraint_set_flags; + uint8_t level_idc; + uint8_t seq_parameter_set_id; + uint8_t chroma_format_idc; + uint8_t separate_colour_plane_flag; + uint8_t ChromaArrayType; + uint8_t bit_depth_luma_minus8; + uint8_t bit_depth_chroma_minus8; + uint8_t log2_max_frame_num; + uint8_t pic_order_cnt_type; + uint8_t delta_pic_order_always_zero_flag; + uint8_t num_ref_frames_in_pic_order_cnt_cycle; + uint8_t frame_mbs_only_flag; + int32_t offset_for_non_ref_pic; + int32_t offset_for_top_to_bottom_field; + int32_t offset_for_ref_frame[255]; + int64_t ExpectedDeltaPerPicOrderCntCycle; + uint32_t max_num_ref_frames; + uint32_t MaxFrameNum; + uint32_t log2_max_pic_order_cnt_lsb; + uint32_t MaxPicOrderCntLsb; + uint32_t PicSizeInMapUnits; + uint32_t cropped_width; + uint32_t cropped_height; + h264_vui_t vui; +} h264_sps_t; + +typedef struct +{ + uint8_t present; + uint8_t pic_parameter_set_id; + uint8_t seq_parameter_set_id; + uint8_t entropy_coding_mode_flag; + uint8_t bottom_field_pic_order_in_frame_present_flag; + uint8_t num_slice_groups_minus1; + uint8_t slice_group_map_type; + uint8_t num_ref_idx_l0_default_active_minus1; + uint8_t num_ref_idx_l1_default_active_minus1; + uint8_t weighted_pred_flag; + uint8_t weighted_bipred_idc; + uint8_t deblocking_filter_control_present_flag; + uint8_t redundant_pic_cnt_present_flag; + uint32_t SliceGroupChangeRate; +} h264_pps_t; + +typedef struct +{ + uint8_t present; + uint8_t pic_struct; +} h264_pic_timing_t; + +typedef struct +{ + uint8_t present; + uint8_t random_accessible; + uint8_t broken_link_flag; + uint32_t recovery_frame_cnt; +} h264_recovery_point_t; + +typedef struct +{ + h264_pic_timing_t pic_timing; + h264_recovery_point_t recovery_point; +} h264_sei_t; + +typedef struct +{ + uint8_t present; + uint8_t slice_id; /* only for slice data partition */ + uint8_t type; + uint8_t pic_order_cnt_type; + uint8_t nal_ref_idc; + uint8_t IdrPicFlag; + uint8_t seq_parameter_set_id; + uint8_t pic_parameter_set_id; + uint8_t field_pic_flag; + uint8_t bottom_field_flag; + uint8_t has_mmco5; + uint8_t has_redundancy; + uint16_t idr_pic_id; + uint32_t frame_num; + int32_t pic_order_cnt_lsb; + int32_t delta_pic_order_cnt_bottom; + int32_t delta_pic_order_cnt[2]; +} h264_slice_info_t; + +typedef enum +{ + H264_PICTURE_TYPE_IDR = 0, + H264_PICTURE_TYPE_I = 1, + H264_PICTURE_TYPE_I_P = 2, + H264_PICTURE_TYPE_I_P_B = 3, + H264_PICTURE_TYPE_SI = 4, + H264_PICTURE_TYPE_SI_SP = 5, + H264_PICTURE_TYPE_I_SI = 6, + H264_PICTURE_TYPE_I_SI_P_SP = 7, + H264_PICTURE_TYPE_I_SI_P_SP_B = 8, + H264_PICTURE_TYPE_NONE = 9, +} h264_picture_type; + +typedef struct +{ + h264_picture_type type; + uint8_t idr; + uint8_t random_accessible; + uint8_t independent; + uint8_t disposable; /* 1: nal_ref_idc == 0, 0: otherwise */ + uint8_t has_redundancy; + uint8_t has_primary; + uint8_t pic_parameter_set_id; + uint8_t field_pic_flag; + uint8_t bottom_field_flag; + uint8_t delta; + uint8_t broken_link_flag; + /* POC */ + uint8_t has_mmco5; + uint8_t ref_pic_has_mmco5; + uint8_t ref_pic_bottom_field_flag; + int32_t ref_pic_TopFieldOrderCnt; + int32_t ref_pic_PicOrderCntMsb; + int32_t ref_pic_PicOrderCntLsb; + int32_t pic_order_cnt_lsb; + int32_t delta_pic_order_cnt_bottom; + int32_t delta_pic_order_cnt[2]; + int32_t PicOrderCnt; + uint32_t FrameNumOffset; + /* */ + uint32_t recovery_frame_cnt; + uint32_t frame_num; +} h264_picture_info_t; + +typedef struct +{ + uint8_t *data; + uint8_t *incomplete_data; + uint32_t length; + uint32_t incomplete_length; + uint32_t number; + h264_picture_info_t picture; +} h264_access_unit_t; + +typedef struct h264_info_tag h264_info_t; + +typedef struct +{ + lsmash_multiple_buffers_t *bank; + uint8_t *rbsp; +} h264_stream_buffer_t; + +struct h264_info_tag +{ + lsmash_h264_specific_parameters_t avcC_param; + lsmash_h264_specific_parameters_t avcC_param_next; + lsmash_entry_list_t sps_list [1]; /* contains entries as h264_sps_t */ + lsmash_entry_list_t pps_list [1]; /* contains entries as h264_pps_t */ + lsmash_entry_list_t slice_list[1]; /* for slice data partition */ + h264_sps_t sps; /* active SPS */ + h264_pps_t pps; /* active PPS */ + h264_sei_t sei; /* active SEI */ + h264_slice_info_t slice; /* active slice */ + h264_access_unit_t au; + uint8_t prev_nalu_type; + uint8_t avcC_pending; + lsmash_bits_t *bits; + h264_stream_buffer_t buffer; +}; + +int h264_setup_parser +( + h264_info_t *info, + int parse_only +); + +void h264_cleanup_parser +( + h264_info_t *info +); + +uint64_t h264_find_next_start_code +( + lsmash_bs_t *bs, + h264_nalu_header_t *nuh, + uint64_t *start_code_length, + uint64_t *trailing_zero_bytes +); + +int h264_calculate_poc +( + h264_info_t *info, + h264_picture_info_t *picture, + h264_picture_info_t *prev_picture +); + +void h264_update_picture_info_for_slice +( + h264_info_t *info, + h264_picture_info_t *picture, + h264_slice_info_t *slice +); + +void h264_update_picture_info +( + h264_info_t *info, + h264_picture_info_t *picture, + h264_slice_info_t *slice, + h264_sei_t *sei +); + +int h264_find_au_delimit_by_slice_info +( + h264_slice_info_t *slice, + h264_slice_info_t *prev_slice +); + +int h264_find_au_delimit_by_nalu_type +( + uint8_t nalu_type, + uint8_t prev_nalu_type +); + +int h264_supplement_buffer +( + h264_stream_buffer_t *buffer, + h264_access_unit_t *au, + uint32_t size +); + +int h264_parse_sps +( + h264_info_t *info, + uint8_t *rbsp_buffer, + uint8_t *ebsp, + uint64_t ebsp_size +); + +int h264_parse_pps +( + h264_info_t *info, + uint8_t *rbsp_buffer, + uint8_t *ebsp, + uint64_t ebsp_size +); + +int h264_parse_sei +( + lsmash_bits_t *bits, + h264_sps_t *sps, + h264_sei_t *sei, + uint8_t *rbsp_buffer, + uint8_t *ebsp, + uint64_t ebsp_size +); + +int h264_parse_slice +( + h264_info_t *info, + h264_nalu_header_t *nuh, + uint8_t *rbsp_buffer, + uint8_t *ebsp, + uint64_t ebsp_size +); + +int h264_try_to_append_parameter_set +( + h264_info_t *info, + lsmash_h264_parameter_set_type ps_type, + void *ps_data, + uint32_t ps_length +); + +int h264_move_pending_avcC_param +( + h264_info_t *info +); diff --git a/3rdparty/l-smash/codecs/hevc.c b/3rdparty/l-smash/codecs/hevc.c new file mode 100644 index 000000000..05c9938be --- /dev/null +++ b/3rdparty/l-smash/codecs/hevc.c @@ -0,0 +1,3150 @@ +/***************************************************************************** + * hevc.c + ***************************************************************************** + * Copyright (C) 2013-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include +#include +#include + +#include "core/box.h" + +/*************************************************************************** + ITU-T Recommendation H.265 (04/13) + ISO/IEC 14496-15:2014 +***************************************************************************/ +#include "hevc.h" +#include "nalu.h" + +#define IF_EXCEED_INT32( x ) if( (x) < INT32_MIN || (x) > INT32_MAX ) +#define HEVC_POC_DEBUG_PRINT 0 + +#define HEVC_MIN_NALU_HEADER_LENGTH 2 +#define HEVC_MAX_VPS_ID 15 +#define HEVC_MAX_SPS_ID 15 +#define HEVC_MAX_PPS_ID 63 +#define HEVC_MAX_DPB_SIZE 16 +#define HVCC_CONFIGURATION_VERSION 1 + +typedef enum +{ + HEVC_SLICE_TYPE_B = 0, + HEVC_SLICE_TYPE_P = 1, + HEVC_SLICE_TYPE_I = 2, +} hevc_slice_type; + +static lsmash_hevc_parameter_arrays_t *hevc_alloc_parameter_arrays( void ) +{ + lsmash_hevc_parameter_arrays_t *parameter_arrays = lsmash_malloc_zero( sizeof(lsmash_hevc_parameter_arrays_t) ); + if( !parameter_arrays ) + return NULL; + parameter_arrays->ps_array[HEVC_DCR_NALU_TYPE_VPS ].array_completeness = 1; + parameter_arrays->ps_array[HEVC_DCR_NALU_TYPE_VPS ].NAL_unit_type = HEVC_NALU_TYPE_VPS; + parameter_arrays->ps_array[HEVC_DCR_NALU_TYPE_SPS ].array_completeness = 1; + parameter_arrays->ps_array[HEVC_DCR_NALU_TYPE_SPS ].NAL_unit_type = HEVC_NALU_TYPE_SPS; + parameter_arrays->ps_array[HEVC_DCR_NALU_TYPE_PPS ].array_completeness = 1; + parameter_arrays->ps_array[HEVC_DCR_NALU_TYPE_PPS ].NAL_unit_type = HEVC_NALU_TYPE_PPS; + parameter_arrays->ps_array[HEVC_DCR_NALU_TYPE_PREFIX_SEI].array_completeness = 0; + parameter_arrays->ps_array[HEVC_DCR_NALU_TYPE_PREFIX_SEI].NAL_unit_type = HEVC_NALU_TYPE_PREFIX_SEI; + parameter_arrays->ps_array[HEVC_DCR_NALU_TYPE_SUFFIX_SEI].array_completeness = 0; + parameter_arrays->ps_array[HEVC_DCR_NALU_TYPE_SUFFIX_SEI].NAL_unit_type = HEVC_NALU_TYPE_SUFFIX_SEI; + for( int i = 0; i < HEVC_DCR_NALU_TYPE_NUM; i++ ) + lsmash_list_init( parameter_arrays->ps_array[i].list, isom_remove_dcr_ps ); + return parameter_arrays; +} + +static int hevc_alloc_parameter_arrays_if_needed +( + lsmash_hevc_specific_parameters_t *param +) +{ + assert( param ); + if( param->parameter_arrays ) + return 0; + lsmash_hevc_parameter_arrays_t *parameter_arrays = hevc_alloc_parameter_arrays(); + if( !parameter_arrays ) + return LSMASH_ERR_MEMORY_ALLOC; + param->parameter_arrays = parameter_arrays; + return 0; +} + +static void hevc_deallocate_parameter_arrays +( + lsmash_hevc_specific_parameters_t *param +) +{ + if( !param || !param->parameter_arrays ) + return; + for( int i = 0; i < HEVC_DCR_NALU_TYPE_NUM; i++ ) + lsmash_list_remove_entries( param->parameter_arrays->ps_array[i].list ); + lsmash_freep( ¶m->parameter_arrays ); +} + +void lsmash_destroy_hevc_parameter_arrays +( + lsmash_hevc_specific_parameters_t *param +) +{ + hevc_deallocate_parameter_arrays( param ); +} + +void hevc_destruct_specific_data +( + void *data +) +{ + if( !data ) + return; + hevc_deallocate_parameter_arrays( data ); + lsmash_free( data ); +} + +static void hevc_remove_pps +( + hevc_pps_t *pps +) +{ + if( !pps ) + return; + lsmash_free( pps->colWidth ); + lsmash_free( pps->rowHeight ); + lsmash_free( pps ); +} + +void hevc_cleanup_parser +( + hevc_info_t *info +) +{ + if( !info ) + return; + lsmash_list_remove_entries( info->vps_list ); + lsmash_list_remove_entries( info->sps_list ); + lsmash_list_remove_entries( info->pps_list ); + hevc_deallocate_parameter_arrays( &info->hvcC_param ); + hevc_deallocate_parameter_arrays( &info->hvcC_param_next ); + lsmash_destroy_multiple_buffers( info->buffer.bank ); + lsmash_bits_adhoc_cleanup( info->bits ); + info->bits = NULL; +} + +int hevc_setup_parser +( + hevc_info_t *info, + int parse_only +) +{ + assert( info ); + memset( info, 0, sizeof(hevc_info_t) ); + info->hvcC_param .lengthSizeMinusOne = NALU_DEFAULT_NALU_LENGTH_SIZE - 1; + info->hvcC_param_next.lengthSizeMinusOne = NALU_DEFAULT_NALU_LENGTH_SIZE - 1; + hevc_stream_buffer_t *sb = &info->buffer; + sb->bank = lsmash_create_multiple_buffers( parse_only ? 1 : 3, NALU_DEFAULT_BUFFER_SIZE ); + if( !sb->bank ) + return LSMASH_ERR_MEMORY_ALLOC; + sb->rbsp = lsmash_withdraw_buffer( sb->bank, 1 ); + if( !parse_only ) + { + info->au.data = lsmash_withdraw_buffer( sb->bank, 2 ); + info->au.incomplete_data = lsmash_withdraw_buffer( sb->bank, 3 ); + } + info->bits = lsmash_bits_adhoc_create(); + if( !info->bits ) + { + lsmash_destroy_multiple_buffers( sb->bank ); + return LSMASH_ERR_MEMORY_ALLOC; + } + lsmash_list_init_simple( info->vps_list ); + lsmash_list_init_simple( info->sps_list ); + lsmash_list_init( info->pps_list, hevc_remove_pps ); + info->prev_nalu_type = HEVC_NALU_TYPE_UNKNOWN; + return 0; +} + +static int hevc_check_nalu_header +( + lsmash_bs_t *bs, + hevc_nalu_header_t *nuh, + int use_long_start_code +) +{ + /* Check if the enough length of NALU header on the buffer. */ + int start_code_length = use_long_start_code ? NALU_LONG_START_CODE_LENGTH : NALU_SHORT_START_CODE_LENGTH; + if( lsmash_bs_is_end( bs, start_code_length + 1 ) ) + return LSMASH_ERR_NAMELESS; + /* Read NALU header. */ + uint16_t temp16 = lsmash_bs_show_be16( bs, start_code_length ); + nuh->forbidden_zero_bit = (temp16 >> 15) & 0x01; + nuh->nal_unit_type = (temp16 >> 9) & 0x3f; + nuh->nuh_layer_id = (temp16 >> 3) & 0x3f; + uint8_t nuh_temporal_id_plus1 = temp16 & 0x07; + if( nuh->forbidden_zero_bit || nuh_temporal_id_plus1 == 0 ) + return LSMASH_ERR_INVALID_DATA; + nuh->TemporalId = nuh_temporal_id_plus1 - 1; + nuh->length = HEVC_MIN_NALU_HEADER_LENGTH; + /* nuh_layer_id shall be 0 in the specification we refer to. */ + if( nuh->nuh_layer_id ) + return LSMASH_ERR_NAMELESS; + if( nuh->TemporalId == 0 ) + { + /* For TSA_N, TSA_R, STSA_N and STSA_R, TemporalId shall not be equal to 0. */ + if( nuh->nal_unit_type >= HEVC_NALU_TYPE_TSA_N + && nuh->nal_unit_type <= HEVC_NALU_TYPE_STSA_R ) + return LSMASH_ERR_INVALID_DATA; + } + else + { + /* For BLA_W_LP to RSV_IRAP_VCL23, TemporalId shall be equal to 0. */ + if( nuh->nal_unit_type >= HEVC_NALU_TYPE_BLA_W_LP + && nuh->nal_unit_type <= HEVC_NALU_TYPE_RSV_IRAP_VCL23 ) + return LSMASH_ERR_INVALID_DATA; + /* For VPS, SPS, EOS and EOB, TemporalId shall be equal to 0. */ + if( nuh->nal_unit_type >= HEVC_NALU_TYPE_VPS + && nuh->nal_unit_type <= HEVC_NALU_TYPE_EOB + && nuh->nal_unit_type != HEVC_NALU_TYPE_PPS + && nuh->nal_unit_type != HEVC_NALU_TYPE_AUD ) + return LSMASH_ERR_INVALID_DATA; + } + /* VPS, SPS and PPS require long start code (0x00000001). + * Also AU delimiter requires it too because this type of NALU shall be the first NALU of any AU if present. */ + if( !use_long_start_code + && nuh->nal_unit_type >= HEVC_NALU_TYPE_VPS + && nuh->nal_unit_type <= HEVC_NALU_TYPE_AUD ) + return LSMASH_ERR_INVALID_DATA; + return 0; +} + +uint64_t hevc_find_next_start_code +( + lsmash_bs_t *bs, + hevc_nalu_header_t *nuh, + uint64_t *start_code_length, + uint64_t *trailing_zero_bytes +) +{ + uint64_t length = 0; /* the length of the latest NALU */ + uint64_t count = 0; /* the number of the trailing zero bytes after the latest NALU */ + /* Check the type of the current start code. */ + int long_start_code + = (!lsmash_bs_is_end( bs, NALU_LONG_START_CODE_LENGTH ) && 0x00000001 == lsmash_bs_show_be32( bs, 0 )) ? 1 + : (!lsmash_bs_is_end( bs, NALU_SHORT_START_CODE_LENGTH ) && 0x000001 == lsmash_bs_show_be24( bs, 0 )) ? 0 + : -1; + if( long_start_code >= 0 && hevc_check_nalu_header( bs, nuh, long_start_code ) == 0 ) + { + *start_code_length = long_start_code ? NALU_LONG_START_CODE_LENGTH : NALU_SHORT_START_CODE_LENGTH; + uint64_t distance = *start_code_length + nuh->length; + /* Find the start code of the next NALU and get the distance from the start code of the latest NALU. */ + if( !lsmash_bs_is_end( bs, distance + NALU_SHORT_START_CODE_LENGTH ) ) + { + uint32_t sync_bytes = lsmash_bs_show_be24( bs, distance ); + while( 0x000001 != sync_bytes ) + { + if( lsmash_bs_is_end( bs, ++distance + NALU_SHORT_START_CODE_LENGTH ) ) + { + distance = lsmash_bs_get_remaining_buffer_size( bs ); + break; + } + sync_bytes <<= 8; + sync_bytes |= lsmash_bs_show_byte( bs, distance + NALU_SHORT_START_CODE_LENGTH - 1 ); + sync_bytes &= 0xFFFFFF; + } + } + else + distance = lsmash_bs_get_remaining_buffer_size( bs ); + /* Any NALU has no consecutive zero bytes at the end. */ + while( 0x00 == lsmash_bs_show_byte( bs, distance - 1 ) ) + { + --distance; + ++count; + } + /* Remove the length of the start code. */ + length = distance - *start_code_length; + /* If there are one or more trailing zero bytes, we treat the last one byte as a part of the next start code. + * This makes the next start code a long start code. */ + if( count ) + --count; + } + else + { + /* No start code. */ + nuh->forbidden_zero_bit = 1; /* shall be 0, so invalid */ + nuh->nal_unit_type = HEVC_NALU_TYPE_UNKNOWN; + nuh->nuh_layer_id = 0; /* arbitrary */ + nuh->TemporalId = 0; /* arbitrary */ + nuh->length = 0; + *start_code_length = 0; + length = NALU_NO_START_CODE_FOUND; + } + *trailing_zero_bytes = count; + return length; +} + +static hevc_vps_t *hevc_get_vps +( + lsmash_entry_list_t *vps_list, + uint8_t vps_id +) +{ + if( !vps_list || vps_id > HEVC_MAX_VPS_ID ) + return NULL; + for( lsmash_entry_t *entry = vps_list->head; entry; entry = entry->next ) + { + hevc_vps_t *vps = (hevc_vps_t *)entry->data; + if( !vps ) + return NULL; + if( vps->video_parameter_set_id == vps_id ) + return vps; + } + hevc_vps_t *vps = lsmash_malloc_zero( sizeof(hevc_vps_t) ); + if( !vps ) + return NULL; + vps->video_parameter_set_id = vps_id; + if( lsmash_list_add_entry( vps_list, vps ) < 0 ) + { + lsmash_free( vps ); + return NULL; + } + return vps; +} + +static hevc_sps_t *hevc_get_sps +( + lsmash_entry_list_t *sps_list, + uint8_t sps_id +) +{ + if( !sps_list || sps_id > HEVC_MAX_SPS_ID ) + return NULL; + for( lsmash_entry_t *entry = sps_list->head; entry; entry = entry->next ) + { + hevc_sps_t *sps = (hevc_sps_t *)entry->data; + if( !sps ) + return NULL; + if( sps->seq_parameter_set_id == sps_id ) + return sps; + } + hevc_sps_t *sps = lsmash_malloc_zero( sizeof(hevc_sps_t) ); + if( !sps ) + return NULL; + sps->seq_parameter_set_id = sps_id; + if( lsmash_list_add_entry( sps_list, sps ) < 0 ) + { + lsmash_free( sps ); + return NULL; + } + return sps; +} + +static hevc_pps_t *hevc_get_pps +( + lsmash_entry_list_t *pps_list, + uint8_t pps_id +) +{ + if( !pps_list || pps_id > HEVC_MAX_PPS_ID ) + return NULL; + for( lsmash_entry_t *entry = pps_list->head; entry; entry = entry->next ) + { + hevc_pps_t *pps = (hevc_pps_t *)entry->data; + if( !pps ) + return NULL; + if( pps->pic_parameter_set_id == pps_id ) + return pps; + } + hevc_pps_t *pps = lsmash_malloc_zero( sizeof(hevc_pps_t) ); + if( !pps ) + return NULL; + pps->pic_parameter_set_id = pps_id; + if( lsmash_list_add_entry( pps_list, pps ) < 0 ) + { + lsmash_free( pps ); + return NULL; + } + return pps; +} + +int hevc_calculate_poc +( + hevc_info_t *info, + hevc_picture_info_t *picture, + hevc_picture_info_t *prev_picture +) +{ +#if HEVC_POC_DEBUG_PRINT + fprintf( stderr, "PictureOrderCount\n" ); +#endif + hevc_pps_t *pps = hevc_get_pps( info->pps_list, picture->pic_parameter_set_id ); + if( !pps ) + return LSMASH_ERR_NAMELESS; + hevc_sps_t *sps = hevc_get_sps( info->sps_list, pps->seq_parameter_set_id ); + if( !sps ) + return LSMASH_ERR_NAMELESS; + /* 8.3.1 Decoding process for picture order count + * This process needs to be invoked only for the first slice segment of a picture. */ + int NoRaslOutputFlag; + if( picture->irap ) + { + /* 8.1 General decoding process + * If the current picture is an IDR picture, a BLA picture, the first picture in the + * bitstream in decoding order, or the first picture that follows an end of sequence + * NAL unit in decoding order, the variable NoRaslOutputFlag is set equal to 1. + * + * Note that not only the end of sequence NAL unit but the end of bistream NAL unit as + * well specify that the current access unit is the last access unit in the coded video + * sequence in decoding order. */ + NoRaslOutputFlag = picture->idr || picture->broken_link || info->eos; + if( info->eos ) + info->eos = 0; + } + else + NoRaslOutputFlag = 0; + int64_t poc_msb; + int32_t poc_lsb = picture->poc_lsb; + if( picture->irap && NoRaslOutputFlag ) + poc_msb = 0; + else + { + int32_t prev_poc_msb = picture->idr ? 0 : prev_picture->tid0_poc_msb; + int32_t prev_poc_lsb = picture->idr ? 0 : prev_picture->tid0_poc_lsb; + int32_t max_poc_lsb = 1 << sps->log2_max_pic_order_cnt_lsb; + if( (poc_lsb < prev_poc_lsb) + && ((prev_poc_lsb - poc_lsb) >= (max_poc_lsb / 2)) ) + poc_msb = prev_poc_msb + max_poc_lsb; + else if( (poc_lsb > prev_poc_lsb) + && ((poc_lsb - prev_poc_lsb) > (max_poc_lsb / 2)) ) + poc_msb = prev_poc_msb - max_poc_lsb; + else + poc_msb = prev_poc_msb; + } + picture->poc = poc_msb + poc_lsb; + if( picture->TemporalId == 0 && (!picture->radl || !picture->rasl || !picture->sublayer_nonref) ) + { + picture->tid0_poc_msb = poc_msb; + picture->tid0_poc_lsb = poc_lsb; + } +#if HEVC_POC_DEBUG_PRINT + fprintf( stderr, " prevPicOrderCntMsb: %"PRId32"\n", prev_poc_msb ); + fprintf( stderr, " prevPicOrderCntLsb: %"PRId32"\n", prev_poc_lsb ); + fprintf( stderr, " PicOrderCntMsb: %"PRId64"\n", poc_msb ); + fprintf( stderr, " pic_order_cnt_lsb: %"PRId32"\n", poc_lsb ); + fprintf( stderr, " MaxPicOrderCntLsb: %"PRIu64"\n", max_poc_lsb ); + fprintf( stderr, " POC: %"PRId32"\n", picture->poc ); +#endif + return 0; +} + +static inline int hevc_activate_vps +( + hevc_info_t *info, + uint8_t video_parameter_set_id +) +{ + hevc_vps_t *vps = hevc_get_vps( info->vps_list, video_parameter_set_id ); + if( !vps ) + return LSMASH_ERR_NAMELESS; + info->vps = *vps; + return 0; +} + +static inline int hevc_activate_sps +( + hevc_info_t *info, + uint8_t seq_parameter_set_id +) +{ + hevc_sps_t *sps = hevc_get_sps( info->sps_list, seq_parameter_set_id ); + if( !sps ) + return LSMASH_ERR_NAMELESS; + info->sps = *sps; + return 0; +} + +static void hevc_parse_scaling_list_data +( + lsmash_bits_t *bits +) +{ + for( int sizeId = 0; sizeId < 4; sizeId++ ) + for( int matrixId = 0; matrixId < (sizeId == 3 ? 2 : 6); matrixId++ ) + { + if( !lsmash_bits_get( bits, 1 ) ) /* scaling_list_pred_mode_flag[sizeId][matrixId] */ + nalu_get_exp_golomb_ue( bits ); /* scaling_list_pred_matrix_id_delta[sizeId][matrixId] */ + else + { + int coefNum = LSMASH_MIN( 64, 1 << (4 + (sizeId << 1)) ); + if( sizeId > 1 ) + nalu_get_exp_golomb_se( bits ); /* scaling_list_dc_coef_minus8[sizeId - 2][matrixId] */ + for( int i = 0; i < coefNum; i++ ) + nalu_get_exp_golomb_se( bits ); /* scaling_list_delta_coef */ + } + } +} + +static int hevc_short_term_ref_pic_set +( + lsmash_bits_t *bits, + hevc_sps_t *sps, + int stRpsIdx +) +{ + int inter_ref_pic_set_prediction_flag = stRpsIdx != 0 ? lsmash_bits_get( bits, 1 ) : 0; + if( inter_ref_pic_set_prediction_flag ) + { + /* delta_idx_minus1 is always 0 in SPS since stRpsIdx must not be equal to num_short_term_ref_pic_sets. */ + uint64_t delta_idx_minus1 = stRpsIdx == sps->num_short_term_ref_pic_sets ? nalu_get_exp_golomb_ue( bits ) : 0; + int delta_rps_sign = lsmash_bits_get( bits, 1 ); + uint64_t abs_delta_rps_minus1 = nalu_get_exp_golomb_ue( bits ); + int RefRpsIdx = stRpsIdx - (delta_idx_minus1 + 1); + int deltaRps = (delta_rps_sign ? -1 : 1) * (abs_delta_rps_minus1 + 1); + hevc_st_rps_t *st_rps = &sps->st_rps[stRpsIdx]; + hevc_st_rps_t *ref_rps = &sps->st_rps[RefRpsIdx]; + uint8_t used_by_curr_pic_flag[32]; + uint8_t use_delta_flag [32]; + for( int j = 0; j <= ref_rps->NumDeltaPocs; j++ ) + { + used_by_curr_pic_flag[j] = lsmash_bits_get( bits, 1 ); + use_delta_flag [j] = !used_by_curr_pic_flag[j] ? lsmash_bits_get( bits, 1 ) : 1; + } + /* NumNegativePics */ + int i = 0; + for( int j = ref_rps->NumPositivePics - 1; j >= 0; j-- ) + { + int dPoc = ref_rps->DeltaPocS1[j] + deltaRps; + if( dPoc < 0 && use_delta_flag[ ref_rps->NumNegativePics + j ] ) + { + st_rps->DeltaPocS0 [i ] = dPoc; + st_rps->UsedByCurrPicS0[i++] = used_by_curr_pic_flag[ ref_rps->NumNegativePics + j ]; + } + } + if( deltaRps < 0 && use_delta_flag[ ref_rps->NumDeltaPocs ] ) + { + st_rps->DeltaPocS0 [i ] = deltaRps; + st_rps->UsedByCurrPicS0[i++] = used_by_curr_pic_flag[ ref_rps->NumDeltaPocs ]; + } + for( int j = 0; j < ref_rps->NumNegativePics; j++ ) + { + int dPoc = ref_rps->DeltaPocS0[j] + deltaRps; + if( dPoc < 0 && use_delta_flag[j] ) + { + st_rps->DeltaPocS0 [i ] = dPoc; + st_rps->UsedByCurrPicS0[i++] = used_by_curr_pic_flag[j]; + } + } + st_rps->NumNegativePics = i; + /* NumPositivePics */ + i = 0; + for( int j = ref_rps->NumNegativePics - 1; j >= 0; j-- ) + { + int dPoc = ref_rps->DeltaPocS0[j] + deltaRps; + if( dPoc > 0 && use_delta_flag[j] ) + { + st_rps->DeltaPocS1 [i ] = dPoc; + st_rps->UsedByCurrPicS1[i++] = used_by_curr_pic_flag[j]; + } + } + if( deltaRps > 0 && use_delta_flag[ ref_rps->NumDeltaPocs ] ) + { + st_rps->DeltaPocS1 [i ] = deltaRps; + st_rps->UsedByCurrPicS1[i++] = used_by_curr_pic_flag[ ref_rps->NumDeltaPocs ]; + } + for( int j = 0; j < ref_rps->NumPositivePics; j++ ) + { + int dPoc = ref_rps->DeltaPocS1[j] + deltaRps; + if( dPoc > 0 && use_delta_flag[ ref_rps->NumNegativePics + j ] ) + { + st_rps->DeltaPocS1 [i ] = dPoc; + st_rps->UsedByCurrPicS1[i++] = used_by_curr_pic_flag[ ref_rps->NumNegativePics + j ]; + } + } + st_rps->NumPositivePics = i; + /* NumDeltaPocs */ + st_rps->NumDeltaPocs = st_rps->NumNegativePics + st_rps->NumPositivePics; + } + else + { + uint64_t num_negative_pics = nalu_get_exp_golomb_ue( bits ); + uint64_t num_positive_pics = nalu_get_exp_golomb_ue( bits ); + if( num_negative_pics >= HEVC_MAX_DPB_SIZE || num_positive_pics >= HEVC_MAX_DPB_SIZE ) + return LSMASH_ERR_INVALID_DATA; + hevc_st_rps_t *st_rps = &sps->st_rps[stRpsIdx]; + st_rps->NumNegativePics = num_negative_pics; + st_rps->NumPositivePics = num_positive_pics; + st_rps->NumDeltaPocs = st_rps->NumNegativePics + st_rps->NumPositivePics; + for( int i = 0; i < num_negative_pics; i++ ) + { + uint64_t delta_poc_s0_minus1 = nalu_get_exp_golomb_ue( bits ); + if( i == 0 ) + st_rps->DeltaPocS0[i] = -(signed)(delta_poc_s0_minus1 + 1); + else + st_rps->DeltaPocS0[i] = st_rps->DeltaPocS0[i - 1] - (delta_poc_s0_minus1 + 1); + st_rps->UsedByCurrPicS0[i] = lsmash_bits_get( bits, 1 ); /* used_by_curr_pic_s0_flag */ + } + for( int i = 0; i < num_positive_pics; i++ ) + { + uint64_t delta_poc_s1_minus1 = nalu_get_exp_golomb_ue( bits ); + if( i == 0 ) + st_rps->DeltaPocS1[i] = +(delta_poc_s1_minus1 + 1); + else + st_rps->DeltaPocS1[i] = st_rps->DeltaPocS1[i - 1] + (delta_poc_s1_minus1 + 1); + st_rps->UsedByCurrPicS0[i] = lsmash_bits_get( bits, 1 ); /* used_by_curr_pic_s1_flag */ + } + } + return 0; +} + +static inline void hevc_parse_sub_layer_hrd_parameters +( + lsmash_bits_t *bits, + int CpbCnt, + int sub_pic_hrd_params_present_flag +) +{ + for( int i = 0; i <= CpbCnt; i++ ) + { + nalu_get_exp_golomb_ue( bits ); /* bit_rate_value_minus1[i] */ + nalu_get_exp_golomb_ue( bits ); /* cpb_size_value_minus1[i] */ + if( sub_pic_hrd_params_present_flag ) + { + nalu_get_exp_golomb_ue( bits ); /* cpb_size_du_value_minus1[i] */ + nalu_get_exp_golomb_ue( bits ); /* bit_rate_du_value_minus1[i] */ + } + lsmash_bits_get( bits, 1 ); /* cbr_flag[i] */ + } +} + +static void hevc_parse_hrd_parameters +( + lsmash_bits_t *bits, + hevc_hrd_t *hrd, + int commonInfPresentFlag, + int maxNumSubLayersMinus1 +) +{ + /* The specification we refer to doesn't define the implicit value of some fields. + * According to JCTVC-HM reference software, + * the implicit value of nal_hrd_parameters_present_flag is to be equal to 0, + * the implicit value of vcl_hrd_parameters_present_flag is to be equal to 0. */ + int nal_hrd_parameters_present_flag = 0; + int vcl_hrd_parameters_present_flag = 0; + memset( hrd, 0, sizeof(hevc_hrd_t) ); + if( commonInfPresentFlag ) + { + nal_hrd_parameters_present_flag = lsmash_bits_get( bits, 1 ); + vcl_hrd_parameters_present_flag = lsmash_bits_get( bits, 1 ); + if( nal_hrd_parameters_present_flag + || vcl_hrd_parameters_present_flag ) + { + hrd->CpbDpbDelaysPresentFlag = 1; + hrd->sub_pic_hrd_params_present_flag = lsmash_bits_get( bits, 1 ); + if( hrd->sub_pic_hrd_params_present_flag ) + { + lsmash_bits_get( bits, 8 ); /* tick_divisor_minus2 */ + hrd->du_cpb_removal_delay_increment_length = lsmash_bits_get( bits, 5 ) + 1; + hrd->sub_pic_cpb_params_in_pic_timing_sei_flag = lsmash_bits_get( bits, 1 ); + hrd->dpb_output_delay_du_length = lsmash_bits_get( bits, 5 ) + 1; + } + lsmash_bits_get( bits, 4 ); /* bit_rate_scale */ + lsmash_bits_get( bits, 4 ); /* cpb_size_scale */ + if( hrd->sub_pic_hrd_params_present_flag ) + lsmash_bits_get( bits, 4 ); /* cpb_size_du_scale */ + lsmash_bits_get( bits, 5 ); /* initial_cpb_removal_delay_length_minus1 */ + hrd->au_cpb_removal_delay_length = lsmash_bits_get( bits, 5 ) + 1; + hrd->dpb_output_delay_length = lsmash_bits_get( bits, 5 ) + 1; + } + } + for( int i = 0; i <= maxNumSubLayersMinus1; i++ ) + { + hrd->fixed_pic_rate_general_flag[i] = lsmash_bits_get( bits, 1 ); + uint8_t fixed_pic_rate_within_cvs_flag = !hrd->fixed_pic_rate_general_flag[i] ? lsmash_bits_get( bits, 1 ) : 1; + uint8_t low_delay_hrd_flag = !fixed_pic_rate_within_cvs_flag ? lsmash_bits_get( bits, 1 ) : 0; + hrd->elemental_duration_in_tc[i] = fixed_pic_rate_within_cvs_flag ? nalu_get_exp_golomb_ue( bits ) + 1 : 0; + uint8_t cpb_cnt_minus1 = !low_delay_hrd_flag ? nalu_get_exp_golomb_ue( bits ) : 0; + if( nal_hrd_parameters_present_flag ) + hevc_parse_sub_layer_hrd_parameters( bits, cpb_cnt_minus1, hrd->sub_pic_hrd_params_present_flag ); + if( vcl_hrd_parameters_present_flag ) + hevc_parse_sub_layer_hrd_parameters( bits, cpb_cnt_minus1, hrd->sub_pic_hrd_params_present_flag ); + } +} + +static inline void hevc_parse_profile_tier_level_common +( + lsmash_bits_t *bits, + hevc_ptl_common_t *ptlc, + int profile_present, + int level_present +) +{ + if( profile_present ) + { + ptlc->profile_space = lsmash_bits_get( bits, 2 ); + ptlc->tier_flag = lsmash_bits_get( bits, 1 ); + ptlc->profile_idc = lsmash_bits_get( bits, 5 ); + ptlc->profile_compatibility_flags = lsmash_bits_get( bits, 32 ); + ptlc->progressive_source_flag = lsmash_bits_get( bits, 1 ); + ptlc->interlaced_source_flag = lsmash_bits_get( bits, 1 ); + ptlc->non_packed_constraint_flag = lsmash_bits_get( bits, 1 ); + ptlc->frame_only_constraint_flag = lsmash_bits_get( bits, 1 ); + ptlc->reserved_zero_44bits = lsmash_bits_get( bits, 44 ); + } + if( level_present ) + ptlc->level_idc = lsmash_bits_get( bits, 8 ); +} + +static void hevc_parse_profile_tier_level +( + lsmash_bits_t *bits, + hevc_ptl_t *ptl, + int maxNumSubLayersMinus1 +) +{ + hevc_parse_profile_tier_level_common( bits, &ptl->general, 1, 1 ); + if( maxNumSubLayersMinus1 == 0 ) + return; + assert( maxNumSubLayersMinus1 <= 6 ); + int sub_layer_profile_present_flag[6] = { 0 }; + int sub_layer_level_present_flag [6] = { 0 }; + for( int i = 0; i < maxNumSubLayersMinus1; i++ ) + { + sub_layer_profile_present_flag[i] = lsmash_bits_get( bits, 1 ); + sub_layer_level_present_flag [i] = lsmash_bits_get( bits, 1 ); + } + for( int i = maxNumSubLayersMinus1; i < 8; i++ ) + lsmash_bits_get( bits, 2 ); /* reserved_zero_2bits[i] */ + for( int i = 0; i < maxNumSubLayersMinus1; i++ ) + hevc_parse_profile_tier_level_common( bits, &ptl->sub_layer[i], sub_layer_profile_present_flag[i], sub_layer_level_present_flag[i] ); +} + +static int hevc_parse_vps_minimally +( + lsmash_bits_t *bits, + hevc_vps_t *vps, + uint8_t *rbsp_buffer, + uint8_t *ebsp, + uint64_t ebsp_size +) +{ + uint64_t rbsp_size; + int err = nalu_import_rbsp_from_ebsp( bits, rbsp_buffer, &rbsp_size, ebsp, ebsp_size ); + if( err < 0 ) + return err; + memset( vps, 0, sizeof(hevc_vps_t) ); + vps->video_parameter_set_id = lsmash_bits_get( bits, 4 ); + /* vps_reserved_three_2bits shall be 3 in the specification we refer to. */ + if( lsmash_bits_get( bits, 2 ) != 3 ) + return LSMASH_ERR_NAMELESS; + /* vps_max_layers_minus1 shall be 0 in the specification we refer to. */ + if( lsmash_bits_get( bits, 6 ) != 0 ) + return LSMASH_ERR_NAMELESS; + vps->max_sub_layers_minus1 = lsmash_bits_get( bits, 3 ); + vps->temporal_id_nesting_flag = lsmash_bits_get( bits, 1 ); + /* When vps_max_sub_layers_minus1 is equal to 0, vps_temporal_id_nesting_flag shall be equal to 1. */ + if( (vps->max_sub_layers_minus1 | vps->temporal_id_nesting_flag) == 0 ) + return LSMASH_ERR_INVALID_DATA; + /* vps_reserved_0xffff_16bits shall be 0xFFFF in the specification we refer to. */ + if( lsmash_bits_get( bits, 16 ) != 0xFFFF ) + return LSMASH_ERR_NAMELESS; + hevc_parse_profile_tier_level( bits, &vps->ptl, vps->max_sub_layers_minus1 ); + vps->frame_field_info_present_flag = vps->ptl.general.progressive_source_flag + && vps->ptl.general.interlaced_source_flag; + int sub_layer_ordering_info_present_flag = lsmash_bits_get( bits, 1 ); + for( int i = sub_layer_ordering_info_present_flag ? 0 : vps->max_sub_layers_minus1; i <= vps->max_sub_layers_minus1; i++ ) + { + nalu_get_exp_golomb_ue( bits ); /* max_dec_pic_buffering_minus1[i] */ + nalu_get_exp_golomb_ue( bits ); /* max_num_reorder_pics [i] */ + nalu_get_exp_golomb_ue( bits ); /* max_latency_increase_plus1 [i] */ + } + uint8_t max_layer_id = lsmash_bits_get( bits, 6 ); + uint16_t num_layer_sets_minus1 = nalu_get_exp_golomb_ue( bits ); + for( int i = 1; i <= num_layer_sets_minus1; i++ ) + for( int j = 0; j <= max_layer_id; j++ ) + lsmash_bits_get( bits, 1 ); /* layer_id_included_flag[i][j] */ + return bits->bs->error ? LSMASH_ERR_NAMELESS : 0; +} + +int hevc_parse_vps +( + hevc_info_t *info, + uint8_t *rbsp_buffer, + uint8_t *ebsp, + uint64_t ebsp_size +) +{ + lsmash_bits_t *bits = info->bits; + hevc_vps_t *vps; + { + /* Parse VPS minimally for configuration records. */ + hevc_vps_t min_vps; + int err = hevc_parse_vps_minimally( bits, &min_vps, rbsp_buffer, ebsp, ebsp_size ); + if( err < 0 ) + return err; + vps = hevc_get_vps( info->vps_list, min_vps.video_parameter_set_id ); + if( !vps ) + return LSMASH_ERR_NAMELESS; + *vps = min_vps; + } + vps->timing_info_present_flag = lsmash_bits_get( bits, 1 ); + if( vps->timing_info_present_flag ) + { + lsmash_bits_get( bits, 32 ); /* num_units_in_tick */ + lsmash_bits_get( bits, 32 ); /* time_scale */ + if( lsmash_bits_get( bits, 1 ) ) /* poc_proportional_to_timing_flag */ + nalu_get_exp_golomb_ue( bits ); /* num_ticks_poc_diff_one_minus1 */ + vps->num_hrd_parameters = nalu_get_exp_golomb_ue( bits ); + for( int i = 0; i < vps->num_hrd_parameters; i++ ) + { + nalu_get_exp_golomb_ue( bits ); /* hrd_layer_set_idx[i] */ + int cprms_present_flag = i > 0 ? lsmash_bits_get( bits, 1 ) : 1; + /* Although the value of vps_num_hrd_parameters is required to be less than or equal to 1 in the spec + * we refer to, decoders shall allow other values of vps_num_hrd_parameters in the range of 0 to 1024, + * inclusive, to appear in the syntax. */ + if( i <= 1 ) + hevc_parse_hrd_parameters( bits, &vps->hrd[i], cprms_present_flag, vps->max_sub_layers_minus1 ); + else + { + hevc_hrd_t dummy_hrd; + hevc_parse_hrd_parameters( bits, &dummy_hrd, cprms_present_flag, vps->max_sub_layers_minus1 ); + } + } + } + /* Skip VPS extension. */ + lsmash_bits_empty( bits ); + if( bits->bs->error ) + return LSMASH_ERR_NAMELESS; + vps->present = 1; + info->vps = *vps; + return 0; +} + +static int hevc_parse_sps_minimally +( + lsmash_bits_t *bits, + hevc_sps_t *sps, + uint8_t *rbsp_buffer, + uint8_t *ebsp, + uint64_t ebsp_size +) +{ + uint64_t rbsp_size; + int err = nalu_import_rbsp_from_ebsp( bits, rbsp_buffer, &rbsp_size, ebsp, ebsp_size ); + if( err < 0 ) + return err; + memset( sps, 0, sizeof(hevc_sps_t) ); + sps->video_parameter_set_id = lsmash_bits_get( bits, 4 ); + sps->max_sub_layers_minus1 = lsmash_bits_get( bits, 3 ); + sps->temporal_id_nesting_flag = lsmash_bits_get( bits, 1 ); + hevc_parse_profile_tier_level( bits, &sps->ptl, sps->max_sub_layers_minus1 ); + sps->seq_parameter_set_id = nalu_get_exp_golomb_ue( bits ); + sps->chroma_format_idc = nalu_get_exp_golomb_ue( bits ); + if( sps->chroma_format_idc == 3 ) + sps->separate_colour_plane_flag = lsmash_bits_get( bits, 1 ); + static const int SubWidthC [] = { 1, 2, 2, 1 }; + static const int SubHeightC[] = { 1, 2, 1, 1 }; + uint64_t pic_width_in_luma_samples = nalu_get_exp_golomb_ue( bits ); + uint64_t pic_height_in_luma_samples = nalu_get_exp_golomb_ue( bits ); + sps->cropped_width = pic_width_in_luma_samples; + sps->cropped_height = pic_height_in_luma_samples; + if( lsmash_bits_get( bits, 1 ) ) /* conformance_window_flag */ + { + uint64_t conf_win_left_offset = nalu_get_exp_golomb_ue( bits ); + uint64_t conf_win_right_offset = nalu_get_exp_golomb_ue( bits ); + uint64_t conf_win_top_offset = nalu_get_exp_golomb_ue( bits ); + uint64_t conf_win_bottom_offset = nalu_get_exp_golomb_ue( bits ); + sps->cropped_width -= (conf_win_left_offset + conf_win_right_offset) * SubWidthC [ sps->chroma_format_idc ]; + sps->cropped_height -= (conf_win_top_offset + conf_win_bottom_offset) * SubHeightC[ sps->chroma_format_idc ]; + } + sps->bit_depth_luma_minus8 = nalu_get_exp_golomb_ue( bits ); + sps->bit_depth_chroma_minus8 = nalu_get_exp_golomb_ue( bits ); + sps->log2_max_pic_order_cnt_lsb = nalu_get_exp_golomb_ue( bits ) + 4; + int sub_layer_ordering_info_present_flag = lsmash_bits_get( bits, 1 ); + for( int i = sub_layer_ordering_info_present_flag ? 0 : sps->max_sub_layers_minus1; i <= sps->max_sub_layers_minus1; i++ ) + { + nalu_get_exp_golomb_ue( bits ); /* max_dec_pic_buffering_minus1[i] */ + nalu_get_exp_golomb_ue( bits ); /* max_num_reorder_pics [i] */ + nalu_get_exp_golomb_ue( bits ); /* max_latency_increase_plus1 [i] */ + } + uint64_t log2_min_luma_coding_block_size_minus3 = nalu_get_exp_golomb_ue( bits ); + uint64_t log2_diff_max_min_luma_coding_block_size = nalu_get_exp_golomb_ue( bits ); + nalu_get_exp_golomb_ue( bits ); /* log2_min_transform_block_size_minus2 */ + nalu_get_exp_golomb_ue( bits ); /* log2_diff_max_min_transform_block_size */ + nalu_get_exp_golomb_ue( bits ); /* max_transform_hierarchy_depth_inter */ + nalu_get_exp_golomb_ue( bits ); /* max_transform_hierarchy_depth_intra */ + { + int MinCbLog2SizeY = log2_min_luma_coding_block_size_minus3 + 3; + int MinCbSizeY = 1 << MinCbLog2SizeY; + if( pic_width_in_luma_samples == 0 || pic_width_in_luma_samples % MinCbSizeY + || pic_height_in_luma_samples == 0 || pic_height_in_luma_samples % MinCbSizeY ) + return LSMASH_ERR_INVALID_DATA; /* Both shall be an integer multiple of MinCbSizeY. */ + int CtbLog2SizeY = MinCbLog2SizeY + log2_diff_max_min_luma_coding_block_size; + int CtbSizeY = 1 << CtbLog2SizeY; + sps->PicWidthInCtbsY = (pic_width_in_luma_samples - 1) / CtbSizeY + 1; + sps->PicHeightInCtbsY = (pic_height_in_luma_samples - 1) / CtbSizeY + 1; + sps->PicSizeInCtbsY = sps->PicWidthInCtbsY * sps->PicHeightInCtbsY; + } + if( lsmash_bits_get( bits, 1 ) /* scaling_list_enabled_flag */ + && lsmash_bits_get( bits, 1 ) ) /* sps_scaling_list_data_present_flag */ + hevc_parse_scaling_list_data( bits ); + lsmash_bits_get( bits, 1 ); /* amp_enabled_flag */ + lsmash_bits_get( bits, 1 ); /* sample_adaptive_offset_enabled_flag */ + if( lsmash_bits_get( bits, 1 ) ) /* pcm_enabled_flag */ + { + lsmash_bits_get( bits, 4 ); /* pcm_sample_bit_depth_luma_minus1 */ + lsmash_bits_get( bits, 4 ); /* pcm_sample_bit_depth_chroma_minus1 */ + nalu_get_exp_golomb_ue( bits ); /* log2_min_pcm_luma_coding_block_size_minus3 */ + nalu_get_exp_golomb_ue( bits ); /* log2_diff_max_min_pcm_luma_coding_block_size */ + lsmash_bits_get( bits, 1 ); /* pcm_loop_filter_disabled_flag */ + } + sps->num_short_term_ref_pic_sets = nalu_get_exp_golomb_ue( bits ); + for( int i = 0; i < sps->num_short_term_ref_pic_sets; i++ ) + if( (err = hevc_short_term_ref_pic_set( bits, sps, i )) < 0 ) + return err; + sps->long_term_ref_pics_present_flag = lsmash_bits_get( bits, 1 ); + if( sps->long_term_ref_pics_present_flag ) + { + sps->num_long_term_ref_pics_sps = nalu_get_exp_golomb_ue( bits ); + for( int i = 0; i < sps->num_long_term_ref_pics_sps; i++ ) + { + lsmash_bits_get( bits, sps->log2_max_pic_order_cnt_lsb ); /* lt_ref_pic_poc_lsb_sps [i] */ + lsmash_bits_get( bits, 1 ); /* used_by_curr_pic_lt_sps_flag[i] */ + } + } + sps->temporal_mvp_enabled_flag = lsmash_bits_get( bits, 1 ); + lsmash_bits_get( bits, 1 ); /* strong_intra_smoothing_enabled_flag */ + sps->vui.present = lsmash_bits_get( bits, 1 ); /* vui_parameters_present_flag */ + if( sps->vui.present ) + { + /* vui_parameters() */ + if( lsmash_bits_get( bits, 1 ) ) /* aspect_ratio_info_present_flag */ + { + uint8_t aspect_ratio_idc = lsmash_bits_get( bits, 8 ); + if( aspect_ratio_idc == 255 ) + { + /* EXTENDED_SAR */ + sps->vui.sar_width = lsmash_bits_get( bits, 16 ); + sps->vui.sar_height = lsmash_bits_get( bits, 16 ); + } + else + { + static const struct + { + uint16_t sar_width; + uint16_t sar_height; + } pre_defined_sar[] = + { + { 0, 0 }, { 1, 1 }, { 12, 11 }, { 10, 11 }, { 16, 11 }, + { 40, 33 }, { 24, 11 }, { 20, 11 }, { 32, 11 }, { 80, 33 }, + { 18, 11 }, { 15, 11 }, { 64, 33 }, { 160, 99 }, { 4, 3 }, + { 3, 2 }, { 2, 1 } + }; + if( aspect_ratio_idc < (sizeof(pre_defined_sar) / sizeof(pre_defined_sar[0])) ) + { + sps->vui.sar_width = pre_defined_sar[ aspect_ratio_idc ].sar_width; + sps->vui.sar_height = pre_defined_sar[ aspect_ratio_idc ].sar_height; + } + else + { + /* Behavior when unknown aspect_ratio_idc is detected is not specified in the specification. */ + sps->vui.sar_width = 0; + sps->vui.sar_height = 0; + } + } + } + else + { + sps->vui.sar_width = 0; + sps->vui.sar_height = 0; + } + if( lsmash_bits_get( bits, 1 ) ) /* overscan_info_present_flag */ + lsmash_bits_get( bits, 1 ); /* overscan_appropriate_flag */ + if( lsmash_bits_get( bits, 1 ) ) /* video_signal_type_present_flag */ + { + lsmash_bits_get( bits, 3 ); /* video_format */ + sps->vui.video_full_range_flag = lsmash_bits_get( bits, 1 ); + sps->vui.colour_description_present_flag = lsmash_bits_get( bits, 1 ); + if( sps->vui.colour_description_present_flag ) + { + sps->vui.colour_primaries = lsmash_bits_get( bits, 8 ); + sps->vui.transfer_characteristics = lsmash_bits_get( bits, 8 ); + sps->vui.matrix_coeffs = lsmash_bits_get( bits, 8 ); + } + else + { + sps->vui.colour_primaries = 2; + sps->vui.transfer_characteristics = 2; + sps->vui.matrix_coeffs = 2; + } + } + if( lsmash_bits_get( bits, 1 ) ) /* chroma_loc_info_present_flag */ + { + nalu_get_exp_golomb_ue( bits ); /* chroma_sample_loc_type_top_field */ + nalu_get_exp_golomb_ue( bits ); /* chroma_sample_loc_type_bottom_field */ + } + lsmash_bits_get( bits, 1 ); /* neutral_chroma_indication_flag */ + sps->vui.field_seq_flag = lsmash_bits_get( bits, 1 ); + sps->vui.frame_field_info_present_flag = lsmash_bits_get( bits, 1 ); + if( sps->vui.field_seq_flag ) + /* cropped_height indicates in a frame. */ + sps->cropped_height *= 2; + if( lsmash_bits_get( bits, 1 ) ) /* default_display_window_flag */ + { + /* default display window + * A rectangular region for display specified by these values is not considered + * as cropped visual presentation size which decoder delivers. + * Maybe, these values shall be indicated by the clean aperture on container level. */ + sps->vui.def_disp_win_offset.left = (lsmash_rational_u32_t){ nalu_get_exp_golomb_ue( bits ) * SubWidthC [ sps->chroma_format_idc ], 1 }; + sps->vui.def_disp_win_offset.right = (lsmash_rational_u32_t){ nalu_get_exp_golomb_ue( bits ) * SubWidthC [ sps->chroma_format_idc ], 1 }; + sps->vui.def_disp_win_offset.top = (lsmash_rational_u32_t){ nalu_get_exp_golomb_ue( bits ) * SubHeightC[ sps->chroma_format_idc ], 1 }; + sps->vui.def_disp_win_offset.bottom = (lsmash_rational_u32_t){ nalu_get_exp_golomb_ue( bits ) * SubHeightC[ sps->chroma_format_idc ], 1 }; + } + if( lsmash_bits_get( bits, 1 ) ) /* vui_timing_info_present_flag */ + { + sps->vui.num_units_in_tick = lsmash_bits_get( bits, 32 ); + sps->vui.time_scale = lsmash_bits_get( bits, 32 ); + if( lsmash_bits_get( bits, 1 ) ) /* vui_poc_proportional_to_timing_flag */ + nalu_get_exp_golomb_ue( bits ); /* vui_num_ticks_poc_diff_one_minus1 */ + if( lsmash_bits_get( bits, 1 ) ) /* vui_hrd_parameters_present_flag */ + hevc_parse_hrd_parameters( bits, &sps->vui.hrd, 1, sps->max_sub_layers_minus1 ); + } + else + { + sps->vui.num_units_in_tick = 1; /* arbitrary */ + sps->vui.time_scale = 25; /* arbitrary */ + } + if( lsmash_bits_get( bits, 1 ) ) /* bitstream_restriction_flag */ + { + lsmash_bits_get( bits, 1 ); /* tiles_fixed_structure_flag */ + lsmash_bits_get( bits, 1 ); /* motion_vectors_over_pic_boundaries_flag */ + lsmash_bits_get( bits, 1 ); /* restricted_ref_pic_lists_flag */ + sps->vui.min_spatial_segmentation_idc = nalu_get_exp_golomb_ue( bits ); + nalu_get_exp_golomb_ue( bits ); /* max_bytes_per_pic_denom */ + nalu_get_exp_golomb_ue( bits ); /* max_bits_per_min_cu_denom */ + nalu_get_exp_golomb_ue( bits ); /* log2_max_mv_length_horizontal */ + nalu_get_exp_golomb_ue( bits ); /* log2_max_mv_length_vertical */ + } + else + sps->vui.min_spatial_segmentation_idc = 0; + } + else + { + sps->vui.sar_width = 0; + sps->vui.sar_height = 0; + sps->vui.colour_primaries = 2; + sps->vui.transfer_characteristics = 2; + sps->vui.matrix_coeffs = 2; + sps->vui.field_seq_flag = 0; + sps->vui.frame_field_info_present_flag = sps->ptl.general.progressive_source_flag + && sps->ptl.general.interlaced_source_flag; + sps->vui.num_units_in_tick = 1; /* arbitrary */ + sps->vui.time_scale = 25; /* arbitrary */ + sps->vui.min_spatial_segmentation_idc = 0; + } + return bits->bs->error ? LSMASH_ERR_NAMELESS : 0; +} + +int hevc_parse_sps +( + hevc_info_t *info, + uint8_t *rbsp_buffer, + uint8_t *ebsp, + uint64_t ebsp_size +) +{ + lsmash_bits_t *bits = info->bits; + hevc_sps_t *sps; + { + /* Parse SPS minimally for configuration records. */ + hevc_sps_t min_sps; + int err = hevc_parse_sps_minimally( bits, &min_sps, rbsp_buffer, ebsp, ebsp_size ); + if( err < 0 ) + return err; + sps = hevc_get_sps( info->sps_list, min_sps.seq_parameter_set_id ); + if( !sps ) + return LSMASH_ERR_NAMELESS; + *sps = min_sps; + } + /* Skip SPS extension. */ + lsmash_bits_empty( bits ); + if( bits->bs->error ) + return LSMASH_ERR_NAMELESS; + sps->present = 1; + info->sps = *sps; + hevc_activate_vps( info, info->sps.video_parameter_set_id ); + return 0; +} + +static int hevc_allocate_tile_sizes +( + hevc_pps_t *pps, + uint32_t num_tile_columns, + uint32_t num_tile_rows +) +{ + /* Allocate columns and rows of tiles. */ + size_t col_alloc_size = 2 * num_tile_columns * sizeof(uint32_t); + if( col_alloc_size > pps->col_alloc_size ) + { + void *temp = lsmash_realloc( pps->colWidth, col_alloc_size ); + if( !temp ) + return LSMASH_ERR_MEMORY_ALLOC; + pps->col_alloc_size = col_alloc_size; + pps->colWidth = temp; + } + size_t row_alloc_size = 2 * num_tile_rows * sizeof(uint32_t); + if( row_alloc_size > pps->row_alloc_size ) + { + void *temp = lsmash_realloc( pps->rowHeight, row_alloc_size ); + if( !temp ) + return LSMASH_ERR_MEMORY_ALLOC; + pps->row_alloc_size = row_alloc_size; + pps->rowHeight = temp; + } + pps->colBd = pps->colWidth + num_tile_columns; + pps->rowBd = pps->rowHeight + num_tile_rows; + return 0; +} + +static int hevc_parse_pps_minimally +( + lsmash_bits_t *bits, + hevc_pps_t *pps, + uint8_t *rbsp_buffer, + uint8_t *ebsp, + uint64_t ebsp_size +) +{ + uint64_t rbsp_size; + int err = nalu_import_rbsp_from_ebsp( bits, rbsp_buffer, &rbsp_size, ebsp, ebsp_size ); + if( err < 0 ) + return err; + memset( pps, 0, SIZEOF_PPS_EXCLUDING_HEAP ); + pps->pic_parameter_set_id = nalu_get_exp_golomb_ue( bits ); + pps->seq_parameter_set_id = nalu_get_exp_golomb_ue( bits ); + pps->dependent_slice_segments_enabled_flag = lsmash_bits_get( bits, 1 ); + pps->output_flag_present_flag = lsmash_bits_get( bits, 1 ); + pps->num_extra_slice_header_bits = lsmash_bits_get( bits, 3 ); + lsmash_bits_get( bits, 1 ); /* sign_data_hiding_enabled_flag */ + lsmash_bits_get( bits, 1 ); /* cabac_init_present_flag */ + nalu_get_exp_golomb_ue( bits ); /* num_ref_idx_l0_default_active_minus1 */ + nalu_get_exp_golomb_ue( bits ); /* num_ref_idx_l1_default_active_minus1 */ + nalu_get_exp_golomb_se( bits ); /* init_qp_minus26 */ + lsmash_bits_get( bits, 1 ); /* constrained_intra_pred_flag */ + lsmash_bits_get( bits, 1 ); /* transform_skip_enabled_flag */ + if( lsmash_bits_get( bits, 1 ) ) /* cu_qp_delta_enabled_flag */ + nalu_get_exp_golomb_ue( bits ); /* diff_cu_qp_delta_depth */ + nalu_get_exp_golomb_se( bits ); /* cb_qp_offset */ + nalu_get_exp_golomb_se( bits ); /* cr_qp_offset */ + lsmash_bits_get( bits, 1 ); /* slice_chroma_qp_offsets_present_flag */ + lsmash_bits_get( bits, 1 ); /* weighted_pred_flag */ + lsmash_bits_get( bits, 1 ); /* weighted_bipred_flag */ + lsmash_bits_get( bits, 1 ) /* transquant_bypass_enabled_flag */; + pps->tiles_enabled_flag = lsmash_bits_get( bits, 1 ); + pps->entropy_coding_sync_enabled_flag = lsmash_bits_get( bits, 1 ); + return bits->bs->error ? LSMASH_ERR_NAMELESS : 0; +} + +int hevc_parse_pps +( + hevc_info_t *info, + uint8_t *rbsp_buffer, + uint8_t *ebsp, + uint64_t ebsp_size +) +{ + int err; + lsmash_bits_t *bits = info->bits; + hevc_pps_t *pps; + { + /* Parse PPS minimally for configuration records. */ + hevc_pps_t min_pps; + if( (err = hevc_parse_pps_minimally( bits, &min_pps, rbsp_buffer, ebsp, ebsp_size )) < 0 ) + return err; + pps = hevc_get_pps( info->pps_list, min_pps.pic_parameter_set_id ); + if( !pps ) + return LSMASH_ERR_NAMELESS; + memcpy( pps, &min_pps, SIZEOF_PPS_EXCLUDING_HEAP ); + } + hevc_sps_t temp_sps = info->sps; + if( (err = hevc_activate_sps( info, pps->seq_parameter_set_id )) < 0 ) + return err; + hevc_sps_t *sps = &info->sps; + if( pps->tiles_enabled_flag ) + { + pps->num_tile_columns_minus1 = nalu_get_exp_golomb_ue( bits ); + pps->num_tile_rows_minus1 = nalu_get_exp_golomb_ue( bits ); + if( pps->num_tile_columns_minus1 >= sps->PicWidthInCtbsY + || pps->num_tile_rows_minus1 >= sps->PicHeightInCtbsY ) + { + err = LSMASH_ERR_INVALID_DATA; + goto fail; + } + if( (err = hevc_allocate_tile_sizes( pps, pps->num_tile_columns_minus1 + 1, pps->num_tile_rows_minus1 + 1 )) < 0 ) + goto fail; + if( lsmash_bits_get( bits, 1 ) ) /* uniform_spacing_flag */ + { + for( int i = 0; i <= pps->num_tile_columns_minus1; i++ ) + pps->colWidth[i] = ((i + 1) * sps->PicWidthInCtbsY) / (pps->num_tile_columns_minus1 + 1) + - ( i * sps->PicWidthInCtbsY) / (pps->num_tile_columns_minus1 + 1); + for( int j = 0; j <= pps->num_tile_rows_minus1; j++ ) + pps->rowHeight[j] = ((j + 1) * sps->PicHeightInCtbsY) / (pps->num_tile_rows_minus1 + 1) + - ( j * sps->PicHeightInCtbsY) / (pps->num_tile_rows_minus1 + 1); + } + else + { + pps->colWidth[ pps->num_tile_columns_minus1 ] = sps->PicWidthInCtbsY; + for( uint64_t i = 0; i < pps->num_tile_columns_minus1; i++ ) + { + pps->colWidth[i] = nalu_get_exp_golomb_ue( bits ) + 1; /* column_width_minus1[i] */ + pps->colWidth[ pps->num_tile_columns_minus1 ] -= pps->colWidth[i]; + } + pps->rowHeight[ pps->num_tile_rows_minus1 ] = sps->PicHeightInCtbsY; + for( uint64_t j = 0; j < pps->num_tile_rows_minus1; j++ ) + { + pps->rowHeight[j] = nalu_get_exp_golomb_ue( bits ) + 1; /* row_height_minus1 [j] */ + pps->rowHeight[ pps->num_tile_rows_minus1 ] -= pps->rowHeight[j]; + } + } + pps->colBd[0] = 0; + for( uint64_t i = 0; i < pps->num_tile_columns_minus1; i++ ) + pps->colBd[i + 1] = pps->colBd[i] + pps->colWidth[i]; + pps->rowBd[0] = 0; + for( uint64_t j = 0; j < pps->num_tile_rows_minus1; j++ ) + pps->rowBd[j + 1] = pps->rowBd[j] + pps->rowHeight[j]; + lsmash_bits_get( bits, 1 ); /* loop_filter_across_tiles_enabled_flag */ + } + else + { + pps->num_tile_columns_minus1 = 0; + pps->num_tile_rows_minus1 = 0; + if( (err = hevc_allocate_tile_sizes( pps, 1, 1 )) < 0 ) + goto fail; + pps->colWidth [0] = sps->PicWidthInCtbsY; + pps->rowHeight[0] = sps->PicHeightInCtbsY; + pps->colBd [0] = 0; + pps->rowBd [0] = 0; + } + /* */ + /* Skip PPS extension. */ + lsmash_bits_empty( bits ); + if( bits->bs->error ) + goto fail; + pps->present = 1; + info->pps = *pps; + hevc_activate_vps( info, info->sps.video_parameter_set_id ); + return 0; +fail: + /* Revert SPS. */ + info->sps = temp_sps; + return err; +} + +int hevc_parse_sei +( + lsmash_bits_t *bits, + hevc_vps_t *vps, + hevc_sps_t *sps, + hevc_sei_t *sei, + hevc_nalu_header_t *nuh, + uint8_t *rbsp_buffer, + uint8_t *ebsp, + uint64_t ebsp_size +) +{ + uint64_t rbsp_size; + int err = nalu_import_rbsp_from_ebsp( bits, rbsp_buffer, &rbsp_size, ebsp, ebsp_size ); + if( err < 0 ) + return err; + uint8_t *rbsp_start = rbsp_buffer; + uint64_t rbsp_pos = 0; + do + { + /* sei_message() */ + uint32_t payloadType = 0; + for( uint8_t temp = lsmash_bits_get( bits, 8 ); ; temp = lsmash_bits_get( bits, 8 ) ) + { + /* 0xff : ff_byte + * otherwise: last_payload_type_byte */ + payloadType += temp; + ++rbsp_pos; + if( temp != 0xff ) + break; + } + uint32_t payloadSize = 0; + for( uint8_t temp = lsmash_bits_get( bits, 8 ); ; temp = lsmash_bits_get( bits, 8 ) ) + { + /* 0xff : ff_byte + * otherwise: last_payload_size_byte */ + payloadSize += temp; + ++rbsp_pos; + if( temp != 0xff ) + break; + } + if( nuh->nal_unit_type == HEVC_NALU_TYPE_PREFIX_SEI ) + { + if( payloadType == 1 ) + { + /* pic_timing */ + hevc_hrd_t *hrd = sps ? &sps->vui.hrd : vps ? &vps->hrd[0] : NULL; + if( !hrd ) + goto skip_sei_message; /* Any active VPS or SPS is not found. */ + sei->pic_timing.present = 1; + if( (sps && sps->vui.frame_field_info_present_flag) || vps->frame_field_info_present_flag ) + { + sei->pic_timing.pic_struct = lsmash_bits_get( bits, 4 ); + lsmash_bits_get( bits, 2 ); /* source_scan_type */ + lsmash_bits_get( bits, 1 ); /* duplicate_flag */ + } + if( hrd->CpbDpbDelaysPresentFlag ) + { + lsmash_bits_get( bits, hrd->au_cpb_removal_delay_length ); /* au_cpb_removal_delay_minus1 */ + lsmash_bits_get( bits, hrd->dpb_output_delay_length ); /* pic_dpb_output_delay */ + if( hrd->sub_pic_hrd_params_present_flag ) + { + lsmash_bits_get( bits, hrd->dpb_output_delay_du_length ); /* pic_dpb_output_du_delay */ + if( hrd->sub_pic_cpb_params_in_pic_timing_sei_flag ) + { + uint64_t num_decoding_units_minus1 = nalu_get_exp_golomb_ue( bits ); + int du_common_cpb_removal_delay_flag = lsmash_bits_get( bits, 1 ); + if( du_common_cpb_removal_delay_flag ) + /* du_common_cpb_removal_delay_increment_minus1 */ + lsmash_bits_get( bits, hrd->du_cpb_removal_delay_increment_length ); + for( uint64_t i = 0; i <= num_decoding_units_minus1; i++ ) + { + nalu_get_exp_golomb_ue( bits ); /* num_nalus_in_du_minus1 */ + if( !du_common_cpb_removal_delay_flag && i < num_decoding_units_minus1 ) + nalu_get_exp_golomb_ue( bits ); /* du_cpb_removal_delay_increment_minus1 */ + } + } + } + } + } + else if( payloadType == 3 ) + { + /* filler_payload + * FIXME: remove if array_completeness equal to 1. */ + return LSMASH_ERR_PATCH_WELCOME; + } + else if( payloadType == 6 ) + { + /* recovery_point */ + sei->recovery_point.present = 1; + sei->recovery_point.recovery_poc_cnt = nalu_get_exp_golomb_se( bits ); + lsmash_bits_get( bits, 1 ); /* exact_match_flag */ + sei->recovery_point.broken_link_flag = lsmash_bits_get( bits, 1 ); + } + else + goto skip_sei_message; + } + else if( nuh->nal_unit_type == HEVC_NALU_TYPE_SUFFIX_SEI ) + { + if( payloadType == 3 ) + { + /* filler_payload + * FIXME: remove if array_completeness equal to 1. */ + return LSMASH_ERR_PATCH_WELCOME; + } + else + goto skip_sei_message; + } + else + { +skip_sei_message: + lsmash_bits_get( bits, payloadSize * 8 ); + } + lsmash_bits_get_align( bits ); + rbsp_pos += payloadSize; + if( rbsp_pos > rbsp_size ) + { + rbsp_pos = rbsp_size; + if( *(rbsp_start + rbsp_pos) != 0x80 ) + { + lsmash_log( NULL, LSMASH_LOG_ERROR, "Invalid payloadSize is there within H.265/HEVC sei_message().\n" ); + return LSMASH_ERR_INVALID_DATA; + } + else + { + /* The last payloadSize is invalid but it probably can do recovery, so just log warning and break loop here. */ + lsmash_log( NULL, LSMASH_LOG_WARNING, "Invalid payloadSize is there within H.265/HEVC sei_message().\n" ); + break; /* redundant but for readability */ + } + } + } while( *(rbsp_start + rbsp_pos) != 0x80 ); /* All SEI messages are byte aligned at their end. + * Therefore, 0x80 shall be rbsp_trailing_bits(). */ + lsmash_bits_empty( bits ); + return bits->bs->error ? LSMASH_ERR_NAMELESS : 0; +} + +int hevc_parse_slice_segment_header +( + hevc_info_t *info, + hevc_nalu_header_t *nuh, + uint8_t *rbsp_buffer, + uint8_t *ebsp, + uint64_t ebsp_size +) +{ + lsmash_bits_t *bits = info->bits; + uint64_t rbsp_size; + int err = nalu_import_rbsp_from_ebsp( bits, rbsp_buffer, &rbsp_size, ebsp, LSMASH_MIN( ebsp_size, 50 ) ); + if( err < 0 ) + return err; + hevc_slice_info_t *slice = &info->slice; + memset( slice, 0, sizeof(hevc_slice_info_t) ); + slice->nalu_type = nuh->nal_unit_type; + slice->TemporalId = nuh->TemporalId; + slice->first_slice_segment_in_pic_flag = lsmash_bits_get( bits, 1 ); + if( nuh->nal_unit_type >= HEVC_NALU_TYPE_BLA_W_LP + && nuh->nal_unit_type <= HEVC_NALU_TYPE_RSV_IRAP_VCL23 ) + lsmash_bits_get( bits, 1 ); /* no_output_of_prior_pics_flag */ + slice->pic_parameter_set_id = nalu_get_exp_golomb_ue( bits ); + /* Get PPS by slice_pic_parameter_set_id. */ + hevc_pps_t *pps = hevc_get_pps( info->pps_list, slice->pic_parameter_set_id ); + if( !pps ) + return LSMASH_ERR_NAMELESS; + /* Get SPS by pps_seq_parameter_set_id. */ + hevc_sps_t *sps = hevc_get_sps( info->sps_list, pps->seq_parameter_set_id ); + if( !sps ) + return LSMASH_ERR_NAMELESS; + slice->video_parameter_set_id = sps->video_parameter_set_id; + slice->seq_parameter_set_id = pps->seq_parameter_set_id; + if( !slice->first_slice_segment_in_pic_flag ) + { + slice->dependent_slice_segment_flag = pps->dependent_slice_segments_enabled_flag ? lsmash_bits_get( bits, 1 ) : 0; + slice->segment_address = lsmash_bits_get( bits, lsmash_ceil_log2( sps->PicSizeInCtbsY ) ); + } + else + { + slice->dependent_slice_segment_flag = 0; + slice->segment_address = 0; + } + if( !slice->dependent_slice_segment_flag ) + { + /* independent slice segment + * The values of the slice segment header of dependent slice segment are inferred from the values + * for the preceding independent slice segment in decoding order, if some of the values are not present. */ + for( int i = 0; i < pps->num_extra_slice_header_bits; i++ ) + lsmash_bits_get( bits, 1 ); /* slice_reserved_flag[i] */ + slice->type = nalu_get_exp_golomb_ue( bits ); + if( pps->output_flag_present_flag ) + lsmash_bits_get( bits, 1 ); /* pic_output_flag */ + if( sps->separate_colour_plane_flag ) + lsmash_bits_get( bits, 1 ); /* colour_plane_id */ + if( nuh->nal_unit_type != HEVC_NALU_TYPE_IDR_W_RADL + && nuh->nal_unit_type != HEVC_NALU_TYPE_IDR_N_LP ) + { + slice->pic_order_cnt_lsb = lsmash_bits_get( bits, sps->log2_max_pic_order_cnt_lsb ); + if( !lsmash_bits_get( bits, 1 ) ) /* short_term_ref_pic_set_sps_flag */ + { + if( (err = hevc_short_term_ref_pic_set( bits, sps, sps->num_short_term_ref_pic_sets )) < 0 ) + return err; + } + else + { + int length = lsmash_ceil_log2( sps->num_short_term_ref_pic_sets ); + if( length > 0 ) + lsmash_bits_get( bits, length ); /* short_term_ref_pic_set_idx */ + } + if( sps->long_term_ref_pics_present_flag ) + { + uint64_t num_long_term_sps = sps->num_long_term_ref_pics_sps > 0 ? nalu_get_exp_golomb_ue( bits ) : 0; + uint64_t num_long_term_pics = nalu_get_exp_golomb_ue( bits ); + for( uint64_t i = 0; i < num_long_term_sps + num_long_term_pics; i++ ) + { + if( i < num_long_term_sps ) + { + int length = lsmash_ceil_log2( sps->num_long_term_ref_pics_sps ); + if( length > 0 ) + lsmash_bits_get( bits, length ); /* lt_idx_sps[i] */ + } + else + { + lsmash_bits_get( bits, sps->log2_max_pic_order_cnt_lsb ); /* poc_lsb_lt [i] */ + lsmash_bits_get( bits, 1 ); /* used_by_curr_pic_lt_flag[i] */ + } + if( lsmash_bits_get( bits, 1 ) ) /* delta_poc_msb_present_flag[i] */ + nalu_get_exp_golomb_ue( bits ); /* delta_poc_msb_cycle_lt [i] */ + } + } + if( sps->temporal_mvp_enabled_flag ) + lsmash_bits_get( bits, 1 ); /* slice_temporal_mvp_enabled_flag */ + } + else + /* For IDR-pictures, slice_pic_order_cnt_lsb is inferred to be 0. */ + slice->pic_order_cnt_lsb = 0; + } + lsmash_bits_empty( bits ); + if( bits->bs->error ) + return LSMASH_ERR_NAMELESS; + info->sps = *sps; + info->pps = *pps; + return 0; +} + +static int hevc_get_vps_id +( + uint8_t *ps_ebsp, + uint32_t ps_ebsp_length, + uint8_t *ps_id +) +{ + /* the number of bits of vps_id = 4 + * (4 - 1) / 8 + 1 = 1 bytes */ + *ps_id = (*ps_ebsp >> 4) & 0x0F; /* vps_video_parameter_set_id */ + return 0; +} + +static int hevc_get_sps_id +( + uint8_t *ps_ebsp, + uint32_t ps_ebsp_length, + uint8_t *ps_id +) +{ + /* the maximum number of bits of sps_id = 9: 0b00001XXXX + * (8 + 688 + 9 - 1) / 8 + 1 = 89 bytes + * Here more additional bytes because there might be emulation_prevention_three_byte(s). */ + lsmash_bits_t bits = { 0 }; + lsmash_bs_t bs = { 0 }; + uint8_t rbsp_buffer[128]; + uint8_t buffer [128]; + bs.buffer.data = buffer; + bs.buffer.alloc = 128; + lsmash_bits_init( &bits, &bs ); + uint64_t rbsp_size; + int err = nalu_import_rbsp_from_ebsp( &bits, rbsp_buffer, &rbsp_size, ps_ebsp, LSMASH_MIN( ps_ebsp_length, 128 ) ); + if( err < 0 ) + return err; + /* Skip sps_video_parameter_set_id and sps_temporal_id_nesting_flag. */ + uint8_t sps_max_sub_layers_minus1 = (lsmash_bits_get( &bits, 8 ) >> 1) & 0x07; + /* profile_tier_level() costs at most 688 bits. */ + hevc_ptl_t sps_ptl; + hevc_parse_profile_tier_level( &bits, &sps_ptl, sps_max_sub_layers_minus1 ); + uint64_t sps_seq_parameter_set_id = nalu_get_exp_golomb_ue( &bits ); + if( sps_seq_parameter_set_id > HEVC_MAX_SPS_ID ) + return LSMASH_ERR_INVALID_DATA; + *ps_id = sps_seq_parameter_set_id; + return bs.error ? LSMASH_ERR_NAMELESS : 0; +} + +static int hevc_get_pps_id +( + uint8_t *ps_ebsp, + uint32_t ps_ebsp_length, + uint8_t *ps_id +) +{ + /* the maximum number of bits of pps_id = 13: 0b0000001XXXXXX + * (13 - 1) / 8 + 1 = 2 bytes + * Why +1? Because there might be an emulation_prevention_three_byte. */ + lsmash_bits_t bits = { 0 }; + lsmash_bs_t bs = { 0 }; + uint8_t rbsp_buffer[3]; + uint8_t buffer [3]; + bs.buffer.data = buffer; + bs.buffer.alloc = 3; + lsmash_bits_init( &bits, &bs ); + uint64_t rbsp_size; + int err = nalu_import_rbsp_from_ebsp( &bits, rbsp_buffer, &rbsp_size, ps_ebsp, LSMASH_MIN( ps_ebsp_length, 3 ) ); + if( err < 0 ) + return err; + uint64_t pic_parameter_set_id = nalu_get_exp_golomb_ue( &bits ); + if( pic_parameter_set_id > HEVC_MAX_PPS_ID ) + return LSMASH_ERR_INVALID_DATA; + *ps_id = pic_parameter_set_id; + return bs.error ? LSMASH_ERR_NAMELESS : 0; +} + +static inline int hevc_get_ps_id +( + uint8_t *ps_ebsp, + uint32_t ps_ebsp_length, + uint8_t *ps_id, + lsmash_hevc_dcr_nalu_type ps_type +) +{ + int (*get_ps_id)( uint8_t *ps_ebsp, uint32_t ps_ebsp_length, uint8_t *ps_id ) + = ps_type == HEVC_DCR_NALU_TYPE_VPS ? hevc_get_vps_id + : ps_type == HEVC_DCR_NALU_TYPE_SPS ? hevc_get_sps_id + : ps_type == HEVC_DCR_NALU_TYPE_PPS ? hevc_get_pps_id + : NULL; + return get_ps_id ? get_ps_id( ps_ebsp, ps_ebsp_length, ps_id ) : LSMASH_ERR_INVALID_DATA; +} + +static inline hevc_parameter_array_t *hevc_get_parameter_set_array +( + lsmash_hevc_specific_parameters_t *param, + lsmash_hevc_dcr_nalu_type ps_type +) +{ + if( !param->parameter_arrays ) + return NULL; + if( ps_type >= HEVC_DCR_NALU_TYPE_NUM ) + return NULL; + return ¶m->parameter_arrays->ps_array[ps_type]; +} + +static inline lsmash_entry_list_t *hevc_get_parameter_set_list +( + lsmash_hevc_specific_parameters_t *param, + lsmash_hevc_dcr_nalu_type ps_type +) +{ + if( !param->parameter_arrays ) + return NULL; + if( ps_type >= HEVC_DCR_NALU_TYPE_NUM ) + return NULL; + return param->parameter_arrays->ps_array[ps_type].list; +} + +static lsmash_entry_t *hevc_get_ps_entry_from_param +( + lsmash_hevc_specific_parameters_t *param, + lsmash_hevc_dcr_nalu_type ps_type, + uint8_t ps_id +) +{ + int (*get_ps_id)( uint8_t *ps_ebsp, uint32_t ps_ebsp_length, uint8_t *ps_id ) + = ps_type == HEVC_DCR_NALU_TYPE_VPS ? hevc_get_vps_id + : ps_type == HEVC_DCR_NALU_TYPE_SPS ? hevc_get_sps_id + : ps_type == HEVC_DCR_NALU_TYPE_PPS ? hevc_get_pps_id + : NULL; + if( !get_ps_id ) + return NULL; + lsmash_entry_list_t *list = hevc_get_parameter_set_list( param, ps_type ); + if( !list ) + return NULL; + for( lsmash_entry_t *entry = list->head; entry; entry = entry->next ) + { + isom_dcr_ps_entry_t *ps = (isom_dcr_ps_entry_t *)entry->data; + if( !ps ) + return NULL; + uint8_t param_ps_id; + if( get_ps_id( ps->nalUnit + HEVC_MIN_NALU_HEADER_LENGTH, + ps->nalUnitLength - HEVC_MIN_NALU_HEADER_LENGTH, ¶m_ps_id ) < 0 ) + return NULL; + if( ps_id == param_ps_id ) + return entry; + } + return NULL; +} + +static inline void hevc_update_picture_type +( + hevc_picture_info_t *picture, + hevc_slice_info_t *slice +) +{ + if( picture->type == HEVC_PICTURE_TYPE_I_P ) + { + if( slice->type == HEVC_SLICE_TYPE_B ) + picture->type = HEVC_PICTURE_TYPE_I_P_B; + } + else if( picture->type == HEVC_PICTURE_TYPE_I ) + { + if( slice->type == HEVC_SLICE_TYPE_P ) + picture->type = HEVC_PICTURE_TYPE_I_P; + else if( slice->type == HEVC_SLICE_TYPE_B ) + picture->type = HEVC_PICTURE_TYPE_I_P_B; + } + else if( picture->type == HEVC_PICTURE_TYPE_NONE ) + { + if( slice->type == HEVC_SLICE_TYPE_P ) + picture->type = HEVC_PICTURE_TYPE_I_P; + else if( slice->type == HEVC_SLICE_TYPE_B ) + picture->type = HEVC_PICTURE_TYPE_I_P_B; + else if( slice->type == HEVC_SLICE_TYPE_I ) + picture->type = HEVC_PICTURE_TYPE_I; + } +#if 0 + fprintf( stderr, "Picture type = %s\n", picture->type == HEVC_PICTURE_TYPE_I_P ? "P" + : picture->type == HEVC_PICTURE_TYPE_I_P_B ? "B" + : picture->type == HEVC_PICTURE_TYPE_I ? "I" ); +#endif +} + +/* Shall be called at least once per picture. */ +void hevc_update_picture_info_for_slice +( + hevc_info_t *info, + hevc_picture_info_t *picture, + hevc_slice_info_t *slice +) +{ + assert( info ); + picture->has_primary |= !slice->dependent_slice_segment_flag; + hevc_update_picture_type( picture, slice ); + /* Mark 'used' on active parameter sets. */ + uint8_t ps_id[3] = { slice->video_parameter_set_id, slice->seq_parameter_set_id, slice->pic_parameter_set_id }; + for( int i = 0; i < 3; i++ ) + { + lsmash_hevc_dcr_nalu_type ps_type = (lsmash_hevc_dcr_nalu_type)i; + lsmash_entry_t *entry = hevc_get_ps_entry_from_param( &info->hvcC_param, ps_type, ps_id[i] ); + if( entry && entry->data ) + { + isom_dcr_ps_entry_t *ps = (isom_dcr_ps_entry_t *)entry->data; + if( ps->unused ) + lsmash_append_hevc_dcr_nalu( &info->hvcC_param, ps_type, ps->nalUnit, ps->nalUnitLength ); + } + } + /* Discard this slice info. */ + slice->present = 0; +} + +/* Shall be called exactly once per picture. */ +void hevc_update_picture_info +( + hevc_info_t *info, + hevc_picture_info_t *picture, + hevc_slice_info_t *slice, + hevc_sps_t *sps, + hevc_sei_t *sei +) +{ + picture->irap = slice->nalu_type >= HEVC_NALU_TYPE_BLA_W_LP && slice->nalu_type <= HEVC_NALU_TYPE_CRA; + picture->idr = slice->nalu_type == HEVC_NALU_TYPE_IDR_W_RADL || slice->nalu_type == HEVC_NALU_TYPE_IDR_N_LP; + picture->broken_link = slice->nalu_type >= HEVC_NALU_TYPE_BLA_W_LP && slice->nalu_type <= HEVC_NALU_TYPE_BLA_N_LP; + picture->radl = slice->nalu_type == HEVC_NALU_TYPE_RADL_N || slice->nalu_type == HEVC_NALU_TYPE_RADL_R; + picture->rasl = slice->nalu_type == HEVC_NALU_TYPE_RASL_N || slice->nalu_type == HEVC_NALU_TYPE_RASL_R; + picture->sublayer_nonref = slice->nalu_type <= HEVC_NALU_TYPE_RSV_VCL_R15 && ((slice->nalu_type & 0x01) == 0); + picture->closed_rap = slice->nalu_type >= HEVC_NALU_TYPE_BLA_W_RADL && slice->nalu_type <= HEVC_NALU_TYPE_IDR_N_LP; + picture->random_accessible = picture->irap; + picture->TemporalId = slice->TemporalId; + picture->pic_parameter_set_id = slice->pic_parameter_set_id; + picture->poc_lsb = slice->pic_order_cnt_lsb; + hevc_update_picture_info_for_slice( info, picture, slice ); + picture->independent = (picture->type == HEVC_PICTURE_TYPE_I); + picture->field_coded = sps->vui.field_seq_flag; + if( sei->pic_timing.present ) + { + if( sei->pic_timing.pic_struct < 13 ) + { + static const uint8_t delta[13] = { 2, 1, 1, 2, 2, 3, 3, 4, 6, 1, 1, 1, 1 }; + picture->delta = delta[ sei->pic_timing.pic_struct ]; + } + else + /* Reserved values in the spec we refer to. */ + picture->delta = picture->field_coded ? 1 : 2; + sei->pic_timing.present = 0; + } + else + picture->delta = picture->field_coded ? 1 : 2; + if( sei->recovery_point.present ) + { + picture->random_accessible |= sei->recovery_point.present; + picture->recovery_poc_cnt = sei->recovery_point.recovery_poc_cnt; + picture->broken_link |= sei->recovery_point.broken_link_flag; + sei->recovery_point.present = 0; + } + else + picture->recovery_poc_cnt = 0; +} + +static uint64_t hevc_get_ctb_address_in_tile_scan +( + hevc_sps_t *sps, + hevc_pps_t *pps, + uint64_t segment_address, + uint64_t *TileId +) +{ + uint64_t tbX = segment_address % sps->PicWidthInCtbsY; + uint64_t tbY = segment_address / sps->PicWidthInCtbsY; + uint32_t tileX = pps->num_tile_columns_minus1; + for( uint32_t i = 0; i <= pps->num_tile_columns_minus1; i++ ) + if( tbX >= pps->colBd[i] ) + tileX = i; + uint32_t tileY = pps->num_tile_rows_minus1; + for( uint32_t j = 0; j <= pps->num_tile_rows_minus1; j++ ) + if( tbY >= pps->rowBd[j] ) + tileY = j; + uint64_t CtbAddrInTs = 0; + for( uint32_t i = 0; i < tileX; i++ ) + CtbAddrInTs += pps->rowHeight[tileY] * pps->colWidth[i]; + for( uint32_t j = 0; j < tileY; j++ ) + CtbAddrInTs += sps->PicWidthInCtbsY * pps->rowHeight[j]; + CtbAddrInTs += (tbY - pps->rowBd[tileY]) * pps->colWidth[tileX] + tbX - pps->colBd[tileX]; + *TileId = (uint64_t)tileY * (pps->num_tile_columns_minus1 + 1) + tileX; + return CtbAddrInTs; +} + +int hevc_find_au_delimit_by_slice_info +( + hevc_info_t *info, + hevc_slice_info_t *slice, + hevc_slice_info_t *prev_slice +) +{ + /* 7.4.2.4.5 Order of VCL NAL units and association to coded pictures + * - The first VCL NAL unit of the coded picture shall have first_slice_segment_in_pic_flag equal to 1. */ + if( slice->first_slice_segment_in_pic_flag ) + return 1; + /* The value of TemporalId shall be the same for all VCL NAL units of an access unit. */ + if( slice->TemporalId != prev_slice->TemporalId ) + return 1; + /* 7.4.2.4.5 Order of VCL NAL units and association to coded pictures + * When either of the following conditions is true, both the current and the previous coded slice segment NAL units + * shall belong to the same coded picture. + * - TileId[ CtbAddrRsToTs[ prev_slice->segment_address ] ] < TileId[ CtbAddrRsToTs[ slice->segment_address ] ] + * - TileId[ CtbAddrRsToTs[ prev_slice->segment_address ] ] == TileId[ CtbAddrRsToTs[ slice->segment_address ] ] + * && CtbAddrRsToTs[ prev_slice->segment_address ] < CtbAddrRsToTs[ slice->segment_address ] + */ + hevc_pps_t *prev_pps = hevc_get_pps( info->pps_list, prev_slice->pic_parameter_set_id ); + if( !prev_pps ) + return 0; + hevc_sps_t *prev_sps = hevc_get_sps( info->sps_list, prev_pps->seq_parameter_set_id ); + if( !prev_sps ) + return 0; + uint64_t currTileId; + uint64_t prevTileId; + uint64_t currCtbAddrInTs = hevc_get_ctb_address_in_tile_scan( &info->sps, &info->pps, slice->segment_address, &currTileId ); + uint64_t prevCtbAddrInTs = hevc_get_ctb_address_in_tile_scan( prev_sps, prev_pps, prev_slice->segment_address, &prevTileId ); + if( prevTileId < currTileId ) + return 0; + if( prevTileId == currTileId && prevCtbAddrInTs < currCtbAddrInTs ) + return 0; + return 1; +} + +int hevc_find_au_delimit_by_nalu_type +( + uint8_t nalu_type, + uint8_t prev_nalu_type +) +{ + /* 7.4.2.4.4 Order of NAL units and coded pictures and their association to access units */ + if( prev_nalu_type <= HEVC_NALU_TYPE_RSV_VCL31 ) + /* The first of any of the following NAL units after the last VCL NAL unit of a coded picture + * specifies the start of a new access unit: + * - access unit delimiter NAL unit (when present) + * - VPS NAL unit (when present) + * - SPS NAL unit (when present) + * - PPS NAL unit (when present) + * - Prefix SEI NAL unit (when present) + * - NAL units with nal_unit_type in the range of RSV_NVCL41..RSV_NVCL44 (when present) + * - NAL units with nal_unit_type in the range of UNSPEC48..UNSPEC55 (when present) + * - first VCL NAL unit of a coded picture (always present) */ + return (nalu_type >= HEVC_NALU_TYPE_VPS && nalu_type <= HEVC_NALU_TYPE_AUD) + || (nalu_type == HEVC_NALU_TYPE_PREFIX_SEI) + || (nalu_type >= HEVC_NALU_TYPE_RSV_NVCL41 && nalu_type <= HEVC_NALU_TYPE_RSV_NVCL44) + || (nalu_type >= HEVC_NALU_TYPE_UNSPEC48 && nalu_type <= HEVC_NALU_TYPE_UNSPEC55); + else if( prev_nalu_type == HEVC_NALU_TYPE_EOS ) + /* An end of sequence NAL unit shall be the last NAL unit in the access unit unless the next + * NAL unit is an end of bitstream NAL unit. */ + return (nalu_type != HEVC_NALU_TYPE_EOB); + else + /* An end of bitstream NAL unit shall be the last NAL unit in the access unit. + * Thus, the next NAL unit shall be the first NAL unit in the next access unit. */ + return (prev_nalu_type == HEVC_NALU_TYPE_EOB); +} + +int hevc_supplement_buffer +( + hevc_stream_buffer_t *sb, + hevc_access_unit_t *au, + uint32_t size +) +{ + lsmash_multiple_buffers_t *bank = lsmash_resize_multiple_buffers( sb->bank, size ); + if( !bank ) + return LSMASH_ERR_MEMORY_ALLOC; + sb->bank = bank; + sb->rbsp = lsmash_withdraw_buffer( bank, 1 ); + if( au && bank->number_of_buffers == 3 ) + { + au->data = lsmash_withdraw_buffer( bank, 2 ); + au->incomplete_data = lsmash_withdraw_buffer( bank, 3 ); + } + return 0; +} + +static void hevc_bs_put_parameter_sets +( + lsmash_bs_t *bs, + lsmash_entry_list_t *dcr_ps_list, + uint32_t max_dcr_ps_count +) +{ + uint32_t dcr_ps_count = 0; + for( lsmash_entry_t *entry = dcr_ps_list->head; entry && dcr_ps_count < max_dcr_ps_count; entry = entry->next ) + { + isom_dcr_ps_entry_t *ps = (isom_dcr_ps_entry_t *)entry->data; + if( ps && !ps->unused ) + { + lsmash_bs_put_be16( bs, ps->nalUnitLength ); + lsmash_bs_put_bytes( bs, ps->nalUnitLength, ps->nalUnit ); + } + else + continue; + ++dcr_ps_count; + } +} + +uint8_t *lsmash_create_hevc_specific_info +( + lsmash_hevc_specific_parameters_t *param, + uint32_t *data_length +) +{ + if( !param || !param->parameter_arrays || !data_length ) + return NULL; + if( param->lengthSizeMinusOne != 0 + && param->lengthSizeMinusOne != 1 + && param->lengthSizeMinusOne != 3 ) + return NULL; + hevc_parameter_array_t *param_arrays[HEVC_DCR_NALU_TYPE_NUM]; + lsmash_entry_list_t *dcr_ps_list [HEVC_DCR_NALU_TYPE_NUM]; + for( int i = 0; i < HEVC_DCR_NALU_TYPE_NUM; i++ ) + { + param_arrays[i] = ¶m->parameter_arrays->ps_array[i]; + dcr_ps_list [i] = param_arrays[i]->list; + } + /* VPS, SPS and PPS are mandatory. */ + if( !dcr_ps_list[0] || !dcr_ps_list[0]->head || dcr_ps_list[0]->entry_count == 0 + || !dcr_ps_list[1] || !dcr_ps_list[1]->head || dcr_ps_list[1]->entry_count == 0 + || !dcr_ps_list[2] || !dcr_ps_list[2]->head || dcr_ps_list[2]->entry_count == 0 ) + return NULL; + /* Calculate enough buffer size. */ + static const uint32_t max_dcr_ps_count[HEVC_DCR_NALU_TYPE_NUM] = + { + HEVC_MAX_VPS_ID + 1, + HEVC_MAX_SPS_ID + 1, + HEVC_MAX_PPS_ID + 1, + UINT16_MAX, + UINT16_MAX + }; + uint32_t ps_count[HEVC_DCR_NALU_TYPE_NUM] = { 0 }; + for( int i = 0; i < HEVC_DCR_NALU_TYPE_NUM; i++ ) + if( dcr_ps_list[i] ) + for( lsmash_entry_t *entry = dcr_ps_list[i]->head; entry && ps_count[i] < max_dcr_ps_count[i]; entry = entry->next ) + { + isom_dcr_ps_entry_t *ps = (isom_dcr_ps_entry_t *)entry->data; + if( !ps ) + return NULL; + if( ps->unused ) + continue; + ++ps_count[i]; + } + /* Create an HEVCConfigurationBox */ + lsmash_bs_t *bs = lsmash_bs_create(); + if( !bs ) + return NULL; + lsmash_bs_put_be32( bs, 0 ); /* box size */ + lsmash_bs_put_be32( bs, ISOM_BOX_TYPE_HVCC.fourcc ); /* box type: 'hvcC' */ + lsmash_bs_put_byte( bs, HVCC_CONFIGURATION_VERSION ); /* configurationVersion */ + uint8_t temp8 = (param->general_profile_space << 6) + | (param->general_tier_flag << 5) + | param->general_profile_idc; + lsmash_bs_put_byte( bs, temp8 ); + lsmash_bs_put_be32( bs, param->general_profile_compatibility_flags ); + lsmash_bs_put_be32( bs, param->general_constraint_indicator_flags >> 16 ); + lsmash_bs_put_be16( bs, param->general_constraint_indicator_flags ); + lsmash_bs_put_byte( bs, param->general_level_idc ); + lsmash_bs_put_be16( bs, param->min_spatial_segmentation_idc | 0xF000 ); + lsmash_bs_put_byte( bs, param->parallelismType | 0xFC ); + lsmash_bs_put_byte( bs, param->chromaFormat | 0xFC ); + lsmash_bs_put_byte( bs, param->bitDepthLumaMinus8 | 0xF8 ); + lsmash_bs_put_byte( bs, param->bitDepthChromaMinus8 | 0xF8 ); + lsmash_bs_put_be16( bs, param->avgFrameRate ); + temp8 = (param->constantFrameRate << 6) + | (param->numTemporalLayers << 3) + | (param->temporalIdNested << 2) + | param->lengthSizeMinusOne; + lsmash_bs_put_byte( bs, temp8 ); + uint8_t numOfArrays = !!ps_count[0] + + !!ps_count[1] + + !!ps_count[2] + + !!ps_count[3] + + !!ps_count[4]; + lsmash_bs_put_byte( bs, numOfArrays ); + for( uint8_t i = 0; i < numOfArrays; i++ ) + { + temp8 = (param_arrays[i]->array_completeness << 7) | param_arrays[i]->NAL_unit_type; + lsmash_bs_put_byte( bs, temp8 ); + lsmash_bs_put_be16( bs, ps_count[i] ); + hevc_bs_put_parameter_sets( bs, dcr_ps_list[i], ps_count[i] ); + } + uint8_t *data = lsmash_bs_export_data( bs, data_length ); + lsmash_bs_cleanup( bs ); + /* Update box size. */ + LSMASH_SET_BE32( data, *data_length ); + return data; +} + +static inline int hevc_validate_dcr_nalu_type +( + lsmash_hevc_dcr_nalu_type ps_type, + void *ps_data, + uint32_t ps_length +) +{ + if( !ps_data || ps_length < 3 ) + return LSMASH_ERR_INVALID_DATA; + if( ps_type != HEVC_DCR_NALU_TYPE_VPS + && ps_type != HEVC_DCR_NALU_TYPE_SPS + && ps_type != HEVC_DCR_NALU_TYPE_PPS + && ps_type != HEVC_DCR_NALU_TYPE_PREFIX_SEI + && ps_type != HEVC_DCR_NALU_TYPE_SUFFIX_SEI ) + return LSMASH_ERR_INVALID_DATA; + uint8_t nalu_type = (*((uint8_t *)ps_data) >> 1) & 0x3f; + if( nalu_type != HEVC_NALU_TYPE_VPS + && nalu_type != HEVC_NALU_TYPE_SPS + && nalu_type != HEVC_NALU_TYPE_PPS + && nalu_type != HEVC_NALU_TYPE_PREFIX_SEI + && nalu_type != HEVC_NALU_TYPE_SUFFIX_SEI ) + return LSMASH_ERR_INVALID_DATA; + if( (ps_type == HEVC_DCR_NALU_TYPE_VPS && nalu_type != HEVC_NALU_TYPE_VPS) + || (ps_type == HEVC_DCR_NALU_TYPE_SPS && nalu_type != HEVC_NALU_TYPE_SPS) + || (ps_type == HEVC_DCR_NALU_TYPE_PPS && nalu_type != HEVC_NALU_TYPE_PPS) + || (ps_type == HEVC_DCR_NALU_TYPE_PREFIX_SEI && nalu_type != HEVC_NALU_TYPE_PREFIX_SEI) + || (ps_type == HEVC_DCR_NALU_TYPE_SUFFIX_SEI && nalu_type != HEVC_NALU_TYPE_SUFFIX_SEI) ) + return LSMASH_ERR_INVALID_DATA; + return 0; +} + +static lsmash_dcr_nalu_appendable hevc_check_vps_appendable +( + lsmash_bits_t *bits, + uint8_t *rbsp_buffer, + lsmash_hevc_specific_parameters_t *param, + uint8_t *ps_data, + uint32_t ps_length, + lsmash_entry_list_t *ps_list +) +{ + hevc_vps_t vps; + if( hevc_parse_vps_minimally( bits, &vps, rbsp_buffer, + ps_data + HEVC_MIN_NALU_HEADER_LENGTH, + ps_length - HEVC_MIN_NALU_HEADER_LENGTH ) < 0 ) + return DCR_NALU_APPEND_ERROR; + /* The value of profile_space must be identical in all the parameter sets in a single HEVC Decoder Configuration Record. */ + if( vps.ptl.general.profile_space != param->general_profile_space ) + return DCR_NALU_APPEND_NEW_DCR_REQUIRED; + /* FIXME */ + if( vps.ptl.general.profile_idc != param->general_profile_idc ) + return DCR_NALU_APPEND_NEW_DCR_REQUIRED; + for( lsmash_entry_t *entry = ps_list->head; entry; entry = entry->next ) + { + isom_dcr_ps_entry_t *ps = (isom_dcr_ps_entry_t *)entry->data; + if( !ps ) + return DCR_NALU_APPEND_ERROR; + if( ps->unused ) + continue; + uint8_t param_vps_id; + if( hevc_get_vps_id( ps->nalUnit + HEVC_MIN_NALU_HEADER_LENGTH, + ps->nalUnitLength - HEVC_MIN_NALU_HEADER_LENGTH, ¶m_vps_id ) < 0 ) + return DCR_NALU_APPEND_ERROR; + if( param_vps_id == vps.video_parameter_set_id ) + /* VPS that has the same video_parameter_set_id already exists with different form. */ + return DCR_NALU_APPEND_NEW_DCR_REQUIRED; + } + return DCR_NALU_APPEND_POSSIBLE; +} + +static lsmash_dcr_nalu_appendable hevc_check_sps_appendable +( + lsmash_bits_t *bits, + uint8_t *rbsp_buffer, + lsmash_hevc_specific_parameters_t *param, + uint8_t *ps_data, + uint32_t ps_length, + lsmash_entry_list_t *ps_list +) +{ + hevc_sps_t sps; + if( hevc_parse_sps_minimally( bits, &sps, rbsp_buffer, + ps_data + HEVC_MIN_NALU_HEADER_LENGTH, + ps_length - HEVC_MIN_NALU_HEADER_LENGTH ) < 0 ) + return DCR_NALU_APPEND_ERROR; + lsmash_bits_empty( bits ); + /* The values of profile_space, chromaFormat, bitDepthLumaMinus8 and bitDepthChromaMinus8 + * must be identical in all the parameter sets in a single HEVC Decoder Configuration Record. */ + if( sps.ptl.general.profile_space != param->general_profile_space + || sps.chroma_format_idc != param->chromaFormat + || sps.bit_depth_luma_minus8 != param->bitDepthLumaMinus8 + || sps.bit_depth_chroma_minus8 != param->bitDepthChromaMinus8 ) + return DCR_NALU_APPEND_NEW_DCR_REQUIRED; + /* FIXME; If the sequence parameter sets are marked with different profiles, + * and the relevant profile compatibility flags are all zero, + * then the stream may need examination to determine which profile, if any, the stream conforms to. + * If the stream is not examined, or the examination reveals that there is no profile to which the stream conforms, + * then the stream must be split into two or more sub-streams with separate configuration records in which these rules can be met. */ +#if 0 + if( sps.ptl.general.profile_idc != param->general_profile_idc + && (sps.ptl.general.profile_compatibility_flags & param->general_profile_compatibility_flags) ) +#else + if( sps.ptl.general.profile_idc != param->general_profile_idc ) +#endif + return DCR_NALU_APPEND_NEW_DCR_REQUIRED; + /* Forbidden to duplicate SPS that has the same seq_parameter_set_id with different form within the same configuration record. */ + for( lsmash_entry_t *entry = ps_list->head; entry; entry = entry->next ) + { + isom_dcr_ps_entry_t *ps = (isom_dcr_ps_entry_t *)entry->data; + if( !ps ) + return DCR_NALU_APPEND_ERROR; + if( ps->unused ) + continue; + uint8_t param_sps_id; + if( hevc_get_sps_id( ps->nalUnit + HEVC_MIN_NALU_HEADER_LENGTH, + ps->nalUnitLength - HEVC_MIN_NALU_HEADER_LENGTH, ¶m_sps_id ) < 0 ) + return DCR_NALU_APPEND_ERROR; + if( param_sps_id == sps.seq_parameter_set_id ) + /* SPS that has the same seq_parameter_set_id already exists with different form. */ + return DCR_NALU_APPEND_NEW_DCR_REQUIRED; + if( entry == ps_list->head ) + { + /* Check if the cropped visual presentation sizes, the sample aspect ratios, the colour descriptions and + * the default display windows are different. */ + hevc_sps_t first_sps; + if( hevc_parse_sps_minimally( bits, &first_sps, rbsp_buffer, + ps->nalUnit + HEVC_MIN_NALU_HEADER_LENGTH, + ps->nalUnitLength - HEVC_MIN_NALU_HEADER_LENGTH ) < 0 ) + return DCR_NALU_APPEND_ERROR; + if( sps.cropped_width != first_sps.cropped_width + || sps.cropped_height != first_sps.cropped_height + || sps.vui.sar_width != first_sps.vui.sar_width + || sps.vui.sar_height != first_sps.vui.sar_height + || sps.vui.colour_primaries != first_sps.vui.colour_primaries + || sps.vui.transfer_characteristics != first_sps.vui.transfer_characteristics + || sps.vui.matrix_coeffs != first_sps.vui.matrix_coeffs + || sps.vui.video_full_range_flag != first_sps.vui.video_full_range_flag + || sps.vui.def_disp_win_offset.left .n != first_sps.vui.def_disp_win_offset.left .n + || sps.vui.def_disp_win_offset.right .n != first_sps.vui.def_disp_win_offset.right .n + || sps.vui.def_disp_win_offset.top .n != first_sps.vui.def_disp_win_offset.top .n + || sps.vui.def_disp_win_offset.bottom.n != first_sps.vui.def_disp_win_offset.bottom.n ) + return DCR_NALU_APPEND_NEW_SAMPLE_ENTRY_REQUIRED; + } + } + return DCR_NALU_APPEND_POSSIBLE; +} + +static lsmash_dcr_nalu_appendable hevc_check_pps_appendable +( + uint8_t *ps_data, + uint32_t ps_length, + lsmash_entry_list_t *ps_list +) +{ + uint8_t pps_id; + if( hevc_get_pps_id( ps_data + HEVC_MIN_NALU_HEADER_LENGTH, + ps_length - HEVC_MIN_NALU_HEADER_LENGTH, &pps_id ) < 0 ) + return DCR_NALU_APPEND_ERROR; + for( lsmash_entry_t *entry = ps_list->head; entry; entry = entry->next ) + { + isom_dcr_ps_entry_t *ps = (isom_dcr_ps_entry_t *)entry->data; + if( !ps ) + return DCR_NALU_APPEND_ERROR; + if( ps->unused ) + continue; + uint8_t param_pps_id; + if( hevc_get_pps_id( ps->nalUnit + HEVC_MIN_NALU_HEADER_LENGTH, + ps->nalUnitLength - HEVC_MIN_NALU_HEADER_LENGTH, ¶m_pps_id ) < 0 ) + return DCR_NALU_APPEND_ERROR; + if( pps_id == param_pps_id ) + /* PPS that has the same pic_parameter_set_id already exists with different form. */ + return DCR_NALU_APPEND_NEW_DCR_REQUIRED; + } + return DCR_NALU_APPEND_POSSIBLE; +} + +lsmash_dcr_nalu_appendable lsmash_check_hevc_dcr_nalu_appendable +( + lsmash_hevc_specific_parameters_t *param, + lsmash_hevc_dcr_nalu_type ps_type, + void *_ps_data, + uint32_t ps_length +) +{ + uint8_t *ps_data = _ps_data; + if( !param ) + return DCR_NALU_APPEND_ERROR; + if( hevc_validate_dcr_nalu_type( ps_type, ps_data, ps_length ) < 0 ) + return DCR_NALU_APPEND_ERROR; + /* Check whether the same parameter set already exsits or not. */ + lsmash_entry_list_t *ps_list = hevc_get_parameter_set_list( param, ps_type ); + if( !ps_list || !ps_list->head ) + return DCR_NALU_APPEND_POSSIBLE; /* No parameter set */ + switch( nalu_check_same_ps_existence( ps_list, ps_data, ps_length ) ) + { + case 0 : break; + case 1 : return DCR_NALU_APPEND_DUPLICATED; /* The same parameter set already exists. */ + default : return DCR_NALU_APPEND_ERROR; /* An error occured. */ + } + /* Check the number of parameter sets in HEVC Decoder Configuration Record. */ + uint32_t ps_count; + if( nalu_get_ps_count( ps_list, &ps_count ) < 0 ) + return DCR_NALU_APPEND_ERROR; + if( (ps_type == HEVC_DCR_NALU_TYPE_VPS && ps_count >= HEVC_MAX_VPS_ID) + || (ps_type == HEVC_DCR_NALU_TYPE_SPS && ps_count >= HEVC_MAX_SPS_ID) + || (ps_type == HEVC_DCR_NALU_TYPE_PPS && ps_count >= HEVC_MAX_PPS_ID) + || (ps_type == HEVC_DCR_NALU_TYPE_PREFIX_SEI && ps_count >= UINT16_MAX) + || (ps_type == HEVC_DCR_NALU_TYPE_SUFFIX_SEI && ps_count >= UINT16_MAX) ) + return DCR_NALU_APPEND_NEW_DCR_REQUIRED; /* No more appendable parameter sets. */ + if( ps_type == HEVC_DCR_NALU_TYPE_PREFIX_SEI + || ps_type == HEVC_DCR_NALU_TYPE_SUFFIX_SEI ) + return DCR_NALU_APPEND_POSSIBLE; + /* Check whether a new specific info is needed or not. */ + if( ps_type == HEVC_DCR_NALU_TYPE_PPS ) + /* PPS */ + return hevc_check_pps_appendable( ps_data, ps_length, ps_list ); + else + { + /* VPS or SPS + * Set up bitstream handler for parse parameter sets. */ + lsmash_bits_t *bits = lsmash_bits_adhoc_create(); + if( !bits ) + return DCR_NALU_APPEND_ERROR; + uint32_t max_ps_length; + uint8_t *rbsp_buffer; + if( nalu_get_max_ps_length( ps_list, &max_ps_length ) < 0 + || (rbsp_buffer = lsmash_malloc( LSMASH_MAX( max_ps_length, ps_length ) )) == NULL ) + { + lsmash_bits_adhoc_cleanup( bits ); + return DCR_NALU_APPEND_ERROR; + } + lsmash_dcr_nalu_appendable appendable; + if( ps_type == HEVC_DCR_NALU_TYPE_VPS ) + appendable = hevc_check_vps_appendable( bits, rbsp_buffer, param, ps_data, ps_length, ps_list ); + else + appendable = hevc_check_sps_appendable( bits, rbsp_buffer, param, ps_data, ps_length, ps_list ); + lsmash_bits_adhoc_cleanup( bits ); + lsmash_free( rbsp_buffer ); + return appendable; + } +} + +static inline void hevc_specific_parameters_ready +( + lsmash_hevc_specific_parameters_t *param +) +{ + param->general_profile_compatibility_flags = ~UINT32_C(0); + param->general_constraint_indicator_flags = 0x0000FFFFFFFFFFFF; + param->min_spatial_segmentation_idc = 0x0FFF; + param->avgFrameRate = 0; /* unspecified average frame rate */ + param->constantFrameRate = 2; + param->numTemporalLayers = 0; + param->temporalIdNested = 1; +} + +static inline void hevc_specific_parameters_update_ptl +( + lsmash_hevc_specific_parameters_t *param, + hevc_ptl_t *ptl +) +{ + param->general_profile_space = ptl->general.profile_space; + param->general_tier_flag = LSMASH_MAX( param->general_tier_flag, ptl->general.tier_flag ); + param->general_profile_idc = ptl->general.profile_idc; + param->general_profile_compatibility_flags &= ptl->general.profile_compatibility_flags; + param->general_constraint_indicator_flags &= ((uint64_t)ptl->general.progressive_source_flag << 47) + | ((uint64_t)ptl->general.interlaced_source_flag << 46) + | ((uint64_t)ptl->general.non_packed_constraint_flag << 45) + | ((uint64_t)ptl->general.frame_only_constraint_flag << 44) + | ptl->general.reserved_zero_44bits; + param->general_level_idc = LSMASH_MAX( param->general_level_idc, ptl->general.level_idc ); +} + +static inline void hevc_reorder_parameter_set_ascending_id +( + lsmash_hevc_specific_parameters_t *param, + lsmash_hevc_dcr_nalu_type ps_type, + lsmash_entry_list_t *ps_list, + uint8_t ps_id +) +{ + lsmash_entry_t *entry = NULL; + if( ps_id ) + for( int i = ps_id - 1; i; i-- ) + { + entry = hevc_get_ps_entry_from_param( param, ps_type, i ); + if( entry ) + break; + } + int append_head = 0; + if( !entry ) + { + /* Couldn't find any parameter set with lower identifier. + * Next, find parameter set with upper identifier. */ + int max_ps_id = ps_type == HEVC_DCR_NALU_TYPE_VPS ? HEVC_MAX_VPS_ID + : ps_type == HEVC_DCR_NALU_TYPE_SPS ? HEVC_MAX_SPS_ID + : HEVC_MAX_PPS_ID; + for( int i = ps_id + 1; i <= max_ps_id; i++ ) + { + entry = hevc_get_ps_entry_from_param( param, ps_type, i ); + if( entry ) + break; + } + if( entry ) + append_head = 1; + } + if( !entry ) + return; /* The new entry was appended to the tail. */ + lsmash_entry_t *new_entry = ps_list->tail; + if( append_head ) + { + /* before: entry[i > ps_id] ... -> prev_entry -> new_entry[ps_id] + * after: new_entry[ps_id] -> entry[i > ps_id] -> ... -> prev_entry */ + if( new_entry->prev ) + new_entry->prev->next = NULL; + new_entry->prev = NULL; + entry->prev = new_entry; + new_entry->next = entry; + return; + } + /* before: entry[i < ps_id] -> next_entry -> ... -> prev_entry -> new_entry[ps_id] + * after: entry[i < ps_id] -> new_entry[ps_id] -> next_entry -> ... -> prev_entry */ + if( new_entry->prev ) + new_entry->prev->next = NULL; + new_entry->prev = entry; + new_entry->next = entry->next; + if( entry->next ) + entry->next->prev = new_entry; + entry->next = new_entry; +} + +int lsmash_append_hevc_dcr_nalu +( + lsmash_hevc_specific_parameters_t *param, + lsmash_hevc_dcr_nalu_type ps_type, + void *_ps_data, + uint32_t ps_length +) +{ + uint8_t *ps_data = _ps_data; + if( !param || !ps_data || ps_length < 2 ) + return LSMASH_ERR_FUNCTION_PARAM; + int err = hevc_alloc_parameter_arrays_if_needed( param ); + if( err < 0 ) + return err; + hevc_parameter_array_t *ps_array = hevc_get_parameter_set_array( param, ps_type ); + if( !ps_array ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_entry_list_t *ps_list = ps_array->list; + if( ps_type == HEVC_DCR_NALU_TYPE_PREFIX_SEI + || ps_type == HEVC_DCR_NALU_TYPE_SUFFIX_SEI ) + { + /* Append a SEI anyway. */ + isom_dcr_ps_entry_t *ps = isom_create_ps_entry( ps_data, ps_length ); + if( !ps ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( ps_list, ps ) < 0 ) + { + isom_remove_dcr_ps( ps ); + return LSMASH_ERR_MEMORY_ALLOC; + } + return 0; + } + if( ps_type != HEVC_DCR_NALU_TYPE_VPS + && ps_type != HEVC_DCR_NALU_TYPE_SPS + && ps_type != HEVC_DCR_NALU_TYPE_PPS ) + return LSMASH_ERR_FUNCTION_PARAM; + /* Check if the same parameter set identifier already exists. */ + uint8_t ps_id; + if( (err = hevc_get_ps_id( ps_data + HEVC_MIN_NALU_HEADER_LENGTH, + ps_length - HEVC_MIN_NALU_HEADER_LENGTH, &ps_id, ps_type )) < 0 ) + return err; + lsmash_entry_t *entry = hevc_get_ps_entry_from_param( param, ps_type, ps_id ); + isom_dcr_ps_entry_t *ps = entry ? (isom_dcr_ps_entry_t *)entry->data : NULL; + if( ps && !ps->unused ) + /* The same parameter set identifier already exists. */ + return LSMASH_ERR_FUNCTION_PARAM; + int invoke_reorder; + if( ps ) + { + /* Reuse an already existed parameter set in the list. */ + ps->unused = 0; + if( ps->nalUnit != ps_data ) + { + /* The same address could be given when called by hevc_update_picture_info_for_slice(). */ + lsmash_free( ps->nalUnit ); + ps->nalUnit = ps_data; + } + ps->nalUnitLength = ps_length; + invoke_reorder = 0; + } + else + { + /* Create a new parameter set and append it into the list. */ + ps = isom_create_ps_entry( ps_data, ps_length ); + if( !ps ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( ps_list, ps ) < 0 ) + { + isom_remove_dcr_ps( ps ); + return LSMASH_ERR_MEMORY_ALLOC; + } + invoke_reorder = 1; + } + lsmash_bits_t *bits = NULL; + uint8_t *rbsp_buffer = NULL; + uint32_t ps_count; + if( (err = nalu_get_ps_count( ps_list, &ps_count )) < 0 ) + goto fail; + if( (bits = lsmash_bits_adhoc_create()) == NULL + || (rbsp_buffer = lsmash_malloc( ps_length )) == NULL ) + { + err = LSMASH_ERR_MEMORY_ALLOC; + goto fail; + } + /* Update specific info with VPS, SPS or PPS. */ + { + if( ps_type == HEVC_DCR_NALU_TYPE_VPS ) + { + hevc_vps_t vps; + if( (err = hevc_parse_vps_minimally( bits, &vps, rbsp_buffer, + ps_data + HEVC_MIN_NALU_HEADER_LENGTH, + ps_length - HEVC_MIN_NALU_HEADER_LENGTH )) < 0 ) + goto fail; + if( ps_count == 1 ) + { + /* Initialize if not initialized yet. */ + lsmash_entry_list_t *sps_list = hevc_get_parameter_set_list( param, HEVC_DCR_NALU_TYPE_SPS ); + uint32_t sps_count; + if( (err = nalu_get_ps_count( sps_list, &sps_count )) < 0 ) + goto fail; + if( sps_count == 0 ) + hevc_specific_parameters_ready( param ); + } + hevc_specific_parameters_update_ptl( param, &vps.ptl ); + param->numTemporalLayers = LSMASH_MAX( param->numTemporalLayers, vps.max_sub_layers_minus1 + 1 ); + //param->temporalIdNested &= vps.temporal_id_nesting_flag; + } + else if( ps_type == HEVC_DCR_NALU_TYPE_SPS ) + { + hevc_sps_t sps; + if( (err = hevc_parse_sps_minimally( bits, &sps, rbsp_buffer, + ps_data + HEVC_MIN_NALU_HEADER_LENGTH, + ps_length - HEVC_MIN_NALU_HEADER_LENGTH )) < 0 ) + goto fail; + if( ps_count == 1 ) + { + /* Initialize if not initialized yet. */ + lsmash_entry_list_t *vps_list = hevc_get_parameter_set_list( param, HEVC_DCR_NALU_TYPE_VPS ); + uint32_t vps_count; + if( (err = nalu_get_ps_count( vps_list, &vps_count )) < 0 ) + goto fail; + if( vps_count == 0 ) + hevc_specific_parameters_ready( param ); + } + hevc_specific_parameters_update_ptl( param, &sps.ptl ); + param->min_spatial_segmentation_idc = LSMASH_MIN( param->min_spatial_segmentation_idc, sps.vui.min_spatial_segmentation_idc ); + param->chromaFormat = sps.chroma_format_idc; + param->bitDepthLumaMinus8 = sps.bit_depth_luma_minus8; + param->bitDepthChromaMinus8 = sps.bit_depth_chroma_minus8; + param->numTemporalLayers = LSMASH_MAX( param->numTemporalLayers, sps.max_sub_layers_minus1 + 1 ); + param->temporalIdNested &= sps.temporal_id_nesting_flag; + /* Check type of constant frame rate. */ + if( param->constantFrameRate ) + { + int cfr; + if( param->constantFrameRate == 2 ) + { + cfr = 1; + for( uint8_t i = 0; i <= sps.max_sub_layers_minus1; i++ ) + cfr &= sps.vui.hrd.fixed_pic_rate_general_flag[i]; + } + else + cfr = 0; + if( cfr ) + param->constantFrameRate = 2; + else + { + for( uint8_t i = 0; i <= sps.max_sub_layers_minus1; i++ ) + cfr |= sps.vui.hrd.fixed_pic_rate_general_flag[i]; + param->constantFrameRate = cfr; + } + } +#if 0 + /* FIXME: probably, we can get average frame rate according to C.3.3 Picture output. */ + if( param->constantFrameRate ) + { + uint64_t interval = 0; + for( uint8_t i = 0; i <= sps.max_sub_layers_minus1; i++ ) + interval += sps.vui.num_units_in_tick * sps.vui.hrd.elemental_duration_in_tc_minus1[i]; + uint64_t frame_rate; + if( interval ) + frame_rate = ((256 * (2 - sps.vui.field_seq_flag) * (uint64_t)sps.vui.time_scale) + * (sps.max_sub_layers_minus1 + 1)) / interval; + else + frame_rate = 0; + if( frame_rate != param->avgFrameRate && param->avgFrameRate ) + param->constantFrameRate = 0; + param->avgFrameRate = frame_rate; + } +#endif + } + else + { + hevc_pps_t pps; + if( (err = hevc_parse_pps_minimally( bits, &pps, rbsp_buffer, + ps_data + HEVC_MIN_NALU_HEADER_LENGTH, + ps_length - HEVC_MIN_NALU_HEADER_LENGTH )) < 0 ) + goto fail; + uint8_t parallelismType = 0; +#if 1 /* Replace 1 with 0 if parallelismType shall be set to 0 when min_spatial_segmentation_idc equal to 0. */ + + if( pps.entropy_coding_sync_enabled_flag ) + parallelismType = pps.tiles_enabled_flag ? 0 : 3; + else if( pps.tiles_enabled_flag ) + parallelismType = 2; + else + parallelismType = 1; +#else + /* Parse SPS and check the value of min_spatial_segmentation_idc is equal to zero or not. + * If the value is not equal to zero, update parallelismType appropriately. + * If corresponding SPS is not found, set 0 to parallelismType. */ + entry = hevc_get_ps_entry_from_param( param, HEVC_DCR_NALU_TYPE_SPS, pps.seq_parameter_set_id ); + if( entry && entry->data ) + { + ps = (isom_dcr_ps_entry_t *)entry->data; + lsmash_bits_t *sps_bits = lsmash_bits_adhoc_create(); + if( !sps_bits ) + { + err = LSMASH_ERR_MEMORY_ALLOC; + goto fail; + } + uint8_t *sps_rbsp_buffer = lsmash_malloc( ps->nalUnitLength ); + if( !sps_rbsp_buffer ) + { + lsmash_bits_adhoc_cleanup( sps_bits ); + err = LSMASH_ERR_MEMORY_ALLOC; + goto fail; + } + hevc_sps_t sps; + if( hevc_parse_sps_minimally( sps_bits, &sps, sps_rbsp_buffer, + ps->nalUnit + HEVC_MIN_NALU_HEADER_LENGTH, + ps->nalUnitLength - HEVC_MIN_NALU_HEADER_LENGTH ) == 0 ) + { + if( sps.vui.min_spatial_segmentation_idc ) + { + if( pps.entropy_coding_sync_enabled_flag ) + parallelismType = pps.tiles_enabled_flag ? 0 : 3; + else if( pps.tiles_enabled_flag ) + parallelismType = 2; + else + parallelismType = 1; + } + else + parallelismType = 0; + } + lsmash_bits_adhoc_cleanup( sps_bits ); + lsmash_free( sps_rbsp_buffer ); + } +#endif + if( ps_count == 1 ) + param->parallelismType = parallelismType; + else if( param->parallelismType != parallelismType ) + param->parallelismType = 0; + } + } + if( invoke_reorder ) + /* Add a new parameter set in order of ascending parameter set identifier. */ + hevc_reorder_parameter_set_ascending_id( param, ps_type, ps_list, ps_id ); + err = 0; + goto clean; +fail: + ps = (isom_dcr_ps_entry_t *)lsmash_list_get_entry_data( ps_list, ps_list->entry_count ); + if( ps ) + ps->unused = 1; +clean: + lsmash_bits_adhoc_cleanup( bits ); + lsmash_free( rbsp_buffer ); + return err; +} + +int hevc_try_to_append_dcr_nalu +( + hevc_info_t *info, + lsmash_hevc_dcr_nalu_type ps_type, + void *_ps_data, + uint32_t ps_length +) +{ + uint8_t *ps_data = _ps_data; + lsmash_dcr_nalu_appendable ret = lsmash_check_hevc_dcr_nalu_appendable( &info->hvcC_param, ps_type, ps_data, ps_length ); + lsmash_hevc_specific_parameters_t *param; + switch( ret ) + { + case DCR_NALU_APPEND_ERROR : /* Error */ + return LSMASH_ERR_NAMELESS; + case DCR_NALU_APPEND_NEW_DCR_REQUIRED : /* Mulitiple sample description is needed. */ + case DCR_NALU_APPEND_NEW_SAMPLE_ENTRY_REQUIRED : /* Mulitiple sample description is needed. */ + param = &info->hvcC_param_next; + info->hvcC_pending = 1; + break; + case DCR_NALU_APPEND_POSSIBLE : /* Appendable */ + param = info->hvcC_pending ? &info->hvcC_param_next : &info->hvcC_param; + break; + default : /* No need to append */ + return DCR_NALU_APPEND_DUPLICATED; + } + int err; + switch( ps_type ) + { + case HEVC_DCR_NALU_TYPE_VPS : + if( (err = hevc_parse_vps( info, info->buffer.rbsp, + ps_data + HEVC_MIN_NALU_HEADER_LENGTH, + ps_length - HEVC_MIN_NALU_HEADER_LENGTH )) < 0 ) + return err; + break; + case HEVC_DCR_NALU_TYPE_SPS : + if( (err = hevc_parse_sps( info, info->buffer.rbsp, + ps_data + HEVC_MIN_NALU_HEADER_LENGTH, + ps_length - HEVC_MIN_NALU_HEADER_LENGTH )) < 0 ) + return err; + break; + case HEVC_DCR_NALU_TYPE_PPS : + if( (err = hevc_parse_pps( info, info->buffer.rbsp, + ps_data + HEVC_MIN_NALU_HEADER_LENGTH, + ps_length - HEVC_MIN_NALU_HEADER_LENGTH )) < 0 ) + return err; + break; + default : + break; + } + return lsmash_append_hevc_dcr_nalu( param, ps_type, ps_data, ps_length ); +} + +static int hevc_move_dcr_nalu_entry +( + lsmash_hevc_specific_parameters_t *dst_data, + lsmash_hevc_specific_parameters_t *src_data, + lsmash_hevc_dcr_nalu_type ps_type +) +{ + lsmash_entry_list_t *src_ps_list = hevc_get_parameter_set_list( src_data, ps_type ); + lsmash_entry_list_t *dst_ps_list = hevc_get_parameter_set_list( dst_data, ps_type ); + assert( src_ps_list && dst_ps_list ); + for( lsmash_entry_t *src_entry = src_ps_list->head; src_entry; src_entry = src_entry->next ) + { + isom_dcr_ps_entry_t *src_ps = (isom_dcr_ps_entry_t *)src_entry->data; + if( !src_ps ) + continue; + uint8_t src_ps_id; + int err; + if( (err = hevc_get_ps_id( src_ps->nalUnit + HEVC_MIN_NALU_HEADER_LENGTH, + src_ps->nalUnitLength - HEVC_MIN_NALU_HEADER_LENGTH, + &src_ps_id, ps_type )) < 0 ) + return err; + lsmash_entry_t *dst_entry; + for( dst_entry = dst_ps_list->head; dst_entry; dst_entry = dst_entry->next ) + { + isom_dcr_ps_entry_t *dst_ps = (isom_dcr_ps_entry_t *)dst_entry->data; + if( !dst_ps ) + continue; + uint8_t dst_ps_id; + if( (err = hevc_get_ps_id( dst_ps->nalUnit + HEVC_MIN_NALU_HEADER_LENGTH, + dst_ps->nalUnitLength - HEVC_MIN_NALU_HEADER_LENGTH, + &dst_ps_id, ps_type )) < 0 ) + return err; + if( dst_ps_id == src_ps_id ) + { + /* Replace the old parameter set with the new one. */ + assert( dst_entry->data != src_entry->data ); + isom_remove_dcr_ps( dst_ps ); + dst_entry->data = src_entry->data; + src_entry->data = NULL; + break; + } + } + if( !dst_entry ) + { + /* Move the parameter set. */ + if( lsmash_list_add_entry( dst_ps_list, src_ps ) < 0 ) + return LSMASH_ERR_MEMORY_ALLOC; + src_entry->data = NULL; + } + } + return 0; +} + +int hevc_move_pending_hvcC_param +( + hevc_info_t *info +) +{ + assert( info ); + if( !info->hvcC_pending ) + return 0; + /* Mark 'unused' on parameter sets within the decoder configuration record. */ + for( int i = 0; i < HEVC_DCR_NALU_TYPE_NUM; i++ ) + { + lsmash_entry_list_t *ps_list = hevc_get_parameter_set_list( &info->hvcC_param, i ); + assert( ps_list ); + for( lsmash_entry_t *entry = ps_list->head; entry; entry = entry->next ) + { + isom_dcr_ps_entry_t *ps = (isom_dcr_ps_entry_t *)entry->data; + if( !ps ) + continue; + ps->unused = 1; + } + } + /* Move the new parameter sets. */ + int err; + if( (err = hevc_move_dcr_nalu_entry( &info->hvcC_param, &info->hvcC_param_next, HEVC_DCR_NALU_TYPE_VPS )) < 0 + || (err = hevc_move_dcr_nalu_entry( &info->hvcC_param, &info->hvcC_param_next, HEVC_DCR_NALU_TYPE_SPS )) < 0 + || (err = hevc_move_dcr_nalu_entry( &info->hvcC_param, &info->hvcC_param_next, HEVC_DCR_NALU_TYPE_PPS )) < 0 + || (err = hevc_move_dcr_nalu_entry( &info->hvcC_param, &info->hvcC_param_next, HEVC_DCR_NALU_TYPE_PREFIX_SEI )) < 0 + || (err = hevc_move_dcr_nalu_entry( &info->hvcC_param, &info->hvcC_param_next, HEVC_DCR_NALU_TYPE_SUFFIX_SEI )) < 0 ) + return err; + /* Move to the pending. */ + lsmash_hevc_parameter_arrays_t *parameter_arrays = info->hvcC_param.parameter_arrays; /* Back up parameter arrays. */ + info->hvcC_param = info->hvcC_param_next; + info->hvcC_param.parameter_arrays = parameter_arrays; + /* No pending hvcC. */ + hevc_deallocate_parameter_arrays( &info->hvcC_param_next ); + uint8_t lengthSizeMinusOne = info->hvcC_param_next.lengthSizeMinusOne; + memset( &info->hvcC_param_next, 0, sizeof(lsmash_hevc_specific_parameters_t) ); + info->hvcC_param_next.lengthSizeMinusOne = lengthSizeMinusOne; + info->hvcC_pending = 0; + return 0; +} + +int lsmash_set_hevc_array_completeness +( + lsmash_hevc_specific_parameters_t *param, + lsmash_hevc_dcr_nalu_type ps_type, + int array_completeness +) +{ + if( hevc_alloc_parameter_arrays_if_needed( param ) < 0 ) + return LSMASH_ERR_MEMORY_ALLOC; + hevc_parameter_array_t *ps_array = hevc_get_parameter_set_array( param, ps_type ); + if( !ps_array ) + return LSMASH_ERR_FUNCTION_PARAM; + ps_array->array_completeness = array_completeness; + return 0; +} + +int lsmash_get_hevc_array_completeness +( + lsmash_hevc_specific_parameters_t *param, + lsmash_hevc_dcr_nalu_type ps_type, + int *array_completeness +) +{ + if( hevc_alloc_parameter_arrays_if_needed( param ) < 0 ) + return LSMASH_ERR_MEMORY_ALLOC; + hevc_parameter_array_t *ps_array = hevc_get_parameter_set_array( param, ps_type ); + if( !ps_array ) + return LSMASH_ERR_FUNCTION_PARAM; + *array_completeness = ps_array->array_completeness; + return 0; +} + +static int hevc_parse_succeeded +( + hevc_info_t *info, + lsmash_hevc_specific_parameters_t *param +) +{ + int ret; + if( info->vps.present + && info->sps.present + && info->pps.present ) + { + *param = info->hvcC_param; + /* Avoid freeing parameter sets. */ + info->hvcC_param.parameter_arrays = NULL; + ret = 0; + } + else + ret = LSMASH_ERR_INVALID_DATA; + hevc_cleanup_parser( info ); + return ret; +} + +static inline int hevc_parse_failed +( + hevc_info_t *info, + int ret +) +{ + hevc_cleanup_parser( info ); + return ret; +} + +int lsmash_setup_hevc_specific_parameters_from_access_unit +( + lsmash_hevc_specific_parameters_t *param, + uint8_t *data, + uint32_t data_length +) +{ + if( !param || !data || data_length == 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + hevc_info_t *info = &(hevc_info_t){ { 0 } }; + lsmash_bs_t *bs = &(lsmash_bs_t){ 0 }; + int err = lsmash_bs_set_empty_stream( bs, data, data_length ); + if( err < 0 ) + return err; + uint64_t sc_head_pos = nalu_find_first_start_code( bs ); + if( sc_head_pos == NALU_NO_START_CODE_FOUND ) + return LSMASH_ERR_INVALID_DATA; + else if( sc_head_pos == NALU_IO_ERROR ) + return LSMASH_ERR_IO; + if( (err = hevc_setup_parser( info, 1 )) < 0 ) + return hevc_parse_failed( info, err ); + hevc_stream_buffer_t *sb = &info->buffer; + hevc_slice_info_t *slice = &info->slice; + while( 1 ) + { + hevc_nalu_header_t nuh; + uint64_t start_code_length; + uint64_t trailing_zero_bytes; + uint64_t nalu_length = hevc_find_next_start_code( bs, &nuh, &start_code_length, &trailing_zero_bytes ); + if( nalu_length == NALU_NO_START_CODE_FOUND ) + /* For the last NALU. This NALU already has been parsed. */ + return hevc_parse_succeeded( info, param ); + uint8_t nalu_type = nuh.nal_unit_type; + uint64_t next_sc_head_pos = sc_head_pos + + start_code_length + + nalu_length + + trailing_zero_bytes; + if( nalu_type == HEVC_NALU_TYPE_FD ) + { + /* We don't support streams with both filler and HRD yet. Otherwise, just skip filler. */ + if( info->sps.vui.hrd.present ) + return hevc_parse_failed( info, LSMASH_ERR_PATCH_WELCOME ); + } + else if( nalu_type <= HEVC_NALU_TYPE_RASL_R + || (nalu_type >= HEVC_NALU_TYPE_BLA_W_LP && nalu_type <= HEVC_NALU_TYPE_CRA) + || (nalu_type >= HEVC_NALU_TYPE_VPS && nalu_type <= HEVC_NALU_TYPE_SUFFIX_SEI) ) + { + /* Increase the buffer if needed. */ + uint64_t possible_au_length = NALU_DEFAULT_NALU_LENGTH_SIZE + nalu_length; + if( sb->bank->buffer_size < possible_au_length + && (err = hevc_supplement_buffer( sb, NULL, 2 * possible_au_length )) < 0 ) + return hevc_parse_failed( info, err ); + /* Get the EBSP of the current NALU here. */ + uint8_t *nalu = lsmash_bs_get_buffer_data( bs ) + start_code_length; + if( nalu_type <= HEVC_NALU_TYPE_RSV_VCL31 ) + { + /* VCL NALU (slice) */ + hevc_slice_info_t prev_slice = *slice; + if( (err = hevc_parse_slice_segment_header( info, &nuh, sb->rbsp, nalu + nuh.length, nalu_length - nuh.length )) < 0 ) + return hevc_parse_failed( info, err ); + if( prev_slice.present ) + { + /* Check whether the AU that contains the previous VCL NALU completed or not. */ + if( hevc_find_au_delimit_by_slice_info( info, slice, &prev_slice ) ) + /* The current NALU is the first VCL NALU of the primary coded picture of a new AU. + * Therefore, the previous slice belongs to that new AU. */ + return hevc_parse_succeeded( info, param ); + } + slice->present = 1; + } + else + { + if( hevc_find_au_delimit_by_nalu_type( nalu_type, info->prev_nalu_type ) ) + /* The last slice belongs to the AU you want at this time. */ + return hevc_parse_succeeded( info, param ); + switch( nalu_type ) + { + case HEVC_NALU_TYPE_VPS : + if( (err = hevc_try_to_append_dcr_nalu( info, HEVC_DCR_NALU_TYPE_VPS, nalu, nalu_length )) < 0 ) + return hevc_parse_failed( info, err ); + break; + case HEVC_NALU_TYPE_SPS : + if( (err = hevc_try_to_append_dcr_nalu( info, HEVC_DCR_NALU_TYPE_SPS, nalu, nalu_length )) < 0 ) + return hevc_parse_failed( info, err ); + break; + case HEVC_NALU_TYPE_PPS : + if( (err = hevc_try_to_append_dcr_nalu( info, HEVC_DCR_NALU_TYPE_PPS, nalu, nalu_length )) < 0 ) + return hevc_parse_failed( info, err ); + break; + default : + break; + } + } + } + /* Move to the first byte of the next start code. */ + info->prev_nalu_type = nalu_type; + if( lsmash_bs_read_seek( bs, next_sc_head_pos, SEEK_SET ) != next_sc_head_pos ) + return hevc_parse_failed( info, LSMASH_ERR_NAMELESS ); + /* Check if no more data to read from the stream. */ + if( !lsmash_bs_is_end( bs, NALU_SHORT_START_CODE_LENGTH ) ) + sc_head_pos = next_sc_head_pos; + else + return hevc_parse_succeeded( info, param ); + } +} + +int hevc_construct_specific_parameters +( + lsmash_codec_specific_t *dst, + lsmash_codec_specific_t *src +) +{ + assert( dst && dst->data.structured && src && src->data.unstructured ); + if( src->size < ISOM_BASEBOX_COMMON_SIZE + 7 ) + return LSMASH_ERR_INVALID_DATA; + lsmash_hevc_specific_parameters_t *param = (lsmash_hevc_specific_parameters_t *)dst->data.structured; + uint8_t *data = src->data.unstructured; + uint64_t size = LSMASH_GET_BE32( data ); + data += ISOM_BASEBOX_COMMON_SIZE; + if( size == 1 ) + { + size = LSMASH_GET_BE64( data ); + data += 8; + } + if( size != src->size ) + return LSMASH_ERR_INVALID_DATA; + if( hevc_alloc_parameter_arrays_if_needed( param ) < 0 ) + return LSMASH_ERR_MEMORY_ALLOC; + lsmash_bs_t *bs = lsmash_bs_create(); + if( !bs ) + return LSMASH_ERR_MEMORY_ALLOC; + int err = lsmash_bs_import_data( bs, data, src->size - (data - src->data.unstructured) ); + if( err < 0 ) + goto fail; + if( lsmash_bs_get_byte( bs ) != HVCC_CONFIGURATION_VERSION ) + { + err = LSMASH_ERR_INVALID_DATA; + goto fail; /* We don't support configurationVersion other than HVCC_CONFIGURATION_VERSION. */ + } + uint8_t temp8 = lsmash_bs_get_byte( bs ); + param->general_profile_space = (temp8 >> 6) & 0x03; + param->general_tier_flag = (temp8 >> 5) & 0x01; + param->general_profile_idc = temp8 & 0x1F; + param->general_profile_compatibility_flags = lsmash_bs_get_be32( bs ); + uint32_t temp32 = lsmash_bs_get_be32( bs ); + uint16_t temp16 = lsmash_bs_get_be16( bs ); + param->general_constraint_indicator_flags = ((uint64_t)temp32 << 16) | temp16; + param->general_level_idc = lsmash_bs_get_byte( bs ); + param->min_spatial_segmentation_idc = lsmash_bs_get_be16( bs ) & 0x0FFF; + param->parallelismType = lsmash_bs_get_byte( bs ) & 0x03; + param->chromaFormat = lsmash_bs_get_byte( bs ) & 0x03; + param->bitDepthLumaMinus8 = lsmash_bs_get_byte( bs ) & 0x07; + param->bitDepthChromaMinus8 = lsmash_bs_get_byte( bs ) & 0x07; + param->avgFrameRate = lsmash_bs_get_be16( bs ); + temp8 = lsmash_bs_get_byte( bs ); + param->constantFrameRate = (temp8 >> 6) & 0x03; + param->numTemporalLayers = (temp8 >> 3) & 0x07; + param->temporalIdNested = (temp8 >> 2) & 0x01; + param->lengthSizeMinusOne = temp8 & 0x03; + uint8_t numOfArrays = lsmash_bs_get_byte( bs ); + for( uint8_t i = 0; i < numOfArrays; i++ ) + { + hevc_parameter_array_t param_array; + memset( ¶m_array, 0, sizeof(hevc_parameter_array_t) ); + temp8 = lsmash_bs_get_byte( bs ); + param_array.array_completeness = (temp8 >> 7) & 0x01; + param_array.NAL_unit_type = temp8 & 0x3F; + param_array.list->entry_count = lsmash_bs_get_be16( bs ); + if( param_array.NAL_unit_type == HEVC_NALU_TYPE_VPS + || param_array.NAL_unit_type == HEVC_NALU_TYPE_SPS + || param_array.NAL_unit_type == HEVC_NALU_TYPE_PPS + || param_array.NAL_unit_type == HEVC_NALU_TYPE_PREFIX_SEI + || param_array.NAL_unit_type == HEVC_NALU_TYPE_SUFFIX_SEI ) + { + if( (err = nalu_get_dcr_ps( bs, param_array.list, param_array.list->entry_count )) < 0 ) + goto fail; + } + else + for( uint16_t j = 0; j < param_array.list->entry_count; j++ ) + { + uint16_t nalUnitLength = lsmash_bs_get_be16( bs ); + lsmash_bs_skip_bytes( bs, nalUnitLength ); /* nalUnit */ + } + switch( param_array.NAL_unit_type ) + { + case HEVC_NALU_TYPE_VPS : + param->parameter_arrays->ps_array[HEVC_DCR_NALU_TYPE_VPS] = param_array; + break; + case HEVC_NALU_TYPE_SPS : + param->parameter_arrays->ps_array[HEVC_DCR_NALU_TYPE_SPS] = param_array; + break; + case HEVC_NALU_TYPE_PPS : + param->parameter_arrays->ps_array[HEVC_DCR_NALU_TYPE_PPS] = param_array; + break; + case HEVC_NALU_TYPE_PREFIX_SEI : + param->parameter_arrays->ps_array[HEVC_DCR_NALU_TYPE_PREFIX_SEI] = param_array; + break; + case HEVC_NALU_TYPE_SUFFIX_SEI : + param->parameter_arrays->ps_array[HEVC_DCR_NALU_TYPE_SUFFIX_SEI] = param_array; + break; + default : + /* Discard unknown NALUs. */ + break; + } + } + lsmash_bs_cleanup( bs ); + return 0; +fail: + lsmash_bs_cleanup( bs ); + return err; +} + +int hevc_print_codec_specific +( + FILE *fp, + lsmash_file_t *file, + isom_box_t *box, + int level +) +{ + assert( box->manager & LSMASH_BINARY_CODED_BOX ); + int indent = level; + lsmash_ifprintf( fp, indent++, "[%s: HEVC Configuration Box]\n", isom_4cc2str( box->type.fourcc ) ); + lsmash_ifprintf( fp, indent, "position = %"PRIu64"\n", box->pos ); + lsmash_ifprintf( fp, indent, "size = %"PRIu64"\n", box->size ); + uint8_t *data = box->binary; + uint32_t offset = isom_skip_box_common( &data ); + lsmash_bs_t *bs = lsmash_bs_create(); + if( !bs ) + return LSMASH_ERR_MEMORY_ALLOC; + int err = lsmash_bs_import_data( bs, data, box->size - offset ); + if( err < 0 ) + { + lsmash_bs_cleanup( bs ); + return err; + } + uint8_t configurationVersion = lsmash_bs_get_byte( bs ); + lsmash_ifprintf( fp, indent, "configurationVersion = %"PRIu8"\n", configurationVersion ); + if( configurationVersion != HVCC_CONFIGURATION_VERSION ) + { + lsmash_bs_cleanup( bs ); + return 0; + } + uint8_t temp8 = lsmash_bs_get_byte( bs ); + lsmash_ifprintf( fp, indent, "general_profile_space = %"PRIu8"\n", (temp8 >> 6) & 0x03 ); + lsmash_ifprintf( fp, indent, "general_tier_flag = %"PRIu8"\n", (temp8 >> 5) & 0x01 ); + lsmash_ifprintf( fp, indent, "general_profile_idc = %"PRIu8"\n", temp8 & 0x1F ); + lsmash_ifprintf( fp, indent, "general_profile_compatibility_flags = 0x%08"PRIx32"\n", lsmash_bs_get_be32( bs ) ); + uint32_t temp32 = lsmash_bs_get_be32( bs ); + uint16_t temp16 = lsmash_bs_get_be16( bs ); + lsmash_ifprintf( fp, indent, "general_constraint_indicator_flags = 0x%012"PRIx64"\n", ((uint64_t)temp32 << 16) | temp16 ); + uint8_t general_level_idc = lsmash_bs_get_byte( bs ); + lsmash_ifprintf( fp, indent, "general_level_idc = %"PRIu8" (Level %g)\n", general_level_idc, general_level_idc / 30.0 ); + temp16 = lsmash_bs_get_be16( bs ); + lsmash_ifprintf( fp, indent, "reserved = 0x%02"PRIx8"\n", (temp16 >> 12) & 0x0F ); + lsmash_ifprintf( fp, indent, "min_spatial_segmentation_idc = %"PRIu16"\n", temp16 & 0x0FFF ); + temp8 = lsmash_bs_get_byte( bs ); + uint8_t parallelismType = temp8 & 0x03; + static const char *parallelism_table[4] = + { + "Mixed types or Unknown", + "Slice based", + "Tile based", + "Entropy coding synchronization based / WPP: Wavefront Parallel Processing" + }; + lsmash_ifprintf( fp, indent, "reserved = 0x%02"PRIx8"\n", (temp8 >> 2) & 0x3F ); + lsmash_ifprintf( fp, indent, "parallelismType = %"PRIu8" (%s)\n", parallelismType, + parallelism_table[parallelismType] ); + temp8 = lsmash_bs_get_byte( bs ); + lsmash_ifprintf( fp, indent, "reserved = 0x%02"PRIx8"\n", (temp8 >> 2) & 0x3F ); + lsmash_ifprintf( fp, indent, "chromaFormat = %"PRIu8"\n", temp8 & 0x03 ); + temp8 = lsmash_bs_get_byte( bs ); + lsmash_ifprintf( fp, indent, "reserved = 0x%02"PRIx8"\n", (temp8 >> 3) & 0x1F ); + lsmash_ifprintf( fp, indent, "bitDepthLumaMinus8 = %"PRIu8"\n", temp8 & 0x07 ); + temp8 = lsmash_bs_get_byte( bs ); + lsmash_ifprintf( fp, indent, "reserved = 0x%02"PRIx8"\n", (temp8 >> 3) & 0x1F ); + lsmash_ifprintf( fp, indent, "bitDepthChromaMinus8 = %"PRIu8"\n", temp8 & 0x07 ); + lsmash_ifprintf( fp, indent, "avgFrameRate = %"PRIu16"\n", lsmash_bs_get_be16( bs ) ); + temp8 = lsmash_bs_get_byte( bs ); + lsmash_ifprintf( fp, indent, "constantFrameRate = %"PRIu8"\n", (temp8 >> 6) & 0x03 ); + lsmash_ifprintf( fp, indent, "numTemporalLayers = %"PRIu8"\n", (temp8 >> 3) & 0x07 ); + lsmash_ifprintf( fp, indent, "temporalIdNested = %"PRIu8"\n", (temp8 >> 2) & 0x01 ); + lsmash_ifprintf( fp, indent, "lengthSizeMinusOne = %"PRIu8"\n", temp8 & 0x03 ); + uint8_t numOfArrays = lsmash_bs_get_byte( bs ); + lsmash_ifprintf( fp, indent, "numOfArrays = %"PRIu8"\n", numOfArrays ); + for( uint8_t i = 0; i < numOfArrays; i++ ) + { + int array_indent = indent + 1; + lsmash_ifprintf( fp, array_indent++, "array[%"PRIu8"]\n", i ); + temp8 = lsmash_bs_get_byte( bs ); + lsmash_ifprintf( fp, array_indent, "array_completeness = %"PRIu8"\n", (temp8 >> 7) & 0x01 ); + lsmash_ifprintf( fp, array_indent, "reserved = %"PRIu8"\n", (temp8 >> 6) & 0x01 ); + lsmash_ifprintf( fp, array_indent, "NAL_unit_type = %"PRIu8"\n", temp8 & 0x3F ); + uint16_t numNalus = lsmash_bs_get_be16( bs ); + lsmash_ifprintf( fp, array_indent, "numNalus = %"PRIu16"\n", numNalus ); + for( uint16_t j = 0; j < numNalus; j++ ) + { + uint16_t nalUnitLength = lsmash_bs_get_be16( bs ); + lsmash_bs_skip_bytes( bs, nalUnitLength ); + lsmash_ifprintf( fp, array_indent, "nalUnit[%"PRIu16"]\n", j ); + lsmash_ifprintf( fp, array_indent + 1, "nalUnitLength = %"PRIu16"\n", nalUnitLength ); + } + } + lsmash_bs_cleanup( bs ); + return 0; +} + +static inline int hevc_copy_dcr_nalu_array +( + lsmash_hevc_specific_parameters_t *dst_data, + lsmash_hevc_specific_parameters_t *src_data, + lsmash_hevc_dcr_nalu_type ps_type +) +{ + hevc_parameter_array_t *src_ps_array = hevc_get_parameter_set_array( src_data, ps_type ); + hevc_parameter_array_t *dst_ps_array = hevc_get_parameter_set_array( dst_data, ps_type ); + assert( src_ps_array && dst_ps_array ); + dst_ps_array->array_completeness = src_ps_array->array_completeness; + dst_ps_array->NAL_unit_type = src_ps_array->NAL_unit_type; + lsmash_entry_list_t *src_ps_list = src_ps_array->list; + lsmash_entry_list_t *dst_ps_list = dst_ps_array->list; + for( lsmash_entry_t *entry = src_ps_list->head; entry; entry = entry->next ) + { + isom_dcr_ps_entry_t *src_ps = (isom_dcr_ps_entry_t *)entry->data; + if( !src_ps || src_ps->unused ) + continue; + isom_dcr_ps_entry_t *dst_ps = isom_create_ps_entry( src_ps->nalUnit, src_ps->nalUnitLength ); + if( !dst_ps ) + { + hevc_deallocate_parameter_arrays( dst_data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + if( lsmash_list_add_entry( dst_ps_list, dst_ps ) < 0 ) + { + hevc_deallocate_parameter_arrays( dst_data ); + isom_remove_dcr_ps( dst_ps ); + return LSMASH_ERR_MEMORY_ALLOC; + } + } + return 0; +} + +int hevc_copy_codec_specific +( + lsmash_codec_specific_t *dst, + lsmash_codec_specific_t *src +) +{ + assert( src && src->format == LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED && src->data.structured ); + assert( dst && dst->format == LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED && dst->data.structured ); + lsmash_hevc_specific_parameters_t *src_data = (lsmash_hevc_specific_parameters_t *)src->data.structured; + lsmash_hevc_specific_parameters_t *dst_data = (lsmash_hevc_specific_parameters_t *)dst->data.structured; + hevc_deallocate_parameter_arrays( dst_data ); + *dst_data = *src_data; + if( !src_data->parameter_arrays ) + return 0; + dst_data->parameter_arrays = hevc_alloc_parameter_arrays(); + if( !dst_data->parameter_arrays ) + return LSMASH_ERR_MEMORY_ALLOC; + for( int i = 0; i < HEVC_DCR_NALU_TYPE_NUM; i++ ) + { + int err = hevc_copy_dcr_nalu_array( dst_data, src_data, (lsmash_hevc_dcr_nalu_type)i ); + if( err < 0 ) + return err; + } + return 0; +} diff --git a/3rdparty/l-smash/codecs/hevc.h b/3rdparty/l-smash/codecs/hevc.h new file mode 100644 index 000000000..ef5d39d72 --- /dev/null +++ b/3rdparty/l-smash/codecs/hevc.h @@ -0,0 +1,447 @@ +/***************************************************************************** + * hevc.h + ***************************************************************************** + * Copyright (C) 2013-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +enum +{ + HEVC_NALU_TYPE_TRAIL_N = 0, + HEVC_NALU_TYPE_TRAIL_R = 1, + HEVC_NALU_TYPE_TSA_N = 2, + HEVC_NALU_TYPE_TSA_R = 3, + HEVC_NALU_TYPE_STSA_N = 4, + HEVC_NALU_TYPE_STSA_R = 5, + HEVC_NALU_TYPE_RADL_N = 6, + HEVC_NALU_TYPE_RADL_R = 7, + HEVC_NALU_TYPE_RASL_N = 8, + HEVC_NALU_TYPE_RASL_R = 9, + HEVC_NALU_TYPE_RSV_VCL_R15 = 15, + HEVC_NALU_TYPE_BLA_W_LP = 16, + HEVC_NALU_TYPE_BLA_W_RADL = 17, + HEVC_NALU_TYPE_BLA_N_LP = 18, + HEVC_NALU_TYPE_IDR_W_RADL = 19, + HEVC_NALU_TYPE_IDR_N_LP = 20, + HEVC_NALU_TYPE_CRA = 21, + HEVC_NALU_TYPE_RSV_IRAP_VCL22 = 22, + HEVC_NALU_TYPE_RSV_IRAP_VCL23 = 23, + HEVC_NALU_TYPE_RSV_VCL31 = 31, + HEVC_NALU_TYPE_VPS = 32, + HEVC_NALU_TYPE_SPS = 33, + HEVC_NALU_TYPE_PPS = 34, + HEVC_NALU_TYPE_AUD = 35, + HEVC_NALU_TYPE_EOS = 36, + HEVC_NALU_TYPE_EOB = 37, + HEVC_NALU_TYPE_FD = 38, + HEVC_NALU_TYPE_PREFIX_SEI = 39, + HEVC_NALU_TYPE_SUFFIX_SEI = 40, + HEVC_NALU_TYPE_RSV_NVCL41 = 41, + HEVC_NALU_TYPE_RSV_NVCL44 = 44, + HEVC_NALU_TYPE_RSV_NVCL47 = 47, + HEVC_NALU_TYPE_UNSPEC48 = 48, + HEVC_NALU_TYPE_UNSPEC55 = 55, + HEVC_NALU_TYPE_UNSPEC63 = 63, + HEVC_NALU_TYPE_UNKNOWN = 64 +}; + +typedef struct +{ + uint8_t array_completeness; + uint8_t NAL_unit_type; + lsmash_entry_list_t list[1]; +} hevc_parameter_array_t; + +struct lsmash_hevc_parameter_arrays_tag +{ + hevc_parameter_array_t ps_array[HEVC_DCR_NALU_TYPE_NUM]; +}; + +typedef struct +{ + unsigned forbidden_zero_bit : 1; + unsigned nal_unit_type : 7; /* +1 bit for HEVC_NALU_TYPE_UNKNOWN */ + unsigned nuh_layer_id : 6; + unsigned TemporalId : 3; + unsigned length : 15; +} hevc_nalu_header_t; + +/* Profile, Tier and Level */ +typedef struct +{ + uint8_t profile_space; + uint8_t tier_flag; + uint8_t profile_idc; + uint32_t profile_compatibility_flags; + uint8_t progressive_source_flag; + uint8_t interlaced_source_flag; + uint8_t non_packed_constraint_flag; + uint8_t frame_only_constraint_flag; + uint64_t reserved_zero_44bits; + uint8_t level_idc; +} hevc_ptl_common_t; + +typedef struct +{ + hevc_ptl_common_t general; + hevc_ptl_common_t sub_layer[6]; +} hevc_ptl_t; + +/* HRD (Hypothetical Reference Decoder) */ +typedef struct +{ + uint8_t present; + uint8_t CpbDpbDelaysPresentFlag; + uint8_t sub_pic_hrd_params_present_flag; + uint8_t du_cpb_removal_delay_increment_length; + uint8_t sub_pic_cpb_params_in_pic_timing_sei_flag; + uint8_t dpb_output_delay_du_length; + uint8_t au_cpb_removal_delay_length; + uint8_t dpb_output_delay_length; + uint8_t fixed_pic_rate_general_flag[7]; + uint16_t elemental_duration_in_tc [7]; +} hevc_hrd_t; + +/* VPS (Video Parameter Set) */ +typedef struct +{ + uint8_t present; + uint8_t video_parameter_set_id; + uint8_t max_sub_layers_minus1; + uint8_t temporal_id_nesting_flag; + uint8_t timing_info_present_flag; + uint8_t frame_field_info_present_flag; + uint16_t num_hrd_parameters; + hevc_ptl_t ptl; + hevc_hrd_t hrd[2]; +} hevc_vps_t; + +typedef struct +{ + uint8_t present; + uint16_t sar_width; + uint16_t sar_height; + uint8_t video_full_range_flag; + uint8_t colour_description_present_flag; + uint8_t colour_primaries; + uint8_t transfer_characteristics; + uint8_t matrix_coeffs; + uint8_t field_seq_flag; + uint8_t frame_field_info_present_flag; + uint32_t num_units_in_tick; + uint32_t time_scale; + uint16_t min_spatial_segmentation_idc; + lsmash_crop_t def_disp_win_offset; + hevc_hrd_t hrd; +} hevc_vui_t; + +/* Short term reference picture sets */ +typedef struct +{ + uint8_t NumNegativePics; + uint8_t NumPositivePics; + uint8_t NumDeltaPocs; + uint8_t UsedByCurrPicS0[16]; + uint8_t UsedByCurrPicS1[16]; + int32_t DeltaPocS0[16]; + int32_t DeltaPocS1[16]; +} hevc_st_rps_t; + +/* SPS (Sequence Parameter Set) */ +typedef struct +{ + uint8_t present; + uint8_t video_parameter_set_id; + uint8_t max_sub_layers_minus1; + uint8_t temporal_id_nesting_flag; + hevc_ptl_t ptl; + uint8_t seq_parameter_set_id; + uint8_t chroma_format_idc; + uint8_t separate_colour_plane_flag; + uint8_t bit_depth_luma_minus8; + uint8_t bit_depth_chroma_minus8; + uint8_t log2_max_pic_order_cnt_lsb; + uint8_t num_short_term_ref_pic_sets; + uint8_t long_term_ref_pics_present_flag; + uint8_t num_long_term_ref_pics_sps; + uint8_t temporal_mvp_enabled_flag; + uint32_t cropped_width; + uint32_t cropped_height; + uint32_t PicWidthInCtbsY; + uint32_t PicHeightInCtbsY; + uint64_t PicSizeInCtbsY; + hevc_st_rps_t st_rps[65]; + hevc_vui_t vui; +} hevc_sps_t; + +/* PPS (Picture Parameter Set) */ +typedef struct +{ + uint8_t present; + uint8_t pic_parameter_set_id; + uint8_t seq_parameter_set_id; + uint8_t dependent_slice_segments_enabled_flag; + uint8_t output_flag_present_flag; + uint8_t num_extra_slice_header_bits; + uint8_t tiles_enabled_flag; + uint8_t entropy_coding_sync_enabled_flag; + uint32_t num_tile_columns_minus1; + uint32_t num_tile_rows_minus1; +#define SIZEOF_PPS_EXCLUDING_HEAP (sizeof(hevc_pps_t) - offsetof( hevc_pps_t, col_alloc_size )) + size_t col_alloc_size; + size_t row_alloc_size; + uint32_t *colWidth; + uint32_t *colBd; + uint32_t *rowHeight; + uint32_t *rowBd; +} hevc_pps_t; + +/* SEI (Supplemental Enhancement Information) */ +typedef struct +{ + uint8_t present; + uint8_t pic_struct; +} hevc_pic_timing_t; + +typedef struct +{ + uint8_t present; + uint8_t broken_link_flag; + int32_t recovery_poc_cnt; +} hevc_recovery_point_t; + +typedef struct +{ + hevc_pic_timing_t pic_timing; + hevc_recovery_point_t recovery_point; +} hevc_sei_t; + +/* Slice segment */ +typedef struct +{ + uint8_t present; + uint8_t nalu_type; + uint8_t TemporalId; + uint8_t type; + uint8_t video_parameter_set_id; + uint8_t seq_parameter_set_id; + uint8_t pic_parameter_set_id; + uint8_t first_slice_segment_in_pic_flag; + uint8_t dependent_slice_segment_flag; + uint64_t segment_address; + int32_t pic_order_cnt_lsb; +} hevc_slice_info_t; + +/* Picture */ +typedef enum +{ + HEVC_PICTURE_TYPE_I = 0, + HEVC_PICTURE_TYPE_I_P = 1, + HEVC_PICTURE_TYPE_I_P_B = 2, + HEVC_PICTURE_TYPE_IDR = 3, + HEVC_PICTURE_TYPE_CRA = 4, + HEVC_PICTURE_TYPE_BLA = 5, + HEVC_PICTURE_TYPE_NONE = 6, +} hevc_picture_type; + +typedef struct +{ + hevc_picture_type type; + uint8_t irap; /* 1: IDR, CRA or BLA picture */ + uint8_t idr; /* 1: IDR picture */ + uint8_t broken_link; /* 1: BLA picture or picture with broken link flag */ + uint8_t radl; /* 1: RADL picture */ + uint8_t rasl; /* 1: RASL picture */ + uint8_t sublayer_nonref; /* 1: sub-layer non-reference picture */ + uint8_t closed_rap; /* 1: no undecodable leading picture in CVS */ + uint8_t random_accessible;/* 1: RAP or starting point of GDR */ + uint8_t TemporalId; + uint8_t independent; /* 1: intra coded picture */ + uint8_t field_coded; /* 1: field coded picture */ + uint8_t pic_parameter_set_id; + uint8_t has_primary; /* 1: an independent slice segment is present. */ + uint8_t delta; + /* POC */ + uint16_t poc_lsb; + int32_t poc; + int32_t tid0_poc_msb; + int32_t tid0_poc_lsb; + /* */ + int32_t recovery_poc_cnt; +} hevc_picture_info_t; + +/* Access unit */ +typedef struct +{ + uint8_t *data; + uint8_t *incomplete_data; + uint32_t length; + uint32_t incomplete_length; + uint32_t number; + uint8_t TemporalId; + hevc_picture_info_t picture; +} hevc_access_unit_t; + +typedef struct hevc_info_tag hevc_info_t; + +typedef struct +{ + lsmash_multiple_buffers_t *bank; + uint8_t *rbsp; +} hevc_stream_buffer_t; + +struct hevc_info_tag +{ + lsmash_hevc_specific_parameters_t hvcC_param; + lsmash_hevc_specific_parameters_t hvcC_param_next; + hevc_nalu_header_t nuh; + lsmash_entry_list_t vps_list[1]; + lsmash_entry_list_t sps_list[1]; + lsmash_entry_list_t pps_list[1]; + hevc_vps_t vps; /* active VPS */ + hevc_sps_t sps; /* active SPS */ + hevc_pps_t pps; /* active PPS */ + hevc_sei_t sei; /* active SEI */ + hevc_slice_info_t slice; /* active slice */ + hevc_access_unit_t au; + uint8_t prev_nalu_type; + uint8_t hvcC_pending; + uint8_t eos; /* end of sequence */ + uint64_t ebsp_head_pos; + lsmash_bits_t *bits; + hevc_stream_buffer_t buffer; +}; + +int hevc_setup_parser +( + hevc_info_t *info, + int parse_only +); + +void hevc_cleanup_parser +( + hevc_info_t *info +); + +uint64_t hevc_find_next_start_code +( + lsmash_bs_t *bs, + hevc_nalu_header_t *nuh, + uint64_t *start_code_length, + uint64_t *trailing_zero_bytes +); + +int hevc_calculate_poc +( + hevc_info_t *info, + hevc_picture_info_t *picture, + hevc_picture_info_t *prev_picture +); + +void hevc_update_picture_info_for_slice +( + hevc_info_t *info, + hevc_picture_info_t *picture, + hevc_slice_info_t *slice +); + +void hevc_update_picture_info +( + hevc_info_t *info, + hevc_picture_info_t *picture, + hevc_slice_info_t *slice, + hevc_sps_t *sps, + hevc_sei_t *sei +); + +int hevc_find_au_delimit_by_slice_info +( + hevc_info_t *info, + hevc_slice_info_t *slice, + hevc_slice_info_t *prev_slice +); + +int hevc_find_au_delimit_by_nalu_type +( + uint8_t nalu_type, + uint8_t prev_nalu_type +); + +int hevc_supplement_buffer +( + hevc_stream_buffer_t *hb, + hevc_access_unit_t *au, + uint32_t size +); + +int hevc_parse_vps +( + hevc_info_t *info, + uint8_t *rbsp_buffer, + uint8_t *ebsp, + uint64_t ebsp_size +); + +int hevc_parse_sps +( + hevc_info_t *info, + uint8_t *rbsp_buffer, + uint8_t *ebsp, + uint64_t ebsp_size +); + +int hevc_parse_pps +( + hevc_info_t *info, + uint8_t *rbsp_buffer, + uint8_t *ebsp, + uint64_t ebsp_size +); + +int hevc_parse_sei +( + lsmash_bits_t *bits, + hevc_vps_t *vps, + hevc_sps_t *sps, + hevc_sei_t *sei, + hevc_nalu_header_t *nuh, + uint8_t *rbsp_buffer, + uint8_t *ebsp, + uint64_t ebsp_size +); +int hevc_parse_slice_segment_header +( + hevc_info_t *info, + hevc_nalu_header_t *nuh, + uint8_t *rbsp_buffer, + uint8_t *ebsp, + uint64_t ebsp_size +); + +int hevc_try_to_append_dcr_nalu +( + hevc_info_t *info, + lsmash_hevc_dcr_nalu_type ps_type, + void *ps_data, + uint32_t ps_length +); + +int hevc_move_pending_hvcC_param +( + hevc_info_t *info +); diff --git a/3rdparty/l-smash/codecs/id.c b/3rdparty/l-smash/codecs/id.c new file mode 100644 index 000000000..6d0c37127 --- /dev/null +++ b/3rdparty/l-smash/codecs/id.c @@ -0,0 +1,28 @@ +/***************************************************************************** + * id.c + ***************************************************************************** + * Copyright (C) 2014-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +/**************************************************************************** + * CODEC identifiers + ****************************************************************************/ + +#define LSMASH_INITIALIZE_CODEC_ID_HERE +#include "lsmash.h" diff --git a/3rdparty/l-smash/codecs/mp4a.c b/3rdparty/l-smash/codecs/mp4a.c new file mode 100644 index 000000000..cb6c1d841 --- /dev/null +++ b/3rdparty/l-smash/codecs/mp4a.c @@ -0,0 +1,1160 @@ +/***************************************************************************** + * mp4a.c + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Takashi Hirata + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#define MP4A_INTERNAL +#include "core/box.h" + +#include "mp4a.h" +#include "mp4sys.h" +#include "description.h" + +#include +#include +#include + +/*************************************************************************** + implementation of part of ISO/IEC 14496-3 (ISO/IEC 14496-1 relevant) +***************************************************************************/ + +/* ISO/IEC 14496-3 samplingFrequencyIndex */ +/* ISO/IEC 14496-3 Sampling frequency mapping */ +const uint32_t mp4a_sampling_frequency_table[13][5] = { + /* threshold, exact, idx_for_ga, idx_for_sbr, idx */ + { 92017, 96000, 0x0, 0xF, 0x0 }, /* SBR is not allowed */ + { 75132, 88200, 0x1, 0xF, 0x1 }, /* SBR is not allowed */ + { 55426, 64000, 0x2, 0xF, 0x2 }, /* SBR is not allowed */ + { 46009, 48000, 0x3, 0x0, 0x3 }, + { 37566, 44100, 0x4, 0x1, 0x4 }, + { 27713, 32000, 0x5, 0x2, 0x5 }, + { 23004, 24000, 0x6, 0x3, 0x6 }, + { 18783, 22050, 0x7, 0x4, 0x7 }, + { 13856, 16000, 0x8, 0x5, 0x8 }, + { 11502, 12000, 0x9, 0x6, 0x9 }, + { 9391, 11025, 0xA, 0x7, 0xA }, + { 8000, 8000, 0xB, 0x8, 0xB }, + { 0, 7350, 0xB, 0xF, 0xC } /* samplingFrequencyIndex for GASpecificConfig is 0xB (same as 8000Hz). */ +}; + +/* ISO/IEC 14496-3 Interface to ISO/IEC 14496-1 (MPEG-4 Systems), Syntax of AudioSpecificConfig(). */ +/* This structure is represent of regularized AudioSpecificConfig. */ +/* for actual definition, see Syntax of GetAudioObjectType() for audioObjectType and extensionAudioObjectType. */ +typedef struct +{ + lsmash_mp4a_aac_sbr_mode sbr_mode; /* L-SMASH's original, including sbrPresent flag. */ + lsmash_mp4a_AudioObjectType audioObjectType; + unsigned samplingFrequencyIndex : 4; + unsigned samplingFrequency : 24; + unsigned channelConfiguration : 4; + lsmash_mp4a_AudioObjectType extensionAudioObjectType; + unsigned extensionSamplingFrequencyIndex : 4; + unsigned extensionSamplingFrequency : 24; + unsigned extensionChannelConfiguration : 4; + /* if( audioObjectType in + #[ 1, 2, 3, 4, 6, 7, *17, *19, *20, *21, *22, *23 ] // GASpecificConfig, AAC relatives and TwinVQ, BSAC + [ 8 ] // CelpSpecificConfig, not supported + [ 9 ] // HvxcSpecificConfig, not supported + [ 12 ] // TTSSpecificConfig, not supported + [ 13, 14, 15, 16 ] // StructuredAudioSpecificConfig, notsupported + [ 24 ] // ErrorResilientCelpSpecificConfig, notsupported + [ 25 ] // ErrorResilientHvxcSpecificConfig, notsupported + [ 26, 27 ] // ParametricSpecificConfig, notsupported + [ 28 ] // SSCSpecificConfig, notsupported + #[ 32, 33, 34 ] // MPEG_1_2_SpecificConfig + [ 35 ] // DSTSpecificConfig, notsupported + ){ */ + void *deepAudioSpecificConfig; // L-SMASH's original name, reperesents such as GASpecificConfig. */ + /* } */ + /* + // error resilient stuff, not supported + if( audioObjectType in [17, 19, 20, 21, 22, 23, 24, 25, 26, 27] ){ + uint8_t epConfig // 2bit + if( epConfig == 2 || epConfig == 3 ){ + ErrorProtectionSpecificConfig(); + } + if( epConfig == 3 ){ + uint8_t directMapping; // 1bit, currently always 1. + if( !directMapping ){ + // tbd + } + } + } + */ +} mp4a_AudioSpecificConfig_t; + +/* ISO/IEC 14496-3 Decoder configuration (GASpecificConfig), Syntax of GASpecificConfig() */ +/* ISO/IEC 14496-3 GASpecificConfig(), Sampling frequency mapping */ +typedef struct +{ + unsigned frameLengthFlag : 1; /* FIXME: AAC_SSR: shall be 0, Others: depends, but noramally 0. */ + unsigned dependsOnCoreCoder : 1; /* FIXME: used if scalable AAC. */ + unsigned coreCoderDelay : 14; + unsigned extensionFlag : 1; /* 1bit, 1 if ErrorResilience */ + /* if( !channelConfiguration ){ */ + void* program_config_element; /* currently not supported. */ + /* } */ + /* + // we do not support AAC_scalable + if( (audioObjectType == MP4A_AUDIO_OBJECT_TYPE_AAC_scalable) || (audioObjectType == MP4A_AUDIO_OBJECT_TYPE_ER_AAC_scalable) ){ + uint8_t layerNr; // 3bits + } + */ + /* + // we do not support special AACs + if( extensionFlag ){ + if( audioObjectType == MP4A_AUDIO_OBJECT_TYPE_ER_BSAC ){ + uint8_t numOfSubFrame; // 5bits + uint8_t layer_length; // 11bits + } + if( audioObjectType == MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LC || audioObjectType == MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LTP + || audioObjectType == MP4A_AUDIO_OBJECT_TYPE_ER_AAC_scalable || audioObjectType == MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LD + ){ + uint8_t aacSectionDataResilienceFlag; // 1bit + uint8_t aacScalefactorDataResilienceFlag; // 1bit + uint8_t aacSpectralDataResilienceFlag; // 1bit + } + uint8_t extensionFlag3; // 1bit + if( extensionFlag3 ){ + // tbd in version 3 + } + } + */ +} mp4a_GASpecificConfig_t; + +/* ISO/IEC 14496-3 MPEG_1_2_SpecificConfig */ +typedef struct +{ + uint8_t extension; /* shall be 0. */ +} mp4a_MPEG_1_2_SpecificConfig_t; + +/* ISO/IEC 14496-3 ALSSpecificConfig */ +typedef struct +{ + uint32_t size; + uint8_t *data; + uint32_t als_id; + uint32_t samp_freq; + uint32_t samples; + uint16_t channels; + unsigned file_type : 3; + unsigned resolution : 3; + unsigned floating : 1; + unsigned msb_first : 1; + uint16_t frame_length; + uint8_t random_access; + unsigned ra_flag : 2; + unsigned adapt_order : 1; + unsigned coef_table : 2; + unsigned long_term_prediction : 1; + unsigned max_order : 10; + unsigned block_switching : 2; + unsigned bgmc_mode : 1; + unsigned sb_part : 1; + unsigned joint_stereo : 1; + unsigned mc_coding : 1; + unsigned chan_config : 1; + unsigned chan_sort : 1; + unsigned crc_enabled : 1; + unsigned RLSLMS : 1; + unsigned reserved : 5; + unsigned aux_data_enabled : 1; +} mp4a_ALSSpecificConfig_t; + +static inline void mp4a_remove_GASpecificConfig( mp4a_GASpecificConfig_t* gasc ) +{ + debug_if( !gasc ) + return; + lsmash_free( gasc->program_config_element ); + lsmash_free( gasc ); +} + +static inline void mp4a_remove_MPEG_1_2_SpecificConfig( mp4a_MPEG_1_2_SpecificConfig_t* mpeg_1_2_sc ) +{ + lsmash_free( mpeg_1_2_sc ); +} + +void mp4a_remove_AudioSpecificConfig( mp4a_AudioSpecificConfig_t* asc ) +{ + if( !asc ) + return; + switch( asc->audioObjectType ){ + case MP4A_AUDIO_OBJECT_TYPE_AAC_MAIN: + case MP4A_AUDIO_OBJECT_TYPE_AAC_LC: + case MP4A_AUDIO_OBJECT_TYPE_AAC_SSR: + case MP4A_AUDIO_OBJECT_TYPE_AAC_LTP: + case MP4A_AUDIO_OBJECT_TYPE_SBR: + case MP4A_AUDIO_OBJECT_TYPE_AAC_scalable: + case MP4A_AUDIO_OBJECT_TYPE_TwinVQ: + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LC: + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LTP: + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_scalable: + case MP4A_AUDIO_OBJECT_TYPE_ER_Twin_VQ: + case MP4A_AUDIO_OBJECT_TYPE_ER_BSAC: + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LD: + mp4a_remove_GASpecificConfig( (mp4a_GASpecificConfig_t*)asc->deepAudioSpecificConfig ); + break; + case MP4A_AUDIO_OBJECT_TYPE_Layer_1: + case MP4A_AUDIO_OBJECT_TYPE_Layer_2: + case MP4A_AUDIO_OBJECT_TYPE_Layer_3: + mp4a_remove_MPEG_1_2_SpecificConfig( (mp4a_MPEG_1_2_SpecificConfig_t*)asc->deepAudioSpecificConfig ); + break; + default: + lsmash_free( asc->deepAudioSpecificConfig ); + break; + } + lsmash_free( asc ); +} + +/* ADIF/PCE(program config element) style GASpecificConfig is not not supported. */ +/* channelConfig/samplingFrequencyIndex will be used when we support ADIF/PCE style GASpecificConfig. */ +static mp4a_GASpecificConfig_t* mp4a_create_GASpecificConfig( uint8_t samplingFrequencyIndex, uint8_t channelConfig, lsmash_mp4a_AudioObjectType aot ) +{ + debug_if( aot != MP4A_AUDIO_OBJECT_TYPE_AAC_MAIN && aot != MP4A_AUDIO_OBJECT_TYPE_AAC_LC + && aot != MP4A_AUDIO_OBJECT_TYPE_AAC_SSR && aot != MP4A_AUDIO_OBJECT_TYPE_AAC_LTP + && aot != MP4A_AUDIO_OBJECT_TYPE_TwinVQ ) + return NULL; + if( samplingFrequencyIndex > 0xB || channelConfig == 0 || channelConfig == 7 ) + return NULL; + mp4a_GASpecificConfig_t *gasc = (mp4a_GASpecificConfig_t *)lsmash_malloc_zero( sizeof(mp4a_GASpecificConfig_t) ); + if( !gasc ) + return NULL; + gasc->frameLengthFlag = 0; /* FIXME: AAC_SSR: shall be 0, Others: depends, but noramally 0. */ + gasc->dependsOnCoreCoder = 0; /* FIXME: used if scalable AAC. */ + switch( aot ){ + case MP4A_AUDIO_OBJECT_TYPE_AAC_MAIN: + case MP4A_AUDIO_OBJECT_TYPE_AAC_LC: + case MP4A_AUDIO_OBJECT_TYPE_AAC_SSR: + case MP4A_AUDIO_OBJECT_TYPE_AAC_LTP: + case MP4A_AUDIO_OBJECT_TYPE_AAC_scalable: + case MP4A_AUDIO_OBJECT_TYPE_TwinVQ: + gasc->extensionFlag = 0; + break; +#if 0 /* intentional dead code */ + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LC: + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LTP: + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_scalable: + case MP4A_AUDIO_OBJECT_TYPE_ER_Twin_VQ: + case MP4A_AUDIO_OBJECT_TYPE_ER_BSAC: + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LD: + gasc->extensionFlag = 1; + break; +#endif + default: + gasc->extensionFlag = 0; + break; + } + return gasc; +} + +static mp4a_MPEG_1_2_SpecificConfig_t* mp4a_create_MPEG_1_2_SpecificConfig() +{ + mp4a_MPEG_1_2_SpecificConfig_t *mpeg_1_2_sc = (mp4a_MPEG_1_2_SpecificConfig_t *)lsmash_malloc_zero( sizeof(mp4a_MPEG_1_2_SpecificConfig_t) ); + if( !mpeg_1_2_sc ) + return NULL; + mpeg_1_2_sc->extension = 0; /* shall be 0. */ + return mpeg_1_2_sc; +} + +static mp4a_ALSSpecificConfig_t *mp4a_create_ALSSpecificConfig( uint8_t *exdata, uint32_t exdata_length ) +{ + mp4a_ALSSpecificConfig_t *alssc = (mp4a_ALSSpecificConfig_t *)lsmash_malloc_zero( sizeof(mp4a_ALSSpecificConfig_t) ); + if( !alssc ) + return NULL; + alssc->data = lsmash_memdup( exdata, exdata_length ); + if( !alssc->data ) + { + lsmash_free( alssc ); + return NULL; + } + alssc->size = exdata_length; + return alssc; +} + +/* Currently, only normal AAC, MPEG_1_2 are supported. + For AAC, other than normal AAC, such as AAC_scalable, ER_AAC_xxx, are not supported. + ADIF/PCE(program config element) style AudioSpecificConfig is not supported. + aot shall not be MP4A_AUDIO_OBJECT_TYPE_SBR even if you wish to signal SBR explicitly, use sbr_mode instead. + Frequency/channels shall be base AAC's one, even if SBR/PS. + If other than AAC with SBR, sbr_mode shall be MP4A_AAC_SBR_NOT_SPECIFIED. */ +mp4a_AudioSpecificConfig_t *mp4a_create_AudioSpecificConfig( + lsmash_mp4a_AudioObjectType aot, + uint32_t frequency, + uint32_t channels, + lsmash_mp4a_aac_sbr_mode sbr_mode, + uint8_t *exdata, + uint32_t exdata_length +) +{ + if( aot != MP4A_AUDIO_OBJECT_TYPE_AAC_MAIN && aot != MP4A_AUDIO_OBJECT_TYPE_AAC_LC + && aot != MP4A_AUDIO_OBJECT_TYPE_AAC_SSR && aot != MP4A_AUDIO_OBJECT_TYPE_AAC_LTP + && aot != MP4A_AUDIO_OBJECT_TYPE_TwinVQ && aot != MP4A_AUDIO_OBJECT_TYPE_Layer_1 + && aot != MP4A_AUDIO_OBJECT_TYPE_Layer_2 && aot != MP4A_AUDIO_OBJECT_TYPE_Layer_3 + && aot != MP4A_AUDIO_OBJECT_TYPE_ALS ) + return NULL; + if( frequency == 0 ) + return NULL; + + uint8_t channelConfig; + switch( channels ){ + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + channelConfig = channels; + break; + case 8: + channelConfig = 7; + break; + default: + return NULL; + } + + mp4a_AudioSpecificConfig_t *asc = (mp4a_AudioSpecificConfig_t *)lsmash_malloc_zero( sizeof(mp4a_AudioSpecificConfig_t) ); + if( !asc ) + return NULL; + + asc->sbr_mode = sbr_mode; + asc->audioObjectType = aot; + asc->channelConfiguration = channelConfig; + + uint8_t samplingFrequencyIndex = 0xF; + uint8_t i = 0x0; + if( sbr_mode != MP4A_AAC_SBR_NOT_SPECIFIED + || aot == MP4A_AUDIO_OBJECT_TYPE_AAC_MAIN + || aot == MP4A_AUDIO_OBJECT_TYPE_AAC_LC + || aot == MP4A_AUDIO_OBJECT_TYPE_AAC_SSR + || aot == MP4A_AUDIO_OBJECT_TYPE_AAC_LTP + || aot == MP4A_AUDIO_OBJECT_TYPE_SBR ) + { + while( frequency < mp4a_sampling_frequency_table[i][0] ) + i++; + asc->samplingFrequencyIndex = frequency == mp4a_sampling_frequency_table[i][1] ? i : 0xF; + asc->samplingFrequency = frequency; + samplingFrequencyIndex = mp4a_sampling_frequency_table[i][2]; + /* SBR settings */ + if( sbr_mode != MP4A_AAC_SBR_NOT_SPECIFIED ) + { + /* SBR limitation */ + /* see ISO/IEC 14496-3 Levels within the profiles / Levels for the High Efficiency AAC Profile */ + if( i < 0x3 ) + { + lsmash_free( asc ); + return NULL; + } + asc->extensionAudioObjectType = MP4A_AUDIO_OBJECT_TYPE_SBR; + } + else + asc->extensionAudioObjectType = MP4A_AUDIO_OBJECT_TYPE_NULL; + + if( sbr_mode == MP4A_AAC_SBR_BACKWARD_COMPATIBLE || sbr_mode == MP4A_AAC_SBR_HIERARCHICAL ) + { + asc->extensionSamplingFrequency = frequency * 2; + asc->extensionSamplingFrequencyIndex = i == 0xC ? 0xF : mp4a_sampling_frequency_table[i][3]; + } + else + { + asc->extensionSamplingFrequencyIndex = asc->samplingFrequencyIndex; + asc->extensionSamplingFrequency = asc->samplingFrequency; + } + } + else + { + while( i < 0xD && frequency != mp4a_sampling_frequency_table[i][1] ) + i++; + asc->samplingFrequencyIndex = i != 0xD ? i : 0xF; + asc->samplingFrequency = frequency; + asc->extensionAudioObjectType = MP4A_AUDIO_OBJECT_TYPE_NULL; + asc->extensionSamplingFrequencyIndex = asc->samplingFrequencyIndex; + asc->extensionSamplingFrequency = asc->samplingFrequency; + } + + switch( aot ) + { + case MP4A_AUDIO_OBJECT_TYPE_AAC_MAIN: + case MP4A_AUDIO_OBJECT_TYPE_AAC_LC: + case MP4A_AUDIO_OBJECT_TYPE_AAC_SSR: + case MP4A_AUDIO_OBJECT_TYPE_AAC_LTP: + case MP4A_AUDIO_OBJECT_TYPE_SBR: +#if 0 /* FIXME: here, stop currently unsupported codecs. */ + case MP4A_AUDIO_OBJECT_TYPE_AAC_scalable: + case MP4A_AUDIO_OBJECT_TYPE_TwinVQ: /* NOTE: I think we already have a support for TwinVQ, but how to test this? */ + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LC: + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LTP: + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_scalable: + case MP4A_AUDIO_OBJECT_TYPE_ER_Twin_VQ: + case MP4A_AUDIO_OBJECT_TYPE_ER_BSAC: + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LD: +#endif + asc->deepAudioSpecificConfig = mp4a_create_GASpecificConfig( samplingFrequencyIndex, channelConfig, aot ); + break; + case MP4A_AUDIO_OBJECT_TYPE_Layer_1: + case MP4A_AUDIO_OBJECT_TYPE_Layer_2: + case MP4A_AUDIO_OBJECT_TYPE_Layer_3: + asc->deepAudioSpecificConfig = mp4a_create_MPEG_1_2_SpecificConfig(); + break; + case MP4A_AUDIO_OBJECT_TYPE_ALS: + asc->deepAudioSpecificConfig = mp4a_create_ALSSpecificConfig( exdata, exdata_length ); + break; + default: + break; /* this case is trapped below. */ + } + if( !asc->deepAudioSpecificConfig ) + { + lsmash_free( asc ); + return NULL; + } + return asc; +} + +/* ADIF/PCE(program config element) style GASpecificConfig is not supported. */ +static void mp4a_put_GASpecificConfig( lsmash_bits_t* bits, mp4a_GASpecificConfig_t* gasc ) +{ + debug_if( !bits || !gasc ) + return; + lsmash_bits_put( bits, 1, gasc->frameLengthFlag ); + lsmash_bits_put( bits, 1, gasc->dependsOnCoreCoder ); + lsmash_bits_put( bits, 1, gasc->extensionFlag ); +} + +static void mp4a_put_MPEG_1_2_SpecificConfig( lsmash_bits_t* bits, mp4a_MPEG_1_2_SpecificConfig_t* mpeg_1_2_sc ) +{ + debug_if( !bits || !mpeg_1_2_sc ) + return; + lsmash_bits_put( bits, 1, mpeg_1_2_sc->extension ); /* shall be 0 */ +} + +static void mp4a_put_ALSSpecificConfig( lsmash_bits_t *bits, mp4a_ALSSpecificConfig_t *alssc ) +{ + debug_if( !bits || !alssc ) + return; + lsmash_bits_import_data( bits, alssc->data, alssc->size ); +} + +static inline void mp4a_put_AudioObjectType( lsmash_bits_t* bits, lsmash_mp4a_AudioObjectType aot ) +{ + if( aot > MP4A_AUDIO_OBJECT_TYPE_ESCAPE ) + { + lsmash_bits_put( bits, 5, MP4A_AUDIO_OBJECT_TYPE_ESCAPE ); + lsmash_bits_put( bits, 6, aot - MP4A_AUDIO_OBJECT_TYPE_ESCAPE - 1 ); + } + else + lsmash_bits_put( bits, 5, aot ); +} + +static inline void mp4a_put_SamplingFrequencyIndex( lsmash_bits_t* bits, uint8_t samplingFrequencyIndex, uint32_t samplingFrequency ) +{ + lsmash_bits_put( bits, 4, samplingFrequencyIndex ); + if( samplingFrequencyIndex == 0xF ) + lsmash_bits_put( bits, 24, samplingFrequency ); +} + +/* Currently, only normal AAC, MPEG_1_2 are supported. + For AAC, other than normal AAC, such as AAC_scalable, ER_AAC_xxx, are not supported. + ADIF/PCE(program config element) style AudioSpecificConfig is not supported either. */ +void mp4a_put_AudioSpecificConfig( lsmash_bs_t* bs, mp4a_AudioSpecificConfig_t* asc ) +{ + debug_if( !bs || !asc ) + return; + lsmash_bits_t bits; + lsmash_bits_init( &bits, bs ); + + if( asc->sbr_mode == MP4A_AAC_SBR_HIERARCHICAL ) + mp4a_put_AudioObjectType( &bits, asc->extensionAudioObjectType ); /* puts MP4A_AUDIO_OBJECT_TYPE_SBR */ + else + mp4a_put_AudioObjectType( &bits, asc->audioObjectType ); + mp4a_put_SamplingFrequencyIndex( &bits, asc->samplingFrequencyIndex, asc->samplingFrequency ); + lsmash_bits_put( &bits, 4, asc->channelConfiguration ); + if( asc->sbr_mode == MP4A_AAC_SBR_HIERARCHICAL ) + { + mp4a_put_SamplingFrequencyIndex( &bits, asc->extensionSamplingFrequencyIndex, asc->extensionSamplingFrequency ); + mp4a_put_AudioObjectType( &bits, asc->audioObjectType ); + } + switch( asc->audioObjectType ){ + case MP4A_AUDIO_OBJECT_TYPE_AAC_MAIN: + case MP4A_AUDIO_OBJECT_TYPE_AAC_LC: + case MP4A_AUDIO_OBJECT_TYPE_AAC_SSR: + case MP4A_AUDIO_OBJECT_TYPE_AAC_LTP: + case MP4A_AUDIO_OBJECT_TYPE_SBR: +#if 0 /* FIXME: here, stop currently unsupported codecs */ + case MP4A_AUDIO_OBJECT_TYPE_AAC_scalable: + case MP4A_AUDIO_OBJECT_TYPE_TwinVQ: /* NOTE: I think we already have a support for TwinVQ, but how to test this? */ + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LC: + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LTP: + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_scalable: + case MP4A_AUDIO_OBJECT_TYPE_ER_Twin_VQ: + case MP4A_AUDIO_OBJECT_TYPE_ER_BSAC: + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LD: +#endif + mp4a_put_GASpecificConfig( &bits, (mp4a_GASpecificConfig_t*)asc->deepAudioSpecificConfig ); + break; + case MP4A_AUDIO_OBJECT_TYPE_Layer_1: + case MP4A_AUDIO_OBJECT_TYPE_Layer_2: + case MP4A_AUDIO_OBJECT_TYPE_Layer_3: + mp4a_put_MPEG_1_2_SpecificConfig( &bits, (mp4a_MPEG_1_2_SpecificConfig_t*)asc->deepAudioSpecificConfig ); + break; + case MP4A_AUDIO_OBJECT_TYPE_ALS: + lsmash_bits_put( &bits, 5, 0 ); /* fillBits for byte alignment */ + mp4a_put_ALSSpecificConfig( &bits, (mp4a_ALSSpecificConfig_t *)asc->deepAudioSpecificConfig ); + break; + default: + break; /* FIXME: do we have to return error? */ + } + + /* FIXME: Error Resiliant stuff omitted here. */ + + if( asc->sbr_mode == MP4A_AAC_SBR_BACKWARD_COMPATIBLE || asc->sbr_mode == MP4A_AAC_SBR_NONE ) + { + lsmash_bits_put( &bits, 11, 0x2b7 ); + mp4a_put_AudioObjectType( &bits, asc->extensionAudioObjectType ); /* puts MP4A_AUDIO_OBJECT_TYPE_SBR */ + if( asc->extensionAudioObjectType == MP4A_AUDIO_OBJECT_TYPE_SBR ) /* this is always true, due to current spec */ + { + /* sbrPresentFlag */ + if( asc->sbr_mode == MP4A_AAC_SBR_NONE ) + lsmash_bits_put( &bits, 1, 0x0 ); + else + { + lsmash_bits_put( &bits, 1, 0x1 ); + mp4a_put_SamplingFrequencyIndex( &bits, asc->extensionSamplingFrequencyIndex, asc->extensionSamplingFrequency ); + } + } + } + lsmash_bits_put_align( &bits ); +} + +static int mp4a_get_GASpecificConfig( lsmash_bits_t *bits, mp4a_AudioSpecificConfig_t *asc ) +{ + mp4a_GASpecificConfig_t *gasc = (mp4a_GASpecificConfig_t *)lsmash_malloc_zero( sizeof(mp4a_GASpecificConfig_t) ); + if( !gasc ) + return LSMASH_ERR_MEMORY_ALLOC; + asc->deepAudioSpecificConfig = gasc; + gasc->frameLengthFlag = lsmash_bits_get( bits, 1 ); + gasc->dependsOnCoreCoder = lsmash_bits_get( bits, 1 ); + if( gasc->dependsOnCoreCoder ) + gasc->coreCoderDelay = lsmash_bits_get( bits, 14 ); + gasc->extensionFlag = lsmash_bits_get( bits, 1 ); + return 0; +} + +static int mp4a_get_MPEG_1_2_SpecificConfig( lsmash_bits_t *bits, mp4a_AudioSpecificConfig_t *asc ) +{ + mp4a_MPEG_1_2_SpecificConfig_t *mpeg_1_2_sc = (mp4a_MPEG_1_2_SpecificConfig_t *)lsmash_malloc_zero( sizeof(mp4a_MPEG_1_2_SpecificConfig_t) ); + if( !mpeg_1_2_sc ) + return LSMASH_ERR_MEMORY_ALLOC; + asc->deepAudioSpecificConfig = mpeg_1_2_sc; + mpeg_1_2_sc->extension = lsmash_bits_get( bits, 1 ); + return 0; +} + +static int mp4a_get_ALSSpecificConfig( lsmash_bits_t *bits, mp4a_AudioSpecificConfig_t *asc ) +{ + mp4a_ALSSpecificConfig_t *alssc = (mp4a_ALSSpecificConfig_t *)lsmash_malloc_zero( sizeof(mp4a_ALSSpecificConfig_t) ); + if( !alssc ) + return LSMASH_ERR_MEMORY_ALLOC; + asc->deepAudioSpecificConfig = alssc; + alssc->als_id = lsmash_bits_get( bits, 32 ); + alssc->samp_freq = lsmash_bits_get( bits, 32 ); + alssc->samples = lsmash_bits_get( bits, 32 ); + alssc->channels = lsmash_bits_get( bits, 16 ); + alssc->file_type = lsmash_bits_get( bits, 3 ); + alssc->resolution = lsmash_bits_get( bits, 3 ); + alssc->floating = lsmash_bits_get( bits, 1 ); + alssc->msb_first = lsmash_bits_get( bits, 1 ); + alssc->frame_length = lsmash_bits_get( bits, 16 ); + alssc->random_access = lsmash_bits_get( bits, 8 ); + alssc->ra_flag = lsmash_bits_get( bits, 2 ); + alssc->adapt_order = lsmash_bits_get( bits, 1 ); + alssc->coef_table = lsmash_bits_get( bits, 2 ); + alssc->long_term_prediction = lsmash_bits_get( bits, 1 ); + alssc->max_order = lsmash_bits_get( bits, 10 ); + alssc->block_switching = lsmash_bits_get( bits, 2 ); + alssc->bgmc_mode = lsmash_bits_get( bits, 1 ); + alssc->sb_part = lsmash_bits_get( bits, 1 ); + alssc->joint_stereo = lsmash_bits_get( bits, 1 ); + alssc->mc_coding = lsmash_bits_get( bits, 1 ); + alssc->chan_config = lsmash_bits_get( bits, 1 ); + alssc->chan_sort = lsmash_bits_get( bits, 1 ); + alssc->crc_enabled = lsmash_bits_get( bits, 1 ); + alssc->RLSLMS = lsmash_bits_get( bits, 1 ); + alssc->reserved = lsmash_bits_get( bits, 5 ); + alssc->aux_data_enabled = lsmash_bits_get( bits, 1 ); + return 0; +} + +static mp4a_AudioSpecificConfig_t *mp4a_get_AudioSpecificConfig( uint8_t *dsi_payload, uint32_t dsi_payload_length ) +{ + lsmash_bits_t *bits = lsmash_bits_adhoc_create(); + if( !bits ) + return NULL; + if( lsmash_bits_import_data( bits, dsi_payload, dsi_payload_length ) < 0 ) + { + lsmash_bits_adhoc_cleanup( bits ); + return NULL; + } + mp4a_AudioSpecificConfig_t *asc = (mp4a_AudioSpecificConfig_t *)lsmash_malloc_zero( sizeof(mp4a_AudioSpecificConfig_t) ); + if( !asc ) + goto fail; + asc->audioObjectType = lsmash_bits_get( bits, 5 ); + if( asc->audioObjectType == 31 ) + asc->extensionAudioObjectType = asc->audioObjectType += 1 + lsmash_bits_get( bits, 6 ); + asc->samplingFrequencyIndex = lsmash_bits_get( bits, 4 ); + if( asc->samplingFrequencyIndex == 0xf ) + asc->samplingFrequency = lsmash_bits_get( bits, 24 ); + asc->channelConfiguration = lsmash_bits_get( bits, 4 ); + int ret = 0; + switch( asc->audioObjectType ) + { + case MP4A_AUDIO_OBJECT_TYPE_AAC_MAIN : + case MP4A_AUDIO_OBJECT_TYPE_AAC_LC : + case MP4A_AUDIO_OBJECT_TYPE_AAC_SSR : + case MP4A_AUDIO_OBJECT_TYPE_AAC_LTP : + case MP4A_AUDIO_OBJECT_TYPE_AAC_scalable : + case MP4A_AUDIO_OBJECT_TYPE_TwinVQ : + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LC : + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LTP : + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_scalable : + case MP4A_AUDIO_OBJECT_TYPE_ER_Twin_VQ : + case MP4A_AUDIO_OBJECT_TYPE_ER_BSAC : + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LD : + ret = mp4a_get_GASpecificConfig( bits, asc ); + break; + case MP4A_AUDIO_OBJECT_TYPE_Layer_1 : + case MP4A_AUDIO_OBJECT_TYPE_Layer_2 : + case MP4A_AUDIO_OBJECT_TYPE_Layer_3 : + ret = mp4a_get_MPEG_1_2_SpecificConfig( bits, asc ); + break; + case MP4A_AUDIO_OBJECT_TYPE_ALS : + lsmash_bits_get( bits, 5 ); + ret = mp4a_get_ALSSpecificConfig( bits, asc ); + break; + default : + break; + } + if( ret < 0 ) + goto fail; + lsmash_bits_adhoc_cleanup( bits ); + return asc; +fail: + lsmash_bits_adhoc_cleanup( bits ); + lsmash_free( asc ); + return NULL; +} + +int mp4a_setup_summary_from_AudioSpecificConfig( lsmash_audio_summary_t *summary, uint8_t *dsi_payload, uint32_t dsi_payload_length ) +{ + mp4a_AudioSpecificConfig_t *asc = mp4a_get_AudioSpecificConfig( dsi_payload, dsi_payload_length ); + if( !asc ) + return LSMASH_ERR_NAMELESS; + summary->summary_type = LSMASH_SUMMARY_TYPE_AUDIO; + summary->sample_type = ISOM_CODEC_TYPE_MP4A_AUDIO; + summary->aot = asc->audioObjectType; + switch( asc->audioObjectType ) + { + case MP4A_AUDIO_OBJECT_TYPE_AAC_MAIN : + case MP4A_AUDIO_OBJECT_TYPE_AAC_LC : + case MP4A_AUDIO_OBJECT_TYPE_AAC_SSR : + case MP4A_AUDIO_OBJECT_TYPE_AAC_LTP : + case MP4A_AUDIO_OBJECT_TYPE_AAC_scalable : + case MP4A_AUDIO_OBJECT_TYPE_TwinVQ : + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LC : + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LTP : + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_scalable : + case MP4A_AUDIO_OBJECT_TYPE_ER_Twin_VQ : + case MP4A_AUDIO_OBJECT_TYPE_ER_BSAC : + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LD : + case MP4A_AUDIO_OBJECT_TYPE_Layer_1 : + case MP4A_AUDIO_OBJECT_TYPE_Layer_2 : + case MP4A_AUDIO_OBJECT_TYPE_Layer_3 : + if( asc->samplingFrequencyIndex == 0xf ) + summary->frequency = asc->samplingFrequency; + else + { + uint8_t i = 0x0; + while( i != 0xc ) + { + if( mp4a_sampling_frequency_table[i][2] == asc->samplingFrequencyIndex ) + { + summary->frequency = mp4a_sampling_frequency_table[i][1]; + break; + } + ++i; + } + if( i == 0xc ) + { + mp4a_remove_AudioSpecificConfig( asc ); + return LSMASH_ERR_INVALID_DATA; + } + } + if( asc->channelConfiguration < 8 ) + summary->channels = asc->channelConfiguration != 7 ? asc->channelConfiguration : 8; + else + summary->channels = 0; /* reserved */ + summary->sample_size = 16; + switch( asc->audioObjectType ) + { + case MP4A_AUDIO_OBJECT_TYPE_AAC_SSR : + summary->samples_in_frame = 1024; + break; + case MP4A_AUDIO_OBJECT_TYPE_Layer_1 : + summary->samples_in_frame = 384; + break; + case MP4A_AUDIO_OBJECT_TYPE_Layer_2 : + case MP4A_AUDIO_OBJECT_TYPE_Layer_3 : + summary->samples_in_frame = 1152; + break; + default : + summary->samples_in_frame = !((mp4a_GASpecificConfig_t *)asc->deepAudioSpecificConfig)->frameLengthFlag ? 1024 : 960; + break; + } + break; + case MP4A_AUDIO_OBJECT_TYPE_ALS : + { + mp4a_ALSSpecificConfig_t *alssc = (mp4a_ALSSpecificConfig_t *)asc->deepAudioSpecificConfig; + summary->frequency = alssc->samp_freq; + summary->channels = alssc->channels + 1; + summary->sample_size = (alssc->resolution + 1) * 8; + summary->samples_in_frame = alssc->frame_length + 1; + break; + } + default : + break; + } + mp4a_remove_AudioSpecificConfig( asc ); + return 0; +} + +/* This function is very ad-hoc. */ +uint8_t *mp4a_export_AudioSpecificConfig( lsmash_mp4a_AudioObjectType aot, + uint32_t frequency, + uint32_t channels, + lsmash_mp4a_aac_sbr_mode sbr_mode, + uint8_t *exdata, + uint32_t exdata_length, + uint32_t *data_length ) +{ + lsmash_bs_t *bs = lsmash_bs_create(); + if( !bs ) + return NULL; + mp4a_AudioSpecificConfig_t *asc = mp4a_create_AudioSpecificConfig( aot, frequency, channels, sbr_mode, exdata, exdata_length ); + if( !asc ) + { + lsmash_bs_cleanup( bs ); + return NULL; + } + mp4a_put_AudioSpecificConfig( bs, asc ); + uint8_t *data = lsmash_bs_export_data( bs, data_length ); + mp4a_remove_AudioSpecificConfig( asc ); + lsmash_bs_cleanup( bs ); + if( !data ) + return NULL; + return data; +} + +static void mp4a_print_GASpecificConfig( FILE *fp, mp4a_AudioSpecificConfig_t *asc, int indent ) +{ + mp4a_GASpecificConfig_t *gasc = (mp4a_GASpecificConfig_t *)asc->deepAudioSpecificConfig; + lsmash_ifprintf( fp, indent++, "[GASpecificConfig]\n" ); + lsmash_ifprintf( fp, indent, "frameLengthFlag = %"PRIu8"\n", gasc->frameLengthFlag ); + lsmash_ifprintf( fp, indent, "dependsOnCoreCoder = %"PRIu8"\n", gasc->dependsOnCoreCoder ); + if( gasc->dependsOnCoreCoder ) + lsmash_ifprintf( fp, indent, "coreCoderDelay = %"PRIu16"\n", gasc->coreCoderDelay ); + lsmash_ifprintf( fp, indent, "extensionFlag = %"PRIu8"\n", gasc->extensionFlag ); + if( !asc->channelConfiguration ) + lsmash_ifprintf( fp, indent, "program_config_element()\n" ); +} + +static void mp4a_print_MPEG_1_2_SpecificConfig( FILE *fp, mp4a_AudioSpecificConfig_t *asc, int indent ) +{ + mp4a_MPEG_1_2_SpecificConfig_t *mpeg_1_2_sc = (mp4a_MPEG_1_2_SpecificConfig_t *)asc->deepAudioSpecificConfig; + lsmash_ifprintf( fp, indent++, "[MPEG_1_2_SpecificConfig]\n" ); + lsmash_ifprintf( fp, indent, "extension = %"PRIu8"\n", mpeg_1_2_sc->extension ); +} + +static void mp4a_print_ALSSpecificConfig( FILE *fp, mp4a_AudioSpecificConfig_t *asc, int indent ) +{ + mp4a_ALSSpecificConfig_t *alssc = (mp4a_ALSSpecificConfig_t *)asc->deepAudioSpecificConfig; + const char *file_type [4] = { "raw", "wave", "aiff", "bwf" }; + const char *floating [2] = { "integer", "IEEE 32-bit floating-point" }; + const char *endian [2] = { "little", "big" }; + const char *ra_flag [4] = { "not stored", "stored at the beginning of frame_data()", "stored at the end of ALSSpecificConfig", "?" }; + lsmash_ifprintf( fp, indent++, "[ALSSpecificConfig]\n" ); + lsmash_ifprintf( fp, indent, "als_id = 0x%"PRIx32"\n", alssc->als_id ); + lsmash_ifprintf( fp, indent, "samp_freq = %"PRIu32" Hz\n", alssc->samp_freq ); + lsmash_ifprintf( fp, indent, "samples = %"PRIu32"\n", alssc->samples ); + lsmash_ifprintf( fp, indent, "channels = %"PRIu16"\n", alssc->channels ); + if( alssc->file_type <= 3 ) + lsmash_ifprintf( fp, indent, "file_type = %"PRIu8" (%s file)\n", alssc->file_type, file_type[ alssc->file_type ] ); + else + lsmash_ifprintf( fp, indent, "file_type = %"PRIu8"\n", alssc->file_type ); + if( alssc->resolution <= 3 ) + lsmash_ifprintf( fp, indent, "resolution = %"PRIu8" (%d-bit)\n", alssc->resolution, 8 * (1 + alssc->resolution) ); + else + lsmash_ifprintf( fp, indent, "resolution = %"PRIu8"\n", alssc->resolution ); + lsmash_ifprintf( fp, indent, "floating = %"PRIu8" (%s)\n", alssc->floating, floating[ alssc->floating ] ); + if( alssc->resolution ) + lsmash_ifprintf( fp, indent, "msb_first = %"PRIu8" (%s-endian)\n", alssc->msb_first, endian[ alssc->msb_first ] ); + else + lsmash_ifprintf( fp, indent, "msb_first = %"PRIu8" (%ssigned data)\n", alssc->msb_first, ((const char *[2]){ "un", "" })[ alssc->msb_first ] ); + lsmash_ifprintf( fp, indent, "frame_length = %"PRIu16"\n", alssc->frame_length ); + lsmash_ifprintf( fp, indent, "random_access = %"PRIu8"\n", alssc->random_access ); + lsmash_ifprintf( fp, indent, "ra_flag = %"PRIu8" (ra_unit_size is %s)\n", alssc->ra_flag, ra_flag[ alssc->ra_flag ] ); + lsmash_ifprintf( fp, indent, "adapt_order = %"PRIu8"\n", alssc->adapt_order ); + lsmash_ifprintf( fp, indent, "coef_table = %"PRIu8"\n", alssc->coef_table ); + lsmash_ifprintf( fp, indent, "long_term_prediction = %"PRIu8"\n", alssc->long_term_prediction ); + lsmash_ifprintf( fp, indent, "max_order = %"PRIu8"\n", alssc->max_order ); + lsmash_ifprintf( fp, indent, "block_switching = %"PRIu8"\n", alssc->block_switching ); + lsmash_ifprintf( fp, indent, "bgmc_mode = %"PRIu8"\n", alssc->bgmc_mode ); + lsmash_ifprintf( fp, indent, "sb_part = %"PRIu8"\n", alssc->sb_part ); + lsmash_ifprintf( fp, indent, "joint_stereo = %"PRIu8"\n", alssc->joint_stereo ); + lsmash_ifprintf( fp, indent, "mc_coding = %"PRIu8"\n", alssc->mc_coding ); + lsmash_ifprintf( fp, indent, "chan_config = %"PRIu8"\n", alssc->chan_config ); + lsmash_ifprintf( fp, indent, "chan_sort = %"PRIu8"\n", alssc->chan_sort ); + lsmash_ifprintf( fp, indent, "crc_enabled = %"PRIu8"\n", alssc->crc_enabled ); + lsmash_ifprintf( fp, indent, "RLSLMS = %"PRIu8"\n", alssc->RLSLMS ); + lsmash_ifprintf( fp, indent, "reserved = %"PRIu8"\n", alssc->reserved ); + lsmash_ifprintf( fp, indent, "aux_data_enabled = %"PRIu8"\n", alssc->aux_data_enabled ); +} + +void mp4a_print_AudioSpecificConfig( FILE *fp, uint8_t *dsi_payload, uint32_t dsi_payload_length, int indent ) +{ + assert( fp && dsi_payload && dsi_payload_length ); + mp4a_AudioSpecificConfig_t *asc = mp4a_get_AudioSpecificConfig( dsi_payload, dsi_payload_length ); + if( !asc ) + return; + const char *audio_object_type[] = + { + "NULL", + "AAC MAIN", + "AAC LC (Low Complexity)", + "AAC SSR (Scalable Sample Rate)", + "AAC LTP (Long Term Prediction)", + "SBR (Spectral Band Replication)", + "AAC scalable", + "TwinVQ", + "CELP (Code Excited Linear Prediction)", + "HVXC (Harmonic Vector Excitation Coding)", + "reserved", + "reserved", + "TTSI (Text-To-Speech Interface)", + "Main synthetic", + "Wavetable synthesis", + "General MIDI", + "Algorithmic Synthesis and Audio FX", + "ER AAC LC", + "reserved", + "ER AAC LTP", + "ER AAC scalable", + "ER Twin VQ", + "ER BSAC (Bit-Sliced Arithmetic Coding)", + "ER AAC LD", + "ER CELP", + "ER HVXC", + "ER HILN (Harmonic and Individual Lines plus Noise)", + "ER Parametric", + "SSC (SinuSoidal Coding)", + "PS (Parametric Stereo)", + "MPEG Surround", + "escape", + "Layer-1", + "Layer-2", + "Layer-3", + "DST (Direct Stream Transfer)", + "ALS (Audio Lossless Coding)", + "SLS (Scalable Lossless Coding)", + "SLS non-core", + "ER AAC ELD", + "SMR Simple", + "SMR Main", + "USAC (Unified Speech and Audio Coding)", + "SAOC", + "LD MPEG Surround", + "SAOC-DE" + }; + lsmash_ifprintf( fp, indent++, "[AudioSpecificConfig]\n" ); + if( asc->audioObjectType < sizeof(audio_object_type) / sizeof(audio_object_type[0]) ) + lsmash_ifprintf( fp, indent, "audioObjectType = %d (%s)\n", asc->audioObjectType, audio_object_type[ asc->audioObjectType ] ); + else + lsmash_ifprintf( fp, indent, "audioObjectType = %d\n", asc->audioObjectType ); + lsmash_ifprintf( fp, indent, "samplingFrequencyIndex = %"PRIu8"\n", asc->samplingFrequencyIndex ); + if( asc->samplingFrequencyIndex == 0xf ) + lsmash_ifprintf( fp, indent, "samplingFrequency = %"PRIu32"\n", asc->samplingFrequency ); + lsmash_ifprintf( fp, indent, "channelConfiguration = %"PRIu8"\n", asc->channelConfiguration ); + if( asc->extensionAudioObjectType == 5 ) + { + lsmash_ifprintf( fp, indent, "extensionSamplingFrequencyIndex = %"PRIu8"\n", asc->extensionSamplingFrequencyIndex ); + if( asc->extensionSamplingFrequencyIndex == 0xf ) + lsmash_ifprintf( fp, indent, "extensionSamplingFrequency = %"PRIu32"\n", asc->extensionSamplingFrequency ); + if( asc->audioObjectType == 22 ) + lsmash_ifprintf( fp, indent, "extensionChannelConfiguration = %"PRIu8"\n", asc->extensionChannelConfiguration ); + } + if( asc->deepAudioSpecificConfig ) + switch( asc->audioObjectType ) + { + case MP4A_AUDIO_OBJECT_TYPE_AAC_MAIN : + case MP4A_AUDIO_OBJECT_TYPE_AAC_LC : + case MP4A_AUDIO_OBJECT_TYPE_AAC_SSR : + case MP4A_AUDIO_OBJECT_TYPE_AAC_LTP : + case MP4A_AUDIO_OBJECT_TYPE_AAC_scalable : + case MP4A_AUDIO_OBJECT_TYPE_TwinVQ : + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LC : + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LTP : + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_scalable : + case MP4A_AUDIO_OBJECT_TYPE_ER_Twin_VQ : + case MP4A_AUDIO_OBJECT_TYPE_ER_BSAC : + case MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LD : + mp4a_print_GASpecificConfig( fp, asc, indent ); + break; + case MP4A_AUDIO_OBJECT_TYPE_Layer_1 : + case MP4A_AUDIO_OBJECT_TYPE_Layer_2 : + case MP4A_AUDIO_OBJECT_TYPE_Layer_3 : + mp4a_print_MPEG_1_2_SpecificConfig( fp, asc, indent ); + break; + case MP4A_AUDIO_OBJECT_TYPE_ALS : + mp4a_print_ALSSpecificConfig( fp, asc, indent ); + break; + default : + break; + } + mp4a_remove_AudioSpecificConfig( asc ); +} + +int mp4a_update_bitrate( isom_stbl_t *stbl, isom_mdhd_t *mdhd, uint32_t sample_description_index ) +{ + isom_audio_entry_t *mp4a = (isom_audio_entry_t *)lsmash_list_get_entry_data( &stbl->stsd->list, sample_description_index ); + if( LSMASH_IS_NON_EXISTING_BOX( mp4a ) ) + return LSMASH_ERR_INVALID_DATA; + isom_esds_t *esds; + if( mp4a->version ) + { + /* MPEG-4 Audio in QTFF */ + isom_wave_t *wave = (isom_wave_t *)isom_get_extension_box_format( &mp4a->extensions, QT_BOX_TYPE_WAVE ); + if( LSMASH_IS_NON_EXISTING_BOX( wave ) ) + return LSMASH_ERR_INVALID_DATA; + esds = (isom_esds_t *)isom_get_extension_box_format( &wave->extensions, QT_BOX_TYPE_ESDS ); + } + else + esds = (isom_esds_t *)isom_get_extension_box_format( &mp4a->extensions, ISOM_BOX_TYPE_ESDS ); + if( LSMASH_IS_NON_EXISTING_BOX( esds ) || !esds->ES ) + return LSMASH_ERR_INVALID_DATA; + uint32_t bufferSizeDB; + uint32_t maxBitrate; + uint32_t avgBitrate; + int err = isom_calculate_bitrate_description( stbl, mdhd, &bufferSizeDB, &maxBitrate, &avgBitrate, sample_description_index ); + if( err < 0 ) + return err; + /* FIXME: avgBitrate is 0 only if VBR in proper. */ + return mp4sys_update_DecoderConfigDescriptor( esds->ES, bufferSizeDB, maxBitrate, 0 ); +} + +/*************************************************************************** + audioProfileLevelIndication +***************************************************************************/ +/* NOTE: This function is not strictly preferable, but accurate. + The spec of audioProfileLevelIndication is too much complicated. */ +mp4a_audioProfileLevelIndication mp4a_get_audioProfileLevelIndication( lsmash_audio_summary_t *summary ) +{ + if( !summary || summary->summary_type != LSMASH_SUMMARY_TYPE_AUDIO ) + return MP4A_AUDIO_PLI_NONE_REQUIRED; /* means error. */ + if( lsmash_mp4sys_get_object_type_indication( (lsmash_summary_t *)summary ) != MP4SYS_OBJECT_TYPE_Audio_ISO_14496_3 ) + return MP4A_AUDIO_PLI_NOT_SPECIFIED; /* This is of audio stream, but not described in ISO/IEC 14496-3. */ + if( summary->channels == 0 || summary->frequency == 0 ) + return MP4A_AUDIO_PLI_NONE_REQUIRED; /* means error. */ + mp4a_audioProfileLevelIndication pli = MP4A_AUDIO_PLI_NOT_SPECIFIED; + switch( summary->aot ) + { + case MP4A_AUDIO_OBJECT_TYPE_AAC_LC: + if( summary->sbr_mode == MP4A_AAC_SBR_HIERARCHICAL ) + { + /* NOTE: This is not strictly preferable, but accurate; just possibly over-estimated. + We do not expect to use MP4A_AAC_SBR_HIERARCHICAL mode without SBR, nor downsampled mode with SBR. */ + if( summary->channels <= 2 && summary->frequency <= 24000 ) + pli = MP4A_AUDIO_PLI_HE_AAC_L2; + else if( summary->channels <= 5 && summary->frequency <= 48000 ) + pli = MP4A_AUDIO_PLI_HE_AAC_L5; + else + pli = MP4A_AUDIO_PLI_NOT_SPECIFIED; + break; + } + /* pretending plain AAC-LC, if actually HE-AAC. */ + static const uint32_t mp4sys_aac_pli_table[5][3] = { + /* channels, frequency, audioProfileLevelIndication */ + { 6, 96000, MP4A_AUDIO_PLI_AAC_L5 }, /* FIXME: 6ch is not strictly correct, but works in many case. */ + { 6, 48000, MP4A_AUDIO_PLI_AAC_L4 }, /* FIXME: 6ch is not strictly correct, but works in many case. */ + { 2, 48000, MP4A_AUDIO_PLI_AAC_L2 }, + { 2, 24000, MP4A_AUDIO_PLI_AAC_L1 }, + { 0, 0, MP4A_AUDIO_PLI_NOT_SPECIFIED } + }; + for( int i = 0; summary->channels <= mp4sys_aac_pli_table[i][0] && summary->frequency <= mp4sys_aac_pli_table[i][1] ; i++ ) + pli = mp4sys_aac_pli_table[i][2]; + break; + case MP4A_AUDIO_OBJECT_TYPE_ALS: + /* FIXME: this is not stricly. Summary shall carry max_order, block_switching, bgmc_mode and RLSLMS. */ + if( summary->channels <= 2 && summary->frequency <= 48000 && summary->sample_size <= 16 && summary->samples_in_frame <= 4096 ) + pli = MP4A_AUDIO_PLI_ALS_Simple_L1; + else + pli = MP4A_AUDIO_PLI_NOT_SPECIFIED; + break; + case MP4A_AUDIO_OBJECT_TYPE_Layer_1: + case MP4A_AUDIO_OBJECT_TYPE_Layer_2: + case MP4A_AUDIO_OBJECT_TYPE_Layer_3: + pli = MP4A_AUDIO_PLI_NOT_SPECIFIED; /* 14496-3, Audio profiles and levels, does not allow any pli. */ + break; + default: + pli = MP4A_AUDIO_PLI_NOT_SPECIFIED; /* something we don't know/support, or what the spec never covers. */ + break; + } + return pli; +} + +static int mp4sys_is_same_profile( mp4a_audioProfileLevelIndication a, mp4a_audioProfileLevelIndication b ) +{ + switch( a ) + { + case MP4A_AUDIO_PLI_Main_L1: + case MP4A_AUDIO_PLI_Main_L2: + case MP4A_AUDIO_PLI_Main_L3: + case MP4A_AUDIO_PLI_Main_L4: + if( MP4A_AUDIO_PLI_Main_L1 <= b && b <= MP4A_AUDIO_PLI_Main_L4 ) + return 1; + return 0; + break; + case MP4A_AUDIO_PLI_Scalable_L1: + case MP4A_AUDIO_PLI_Scalable_L2: + case MP4A_AUDIO_PLI_Scalable_L3: + case MP4A_AUDIO_PLI_Scalable_L4: + if( MP4A_AUDIO_PLI_Scalable_L1 <= b && b <= MP4A_AUDIO_PLI_Scalable_L4 ) + return 1; + return 0; + break; + case MP4A_AUDIO_PLI_Speech_L1: + case MP4A_AUDIO_PLI_Speech_L2: + if( MP4A_AUDIO_PLI_Speech_L1 <= b && b <= MP4A_AUDIO_PLI_Speech_L2 ) + return 1; + return 0; + break; + case MP4A_AUDIO_PLI_Synthetic_L1: + case MP4A_AUDIO_PLI_Synthetic_L2: + case MP4A_AUDIO_PLI_Synthetic_L3: + if( MP4A_AUDIO_PLI_Synthetic_L1 <= b && b <= MP4A_AUDIO_PLI_Synthetic_L3 ) + return 1; + return 0; + break; + case MP4A_AUDIO_PLI_HighQuality_L1: + case MP4A_AUDIO_PLI_HighQuality_L2: + case MP4A_AUDIO_PLI_HighQuality_L3: + case MP4A_AUDIO_PLI_HighQuality_L4: + case MP4A_AUDIO_PLI_HighQuality_L5: + case MP4A_AUDIO_PLI_HighQuality_L6: + case MP4A_AUDIO_PLI_HighQuality_L7: + case MP4A_AUDIO_PLI_HighQuality_L8: + if( MP4A_AUDIO_PLI_HighQuality_L1 <= b && b <= MP4A_AUDIO_PLI_HighQuality_L8 ) + return 1; + return 0; + break; + case MP4A_AUDIO_PLI_LowDelay_L1: + case MP4A_AUDIO_PLI_LowDelay_L2: + case MP4A_AUDIO_PLI_LowDelay_L3: + case MP4A_AUDIO_PLI_LowDelay_L4: + case MP4A_AUDIO_PLI_LowDelay_L5: + case MP4A_AUDIO_PLI_LowDelay_L6: + case MP4A_AUDIO_PLI_LowDelay_L7: + case MP4A_AUDIO_PLI_LowDelay_L8: + if( MP4A_AUDIO_PLI_LowDelay_L1 <= b && b <= MP4A_AUDIO_PLI_LowDelay_L8 ) + return 1; + return 0; + break; + case MP4A_AUDIO_PLI_Natural_L1: + case MP4A_AUDIO_PLI_Natural_L2: + case MP4A_AUDIO_PLI_Natural_L3: + case MP4A_AUDIO_PLI_Natural_L4: + if( MP4A_AUDIO_PLI_Natural_L1 <= b && b <= MP4A_AUDIO_PLI_Natural_L4 ) + return 1; + return 0; + break; + case MP4A_AUDIO_PLI_MobileInternetworking_L1: + case MP4A_AUDIO_PLI_MobileInternetworking_L2: + case MP4A_AUDIO_PLI_MobileInternetworking_L3: + case MP4A_AUDIO_PLI_MobileInternetworking_L4: + case MP4A_AUDIO_PLI_MobileInternetworking_L5: + case MP4A_AUDIO_PLI_MobileInternetworking_L6: + if( MP4A_AUDIO_PLI_MobileInternetworking_L1 <= b && b <= MP4A_AUDIO_PLI_MobileInternetworking_L6 ) + return 1; + return 0; + break; + case MP4A_AUDIO_PLI_AAC_L1: + case MP4A_AUDIO_PLI_AAC_L2: + case MP4A_AUDIO_PLI_AAC_L4: + case MP4A_AUDIO_PLI_AAC_L5: + if( MP4A_AUDIO_PLI_AAC_L1 <= b && b <= MP4A_AUDIO_PLI_AAC_L5 ) + return 1; + return 0; + break; + case MP4A_AUDIO_PLI_HE_AAC_L2: + case MP4A_AUDIO_PLI_HE_AAC_L3: + case MP4A_AUDIO_PLI_HE_AAC_L4: + case MP4A_AUDIO_PLI_HE_AAC_L5: + if( MP4A_AUDIO_PLI_HE_AAC_L2 <= b && b <= MP4A_AUDIO_PLI_HE_AAC_L5 ) + return 1; + return 0; + break; + default: + break; + } + return 0; +} + +/* NOTE: This function is not strictly preferable, but accurate. + The spec of audioProfileLevelIndication is too much complicated. */ +mp4a_audioProfileLevelIndication mp4a_max_audioProfileLevelIndication( mp4a_audioProfileLevelIndication a, mp4a_audioProfileLevelIndication b ) +{ + /* NONE_REQUIRED is minimal priotity, and NOT_SPECIFIED is max priority. */ + if( a == MP4A_AUDIO_PLI_NOT_SPECIFIED || b == MP4A_AUDIO_PLI_NONE_REQUIRED ) + return a; + if( a == MP4A_AUDIO_PLI_NONE_REQUIRED || b == MP4A_AUDIO_PLI_NOT_SPECIFIED ) + return b; + mp4a_audioProfileLevelIndication c, d; + if( a < b ) + { + c = a; + d = b; + } + else + { + c = b; + d = a; + } + /* AAC-LC and SBR specific; If mixtured there, use correspond HE_AAC profile. */ + if( MP4A_AUDIO_PLI_AAC_L1 <= c && c <= MP4A_AUDIO_PLI_AAC_L5 + && MP4A_AUDIO_PLI_HE_AAC_L2 <= d && d <= MP4A_AUDIO_PLI_HE_AAC_L5 ) + { + if( c <= MP4A_AUDIO_PLI_AAC_L2 ) + return d; + c += 4; /* upgrade to HE-AAC */ + return c > d ? c : d; + } + /* General */ + if( mp4sys_is_same_profile( c, d ) ) + return d; + return MP4A_AUDIO_PLI_NOT_SPECIFIED; +} diff --git a/3rdparty/l-smash/codecs/mp4a.h b/3rdparty/l-smash/codecs/mp4a.h new file mode 100644 index 000000000..c1d2a5ac4 --- /dev/null +++ b/3rdparty/l-smash/codecs/mp4a.h @@ -0,0 +1,136 @@ +/***************************************************************************** + * mp4a.h + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Takashi Hirata + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#ifndef MP4A_H +#define MP4A_H + +/*************************************************************************** + MPEG-4 Systems for MPEG-4 Audio +***************************************************************************/ + +/* 14496-3 audioProfileLevelIndication */ +typedef enum { + MP4A_AUDIO_PLI_Reserved = 0x00, /* Reserved for ISO use */ + MP4A_AUDIO_PLI_Main_L1 = 0x01, /* Main Audio Profile L1 */ + MP4A_AUDIO_PLI_Main_L2 = 0x02, /* Main Audio Profile L2 */ + MP4A_AUDIO_PLI_Main_L3 = 0x03, /* Main Audio Profile L3 */ + MP4A_AUDIO_PLI_Main_L4 = 0x04, /* Main Audio Profile L4 */ + MP4A_AUDIO_PLI_Scalable_L1 = 0x05, /* Scalable Audio Profile L1 */ + MP4A_AUDIO_PLI_Scalable_L2 = 0x06, /* Scalable Audio Profile L2 */ + MP4A_AUDIO_PLI_Scalable_L3 = 0x07, /* Scalable Audio Profile L3 */ + MP4A_AUDIO_PLI_Scalable_L4 = 0x08, /* Scalable Audio Profile L4 */ + MP4A_AUDIO_PLI_Speech_L1 = 0x09, /* Speech Audio Profile L1 */ + MP4A_AUDIO_PLI_Speech_L2 = 0x0A, /* Speech Audio Profile L2 */ + MP4A_AUDIO_PLI_Synthetic_L1 = 0x0B, /* Synthetic Audio Profile L1 */ + MP4A_AUDIO_PLI_Synthetic_L2 = 0x0C, /* Synthetic Audio Profile L2 */ + MP4A_AUDIO_PLI_Synthetic_L3 = 0x0D, /* Synthetic Audio Profile L3 */ + MP4A_AUDIO_PLI_HighQuality_L1 = 0x0E, /* High Quality Audio Profile L1 */ + MP4A_AUDIO_PLI_HighQuality_L2 = 0x0F, /* High Quality Audio Profile L2 */ + MP4A_AUDIO_PLI_HighQuality_L3 = 0x10, /* High Quality Audio Profile L3 */ + MP4A_AUDIO_PLI_HighQuality_L4 = 0x11, /* High Quality Audio Profile L4 */ + MP4A_AUDIO_PLI_HighQuality_L5 = 0x12, /* High Quality Audio Profile L5 */ + MP4A_AUDIO_PLI_HighQuality_L6 = 0x13, /* High Quality Audio Profile L6 */ + MP4A_AUDIO_PLI_HighQuality_L7 = 0x14, /* High Quality Audio Profile L7 */ + MP4A_AUDIO_PLI_HighQuality_L8 = 0x15, /* High Quality Audio Profile L8 */ + MP4A_AUDIO_PLI_LowDelay_L1 = 0x16, /* Low Delay Audio Profile L1 */ + MP4A_AUDIO_PLI_LowDelay_L2 = 0x17, /* Low Delay Audio Profile L2 */ + MP4A_AUDIO_PLI_LowDelay_L3 = 0x18, /* Low Delay Audio Profile L3 */ + MP4A_AUDIO_PLI_LowDelay_L4 = 0x19, /* Low Delay Audio Profile L4 */ + MP4A_AUDIO_PLI_LowDelay_L5 = 0x1A, /* Low Delay Audio Profile L5 */ + MP4A_AUDIO_PLI_LowDelay_L6 = 0x1B, /* Low Delay Audio Profile L6 */ + MP4A_AUDIO_PLI_LowDelay_L7 = 0x1C, /* Low Delay Audio Profile L7 */ + MP4A_AUDIO_PLI_LowDelay_L8 = 0x1D, /* Low Delay Audio Profile L8 */ + MP4A_AUDIO_PLI_Natural_L1 = 0x1E, /* Natural Audio Profile L1 */ + MP4A_AUDIO_PLI_Natural_L2 = 0x1F, /* Natural Audio Profile L2 */ + MP4A_AUDIO_PLI_Natural_L3 = 0x20, /* Natural Audio Profile L3 */ + MP4A_AUDIO_PLI_Natural_L4 = 0x21, /* Natural Audio Profile L4 */ + MP4A_AUDIO_PLI_MobileInternetworking_L1 = 0x22, /* Mobile Audio Internetworking Profile L1 */ + MP4A_AUDIO_PLI_MobileInternetworking_L2 = 0x23, /* Mobile Audio Internetworking Profile L2 */ + MP4A_AUDIO_PLI_MobileInternetworking_L3 = 0x24, /* Mobile Audio Internetworking Profile L3 */ + MP4A_AUDIO_PLI_MobileInternetworking_L4 = 0x25, /* Mobile Audio Internetworking Profile L4 */ + MP4A_AUDIO_PLI_MobileInternetworking_L5 = 0x26, /* Mobile Audio Internetworking Profile L5 */ + MP4A_AUDIO_PLI_MobileInternetworking_L6 = 0x27, /* Mobile Audio Internetworking Profile L6 */ + MP4A_AUDIO_PLI_AAC_L1 = 0x28, /* AAC Profile L1 */ + MP4A_AUDIO_PLI_AAC_L2 = 0x29, /* AAC Profile L2 */ + MP4A_AUDIO_PLI_AAC_L4 = 0x2A, /* AAC Profile L4 */ + MP4A_AUDIO_PLI_AAC_L5 = 0x2B, /* AAC Profile L5 */ + MP4A_AUDIO_PLI_HE_AAC_L2 = 0x2C, /* High Efficiency AAC Profile L2 */ + MP4A_AUDIO_PLI_HE_AAC_L3 = 0x2D, /* High Efficiency AAC Profile L3 */ + MP4A_AUDIO_PLI_HE_AAC_L4 = 0x2E, /* High Efficiency AAC Profile L4 */ + MP4A_AUDIO_PLI_HE_AAC_L5 = 0x2F, /* High Efficiency AAC Profile L5 */ + MP4A_AUDIO_PLI_HE_AAC_v2_L2 = 0x30, /* High Efficiency AAC v2 Profile L2 */ + MP4A_AUDIO_PLI_HE_AAC_v2_L3 = 0x31, /* High Efficiency AAC v2 Profile L3 */ + MP4A_AUDIO_PLI_HE_AAC_v2_L4 = 0x32, /* High Efficiency AAC v2 Profile L4 */ + MP4A_AUDIO_PLI_HE_AAC_v2_L5 = 0x33, /* High Efficiency AAC v2 Profile L5 */ + MP4A_AUDIO_PLI_LowDelay_AAC_L1 = 0x34, /* Low Delay AAC Profile L1 */ + MP4A_AUDIO_PLI_Baseline_MPEG_Surround_L1= 0x35, /* Baseline MPEG Surround Profile L1 */ + MP4A_AUDIO_PLI_Baseline_MPEG_Surround_L2= 0x36, /* Baseline MPEG Surround Profile L2 */ + MP4A_AUDIO_PLI_Baseline_MPEG_Surround_L3= 0x37, /* Baseline MPEG Surround Profile L3 */ + MP4A_AUDIO_PLI_Baseline_MPEG_Surround_L4= 0x38, /* Baseline MPEG Surround Profile L4 */ + MP4A_AUDIO_PLI_Baseline_MPEG_Surround_L5= 0x39, /* Baseline MPEG Surround Profile L5 */ + MP4A_AUDIO_PLI_Baseline_MPEG_Surround_L6= 0x3A, /* Baseline MPEG Surround Profile L6 */ + MP4A_AUDIO_PLI_HD_AAC_L1 = 0x3B, /* High Definition AAC Profile L1 */ + MP4A_AUDIO_PLI_ALS_Simple_L1 = 0x3C, /* ALS Simple Profile L1 */ + MP4A_AUDIO_PLI_NOT_SPECIFIED = 0xFE, /* no audio profile specified */ + MP4A_AUDIO_PLI_NONE_REQUIRED = 0xFF, /* no audio capability required */ +} mp4a_audioProfileLevelIndication; + +#ifndef MP4A_INTERNAL + +typedef void mp4a_AudioSpecificConfig_t; + +/* export for mp4sys / importer */ +mp4a_AudioSpecificConfig_t *mp4a_create_AudioSpecificConfig( + lsmash_mp4a_AudioObjectType aot, + uint32_t frequency, + uint32_t channels, + lsmash_mp4a_aac_sbr_mode sbr_mode, + uint8_t *exdata, + uint32_t exdata_length +); +void mp4a_put_AudioSpecificConfig( lsmash_bs_t* bs, mp4a_AudioSpecificConfig_t* asc ); +void mp4a_remove_AudioSpecificConfig( mp4a_AudioSpecificConfig_t* asc ); + +uint8_t *mp4a_export_AudioSpecificConfig( lsmash_mp4a_AudioObjectType aot, + uint32_t frequency, + uint32_t channels, + lsmash_mp4a_aac_sbr_mode sbr_mode, + uint8_t *exdata, + uint32_t exdata_length, + uint32_t *data_length ); + +/* export for importer */ +extern const uint32_t mp4a_sampling_frequency_table[13][5]; + +/* setup for summary */ +int mp4a_setup_summary_from_AudioSpecificConfig( lsmash_audio_summary_t *summary, uint8_t *dsi_payload, uint32_t dsi_payload_length ); + +/* profileLevelIndication relative functions. */ +mp4a_audioProfileLevelIndication mp4a_get_audioProfileLevelIndication( lsmash_audio_summary_t *summary ); +mp4a_audioProfileLevelIndication mp4a_max_audioProfileLevelIndication( + mp4a_audioProfileLevelIndication a, + mp4a_audioProfileLevelIndication b +); + +#endif + +#endif diff --git a/3rdparty/l-smash/codecs/mp4sys.c b/3rdparty/l-smash/codecs/mp4sys.c new file mode 100644 index 000000000..487960d85 --- /dev/null +++ b/3rdparty/l-smash/codecs/mp4sys.c @@ -0,0 +1,1610 @@ +/***************************************************************************** + * mp4sys.c + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Takashi Hirata + * Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include +#include +#include + +#include "core/box.h" + +#include "description.h" +#include "mp4a.h" +#define MP4SYS_INTERNAL +#include "mp4sys.h" + +/*************************************************************************** + MPEG-4 Systems +***************************************************************************/ + +#define ALWAYS_28BITS_LENGTH_CODING 1 // for some weird (but originator's) devices + +static const lsmash_class_t lsmash_mp4sys_class = +{ + "mp4sys" +}; + +/* List of Class Tags for Descriptors */ +typedef enum +{ + MP4SYS_DESCRIPTOR_TAG_Forbidden = 0x00, /* Forbidden */ + MP4SYS_DESCRIPTOR_TAG_ObjectDescrTag = 0x01, /* ObjectDescrTag */ + MP4SYS_DESCRIPTOR_TAG_InitialObjectDescrTag = 0x02, /* InitialObjectDescrTag */ + MP4SYS_DESCRIPTOR_TAG_ES_DescrTag = 0x03, /* ES_DescrTag */ + MP4SYS_DESCRIPTOR_TAG_DecoderConfigDescrTag = 0x04, /* DecoderConfigDescrTag */ + MP4SYS_DESCRIPTOR_TAG_DecSpecificInfoTag = 0x05, /* DecSpecificInfoTag */ + MP4SYS_DESCRIPTOR_TAG_SLConfigDescrTag = 0x06, /* SLConfigDescrTag */ + MP4SYS_DESCRIPTOR_TAG_ContentIdentDescrTag = 0x07, /* ContentIdentDescrTag */ + MP4SYS_DESCRIPTOR_TAG_SupplContentIdentDescrTag = 0x08, /* SupplContentIdentDescrTag */ + MP4SYS_DESCRIPTOR_TAG_IPI_DescrPointerTag = 0x09, /* IPI_DescrPointerTag */ + MP4SYS_DESCRIPTOR_TAG_IPMP_DescrPointerTag = 0x0A, /* IPMP_DescrPointerTag */ + MP4SYS_DESCRIPTOR_TAG_IPMP_DescrTag = 0x0B, /* IPMP_DescrTag */ + MP4SYS_DESCRIPTOR_TAG_QoS_DescrTag = 0x0C, /* QoS_DescrTag */ + MP4SYS_DESCRIPTOR_TAG_RegistrationDescrTag = 0x0D, /* RegistrationDescrTag */ + MP4SYS_DESCRIPTOR_TAG_ES_ID_IncTag = 0x0E, /* ES_ID_IncTag */ + MP4SYS_DESCRIPTOR_TAG_ES_ID_RefTag = 0x0F, /* ES_ID_RefTag */ + MP4SYS_DESCRIPTOR_TAG_MP4_IOD_Tag = 0x10, /* MP4_IOD_Tag, InitialObjectDescriptor for MP4 */ + MP4SYS_DESCRIPTOR_TAG_MP4_OD_Tag = 0x11, /* MP4_OD_Tag, ObjectDescriptor for MP4 */ + MP4SYS_DESCRIPTOR_TAG_IPI_DescrPointerRefTag = 0x12, /* IPI_DescrPointerRefTag */ + MP4SYS_DESCRIPTOR_TAG_ExtendedProfileLevelDescrTag = 0x13, /* ExtendedProfileLevelDescrTag */ + MP4SYS_DESCRIPTOR_TAG_profileLevelIndicationIndexDescrTag = 0x14, /* profileLevelIndicationIndexDescrTag */ + MP4SYS_DESCRIPTOR_TAG_ContentClassificationDescrTag = 0x40, /* ContentClassificationDescrTag */ + MP4SYS_DESCRIPTOR_TAG_KeyWordDescrTag = 0x41, /* KeyWordDescrTag */ + MP4SYS_DESCRIPTOR_TAG_RatingDescrTag = 0x42, /* RatingDescrTag */ + MP4SYS_DESCRIPTOR_TAG_LanguageDescrTag = 0x43, /* LanguageDescrTag */ + MP4SYS_DESCRIPTOR_TAG_ShortTextualDescrTag = 0x44, /* ShortTextualDescrTag */ + MP4SYS_DESCRIPTOR_TAG_ExpandedTextualDescrTag = 0x45, /* ExpandedTextualDescrTag */ + MP4SYS_DESCRIPTOR_TAG_ContentCreatorNameDescrTag = 0x46, /* ContentCreatorNameDescrTag */ + MP4SYS_DESCRIPTOR_TAG_ContentCreationDateDescrTag = 0x47, /* ContentCreationDateDescrTag */ + MP4SYS_DESCRIPTOR_TAG_OCICreatorNameDescrTag = 0x48, /* OCICreatorNameDescrTag */ + MP4SYS_DESCRIPTOR_TAG_OCICreationDateDescrTag = 0x49, /* OCICreationDateDescrTag */ + MP4SYS_DESCRIPTOR_TAG_SmpteCameraPositionDescrTag = 0x4A, /* SmpteCameraPositionDescrTag */ + MP4SYS_DESCRIPTOR_TAG_Forbidden1 = 0xFF, /* Forbidden */ +} mp4sys_descriptor_tag; +// MP4SYS_DESCRIPTOR_TAG_ES_DescrRemoveRefTag = 0x07, /* FIXME: (command tag), see 14496-14 Object Descriptors */ + +typedef struct +{ + uint32_t size; // 2^28 at most + mp4sys_descriptor_tag tag; +} mp4sys_descriptor_head_t; + +typedef struct mp4sys_descriptor_tag mp4sys_descriptor_t; + +typedef void (*mp4sys_descriptor_destructor_t)( void * ); +typedef int (*mp4sys_descriptor_writer_t)( lsmash_bs_t *, void * ); + +#define MP4SYS_DESCRIPTOR_COMMON \ + const lsmash_class_t *class; \ + mp4sys_descriptor_t *parent; \ + mp4sys_descriptor_destructor_t destruct; \ + mp4sys_descriptor_writer_t write; \ + mp4sys_descriptor_head_t header; \ + lsmash_entry_list_t children + +struct mp4sys_descriptor_tag +{ + MP4SYS_DESCRIPTOR_COMMON; +}; + +typedef struct +{ + MP4SYS_DESCRIPTOR_COMMON; +} mp4sys_BaseDescriptor_t; + +/* DecoderSpecificInfo */ +/* contents varies depends on ObjectTypeIndication and StreamType. */ +typedef struct +{ + MP4SYS_DESCRIPTOR_COMMON; + uint8_t *data; +} mp4sys_DecoderSpecificInfo_t; + +/* DecoderConfigDescriptor */ +typedef struct +{ + MP4SYS_DESCRIPTOR_COMMON; + lsmash_mp4sys_object_type_indication objectTypeIndication; + lsmash_mp4sys_stream_type streamType; + uint8_t upStream; /* bit(1), always 0 in this muxer, used for interactive contents. */ + uint8_t reserved; /* const bit(1), always 1. */ + uint32_t bufferSizeDB; /* maybe CPB size in bytes, NOT bits. */ + uint32_t maxBitrate; + uint32_t avgBitrate; /* 0 if VBR */ + mp4sys_DecoderSpecificInfo_t *decSpecificInfo; /* can be NULL. */ + /* 14496-1 seems to say if we are in IOD(InitialObjectDescriptor), we might use this. + * See ExtensionProfileLevelDescr, The Initial Object Descriptor. + * But I don't think this is mandatory despite 14496-1, because 14496-14 says, in OD or IOD, + * we have to use ES_ID_Inc instead of ES_Descriptor, which does not have DecoderConfigDescriptor. */ + // profileLevelIndicationIndexDescriptor profileLevelIndicationIndexDescr [0..255]; +} mp4sys_DecoderConfigDescriptor_t; + +/* SLConfigDescriptor */ +typedef struct +{ + MP4SYS_DESCRIPTOR_COMMON; + uint8_t predefined; /* default the values from a set of predefined parameter sets as detailed below. + * 0x00 : Custom + * 0x01 : null SL packet header + * 0x02 : Reserved for use in MP4 files + * 0x03 - 0xFF : Reserved for ISO use + * MP4 file that does not use URL_Flag shall have constant value 0x02. */ + /* Custom values + * The following fields are placed if predefined == 0x00. */ + unsigned useAccessUnitStartFlag : 1; + unsigned useAccessUnitEndFlag : 1; + unsigned useRandomAccessPointFlag : 1; + unsigned hasRandomAccessUnitsOnlyFlag : 1; + unsigned usePaddingFlag : 1; + unsigned useTimeStampsFlag : 1; + unsigned useIdleFlag : 1; + unsigned durationFlag : 1; + uint32_t timeStampResolution; + uint32_t OCRResolution; + uint8_t timeStampLength; + uint8_t OCRLength; + uint8_t AU_Length; + uint8_t instantBitrateLength; + unsigned degradationPriorityLength : 4; + unsigned AU_seqNumLength : 5; + unsigned packetSeqNumLength : 5; + unsigned reserved : 2; + /* The following fields are placed if durationFlag is true. */ + uint32_t timeScale; + uint16_t accessUnitDuration; + uint16_t compositionUnitDuration; + /* The following fields are placed if useTimeStampsFlag is false. */ + uint64_t startDecodingTimeStamp; + uint64_t startCompositionTimeStamp; +} mp4sys_SLConfigDescriptor_t; + +/* ES_Descriptor */ +typedef struct mp4sys_ES_Descriptor_t +{ + MP4SYS_DESCRIPTOR_COMMON; + uint16_t ES_ID; + unsigned streamDependenceFlag : 1; /* no stream depencies between streams in this muxer, ES_ID of another elementary stream */ + unsigned URL_Flag : 1; /* no external URL referencing stream in MP4 */ + unsigned OCRstreamFlag : 1; /* no Object Clock Reference stream in this muxer (shall be false in MP4, useful if we're importing from MPEG-2?) */ + unsigned streamPriority : 5; /* no priority among streams in this muxer, higher is important */ + uint16_t dependsOn_ES_ID; + uint8_t URLlength; + char URLstring[256]; + uint16_t OCR_ES_Id; + mp4sys_DecoderConfigDescriptor_t *decConfigDescr; /* cannot be NULL. */ + mp4sys_SLConfigDescriptor_t *slConfigDescr; + /* descriptors below are not mandatory, I think Language Descriptor may somewhat useful */ + /* + IPI_DescrPointer ipiPtr[0 .. 1]; // used to indicate using other ES's IP_IdentificationDataSet + IP_IdentificationDataSet ipIDS[0 .. 255]; // abstract class, actually ContentIdentificationDescriptor(for commercial contents management), + // or SupplementaryContentIdentificationDescriptor(for embedding titles) + IPMP_DescriptorPointer ipmpDescrPtr[0 .. 255]; // used to intellectual property / protection management + LanguageDescriptor langDescr[0 .. 255]; // used to identify the language of the audio/speech or text object + QoS_Descriptor qosDescr[0 .. 1]; // used to achieve QoS + RegistrationDescriptor regDescr[0 .. 1]; // used to carry elementary streams with data whose format is not recognized by ISO/IEC 14496-1 + ExtensionDescriptor extDescr[0 .. 255]; // abstract class, actually defined no subclass, maybe useless + */ +} mp4sys_ES_Descriptor_t; + +/* 14496-14 Object Descriptors (ES_ID_Inc) */ +typedef struct +{ + MP4SYS_DESCRIPTOR_COMMON; + uint32_t Track_ID; +} mp4sys_ES_ID_Inc_t; + +/* 14496-1 ObjectDescriptor / InitialObjectDescriptor */ +typedef struct +{ + MP4SYS_DESCRIPTOR_COMMON; + unsigned ObjectDescriptorID : 10; + unsigned URL_Flag : 1; + unsigned includeInlineProfileLevelFlag : 1; /* for OD, reserved and set to 1 */ + unsigned reserved : 4; /* 0b1111 */ + uint8_t URLlength; + char URLstring[256]; + /* IOD only */ + mp4sys_ODProfileLevelIndication ODProfileLevelIndication; + mp4sys_sceneProfileLevelIndication sceneProfileLevelIndication; + mp4a_audioProfileLevelIndication audioProfileLevelIndication; + mp4sys_visualProfileLevelIndication visualProfileLevelIndication; + mp4sys_graphicsProfileLevelIndication graphicsProfileLevelIndication; + /* */ + lsmash_entry_list_t esDescr; /* List of ES_ID_Inc, not ES_Descriptor defined in 14496-1. 14496-14 overrides. */ + // OCI_Descriptor ociDescr[0 .. 255]; + // IPMP_DescriptorPointer ipmpDescrPtr[0 .. 255]; + // ExtensionDescriptor extDescr[0 .. 255]; +} mp4sys_ObjectDescriptor_t; + +static void mp4sys_remove_predefined_descriptor( void *opaque_descriptor, size_t offset_of_descriptor ) +{ + assert( opaque_descriptor ); + mp4sys_descriptor_t *descriptor = (mp4sys_descriptor_t *)opaque_descriptor; + if( descriptor->parent ) + { + mp4sys_descriptor_t **p = (mp4sys_descriptor_t **)(((int8_t *)descriptor->parent) + offset_of_descriptor); + if( *p == descriptor ) + *p = NULL; + } +} + +/* We always free descriptors through the children list of the parent descriptor. + * Therefore, don't free descriptors through any list other than the children list. */ +static void mp4sys_remove_descriptor_in_predefined_list( void *opaque_descriptor, size_t offset_of_list ) +{ + assert( opaque_descriptor ); + mp4sys_descriptor_t *descriptor = (mp4sys_descriptor_t *)opaque_descriptor; + if( descriptor->parent ) + { + lsmash_entry_list_t *list = (lsmash_entry_list_t *)(((int8_t *)descriptor->parent) + offset_of_list); + for( lsmash_entry_t *entry = list->head; entry; entry = entry->next ) + if( descriptor == entry->data ) + { + /* We don't free this descriptor here. + * Because of freeing an entry of the list here, don't pass the list to free this descriptor. + * Or double free. */ + entry->data = NULL; + lsmash_list_remove_entry_direct( list, entry ); + break; + } + } +} + +static void mp4sys_remove_all_child_descriptors( lsmash_entry_list_t *children ); + +/* Free a descriptor and its children. */ +static void mp4sys_destruct_descriptor( mp4sys_descriptor_t *descriptor ) +{ + if( !descriptor ) + return; + if( descriptor->destruct ) + descriptor->destruct( descriptor ); + mp4sys_remove_all_child_descriptors( &descriptor->children ); + lsmash_free( descriptor ); +} + +static void mp4sys_remove_all_child_descriptors( lsmash_entry_list_t *children ) +{ + lsmash_list_remove_entries( children ); +} + +/* Remove a descriptor by the pointer containing its address. + * In addition, remove from the children list of the parent descriptor if possible. + * Don't call this function within a function freeing one or more entries of any children list because of double free. + * Basically, don't use this function as a callback function. */ +void mp4sys_remove_descriptor( void *opaque_descriptor ) +{ + if( !opaque_descriptor ) + return; + mp4sys_descriptor_t *descriptor = (mp4sys_descriptor_t *)opaque_descriptor; + if( descriptor->parent ) + { + mp4sys_descriptor_t *parent = descriptor->parent; + for( lsmash_entry_t *entry = parent->children.head; entry; entry = entry->next ) + if( descriptor == entry->data ) + { + /* Free the corresponding entry here, therefore don't call this function as a callback function + * if a function frees the same entry later and calls this function. */ + lsmash_list_remove_entry_direct( &parent->children, entry ); + return; + } + } + mp4sys_destruct_descriptor( descriptor ); +} + +static void mp4sys_remove_DecoderSpecificInfo( mp4sys_DecoderSpecificInfo_t *dsi ) +{ + if( !dsi ) + return; + lsmash_free( dsi->data ); + mp4sys_remove_predefined_descriptor( dsi, offsetof( mp4sys_DecoderConfigDescriptor_t, decSpecificInfo ) ); +} + +static void mp4sys_remove_DecoderConfigDescriptor( mp4sys_DecoderConfigDescriptor_t *dcd ) +{ + if( !dcd ) + return; + mp4sys_remove_predefined_descriptor( dcd, offsetof( mp4sys_ES_Descriptor_t, decConfigDescr ) ); +} + +static void mp4sys_remove_SLConfigDescriptor( mp4sys_SLConfigDescriptor_t *slcd ) +{ + if( !slcd ) + return; + mp4sys_remove_predefined_descriptor( slcd, offsetof( mp4sys_ES_Descriptor_t, slConfigDescr ) ); +} + +static void mp4sys_remove_ES_Descriptor( mp4sys_ES_Descriptor_t *esd ) +{ + if( !esd || (esd->parent && (esd->parent->header.tag == MP4SYS_DESCRIPTOR_TAG_ObjectDescrTag + || esd->parent->header.tag == MP4SYS_DESCRIPTOR_TAG_InitialObjectDescrTag)) ) + return; + mp4sys_remove_descriptor_in_predefined_list( esd, offsetof( mp4sys_ObjectDescriptor_t, esDescr ) ); +} + +static void mp4sys_remove_ES_ID_Inc( mp4sys_ES_ID_Inc_t *es_id_inc ) +{ + if( !es_id_inc ) + return; + mp4sys_remove_descriptor_in_predefined_list( es_id_inc, offsetof( mp4sys_ObjectDescriptor_t, esDescr ) ); +} + +static void mp4sys_remove_ObjectDescriptor( mp4sys_ObjectDescriptor_t *od ) +{ +} + +static inline uint32_t mp4sys_get_descriptor_header_size( uint32_t payload_size_in_byte ) +{ +#if ALWAYS_28BITS_LENGTH_CODING + return 4 + 1; /* +4 means 28bits length coding, +1 means tag's space */ +#else + /* descriptor length will be split into 7bits + * see 14496-1 Expandable classes and Length encoding of descriptors and commands */ + uint32_t i; + for( i = 1; payload_size_in_byte >> ( 7 * i ); i++ ); + return i + 1; /* +1 means tag's space */ +#endif +} + +/* returns total size of descriptor, including header, 2 at least */ +static inline uint32_t mp4sys_get_descriptor_size( uint32_t payload_size_in_byte ) +{ + return payload_size_in_byte + mp4sys_get_descriptor_header_size( payload_size_in_byte ); +} + +static inline void mp4sys_write_descriptor_header( lsmash_bs_t *bs, mp4sys_descriptor_head_t *header ) +{ + lsmash_bs_put_byte( bs, header->tag ); + /* Descriptor length will be splitted into 7bits. + * See 14496-1 Expandable classes and Length encoding of descriptors and commands */ +#if ALWAYS_28BITS_LENGTH_CODING + lsmash_bs_put_byte( bs, ( header->size >> 21 ) | 0x80 ); + lsmash_bs_put_byte( bs, ( header->size >> 14 ) | 0x80 ); + lsmash_bs_put_byte( bs, ( header->size >> 7 ) | 0x80 ); +#else + for( uint32_t i = mp4sys_get_descriptor_size( header->size ) - header->size - 2; i; i-- ){ + lsmash_bs_put_byte( bs, ( header->size >> ( 7 * i ) ) | 0x80 ); + } +#endif + lsmash_bs_put_byte( bs, header->size & 0x7F ); +} + +int mp4sys_write_descriptor( lsmash_bs_t *bs, void *opaque_descriptor ); + +static int mp4sys_write_DecoderSpecificInfo( lsmash_bs_t *bs, mp4sys_DecoderSpecificInfo_t *dsi ) +{ + if( dsi->data && dsi->header.size != 0 ) + lsmash_bs_put_bytes( bs, dsi->header.size, dsi->data ); + return 0; +} + +static int mp4sys_write_DecoderConfigDescriptor( lsmash_bs_t *bs, mp4sys_DecoderConfigDescriptor_t *dcd ) +{ + lsmash_bs_put_byte( bs, dcd->objectTypeIndication ); + uint8_t temp; + temp = (dcd->streamType & 0x3F) << 2; + temp |= (dcd->upStream & 0x01) << 1; + temp |= dcd->reserved & 0x01; + lsmash_bs_put_byte( bs, temp ); + lsmash_bs_put_be24( bs, dcd->bufferSizeDB ); + lsmash_bs_put_be32( bs, dcd->maxBitrate ); + lsmash_bs_put_be32( bs, dcd->avgBitrate ); + /* Here, profileLevelIndicationIndexDescriptor is omitted. */ + return 0; +} + +static int mp4sys_write_SLConfigDescriptor( lsmash_bs_t *bs, mp4sys_SLConfigDescriptor_t *slcd ) +{ + lsmash_bs_put_byte( bs, slcd->predefined ); + if( slcd->predefined == 0x00 ) + { + uint8_t temp8; + temp8 = slcd->useAccessUnitStartFlag << 7; + temp8 |= slcd->useAccessUnitEndFlag << 6; + temp8 |= slcd->useRandomAccessPointFlag << 5; + temp8 |= slcd->hasRandomAccessUnitsOnlyFlag << 4; + temp8 |= slcd->usePaddingFlag << 3; + temp8 |= slcd->useTimeStampsFlag << 2; + temp8 |= slcd->useIdleFlag << 1; + temp8 |= slcd->durationFlag; + lsmash_bs_put_byte( bs, temp8 ); + lsmash_bs_put_be32( bs, slcd->timeStampResolution ); + lsmash_bs_put_be32( bs, slcd->OCRResolution ); + lsmash_bs_put_byte( bs, slcd->timeStampLength ); + lsmash_bs_put_byte( bs, slcd->OCRLength ); + lsmash_bs_put_byte( bs, slcd->AU_Length ); + lsmash_bs_put_byte( bs, slcd->instantBitrateLength ); + uint16_t temp16; + temp16 = slcd->degradationPriorityLength << 12; + temp16 |= slcd->AU_seqNumLength << 7; + temp16 |= slcd->packetSeqNumLength << 2; + temp16 |= slcd->reserved; + lsmash_bs_put_be16( bs, temp16 ); + } + if( slcd->durationFlag ) + { + lsmash_bs_put_be32( bs, slcd->timeScale ); + lsmash_bs_put_be16( bs, slcd->accessUnitDuration ); + lsmash_bs_put_be16( bs, slcd->compositionUnitDuration ); + } + if( !slcd->useTimeStampsFlag ) + { + lsmash_bits_t *bits = lsmash_bits_create( bs ); + if( !bits ) + return LSMASH_ERR_MEMORY_ALLOC; + lsmash_bits_put( bits, slcd->timeStampLength, slcd->startDecodingTimeStamp ); + lsmash_bits_put( bits, slcd->timeStampLength, slcd->startCompositionTimeStamp ); + lsmash_bits_put_align( bits ); + lsmash_bits_cleanup( bits ); + } + return 0; +} + +static int mp4sys_write_ES_Descriptor( lsmash_bs_t *bs, mp4sys_ES_Descriptor_t *esd ) +{ + lsmash_bs_put_be16( bs, esd->ES_ID ); + uint8_t temp; + temp = esd->streamDependenceFlag << 7; + temp |= esd->URL_Flag << 6; + temp |= esd->OCRstreamFlag << 5; + temp |= esd->streamPriority; + lsmash_bs_put_byte( bs, temp ); + if( esd->streamDependenceFlag ) + lsmash_bs_put_be16( bs, esd->dependsOn_ES_ID ); + if( esd->URL_Flag ) + { + lsmash_bs_put_byte( bs, esd->URLlength ); + lsmash_bs_put_bytes( bs, esd->URLlength, esd->URLstring ); + } + if( esd->OCRstreamFlag ) + lsmash_bs_put_be16( bs, esd->OCR_ES_Id ); + /* Here, some syntax elements are omitted due to previous flags (all 0). */ + return 0; +} + +static int mp4sys_write_ES_ID_Inc( lsmash_bs_t *bs, mp4sys_ES_ID_Inc_t *es_id_inc ) +{ + lsmash_bs_put_be32( bs, es_id_inc->Track_ID ); + return 0; +} + +static int mp4sys_write_ObjectDescriptor( lsmash_bs_t *bs, mp4sys_ObjectDescriptor_t *od ) +{ + uint16_t temp = (od->ObjectDescriptorID << 6); + // temp |= (0x0 << 5); /* URL_Flag */ + temp |= (od->includeInlineProfileLevelFlag << 4); /* if MP4_OD, includeInlineProfileLevelFlag is 0x1. */ + temp |= 0xF; /* reserved */ + lsmash_bs_put_be16( bs, temp ); + /* here, since we don't support URL_Flag, we put ProfileLevelIndications */ + if( od->header.tag == MP4SYS_DESCRIPTOR_TAG_MP4_IOD_Tag ) + { + lsmash_bs_put_byte( bs, od->ODProfileLevelIndication ); + lsmash_bs_put_byte( bs, od->sceneProfileLevelIndication ); + lsmash_bs_put_byte( bs, od->audioProfileLevelIndication ); + lsmash_bs_put_byte( bs, od->visualProfileLevelIndication ); + lsmash_bs_put_byte( bs, od->graphicsProfileLevelIndication ); + } + return 0; +} + +static int mp4sys_write_children( lsmash_bs_t *bs, mp4sys_descriptor_t *descriptor ) +{ + for( lsmash_entry_t *entry = descriptor->children.head; entry; entry = entry->next ) + { + mp4sys_descriptor_t *child = (mp4sys_descriptor_t *)entry->data; + if( !child ) + continue; + int ret = mp4sys_write_descriptor( bs, child ); + if( ret < 0 ) + return ret; + } + return 0; +} + +int mp4sys_write_descriptor( lsmash_bs_t *bs, void *opaque_descriptor ) +{ + if( !bs || !opaque_descriptor ) + return LSMASH_ERR_NAMELESS; + mp4sys_descriptor_t *descriptor = (mp4sys_descriptor_t *)opaque_descriptor; + mp4sys_write_descriptor_header( bs, &descriptor->header ); + if( !descriptor->write ) + return 0; + int err = descriptor->write( bs, descriptor ); + if( err < 0 ) + return err; + return mp4sys_write_children( bs, descriptor ); +} + +/* descriptor size updater */ +uint32_t mp4sys_update_descriptor_size( void *opaque_descriptor ) +{ + assert( opaque_descriptor ); + mp4sys_descriptor_t *descriptor = (mp4sys_descriptor_t *)opaque_descriptor; + uint64_t size = 0; + if( descriptor->write ) + { + uint32_t header_size = descriptor->header.size; + /* Calculate the size of this descriptor excluding its children with a fake bytestream writer. */ + { + lsmash_bs_t fake_bs = { NULL }; + mp4sys_write_descriptor_header( &fake_bs, &descriptor->header ); + if( descriptor->write( &fake_bs, descriptor ) == 0 ) + size = lsmash_bs_get_valid_data_size( &fake_bs ); + } + /* Calculate the size including the children if no error. */ + if( size >= mp4sys_get_descriptor_header_size( header_size ) ) + { + for( lsmash_entry_t *entry = descriptor->children.head; entry; entry = entry->next ) + if( entry->data ) + size += mp4sys_update_descriptor_size( entry->data ); + /* Calculate the size of this descriptor excluding its header. */ + size -= mp4sys_get_descriptor_header_size( header_size ); + descriptor->header.size = size; + /* Now, we get the actual size of this descriptor. */ + size += mp4sys_get_descriptor_header_size( size ); + } + else + { + /* Invalid descriptor */ + descriptor->header.size = 0; + size = 0; + } + } + else + descriptor->header.size = 0; + return size; +} + +static inline void *mp4sys_construct_descriptor_orig +( + size_t size, + mp4sys_descriptor_t *parent, + mp4sys_descriptor_destructor_t destructor, + mp4sys_descriptor_writer_t writer +) +{ + assert( size >= sizeof(mp4sys_BaseDescriptor_t) ); + mp4sys_descriptor_t *descriptor = lsmash_malloc_zero( size ); + if( !descriptor ) + return NULL; + descriptor->class = &lsmash_mp4sys_class; + descriptor->parent = parent; + descriptor->destruct = destructor; + descriptor->write = writer; + lsmash_list_init( &descriptor->children, mp4sys_destruct_descriptor ); + return descriptor; +} + +#define mp4sys_construct_descriptor( size, parent, destructor, writer ) \ + mp4sys_construct_descriptor_orig \ + ( \ + size, \ + (mp4sys_descriptor_t *)parent, \ + (mp4sys_descriptor_destructor_t)destructor, \ + (mp4sys_descriptor_writer_t)writer \ + ) + +#define MP4SYS_CONSTRUCT_DESCRIPTOR( var, descriptor_name, parent, ret ) \ + mp4sys_##descriptor_name##_t *var = \ + mp4sys_construct_descriptor \ + ( \ + sizeof(mp4sys_##descriptor_name##_t), \ + parent, \ + mp4sys_remove_##descriptor_name, \ + mp4sys_write_##descriptor_name \ + ); \ + if( !var ) \ + return ret + +static mp4sys_DecoderSpecificInfo_t *mp4sys_add_DecoderSpecificInfo( mp4sys_DecoderConfigDescriptor_t *dcd ) +{ + if( !dcd ) + return NULL; + MP4SYS_CONSTRUCT_DESCRIPTOR( dsi, DecoderSpecificInfo, dcd, NULL ); + dsi->header.tag = MP4SYS_DESCRIPTOR_TAG_DecSpecificInfoTag; + if( lsmash_list_add_entry( &dcd->children, dsi ) < 0 ) + { + mp4sys_remove_descriptor( dsi ); + return NULL; + } + dcd->decSpecificInfo = dsi; + return dsi; +} + +/* + bufferSizeDB is byte unit, NOT bit unit. + avgBitrate is 0 if VBR +*/ +static mp4sys_DecoderConfigDescriptor_t *mp4sys_add_DecoderConfigDescriptor +( + mp4sys_ES_Descriptor_t *esd +) +{ + if( !esd ) + return NULL; + MP4SYS_CONSTRUCT_DESCRIPTOR( dcd, DecoderConfigDescriptor, esd, NULL ); + dcd->header.tag = MP4SYS_DESCRIPTOR_TAG_DecoderConfigDescrTag; + if( lsmash_list_add_entry( &esd->children, dcd ) < 0 ) + { + mp4sys_remove_descriptor( dcd ); + return NULL; + } + esd->decConfigDescr = dcd; + return dcd; +} + +static mp4sys_SLConfigDescriptor_t *mp4sys_add_SLConfigDescriptor( mp4sys_ES_Descriptor_t *esd ) +{ + if( !esd ) + return NULL; + MP4SYS_CONSTRUCT_DESCRIPTOR( slcd, SLConfigDescriptor, esd, NULL ); + slcd->header.tag = MP4SYS_DESCRIPTOR_TAG_SLConfigDescrTag; + if( lsmash_list_add_entry( &esd->children, slcd ) < 0 ) + { + mp4sys_remove_descriptor( slcd ); + return NULL; + } + esd->slConfigDescr = slcd; + return slcd; +} + +/* NOTE: This is only for MP4_IOD and MP4_OD, not for ISO Base Media's ObjectDescriptor and InitialObjectDescriptor */ +static mp4sys_ES_ID_Inc_t *mp4sys_add_ES_ID_Inc( mp4sys_ObjectDescriptor_t *od ) +{ + if( !od + || (od->header.tag != MP4SYS_DESCRIPTOR_TAG_MP4_IOD_Tag + && od->header.tag != MP4SYS_DESCRIPTOR_TAG_MP4_OD_Tag) ) + return NULL; + MP4SYS_CONSTRUCT_DESCRIPTOR( es_id_inc, ES_ID_Inc, od, NULL ); + es_id_inc->header.tag = MP4SYS_DESCRIPTOR_TAG_ES_ID_IncTag; + if( lsmash_list_add_entry( &od->children, es_id_inc ) < 0 ) + { + mp4sys_remove_descriptor( es_id_inc ); + return NULL; + } + if( lsmash_list_add_entry( &od->esDescr, es_id_inc ) < 0 ) + { + lsmash_list_remove_entry_tail( &od->children ); + return NULL; + } + return es_id_inc; +} + +int mp4sys_create_ES_ID_Inc( mp4sys_ObjectDescriptor_t *od, uint32_t Track_ID ) +{ + mp4sys_ES_ID_Inc_t *es_id_inc = mp4sys_add_ES_ID_Inc( od ); + if( !es_id_inc ) + return LSMASH_ERR_NAMELESS; + es_id_inc->Track_ID = Track_ID; + return 0; +} + +/* ES_ID of the ES Descriptor is stored as 0 when the ES Descriptor is built into sample descriptions in MP4 file format + * since the lower 16 bits of the track_ID is used, instead of ES_ID, for the identifier of the elemental stream within the track. */ +mp4sys_ES_Descriptor_t *mp4sys_create_ES_Descriptor( uint16_t ES_ID ) +{ + MP4SYS_CONSTRUCT_DESCRIPTOR( esd, ES_Descriptor, NULL, NULL ); + esd->header.tag = MP4SYS_DESCRIPTOR_TAG_ES_DescrTag; + esd->ES_ID = ES_ID; + return esd; +} + +/* NOTE: This is only for MP4_OD, not for ISO Base Media's ObjectDescriptor */ +mp4sys_ObjectDescriptor_t *mp4sys_create_ObjectDescriptor( uint16_t ObjectDescriptorID ) +{ + MP4SYS_CONSTRUCT_DESCRIPTOR( od, ObjectDescriptor, NULL, NULL ); + od->header.tag = MP4SYS_DESCRIPTOR_TAG_MP4_OD_Tag; + od->ObjectDescriptorID = ObjectDescriptorID; + od->includeInlineProfileLevelFlag = 1; /* 1 as part of reserved flag. */ + od->ODProfileLevelIndication = MP4SYS_OD_PLI_NONE_REQUIRED; + od->sceneProfileLevelIndication = MP4SYS_SCENE_PLI_NONE_REQUIRED; + od->audioProfileLevelIndication = MP4A_AUDIO_PLI_NONE_REQUIRED; + od->visualProfileLevelIndication = MP4SYS_VISUAL_PLI_NONE_REQUIRED; + od->graphicsProfileLevelIndication = MP4SYS_GRAPHICS_PLI_NONE_REQUIRED; + return od; +} + +/* NOTE: This is only for MP4_IOD, not for Iso Base Media's InitialObjectDescriptor */ +int mp4sys_to_InitialObjectDescriptor +( + mp4sys_ObjectDescriptor_t *od, + uint8_t include_inline_pli, + mp4sys_ODProfileLevelIndication od_pli, + mp4sys_sceneProfileLevelIndication scene_pli, + mp4a_audioProfileLevelIndication audio_pli, + mp4sys_visualProfileLevelIndication visual_pli, + mp4sys_graphicsProfileLevelIndication graph_pli +) +{ + if( !od ) + return LSMASH_ERR_NAMELESS; + od->header.tag = MP4SYS_DESCRIPTOR_TAG_MP4_IOD_Tag; + od->includeInlineProfileLevelFlag = include_inline_pli; + od->ODProfileLevelIndication = od_pli; + od->sceneProfileLevelIndication = scene_pli; + od->audioProfileLevelIndication = audio_pli; + od->visualProfileLevelIndication = visual_pli; + od->graphicsProfileLevelIndication = graph_pli; + return 0; +} + +/* + bufferSizeDB is byte unit, NOT bit unit. + avgBitrate is 0 if VBR +*/ +int mp4sys_update_DecoderConfigDescriptor( mp4sys_ES_Descriptor_t *esd, uint32_t bufferSizeDB, uint32_t maxBitrate, uint32_t avgBitrate ) +{ + if( !esd || !esd->decConfigDescr ) + return LSMASH_ERR_NAMELESS; + mp4sys_DecoderConfigDescriptor_t *dcd = esd->decConfigDescr; + dcd->bufferSizeDB = bufferSizeDB; + dcd->maxBitrate = maxBitrate; + dcd->avgBitrate = avgBitrate; + return 0; +} + +void mp4sys_print_descriptor( FILE *fp, mp4sys_descriptor_t *descriptor, int indent ); + +static void mp4sys_print_descriptor_header( FILE *fp, mp4sys_descriptor_head_t *header, int indent ) +{ + static const char *descriptor_names_table[256] = + { + "Forbidden", + "ObjectDescriptor", + "InitialObjectDescriptor", + "ES_Descriptor", + "DecoderConfigDescriptor", + "DecoderSpecificInfo", + "SLConfigDescriptor", + [0x0E] = "ES_ID_Inc", + [0x0F] = "ES_ID_Ref", + [0x10] = "MP4_IOD", + [0x11] = "MP4_OD" + }; + if( descriptor_names_table[ header->tag ] ) + lsmash_ifprintf( fp, indent, "[tag = 0x%02"PRIx8": %s]\n", header->tag, descriptor_names_table[ header->tag ] ); + else + lsmash_ifprintf( fp, indent, "[tag = 0x%02"PRIx8"]\n", header->tag ); + lsmash_ifprintf( fp, ++indent, "expandableClassSize = %"PRIu32"\n", header->size ); +} + +static void mp4sys_print_DecoderSpecificInfo( FILE *fp, mp4sys_descriptor_t *descriptor, int indent ) +{ + extern void mp4a_print_AudioSpecificConfig( FILE *, uint8_t *, uint32_t, int ); + if( !descriptor->parent || descriptor->parent->header.tag != MP4SYS_DESCRIPTOR_TAG_DecoderConfigDescrTag ) + return; + mp4sys_DecoderConfigDescriptor_t *dcd = (mp4sys_DecoderConfigDescriptor_t *)descriptor->parent; + if( dcd->streamType != MP4SYS_STREAM_TYPE_AudioStream + || dcd->objectTypeIndication != MP4SYS_OBJECT_TYPE_Audio_ISO_14496_3 ) + return; /* We support only AudioSpecificConfig here currently. */ + mp4sys_DecoderSpecificInfo_t *dsi = (mp4sys_DecoderSpecificInfo_t *)descriptor; + mp4a_print_AudioSpecificConfig( fp, dsi->data, dsi->header.size, indent ); +} + +static void mp4sys_print_DecoderConfigDescriptor( FILE *fp, mp4sys_descriptor_t *descriptor, int indent ) +{ + mp4sys_DecoderConfigDescriptor_t *dcd = (mp4sys_DecoderConfigDescriptor_t *)descriptor; + static const char *object_type_indication_descriptions_table[256] = + { + "Forbidden", + "Systems ISO/IEC 14496-1 (a)", + "Systems ISO/IEC 14496-1 (b)", + "Interaction Stream", + "Systems ISO/IEC 14496-1 Extended BIFS Configuration", + "Systems ISO/IEC 14496-1 AFX", + "Font Data Stream", + "Synthesized Texture Stream", + "Streaming Text Stream", + "LASeR Stream", + "Simple Aggregation Format (SAF) Stream", + [0x20] = "Visual ISO/IEC 14496-2", + [0x21] = "Visual ITU-T Recommendation H.264 | ISO/IEC 14496-10", + [0x22] = "Parameter Sets for ITU-T Recommendation H.264 | ISO/IEC 14496-10", + [0x40] = "Audio ISO/IEC 14496-3", + [0x60] = "Visual ISO/IEC 13818-2 Simple Profile", + [0x61] = "Visual ISO/IEC 13818-2 Main Profile", + [0x62] = "Visual ISO/IEC 13818-2 SNR Profile", + [0x63] = "Visual ISO/IEC 13818-2 Spatial Profile", + [0x64] = "Visual ISO/IEC 13818-2 High Profile", + [0x65] = "Visual ISO/IEC 13818-2 422 Profile", + [0x66] = "Audio ISO/IEC 13818-7 Main Profile", + [0x67] = "Audio ISO/IEC 13818-7 LowComplexity Profile", + [0x68] = "Audio ISO/IEC 13818-7 Scaleable Sampling Rate Profile", + [0x69] = "Audio ISO/IEC 13818-3", + [0x6A] = "Visual ISO/IEC 11172-2", + [0x6B] = "Audio ISO/IEC 11172-3", + [0x6C] = "Visual ISO/IEC 10918-1", + [0x6D] = "Portable Network Graphics", + [0x6E] = "Visual ISO/IEC 15444-1 (JPEG 2000)", + [0xA0] = "EVRC Voice", + [0xA1] = "SMV Voice", + [0xA2] = "3GPP2 Compact Multimedia Format (CMF)", + [0xA3] = "SMPTE VC-1 Video", + [0xA4] = "Dirac Video Coder", + [0xA5] = "AC-3 Audio", + [0xA6] = "Enhanced AC-3 audio", + [0xA7] = "DRA Audio", + [0xA8] = "ITU G.719 Audio", + [0xA9] = "DTS Coherent Acoustics audio", + [0xAA] = "DTS-HD High Resolution Audio", + [0xAB] = "DTS-HD Master Audio", + [0xAC] = "DTS Express low bit rate audio", + [0xE1] = "13K Voice", + [0xFF] = "no object type specified" + }; + static const char *stream_type_descriptions_table[64] = + { + "Forbidden", + "ObjectDescriptorStream", + "ClockReferenceStream", + "SceneDescriptionStream", + "VisualStream", + "AudioStream", + "MPEG7Stream", + "IPMPStream", + "ObjectContentInfoStream", + "MPEGJStream", + "Interaction Stream", + "IPMPToolStream", + "FontDataStream", + "StreamingText" + }; + if( object_type_indication_descriptions_table[ dcd->objectTypeIndication ] ) + lsmash_ifprintf( fp, indent, "objectTypeIndication = 0x%02"PRIx8" (%s)\n", dcd->objectTypeIndication, object_type_indication_descriptions_table[ dcd->objectTypeIndication ] ); + else + lsmash_ifprintf( fp, indent, "objectTypeIndication = 0x%02"PRIx8"\n", dcd->objectTypeIndication ); + if( stream_type_descriptions_table[ dcd->streamType ] ) + lsmash_ifprintf( fp, indent, "streamType = 0x%02"PRIx8" (%s)\n", dcd->streamType, stream_type_descriptions_table[ dcd->streamType ] ); + else + lsmash_ifprintf( fp, indent, "streamType = 0x%02"PRIx8"\n", dcd->streamType ); + lsmash_ifprintf( fp, indent, "upStream = %"PRIu8"\n", dcd->upStream ); + lsmash_ifprintf( fp, indent, "reserved = %"PRIu8"\n", dcd->reserved ); + lsmash_ifprintf( fp, indent, "bufferSizeDB = %"PRIu32"\n", dcd->bufferSizeDB ); + lsmash_ifprintf( fp, indent, "maxBitrate = %"PRIu32"\n", dcd->maxBitrate ); + lsmash_ifprintf( fp, indent, "avgBitrate = %"PRIu32"%s\n", dcd->avgBitrate, dcd->avgBitrate ? "" : " (variable bitrate)" ); +} + +static void mp4sys_print_SLConfigDescriptor( FILE *fp, mp4sys_descriptor_t *descriptor, int indent ) +{ + mp4sys_SLConfigDescriptor_t *slcd = (mp4sys_SLConfigDescriptor_t *)descriptor; + lsmash_ifprintf( fp, indent, "predefined = %"PRIu8"\n", slcd->predefined ); + if( slcd->predefined == 0 ) + { + lsmash_ifprintf( fp, indent, "useAccessUnitStartFlag = %"PRIu8"\n", slcd->useAccessUnitStartFlag ); + lsmash_ifprintf( fp, indent, "useAccessUnitEndFlag = %"PRIu8"\n", slcd->useAccessUnitEndFlag ); + lsmash_ifprintf( fp, indent, "useRandomAccessPointFlag = %"PRIu8"\n", slcd->useRandomAccessPointFlag ); + lsmash_ifprintf( fp, indent, "hasRandomAccessUnitsOnlyFlag = %"PRIu8"\n", slcd->hasRandomAccessUnitsOnlyFlag ); + lsmash_ifprintf( fp, indent, "usePaddingFlag = %"PRIu8"\n", slcd->usePaddingFlag ); + lsmash_ifprintf( fp, indent, "useTimeStampsFlag = %"PRIu8"\n", slcd->useTimeStampsFlag ); + lsmash_ifprintf( fp, indent, "useIdleFlag = %"PRIu8"\n", slcd->useIdleFlag ); + lsmash_ifprintf( fp, indent, "durationFlag = %"PRIu8"\n", slcd->durationFlag ); + lsmash_ifprintf( fp, indent, "timeStampResolution = %"PRIu32"\n", slcd->timeStampResolution ); + lsmash_ifprintf( fp, indent, "OCRResolution = %"PRIu32"\n", slcd->OCRResolution ); + lsmash_ifprintf( fp, indent, "timeStampLength = %"PRIu8"\n", slcd->timeStampLength ); + lsmash_ifprintf( fp, indent, "OCRLength = %"PRIu8"\n", slcd->OCRLength ); + lsmash_ifprintf( fp, indent, "AU_Length = %"PRIu8"\n", slcd->AU_Length ); + lsmash_ifprintf( fp, indent, "instantBitrateLength = %"PRIu8"\n", slcd->instantBitrateLength ); + lsmash_ifprintf( fp, indent, "degradationPriorityLength = %"PRIu8"\n", slcd->degradationPriorityLength ); + lsmash_ifprintf( fp, indent, "AU_seqNumLength = %"PRIu8"\n", slcd->AU_seqNumLength ); + lsmash_ifprintf( fp, indent, "packetSeqNumLength = %"PRIu8"\n", slcd->packetSeqNumLength ); + lsmash_ifprintf( fp, indent, "reserved = 0x%01"PRIx8"\n", slcd->reserved ); + } + if( slcd->durationFlag ) + { + lsmash_ifprintf( fp, indent, "timeScale = %"PRIu32"\n", slcd->timeScale ); + lsmash_ifprintf( fp, indent, "accessUnitDuration = %"PRIu16"\n", slcd->accessUnitDuration ); + lsmash_ifprintf( fp, indent, "compositionUnitDuration = %"PRIu16"\n", slcd->compositionUnitDuration ); + } + if( !slcd->useTimeStampsFlag ) + { + lsmash_ifprintf( fp, indent, "startDecodingTimeStamp = %"PRIu64"\n", slcd->startDecodingTimeStamp ); + lsmash_ifprintf( fp, indent, "startCompositionTimeStamp = %"PRIu64"\n", slcd->startCompositionTimeStamp ); + } +} + +static void mp4sys_print_ES_Descriptor( FILE *fp, mp4sys_descriptor_t *descriptor, int indent ) +{ + mp4sys_ES_Descriptor_t *esd = (mp4sys_ES_Descriptor_t *)descriptor; + lsmash_ifprintf( fp, indent, "ES_ID = %"PRIu16"\n", esd->ES_ID ); + lsmash_ifprintf( fp, indent, "streamDependenceFlag = %"PRIu8"\n", esd->streamDependenceFlag ); + lsmash_ifprintf( fp, indent, "URL_Flag = %"PRIu8"\n", esd->URL_Flag ); + lsmash_ifprintf( fp, indent, "OCRstreamFlag = %"PRIu8"\n", esd->OCRstreamFlag ); + lsmash_ifprintf( fp, indent, "streamPriority = %"PRIu8"\n", esd->streamPriority ); + if( esd->streamDependenceFlag ) + lsmash_ifprintf( fp, indent, "dependsOn_ES_ID = %"PRIu16"\n", esd->dependsOn_ES_ID ); + if( esd->URL_Flag ) + { + lsmash_ifprintf( fp, indent, "URLlength = %"PRIu8"\n", esd->URLlength ); + lsmash_ifprintf( fp, indent, "URLstring = %s\n", esd->URLstring ); + } + if( esd->OCRstreamFlag ) + lsmash_ifprintf( fp, indent, "OCR_ES_Id = %"PRIu16"\n", esd->OCR_ES_Id ); +} + +static void mp4sys_print_ES_ID_Inc( FILE *fp, mp4sys_descriptor_t *descriptor, int indent ) +{ + mp4sys_ES_ID_Inc_t *es_id_inc = (mp4sys_ES_ID_Inc_t *)descriptor; + lsmash_ifprintf( fp, indent, "Track_ID = %"PRIu32"\n", es_id_inc->Track_ID ); +} + +static void mp4sys_print_ObjectDescriptor( FILE *fp, mp4sys_descriptor_t *descriptor, int indent ) +{ + mp4sys_ObjectDescriptor_t *od = (mp4sys_ObjectDescriptor_t *)descriptor; + lsmash_ifprintf( fp, indent, "ObjectDescriptorID = %"PRIu16"\n", od->ObjectDescriptorID ); + lsmash_ifprintf( fp, indent, "URL_Flag = %"PRIu8"\n", od->URL_Flag ); + if( od->header.tag == MP4SYS_DESCRIPTOR_TAG_InitialObjectDescrTag + || od->header.tag == MP4SYS_DESCRIPTOR_TAG_MP4_IOD_Tag ) + { + lsmash_ifprintf( fp, indent, "includeInlineProfileLevelFlag = %"PRIu8"\n", od->includeInlineProfileLevelFlag ); + lsmash_ifprintf( fp, indent, "reserved = 0x%01"PRIx8"\n", od->reserved ); + } + else + lsmash_ifprintf( fp, indent, "reserved = 0x%02"PRIx8"\n", od->reserved | (od->includeInlineProfileLevelFlag << 4) ); + if( od->URL_Flag ) + { + lsmash_ifprintf( fp, indent, "URLlength = %"PRIu8"\n", od->URLlength ); + lsmash_ifprintf( fp, indent, "URLstring = %s\n", od->URLstring ); + } + else + { + if( od->header.tag == MP4SYS_DESCRIPTOR_TAG_InitialObjectDescrTag + || od->header.tag == MP4SYS_DESCRIPTOR_TAG_MP4_IOD_Tag ) + { + lsmash_ifprintf( fp, indent, "ODProfileLevelIndication = 0x%02"PRIx8"\n", od->ODProfileLevelIndication ); + lsmash_ifprintf( fp, indent, "sceneProfileLevelIndication = 0x%02"PRIx8"\n", od->sceneProfileLevelIndication ); + lsmash_ifprintf( fp, indent, "audioProfileLevelIndication = 0x%02"PRIx8"\n", od->audioProfileLevelIndication ); + lsmash_ifprintf( fp, indent, "visualProfileLevelIndication = 0x%02"PRIx8"\n", od->visualProfileLevelIndication ); + lsmash_ifprintf( fp, indent, "graphicsProfileLevelIndication = 0x%02"PRIx8"\n", od->graphicsProfileLevelIndication ); + } + } +} + +void mp4sys_print_descriptor( FILE *fp, mp4sys_descriptor_t *descriptor, int indent ) +{ + if( !descriptor ) + return; + mp4sys_print_descriptor_header( fp, &descriptor->header, indent++ ); + switch( descriptor->header.tag ) + { + case MP4SYS_DESCRIPTOR_TAG_ObjectDescrTag : + case MP4SYS_DESCRIPTOR_TAG_InitialObjectDescrTag : + case MP4SYS_DESCRIPTOR_TAG_MP4_OD_Tag : + case MP4SYS_DESCRIPTOR_TAG_MP4_IOD_Tag : + mp4sys_print_ObjectDescriptor( fp, descriptor, indent ); + break; + case MP4SYS_DESCRIPTOR_TAG_ES_DescrTag : + mp4sys_print_ES_Descriptor( fp, descriptor, indent ); + break; + case MP4SYS_DESCRIPTOR_TAG_DecoderConfigDescrTag : + mp4sys_print_DecoderConfigDescriptor( fp, descriptor, indent ); + break; + case MP4SYS_DESCRIPTOR_TAG_DecSpecificInfoTag : + mp4sys_print_DecoderSpecificInfo( fp, descriptor, indent ); + break; + case MP4SYS_DESCRIPTOR_TAG_SLConfigDescrTag : + mp4sys_print_SLConfigDescriptor( fp, descriptor, indent ); + break; + case MP4SYS_DESCRIPTOR_TAG_ES_ID_IncTag : + mp4sys_print_ES_ID_Inc( fp, descriptor, indent ); + break; + default : + break; + } + for( lsmash_entry_t *entry = descriptor->children.head; entry; entry = entry->next ) + if( entry->data ) + mp4sys_print_descriptor( fp, entry->data, indent ); +} + +int mp4sys_print_codec_specific( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + assert( !(box->manager & LSMASH_BINARY_CODED_BOX) ); + isom_esds_t *esds = (isom_esds_t *)box; + int indent = level; + lsmash_ifprintf( fp, indent++, "[%s: Elemental Stream Descriptor Box]\n", isom_4cc2str( esds->type.fourcc ) ); + lsmash_ifprintf( fp, indent, "position = %"PRIu64"\n", esds->pos ); + lsmash_ifprintf( fp, indent, "size = %"PRIu64"\n", esds->size ); + lsmash_ifprintf( fp, indent, "version = %"PRIu8"\n", esds->version ); + lsmash_ifprintf( fp, indent, "flags = 0x%06"PRIx32"\n", esds->flags & 0x00ffffff ); + mp4sys_print_descriptor( fp, (mp4sys_descriptor_t *)esds->ES, indent ); + return 0; +} + +mp4sys_descriptor_t *mp4sys_get_descriptor( lsmash_bs_t *bs, void *parent ); + +static void mp4sys_get_descriptor_header( lsmash_bs_t *bs, mp4sys_descriptor_head_t *header ) +{ + header->tag = lsmash_bs_get_byte( bs ); + uint8_t temp = lsmash_bs_get_byte( bs ); + int nextByte = temp & 0x80; + uint32_t sizeOfInstance = temp & 0x7F; + while( nextByte ) + { + temp = lsmash_bs_get_byte( bs ); + nextByte = temp & 0x80; + sizeOfInstance = (sizeOfInstance << 7) | (temp & 0x7F); + } + header->size = sizeOfInstance; +} + +static mp4sys_DecoderSpecificInfo_t *mp4sys_get_DecoderSpecificInfo( lsmash_bs_t *bs, mp4sys_descriptor_head_t *header, void *parent ) +{ + mp4sys_DecoderSpecificInfo_t *dsi = mp4sys_add_DecoderSpecificInfo( parent ); + if( !dsi ) + return NULL; + dsi->header.size = header->size; + if( dsi->header.size ) + { + dsi->data = lsmash_bs_get_bytes( bs, dsi->header.size ); + if( !dsi->data ) + { + mp4sys_remove_descriptor( dsi ); + return NULL; + } + } + return dsi; +} + +static mp4sys_DecoderConfigDescriptor_t *mp4sys_get_DecoderConfigDescriptor( lsmash_bs_t *bs, mp4sys_descriptor_head_t *header, void *parent ) +{ + mp4sys_DecoderConfigDescriptor_t *dcd = mp4sys_add_DecoderConfigDescriptor( parent ); + if( !dcd ) + return NULL; + uint64_t end_pos = header->size + lsmash_bs_count( bs ); + dcd->header.size = header->size; + dcd->objectTypeIndication = lsmash_bs_get_byte( bs ); + uint8_t temp = lsmash_bs_get_byte( bs ); + dcd->streamType = (temp >> 2) & 0x3F; + dcd->upStream = (temp >> 1) & 0x01; + dcd->reserved = temp & 0x01; + dcd->bufferSizeDB = lsmash_bs_get_be24( bs ); + dcd->maxBitrate = lsmash_bs_get_be32( bs ); + dcd->avgBitrate = lsmash_bs_get_be32( bs ); + while( lsmash_bs_count( bs ) < end_pos ) + { + mp4sys_descriptor_t *desc = mp4sys_get_descriptor( bs, dcd ); + if( desc ) + { + if( desc->header.tag == MP4SYS_DESCRIPTOR_TAG_DecSpecificInfoTag ) + dcd->decSpecificInfo = (mp4sys_DecoderSpecificInfo_t *)desc; + else + mp4sys_remove_descriptor( desc ); + } + else + break; + } + return dcd; +} + +static mp4sys_SLConfigDescriptor_t *mp4sys_get_SLConfigDescriptor( lsmash_bs_t *bs, mp4sys_descriptor_head_t *header, void *parent ) +{ + mp4sys_SLConfigDescriptor_t *slcd = mp4sys_add_SLConfigDescriptor( parent ); + if( !slcd ) + return NULL; + slcd->header.size = header->size; + slcd->predefined = lsmash_bs_get_byte( bs ); + if( slcd->predefined == 0x00 ) + { + uint8_t temp8 = lsmash_bs_get_byte( bs ); + slcd->useAccessUnitStartFlag = (temp8 >> 7) & 0x01; + slcd->useAccessUnitEndFlag = (temp8 >> 6) & 0x01; + slcd->useRandomAccessPointFlag = (temp8 >> 5) & 0x01; + slcd->hasRandomAccessUnitsOnlyFlag = (temp8 >> 4) & 0x01; + slcd->usePaddingFlag = (temp8 >> 3) & 0x01; + slcd->useTimeStampsFlag = (temp8 >> 2) & 0x01; + slcd->useIdleFlag = (temp8 >> 1) & 0x01; + slcd->durationFlag = temp8 & 0x01; + slcd->timeStampResolution = lsmash_bs_get_be32( bs ); + slcd->OCRResolution = lsmash_bs_get_be32( bs ); + slcd->timeStampLength = lsmash_bs_get_byte( bs ); + slcd->OCRLength = lsmash_bs_get_byte( bs ); + slcd->AU_Length = lsmash_bs_get_byte( bs ); + slcd->instantBitrateLength = lsmash_bs_get_byte( bs ); + uint16_t temp16 = lsmash_bs_get_be16( bs ); + slcd->degradationPriorityLength = (temp16 >> 12) & 0x0F; + slcd->AU_seqNumLength = (temp16 >> 7) & 0x1F; + slcd->packetSeqNumLength = (temp16 >> 2) & 0x1F; + slcd->reserved = temp16 & 0x03; + } + else if( slcd->predefined == 0x01 ) + { + slcd->timeStampResolution = 1000; + slcd->timeStampLength = 32; + } + else if( slcd->predefined == 0x02 ) + slcd->useTimeStampsFlag = 1; + if( slcd->durationFlag ) + { + slcd->timeScale = lsmash_bs_get_be32( bs ); + slcd->accessUnitDuration = lsmash_bs_get_be16( bs ); + slcd->compositionUnitDuration = lsmash_bs_get_be16( bs ); + } + if( !slcd->useTimeStampsFlag ) + { + lsmash_bits_t *bits = lsmash_bits_create( bs ); + if( !bits ) + { + mp4sys_remove_descriptor( slcd ); + return NULL; + } + slcd->startDecodingTimeStamp = lsmash_bits_get( bits, slcd->timeStampLength ); + slcd->startCompositionTimeStamp = lsmash_bits_get( bits, slcd->timeStampLength ); + lsmash_bits_cleanup( bits ); + } + return slcd; +} + +static mp4sys_ES_Descriptor_t *mp4sys_get_ES_Descriptor( lsmash_bs_t *bs, mp4sys_descriptor_head_t *header, void *parent ) +{ + MP4SYS_CONSTRUCT_DESCRIPTOR( esd, ES_Descriptor, parent, NULL ); + if( parent && lsmash_list_add_entry( &((mp4sys_descriptor_t *)parent)->children, esd ) < 0 ) + { + mp4sys_remove_descriptor( esd ); + return NULL; + } + uint64_t end_pos = header->size + lsmash_bs_count( bs ); + esd->header = *header; + esd->ES_ID = lsmash_bs_get_be16( bs ); + uint8_t temp = lsmash_bs_get_byte( bs ); + esd->streamDependenceFlag = (temp >> 7) & 0x01; + esd->URL_Flag = (temp >> 6) & 0x01; + esd->OCRstreamFlag = (temp >> 5) & 0x01; + esd->streamPriority = temp & 0x1F; + if( esd->streamDependenceFlag ) + esd->dependsOn_ES_ID = lsmash_bs_get_be16( bs ); + if( esd->URL_Flag ) + { + size_t length = lsmash_bs_get_byte( bs ); + lsmash_bs_read_data( bs, (uint8_t *)esd->URLstring, &length ); + esd->URLlength = length; + } + if( esd->OCRstreamFlag ) + esd->OCR_ES_Id = lsmash_bs_get_be16( bs ); + /* DecoderConfigDescriptor and SLConfigDescriptor are mandatory. */ + while( lsmash_bs_count( bs ) < end_pos ) + { + mp4sys_descriptor_t *desc = mp4sys_get_descriptor( bs, esd ); + if( desc ) + { + if( desc->header.tag == MP4SYS_DESCRIPTOR_TAG_DecoderConfigDescrTag ) + esd->decConfigDescr = (mp4sys_DecoderConfigDescriptor_t *)desc; + else if( desc->header.tag == MP4SYS_DESCRIPTOR_TAG_SLConfigDescrTag ) + esd->slConfigDescr = (mp4sys_SLConfigDescriptor_t *)desc; + else + mp4sys_remove_descriptor( desc ); + } + else + break; + } + if( !esd->decConfigDescr || !esd->slConfigDescr ) + { + mp4sys_remove_descriptor( esd ); + return NULL; + } + return esd; +} + +static mp4sys_ES_ID_Inc_t *mp4sys_get_ES_ID_Inc( lsmash_bs_t *bs, mp4sys_descriptor_head_t *header, void *parent ) +{ + mp4sys_ES_ID_Inc_t *es_id_inc = mp4sys_add_ES_ID_Inc( parent ); + if( !es_id_inc ) + return NULL; + es_id_inc->header.size = header->size; + es_id_inc->Track_ID = lsmash_bs_get_be32( bs ); + return es_id_inc; +} + +static mp4sys_ObjectDescriptor_t *mp4sys_get_ObjectDescriptor( lsmash_bs_t *bs, mp4sys_descriptor_head_t *header, void *parent ) +{ + MP4SYS_CONSTRUCT_DESCRIPTOR( od, ObjectDescriptor, parent, NULL ); + if( parent && lsmash_list_add_entry( &((mp4sys_descriptor_t *)parent)->children, od ) < 0 ) + { + mp4sys_remove_descriptor( od ); + return NULL; + } + od->header = *header; + uint64_t end_pos = header->size + lsmash_bs_count( bs ); + uint16_t temp16 = lsmash_bs_get_be16( bs ); + od->ObjectDescriptorID = (temp16 >> 6) & 0x03FF; + od->URL_Flag = (temp16 >> 5) & 0x0001; + od->includeInlineProfileLevelFlag = (temp16 >> 4) & 0x0001; + od->reserved = temp16 & 0x000F; + if( od->URL_Flag ) + { + size_t length = lsmash_bs_get_byte( bs ); + lsmash_bs_read_data( bs, (uint8_t *)od->URLstring, &length ); + od->URLlength = length; + } + else + { + if( od->header.tag == MP4SYS_DESCRIPTOR_TAG_InitialObjectDescrTag + || od->header.tag == MP4SYS_DESCRIPTOR_TAG_MP4_IOD_Tag ) + { + od->ODProfileLevelIndication = lsmash_bs_get_byte( bs ); + od->sceneProfileLevelIndication = lsmash_bs_get_byte( bs ); + od->audioProfileLevelIndication = lsmash_bs_get_byte( bs ); + od->visualProfileLevelIndication = lsmash_bs_get_byte( bs ); + od->graphicsProfileLevelIndication = lsmash_bs_get_byte( bs ); + } + const mp4sys_descriptor_tag at_least_one_descriptor_tag + = od->header.tag == MP4SYS_DESCRIPTOR_TAG_MP4_OD_Tag + || od->header.tag == MP4SYS_DESCRIPTOR_TAG_MP4_IOD_Tag + ? MP4SYS_DESCRIPTOR_TAG_ES_ID_IncTag + : MP4SYS_DESCRIPTOR_TAG_ES_DescrTag; + while( lsmash_bs_count( bs ) < end_pos && od->esDescr.entry_count < 255 ) + { + mp4sys_descriptor_t *desc = mp4sys_get_descriptor( bs, od ); + if( !desc ) + break; + if( desc->header.tag != at_least_one_descriptor_tag ) + { + mp4sys_remove_descriptor( desc ); + break; + } + } + } + return od; +} + +mp4sys_descriptor_t *mp4sys_get_descriptor( lsmash_bs_t *bs, void *parent ) +{ + mp4sys_descriptor_head_t header; + mp4sys_get_descriptor_header( bs, &header ); + uint64_t end_pos = header.size + lsmash_bs_count( bs ); + mp4sys_descriptor_t *desc; + switch( header.tag ) + { + case MP4SYS_DESCRIPTOR_TAG_ObjectDescrTag : + case MP4SYS_DESCRIPTOR_TAG_InitialObjectDescrTag : + case MP4SYS_DESCRIPTOR_TAG_MP4_OD_Tag : + case MP4SYS_DESCRIPTOR_TAG_MP4_IOD_Tag : + desc = (mp4sys_descriptor_t *)mp4sys_get_ObjectDescriptor( bs, &header, parent ); + break; + case MP4SYS_DESCRIPTOR_TAG_ES_DescrTag : + desc = (mp4sys_descriptor_t *)mp4sys_get_ES_Descriptor( bs, &header, parent ); + break; + case MP4SYS_DESCRIPTOR_TAG_DecoderConfigDescrTag : + desc = (mp4sys_descriptor_t *)mp4sys_get_DecoderConfigDescriptor( bs, &header, parent ); + break; + case MP4SYS_DESCRIPTOR_TAG_DecSpecificInfoTag : + desc = (mp4sys_descriptor_t *)mp4sys_get_DecoderSpecificInfo( bs, &header, parent ); + break; + case MP4SYS_DESCRIPTOR_TAG_SLConfigDescrTag : + desc = (mp4sys_descriptor_t *)mp4sys_get_SLConfigDescriptor( bs, &header, parent ); + break; + case MP4SYS_DESCRIPTOR_TAG_ES_ID_IncTag : + desc = (mp4sys_descriptor_t *)mp4sys_get_ES_ID_Inc( bs, &header, parent ); + break; + default : + desc = lsmash_malloc_zero( sizeof(mp4sys_descriptor_t) ); + if( desc ) + { + desc->parent = parent; + desc->header = header; + } + break; + } + /* Skip extra bytes if present. */ + uint64_t skip_bytes = end_pos - lsmash_bs_count( bs ); + if( skip_bytes ) + { + fprintf( stderr, "[MPEG-4 Systems Descriptor Tag = 0x%02"PRIx8"] has more bytes than expected: %"PRId64"\n", header.tag, skip_bytes ); + if( !bs->unseekable ) + { + /* The stream is seekable. So, skip by seeking the stream. */ + uint64_t start = lsmash_bs_get_stream_pos( bs ); + lsmash_bs_read_seek( bs, skip_bytes, SEEK_CUR ); + uint64_t end = lsmash_bs_get_stream_pos( bs ); + bs->buffer.count += end - start; + } + else + /* The stream is unseekable. So, skip by reading the stream. */ + lsmash_bs_skip_bytes_64( bs, skip_bytes ); + } + return desc; +} + +static uint8_t *mp4sys_export_DecoderSpecificInfo( mp4sys_ES_Descriptor_t *esd, uint32_t *dsi_payload_length ) +{ + if( !esd || !esd->decConfigDescr || !esd->decConfigDescr->decSpecificInfo ) + return NULL; + mp4sys_DecoderSpecificInfo_t *dsi = (mp4sys_DecoderSpecificInfo_t *)esd->decConfigDescr->decSpecificInfo; + uint8_t *dsi_payload = NULL; + /* DecoderSpecificInfo can be absent. */ + if( dsi->header.size ) + { + dsi_payload = lsmash_memdup( dsi->data, dsi->header.size ); + if( !dsi_payload ) + return NULL; + } + if( dsi_payload_length ) + *dsi_payload_length = dsi->header.size; + return dsi_payload; +} + +/* Sumamry is needed to decide ProfileLevelIndication. + * Currently, support audio's only. */ +int mp4sys_setup_summary_from_DecoderSpecificInfo( lsmash_audio_summary_t *summary, mp4sys_ES_Descriptor_t *esd ) +{ + uint32_t dsi_payload_length = UINT32_MAX; /* arbitrary */ + uint8_t *dsi_payload = mp4sys_export_DecoderSpecificInfo( esd, &dsi_payload_length ); + if( !dsi_payload && dsi_payload_length ) + return LSMASH_ERR_NAMELESS; + int err = 0; + if( dsi_payload_length ) + { + lsmash_codec_specific_t *cs = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_MP4SYS_DECODER_CONFIG, + LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !cs ) + { + err = LSMASH_ERR_MEMORY_ALLOC; + goto fail; + } + lsmash_mp4sys_decoder_parameters_t *params = (lsmash_mp4sys_decoder_parameters_t *)cs->data.structured; + mp4sys_DecoderConfigDescriptor_t *dcd = esd->decConfigDescr; + params->objectTypeIndication = dcd->objectTypeIndication; + params->streamType = dcd->streamType; + params->bufferSizeDB = dcd->bufferSizeDB; + params->maxBitrate = dcd->maxBitrate; + params->avgBitrate = dcd->avgBitrate; + if( (err = mp4a_setup_summary_from_AudioSpecificConfig( summary, dsi_payload, dsi_payload_length )) < 0 + || (err = lsmash_set_mp4sys_decoder_specific_info( params, dsi_payload, dsi_payload_length )) < 0 + || (err = lsmash_list_add_entry( &summary->opaque->list, cs )) < 0 ) + { + lsmash_destroy_codec_specific_data( cs ); + goto fail; + } + } +fail: + lsmash_free( dsi_payload ); + return err; +} + +/**** following functions are for facilitation purpose ****/ + +mp4sys_ES_Descriptor_t *mp4sys_setup_ES_Descriptor( mp4sys_ES_Descriptor_params_t *params ) +{ + if( !params ) + return NULL; + mp4sys_ES_Descriptor_t *esd = mp4sys_create_ES_Descriptor( params->ES_ID ); + if( !esd ) + return NULL; + /* DecoderConfigDescriptor */ + mp4sys_DecoderConfigDescriptor_t *dcd = mp4sys_add_DecoderConfigDescriptor( esd ); + if( !dcd ) + goto fail; + dcd->objectTypeIndication = params->objectTypeIndication; + dcd->streamType = params->streamType; + dcd->upStream = 0; + dcd->reserved = 1; + dcd->bufferSizeDB = params->bufferSizeDB; + dcd->maxBitrate = params->maxBitrate; + dcd->avgBitrate = params->avgBitrate; + /* DecoderSpecificInfo */ + if( params->dsi_payload && params->dsi_payload_length != 0 ) + { + mp4sys_DecoderSpecificInfo_t *dsi = mp4sys_add_DecoderSpecificInfo( dcd ); + if( !dsi ) + goto fail; + dsi->data = lsmash_memdup( params->dsi_payload, params->dsi_payload_length ); + if( !dsi->data ) + goto fail; + dsi->header.size = params->dsi_payload_length; + } + /* SLConfigDescriptor */ + { + mp4sys_SLConfigDescriptor_t *slcd = mp4sys_add_SLConfigDescriptor( esd ); + if( !slcd ) + goto fail; + slcd->predefined = 0x02; /* MP4 file which does not use URL_Flag shall have constant value 0x02 */ + slcd->useTimeStampsFlag = 1; /* set to 1 if predefined == 2 */ + } + return esd; +fail: + mp4sys_remove_descriptor( esd ); + return NULL; +} + +int lsmash_set_mp4sys_decoder_specific_info( lsmash_mp4sys_decoder_parameters_t *param, uint8_t *payload, uint32_t payload_length ) +{ + if( !param || !payload || payload_length == 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + if( !param->dsi ) + { + param->dsi = lsmash_malloc_zero( sizeof(lsmash_mp4sys_decoder_specific_info_t) ); + if( !param->dsi ) + return LSMASH_ERR_MEMORY_ALLOC; + } + else + { + lsmash_freep( ¶m->dsi->payload ); + param->dsi->payload_length = 0; + } + param->dsi->payload = lsmash_memdup( payload, payload_length ); + if( !param->dsi->payload ) + return LSMASH_ERR_MEMORY_ALLOC; + param->dsi->payload_length = payload_length; + return 0; +} + +void lsmash_destroy_mp4sys_decoder_specific_info( lsmash_mp4sys_decoder_parameters_t *param ) +{ + if( !param || !param->dsi ) + return; + lsmash_free( param->dsi->payload ); + lsmash_freep( ¶m->dsi ); +} + +void mp4sys_destruct_decoder_config( void *data ) +{ + if( !data ) + return; + lsmash_destroy_mp4sys_decoder_specific_info( data ); + lsmash_free( data ); +} + +uint8_t *lsmash_create_mp4sys_decoder_config( lsmash_mp4sys_decoder_parameters_t *param, uint32_t *data_length ) +{ + if( !param || !data_length ) + return NULL; + mp4sys_ES_Descriptor_params_t esd_param = { 0 }; + esd_param.ES_ID = 0; /* Within sample description, ES_ID is stored as 0. */ + esd_param.objectTypeIndication = param->objectTypeIndication; + esd_param.streamType = param->streamType; + esd_param.bufferSizeDB = param->bufferSizeDB; + esd_param.maxBitrate = param->maxBitrate; + esd_param.avgBitrate = param->avgBitrate; + if( param->dsi + && param->dsi->payload + && param->dsi->payload_length ) + { + esd_param.dsi_payload = param->dsi->payload; + esd_param.dsi_payload_length = param->dsi->payload_length; + } + mp4sys_ES_Descriptor_t *esd = mp4sys_setup_ES_Descriptor( &esd_param ); + if( !esd ) + return NULL; + lsmash_bs_t *bs = lsmash_bs_create(); + if( !bs ) + { + mp4sys_remove_descriptor( esd ); + return NULL; + } + lsmash_bs_put_be32( bs, 0 ); /* update later */ + lsmash_bs_put_be32( bs, ISOM_BOX_TYPE_ESDS.fourcc ); + lsmash_bs_put_be32( bs, 0 ); + mp4sys_update_descriptor_size( esd ); + mp4sys_write_descriptor( bs, esd ); + mp4sys_remove_descriptor( esd ); + uint8_t *data = lsmash_bs_export_data( bs, data_length ); + lsmash_bs_cleanup( bs ); + if( !data ) + return NULL; + /* Update box size. */ + LSMASH_SET_BE32( data, *data_length ); + return data; +} + +int mp4sys_construct_decoder_config( lsmash_codec_specific_t *dst, lsmash_codec_specific_t *src ) +{ + assert( dst && dst->data.structured && src && src->data.unstructured ); + if( src->size < ISOM_FULLBOX_COMMON_SIZE + 23 ) + return LSMASH_ERR_INVALID_DATA; + lsmash_mp4sys_decoder_parameters_t *param = (lsmash_mp4sys_decoder_parameters_t *)dst->data.structured; + uint8_t *data = src->data.unstructured; + uint64_t size = LSMASH_GET_BE32( data ); + data += ISOM_BASEBOX_COMMON_SIZE; + if( size == 1 ) + { + size = LSMASH_GET_BE64( data ); + data += 8; + } + if( size != src->size ) + return LSMASH_ERR_INVALID_DATA; + data += 4; /* Skip version and flags. */ + lsmash_bs_t *bs = lsmash_bs_create(); + if( !bs ) + return LSMASH_ERR_MEMORY_ALLOC; + int err = lsmash_bs_import_data( bs, data, src->size - (data - src->data.unstructured) ); + if( err < 0 ) + { + lsmash_bs_cleanup( bs ); + return err; + } + mp4sys_ES_Descriptor_t *esd = (mp4sys_ES_Descriptor_t *)mp4sys_get_descriptor( bs, NULL ); + lsmash_bs_cleanup( bs ); + if( !esd || esd->header.tag != MP4SYS_DESCRIPTOR_TAG_ES_DescrTag || !esd->decConfigDescr ) + return LSMASH_ERR_INVALID_DATA; + mp4sys_DecoderConfigDescriptor_t *dcd = esd->decConfigDescr; + param->objectTypeIndication = dcd->objectTypeIndication; + param->streamType = dcd->streamType; + param->bufferSizeDB = dcd->bufferSizeDB; + param->maxBitrate = dcd->maxBitrate; + param->avgBitrate = dcd->avgBitrate; + mp4sys_DecoderSpecificInfo_t *dsi = dcd->decSpecificInfo; + if( dsi + && dsi->header.size + && dsi->data + && (err = lsmash_set_mp4sys_decoder_specific_info( param, dsi->data, dsi->header.size )) < 0 ) + { + mp4sys_remove_descriptor( esd ); + return err; + } + mp4sys_remove_descriptor( esd ); + return 0; +} + +int mp4sys_copy_decoder_config( lsmash_codec_specific_t *dst, lsmash_codec_specific_t *src ) +{ + assert( src && src->format == LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED && src->data.structured ); + assert( dst && dst->format == LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED && dst->data.structured ); + lsmash_mp4sys_decoder_parameters_t *src_data = (lsmash_mp4sys_decoder_parameters_t *)src->data.structured; + lsmash_mp4sys_decoder_parameters_t *dst_data = (lsmash_mp4sys_decoder_parameters_t *)dst->data.structured; + lsmash_destroy_mp4sys_decoder_specific_info( dst_data ); + *dst_data = *src_data; + dst_data->dsi = NULL; + if( !src_data->dsi || !src_data->dsi->payload || src_data->dsi->payload_length == 0 ) + return 0; + return lsmash_set_mp4sys_decoder_specific_info( dst_data, src_data->dsi->payload, src_data->dsi->payload_length ); +} + +lsmash_mp4sys_object_type_indication lsmash_mp4sys_get_object_type_indication( lsmash_summary_t *summary ) +{ + if( !summary ) + return MP4SYS_OBJECT_TYPE_Forbidden; + lsmash_codec_specific_t *orig = isom_get_codec_specific( summary->opaque, LSMASH_CODEC_SPECIFIC_DATA_TYPE_MP4SYS_DECODER_CONFIG ); + if( !orig ) + return MP4SYS_OBJECT_TYPE_Forbidden; + /* Found decoder configuration. + * Let's get objectTypeIndication. */ + lsmash_mp4sys_object_type_indication objectTypeIndication; + if( orig->format == LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ) + objectTypeIndication = ((lsmash_mp4sys_decoder_parameters_t *)orig->data.structured)->objectTypeIndication; + else + { + lsmash_codec_specific_t *conv = lsmash_convert_codec_specific_format( orig, LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !conv ) + return MP4SYS_OBJECT_TYPE_Forbidden; + objectTypeIndication = ((lsmash_mp4sys_decoder_parameters_t *)conv->data.structured)->objectTypeIndication; + lsmash_destroy_codec_specific_data( conv ); + } + return objectTypeIndication; +} + +int lsmash_get_mp4sys_decoder_specific_info( lsmash_mp4sys_decoder_parameters_t *param, uint8_t **payload, uint32_t *payload_length ) +{ + if( !param || !payload || !payload_length ) + return LSMASH_ERR_FUNCTION_PARAM; + if( !param->dsi || !param->dsi->payload || param->dsi->payload_length == 0 ) + { + *payload = NULL; + *payload_length = 0; + return 0; + } + uint8_t *temp = lsmash_memdup( param->dsi->payload, param->dsi->payload_length ); + if( !temp ) + return LSMASH_ERR_MEMORY_ALLOC; + *payload = temp; + *payload_length = param->dsi->payload_length; + return 0; +} diff --git a/3rdparty/l-smash/codecs/mp4sys.h b/3rdparty/l-smash/codecs/mp4sys.h new file mode 100644 index 000000000..d4c7aa2b8 --- /dev/null +++ b/3rdparty/l-smash/codecs/mp4sys.h @@ -0,0 +1,191 @@ +/***************************************************************************** + * mp4sys.h + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Takashi Hirata + * Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#ifndef MP4SYS_H +#define MP4SYS_H + +/*************************************************************************** + MPEG-4 Systems +***************************************************************************/ + +/* ODProfileLevelIndication */ +typedef enum +{ + MP4SYS_OD_PLI_Forbidden = 0x00, /* Forbidden */ + MP4SYS_OD_PLI_NOT_SPECIFIED = 0xFE, /* no OD profile specified */ + MP4SYS_OD_PLI_NONE_REQUIRED = 0xFF, /* no OD capability required */ +} mp4sys_ODProfileLevelIndication; + +/* sceneProfileLevelIndication */ +typedef enum +{ + MP4SYS_SCENE_PLI_RESERVED = 0x00, /* Reserved for ISO use */ + MP4SYS_SCENE_PLI_Simple2D_L1 = 0x01, /* Simple 2D L1 */ + MP4SYS_SCENE_PLI_Simple2D_L2 = 0x02, /* Simple 2D L2 */ + MP4SYS_SCENE_PLI_Audio_L1 = 0x03, /* Audio L1 */ + MP4SYS_SCENE_PLI_Audio_L2 = 0x04, /* Audio L2 */ + MP4SYS_SCENE_PLI_Audio_L3 = 0x05, /* Audio L3 */ + MP4SYS_SCENE_PLI_Audio_L4 = 0x06, /* Audio L4 */ + MP4SYS_SCENE_PLI_3D_Audio_L1 = 0x07, /* 3D Audio L1 */ + MP4SYS_SCENE_PLI_3D_Audio_L2 = 0x08, /* 3D Audio L2 */ + MP4SYS_SCENE_PLI_3D_Audio_L3 = 0x09, /* 3D Audio L3 */ + MP4SYS_SCENE_PLI_3D_Audio_L4 = 0x0A, /* 3D Audio L4 */ + MP4SYS_SCENE_PLI_Basic2D_L1 = 0x0B, /* Basic 2D L1 */ + MP4SYS_SCENE_PLI_Core2D_L1 = 0x0C, /* Core 2D L1 */ + MP4SYS_SCENE_PLI_Core2D_L2 = 0x0D, /* Core 2D L2 */ + MP4SYS_SCENE_PLI_Advanced2D_L1 = 0x0E, /* Advanced 2D L1 */ + MP4SYS_SCENE_PLI_Advanced2D_L2 = 0x0F, /* Advanced 2D L2 */ + MP4SYS_SCENE_PLI_Advanced2D_L3 = 0x10, /* Advanced 2D L3 */ + MP4SYS_SCENE_PLI_Main2D_L1 = 0x11, /* Main 2D L1 */ + MP4SYS_SCENE_PLI_Main2D_L2 = 0x12, /* Main 2D L2 */ + MP4SYS_SCENE_PLI_Main2D_L3 = 0x13, /* Main 2D L3 */ + MP4SYS_SCENE_PLI_NOT_SPECIFIED = 0xFE, /* no scene profile specified */ + MP4SYS_SCENE_PLI_NONE_REQUIRED = 0xFF, /* no scene capability required */ +} mp4sys_sceneProfileLevelIndication; + +/* 14496-2 Profile and level indication and restrictions */ +typedef enum +{ + MP4SYS_VISUAL_PLI_Reserved = 0x00, /* 0b00000000, Reserved */ + MP4SYS_VISUAL_PLI_Simple_PL1 = 0x01, /* 0b00000001, Simple Profile/Level 1 */ + MP4SYS_VISUAL_PLI_Simple_PL2 = 0x02, /* 0b00000010, Simple Profile/Level 2 */ + MP4SYS_VISUAL_PLI_Simple_PL3 = 0x03, /* 0b00000011, Simple Profile/Level 3 */ + MP4SYS_VISUAL_PLI_Simple_Scalable_PL1 = 0x11, /* 0b00010001, Simple Scalable Profile/Level 1 */ + MP4SYS_VISUAL_PLI_Simple_Scalable_PL2 = 0x12, /* 0b00010010, Simple Scalable Profile/Level 2 */ + MP4SYS_VISUAL_PLI_Core_PL1 = 0x21, /* 0b00100001, Core Profile/Level 1 */ + MP4SYS_VISUAL_PLI_Core_PL2 = 0x22, /* 0b00100010, Core Profile/Level 2 */ + MP4SYS_VISUAL_PLI_Main_PL2 = 0x32, /* 0b00110010, Main Profile/Level 2 */ + MP4SYS_VISUAL_PLI_Main_PL3 = 0x33, /* 0b00110011, Main Profile/Level 3 */ + MP4SYS_VISUAL_PLI_Main_PL4 = 0x34, /* 0b00110100, Main Profile/Level 4 */ + MP4SYS_VISUAL_PLI_N_bit_PL2 = 0x42, /* 0b01000010, N-bit Profile/Level 2 */ + MP4SYS_VISUAL_PLI_Scalable_Texture_PL1 = 0x51, /* 0b01010001, Scalable Texture Profile/Level 1 */ + MP4SYS_VISUAL_PLI_Simple_Face_Animation_PL1 = 0x61, /* 0b01100001, Simple Face Animation Profile/Level 1 */ + MP4SYS_VISUAL_PLI_Simple_Face_Animation_PL2 = 0x62, /* 0b01100010, Simple Face Animation Profile/Level 2 */ + MP4SYS_VISUAL_PLI_Simple_FBA_PL1 = 0x63, /* 0b01100011, Simple FBA Profile/Level 1 */ + MP4SYS_VISUAL_PLI_Simple_FBA_PL2 = 0x64, /* 0b01100100, Simple FBA Profile/Level 2 */ + MP4SYS_VISUAL_PLI_Basic_Animated_Texture_PL1 = 0x71, /* 0b01110001, Basic Animated Texture Profile/Level 1 */ + MP4SYS_VISUAL_PLI_Basic_Animated_Texture_PL2 = 0x72, /* 0b01110010, Basic Animated Texture Profile/Level 2 */ + MP4SYS_VISUAL_PLI_H264_AVC = 0x7F, /* ISO/IEC 14496-10 Advanced Video Codec / H.264, defined in ISO/IEC 14496-1:2010 */ + /* NOTE: Some other implementations seem to use 0x15(0b00010101) for AVC, but I think that's wrong. */ + MP4SYS_VISUAL_PLI_Hybrid_PL1 = 0x81, /* 0b10000001, Hybrid Profile/Level 1 */ + MP4SYS_VISUAL_PLI_Hybrid_PL2 = 0x82, /* 0b10000010, Hybrid Profile/Level 2 */ + MP4SYS_VISUAL_PLI_Advanced_Real_Time_Simple_PL1 = 0x91, /* 0b10010001, Advanced Real Time Simple Profile/Level 1 */ + MP4SYS_VISUAL_PLI_Advanced_Real_Time_Simple_PL2 = 0x92, /* 0b10010010, Advanced Real Time Simple Profile/Level 2 */ + MP4SYS_VISUAL_PLI_Advanced_Real_Time_Simple_PL3 = 0x93, /* 0b10010011, Advanced Real Time Simple Profile/Level 3 */ + MP4SYS_VISUAL_PLI_Advanced_Real_Time_Simple_PL4 = 0x94, /* 0b10010100, Advanced Real Time Simple Profile/Level 4 */ + MP4SYS_VISUAL_PLI_Core_Scalable_PL1 = 0xA1, /* 0b10100001, Core Scalable Profile/Level1 */ + MP4SYS_VISUAL_PLI_Core_Scalable_PL2 = 0xA2, /* 0b10100010, Core Scalable Profile/Level2 */ + MP4SYS_VISUAL_PLI_Core_Scalable_PL3 = 0xA3, /* 0b10100011, Core Scalable Profile/Level3 */ + MP4SYS_VISUAL_PLI_Advanced_Coding_Efficiency_PL1 = 0xB1, /* 0b10110001, Advanced Coding Efficiency Profile/Level 1 */ + MP4SYS_VISUAL_PLI_Advanced_Coding_Efficiency_PL2 = 0xB2, /* 0b10110010, Advanced Coding Efficiency Profile/Level 2 */ + MP4SYS_VISUAL_PLI_Advanced_Coding_Efficiency_PL3 = 0xB3, /* 0b10110011, Advanced Coding Efficiency Profile/Level 3 */ + MP4SYS_VISUAL_PLI_Advanced_Coding_Efficiency_PL4 = 0xB4, /* 0b10110100, Advanced Coding Efficiency Profile/Level 4 */ + MP4SYS_VISUAL_PLI_Advanced_Core_PL1 = 0xC1, /* 0b11000001, Advanced Core Profile/Level 1 */ + MP4SYS_VISUAL_PLI_Advanced_Core_PL2 = 0xC2, /* 0b11000010, Advanced Core Profile/Level 2 */ + MP4SYS_VISUAL_PLI_Advanced_Scalable_Texture_L1 = 0xD1, /* 0b11010001, Advanced Scalable Texture/Level1 */ + MP4SYS_VISUAL_PLI_Advanced_Scalable_Texture_L2 = 0xD2, /* 0b11010010, Advanced Scalable Texture/Level2 */ + MP4SYS_VISUAL_PLI_Advanced_Scalable_Texture_L3 = 0xD3, /* 0b11010011, Advanced Scalable Texture/Level3 */ + MP4SYS_VISUAL_PLI_NOT_SPECIFIED = 0xFE, /* no visual profile specified */ + MP4SYS_VISUAL_PLI_NONE_REQUIRED = 0xFF, /* no visual capability required */ +} mp4sys_visualProfileLevelIndication; + +/* graphicsProfileLevelIndication */ +typedef enum +{ + MP4SYS_GRAPHICS_PLI_RESERVED = 0x00, /* Reserved for ISO use */ + MP4SYS_GRAPHICS_PLI_Simple2D_L1 = 0x01, /* Simple2D profile L1 */ + MP4SYS_GRAPHICS_PLI_Simple2D_Text_L1 = 0x02, /* Simple 2D + Text profile L1 */ + MP4SYS_GRAPHICS_PLI_Simple2D_Text_L2 = 0x03, /* Simple 2D + Text profile L2 */ + MP4SYS_GRAPHICS_PLI_Core2D_L1 = 0x04, /* Core 2D profile L1 */ + MP4SYS_GRAPHICS_PLI_Core2D_L2 = 0x05, /* Core 2D profile L2 */ + MP4SYS_GRAPHICS_PLI_Advanced2D_L1 = 0x06, /* Advanced 2D profile L1 */ + MP4SYS_GRAPHICS_PLI_Advanced2D_L2 = 0x07, /* Advanced 2D profile L2 */ + MP4SYS_GRAPHICS_PLI_NOT_SPECIFIED = 0xFE, /* no graphics profile specified */ + MP4SYS_GRAPHICS_PLI_NONE_REQUIRED = 0xFF, /* no graphics capability required */ +} mp4sys_graphicsProfileLevelIndication; + +/* Just for mp4sys_setup_ES_Descriptor, to facilitate to make ES_Descriptor */ +typedef struct +{ + uint16_t ES_ID; /* Maybe 0 in stsd(esds), or alternatively, lower 16 bits of the TrackID */ + lsmash_mp4sys_object_type_indication objectTypeIndication; + lsmash_mp4sys_stream_type streamType; + uint32_t bufferSizeDB; /* byte unit, NOT bit unit. */ + uint32_t maxBitrate; + uint32_t avgBitrate; /* 0 if VBR */ + void *dsi_payload; /* AudioSpecificConfig or so */ + uint32_t dsi_payload_length ; /* size of dsi_payload */ +} mp4sys_ES_Descriptor_params_t; + +struct lsmash_mp4sys_decoder_specific_info_tag +{ + uint8_t *payload; + uint32_t payload_length; +}; + +#ifndef MP4SYS_INTERNAL + +typedef void mp4sys_descriptor_t; +typedef void mp4sys_ES_Descriptor_t; +typedef void mp4sys_ObjectDescriptor_t; + +void mp4sys_remove_descriptor( mp4sys_descriptor_t *descriptor ); + +/* ES_ID of the ES Descriptor is stored as 0 when the ES Descriptor is built into sample descriptions in MP4 file format + * since the lower 16 bits of the track_ID is used, instead of ES_ID, for the identifier of the elemental stream within the track. */ +mp4sys_ES_Descriptor_t *mp4sys_create_ES_Descriptor( uint16_t ES_ID ); +mp4sys_ObjectDescriptor_t *mp4sys_create_ObjectDescriptor( uint16_t ObjectDescriptorID ); +int mp4sys_create_ES_ID_Inc( mp4sys_ObjectDescriptor_t *od, uint32_t Track_ID ); + +int mp4sys_to_InitialObjectDescriptor +( + mp4sys_ObjectDescriptor_t *od, + uint8_t include_inline_pli, + mp4sys_ODProfileLevelIndication od_pli, + mp4sys_sceneProfileLevelIndication scene_pli, + mp4a_audioProfileLevelIndication audio_pli, + mp4sys_visualProfileLevelIndication visual_pli, + mp4sys_graphicsProfileLevelIndication graph_pli +); + +int mp4sys_update_DecoderConfigDescriptor +( + mp4sys_ES_Descriptor_t *esd, + uint32_t bufferSizeDB, + uint32_t maxBitrate, + uint32_t avgBitrate +); + +uint32_t mp4sys_update_descriptor_size( mp4sys_descriptor_t *descriptor ); +int mp4sys_write_descriptor( lsmash_bs_t *bs, mp4sys_descriptor_t *descriptor ); +void mp4sys_print_descriptor( FILE *fp, mp4sys_descriptor_t *descriptor, int indent ); +mp4sys_descriptor_t *mp4sys_get_descriptor( lsmash_bs_t *bs, mp4sys_descriptor_t *parent ); + +int mp4sys_setup_summary_from_DecoderSpecificInfo( lsmash_audio_summary_t *summary, mp4sys_ES_Descriptor_t *esd ); + +/* to facilitate to make ES_Descriptor */ +mp4sys_ES_Descriptor_t *mp4sys_setup_ES_Descriptor( mp4sys_ES_Descriptor_params_t *params ); + +#endif /* #ifndef MP4SYS_INTERNAL */ + +#endif /* #ifndef MP4SYS_H */ diff --git a/3rdparty/l-smash/codecs/mp4v.c b/3rdparty/l-smash/codecs/mp4v.c new file mode 100644 index 000000000..907d12eaf --- /dev/null +++ b/3rdparty/l-smash/codecs/mp4v.c @@ -0,0 +1,46 @@ +/***************************************************************************** + * mp4v.c + ***************************************************************************** + * Copyright (C) 2011-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include "core/box.h" + +#include "mp4a.h" /* ugly hack for the definition of mp4a_audioProfileLevelIndication */ +#include "mp4sys.h" + +int mp4v_update_bitrate( isom_stbl_t *stbl, isom_mdhd_t *mdhd, uint32_t sample_description_index ) +{ + isom_visual_entry_t *mp4v = (isom_visual_entry_t *)lsmash_list_get_entry_data( &stbl->stsd->list, sample_description_index ); + if( LSMASH_IS_NON_EXISTING_BOX( mp4v ) ) + return LSMASH_ERR_INVALID_DATA; + isom_esds_t *esds = (isom_esds_t *)isom_get_extension_box_format( &mp4v->extensions, ISOM_BOX_TYPE_ESDS ); + if( LSMASH_IS_NON_EXISTING_BOX( esds ) || !esds->ES ) + return LSMASH_ERR_INVALID_DATA; + uint32_t bufferSizeDB; + uint32_t maxBitrate; + uint32_t avgBitrate; + int err = isom_calculate_bitrate_description( stbl, mdhd, &bufferSizeDB, &maxBitrate, &avgBitrate, sample_description_index ); + if( err < 0 ) + return err; + /* FIXME: avgBitrate is 0 only if VBR in proper. */ + return mp4sys_update_DecoderConfigDescriptor( esds->ES, bufferSizeDB, maxBitrate, 0 ); +} diff --git a/3rdparty/l-smash/codecs/nalu.c b/3rdparty/l-smash/codecs/nalu.c new file mode 100644 index 000000000..203803d8c --- /dev/null +++ b/3rdparty/l-smash/codecs/nalu.c @@ -0,0 +1,267 @@ +/***************************************************************************** + * nalu.c + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include + +#include "core/box.h" + +#include "nalu.h" + +isom_dcr_ps_entry_t *isom_create_ps_entry +( + uint8_t *ps, + uint32_t ps_size +) +{ + isom_dcr_ps_entry_t *entry = lsmash_malloc( sizeof(isom_dcr_ps_entry_t) ); + if( !entry ) + return NULL; + entry->nalUnit = lsmash_memdup( ps, ps_size ); + if( !entry->nalUnit ) + { + lsmash_free( entry ); + return NULL; + } + entry->nalUnitLength = ps_size; + entry->unused = 0; + return entry; +} + +void isom_remove_dcr_ps +( + isom_dcr_ps_entry_t *ps +) +{ + if( !ps ) + return; + lsmash_free( ps->nalUnit ); + lsmash_free( ps ); +} + +/* Convert EBSP (Encapsulated Byte Sequence Packets) to RBSP (Raw Byte Sequence Packets). */ +static uint8_t *nalu_remove_emulation_prevention +( + uint8_t *src, + uint64_t src_length, + uint8_t *dst +) +{ + uint8_t *src_end = src + src_length; + while( src < src_end ) + if( ((src + 2) < src_end) && !src[0] && !src[1] && (src[2] == 0x03) ) + { + /* 0x000003 -> 0x0000 */ + *dst++ = *src++; + *dst++ = *src++; + src++; /* Skip emulation_prevention_three_byte (0x03). */ + } + else + *dst++ = *src++; + return dst; +} + +int nalu_import_rbsp_from_ebsp +( + lsmash_bits_t *bits, + uint8_t *rbsp_buffer, + uint64_t *rbsp_size, + uint8_t *ebsp, + uint64_t ebsp_size +) +{ + uint8_t *rbsp_start = rbsp_buffer; + uint8_t *rbsp_end = nalu_remove_emulation_prevention( ebsp, ebsp_size, rbsp_buffer ); + *rbsp_size = (uint64_t)(rbsp_end - rbsp_start); + if( *rbsp_size > ebsp_size ) + return LSMASH_ERR_INVALID_DATA; + return lsmash_bits_import_data( bits, rbsp_start, *rbsp_size ); +} + +int nalu_check_more_rbsp_data +( + lsmash_bits_t *bits +) +{ + lsmash_bs_t *bs = bits->bs; + lsmash_buffer_t *buffer = &bs->buffer; + if( buffer->pos < buffer->store && !(bits->store == 0 && (buffer->store == buffer->pos + 1)) ) + return 1; /* rbsp_trailing_bits will be placed at the next or later byte. + * Note: bs->pos points at the next byte if bits->store isn't empty. */ + if( bits->store == 0 ) + { + if( buffer->store == buffer->pos + 1 ) + return buffer->data[ buffer->pos ] != 0x80; + /* No rbsp_trailing_bits is present in RBSP data. */ + bs->error = 1; + return 0; + } + /* Check whether remainder of bits is identical to rbsp_trailing_bits. */ + uint8_t remainder_bits = bits->cache & ~(~0U << bits->store); + uint8_t rbsp_trailing_bits = 1U << (bits->store - 1); + return remainder_bits != rbsp_trailing_bits; +} + +int nalu_get_max_ps_length +( + lsmash_entry_list_t *ps_list, + uint32_t *max_ps_length +) +{ + *max_ps_length = 0; + for( lsmash_entry_t *entry = ps_list->head; entry; entry = entry->next ) + { + isom_dcr_ps_entry_t *ps = (isom_dcr_ps_entry_t *)entry->data; + if( !ps ) + return LSMASH_ERR_NAMELESS; + if( ps->unused ) + continue; + *max_ps_length = LSMASH_MAX( *max_ps_length, ps->nalUnitLength ); + } + return 0; +} + +int nalu_get_ps_count +( + lsmash_entry_list_t *ps_list, + uint32_t *ps_count +) +{ + *ps_count = 0; + for( lsmash_entry_t *entry = ps_list ? ps_list->head : NULL; entry; entry = entry->next ) + { + isom_dcr_ps_entry_t *ps = (isom_dcr_ps_entry_t *)entry->data; + if( !ps ) + return LSMASH_ERR_NAMELESS; + if( ps->unused ) + continue; + ++(*ps_count); + } + return 0; +} + +int nalu_check_same_ps_existence +( + lsmash_entry_list_t *ps_list, + void *ps_data, + uint32_t ps_length +) +{ + for( lsmash_entry_t *entry = ps_list->head; entry; entry = entry->next ) + { + isom_dcr_ps_entry_t *ps = (isom_dcr_ps_entry_t *)entry->data; + if( !ps ) + return LSMASH_ERR_NAMELESS; + if( ps->unused ) + continue; + if( ps->nalUnitLength == ps_length && !memcmp( ps->nalUnit, ps_data, ps_length ) ) + return 1; /* The same parameter set already exists. */ + } + return 0; +} + +int nalu_get_dcr_ps +( + lsmash_bs_t *bs, + lsmash_entry_list_t *list, + uint8_t entry_count +) +{ + for( uint8_t i = 0; i < entry_count; i++ ) + { + isom_dcr_ps_entry_t *data = lsmash_malloc( sizeof(isom_dcr_ps_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + data->nalUnitLength = lsmash_bs_get_be16( bs ); + data->nalUnit = lsmash_bs_get_bytes( bs, data->nalUnitLength ); + if( !data->nalUnit ) + { + lsmash_list_remove_entries( list ); + return LSMASH_ERR_NAMELESS; + } + } + return 0; +} + +/* Return the offset from the beginning of stream if a start code is found. + * Return NALU_NO_START_CODE_FOUND otherwise. */ +uint64_t nalu_find_first_start_code +( + lsmash_bs_t *bs +) +{ + uint64_t first_sc_head_pos = 0; + while( 1 ) + { + if( lsmash_bs_is_error( bs ) ) + return NALU_IO_ERROR; + if( lsmash_bs_is_end( bs, first_sc_head_pos + NALU_LONG_START_CODE_LENGTH ) ) + return NALU_NO_START_CODE_FOUND; + /* Invalid if encountered any value of non-zero before the first start code. */ + if( lsmash_bs_show_byte( bs, first_sc_head_pos ) ) + return NALU_NO_START_CODE_FOUND; + /* The first NALU of an AU in decoding order shall have long start code (0x00000001). */ + if( 0x00000001 == lsmash_bs_show_be32( bs, first_sc_head_pos ) ) + break; + ++first_sc_head_pos; + } + return first_sc_head_pos; +} + +uint64_t nalu_get_codeNum +( + lsmash_bits_t *bits +) +{ + uint32_t leadingZeroBits = 0; + for( int b = 0; !b; leadingZeroBits++ ) + b = lsmash_bits_get( bits, 1 ); + --leadingZeroBits; + return ((uint64_t)1 << leadingZeroBits) - 1 + lsmash_bits_get( bits, leadingZeroBits ); +} + +int nalu_update_bitrate( isom_stbl_t *stbl, isom_mdhd_t *mdhd, uint32_t sample_description_index ) +{ + isom_visual_entry_t *sample_entry = (isom_visual_entry_t *)lsmash_list_get_entry_data( &stbl->stsd->list, sample_description_index ); + if( LSMASH_IS_NON_EXISTING_BOX( sample_entry ) ) + return LSMASH_ERR_INVALID_DATA; + isom_btrt_t *btrt = (isom_btrt_t *)isom_get_extension_box_format( &sample_entry->extensions, ISOM_BOX_TYPE_BTRT ); + if( LSMASH_IS_EXISTING_BOX( btrt ) ) + { + uint32_t bufferSizeDB; + uint32_t maxBitrate; + uint32_t avgBitrate; + int err = isom_calculate_bitrate_description( stbl, mdhd, &bufferSizeDB, &maxBitrate, &avgBitrate, sample_description_index ); + if( err < 0 ) + return err; + btrt->bufferSizeDB = bufferSizeDB; + btrt->maxBitrate = maxBitrate; + btrt->avgBitrate = avgBitrate; + } + return 0; +} diff --git a/3rdparty/l-smash/codecs/nalu.h b/3rdparty/l-smash/codecs/nalu.h new file mode 100644 index 000000000..2c9d813b4 --- /dev/null +++ b/3rdparty/l-smash/codecs/nalu.h @@ -0,0 +1,145 @@ +/***************************************************************************** + * nalu.h + ***************************************************************************** + * Copyright (C) 2013-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#define NALU_DEFAULT_BUFFER_SIZE (1<<16) +#define NALU_DEFAULT_NALU_LENGTH_SIZE 4 /* We always use 4 bytes length. */ +#define NALU_SHORT_START_CODE_LENGTH 3 +#define NALU_LONG_START_CODE_LENGTH 4 +#define NALU_IO_ERROR UINT64_MAX - 1 +#define NALU_NO_START_CODE_FOUND UINT64_MAX + +/* Parameter Set Entry within AVC/HEVC Decoder Configuration Record */ +typedef struct +{ + uint16_t nalUnitLength; + uint8_t *nalUnit; + /* */ + int unused; +} isom_dcr_ps_entry_t; + +isom_dcr_ps_entry_t *isom_create_ps_entry +( + uint8_t *ps, + uint32_t ps_size +); + +void isom_remove_dcr_ps +( + isom_dcr_ps_entry_t *ps +); + +int nalu_import_rbsp_from_ebsp +( + lsmash_bits_t *bits, + uint8_t *rbsp_buffer, + uint64_t *rbsp_size, + uint8_t *ebsp, + uint64_t ebsp_size +); + +int nalu_check_more_rbsp_data +( + lsmash_bits_t *bits +); + +int nalu_get_max_ps_length +( + lsmash_entry_list_t *ps_list, + uint32_t *max_ps_length +); + +int nalu_get_ps_count +( + lsmash_entry_list_t *ps_list, + uint32_t *ps_count +); + +int nalu_check_same_ps_existence +( + lsmash_entry_list_t *ps_list, + void *ps_data, + uint32_t ps_length +); + +int nalu_get_dcr_ps +( + lsmash_bs_t *bs, + lsmash_entry_list_t *list, + uint8_t entry_count +); + +/* Return the offset from the beginning of stream if a start code is found. + * Return NALU_NO_START_CODE_FOUND otherwise. */ +uint64_t nalu_find_first_start_code +( + lsmash_bs_t *bs +); + +uint64_t nalu_get_codeNum +( + lsmash_bits_t *bits +); + +static inline uint64_t nalu_decode_exp_golomb_ue +( + uint64_t codeNum +) +{ + return codeNum; +} + +static inline int64_t nalu_decode_exp_golomb_se +( + uint64_t codeNum +) +{ + if( codeNum & 1 ) + return (int64_t)((codeNum >> 1) + 1); + return -1 * (int64_t)(codeNum >> 1); +} + +static inline uint64_t nalu_get_exp_golomb_ue +( + lsmash_bits_t *bits +) +{ + uint64_t codeNum = nalu_get_codeNum( bits ); + return nalu_decode_exp_golomb_ue( codeNum ); +} + +static inline uint64_t nalu_get_exp_golomb_se +( + lsmash_bits_t *bits +) +{ + uint64_t codeNum = nalu_get_codeNum( bits ); + return nalu_decode_exp_golomb_se( codeNum ); +} + +static inline int nalu_check_next_short_start_code +( + uint8_t *buf_pos, + uint8_t *buf_end +) +{ + return ((buf_pos + 2) < buf_end) && !buf_pos[0] && !buf_pos[1] && (buf_pos[2] == 0x01); +} diff --git a/3rdparty/l-smash/codecs/qt_wfex.c b/3rdparty/l-smash/codecs/qt_wfex.c new file mode 100644 index 000000000..1a1fe7d38 --- /dev/null +++ b/3rdparty/l-smash/codecs/qt_wfex.c @@ -0,0 +1,79 @@ +/***************************************************************************** + * qt_wfex.c + ***************************************************************************** + * Copyright (C) 2014-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include "core/box.h" + +/********************************************************************************* + QuickTime Waveform Audio +**********************************************************************************/ + +int waveform_audio_update_bitrate( isom_stbl_t *stbl, isom_mdhd_t *mdhd, uint32_t sample_description_index ) +{ + isom_sample_entry_t *sample_entry = (isom_sample_entry_t *)lsmash_list_get_entry_data( &stbl->stsd->list, sample_description_index ); + if( LSMASH_IS_NON_EXISTING_BOX( sample_entry ) ) + return LSMASH_ERR_INVALID_DATA; + isom_box_t *ext = isom_get_extension_box( &sample_entry->extensions, QT_BOX_TYPE_WAVE ); + if( LSMASH_IS_NON_EXISTING_BOX( ext ) ) + return LSMASH_ERR_INVALID_DATA; + uint8_t *exdata = NULL; + uint32_t exdata_size = 0; + if( ext->manager & LSMASH_BINARY_CODED_BOX ) + exdata = isom_get_child_box_position( ext->binary, ext->size, sample_entry->type, &exdata_size ); + else + { + isom_wave_t *wave = (isom_wave_t *)ext; + isom_box_t *wave_ext = isom_get_extension_box( &wave->extensions, sample_entry->type ); + if( !(wave_ext->manager & LSMASH_BINARY_CODED_BOX) ) + return LSMASH_ERR_INVALID_DATA; + exdata = wave_ext->binary; + exdata_size = wave_ext->size; + } + /* Check whether exdata is valid or not. */ + if( !exdata || exdata_size < ISOM_BASEBOX_COMMON_SIZE + 18 ) + return LSMASH_ERR_INVALID_DATA; + exdata += ISOM_BASEBOX_COMMON_SIZE; + uint16_t cbSize = LSMASH_GET_LE16( &exdata[16] ); + if( exdata_size < ISOM_BASEBOX_COMMON_SIZE + 18 + cbSize ) + return LSMASH_ERR_INVALID_DATA; + /* WAVEFORMATEX.nAvgBytesPerSec */ + uint32_t bufferSizeDB; + uint32_t maxBitrate; + uint32_t avgBitrate; + int err = isom_calculate_bitrate_description( stbl, mdhd, &bufferSizeDB, &maxBitrate, &avgBitrate, sample_description_index ); + if( err < 0 ) + return err; + uint32_t nAvgBytesPerSec = avgBitrate / 8; + LSMASH_SET_LE32( &exdata[8], nAvgBytesPerSec ); + if( lsmash_check_codec_type_identical( sample_entry->type, QT_CODEC_TYPE_FULLMP3_AUDIO ) + || lsmash_check_codec_type_identical( sample_entry->type, QT_CODEC_TYPE_MP3_AUDIO ) ) + { + /* MPEGLAYER3WAVEFORMAT.nBlockSize */ + uint32_t nSamplesPerSec = LSMASH_GET_LE32( &exdata[ 4] ); + uint16_t nFramesPerBlock = LSMASH_GET_LE16( &exdata[26] ); + uint16_t padding = 0; /* FIXME? */ + uint16_t nBlockSize = (144 * (avgBitrate / nSamplesPerSec) + padding) * nFramesPerBlock; + LSMASH_SET_LE16( &exdata[24], nBlockSize ); + } + return 0; +} diff --git a/3rdparty/l-smash/codecs/vc1.c b/3rdparty/l-smash/codecs/vc1.c new file mode 100644 index 000000000..c355fb309 --- /dev/null +++ b/3rdparty/l-smash/codecs/vc1.c @@ -0,0 +1,965 @@ +/***************************************************************************** + * vc1.c + ***************************************************************************** + * Copyright (C) 2012-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include +#include +#include + +#include "core/box.h" + +/*************************************************************************** + SMPTE 421M-2006 + SMPTE RP 2025-2007 +***************************************************************************/ +#include "vc1.h" + +struct lsmash_vc1_header_tag +{ + uint8_t *ebdu; + uint32_t ebdu_size; +}; + +typedef enum +{ + VC1_ADVANCED_PICTURE_TYPE_P = 0x0, /* 0b0 */ + VC1_ADVANCED_PICTURE_TYPE_B = 0x2, /* 0b10 */ + VC1_ADVANCED_PICTURE_TYPE_I = 0x6, /* 0b110 */ + VC1_ADVANCED_PICTURE_TYPE_BI = 0xE, /* 0b1110 */ + VC1_ADVANCED_PICTURE_TYPE_SKIPPED = 0xF, /* 0b1111 */ +} vc1_picture_type; + +typedef enum +{ + VC1_ADVANCED_FIELD_PICTURE_TYPE_II = 0x0, /* 0b000 */ + VC1_ADVANCED_FIELD_PICTURE_TYPE_IP = 0x1, /* 0b001 */ + VC1_ADVANCED_FIELD_PICTURE_TYPE_PI = 0x2, /* 0b010 */ + VC1_ADVANCED_FIELD_PICTURE_TYPE_PP = 0x3, /* 0b011 */ + VC1_ADVANCED_FIELD_PICTURE_TYPE_BB = 0x4, /* 0b100 */ + VC1_ADVANCED_FIELD_PICTURE_TYPE_BBI = 0x5, /* 0b101 */ + VC1_ADVANCED_FIELD_PICTURE_TYPE_BIB = 0x6, /* 0b110 */ + VC1_ADVANCED_FIELD_PICTURE_TYPE_BIBI = 0x7, /* 0b111 */ +} vc1_field_picture_type; + +typedef enum +{ + VC1_FRAME_CODING_MODE_PROGRESSIVE = 0x0, /* 0b0 */ + VC1_FRAME_CODING_MODE_FRAME_INTERLACE = 0x2, /* 0b10 */ + VC1_FRAME_CODING_MODE_FIELD_INTERLACE = 0x3, /* 0b11 */ +} vc1_frame_coding_mode; + +static void vc1_destroy_header( lsmash_vc1_header_t *hdr ) +{ + if( !hdr ) + return; + lsmash_free( hdr->ebdu ); + lsmash_free( hdr ); +} + +void lsmash_destroy_vc1_headers( lsmash_vc1_specific_parameters_t *param ) +{ + if( !param ) + return; + vc1_destroy_header( param->seqhdr ); + vc1_destroy_header( param->ephdr ); + param->seqhdr = NULL; + param->ephdr = NULL; +} + +void vc1_destruct_specific_data( void *data ) +{ + if( !data ) + return; + lsmash_destroy_vc1_headers( data ); + lsmash_free( data ); +} + +void vc1_cleanup_parser( vc1_info_t *info ) +{ + if( !info ) + return; + lsmash_destroy_vc1_headers( &info->dvc1_param ); + lsmash_destroy_multiple_buffers( info->buffer.bank ); + lsmash_bits_adhoc_cleanup( info->bits ); + info->bits = NULL; +} + +int vc1_setup_parser +( + vc1_info_t *info, + int parse_only +) +{ + assert( info ); + memset( info, 0, sizeof(vc1_info_t) ); + vc1_stream_buffer_t *sb = &info->buffer; + sb->bank = lsmash_create_multiple_buffers( parse_only ? 1 : 3, VC1_DEFAULT_BUFFER_SIZE ); + if( !sb->bank ) + return LSMASH_ERR_MEMORY_ALLOC; + sb->rbdu = lsmash_withdraw_buffer( sb->bank, 1 ); + if( !parse_only ) + { + info->access_unit.data = lsmash_withdraw_buffer( sb->bank, 2 ); + info->access_unit.incomplete_data = lsmash_withdraw_buffer( sb->bank, 3 ); + } + info->bits = lsmash_bits_adhoc_create(); + if( !info->bits ) + { + lsmash_destroy_multiple_buffers( sb->bank ); + return LSMASH_ERR_MEMORY_ALLOC; + } + info->prev_bdu_type = 0xFF; /* 0xFF is a forbidden value. */ + return 0; +} + +uint64_t vc1_find_next_start_code_prefix +( + lsmash_bs_t *bs, + uint8_t *bdu_type, + uint64_t *trailing_zero_bytes +) +{ + uint64_t length = 0; /* the length of the latest EBDU */ + uint64_t count = 0; /* the number of the trailing zero bytes after the latest EBDU */ + if( !lsmash_bs_is_end( bs, VC1_START_CODE_LENGTH - 1 ) + && 0x000001 == lsmash_bs_show_be24( bs, 0 ) ) + { + *bdu_type = lsmash_bs_show_byte( bs, VC1_START_CODE_PREFIX_LENGTH ); + length = VC1_START_CODE_LENGTH; + /* Find the start code of the next EBDU and get the length of the latest EBDU. */ + int no_more = lsmash_bs_is_end( bs, length + VC1_START_CODE_LENGTH - 1 ); + if( !no_more ) + { + uint32_t sync_bytes = lsmash_bs_show_be24( bs, length ); + while( 0x000001 != sync_bytes ) + { + no_more = lsmash_bs_is_end( bs, ++length + VC1_START_CODE_LENGTH - 1 ); + if( no_more ) + break; + sync_bytes <<= 8; + sync_bytes |= lsmash_bs_show_byte( bs, length + VC1_START_CODE_PREFIX_LENGTH - 1 ); + sync_bytes &= 0xFFFFFF; + } + } + if( no_more ) + length = lsmash_bs_get_remaining_buffer_size( bs ); + /* Any EBDU has no consecutive zero bytes at the end. */ + while( 0x00 == lsmash_bs_show_byte( bs, length - 1 ) ) + { + --length; + ++count; + } + } + else + *bdu_type = 0xFF; /* 0xFF is a forbidden value. */ + *trailing_zero_bytes = count; + return length; +} + +int vc1_check_next_start_code_suffix +( + lsmash_bs_t *bs, + uint8_t *p_bdu_type +) +{ + uint8_t bdu_type = *((uint8_t *)lsmash_bs_get_buffer_data( bs ) + VC1_START_CODE_PREFIX_LENGTH); + if( (bdu_type >= 0x00 && bdu_type <= 0x09) + || (bdu_type >= 0x20 && bdu_type <= 0x7F) ) + return LSMASH_ERR_NAMELESS; /* SMPTE reserved */ + if( bdu_type >= 0x80 && bdu_type <= 0xFF ) + return LSMASH_ERR_INVALID_DATA; /* Forbidden */ + *p_bdu_type = bdu_type; + return 0; +} + +static inline uint8_t vc1_get_vlc( lsmash_bits_t *bits, int length ) +{ + uint8_t value = 0; + for( int i = 0; i < length; i++ ) + if( lsmash_bits_get( bits, 1 ) ) + value = (value << 1) | 1; + else + { + value = value << 1; + break; + } + return value; +} + +/* Convert EBDU (Encapsulated Byte Data Unit) to RBDU (Raw Byte Data Unit). */ +static uint8_t *vc1_remove_emulation_prevention( uint8_t *src, uint64_t src_length, uint8_t *dst ) +{ + uint8_t *src_end = src + src_length; + while( src < src_end ) + if( ((src + 2) < src_end) && !src[0] && !src[1] && (src[2] == 0x03) ) + { + /* 0x000003 -> 0x0000 */ + *dst++ = *src++; + *dst++ = *src++; + src++; /* Skip emulation_prevention_three_byte (0x03). */ + } + else + *dst++ = *src++; + return dst; +} + +static int vc1_import_rbdu_from_ebdu( lsmash_bits_t *bits, uint8_t *rbdu_buffer, uint8_t *ebdu, uint64_t ebdu_size ) +{ + uint8_t *rbdu_start = rbdu_buffer; + uint8_t *rbdu_end = vc1_remove_emulation_prevention( ebdu, ebdu_size, rbdu_buffer ); + uint64_t rbdu_length = rbdu_end - rbdu_start; + return lsmash_bits_import_data( bits, rbdu_start, rbdu_length ); +} + +static void vc1_parse_hrd_param( lsmash_bits_t *bits, vc1_hrd_param_t *hrd_param ) +{ + hrd_param->hrd_num_leaky_buckets = lsmash_bits_get( bits, 5 ); + lsmash_bits_get( bits, 4 ); /* bitrate_exponent */ + lsmash_bits_get( bits, 4 ); /* buffer_size_exponent */ + for( uint8_t i = 0; i < hrd_param->hrd_num_leaky_buckets; i++ ) + { + lsmash_bits_get( bits, 16 ); /* hrd_rate */ + lsmash_bits_get( bits, 16 ); /* hrd_buffer */ + } +} + +int vc1_parse_sequence_header( vc1_info_t *info, uint8_t *ebdu, uint64_t ebdu_size, int try_append ) +{ + lsmash_bits_t *bits = info->bits; + vc1_sequence_header_t *sequence = &info->sequence; + int err = vc1_import_rbdu_from_ebdu( bits, info->buffer.rbdu, ebdu + VC1_START_CODE_LENGTH, ebdu_size ); + if( err < 0 ) + return err; + memset( sequence, 0, sizeof(vc1_sequence_header_t) ); + sequence->profile = lsmash_bits_get( bits, 2 ); + if( sequence->profile != 3 ) + return LSMASH_ERR_NAMELESS; /* SMPTE Reserved */ + sequence->level = lsmash_bits_get( bits, 3 ); + if( sequence->level > 4 ) + return LSMASH_ERR_NAMELESS; /* SMPTE Reserved */ + sequence->colordiff_format = lsmash_bits_get( bits, 2 ); + if( sequence->colordiff_format != 1 ) + return LSMASH_ERR_NAMELESS; /* SMPTE Reserved */ + lsmash_bits_get( bits, 9 ); /* frmrtq_postproc (3) + * bitrtq_postproc (5) + * postproc_flag (1) */ + sequence->max_coded_width = lsmash_bits_get( bits, 12 ); + sequence->max_coded_height = lsmash_bits_get( bits, 12 ); + lsmash_bits_get( bits, 1 ); /* pulldown */ + sequence->interlace = lsmash_bits_get( bits, 1 ); + lsmash_bits_get( bits, 4 ); /* tfcntrflag (1) + * finterpflag (1) + * reserved (1) + * psf (1) */ + if( lsmash_bits_get( bits, 1 ) ) /* display_ext */ + { + sequence->disp_horiz_size = lsmash_bits_get( bits, 14 ) + 1; + sequence->disp_vert_size = lsmash_bits_get( bits, 14 ) + 1; + if( lsmash_bits_get( bits, 1 ) ) /* aspect_ratio_flag */ + { + uint8_t aspect_ratio = lsmash_bits_get( bits, 4 ); + if( aspect_ratio == 15 ) + { + sequence->aspect_width = lsmash_bits_get( bits, 8 ) + 1; /* aspect_horiz_size */ + sequence->aspect_height = lsmash_bits_get( bits, 8 ) + 1; /* aspect_vert_size */ + } + else + { + static const struct + { + uint32_t aspect_width; + uint32_t aspect_height; + } vc1_aspect_ratio[15] = + { + { 0, 0 }, { 1, 1 }, { 12, 11 }, { 10, 11 }, { 16, 11 }, { 40, 33 }, { 24, 11 }, + { 20, 11 }, { 32, 11 }, { 80, 33 }, { 18, 11 }, { 15, 11 }, { 64, 33 }, { 160, 99 }, + { 0, 0 } /* SMPTE Reserved */ + }; + sequence->aspect_width = vc1_aspect_ratio[ aspect_ratio ].aspect_width; + sequence->aspect_height = vc1_aspect_ratio[ aspect_ratio ].aspect_height; + } + } + sequence->framerate_flag = lsmash_bits_get( bits, 1 ); + if( sequence->framerate_flag ) + { + if( lsmash_bits_get( bits, 1 ) ) /* framerateind */ + { + sequence->framerate_numerator = lsmash_bits_get( bits, 16 ) + 1; + sequence->framerate_denominator = 32; + } + else + { + static const uint32_t vc1_frameratenr_table[8] = { 0, 24, 25, 30, 50, 60, 48, 72 }; + uint8_t frameratenr = lsmash_bits_get( bits, 8 ); + if( frameratenr == 0 ) + return LSMASH_ERR_INVALID_DATA; /* Forbidden */ + if( frameratenr > 7 ) + return LSMASH_ERR_NAMELESS; /* SMPTE Reserved */ + uint8_t frameratedr = lsmash_bits_get( bits, 4 ); + if( frameratedr != 1 && frameratedr != 2 ) + /* 0: Forbidden, 3-15: SMPTE Reserved */ + return frameratedr == 0 + ? LSMASH_ERR_INVALID_DATA + : LSMASH_ERR_NAMELESS; + if( frameratedr == 1 ) + { + sequence->framerate_numerator = vc1_frameratenr_table[ frameratenr ]; + sequence->framerate_denominator = 1; + } + else + { + sequence->framerate_numerator = vc1_frameratenr_table[ frameratenr ] * 1000; + sequence->framerate_denominator = 1001; + } + } + } + if( lsmash_bits_get( bits, 1 ) ) /* color_format_flag */ + { + sequence->color_prim = lsmash_bits_get( bits, 8 ); + sequence->transfer_char = lsmash_bits_get( bits, 8 ); + sequence->matrix_coef = lsmash_bits_get( bits, 8 ); + } + sequence->hrd_param_flag = lsmash_bits_get( bits, 1 ); + if( sequence->hrd_param_flag ) + vc1_parse_hrd_param( bits, &sequence->hrd_param ); + } + /* '1' and stuffing bits ('0's) */ + if( !lsmash_bits_get( bits, 1 ) ) + return LSMASH_ERR_INVALID_DATA; + lsmash_bits_empty( bits ); + /* Preparation for creating VC1SpecificBox */ + if( try_append ) + { + /* Update some specific parameters. */ + lsmash_vc1_specific_parameters_t *param = &info->dvc1_param; + lsmash_vc1_header_t *seqhdr = param->seqhdr; + if( !seqhdr ) + { + seqhdr = lsmash_malloc( sizeof(lsmash_vc1_header_t) ); + if( !seqhdr ) + return LSMASH_ERR_MEMORY_ALLOC; + seqhdr->ebdu = lsmash_memdup( ebdu, ebdu_size ); + if( !seqhdr->ebdu ) + { + lsmash_free( seqhdr ); + return LSMASH_ERR_MEMORY_ALLOC; + } + seqhdr->ebdu_size = ebdu_size; + param->seqhdr = seqhdr; + } + else if( seqhdr && seqhdr->ebdu && (seqhdr->ebdu_size == ebdu_size) ) + param->multiple_sequence |= !!memcmp( ebdu, seqhdr->ebdu, seqhdr->ebdu_size ); + param->profile = sequence->profile << 2; + param->level = LSMASH_MAX( param->level, sequence->level ); + param->interlaced |= sequence->interlace; + uint32_t framerate = sequence->framerate_flag + ? ((double)sequence->framerate_numerator / sequence->framerate_denominator) + 0.5 + : 0xffffffff; /* 0xffffffff means framerate is unknown or unspecified. */ + if( param->framerate == 0 ) + param->framerate = framerate; + else if( param->framerate != framerate ) + param->framerate = 0xffffffff; + } + info->sequence.present = 1; + return bits->bs->error ? LSMASH_ERR_NAMELESS : 0; +} + +int vc1_parse_entry_point_header( vc1_info_t *info, uint8_t *ebdu, uint64_t ebdu_size, int try_append ) +{ + lsmash_bits_t *bits = info->bits; + vc1_sequence_header_t *sequence = &info->sequence; + vc1_entry_point_t *entry_point = &info->entry_point; + int err = vc1_import_rbdu_from_ebdu( bits, info->buffer.rbdu, ebdu + VC1_START_CODE_LENGTH, ebdu_size ); + if( err < 0 ) + return err; + memset( entry_point, 0, sizeof(vc1_entry_point_t) ); + uint8_t broken_link_flag = lsmash_bits_get( bits, 1 ); /* 0: no concatenation between the current and the previous entry points + * 1: concatenated and needed to discard B-pictures */ + entry_point->closed_entry_point = lsmash_bits_get( bits, 1 ); /* 0: Open RAP, 1: Closed RAP */ + if( broken_link_flag && entry_point->closed_entry_point ) + return LSMASH_ERR_INVALID_DATA; /* invalid combination */ + lsmash_bits_get( bits, 4 ); /* panscan_flag (1) + * refdist_flag (1) + * loopfilter (1) + * fastuvmc (1) */ + uint8_t extended_mv = lsmash_bits_get( bits, 1 ); + lsmash_bits_get( bits, 6 ); /* dquant (2) + * vstransform (1) + * overlap (1) + * quantizer (2) */ + if( sequence->hrd_param_flag ) + for( uint8_t i = 0; i < sequence->hrd_param.hrd_num_leaky_buckets; i++ ) + lsmash_bits_get( bits, 8 ); /* hrd_full */ + /* Decide coded size here. + * The correct formula is defined in Amendment 2:2011 to SMPTE ST 421M:2006. + * Don't use the formula specified in SMPTE 421M-2006. */ + uint16_t coded_width; + uint16_t coded_height; + if( lsmash_bits_get( bits, 1 ) ) /* coded_size_flag */ + { + coded_width = lsmash_bits_get( bits, 12 ); + coded_height = lsmash_bits_get( bits, 12 ); + } + else + { + coded_width = sequence->max_coded_width; + coded_height = sequence->max_coded_height; + } + coded_width = 2 * (coded_width + 1); /* corrected */ + coded_height = 2 * (coded_height + 1); /* corrected */ + if( sequence->disp_horiz_size == 0 || sequence->disp_vert_size == 0 ) + { + sequence->disp_horiz_size = coded_width; + sequence->disp_vert_size = coded_height; + } + /* */ + if( extended_mv ) + lsmash_bits_get( bits, 1 ); /* extended_dmv */ + if( lsmash_bits_get( bits, 1 ) ) /* range_mapy_flag */ + lsmash_bits_get( bits, 3 ); /* range_mapy */ + if( lsmash_bits_get( bits, 1 ) ) /* range_mapuv_flag */ + lsmash_bits_get( bits, 3 ); /* range_mapuv */ + /* '1' and stuffing bits ('0's) */ + if( !lsmash_bits_get( bits, 1 ) ) + return LSMASH_ERR_INVALID_DATA; + lsmash_bits_empty( bits ); + /* Preparation for creating VC1SpecificBox */ + if( try_append ) + { + lsmash_vc1_specific_parameters_t *param = &info->dvc1_param; + lsmash_vc1_header_t *ephdr = param->ephdr; + if( !ephdr ) + { + ephdr = lsmash_malloc( sizeof(lsmash_vc1_header_t) ); + if( !ephdr ) + return LSMASH_ERR_MEMORY_ALLOC; + ephdr->ebdu = lsmash_memdup( ebdu, ebdu_size ); + if( !ephdr->ebdu ) + { + lsmash_free( ephdr ); + return LSMASH_ERR_MEMORY_ALLOC; + } + ephdr->ebdu_size = ebdu_size; + param->ephdr = ephdr; + } + else if( ephdr && ephdr->ebdu && (ephdr->ebdu_size == ebdu_size) ) + param->multiple_entry |= !!memcmp( ebdu, ephdr->ebdu, ephdr->ebdu_size ); + } + info->entry_point.present = 1; + return bits->bs->error ? LSMASH_ERR_NAMELESS : 0; +} + +int vc1_parse_advanced_picture( lsmash_bits_t *bits, + vc1_sequence_header_t *sequence, vc1_picture_info_t *picture, + uint8_t *rbdu_buffer, uint8_t *ebdu, uint64_t ebdu_size ) +{ + int err = vc1_import_rbdu_from_ebdu( bits, rbdu_buffer, ebdu + VC1_START_CODE_LENGTH, ebdu_size ); + if( err < 0 ) + return err; + if( sequence->interlace ) + picture->frame_coding_mode = vc1_get_vlc( bits, 2 ); + else + picture->frame_coding_mode = 0; + if( picture->frame_coding_mode != 0x3 ) + picture->type = vc1_get_vlc( bits, 4 ); /* ptype (variable length) */ + else + picture->type = lsmash_bits_get( bits, 3 ); /* fptype (3) */ + picture->present = 1; + lsmash_bits_empty( bits ); + return bits->bs->error ? LSMASH_ERR_NAMELESS : 0; +} + +void vc1_update_au_property( vc1_access_unit_t *access_unit, vc1_picture_info_t *picture ) +{ + access_unit->random_accessible = picture->random_accessible; + access_unit->closed_gop = picture->closed_gop; + /* I-picture + * Be coded using information only from itself. (independent) + * All the macroblocks in an I-picture are intra-coded. + * P-picture + * Be coded using motion compensated prediction from past reference fields or frame. + * Can contain macroblocks that are inter-coded (i.e. coded using prediction) and macroblocks that are intra-coded. + * B-picture + * Be coded using motion compensated prediction from past and/or future reference fields or frames. (bi-predictive) + * Cannot be used for predicting any other picture. (disposable) + * BI-picture + * All the macroblocks in BI-picture are intra-coded. (independent) + * Cannot be used for predicting any other picture. (disposable) */ + if( picture->frame_coding_mode == 0x3 ) + { + /* field interlace */ + access_unit->independent = picture->type == VC1_ADVANCED_FIELD_PICTURE_TYPE_II || picture->type == VC1_ADVANCED_FIELD_PICTURE_TYPE_BIBI; + access_unit->non_bipredictive = picture->type < VC1_ADVANCED_FIELD_PICTURE_TYPE_BB || picture->type == VC1_ADVANCED_FIELD_PICTURE_TYPE_BIBI; + access_unit->disposable = picture->type >= VC1_ADVANCED_FIELD_PICTURE_TYPE_BB; + } + else + { + /* frame progressive/interlace */ + access_unit->independent = picture->type == VC1_ADVANCED_PICTURE_TYPE_I || picture->type == VC1_ADVANCED_PICTURE_TYPE_BI; + access_unit->non_bipredictive = picture->type != VC1_ADVANCED_PICTURE_TYPE_B; + access_unit->disposable = picture->type == VC1_ADVANCED_PICTURE_TYPE_B || picture->type == VC1_ADVANCED_PICTURE_TYPE_BI; + } + picture->present = 0; + picture->type = 0; + picture->closed_gop = 0; + picture->start_of_sequence = 0; + picture->random_accessible = 0; +} + +int vc1_find_au_delimit_by_bdu_type( uint8_t bdu_type, uint8_t prev_bdu_type ) +{ + /* In any access unit, EBDU with smaller least significant 8-bits of BDU type doesn't precede EBDU with larger one. + * Therefore, the condition: (bdu_type 0xF) > (prev_bdu_type & 0xF) is more precisely. + * No two or more frame start codes shall be in the same access unit. */ + return bdu_type > prev_bdu_type || (bdu_type == 0x0D && prev_bdu_type == 0x0D); +} + +int vc1_supplement_buffer( vc1_stream_buffer_t *sb, vc1_access_unit_t *access_unit, uint32_t size ) +{ + lsmash_multiple_buffers_t *bank = lsmash_resize_multiple_buffers( sb->bank, size ); + if( !bank ) + return LSMASH_ERR_MEMORY_ALLOC; + sb->bank = bank; + sb->rbdu = lsmash_withdraw_buffer( bank, 1 ); + if( access_unit && bank->number_of_buffers == 3 ) + { + access_unit->data = lsmash_withdraw_buffer( bank, 2 ); + access_unit->incomplete_data = lsmash_withdraw_buffer( bank, 3 ); + } + return 0; +} + +uint8_t *lsmash_create_vc1_specific_info( lsmash_vc1_specific_parameters_t *param, uint32_t *data_length ) +{ + if( !param || !data_length ) + return NULL; + if( !param->seqhdr || !param->ephdr ) + return NULL; + /* Calculate enough buffer size. */ + lsmash_vc1_header_t *seqhdr = param->seqhdr; + lsmash_vc1_header_t *ephdr = param->ephdr; + /* Create a VC1SpecificBox */ + lsmash_bits_t *bits = lsmash_bits_adhoc_create(); + if( !bits ) + return NULL; + lsmash_bits_put( bits, 32, 0 ); /* box size */ + lsmash_bits_put( bits, 32, ISOM_BOX_TYPE_DVC1.fourcc ); /* box type: 'dvc1' */ + lsmash_bits_put( bits, 4, param->profile ); /* profile */ + lsmash_bits_put( bits, 3, param->level ); /* level */ + lsmash_bits_put( bits, 1, 0 ); /* reserved */ + /* VC1AdvDecSpecStruc (for Advanced Profile) */ + lsmash_bits_put( bits, 3, param->level ); /* level (identical to the previous level field) */ + lsmash_bits_put( bits, 1, param->cbr ); /* cbr */ + lsmash_bits_put( bits, 6, 0 ); /* reserved */ + lsmash_bits_put( bits, 1, !param->interlaced ); /* no_interlace */ + lsmash_bits_put( bits, 1, !param->multiple_sequence ); /* no_multiple_seq */ + lsmash_bits_put( bits, 1, !param->multiple_entry ); /* no_multiple_entry */ + lsmash_bits_put( bits, 1, !param->slice_present ); /* no_slice_code */ + lsmash_bits_put( bits, 1, !param->bframe_present ); /* no_bframe */ + lsmash_bits_put( bits, 1, 0 ); /* reserved */ + lsmash_bits_put( bits, 32, param->framerate ); /* framerate */ + /* seqhdr_ephdr[] */ + for( uint32_t i = 0; i < seqhdr->ebdu_size; i++ ) + lsmash_bits_put( bits, 8, *(seqhdr->ebdu + i) ); + for( uint32_t i = 0; i < ephdr->ebdu_size; i++ ) + lsmash_bits_put( bits, 8, *(ephdr->ebdu + i) ); + /* */ + uint8_t *data = lsmash_bits_export_data( bits, data_length ); + lsmash_bits_adhoc_cleanup( bits ); + /* Update box size. */ + LSMASH_SET_BE32( data, *data_length ); + return data; +} + +static int vc1_try_to_put_header( lsmash_vc1_header_t **p_hdr, uint8_t *multiple_hdr, void *hdr_data, uint32_t hdr_length ) +{ + lsmash_vc1_header_t *hdr = *p_hdr; + if( !hdr ) + { + hdr = lsmash_malloc_zero( sizeof(lsmash_vc1_header_t) ); + if( !hdr ) + return LSMASH_ERR_MEMORY_ALLOC; + } + else if( hdr->ebdu ) + { + *multiple_hdr |= hdr->ebdu_size == hdr_length ? !!memcmp( hdr_data, hdr->ebdu, hdr->ebdu_size ) : 1; + return 0; + } + hdr->ebdu = lsmash_memdup( hdr_data, hdr_length ); + hdr->ebdu_size = hdr->ebdu ? hdr_length : 0; + *p_hdr = hdr; + return hdr->ebdu ? 0 : LSMASH_ERR_MEMORY_ALLOC; +} + +int lsmash_put_vc1_header( lsmash_vc1_specific_parameters_t *param, void *hdr_data, uint32_t hdr_length ) +{ + if( !param || !hdr_data || hdr_length < 5 ) + return LSMASH_ERR_FUNCTION_PARAM; + /* Check start code prefix (0x000001). */ + uint8_t *data = (uint8_t *)hdr_data; + if( data[0] != 0x00 || data[1] != 0x00 || data[2] != 0x01 ) + return LSMASH_ERR_INVALID_DATA; + if( data[3] == 0x0F ) /* sequence header */ + return vc1_try_to_put_header( ¶m->seqhdr, ¶m->multiple_sequence, hdr_data, hdr_length ); + else if( data[3] == 0x0E ) /* entry point header */ + return vc1_try_to_put_header( ¶m->ephdr, ¶m->multiple_entry, hdr_data, hdr_length ); + return LSMASH_ERR_INVALID_DATA; +} + +static int vc1_parse_succeeded +( + vc1_info_t *info, + lsmash_vc1_specific_parameters_t *param +) +{ + int ret; + if( info->sequence.present && info->entry_point.present ) + { + *param = info->dvc1_param; + /* Avoid freeing headers. */ + info->dvc1_param.seqhdr = NULL; + info->dvc1_param.ephdr = NULL; + ret = 0; + } + else + ret = LSMASH_ERR_INVALID_DATA; + vc1_cleanup_parser( info ); + return ret; +} + +static inline int vc1_parse_failed +( + vc1_info_t *info, + int ret +) +{ + vc1_cleanup_parser( info ); + return ret; +} + +int lsmash_setup_vc1_specific_parameters_from_access_unit( lsmash_vc1_specific_parameters_t *param, uint8_t *data, uint32_t data_length ) +{ + if( !param || !data || data_length == 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + vc1_info_t *info = &(vc1_info_t){ { 0 } }; + lsmash_bs_t *bs = &(lsmash_bs_t){ 0 }; + int err = lsmash_bs_set_empty_stream( bs, data, data_length ); + if( err < 0 ) + return err; + if( (err = vc1_setup_parser( info, 1 )) < 0 ) + return vc1_parse_failed( info, err ); + info->dvc1_param = *param; + vc1_stream_buffer_t *sb = &info->buffer; + while( 1 ) + { + uint8_t bdu_type; + uint64_t trailing_zero_bytes; + uint64_t ebdu_length = vc1_find_next_start_code_prefix( bs, &bdu_type, &trailing_zero_bytes ); + if( ebdu_length <= VC1_START_CODE_LENGTH && lsmash_bs_is_end( bs, ebdu_length ) ) + /* For the last EBDU. This EBDU already has been parsed. */ + return vc1_parse_succeeded( info, param ); + else if( bdu_type == 0xFF ) + return vc1_parse_failed( info, LSMASH_ERR_INVALID_DATA ); + uint64_t next_ebdu_head_pos = info->ebdu_head_pos + + ebdu_length + + trailing_zero_bytes; + if( bdu_type >= 0x0A && bdu_type <= 0x0F ) + { + /* Complete the current access unit if encountered delimiter of current access unit. */ + if( vc1_find_au_delimit_by_bdu_type( bdu_type, info->prev_bdu_type ) ) + /* The last video coded EBDU belongs to the access unit you want at this time. */ + return vc1_parse_succeeded( info, param ); + /* Increase the buffer if needed. */ + if( sb->bank->buffer_size < ebdu_length + && (err = vc1_supplement_buffer( sb, NULL, 2 * ebdu_length )) < 0 ) + return vc1_parse_failed( info, err ); + /* Process EBDU by its BDU type. */ + uint8_t *ebdu = lsmash_bs_get_buffer_data( bs ); + switch( bdu_type ) + { + /* FRM_SC: Frame start code + * FLD_SC: Field start code + * SLC_SC: Slice start code + * SEQ_SC: Sequence header start code + * EP_SC: Entry-point start code + * PIC_L: Picture layer + * SLC_L: Slice layer + * SEQ_L: Sequence layer + * EP_L: Entry-point layer */ + case 0x0D : /* Frame + * For the Progressive or Frame Interlace mode, shall signal the beginning of a new video frame. + * For the Field Interlace mode, shall signal the beginning of a sequence of two independently coded video fields. + * [FRM_SC][PIC_L][[FLD_SC][PIC_L] (optional)][[SLC_SC][SLC_L] (optional)] ... */ + { + vc1_picture_info_t *picture = &info->picture; + if( (err = vc1_parse_advanced_picture( info->bits, &info->sequence, picture, info->buffer.rbdu, ebdu, ebdu_length )) < 0 ) + return vc1_parse_failed( info, err ); + info->dvc1_param.bframe_present |= picture->frame_coding_mode == 0x3 + ? picture->type >= VC1_ADVANCED_FIELD_PICTURE_TYPE_BB + : picture->type == VC1_ADVANCED_PICTURE_TYPE_B || picture->type == VC1_ADVANCED_PICTURE_TYPE_BI; + } + case 0x0C : /* Field + * Shall only be used for Field Interlaced frames + * and shall only be used to signal the beginning of the second field of the frame. + * [FRM_SC][PIC_L][FLD_SC][PIC_L][[SLC_SC][SLC_L] (optional)] ... + * Field start code is followed by INTERLACE_FIELD_PICTURE_FIELD2() which doesn't have info of its field picture type.*/ + break; + case 0x0B : /* Slice + * Shall not be used for start code of the first slice of a frame. + * Shall not be used for start code of the first slice of an interlace field coded picture. + * [FRM_SC][PIC_L][[FLD_SC][PIC_L] (optional)][SLC_SC][SLC_L][[SLC_SC][SLC_L] (optional)] ... + * Slice layer may repeat frame header. We just ignore it. */ + info->dvc1_param.slice_present = 1; + break; + case 0x0E : /* Entry-point header + * Entry-point indicates the direct followed frame is a start of group of frames. + * Entry-point doesn't indicates the frame is a random access point when multiple sequence headers are present, + * since it is necessary to decode sequence header which subsequent frames belong to for decoding them. + * Entry point shall be followed by + * 1. I-picture - progressive or frame interlace + * 2. I/I-picture, I/P-picture, or P/I-picture - field interlace + * [[SEQ_SC][SEQ_L] (optional)][EP_SC][EP_L][FRM_SC][PIC_L] ... */ + if( (err = vc1_parse_entry_point_header( info, ebdu, ebdu_length, 1 )) < 0 ) + return vc1_parse_failed( info, err ); + break; + case 0x0F : /* Sequence header + * [SEQ_SC][SEQ_L][EP_SC][EP_L][FRM_SC][PIC_L] ... */ + if( (err = vc1_parse_sequence_header( info, ebdu, ebdu_length, 1 )) < 0 ) + return vc1_parse_failed( info, err ); + break; + default : /* End-of-sequence (0x0A) */ + break; + } + } + /* Move to the first byte of the next EBDU. */ + info->prev_bdu_type = bdu_type; + if( lsmash_bs_read_seek( bs, next_ebdu_head_pos, SEEK_SET ) != next_ebdu_head_pos ) + return vc1_parse_failed( info, LSMASH_ERR_NAMELESS ); + /* Check if no more data to read from the stream. */ + if( !lsmash_bs_is_end( bs, VC1_START_CODE_PREFIX_LENGTH ) ) + info->ebdu_head_pos = next_ebdu_head_pos; + else + return vc1_parse_succeeded( info, param ); + } +} + +static inline int vc1_check_next_start_code_prefix( uint8_t *buf_pos, uint8_t *buf_end ) +{ + return ((buf_pos + 2) < buf_end) && !buf_pos[0] && !buf_pos[1] && (buf_pos[2] == 0x01); +} + +int vc1_construct_specific_parameters( lsmash_codec_specific_t *dst, lsmash_codec_specific_t *src ) +{ + assert( dst && dst->data.structured && src && src->data.unstructured ); + if( src->size < ISOM_BASEBOX_COMMON_SIZE + 7 ) + return LSMASH_ERR_INVALID_DATA; + lsmash_vc1_specific_parameters_t *param = (lsmash_vc1_specific_parameters_t *)dst->data.structured; + uint8_t *data = src->data.unstructured; + uint64_t size = LSMASH_GET_BE32( data ); + data += ISOM_BASEBOX_COMMON_SIZE; + if( size == 1 ) + { + size = LSMASH_GET_BE64( data ); + data += 8; + } + if( size != src->size ) + return LSMASH_ERR_INVALID_DATA; + param->profile = (data[0] >> 4) & 0x0F; + if( param->profile != 12 ) + return LSMASH_ERR_PATCH_WELCOME; /* We don't support profile other than 12 (Advanced profile). */ + param->level = (data[0] >> 1) & 0x07; + param->cbr = (data[1] >> 4) & 0x01; + param->interlaced = !((data[2] >> 5) & 0x01); + param->multiple_sequence = !((data[2] >> 4) & 0x01); + param->multiple_entry = !((data[2] >> 3) & 0x01); + param->slice_present = !((data[2] >> 2) & 0x01); + param->bframe_present = !((data[2] >> 1) & 0x01); + param->framerate = LSMASH_GET_BE32( &data[3] ); + /* Try to get seqhdr_ephdr[]. */ + if( !param->seqhdr ) + { + param->seqhdr = lsmash_malloc_zero( sizeof(lsmash_vc1_header_t) ); + if( !param->seqhdr ) + return LSMASH_ERR_MEMORY_ALLOC; + } + if( !param->ephdr ) + { + param->ephdr = lsmash_malloc_zero( sizeof(lsmash_vc1_header_t) ); + if( !param->ephdr ) + return LSMASH_ERR_MEMORY_ALLOC; + } + lsmash_vc1_header_t *seqhdr = param->seqhdr; + lsmash_vc1_header_t *ephdr = param->ephdr; + data += 7; + uint8_t *pos = data; + uint8_t *end = src->data.unstructured + src->size; + /* Find the start point of Sequence header EBDU. */ + while( pos < end ) + { + if( vc1_check_next_start_code_prefix( pos, end ) && (pos + 3 < end) && *(pos + 3) == 0x0F ) + { + seqhdr->ebdu_size = 4; + pos += 4; + break; + } + ++pos; + } + /* Find the end point of Sequence header EBDU. */ + while( pos < end ) + { + if( vc1_check_next_start_code_prefix( pos, end ) ) + break; + ++ seqhdr->ebdu_size; + } + /* Find the start point of Entry-point header EBDU. */ + while( pos < end ) + { + if( vc1_check_next_start_code_prefix( pos, end ) && (pos + 3 < end) && *(pos + 3) == 0x0E ) + { + ephdr->ebdu_size = 4; + pos += 4; + break; + } + ++pos; + } + /* Find the end point of Entry-point header EBDU. */ + while( pos < end ) + { + if( vc1_check_next_start_code_prefix( pos, end ) ) + break; + ++ ephdr->ebdu_size; + } + /* Append the Sequence header EBDU and Entry-point header EBDU if present. */ + if( seqhdr->ebdu_size ) + { + lsmash_free( seqhdr->ebdu ); + seqhdr->ebdu = lsmash_memdup( data, seqhdr->ebdu_size ); + if( !seqhdr->ebdu ) + return LSMASH_ERR_MEMORY_ALLOC; + } + if( ephdr->ebdu_size ) + { + lsmash_free( ephdr->ebdu ); + ephdr->ebdu = lsmash_memdup( data, ephdr->ebdu_size ); + if( !ephdr->ebdu ) + return LSMASH_ERR_MEMORY_ALLOC; + } + return 0; +} + +int vc1_copy_codec_specific( lsmash_codec_specific_t *dst, lsmash_codec_specific_t *src ) +{ + assert( src && src->format == LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED && src->data.structured ); + assert( dst && dst->format == LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED && dst->data.structured ); + lsmash_vc1_specific_parameters_t *src_data = (lsmash_vc1_specific_parameters_t *)src->data.structured; + lsmash_vc1_specific_parameters_t *dst_data = (lsmash_vc1_specific_parameters_t *)dst->data.structured; + lsmash_destroy_vc1_headers( dst_data ); + *dst_data = *src_data; + if( !src_data->seqhdr && !src_data->ephdr ) + return 0; + if( src_data->seqhdr ) + { + dst_data->seqhdr = lsmash_malloc_zero( sizeof(lsmash_vc1_header_t) ); + if( !dst_data->seqhdr ) + return LSMASH_ERR_MEMORY_ALLOC; + if( src_data->seqhdr->ebdu_size ) + { + dst_data->seqhdr->ebdu = lsmash_memdup( src_data->seqhdr->ebdu, src_data->seqhdr->ebdu_size ); + if( !dst_data->seqhdr->ebdu ) + { + lsmash_destroy_vc1_headers( dst_data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + } + dst_data->seqhdr->ebdu_size = src_data->seqhdr->ebdu_size; + } + if( src_data->ephdr ) + { + dst_data->ephdr = lsmash_malloc_zero( sizeof(lsmash_vc1_header_t) ); + if( !dst_data->ephdr ) + { + lsmash_destroy_vc1_headers( dst_data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + if( src_data->ephdr->ebdu_size ) + { + dst_data->ephdr->ebdu = lsmash_memdup( src_data->ephdr->ebdu, src_data->ephdr->ebdu_size ); + if( !dst_data->ephdr->ebdu ) + { + lsmash_destroy_vc1_headers( dst_data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + } + dst_data->ephdr->ebdu_size = src_data->ephdr->ebdu_size; + } + return 0; +} + +int vc1_print_codec_specific( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + assert( box->manager & LSMASH_BINARY_CODED_BOX ); + int indent = level; + lsmash_ifprintf( fp, indent++, "[%s: VC1 Specific Box]\n", isom_4cc2str( box->type.fourcc ) ); + lsmash_ifprintf( fp, indent, "position = %"PRIu64"\n", box->pos ); + lsmash_ifprintf( fp, indent, "size = %"PRIu64"\n", box->size ); + if( box->size < ISOM_BASEBOX_COMMON_SIZE + 7 ) + return LSMASH_ERR_INVALID_DATA; + uint8_t *data = box->binary; + isom_skip_box_common( &data ); + uint8_t profile = (data[0] >> 4) & 0x0F; + if( profile != 12 ) + return 0; /* We don't support profile other than 12 (Advanced profile). */ + lsmash_ifprintf( fp, indent, "profile = %"PRIu8"\n", profile ); + lsmash_ifprintf( fp, indent, "level = %"PRIu8"\n", (data[0] >> 1) & 0x07 ); + lsmash_ifprintf( fp, indent, "reserved = %"PRIu8"\n", data[0] & 0x01 ); + lsmash_ifprintf( fp, indent, "level = %"PRIu8"\n", (data[1] >> 5) & 0x07 ); + lsmash_ifprintf( fp, indent, "cbr = %"PRIu8"\n", (data[1] >> 4) & 0x01 ); + lsmash_ifprintf( fp, indent, "reserved1 = 0x%02"PRIx8"\n", (data[1] & 0x0F) | ((data[2] >> 6) & 0x03) ); + lsmash_ifprintf( fp, indent, "no_interlace = %"PRIu8"\n", (data[2] >> 5) & 0x01 ); + lsmash_ifprintf( fp, indent, "no_multiple_seq = %"PRIu8"\n", (data[2] >> 4) & 0x01 ); + lsmash_ifprintf( fp, indent, "no_multiple_entry = %"PRIu8"\n", (data[2] >> 3) & 0x01 ); + lsmash_ifprintf( fp, indent, "no_slice_code = %"PRIu8"\n", (data[2] >> 2) & 0x01 ); + lsmash_ifprintf( fp, indent, "no_bframe = %"PRIu8"\n", (data[2] >> 1) & 0x01 ); + lsmash_ifprintf( fp, indent, "reserved2 = %"PRIu8"\n", data[2] & 0x01 ); + uint32_t framerate = LSMASH_GET_BE32( &data[3] ); + lsmash_ifprintf( fp, indent, "framerate = %"PRIu32"\n", framerate ); + uint32_t seqhdr_ephdr_size = box->size - (data - box->binary + 7); + if( seqhdr_ephdr_size ) + { + lsmash_ifprintf( fp, indent, "seqhdr_ephdr[]\n" ); + data += 7; + for( uint32_t i = 0; i < seqhdr_ephdr_size; i += 8 ) + { + lsmash_ifprintf( fp, indent + 1, "" ); + for( uint32_t j = 0; ; j++ ) + if( j == 7 || (i + j == seqhdr_ephdr_size - 1) ) + { + fprintf( fp, "0x%02"PRIx8"\n", data[i + j] ); + break; + } + else + fprintf( fp, "0x%02"PRIx8" ", data[i + j] ); + } + } + return 0; +} diff --git a/3rdparty/l-smash/codecs/vc1.h b/3rdparty/l-smash/codecs/vc1.h new file mode 100644 index 000000000..88d3b3190 --- /dev/null +++ b/3rdparty/l-smash/codecs/vc1.h @@ -0,0 +1,134 @@ +/***************************************************************************** + * vc1.h + ***************************************************************************** + * Copyright (C) 2012-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#define VC1_DEFAULT_BUFFER_SIZE (1<<16) +#define VC1_START_CODE_PREFIX_LENGTH 3 /* 0x000001 */ +#define VC1_START_CODE_SUFFIX_LENGTH 1 /* BDU type */ +#define VC1_START_CODE_LENGTH (VC1_START_CODE_PREFIX_LENGTH + VC1_START_CODE_SUFFIX_LENGTH) /* = 4 */ + +typedef struct +{ + uint8_t hrd_num_leaky_buckets; +} vc1_hrd_param_t; + +typedef struct +{ + uint8_t present; + uint8_t profile; + uint8_t level; + uint8_t colordiff_format; /* currently 4:2:0 only */ + uint8_t interlace; + uint8_t color_prim; + uint8_t transfer_char; + uint8_t matrix_coef; + uint8_t hrd_param_flag; + uint8_t aspect_width; + uint8_t aspect_height; + uint8_t framerate_flag; + uint32_t framerate_numerator; + uint32_t framerate_denominator; + uint16_t max_coded_width; + uint16_t max_coded_height; + uint16_t disp_horiz_size; + uint16_t disp_vert_size; + vc1_hrd_param_t hrd_param; +} vc1_sequence_header_t; + +typedef struct +{ + uint8_t present; + uint8_t closed_entry_point; +} vc1_entry_point_t; + +typedef struct +{ + uint8_t present; + uint8_t frame_coding_mode; + uint8_t type; + uint8_t closed_gop; + uint8_t start_of_sequence; + uint8_t random_accessible; +} vc1_picture_info_t; + +typedef struct +{ + uint8_t random_accessible; + uint8_t closed_gop; + uint8_t independent; + uint8_t non_bipredictive; + uint8_t disposable; + uint8_t *data; + uint32_t data_length; + uint8_t *incomplete_data; + uint32_t incomplete_data_length; + uint32_t number; +} vc1_access_unit_t; + +typedef struct vc1_info_tag vc1_info_t; + +typedef struct +{ + lsmash_multiple_buffers_t *bank; + uint8_t *rbdu; +} vc1_stream_buffer_t; + +struct vc1_info_tag +{ + lsmash_vc1_specific_parameters_t dvc1_param; + vc1_sequence_header_t sequence; + vc1_entry_point_t entry_point; + vc1_picture_info_t picture; + vc1_access_unit_t access_unit; + uint8_t prev_bdu_type; + uint64_t ebdu_head_pos; + lsmash_bits_t *bits; + vc1_stream_buffer_t buffer; +}; + +int vc1_setup_parser +( + vc1_info_t *info, + int parse_only +); + +uint64_t vc1_find_next_start_code_prefix +( + lsmash_bs_t *bs, + uint8_t *bdu_type, + uint64_t *trailing_zero_bytes +); + +int vc1_check_next_start_code_suffix +( + lsmash_bs_t *bs, + uint8_t *p_bdu_type +); + +void vc1_cleanup_parser( vc1_info_t *info ); +int vc1_parse_sequence_header( vc1_info_t *info, uint8_t *ebdu, uint64_t ebdu_size, int probe ); +int vc1_parse_entry_point_header( vc1_info_t *info, uint8_t *ebdu, uint64_t ebdu_size, int probe ); +int vc1_parse_advanced_picture( lsmash_bits_t *bits, + vc1_sequence_header_t *sequence, vc1_picture_info_t *picture, + uint8_t *rbdu_buffer, uint8_t *ebdu, uint64_t ebdu_size ); +void vc1_update_au_property( vc1_access_unit_t *access_unit, vc1_picture_info_t *picture ); +int vc1_find_au_delimit_by_bdu_type( uint8_t bdu_type, uint8_t prev_bdu_type ); +int vc1_supplement_buffer( vc1_stream_buffer_t *buffer, vc1_access_unit_t *access_unit, uint32_t size ); diff --git a/3rdparty/l-smash/codecs/wma.c b/3rdparty/l-smash/codecs/wma.c new file mode 100644 index 000000000..604eb115b --- /dev/null +++ b/3rdparty/l-smash/codecs/wma.c @@ -0,0 +1,86 @@ +/***************************************************************************** + * wma.c + ***************************************************************************** + * Copyright (C) 2014-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include + +#include "core/box.h" + +#define WFEX_BOX_MIN_LENGTH 26 + +#define WAVE_FORMAT_TAG_ID_WMA_V2 0x0161 +#define WAVE_FORMAT_TAG_ID_WMA_V3 0x0162 + +int wma_print_codec_specific( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + assert( box->manager & LSMASH_BINARY_CODED_BOX ); + int indent = level; + lsmash_ifprintf( fp, indent++, "[%s: General Extended Waveform Format Box]\n", isom_4cc2str( box->type.fourcc ) ); + lsmash_ifprintf( fp, indent, "position = %"PRIu64"\n", box->pos ); + lsmash_ifprintf( fp, indent, "size = %"PRIu64"\n", box->size ); + if( box->size < WFEX_BOX_MIN_LENGTH ) + return LSMASH_ERR_INVALID_DATA; + uint8_t *data = box->binary; + isom_skip_box_common( &data ); + uint16_t wFormatTag = LSMASH_GET_LE16( &data[0] ); + static const char *codec_name[] = + { + "Windows Media Audio V2", + "Windows Media Audio V3" + }; + if( wFormatTag == WAVE_FORMAT_TAG_ID_WMA_V2 + || wFormatTag == WAVE_FORMAT_TAG_ID_WMA_V3 ) + lsmash_ifprintf( fp, indent, "wFormatTag = 0x%04"PRIx16" (%s)\n", wFormatTag, codec_name[wFormatTag - WAVE_FORMAT_TAG_ID_WMA_V2] ); + else + lsmash_ifprintf( fp, indent, "wFormatTag = 0x%04"PRIx16"\n", wFormatTag ); + lsmash_ifprintf( fp, indent, "nChannels = %"PRIu16"\n", LSMASH_GET_LE16( &data[ 2] ) ); + lsmash_ifprintf( fp, indent, "nSamplesPerSec = %"PRIu32"\n", LSMASH_GET_LE32( &data[ 4] ) ); + lsmash_ifprintf( fp, indent, "nAvgBytesPerSec = %"PRIu32"\n", LSMASH_GET_LE32( &data[ 8] ) ); + lsmash_ifprintf( fp, indent, "nBlockAlign = %"PRIu16"\n", LSMASH_GET_LE16( &data[12] ) ); + lsmash_ifprintf( fp, indent, "wBitsPerSample = %"PRIu16"\n", LSMASH_GET_LE16( &data[14] ) ); + uint16_t cbSize = LSMASH_GET_BYTE( &data[16] ); + lsmash_ifprintf( fp, indent, "cbSize = %"PRIu16"\n", cbSize ); + switch( wFormatTag ) + { + case WAVE_FORMAT_TAG_ID_WMA_V2 : + if( cbSize < 10 ) + return LSMASH_ERR_INVALID_DATA; + lsmash_ifprintf( fp, indent, "dwSamplesPerBlock = %"PRIu32"\n", LSMASH_GET_LE32( &data[18] ) ); + lsmash_ifprintf( fp, indent, "wEncodeOptions = 0x%04"PRIu16"\n", LSMASH_GET_LE16( &data[22] ) ); + lsmash_ifprintf( fp, indent, "dwSuperBlockAlign = %"PRIu32"\n", LSMASH_GET_LE32( &data[24] ) ); + break; + case WAVE_FORMAT_TAG_ID_WMA_V3 : + if( cbSize < 18 ) + return LSMASH_ERR_INVALID_DATA; + lsmash_ifprintf( fp, indent, "wValidBitsPerSample = %"PRIu16"\n", LSMASH_GET_LE16( &data[18] ) ); + lsmash_ifprintf( fp, indent, "dwChannelMask = 0x%08"PRIu32"\n", LSMASH_GET_LE32( &data[20] ) ); + lsmash_ifprintf( fp, indent, "dwReserved1 = 0x%08"PRIu32"\n", LSMASH_GET_LE32( &data[24] ) ); + lsmash_ifprintf( fp, indent, "dwReserved2 = 0x%08"PRIu32"\n", LSMASH_GET_LE32( &data[28] ) ); + lsmash_ifprintf( fp, indent, "wEncodeOptions = 0x%04"PRIu16"\n", LSMASH_GET_LE16( &data[32] ) ); + lsmash_ifprintf( fp, indent, "wReserved3 = 0x%04"PRIu16"\n", LSMASH_GET_LE16( &data[34] ) ); + break; + default : + break; + } + return 0; +} diff --git a/3rdparty/l-smash/common/alloc.c b/3rdparty/l-smash/common/alloc.c new file mode 100644 index 000000000..fb2c09299 --- /dev/null +++ b/3rdparty/l-smash/common/alloc.c @@ -0,0 +1,73 @@ +/***************************************************************************** + * alloc.c + ***************************************************************************** + * Copyright (C) 2011-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "internal.h" /* must be placed first */ + +#include +#include + +void *lsmash_malloc( size_t size ) +{ + return malloc( size ); +} + +void *lsmash_malloc_zero( size_t size ) +{ + if( !size ) + return NULL; + void *p = malloc( size ); + if( !p ) + return NULL; + memset( p, 0, size ); + return p; +} + +void *lsmash_realloc( void *ptr, size_t size ) +{ + return realloc( ptr, size ); +} + +void *lsmash_memdup( const void *ptr, size_t size ) +{ + if( !ptr || size == 0 ) + return NULL; + void *dst = malloc( size ); + if( !dst ) + return NULL; + memcpy( dst, ptr, size ); + return dst; +} + +void lsmash_free( void *ptr ) +{ + /* free() shall do nothing if a given address is NULL. */ + free( ptr ); +} + +void lsmash_freep( void *ptrptr ) +{ + if( !ptrptr ) + return; + void **ptr = (void **)ptrptr; + free( *ptr ); + *ptr = NULL; +} diff --git a/3rdparty/l-smash/common/bits.c b/3rdparty/l-smash/common/bits.c new file mode 100644 index 000000000..d99b8375d --- /dev/null +++ b/3rdparty/l-smash/common/bits.c @@ -0,0 +1,194 @@ +/***************************************************************************** + * bits.c + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Takashi Hirata + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "internal.h" /* must be placed first */ + +void lsmash_bits_init( lsmash_bits_t *bits, lsmash_bs_t *bs ) +{ + debug_if( !bits || !bs ) + return; + bits->bs = bs; + bits->store = 0; + bits->cache = 0; +} + +lsmash_bits_t *lsmash_bits_create( lsmash_bs_t *bs ) +{ + debug_if( !bs ) + return NULL; + lsmash_bits_t *bits = (lsmash_bits_t *)lsmash_malloc( sizeof(lsmash_bits_t) ); + if( !bits ) + return NULL; + lsmash_bits_init( bits, bs ); + return bits; +} + +/* Must be used ONLY for bits struct created with isom_create_bits. + * Otherwise, just lsmash_free() the bits struct. */ +void lsmash_bits_cleanup( lsmash_bits_t *bits ) +{ + debug_if( !bits ) + return; + lsmash_free( bits ); +} + +void lsmash_bits_empty( lsmash_bits_t *bits ) +{ + debug_if( !bits ) + return; + lsmash_bs_empty( bits->bs ); + bits->store = 0; + bits->cache = 0; +} + +#define BITS_IN_BYTE 8 +void lsmash_bits_put_align( lsmash_bits_t *bits ) +{ + debug_if( !bits ) + return; + if( !bits->store ) + return; + lsmash_bs_put_byte( bits->bs, bits->cache << ( BITS_IN_BYTE - bits->store ) ); +} + +void lsmash_bits_get_align( lsmash_bits_t *bits ) +{ + debug_if( !bits ) + return; + bits->store = 0; + bits->cache = 0; +} + +/* we can change value's type to unsigned int for 64-bit operation if needed. */ +static inline uint8_t lsmash_bits_mask_lsb8( uint32_t value, uint32_t width ) +{ + return (uint8_t)( value & ~( ~0U << width ) ); +} + +void lsmash_bits_put( lsmash_bits_t *bits, uint32_t width, uint64_t value ) +{ + debug_if( !bits || !width ) + return; + if( bits->store ) + { + if( bits->store + width < BITS_IN_BYTE ) + { + /* cache can contain all of value's bits. */ + bits->cache <<= width; + bits->cache |= lsmash_bits_mask_lsb8( value, width ); + bits->store += width; + return; + } + /* flush cache with value's some leading bits. */ + uint32_t free_bits = BITS_IN_BYTE - bits->store; + bits->cache <<= free_bits; + bits->cache |= lsmash_bits_mask_lsb8( value >> (width -= free_bits), free_bits ); + lsmash_bs_put_byte( bits->bs, bits->cache ); + bits->store = 0; + bits->cache = 0; + } + /* cache is empty here. */ + /* byte unit operation. */ + while( width > BITS_IN_BYTE ) + lsmash_bs_put_byte( bits->bs, (uint8_t)(value >> (width -= BITS_IN_BYTE)) ); + /* bit unit operation for residual. */ + if( width ) + { + bits->cache = lsmash_bits_mask_lsb8( value, width ); + bits->store = width; + } +} + +uint64_t lsmash_bits_get( lsmash_bits_t *bits, uint32_t width ) +{ + debug_if( !bits || !width ) + return 0; + uint64_t value = 0; + if( bits->store ) + { + if( bits->store >= width ) + { + /* cache contains all of bits required. */ + bits->store -= width; + return lsmash_bits_mask_lsb8( bits->cache >> bits->store, width ); + } + /* fill value's leading bits with cache's residual. */ + value = lsmash_bits_mask_lsb8( bits->cache, bits->store ); + width -= bits->store; + bits->store = 0; + bits->cache = 0; + } + /* cache is empty here. */ + /* byte unit operation. */ + while( width > BITS_IN_BYTE ) + { + value <<= BITS_IN_BYTE; + width -= BITS_IN_BYTE; + value |= lsmash_bs_get_byte( bits->bs ); + } + /* bit unit operation for residual. */ + if( width ) + { + bits->cache = lsmash_bs_get_byte( bits->bs ); + bits->store = BITS_IN_BYTE - width; + value <<= width; + value |= lsmash_bits_mask_lsb8( bits->cache >> bits->store, width ); + } + return value; +} + +void *lsmash_bits_export_data( lsmash_bits_t *bits, uint32_t *length ) +{ + lsmash_bits_put_align( bits ); + return lsmash_bs_export_data( bits->bs, length ); +} + +int lsmash_bits_import_data( lsmash_bits_t *bits, void *data, uint32_t length ) +{ + return lsmash_bs_import_data( bits->bs, data, length ); +} + +/**** + bitstream with bytestream for adhoc operation +****/ + +lsmash_bits_t *lsmash_bits_adhoc_create() +{ + lsmash_bs_t *bs = lsmash_bs_create(); + if( !bs ) + return NULL; + lsmash_bits_t *bits = lsmash_bits_create( bs ); + if( !bits ) + { + lsmash_bs_cleanup( bs ); + return NULL; + } + return bits; +} + +void lsmash_bits_adhoc_cleanup( lsmash_bits_t *bits ) +{ + if( !bits ) + return; + lsmash_bs_cleanup( bits->bs ); + lsmash_bits_cleanup( bits ); +} diff --git a/3rdparty/l-smash/common/bits.h b/3rdparty/l-smash/common/bits.h new file mode 100644 index 000000000..f6028e822 --- /dev/null +++ b/3rdparty/l-smash/common/bits.h @@ -0,0 +1,42 @@ +/***************************************************************************** + * bits.h + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Takashi Hirata + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +typedef struct +{ + lsmash_bs_t *bs; + uint8_t store; + uint8_t cache; +} lsmash_bits_t; + +void lsmash_bits_init( lsmash_bits_t* bits, lsmash_bs_t *bs ); +lsmash_bits_t *lsmash_bits_create( lsmash_bs_t *bs ); +void lsmash_bits_cleanup( lsmash_bits_t *bits ); +void lsmash_bits_empty( lsmash_bits_t *bits ); +void lsmash_bits_put_align( lsmash_bits_t *bits ); +void lsmash_bits_get_align( lsmash_bits_t *bits ); +void lsmash_bits_put( lsmash_bits_t *bits, uint32_t width, uint64_t value ); +uint64_t lsmash_bits_get( lsmash_bits_t *bits, uint32_t width ); +void *lsmash_bits_export_data( lsmash_bits_t *bits, uint32_t *length ); +int lsmash_bits_import_data( lsmash_bits_t *bits, void *data, uint32_t length ); + +lsmash_bits_t *lsmash_bits_adhoc_create(); +void lsmash_bits_adhoc_cleanup( lsmash_bits_t *bits ); diff --git a/3rdparty/l-smash/common/bytes.c b/3rdparty/l-smash/common/bytes.c new file mode 100644 index 000000000..d3a666376 --- /dev/null +++ b/3rdparty/l-smash/common/bytes.c @@ -0,0 +1,707 @@ +/***************************************************************************** + * bytes.c + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "internal.h" /* must be placed first */ + +#include +#include + +lsmash_bs_t *lsmash_bs_create( void ) +{ + lsmash_bs_t *bs = lsmash_malloc_zero( sizeof(lsmash_bs_t) ); + if( !bs ) + return NULL; + bs->unseekable = 1; + bs->buffer.internal = 1; + bs->buffer.max_size = BS_MAX_DEFAULT_READ_SIZE; + return bs; +} + +static void bs_buffer_free( lsmash_bs_t *bs ) +{ + if( bs->buffer.internal ) + lsmash_free( bs->buffer.data ); + bs->buffer.data = NULL; + bs->buffer.alloc = 0; + bs->buffer.store = 0; + bs->buffer.pos = 0; +} + +void lsmash_bs_cleanup( lsmash_bs_t *bs ) +{ + if( !bs ) + return; + bs_buffer_free( bs ); + lsmash_free( bs ); +} + +int lsmash_bs_set_empty_stream( lsmash_bs_t *bs, uint8_t *data, size_t size ) +{ + if( !bs ) + return LSMASH_ERR_FUNCTION_PARAM; + bs->stream = NULL; /* empty stream */ + bs->eof = 1; /* unreadable because of empty stream */ + bs->eob = 0; /* readable on the buffer */ + bs->error = 0; + bs->unseekable = 1; /* only seek on the buffer */ + bs->written = size; /* behave as if the size of the empty stream is 'size' */ + bs->offset = size; /* behave as if the poiter of the stream is at the end */ + bs->buffer.unseekable = 0; /* only seek on the buffer */ + bs->buffer.internal = 0; /* must not be allocated internally */ + bs->buffer.data = data; + bs->buffer.store = size; + bs->buffer.alloc = size; + bs->buffer.pos = 0; + bs->buffer.max_size = 0; /* make no sense */ + bs->buffer.count = 0; + bs->read = NULL; + bs->write = NULL; + bs->seek = NULL; + return 0; +} + +void lsmash_bs_empty( lsmash_bs_t *bs ) +{ + if( !bs ) + return; + if( bs->buffer.data ) + memset( bs->buffer.data, 0, bs->buffer.alloc ); + bs->buffer.store = 0; + bs->buffer.pos = 0; +} + +static void bs_alloc( lsmash_bs_t *bs, size_t alloc ) +{ + if( (bs->buffer.alloc >= alloc) || bs->error ) + return; + if( !bs->buffer.internal ) + { + /* We cannot re-allocate the memory block. */ + bs->error = 1; + return; + } + alloc = LSMASH_MAX( alloc, bs->buffer.max_size ); + uint8_t *data; + if( !bs->buffer.data ) + data = lsmash_malloc( alloc ); + else + data = lsmash_realloc( bs->buffer.data, alloc ); + if( !data ) + { + bs_buffer_free( bs ); + bs->error = 1; + return; + } + bs->buffer.internal = 1; + bs->buffer.data = data; + bs->buffer.alloc = alloc; +} + +static uint64_t bs_estimate_seek_offset( lsmash_bs_t *bs, int64_t offset, int whence ) +{ + /* Calculate the offset after the seek. */ + uint64_t dst_offset; + if( whence == SEEK_SET ) + { + assert( offset >= 0 ); + if( bs->written < offset ) + dst_offset = bs->written; + else + dst_offset = offset; + } + else if( whence == SEEK_CUR ) + { + if( offset < 0 && bs->offset < -offset ) + dst_offset = 0; + else if( offset > 0 && bs->written < bs->offset + offset ) + dst_offset = bs->written; + else + dst_offset = bs->offset + offset; + } + else /* if( whence == SEEK_END ) */ + { + assert( offset <= 0 ); + if( bs->written < -offset ) + dst_offset = 0; + else + dst_offset = bs->written + offset; + } + return dst_offset; +} + +/* TODO: Support offset > INT64_MAX */ +int64_t lsmash_bs_write_seek( lsmash_bs_t *bs, int64_t offset, int whence ) +{ + if( bs->unseekable ) + return LSMASH_ERR_NAMELESS; + if( whence != SEEK_SET && whence != SEEK_CUR && whence != SEEK_END ) + return LSMASH_ERR_FUNCTION_PARAM; + /* Try to seek the stream. */ + int64_t ret = bs->seek( bs->stream, offset, whence ); + if( ret < 0 ) + return ret; + bs->offset = bs_estimate_seek_offset( bs, offset, whence ); + bs->eof = 0; + bs->eob = 0; + return ret; +} + +/* TODO: Support offset > INT64_MAX */ +int64_t lsmash_bs_read_seek( lsmash_bs_t *bs, int64_t offset, int whence ) +{ + if( whence != SEEK_SET && whence != SEEK_CUR && whence != SEEK_END ) + return LSMASH_ERR_FUNCTION_PARAM; + if( whence == SEEK_CUR ) + offset -= lsmash_bs_get_remaining_buffer_size( bs ); + /* Check whether we can seek on the buffer. */ + if( !bs->buffer.unseekable ) + { + assert( bs->offset >= bs->buffer.store ); + uint64_t dst_offset = bs_estimate_seek_offset( bs, offset, whence ); + uint64_t offset_s = bs->offset - bs->buffer.store; + uint64_t offset_e = bs->offset; + if( bs->unseekable || (dst_offset >= offset_s && dst_offset < offset_e) ) + { + /* OK, we can. So, seek on the buffer. */ + bs->buffer.pos = dst_offset - offset_s; + bs->eob = 0; + return lsmash_bs_get_stream_pos( bs ); + } + } + if( bs->unseekable ) + return LSMASH_ERR_NAMELESS; + /* Try to seek the stream. */ + int64_t ret = bs->seek( bs->stream, offset, whence ); + if( ret < 0 ) + return ret; + bs->offset = ret; + bs->written = LSMASH_MAX( bs->written, bs->offset ); + bs->eof = 0; + bs->eob = 0; + /* The data on the buffer is invalid. */ + lsmash_bs_empty( bs ); + return ret; +} + +void lsmash_bs_dispose_past_data( lsmash_bs_t *bs ) +{ + /* Move remainder bytes. */ + assert( bs->buffer.store >= bs->buffer.pos ); + size_t remainder = lsmash_bs_get_remaining_buffer_size( bs ); + if( bs->buffer.pos && remainder ) + memmove( lsmash_bs_get_buffer_data_start( bs ), lsmash_bs_get_buffer_data( bs ), remainder ); + bs->buffer.store = remainder; + bs->buffer.pos = 0; +} + +/*---- bitstream writer ----*/ +void lsmash_bs_put_byte( lsmash_bs_t *bs, uint8_t value ) +{ + if( bs->buffer.internal + || bs->buffer.data ) + { + bs_alloc( bs, bs->buffer.store + 1 ); + if( bs->error ) + return; + bs->buffer.data[ bs->buffer.store ] = value; + } + ++ bs->buffer.store; +} + +void lsmash_bs_put_bytes( lsmash_bs_t *bs, uint32_t size, void *value ) +{ + if( size == 0 || !value ) + return; + if( bs->buffer.internal + || bs->buffer.data ) + { + bs_alloc( bs, bs->buffer.store + size ); + if( bs->error ) + return; + memcpy( lsmash_bs_get_buffer_data_end( bs ), value, size ); + } + bs->buffer.store += size; +} + +void lsmash_bs_put_be16( lsmash_bs_t *bs, uint16_t value ) +{ + lsmash_bs_put_byte( bs, value >> 8 ); + lsmash_bs_put_byte( bs, value ); +} + +void lsmash_bs_put_be24( lsmash_bs_t *bs, uint32_t value ) +{ + lsmash_bs_put_byte( bs, value >> 16 ); + lsmash_bs_put_be16( bs, value ); +} + +void lsmash_bs_put_be32( lsmash_bs_t *bs, uint32_t value ) +{ + lsmash_bs_put_be16( bs, value >> 16 ); + lsmash_bs_put_be16( bs, value ); +} + +void lsmash_bs_put_be64( lsmash_bs_t *bs, uint64_t value ) +{ + lsmash_bs_put_be32( bs, value >> 32 ); + lsmash_bs_put_be32( bs, value ); +} + +void lsmash_bs_put_byte_from_64( lsmash_bs_t *bs, uint64_t value ) +{ + lsmash_bs_put_byte( bs, value ); +} + +void lsmash_bs_put_be16_from_64( lsmash_bs_t *bs, uint64_t value ) +{ + lsmash_bs_put_be16( bs, value ); +} + +void lsmash_bs_put_be24_from_64( lsmash_bs_t *bs, uint64_t value ) +{ + lsmash_bs_put_be24( bs, value ); +} + +void lsmash_bs_put_be32_from_64( lsmash_bs_t *bs, uint64_t value ) +{ + lsmash_bs_put_be32( bs, value ); +} + +void lsmash_bs_put_le16( lsmash_bs_t *bs, uint16_t value ) +{ + lsmash_bs_put_byte( bs, value ); + lsmash_bs_put_byte( bs, value >> 8 ); +} + +void lsmash_bs_put_le32( lsmash_bs_t *bs, uint32_t value ) +{ + lsmash_bs_put_le16( bs, value ); + lsmash_bs_put_le16( bs, value >> 16 ); +} + +int lsmash_bs_flush_buffer( lsmash_bs_t *bs ) +{ + if( !bs ) + return LSMASH_ERR_FUNCTION_PARAM; + if( bs->buffer.store == 0 + || (bs->stream && bs->write && !bs->buffer.data) ) + return 0; + if( bs->error + || (bs->stream && bs->write && bs->write( bs->stream, lsmash_bs_get_buffer_data_start( bs ), bs->buffer.store ) != bs->buffer.store) ) + { + bs_buffer_free( bs ); + bs->error = 1; + return LSMASH_ERR_NAMELESS; + } + if( bs->write ) + { + bs->written += bs->buffer.store; + bs->offset += bs->buffer.store; + } + bs->buffer.store = 0; + return 0; +} + +int lsmash_bs_write_data( lsmash_bs_t *bs, const uint8_t *buf, size_t size ) +{ + if( !bs || size > INT_MAX ) + return LSMASH_ERR_FUNCTION_PARAM; + if( !buf || size == 0 ) + return 0; + if( bs->error || !bs->stream ) + { + bs_buffer_free( bs ); + bs->error = 1; + return LSMASH_ERR_NAMELESS; + } + int write_size = bs->write( bs->stream, (uint8_t *)buf, size ); + bs->written += write_size; + bs->offset += write_size; + return write_size != size ? LSMASH_ERR_NAMELESS : 0; +} + +void *lsmash_bs_export_data( lsmash_bs_t *bs, uint32_t *length ) +{ + if( !bs || !bs->buffer.data || bs->buffer.store == 0 || bs->error ) + return NULL; + void *buf = lsmash_memdup( lsmash_bs_get_buffer_data_start( bs ), bs->buffer.store ); + if( !buf ) + return NULL; + if( length ) + *length = bs->buffer.store; + return buf; +} +/*---- ----*/ + +/*---- bitstream reader ----*/ +static void bs_fill_buffer( lsmash_bs_t *bs ) +{ + if( bs->eof || bs->error ) + return; + if( !bs->read || !bs->stream || bs->buffer.max_size == 0 ) + { + bs->eof = 1; + return; + } + if( !bs->buffer.data ) + { + bs_alloc( bs, bs->buffer.max_size ); + if( bs->error ) + return; + } + /* Read bytes from the stream to fill the buffer. */ + lsmash_bs_dispose_past_data( bs ); + while( bs->buffer.alloc > bs->buffer.store ) + { + uint64_t invalid_buffer_size = bs->buffer.alloc - bs->buffer.store; + int max_read_size = LSMASH_MIN( invalid_buffer_size, bs->buffer.max_size ); + int read_size = bs->read( bs->stream, lsmash_bs_get_buffer_data_end( bs ), max_read_size ); + if( read_size == 0 ) + { + bs->eof = 1; + return; + } + else if( read_size < 0 ) + { + bs->error = 1; + return; + } + bs->buffer.unseekable = 0; + bs->buffer.store += read_size; + bs->offset += read_size; + bs->written = LSMASH_MAX( bs->written, bs->offset ); + } +} + +uint8_t lsmash_bs_show_byte( lsmash_bs_t *bs, uint32_t offset ) +{ + if( bs->error ) + return 0; + if( offset >= lsmash_bs_get_remaining_buffer_size( bs ) ) + { + bs_fill_buffer( bs ); + if( bs->error ) + return 0; + if( offset >= lsmash_bs_get_remaining_buffer_size( bs ) ) + { + if( bs->eof ) + /* No more read from both the stream and the buffer. */ + return 0; + /* We need increase the buffer size. */ + bs_alloc( bs, bs->buffer.pos + offset + 1 ); + bs_fill_buffer( bs ); + if( bs->error ) + return 0; + } + } + return bs->buffer.data[ bs->buffer.pos + offset ]; +} + +uint16_t lsmash_bs_show_be16( lsmash_bs_t *bs, uint32_t offset ) +{ + return ((uint16_t)lsmash_bs_show_byte( bs, offset ) << 8) + | ((uint16_t)lsmash_bs_show_byte( bs, offset + 1 )); +} + +uint32_t lsmash_bs_show_be24( lsmash_bs_t *bs, uint32_t offset ) +{ + return ((uint32_t)lsmash_bs_show_byte( bs, offset ) << 16) + | ((uint32_t)lsmash_bs_show_byte( bs, offset + 1 ) << 8) + | ((uint32_t)lsmash_bs_show_byte( bs, offset + 2 )); +} + +uint32_t lsmash_bs_show_be32( lsmash_bs_t *bs, uint32_t offset ) +{ + return ((uint32_t)lsmash_bs_show_byte( bs, offset ) << 24) + | ((uint32_t)lsmash_bs_show_byte( bs, offset + 1 ) << 16) + | ((uint32_t)lsmash_bs_show_byte( bs, offset + 2 ) << 8) + | ((uint32_t)lsmash_bs_show_byte( bs, offset + 3 )); +} + +uint64_t lsmash_bs_show_be64( lsmash_bs_t *bs, uint32_t offset ) +{ + return ((uint64_t)lsmash_bs_show_byte( bs, offset ) << 56) + | ((uint64_t)lsmash_bs_show_byte( bs, offset + 1 ) << 48) + | ((uint64_t)lsmash_bs_show_byte( bs, offset + 2 ) << 40) + | ((uint64_t)lsmash_bs_show_byte( bs, offset + 3 ) << 32) + | ((uint64_t)lsmash_bs_show_byte( bs, offset + 4 ) << 24) + | ((uint64_t)lsmash_bs_show_byte( bs, offset + 5 ) << 16) + | ((uint64_t)lsmash_bs_show_byte( bs, offset + 6 ) << 8) + | ((uint64_t)lsmash_bs_show_byte( bs, offset + 7 )); +} + +uint8_t lsmash_bs_get_byte( lsmash_bs_t *bs ) +{ + if( bs->eob || bs->error ) + return 0; + assert( bs->buffer.pos <= bs->buffer.store ); + if( bs->buffer.pos == bs->buffer.store ) + { + bs_fill_buffer( bs ); + if( bs->error ) + return 0; + if( bs->buffer.pos == bs->buffer.store && bs->eof ) + { + /* No more read from both the stream and the buffer. */ + bs->eob = 1; + return 0; + } + } + ++ bs->buffer.count; /* increment counter */ + return bs->buffer.data[ bs->buffer.pos ++ ]; +} + +void lsmash_bs_skip_bytes( lsmash_bs_t *bs, uint32_t size ) +{ + if( bs->eob || bs->error || size == 0 ) + return; + uint64_t remainder; + uint64_t offset = 0; + while( size > lsmash_bs_get_remaining_buffer_size( bs ) ) + { + remainder = lsmash_bs_get_remaining_buffer_size( bs ); + offset += remainder; + size -= remainder; + bs->buffer.pos = bs->buffer.store; + if( bs->eof ) + { + /* No more read from both the stream and the buffer. */ + bs->eob = 1; + break; + } + else + { + bs_fill_buffer( bs ); + if( bs->error ) + break; + } + } + remainder = LSMASH_MIN( size, lsmash_bs_get_remaining_buffer_size( bs ) ); + offset += remainder; + bs->buffer.pos += remainder; + bs->buffer.count += offset; +} + +void lsmash_bs_skip_bytes_64( lsmash_bs_t *bs, uint64_t size ) +{ + while( size ) + { + uint64_t skip_bytes = LSMASH_MIN( size, UINT32_MAX ); + lsmash_bs_skip_bytes( bs, (uint32_t)skip_bytes ); + size -= skip_bytes; + if( bs->eob ) + return; + } +} + +static int64_t bs_get_bytes( lsmash_bs_t *bs, uint32_t size, uint8_t *buf ) +{ + size_t remainder; + size_t remain_size = size; + uintptr_t offset = 0; + while( remain_size > lsmash_bs_get_remaining_buffer_size( bs ) ) + { + remainder = lsmash_bs_get_remaining_buffer_size( bs ); + memcpy( buf + offset, lsmash_bs_get_buffer_data( bs ), remainder ); + offset += remainder; + remain_size -= remainder; + bs->buffer.pos = bs->buffer.store; + if( bs->eof ) + { + /* No more read from both the stream and the buffer. */ + bs->eob = 1; + break; + } + else + { + bs_fill_buffer( bs ); + if( bs->error ) + { + bs->buffer.count += offset; + return LSMASH_ERR_NAMELESS; + } + } + } + remainder = LSMASH_MIN( remain_size, lsmash_bs_get_remaining_buffer_size( bs ) ); + memcpy( buf + offset, lsmash_bs_get_buffer_data( bs ), remainder ); + offset += remainder; + bs->buffer.pos += remainder; + bs->buffer.count += offset; + if( offset < size ) + memset( buf + offset, 0, size - offset ); + return (int64_t)offset; +} + +uint8_t *lsmash_bs_get_bytes( lsmash_bs_t *bs, uint32_t size ) +{ + if( bs->eob || bs->error || size == 0 ) + return NULL; + uint8_t *value = lsmash_malloc( size ); + if( !value ) + { + bs->error = 1; + return NULL; + } + if( bs_get_bytes( bs, size, value ) < 0 ) + { + lsmash_free( value ); + return NULL; + } + return value; +} + +int64_t lsmash_bs_get_bytes_ex( lsmash_bs_t *bs, uint32_t size, uint8_t *value ) +{ + if( size == 0 ) + return 0; + if( bs->eob || bs->error ) + return LSMASH_ERR_NAMELESS; + return bs_get_bytes( bs, size, value ); +} + +uint16_t lsmash_bs_get_be16( lsmash_bs_t *bs ) +{ + uint16_t value = lsmash_bs_get_byte( bs ); + return (value<<8) | lsmash_bs_get_byte( bs ); +} + +uint32_t lsmash_bs_get_be24( lsmash_bs_t *bs ) +{ + uint32_t value = lsmash_bs_get_byte( bs ); + return (value<<16) | lsmash_bs_get_be16( bs ); +} + +uint32_t lsmash_bs_get_be32( lsmash_bs_t *bs ) +{ + uint32_t value = lsmash_bs_get_be16( bs ); + return (value<<16) | lsmash_bs_get_be16( bs ); +} + +uint64_t lsmash_bs_get_be64( lsmash_bs_t *bs ) +{ + uint64_t value = lsmash_bs_get_be32( bs ); + return (value<<32) | lsmash_bs_get_be32( bs ); +} + +uint64_t lsmash_bs_get_byte_to_64( lsmash_bs_t *bs ) +{ + return lsmash_bs_get_byte( bs ); +} + +uint64_t lsmash_bs_get_be16_to_64( lsmash_bs_t *bs ) +{ + return lsmash_bs_get_be16( bs ); +} + +uint64_t lsmash_bs_get_be24_to_64( lsmash_bs_t *bs ) +{ + return lsmash_bs_get_be24( bs ); +} + +uint64_t lsmash_bs_get_be32_to_64( lsmash_bs_t *bs ) +{ + return lsmash_bs_get_be32( bs ); +} + +uint16_t lsmash_bs_get_le16( lsmash_bs_t *bs ) +{ + uint16_t value = lsmash_bs_get_byte( bs ); + return value | (lsmash_bs_get_byte( bs ) << 8); +} + +uint32_t lsmash_bs_get_le32( lsmash_bs_t *bs ) +{ + uint32_t value = lsmash_bs_get_le16( bs ); + return value | (lsmash_bs_get_le16( bs ) << 16); +} + +int lsmash_bs_read( lsmash_bs_t *bs, uint32_t size ) +{ + if( !bs || size > INT_MAX ) + return LSMASH_ERR_FUNCTION_PARAM; + if( size == 0 ) + return 0; + bs_alloc( bs, bs->buffer.store + size ); + if( bs->error || !bs->stream ) + { + bs->error = 1; + return LSMASH_ERR_NAMELESS; + } + int read_size = bs->read( bs->stream, lsmash_bs_get_buffer_data_end( bs ), size ); + if( read_size == 0 ) + { + bs->eof = 1; + return 0; + } + else if( read_size < 0 ) + { + bs->error = 1; + return LSMASH_ERR_NAMELESS; + } + bs->buffer.store += read_size; + bs->offset += read_size; + bs->written = LSMASH_MAX( bs->written, bs->offset ); + return read_size; +} + +int lsmash_bs_read_data( lsmash_bs_t *bs, uint8_t *buf, size_t *size ) +{ + if( !bs || !size || *size > INT_MAX ) + return LSMASH_ERR_FUNCTION_PARAM; + if( !buf || *size == 0 ) + return 0; + if( bs->error || !bs->stream ) + { + bs->error = 1; + return LSMASH_ERR_NAMELESS; + } + int read_size = bs->read( bs->stream, buf, *size ); + if( read_size == 0 ) + bs->eof = 1; + else if( read_size < 0 ) + { + bs->error = 1; + return LSMASH_ERR_NAMELESS; + } + bs->buffer.unseekable = 1; + bs->offset += read_size; + *size = read_size; + bs->written = LSMASH_MAX( bs->written, bs->offset ); + return 0; +} + +int lsmash_bs_import_data( lsmash_bs_t *bs, void *data, uint32_t length ) +{ + if( !bs || !data || length == 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + if( bs->error ) + return LSMASH_ERR_NAMELESS; + bs_alloc( bs, bs->buffer.store + length ); + if( bs->error || !bs->buffer.data ) /* means, failed to alloc. */ + { + bs_buffer_free( bs ); + return LSMASH_ERR_NAMELESS; + } + memcpy( lsmash_bs_get_buffer_data_end( bs ), data, length ); + bs->buffer.store += length; + return 0; +} diff --git a/3rdparty/l-smash/common/bytes.h b/3rdparty/l-smash/common/bytes.h new file mode 100644 index 000000000..77f6b274d --- /dev/null +++ b/3rdparty/l-smash/common/bytes.h @@ -0,0 +1,163 @@ +/***************************************************************************** + * bytes.h + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +/*---- bytestream ----*/ +#define BS_MAX_DEFAULT_READ_SIZE (4 * 1024 * 1024) + +typedef struct +{ + int unseekable; /* If set to 1, the buffer is unseekable. */ + int internal; /* If set to 1, the buffer is allocated on heap internally. + * The pointer to the buffer shall not be changed by any method other than internal allocation. */ + uint8_t *data; /* the pointer to the buffer for reading/writing */ + size_t store; /* valid data size on the buffer */ + size_t alloc; /* total buffer size including invalid area */ + size_t pos; /* the data position on the buffer to be read next */ + size_t max_size; /* the maximum number of bytes for reading from the stream at one time */ + uint64_t count; /* counter for arbitrary usage */ +} lsmash_buffer_t; + +typedef struct +{ + void *stream; /* I/O stream */ + uint8_t eof; /* If set to 1, the stream reached EOF. */ + uint8_t eob; /* if set to 1, we cannot read more bytes from the stream and the buffer until any seek. */ + uint8_t error; /* If set to 1, any error is detected. */ + uint8_t unseekable; /* If set to 1, the stream is unseekable. */ + uint64_t written; /* the number of bytes written into 'stream' already */ + uint64_t offset; /* the current position in the 'stream' + * the number of bytes from the beginning */ + lsmash_buffer_t buffer; + int (*read) ( void *opaque, uint8_t *buf, int size ); + int (*write)( void *opaque, uint8_t *buf, int size ); + int64_t (*seek) ( void *opaque, int64_t offset, int whence ); +} lsmash_bs_t; + +static inline void lsmash_bs_reset_counter( lsmash_bs_t *bs ) +{ + bs->buffer.count = 0; +} + +static inline uint64_t lsmash_bs_count( lsmash_bs_t *bs ) +{ + return bs->buffer.count; +} + +static inline uint64_t lsmash_bs_get_remaining_buffer_size( lsmash_bs_t *bs ) +{ + assert( bs->buffer.store >= bs->buffer.pos ); + return bs->buffer.store - bs->buffer.pos; +} + +static inline uint8_t *lsmash_bs_get_buffer_data( lsmash_bs_t *bs ) +{ + return bs->buffer.data + (uintptr_t)bs->buffer.pos; +} + +static inline uint8_t *lsmash_bs_get_buffer_data_start( lsmash_bs_t *bs ) +{ + return bs->buffer.data; +} + +static inline uint8_t *lsmash_bs_get_buffer_data_end( lsmash_bs_t *bs ) +{ + return bs->buffer.data + (uintptr_t)bs->buffer.store; +} + +static inline uint64_t lsmash_bs_get_pos( lsmash_bs_t *bs ) +{ + return bs->buffer.pos; +} + +static inline uint64_t lsmash_bs_get_stream_pos( lsmash_bs_t *bs ) +{ + assert( bs->buffer.store <= bs->offset ); + return bs->offset - lsmash_bs_get_remaining_buffer_size( bs ); +} + +static inline size_t lsmash_bs_get_valid_data_size( lsmash_bs_t *bs ) +{ + return bs->buffer.store; +} + +lsmash_bs_t *lsmash_bs_create( void ); +void lsmash_bs_cleanup( lsmash_bs_t *bs ); +int lsmash_bs_set_empty_stream( lsmash_bs_t *bs, uint8_t *data, size_t size ); +void lsmash_bs_empty( lsmash_bs_t *bs ); +int64_t lsmash_bs_write_seek( lsmash_bs_t *bs, int64_t offset, int whence ); +int64_t lsmash_bs_read_seek( lsmash_bs_t *bs, int64_t offset, int whence ); +void lsmash_bs_dispose_past_data( lsmash_bs_t *bs ); + +/*---- bytestream writer ----*/ +void lsmash_bs_put_byte( lsmash_bs_t *bs, uint8_t value ); +void lsmash_bs_put_bytes( lsmash_bs_t *bs, uint32_t size, void *value ); +void lsmash_bs_put_be16( lsmash_bs_t *bs, uint16_t value ); +void lsmash_bs_put_be24( lsmash_bs_t *bs, uint32_t value ); +void lsmash_bs_put_be32( lsmash_bs_t *bs, uint32_t value ); +void lsmash_bs_put_be64( lsmash_bs_t *bs, uint64_t value ); +void lsmash_bs_put_byte_from_64( lsmash_bs_t *bs, uint64_t value ); +void lsmash_bs_put_be16_from_64( lsmash_bs_t *bs, uint64_t value ); +void lsmash_bs_put_be24_from_64( lsmash_bs_t *bs, uint64_t value ); +void lsmash_bs_put_be32_from_64( lsmash_bs_t *bs, uint64_t value ); +void lsmash_bs_put_le16( lsmash_bs_t *bs, uint16_t value ); +void lsmash_bs_put_le32( lsmash_bs_t *bs, uint32_t value ); +int lsmash_bs_flush_buffer( lsmash_bs_t *bs ); +int lsmash_bs_write_data( lsmash_bs_t *bs, const uint8_t *buf, size_t size ); +void *lsmash_bs_export_data( lsmash_bs_t *bs, uint32_t *length ); + +/*---- bytestream reader ----*/ +uint8_t lsmash_bs_show_byte( lsmash_bs_t *bs, uint32_t offset ); +uint16_t lsmash_bs_show_be16( lsmash_bs_t *bs, uint32_t offset ); +uint32_t lsmash_bs_show_be24( lsmash_bs_t *bs, uint32_t offset ); +uint32_t lsmash_bs_show_be32( lsmash_bs_t *bs, uint32_t offset ); +uint64_t lsmash_bs_show_be64( lsmash_bs_t *bs, uint32_t offset ); +uint8_t lsmash_bs_get_byte( lsmash_bs_t *bs ); +void lsmash_bs_skip_bytes( lsmash_bs_t *bs, uint32_t size ); +void lsmash_bs_skip_bytes_64( lsmash_bs_t *bs, uint64_t size ); +uint8_t *lsmash_bs_get_bytes( lsmash_bs_t *bs, uint32_t size ); +int64_t lsmash_bs_get_bytes_ex( lsmash_bs_t *bs, uint32_t size, uint8_t *value ); +uint16_t lsmash_bs_get_be16( lsmash_bs_t *bs ); +uint32_t lsmash_bs_get_be24( lsmash_bs_t *bs ); +uint32_t lsmash_bs_get_be32( lsmash_bs_t *bs ); +uint64_t lsmash_bs_get_be64( lsmash_bs_t *bs ); +uint64_t lsmash_bs_get_byte_to_64( lsmash_bs_t *bs ); +uint64_t lsmash_bs_get_be16_to_64( lsmash_bs_t *bs ); +uint64_t lsmash_bs_get_be24_to_64( lsmash_bs_t *bs ); +uint64_t lsmash_bs_get_be32_to_64( lsmash_bs_t *bs ); +uint16_t lsmash_bs_get_le16( lsmash_bs_t *bs ); +uint32_t lsmash_bs_get_le32( lsmash_bs_t *bs ); +int lsmash_bs_read( lsmash_bs_t *bs, uint32_t size ); +int lsmash_bs_read_data( lsmash_bs_t *bs, uint8_t *buf, size_t *size ); +int lsmash_bs_import_data( lsmash_bs_t *bs, void *data, uint32_t length ); + +/* Check if the given offset reaches both EOF of the stream and the end of the buffer. */ +static inline int lsmash_bs_is_end( lsmash_bs_t *bs, uint32_t offset ) +{ + lsmash_bs_show_byte( bs, offset ); + return bs->eof && (offset >= lsmash_bs_get_remaining_buffer_size( bs )); +} + +/* Check if an error has occurred. */ +static inline int lsmash_bs_is_error( lsmash_bs_t *bs ) +{ + return bs->error; +} diff --git a/3rdparty/l-smash/common/internal.h b/3rdparty/l-smash/common/internal.h new file mode 100644 index 000000000..65adad904 --- /dev/null +++ b/3rdparty/l-smash/common/internal.h @@ -0,0 +1,39 @@ +/***************************************************************************** + * internal.h + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Takashi Hirata + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#ifndef INTERNAL_H +#define INTERNAL_H + +#include "osdep.h" /* must be placed before stdio.h */ +#include +#include + +#include "lsmash.h" + +#include "utils.h" +#include "memint.h" +#include "bytes.h" +#include "bits.h" +#include "multibuf.h" +#include "list.h" + +#endif diff --git a/3rdparty/l-smash/common/list.c b/3rdparty/l-smash/common/list.c new file mode 100644 index 000000000..10f679966 --- /dev/null +++ b/3rdparty/l-smash/common/list.c @@ -0,0 +1,252 @@ +/***************************************************************************** + * list.c + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "internal.h" /* must be placed first */ + +#include + +static void lsmash_list_clear +( + lsmash_entry_list_t *list +) +{ + list->head = NULL; + list->tail = NULL; + list->last_accessed_entry = NULL; + list->last_accessed_number = 0; + list->entry_count = 0; + list->eliminator = NULL; +} + +void lsmash_list_init_orig +( + lsmash_entry_list_t *list, + lsmash_entry_data_eliminator eliminator +) +{ + assert( eliminator != NULL ); + list->head = NULL; + list->tail = NULL; + list->last_accessed_entry = NULL; + list->last_accessed_number = 0; + list->entry_count = 0; + list->eliminator = eliminator; +} + +lsmash_entry_list_t *lsmash_list_create_orig +( + lsmash_entry_data_eliminator eliminator +) +{ + lsmash_entry_list_t *list = lsmash_malloc( sizeof(lsmash_entry_list_t) ); + if( !list ) + return NULL; + lsmash_list_init( list, eliminator ); + return list; +} + +void lsmash_list_destroy +( + lsmash_entry_list_t *list +) +{ + lsmash_list_remove_entries( list ); + lsmash_free( list ); +} + +int lsmash_list_add_entry +( + lsmash_entry_list_t *list, + void *data +) +{ + if( !list ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_entry_t *entry = lsmash_malloc( sizeof(lsmash_entry_t) ); + if( !entry ) + return LSMASH_ERR_MEMORY_ALLOC; + entry->next = NULL; + entry->prev = list->tail; + entry->data = data; + if( list->head ) + list->tail->next = entry; + else + list->head = entry; + list->tail = entry; + list->entry_count += 1; + return 0; +} + +int lsmash_list_remove_entry_direct +( + lsmash_entry_list_t *list, + lsmash_entry_t *entry +) +{ + if( !list || !entry ) + return LSMASH_ERR_FUNCTION_PARAM; + assert( !entry->data || list->eliminator ); + lsmash_entry_t *next = entry->next; + lsmash_entry_t *prev = entry->prev; + if( entry == list->head ) + list->head = next; + else + prev->next = next; + if( entry == list->tail ) + list->tail = prev; + else + next->prev = prev; + if( entry->data ) + list->eliminator( entry->data ); + if( entry == list->last_accessed_entry ) + { + if( next ) + list->last_accessed_entry = next; + else if( prev ) + { + list->last_accessed_entry = prev; + list->last_accessed_number -= 1; + } + else + { + list->last_accessed_entry = NULL; + list->last_accessed_number = 0; + } + } + else + { + /* We can't know the current entry number immediately, + * so discard the last accessed entry info because time is wasted to know it. */ + list->last_accessed_entry = NULL; + list->last_accessed_number = 0; + } + lsmash_free( entry ); + list->entry_count -= 1; + return 0; +} + +int lsmash_list_remove_entry +( + lsmash_entry_list_t *list, + uint32_t entry_number +) +{ + lsmash_entry_t *entry = lsmash_list_get_entry( list, entry_number ); + return lsmash_list_remove_entry_direct( list, entry ); +} + +int lsmash_list_remove_entry_tail +( + lsmash_entry_list_t *list +) +{ + return lsmash_list_remove_entry_direct( list, list->tail ); +} + +void lsmash_list_remove_entries +( + lsmash_entry_list_t *list +) +{ + if( !list ) + return; + /* Note that it's valid that list contain no entries or no data to be eliminated. */ + for( lsmash_entry_t *entry = list->head; entry; ) + { + lsmash_entry_t *next = entry->next; + if( entry->data ) + list->eliminator( entry->data ); + lsmash_free( entry ); + entry = next; + } + lsmash_entry_data_eliminator eliminator = list->eliminator; + lsmash_list_clear( list ); + list->eliminator = eliminator; +} + +void lsmash_list_move_entries +( + lsmash_entry_list_t *dst, + lsmash_entry_list_t *src +) +{ + *dst = *src; + lsmash_entry_data_eliminator eliminator = src->eliminator; + lsmash_list_clear( src ); + src->eliminator = eliminator; +} + +lsmash_entry_t *lsmash_list_get_entry +( + lsmash_entry_list_t *list, + uint32_t entry_number +) +{ + if( !list || !entry_number || entry_number > list->entry_count ) + return NULL; + int shortcut = 1; + lsmash_entry_t *entry = NULL; + if( list->last_accessed_entry ) + { + if( entry_number == list->last_accessed_number ) + entry = list->last_accessed_entry; + else if( entry_number == list->last_accessed_number + 1 ) + entry = list->last_accessed_entry->next; + else if( entry_number == list->last_accessed_number - 1 ) + entry = list->last_accessed_entry->prev; + else + shortcut = 0; + } + else + shortcut = 0; + if( !shortcut ) + { + if( entry_number <= (list->entry_count >> 1) ) + { + /* Look for from the head. */ + uint32_t distance_plus_one = entry_number; + for( entry = list->head; entry && --distance_plus_one; entry = entry->next ); + } + else + { + /* Look for from the tail. */ + uint32_t distance = list->entry_count - entry_number; + for( entry = list->tail; entry && distance--; entry = entry->prev ); + } + } + if( entry ) + { + list->last_accessed_entry = entry; + list->last_accessed_number = entry_number; + } + return entry; +} + +void *lsmash_list_get_entry_data +( + lsmash_entry_list_t *list, + uint32_t entry_number +) +{ + lsmash_entry_t *entry = lsmash_list_get_entry( list, entry_number ); + return entry ? entry->data : NULL; +} diff --git a/3rdparty/l-smash/common/list.h b/3rdparty/l-smash/common/list.h new file mode 100644 index 000000000..ab489c13c --- /dev/null +++ b/3rdparty/l-smash/common/list.h @@ -0,0 +1,117 @@ +/***************************************************************************** + * list.h + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +typedef void (*lsmash_entry_data_eliminator)( void *data ); /* very same as free() of standard C lib; void free( void * ); + * If 'data' is equal to NULL, the eliminator must do nothing. */ + +typedef struct lsmash_entry_tag lsmash_entry_t; + +struct lsmash_entry_tag +{ + lsmash_entry_t *next; + lsmash_entry_t *prev; + void *data; +}; + +typedef struct +{ + lsmash_entry_t *head; + lsmash_entry_t *tail; + lsmash_entry_t *last_accessed_entry; + uint32_t last_accessed_number; + uint32_t entry_count; + lsmash_entry_data_eliminator eliminator; +} lsmash_entry_list_t; + +/* Utility macros to avoid 'lsmash_entry_data_eliminator' casts to the 'eliminator' argument */ +#define lsmash_list_init( list, eliminator ) \ + lsmash_list_init_orig( list, (lsmash_entry_data_eliminator)(eliminator) ) +#define lsmash_list_init_simple( list ) \ + lsmash_list_init_orig( list, (lsmash_entry_data_eliminator)lsmash_free ) + +#define lsmash_list_create( eliminator ) \ + lsmash_list_create_orig( (lsmash_entry_data_eliminator)(eliminator) ) +#define lsmash_list_create_simple() \ + lsmash_list_create_orig( (lsmash_entry_data_eliminator)lsmash_free ) + +/* functions for internal usage */ +void lsmash_list_init_orig +( + lsmash_entry_list_t *list, + lsmash_entry_data_eliminator eliminator +); + +lsmash_entry_list_t *lsmash_list_create_orig +( + lsmash_entry_data_eliminator eliminator +); + +void lsmash_list_destroy +( + lsmash_entry_list_t *list +); + +int lsmash_list_add_entry +( + lsmash_entry_list_t *list, + void *data +); + +int lsmash_list_remove_entry_direct +( + lsmash_entry_list_t *list, + lsmash_entry_t *entry +); + +int lsmash_list_remove_entry +( + lsmash_entry_list_t *list, + uint32_t entry_number +); + +int lsmash_list_remove_entry_tail +( + lsmash_entry_list_t *list +); + +void lsmash_list_remove_entries +( + lsmash_entry_list_t *list +); + +void lsmash_list_move_entries +( + lsmash_entry_list_t *dst, + lsmash_entry_list_t *src +); + +lsmash_entry_t *lsmash_list_get_entry +( + lsmash_entry_list_t *list, + uint32_t entry_number +); + +void *lsmash_list_get_entry_data +( + lsmash_entry_list_t *list, + uint32_t entry_number +); diff --git a/3rdparty/l-smash/common/memint.h b/3rdparty/l-smash/common/memint.h new file mode 100644 index 000000000..e5f4a02d1 --- /dev/null +++ b/3rdparty/l-smash/common/memint.h @@ -0,0 +1,108 @@ +/***************************************************************************** + * memint.h + ***************************************************************************** + * Copyright (C) 2014-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +/*---- memory writers ----*/ +#define LSMASH_SET_BYTE( p, x ) \ + do \ + { \ + ((uint8_t *)(p))[0] = (x); \ + } while( 0 ) +#define LSMASH_SET_BE16( p, x ) \ + do \ + { \ + ((uint8_t *)(p))[0] = (x) >> 8; \ + ((uint8_t *)(p))[1] = (x); \ + } while( 0 ) +#define LSMASH_SET_BE24( p, x ) \ + do \ + { \ + ((uint8_t *)(p))[0] = (x) >> 16; \ + ((uint8_t *)(p))[1] = (x) >> 8; \ + ((uint8_t *)(p))[2] = (x); \ + } while( 0 ) +#define LSMASH_SET_BE32( p, x ) \ + do \ + { \ + ((uint8_t *)(p))[0] = (x) >> 24; \ + ((uint8_t *)(p))[1] = (x) >> 16; \ + ((uint8_t *)(p))[2] = (x) >> 8; \ + ((uint8_t *)(p))[3] = (x); \ + } while( 0 ) +#define LSMASH_SET_BE64( p, x ) \ + do \ + { \ + ((uint8_t *)(p))[0] = (x) >> 56; \ + ((uint8_t *)(p))[1] = (x) >> 48; \ + ((uint8_t *)(p))[2] = (x) >> 40; \ + ((uint8_t *)(p))[3] = (x) >> 32; \ + ((uint8_t *)(p))[4] = (x) >> 24; \ + ((uint8_t *)(p))[5] = (x) >> 16; \ + ((uint8_t *)(p))[6] = (x) >> 8; \ + ((uint8_t *)(p))[7] = (x); \ + } while( 0 ) +#define LSMASH_SET_LE16( p, x ) \ + do \ + { \ + ((uint8_t *)(p))[0] = (x); \ + ((uint8_t *)(p))[1] = (x) >> 8; \ + } while( 0 ) +#define LSMASH_SET_LE32( p, x ) \ + do \ + { \ + ((uint8_t *)(p))[0] = (x); \ + ((uint8_t *)(p))[1] = (x) >> 8; \ + ((uint8_t *)(p))[2] = (x) >> 16; \ + ((uint8_t *)(p))[3] = (x) >> 24; \ + } while( 0 ) + +/*---- memory readers ----*/ +#define LSMASH_GET_BYTE( p ) \ + (((const uint8_t *)(p))[0]) +#define LSMASH_GET_BE16( p ) \ + (((uint16_t)((const uint8_t *)(p))[0] << 8) \ + | ((uint16_t)((const uint8_t *)(p))[1])) +#define LSMASH_GET_BE24( p ) \ + (((uint32_t)((const uint8_t *)(p))[0] << 16) \ + | ((uint32_t)((const uint8_t *)(p))[1] << 8) \ + | ((uint32_t)((const uint8_t *)(p))[2])) +#define LSMASH_GET_BE32( p ) \ + (((uint32_t)((const uint8_t *)(p))[0] << 24) \ + | ((uint32_t)((const uint8_t *)(p))[1] << 16) \ + | ((uint32_t)((const uint8_t *)(p))[2] << 8) \ + | ((uint32_t)((const uint8_t *)(p))[3])) +#define LSMASH_GET_BE64( p ) \ + (((uint64_t)((const uint8_t *)(p))[0] << 56) \ + | ((uint64_t)((const uint8_t *)(p))[1] << 48) \ + | ((uint64_t)((const uint8_t *)(p))[2] << 40) \ + | ((uint64_t)((const uint8_t *)(p))[3] << 32) \ + | ((uint64_t)((const uint8_t *)(p))[4] << 24) \ + | ((uint64_t)((const uint8_t *)(p))[5] << 16) \ + | ((uint64_t)((const uint8_t *)(p))[6] << 8) \ + | ((uint64_t)((const uint8_t *)(p))[7])) +#define LSMASH_GET_LE16( p ) \ + (((uint16_t)((const uint8_t *)(p))[0]) \ + | ((uint16_t)((const uint8_t *)(p))[1] << 8)) +#define LSMASH_GET_LE32( p ) \ + (((uint32_t)((const uint8_t *)(p))[0]) \ + | ((uint32_t)((const uint8_t *)(p))[1] << 8) \ + | ((uint32_t)((const uint8_t *)(p))[2] << 16) \ + | ((uint32_t)((const uint8_t *)(p))[3] << 24)) diff --git a/3rdparty/l-smash/common/multibuf.c b/3rdparty/l-smash/common/multibuf.c new file mode 100644 index 000000000..cc832e74a --- /dev/null +++ b/3rdparty/l-smash/common/multibuf.c @@ -0,0 +1,90 @@ +/***************************************************************************** + * multibuf.c + ***************************************************************************** + * Copyright (C) 2011-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "internal.h" /* must be placed first */ + +#include + +lsmash_multiple_buffers_t *lsmash_create_multiple_buffers( uint32_t number_of_buffers, uint32_t buffer_size ) +{ + if( (uint64_t)number_of_buffers * buffer_size > UINT32_MAX ) + return NULL; + lsmash_multiple_buffers_t *multiple_buffer = lsmash_malloc( sizeof(lsmash_multiple_buffers_t) ); + if( !multiple_buffer ) + return NULL; + multiple_buffer->buffers = lsmash_malloc( number_of_buffers * buffer_size ); + if( !multiple_buffer->buffers ) + { + lsmash_free( multiple_buffer ); + return NULL; + } + multiple_buffer->number_of_buffers = number_of_buffers; + multiple_buffer->buffer_size = buffer_size; + return multiple_buffer; +} + +void *lsmash_withdraw_buffer( lsmash_multiple_buffers_t *multiple_buffer, uint32_t buffer_number ) +{ + if( !multiple_buffer || !buffer_number || buffer_number > multiple_buffer->number_of_buffers ) + return NULL; + return (uint8_t *)multiple_buffer->buffers + (buffer_number - 1) * multiple_buffer->buffer_size; +} + +lsmash_multiple_buffers_t *lsmash_resize_multiple_buffers( lsmash_multiple_buffers_t *multiple_buffer, uint32_t buffer_size ) +{ + if( !multiple_buffer ) + return NULL; + if( buffer_size == multiple_buffer->buffer_size ) + return multiple_buffer; + if( (uint64_t)multiple_buffer->number_of_buffers * buffer_size > UINT32_MAX ) + return NULL; + uint8_t *temp; + if( buffer_size > multiple_buffer->buffer_size ) + { + temp = lsmash_realloc( multiple_buffer->buffers, multiple_buffer->number_of_buffers * buffer_size ); + if( !temp ) + return NULL; + for( uint32_t i = multiple_buffer->number_of_buffers - 1; i ; i-- ) + memmove( temp + buffer_size, temp + i * multiple_buffer->buffer_size, multiple_buffer->buffer_size ); + } + else + { + for( uint32_t i = 1; i < multiple_buffer->number_of_buffers; i++ ) + memmove( (uint8_t *)multiple_buffer->buffers + buffer_size, + (uint8_t *)multiple_buffer->buffers + i * multiple_buffer->buffer_size, + multiple_buffer->buffer_size ); + temp = lsmash_realloc( multiple_buffer->buffers, multiple_buffer->number_of_buffers * buffer_size ); + if( !temp ) + return NULL; + } + multiple_buffer->buffers = temp; + multiple_buffer->buffer_size = buffer_size; + return multiple_buffer; +} + +void lsmash_destroy_multiple_buffers( lsmash_multiple_buffers_t *multiple_buffer ) +{ + if( !multiple_buffer ) + return; + lsmash_free( multiple_buffer->buffers ); + lsmash_free( multiple_buffer ); +} diff --git a/3rdparty/l-smash/common/multibuf.h b/3rdparty/l-smash/common/multibuf.h new file mode 100644 index 000000000..828930bc5 --- /dev/null +++ b/3rdparty/l-smash/common/multibuf.h @@ -0,0 +1,33 @@ +/***************************************************************************** + * multibuf.h + ***************************************************************************** + * Copyright (C) 2011-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +typedef struct +{ + uint32_t number_of_buffers; + uint32_t buffer_size; + void *buffers; +} lsmash_multiple_buffers_t; + +lsmash_multiple_buffers_t *lsmash_create_multiple_buffers( uint32_t number_of_buffers, uint32_t buffer_size ); +void *lsmash_withdraw_buffer( lsmash_multiple_buffers_t *multiple_buffer, uint32_t buffer_number ); +lsmash_multiple_buffers_t *lsmash_resize_multiple_buffers( lsmash_multiple_buffers_t *multiple_buffer, uint32_t buffer_size ); +void lsmash_destroy_multiple_buffers( lsmash_multiple_buffers_t *multiple_buffer ); diff --git a/3rdparty/l-smash/common/osdep.c b/3rdparty/l-smash/common/osdep.c new file mode 100644 index 000000000..cfd5cacca --- /dev/null +++ b/3rdparty/l-smash/common/osdep.c @@ -0,0 +1,67 @@ +/***************************************************************************** + * osdep.c + ***************************************************************************** + * Copyright (C) 2013-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "internal.h" /* must be placed first */ + +#include + +#ifdef _WIN32 +#include +#endif + +#ifdef _WIN32 + +int lsmash_string_to_wchar( int cp, const char *from, wchar_t **to ) +{ + int nc = MultiByteToWideChar( cp, 0, from, -1, 0, 0 ); + if( nc == 0 ) + return 0; + *to = lsmash_malloc( nc * sizeof(wchar_t) ); + MultiByteToWideChar( cp, 0, from, -1, *to, nc ); + return nc; +} + +int lsmash_string_from_wchar( int cp, const wchar_t *from, char **to ) +{ + int nc = WideCharToMultiByte( cp, 0, from, -1, 0, 0, 0, 0 ); + if( nc == 0 ) + return 0; + *to = lsmash_malloc( nc * sizeof(char) ); + WideCharToMultiByte( cp, 0, from, -1, *to, nc, 0, 0 ); + return nc; +} + +FILE *lsmash_win32_fopen( const char *name, const char *mode ) +{ + wchar_t *wname = NULL, *wmode = NULL; + lsmash_string_to_wchar( CP_UTF8, name, &wname ); + lsmash_string_to_wchar( CP_UTF8, mode, &wmode ); + FILE *fp = _wfopen( wname, wmode ); + if( !fp ) + fp = fopen( name, mode ); + lsmash_free( wname ); + lsmash_free( wmode ); + return fp; +} + +#endif + diff --git a/3rdparty/l-smash/common/osdep.h b/3rdparty/l-smash/common/osdep.h new file mode 100644 index 000000000..f3526eb3c --- /dev/null +++ b/3rdparty/l-smash/common/osdep.h @@ -0,0 +1,61 @@ +/***************************************************************************** + * osdep.h: + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * Takashi Hirata + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#ifndef OSDEP_H +#define OSDEP_H + +#define _FILE_OFFSET_BITS 64 +#define _LARGEFILE_SOURCE + +#if defined( _MSC_VER ) +#define lsmash_fseek _fseeki64 +#define lsmash_ftell _ftelli64 +#elif defined( __MINGW32__ ) +#define lsmash_fseek fseeko64 +#define lsmash_ftell ftello64 +#else +#define _POSIX_C_SOURCE 200809L +#define lsmash_fseek fseeko +#define lsmash_ftell ftello +#endif + +#ifdef _MSC_VER +#define inline __inline +#define _CRT_SECURE_NO_WARNINGS +#endif + +#ifdef _WIN32 +# include + FILE *lsmash_win32_fopen( const char *name, const char *mode ); +# define lsmash_fopen lsmash_win32_fopen +#else +# define lsmash_fopen fopen +#endif + +#ifdef _WIN32 +# include + int lsmash_string_to_wchar( int cp, const char *from, wchar_t **to ); + int lsmash_string_from_wchar( int cp, const wchar_t *from, char **to ); +#endif + +#endif diff --git a/3rdparty/l-smash/common/utils.c b/3rdparty/l-smash/common/utils.c new file mode 100644 index 000000000..cb127c84f --- /dev/null +++ b/3rdparty/l-smash/common/utils.c @@ -0,0 +1,179 @@ +/***************************************************************************** + * utils.c + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "internal.h" /* must be placed first */ + +#include +#include +#include +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif + +/*---- type ----*/ +double lsmash_fixed2double( int64_t value, int frac_width ) +{ + return value / (double)(1ULL << frac_width); +} + +float lsmash_int2float32( uint32_t value ) +{ + return (union {uint32_t i; float f;}){value}.f; +} + +double lsmash_int2float64( uint64_t value ) +{ + return (union {uint64_t i; double d;}){value}.d; +} +/*---- ----*/ + +/*---- others ----*/ +void lsmash_log +( + const void *class, + lsmash_log_level level, + const char *message, + ... +) +{ + /* Dereference lsmash_class_t pointer if 'class' is non-NULL. */ + lsmash_class_t *cls = class ? (lsmash_class_t *)*(intptr_t *)class : NULL; + if( cls && cls->log_level_offset ) + { + lsmash_log_level log_level = *(lsmash_log_level *)((int8_t *)class + cls->log_level_offset); + if( level > log_level ) + return; + } + char *prefix; + va_list args; + va_start( args, message ); + switch( level ) + { + case LSMASH_LOG_ERROR: + prefix = "Error"; + break; + case LSMASH_LOG_WARNING: + prefix = "Warning"; + break; + case LSMASH_LOG_INFO: + prefix = "Info"; + break; + default: + prefix = "Unknown"; + break; + } + if( cls ) + fprintf( stderr, "[%s: %s]: ", cls->name, prefix ); + else + fprintf( stderr, "[%s]: ", prefix ); + vfprintf( stderr, message, args ); + va_end( args ); +} + +void lsmash_log_refresh_line +( + const void *class /* unused, but for forward compatibility */ +) +{ + /* Assume 80 characters per line. */ + fprintf( stderr, "%80c", '\r' ); +} + +void lsmash_ifprintf +( + FILE *fp, + int indent, + const char *format, ... +) +{ + va_list args; + va_start( args, format ); + if( indent <= 10 ) + { + static const char *indent_string[] = + { + "", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " " + }; + fprintf( fp, "%s", indent_string[indent] ); + } + else + for( int i = 0; i < indent; i++ ) + fprintf( fp, " " ); + vfprintf( fp, format, args ); + va_end( args ); +} + +/* for qsort function */ +int lsmash_compare_dts +( + const lsmash_media_ts_t *a, + const lsmash_media_ts_t *b +) +{ + int64_t diff = (int64_t)(a->dts - b->dts); + return diff > 0 ? 1 : (diff == 0 ? 0 : -1); +} + +int lsmash_compare_cts +( + const lsmash_media_ts_t *a, + const lsmash_media_ts_t *b +) +{ + int64_t diff = (int64_t)(a->cts - b->cts); + return diff > 0 ? 1 : (diff == 0 ? 0 : -1); +} + +#ifdef _WIN32 +int lsmash_convert_ansi_to_utf8( const char *ansi, char *utf8, int length ) +{ + int len0 = MultiByteToWideChar( CP_THREAD_ACP, 0, ansi, -1, 0, 0 ); + wchar_t *buff = lsmash_malloc( len0 * sizeof(wchar_t) ); + if( !buff ) + return 0; + int len1 = MultiByteToWideChar( CP_THREAD_ACP, 0, ansi, -1, buff, len0 ); + if( len0 != len1 ) + goto convert_fail; + len0 = WideCharToMultiByte( CP_UTF8, 0, buff, -1, 0, 0, 0, 0 ); + if( len0 > length - 1 ) + goto convert_fail; + len1 = WideCharToMultiByte( CP_UTF8, 0, buff, -1, utf8, length, 0, 0 ); + lsmash_free( buff ); + if( len0 != len1 ) + return 0; + return len1; +convert_fail: + lsmash_free( buff ); + return 0; +} +#endif diff --git a/3rdparty/l-smash/common/utils.h b/3rdparty/l-smash/common/utils.h new file mode 100644 index 000000000..90365ccdb --- /dev/null +++ b/3rdparty/l-smash/common/utils.h @@ -0,0 +1,232 @@ +/***************************************************************************** + * utils.h + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#ifndef LSMASH_UTIL_H +#define LSMASH_UTIL_H + +#define debug_if(x) if(x) + +#define LSMASH_MAX( a, b ) ((a) > (b) ? (a) : (b)) +#define LSMASH_MIN( a, b ) ((a) < (b) ? (a) : (b)) + +#define EXPAND_VA_ARGS( ... ) __VA_ARGS__ + +/* default arguments + * Use only CALL_FUNC_DEFAULT_ARGS(). + * The defined macros can't be passed a macro argument requiring the empty parameter list. + * + * The following is an example. + * #define TEMPLATE_A( ... ) CALL_FUNC_DEFAULT_ARGS( TEMPLATE_A, __VA_ARGS__ ) + * #define TEMPLATE_A_1( _1 ) _1( 1 ) + * #define TEMPLATE_B( ... ) CALL_FUNC_DEFAULT_ARGS( TEMPLATE_B, __VA_ARGS__ ) + * #define TEMPLATE_B_2( _1, _2 ) ((_1) + (_2)) + * #define TEMPLATE_B_1( _1 ) TEMPLATE_B_2( _1, 0 ) + * #define TEMPLATE_B_0() + * int main( void ) + * { + * TEMPLATE_A( TEMPLATE_B_1 ); // OK + * TEMPLATE_A( TEMPLATE_B ); // NG + * TEMPLATE_B( 1, 2 ); // OK + * TEMPLATE_B(); // NG + * return 0; + * } + * */ +#define NUM_ARGS( _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, ... ) _12 +#ifdef _MSC_VER +#define COUNT_NUM_ARGS( ... ) EXPAND_VA_ARGS( NUM_ARGS( __VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0 ) ) +#else +#define COUNT_NUM_ARGS( ... ) NUM_ARGS( __VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0 ) +#endif +#define GET_FUNC_BY_NUM_ARGS_EXN( func_name, N ) func_name ## _ ## N +#define GET_FUNC_BY_NUM_ARGS_EX0( func_name, N ) GET_FUNC_BY_NUM_ARGS_EXN( func_name, N ) +#define GET_FUNC_BY_NUM_ARGS_EX1( func_name, ... ) GET_FUNC_BY_NUM_ARGS_EX0( func_name, COUNT_NUM_ARGS( __VA_ARGS__ ) ) +#define CALL_FUNC_DEFAULT_ARGS( func_name, ... ) GET_FUNC_BY_NUM_ARGS_EX1( func_name, __VA_ARGS__ ) ( __VA_ARGS__ ) + +/*---- class ----*/ +typedef struct +{ + char *name; + size_t log_level_offset; /* offset in the struct where 'log_level' is placed + * If set to 0, 'log_level' is unavailable and implicitly set to LSMASH_LOG_INFO. */ +} lsmash_class_t; + +/*---- type ----*/ +double lsmash_fixed2double( int64_t value, int frac_width ); +float lsmash_int2float32( uint32_t value ); +double lsmash_int2float64( uint64_t value ); + +/*---- others ----*/ +typedef enum +{ + LSMASH_LOG_QUIET = 0, + LSMASH_LOG_ERROR, + LSMASH_LOG_WARNING, + LSMASH_LOG_INFO, +} lsmash_log_level; + +typedef struct +{ + uint64_t n; + uint64_t d; +} lsmash_rational_u64_t; + +typedef struct +{ + int64_t n; + uint64_t d; +} lsmash_rational_s64_t; + +void lsmash_log +( + const void *class, + lsmash_log_level level, + const char *message, + ... +); + +void lsmash_log_refresh_line +( + const void *class +); + +void lsmash_ifprintf +( + FILE *fp, + int indent, + const char *format, ... +); + +static inline uint32_t lsmash_count_bits +( + uint32_t bits +) +{ + bits = (bits & 0x55555555) + ((bits >> 1) & 0x55555555); + bits = (bits & 0x33333333) + ((bits >> 2) & 0x33333333); + bits = (bits & 0x0f0f0f0f) + ((bits >> 4) & 0x0f0f0f0f); + bits = (bits & 0x00ff00ff) + ((bits >> 8) & 0x00ff00ff); + return (bits & 0x0000ffff) + ((bits >> 16) & 0x0000ffff); +} + +static inline size_t lsmash_floor_log2 +( + uint64_t value +) +{ + assert( value >= 1 ); +#if defined(__GNUC__) && (__GNUC__ >= 4 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)) /* GCC >= 3.4 */ + return (sizeof(uint64_t) - 1) - __builtin_clzll( value ); +#else + size_t s = 0; + while( value ) + { + value >>= 1; + ++s; + } + return s - 1; +#endif +} + +static inline size_t lsmash_ceil_log2 +( + uint64_t value +) +{ + return lsmash_floor_log2( value ) + ((value & (value - 1)) != 0); +} + +int lsmash_compare_dts +( + const lsmash_media_ts_t *a, + const lsmash_media_ts_t *b +); + +int lsmash_compare_cts +( + const lsmash_media_ts_t *a, + const lsmash_media_ts_t *b +); + +static inline uint64_t lsmash_get_gcd +( + uint64_t a, + uint64_t b +) +{ + if( !b ) + return a; + while( 1 ) + { + uint64_t c = a % b; + if( !c ) + return b; + a = b; + b = c; + } +} + +static inline uint64_t lsmash_get_lcm +( + uint64_t a, + uint64_t b +) +{ + if( !a ) + return 0; + return (a / lsmash_get_gcd( a, b )) * b; +} + +static inline void lsmash_reduce_fraction +( + uint64_t *a, + uint64_t *b +) +{ + if( !a || !b ) + return; + uint64_t gcd = lsmash_get_gcd( *a, *b ); + if( gcd ) + { + *a /= gcd; + *b /= gcd; + } +} + +static inline void lsmash_reduce_fraction_su +( + int64_t *a, + uint64_t *b +) +{ + if( !a || !b ) + return; + uint64_t c = *a > 0 ? *a : -(*a); + uint64_t gcd = lsmash_get_gcd( c, *b ); + if( gcd ) + { + c /= gcd; + *b /= gcd; + *a = *a > 0 ? (signed)c : -(signed)c; + } +} + +#endif diff --git a/3rdparty/l-smash/core/box.c b/3rdparty/l-smash/core/box.c new file mode 100644 index 000000000..b15a1a21b --- /dev/null +++ b/3rdparty/l-smash/core/box.c @@ -0,0 +1,2071 @@ +/***************************************************************************** + * box.c + ***************************************************************************** + * Copyright (C) 2012-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include +#include + +#include "box.h" +#include "box_default.h" +#include "write.h" +#include "read.h" +#include "print.h" +#include "timeline.h" + +#include "codecs/mp4a.h" +#include "codecs/mp4sys.h" + +#include "importer/importer.h" + +static const lsmash_class_t lsmash_box_class = +{ + "box" +}; + +const lsmash_box_type_t static_lsmash_box_type_unspecified = LSMASH_BOX_TYPE_INITIALIZER; + +void isom_init_box_common_orig +( + void *_box, + void *_parent, + lsmash_box_type_t box_type, + uint64_t precedence, + isom_extension_destructor_t destructor +) +{ + isom_box_t *box = (isom_box_t *)_box; + isom_box_t *parent = (isom_box_t *)_parent; + assert( box && parent && parent->root ); + box->class = &lsmash_box_class; + box->root = parent->root; + box->file = parent->file; + box->parent = parent; + box->precedence = precedence; + box->destruct = destructor; + box->size = 0; + box->type = box_type; + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STSD ) && isom_is_fullbox( box ) ) + { + box->version = 0; + box->flags = 0; + } + isom_set_box_writer( box ); +} + +static void isom_reorder_tail_box( isom_box_t *parent ) +{ + /* Reorder the appended box by 'precedence'. */ + lsmash_entry_t *x = parent->extensions.tail; + assert( x && x->data ); + uint64_t precedence = ((isom_box_t *)x->data)->precedence; + for( lsmash_entry_t *y = x->prev; y; y = y->prev ) + { + isom_box_t *box = (isom_box_t *)y->data; + if( LSMASH_IS_NON_EXISTING_BOX( box ) || precedence > box->precedence ) + { + /* Exchange the entity data of adjacent two entries. */ + y->data = x->data; + x->data = box; + x = y; + } + else + break; + } +} + +int isom_add_box_to_extension_list( void *parent_box, void *child_box ) +{ + isom_box_t *parent = (isom_box_t *)parent_box; + isom_box_t *child = (isom_box_t *)child_box; + assert( LSMASH_IS_EXISTING_BOX( parent ) && LSMASH_IS_EXISTING_BOX( child ) ); + /* Append at the end of the list. */ + if( lsmash_list_add_entry( &parent->extensions, child ) < 0 ) + return LSMASH_ERR_MEMORY_ALLOC; + /* Don't reorder the appended box when the file is opened for reading. */ + if( LSMASH_IS_NON_EXISTING_BOX( parent->file ) + || (parent->file->flags & LSMASH_FILE_MODE_READ) + || parent->file->fake_file_mode ) + return 0; + isom_reorder_tail_box( parent ); + return 0; +} + +void isom_bs_put_basebox_common( lsmash_bs_t *bs, isom_box_t *box ) +{ + if( box->size > UINT32_MAX ) + { + lsmash_bs_put_be32( bs, 1 ); + lsmash_bs_put_be32( bs, box->type.fourcc ); + lsmash_bs_put_be64( bs, box->size ); /* largesize */ + } + else + { + lsmash_bs_put_be32( bs, (uint32_t)box->size ); + lsmash_bs_put_be32( bs, box->type.fourcc ); + } + if( box->type.fourcc == ISOM_BOX_TYPE_UUID.fourcc ) + { + lsmash_bs_put_be32( bs, box->type.user.fourcc ); + lsmash_bs_put_bytes( bs, 12, box->type.user.id ); + } +} + +void isom_bs_put_fullbox_common( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_bs_put_basebox_common( bs, box ); + lsmash_bs_put_byte( bs, box->version ); + lsmash_bs_put_be24( bs, box->flags ); +} + +void isom_bs_put_box_common( lsmash_bs_t *bs, void *box ) +{ + if( !box ) + { + bs->error = 1; + return; + } + isom_box_t *parent = ((isom_box_t *)box)->parent; + if( parent && lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STSD ) ) + { + isom_bs_put_basebox_common( bs, (isom_box_t *)box ); + return; + } + if( isom_is_fullbox( box ) ) + isom_bs_put_fullbox_common( bs, (isom_box_t *)box ); + else + isom_bs_put_basebox_common( bs, (isom_box_t *)box ); +} + +/* Return 1 if the box is fullbox, Otherwise return 0. */ +int isom_is_fullbox( const void *box ) +{ + const isom_box_t *current = (const isom_box_t *)box; + lsmash_box_type_t type = current->type; + static lsmash_box_type_t fullbox_type_table[50] = { LSMASH_BOX_TYPE_INITIALIZER }; + if( !lsmash_check_box_type_specified( &fullbox_type_table[0] ) ) + { + /* Initialize the table. */ + int i = 0; + fullbox_type_table[i++] = ISOM_BOX_TYPE_SIDX; + fullbox_type_table[i++] = ISOM_BOX_TYPE_MVHD; + fullbox_type_table[i++] = ISOM_BOX_TYPE_TKHD; + fullbox_type_table[i++] = ISOM_BOX_TYPE_IODS; + fullbox_type_table[i++] = ISOM_BOX_TYPE_ESDS; + fullbox_type_table[i++] = QT_BOX_TYPE_ESDS; + fullbox_type_table[i++] = QT_BOX_TYPE_CLEF; + fullbox_type_table[i++] = QT_BOX_TYPE_PROF; + fullbox_type_table[i++] = QT_BOX_TYPE_ENOF; + fullbox_type_table[i++] = ISOM_BOX_TYPE_ELST; + fullbox_type_table[i++] = ISOM_BOX_TYPE_MDHD; + fullbox_type_table[i++] = ISOM_BOX_TYPE_HDLR; + fullbox_type_table[i++] = ISOM_BOX_TYPE_VMHD; + fullbox_type_table[i++] = ISOM_BOX_TYPE_SMHD; + fullbox_type_table[i++] = ISOM_BOX_TYPE_HMHD; + fullbox_type_table[i++] = ISOM_BOX_TYPE_NMHD; + fullbox_type_table[i++] = QT_BOX_TYPE_GMIN; + fullbox_type_table[i++] = ISOM_BOX_TYPE_DREF; + fullbox_type_table[i++] = ISOM_BOX_TYPE_STSD; + fullbox_type_table[i++] = ISOM_BOX_TYPE_STSL; + fullbox_type_table[i++] = QT_BOX_TYPE_CHAN; + fullbox_type_table[i++] = ISOM_BOX_TYPE_SRAT; + fullbox_type_table[i++] = ISOM_BOX_TYPE_STTS; + fullbox_type_table[i++] = ISOM_BOX_TYPE_CTTS; + fullbox_type_table[i++] = ISOM_BOX_TYPE_CSLG; + fullbox_type_table[i++] = ISOM_BOX_TYPE_STSS; + fullbox_type_table[i++] = QT_BOX_TYPE_STPS; + fullbox_type_table[i++] = ISOM_BOX_TYPE_SDTP; + fullbox_type_table[i++] = ISOM_BOX_TYPE_STSC; + fullbox_type_table[i++] = ISOM_BOX_TYPE_STSZ; + fullbox_type_table[i++] = ISOM_BOX_TYPE_STZ2; + fullbox_type_table[i++] = ISOM_BOX_TYPE_STCO; + fullbox_type_table[i++] = ISOM_BOX_TYPE_CO64; + fullbox_type_table[i++] = ISOM_BOX_TYPE_SGPD; + fullbox_type_table[i++] = ISOM_BOX_TYPE_SBGP; + fullbox_type_table[i++] = ISOM_BOX_TYPE_CHPL; + fullbox_type_table[i++] = ISOM_BOX_TYPE_META; + fullbox_type_table[i++] = QT_BOX_TYPE_KEYS; + fullbox_type_table[i++] = ISOM_BOX_TYPE_MEAN; + fullbox_type_table[i++] = ISOM_BOX_TYPE_NAME; + fullbox_type_table[i++] = ISOM_BOX_TYPE_MEHD; + fullbox_type_table[i++] = ISOM_BOX_TYPE_TREX; + fullbox_type_table[i++] = ISOM_BOX_TYPE_MFHD; + fullbox_type_table[i++] = ISOM_BOX_TYPE_TFHD; + fullbox_type_table[i++] = ISOM_BOX_TYPE_TFDT; + fullbox_type_table[i++] = ISOM_BOX_TYPE_TRUN; + fullbox_type_table[i++] = ISOM_BOX_TYPE_TFRA; + fullbox_type_table[i++] = ISOM_BOX_TYPE_MFRO; + fullbox_type_table[i] = LSMASH_BOX_TYPE_UNSPECIFIED; + } + for( int i = 0; lsmash_check_box_type_specified( &fullbox_type_table[i] ); i++ ) + if( lsmash_check_box_type_identical( type, fullbox_type_table[i] ) ) + return 1; + if( current->parent ) + { + if( lsmash_check_box_type_identical( current->parent->type, ISOM_BOX_TYPE_DREF ) + || (lsmash_check_box_type_identical( type, ISOM_BOX_TYPE_CPRT ) + && lsmash_check_box_type_identical( current->parent->type, ISOM_BOX_TYPE_UDTA )) ) + return 1; + } + return 0; +} + +/* Return 1 if the sample type is LPCM audio, Otherwise return 0. */ +int isom_is_lpcm_audio( const void *box ) +{ + const isom_box_t *current = (const isom_box_t *)box; + lsmash_box_type_t type = current->type; + return lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_23NI_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_NONE_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_LPCM_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_SOWT_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_TWOS_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_FL32_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_FL64_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_IN24_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_IN32_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_NOT_SPECIFIED ) + || (lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_RAW_AUDIO ) && (current->manager & LSMASH_AUDIO_DESCRIPTION)); +} + +int isom_is_qt_audio( lsmash_codec_type_t type ) +{ + return lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_23NI_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_MAC3_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_MAC6_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_NONE_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_QDM2_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_QDMC_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_QCLP_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_AC_3_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_AGSM_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_ALAC_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_ALAW_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_CDX2_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_CDX4_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_DVCA_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_DVI_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_FL32_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_FL64_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_IMA4_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_IN24_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_IN32_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_LPCM_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_MP4A_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_RAW_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_SOWT_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_TWOS_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_ULAW_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_VDVA_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_FULLMP3_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_MP3_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_ADPCM2_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_ADPCM17_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_GSM49_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_NOT_SPECIFIED ); +} + +/* Return 1 if the sample type is uncompressed Y'CbCr video, Otherwise return 0. */ +int isom_is_uncompressed_ycbcr( lsmash_codec_type_t type ) +{ + return lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_2VUY_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_V210_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_V216_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_V308_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_V408_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_V410_VIDEO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_YUV2_VIDEO ); +} + +int isom_is_waveform_audio( lsmash_box_type_t type ) +{ + return lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_ADPCM2_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_ADPCM17_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_GSM49_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_FULLMP3_AUDIO ) + || lsmash_check_codec_type_identical( type, QT_CODEC_TYPE_MP3_AUDIO ); +} + +size_t isom_skip_box_common( uint8_t **p_data ) +{ + uint8_t *orig = *p_data; + uint8_t *data = *p_data; + uint64_t size = LSMASH_GET_BE32( data ); + data += ISOM_BASEBOX_COMMON_SIZE; + if( size == 1 ) + { + /* 'size = LSMASH_GET_BE64( data );' is a dead assignment here. */ + data += 8; + } + *p_data = data; + return data - orig; +} + +/* TODO: more secure handling */ +static size_t isom_read_box_size_and_type_from_binary_string( uint8_t **p_data, uint64_t *size, lsmash_box_type_t *type ) +{ + uint8_t *orig = *p_data; + uint8_t *data = *p_data; + *size = LSMASH_GET_BE32( &data[0] ); + type->fourcc = LSMASH_GET_BE32( &data[4] ); + data += ISOM_BASEBOX_COMMON_SIZE; + if( *size == 1 ) + { + *size = LSMASH_GET_BE64( data ); + data += 8; + } + *p_data = data; + if( type->fourcc == ISOM_BOX_TYPE_UUID.fourcc ) + { + type->user.fourcc = LSMASH_GET_BE32( &data[0] ); + memcpy( type->user.id, &data[4], 12 ); + } + return data - orig; +} + +uint8_t *isom_get_child_box_position( uint8_t *parent_data, uint32_t parent_size, lsmash_box_type_t child_type, uint32_t *child_size ) +{ + if( !parent_data || !child_size || parent_size < ISOM_BASEBOX_COMMON_SIZE ) + return NULL; + uint8_t *data = parent_data; + uint64_t size; + lsmash_box_type_t type; + (void)isom_read_box_size_and_type_from_binary_string( &data, &size, &type ); + if( size != parent_size ) + return NULL; + uint8_t *end = parent_data + parent_size; + for( uint8_t *pos = data; pos + ISOM_BASEBOX_COMMON_SIZE <= end; ) + { + uint32_t offset = isom_read_box_size_and_type_from_binary_string( &pos, &size, &type ); + if( lsmash_check_box_type_identical( type, child_type ) ) + { + *child_size = size; + return pos - offset; + } + pos += size - offset; /* Move to the next box. */ + } + return NULL; +} + +static void isom_destruct_extension_binary( void *ext ) +{ + if( !ext ) + return; + isom_box_t *box = (isom_box_t *)ext; + lsmash_free( box->binary ); +} + +int isom_add_extension_binary +( + void *parent_box, + lsmash_box_type_t box_type, + uint64_t precedence, + uint8_t *box_data, + uint32_t box_size +) +{ + if( !parent_box || !box_data || box_size < ISOM_BASEBOX_COMMON_SIZE + || !lsmash_check_box_type_specified( &box_type ) ) + return LSMASH_ERR_FUNCTION_PARAM; + isom_box_t *ext = lsmash_malloc_zero( sizeof(isom_box_t) ); + if( !ext ) + return LSMASH_ERR_MEMORY_ALLOC; + isom_box_t *parent = (isom_box_t *)parent_box; + ext->class = &lsmash_box_class; + ext->root = parent->root; + ext->file = parent->file; + ext->parent = parent; + ext->manager = LSMASH_BINARY_CODED_BOX; + ext->precedence = precedence; + ext->size = box_size; + ext->type = box_type; + ext->binary = box_data; + ext->destruct = isom_destruct_extension_binary; + if( isom_add_box_to_extension_list( parent, ext ) < 0 ) + { + lsmash_free( ext ); + return LSMASH_ERR_MEMORY_ALLOC; + } + isom_set_box_writer( ext ); + return 0; +} + +void isom_remove_extension_box( isom_box_t *ext ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( ext ) ) + return; + if( ext->destruct ) + ext->destruct( ext ); + isom_remove_all_extension_boxes( &ext->extensions ); + lsmash_free( ext ); +} + +void isom_remove_all_extension_boxes( lsmash_entry_list_t *extensions ) +{ + lsmash_list_remove_entries( extensions ); +} + +isom_box_t *isom_get_extension_box( lsmash_entry_list_t *extensions, lsmash_box_type_t box_type ) +{ + for( lsmash_entry_t *entry = extensions->head; entry; entry = entry->next ) + { + isom_box_t *ext = (isom_box_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( ext ) ) + continue; + if( lsmash_check_box_type_identical( ext->type, box_type ) ) + return ext; + } + return (isom_box_t *)isom_non_existing_unknown(); +} + +void *isom_get_extension_box_format( lsmash_entry_list_t *extensions, lsmash_box_type_t box_type ) +{ + for( lsmash_entry_t *entry = extensions->head; entry; entry = entry->next ) + { + isom_box_t *ext = (isom_box_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( ext ) + || (ext->manager & LSMASH_BINARY_CODED_BOX) + || !lsmash_check_box_type_identical( ext->type, box_type ) ) + continue; + return ext; + } + return isom_non_existing_unknown(); +} + +lsmash_entry_t *isom_get_entry_of_box +( + lsmash_box_t *parent, + const lsmash_box_path_t box_path[] +) +{ + if( LSMASH_IS_NON_EXISTING_BOX( parent ) ) + return NULL; + lsmash_entry_t *entry = NULL; + const lsmash_box_path_t *path = &box_path[0]; + while( lsmash_check_box_type_specified( &path->type ) ) + { + entry = parent->extensions.head; + if( !entry ) + return NULL; + parent = NULL; + uint32_t i = 1; + uint32_t number = path->number ? path->number : 1; + while( entry ) + { + isom_box_t *box = entry->data; + if( box && lsmash_check_box_type_identical( path->type, box->type ) ) + { + if( i == number ) + { + /* Found a box. Move to a child box. */ + parent = box; + ++path; + break; + } + ++i; + } + entry = entry->next; + } + if( LSMASH_IS_NON_EXISTING_BOX( parent ) ) + return NULL; + } + return entry; +} + +/* box destructors + * TODO: To eliminate REMOVE_LIST_BOX_2(), an eliminator should be determined when initializing + * the list to which the eliminator belongs. */ +#define REMOVE_BOX( box_name ) \ + isom_remove_predefined_box( box_name ) + +#define REMOVE_BOX_IN_LIST( box_name ) \ + isom_remove_box_in_predefined_list( box_name ) + +#define REMOVE_LIST_BOX_TEMPLATE( REMOVER, box_name ) \ + do \ + { \ + lsmash_list_destroy( box_name->list ); \ + REMOVER( box_name ); \ + } while( 0 ) + +#define REMOVE_LIST_BOX( box_name ) \ + REMOVE_LIST_BOX_TEMPLATE( REMOVE_BOX, box_name ) + +#define REMOVE_LIST_BOX_IN_LIST( box_name ) \ + REMOVE_LIST_BOX_TEMPLATE( REMOVE_BOX_IN_LIST, box_name ) + +#define DEFINE_SIMPLE_BOX_REMOVER_TEMPLATE( ... ) \ + CALL_FUNC_DEFAULT_ARGS( DEFINE_SIMPLE_BOX_REMOVER_TEMPLATE, __VA_ARGS__ ) +#define DEFINE_SIMPLE_BOX_REMOVER_TEMPLATE_3( REMOVER, box_name, ... ) \ + static void isom_remove_##box_name( isom_##box_name##_t *box_name ) \ + { \ + REMOVER( box_name, __VA_ARGS__ ); \ + } +#define DEFINE_SIMPLE_BOX_REMOVER_TEMPLATE_2( REMOVER, box_name ) \ + static void isom_remove_##box_name( isom_##box_name##_t *box_name ) \ + { \ + REMOVER( box_name ); \ + } + +#define DEFINE_SIMPLE_BOX_REMOVER( func_name, box_name ) \ + DEFINE_SIMPLE_BOX_REMOVER_TEMPLATE( REMOVE_BOX, box_name ) + +#define DEFINE_SIMPLE_BOX_IN_LIST_REMOVER( func_name, box_name ) \ + DEFINE_SIMPLE_BOX_REMOVER_TEMPLATE( REMOVE_BOX_IN_LIST, box_name ) + +#define DEFINE_SIMPLE_LIST_BOX_REMOVER( func_name, box_name ) \ + DEFINE_SIMPLE_BOX_REMOVER_TEMPLATE( REMOVE_LIST_BOX, box_name ) + +#define DEFINE_SIMPLE_LIST_BOX_IN_LIST_REMOVER( func_name, box_name ) \ + DEFINE_SIMPLE_BOX_REMOVER_TEMPLATE( REMOVE_LIST_BOX_IN_LIST, box_name ) + +static void isom_remove_predefined_box( void *opaque_box ) +{ + isom_box_t *box = (isom_box_t *)opaque_box; + if( LSMASH_IS_EXISTING_BOX( box ) + && LSMASH_IS_EXISTING_BOX( box->parent ) ) + { + isom_box_t **p = (isom_box_t **)(((int8_t *)box->parent) + box->offset_in_parent); + if( *p == box ) + *p = box->nonexist_ptr; + } +} + +/* We always free boxes through the extension list of the parent box. + * Therefore, don't free boxes through any list other than the extension list. */ +static void isom_remove_box_in_predefined_list( void *opaque_box ) +{ + isom_box_t *box = (isom_box_t *)opaque_box; + if( LSMASH_IS_EXISTING_BOX( box ) + && LSMASH_IS_EXISTING_BOX( box->parent ) ) + { + lsmash_entry_list_t *list = (lsmash_entry_list_t *)(((int8_t *)box->parent) + box->offset_in_parent); + if( list ) + for( lsmash_entry_t *entry = list->head; entry; entry = entry->next ) + if( box == entry->data ) + { + /* We don't free this box here. + * Because of freeing an entry of the list here, don't pass the list to free this box. + * Or double free. */ + entry->data = NULL; + lsmash_list_remove_entry_direct( list, entry ); + break; + } + } +} + +/* Remove a box by the pointer containing its address. + * In addition, remove from the extension list of the parent box if possible. + * Don't call this function within a function freeing one or more entries of any extension list because of double free. + * Basically, don't use this function as a callback function. */ +void isom_remove_box_by_itself( void *opaque_box ) +{ + isom_box_t *box = (isom_box_t *)opaque_box; + if( LSMASH_IS_NON_EXISTING_BOX( box ) ) + return; + if( LSMASH_IS_EXISTING_BOX( box->parent ) ) + { + isom_box_t *parent = box->parent; + for( lsmash_entry_t *entry = parent->extensions.head; entry; entry = entry->next ) + if( box == entry->data ) + { + /* Free the corresponding entry here, therefore don't call this function as a callback function + * if a function frees the same entry later and calls this function. */ + lsmash_list_remove_entry_direct( &parent->extensions, entry ); + return; + } + } + isom_remove_extension_box( box ); +} + +void isom_remove_unknown_box( isom_unknown_box_t *unknown_box ) +{ + lsmash_free( unknown_box->unknown_field ); +} + +static void isom_remove_file_abstract( isom_file_abstract_t *file_abstract ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( file_abstract ) ) + return; + isom_printer_destory_list( file_abstract ); + isom_remove_timelines( file_abstract ); + lsmash_free( file_abstract->compatible_brands ); + lsmash_bs_cleanup( file_abstract->bs ); + lsmash_importer_destroy( file_abstract->importer ); + if( file_abstract->fragment ) + { + lsmash_list_destroy( file_abstract->fragment->pool ); + lsmash_free( file_abstract->fragment ); + } + REMOVE_BOX_IN_LIST( file_abstract ); +} + +static void isom_remove_ftyp( isom_ftyp_t *ftyp ) +{ + lsmash_free( ftyp->compatible_brands ); + REMOVE_BOX( ftyp ); +} + +static void isom_remove_iods( isom_iods_t *iods ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( iods ) ) + return; + mp4sys_remove_descriptor( iods->OD ); + REMOVE_BOX( iods ); +} + +static void isom_remove_trak( isom_trak_t *trak ) +{ + if( trak->cache ) + { + isom_remove_sample_pool( trak->cache->chunk.pool ); + lsmash_list_destroy( trak->cache->roll.pool ); + lsmash_free( trak->cache->rap ); + lsmash_free( trak->cache->fragment ); + lsmash_free( trak->cache ); + } + REMOVE_BOX_IN_LIST( trak ); +} + +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_tkhd, tkhd ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_clef, clef ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_prof, prof ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_enof, enof ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_tapt, tapt ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_edts, edts ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_tref, tref ) +DEFINE_SIMPLE_LIST_BOX_REMOVER( isom_remove_elst, elst ) + +static void isom_remove_track_reference_type( isom_tref_type_t *ref ) +{ + lsmash_free( ref->track_ID ); + isom_remove_box_in_predefined_list( ref ); +} + +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_mdhd, mdhd ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_vmhd, vmhd ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_smhd, smhd ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_hmhd, hmhd ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_nmhd, nmhd ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_gmhd, gmhd ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_gmin, gmin ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_text, text ) + +static void isom_remove_hdlr( isom_hdlr_t *hdlr ) +{ + lsmash_free( hdlr->componentName ); + REMOVE_BOX( hdlr ); +} + +static void isom_remove_glbl( isom_glbl_t *glbl ) +{ + lsmash_free( glbl->header_data ); +} + +static void isom_remove_esds( isom_esds_t *esds ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( esds ) ) + return; + mp4sys_remove_descriptor( esds->ES ); +} + +DEFINE_SIMPLE_LIST_BOX_REMOVER( isom_remove_ftab, ftab ) + +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_frma, frma ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_enda, enda ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_mp4a, mp4a ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_terminator, terminator ) + +static void isom_remove_chan( isom_chan_t *chan ) +{ + lsmash_free( chan->channelDescriptions ); +} + +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_stsd, stsd ) + +static void isom_remove_visual_description( isom_sample_entry_t *description ) +{ + isom_visual_entry_t *visual = (isom_visual_entry_t *)description; + lsmash_free( visual->color_table.array ); + isom_remove_box_in_predefined_list( visual ); +} + +static void isom_remove_audio_description( isom_sample_entry_t *description ) +{ + isom_remove_box_in_predefined_list( description ); +} + +static void isom_remove_hint_description( isom_sample_entry_t *description ) +{ + isom_hint_entry_t *hint = (isom_hint_entry_t *)description; + isom_remove_box_in_predefined_list( hint ); +} + +static void isom_remove_metadata_description( isom_sample_entry_t *description ) +{ + isom_remove_box_in_predefined_list( description ); +} + +static void isom_remove_tx3g_description( isom_sample_entry_t *description ) +{ + isom_remove_box_in_predefined_list( description ); +} + +static void isom_remove_qt_text_description( isom_sample_entry_t *description ) +{ + isom_qt_text_entry_t *text = (isom_qt_text_entry_t *)description; + lsmash_free( text->font_name ); + isom_remove_box_in_predefined_list( text ); +} + +static void isom_remove_mp4s_description( isom_sample_entry_t *description ) +{ + isom_remove_box_in_predefined_list( description ); +} + +void isom_remove_sample_description( isom_sample_entry_t *sample ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( sample ) ) + return; + lsmash_codec_type_t sample_type = sample->type; + if( lsmash_check_box_type_identical( sample_type, LSMASH_CODEC_TYPE_RAW ) ) + { + if( sample->manager & LSMASH_VIDEO_DESCRIPTION ) + { + isom_remove_visual_description( sample ); + return; + } + else if( sample->manager & LSMASH_AUDIO_DESCRIPTION ) + { + isom_remove_audio_description( sample ); + return; + } + } + static struct description_remover_table_tag + { + lsmash_codec_type_t type; + void (*func)( isom_sample_entry_t * ); + } description_remover_table[160] = { { LSMASH_CODEC_TYPE_INITIALIZER, NULL } }; + if( !description_remover_table[0].func ) + { + /* Initialize the table. */ + int i = 0; +#define ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( type, func ) \ + description_remover_table[i++] = (struct description_remover_table_tag){ type, func } + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_AVC1_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_AVC2_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_AVC3_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_AVC4_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_AVCP_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_HVC1_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_HEV1_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_SVC1_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_MVC1_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_MVC2_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_MP4V_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_DRAC_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_ENCV_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_MJP2_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_S263_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_VC_1_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_2VUY_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_CFHD_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_DV10_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_DVOO_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_DVOR_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_DVTV_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_DVVT_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_HD10_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_M105_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_PNTG_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_SVQ1_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_SVQ3_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_SHR0_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_SHR1_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_SHR2_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_SHR3_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_SHR4_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_WRLE_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_APCH_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_APCN_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_APCS_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_APCO_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_AP4H_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_AP4X_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_CIVD_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_DRAC_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_DVC_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_DVCP_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_DVPP_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_DV5N_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_DV5P_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_DVH2_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_DVH3_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_DVH5_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_DVH6_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_DVHP_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_DVHQ_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_FLIC_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_GIF_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_H261_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_H263_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_JPEG_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_MJPA_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_MJPB_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_PNG_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_RLE_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_RPZA_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_TGA_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_TIFF_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_ULRA_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_ULRG_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_ULY2_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_ULY0_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_ULH2_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_ULH0_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_UQY2_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_V210_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_V216_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_V308_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_V408_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_V410_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_YUV2_VIDEO, isom_remove_visual_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_MP4A_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_AC_3_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_ALAC_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_DTSEL_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_DTSDL_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_DTSC_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_DTSE_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_DTSH_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_DTSL_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_DTSX_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_EC_3_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_SAMR_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_SAWB_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_MP4A_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_23NI_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_NONE_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_LPCM_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_SOWT_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_TWOS_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_FL32_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_FL64_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_IN24_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_IN32_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_NOT_SPECIFIED, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_DRA1_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_ENCA_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_G719_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_G726_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_M4AE_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_MLPA_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_SAWP_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_SEVC_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_SQCP_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_SSMV_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_TWOS_AUDIO, isom_remove_audio_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_FDP_HINT, isom_remove_hint_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_M2TS_HINT, isom_remove_hint_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_PM2T_HINT, isom_remove_hint_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_PRTP_HINT, isom_remove_hint_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_RM2T_HINT, isom_remove_hint_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_RRTP_HINT, isom_remove_hint_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_RSRP_HINT, isom_remove_hint_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_RTP_HINT , isom_remove_hint_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_SM2T_HINT, isom_remove_hint_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_SRTP_HINT, isom_remove_hint_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_IXSE_META, isom_remove_metadata_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_METT_META, isom_remove_metadata_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_METX_META, isom_remove_metadata_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_MLIX_META, isom_remove_metadata_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_OKSD_META, isom_remove_metadata_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_SVCM_META, isom_remove_metadata_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_TEXT_META, isom_remove_metadata_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_URIM_META, isom_remove_metadata_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_XML_META, isom_remove_metadata_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_TX3G_TEXT, isom_remove_tx3g_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( QT_CODEC_TYPE_TEXT_TEXT, isom_remove_qt_text_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( ISOM_CODEC_TYPE_MP4S_SYSTEM, isom_remove_mp4s_description ); + ADD_DESCRIPTION_REMOVER_TABLE_ELEMENT( LSMASH_CODEC_TYPE_UNSPECIFIED, NULL ); + } + for( int i = 0; description_remover_table[i].func; i++ ) + if( lsmash_check_codec_type_identical( sample_type, description_remover_table[i].type ) ) + { + description_remover_table[i].func( sample ); + return; + } +} + +DEFINE_SIMPLE_LIST_BOX_REMOVER( isom_remove_stts, stts ) +DEFINE_SIMPLE_LIST_BOX_REMOVER( isom_remove_ctts, ctts ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_cslg, cslg ) +DEFINE_SIMPLE_LIST_BOX_REMOVER( isom_remove_stsc, stsc ) +DEFINE_SIMPLE_LIST_BOX_REMOVER( isom_remove_stsz, stsz ) +DEFINE_SIMPLE_LIST_BOX_REMOVER( isom_remove_stz2, stz2 ) +DEFINE_SIMPLE_LIST_BOX_REMOVER( isom_remove_stss, stss ) +DEFINE_SIMPLE_LIST_BOX_REMOVER( isom_remove_stps, stps ) +DEFINE_SIMPLE_LIST_BOX_REMOVER( isom_remove_stco, stco ) + +static void isom_remove_sdtp( isom_sdtp_t *sdtp ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( sdtp ) ) + return; + lsmash_list_destroy( sdtp->list ); + REMOVE_BOX( sdtp ); +} + +static void isom_remove_sgpd( isom_sgpd_t *sgpd ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( sgpd ) ) + return; + lsmash_list_destroy( sgpd->list ); + REMOVE_BOX_IN_LIST( sgpd ); +} + +static void isom_remove_sbgp( isom_sbgp_t *sbgp ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( sbgp ) ) + return; + lsmash_list_destroy( sbgp->list ); + REMOVE_BOX_IN_LIST( sbgp ); +} + +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_stbl, stbl ) + +static void isom_remove_dref_entry( isom_dref_entry_t *data_entry ) +{ + lsmash_free( data_entry->name ); + lsmash_free( data_entry->location ); + isom_remove_box_in_predefined_list( data_entry ); +} + +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_dref, dref ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_dinf, dinf ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_minf, minf ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_mdia, mdia ) +DEFINE_SIMPLE_LIST_BOX_REMOVER( isom_remove_chpl, chpl ) +DEFINE_SIMPLE_LIST_BOX_REMOVER( isom_remove_keys, keys ) + +static void isom_remove_mean( isom_mean_t *mean ) +{ + lsmash_free( mean->meaning_string ); + REMOVE_BOX( mean ); +} + +static void isom_remove_name( isom_name_t *name ) +{ + lsmash_free( name->name ); + REMOVE_BOX( name ); +} + +static void isom_remove_data( isom_data_t *data ) +{ + lsmash_free( data->value ); + REMOVE_BOX( data ); +} + +DEFINE_SIMPLE_BOX_IN_LIST_REMOVER( isom_remove_metaitem, metaitem ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_ilst, ilst ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_meta, meta ) + +static void isom_remove_cprt( isom_cprt_t *cprt ) +{ + lsmash_free( cprt->notice ); + REMOVE_BOX_IN_LIST( cprt ); +} + +static void isom_remove_rtp( isom_rtp_t *rtp ) +{ + lsmash_free( rtp->sdptext ); + REMOVE_BOX( rtp ); +} + +static void isom_remove_sdp( isom_sdp_t *sdp ) +{ + lsmash_free( sdp->sdptext ); + REMOVE_BOX( sdp ); +} + +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_udta, udta ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_WLOC, WLOC ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_LOOP, LOOP ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_SelO, SelO ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_AllF, AllF ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_hnti, hnti ) + +static void isom_remove_ctab( isom_ctab_t *ctab ) +{ + lsmash_free( ctab->color_table.array ); + REMOVE_BOX( ctab ); +} + +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_mvex, mvex ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_mvhd, mvhd ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_mehd, mehd ) +DEFINE_SIMPLE_BOX_IN_LIST_REMOVER( isom_remove_trex, trex ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_moov, moov ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_mdat, mdat ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_mfhd, mfhd ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_tfhd, tfhd ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_tfdt, tfdt ) + +static void isom_remove_trun( isom_trun_t *trun ) +{ + lsmash_list_destroy( trun->optional ); + REMOVE_BOX_IN_LIST( trun ); +} + +DEFINE_SIMPLE_BOX_IN_LIST_REMOVER( isom_remove_traf, traf ) +DEFINE_SIMPLE_BOX_IN_LIST_REMOVER( isom_remove_moof, moof ) + +static void isom_remove_free( isom_free_t *skip ) +{ + lsmash_free( skip->data ); +} +#define isom_remove_skip isom_remove_free + +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_mfra, mfra ) +DEFINE_SIMPLE_BOX_REMOVER( isom_remove_mfro, mfro ) +DEFINE_SIMPLE_LIST_BOX_IN_LIST_REMOVER( isom_remove_tfra, tfra ) +DEFINE_SIMPLE_LIST_BOX_IN_LIST_REMOVER( isom_remove_sidx, sidx ) + +static void isom_remove_styp( isom_styp_t *styp ) +{ + lsmash_free( styp->compatible_brands ); + REMOVE_BOX_IN_LIST( styp ); +} + +#define isom_remove_elst_entry lsmash_free +#define isom_remove_stts_entry lsmash_free +#define isom_remove_ctts_entry lsmash_free +#define isom_remove_stsz_entry lsmash_free +#define isom_remove_stz2_entry lsmash_free +#define isom_remove_stss_entry lsmash_free +#define isom_remove_stps_entry lsmash_free +#define isom_remove_sdtp_entry lsmash_free +#define isom_remove_stsc_entry lsmash_free +#define isom_remove_stco_entry lsmash_free +#define isom_remove_co64_entry lsmash_free +#define isom_remove_sgpd_entry lsmash_free +#define isom_remove_sbgp_entry lsmash_free +#define isom_remove_trun_entry lsmash_free +#define isom_remove_tfra_entry lsmash_free +#define isom_remove_sidx_entry lsmash_free + +static void isom_remove_ftab_entry( isom_font_record_t *font_record ) +{ + if( !font_record ) + return; + lsmash_free( font_record->font_name ); + lsmash_free( font_record ); +} + +static void isom_remove_chpl_entry( isom_chpl_entry_t *data ) +{ + if( !data ) + return; + lsmash_free( data->chapter_name ); + lsmash_free( data ); +} + +static void isom_remove_keys_entry( isom_keys_entry_t *data ) +{ + if( !data ) + return; + lsmash_free( data->key_value ); + lsmash_free( data ); +} + +/* box size updater */ +uint64_t isom_update_box_size( void *opaque_box ) +{ + isom_box_t *box = (isom_box_t *)opaque_box; + assert( LSMASH_IS_EXISTING_BOX( box ) ); + if( box->manager & LSMASH_WRITTEN_BOX ) + /* No need to calculate the size of this box since the size is already decided and fixed. */ + return box->size; + uint64_t size = 0; + if( box->write ) + { + /* Calculate the size of this box excluding its children with a fake bytestream writer. */ + { + lsmash_bs_t fake_bs = { NULL }; + if( box->write( &fake_bs, box ) == 0 ) + size = lsmash_bs_get_valid_data_size( &fake_bs ); + } + /* Calculate the size of the children if no error. */ + if( size >= ISOM_BASEBOX_COMMON_SIZE ) + { + for( lsmash_entry_t *entry = box->extensions.head; entry; entry = entry->next ) + if( entry->data ) + size += isom_update_box_size( entry->data ); + /* Check large size. */ + if( size > UINT32_MAX ) + size += 8; + } + else + /* TODO: add error handling. */ + size = 0; + } + box->size = size; + return size; +} + +/* box adding functions */ +#define ATTACH_EXACTLY_ONE_BOX_TO_PARENT( box_name, parent_type ) \ + do \ + { \ + size_t offset_in_parent = offsetof( parent_type, box_name ); \ + isom_box_t **p = (isom_box_t **)(((int8_t *)box_name->parent) \ + + offset_in_parent); \ + assert( *p ); \ + if( LSMASH_IS_NON_EXISTING_BOX( *p ) ) \ + { \ + *p = (isom_box_t *)box_name; \ + (*p)->offset_in_parent = offset_in_parent; \ + } \ + } while( 0 ) + +#define ADD_BOX_TO_PREDEFINED_LIST( box_name, parent_name ) \ + if( lsmash_list_add_entry( &parent_name->box_name##_list, box_name ) < 0 ) \ + { \ + lsmash_list_remove_entry_tail( &parent_name->extensions ); \ + return isom_non_existing_##box_name(); \ + } \ + box_name->offset_in_parent = offsetof( isom_##parent_name##_t, box_name##_list ) + +#define INIT_BOX_COMMON0( box_name, parent_name, box_type, precedence ) \ + const isom_extension_destructor_t isom_remove_##box_name = NULL; \ + isom_init_box_common( box_name, parent_name, box_type, precedence, isom_remove_##box_name ) +#define INIT_BOX_COMMON1( box_name, parent_name, box_type, precedence ) \ + isom_init_box_common( box_name, parent_name, box_type, precedence, isom_remove_##box_name ) + +#define CREATE_BOX( box_name, parent_name, box_type, precedence, has_destructor ) \ + if( LSMASH_IS_NON_EXISTING_BOX( (isom_box_t *)parent_name ) ) \ + return isom_non_existing_##box_name(); \ + isom_##box_name##_t *box_name = ALLOCATE_BOX( box_name ); \ + if( LSMASH_IS_NON_EXISTING_BOX( box_name ) ) \ + return box_name; \ + INIT_BOX_COMMON ## has_destructor( box_name, parent_name, box_type, precedence ); \ + if( isom_add_box_to_extension_list( parent_name, box_name ) < 0 ) \ + { \ + lsmash_free( box_name ); \ + return isom_non_existing_##box_name(); \ + } +#define CREATE_LIST_BOX( box_name, parent_name, box_type, precedence, has_destructor ) \ + CREATE_BOX( box_name, parent_name, box_type, precedence, has_destructor ); \ + box_name->list = lsmash_list_create( isom_remove_##box_name##_entry ); \ + if( !box_name->list ) \ + { \ + lsmash_list_remove_entry_tail( &parent_name->extensions ); \ + return isom_non_existing_##box_name(); \ + } + +#define ADD_BOX_TEMPLATE( box_name, parent_name, box_type, precedence, BOX_CREATOR ) \ + BOX_CREATOR( box_name, parent_name, box_type, precedence, 1 ); \ + if( LSMASH_IS_NON_EXISTING_BOX( parent_name->box_name ) ) \ + { \ + parent_name->box_name = box_name; \ + box_name->offset_in_parent = offsetof( isom_##parent_name##_t, box_name ); \ + } do {} while( 0 ) +#define ADD_BOX_IN_LIST_TEMPLATE( box_name, parent_name, box_type, precedence, BOX_CREATOR ) \ + BOX_CREATOR( box_name, parent_name, box_type, precedence, 1 ); \ + ADD_BOX_TO_PREDEFINED_LIST( box_name, parent_name ) + +#define ADD_BOX( box_name, parent_name, box_type, precedence ) \ + ADD_BOX_TEMPLATE( box_name, parent_name, box_type, precedence, CREATE_BOX ) +#define ADD_BOX_IN_LIST( box_name, parent_name, box_type, precedence ) \ + ADD_BOX_IN_LIST_TEMPLATE( box_name, parent_name, box_type, precedence, CREATE_BOX ) +#define ADD_LIST_BOX( box_name, parent_name, box_type, precedence ) \ + ADD_BOX_TEMPLATE( box_name, parent_name, box_type, precedence, CREATE_LIST_BOX ) +#define ADD_LIST_BOX_IN_LIST( box_name, parent_name, box_type, precedence ) \ + ADD_BOX_IN_LIST_TEMPLATE( box_name, parent_name, box_type, precedence, CREATE_LIST_BOX ) + +#define DEFINE_SIMPLE_BOX_ADDER_TEMPLATE( ... ) CALL_FUNC_DEFAULT_ARGS( DEFINE_SIMPLE_BOX_ADDER_TEMPLATE, __VA_ARGS__ ) +#define DEFINE_SIMPLE_BOX_ADDER_TEMPLATE_6( ADDER, box_name, parent_name, box_type, precedence, postprocess ) \ + isom_##box_name##_t *isom_add_##box_name( isom_##parent_name##_t *parent_name ) \ + { \ + ADDER( box_name, parent_name, box_type, precedence ); \ + do { postprocess } while( 0 ); \ + return box_name; \ + } +#define DEFINE_SIMPLE_BOX_ADDER_TEMPLATE_5( ADDER, box_name, parent_name, box_type, precedence ) \ + DEFINE_SIMPLE_BOX_ADDER_TEMPLATE_6( ADDER, box_name, parent_name, box_type, precedence, ) + +#define DEFINE_SIMPLE_BOX_ADDER( func_name, ... ) \ + DEFINE_SIMPLE_BOX_ADDER_TEMPLATE( ADD_BOX, __VA_ARGS__ ) +#define DEFINE_SIMPLE_BOX_IN_LIST_ADDER( func_name, ... ) \ + DEFINE_SIMPLE_BOX_ADDER_TEMPLATE( ADD_BOX_IN_LIST, __VA_ARGS__ ) +#define DEFINE_SIMPLE_LIST_BOX_ADDER( func_name, ... ) \ + DEFINE_SIMPLE_BOX_ADDER_TEMPLATE( ADD_LIST_BOX, __VA_ARGS__ ) + +#define DEFINE_SIMPLE_SAMPLE_EXTENSION_ADDER( func_name, box_name, parent_name, box_type, precedence, has_destructor, parent_type ) \ + isom_##box_name##_t *isom_add_##box_name( parent_type *parent_name ) \ + { \ + CREATE_BOX( box_name, parent_name, box_type, precedence, has_destructor ); \ + return box_name; \ + } + +DEFINE_SIMPLE_BOX_IN_LIST_ADDER( isom_add_file_abstract, file_abstract, root_abstract, LSMASH_BOX_TYPE_UNSPECIFIED, 0, + file_abstract->file = file_abstract; ) + +isom_tref_type_t *isom_add_track_reference_type( isom_tref_t *tref, isom_track_reference_type type ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( tref ) ) + return isom_non_existing_tref_type(); + isom_tref_type_t *tref_type = ALLOCATE_BOX( tref_type ); + if( LSMASH_IS_NON_EXISTING_BOX( tref_type ) ) + return tref_type; + /* Initialize common fields. */ + tref_type->class = &lsmash_box_class; + tref_type->root = tref->root; + tref_type->file = tref->file; + tref_type->parent = (isom_box_t *)tref; + tref_type->precedence = LSMASH_BOX_PRECEDENCE_ISOM_TREF_TYPE; + tref_type->destruct = (isom_extension_destructor_t)isom_remove_track_reference_type; + tref_type->size = 0; + tref_type->type = lsmash_form_iso_box_type( type ); + isom_set_box_writer( (isom_box_t *)tref_type ); + if( isom_add_box_to_extension_list( tref, tref_type ) < 0 ) + { + lsmash_free( tref_type ); + return isom_non_existing_tref_type(); + } + if( lsmash_list_add_entry( &tref->ref_list, tref_type ) < 0 ) + { + lsmash_list_remove_entry_tail( &tref->extensions ); + return isom_non_existing_tref_type(); + } + tref_type->offset_in_parent = offsetof( isom_tref_t, ref_list ); + return tref_type; +} + +DEFINE_SIMPLE_BOX_ADDER( isom_add_terminator, terminator, wave, QT_BOX_TYPE_TERMINATOR, LSMASH_BOX_PRECEDENCE_QTFF_TERMINATOR ) +DEFINE_SIMPLE_BOX_ADDER( isom_add_frma, frma, wave, QT_BOX_TYPE_FRMA, LSMASH_BOX_PRECEDENCE_QTFF_FRMA ) +DEFINE_SIMPLE_BOX_ADDER( isom_add_enda, enda, wave, QT_BOX_TYPE_ENDA, LSMASH_BOX_PRECEDENCE_QTFF_ENDA ) +DEFINE_SIMPLE_BOX_ADDER( isom_add_mp4a, mp4a, wave, QT_BOX_TYPE_MP4A, LSMASH_BOX_PRECEDENCE_QTFF_MP4A ) +DEFINE_SIMPLE_LIST_BOX_ADDER( isom_add_ftab, ftab, tx3g_entry, ISOM_BOX_TYPE_FTAB, LSMASH_BOX_PRECEDENCE_ISOM_FTAB ) +DEFINE_SIMPLE_BOX_ADDER( isom_add_ftyp, ftyp, file_abstract, ISOM_BOX_TYPE_FTYP, LSMASH_BOX_PRECEDENCE_ISOM_FTYP ) +DEFINE_SIMPLE_BOX_ADDER( isom_add_moov, moov, file_abstract, ISOM_BOX_TYPE_MOOV, LSMASH_BOX_PRECEDENCE_ISOM_MOOV ) +DEFINE_SIMPLE_BOX_ADDER( isom_add_mvhd, mvhd, moov, ISOM_BOX_TYPE_MVHD, LSMASH_BOX_PRECEDENCE_ISOM_MVHD ) +DEFINE_SIMPLE_BOX_ADDER( isom_add_iods, iods, moov, ISOM_BOX_TYPE_IODS, LSMASH_BOX_PRECEDENCE_ISOM_IODS ) + +isom_ctab_t *isom_add_ctab( void *parent_box ) +{ + /* According to QuickTime File Format Specification, this box is placed inside Movie Box if present. + * However, sometimes this box occurs inside an image description entry or the end of Sample Description Box. */ + isom_box_t *parent = (isom_box_t *)parent_box; + if( LSMASH_IS_NON_EXISTING_BOX( parent ) ) + return isom_non_existing_ctab(); + CREATE_BOX( ctab, parent, QT_BOX_TYPE_CTAB, LSMASH_BOX_PRECEDENCE_QTFF_CTAB, 1 ); + if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MOOV ) ) + ATTACH_EXACTLY_ONE_BOX_TO_PARENT( ctab, isom_moov_t ); + return ctab; +} + +isom_trak_t *isom_add_trak( isom_moov_t *moov ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( moov ) + || LSMASH_IS_NON_EXISTING_BOX( moov->file ) ) + return isom_non_existing_trak(); + CREATE_BOX( trak, moov, ISOM_BOX_TYPE_TRAK, LSMASH_BOX_PRECEDENCE_ISOM_TRAK, 1 ); + isom_fragment_t *fragment = NULL; + isom_cache_t *cache = lsmash_malloc_zero( sizeof(isom_cache_t) ); + if( !cache ) + goto fail; + if( moov->file->fragment ) + { + fragment = lsmash_malloc_zero( sizeof(isom_fragment_t) ); + if( !fragment ) + goto fail; + cache->fragment = fragment; + fragment->largest_cts = LSMASH_TIMESTAMP_UNDEFINED; + fragment->subsegment.largest_cts = LSMASH_TIMESTAMP_UNDEFINED; + fragment->subsegment.smallest_cts = LSMASH_TIMESTAMP_UNDEFINED; + fragment->subsegment.first_sample_cts = LSMASH_TIMESTAMP_UNDEFINED; + fragment->subsegment.first_ed_cts = LSMASH_TIMESTAMP_UNDEFINED; + fragment->subsegment.first_rp_cts = LSMASH_TIMESTAMP_UNDEFINED; + } + if( lsmash_list_add_entry( &moov->trak_list, trak ) < 0 ) + goto fail; + trak->offset_in_parent = offsetof( isom_moov_t, trak_list ); + trak->cache = cache; + return trak; +fail: + lsmash_free( fragment ); + lsmash_free( cache ); + lsmash_list_remove_entry_tail( &moov->extensions ); + return isom_non_existing_trak(); +} + +DEFINE_SIMPLE_BOX_ADDER ( isom_add_tkhd, tkhd, trak, ISOM_BOX_TYPE_TKHD, LSMASH_BOX_PRECEDENCE_ISOM_TKHD ) +DEFINE_SIMPLE_BOX_ADDER ( isom_add_tapt, tapt, trak, QT_BOX_TYPE_TAPT, LSMASH_BOX_PRECEDENCE_QTFF_TAPT ) +DEFINE_SIMPLE_BOX_ADDER ( isom_add_clef, clef, tapt, QT_BOX_TYPE_CLEF, LSMASH_BOX_PRECEDENCE_QTFF_CLEF ) +DEFINE_SIMPLE_BOX_ADDER ( isom_add_prof, prof, tapt, QT_BOX_TYPE_PROF, LSMASH_BOX_PRECEDENCE_QTFF_PROF ) +DEFINE_SIMPLE_BOX_ADDER ( isom_add_enof, enof, tapt, QT_BOX_TYPE_ENOF, LSMASH_BOX_PRECEDENCE_QTFF_ENOF ) +DEFINE_SIMPLE_BOX_ADDER ( isom_add_edts, edts, trak, ISOM_BOX_TYPE_EDTS, LSMASH_BOX_PRECEDENCE_ISOM_EDTS ) +DEFINE_SIMPLE_LIST_BOX_ADDER( isom_add_elst, elst, edts, ISOM_BOX_TYPE_ELST, LSMASH_BOX_PRECEDENCE_ISOM_ELST ) +DEFINE_SIMPLE_BOX_ADDER ( isom_add_tref, tref, trak, ISOM_BOX_TYPE_TREF, LSMASH_BOX_PRECEDENCE_ISOM_TREF ) +DEFINE_SIMPLE_BOX_ADDER ( isom_add_mdia, mdia, trak, ISOM_BOX_TYPE_MDIA, LSMASH_BOX_PRECEDENCE_ISOM_MDIA ) +DEFINE_SIMPLE_BOX_ADDER ( isom_add_mdhd, mdhd, mdia, ISOM_BOX_TYPE_MDHD, LSMASH_BOX_PRECEDENCE_ISOM_MDHD ) + +isom_hdlr_t *isom_add_hdlr( void *parent_box ) +{ + isom_box_t *parent = (isom_box_t *)parent_box; + if( LSMASH_IS_NON_EXISTING_BOX( parent ) ) + return isom_non_existing_hdlr(); + CREATE_BOX( hdlr, parent, ISOM_BOX_TYPE_HDLR, LSMASH_BOX_PRECEDENCE_ISOM_HDLR, 1 ); + if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MDIA ) ) + ATTACH_EXACTLY_ONE_BOX_TO_PARENT( hdlr, isom_mdia_t ); + else if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_META ) + || lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_META ) ) + ATTACH_EXACTLY_ONE_BOX_TO_PARENT( hdlr, isom_meta_t ); + else if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MINF ) ) + ATTACH_EXACTLY_ONE_BOX_TO_PARENT( hdlr, isom_minf_t ); + else + assert( 0 ); + return hdlr; +} + +DEFINE_SIMPLE_BOX_ADDER( isom_add_minf, minf, mdia, ISOM_BOX_TYPE_MINF, LSMASH_BOX_PRECEDENCE_ISOM_MINF ) +DEFINE_SIMPLE_BOX_ADDER( isom_add_vmhd, vmhd, minf, ISOM_BOX_TYPE_VMHD, LSMASH_BOX_PRECEDENCE_ISOM_VMHD ) +DEFINE_SIMPLE_BOX_ADDER( isom_add_smhd, smhd, minf, ISOM_BOX_TYPE_SMHD, LSMASH_BOX_PRECEDENCE_ISOM_SMHD ) +DEFINE_SIMPLE_BOX_ADDER( isom_add_hmhd, hmhd, minf, ISOM_BOX_TYPE_HMHD, LSMASH_BOX_PRECEDENCE_ISOM_HMHD ) +DEFINE_SIMPLE_BOX_ADDER( isom_add_nmhd, nmhd, minf, ISOM_BOX_TYPE_NMHD, LSMASH_BOX_PRECEDENCE_ISOM_NMHD ) +DEFINE_SIMPLE_BOX_ADDER( isom_add_gmhd, gmhd, minf, QT_BOX_TYPE_GMHD, LSMASH_BOX_PRECEDENCE_QTFF_GMHD ) +DEFINE_SIMPLE_BOX_ADDER( isom_add_gmin, gmin, gmhd, QT_BOX_TYPE_GMIN, LSMASH_BOX_PRECEDENCE_QTFF_GMIN ) +DEFINE_SIMPLE_BOX_ADDER( isom_add_text, text, gmhd, QT_BOX_TYPE_TEXT, LSMASH_BOX_PRECEDENCE_QTFF_TEXT ) + +isom_dinf_t *isom_add_dinf( void *parent_box ) +{ + isom_box_t *parent = (isom_box_t *)parent_box; + if( LSMASH_IS_NON_EXISTING_BOX( parent ) ) + return isom_non_existing_dinf(); + CREATE_BOX( dinf, parent, ISOM_BOX_TYPE_DINF, LSMASH_BOX_PRECEDENCE_ISOM_DINF, 1 ); + if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MINF ) ) + ATTACH_EXACTLY_ONE_BOX_TO_PARENT( dinf, isom_minf_t ); + else if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_META ) + || lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_META ) ) + ATTACH_EXACTLY_ONE_BOX_TO_PARENT( dinf, isom_meta_t ); + else + assert( 0 ); + return dinf; +} + +isom_dref_entry_t *isom_add_dref_entry( isom_dref_t *dref, lsmash_box_type_t type ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( dref ) ) + return isom_non_existing_dref_entry(); + isom_dref_entry_t *dref_entry = ALLOCATE_BOX( dref_entry ); + if( LSMASH_IS_NON_EXISTING_BOX( dref_entry ) ) + return dref_entry; + isom_init_box_common( dref_entry, dref, type, LSMASH_BOX_PRECEDENCE_ISOM_DREF_ENTRY, isom_remove_dref_entry ); + if( isom_add_box_to_extension_list( dref, dref_entry ) < 0 ) + { + lsmash_free( dref_entry ); + return isom_non_existing_dref_entry(); + } + if( lsmash_list_add_entry( &dref->list, dref_entry ) < 0 ) + { + lsmash_list_remove_entry_tail( &dref->extensions ); + return isom_non_existing_dref_entry(); + } + dref_entry->offset_in_parent = offsetof( isom_dref_t, list ); + return dref_entry; +} + +DEFINE_SIMPLE_BOX_ADDER( isom_add_dref, dref, dinf, ISOM_BOX_TYPE_DREF, LSMASH_BOX_PRECEDENCE_ISOM_DREF ) +DEFINE_SIMPLE_BOX_ADDER( isom_add_stbl, stbl, minf, ISOM_BOX_TYPE_STBL, LSMASH_BOX_PRECEDENCE_ISOM_STBL ) +DEFINE_SIMPLE_BOX_ADDER( isom_add_stsd, stsd, stbl, ISOM_BOX_TYPE_STSD, LSMASH_BOX_PRECEDENCE_ISOM_STSD ) + +static void *isom_add_sample_description_entry +( + isom_stsd_t *stsd, + void *description +) +{ + assert( description ); + if( isom_add_box_to_extension_list( stsd, description ) < 0 ) + { + isom_remove_box_by_itself( description ); + return description; + } + if( lsmash_list_add_entry( &stsd->list, description ) < 0 ) + { + lsmash_list_remove_entry_tail( &stsd->extensions ); + return description; + } + ((isom_box_t *)description)->offset_in_parent = offsetof( isom_stsd_t, list ); + return description; +} + +isom_visual_entry_t *isom_add_visual_description( isom_stsd_t *stsd, lsmash_codec_type_t sample_type ) +{ + assert( LSMASH_IS_EXISTING_BOX( stsd ) ); + isom_visual_entry_t *visual = ALLOCATE_BOX( visual_entry ); + if( LSMASH_IS_NON_EXISTING_BOX( visual ) ) + return visual; + isom_init_box_common( visual, stsd, sample_type, LSMASH_BOX_PRECEDENCE_HM, isom_remove_visual_description ); + visual->manager |= LSMASH_VIDEO_DESCRIPTION; + return isom_add_sample_description_entry( stsd, visual ); +} + +isom_audio_entry_t *isom_add_audio_description( isom_stsd_t *stsd, lsmash_codec_type_t sample_type ) +{ + assert( LSMASH_IS_EXISTING_BOX( stsd ) ); + isom_audio_entry_t *audio = ALLOCATE_BOX( audio_entry ); + if( LSMASH_IS_NON_EXISTING_BOX( audio ) ) + return audio; + isom_init_box_common( audio, stsd, sample_type, LSMASH_BOX_PRECEDENCE_HM, isom_remove_audio_description ); + audio->manager |= LSMASH_AUDIO_DESCRIPTION; + return isom_add_sample_description_entry( stsd, audio ); +} + +isom_hint_entry_t *isom_add_hint_description( isom_stsd_t *stsd, lsmash_codec_type_t sample_type ) +{ + assert( stsd ); + isom_hint_entry_t *hint = ALLOCATE_BOX( hint_entry ); + if ( LSMASH_IS_NON_EXISTING_BOX( hint ) ) + return hint; + isom_init_box_common( hint, stsd, sample_type, LSMASH_BOX_PRECEDENCE_HM, isom_remove_hint_description ); + return isom_add_sample_description_entry( stsd, hint ); +} + +isom_qt_text_entry_t *isom_add_qt_text_description( isom_stsd_t *stsd ) +{ + assert( LSMASH_IS_EXISTING_BOX( stsd ) ); + isom_qt_text_entry_t *text = ALLOCATE_BOX( qt_text_entry ); + if( LSMASH_IS_NON_EXISTING_BOX( text ) ) + return text; + isom_init_box_common( text, stsd, QT_CODEC_TYPE_TEXT_TEXT, LSMASH_BOX_PRECEDENCE_HM, isom_remove_qt_text_description ); + return isom_add_sample_description_entry( stsd, text ); +} + +isom_tx3g_entry_t *isom_add_tx3g_description( isom_stsd_t *stsd ) +{ + assert( LSMASH_IS_EXISTING_BOX( stsd ) ); + isom_tx3g_entry_t *tx3g = ALLOCATE_BOX( tx3g_entry ); + if( LSMASH_IS_NON_EXISTING_BOX( tx3g ) ) + return tx3g; + isom_init_box_common( tx3g, stsd, ISOM_CODEC_TYPE_TX3G_TEXT, LSMASH_BOX_PRECEDENCE_HM, isom_remove_tx3g_description ); + return isom_add_sample_description_entry( stsd, tx3g ); +} + +isom_esds_t *isom_add_esds( void *parent_box ) +{ + isom_box_t *parent = (isom_box_t *)parent_box; + int is_qt = lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_WAVE ); + lsmash_box_type_t box_type = is_qt ? QT_BOX_TYPE_ESDS : ISOM_BOX_TYPE_ESDS; + uint64_t precedence = is_qt ? LSMASH_BOX_PRECEDENCE_QTFF_ESDS : LSMASH_BOX_PRECEDENCE_ISOM_ESDS; + CREATE_BOX( esds, parent, box_type, precedence, 1 ); + return esds; +} + +DEFINE_SIMPLE_SAMPLE_EXTENSION_ADDER( isom_add_glbl, glbl, parent_box, QT_BOX_TYPE_GLBL, LSMASH_BOX_PRECEDENCE_QTFF_GLBL, 1, void ) +DEFINE_SIMPLE_SAMPLE_EXTENSION_ADDER( isom_add_clap, clap, visual, ISOM_BOX_TYPE_CLAP, LSMASH_BOX_PRECEDENCE_ISOM_CLAP, 0, isom_visual_entry_t ) +DEFINE_SIMPLE_SAMPLE_EXTENSION_ADDER( isom_add_pasp, pasp, visual, ISOM_BOX_TYPE_PASP, LSMASH_BOX_PRECEDENCE_ISOM_PASP, 0, isom_visual_entry_t ) +DEFINE_SIMPLE_SAMPLE_EXTENSION_ADDER( isom_add_colr, colr, visual, ISOM_BOX_TYPE_COLR, LSMASH_BOX_PRECEDENCE_ISOM_COLR, 0, isom_visual_entry_t ) +DEFINE_SIMPLE_SAMPLE_EXTENSION_ADDER( isom_add_gama, gama, visual, QT_BOX_TYPE_GAMA, LSMASH_BOX_PRECEDENCE_QTFF_GAMA, 0, isom_visual_entry_t ) +DEFINE_SIMPLE_SAMPLE_EXTENSION_ADDER( isom_add_fiel, fiel, visual, QT_BOX_TYPE_FIEL, LSMASH_BOX_PRECEDENCE_QTFF_FIEL, 0, isom_visual_entry_t ) +DEFINE_SIMPLE_SAMPLE_EXTENSION_ADDER( isom_add_clli, clli, visual, QT_BOX_TYPE_CLLI, LSMASH_BOX_PRECEDENCE_QTFF_CLLI, 0, isom_visual_entry_t ) +DEFINE_SIMPLE_SAMPLE_EXTENSION_ADDER( isom_add_mdcv, mdcv, visual, QT_BOX_TYPE_MDCV, LSMASH_BOX_PRECEDENCE_QTFF_MDCV, 0, isom_visual_entry_t ) +DEFINE_SIMPLE_SAMPLE_EXTENSION_ADDER( isom_add_cspc, cspc, visual, QT_BOX_TYPE_CSPC, LSMASH_BOX_PRECEDENCE_QTFF_CSPC, 0, isom_visual_entry_t ) +DEFINE_SIMPLE_SAMPLE_EXTENSION_ADDER( isom_add_sgbt, sgbt, visual, QT_BOX_TYPE_SGBT, LSMASH_BOX_PRECEDENCE_QTFF_SGBT, 0, isom_visual_entry_t ) +DEFINE_SIMPLE_SAMPLE_EXTENSION_ADDER( isom_add_stsl, stsl, visual, ISOM_BOX_TYPE_STSL, LSMASH_BOX_PRECEDENCE_ISOM_STSL, 0, isom_visual_entry_t ) +DEFINE_SIMPLE_SAMPLE_EXTENSION_ADDER( isom_add_btrt, btrt, visual, ISOM_BOX_TYPE_BTRT, LSMASH_BOX_PRECEDENCE_ISOM_BTRT, 0, isom_visual_entry_t ) +DEFINE_SIMPLE_SAMPLE_EXTENSION_ADDER( isom_add_wave, wave, audio, QT_BOX_TYPE_WAVE, LSMASH_BOX_PRECEDENCE_QTFF_WAVE, 0, isom_audio_entry_t ) +DEFINE_SIMPLE_SAMPLE_EXTENSION_ADDER( isom_add_chan, chan, audio, QT_BOX_TYPE_CHAN, LSMASH_BOX_PRECEDENCE_QTFF_CHAN, 1, isom_audio_entry_t ) +DEFINE_SIMPLE_SAMPLE_EXTENSION_ADDER( isom_add_srat, srat, audio, ISOM_BOX_TYPE_SRAT, LSMASH_BOX_PRECEDENCE_ISOM_SRAT, 0, isom_audio_entry_t ) +DEFINE_SIMPLE_SAMPLE_EXTENSION_ADDER( isom_add_tims, tims, hint, ISOM_BOX_TYPE_TIMS, LSMASH_BOX_PRECEDENCE_ISOM_TIMS, 0, isom_hint_entry_t ) +DEFINE_SIMPLE_SAMPLE_EXTENSION_ADDER( isom_add_tsro, tsro, hint, ISOM_BOX_TYPE_TSRO, LSMASH_BOX_PRECEDENCE_ISOM_TSRO, 0, isom_hint_entry_t ) +DEFINE_SIMPLE_SAMPLE_EXTENSION_ADDER( isom_add_tssy, tssy, hint, ISOM_BOX_TYPE_TSSY, LSMASH_BOX_PRECEDENCE_ISOM_TSSY, 0, isom_hint_entry_t ) + +DEFINE_SIMPLE_LIST_BOX_ADDER( isom_add_stts, stts, stbl, ISOM_BOX_TYPE_STTS, LSMASH_BOX_PRECEDENCE_ISOM_STTS ) +DEFINE_SIMPLE_LIST_BOX_ADDER( isom_add_ctts, ctts, stbl, ISOM_BOX_TYPE_CTTS, LSMASH_BOX_PRECEDENCE_ISOM_CTTS ) +DEFINE_SIMPLE_BOX_ADDER ( isom_add_cslg, cslg, stbl, ISOM_BOX_TYPE_CSLG, LSMASH_BOX_PRECEDENCE_ISOM_CSLG ) +DEFINE_SIMPLE_LIST_BOX_ADDER( isom_add_stsc, stsc, stbl, ISOM_BOX_TYPE_STSC, LSMASH_BOX_PRECEDENCE_ISOM_STSC ) +DEFINE_SIMPLE_BOX_ADDER ( isom_add_stsz, stsz, stbl, ISOM_BOX_TYPE_STSZ, LSMASH_BOX_PRECEDENCE_ISOM_STSZ ) /* We don't create a list here. */ +DEFINE_SIMPLE_LIST_BOX_ADDER( isom_add_stz2, stz2, stbl, ISOM_BOX_TYPE_STZ2, LSMASH_BOX_PRECEDENCE_ISOM_STZ2 ) +DEFINE_SIMPLE_LIST_BOX_ADDER( isom_add_stss, stss, stbl, ISOM_BOX_TYPE_STSS, LSMASH_BOX_PRECEDENCE_ISOM_STSS ) +DEFINE_SIMPLE_LIST_BOX_ADDER( isom_add_stps, stps, stbl, QT_BOX_TYPE_STPS, LSMASH_BOX_PRECEDENCE_QTFF_STPS ) + +isom_stco_t *isom_add_stco( isom_stbl_t *stbl ) +{ + ADD_LIST_BOX( stco, stbl, ISOM_BOX_TYPE_STCO, LSMASH_BOX_PRECEDENCE_ISOM_STCO ); + stco->large_presentation = 0; + return stco; +} + +isom_stco_t *isom_add_co64( isom_stbl_t *stbl ) +{ + ADD_LIST_BOX( stco, stbl, ISOM_BOX_TYPE_CO64, LSMASH_BOX_PRECEDENCE_ISOM_CO64 ); + stco->large_presentation = 1; + return stco; +} + +isom_sdtp_t *isom_add_sdtp( isom_box_t *parent ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( parent ) ) + return isom_non_existing_sdtp(); + if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STBL ) ) + { + isom_stbl_t *stbl = (isom_stbl_t *)parent; + ADD_LIST_BOX( sdtp, stbl, ISOM_BOX_TYPE_SDTP, LSMASH_BOX_PRECEDENCE_ISOM_SDTP ); + return sdtp; + } + else if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TRAF ) ) + { + isom_traf_t *traf = (isom_traf_t *)parent; + ADD_LIST_BOX( sdtp, traf, ISOM_BOX_TYPE_SDTP, LSMASH_BOX_PRECEDENCE_ISOM_SDTP ); + return sdtp; + } + assert( 0 ); + return isom_non_existing_sdtp(); +} + +isom_sgpd_t *isom_add_sgpd( void *parent_box ) +{ + isom_box_t *parent = (isom_box_t *)parent_box; + if( LSMASH_IS_NON_EXISTING_BOX( parent ) ) + return isom_non_existing_sgpd(); + if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STBL ) ) + { + isom_stbl_t *stbl = (isom_stbl_t *)parent; + ADD_LIST_BOX_IN_LIST( sgpd, stbl, ISOM_BOX_TYPE_SGPD, LSMASH_BOX_PRECEDENCE_ISOM_SGPD ); + return sgpd; + } + else if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TRAF ) ) + { + isom_traf_t *traf = (isom_traf_t *)parent; + ADD_LIST_BOX_IN_LIST( sgpd, traf, ISOM_BOX_TYPE_SGPD, LSMASH_BOX_PRECEDENCE_ISOM_SGPD ); + return sgpd; + } + assert( 0 ); + return isom_non_existing_sgpd(); +} + +isom_sbgp_t *isom_add_sbgp( void *parent_box ) +{ + isom_box_t *parent = (isom_box_t *)parent_box; + if( LSMASH_IS_NON_EXISTING_BOX( parent ) ) + return isom_non_existing_sbgp(); + if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STBL ) ) + { + isom_stbl_t *stbl = (isom_stbl_t *)parent; + ADD_LIST_BOX_IN_LIST( sbgp, stbl, ISOM_BOX_TYPE_SBGP, LSMASH_BOX_PRECEDENCE_ISOM_SBGP ); + return sbgp; + } + else if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TRAF ) ) + { + isom_traf_t *traf = (isom_traf_t *)parent; + ADD_LIST_BOX_IN_LIST( sbgp, traf, ISOM_BOX_TYPE_SBGP, LSMASH_BOX_PRECEDENCE_ISOM_SBGP ); + return sbgp; + } + assert( 0 ); + return isom_non_existing_sbgp(); +} + +DEFINE_SIMPLE_LIST_BOX_ADDER( isom_add_chpl, chpl, udta, ISOM_BOX_TYPE_CHPL, LSMASH_BOX_PRECEDENCE_ISOM_CHPL ) + +isom_metaitem_t *isom_add_metaitem( isom_ilst_t *ilst, lsmash_itunes_metadata_item item ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( ilst ) ) + return isom_non_existing_metaitem(); + lsmash_box_type_t type = lsmash_form_iso_box_type( item ); + ADD_BOX_IN_LIST( metaitem, ilst, type, LSMASH_BOX_PRECEDENCE_ISOM_METAITEM ); + return metaitem; +} + +DEFINE_SIMPLE_BOX_ADDER ( isom_add_mean, mean, metaitem, ISOM_BOX_TYPE_MEAN, LSMASH_BOX_PRECEDENCE_ISOM_MEAN ) +DEFINE_SIMPLE_BOX_ADDER ( isom_add_name, name, metaitem, ISOM_BOX_TYPE_NAME, LSMASH_BOX_PRECEDENCE_ISOM_NAME ) +DEFINE_SIMPLE_BOX_ADDER ( isom_add_data, data, metaitem, ISOM_BOX_TYPE_DATA, LSMASH_BOX_PRECEDENCE_ISOM_DATA ) +DEFINE_SIMPLE_BOX_ADDER ( isom_add_ilst, ilst, meta, ISOM_BOX_TYPE_ILST, LSMASH_BOX_PRECEDENCE_ISOM_ILST ) +DEFINE_SIMPLE_LIST_BOX_ADDER( isom_add_keys, keys, meta, QT_BOX_TYPE_KEYS, LSMASH_BOX_PRECEDENCE_QTFF_KEYS ) + +isom_meta_t *isom_add_meta( void *parent_box ) +{ + isom_box_t *parent = (isom_box_t *)parent_box; + if( LSMASH_IS_NON_EXISTING_BOX( parent ) ) + return isom_non_existing_meta(); + CREATE_BOX( meta, parent, ISOM_BOX_TYPE_META, LSMASH_BOX_PRECEDENCE_ISOM_META, 1 ); + if( parent->file == (lsmash_file_t *)parent ) + ATTACH_EXACTLY_ONE_BOX_TO_PARENT( meta, lsmash_file_t ); + else if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MOOV ) ) + ATTACH_EXACTLY_ONE_BOX_TO_PARENT( meta, isom_moov_t ); + else if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TRAK ) ) + ATTACH_EXACTLY_ONE_BOX_TO_PARENT( meta, isom_trak_t ); + else if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_UDTA ) ) + ATTACH_EXACTLY_ONE_BOX_TO_PARENT( meta, isom_udta_t ); + else + assert( 0 ); + return meta; +} + +DEFINE_SIMPLE_BOX_IN_LIST_ADDER( isom_add_cprt, cprt, udta, ISOM_BOX_TYPE_CPRT, LSMASH_BOX_PRECEDENCE_ISOM_CPRT ) +DEFINE_SIMPLE_BOX_ADDER( isom_add_hnti, hnti, udta, ISOM_BOX_TYPE_HNTI, LSMASH_BOX_PRECEDENCE_ISOM_HNTI ) +DEFINE_SIMPLE_BOX_ADDER( isom_add_rtp, rtp, hnti, ISOM_BOX_TYPE_RTP, LSMASH_BOX_PRECEDENCE_ISOM_RTP ) +DEFINE_SIMPLE_BOX_ADDER( isom_add_sdp, sdp, hnti, ISOM_BOX_TYPE_SDP, LSMASH_BOX_PRECEDENCE_ISOM_SDP ) + +isom_udta_t *isom_add_udta( void *parent_box ) +{ + isom_box_t *parent = (isom_box_t *)parent_box; + if( LSMASH_IS_NON_EXISTING_BOX( parent ) ) + return isom_non_existing_udta(); + if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MOOV ) ) + { + isom_moov_t *moov = (isom_moov_t *)parent; + ADD_BOX( udta, moov, ISOM_BOX_TYPE_UDTA, LSMASH_BOX_PRECEDENCE_ISOM_UDTA ); + return udta; + } + else if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TRAK ) ) + { + isom_trak_t *trak = (isom_trak_t *)parent; + ADD_BOX( udta, trak, ISOM_BOX_TYPE_UDTA, LSMASH_BOX_PRECEDENCE_ISOM_UDTA ); + return udta; + } + assert( 0 ); + return isom_non_existing_udta(); +} + +DEFINE_SIMPLE_BOX_ADDER ( isom_add_WLOC, WLOC, udta, QT_BOX_TYPE_WLOC, LSMASH_BOX_PRECEDENCE_QTFF_WLOC ) +DEFINE_SIMPLE_BOX_ADDER ( isom_add_LOOP, LOOP, udta, QT_BOX_TYPE_LOOP, LSMASH_BOX_PRECEDENCE_QTFF_LOOP ) +DEFINE_SIMPLE_BOX_ADDER ( isom_add_SelO, SelO, udta, QT_BOX_TYPE_SELO, LSMASH_BOX_PRECEDENCE_QTFF_SELO ) +DEFINE_SIMPLE_BOX_ADDER ( isom_add_AllF, AllF, udta, QT_BOX_TYPE_ALLF, LSMASH_BOX_PRECEDENCE_QTFF_ALLF ) +DEFINE_SIMPLE_BOX_ADDER ( isom_add_mvex, mvex, moov, ISOM_BOX_TYPE_MVEX, LSMASH_BOX_PRECEDENCE_ISOM_MVEX ) +DEFINE_SIMPLE_BOX_ADDER ( isom_add_mehd, mehd, mvex, ISOM_BOX_TYPE_MEHD, LSMASH_BOX_PRECEDENCE_ISOM_MEHD ) +DEFINE_SIMPLE_BOX_IN_LIST_ADDER( isom_add_trex, trex, mvex, ISOM_BOX_TYPE_TREX, LSMASH_BOX_PRECEDENCE_ISOM_TREX ) +DEFINE_SIMPLE_BOX_IN_LIST_ADDER( isom_add_moof, moof, file_abstract, ISOM_BOX_TYPE_MOOF, LSMASH_BOX_PRECEDENCE_ISOM_MOOF ) +DEFINE_SIMPLE_BOX_ADDER ( isom_add_mfhd, mfhd, moof, ISOM_BOX_TYPE_MFHD, LSMASH_BOX_PRECEDENCE_ISOM_MFHD ) +DEFINE_SIMPLE_BOX_IN_LIST_ADDER( isom_add_traf, traf, moof, ISOM_BOX_TYPE_TRAF, LSMASH_BOX_PRECEDENCE_ISOM_TRAF ) +DEFINE_SIMPLE_BOX_ADDER ( isom_add_tfhd, tfhd, traf, ISOM_BOX_TYPE_TFHD, LSMASH_BOX_PRECEDENCE_ISOM_TFHD ) +DEFINE_SIMPLE_BOX_ADDER ( isom_add_tfdt, tfdt, traf, ISOM_BOX_TYPE_TFDT, LSMASH_BOX_PRECEDENCE_ISOM_TFDT ) +DEFINE_SIMPLE_BOX_IN_LIST_ADDER( isom_add_trun, trun, traf, ISOM_BOX_TYPE_TRUN, LSMASH_BOX_PRECEDENCE_ISOM_TRUN ) +DEFINE_SIMPLE_BOX_ADDER ( isom_add_mfra, mfra, file_abstract, ISOM_BOX_TYPE_MFRA, LSMASH_BOX_PRECEDENCE_ISOM_MFRA ) +DEFINE_SIMPLE_BOX_IN_LIST_ADDER( isom_add_tfra, tfra, mfra, ISOM_BOX_TYPE_TFRA, LSMASH_BOX_PRECEDENCE_ISOM_TFRA ) +DEFINE_SIMPLE_BOX_ADDER ( isom_add_mfro, mfro, mfra, ISOM_BOX_TYPE_MFRO, LSMASH_BOX_PRECEDENCE_ISOM_MFRO ) + +isom_mdat_t *isom_add_mdat( isom_file_abstract_t *file ) +{ + assert( LSMASH_IS_NON_EXISTING_BOX( file->mdat ) ); + CREATE_BOX( mdat, file, ISOM_BOX_TYPE_MDAT, LSMASH_BOX_PRECEDENCE_ISOM_MDAT, 1 ); + file->mdat = mdat; + return mdat; +} + +isom_free_t *isom_add_free( void *parent_box ) +{ + isom_box_t *parent = (isom_box_t *)parent_box; + if( LSMASH_IS_NON_EXISTING_BOX( parent ) ) + return isom_non_existing_skip(); + CREATE_BOX( skip, parent, ISOM_BOX_TYPE_FREE, LSMASH_BOX_PRECEDENCE_ISOM_FREE, 1 ); + return skip; +} + +DEFINE_SIMPLE_BOX_IN_LIST_ADDER( isom_add_styp, styp, file_abstract, ISOM_BOX_TYPE_STYP, LSMASH_BOX_PRECEDENCE_ISOM_STYP ) + +isom_sidx_t *isom_add_sidx( isom_file_abstract_t *file_abstract ) +{ + ADD_LIST_BOX_IN_LIST( sidx, file_abstract, ISOM_BOX_TYPE_SIDX, LSMASH_BOX_PRECEDENCE_ISOM_SIDX ); + return sidx; +} + +#undef ATTACH_EXACTLY_ONE_BOX_TO_PARENT +#undef CREATE_BOX +#undef CREATE_LIST_BOX +#undef ADD_BOX_TEMPLATE +#undef ADD_BOX_IN_LIST_TEMPLATE +#undef ADD_BOX +#undef ADD_BOX_IN_LIST +#undef ADD_LIST_BOX +#undef ADD_LIST_BOX_IN_LIST +#undef DEFINE_SIMPLE_BOX_ADDER_TEMPLATE +#undef DEFINE_SIMPLE_BOX_ADDER_TEMPLATE_6 +#undef DEFINE_SIMPLE_BOX_ADDER_TEMPLATE_5 +#undef DEFINE_SIMPLE_BOX_ADDER +#undef DEFINE_SIMPLE_BOX_IN_LIST_ADDER +#undef DEFINE_SIMPLE_LIST_BOX_ADDER + +static int fake_file_read +( + void *opaque, + uint8_t *buf, + int size +) +{ + fake_file_stream_t *stream = (fake_file_stream_t *)opaque; + int read_size; + if( stream->pos + size > stream->size ) + read_size = stream->size - stream->pos; + else + read_size = size; + memcpy( buf, stream->data + stream->pos, read_size ); + stream->pos += read_size; + return read_size; +} + +static int64_t fake_file_seek +( + void *opaque, + int64_t offset, + int whence +) +{ + fake_file_stream_t *stream = (fake_file_stream_t *)opaque; + if( whence == SEEK_SET ) + stream->pos = offset; + else if( whence == SEEK_CUR ) + stream->pos += offset; + else if( whence == SEEK_END ) + stream->pos = stream->size + offset; + return stream->pos; +} + +/* Public functions */ +lsmash_root_t *lsmash_create_root( void ) +{ + lsmash_root_t *root = ALLOCATE_BOX( root_abstract ); + if( LSMASH_IS_NON_EXISTING_BOX( root ) ) + return NULL; + root->root = root; + return root; +} + +void lsmash_destroy_root( lsmash_root_t *root ) +{ + isom_remove_box_by_itself( root ); +} + +lsmash_extended_box_type_t lsmash_form_extended_box_type( uint32_t fourcc, const uint8_t id[12] ) +{ + return (lsmash_extended_box_type_t){ fourcc, { id[0], id[1], id[2], id[3], id[4], id[5], + id[6], id[7], id[8], id[9], id[10], id[11] } }; +} + +lsmash_box_type_t lsmash_form_box_type +( + lsmash_compact_box_type_t type, + lsmash_extended_box_type_t user +) +{ + return (lsmash_box_type_t){ type, user }; +} + +lsmash_box_type_t lsmash_form_iso_box_type( uint32_t fourcc ) +{ + return (lsmash_box_type_t){ fourcc, lsmash_form_extended_box_type( fourcc, LSMASH_ISO_12_BYTES ) }; +} + +lsmash_box_type_t lsmash_form_qtff_box_type( uint32_t fourcc ) +{ + return (lsmash_box_type_t){ fourcc, lsmash_form_extended_box_type( fourcc, LSMASH_QTFF_12_BYTES ) }; +} + +#define CHECK_BOX_TYPE_IDENTICAL( a, b ) \ + a.fourcc == b.fourcc \ + && a.user.fourcc == b.user.fourcc \ + && a.user.id[0] == b.user.id[0] \ + && a.user.id[1] == b.user.id[1] \ + && a.user.id[2] == b.user.id[2] \ + && a.user.id[3] == b.user.id[3] \ + && a.user.id[4] == b.user.id[4] \ + && a.user.id[5] == b.user.id[5] \ + && a.user.id[6] == b.user.id[6] \ + && a.user.id[7] == b.user.id[7] \ + && a.user.id[8] == b.user.id[8] \ + && a.user.id[9] == b.user.id[9] \ + && a.user.id[10] == b.user.id[10] \ + && a.user.id[11] == b.user.id[11] + +int lsmash_check_box_type_identical( lsmash_box_type_t a, lsmash_box_type_t b ) +{ + return CHECK_BOX_TYPE_IDENTICAL( a, b ); +} + +int lsmash_check_codec_type_identical( lsmash_codec_type_t a, lsmash_codec_type_t b ) +{ + return CHECK_BOX_TYPE_IDENTICAL( a, b ); +} + +int lsmash_check_box_type_specified( const lsmash_box_type_t *box_type ) +{ + assert( box_type ); + if( !box_type ) + return 0; + return !!(box_type->fourcc + | box_type->user.fourcc + | box_type->user.id[0] | box_type->user.id[1] | box_type->user.id[2] | box_type->user.id[3] + | box_type->user.id[4] | box_type->user.id[5] | box_type->user.id[6] | box_type->user.id[7] + | box_type->user.id[8] | box_type->user.id[9] | box_type->user.id[10] | box_type->user.id[11]); +} + +lsmash_box_t *lsmash_get_box +( + lsmash_box_t *parent, + const lsmash_box_path_t box_path[] +) +{ + lsmash_entry_t *entry = isom_get_entry_of_box( parent, box_path ); + return (lsmash_box_t *)(entry ? entry->data : NULL); +} + +lsmash_box_t *lsmash_create_box +( + lsmash_box_type_t type, + uint8_t *data, + uint32_t size, + uint64_t precedence +) +{ + if( !lsmash_check_box_type_specified( &type ) ) + return NULL; + isom_unknown_box_t *box = ALLOCATE_BOX( unknown ); + if( LSMASH_IS_NON_EXISTING_BOX( box ) ) + return NULL; + if( size && data ) + { + box->unknown_size = size; + box->unknown_field = lsmash_memdup( data, size ); + if( !box->unknown_field ) + { + lsmash_free( box ); + return NULL; + } + } + else + { + box->unknown_size = 0; + box->unknown_field = NULL; + size = 0; + } + box->class = &lsmash_box_class; + box->root = isom_non_existing_root_abstract(); + box->file = isom_non_existing_file_abstract(); + box->parent = (isom_box_t *)isom_non_existing_unknown(); + box->destruct = (isom_extension_destructor_t)isom_remove_unknown_box; + box->manager = LSMASH_UNKNOWN_BOX; + box->precedence = precedence; + box->size = ISOM_BASEBOX_COMMON_SIZE + size + (type.fourcc == ISOM_BOX_TYPE_UUID.fourcc ? 16 : 0); + box->type = type; + isom_set_box_writer( (isom_box_t *)box ); + return (lsmash_box_t *)box; +} + +int lsmash_add_box +( + lsmash_box_t *parent, + lsmash_box_t *box +) +{ + if( LSMASH_IS_NON_EXISTING_BOX( parent ) ) + /* You cannot add any box without a box being its parent. */ + return LSMASH_ERR_FUNCTION_PARAM; + if( LSMASH_IS_NON_EXISTING_BOX( box ) || box->size < ISOM_BASEBOX_COMMON_SIZE ) + return LSMASH_ERR_FUNCTION_PARAM; + if( parent->root == (lsmash_root_t *)parent ) + { + /* Only files can be added into any ROOT. + * For backward compatibility, use the active file as the parent. */ + if( LSMASH_IS_EXISTING_BOX( parent->file ) ) + parent = (isom_box_t *)parent->file; + else + return LSMASH_ERR_FUNCTION_PARAM; + } + /* Add a box as a child box. */ + box->class = &lsmash_box_class; + box->root = parent->root; + box->file = parent->file; + box->parent = parent; + return isom_add_box_to_extension_list( parent, box ); +} + +int lsmash_add_box_ex +( + lsmash_box_t *parent, + lsmash_box_t **p_box +) +{ + if( LSMASH_IS_NON_EXISTING_BOX( parent ) ) + /* You cannot add any box without a box being its parent. */ + return LSMASH_ERR_FUNCTION_PARAM; + isom_unknown_box_t *box = (isom_unknown_box_t *)*p_box; + if( LSMASH_IS_NON_EXISTING_BOX( box ) || box->size < ISOM_BASEBOX_COMMON_SIZE ) + return LSMASH_ERR_FUNCTION_PARAM; + if( !(box->manager & LSMASH_UNKNOWN_BOX) ) + /* Simply add the box. */ + return lsmash_add_box( parent, *p_box ); + /* Check if the size of the box to be added is valid. */ + if( box->size != ISOM_BASEBOX_COMMON_SIZE + box->unknown_size + (box->type.fourcc == ISOM_BOX_TYPE_UUID.fourcc ? 16 : 0) ) + return LSMASH_ERR_FUNCTION_PARAM; + if( LSMASH_IS_NON_EXISTING_BOX( parent->file ) || parent->file == (lsmash_file_t *)box ) + return LSMASH_ERR_FUNCTION_PARAM; + if( parent->root == (lsmash_root_t *)parent ) + /* Only files can be added into any ROOT. + * For backward compatibility, use the active file as the parent. */ + parent = (isom_box_t *)parent->file; + /* Switch to the fake-file stream mode. */ + lsmash_file_t *file = parent->file; + lsmash_bs_t *bs_backup = file->bs; + lsmash_bs_t *bs = lsmash_bs_create(); + if( !bs ) + return LSMASH_ERR_MEMORY_ALLOC; + uint8_t *buf = lsmash_malloc( box->size ); + if( !buf ) + { + lsmash_bs_cleanup( bs ); + return LSMASH_ERR_MEMORY_ALLOC; + } + fake_file_stream_t fake_file = + { + .size = box->size, + .data = buf, + .pos = 0 + }; + bs->stream = &fake_file; + bs->read = fake_file_read; + bs->write = NULL; + bs->seek = fake_file_seek; + file->bs = bs; + file->fake_file_mode = 1; + /* Make the byte string representing the given box. */ + LSMASH_SET_BE32( &buf[0], box->size ); + LSMASH_SET_BE32( &buf[4], box->type.fourcc ); + if( box->type.fourcc == ISOM_BOX_TYPE_UUID.fourcc ) + { + LSMASH_SET_BE32( &buf[8], box->type.user.fourcc ); + memcpy( &buf[12], box->type.user.id, 12 ); + } + memcpy( buf + (uintptr_t)(box->size - box->unknown_size), box->unknown_field, box->unknown_size ); + /* Add a box as a child box and try to expand into struct format. */ + lsmash_box_t dummy = { 0 }; + int ret = isom_read_box( file, &dummy, parent, 0, 0 ); + lsmash_free( buf ); + lsmash_bs_cleanup( bs ); + file->bs = bs_backup; /* Switch back to the normal file stream mode. */ + file->fake_file_mode = 0; + if( ret < 0 ) + return ret; + /* Reorder the added box by 'precedence'. */ + *p_box = (lsmash_box_t *)parent->extensions.tail->data; + (*p_box)->precedence = box->precedence; + isom_reorder_tail_box( parent ); + /* Do also its children by the same way. */ + lsmash_entry_list_t extensions = box->extensions; + lsmash_list_init_simple( &box->extensions ); /* to avoid freeing the children */ + isom_remove_box_by_itself( box ); + for( lsmash_entry_t *entry = extensions.head; entry; entry = entry->next ) + { + if( !entry->data ) + continue; + lsmash_box_t *child = (lsmash_box_t *)entry->data; + if( lsmash_add_box_ex( *p_box, &child ) == 0 ) + { + (*p_box)->size += child->size; + /* Avoid freeing at the end of this function. */ + entry->data = NULL; + } + } + isom_remove_all_extension_boxes( &extensions ); + return 0; +} + +void lsmash_destroy_box +( + lsmash_box_t *box +) +{ + isom_remove_box_by_itself( box ); +} + +void lsmash_destroy_children +( + lsmash_box_t *box +) +{ + if( LSMASH_IS_EXISTING_BOX( box ) ) + isom_remove_all_extension_boxes( &box->extensions ); +} + +int lsmash_get_box_precedence +( + lsmash_box_t *box, + uint64_t *precedence +) +{ + if( !box || !precedence ) + return LSMASH_ERR_FUNCTION_PARAM; + *precedence = box->precedence; + return 0; +} + +lsmash_box_t *lsmash_root_as_box +( + lsmash_root_t *root +) +{ + return (lsmash_box_t *)root; +} + +lsmash_box_t *lsmash_file_as_box +( + lsmash_file_t *file +) +{ + return (lsmash_box_t *)file; +} + +int lsmash_write_top_level_box +( + lsmash_box_t *box +) +{ + if( !box || (isom_box_t *)box->file != box->parent ) + return LSMASH_ERR_FUNCTION_PARAM; + int ret = isom_write_box( box->file->bs, box ); + if( ret < 0 ) + return ret; + box->file->size += box->size; + return 0; +} + +uint8_t *lsmash_export_box +( + lsmash_box_t *box, + uint32_t *size +) +{ + if( !box || !size ) + return NULL; + lsmash_bs_t *bs = lsmash_bs_create(); + if( !bs ) + return NULL; + if( isom_write_box( bs, box ) < 0 ) + { + lsmash_bs_cleanup( bs ); + return NULL; + } + *size = bs->buffer.store; + uint8_t *data = bs->buffer.data; + bs->buffer.data = NULL; + lsmash_bs_cleanup( bs ); + return data; +} diff --git a/3rdparty/l-smash/core/box.h b/3rdparty/l-smash/core/box.h new file mode 100644 index 000000000..8bee76cae --- /dev/null +++ b/3rdparty/l-smash/core/box.h @@ -0,0 +1,2669 @@ +/***************************************************************************** + * box.h + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#ifndef LSMASH_BOX_H +#define LSMASH_BOX_H + +/* For generating creation_time and modification_time. + * According to ISO/IEC-14496-5-2001, the difference between Unix time and Mac OS time is 2082758400. + * However this is wrong and 2082844800 is correct. */ +#include +#define ISOM_MAC_EPOCH_OFFSET 2082844800 + +#include "box_type.h" + +/* aliases internally used only for convenience */ +typedef struct lsmash_file_tag isom_file_abstract_t; +typedef struct lsmash_root_tag isom_root_abstract_t; +typedef struct isom_unknown_box_tag isom_unknown_t; +typedef struct lsmash_box_tag isom_dummy_t; /* for dummy usage */ + +typedef struct lsmash_box_tag isom_box_t; +typedef struct isom_unknown_box_tag isom_unknown_box_t; +typedef struct isom_mdhd_tag isom_mdhd_t; +typedef struct isom_stbl_tag isom_stbl_t; + +typedef void (*isom_extension_destructor_t)( void *extension_data ); +typedef int (*isom_extension_writer_t)( lsmash_bs_t *bs, isom_box_t *box ); + +typedef int (*isom_bitrate_updater_t)( isom_stbl_t *stbl, isom_mdhd_t *mdhd, uint32_t sample_description_index ); + +/* If size is 1, then largesize is actual size. + * If size is 0, then this box is the last one in the file. */ +#define ISOM_BASEBOX_COMMON \ + const lsmash_class_t *class; \ + lsmash_root_t *root; /* pointer to root */ \ + lsmash_file_t *file; /* pointer to file */ \ + isom_box_t *parent; /* pointer to the parent box of this box */ \ + void *nonexist_ptr; /* pointer to non-existing box constant */ \ + uint8_t *binary; /* used only when LSMASH_BINARY_CODED_BOX */ \ + isom_extension_destructor_t destruct; /* box specific destructor */ \ + isom_extension_writer_t write; /* box specific writer */ \ + size_t offset_in_parent; /* offset of this box in parent box struct */ \ + uint32_t manager; /* flags for L-SMASH */ \ + uint64_t precedence; /* precedence of the box position */ \ + uint64_t pos; /* starting position of this box in the file */ \ + lsmash_entry_list_t extensions; /* extension boxes */ \ + uint64_t size; /* the number of bytes in this box */ \ + lsmash_box_type_t type + +#define ISOM_FULLBOX_COMMON \ + ISOM_BASEBOX_COMMON; \ + uint8_t version; /* Basically, version is either 0 or 1 */ \ + uint32_t flags /* In the actual structure of box, flags is 24 bits. */ + +#define ISOM_BASEBOX_COMMON_SIZE 8 +#define ISOM_FULLBOX_COMMON_SIZE 12 +#define ISOM_LIST_FULLBOX_COMMON_SIZE 16 + +/* flags for L-SMASH */ +#define LSMASH_UNKNOWN_BOX 0x001 +#define LSMASH_ABSENT_IN_FILE 0x002 +#define LSMASH_QTFF_BASE 0x004 +#define LSMASH_VIDEO_DESCRIPTION 0x008 +#define LSMASH_AUDIO_DESCRIPTION 0x010 +#define LSMASH_FULLBOX 0x020 +#define LSMASH_LAST_BOX 0x040 +#define LSMASH_INCOMPLETE_BOX 0x080 +#define LSMASH_BINARY_CODED_BOX 0x100 +#define LSMASH_PLACEHOLDER 0x200 +#define LSMASH_WRITTEN_BOX 0x400 +#define LSMASH_NON_EXISTING_BOX 0x800 /* This flag indicates a read only non-existing box constant. + * Don't use for wild boxes other than non-existing box constants + * because this flags prevents attempting to freeing its box. */ + +/* Use these macros for checking existences of boxes. + * If the result of LSMASH_IS_EXISTING_BOX is 0, the evaluated box is read only. + * If the result of LSMASH_IS_NON_EXISTING_BOX is 1, the evaluated box is read only. */ +#define LSMASH_IS_EXISTING_BOX( box_ptr ) \ + ((box_ptr) && !((box_ptr)->manager & LSMASH_NON_EXISTING_BOX)) +#define LSMASH_IS_NON_EXISTING_BOX( box_ptr ) \ + (!(box_ptr) || ((box_ptr)->manager & LSMASH_NON_EXISTING_BOX)) + +#define LSMASH_IS_BOX_ADDITION_SUCCESS( box_ptr ) \ + (!((box_ptr)->manager & LSMASH_NON_EXISTING_BOX)) +#define LSMASH_IS_BOX_ADDITION_FAILURE( box_ptr ) \ + (!!((box_ptr)->manager & LSMASH_NON_EXISTING_BOX)) + +/* Use this macro for disabling a predefined child box in struct. + * Predefined childs must not be NULL for safety. */ +#define LSMASH_MAKE_BOX_NON_EXISTING( box_ptr ) \ + (box_ptr) = (void *)(box_ptr)->nonexist_ptr + +/* 12-byte ISO reserved value: + * 0xXXXXXXXX-0011-0010-8000-00AA00389B71 */ +static const uint8_t static_lsmash_iso_12_bytes[12] + = { 0x00, 0x11, 0x00, 0x10, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }; +#define LSMASH_ISO_12_BYTES static_lsmash_iso_12_bytes + +/* L-SMASH original 12-byte QuickTime file format value for CODEC discrimination mainly: + * 0xXXXXXXXX-0F11-4DA5-BF4E-F2C48C6AA11E */ +static const uint8_t static_lsmash_qtff_12_bytes[12] + = { 0x0F, 0x11, 0x4D, 0xA5, 0xBF, 0x4E, 0xF2, 0xC4, 0x8C, 0x6A, 0xA1, 0x1E }; +#define LSMASH_QTFF_12_BYTES static_lsmash_qtff_12_bytes + +struct lsmash_box_tag +{ + ISOM_FULLBOX_COMMON; +}; + +/* Unknown Box + * This structure is for boxes we don't know or define yet. + * This box must be always appended as an extension box. */ +struct isom_unknown_box_tag +{ + ISOM_BASEBOX_COMMON; + uint32_t unknown_size; + uint8_t *unknown_field; +}; + +/* File Type Box + * This box identifies the specifications to which this file complies. + * This box shall occur before any variable-length box. + * In the absence of this box, the file is QuickTime file format or MP4 version 1 file format. + * In MP4 version 1 file format, Object Descriptor Box is mandatory. + * In QuickTime file format, Object Descriptor Box isn't defined. + * Therefore, if this box and an Object Descriptor Box are absent in the file, the file shall be QuickTime file format. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint32_t major_brand; /* brand identifier */ + uint32_t minor_version; /* the minor version of the major brand */ + uint32_t *compatible_brands; /* a list, to the end of the box, of brands */ + + uint32_t brand_count; /* the number of factors in compatible_brands array */ +} isom_ftyp_t; + +/* Color Table Box + * This box defines a list of preferred colors for displaying the movie on devices that support only 256 colors. + * The list may contain up to 256 colors. This box contains a Macintosh color table data structure. + * This box is defined in QuickTime File Format Specification. + * The color table structure is also defined in struct ColorTable defined in Quickdraw.h. */ +typedef struct +{ + /* An array of colors. + * Each color is made of four unsigned 16-bit integers. */ + uint16_t value; /* index or other value + * Must be set to 0. */ + /* true color */ + uint16_t r; /* magnitude of red component */ + uint16_t g; /* magnitude of green component */ + uint16_t b; /* magnitude of blue component */ +} isom_qt_color_array_t; + +typedef struct +{ + uint32_t seed; /* unique identifier for table + * Must be set to 0. */ + uint16_t flags; /* high bit: 0 = PixMap; 1 = device + * Must be set to 0x8000. */ + uint16_t size; /* the number of colors in the following color array + * This is a zero-relative value; + * setting this field to 0 means that there is one color in the array. */ + isom_qt_color_array_t *array; +} isom_qt_color_table_t; + +typedef struct +{ + ISOM_BASEBOX_COMMON; + isom_qt_color_table_t color_table; +} isom_ctab_t; + +/* Track Header Box + * This box specifies the characteristics of a single track. */ +typedef struct +{ + /* version is either 0 or 1 + * flags + * 0x000001: Indicates that the track is enabled. + * A disabled track is treated as if it were not present. + * 0x000002: Indicates that the track is used in the presentation. + * 0x000004: Indicates that the track is used when previewing the presentation. + * 0x000008: Indicates that the track is used in the movie's poster. (only defined in QuickTime file format) + * ISOM: If in a presentation all tracks have neither track_in_movie nor track_in_preview set, + * then all tracks shall be treated as if both flags were set on all tracks. */ + ISOM_FULLBOX_COMMON; + /* version == 0: uint64_t -> uint32_t */ + uint64_t creation_time; /* the creation time of this track (in seconds since midnight, Jan. 1, 1904, in UTC time) */ + uint64_t modification_time; /* the most recent time the track was modified (in seconds since midnight, Jan. 1, 1904, in UTC time) */ + uint32_t track_ID; /* an integer that uniquely identifies the track + * Track IDs are never re-used and cannot be zero. */ + uint32_t reserved1; + uint64_t duration; /* the duration of this track expressed in the movie timescale units */ + /* The following fields are treated as + * ISOM: template fields. + * MP41: reserved fields. + * MP42: ignored fileds since compositions are done using BIFS system. + * 3GPP: ignored fields except for alternate_group. + * QTFF: usable fields. */ + uint32_t reserved2[2]; + int16_t layer; /* the front-to-back ordering of video tracks; tracks with lower numbers are closer to the viewer. */ + int16_t alternate_group; /* an integer that specifies a group or collection of tracks + * If this field is not 0, it should be the same for tracks that contain alternate data for one another + * and different for tracks belonging to different such groups. + * Only one track within an alternate group should be played or streamed at any one time. */ + int16_t volume; /* fixed point 8.8 number. 0x0100 is full volume. */ + uint16_t reserved3; + int32_t matrix[9]; /* transformation matrix for the video */ + /* track's visual presentation size + * All images in the sequence are scaled to this size, before any overall transformation of the track represented by the matrix. + * Note: these fields are treated as reserved in MP4 version 1. */ + uint32_t width; /* fixed point 16.16 number */ + uint32_t height; /* fixed point 16.16 number */ + /* */ +} isom_tkhd_t; + +/* Track Clean Aperture Dimensions Box + * A presentation mode where clap and pasp are reflected. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint32_t width; /* fixed point 16.16 number */ + uint32_t height; /* fixed point 16.16 number */ +} isom_clef_t; + +/* Track Production Aperture Dimensions Box + * A presentation mode where pasp is reflected. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint32_t width; /* fixed point 16.16 number */ + uint32_t height; /* fixed point 16.16 number */ +} isom_prof_t; + +/* Track Encoded Pixels Dimensions Box + * A presentation mode where clap and pasp are not reflected. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint32_t width; /* fixed point 16.16 number */ + uint32_t height; /* fixed point 16.16 number */ +} isom_enof_t; + +/* Track Aperture Mode Dimensions Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + isom_clef_t *clef; /* Track Clean Aperture Dimensions Box */ + isom_prof_t *prof; /* Track Production Aperture Dimensions Box */ + isom_enof_t *enof; /* Track Encoded Pixels Dimensions Box */ +} isom_tapt_t; + +/* Edit List Box + * This box contains an explicit timeline map. + * Each entry defines part of the track timeline: by mapping part of the media timeline, or by indicating 'empty' time, + * or by defining a 'dwell', where a single time-point in the media is held for a period. + * The last edit in a track shall never be an empty edit. + * Any difference between the duration in the Movie Header Box, and the track's duration is expressed as an implicit empty edit at the end. + * It is recommended that any edits, explicit or implied, not select any portion of the composition timeline that doesn't map to a sample. + * Therefore, if the first sample in the track has non-zero CTS, then this track should have at least one edit and the start time in it should + * correspond to the value of the CTS the first sample has or more not to exceed the largest CTS in this track. */ +typedef struct +{ + /* This entry is called Timeline Mapping Edit (TME) entry in UltraViolet Common File Format. + * version == 0: 64bits -> 32bits */ + uint64_t segment_duration; /* the duration of this edit expressed in the movie timescale units */ + int64_t media_time; /* the starting composition time within the media of this edit segment + * If this field is set to -1, it is an empty edit. */ + int32_t media_rate; /* the relative rate at which to play the media corresponding to this edit segment + * If this value is 0, then the edit is specifying a 'dwell': + * the media at media_time is presented for the segment_duration. + * This field is expressed as 16.16 fixed-point number. */ +} isom_elst_entry_t; + +typedef struct +{ + ISOM_FULLBOX_COMMON; /* version is either 0 or 1 */ + lsmash_entry_list_t *list; +} isom_elst_t; + +/* Edit Box + * This optional box maps the presentation time-line to the media time-line as it is stored in the file. + * In the absence of this box, there is an implicit one-to-one mapping of these time-lines, + * and the presentation of a track starts at the beginning of the presentation. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + isom_elst_t *elst; /* Edit List Box */ +} isom_edts_t; + +/* Track Reference Box + * The Track Reference Box contains Track Reference Type Boxes. + * Track Reference Type Boxes define relationships between tracks. + * They allow one track to specify how it is related to other tracks. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint32_t *track_ID; /* track_IDs of reference tracks / Zero value must not be used */ + + uint32_t ref_count; /* number of reference tracks */ +} isom_tref_type_t; + +typedef struct +{ + ISOM_BASEBOX_COMMON; + lsmash_entry_list_t ref_list; /* Track Reference Type Boxes */ +} isom_tref_t; + +/* Media Header Box + * This box declares overall information that is media-independent, and relevant to characteristics of the media in a track.*/ +struct isom_mdhd_tag +{ + ISOM_FULLBOX_COMMON; /* version is either 0 or 1 */ + /* version == 0: uint64_t -> uint32_t */ + uint64_t creation_time; /* the creation time of the media in this track (in seconds since midnight, Jan. 1, 1904, in UTC time) */ + uint64_t modification_time; /* the most recent time the media in this track was modified (in seconds since midnight, Jan. 1, 1904, in UTC time) */ + uint32_t timescale; /* media timescale: timescale for this media */ + uint64_t duration; /* the duration of this media expressed in the timescale indicated in this box */ + /* */ + uint16_t language; /* ISOM: ISO-639-2/T language codes. Most significant 1-bit is 0. + * Each character is packed as the difference between its ASCII value and 0x60. + * QTFF: Macintosh language codes is usually used. + * Mac's value is less than 0x800 while ISO's value is 0x800 or greater. */ + int16_t quality; /* ISOM: pre_defined / QTFF: the media's playback quality */ +}; + +/* Handler Reference Box + * In Media Box, this box is mandatory and (ISOM: should/QTFF: must) come before Media Information Box. + * ISOM: this box might be also in Meta Box. + * QTFF: this box might be also in Media Information Box. If this box is present there, it must come before Data Information Box. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint32_t componentType; /* ISOM: pre_difined = 0 + * QTFF: 'mhlr' for Media Handler Reference Box and 'dhlr' for Data Handler Reference Box */ + uint32_t componentSubtype; /* Both ISOM and QT: when present in Media Handler Reference Box, this field defines the type of media data. + * ISOM: when present in Metadata Handler Reference Box, this field defines the format of the meta box contents. + * QTFF: when present in Data Handler Reference Box, this field defines the data reference type. */ + /* The following fields are defined in QTFF however these fields aren't mentioned in QuickTime SDK and are reserved in the specification. + * In ISOM, these fields are still defined as reserved. */ + uint32_t componentManufacturer; /* vendor indentification / A value of 0 matches any manufacturer. */ + uint32_t componentFlags; /* flags describing required component capabilities + * The high-order 8 bits should be set to 0. + * The low-order 24 bits are specific to each component type. */ + uint32_t componentFlagsMask; /* This field indicates which flags in the componentFlags field are relevant to this operation. */ + /* */ + uint8_t *componentName; /* ISOM: a null-terminated string in UTF-8 characters + * QTFF: Pascal string */ + + uint32_t componentName_length; +} isom_hdlr_t; + + +/** Media Information Header Boxes + ** There is a different media information header for each track type + ** (corresponding to the media handler-type); the matching header shall be present. **/ +/* Video Media Header Box + * This box contains general presentation information, independent of the coding, for video media. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; /* flags is 1 */ + uint16_t graphicsmode; /* template: graphicsmode = 0 */ + uint16_t opcolor[3]; /* template: opcolor = { 0, 0, 0 } */ +} isom_vmhd_t; + +/* Sound Media Header Box + * This box contains general presentation information, independent of the coding, for audio media. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + int16_t balance; /* a fixed-point 8.8 number that places mono audio tracks in a stereo space. template: balance = 0 */ + uint16_t reserved; +} isom_smhd_t; + +/* Hint Media Header Box + * This box contains general information, independent of the protocol, for hint tracks. (A PDU is a Protocol Data Unit.) */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint16_t maxPDUsize; /* the size in bytes of the largest PDU in this (hint) stream */ + uint16_t avgPDUsize; /* the average size of a PDU over the entire presentation */ + uint32_t maxbitrate; /* the maximum rate in bits/second over any window of one second */ + uint32_t avgbitrate; /* the average rate in bits/second over the entire presentation */ + uint32_t reserved; + /* run time variables for calculating avgPDUsize, should not be written to file */ + uint64_t combinedPDUsize; + uint64_t PDUcount; +} isom_hmhd_t; + +/* Null Media Header Box + * This box may be used for streams other than visual and audio (e.g., timed metadata streams). */ +typedef struct +{ + /* Streams other than visual and audio may use a Null Media Header Box */ + ISOM_FULLBOX_COMMON; /* flags is currently all zero */ +} isom_nmhd_t; + +/* Generic Media Information Box */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint16_t graphicsmode; + uint16_t opcolor[3]; + int16_t balance; /* This field is nomally set to 0. */ + uint16_t reserved; /* Reserved for use by Apple. Set this field to 0. */ +} isom_gmin_t; + +/* Text Media Information Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + int32_t matrix[9]; /* Unkown fields. Default values are probably: + * { 0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000 } */ +} isom_text_t; + +/* Generic Media Information Header Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + isom_gmin_t *gmin; /* Generic Media Information Box */ + isom_text_t *text; /* Text Media Information Box */ +} isom_gmhd_t; +/** **/ + +/* Data Reference Box + * name and location fields are expressed in null-terminated string using UTF-8 characters. */ +typedef struct +{ + /* This box is DataEntryUrlBox or DataEntryUrnBox */ + ISOM_FULLBOX_COMMON; /* flags == 0x000001 means that the media data is in the same file + * as the Movie Box containing this data reference. */ + char *name; /* only for DataEntryUrnBox */ + char *location; /* a location to find the resource with the given name */ + + uint32_t name_length; + uint32_t location_length; + lsmash_file_t *ref_file; /* pointer to the handle of the referenced file */ +} isom_dref_entry_t; + +typedef struct +{ + ISOM_FULLBOX_COMMON; + lsmash_entry_list_t list; +} isom_dref_t; + +/* Data Information Box */ +typedef struct +{ + /* This box is in Media Information Box or Meta Box */ + ISOM_BASEBOX_COMMON; + isom_dref_t *dref; /* Data Reference Box */ +} isom_dinf_t; + +/** Sample Description **/ +/* ES Descriptor Box */ +struct mp4sys_ES_Descriptor_t; /* FIXME: I think these structs using mp4sys should be placed in isom.c */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + struct mp4sys_ES_Descriptor_t *ES; +} isom_esds_t; + +/* MPEG-4 Bit Rate Box + * This box signals the bit rate information of the AVC video stream. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint32_t bufferSizeDB; /* the size of the decoding buffer for the elementary stream in bytes */ + uint32_t maxBitrate; /* the maximum rate in bits/second over any window of one second */ + uint32_t avgBitrate; /* the average rate in bits/second over the entire presentation */ +} isom_btrt_t; + +typedef struct +{ + /* This box is in RTP and RTP reception hint track sample descriptions */ + ISOM_BASEBOX_COMMON; + uint32_t timescale; +} isom_tims_t; + +typedef struct +{ + /* This box is in RTP and RTP reception hint track sample descriptions */ + ISOM_BASEBOX_COMMON; + int32_t offset; +} isom_tsro_t; + +typedef struct +{ + /* This box is in RTP reception hint track sample description */ + ISOM_BASEBOX_COMMON; + unsigned int reserved : 6; + unsigned int timestamp_sync : 2; +} isom_tssy_t; + + +/* Global Header Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint32_t header_size; + uint8_t *header_data; +} isom_glbl_t; + +/* Clean Aperture Box + * There are notionally four values in this box and these parameters are represented as a fraction N/D. + * Here, we refer to the pair of parameters fooN and fooD as foo. + * Considering the pixel dimensions as defined by the VisualSampleEntry width and height. + * If picture centre of the image is at pcX and pcY, then horizOff and vertOff are defined as follows: + * pcX = horizOff + (width - 1)/2; + * pcY = vertOff + (height - 1)/2; + * The leftmost/rightmost pixel and the topmost/bottommost line of the clean aperture fall at: + * pcX +/- (cleanApertureWidth - 1)/2; + * pcY +/- (cleanApertureHeight - 1)/2; + * QTFF: this box is a mandatory extension for all uncompressed Y'CbCr data formats. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint32_t cleanApertureWidthN; + uint32_t cleanApertureWidthD; + uint32_t cleanApertureHeightN; + uint32_t cleanApertureHeightD; + int32_t horizOffN; + uint32_t horizOffD; + int32_t vertOffN; + uint32_t vertOffD; +} isom_clap_t; + +/* Pixel Aspect Ratio Box + * This box specifies the aspect ratio of a pixel, in arbitrary units. + * If a pixel appears H wide and V tall, then hSpacing/vSpacing is equal to H/V. + * When adjusting pixel aspect ratio, normally, the horizontal dimension of the video is scaled, if needed. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint32_t hSpacing; /* horizontal spacing */ + uint32_t vSpacing; /* vertical spacing */ +} isom_pasp_t; + +/* ISOM: Colour Information Box / QTFF: Color Parameter Box + * This box is used to map the numerical values of pixels in the file to a common representation of color + * in which images can be correctly compared, combined, and displayed. + * If colour information is supplied in both this box, and also in the video bitstream, + * this box takes precedence, and over-rides the information in the bitstream. + * For QuickTime file format: + * This box ('colr') supersedes the Gamma Level Box ('gama'). + * Writers of QTFF should never write both into an Image Description, and readers of QTFF should ignore 'gama' if 'colr' is present. + * Note: this box is a mandatory extension for all uncompressed Y'CbCr data formats. + * For ISO Base Media file format: + * Colour information may be supplied in one or more Colour Information Boxes placed in a VisualSampleEntry. + * These should be placed in order in the sample entry starting with the most accurate (and potentially the most difficult to process), in progression to the least. + * These are advisory and concern rendering and colour conversion, and there is no normative behaviour associated with them; a reader may choose to use the most suitable. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint32_t color_parameter_type; /* QTFF: 'nclc' or 'prof' + * ISOM: 'nclx', 'rICC' or 'prof' */ + /* for 'nclc' and 'nclx' */ + uint16_t primaries_index; /* CIE 1931 xy chromaticity coordinates */ + uint16_t transfer_function_index; /* nonlinear transfer function from RGB to ErEgEb */ + uint16_t matrix_index; /* matrix from ErEgEb to EyEcbEcr */ + /* for 'nclx' */ + unsigned full_range_flag : 1; + unsigned reserved : 7; +} isom_colr_t; + +/* Gamma Level Box + * This box is used to indicate that the decompressor corrects gamma level at display time. + * This box is defined in QuickTime File Format Specification and ImageCompression.h. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint32_t level; /* A fixed-point 16.16 number indicating the gamma level at which the image was captured. + * Zero value indicates platform's standard gamma. */ +} isom_gama_t; + +/* Field/Frame Information Box + * This box is used by applications to modify decompressed image data or by decompressor components to determine field display order. + * This box is defined in QuickTime File Format Specification, dispatch019 and ImageCodec.h. + * Note: this box is a mandatory extension for all uncompressed Y'CbCr data formats. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint8_t fields; /* the number of fields per frame + * 1: progressive scan + * 2: 2:1 interlaced */ + uint8_t detail; /* field ordering */ +} isom_fiel_t; + +/* Content Light Level Info Box + * This Box is used to identify the upper bounds for the nominal target brightness light level of the pictures of the video. + * Note: The format of this box is identical to h.265 (HEVC) SEI Payload Type 144 (ISO-23008-2 D.2.35) */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint16_t max_content_light_level; + uint16_t max_pic_average_light_level; +} isom_clli_t; + +/* Master Display Color Volume Box + * This box is used to identify the color volume of a display that is considered to be the mastering display for the video content + * Note: The format of this box is identical to the h.265 (HEVC) SEI Payload Type 137 (ISO-23008-2 D.2.28) */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint16_t display_primaries_g_x; + uint16_t display_primaries_g_y; + uint16_t display_primaries_b_x; + uint16_t display_primaries_b_y; + uint16_t display_primaries_r_x; + uint16_t display_primaries_r_y; + uint16_t white_point_x; + uint16_t white_point_y; + uint32_t max_display_mastering_luminance; + uint32_t min_display_mastering_luminance; +} isom_mdcv_t; + +/* Colorspace Box + * This box is defined in ImageCompression.h. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint32_t pixel_format; /* the native pixel format of an image */ +} isom_cspc_t; + +/* Significant Bits Box + * This box is defined in Letters from the Ice Floe dispatch019. + * Note: this box is a mandatory extension for 'v216' (Uncompressed Y'CbCr, 10, 12, 14, or 16-bit-per-component 4:2:2). */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint8_t significantBits; /* the number of significant bits per component */ +} isom_sgbt_t; + +/* Sample Scale Box + * If this box is present and can be interpreted by the decoder, + * all samples shall be displayed according to the scaling behaviour that is specified in this box. + * Otherwise, all samples are scaled to the size that is indicated by the width and height field in the Track Header Box. + * This box is defined in ISO Base Media file format. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint8_t constraint_flag; /* Upper 7-bits are reserved. + * If this flag is set, all samples described by this sample entry shall be scaled + * according to the method specified by the field 'scale_method'. */ + uint8_t scale_method; /* The semantics of the values for scale_method are as specified for the 'fit' attribute of regions in SMIL 1.0. */ + int16_t display_center_x; + int16_t display_center_y; +} isom_stsl_t; + +/* Sample Entry */ +#define ISOM_SAMPLE_ENTRY \ + ISOM_BASEBOX_COMMON; \ + uint8_t reserved[6]; \ + uint16_t data_reference_index + +typedef struct +{ + ISOM_SAMPLE_ENTRY; +} isom_sample_entry_t; + +/* Mpeg Sample Entry */ +typedef struct +{ + ISOM_SAMPLE_ENTRY; +} isom_mp4s_entry_t; + +/* ISOM: Visual Sample Entry / QTFF: Image Description + * For maximum compatibility, the following extension boxes should follow, not precede, + * any extension boxes defined in or required by derived specifications. + * Clean Aperture Box + * Pixel Aspect Ratio Box */ +typedef struct +{ + ISOM_SAMPLE_ENTRY; + int16_t version; /* ISOM: pre_defined / QTFF: sample description version */ + int16_t revision_level; /* ISOM: reserved / QTFF: version of the CODEC */ + int32_t vendor; /* ISOM: pre_defined / QTFF: whose CODEC */ + uint32_t temporalQuality; /* ISOM: pre_defined / QTFF: the temporal quality factor */ + uint32_t spatialQuality; /* ISOM: pre_defined / QTFF: the spatial quality factor */ + /* The width and height are the maximum pixel counts that the codec will deliver. + * Since these are counts they do not take into account pixel aspect ratio. */ + uint16_t width; + uint16_t height; + /* */ + uint32_t horizresolution; /* 16.16 fixed-point / template: horizresolution = 0x00480000 / 72 dpi */ + uint32_t vertresolution; /* 16.16 fixed-point / template: vertresolution = 0x00480000 / 72 dpi */ + uint32_t dataSize; /* ISOM: reserved / QTFF: if known, the size of data for this descriptor */ + uint16_t frame_count; /* frame per sample / template: frame_count = 1 */ + char compressorname[33]; /* a fixed 32-byte field, with the first byte set to the number of bytes to be displayed */ + uint16_t depth; /* ISOM: template: depth = 0x0018 + * AVC : 0x0018: colour with no alpha + * 0x0028: grayscale with no alpha + * 0x0020: gray or colour with alpha + * QTFF: depth of this data (1-32) or (33-40 grayscale) */ + int16_t color_table_ID; /* ISOM: template: pre_defined = -1 + * QTFF: color table ID + * If this field is set to -1, the default color table should be used for the specified depth + * If the color table ID is set to 0, a color table is contained within the sample description itself. + * The color table immediately follows the color table ID field. */ + /* Color table follows color_table_ID only when color_table_ID is set to 0. */ + isom_qt_color_table_t color_table; /* a list of preferred colors for displaying the movie on devices that support only 256 colors */ +} isom_visual_entry_t; + +/* Format Box + * This box shows the data format of the stored sound media. + * ISO base media file format also defines the same four-character-code for the type field, + * however, that is used to indicate original sample description of the media when a protected sample entry is used. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint32_t data_format; /* copy of sample description type */ +} isom_frma_t; + +/* Audio Endian Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + int16_t littleEndian; +} isom_enda_t; + +/* MPEG-4 Audio Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint32_t unknown; /* always 0? */ +} isom_mp4a_t; + +/* Terminator Box + * This box is present to indicate the end of the sound description. It contains no data. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; /* size = 8, type = 0x00000000 */ +} isom_terminator_t; + +/* Sound Information Decompression Parameters Box + * This box is defined in QuickTime file format. + * This box provides the ability to store data specific to a given audio decompressor in the sound description. + * The contents of this box are dependent on the audio decompressor. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + isom_frma_t *frma; /* Format Box */ + isom_enda_t *enda; /* Audio Endian Box */ + isom_mp4a_t *mp4a; /* MPEG-4 Audio Box */ + isom_terminator_t *terminator; /* Terminator Box */ +} isom_wave_t; + +/* Audio Channel Layout Box + * This box is defined in QuickTime file format or Apple Lossless Audio inside ISO Base Media. */ +typedef struct +{ + uint32_t channelLabel; /* the channelLabel that describes the channel */ + uint32_t channelFlags; /* flags that control the interpretation of coordinates */ + uint32_t coordinates[3]; /* an ordered triple that specifies a precise speaker location / 32-bit floating point */ +} isom_channel_description_t; + +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint32_t channelLayoutTag; /* the channelLayoutTag indicates the layout */ + uint32_t channelBitmap; /* If channelLayoutTag is set to 0x00010000, this field is the channel usage bitmap. */ + uint32_t numberChannelDescriptions; /* the number of items in the Channel Descriptions array */ + /* Channel Descriptions array */ + isom_channel_description_t *channelDescriptions; +} isom_chan_t; + +/* Sampling Rate Box + * This box may be present only in an AudioSampleEntryV1, and when present, + * it overrides the samplerate field and documents the actual sampling rate. + * When this box is present, the media timescale should be the same as the + * sampling rate, or an integer division or multiple of it. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint32_t sampling_rate; /* the actual sampling rate of the audio media, expressed as a 32-bit integer + * The value of this field overrides the samplerate field in the AudioSampleEntryV1 + * and documents the actual sampling rate. */ +} isom_srat_t; + +/* ISOM: Audio Sample Entry / QTFF: Sound Description */ +typedef struct +{ + ISOM_SAMPLE_ENTRY; + int16_t version; /* ISOM: version = 0 is used to support non-high samplerate audio format. + * version = 1, called AudioSampleEntryV1, is used to support high samplerate audio format. + * An AudioSampleEntryV1 requires that the enclosing Sample Description Box also takes the version 1. + * For maximum compatibility, an AudioSampleEntryV1 should only be used when needed. + * QTFF: version = 0 supports only 'raw ' or 'twos' audio format. + * version = 1 is used to support out-of-band configuration settings for decompression. + * version = 2 is used to support high samplerate, or 3 or more multichannel audio format. */ + int16_t revision_level; /* ISOM: reserved / QTFF: version of the CODEC */ + int32_t vendor; /* ISOM: reserved / QTFF: whose CODEC */ + uint16_t channelcount; /* ISOM: template: channelcount = 2 + * channelcount is a value greater than zero that indicates the maximum number of channels that the + * audio could deliver. + * A channelcount of 1 indicates mono audio, and 2 indicates stereo (left/right). + * When values greater than 2 are used, the codec configuration should identify the channel assignment. + * QTFF: the number of audio channels + * Allowable values are 1 (mono) or 2 (stereo). + * For more than 2, set this field to 3 and use numAudioChannels instead of this field. */ + uint16_t samplesize; /* ISOM: template: samplesize = 16 + * QTFF: the number of bits in each uncompressed sample for a single channel + * Allowable values are 8 or 16. + * For non-mod8, set this field to 16 and use constBitsPerChannel instead of this field. + * For more than 16, set this field to 16 and use bytesPerPacket instead of this field. */ + int16_t compression_ID; /* ISOM: pre_defined + * QTFF: version = 0 -> must be set to 0. + * version = 2 -> must be set to -2. */ + uint16_t packet_size; /* ISOM: reserved / QTFF: must be set to 0. */ + uint32_t samplerate; /* the sampling rate expressed as a 16.16 fixed-point number + * ISOM: template: samplerate = {default samplerate of media}<<16 + * When it is desired to indicate an audio sampling rate greater than the value that can be represented in + * this field, this field should contain a value left-shifted 16 bits that matches the media timescale, + * or be an integer division or multiple of it. + * QTFF: the integer portion should match the media's timescale. + * If this field is invalid because of higher samplerate, + * then set this field to 0x00010000 and use audioSampleRate instead of this field. */ + /* QTFF-based version 1 fields + * These fields are for description of the compression ratio of fixed ratio audio compression algorithms. + * If these fields are not used, they are set to 0. */ + uint32_t samplesPerPacket; /* For compressed audio, be set to the number of uncompressed frames generated by a compressed frame. + * For uncompressed audio, shall be set to 1. */ + uint32_t bytesPerPacket; /* the number of bytes in a sample for a single channel */ + uint32_t bytesPerFrame; /* the number of bytes in a frame */ + uint32_t bytesPerSample; /* 8-bit audio: 1, other audio: 2 */ + /* QTFF-based version 2 fields + * LPCMFrame: one sample from each channel. + * AudioPacket: For uncompressed audio, an AudioPacket is simply one LPCMFrame. + * For compressed audio, an AudioPacket is the natural compressed access unit of that format. */ + uint32_t sizeOfStructOnly; /* offset to extensions */ + uint64_t audioSampleRate; /* 64-bit floating point */ + uint32_t numAudioChannels; /* any channel assignment info will be in Audio Channel Layout Box. */ + int32_t always7F000000; /* always 0x7F000000 */ + uint32_t constBitsPerChannel; /* only set if constant (and only for uncompressed audio) */ + uint32_t formatSpecificFlags; + uint32_t constBytesPerAudioPacket; /* only set if constant */ + uint32_t constLPCMFramesPerAudioPacket; /* only set if constant */ +} isom_audio_entry_t; + +/* Hint Sample Entry data field for + * rtp hint track, + * srtp hint track, + * rtp reception hint track and + * srtp reception hint track + * rtcp reception hint track + * srtcp reception hint track + */ +typedef struct +{ + ISOM_SAMPLE_ENTRY; + uint16_t hinttrackversion; /* = 1 */ + uint16_t highestcompatibleversion; /* = 1 */ + uint32_t maxpacketsize; +} isom_hint_entry_t; + +/* Metadata Sample Entry */ +#define ISOM_METADATA_SAMPLE_ENTRY \ + ISOM_SAMPLE_ENTRY + +typedef struct +{ + ISOM_METADATA_SAMPLE_ENTRY; +} isom_metadata_entry_t; + +/* QuickTime Text Sample Description */ +typedef struct +{ + ISOM_SAMPLE_ENTRY; + int32_t displayFlags; + int32_t textJustification; + uint16_t bgColor[3]; /* background RGB color */ + /* defaultTextBox */ + int16_t top; + int16_t left; + int16_t bottom; + int16_t right; + /* defaultStyle */ + int32_t scrpStartChar; /* starting character position */ + int16_t scrpHeight; + int16_t scrpAscent; + int16_t scrpFont; + uint16_t scrpFace; /* only first 8-bits are used */ + int16_t scrpSize; + uint16_t scrpColor[3]; /* foreground RGB color */ + /* defaultFontName is Pascal string */ + uint8_t font_name_length; + char *font_name; +} isom_qt_text_entry_t; + +/* FontRecord */ +typedef struct +{ + uint16_t font_ID; + /* Pascal string */ + uint8_t font_name_length; + char *font_name; +} isom_font_record_t; + +/* Font Table Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + /* FontRecord + * entry_count is uint16_t. */ + lsmash_entry_list_t *list; +} isom_ftab_t; + +/* 3GPP Timed Text Sample Entry */ +typedef struct +{ + ISOM_SAMPLE_ENTRY; + uint32_t displayFlags; + int8_t horizontal_justification; + int8_t vertical_justification; + uint8_t background_color_rgba[4]; + /* BoxRecord default_text_box */ + int16_t top; + int16_t left; + int16_t bottom; + int16_t right; + /* StyleRecord default_style */ + uint16_t startChar; /* always 0 */ + uint16_t endChar; /* always 0 */ + uint16_t font_ID; + uint8_t face_style_flags; + uint8_t font_size; + uint8_t text_color_rgba[4]; + /* Font Table Box font_table */ + isom_ftab_t *ftab; +} isom_tx3g_entry_t; + +/* Sample Description Box */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint32_t entry_count; /* print only */ + lsmash_entry_list_t list; +} isom_stsd_t; +/** **/ + +/* Decoding Time to Sample Box + * This box contains a compact version of a table that allows indexing from decoding time to sample number. + * Each entry in the table gives the number of consecutive samples with the same time delta, and the delta of those samples. + * By adding the deltas a complete time-to-sample map may be built. + * All samples must have non-zero durations except for the last one. + * The sum of all deltas gives the media duration in the track (not mapped to the movie timescale, and not considering any edit list). + * DTS is an abbreviation of 'decoding time stamp'. */ +typedef struct +{ + uint32_t sample_count; /* number of consecutive samples that have the given sample_delta */ + uint32_t sample_delta; /* DTS[0] = 0; DTS[n+1] = DTS[n] + sample_delta[n]; */ +} isom_stts_entry_t; + +typedef struct +{ + ISOM_FULLBOX_COMMON; + lsmash_entry_list_t *list; +} isom_stts_t; + +/* Composition Time to Sample Box + * This box provides the offset between decoding time and composition time. + * CTS is an abbreviation of 'composition time stamp'. + * This box is optional and must only be present if DTS and CTS differ for any samples. */ +typedef struct +{ +#define ISOM_NON_OUTPUT_SAMPLE_OFFSET 0x80000000 + uint32_t sample_count; /* number of consecutive samples that have the given sample_offset */ + uint32_t sample_offset; /* CTS[n] = DTS[n] + sample_offset[n]; + * ISOM: if version is set to 1, sample_offset is signed 32-bit integer. + * QTFF: sample_offset is always signed 32-bit integer. */ +} isom_ctts_entry_t; + +typedef struct +{ + ISOM_FULLBOX_COMMON; + lsmash_entry_list_t *list; +} isom_ctts_t; + +/* Composition to Decode Box (Composition Shift Least Greatest Box) + * This box may be used to relate the composition and decoding timelines, + * and deal with some of the ambiguities that signed composition offsets introduce. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + int32_t compositionToDTSShift; /* If this value is added to the composition times (as calculated by the CTS offsets from the DTS), + * then for all samples, their CTS is guaranteed to be greater than or equal to their DTS, + * and the buffer model implied by the indicated profile/level will be honoured; + * if leastDecodeToDisplayDelta is positive or zero, this field can be 0; + * otherwise it should be at least (- leastDecodeToDisplayDelta). */ + int32_t leastDecodeToDisplayDelta; /* the smallest sample_offset in this track */ + int32_t greatestDecodeToDisplayDelta; /* the largest sample_offset in this track */ + int32_t compositionStartTime; /* the smallest CTS for any sample */ + int32_t compositionEndTime; /* the CTS plus the composition duration, of the sample with the largest CTS in this track */ +} isom_cslg_t; + +/* Sample Size Box / Compact Sample Size Box + * This box contains the sample count and a table giving the size in bytes of each sample. + * The total number of samples in the media within the initial movie is always indicated in the sample_count. + * Note: a sample size of zero is not prohibited in general, but it must be valid and defined for the coding system, + * as defined by the sample entry, that the sample belongs to. */ +typedef struct +{ + uint32_t entry_size; /* the size of a sample */ +} isom_stsz_entry_t; + +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint32_t sample_size; /* the default sample size + * If this field is set to 0, then the samples have different sizes. */ + uint32_t sample_count; /* the number of samples in the media within the initial movie */ + lsmash_entry_list_t *list; /* available if sample_size == 0 */ +} isom_stsz_t; + +typedef struct +{ + ISOM_FULLBOX_COMMON; + unsigned int reserved : 24; /* 0 */ + unsigned int field_size : 8; /* the size in bits of the entries in the following table + * It shall take the value 4, 8 or 16. If the value 4 is used, then each byte contains two values + * entry[i]<<4 + entry[i+1]; if the sizes do not fill an integral number of bytes, the last byte is + * padded with zero. */ + uint32_t sample_count; /* the number of entries in the following table */ + lsmash_entry_list_t *list; /* L-SMASH uses isom_stsz_entry_t for its internal processes. */ +} isom_stz2_t; + +/* Sync Sample Box + * If this box is not present, every sample is a random access point. + * In AVC streams, this box cannot point non-IDR samples. + * The table is arranged in strictly increasing order of sample number. */ +typedef struct +{ + uint32_t sample_number; /* the numbers of the samples that are random access points in the stream. */ +} isom_stss_entry_t; + +typedef struct +{ + ISOM_FULLBOX_COMMON; + lsmash_entry_list_t *list; +} isom_stss_t; + +/* Partial Sync Sample Box + * Tip from QT engineering - Open-GOP intra frames need to be marked as "partial sync samples". + * Partial sync frames perform a partial reset of inter-frame dependencies; + * decoding two partial sync frames and the non-droppable difference frames between them is + * sufficient to prepare a decompressor for correctly decoding the difference frames that follow. */ +typedef struct +{ + uint32_t sample_number; /* the numbers of the samples that are partial sync samples in the stream. */ +} isom_stps_entry_t; + +typedef struct +{ + ISOM_FULLBOX_COMMON; + lsmash_entry_list_t *list; +} isom_stps_t; + +/* Independent and Disposable Samples Box */ +typedef struct +{ + unsigned is_leading : 2; /* ISOM: leading / QTFF: samples later in decode order may have earlier display times */ + unsigned sample_depends_on : 2; /* independency */ + unsigned sample_is_depended_on : 2; /* disposable */ + unsigned sample_has_redundancy : 2; /* redundancy */ +} isom_sdtp_entry_t; + +typedef struct +{ + ISOM_FULLBOX_COMMON; + /* According to the specification, the size of the table, sample_count, doesn't exist in this box. + * Instead of this, it is taken from the sample_count in the stsz or the stz2 box. */ + lsmash_entry_list_t *list; +} isom_sdtp_t; + +/* Sample To Chunk Box + * This box can be used to find the chunk that contains a sample, its position, and the associated sample description. + * The table is compactly coded. Each entry gives the index of the first chunk of a run of chunks with the same characteristics. + * By subtracting one entry here from the previous one, you can compute how many chunks are in this run. + * You can convert this to a sample count by multiplying by the appropriate samples_per_chunk. */ +typedef struct +{ + uint32_t first_chunk; /* the index of the first chunk in this run of chunks that share the same samples_per_chunk and sample_description_index */ + uint32_t samples_per_chunk; /* the number of samples in each of these chunks */ + uint32_t sample_description_index; /* the index of the sample entry that describes the samples in this chunk */ +} isom_stsc_entry_t; + +typedef struct +{ + ISOM_FULLBOX_COMMON; + lsmash_entry_list_t *list; +} isom_stsc_t; + +/* Chunk Offset Box + * chunk_offset is the offset of the start of a chunk into its containing media file. + * Offsets are file offsets, not the offset into any box within the file. */ +typedef struct +{ + uint32_t chunk_offset; +} isom_stco_entry_t; + +typedef struct +{ + /* for large presentations */ + uint64_t chunk_offset; +} isom_co64_entry_t; + +typedef struct +{ + ISOM_FULLBOX_COMMON; /* type = 'stco': 32-bit chunk offsets / type = 'co64': 64-bit chunk offsets */ + lsmash_entry_list_t *list; + + uint8_t large_presentation; /* Set 1 to this if 64-bit chunk-offset are needed. */ +} isom_stco_t; /* share with co64 box */ + +/* Sample Group Description Box + * This box gives information about the characteristics of sample groups. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; /* Use of version 0 entries is deprecated. */ + uint32_t grouping_type; /* an integer that identifies the sbgp that is associated with this sample group description */ + uint32_t default_length; /* the length of every group entry (if the length is constant), or zero (if it is variable) + * This field is available only if version == 1. */ + lsmash_entry_list_t *list; +} isom_sgpd_t; + +/* Random Access Entry + * Samples marked by this group must be random access points, and may also be sync points. */ +typedef struct +{ + /* grouping_type is 'rap ' */ + uint32_t description_length; /* This field is available only if version == 1 and default_length == 0. */ + unsigned num_leading_samples_known : 1; /* the value of one indicates that the number of leading samples is known for each sample in this group, + * and the number is specified by num_leading_samples. */ + unsigned num_leading_samples : 7; /* the number of leading samples for each sample in this group + * Note: when num_leading_samples_known is equal to 0, this field should be ignored. */ +} isom_rap_entry_t; + +/* Roll Recovery Entry + * This grouping type is defined as that group of samples having the same roll distance. */ +typedef struct +{ + /* grouping_type is 'roll' */ + uint32_t description_length; /* This field is available only if version == 1 and default_length == 0. */ + int16_t roll_distance; /* the number of samples that must be decoded in order for a sample to be decoded correctly + * A positive value indicates post-roll, and a negative value indicates pre-roll. + * The value zero must not be used. */ +} isom_roll_entry_t; + +/* Sample to Group Box + * This box is used to find the group that a sample belongs to and the associated description of that sample group. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint32_t grouping_type; /* Links it to its sample group description table with the same value for grouping type. */ + uint32_t grouping_type_parameter; /* an indication of the sub-type of the grouping + * This field is available only if version == 1. */ + lsmash_entry_list_t *list; +} isom_sbgp_t; + +typedef struct +{ + uint32_t sample_count; /* the number of consecutive samples with the same sample group descriptor */ + uint32_t group_description_index; /* the index of the sample group entry which describes the samples in this group + * The index ranges from 1 to the number of sample group entries in the Sample Group Description Box, + * or takes the value 0 to indicate that this sample is a member of no group of this type. + * Within the Sample to Group Box in movie fragment, the group description indexes for groups defined + * within the same fragment start at 0x10001, i.e. the index value 1, with the value 1 in the top 16 bits. */ +} isom_group_assignment_entry_t; + +/* Sample Table Box */ +struct isom_stbl_tag +{ + ISOM_BASEBOX_COMMON; + isom_stsd_t *stsd; /* Sample Description Box */ + isom_stts_t *stts; /* Decoding Time to Sample Box */ + isom_ctts_t *ctts; /* Composition Time to Sample Box */ + isom_cslg_t *cslg; /* ISOM: Composition to Decode Box / QTFF: Composition Shift Least Greatest Box */ + isom_stss_t *stss; /* Sync Sample Box */ + isom_stps_t *stps; /* ISOM: null / QTFF: Partial Sync Sample Box */ + isom_sdtp_t *sdtp; /* Independent and Disposable Samples Box */ + isom_stsc_t *stsc; /* Sample To Chunk Box */ + isom_stsz_t *stsz; /* Sample Size Box */ + isom_stz2_t *stz2; /* Compact Sample Size Box */ + isom_stco_t *stco; /* Chunk Offset Box */ + lsmash_entry_list_t sgpd_list; /* Sample Group Description Boxes */ + lsmash_entry_list_t sbgp_list; /* Sample To Group Boxes */ + + /* Use 'stz2' instead of 'stsz' if possible. (write mode only) */ + int (*compress_sample_size_table)( isom_stbl_t *stbl ); + /* Add independent and disposable info for each sample if possible. (write mode only) */ + int (*add_dependency_type)( isom_stbl_t *stbl, lsmash_file_t *file, lsmash_sample_property_t *prop ); +}; + +/* Media Information Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + /* Media Information Header Boxes */ + isom_vmhd_t *vmhd; /* Video Media Header Box */ + isom_smhd_t *smhd; /* Sound Media Header Box */ + isom_hmhd_t *hmhd; /* ISOM: Hint Media Header Box / QTFF: null */ + isom_nmhd_t *nmhd; /* ISOM: Null Media Header Box / QTFF: null */ + isom_gmhd_t *gmhd; /* ISOM: null / QTFF: Generic Media Information Header Box */ + /* */ + isom_hdlr_t *hdlr; /* ISOM: null / QTFF: Data Handler Reference Box + * Note: this box must come before Data Information Box. */ + isom_dinf_t *dinf; /* Data Information Box */ + isom_stbl_t *stbl; /* Sample Table Box */ +} isom_minf_t; + +/* Media Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + isom_mdhd_t *mdhd; /* Media Header Box */ + isom_hdlr_t *hdlr; /* ISOM: Handler Reference Box / QTFF: Media Handler Reference Box + * Note: this box must come before Media Information Box. */ + isom_minf_t *minf; /* Media Information Box */ +} isom_mdia_t; + +/* Movie Header Box + * This box defines overall information which is media-independent, and relevant to the entire presentation considered as a whole. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; /* version is either 0 or 1 */ + /* version == 0: uint64_t -> uint32_t */ + uint64_t creation_time; /* the creation time of the presentation (in seconds since midnight, Jan. 1, 1904, in UTC time) */ + uint64_t modification_time; /* the most recent time the presentation was modified (in seconds since midnight, Jan. 1, 1904, in UTC time) */ + uint32_t timescale; /* movie timescale: timescale for the entire presentation */ + uint64_t duration; /* the duration, expressed in movie timescale, of the longest track */ + /* The following fields are treated as + * ISOM: template fields. + * MP41: reserved fields. + * MP42: ignored fileds since compositions are done using BIFS system. + * 3GPP: ignored fields. + * QTFF: usable fields. */ + int32_t rate; /* fixed point 16.16 number. 0x00010000 is normal forward playback. */ + int16_t volume; /* fixed point 8.8 number. 0x0100 is full volume. */ + int16_t reserved; + int32_t preferredLong[2]; /* ISOM: reserved / QTFF: unknown */ + int32_t matrix[9]; /* transformation matrix for the video */ + /* The following fields are defined in QuickTime file format. + * In ISO Base Media file format, these fields are treated as pre_defined. */ + int32_t previewTime; /* the time value in the movie at which the preview begins */ + int32_t previewDuration; /* the duration of the movie preview in movie timescale units */ + int32_t posterTime; /* the time value of the time of the movie poster */ + int32_t selectionTime; /* the time value for the start time of the current selection */ + int32_t selectionDuration; /* the duration of the current selection in movie timescale units */ + int32_t currentTime; /* the time value for current time position within the movie */ + /* */ + uint32_t next_track_ID; /* larger than the largest track-ID in use */ +} isom_mvhd_t; + +/* Object Descriptor Box + * Note that this box is mandatory under 14496-1:2001 (mp41) while not mandatory under 14496-14:2003 (mp42). */ +struct mp4sys_ObjectDescriptor_t; /* FIXME: I think these structs using mp4sys should be placed in isom.c */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + struct mp4sys_ObjectDescriptor_t *OD; +} isom_iods_t; + +/* Media Data Box + * This box contains the media data. + * A presentation may contain zero or more Media Data Boxes.*/ +typedef struct +{ + ISOM_BASEBOX_COMMON; /* If size is 0, then this box is the last box. */ + + uint64_t media_size; /* the total media size already written in this box */ + uint64_t reserved_size; /* the reserved total media size in this box + * If 'media_size' > 'reserved_size' occurs when finishing a non-fragmented movie, + * rewrite the size of this box. */ +} isom_mdat_t; + +/* Free Space Box + * The contents of a free-space box are irrelevant and may be ignored without affecting the presentation. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; /* type is 'free' or 'skip' */ + uint32_t length; + uint8_t *data; +} isom_free_t; + +typedef isom_free_t isom_skip_t; + +/* Chapter List Box + * This box is NOT defined in the ISO/MPEG-4 specs. + * Basically, this box exists in User Data Box inside Movie Box if present. */ +typedef struct +{ + uint64_t start_time; /* version = 0: expressed in movie timescale units + * version = 1: expressed in 100 nanoseconds */ + /* Chapter name is Pascal string */ + uint8_t chapter_name_length; + char *chapter_name; +} isom_chpl_entry_t; + +typedef struct +{ + ISOM_FULLBOX_COMMON; /* version = 0 is defined in F4V file format. */ + uint8_t unknown; /* only available under version = 1 */ + lsmash_entry_list_t *list; /* if version is set to 0, entry_count is uint8_t. */ +} isom_chpl_t; + +typedef struct +{ + char *chapter_name; + uint64_t start_time; +} isom_chapter_entry_t; + +/* Metadata Item Keys Box */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + lsmash_entry_list_t *list; +} isom_keys_t; + +typedef struct +{ + uint32_t key_size; /* the size of the entire structure containing a key definition + * key_size = sizeof(key_size) + sizeof(key_namespace) + sizeof(key_value) */ + uint32_t key_namespace; /* a naming scheme used for metadata keys + * Location metadata keys, for example, use the 'mdta' key namespace. */ + uint8_t *key_value; /* the actual name of the metadata key + * Keys with the 'mdta' namespace use a reverse DNS naming convention. */ +} isom_keys_entry_t; + +/* Meaning Box */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint8_t *meaning_string; /* to fill the box */ + + uint32_t meaning_string_length; +} isom_mean_t; + +/* Name Box */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint8_t *name; /* to fill the box */ + + uint32_t name_length; +} isom_name_t; + +/* Data Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + /* type indicator */ + uint16_t reserved; /* always 0 */ + uint8_t type_set_identifier; /* 0: type set of the common basic data types */ + uint8_t type_code; /* type of data code */ + /* */ + uint32_t the_locale; /* reserved to be 0 */ + uint8_t *value; /* to fill the box */ + + uint32_t value_length; +} isom_data_t; + +/* Metadata Item Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + isom_mean_t *mean; /* Meaning Box */ + isom_name_t *name; /* Name Box */ + isom_data_t *data; /* Data Box */ +} isom_metaitem_t; + +/* Metadata Item List Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + lsmash_entry_list_t metaitem_list; /* Metadata Item Box List + * There is no entry_count field. */ +} isom_ilst_t; + +/* Meta Box */ +typedef struct +{ + ISOM_FULLBOX_COMMON; /* ISOM: FullBox / QTFF: BaseBox */ + isom_hdlr_t *hdlr; /* Metadata Handler Reference Box */ + isom_dinf_t *dinf; /* ISOM: Data Information Box / QTFF: null */ + isom_keys_t *keys; /* ISOM: null / QTFF: Metadata Item Keys Box */ + isom_ilst_t *ilst; /* Metadata Item List Box only defined in Apple MPEG-4 and QTFF */ +} isom_meta_t; + +/* Window Location Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + /* default window location for movie */ + uint16_t x; + uint16_t y; +} isom_WLOC_t; + +/* Looping Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint32_t looping_mode; /* 0 for none, 1 for looping, 2 for palindromic looping */ +} isom_LOOP_t; + +/* Play Selection Only Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint8_t selection_only; /* whether only the selected area of the movie should be played */ +} isom_SelO_t; + +/* Play All Frames Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint8_t play_all_frames; /* whether all frames of video should be played, regardless of timing */ +} isom_AllF_t; + +/* Copyright Box + * The Copyright box contains a copyright declaration which applies to the entire presentation, + * when contained within the Movie Box, or, when contained in a track, to that entire track. + * There may be multiple copyright boxes using different language codes. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint16_t language; /* ISO-639-2/T language codes. Most significant 1-bit is 0. + * Each character is packed as the difference between its ASCII value and 0x60. */ + uint8_t *notice; /* a null-terminated string in either UTF-8 or UTF-16 characters, giving a copyright notice. + * If UTF-16 is used, the string shall start with the BYTE ORDER MARK (0xFEFF), to distinguish it from a UTF-8 string. + * This mark does not form part of the final string. */ + uint32_t notice_length; +} isom_cprt_t; + +/* Movie SDP Information box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint32_t descriptionformat; + uint8_t *sdptext; + uint32_t sdp_length; +}isom_rtp_t; + +/* Track SDP Information box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + uint8_t *sdptext; + uint32_t sdp_length; +}isom_sdp_t; + +typedef struct +{ + ISOM_BASEBOX_COMMON; + isom_rtp_t *rtp; /* Movie-level SDP box*/ + isom_sdp_t *sdp; /* Track-level SDP box*/ +} isom_hnti_t; + +/* User Data Box + * This box is a container box for informative user-data. + * This user data is formatted as a set of boxes with more specific box types, which declare more precisely their content. + * QTFF: for historical reasons, this box is optionally terminated by a 32-bit integer set to 0. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + isom_chpl_t *chpl; /* Chapter List Box */ + isom_meta_t *meta; /* Meta Box extended by Apple for iTunes movie */ + /* QuickTime user data */ + isom_WLOC_t *WLOC; /* Window Location Box */ + isom_LOOP_t *LOOP; /* Looping Box */ + isom_SelO_t *SelO; /* Play Selection Only Box */ + isom_AllF_t *AllF; /* Play All Frames Box */ + /* Copyright Box List */ + lsmash_entry_list_t cprt_list; /* Copyright Boxes is defined in ISO Base Media and 3GPP file format */ + /* Hint information box */ + isom_hnti_t *hnti; +} isom_udta_t; + +/** Caches for handling tracks **/ +typedef struct +{ + uint64_t alloc; /* total buffer size for the pool */ + uint64_t size; /* total size of samples in the pool */ + uint32_t sample_count; /* number of samples in the pool */ + uint8_t *data; /* actual data of samples in the pool */ +} isom_sample_pool_t; + +typedef struct +{ + uint32_t chunk_number; /* chunk number */ + uint32_t sample_description_index; /* sample description index */ + uint64_t first_dts; /* the first DTS in chunk */ + isom_sample_pool_t *pool; /* samples pooled to interleave */ +} isom_chunk_t; + +typedef struct +{ + uint64_t dts; + uint64_t cts; + int32_t ctd_shift; +} isom_timestamp_t; + +typedef struct +{ + isom_group_assignment_entry_t *assignment; /* the address corresponding to the entry in Sample to Group Box */ + isom_group_assignment_entry_t *prev_assignment; /* the address of the previous assignment */ + isom_rap_entry_t *random_access; /* the address corresponding to the random access entry in Sample Group Description Box */ + uint8_t is_prev_rap; /* whether the previous sample is a random access point or not */ +} isom_rap_group_t; + +typedef struct +{ + isom_group_assignment_entry_t *assignment; /* the address corresponding to the entry in Sample to Group Box */ + isom_sgpd_t *sgpd; /* the address to the active Sample Group Description Box */ + uint32_t first_sample; /* the number of the first sample of the group */ + uint32_t recovery_point; /* the identifier necessary for the recovery from its starting point to be completed */ + uint64_t rp_cts; /* the CTS of the recovery point */ + int16_t roll_distance; /* the current roll_distance + * The value may be updated when 'described' is set to ROLL_DISTANCE_INITIALIZED. */ +#define MAX_ROLL_WAIT_AND_SEE_COUNT 64 + uint8_t wait_and_see_count; /* Wait-and-see after initialization of roll_distance until reaching MAX_ROLL_WAIT_AND_SEE. */ + uint8_t is_fragment; /* the flag if the current group is in fragment */ + uint8_t prev_is_recovery_start; /* whether the previous sample is a starting point of recovery or not */ + uint8_t delimited; /* the flag if the sample_count is determined */ +#define ROLL_DISTANCE_INITIALIZED 1 +#define ROLL_DISTANCE_DETERMINED 2 + uint8_t described; /* the status of the group description */ +} isom_roll_group_t; + +typedef struct +{ + lsmash_entry_list_t *pool; /* grouping pooled to delimit and describe */ +} isom_grouping_t; + +typedef struct +{ + uint64_t segment_duration; /* the sum of the subsegment_duration of preceeding subsegments */ + uint64_t largest_cts; /* the largest CTS of a subsegment of the reference stream */ + uint64_t smallest_cts; /* the smallest CTS of a subsegment of the reference stream */ + uint64_t first_sample_cts; /* the CTS of the first sample of a subsegment of the reference stream */ + /* SAP related info within the active subsegment of the reference stream */ + uint64_t first_ed_cts; /* the earliest CTS of decodable samples after the first recovery point */ + uint64_t first_rp_cts; /* the CTS of the first recovery point */ + uint32_t first_rp_number; /* the number of the first recovery point */ + uint32_t first_ra_number; /* the number of the first random accessible sample */ + lsmash_random_access_flag first_ra_flags; /* the flags of the first random accessible sample */ + int is_first_recovery_point; + int decodable; +} isom_subsegment_t; + +typedef struct +{ + uint8_t has_samples; /* Whether whole movie has any sample or not. */ + uint8_t roll_grouping; + uint8_t rap_grouping; + uint32_t traf_number; + uint32_t last_duration; /* the last sample duration in this track fragment */ + uint64_t largest_cts; /* the largest CTS in this track fragment */ + uint32_t sample_count; /* the number of samples in this track fragment */ + uint32_t output_sample_count; /* the number of output samples in this track fragment */ + isom_subsegment_t subsegment; +} isom_fragment_t; + +typedef struct +{ + uint8_t all_sync; /* if all samples are sync sample */ + uint8_t is_audio; + isom_chunk_t chunk; + isom_timestamp_t timestamp; /* Each field stores the last valid value. */ + isom_grouping_t roll; + isom_rap_group_t *rap; + isom_fragment_t *fragment; +} isom_cache_t; + +/** Movie Fragments Boxes **/ +/* Track Fragments Flags ('tf_flags') */ +typedef enum +{ + ISOM_TF_FLAGS_BASE_DATA_OFFSET_PRESENT = 0x000001, /* base-data-offset-present: + * This flag indicates the presence of the base_data_offset field. + * The base_data_offset is the base offset to use when calculating data offsets. + * Offsets are file offsets as like as chunk_offset in Chunk Offset Box. + * If this flag is set and default-base-is-moof is not set, the base_data_offset + * for the first track in the movie fragment is the position of the first byte + * of the enclosing Movie Fragment Box, and for second and subsequent track + * fragments, the default is the end of the data defined by the preceding fragment. */ + ISOM_TF_FLAGS_SAMPLE_DESCRIPTION_INDEX_PRESENT = 0x000002, /* sample-description-index-present + * This flag indicates the presence of the sample_description_index field. */ + ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT = 0x000008, /* default-sample-duration-present: + * This flag indicates the presence of the default_sample_duration field. */ + ISOM_TF_FLAGS_DEFAULT_SAMPLE_SIZE_PRESENT = 0x000010, /* default-sample-size-present: + * This flag indicates the presence of the default_sample_size field. */ + ISOM_TF_FLAGS_DEFAULT_SAMPLE_FLAGS_PRESENT = 0x000020, /* default-sample-flags-present: + * This flag indicates the presence of the default_sample_flags field. */ + ISOM_TF_FLAGS_DURATION_IS_EMPTY = 0x010000, /* duration-is-empty: + * This flag indicates there are no samples for this time interval. */ + ISOM_TF_FLAGS_DEFAULT_BASE_IS_MOOF = 0x020000, /* default-base-is-moof: + * If base-data-offset-present is not set, this flag indicates the implicit + * base_data_offset is always equal to the position of the first byte of the + * enclosing Movie Fragment BOX. + * This flag is only available under the 'iso5' or later brands and cannot be set + * when earlier brands are included in the File Type box. */ +} isom_tf_flags_code; + +/* Track Run Flags ('tr_flags') */ +typedef enum +{ + ISOM_TR_FLAGS_DATA_OFFSET_PRESENT = 0x000001, /* data-offset-present: + * This flag indicates the presence of the data_offset field. */ + ISOM_TR_FLAGS_FIRST_SAMPLE_FLAGS_PRESENT = 0x000004, /* first-sample-flags-present: + * This flag indicates the presence of the first_sample_flags field. */ + ISOM_TR_FLAGS_SAMPLE_DURATION_PRESENT = 0x000100, /* sample-duration-present: + * This flag indicates the presence of the sample_duration field. */ + ISOM_TR_FLAGS_SAMPLE_SIZE_PRESENT = 0x000200, /* sample-size-present: + * This flag indicates the presence of the sample_size field. */ + ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT = 0x000400, /* sample-flags-present: + * This flag indicates the presence of the sample_flags field. */ + ISOM_TR_FLAGS_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT = 0x000800, /* sample-composition-time-offsets-present: + * This flag indicates the presence of the sample_composition_time_offset field. */ +} isom_tr_flags_code; + +/* Sample Flags */ +typedef struct +{ + unsigned reserved : 4; + /* The definition of the following fields is quite the same as Independent and Disposable Samples Box. */ + unsigned is_leading : 2; + unsigned sample_depends_on : 2; + unsigned sample_is_depended_on : 2; + unsigned sample_has_redundancy : 2; + /* */ + unsigned sample_padding_value : 3; /* the number of bits at the end of this sample */ + unsigned sample_is_non_sync_sample : 1; /* 0 value means this sample is sync sample. */ + uint16_t sample_degradation_priority; +} isom_sample_flags_t; + +/* Movie Extends Header Box + * This box is omitted when used in live streaming. + * If this box is not present, the overall duration must be computed by examining each fragment. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + /* version == 0: uint64_t -> uint32_t */ + uint64_t fragment_duration; /* the duration of the longest track, in the timescale indicated in the Movie Header Box, including movie fragments. */ +} isom_mehd_t; + +/* Track Extends Box + * This box sets up default values used by the movie fragments. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint32_t track_ID; /* identifier of the track; this shall be the track ID of a track in the Movie Box */ + uint32_t default_sample_description_index; + uint32_t default_sample_duration; + uint32_t default_sample_size; + isom_sample_flags_t default_sample_flags; +} isom_trex_t; + +/* Movie Extends Box + * This box warns readers that there might be Movie Fragment Boxes in this file. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + isom_mehd_t *mehd; /* Movie Extends Header Box / omitted when used in live streaming */ + lsmash_entry_list_t trex_list; /* Track Extends Box */ +} isom_mvex_t; + +/* Movie Fragment Header Box + * This box contains a sequence number, as a safety check. + * The sequence number 'usually' starts at 1 and must increase for each movie fragment in the file, in the order in which they occur. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint32_t sequence_number; /* the ordinal number of this fragment, in increasing order */ +} isom_mfhd_t; + +/* Track Fragment Header Box + * Each movie fragment can contain zero or more fragments for each track; + * and a track fragment can contain zero or more contiguous runs of samples. + * This box sets up information and defaults used for those runs of samples. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; /* flags field is used for 'tf_flags'. */ + uint32_t track_ID; + /* all the following are optional fields */ + uint64_t base_data_offset; /* an explicit anchor for the data offsets in each track run + * To avoid the case this field might overflow, e.g. semi-permanent live streaming and broadcasting, + * you shall not use this optional field. */ + uint32_t sample_description_index; /* override default_sample_description_index in Track Extends Box */ + uint32_t default_sample_duration; /* override default_sample_duration in Track Extends Box */ + uint32_t default_sample_size; /* override default_sample_size in Track Extends Box */ + isom_sample_flags_t default_sample_flags; /* override default_sample_flags in Track Extends Box */ +} isom_tfhd_t; + +/* Track Fragment Base Media Decode Time Box + * This box provides the absolute decode time, measured on the media timeline, of the first sample in decode order in the track fragment. + * This can be useful, for example, when performing random access in a file; + * it is not necessary to sum the sample durations of all preceding samples in previous fragments to find this value + * (where the sample durations are the deltas in the Decoding Time to Sample Box and the sample_durations in the preceding track runs). + * This box, if present, shall be positioned after the Track Fragment Header Box and before the first Track Fragment Run box. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; /* version is either 0 or 1 */ + /* version == 0: 64bits -> 32bits */ + uint64_t baseMediaDecodeTime; /* an integer equal to the sum of the decode durations of all earlier samples in the media, expressed in the media's timescale + * It does not include the samples added in the enclosing track fragment. + * NOTE: the decode timeline is a media timeline, established before any explicit or implied mapping of media time to presentation time, + * for example by an edit list or similar structure. */ +} isom_tfdt_t; + +/* Track Fragment Run Box + * Within the Track Fragment Box, there are zero or more Track Fragment Run Boxes. + * If the duration-is-empty flag is set in the tf_flags, there are no track runs. + * A track run documents a contiguous set of samples for a track. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; /* flags field is used for 'tr_flags'. */ + uint32_t sample_count; /* the number of samples being added in this run; also the number of rows in the following table */ + /* The following are optional fields. */ + int32_t data_offset; /* This value is added to the implicit or explicit data_offset established in the Track Fragment Header Box. + * If this field is not present, then the data for this run starts immediately after the data of the previous run, + * or at the base_data_offset defined by the Track Fragment Header Box if this is the first run in a track fragment. */ + isom_sample_flags_t first_sample_flags; /* a set of flags for the first sample only of this run */ + lsmash_entry_list_t *optional; /* all fields in this array are optional. */ +} isom_trun_t; + +typedef struct +{ + /* If the following fields is present, each field overrides default value described in Track Fragment Header Box or Track Extends Box. */ + uint32_t sample_duration; /* override default_sample_duration */ + uint32_t sample_size; /* override default_sample_size */ + isom_sample_flags_t sample_flags; /* override default_sample_flags */ + /* */ + uint32_t sample_composition_time_offset; /* composition time offset + * If version == 0, unsigned 32-bit integer. + * Otherwise, signed 32-bit integer. */ +} isom_trun_optional_row_t; + +/* Track Fragment Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + isom_tfhd_t *tfhd; /* Track Fragment Header Box */ + isom_tfdt_t *tfdt; /* Track Fragment Base Media Decode Time Box */ + lsmash_entry_list_t trun_list; /* Track Fragment Run Box List + * If the duration-is-empty flag is set in the tf_flags, there are no track runs. */ + isom_sdtp_t *sdtp; /* Independent and Disposable Samples Box (available under Protected Interoperable File Format) */ + lsmash_entry_list_t sgpd_list; /* Sample Group Description Boxes (available under ISO Base Media version 6 or later) */ + lsmash_entry_list_t sbgp_list; /* Sample To Group Boxes */ + + isom_cache_t *cache; /* taken over from corresponding 'trak' */ +} isom_traf_t; + +/* Movie Fragment Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + isom_mfhd_t *mfhd; /* Movie Fragment Header Box */ + lsmash_entry_list_t traf_list; /* Track Fragment Box List */ +} isom_moof_t; + +/* Track Fragment Random Access Box + * Each entry in this box contains the location and the presentation time of the sync sample. + * Note that not every sync sample in the track needs to be listed in the table. + * The absence of this box does not mean that all the samples are sync samples. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint32_t track_ID; + unsigned int reserved : 26; + unsigned int length_size_of_traf_num : 2; /* the length in byte of the traf_number field minus one */ + unsigned int length_size_of_trun_num : 2; /* the length in byte of the trun_number field minus one */ + unsigned int length_size_of_sample_num : 2; /* the length in byte of the sample_number field minus one */ + uint32_t number_of_entry; /* the number of the entries for this track + * Value zero indicates that every sample is a sync sample and no table entry follows. */ + lsmash_entry_list_t *list; /* entry_count corresponds to number_of_entry. */ +} isom_tfra_t; + +typedef struct +{ + /* version == 0: 64bits -> 32bits */ + uint64_t time; /* the presentation time of the sync sample in units defined in the Media Header Box of the associated track + * For segments based on movie sample tables or movie fragments, presentation times are in the movie timeline, + * that is they are composition times after the application of any edit list for the track. + * Note: the definition of segment is portion of an ISO base media file format file, consisting of either + * (a) a movie box, with its associated media data (if any) and other associated boxes + * or + * (b) one or more movie fragment boxes, with their associated media data, and other associated boxes. */ + uint64_t moof_offset; /* the offset of the Movie Fragment Box used in this entry + * Offset is the byte-offset between the beginning of the file and the beginning of the Movie Fragment Box. */ + /* */ + uint32_t traf_number; /* the Track Fragment Box ('traf') number that contains the sync sample + * The number ranges from 1 in each Movie Fragment Box ('moof'). */ + uint32_t trun_number; /* the Track Fragment Run Box ('trun') number that contains the sync sample + * The number ranges from 1 in each Track Fragment Box ('traf'). */ + uint32_t sample_number; /* the sample number that contains the sync sample + * The number ranges from 1 in each Track Fragment Run Box ('trun'). */ +} isom_tfra_location_time_entry_t; + +/* Movie Fragment Random Access Offset Box + * This box provides a copy of the length field from the enclosing Movie Fragment Random Access Box. */ +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint32_t length; /* an integer gives the number of bytes of the enclosing Movie Fragment Random Access Box + * This field is placed at the last of the enclosing box to assist readers scanning + * from the end of the file in finding the Movie Fragment Random Access Box. */ +} isom_mfro_t; + +/* Movie Fragment Random Access Box + * This box provides a table which may assist readers in finding sync samples in a file using movie fragments, + * and is usually placed at or near the end of the file. + * The last box within the Movie Fragment Random Access Box, which is called Movie Fragment Random Access Offset Box, + * provides a copy of the length field from the Movie Fragment Random Access Box. */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + lsmash_entry_list_t tfra_list; /* Track Fragment Random Access Box */ + isom_mfro_t *mfro; /* Movie Fragment Random Access Offset Box */ +} isom_mfra_t; + +/* Movie fragment manager + * The presence of this means we use the structure of movie fragments. */ +typedef struct +{ +#define FIRST_MOOF_POS_UNDETERMINED UINT64_MAX + isom_moof_t *movie; /* the address corresponding to the current Movie Fragment Box */ + uint64_t first_moof_pos; + uint64_t pool_size; /* the total sample size in the current movie fragment */ + uint64_t sample_count; /* the number of samples within the current movie fragment */ + lsmash_entry_list_t *pool; /* samples pooled to interleave for the current movie fragment */ +} isom_fragment_manager_t; + +/** **/ + +/* Track Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + isom_tkhd_t *tkhd; /* Track Header Box */ + isom_tapt_t *tapt; /* ISOM: null / QTFF: Track Aperture Mode Dimensions Box */ + isom_edts_t *edts; /* Edit Box */ + isom_tref_t *tref; /* Track Reference Box */ + isom_mdia_t *mdia; /* Media Box */ + isom_udta_t *udta; /* User Data Box */ + isom_meta_t *meta; /* Meta Box */ + + isom_cache_t *cache; + uint32_t related_track_ID; + uint8_t is_chapter; +} isom_trak_t; + +/* Movie Box */ +typedef struct +{ + ISOM_BASEBOX_COMMON; + isom_mvhd_t *mvhd; /* Movie Header Box */ + isom_iods_t *iods; /* MP4: Object Descriptor Box */ + lsmash_entry_list_t trak_list; /* Track Box List */ + isom_udta_t *udta; /* User Data Box */ + isom_ctab_t *ctab; /* ISOM: null / QTFF: Color Table Box */ + isom_meta_t *meta; /* Meta Box */ + isom_mvex_t *mvex; /* Movie Extends Box */ +} isom_moov_t; + +/** Segments + * segment + * portion of an ISO base media file format file, consisting of either (a) a movie box, with its associated media data + * (if any) and other associated boxes or (b) one or more movie fragment boxes, with their associated media data, and + * and other associated boxes + * subsegment + * time interval of a segment formed from movie fragment boxes, that is also a valid segment + * A subsegment is defined as a time interval of the containing (sub)segment, and corresponds to a single range of + * bytes of the containing (sub)segment. The durations of all the subsegments sum to the duration of the containing + * (sub)segment. + **/ +/* Segment Type Box + * Media presentations may be divided into segments for delivery, for example, it is possible (e.g. in HTTP streaming) to + * form files that contain a segment ? or concatenated segments ? which would not necessarily form ISO Base Media file + * format compliant files (e.g. they do not contain a Movie Box). + * If segments are stored in separate files (e.g. on a standard HTTP server) it is recommended that these 'segment files' + * contain a Segment Type Box, which must be first if present, to enable identification of those files, and declaration of + * the specifications with which they are compliant. + * Segment Type Boxes that are not first in a file may be ignored. + * Valid Segment Type Boxes shall be the first box in a segment. + * Note: + * The 'valid' here does not always mean that any brand of that segment has compatibility against other brands of it. + * After concatenations of segments, the result file might contain incompatibilities among brands. */ +typedef isom_ftyp_t isom_styp_t; + +/* Segment Index Box + * This box provides a compact index of one media stream within the media segment to which it applies. + * + * Each Segment Index Box documents how a (sub)segment is divided into one or more subsegments (which may themselves be + * further subdivided using Segment Index boxes). + * + * Each entry in the Segment Index Box contains a reference type that indicates whether the reference points directly to + * the media bytes of a referenced leaf subsegment, which is a subsegment that does not contain any indexing information + * that would enable its further division into subsegments, or to a Segment Index box that describes how the referenced + * subsegment is further subdivided; as a result, the segment may be indexed in a 'hierarchical' or 'daisy-chain' or + * other form by documenting time and byte offset information for other Segment Index Boxes applying to portions of the + * same (sub)segment. + * + * For segments based on ISO Base Media file format (i.e. based on movie sample tables or movie fragments): + * ! an access unit is a sample; + * ! a subsegment is a self-contained set of one or more consecutive movie fragments; a self-contained set contains + * one or more Movie Fragment Boxes with the corresponding Media Data Box(es), and a Media Data Box containing data + * referenced by a Movie Fragment Box must follow that Movie Fragment Box and precede the next Movie Fragment box + * containing information about the same track; + * ! Segment Index Boxes shall be placed before subsegment material they document, that is, before any Movie Fragment + * Box of the documented material of the subsegment; + * ! streams are tracks in the file format, and stream IDs are track IDs; + * ! a subsegment contains a stream access point if a track fragment within the subsegment for the track with track_ID + * equal to reference_ID contains a stream access point; + * ! initialisation data for SAPs consists of the Movie Box; + * ! presentation times are in the movie timeline, that is they are composition times after the application of any edit + * list for the track; + * ! the ISAP is a position exactly pointing to the start of a top-level box, such as a Movie Fragment Box; + * ! a SAP of type 1 or type 2 is indicated as a sync sample; + * ! a SAP of type 3 is marked as a member of a sample group of type 'rap '; + * ! a SAP of type 4 is marked as a member of a sample group of type 'roll' where the value of the roll_distance field + * is greater than 0. + * For SAPs of type 5 and 6, no specific signalling in the ISO Base Media file format is supported. */ +typedef struct +{ + unsigned int reference_type : 1; /* 1: the reference is to a Segment Index Box + * 0: the reference is to media content + * For files based on the ISO Base Media file format, the reference is to a + * Movie Fragment Box. + * If a separate index segment is used, then entries with reference type 1 are + * in the index segment, and entries with reference type 0 are in the media file. */ + unsigned int reference_size : 31; /* the distance in bytes from the first byte of the referenced item to the first + * byte of the next referenced item, or in the case of the last entry, the end of + * the referenced material */ + uint32_t subsegment_duration; /* when the reference is to Segment Index Box, i.e. reference_type is equal to 1: + * this field carries the sum of the subsegment_duration fields in that box; + * when the reference is to a subsegment: + * this field carries the difference between the earliest presentation time of + * any access unit of the reference stream in the next subsegment (or the first + * subsegment of the next segment, if this is the last subsegment of the segment, + * or the end presentation time of the reference stream if this is the last + * subsegment of the stream) and the earliest presentation time of any access + * unit of the reference stream in the referenced subsegment; + * The duration is expressed in the timescale of the enclosing Segment Index Box. */ + unsigned int starts_with_SAP : 1; /* whether the referenced subsegments start with a SAP */ + unsigned int SAP_type : 3; /* a SAP type or the value 0 + * When starting with a SAP, the value 0 means a SAP may be of an unknown type. + * Otherwise, the value 0 means no information of SAPs is provided. */ + unsigned int SAP_delta_time : 28; /* TSAP of the first SAP, in decoding order, in the referenced subsegment for + * the reference stream + * If the referenced subsegments do not contain a SAP, SAP_delta_time is + * reserved with the value 0, otherwise SAP_delta_time is the difference between + * the earliest presentation time of the subsegment, and the TSAP. + * Note that this difference may be zero, in the case that the subsegment starts + * with a SAP. */ +} isom_sidx_referenced_item_t; + +typedef struct +{ + ISOM_FULLBOX_COMMON; + uint32_t reference_ID; /* the stream ID for the reference stream + * If this Segment Index box is referenced from a "parent" Segment Index box, the value + * of the value of reference_ID shall be the same as the value of reference_ID of the + * "parent" Segment Index Box. */ + uint32_t timescale; /* the timescale, in ticks per second, for the time and duration fields within this box + * It is recommended that this match the timescale of the reference stream or track. + * For files based on the ISO Base Media file format, that is the timescale field of + * the Media Header Box of the track. */ + /* version == 0: 64bits -> 32bits */ + uint64_t earliest_presentation_time; /* the earliest presentation time of any access unit in the reference stream + * in the first subsegment, in the timescale indicated in the timescale field */ + uint64_t first_offset; /* the distance in bytes, in the file containing media, from the anchor point, + * to the first byte of the indexed material */ + /* */ + uint16_t reserved; /* 0 */ + uint16_t reference_count; /* the number of referenced items */ + lsmash_entry_list_t *list; /* entry_count corresponds to reference_count. */ +} isom_sidx_t; + +/** **/ + +/* File */ +struct lsmash_file_tag +{ + ISOM_FULLBOX_COMMON; /* The 'size' field indicates total file size. + * The 'flags' field indicates file mode. */ + isom_ftyp_t *ftyp; /* File Type Box */ + lsmash_entry_list_t styp_list; /* Segment Type Box List */ + isom_moov_t *moov; /* Movie Box */ + lsmash_entry_list_t sidx_list; /* Segment Index Box List */ + lsmash_entry_list_t moof_list; /* Movie Fragment Box List */ + isom_mdat_t *mdat; /* Media Data Box */ + isom_meta_t *meta; /* Meta Box */ + isom_mfra_t *mfra; /* Movie Fragment Random Access Box */ + + lsmash_bs_t *bs; /* bytestream manager */ + isom_fragment_manager_t *fragment; /* movie fragment manager */ + lsmash_entry_list_t *print; + lsmash_entry_list_t *timeline; + lsmash_file_t *initializer; /* A file containing the initialization information of whole movie including subsequent segments + * For ISOBMFF, an initializer corresponds to a file containing the 'moov' box. + * ROOT-to-initializer is designed to be a one-to-one relationship while initializer-to-file + * is designed to be a one-to-many relationship. */ + struct importer_tag *importer; /* An importer of this file + * Importer-to-file is designed to be a one-to-one relationship. */ + uint64_t fragment_count; /* the number of movie fragments we created */ + double max_chunk_duration; /* max duration per chunk in seconds */ + double max_async_tolerance; /* max tolerance, in seconds, for amount of interleaving asynchronization between tracks */ + uint64_t max_chunk_size; /* max size per chunk in bytes. */ + uint32_t brand_count; + uint32_t *compatible_brands; /* the backup of the compatible brands in the File Type Box or the valid Segment Type Box */ + uint8_t fake_file_mode; /* If set to 1, the bytestream manager handles fake-file stream. */ + /* flags for compatibility */ +#define COMPAT_FLAGS_OFFSET offsetof( lsmash_file_t, qt_compatible ) + uint8_t qt_compatible; /* compatibility with QuickTime file format */ + uint8_t isom_compatible; /* compatibility with ISO Base Media file format */ + uint8_t avc_extensions; /* compatibility with AVC extensions */ + uint8_t mp4_version1; /* compatibility with MP4 ver.1 file format */ + uint8_t mp4_version2; /* compatibility with MP4 ver.2 file format */ + uint8_t itunes_movie; /* compatibility with iTunes Movie */ + uint8_t max_3gpp_version; /* maximum 3GPP version */ + uint8_t max_isom_version; /* maximum ISO Base Media file format version */ + uint8_t min_isom_version; /* minimum ISO Base Media file format version */ + uint8_t forbid_tref; /* If set to 1, track reference is forbidden. */ + uint8_t undefined_64_ver; /* If set to 1, 64-bit version fields, e.g. duration, are undefined. */ + uint8_t allow_moof_base; /* If set to 1, default-base-is-moof is available for muxing. */ + uint8_t media_segment; /* If set to 1, this file is a media segment. */ +}; + +/* fake-file stream */ +typedef struct +{ + uint32_t size; + uint8_t *data; + uint32_t pos; +} fake_file_stream_t; + +/* ROOT */ +struct lsmash_root_tag +{ + ISOM_FULLBOX_COMMON; /* The 'file' field contains the address of the current active file. */ + lsmash_entry_list_t file_abstract_list; /* the list of all files the ROOT contains */ +}; + +/** **/ + +/* Pre-defined precedence */ +#define LSMASH_BOX_PRECEDENCE_ISOM_FTYP (LSMASH_BOX_PRECEDENCE_H - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_STYP (LSMASH_BOX_PRECEDENCE_H - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_SIDX (LSMASH_BOX_PRECEDENCE_N + 1 * LSMASH_BOX_PRECEDENCE_S) /* shall be placed before any 'moof' of the documented subsegments */ +#define LSMASH_BOX_PRECEDENCE_ISOM_MOOV (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_MVHD (LSMASH_BOX_PRECEDENCE_HM - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_IODS (LSMASH_BOX_PRECEDENCE_HM - 2 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_TRAK (LSMASH_BOX_PRECEDENCE_N - 2 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_TKHD (LSMASH_BOX_PRECEDENCE_HM - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_TAPT (LSMASH_BOX_PRECEDENCE_N - 1 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_CLEF (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_PROF (LSMASH_BOX_PRECEDENCE_N - 1 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_ENOF (LSMASH_BOX_PRECEDENCE_N - 2 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_EDTS (LSMASH_BOX_PRECEDENCE_N - 2 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_ELST (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_TREF (LSMASH_BOX_PRECEDENCE_N - 3 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_TREF_TYPE (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_MDIA (LSMASH_BOX_PRECEDENCE_N - 4 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_MDHD (LSMASH_BOX_PRECEDENCE_HM - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_HDLR (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_MINF (LSMASH_BOX_PRECEDENCE_N - 1 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_VMHD (LSMASH_BOX_PRECEDENCE_HM - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_SMHD (LSMASH_BOX_PRECEDENCE_HM - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_HMHD (LSMASH_BOX_PRECEDENCE_HM - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_NMHD (LSMASH_BOX_PRECEDENCE_HM - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_GMHD (LSMASH_BOX_PRECEDENCE_HM - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_GMIN (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_TEXT (LSMASH_BOX_PRECEDENCE_N - 1 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_DINF (LSMASH_BOX_PRECEDENCE_N - 1 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_DREF (LSMASH_BOX_PRECEDENCE_N - 1 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_DREF_ENTRY (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_STBL (LSMASH_BOX_PRECEDENCE_N - 2 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_STSD (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_GLBL (LSMASH_BOX_PRECEDENCE_HM - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_ESDS (LSMASH_BOX_PRECEDENCE_HM - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_ESDS (LSMASH_BOX_PRECEDENCE_HM - 1 * LSMASH_BOX_PRECEDENCE_S) /* preceded by 'frma' and 'mp4a' */ +#define LSMASH_BOX_PRECEDENCE_ISOM_BTRT (LSMASH_BOX_PRECEDENCE_HM - 1 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_TIMS (LSMASH_BOX_PRECEDENCE_HM - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_TSRO (LSMASH_BOX_PRECEDENCE_HM - 1 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_TSSY (LSMASH_BOX_PRECEDENCE_HM - 2 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_COLR (LSMASH_BOX_PRECEDENCE_LP + 2 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_COLR (LSMASH_BOX_PRECEDENCE_LP + 2 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_GAMA (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_FIEL (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_CLLI (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_MDCV (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_CSPC (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_SGBT (LSMASH_BOX_PRECEDENCE_HM - 0 * LSMASH_BOX_PRECEDENCE_S) /* 'v216' specific */ +#define LSMASH_BOX_PRECEDENCE_ISOM_CLAP (LSMASH_BOX_PRECEDENCE_LP + 1 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_PASP (LSMASH_BOX_PRECEDENCE_LP - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_STSL (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_CHAN (LSMASH_BOX_PRECEDENCE_LP - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_CHAN (LSMASH_BOX_PRECEDENCE_LP - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_WAVE (LSMASH_BOX_PRECEDENCE_HM - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_FRMA (LSMASH_BOX_PRECEDENCE_HM + 1 * LSMASH_BOX_PRECEDENCE_S) /* precede any as much as possible */ +#define LSMASH_BOX_PRECEDENCE_QTFF_ENDA (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_MP4A (LSMASH_BOX_PRECEDENCE_HM - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_TERMINATOR (LSMASH_BOX_PRECEDENCE_L - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_SRAT (LSMASH_BOX_PRECEDENCE_LP - 1 * LSMASH_BOX_PRECEDENCE_S) /* place at the end for maximum compatibility */ +#define LSMASH_BOX_PRECEDENCE_ISOM_FTAB (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_STTS (LSMASH_BOX_PRECEDENCE_N - 2 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_CTTS (LSMASH_BOX_PRECEDENCE_N - 4 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_CSLG (LSMASH_BOX_PRECEDENCE_N - 6 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_STSS (LSMASH_BOX_PRECEDENCE_N - 8 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_STPS (LSMASH_BOX_PRECEDENCE_N - 10 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_SDTP (LSMASH_BOX_PRECEDENCE_N - 12 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_STSC (LSMASH_BOX_PRECEDENCE_N - 14 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_STSZ (LSMASH_BOX_PRECEDENCE_N - 16 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_STZ2 (LSMASH_BOX_PRECEDENCE_N - 16 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_STCO (LSMASH_BOX_PRECEDENCE_N - 18 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_CO64 (LSMASH_BOX_PRECEDENCE_N - 18 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_SGPD (LSMASH_BOX_PRECEDENCE_N - 20 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_SBGP (LSMASH_BOX_PRECEDENCE_N - 22 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_UDTA (LSMASH_BOX_PRECEDENCE_N - 5 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_MEAN (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_NAME (LSMASH_BOX_PRECEDENCE_N - 1 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_DATA (LSMASH_BOX_PRECEDENCE_N - 2 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_KEYS (LSMASH_BOX_PRECEDENCE_N - 1 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_ILST (LSMASH_BOX_PRECEDENCE_N - 2 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_METAITEM (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_CHPL (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_META (LSMASH_BOX_PRECEDENCE_N - 7 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_HNTI (LSMASH_BOX_PRECEDENCE_N - 8 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_RTP (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_SDP (LSMASH_BOX_PRECEDENCE_N - 1 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_WLOC (LSMASH_BOX_PRECEDENCE_N - 8 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_LOOP (LSMASH_BOX_PRECEDENCE_N - 9 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_SELO (LSMASH_BOX_PRECEDENCE_N - 10 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_ALLF (LSMASH_BOX_PRECEDENCE_N - 11 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_CPRT (LSMASH_BOX_PRECEDENCE_N - 12 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_QTFF_CTAB (LSMASH_BOX_PRECEDENCE_N - 6 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_MVEX (LSMASH_BOX_PRECEDENCE_N - 8 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_MEHD (LSMASH_BOX_PRECEDENCE_HM - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_TREX (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_MOOF (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_MFHD (LSMASH_BOX_PRECEDENCE_HM - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_TRAF (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_TFHD (LSMASH_BOX_PRECEDENCE_HM - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_TFDT (LSMASH_BOX_PRECEDENCE_HM - 1 * LSMASH_BOX_PRECEDENCE_S) /* shall be positioned after 'tfhd' and before 'trun' */ +#define LSMASH_BOX_PRECEDENCE_ISOM_TRUN (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_MFRA (LSMASH_BOX_PRECEDENCE_L - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_TFRA (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_MFRO (LSMASH_BOX_PRECEDENCE_L - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_MDAT (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_FREE (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) +#define LSMASH_BOX_PRECEDENCE_ISOM_SKIP (LSMASH_BOX_PRECEDENCE_N - 0 * LSMASH_BOX_PRECEDENCE_S) + +/* Track reference types */ +typedef enum +{ + ISOM_TREF_TYPE_AVCP = LSMASH_4CC( 'a', 'v', 'c', 'p' ), /* AVC parameter set stream link */ + ISOM_TREF_TYPE_CDSC = LSMASH_4CC( 'c', 'd', 's', 'c' ), /* This track describes the referenced track. */ + ISOM_TREF_TYPE_DPND = LSMASH_4CC( 'd', 'p', 'n', 'd' ), /* This track has an MPEG-4 dependency on the referenced track. */ + ISOM_TREF_TYPE_HIND = LSMASH_4CC( 'h', 'i', 'n', 'd' ), /* Hint dependency */ + ISOM_TREF_TYPE_HINT = LSMASH_4CC( 'h', 'i', 'n', 't' ), /* Links hint track to original media track */ + ISOM_TREF_TYPE_IPIR = LSMASH_4CC( 'i', 'p', 'i', 'r' ), /* This track contains IPI declarations for the referenced track. */ + ISOM_TREF_TYPE_MPOD = LSMASH_4CC( 'm', 'p', 'o', 'd' ), /* This track is an OD track which uses the referenced track as an included elementary stream track. */ + ISOM_TREF_TYPE_SBAS = LSMASH_4CC( 's', 'b', 'a', 's' ), /* Scalable base */ + ISOM_TREF_TYPE_SCAL = LSMASH_4CC( 's', 'c', 'a', 'l' ), /* Scalable extraction */ + ISOM_TREF_TYPE_SWFR = LSMASH_4CC( 's', 'w', 'f', 'r' ), /* AVC Switch from */ + ISOM_TREF_TYPE_SWTO = LSMASH_4CC( 's', 'w', 't', 'o' ), /* AVC Switch to */ + ISOM_TREF_TYPE_SYNC = LSMASH_4CC( 's', 'y', 'n', 'c' ), /* This track uses the referenced track as its synchronization source. */ + ISOM_TREF_TYPE_VDEP = LSMASH_4CC( 'v', 'd', 'e', 'p' ), /* Auxiliary video depth */ + ISOM_TREF_TYPE_VPLX = LSMASH_4CC( 'v', 'p', 'l', 'x' ), /* Auxiliary video parallax */ + + QT_TREF_TYPE_CHAP = LSMASH_4CC( 'c', 'h', 'a', 'p' ), /* Chapter or scene list. Usually references a text track. */ + QT_TREF_TYPE_SCPT = LSMASH_4CC( 's', 'c', 'p', 't' ), /* Transcript. Usually references a text track. */ + QT_TREF_TYPE_SSRC = LSMASH_4CC( 's', 's', 'r', 'c' ), /* Nonprimary source. Indicates that the referenced track should send its data to this track, rather than presenting it. */ + QT_TREF_TYPE_TMCD = LSMASH_4CC( 't', 'm', 'c', 'd' ), /* Time code. Usually references a time code track. */ +} isom_track_reference_type; + +/* Handler types */ +enum isom_handler_type +{ + QT_HANDLER_TYPE_DATA = LSMASH_4CC( 'd', 'h', 'l', 'r' ), + QT_HANDLER_TYPE_MEDIA = LSMASH_4CC( 'm', 'h', 'l', 'r' ), +}; + +enum isom_meta_type +{ + ISOM_META_HANDLER_TYPE_ITUNES_METADATA = LSMASH_4CC( 'm', 'd', 'i', 'r' ), +}; + +/* Data reference types */ +enum isom_data_reference_type +{ + ISOM_REFERENCE_HANDLER_TYPE_URL = LSMASH_4CC( 'u', 'r', 'l', ' ' ), + ISOM_REFERENCE_HANDLER_TYPE_URN = LSMASH_4CC( 'u', 'r', 'n', ' ' ), + + QT_REFERENCE_HANDLER_TYPE_ALIAS = LSMASH_4CC( 'a', 'l', 'i', 's' ), + QT_REFERENCE_HANDLER_TYPE_RESOURCE = LSMASH_4CC( 'r', 's', 'r', 'c' ), + QT_REFERENCE_HANDLER_TYPE_URL = LSMASH_4CC( 'u', 'r', 'l', ' ' ), +}; + +/* Lanuage codes */ +typedef struct +{ + uint16_t mac_value; + uint16_t iso_name; +} isom_language_t; + +static const isom_language_t isom_languages[] = +{ + { 0, ISOM_LANGUAGE_CODE_ENGLISH }, + { 1, ISOM_LANGUAGE_CODE_FRENCH }, + { 2, ISOM_LANGUAGE_CODE_GERMAN }, + { 3, ISOM_LANGUAGE_CODE_ITALIAN }, + { 4, ISOM_LANGUAGE_CODE_DUTCH_M }, + { 5, ISOM_LANGUAGE_CODE_SWEDISH }, + { 6, ISOM_LANGUAGE_CODE_SPANISH }, + { 7, ISOM_LANGUAGE_CODE_DANISH }, + { 8, ISOM_LANGUAGE_CODE_PORTUGUESE }, + { 9, ISOM_LANGUAGE_CODE_NORWEGIAN }, + { 10, ISOM_LANGUAGE_CODE_HEBREW }, + { 11, ISOM_LANGUAGE_CODE_JAPANESE }, + { 12, ISOM_LANGUAGE_CODE_ARABIC }, + { 13, ISOM_LANGUAGE_CODE_FINNISH }, + { 14, ISOM_LANGUAGE_CODE_GREEK }, + { 15, ISOM_LANGUAGE_CODE_ICELANDIC }, + { 16, ISOM_LANGUAGE_CODE_MALTESE }, + { 17, ISOM_LANGUAGE_CODE_TURKISH }, + { 18, ISOM_LANGUAGE_CODE_CROATIAN }, + { 19, ISOM_LANGUAGE_CODE_CHINESE }, + { 20, ISOM_LANGUAGE_CODE_URDU }, + { 21, ISOM_LANGUAGE_CODE_HINDI }, + { 22, ISOM_LANGUAGE_CODE_THAI }, + { 23, ISOM_LANGUAGE_CODE_KOREAN }, + { 24, ISOM_LANGUAGE_CODE_LITHUANIAN }, + { 25, ISOM_LANGUAGE_CODE_POLISH }, + { 26, ISOM_LANGUAGE_CODE_HUNGARIAN }, + { 27, ISOM_LANGUAGE_CODE_ESTONIAN }, + { 28, ISOM_LANGUAGE_CODE_LATVIAN }, + { 29, ISOM_LANGUAGE_CODE_SAMI }, + { 30, ISOM_LANGUAGE_CODE_FAROESE }, + { 32, ISOM_LANGUAGE_CODE_RUSSIAN }, + { 33, ISOM_LANGUAGE_CODE_CHINESE }, + { 34, ISOM_LANGUAGE_CODE_DUTCH }, + { 35, ISOM_LANGUAGE_CODE_IRISH }, + { 36, ISOM_LANGUAGE_CODE_ALBANIAN }, + { 37, ISOM_LANGUAGE_CODE_ROMANIAN }, + { 38, ISOM_LANGUAGE_CODE_CZECH }, + { 39, ISOM_LANGUAGE_CODE_SLOVAK }, + { 40, ISOM_LANGUAGE_CODE_SLOVENIA }, + { 41, ISOM_LANGUAGE_CODE_YIDDISH }, + { 42, ISOM_LANGUAGE_CODE_SERBIAN }, + { 43, ISOM_LANGUAGE_CODE_MACEDONIAN }, + { 44, ISOM_LANGUAGE_CODE_BULGARIAN }, + { 45, ISOM_LANGUAGE_CODE_UKRAINIAN }, + { 46, ISOM_LANGUAGE_CODE_BELARUSIAN }, + { 47, ISOM_LANGUAGE_CODE_UZBEK }, + { 48, ISOM_LANGUAGE_CODE_KAZAKH }, + { 49, ISOM_LANGUAGE_CODE_AZERBAIJANI }, + { 51, ISOM_LANGUAGE_CODE_ARMENIAN }, + { 52, ISOM_LANGUAGE_CODE_GEORGIAN }, + { 53, ISOM_LANGUAGE_CODE_MOLDAVIAN }, + { 54, ISOM_LANGUAGE_CODE_KIRGHIZ }, + { 55, ISOM_LANGUAGE_CODE_TAJIK }, + { 56, ISOM_LANGUAGE_CODE_TURKMEN }, + { 57, ISOM_LANGUAGE_CODE_MONGOLIAN }, + { 59, ISOM_LANGUAGE_CODE_PASHTO }, + { 60, ISOM_LANGUAGE_CODE_KURDISH }, + { 61, ISOM_LANGUAGE_CODE_KASHMIRI }, + { 62, ISOM_LANGUAGE_CODE_SINDHI }, + { 63, ISOM_LANGUAGE_CODE_TIBETAN }, + { 64, ISOM_LANGUAGE_CODE_NEPALI }, + { 65, ISOM_LANGUAGE_CODE_SANSKRIT }, + { 66, ISOM_LANGUAGE_CODE_MARATHI }, + { 67, ISOM_LANGUAGE_CODE_BENGALI }, + { 68, ISOM_LANGUAGE_CODE_ASSAMESE }, + { 69, ISOM_LANGUAGE_CODE_GUJARATI }, + { 70, ISOM_LANGUAGE_CODE_PUNJABI }, + { 71, ISOM_LANGUAGE_CODE_ORIYA }, + { 72, ISOM_LANGUAGE_CODE_MALAYALAM }, + { 73, ISOM_LANGUAGE_CODE_KANNADA }, + { 74, ISOM_LANGUAGE_CODE_TAMIL }, + { 75, ISOM_LANGUAGE_CODE_TELUGU }, + { 76, ISOM_LANGUAGE_CODE_SINHALESE }, + { 77, ISOM_LANGUAGE_CODE_BURMESE }, + { 78, ISOM_LANGUAGE_CODE_KHMER }, + { 79, ISOM_LANGUAGE_CODE_LAO }, + { 80, ISOM_LANGUAGE_CODE_VIETNAMESE }, + { 81, ISOM_LANGUAGE_CODE_INDONESIAN }, + { 82, ISOM_LANGUAGE_CODE_TAGALOG }, + { 83, ISOM_LANGUAGE_CODE_MALAY_ROMAN }, + { 84, ISOM_LANGUAGE_CODE_MAYAY_ARABIC }, + { 85, ISOM_LANGUAGE_CODE_AMHARIC }, + { 87, ISOM_LANGUAGE_CODE_OROMO }, + { 88, ISOM_LANGUAGE_CODE_SOMALI }, + { 89, ISOM_LANGUAGE_CODE_SWAHILI }, + { 90, ISOM_LANGUAGE_CODE_KINYARWANDA }, + { 91, ISOM_LANGUAGE_CODE_RUNDI }, + { 92, ISOM_LANGUAGE_CODE_CHEWA }, + { 93, ISOM_LANGUAGE_CODE_MALAGASY }, + { 94, ISOM_LANGUAGE_CODE_ESPERANTO }, + { 128, ISOM_LANGUAGE_CODE_WELSH }, + { 129, ISOM_LANGUAGE_CODE_BASQUE }, + { 130, ISOM_LANGUAGE_CODE_CATALAN }, + { 131, ISOM_LANGUAGE_CODE_LATIN }, + { 132, ISOM_LANGUAGE_CODE_QUECHUA }, + { 133, ISOM_LANGUAGE_CODE_GUARANI }, + { 134, ISOM_LANGUAGE_CODE_AYMARA }, + { 135, ISOM_LANGUAGE_CODE_TATAR }, + { 136, ISOM_LANGUAGE_CODE_UIGHUR }, + { 137, ISOM_LANGUAGE_CODE_DZONGKHA }, + { 138, ISOM_LANGUAGE_CODE_JAVANESE }, + { UINT16_MAX, 0 } +}; + +/* Color parameters */ +enum isom_color_patameter_type +{ + ISOM_COLOR_PARAMETER_TYPE_NCLX = LSMASH_4CC( 'n', 'c', 'l', 'x' ), /* on-screen colours */ + ISOM_COLOR_PARAMETER_TYPE_RICC = LSMASH_4CC( 'r', 'I', 'C', 'C' ), /* restricted ICC profile */ + ISOM_COLOR_PARAMETER_TYPE_PROF = LSMASH_4CC( 'p', 'r', 'o', 'f' ), /* unrestricted ICC profile */ + + QT_COLOR_PARAMETER_TYPE_NCLC = LSMASH_4CC( 'n', 'c', 'l', 'c' ), /* NonConstant Luminance Coding */ + QT_COLOR_PARAMETER_TYPE_PROF = LSMASH_4CC( 'p', 'r', 'o', 'f' ), /* ICC profile */ +}; + +/* Sample grouping types */ +typedef enum +{ + ISOM_GROUP_TYPE_3GAG = LSMASH_4CC( '3', 'g', 'a', 'g' ), /* Text track3GPP PSS Annex G video buffer parameters */ + ISOM_GROUP_TYPE_ALST = LSMASH_4CC( 'a', 'l', 's', 't' ), /* Alternative startup sequence */ + ISOM_GROUP_TYPE_AVCB = LSMASH_4CC( 'a', 'v', 'c', 'b' ), /* AVC HRD parameters */ + ISOM_GROUP_TYPE_AVLL = LSMASH_4CC( 'a', 'v', 'l', 'l' ), /* AVC Layer */ + ISOM_GROUP_TYPE_AVSS = LSMASH_4CC( 'a', 'v', 's', 's' ), /* AVC Sub Sequence */ + ISOM_GROUP_TYPE_DTRT = LSMASH_4CC( 'd', 't', 'r', 't' ), /* Decode re-timing */ + ISOM_GROUP_TYPE_MVIF = LSMASH_4CC( 'm', 'v', 'i', 'f' ), /* MVC Scalability Information */ + ISOM_GROUP_TYPE_PROL = LSMASH_4CC( 'p', 'r', 'o', 'l' ), /* Pre-roll */ + ISOM_GROUP_TYPE_RAP = LSMASH_4CC( 'r', 'a', 'p', ' ' ), /* Random Access Point */ + ISOM_GROUP_TYPE_RASH = LSMASH_4CC( 'r', 'a', 's', 'h' ), /* Rate Share */ + ISOM_GROUP_TYPE_ROLL = LSMASH_4CC( 'r', 'o', 'l', 'l' ), /* Pre-roll/Post-roll */ + ISOM_GROUP_TYPE_SCIF = LSMASH_4CC( 's', 'c', 'i', 'f' ), /* SVC Scalability Information */ + ISOM_GROUP_TYPE_SCNM = LSMASH_4CC( 's', 'c', 'n', 'm' ), /* AVC/SVC/MVC map groups */ + ISOM_GROUP_TYPE_VIPR = LSMASH_4CC( 'v', 'i', 'p', 'r' ), /* View priority */ +} isom_grouping_type; + +/* wrapper to avoid boring cast */ +#define isom_init_box_common( box, parent, box_type, precedence, destructor ) \ + isom_init_box_common_orig( box, parent, box_type, precedence, (isom_extension_destructor_t)(destructor) ) + +void isom_init_box_common_orig +( + void *box, + void *parent, + lsmash_box_type_t box_type, + uint64_t precedence, + isom_extension_destructor_t destructor +); + +int isom_is_fullbox( const void *box ); +int isom_is_lpcm_audio( const void *box ); +int isom_is_qt_audio( lsmash_codec_type_t type ); +int isom_is_uncompressed_ycbcr( lsmash_codec_type_t type ); +int isom_is_waveform_audio( lsmash_box_type_t type ); + +size_t isom_skip_box_common +( + uint8_t **p_data +); + +uint8_t *isom_get_child_box_position +( + uint8_t *parent_data, + uint32_t parent_size, + lsmash_box_type_t child_type, + uint32_t *child_size +); + +void isom_bs_put_basebox_common( lsmash_bs_t *bs, isom_box_t *box ); +void isom_bs_put_fullbox_common( lsmash_bs_t *bs, isom_box_t *box ); +void isom_bs_put_box_common( lsmash_bs_t *bs, void *box ); + +#define isom_is_printable_char( c ) ((c) >= 32 && (c) < 128) +#define isom_is_printable_4cc( fourcc ) \ + (isom_is_printable_char( ((fourcc) >> 24) & 0xff ) \ + && isom_is_printable_char( ((fourcc) >> 16) & 0xff ) \ + && isom_is_printable_char( ((fourcc) >> 8) & 0xff ) \ + && isom_is_printable_char( (fourcc) & 0xff )) + +#define isom_4cc2str( fourcc ) (const char [5]){ (fourcc) >> 24, (fourcc) >> 16, (fourcc) >> 8, (fourcc), 0 } + +int isom_check_initializer_present( lsmash_root_t *root ); + +isom_trak_t *isom_get_trak( lsmash_file_t *file, uint32_t track_ID ); +isom_trex_t *isom_get_trex( isom_mvex_t *mvex, uint32_t track_ID ); +isom_traf_t *isom_get_traf( isom_moof_t *moof, uint32_t track_ID ); +isom_tfra_t *isom_get_tfra( isom_mfra_t *mfra, uint32_t track_ID ); +isom_sgpd_t *isom_get_sample_group_description( isom_stbl_t *stbl, uint32_t grouping_type ); +isom_sbgp_t *isom_get_sample_to_group( isom_stbl_t *stbl, uint32_t grouping_type ); +isom_sgpd_t *isom_get_roll_recovery_sample_group_description( lsmash_entry_list_t *list ); +isom_sbgp_t *isom_get_roll_recovery_sample_to_group( lsmash_entry_list_t *list ); +isom_sgpd_t *isom_get_fragment_sample_group_description( isom_traf_t *traf, uint32_t grouping_type ); +isom_sbgp_t *isom_get_fragment_sample_to_group( isom_traf_t *traf, uint32_t grouping_type ); + +isom_trak_t *isom_track_create( lsmash_file_t *file, lsmash_media_type media_type ); +isom_moov_t *isom_movie_create( lsmash_file_t *file ); + +int isom_setup_handler_reference( isom_hdlr_t *hdlr, uint32_t media_type ); +int isom_setup_iods( isom_moov_t *moov ); + +uint32_t isom_get_sample_count +( + isom_trak_t *trak +); + +isom_sample_pool_t *isom_create_sample_pool +( + uint64_t size +); + +int isom_update_sample_tables +( + isom_trak_t *trak, + lsmash_sample_t *sample, + uint32_t *samples_per_packet, + isom_sample_entry_t *sample_entry +); + +int isom_pool_sample +( + isom_sample_pool_t *pool, + lsmash_sample_t *sample, + uint32_t samples_per_packet +); + +int isom_append_sample_by_type +( + void *track, + lsmash_sample_t *sample, + isom_sample_entry_t *sample_entry, + int (*func_append_sample)( void *, lsmash_sample_t *, isom_sample_entry_t * ) +); + +int isom_calculate_bitrate_description +( + isom_stbl_t *stbl, + isom_mdhd_t *mdhd, + uint32_t *bufferSizeDB, + uint32_t *maxBitrate, + uint32_t *avgBitrate, + uint32_t sample_description_index +); + +int isom_is_variable_size +( + isom_stbl_t *stbl +); + +uint32_t isom_get_first_sample_size +( + isom_stbl_t *stbl +); + + +void isom_update_cache_timestamp +( + isom_cache_t *cache, + uint64_t dts, + uint64_t cts, + int32_t ctd_shift, + uint32_t sample_duration, + int non_output_sample +); + +/* Make CTS from DTS and sample_offset. + * This function does NOT add the value of composition to decode timeline shift to the result. */ +static inline uint64_t isom_make_cts +( + uint64_t dts, + uint32_t sample_offset, + int32_t ctd_shift +) +{ + if( sample_offset != ISOM_NON_OUTPUT_SAMPLE_OFFSET ) + return ctd_shift ? (dts + (int32_t)sample_offset) : (dts + sample_offset); + else + return LSMASH_TIMESTAMP_UNDEFINED; +} + +/* Make CTS from DTS and sample_offset. + * This function adds the value of composition to decode timeline shift to the result. */ +static inline uint64_t isom_make_cts_adjust +( + uint64_t dts, + uint32_t sample_offset, + int32_t ctd_shift +) +{ + if( sample_offset != ISOM_NON_OUTPUT_SAMPLE_OFFSET ) + return ctd_shift ? (dts + (int32_t)sample_offset + ctd_shift) : (dts + sample_offset); + else + return LSMASH_TIMESTAMP_UNDEFINED; +} + +/* Utilities for sample entry type decision + * NOTE: This implementation does not work when 'mdia' and/or 'hdlr' is stored as binary string. */ +static inline int isom_check_media_hdlr_from_stsd( isom_stsd_t *stsd ) +{ + return ((isom_stbl_t *)stsd->parent + && (isom_minf_t *)stsd->parent->parent + && (isom_mdia_t *)stsd->parent->parent->parent + && ((isom_mdia_t *)stsd->parent->parent->parent)->hdlr); +} +static inline lsmash_media_type isom_get_media_type_from_stsd( isom_stsd_t *stsd ) +{ + assert( isom_check_media_hdlr_from_stsd( stsd ) ); + return ((isom_mdia_t *)stsd->parent->parent->parent)->hdlr->componentSubtype; +} + +int isom_add_sample_grouping( isom_box_t *parent, isom_grouping_type grouping_type ); +int isom_group_random_access( isom_box_t *parent, isom_cache_t *cache, lsmash_sample_t *sample ); +int isom_group_roll_recovery( isom_box_t *parent, isom_cache_t *cache, lsmash_sample_t *sample ); + +int isom_update_tkhd_duration( isom_trak_t *trak ); +int isom_update_bitrate_description( isom_mdia_t *mdia ); +int isom_complement_data_reference( isom_minf_t *minf ); +int isom_check_large_offset_requirement( isom_moov_t *moov, uint64_t meta_size ); +void isom_add_preceding_box_size( isom_moov_t *moov, uint64_t preceding_size ); +int isom_establish_movie( lsmash_file_t *file ); +int isom_rap_grouping_established( isom_rap_group_t *group, int num_leading_samples_known, isom_sgpd_t *sgpd, int is_fragment ); +int isom_all_recovery_completed( isom_sbgp_t *sbgp, lsmash_entry_list_t *pool ); + +lsmash_file_t *isom_add_file_abstract( lsmash_root_t *root ); +isom_ftyp_t *isom_add_ftyp( lsmash_file_t *file ); +isom_moov_t *isom_add_moov( lsmash_file_t *file ); +isom_mvhd_t *isom_add_mvhd( isom_moov_t *moov ); +isom_iods_t *isom_add_iods( isom_moov_t *moov ); +isom_ctab_t *isom_add_ctab( void *parent_box ); +isom_trak_t *isom_add_trak( isom_moov_t *moov ); +isom_tkhd_t *isom_add_tkhd( isom_trak_t *trak ); +isom_tapt_t *isom_add_tapt( isom_trak_t *trak ); +isom_clef_t *isom_add_clef( isom_tapt_t *tapt ); +isom_prof_t *isom_add_prof( isom_tapt_t *tapt ); +isom_enof_t *isom_add_enof( isom_tapt_t *tapt ); +isom_edts_t *isom_add_edts( isom_trak_t *trak ); +isom_elst_t *isom_add_elst( isom_edts_t *edts ); +isom_tref_t *isom_add_tref( isom_trak_t *trak ); +isom_tref_type_t *isom_add_track_reference_type( isom_tref_t *tref, isom_track_reference_type type ); +isom_mdia_t *isom_add_mdia( isom_trak_t *trak ); +isom_mdhd_t *isom_add_mdhd( isom_mdia_t *mdia ); +isom_hdlr_t *isom_add_hdlr( void *parent_box ); +isom_minf_t *isom_add_minf( isom_mdia_t *mdia ); +isom_vmhd_t *isom_add_vmhd( isom_minf_t *minf ); +isom_smhd_t *isom_add_smhd( isom_minf_t *minf ); +isom_hmhd_t *isom_add_hmhd( isom_minf_t *minf ); +isom_nmhd_t *isom_add_nmhd( isom_minf_t *minf ); +isom_gmhd_t *isom_add_gmhd( isom_minf_t *minf ); +isom_gmin_t *isom_add_gmin( isom_gmhd_t *gmhd ); +isom_text_t *isom_add_text( isom_gmhd_t *gmhd ); +isom_dinf_t *isom_add_dinf( void *parent_box ); +isom_dref_t *isom_add_dref( isom_dinf_t *dinf ); +isom_dref_entry_t *isom_add_dref_entry( isom_dref_t *dref, lsmash_box_type_t type ); +isom_stbl_t *isom_add_stbl( isom_minf_t *minf ); +isom_stsd_t *isom_add_stsd( isom_stbl_t *stbl ); +isom_visual_entry_t *isom_add_visual_description( isom_stsd_t *stsd, lsmash_codec_type_t sample_type ); +isom_audio_entry_t *isom_add_audio_description( isom_stsd_t *stsd, lsmash_codec_type_t sample_type ); +isom_hint_entry_t *isom_add_hint_description(isom_stsd_t *stsd, lsmash_codec_type_t sample_type); +isom_qt_text_entry_t *isom_add_qt_text_description( isom_stsd_t *stsd ); +isom_tx3g_entry_t *isom_add_tx3g_description( isom_stsd_t *stsd ); +isom_esds_t *isom_add_esds( void *parent_box ); +isom_glbl_t *isom_add_glbl( void *parent_box ); +isom_clap_t *isom_add_clap( isom_visual_entry_t *visual ); +isom_pasp_t *isom_add_pasp( isom_visual_entry_t *visual ); +isom_colr_t *isom_add_colr( isom_visual_entry_t *visual ); +isom_gama_t *isom_add_gama( isom_visual_entry_t *visual ); +isom_fiel_t *isom_add_fiel( isom_visual_entry_t *visual ); +isom_clli_t *isom_add_clli( isom_visual_entry_t *visual ); +isom_mdcv_t *isom_add_mdcv( isom_visual_entry_t *visual ); +isom_cspc_t *isom_add_cspc( isom_visual_entry_t *visual ); +isom_sgbt_t *isom_add_sgbt( isom_visual_entry_t *visual ); +isom_stsl_t *isom_add_stsl( isom_visual_entry_t *visual ); +isom_btrt_t *isom_add_btrt( isom_visual_entry_t *visual ); +isom_tims_t *isom_add_tims( isom_hint_entry_t *hint ); +isom_tsro_t *isom_add_tsro( isom_hint_entry_t *hint ); +isom_tssy_t *isom_add_tssy( isom_hint_entry_t *hint ); +isom_wave_t *isom_add_wave( isom_audio_entry_t *audio ); +isom_frma_t *isom_add_frma( isom_wave_t *wave ); +isom_enda_t *isom_add_enda( isom_wave_t *wave ); +isom_mp4a_t *isom_add_mp4a( isom_wave_t *wave ); +isom_terminator_t *isom_add_terminator( isom_wave_t *wave ); +isom_chan_t *isom_add_chan( isom_audio_entry_t *audio ); +isom_srat_t *isom_add_srat( isom_audio_entry_t *audio ); +isom_ftab_t *isom_add_ftab( isom_tx3g_entry_t *tx3g ); +isom_stts_t *isom_add_stts( isom_stbl_t *stbl ); +isom_ctts_t *isom_add_ctts( isom_stbl_t *stbl ); +isom_cslg_t *isom_add_cslg( isom_stbl_t *stbl ); +isom_stsc_t *isom_add_stsc( isom_stbl_t *stbl ); +isom_stsz_t *isom_add_stsz( isom_stbl_t *stbl ); +isom_stz2_t *isom_add_stz2( isom_stbl_t *stbl ); +isom_stss_t *isom_add_stss( isom_stbl_t *stbl ); +isom_stps_t *isom_add_stps( isom_stbl_t *stbl ); +isom_sdtp_t *isom_add_sdtp( isom_box_t *parent ); +isom_sgpd_t *isom_add_sgpd( void *parent_box ); +isom_sbgp_t *isom_add_sbgp( void *parent_box ); +isom_stco_t *isom_add_stco( isom_stbl_t *stbl ); +isom_stco_t *isom_add_co64( isom_stbl_t *stbl ); +isom_udta_t *isom_add_udta( void *parent_box ); +isom_cprt_t *isom_add_cprt( isom_udta_t *udta ); +isom_hnti_t *isom_add_hnti( isom_udta_t *udta ); +isom_rtp_t *isom_add_rtp( isom_hnti_t *hnti ); +isom_sdp_t *isom_add_sdp( isom_hnti_t *hnti ); +isom_WLOC_t *isom_add_WLOC( isom_udta_t *udta ); +isom_LOOP_t *isom_add_LOOP( isom_udta_t *udta ); +isom_SelO_t *isom_add_SelO( isom_udta_t *udta ); +isom_AllF_t *isom_add_AllF( isom_udta_t *udta ); +isom_chpl_t *isom_add_chpl( isom_udta_t *udta ); +isom_meta_t *isom_add_meta( void *parent_box ); +isom_keys_t *isom_add_keys( isom_meta_t *meta ); +isom_ilst_t *isom_add_ilst( isom_meta_t *meta ); +isom_metaitem_t *isom_add_metaitem( isom_ilst_t *ilst, lsmash_itunes_metadata_item item ); +isom_mean_t *isom_add_mean( isom_metaitem_t *metaitem ); +isom_name_t *isom_add_name( isom_metaitem_t *metaitem ); +isom_data_t *isom_add_data( isom_metaitem_t *metaitem ); +isom_mvex_t *isom_add_mvex( isom_moov_t *moov ); +isom_mehd_t *isom_add_mehd( isom_mvex_t *mvex ); +isom_trex_t *isom_add_trex( isom_mvex_t *mvex ); +isom_moof_t *isom_add_moof( lsmash_file_t *file ); +isom_mfhd_t *isom_add_mfhd( isom_moof_t *moof ); +isom_traf_t *isom_add_traf( isom_moof_t *moof ); +isom_tfhd_t *isom_add_tfhd( isom_traf_t *traf ); +isom_tfdt_t *isom_add_tfdt( isom_traf_t *traf ); +isom_trun_t *isom_add_trun( isom_traf_t *traf ); +isom_mfra_t *isom_add_mfra( lsmash_file_t *file ); +isom_tfra_t *isom_add_tfra( isom_mfra_t *mfra ); +isom_mfro_t *isom_add_mfro( isom_mfra_t *mfra ); +isom_mdat_t *isom_add_mdat( lsmash_file_t *file ); +isom_free_t *isom_add_free( void *parent_box ); +isom_styp_t *isom_add_styp( lsmash_file_t *file ); +isom_sidx_t *isom_add_sidx( lsmash_file_t *file ); + +void isom_remove_extension_box( isom_box_t *ext ); +void isom_remove_sample_description( isom_sample_entry_t *sample ); +void isom_remove_unknown_box( isom_unknown_box_t *unknown_box ); +void isom_remove_sample_pool( isom_sample_pool_t *pool ); + +uint64_t isom_update_box_size( void *box ); + +int isom_add_extension_binary( void *parent_box, lsmash_box_type_t box_type, uint64_t precedence, uint8_t *box_data, uint32_t box_size ); +void isom_remove_all_extension_boxes( lsmash_entry_list_t *extensions ); +isom_box_t *isom_get_extension_box( lsmash_entry_list_t *extensions, lsmash_box_type_t box_type ); +void *isom_get_extension_box_format( lsmash_entry_list_t *extensions, lsmash_box_type_t box_type ); +void isom_remove_box_by_itself( void *opaque_box ); + +#endif diff --git a/3rdparty/l-smash/core/box_default.c b/3rdparty/l-smash/core/box_default.c new file mode 100644 index 000000000..0cbf4c380 --- /dev/null +++ b/3rdparty/l-smash/core/box_default.c @@ -0,0 +1,276 @@ +/***************************************************************************** + * box_default.c + ***************************************************************************** + * Copyright (C) 2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include "box.h" +#include "box_default.h" + +#define INITIALIZE_BASEBOX_COMMON( box_name ) \ + .class = &isom_box_default_class, \ + .root = (isom_root_abstract_t *)&isom_root_abstract_box_default, \ + .file = (isom_file_abstract_t *)&isom_file_abstract_box_default, \ + .parent = (isom_box_t *)&isom_opaque_box_default, \ + .nonexist_ptr = (void *)&isom_##box_name##_box_default, \ + .binary = NULL, \ + .destruct = NULL, \ + .write = NULL, \ + .manager = LSMASH_NON_EXISTING_BOX, \ + .precedence = 0, \ + .pos = 0, \ + .extensions = { .head = NULL }, \ + .size = 0, \ + .type = LSMASH_BOX_TYPE_INITIALIZER + +#define DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( box_name ) \ + const isom_##box_name##_t isom_##box_name##_box_default = \ + { \ + INITIALIZE_BASEBOX_COMMON( box_name ) \ + } + +static const lsmash_class_t isom_box_default_class = +{ + "box_default" +}; + +typedef isom_box_t isom_opaque_t; +static const isom_box_t isom_opaque_box_default = +{ + INITIALIZE_BASEBOX_COMMON( opaque ) +}; + +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( dummy ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( unknown ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( ftyp ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( ctab ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( tkhd ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( clef ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( prof ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( enof ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( elst ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( tref_type ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( tref ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( mdhd ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( hdlr ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( vmhd ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( smhd ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( hmhd ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( nmhd ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( gmin ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( text ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( dref_entry ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( dref ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( esds ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( btrt ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( glbl ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( clap ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( pasp ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( colr ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( gama ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( fiel ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( cspc ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( clli ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( mdcv ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( sgbt ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( stsl ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( sample_entry ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( mp4s_entry ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( visual_entry ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( frma ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( enda ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( mp4a ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( terminator ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( chan ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( srat ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( audio_entry ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( tims ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( tsro ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( tssy ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( hint_entry ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( metadata_entry ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( qt_text_entry ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( ftab ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( stsd ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( stts ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( ctts ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( cslg ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( stsz ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( stz2 ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( stss ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( stps ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( sdtp ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( stsc ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( stco ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( sgpd ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( sbgp ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( mvhd ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( iods ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( mdat ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( skip ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( chpl ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( keys ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( mean ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( name ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( data ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( ilst ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( WLOC ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( LOOP ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( SelO ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( AllF ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( rtp ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( sdp ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( cprt ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( mehd ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( trex ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( mfhd ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( tfhd ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( tfdt ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( trun ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( tfra ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( mfro ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( styp ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( sidx ); +DEFINE_SIMPLE_BOX_DEFAULT_CONSTANT( root_abstract ); + +#define INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( child_name ) \ + .child_name = (isom_##child_name##_t *)&isom_##child_name##_box_default + +#define DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT( ... ) \ + CALL_FUNC_DEFAULT_ARGS( DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT, __VA_ARGS__ ) +#define DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT_TEMPLATE( box_name, expanded_initializations ) \ + const isom_##box_name##_t isom_##box_name##_box_default = \ + { \ + INITIALIZE_BASEBOX_COMMON( box_name ), \ + expanded_initializations \ + } +#define DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT_12( box_name, \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10 ) \ + DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT_TEMPLATE( box_name, \ + EXPAND_VA_ARGS( INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _0 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _1 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _2 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _3 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _4 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _5 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _6 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _7 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _8 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _9 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _10 ) ) ) +#define DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT_9( box_name, _0, _1, _2, _3, _4, _5, _6, _7 ) \ + DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT_TEMPLATE( box_name, \ + EXPAND_VA_ARGS( INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _0 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _1 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _2 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _3 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _4 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _5 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _6 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _7 ) ) ) +#define DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT_8( box_name, _0, _1, _2, _3, _4, _5, _6 ) \ + DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT_TEMPLATE( box_name, \ + EXPAND_VA_ARGS( INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _0 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _1 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _2 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _3 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _4 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _5 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _6 ) ) ) +#define DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT_7( box_name, _0, _1, _2, _3, _4, _5 ) \ + DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT_TEMPLATE( box_name, \ + EXPAND_VA_ARGS( INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _0 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _1 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _2 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _3 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _4 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _5 ) ) ) +#define DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT_6( box_name, _0, _1, _2, _3, _4 ) \ + DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT_TEMPLATE( box_name, \ + EXPAND_VA_ARGS( INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _0 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _1 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _2 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _3 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _4 ) ) ) +#define DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT_5( box_name, _0, _1, _2, _3 ) \ + DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT_TEMPLATE( box_name, \ + EXPAND_VA_ARGS( INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _0 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _1 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _2 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _3 ) ) ) +#define DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT_4( box_name, _0, _1, _2 ) \ + DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT_TEMPLATE( box_name, \ + EXPAND_VA_ARGS( INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _0 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _1 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _2 ) ) ) +#define DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT_3( box_name, _0, _1 ) \ + DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT_TEMPLATE( box_name, \ + EXPAND_VA_ARGS( INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _0 ), \ + INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _1 ) ) ) +#define DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT_2( box_name, _0 ) \ + DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT_TEMPLATE( box_name, \ + EXPAND_VA_ARGS( INITIALIZE_PREDEFINED_CHILD_BY_DEFAULT_CONSTANT_PTR( _0 ) ) ) + +DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT( tapt, clef, enof, prof ); +DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT( edts, elst ); +DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT( gmhd, gmin, text ); +DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT( dinf, dref ); +DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT( wave, frma, enda, mp4a ); +DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT( tx3g_entry, ftab ); +DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT( stbl, stsd, stts, ctts, cslg, stss, stps, sdtp, stsc, stsz, stz2, stco ); +DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT( minf, vmhd, smhd, hmhd, nmhd, gmhd, hdlr, dinf, stbl ); +DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT( mdia, mdhd, hdlr, minf ); +DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT( metaitem, mean, name, data ); +DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT( meta, hdlr, dinf, keys, ilst ); +DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT( udta, chpl, meta, WLOC, LOOP, SelO, AllF, hnti ); +DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT( hnti, rtp, sdp ); +DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT( mvex, mehd ); +DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT( traf, tfhd, tfdt, sdtp ); +DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT( moof, mfhd ); +DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT( mfra, mfro ); +DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT( trak, tkhd, tapt, edts, tref, mdia, udta, meta ); +DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT( moov, mvhd, iods, udta, ctab, meta, mvex ); +DEFINE_SIMPLE_CONTAINER_BOX_DEFAULT_CONSTANT( file_abstract, ftyp, moov, mdat, meta, mfra ); + +/* Box-lists predefined in the parent box definitions are initialized by 0s, thus the entry eliminator + * of each list also be set to NULL (0). This must not be an issue since all box are deallocated through + * the 'extensions' list of the parent box. Only 'entry' which holds the actual box data is deallocated + * when the box is deallocated by the isom_remove_box_in_predefined_list() function. Because of that, + * here, there are no explicit default constant definitions of indivisual predefined box-lists. */ + + +/* Allocate box by default settings. + * + * Use this function to allocate boxes as much as possible, it covers forgetful settings. */ +void *allocate_box_by_default +( + const void *nonexist_ptr, + const size_t data_type_size +) +{ + assert( data_type_size >= offsetof( isom_box_t, manager ) + sizeof(((isom_box_t *)0)->manager) ); + isom_box_t *box = (isom_box_t *)lsmash_memdup( nonexist_ptr, data_type_size ); + if( !box ) + return (void *)nonexist_ptr; + box->manager &= ~LSMASH_NON_EXISTING_BOX; + lsmash_list_init( &box->extensions, isom_remove_extension_box ); + return (void *)box; +} diff --git a/3rdparty/l-smash/core/box_default.h b/3rdparty/l-smash/core/box_default.h new file mode 100644 index 000000000..7ce010462 --- /dev/null +++ b/3rdparty/l-smash/core/box_default.h @@ -0,0 +1,151 @@ +/***************************************************************************** + * box_default.h + ***************************************************************************** + * Copyright (C) 2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#define ALLOCATE_BOX( box_name ) \ + (isom_##box_name##_t *)allocate_box_by_default( &isom_##box_name##_box_default, \ + sizeof(isom_##box_name##_box_default) ) + +#define DEFINE_BOX_DEFAULT_CONSTANT( box_name ) \ + extern const isom_##box_name##_t isom_##box_name##_box_default; \ + static inline isom_##box_name##_t *isom_non_existing_##box_name( void ) \ + { \ + return (isom_##box_name##_t *)&isom_##box_name##_box_default; \ + } + +DEFINE_BOX_DEFAULT_CONSTANT( dummy ) +DEFINE_BOX_DEFAULT_CONSTANT( unknown ) +DEFINE_BOX_DEFAULT_CONSTANT( ftyp ) +DEFINE_BOX_DEFAULT_CONSTANT( ctab ) +DEFINE_BOX_DEFAULT_CONSTANT( tkhd ) +DEFINE_BOX_DEFAULT_CONSTANT( clef ) +DEFINE_BOX_DEFAULT_CONSTANT( prof ) +DEFINE_BOX_DEFAULT_CONSTANT( enof ) +DEFINE_BOX_DEFAULT_CONSTANT( tapt ) +DEFINE_BOX_DEFAULT_CONSTANT( elst ) +DEFINE_BOX_DEFAULT_CONSTANT( edts ) +DEFINE_BOX_DEFAULT_CONSTANT( tref_type ) +DEFINE_BOX_DEFAULT_CONSTANT( tref ) +DEFINE_BOX_DEFAULT_CONSTANT( mdhd ) +DEFINE_BOX_DEFAULT_CONSTANT( hdlr ) +DEFINE_BOX_DEFAULT_CONSTANT( vmhd ) +DEFINE_BOX_DEFAULT_CONSTANT( smhd ) +DEFINE_BOX_DEFAULT_CONSTANT( hmhd ) +DEFINE_BOX_DEFAULT_CONSTANT( nmhd ) +DEFINE_BOX_DEFAULT_CONSTANT( gmin ) +DEFINE_BOX_DEFAULT_CONSTANT( text ) +DEFINE_BOX_DEFAULT_CONSTANT( gmhd ) +DEFINE_BOX_DEFAULT_CONSTANT( dref_entry ) +DEFINE_BOX_DEFAULT_CONSTANT( dref ) +DEFINE_BOX_DEFAULT_CONSTANT( dinf ) +DEFINE_BOX_DEFAULT_CONSTANT( esds ) +DEFINE_BOX_DEFAULT_CONSTANT( btrt ) +DEFINE_BOX_DEFAULT_CONSTANT( glbl ) +DEFINE_BOX_DEFAULT_CONSTANT( clap ) +DEFINE_BOX_DEFAULT_CONSTANT( pasp ) +DEFINE_BOX_DEFAULT_CONSTANT( colr ) +DEFINE_BOX_DEFAULT_CONSTANT( gama ) +DEFINE_BOX_DEFAULT_CONSTANT( fiel ) +DEFINE_BOX_DEFAULT_CONSTANT( clli ) +DEFINE_BOX_DEFAULT_CONSTANT( mdcv ) +DEFINE_BOX_DEFAULT_CONSTANT( cspc ) +DEFINE_BOX_DEFAULT_CONSTANT( sgbt ) +DEFINE_BOX_DEFAULT_CONSTANT( stsl ) +DEFINE_BOX_DEFAULT_CONSTANT( sample_entry ) +DEFINE_BOX_DEFAULT_CONSTANT( mp4s_entry ) +DEFINE_BOX_DEFAULT_CONSTANT( visual_entry ) +DEFINE_BOX_DEFAULT_CONSTANT( frma ) +DEFINE_BOX_DEFAULT_CONSTANT( enda ) +DEFINE_BOX_DEFAULT_CONSTANT( mp4a ) +DEFINE_BOX_DEFAULT_CONSTANT( terminator ) +DEFINE_BOX_DEFAULT_CONSTANT( wave ) +DEFINE_BOX_DEFAULT_CONSTANT( chan ) +DEFINE_BOX_DEFAULT_CONSTANT( srat ) +DEFINE_BOX_DEFAULT_CONSTANT( audio_entry ) +DEFINE_BOX_DEFAULT_CONSTANT( tims ) +DEFINE_BOX_DEFAULT_CONSTANT( tsro ) +DEFINE_BOX_DEFAULT_CONSTANT( tssy ) +DEFINE_BOX_DEFAULT_CONSTANT( hint_entry ) +DEFINE_BOX_DEFAULT_CONSTANT( metadata_entry ) +DEFINE_BOX_DEFAULT_CONSTANT( qt_text_entry ) +DEFINE_BOX_DEFAULT_CONSTANT( ftab ) +DEFINE_BOX_DEFAULT_CONSTANT( tx3g_entry ) +DEFINE_BOX_DEFAULT_CONSTANT( stsd ) +DEFINE_BOX_DEFAULT_CONSTANT( stts ) +DEFINE_BOX_DEFAULT_CONSTANT( ctts ) +DEFINE_BOX_DEFAULT_CONSTANT( cslg ) +DEFINE_BOX_DEFAULT_CONSTANT( stsz ) +DEFINE_BOX_DEFAULT_CONSTANT( stz2 ) +DEFINE_BOX_DEFAULT_CONSTANT( stss ) +DEFINE_BOX_DEFAULT_CONSTANT( stps ) +DEFINE_BOX_DEFAULT_CONSTANT( sdtp ) +DEFINE_BOX_DEFAULT_CONSTANT( stsc ) +DEFINE_BOX_DEFAULT_CONSTANT( stco ) +DEFINE_BOX_DEFAULT_CONSTANT( sgpd ) +DEFINE_BOX_DEFAULT_CONSTANT( sbgp ) +DEFINE_BOX_DEFAULT_CONSTANT( stbl ) +DEFINE_BOX_DEFAULT_CONSTANT( minf ) +DEFINE_BOX_DEFAULT_CONSTANT( mdia ) +DEFINE_BOX_DEFAULT_CONSTANT( mvhd ) +DEFINE_BOX_DEFAULT_CONSTANT( iods ) +DEFINE_BOX_DEFAULT_CONSTANT( mdat ) +DEFINE_BOX_DEFAULT_CONSTANT( skip ) +DEFINE_BOX_DEFAULT_CONSTANT( chpl ) +DEFINE_BOX_DEFAULT_CONSTANT( keys ) +DEFINE_BOX_DEFAULT_CONSTANT( mean ) +DEFINE_BOX_DEFAULT_CONSTANT( name ) +DEFINE_BOX_DEFAULT_CONSTANT( data ) +DEFINE_BOX_DEFAULT_CONSTANT( metaitem ) +DEFINE_BOX_DEFAULT_CONSTANT( ilst ) +DEFINE_BOX_DEFAULT_CONSTANT( meta ) +DEFINE_BOX_DEFAULT_CONSTANT( WLOC ) +DEFINE_BOX_DEFAULT_CONSTANT( LOOP ) +DEFINE_BOX_DEFAULT_CONSTANT( SelO ) +DEFINE_BOX_DEFAULT_CONSTANT( AllF ) +DEFINE_BOX_DEFAULT_CONSTANT( hnti ) +DEFINE_BOX_DEFAULT_CONSTANT( rtp ) +DEFINE_BOX_DEFAULT_CONSTANT( sdp ) +DEFINE_BOX_DEFAULT_CONSTANT( cprt ) +DEFINE_BOX_DEFAULT_CONSTANT( udta ) +DEFINE_BOX_DEFAULT_CONSTANT( mehd ) +DEFINE_BOX_DEFAULT_CONSTANT( trex ) +DEFINE_BOX_DEFAULT_CONSTANT( mvex ) +DEFINE_BOX_DEFAULT_CONSTANT( mfhd ) +DEFINE_BOX_DEFAULT_CONSTANT( tfhd ) +DEFINE_BOX_DEFAULT_CONSTANT( tfdt ) +DEFINE_BOX_DEFAULT_CONSTANT( trun ) +DEFINE_BOX_DEFAULT_CONSTANT( traf ) +DEFINE_BOX_DEFAULT_CONSTANT( moof ) +DEFINE_BOX_DEFAULT_CONSTANT( tfra ) +DEFINE_BOX_DEFAULT_CONSTANT( mfro ) +DEFINE_BOX_DEFAULT_CONSTANT( mfra ) +DEFINE_BOX_DEFAULT_CONSTANT( trak ) +DEFINE_BOX_DEFAULT_CONSTANT( moov ) +DEFINE_BOX_DEFAULT_CONSTANT( styp ) +DEFINE_BOX_DEFAULT_CONSTANT( sidx ) +DEFINE_BOX_DEFAULT_CONSTANT( file_abstract ) +DEFINE_BOX_DEFAULT_CONSTANT( root_abstract ) + +void *allocate_box_by_default +( + const void *nonexist_ptr, + const size_t data_type_size +); diff --git a/3rdparty/l-smash/core/box_type.c b/3rdparty/l-smash/core/box_type.c new file mode 100644 index 000000000..9ee75771a --- /dev/null +++ b/3rdparty/l-smash/core/box_type.c @@ -0,0 +1,27 @@ +/***************************************************************************** + * box_type.c + ***************************************************************************** + * Copyright (C) 2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +/* Box types */ +#define LSMASH_INITIALIZE_BOX_TYPE_HERE +#include "box_type.h" diff --git a/3rdparty/l-smash/core/box_type.h b/3rdparty/l-smash/core/box_type.h new file mode 100644 index 000000000..a11235532 --- /dev/null +++ b/3rdparty/l-smash/core/box_type.h @@ -0,0 +1,263 @@ +/***************************************************************************** + * box_type.h + ***************************************************************************** + * Copyright (C) 2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +/* Box types */ +#ifndef LSMASH_INITIALIZE_BOX_TYPE_HERE + #define DEFINE_BOX_TYPE( BOX_TYPE, FOURCC, UUID_TYPE ) \ + extern const lsmash_box_type_t BOX_TYPE +#else + #define DEFINE_BOX_TYPE( BOX_TYPE, FOURCC, UUID_TYPE ) \ + const lsmash_box_type_t BOX_TYPE = LSMASH_##UUID_TYPE##_BOX_TYPE_INITIALIZER( FOURCC ) +#endif + +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_ID32, LSMASH_4CC( 'I', 'D', '3', '2' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_ALBM, LSMASH_4CC( 'a', 'l', 'b', 'm' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_AUTH, LSMASH_4CC( 'a', 'u', 't', 'h' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_BPCC, LSMASH_4CC( 'b', 'p', 'c', 'c' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_BUFF, LSMASH_4CC( 'b', 'u', 'f', 'f' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_BXML, LSMASH_4CC( 'b', 'x', 'm', 'l' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_CCID, LSMASH_4CC( 'c', 'c', 'i', 'd' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_CDEF, LSMASH_4CC( 'c', 'd', 'e', 'f' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_CLSF, LSMASH_4CC( 'c', 'l', 's', 'f' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_CMAP, LSMASH_4CC( 'c', 'm', 'a', 'p' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_CO64, LSMASH_4CC( 'c', 'o', '6', '4' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_COLR, LSMASH_4CC( 'c', 'o', 'l', 'r' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_CPRT, LSMASH_4CC( 'c', 'p', 'r', 't' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_CSLG, LSMASH_4CC( 'c', 's', 'l', 'g' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_CTTS, LSMASH_4CC( 'c', 't', 't', 's' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_CVRU, LSMASH_4CC( 'c', 'v', 'r', 'u' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_DCFD, LSMASH_4CC( 'd', 'c', 'f', 'D' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_DINF, LSMASH_4CC( 'd', 'i', 'n', 'f' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_DREF, LSMASH_4CC( 'd', 'r', 'e', 'f' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_DSCP, LSMASH_4CC( 'd', 's', 'c', 'p' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_DSGD, LSMASH_4CC( 'd', 's', 'g', 'd' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_DSTG, LSMASH_4CC( 'd', 's', 't', 'g' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_EDTS, LSMASH_4CC( 'e', 'd', 't', 's' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_ELST, LSMASH_4CC( 'e', 'l', 's', 't' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_FECI, LSMASH_4CC( 'f', 'e', 'c', 'i' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_FECR, LSMASH_4CC( 'f', 'e', 'c', 'r' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_FIIN, LSMASH_4CC( 'f', 'i', 'i', 'n' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_FIRE, LSMASH_4CC( 'f', 'i', 'r', 'e' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_FPAR, LSMASH_4CC( 'f', 'p', 'a', 'r' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_FREE, LSMASH_4CC( 'f', 'r', 'e', 'e' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_FRMA, LSMASH_4CC( 'f', 'r', 'm', 'a' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_FTYP, LSMASH_4CC( 'f', 't', 'y', 'p' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_GITN, LSMASH_4CC( 'g', 'i', 't', 'n' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_GNRE, LSMASH_4CC( 'g', 'n', 'r', 'e' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_GRPI, LSMASH_4CC( 'g', 'r', 'p', 'i' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_HDLR, LSMASH_4CC( 'h', 'd', 'l', 'r' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_HMHD, LSMASH_4CC( 'h', 'm', 'h', 'd' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_ICNU, LSMASH_4CC( 'i', 'c', 'n', 'u' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_IDAT, LSMASH_4CC( 'i', 'd', 'a', 't' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_IHDR, LSMASH_4CC( 'i', 'h', 'd', 'r' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_IINF, LSMASH_4CC( 'i', 'i', 'n', 'f' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_ILOC, LSMASH_4CC( 'i', 'l', 'o', 'c' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_IMIF, LSMASH_4CC( 'i', 'm', 'i', 'f' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_INFU, LSMASH_4CC( 'i', 'n', 'f', 'u' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_IODS, LSMASH_4CC( 'i', 'o', 'd', 's' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_IPHD, LSMASH_4CC( 'i', 'p', 'h', 'd' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_IPMC, LSMASH_4CC( 'i', 'p', 'm', 'c' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_IPRO, LSMASH_4CC( 'i', 'p', 'r', 'o' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_IREF, LSMASH_4CC( 'i', 'r', 'e', 'f' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_JP , LSMASH_4CC( 'j', 'p', ' ', ' ' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_JP2C, LSMASH_4CC( 'j', 'p', '2', 'c' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_JP2H, LSMASH_4CC( 'j', 'p', '2', 'h' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_JP2I, LSMASH_4CC( 'j', 'p', '2', 'i' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_KYWD, LSMASH_4CC( 'k', 'y', 'w', 'd' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_LOCI, LSMASH_4CC( 'l', 'o', 'c', 'i' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_LRCU, LSMASH_4CC( 'l', 'r', 'c', 'u' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_MDAT, LSMASH_4CC( 'm', 'd', 'a', 't' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_MDHD, LSMASH_4CC( 'm', 'd', 'h', 'd' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_MDIA, LSMASH_4CC( 'm', 'd', 'i', 'a' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_MDRI, LSMASH_4CC( 'm', 'd', 'r', 'i' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_MECO, LSMASH_4CC( 'm', 'e', 'c', 'o' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_MEHD, LSMASH_4CC( 'm', 'e', 'h', 'd' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_M7HD, LSMASH_4CC( 'm', '7', 'h', 'd' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_MERE, LSMASH_4CC( 'm', 'e', 'r', 'e' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_META, LSMASH_4CC( 'm', 'e', 't', 'a' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_MFHD, LSMASH_4CC( 'm', 'f', 'h', 'd' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_MFRA, LSMASH_4CC( 'm', 'f', 'r', 'a' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_MFRO, LSMASH_4CC( 'm', 'f', 'r', 'o' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_MINF, LSMASH_4CC( 'm', 'i', 'n', 'f' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_MJHD, LSMASH_4CC( 'm', 'j', 'h', 'd' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_MOOF, LSMASH_4CC( 'm', 'o', 'o', 'f' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_MOOV, LSMASH_4CC( 'm', 'o', 'o', 'v' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_MVCG, LSMASH_4CC( 'm', 'v', 'c', 'g' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_MVCI, LSMASH_4CC( 'm', 'v', 'c', 'i' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_MVEX, LSMASH_4CC( 'm', 'v', 'e', 'x' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_MVHD, LSMASH_4CC( 'm', 'v', 'h', 'd' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_MVRA, LSMASH_4CC( 'm', 'v', 'r', 'a' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_NMHD, LSMASH_4CC( 'n', 'm', 'h', 'd' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_OCHD, LSMASH_4CC( 'o', 'c', 'h', 'd' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_ODAF, LSMASH_4CC( 'o', 'd', 'a', 'f' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_ODDA, LSMASH_4CC( 'o', 'd', 'd', 'a' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_ODHD, LSMASH_4CC( 'o', 'd', 'h', 'd' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_ODHE, LSMASH_4CC( 'o', 'd', 'h', 'e' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_ODRB, LSMASH_4CC( 'o', 'd', 'r', 'b' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_ODRM, LSMASH_4CC( 'o', 'd', 'r', 'm' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_ODTT, LSMASH_4CC( 'o', 'd', 't', 't' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_OHDR, LSMASH_4CC( 'o', 'h', 'd', 'r' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_PADB, LSMASH_4CC( 'p', 'a', 'd', 'b' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_PAEN, LSMASH_4CC( 'p', 'a', 'e', 'n' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_PCLR, LSMASH_4CC( 'p', 'c', 'l', 'r' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_PDIN, LSMASH_4CC( 'p', 'd', 'i', 'n' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_PERF, LSMASH_4CC( 'p', 'e', 'r', 'f' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_PITM, LSMASH_4CC( 'p', 'i', 't', 'm' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_RES , LSMASH_4CC( 'r', 'e', 's', ' ' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_RESC, LSMASH_4CC( 'r', 'e', 's', 'c' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_RESD, LSMASH_4CC( 'r', 'e', 's', 'd' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_RTNG, LSMASH_4CC( 'r', 't', 'n', 'g' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_SBGP, LSMASH_4CC( 's', 'b', 'g', 'p' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_SCHI, LSMASH_4CC( 's', 'c', 'h', 'i' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_SCHM, LSMASH_4CC( 's', 'c', 'h', 'm' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_SDEP, LSMASH_4CC( 's', 'd', 'e', 'p' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_SDHD, LSMASH_4CC( 's', 'd', 'h', 'd' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_SDTP, LSMASH_4CC( 's', 'd', 't', 'p' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_SDVP, LSMASH_4CC( 's', 'd', 'v', 'p' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_SEGR, LSMASH_4CC( 's', 'e', 'g', 'r' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_SGPD, LSMASH_4CC( 's', 'g', 'p', 'd' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_SIDX, LSMASH_4CC( 's', 'i', 'd', 'x' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_SINF, LSMASH_4CC( 's', 'i', 'n', 'f' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_SKIP, LSMASH_4CC( 's', 'k', 'i', 'p' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_SMHD, LSMASH_4CC( 's', 'm', 'h', 'd' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_SRMB, LSMASH_4CC( 's', 'r', 'm', 'b' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_SRMC, LSMASH_4CC( 's', 'r', 'm', 'c' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_SRPP, LSMASH_4CC( 's', 'r', 'p', 'p' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_STBL, LSMASH_4CC( 's', 't', 'b', 'l' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_STCO, LSMASH_4CC( 's', 't', 'c', 'o' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_STDP, LSMASH_4CC( 's', 't', 'd', 'p' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_STSC, LSMASH_4CC( 's', 't', 's', 'c' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_STSD, LSMASH_4CC( 's', 't', 's', 'd' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_STSH, LSMASH_4CC( 's', 't', 's', 'h' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_STSS, LSMASH_4CC( 's', 't', 's', 's' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_STSZ, LSMASH_4CC( 's', 't', 's', 'z' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_STTS, LSMASH_4CC( 's', 't', 't', 's' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_STYP, LSMASH_4CC( 's', 't', 'y', 'p' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_STZ2, LSMASH_4CC( 's', 't', 'z', '2' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_SUBS, LSMASH_4CC( 's', 'u', 'b', 's' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_SWTC, LSMASH_4CC( 's', 'w', 't', 'c' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_TFHD, LSMASH_4CC( 't', 'f', 'h', 'd' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_TFDT, LSMASH_4CC( 't', 'f', 'd', 't' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_TFRA, LSMASH_4CC( 't', 'f', 'r', 'a' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_TIBR, LSMASH_4CC( 't', 'i', 'b', 'r' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_TIRI, LSMASH_4CC( 't', 'i', 'r', 'i' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_TITL, LSMASH_4CC( 't', 'i', 't', 'l' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_TKHD, LSMASH_4CC( 't', 'k', 'h', 'd' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_TRAF, LSMASH_4CC( 't', 'r', 'a', 'f' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_TRAK, LSMASH_4CC( 't', 'r', 'a', 'k' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_TREF, LSMASH_4CC( 't', 'r', 'e', 'f' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_TREX, LSMASH_4CC( 't', 'r', 'e', 'x' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_TRGR, LSMASH_4CC( 't', 'r', 'g', 'r' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_TRUN, LSMASH_4CC( 't', 'r', 'u', 'n' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_TSEL, LSMASH_4CC( 't', 's', 'e', 'l' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_UDTA, LSMASH_4CC( 'u', 'd', 't', 'a' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_UINF, LSMASH_4CC( 'u', 'i', 'n', 'f' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_ULST, LSMASH_4CC( 'u', 'l', 's', 't' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_URL , LSMASH_4CC( 'u', 'r', 'l', ' ' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_URN , LSMASH_4CC( 'u', 'r', 'n', ' ' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_UUID, LSMASH_4CC( 'u', 'u', 'i', 'd' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_VMHD, LSMASH_4CC( 'v', 'm', 'h', 'd' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_VWDI, LSMASH_4CC( 'v', 'w', 'd', 'i' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_XML , LSMASH_4CC( 'x', 'm', 'l', ' ' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_YRRC, LSMASH_4CC( 'y', 'r', 'r', 'c' ), ISO ); + +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_BTRT, LSMASH_4CC( 'b', 't', 'r', 't' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_CLAP, LSMASH_4CC( 'c', 'l', 'a', 'p' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_PASP, LSMASH_4CC( 'p', 'a', 's', 'p' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_SRAT, LSMASH_4CC( 's', 'r', 'a', 't' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_STSL, LSMASH_4CC( 's', 't', 's', 'l' ), ISO ); + +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_FTAB, LSMASH_4CC( 'f', 't', 'a', 'b' ), ISO ); + +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_HNTI, LSMASH_4CC( 'h', 'n', 't', 'i' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_RTP , LSMASH_4CC( 'r', 't', 'p', ' ' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_SDP , LSMASH_4CC( 's', 'd', 'p', ' ' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_TIMS, LSMASH_4CC( 't', 'i', 'm', 's' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_TSRO, LSMASH_4CC( 't', 's', 'r', 'o' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_TSSY, LSMASH_4CC( 't', 's', 's', 'y' ), ISO ); + +/* iTunes Metadata */ +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_DATA, LSMASH_4CC( 'd', 'a', 't', 'a' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_ILST, LSMASH_4CC( 'i', 'l', 's', 't' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_MEAN, LSMASH_4CC( 'm', 'e', 'a', 'n' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_NAME, LSMASH_4CC( 'n', 'a', 'm', 'e' ), ISO ); + +/* Tyrant extension */ +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_CHPL, LSMASH_4CC( 'c', 'h', 'p', 'l' ), ISO ); + +/* Decoder Specific Info */ +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_ALAC, LSMASH_4CC( 'a', 'l', 'a', 'c' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_AVCC, LSMASH_4CC( 'a', 'v', 'c', 'C' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_DAC3, LSMASH_4CC( 'd', 'a', 'c', '3' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_DAMR, LSMASH_4CC( 'd', 'a', 'm', 'r' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_DDTS, LSMASH_4CC( 'd', 'd', 't', 's' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_DEC3, LSMASH_4CC( 'd', 'e', 'c', '3' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_DVC1, LSMASH_4CC( 'd', 'v', 'c', '1' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_ESDS, LSMASH_4CC( 'e', 's', 'd', 's' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_HVCC, LSMASH_4CC( 'h', 'v', 'c', 'C' ), ISO ); +DEFINE_BOX_TYPE( ISOM_BOX_TYPE_WFEX, LSMASH_4CC( 'w', 'f', 'e', 'x' ), ISO ); + +DEFINE_BOX_TYPE( QT_BOX_TYPE_ALLF, LSMASH_4CC( 'A', 'l', 'l', 'F' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_CLEF, LSMASH_4CC( 'c', 'l', 'e', 'f' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_CLIP, LSMASH_4CC( 'c', 'l', 'i', 'p' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_CLLI, LSMASH_4CC( 'c', 'l', 'l', 'i' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_CRGN, LSMASH_4CC( 'c', 'r', 'g', 'n' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_CTAB, LSMASH_4CC( 'c', 't', 'a', 'b' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_ENOF, LSMASH_4CC( 'e', 'n', 'o', 'f' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_GMHD, LSMASH_4CC( 'g', 'm', 'h', 'd' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_GMIN, LSMASH_4CC( 'g', 'm', 'i', 'n' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_ILST, LSMASH_4CC( 'i', 'l', 's', 't' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_IMAP, LSMASH_4CC( 'i', 'm', 'a', 'p' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_KEYS, LSMASH_4CC( 'k', 'e', 'y', 's' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_KMAT, LSMASH_4CC( 'k', 'm', 'a', 't' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_LOAD, LSMASH_4CC( 'l', 'o', 'a', 'd' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_LOOP, LSMASH_4CC( 'L', 'O', 'O', 'P' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_MATT, LSMASH_4CC( 'm', 'a', 't', 't' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_MDCV, LSMASH_4CC( 'm', 'd', 'c', 'v' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_META, LSMASH_4CC( 'm', 'e', 't', 'a' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_PNOT, LSMASH_4CC( 'p', 'n', 'o', 't' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_PROF, LSMASH_4CC( 'p', 'r', 'o', 'f' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_SELO, LSMASH_4CC( 'S', 'e', 'l', 'O' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_STPS, LSMASH_4CC( 's', 't', 'p', 's' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_TAPT, LSMASH_4CC( 't', 'a', 'p', 't' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_TEXT, LSMASH_4CC( 't', 'e', 'x', 't' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_WLOC, LSMASH_4CC( 'W', 'L', 'O', 'C' ), QTFF ); + +DEFINE_BOX_TYPE( QT_BOX_TYPE_ALIS, LSMASH_4CC( 'a', 'l', 'i', 's' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_RSRC, LSMASH_4CC( 'r', 's', 'r', 'c' ), QTFF ); + +DEFINE_BOX_TYPE( QT_BOX_TYPE_CHAN, LSMASH_4CC( 'c', 'h', 'a', 'n' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_COLR, LSMASH_4CC( 'c', 'o', 'l', 'r' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_CSPC, LSMASH_4CC( 'c', 's', 'p', 'c' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_ENDA, LSMASH_4CC( 'e', 'n', 'd', 'a' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_FIEL, LSMASH_4CC( 'f', 'i', 'e', 'l' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_FRMA, LSMASH_4CC( 'f', 'r', 'm', 'a' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_GAMA, LSMASH_4CC( 'g', 'a', 'm', 'a' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_SGBT, LSMASH_4CC( 's', 'g', 'b', 't' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_WAVE, LSMASH_4CC( 'w', 'a', 'v', 'e' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_TERMINATOR, 0x00000000, QTFF ); + +/* Decoder Specific Info */ +DEFINE_BOX_TYPE( QT_BOX_TYPE_ALAC, LSMASH_4CC( 'a', 'l', 'a', 'c' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_ESDS, LSMASH_4CC( 'e', 's', 'd', 's' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_GLBL, LSMASH_4CC( 'g', 'l', 'b', 'l' ), QTFF ); +DEFINE_BOX_TYPE( QT_BOX_TYPE_MP4A, LSMASH_4CC( 'm', 'p', '4', 'a' ), QTFF ); diff --git a/3rdparty/l-smash/core/chapter.c b/3rdparty/l-smash/core/chapter.c new file mode 100644 index 000000000..a9dde8d5c --- /dev/null +++ b/3rdparty/l-smash/core/chapter.c @@ -0,0 +1,440 @@ +/***************************************************************************** + * chapter.c + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * Contributors: Takashi Hirata + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include +#include +#include +#include + +#include "box.h" + +#define CHAPTER_BUFSIZE 512 +#define UTF8_BOM "\xEF\xBB\xBF" +#define UTF8_BOM_LENGTH 3 + +static int isom_get_start_time( char *chap_time, isom_chapter_entry_t *data ) +{ + uint64_t hh, mm; + double ss; + if( sscanf( chap_time, "%"SCNu64":%2"SCNu64":%lf", &hh, &mm, &ss ) != 3 ) + return LSMASH_ERR_INVALID_DATA; + /* check overflow */ + if( hh >= 5124095 + || mm >= 60 + || ss >= 60 ) + return LSMASH_ERR_INVALID_DATA; + /* 1ns timescale */ + data->start_time = (hh * 3600 + mm * 60 + ss) * 1e9; + return 0; +} + +static int isom_lumber_line( char *buff, int bufsize, FILE *chapter ) +{ + char *tail; + /* remove newline codes and skip empty line */ + do + { + if( fgets( buff, bufsize, chapter ) == NULL ) + return LSMASH_ERR_NAMELESS; + tail = &buff[ strlen( buff ) - 1 ]; + while( tail >= buff && (*tail == '\n' || *tail == '\r') ) + *tail-- = '\0'; + } while( tail < buff ); + return 0; +} + +static int isom_read_simple_chapter( FILE *chapter, isom_chapter_entry_t *data ) +{ + char buff[CHAPTER_BUFSIZE]; + /* get start_time */ + if( isom_lumber_line( buff, CHAPTER_BUFSIZE, chapter ) < 0 ) + return LSMASH_ERR_NAMELESS; + char *chapter_time = strchr( buff, '=' ); /* find separator */ + if( !chapter_time++ ) + return LSMASH_ERR_INVALID_DATA; + if( isom_get_start_time( chapter_time, data ) < 0 ) + return LSMASH_ERR_INVALID_DATA; + if( isom_lumber_line( buff, CHAPTER_BUFSIZE, chapter ) < 0 ) /* get chapter_name */ + return LSMASH_ERR_NAMELESS; + char *chapter_name = strchr( buff, '=' ); /* find separator */ + if( !chapter_name++ ) + return LSMASH_ERR_INVALID_DATA; + int len = LSMASH_MIN( 255, strlen( chapter_name ) ); /* We support length of chapter_name up to 255 */ + data->chapter_name = (char *)lsmash_malloc( len + 1 ); + if( !data->chapter_name ) + return LSMASH_ERR_MEMORY_ALLOC; + memcpy( data->chapter_name, chapter_name, len ); + data->chapter_name[len] = '\0'; + return 0; +} + +static int isom_read_minimum_chapter( FILE *chapter, isom_chapter_entry_t *data ) +{ + char buff[CHAPTER_BUFSIZE]; + if( isom_lumber_line( buff, CHAPTER_BUFSIZE, chapter ) < 0 ) /* read newline */ + return LSMASH_ERR_NAMELESS; + char *p_buff = &buff[ !memcmp( buff, UTF8_BOM, UTF8_BOM_LENGTH ) ? UTF8_BOM_LENGTH : 0 ]; /* BOM detection */ + if( isom_get_start_time( p_buff, data ) < 0 ) /* get start_time */ + return LSMASH_ERR_INVALID_DATA; + /* get chapter_name */ + char *chapter_name = strchr( buff, ' ' ); /* find separator */ + if( !chapter_name++ ) + return LSMASH_ERR_INVALID_DATA; + int len = LSMASH_MIN( 255, strlen( chapter_name ) ); /* We support length of chapter_name up to 255 */ + data->chapter_name = (char *)lsmash_malloc( len + 1 ); + if( !data->chapter_name ) + return LSMASH_ERR_MEMORY_ALLOC; + memcpy( data->chapter_name, chapter_name, len ); + data->chapter_name[len] = '\0'; + return 0; +} + +typedef int (*fn_get_chapter_data)( FILE *, isom_chapter_entry_t * ); + +static fn_get_chapter_data isom_check_chap_line( char *file_name ) +{ + FILE *fp = lsmash_fopen( file_name, "rb" ); + if( !fp ) + { + lsmash_log( NULL, LSMASH_LOG_ERROR, "failed to open the chapter file \"%s\".\n", file_name ); + return NULL; + } + char buff[CHAPTER_BUFSIZE]; + fn_get_chapter_data fnc = NULL; + if( fgets( buff, CHAPTER_BUFSIZE, fp ) != NULL ) + { + char *p_buff = &buff[ !memcmp( buff, UTF8_BOM, UTF8_BOM_LENGTH ) ? UTF8_BOM_LENGTH : 0 ]; /* BOM detection */ + if( !strncmp( p_buff, "CHAPTER", 7 ) ) + fnc = isom_read_simple_chapter; + else if( isdigit( (unsigned char)p_buff[0] ) && isdigit( (unsigned char)p_buff[1] ) && p_buff[2] == ':' + && isdigit( (unsigned char)p_buff[3] ) && isdigit( (unsigned char)p_buff[4] ) && p_buff[5] == ':' ) + fnc = isom_read_minimum_chapter; + else + lsmash_log( NULL, LSMASH_LOG_ERROR, "the chapter file is malformed.\n" ); + } + fclose( fp ); + return fnc; +} + +static int isom_add_chpl_entry( isom_chpl_t *chpl, isom_chapter_entry_t *chap_data ) +{ + assert( LSMASH_IS_EXISTING_BOX( chpl ) ); + if( !chap_data->chapter_name || !chpl->list ) + return LSMASH_ERR_FUNCTION_PARAM; + isom_chpl_entry_t *data = lsmash_malloc( sizeof(isom_chpl_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + data->start_time = chap_data->start_time; + data->chapter_name_length = strlen( chap_data->chapter_name ); + data->chapter_name = (char *)lsmash_malloc( data->chapter_name_length + 1 ); + if( !data->chapter_name ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + memcpy( data->chapter_name, chap_data->chapter_name, data->chapter_name_length ); + data->chapter_name[ data->chapter_name_length ] = '\0'; + if( lsmash_list_add_entry( chpl->list, data ) < 0 ) + { + lsmash_free( data->chapter_name ); + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + return 0; +} + +int lsmash_set_tyrant_chapter( lsmash_root_t *root, char *file_name, int add_bom ) +{ + if( isom_check_initializer_present( root ) < 0 ) + goto error_message; + /* This function should be called after updating of the latest movie duration. */ + lsmash_file_t *file = root->file; + if( LSMASH_IS_NON_EXISTING_BOX( file->moov->mvhd ) + || file->moov->mvhd->timescale == 0 + || file->moov->mvhd->duration == 0 ) + goto error_message; + /* check each line format */ + fn_get_chapter_data fnc = isom_check_chap_line( file_name ); + if( !fnc ) + goto error_message; + FILE *chapter = lsmash_fopen( file_name, "rb" ); + if( !chapter ) + { + lsmash_log( NULL, LSMASH_LOG_ERROR, "failed to open the chapter file \"%s\".\n", file_name ); + goto error_message; + } + if( (LSMASH_IS_NON_EXISTING_BOX( file->moov->udta ) && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_udta( file->moov ) )) + || (LSMASH_IS_NON_EXISTING_BOX( file->moov->udta->chpl ) && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_chpl( file->moov->udta ) )) ) + goto fail; + file->moov->udta->chpl->version = 1; /* version = 1 is popular. */ + isom_chapter_entry_t data = { 0 }; + while( !fnc( chapter, &data ) ) + { + if( add_bom ) + { + char *chapter_name_with_bom = (char *)lsmash_malloc( strlen( data.chapter_name ) + 1 + UTF8_BOM_LENGTH ); + if( !chapter_name_with_bom ) + goto fail2; + sprintf( chapter_name_with_bom, "%s%s", UTF8_BOM, data.chapter_name ); + lsmash_free( data.chapter_name ); + data.chapter_name = chapter_name_with_bom; + } + data.start_time = (data.start_time + 50) / 100; /* convert to 100ns unit */ + if( data.start_time / 1e7 > (double)file->moov->mvhd->duration / file->moov->mvhd->timescale ) + { + lsmash_log( NULL, LSMASH_LOG_WARNING, + "a chapter point exceeding the actual duration detected." + "This chapter point and the following ones (if any) will be cut off.\n" ); + lsmash_free( data.chapter_name ); + break; + } + if( isom_add_chpl_entry( file->moov->udta->chpl, &data ) < 0 ) + goto fail2; + lsmash_freep( &data.chapter_name ); + } + fclose( chapter ); + return 0; +fail2: + lsmash_free( data.chapter_name ); +fail: + fclose( chapter ); +error_message: + lsmash_log( NULL, LSMASH_LOG_ERROR, "failed to set chapter list.\n" ); + return LSMASH_ERR_NAMELESS; +} + +int lsmash_create_reference_chapter_track( lsmash_root_t *root, uint32_t track_ID, char *file_name ) +{ + if( isom_check_initializer_present( root ) < 0 ) + goto error_message; + lsmash_file_t *file = root->file; + if( LSMASH_IS_NON_EXISTING_BOX( file->moov->mvhd ) ) + goto error_message; + if( file->forbid_tref || (!file->qt_compatible && !file->itunes_movie) ) + { + lsmash_log( NULL, LSMASH_LOG_ERROR, "reference chapter is not available for this file.\n" ); + goto error_message; + } + FILE *chapter = NULL; /* shut up 'uninitialized' warning */ + /* Create a Track Reference Box. */ + isom_trak_t *trak = isom_get_trak( file, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak ) ) + { + lsmash_log( NULL, LSMASH_LOG_ERROR, "the specified track ID to apply the chapter doesn't exist.\n" ); + goto error_message; + } + if( LSMASH_IS_NON_EXISTING_BOX( trak->tref ) && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_tref( trak ) ) ) + goto error_message; + /* Create a track_ID for a new chapter track. */ + uint32_t *id = (uint32_t *)lsmash_malloc( sizeof(uint32_t) ); + if( !id ) + goto error_message; + uint32_t chapter_track_ID = *id = file->moov->mvhd->next_track_ID; + /* Create a Track Reference Type Box. */ + isom_tref_type_t *chap = isom_add_track_reference_type( trak->tref, QT_TREF_TYPE_CHAP ); + if( LSMASH_IS_NON_EXISTING_BOX( chap ) ) + { + lsmash_free( id ); + goto error_message; + } + chap->ref_count = 1; + chap->track_ID = id; + /* Create a reference chapter track. */ + if( chapter_track_ID != lsmash_create_track( root, ISOM_MEDIA_HANDLER_TYPE_TEXT_TRACK ) ) + goto error_message; + /* Set track parameters. */ + lsmash_track_parameters_t track_param; + lsmash_initialize_track_parameters( &track_param ); + track_param.mode = ISOM_TRACK_IN_MOVIE | ISOM_TRACK_IN_PREVIEW; + if( lsmash_set_track_parameters( root, chapter_track_ID, &track_param ) < 0 ) + goto fail; + /* Set media parameters. */ + uint64_t media_timescale = lsmash_get_media_timescale( root, track_ID ); + if( media_timescale == 0 ) + goto fail; + lsmash_media_parameters_t media_param; + lsmash_initialize_media_parameters( &media_param ); + media_param.timescale = media_timescale; + media_param.ISO_language = file->max_3gpp_version >= 6 || file->itunes_movie ? ISOM_LANGUAGE_CODE_UNDEFINED : 0; + media_param.MAC_language = 0; + if( lsmash_set_media_parameters( root, chapter_track_ID, &media_param ) < 0 ) + goto fail; + /* Create a sample description. */ + lsmash_codec_type_t sample_type = file->max_3gpp_version >= 6 || file->itunes_movie + ? ISOM_CODEC_TYPE_TX3G_TEXT + : QT_CODEC_TYPE_TEXT_TEXT; + lsmash_summary_t summary = { .sample_type = sample_type, .data_ref_index = 1 }; + uint32_t sample_entry = lsmash_add_sample_entry( root, chapter_track_ID, &summary ); + if( sample_entry == 0 ) + goto fail; + /* Check each line format. */ + fn_get_chapter_data fnc = isom_check_chap_line( file_name ); + if( !fnc ) + goto fail; + /* Open chapter format file. */ + chapter = lsmash_fopen( file_name, "rb" ); + if( !chapter ) + { + lsmash_log( NULL, LSMASH_LOG_ERROR, "failed to open the chapter file \"%s\".\n", file_name ); + goto fail; + } + /* Parse the file and write text samples. */ + isom_chapter_entry_t data; + while( !fnc( chapter, &data ) ) + { + /* set start_time */ + data.start_time = data.start_time * 1e-9 * media_timescale + 0.5; + /* write a text sample here */ + int is_qt_text = lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_TEXT_TEXT ); + uint16_t name_length = strlen( data.chapter_name ); + lsmash_sample_t *sample = lsmash_create_sample( 2 + name_length + 12 * is_qt_text ); + if( !sample ) + { + lsmash_free( data.chapter_name ); + goto fail; + } + sample->data[0] = (name_length >> 8) & 0xff; + sample->data[1] = name_length & 0xff; + memcpy( sample->data + 2, data.chapter_name, name_length ); + if( is_qt_text ) + { + /* QuickTime Player requires Text Encoding Attribute Box ('encd') if media language is ISO language codes : undefined. + * Also this box can avoid garbling if the QuickTime text sample is encoded by Unicode characters. + * Note: 3GPP Timed Text supports only UTF-8 or UTF-16, so this box isn't needed. */ + static const uint8_t encd[12] = + { + 0x00, 0x00, 0x00, 0x0C, /* size: 12 */ + 0x65, 0x6E, 0x63, 0x64, /* type: 'encd' */ + 0x00, 0x00, 0x01, 0x00 /* Unicode Encoding */ + }; + memcpy( sample->data + 2 + name_length, encd, 12 ); + } + sample->dts = data.start_time; + sample->cts = data.start_time; + sample->prop.ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; + sample->index = sample_entry; + if( lsmash_append_sample( root, chapter_track_ID, sample ) < 0 ) + { + lsmash_free( data.chapter_name ); + goto fail; + } + lsmash_freep( &data.chapter_name ); + } + if( lsmash_flush_pooled_samples( root, chapter_track_ID, 0 ) < 0 ) + goto fail; + isom_trak_t *chapter_trak = isom_get_trak( file, chapter_track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( chapter_trak ) ) + goto fail; + fclose( chapter ); + chapter_trak->is_chapter = 1; + chapter_trak->related_track_ID = track_ID; + return 0; +fail: + if( chapter ) + fclose( chapter ); + /* Remove chapter track reference. */ + if( trak->tref->ref_list.tail ) + isom_remove_box_by_itself( trak->tref->ref_list.tail->data ); + if( trak->tref->ref_list.entry_count == 0 ) + isom_remove_box_by_itself( trak->tref ); + /* Remove the reference chapter track attached at tail of the list. */ + if( file->moov->trak_list.tail ) + isom_remove_box_by_itself( file->moov->trak_list.tail->data ); +error_message: + lsmash_log( NULL, LSMASH_LOG_ERROR, "failed to set reference chapter.\n" ); + return LSMASH_ERR_NAMELESS; +} + +uint32_t lsmash_count_tyrant_chapter( lsmash_root_t *root ) +{ + if( isom_check_initializer_present( root ) < 0 + && root->file->initializer->moov->udta->chpl->list ) + return root->file->initializer->moov->udta->chpl->list->entry_count; + return 0; +} + +char *lsmash_get_tyrant_chapter( lsmash_root_t *root, uint32_t index, double *timestamp ) +{ + if( isom_check_initializer_present( root ) < 0 ) + return NULL; + lsmash_file_t *file = root->file->initializer; + if( LSMASH_IS_NON_EXISTING_BOX( file->moov->mvhd ) + || LSMASH_IS_NON_EXISTING_BOX( file->moov->udta->chpl ) ) + return NULL; + isom_chpl_t *chpl = file->moov->udta->chpl; + isom_chpl_entry_t *data = (isom_chpl_entry_t *)lsmash_list_get_entry_data( chpl->list, index ); + if( !data ) + return NULL; + double timescale = chpl->version ? 10000000.0 : file->moov->mvhd->timescale; + *timestamp = data->start_time / timescale; + if( !memcmp( data->chapter_name, UTF8_BOM, UTF8_BOM_LENGTH ) ) + return data->chapter_name + UTF8_BOM_LENGTH; + return data->chapter_name; +} + + +int lsmash_print_chapter_list( lsmash_root_t *root ) +{ + if( isom_check_initializer_present( root ) < 0 + || !(root->file->initializer->flags & LSMASH_FILE_MODE_READ) ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_file_t *file = root->file->initializer; + if( LSMASH_IS_EXISTING_BOX( file->moov->udta->chpl ) ) + { + isom_chpl_t *chpl = file->moov->udta->chpl; + uint32_t timescale; + if( chpl->version == 0 ) + { + if( LSMASH_IS_NON_EXISTING_BOX( file->moov->mvhd ) ) + return LSMASH_ERR_NAMELESS; + timescale = file->moov->mvhd->timescale; + } + else + timescale = 10000000; + uint32_t i = 1; + for( lsmash_entry_t *entry = chpl->list->head; entry; entry = entry->next ) + { + isom_chpl_entry_t *data = (isom_chpl_entry_t *)entry->data; + int64_t start_time = data->start_time / timescale; + int hh = start_time / 3600; + int mm = (start_time / 60) % 60; + int ss = start_time % 60; + int ms = ((data->start_time / (double)timescale) - hh * 3600 - mm * 60 - ss) * 1e3 + 0.5; + if( !memcmp( data->chapter_name, UTF8_BOM, UTF8_BOM_LENGTH ) ) /* detect BOM */ + { + data->chapter_name += UTF8_BOM_LENGTH; +#ifdef _WIN32 + if( i == 1 ) + printf( UTF8_BOM ); /* add BOM on Windows */ +#endif + } + printf( "CHAPTER%02"PRIu32"=%02d:%02d:%02d.%03d\n", i, hh, mm, ss, ms ); + printf( "CHAPTER%02"PRIu32"NAME=%s\n", i++, data->chapter_name ); + } + return 0; + } + lsmash_log( NULL, LSMASH_LOG_ERROR, "this file doesn't have a chapter list.\n" ); + return LSMASH_ERR_NAMELESS; +} diff --git a/3rdparty/l-smash/core/file.c b/3rdparty/l-smash/core/file.c new file mode 100644 index 000000000..135345523 --- /dev/null +++ b/3rdparty/l-smash/core/file.c @@ -0,0 +1,718 @@ +/***************************************************************************** + * file.c + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +/* for _setmode() */ +#ifdef _WIN32 +#include +#endif + +#include +#include + +#include "box.h" +#include "read.h" +#include "fragment.h" + +#include "importer/importer.h" + +static void isom_clear_compat_flags +( + lsmash_file_t *file +) +{ + /* Clear flags for compatibility. */ + memset( (int8_t *)file + COMPAT_FLAGS_OFFSET, 0, sizeof(lsmash_file_t) - COMPAT_FLAGS_OFFSET ); + file->min_isom_version = UINT8_MAX; /* undefined value */ +} + +int isom_check_compatibility +( + lsmash_file_t *file +) +{ + if( LSMASH_IS_NON_EXISTING_BOX( file ) ) + return LSMASH_ERR_FUNCTION_PARAM; + isom_clear_compat_flags( file ); + /* Get the brand container. */ + isom_ftyp_t *ftyp = LSMASH_IS_EXISTING_BOX( file->ftyp ) + ? file->ftyp + : (isom_ftyp_t *)lsmash_list_get_entry_data( &file->styp_list, 1 ); + /* Check brand to decide mandatory boxes. */ + if( LSMASH_IS_NON_EXISTING_BOX( ftyp ) ) + { + /* No brand declaration means this file is a MP4 version 1 or QuickTime file format. */ + if( LSMASH_IS_EXISTING_BOX( file->moov->iods ) ) + { + file->mp4_version1 = 1; + file->isom_compatible = 1; + } + else + { + file->qt_compatible = 1; + file->undefined_64_ver = 1; + } + return 0; + } + for( uint32_t i = 0; i <= ftyp->brand_count; i++ ) + { + uint32_t brand = (i == ftyp->brand_count ? ftyp->major_brand : ftyp->compatible_brands[i]); + switch( brand ) + { + case ISOM_BRAND_TYPE_QT : + file->qt_compatible = 1; + break; + case ISOM_BRAND_TYPE_MP41 : + file->mp4_version1 = 1; + break; + case ISOM_BRAND_TYPE_MP42 : + file->mp4_version2 = 1; + break; + case ISOM_BRAND_TYPE_AVC1 : + case ISOM_BRAND_TYPE_ISOM : + file->max_isom_version = LSMASH_MAX( file->max_isom_version, 1 ); + file->min_isom_version = LSMASH_MIN( file->min_isom_version, 1 ); + break; + case ISOM_BRAND_TYPE_ISO2 : + file->max_isom_version = LSMASH_MAX( file->max_isom_version, 2 ); + file->min_isom_version = LSMASH_MIN( file->min_isom_version, 2 ); + break; + case ISOM_BRAND_TYPE_ISO3 : + file->max_isom_version = LSMASH_MAX( file->max_isom_version, 3 ); + file->min_isom_version = LSMASH_MIN( file->min_isom_version, 3 ); + break; + case ISOM_BRAND_TYPE_ISO4 : + file->max_isom_version = LSMASH_MAX( file->max_isom_version, 4 ); + file->min_isom_version = LSMASH_MIN( file->min_isom_version, 4 ); + break; + case ISOM_BRAND_TYPE_ISO5 : + file->max_isom_version = LSMASH_MAX( file->max_isom_version, 5 ); + file->min_isom_version = LSMASH_MIN( file->min_isom_version, 5 ); + break; + case ISOM_BRAND_TYPE_ISO6 : + file->max_isom_version = LSMASH_MAX( file->max_isom_version, 6 ); + file->min_isom_version = LSMASH_MIN( file->min_isom_version, 6 ); + break; + case ISOM_BRAND_TYPE_ISO7 : + file->max_isom_version = LSMASH_MAX( file->max_isom_version, 7 ); + file->min_isom_version = LSMASH_MIN( file->min_isom_version, 7 ); + break; + case ISOM_BRAND_TYPE_M4A : + case ISOM_BRAND_TYPE_M4B : + case ISOM_BRAND_TYPE_M4P : + case ISOM_BRAND_TYPE_M4V : + file->itunes_movie = 1; + break; + case ISOM_BRAND_TYPE_3GP4 : + file->max_3gpp_version = LSMASH_MAX( file->max_3gpp_version, 4 ); + break; + case ISOM_BRAND_TYPE_3GP5 : + file->max_3gpp_version = LSMASH_MAX( file->max_3gpp_version, 5 ); + break; + case ISOM_BRAND_TYPE_3GE6 : + case ISOM_BRAND_TYPE_3GG6 : + case ISOM_BRAND_TYPE_3GP6 : + case ISOM_BRAND_TYPE_3GR6 : + case ISOM_BRAND_TYPE_3GS6 : + file->max_3gpp_version = LSMASH_MAX( file->max_3gpp_version, 6 ); + break; + case ISOM_BRAND_TYPE_3GP7 : + file->max_3gpp_version = LSMASH_MAX( file->max_3gpp_version, 7 ); + break; + case ISOM_BRAND_TYPE_3GP8 : + file->max_3gpp_version = LSMASH_MAX( file->max_3gpp_version, 8 ); + break; + case ISOM_BRAND_TYPE_3GE9 : + case ISOM_BRAND_TYPE_3GF9 : + case ISOM_BRAND_TYPE_3GG9 : + case ISOM_BRAND_TYPE_3GH9 : + case ISOM_BRAND_TYPE_3GM9 : + case ISOM_BRAND_TYPE_3GP9 : + case ISOM_BRAND_TYPE_3GR9 : + case ISOM_BRAND_TYPE_3GS9 : + case ISOM_BRAND_TYPE_3GT9 : + file->max_3gpp_version = LSMASH_MAX( file->max_3gpp_version, 9 ); + break; + default : + break; + } + switch( brand ) + { + case ISOM_BRAND_TYPE_AVC1 : + case ISOM_BRAND_TYPE_ISO2 : + case ISOM_BRAND_TYPE_ISO3 : + case ISOM_BRAND_TYPE_ISO4 : + case ISOM_BRAND_TYPE_ISO5 : + case ISOM_BRAND_TYPE_ISO6 : + file->avc_extensions = 1; + break; + case ISOM_BRAND_TYPE_3GP4 : + case ISOM_BRAND_TYPE_3GP5 : + case ISOM_BRAND_TYPE_3GP6 : + case ISOM_BRAND_TYPE_3GP7 : + case ISOM_BRAND_TYPE_3GP8 : + case ISOM_BRAND_TYPE_3GP9 : + file->forbid_tref = 1; + break; + case ISOM_BRAND_TYPE_3GH9 : + case ISOM_BRAND_TYPE_3GM9 : + case ISOM_BRAND_TYPE_DASH : + case ISOM_BRAND_TYPE_DSMS : + case ISOM_BRAND_TYPE_LMSG : + case ISOM_BRAND_TYPE_MSDH : + case ISOM_BRAND_TYPE_MSIX : + case ISOM_BRAND_TYPE_SIMS : + file->media_segment = 1; + break; + default : + break; + } + } + file->isom_compatible = !file->qt_compatible + || file->mp4_version1 + || file->mp4_version2 + || file->itunes_movie + || file->max_3gpp_version; + file->undefined_64_ver = file->qt_compatible || file->itunes_movie; + if( file->flags & LSMASH_FILE_MODE_WRITE ) + { + /* Media Segment is incompatible with ISO Base Media File Format version 4 or former must be compatible with + * version 6 or later since it requires default-base-is-moof and Track Fragment Base Media Decode Time Box. */ + if( file->media_segment && (file->min_isom_version < 5 || (file->max_isom_version && file->max_isom_version < 6)) ) + return LSMASH_ERR_INVALID_DATA; + file->allow_moof_base = (file->max_isom_version >= 5 && file->min_isom_version >= 5) + || (file->max_isom_version == 0 && file->min_isom_version == UINT8_MAX && file->media_segment); + } + return 0; +} + +int isom_check_mandatory_boxes +( + lsmash_file_t *file +) +{ + assert( LSMASH_IS_EXISTING_BOX( file ) ); + /* A movie requires at least one track. */ + if( !file->moov->trak_list.head ) + return LSMASH_ERR_INVALID_DATA; + for( lsmash_entry_t *entry = file->moov->trak_list.head; entry; entry = entry->next ) + { + isom_trak_t *trak = (isom_trak_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( trak ) + || LSMASH_IS_NON_EXISTING_BOX( trak->tkhd ) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->mdhd ) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->hdlr ) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->minf->dinf->dref ) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->minf->stbl->stsd ) + || (LSMASH_IS_NON_EXISTING_BOX( trak->mdia->minf->stbl->stsz ) + && LSMASH_IS_NON_EXISTING_BOX( trak->mdia->minf->stbl->stz2 )) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->minf->stbl->stts ) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->minf->stbl->stsc ) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->minf->stbl->stco ) ) + return LSMASH_ERR_INVALID_DATA; + if( file->qt_compatible && LSMASH_IS_NON_EXISTING_BOX( trak->mdia->minf->hdlr ) ) + return LSMASH_ERR_INVALID_DATA; + isom_stbl_t *stbl = trak->mdia->minf->stbl; + if( !stbl->stsd->list.head ) + return LSMASH_ERR_INVALID_DATA; + if( !file->fragment + && (!stbl->stsd->list.head + || !stbl->stts->list || !stbl->stts->list->head + || !stbl->stsc->list || !stbl->stsc->list->head + || !stbl->stco->list || !stbl->stco->list->head) ) + return LSMASH_ERR_INVALID_DATA; + } + if( !file->fragment ) + return 0; + if( LSMASH_IS_NON_EXISTING_BOX( file->moov->mvex ) ) + return LSMASH_ERR_INVALID_DATA; + for( lsmash_entry_t *entry = file->moov->mvex->trex_list.head; entry; entry = entry->next ) + if( LSMASH_IS_NON_EXISTING_BOX( (isom_trex_t *)entry->data ) ) + return LSMASH_ERR_INVALID_DATA; + return 0; +} + +int isom_rearrange_data +( + lsmash_file_t *file, + lsmash_adhoc_remux_t *remux, + uint8_t *buf[2], + size_t read_num, + size_t size, + uint64_t read_pos, + uint64_t write_pos, + uint64_t file_size +) +{ + assert( remux ); + /* Copy-pastan */ + int buf_switch = 1; + lsmash_bs_t *bs = file->bs; + int ret; + int64_t ret64; + while( read_num == size ) + { + ret64 = lsmash_bs_write_seek( bs, read_pos, SEEK_SET ); + if( ret64 < 0 ) + return ret64; + ret = lsmash_bs_read_data( bs, buf[buf_switch], &read_num ); + if( ret < 0 ) + return ret; + read_pos = bs->offset; + buf_switch ^= 0x1; + ret64 = lsmash_bs_write_seek( bs, write_pos, SEEK_SET ); + if( ret64 < 0 ) + return ret64; + ret = lsmash_bs_write_data( bs, buf[buf_switch], size ); + if( ret < 0 ) + return ret; + write_pos = bs->offset; + if( remux->func ) + remux->func( remux->param, write_pos, file_size ); // FIXME: + } + ret = lsmash_bs_write_data( bs, buf[buf_switch ^ 0x1], read_num ); + if( ret < 0 ) + return ret; + if( remux->func ) + remux->func( remux->param, file_size, file_size ); // FIXME: + return 0; +} + +static int isom_set_brands +( + lsmash_file_t *file, + lsmash_brand_type major_brand, + uint32_t minor_version, + lsmash_brand_type *brands, + uint32_t brand_count +) +{ + if( brand_count > 50 ) + return LSMASH_ERR_FUNCTION_PARAM; /* We support setting brands up to 50. */ + if( major_brand == 0 && (!brands || brand_count == 0 || brands[0] == 0) ) + { + if( file->flags & LSMASH_FILE_MODE_INITIALIZATION ) + { + /* Absence of File Type Box means this file is a QuickTime or MP4 version 1 format file. */ + isom_remove_box_by_itself( file->ftyp ); + /* Anyway we use QTFF as a default file format. */ + isom_clear_compat_flags( file ); + file->qt_compatible = 1; + } + else + { + /* The absence of the Segment Type Box is allowed. + * We set brands from the initialization segment after switching to this segment. */ + for( lsmash_entry_t *entry = file->styp_list.head; entry; entry = entry->next ) + isom_remove_box_by_itself( entry->data ); + if( LSMASH_IS_EXISTING_BOX( file->initializer ) ) + { + /* Copy flags for compatibility. */ + memcpy( (int8_t *)file + COMPAT_FLAGS_OFFSET, file->initializer, sizeof(lsmash_file_t) - COMPAT_FLAGS_OFFSET ); + file->isom_compatible = 1; + file->allow_moof_base = 1; + file->media_segment = 1; + if( file->min_isom_version < 5 ) + file->min_isom_version = 5; + if( file->max_isom_version < 6 ) + file->max_isom_version = 6; + } + } + return 0; + } + else if( major_brand == 0 ) + { + major_brand = brands[0]; + lsmash_log( NULL, LSMASH_LOG_WARNING, + "major_brand is not specified. Use the first brand in the compatible brand list as major_brand.\n" ); + } + else if( !brands ) + brand_count = 0; + isom_ftyp_t *ftyp; + if( file->flags & LSMASH_FILE_MODE_INITIALIZATION ) + { + /* Add File Type Box if absent yet. */ + if( LSMASH_IS_NON_EXISTING_BOX( file->ftyp ) && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_ftyp( file ) ) ) + return LSMASH_ERR_NAMELESS; + ftyp = file->ftyp; + } + else + { + /* Add Segment Type Box if absent yet. */ + ftyp = file->styp_list.head && LSMASH_IS_EXISTING_BOX( (isom_styp_t *)file->styp_list.head->data ) + ? (isom_styp_t *)file->styp_list.head->data + : isom_add_styp( file ); + if( LSMASH_IS_NON_EXISTING_BOX( ftyp ) ) + return LSMASH_ERR_NAMELESS; + } + /* Allocate an array of compatible brands. + * ISO/IEC 14496-12 doesn't forbid the absence of brands in the compatible brand list. + * For a reason of safety, however, we set at least one brand in the list. */ + size_t alloc_size = (brand_count ? brand_count : 1) * sizeof(uint32_t); + lsmash_brand_type *compatible_brands; + if( !file->compatible_brands ) + compatible_brands = lsmash_malloc( alloc_size ); + else + compatible_brands = lsmash_realloc( file->compatible_brands, alloc_size ); + if( !compatible_brands ) + return LSMASH_ERR_MEMORY_ALLOC; + /* Set compatible brands. */ + if( brand_count ) + for( uint32_t i = 0; i < brand_count; i++ ) + compatible_brands[i] = brands[i]; + else + { + /* At least one compatible brand. */ + compatible_brands[0] = major_brand; + brand_count = 1; + } + file->compatible_brands = compatible_brands; + /* Duplicate an array of compatible brands. */ + lsmash_free( ftyp->compatible_brands ); + ftyp->compatible_brands = lsmash_memdup( compatible_brands, alloc_size ); + if( !ftyp->compatible_brands ) + { + lsmash_freep( &file->compatible_brands ); + return LSMASH_ERR_MEMORY_ALLOC; + } + ftyp->size = ISOM_BASEBOX_COMMON_SIZE + 8 + brand_count * 4; + ftyp->major_brand = major_brand; + ftyp->minor_version = minor_version; + ftyp->brand_count = brand_count; + file->brand_count = brand_count; + return isom_check_compatibility( file ); +} + +/*---- default I/O ----*/ +typedef struct +{ + FILE *file_ptr; + int is_standard_stream; /* If set to 1, 'file_ptr' points to standard stream (i.e. stdin, stdout or stderr). + * This flag prevents from accidentally closing standard streams. */ + lsmash_file_mode file_mode; +} default_io_stream_t; + +static default_io_stream_t *default_io_stream_open( const char *filename, int open_mode ) +{ +#ifdef _WIN32 + _setmode( _fileno( stdin ), _O_BINARY ); + _setmode( _fileno( stdout ), _O_BINARY ); + _setmode( _fileno( stderr ), _O_BINARY ); +#endif + default_io_stream_t *stream = (default_io_stream_t *)lsmash_malloc_zero( sizeof(default_io_stream_t) ); + if( !stream ) + return NULL; + char mode[4] = { 0 }; + if( open_mode == 0 ) + { + memcpy( mode, "w+b", 4 ); + stream->file_mode = LSMASH_FILE_MODE_WRITE + | LSMASH_FILE_MODE_BOX + | LSMASH_FILE_MODE_INITIALIZATION + | LSMASH_FILE_MODE_MEDIA; + } + else if( open_mode == 1 ) + { + memcpy( mode, "rb", 3 ); + stream->file_mode = LSMASH_FILE_MODE_READ; + } + else + assert( 0 ); + if( !strcmp( filename, "-" ) ) + { + if( stream->file_mode & LSMASH_FILE_MODE_READ ) + { + stream->file_ptr = stdin; + stream->is_standard_stream = 1; + } + else if( stream->file_mode & LSMASH_FILE_MODE_WRITE ) + { + stream->file_ptr = stdout; + stream->is_standard_stream = 1; + stream->file_mode |= LSMASH_FILE_MODE_FRAGMENTED; + } + } + else + stream->file_ptr = lsmash_fopen( filename, mode ); + if( stream->file_ptr == NULL ) + lsmash_freep( &stream ); + return stream; +} + +static int default_io_stream_close( default_io_stream_t *stream ) +{ + if( !stream ) + return 0; + int ret = stream->is_standard_stream ? 0 : fclose( stream->file_ptr ); + lsmash_free( stream ); + return ret; +} + +static int default_io_stream_read( void *opaque, uint8_t *buf, int size ) +{ + int read_size = fread( buf, 1, size, ((default_io_stream_t *)opaque)->file_ptr ); + return ferror( ((default_io_stream_t *)opaque)->file_ptr ) ? LSMASH_ERR_NAMELESS : read_size; +} + +static int default_io_stream_write( void *opaque, uint8_t *buf, int size ) +{ + return fwrite( buf, 1, size, ((default_io_stream_t *)opaque)->file_ptr ); +} + +static int64_t default_io_stream_seek( void *opaque, int64_t offset, int whence ) +{ + if( lsmash_fseek( ((default_io_stream_t *)opaque)->file_ptr, offset, whence ) != 0 ) + return LSMASH_ERR_NAMELESS; + return lsmash_ftell( ((default_io_stream_t *)opaque)->file_ptr ); +} + +/******************************* + public interfaces +*******************************/ + +void lsmash_discard_boxes +( + lsmash_root_t *root +) +{ + if( LSMASH_IS_NON_EXISTING_BOX( root ) + || LSMASH_IS_NON_EXISTING_BOX( root->file ) ) + return; + isom_remove_all_extension_boxes( &root->file->extensions ); +} + +int lsmash_open_file +( + const char *filename, + int open_mode, + lsmash_file_parameters_t *param +) +{ + if( !filename || !param || (open_mode != 0 && open_mode != 1) ) + return LSMASH_ERR_FUNCTION_PARAM; + default_io_stream_t *stream = default_io_stream_open( filename, open_mode ); + if( !stream ) + return LSMASH_ERR_NAMELESS; + memset( param, 0, sizeof(lsmash_file_parameters_t) ); + param->mode = stream->file_mode; + param->opaque = (void *)stream; + param->read = default_io_stream_read; + param->write = default_io_stream_write; + param->seek = stream->is_standard_stream ? NULL : default_io_stream_seek; + param->major_brand = 0; + param->brands = NULL; + param->brand_count = 0; + param->minor_version = 0; + param->max_chunk_duration = 0.5; + param->max_async_tolerance = 2.0; + param->max_chunk_size = 4 * 1024 * 1024; + param->max_read_size = 4 * 1024 * 1024; + return 0; +} + +int lsmash_close_file +( + lsmash_file_parameters_t *param +) +{ + if( !param ) + return LSMASH_ERR_NAMELESS; + int ret = default_io_stream_close( (default_io_stream_t *)param->opaque ); + param->opaque = NULL; + return ret == 0 ? 0 : LSMASH_ERR_UNKNOWN; +} + +lsmash_file_t *lsmash_set_file +( + lsmash_root_t *root, + lsmash_file_parameters_t *param +) +{ + if( LSMASH_IS_NON_EXISTING_BOX( root ) || !param ) + return NULL; + lsmash_file_t *file = isom_add_file_abstract( root ); + if( LSMASH_IS_NON_EXISTING_BOX( file ) ) + return NULL; + lsmash_bs_t *bs = lsmash_bs_create(); + if( !bs ) + goto fail; + file->bs = bs; + file->flags = param->mode; + file->bs->stream = param->opaque; + file->bs->read = param->read; + file->bs->write = param->write; + file->bs->seek = param->seek; + file->bs->unseekable = (param->seek == NULL); + file->bs->buffer.max_size = param->max_read_size; + file->max_chunk_duration = param->max_chunk_duration; + file->max_async_tolerance = LSMASH_MAX( param->max_async_tolerance, 2 * param->max_chunk_duration ); + file->max_chunk_size = param->max_chunk_size; + if( (file->flags & LSMASH_FILE_MODE_WRITE) + && (file->flags & LSMASH_FILE_MODE_BOX) ) + { + /* Construction of Segment Index Box requires seekability at our current implementation. + * If segment is not so large, data rearrangement can be avoided by buffering i.e. the + * seekability is not essential, but at present we don't support buffering of all materials + * within segment. */ + if( (file->flags & LSMASH_FILE_MODE_INDEX) && file->bs->unseekable ) + goto fail; + /* Establish the fragment handler if required. */ + if( file->flags & LSMASH_FILE_MODE_FRAGMENTED ) + { + file->fragment = lsmash_malloc_zero( sizeof(isom_fragment_manager_t) ); + if( !file->fragment ) + goto fail; + file->fragment->first_moof_pos = FIRST_MOOF_POS_UNDETERMINED; + file->fragment->pool = lsmash_list_create( isom_remove_sample_pool ); + if( !file->fragment->pool ) + goto fail; + } + else if( file->bs->unseekable ) + /* For unseekable output operations, LSMASH_FILE_MODE_FRAGMENTED shall be set. */ + goto fail; + /* Establish file types. */ + if( isom_set_brands( file, param->major_brand, + param->minor_version, + param->brands, param->brand_count ) < 0 ) + goto fail; + /* Create the movie header if the initialization of the streams is required. */ + if( (file->flags & LSMASH_FILE_MODE_INITIALIZATION) && !isom_movie_create( file ) ) + goto fail; + } + if( LSMASH_IS_NON_EXISTING_BOX( root->file ) ) + root->file = file; + return file; +fail: + isom_remove_box_by_itself( file ); + return NULL; +} + +int64_t lsmash_read_file +( + lsmash_file_t *file, + lsmash_file_parameters_t *param +) +{ + if( LSMASH_IS_NON_EXISTING_BOX( file ) ) + return (int64_t)LSMASH_ERR_FUNCTION_PARAM; + if( !file->bs ) + return (int64_t)LSMASH_ERR_NAMELESS; + int64_t ret = LSMASH_ERR_NAMELESS; + if( file->flags & (LSMASH_FILE_MODE_READ | LSMASH_FILE_MODE_DUMP) ) + { + importer_t *importer = lsmash_importer_alloc( file->root ); + if( !importer ) + return (int64_t)LSMASH_ERR_MEMORY_ALLOC; + lsmash_importer_set_file( importer, file ); + ret = lsmash_importer_find( importer, "ISOBMFF/QTFF", !file->bs->unseekable ); + if( ret < 0 ) + return ret; + if( param ) + { + if( LSMASH_IS_EXISTING_BOX( file->ftyp ) ) + { + /* file types */ + isom_ftyp_t *ftyp = file->ftyp; + param->major_brand = ftyp->major_brand ? ftyp->major_brand : ISOM_BRAND_TYPE_QT; + param->minor_version = ftyp->minor_version; + param->brands = file->compatible_brands; + param->brand_count = file->brand_count; + } + else if( file->styp_list.head && LSMASH_IS_EXISTING_BOX( (isom_styp_t *)file->styp_list.head->data ) ) + { + /* segment types */ + isom_styp_t *styp = (isom_styp_t *)file->styp_list.head->data; + param->major_brand = styp->major_brand ? styp->major_brand : ISOM_BRAND_TYPE_QT; + param->minor_version = styp->minor_version; + param->brands = file->compatible_brands; + param->brand_count = file->brand_count; + } + else + { + param->major_brand = file->mp4_version1 ? ISOM_BRAND_TYPE_MP41 : ISOM_BRAND_TYPE_QT; + param->minor_version = 0; + param->brands = NULL; + param->brand_count = 0; + } + } + } + return ret; +} + +int lsmash_activate_file +( + lsmash_root_t *root, + lsmash_file_t *file +) +{ + if( !root || !file || file->root != root ) + return LSMASH_ERR_FUNCTION_PARAM; + root->file = file; + return 0; +} + +int lsmash_switch_media_segment +( + lsmash_root_t *root, + lsmash_file_t *successor, + lsmash_adhoc_remux_t *remux +) +{ + if( LSMASH_IS_NON_EXISTING_BOX( root ) || !remux ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_file_t *predecessor = root->file; + if( LSMASH_IS_NON_EXISTING_BOX( predecessor ) || LSMASH_IS_NON_EXISTING_BOX( successor ) + || predecessor == successor + || predecessor->root != successor->root + || LSMASH_IS_NON_EXISTING_BOX( predecessor->root ) || LSMASH_IS_NON_EXISTING_BOX( successor->root ) + || predecessor->root != root || successor->root != root + || (successor->flags & LSMASH_FILE_MODE_INITIALIZATION) + || !(successor->flags & LSMASH_FILE_MODE_MEDIA) + || !(predecessor->flags & LSMASH_FILE_MODE_WRITE) || !(successor->flags & LSMASH_FILE_MODE_WRITE) + || !(predecessor->flags & LSMASH_FILE_MODE_BOX) || !(successor->flags & LSMASH_FILE_MODE_BOX) + || !(predecessor->flags & LSMASH_FILE_MODE_FRAGMENTED) || !(successor->flags & LSMASH_FILE_MODE_FRAGMENTED) + || !(predecessor->flags & LSMASH_FILE_MODE_SEGMENT) || !(successor->flags & LSMASH_FILE_MODE_SEGMENT) + || (!(predecessor->flags & LSMASH_FILE_MODE_MEDIA) && !(predecessor->flags & LSMASH_FILE_MODE_INITIALIZATION)) ) + return LSMASH_ERR_FUNCTION_PARAM; + int ret = isom_finish_final_fragment_movie( predecessor, remux ); + if( ret < 0 ) + return ret; + if( predecessor->flags & LSMASH_FILE_MODE_INITIALIZATION ) + { + if( predecessor->initializer != predecessor ) + return LSMASH_ERR_INVALID_DATA; + successor->initializer = predecessor; + } + else + successor->initializer = predecessor->initializer; + isom_styp_t *styp = (isom_styp_t *)lsmash_list_get_entry_data( &successor->styp_list, 1 ); + if( LSMASH_IS_NON_EXISTING_BOX( styp ) ) + { + ret = isom_set_brands( successor, 0, 0, NULL, 0 ); + if( ret < 0 ) + return LSMASH_ERR_NAMELESS; + } + successor->fragment_count = predecessor->fragment_count; + root->file = successor; + return 0; +} diff --git a/3rdparty/l-smash/core/file.h b/3rdparty/l-smash/core/file.h new file mode 100644 index 000000000..19f5be24c --- /dev/null +++ b/3rdparty/l-smash/core/file.h @@ -0,0 +1,43 @@ +/***************************************************************************** + * file.h + ***************************************************************************** + * Copyright (C) 2014-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +int isom_check_compatibility +( + lsmash_file_t *file +); + +int isom_check_mandatory_boxes +( + lsmash_file_t *file +); + +int isom_rearrange_data +( + lsmash_file_t *file, + lsmash_adhoc_remux_t *remux, + uint8_t *buf[2], + size_t read_num, + size_t size, + uint64_t read_pos, + uint64_t write_pos, + uint64_t file_size +); diff --git a/3rdparty/l-smash/core/fragment.c b/3rdparty/l-smash/core/fragment.c new file mode 100644 index 000000000..9bdf31e7d --- /dev/null +++ b/3rdparty/l-smash/core/fragment.c @@ -0,0 +1,1783 @@ +/***************************************************************************** + * fragment.c + ***************************************************************************** + * Copyright (C) 2011-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include +#include "box.h" +#include "box_default.h" +#include "file.h" +#include "write.h" +#include "fragment.h" + +static isom_sidx_t *isom_get_sidx( lsmash_file_t *file, uint32_t reference_ID ) +{ + if( reference_ID == 0 || !file ) + return isom_non_existing_sidx(); + for( lsmash_entry_t *entry = file->sidx_list.head; entry; entry = entry->next ) + { + isom_sidx_t *sidx = (isom_sidx_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( sidx ) ) + return isom_non_existing_sidx(); + if( sidx->reference_ID == reference_ID ) + return sidx; + } + return isom_non_existing_sidx(); +} + +static int isom_finish_fragment_movie( lsmash_file_t *file ); + +/* A movie fragment cannot switch a sample description to another. + * So you must call this function before switching sample descriptions. */ +int lsmash_create_fragment_movie( lsmash_root_t *root ) +{ + if( isom_check_initializer_present( root ) < 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_file_t *file = root->file; + if( !file->bs + || !file->fragment ) + return LSMASH_ERR_NAMELESS; + /* Finish and write the current movie fragment before starting a new one. */ + int ret = isom_finish_fragment_movie( file ); + if( ret < 0 ) + return ret; + /* Add a new movie fragment if the current one is not present or not written. */ + isom_moof_t *moof = file->fragment->movie; + if( LSMASH_IS_NON_EXISTING_BOX( moof ) || (moof->manager & LSMASH_WRITTEN_BOX) ) + { + /* We always hold only one movie fragment except for the initial movie (a pair of moov and mdat). */ + if( LSMASH_IS_EXISTING_BOX( moof ) + && file->moof_list.entry_count != 1 ) + return LSMASH_ERR_NAMELESS; + moof = isom_add_moof( file ); + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_mfhd( moof ) ) ) + return LSMASH_ERR_NAMELESS; + file->fragment->movie = moof; + moof->mfhd->sequence_number = ++ file->fragment_count; + if( file->moof_list.entry_count == 1 ) + return 0; + /* Remove the previous movie fragment. */ + if( file->moof_list.head ) + isom_remove_box_by_itself( file->moof_list.head->data ); + } + return 0; +} + +static inline uint64_t isom_fragment_get_implicit_segment_duration +( + isom_cache_t *cache +) +{ + return cache->fragment->largest_cts != LSMASH_TIMESTAMP_UNDEFINED + ? cache->fragment->largest_cts + cache->fragment->last_duration + : 0; +} + +static int isom_set_fragment_overall_duration( lsmash_file_t *file ) +{ + assert( file == file->initializer ); + /* Get the longest duration of the tracks. */ + uint64_t longest_duration = 0; + for( lsmash_entry_t *entry = file->moov->trak_list.head; entry; entry = entry->next ) + { + isom_trak_t *trak = (isom_trak_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( trak ) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->mdhd ) + || !trak->cache + || !trak->cache->fragment + || trak->mdia->mdhd->timescale == 0 ) + return LSMASH_ERR_NAMELESS; + uint64_t duration; + uint64_t implicit_segment_duration = isom_fragment_get_implicit_segment_duration( trak->cache ); + if( !trak->edts->elst->list ) + duration = (uint64_t)(((double)implicit_segment_duration / trak->mdia->mdhd->timescale) * file->moov->mvhd->timescale); + else + { + duration = 0; + for( lsmash_entry_t *elst_entry = trak->edts->elst->list->head; elst_entry; elst_entry = elst_entry->next ) + { + isom_elst_entry_t *data = (isom_elst_entry_t *)elst_entry->data; + if( !data ) + return LSMASH_ERR_NAMELESS; + if( data->segment_duration == ISOM_EDIT_DURATION_IMPLICIT ) + duration += (uint64_t)(((double)implicit_segment_duration / trak->mdia->mdhd->timescale) * file->moov->mvhd->timescale); + else + duration += data->segment_duration; + } + } + longest_duration = LSMASH_MAX( duration, longest_duration ); + } + isom_mehd_t *mehd = file->moov->mvex->mehd; + mehd->fragment_duration = longest_duration; + mehd->version = 1; + mehd->manager &= ~(LSMASH_PLACEHOLDER | LSMASH_WRITTEN_BOX); /* Update per media segment. */ + isom_update_box_size( mehd ); + /* Write Movie Extends Header Box here. */ + lsmash_bs_t *bs = file->bs; + uint64_t current_pos = bs->offset; + lsmash_bs_write_seek( bs, mehd->pos, SEEK_SET ); + int ret = isom_write_box( bs, (isom_box_t *)mehd ); + lsmash_bs_write_seek( bs, current_pos, SEEK_SET ); + return ret; +} + +static int isom_write_fragment_random_access_info( lsmash_file_t *file ) +{ + assert( file == file->initializer ); + if( LSMASH_IS_NON_EXISTING_BOX( file->mfra ) ) + return 0; + if( LSMASH_IS_NON_EXISTING_BOX( file->moov->mvex ) ) + return LSMASH_ERR_NAMELESS; + /* Reconstruct the Movie Fragment Random Access Box. + * All 'time' field in the Track Fragment Random Access Boxes shall reflect edit list. */ + uint32_t movie_timescale = lsmash_get_movie_timescale( file->root ); + if( movie_timescale == 0 ) + return LSMASH_ERR_NAMELESS; /* Division by zero will occur. */ + for( lsmash_entry_t *trex_entry = file->moov->mvex->trex_list.head; trex_entry; trex_entry = trex_entry->next ) + { + isom_trex_t *trex = (isom_trex_t *)trex_entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( trex ) ) + return LSMASH_ERR_NAMELESS; + /* Get the edit list of the track associated with the trex->track_ID. + * If failed or absent, implicit timeline mapping edit is used, and skip this operation for the track. */ + isom_trak_t *trak = isom_get_trak( file, trex->track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak ) ) + return LSMASH_ERR_NAMELESS; + if( !trak->edts->elst->list + || !trak->edts->elst->list->head + || !trak->edts->elst->list->head->data ) + continue; + isom_elst_t *elst = trak->edts->elst; + /* Get the Track Fragment Random Access Boxes of the track associated with the trex->track_ID. + * If failed or absent, skip reconstructing the Track Fragment Random Access Box of the track. */ + isom_tfra_t *tfra = isom_get_tfra( file->mfra, trex->track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( tfra ) ) + continue; + /* Reconstruct the Track Fragment Random Access Box. */ + lsmash_entry_t *edit_entry = elst->list->head; + isom_elst_entry_t *edit = edit_entry->data; + uint64_t edit_offset = 0; /* units in media timescale */ + uint32_t media_timescale = lsmash_get_media_timescale( file->root, trex->track_ID ); + for( lsmash_entry_t *rap_entry = tfra->list->head; rap_entry; ) + { + isom_tfra_location_time_entry_t *rap = (isom_tfra_location_time_entry_t *)rap_entry->data; + if( !rap ) + { + /* Irregular case. Drop this entry. */ + lsmash_entry_t *next = rap_entry->next; + lsmash_list_remove_entry_direct( tfra->list, rap_entry ); + rap_entry = next; + continue; + } + uint64_t composition_time = rap->time; + uint64_t implicit_segment_duration = isom_fragment_get_implicit_segment_duration( trak->cache ); + /* Skip edits that doesn't need the current sync sample indicated in the Track Fragment Random Access Box. */ + while( edit ) + { + uint64_t segment_duration = edit->segment_duration == ISOM_EDIT_DURATION_IMPLICIT + ? implicit_segment_duration + : ((edit->segment_duration - 1) / movie_timescale + 1) * media_timescale; + if( edit->media_time != ISOM_EDIT_MODE_EMPTY + && composition_time < edit->media_time + segment_duration ) + break; /* This Timeline Mapping Edit might require the current sync sample. + * Note: this condition doesn't cover all cases. + * For instance, matching the both following conditions + * 1. A sync sample isn't in the presentation. + * 2. The other samples, which precede it in the composition timeline, is in the presentation. */ + edit_offset += segment_duration; + edit_entry = edit_entry->next; + if( !edit_entry ) + { + /* No more presentation. */ + edit = NULL; + break; + } + edit = edit_entry->data; + } + if( !edit ) + { + /* No more presentation. + * Drop the rest of sync samples since they are generally absent in the whole presentation. + * Though the exceptions are sync samples with earlier composition time, we ignore them. (SAP type 2: TEPT = TDEC = TSAP < TPTF) + * To support this exception, we need sorting entries of the list by composition times. */ + while( rap_entry ) + { + lsmash_entry_t *next = rap_entry->next; + lsmash_list_remove_entry_direct( tfra->list, rap_entry ); + rap_entry = next; + } + break; + } + /* If the sync sample isn't in the presentation, + * we pick the earliest presentation time of the current edit as its presentation time. */ + rap->time = edit_offset; + if( composition_time >= edit->media_time ) + rap->time += composition_time - edit->media_time; + rap_entry = rap_entry->next; + } + tfra->number_of_entry = tfra->list->entry_count; + } + /* Decide the size of the Movie Fragment Random Access Box. */ + if( isom_update_box_size( file->mfra ) == 0 ) + return LSMASH_ERR_NAMELESS; + /* Write the Movie Fragment Random Access Box. */ + return isom_write_box( file->bs, (isom_box_t *)file->mfra ); +} + +static int isom_update_indexed_material_offset +( + lsmash_file_t *file, + isom_sidx_t *last_sidx +) +{ + /* Update the size of each Segment Index Box. */ + for( lsmash_entry_t *entry = file->sidx_list.head; entry; entry = entry->next ) + { + isom_sidx_t *sidx = (isom_sidx_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( sidx ) ) + continue; + if( isom_update_box_size( sidx ) == 0 ) + return LSMASH_ERR_NAMELESS; + } + /* first_offset: the sum of the size of subsequent Segment Index Boxes + * Be careful about changing the size of them. */ + last_sidx->first_offset = 0; + for( lsmash_entry_t *a_entry = file->sidx_list.head; a_entry && a_entry->data != last_sidx; a_entry = a_entry->next ) + { + isom_sidx_t *a = (isom_sidx_t *)a_entry->data; + a->first_offset = 0; + for( lsmash_entry_t *b_entry = a_entry->next; b_entry; b_entry = b_entry->next ) + { + isom_sidx_t *b = (isom_sidx_t *)b_entry->data; + a->first_offset += b->size; + } + } + return 0; +} + +static int isom_write_segment_indexes +( + lsmash_file_t *file, + lsmash_adhoc_remux_t *remux +) +{ + /* Update the size of each Segment Index Box and establish the offset from the anchor point to the indexed material. */ + int ret; + if( (ret = isom_update_indexed_material_offset( file, (isom_sidx_t *)file->sidx_list.tail->data )) < 0 ) + return ret; + /* Get the total size of all Segment Index Boxes. */ + uint64_t total_sidx_size = 0; + for( lsmash_entry_t *entry = file->sidx_list.head; entry; entry = entry->next ) + { + isom_sidx_t *sidx = (isom_sidx_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( sidx ) ) + continue; + total_sidx_size += sidx->size; + } + /* The buffer size must be at least total_sidx_size * 2. */ + size_t buffer_size = total_sidx_size * 2; + if( remux->buffer_size > buffer_size ) + buffer_size = remux->buffer_size; + /* Split to 2 buffers. */ + uint8_t *buf[2] = { NULL, NULL }; + if( (buf[0] = (uint8_t *)lsmash_malloc( buffer_size )) == NULL ) + return LSMASH_ERR_MEMORY_ALLOC; + size_t size = buffer_size / 2; + buf[1] = buf[0] + size; + /* Seek to the beginning of the first Movie Fragment Box i.e. the first subsegment within this media segment. */ + lsmash_bs_t *bs = file->bs; + int64_t ret64; + if( (ret64 = lsmash_bs_write_seek( bs, file->fragment->first_moof_pos, SEEK_SET )) < 0 ) + { + ret = ret64; + goto fail; + } + size_t read_num = size; + lsmash_bs_read_data( bs, buf[0], &read_num ); + uint64_t read_pos = bs->offset; + /* Write the Segment Index Boxes actually here. */ + if( (ret64 = lsmash_bs_write_seek( bs, file->fragment->first_moof_pos, SEEK_SET )) < 0 ) + { + ret = ret64; + goto fail; + } + for( lsmash_entry_t *entry = file->sidx_list.head; entry; entry = entry->next ) + { + isom_sidx_t *sidx = (isom_sidx_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( sidx ) ) + continue; + if( (ret = isom_write_box( file->bs, (isom_box_t *)sidx )) < 0 ) + goto fail; + } + /* Rearrange subsequent data. */ + uint64_t write_pos = bs->offset; + uint64_t total = file->size + total_sidx_size; + if( (ret = isom_rearrange_data( file, remux, buf, read_num, size, read_pos, write_pos, total )) < 0 ) + goto fail; + file->size += total_sidx_size; + lsmash_freep( &buf[0] ); + /* Update 'moof_offset' of each entry within the Track Fragment Random Access Boxes. */ + if( file->mfra ) + for( lsmash_entry_t *entry = file->mfra->tfra_list.head; entry; entry = entry->next ) + { + isom_tfra_t *tfra = (isom_tfra_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( tfra ) ) + continue; + for( lsmash_entry_t *rap_entry = tfra->list->head; rap_entry; rap_entry = rap_entry->next ) + { + isom_tfra_location_time_entry_t *rap = (isom_tfra_location_time_entry_t *)rap_entry->data; + if( !rap ) + continue; + rap->moof_offset += total_sidx_size; + } + } + return 0; +fail: + lsmash_free( buf[0] ); + return ret; +} + +int isom_finish_final_fragment_movie +( + lsmash_file_t *file, + lsmash_adhoc_remux_t *remux +) +{ + /* Output the final movie fragment. */ + int ret; + if( (ret = isom_finish_fragment_movie( file )) < 0 ) + return ret; + if( file->bs->unseekable ) + return 0; + /* Write Segment Index Boxes. + * This occurs only when the initial movie has no samples. + * We don't consider updating of chunk offsets within initial movie sample table here. + * This is reasonable since DASH requires no samples in the initial movie. + * This implementation is not suitable for live-streaming. + + To support live-streaming, it is good to use daisy-chained index. */ + if( (file->flags & LSMASH_FILE_MODE_MEDIA) + && (file->flags & LSMASH_FILE_MODE_INDEX) + && (file->flags & LSMASH_FILE_MODE_SEGMENT) ) + { + if( !remux ) + return LSMASH_ERR_FUNCTION_PARAM; + if( (ret = isom_write_segment_indexes( file, remux )) < 0 ) + return ret; + } + /* Write the overall random access information at the tail of the movie if this file is self-contained. */ + if( (ret = isom_write_fragment_random_access_info( file->initializer )) < 0 ) + return ret; + /* Set overall duration of the movie. */ + return isom_set_fragment_overall_duration( file->initializer ); +} + +#define GET_MOST_USED( box_name, index, flag_name ) \ + if( most_used[index] < stats.flag_name[i] ) \ + { \ + most_used[index] = stats.flag_name[i]; \ + box_name->default_sample_flags.flag_name = i; \ + } + +static int isom_create_fragment_overall_default_settings( lsmash_file_t *file ) +{ + assert( file == file->initializer ); + if( !isom_add_mvex( file->moov ) ) + return LSMASH_ERR_NAMELESS; + if( !file->bs->unseekable ) + { + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_mehd( file->moov->mvex ) ) ) + return LSMASH_ERR_NAMELESS; + file->moov->mvex->mehd->manager |= LSMASH_PLACEHOLDER; + } + for( lsmash_entry_t *trak_entry = file->moov->trak_list.head; trak_entry; trak_entry = trak_entry->next ) + { + isom_trak_t *trak = (isom_trak_t *)trak_entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( trak ) + || LSMASH_IS_NON_EXISTING_BOX( trak->tkhd ) + || !trak->cache ) + return LSMASH_ERR_NAMELESS; + isom_stbl_t *stbl = trak->mdia->minf->stbl; + if( !stbl->stts->list + || (stbl->stts->list->tail && !stbl->stts->list->tail->data) + || (LSMASH_IS_NON_EXISTING_BOX( stbl->stsz ) && LSMASH_IS_NON_EXISTING_BOX( stbl->stz2 )) + || (LSMASH_IS_EXISTING_BOX( stbl->stsz ) && stbl->stsz->list && stbl->stsz->list->head && !stbl->stsz->list->head->data) + || (LSMASH_IS_EXISTING_BOX( stbl->stz2 ) && stbl->stz2->list && stbl->stz2->list->head && !stbl->stz2->list->head->data)) + return LSMASH_ERR_NAMELESS; + isom_trex_t *trex = isom_add_trex( file->moov->mvex ); + if( LSMASH_IS_NON_EXISTING_BOX( trex ) ) + return LSMASH_ERR_NAMELESS; + trex->track_ID = trak->tkhd->track_ID; + /* Set up defaults. */ + trex->default_sample_description_index = trak->cache->chunk.sample_description_index + ? trak->cache->chunk.sample_description_index + : 1; + trex->default_sample_duration = stbl->stts->list->tail + ? ((isom_stts_entry_t *)stbl->stts->list->tail->data)->sample_delta + : 1; + trex->default_sample_size = isom_get_first_sample_size( stbl ); + if( stbl->sdtp->list ) + { + struct sample_flags_stats_t + { + uint32_t is_leading [4]; + uint32_t sample_depends_on [4]; + uint32_t sample_is_depended_on[4]; + uint32_t sample_has_redundancy[4]; + } stats = { { 0 }, { 0 }, { 0 }, { 0 } }; + for( lsmash_entry_t *sdtp_entry = stbl->sdtp->list->head; sdtp_entry; sdtp_entry = sdtp_entry->next ) + { + isom_sdtp_entry_t *data = (isom_sdtp_entry_t *)sdtp_entry->data; + if( !data ) + return LSMASH_ERR_NAMELESS; + ++ stats.is_leading [ data->is_leading ]; + ++ stats.sample_depends_on [ data->sample_depends_on ]; + ++ stats.sample_is_depended_on[ data->sample_is_depended_on ]; + ++ stats.sample_has_redundancy[ data->sample_has_redundancy ]; + } + uint32_t most_used[4] = { 0, 0, 0, 0 }; + for( int i = 0; i < 4; i++ ) + { + GET_MOST_USED( trex, 0, is_leading ); + GET_MOST_USED( trex, 1, sample_depends_on ); + GET_MOST_USED( trex, 2, sample_is_depended_on ); + GET_MOST_USED( trex, 3, sample_has_redundancy ); + } + } + trex->default_sample_flags.sample_is_non_sync_sample = !trak->cache->all_sync; + } + return 0; +} + +static int isom_prepare_random_access_info( lsmash_file_t *file ) +{ + assert( file == file->initializer ); + /* Don't write the random access info at the end of the file if unseekable or not self-contained. */ + if( file->bs->unseekable + || !(file->flags & LSMASH_FILE_MODE_BOX) + || !(file->flags & LSMASH_FILE_MODE_INITIALIZATION) + || !(file->flags & LSMASH_FILE_MODE_MEDIA) + || (file->flags & LSMASH_FILE_MODE_SEGMENT) ) + return 0; + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_mfra( file ) ) + || LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_mfro( file->mfra ) ) ) + return LSMASH_ERR_NAMELESS; + return 0; +} + +static int isom_output_fragment_media_data( lsmash_file_t *file ) +{ + isom_fragment_manager_t *frag_manager = file->fragment; + /* If there is no available Media Data Box to write samples, add and write a new one. */ + if( frag_manager->sample_count ) + { + if( LSMASH_IS_NON_EXISTING_BOX( file->mdat ) && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_mdat( file ) ) ) + return LSMASH_ERR_NAMELESS; + file->mdat->manager &= ~(LSMASH_INCOMPLETE_BOX | LSMASH_WRITTEN_BOX); + int ret = isom_write_box( file->bs, (isom_box_t *)file->mdat ); + if( ret < 0 ) + return ret; + file->size += file->mdat->size; + file->mdat->size = 0; + file->mdat->media_size = 0; + } + lsmash_list_remove_entries( frag_manager->pool ); + frag_manager->pool_size = 0; + frag_manager->sample_count = 0; + return 0; +} + +static inline void isom_fragment_reset_sample_counts +( + isom_cache_t *cache +) +{ + cache->fragment->sample_count = 0; + cache->fragment->output_sample_count = 0; +} + +static int isom_finish_fragment_initial_movie( lsmash_file_t *file ) +{ + assert( file == file->initializer ); + if( !file->moov ) + return LSMASH_ERR_NAMELESS; + isom_moov_t *moov = file->moov; + int ret; + for( lsmash_entry_t *entry = moov->trak_list.head; entry; entry = entry->next ) + { + isom_trak_t *trak = (isom_trak_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( trak ) + || LSMASH_IS_NON_EXISTING_BOX( trak->tkhd ) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->mdhd ) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->minf->stbl ) + || !trak->cache ) + return LSMASH_ERR_NAMELESS; + if( (ret = isom_complement_data_reference( trak->mdia->minf )) < 0 ) + return ret; + isom_stbl_t *stbl = trak->mdia->minf->stbl; + if( isom_get_sample_count( trak ) ) + { + /* Compress sample size table. */ + if( stbl->compress_sample_size_table + && (ret = stbl->compress_sample_size_table( stbl )) < 0 ) + return ret; + /* Add stss box if any samples aren't sync sample. */ + if( !trak->cache->all_sync + && LSMASH_IS_NON_EXISTING_BOX( stbl->stss ) + && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_stss( stbl ) ) ) + return LSMASH_ERR_NAMELESS; + if( (ret = isom_update_tkhd_duration( trak )) < 0 ) + return ret; + } + else + trak->tkhd->duration = 0; + if( (ret = isom_update_bitrate_description( trak->mdia )) < 0 ) + return ret; + /* Complete the last sample groups within tracks in the initial movie. */ + if( trak->cache->rap ) + { + isom_sgpd_t *sgpd = isom_get_sample_group_description( stbl, ISOM_GROUP_TYPE_RAP ); + if( LSMASH_IS_NON_EXISTING_BOX( sgpd ) ) + return LSMASH_ERR_NAMELESS; + if( (ret = isom_rap_grouping_established( trak->cache->rap, 1, sgpd, 0 )) < 0 ) + return ret; + lsmash_freep( &trak->cache->rap ); + } + if( trak->cache->roll.pool ) + { + isom_sbgp_t *sbgp = isom_get_roll_recovery_sample_to_group( &stbl->sbgp_list ); + if( LSMASH_IS_NON_EXISTING_BOX( sbgp ) ) + return LSMASH_ERR_NAMELESS; + if( (ret = isom_all_recovery_completed( sbgp, trak->cache->roll.pool )) < 0 ) + return ret; + } + } + if( file->mp4_version1 == 1 && (ret = isom_setup_iods( moov )) < 0 ) + return ret; + if( (ret = isom_create_fragment_overall_default_settings( file )) < 0 + || (ret = isom_prepare_random_access_info ( file )) < 0 + || (ret = isom_establish_movie ( file )) < 0 ) + return ret; + /* stco->co64 conversion, depending on last chunk's offset */ + uint64_t meta_size = LSMASH_IS_EXISTING_BOX( file->meta ) ? file->meta->size : 0; + if( (ret = isom_check_large_offset_requirement( moov, meta_size )) < 0 ) + return ret; + /* Now, the amount of the offset is fixed. apply it to stco/co64 */ + uint64_t preceding_size = moov->size + meta_size; + isom_add_preceding_box_size( moov, preceding_size ); + /* Write File Type Box here if it was not written yet. */ + if( LSMASH_IS_EXISTING_BOX( file->ftyp ) && !(file->ftyp->manager & LSMASH_WRITTEN_BOX) ) + { + if( (ret = isom_write_box( file->bs, (isom_box_t *)file->ftyp )) < 0 ) + return ret; + file->size += file->ftyp->size; + } + /* Write Movie Box. */ + if( (ret = isom_write_box( file->bs, (isom_box_t *)file->moov )) < 0 + || (ret = isom_write_box( file->bs, (isom_box_t *)file->meta )) < 0 ) + return ret; + file->size += preceding_size; + /* Output samples. */ + if( (ret = isom_output_fragment_media_data( file )) < 0 ) + return ret; + /* Revert the number of samples in tracks to 0. */ + for( lsmash_entry_t *entry = moov->trak_list.head; entry; entry = entry->next ) + { + isom_trak_t *trak = (isom_trak_t *)entry->data; + assert( LSMASH_IS_EXISTING_BOX( trak ) ); + if( trak->cache->fragment ) + isom_fragment_reset_sample_counts( trak->cache ); + } + return 0; +} + +/* Return 1 if there is diffrence, otherwise return 0. */ +static int isom_compare_sample_flags( isom_sample_flags_t *a, isom_sample_flags_t *b ) +{ + return (a->reserved != b->reserved) + || (a->is_leading != b->is_leading) + || (a->sample_depends_on != b->sample_depends_on) + || (a->sample_is_depended_on != b->sample_is_depended_on) + || (a->sample_has_redundancy != b->sample_has_redundancy) + || (a->sample_padding_value != b->sample_padding_value) + || (a->sample_is_non_sync_sample != b->sample_is_non_sync_sample) + || (a->sample_degradation_priority != b->sample_degradation_priority); +} + +static int isom_make_segment_index_entry +( + lsmash_file_t *file, + isom_moof_t *moof +) +{ + /* Make the index of this subsegment. */ + for( lsmash_entry_t *entry = moof->traf_list.head; entry; entry = entry->next ) + { + isom_traf_t *traf = (isom_traf_t *)entry->data; + isom_tfhd_t *tfhd = traf->tfhd; + isom_fragment_t *track_fragment = traf->cache->fragment; + isom_subsegment_t *subsegment = &track_fragment->subsegment; + isom_sidx_t *sidx = isom_get_sidx( file, tfhd->track_ID ); + isom_trak_t *trak = isom_get_trak( file->initializer, tfhd->track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak->mdia->mdhd ) ) + return LSMASH_ERR_NAMELESS; + assert( LSMASH_IS_EXISTING_BOX( traf->tfdt ) ); + if( LSMASH_IS_NON_EXISTING_BOX( sidx ) ) + { + sidx = isom_add_sidx( file ); + if( LSMASH_IS_NON_EXISTING_BOX( sidx ) ) + return LSMASH_ERR_NAMELESS; + sidx->reference_ID = tfhd->track_ID; + sidx->timescale = trak->mdia->mdhd->timescale; + sidx->reserved = 0; + sidx->reference_count = 0; + int ret = isom_update_indexed_material_offset( file, sidx ); + if( ret < 0 ) + return ret; + } + /* One pair of a Movie Fragment Box with an associated Media Box per subsegment. */ + isom_sidx_referenced_item_t *data = lsmash_malloc( sizeof(isom_sidx_referenced_item_t) ); + if( !data ) + return LSMASH_ERR_NAMELESS; + if( lsmash_list_add_entry( sidx->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + sidx->reference_count = sidx->list->entry_count; + data->reference_type = 0; /* media */ + data->reference_size = file->size - moof->pos; + /* presentation */ + uint64_t TSAP; + uint64_t TDEC; + uint64_t TEPT; + uint64_t TPTF; + uint64_t composition_duration = subsegment->largest_cts - subsegment->smallest_cts; + if( subsegment->smallest_cts != LSMASH_TIMESTAMP_UNDEFINED + && subsegment->largest_cts != LSMASH_TIMESTAMP_UNDEFINED ) + composition_duration += track_fragment->last_duration; + if( trak->edts->elst->list ) + { + /**-- Explicit edits --**/ + const isom_elst_t *elst = trak->edts->elst; + const isom_elst_entry_t *edit = NULL; + uint32_t movie_timescale = file->initializer->moov->mvhd->timescale; + uint64_t pts = subsegment->segment_duration; + int subsegment_in_presentation = 0; /* If set to 1, TEPT is available. */ + int first_rp_in_presentation = 0; /* If set to 1, both TSAP and TDEC are available. */ + int first_sample_in_presentation = 0; /* If set to 1, TPTF is available. */ + TSAP = LSMASH_TIMESTAMP_UNDEFINED; + TDEC = LSMASH_TIMESTAMP_UNDEFINED; + TEPT = LSMASH_TIMESTAMP_UNDEFINED; + TPTF = LSMASH_TIMESTAMP_UNDEFINED; + /* */ + for( lsmash_entry_t *elst_entry = elst->list->head; elst_entry; elst_entry = elst_entry->next ) + { + edit = (isom_elst_entry_t *)elst_entry->data; + if( !edit ) + continue; + uint64_t edit_end_pts; + uint64_t edit_end_cts; + if( edit->segment_duration == ISOM_EDIT_DURATION_IMPLICIT + || (elst->version == 0 && edit->segment_duration == ISOM_EDIT_DURATION_UNKNOWN32) + || (elst->version == 1 && edit->segment_duration == ISOM_EDIT_DURATION_UNKNOWN64) ) + { + edit_end_cts = UINT64_MAX; + edit_end_pts = UINT64_MAX; + } + else + { + double segment_duration = edit->segment_duration * ((double)sidx->timescale / movie_timescale); + edit_end_cts = edit->media_time + (uint64_t)(segment_duration * ((double)edit->media_rate / (1 << 16))); + edit_end_pts = pts + (uint64_t)segment_duration; + } + if( edit->media_time == ISOM_EDIT_MODE_EMPTY ) + { + pts = edit_end_pts; + continue; + } + if( subsegment->smallest_cts != LSMASH_TIMESTAMP_UNDEFINED + && subsegment->largest_cts != LSMASH_TIMESTAMP_UNDEFINED + && ((subsegment->smallest_cts >= edit->media_time && subsegment->smallest_cts < edit_end_cts) + || (subsegment->largest_cts >= edit->media_time && subsegment->largest_cts < edit_end_cts)) ) + { + /* This subsegment is present in this edit. */ + double rate = (double)edit->media_rate / (1 << 16); + uint64_t start_time = LSMASH_MAX( subsegment->smallest_cts, edit->media_time ); + if( sidx->reference_count == 1 ) + sidx->earliest_presentation_time = pts; + if( subsegment_in_presentation == 0 ) + { + subsegment_in_presentation = 1; + if( subsegment->smallest_cts >= edit->media_time ) + TEPT = pts + (uint64_t)((subsegment->smallest_cts - start_time) / rate); + else + TEPT = pts; + } + if( first_rp_in_presentation == 0 + && subsegment->first_ed_cts != LSMASH_TIMESTAMP_UNDEFINED + && subsegment->first_rp_cts != LSMASH_TIMESTAMP_UNDEFINED + && ((subsegment->first_ed_cts >= edit->media_time && subsegment->first_ed_cts < edit_end_cts) + || (subsegment->first_rp_cts >= edit->media_time && subsegment->first_rp_cts < edit_end_cts)) ) + { + /* FIXME: to distinguish TSAP and TDEC, need something to indicate incorrectly decodable sample. */ + first_rp_in_presentation = 1; + if( subsegment->first_ed_cts >= edit->media_time && subsegment->first_ed_cts < edit_end_cts ) + TSAP = pts + (uint64_t)((subsegment->first_ed_cts - start_time) / rate); + else + TSAP = pts; + TDEC = TSAP; + } + if( first_sample_in_presentation == 0 + && subsegment->first_sample_cts != LSMASH_TIMESTAMP_UNDEFINED + && subsegment->first_sample_cts >= edit->media_time && subsegment->first_sample_cts < edit_end_cts ) + { + first_sample_in_presentation = 1; + TPTF = pts + (uint64_t)((subsegment->first_sample_cts - start_time) / rate); + } + uint64_t subsegment_end_pts = pts + (uint64_t)(composition_duration / rate); + pts = LSMASH_MIN( edit_end_pts, subsegment_end_pts ); + /* Update subsegment_duration. */ + data->subsegment_duration = pts - subsegment->segment_duration; + } + else + /* This subsegment is not present in this edit. */ + pts = edit_end_pts; + } + } + else + { + /**-- Implicit edit --**/ + if( sidx->reference_count == 1 ) + sidx->earliest_presentation_time = subsegment->smallest_cts; + data->subsegment_duration = composition_duration; + /* FIXME: to distinguish TSAP and TDEC, need something to indicate incorrectly decodable sample. */ + TSAP = subsegment->first_rp_cts; + TDEC = subsegment->first_rp_cts; + TEPT = subsegment->smallest_cts; + TPTF = subsegment->first_sample_cts; + } + /* Decide SAP_type. */ + data->starts_with_SAP = (subsegment->first_ra_number == 1); + data->SAP_type = 0; + data->SAP_delta_time = 0; + if( TSAP != LSMASH_TIMESTAMP_UNDEFINED + && TDEC != LSMASH_TIMESTAMP_UNDEFINED + && TEPT != LSMASH_TIMESTAMP_UNDEFINED ) + { + if( TPTF != LSMASH_TIMESTAMP_UNDEFINED ) + { + if( TEPT == TDEC && TDEC == TSAP && TSAP == TPTF ) + data->SAP_type = 1; + else if( TEPT == TDEC && TDEC == TSAP && TSAP < TPTF ) + data->SAP_type = 2; + else if( TEPT < TDEC && TDEC == TSAP && TSAP <= TPTF ) + data->SAP_type = 3; + else if( TEPT <= TPTF && TPTF < TDEC && TDEC == TSAP ) + data->SAP_type = 4; + } + if( data->SAP_type == 0 ) + { + if( TEPT == TDEC && TDEC < TSAP ) + data->SAP_type = 5; + else if( TEPT < TDEC && TDEC < TSAP ) + data->SAP_type = 6; + } + if( data->SAP_type != 0 ) + data->SAP_delta_time = TSAP - TEPT; + } + /* Prepare for the next subsegment. */ + subsegment->segment_duration += data->subsegment_duration; + subsegment->largest_cts = LSMASH_TIMESTAMP_UNDEFINED; + subsegment->smallest_cts = LSMASH_TIMESTAMP_UNDEFINED; + subsegment->first_sample_cts = LSMASH_TIMESTAMP_UNDEFINED; + subsegment->first_ed_cts = LSMASH_TIMESTAMP_UNDEFINED; + subsegment->first_rp_cts = LSMASH_TIMESTAMP_UNDEFINED; + subsegment->first_rp_number = 0; + subsegment->first_ra_number = 0; + subsegment->first_ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE; + subsegment->decodable = 0; + } + return 0; +} + +static int isom_finish_fragment_movie +( + lsmash_file_t *file +) +{ + if( !file->fragment + || !file->fragment->pool ) + return LSMASH_ERR_NAMELESS; + isom_moof_t *moof = file->fragment->movie; + if( LSMASH_IS_NON_EXISTING_BOX( moof ) ) + { + if( file == file->initializer ) + return isom_finish_fragment_initial_movie( file ); + else + return 0; /* No movie fragment to be finished. */ + } + /* Don't write the current movie fragment if containing no track fragments. + * This is a requirement of DASH Media Segment. */ + if( !moof->traf_list.head + || !moof->traf_list.head->data ) + return 0; + /* Calculate appropriate default_sample_flags of each Track Fragment Header Box. + * And check whether that default_sample_flags is useful or not. */ + for( lsmash_entry_t *entry = moof->traf_list.head; entry; entry = entry->next ) + { + isom_traf_t *traf = (isom_traf_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( traf ) + || LSMASH_IS_NON_EXISTING_BOX( traf->tfhd ) + || LSMASH_IS_NON_EXISTING_BOX( traf->file->initializer->moov->mvex ) ) + return LSMASH_ERR_NAMELESS; + isom_tfhd_t *tfhd = traf->tfhd; + isom_trex_t *trex = isom_get_trex( file->initializer->moov->mvex, tfhd->track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trex ) ) + return LSMASH_ERR_NAMELESS; + struct sample_flags_stats_t + { + uint32_t is_leading [4]; + uint32_t sample_depends_on [4]; + uint32_t sample_is_depended_on [4]; + uint32_t sample_has_redundancy [4]; + uint32_t sample_is_non_sync_sample[2]; + } stats = { { 0 }, { 0 }, { 0 }, { 0 }, { 0 } }; + for( lsmash_entry_t *trun_entry = traf->trun_list.head; trun_entry; trun_entry = trun_entry->next ) + { + isom_trun_t *trun = (isom_trun_t *)trun_entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( trun ) || trun->sample_count == 0 ) + return LSMASH_ERR_NAMELESS; + isom_sample_flags_t *sample_flags; + if( trun->flags & ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT ) + { + if( !trun->optional ) + return LSMASH_ERR_NAMELESS; + for( lsmash_entry_t *optional_entry = trun->optional->head; optional_entry; optional_entry = optional_entry->next ) + { + isom_trun_optional_row_t *row = (isom_trun_optional_row_t *)optional_entry->data; + if( !row ) + return LSMASH_ERR_NAMELESS; + sample_flags = &row->sample_flags; + ++ stats.is_leading [ sample_flags->is_leading ]; + ++ stats.sample_depends_on [ sample_flags->sample_depends_on ]; + ++ stats.sample_is_depended_on [ sample_flags->sample_is_depended_on ]; + ++ stats.sample_has_redundancy [ sample_flags->sample_has_redundancy ]; + ++ stats.sample_is_non_sync_sample[ sample_flags->sample_is_non_sync_sample ]; + } + } + else + { + sample_flags = &tfhd->default_sample_flags; + stats.is_leading [ sample_flags->is_leading ] += trun->sample_count; + stats.sample_depends_on [ sample_flags->sample_depends_on ] += trun->sample_count; + stats.sample_is_depended_on [ sample_flags->sample_is_depended_on ] += trun->sample_count; + stats.sample_has_redundancy [ sample_flags->sample_has_redundancy ] += trun->sample_count; + stats.sample_is_non_sync_sample[ sample_flags->sample_is_non_sync_sample ] += trun->sample_count; + } + } + uint32_t most_used[5] = { 0, 0, 0, 0, 0 }; + for( int i = 0; i < 4; i++ ) + { + GET_MOST_USED( tfhd, 0, is_leading ); + GET_MOST_USED( tfhd, 1, sample_depends_on ); + GET_MOST_USED( tfhd, 2, sample_is_depended_on ); + GET_MOST_USED( tfhd, 3, sample_has_redundancy ); + if( i < 2 ) + GET_MOST_USED( tfhd, 4, sample_is_non_sync_sample ); + } + int useful_default_sample_duration = 0; + int useful_default_sample_size = 0; + for( lsmash_entry_t *trun_entry = traf->trun_list.head; trun_entry; trun_entry = trun_entry->next ) + { + isom_trun_t *trun = (isom_trun_t *)trun_entry->data; + assert( LSMASH_IS_EXISTING_BOX( trun ) ); + if( !(trun->flags & ISOM_TR_FLAGS_SAMPLE_DURATION_PRESENT) ) + useful_default_sample_duration = 1; + if( !(trun->flags & ISOM_TR_FLAGS_SAMPLE_SIZE_PRESENT) ) + useful_default_sample_size = 1; + int useful_first_sample_flags = 1; + int useful_default_sample_flags = 1; + if( trun->sample_count == 1 ) + { + /* It is enough to check only if first_sample_flags equals default_sample_flags or not. + * If it is equal, just use default_sample_flags. + * If not, just use first_sample_flags of this run. */ + if( !isom_compare_sample_flags( &trun->first_sample_flags, &tfhd->default_sample_flags ) ) + useful_first_sample_flags = 0; + } + else if( trun->optional + && trun->optional->head ) + { + lsmash_entry_t *optional_entry = trun->optional->head->next; + isom_trun_optional_row_t *row = (isom_trun_optional_row_t *)optional_entry->data; + isom_sample_flags_t representative_sample_flags = row->sample_flags; + if( isom_compare_sample_flags( &tfhd->default_sample_flags, &representative_sample_flags ) ) + useful_default_sample_flags = 0; + if( !isom_compare_sample_flags( &trun->first_sample_flags, &representative_sample_flags ) ) + useful_first_sample_flags = 0; + if( useful_default_sample_flags ) + for( optional_entry = optional_entry->next; optional_entry; optional_entry = optional_entry->next ) + { + row = (isom_trun_optional_row_t *)optional_entry->data; + if( isom_compare_sample_flags( &representative_sample_flags, &row->sample_flags ) ) + { + useful_default_sample_flags = 0; + break; + } + } + } + if( useful_default_sample_flags ) + { + tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_FLAGS_PRESENT; + trun->flags &= ~ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT; + } + else + { + useful_first_sample_flags = 0; + trun->flags |= ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT; + } + if( useful_first_sample_flags ) + trun->flags |= ISOM_TR_FLAGS_FIRST_SAMPLE_FLAGS_PRESENT; + } + if( useful_default_sample_duration && tfhd->default_sample_duration != trex->default_sample_duration ) + tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT; + else + tfhd->default_sample_duration = trex->default_sample_duration; /* This might be redundant, but is to be more natural. */ + if( useful_default_sample_size && tfhd->default_sample_size != trex->default_sample_size ) + tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_SIZE_PRESENT; + else + tfhd->default_sample_size = trex->default_sample_size; /* This might be redundant, but is to be more natural. */ + if( !(tfhd->flags & ISOM_TF_FLAGS_DEFAULT_SAMPLE_FLAGS_PRESENT) ) + tfhd->default_sample_flags = trex->default_sample_flags; /* This might be redundant, but is to be more natural. */ + else if( !isom_compare_sample_flags( &tfhd->default_sample_flags, &trex->default_sample_flags ) ) + tfhd->flags &= ~ISOM_TF_FLAGS_DEFAULT_SAMPLE_FLAGS_PRESENT; + } + /* Complete the last sample groups in the previous track fragments. */ + int ret; + for( lsmash_entry_t *entry = moof->traf_list.head; entry; entry = entry->next ) + { + isom_traf_t *traf = (isom_traf_t *)entry->data; + assert( LSMASH_IS_EXISTING_BOX( traf ) ); + if( traf->cache->rap ) + { + isom_sgpd_t *sgpd = isom_get_fragment_sample_group_description( traf, ISOM_GROUP_TYPE_RAP ); + if( LSMASH_IS_NON_EXISTING_BOX( sgpd ) ) + return LSMASH_ERR_NAMELESS; + if( (ret = isom_rap_grouping_established( traf->cache->rap, 1, sgpd, 1 )) < 0 ) + return ret; + lsmash_freep( &traf->cache->rap ); + } + if( traf->cache->roll.pool ) + { + isom_sbgp_t *sbgp = isom_get_roll_recovery_sample_to_group( &traf->sbgp_list ); + if( LSMASH_IS_NON_EXISTING_BOX( sbgp ) ) + return LSMASH_ERR_NAMELESS; + if( (ret = isom_all_recovery_completed( sbgp, traf->cache->roll.pool )) < 0 ) + return ret; + } + } + /* Establish Movie Fragment Box. + * We write exactly one Media Data Box starting immediately after the corresponding Movie Fragment Box. */ + if( file->allow_moof_base ) + { + /* In this branch, we use default-base-is-moof flag, which indicates implicit base_data_offsets originate in the + * first byte of each enclosing Movie Fragment Box. + * We use the sum of the size of the Movie Fragment Box and the offset from the size field of the Media Data Box to + * the type field of it as the data_offset of the first track run like the following. + * + * _____________ _ offset := 0 + * | | | + * | m | s i z e | + * | |_________| + * | o | | + * | | t y p e | + * | o |_________| + * | | | + * | f | d a t a | + * |___|_________|_ offset := the size of the Movie Fragment Box + * | | | + * | m | s i z e | + * | |_________| + * | d | | + * | | t y p e | + * | a |_________|_ offset := the data_offset of the first track run + * | | | + * | t | d a t a | + * |___|_________|_ offset := the size of a subsegment containing exactly one movie fragment + * + * For a pair of one Movie Fragment Box and one Media Data Box, placed in this order, implicit base_data_offsets + * indicated by the absence of both base-data-offset-present and default-base-is-moof are somewhat complicated + * since the implicit base_data_offset of the current track fragment is defined by the end of the data of the + * previous track fragment and the data_offset of the track runs could be negative value because of interleaving + * track runs or something other reasons. + * In contrast, implicit base_data_offsets indicated by default-base-is-moof are simple since the base_data_offset + * of each track fragment is always constant for that pair and has no dependency on other track fragments. + */ + for( lsmash_entry_t *entry = moof->traf_list.head; entry; entry = entry->next ) + { + isom_traf_t *traf = (isom_traf_t *)entry->data; + assert( LSMASH_IS_EXISTING_BOX( traf ) ); + traf->tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_BASE_IS_MOOF; + traf->tfhd->base_data_offset = file->size; /* not written actually though */ + for( lsmash_entry_t *trun_entry = traf->trun_list.head; trun_entry; trun_entry = trun_entry->next ) + { + /* Here, data_offset is always greater than zero. */ + isom_trun_t *trun = trun_entry->data; + assert( LSMASH_IS_EXISTING_BOX( trun ) ); + trun->flags |= ISOM_TR_FLAGS_DATA_OFFSET_PRESENT; + } + } + /* Consider the update of tr_flags here. */ + if( isom_update_box_size( moof ) == 0 ) + return LSMASH_ERR_NAMELESS; + /* Now, we can calculate offsets in the current movie fragment, so do it. */ + for( lsmash_entry_t *entry = moof->traf_list.head; entry; entry = entry->next ) + { + isom_traf_t *traf = (isom_traf_t *)entry->data; + assert( LSMASH_IS_EXISTING_BOX( traf ) ); + for( lsmash_entry_t *trun_entry = traf->trun_list.head; trun_entry; trun_entry = trun_entry->next ) + { + isom_trun_t *trun = trun_entry->data; + assert( LSMASH_IS_EXISTING_BOX( trun ) ); + trun->data_offset += moof->size + ISOM_BASEBOX_COMMON_SIZE; + } + } + } + else + { + /* In this branch, we use explicit base_data_offset. */ + for( lsmash_entry_t *entry = moof->traf_list.head; entry; entry = entry->next ) + { + isom_traf_t *traf = (isom_traf_t *)entry->data; + assert( LSMASH_IS_EXISTING_BOX( traf ) ); + traf->tfhd->flags |= ISOM_TF_FLAGS_BASE_DATA_OFFSET_PRESENT; + } + /* Consider the update of tf_flags here. */ + if( isom_update_box_size( moof ) == 0 ) + return LSMASH_ERR_NAMELESS; + /* Now, we can calculate offsets in the current movie fragment, so do it. */ + for( lsmash_entry_t *entry = moof->traf_list.head; entry; entry = entry->next ) + { + isom_traf_t *traf = (isom_traf_t *)entry->data; + assert( LSMASH_IS_EXISTING_BOX( traf ) ); + traf->tfhd->base_data_offset = file->size + moof->size + ISOM_BASEBOX_COMMON_SIZE; + } + } + /* Write Movie Fragment Box and its children. */ + moof->pos = file->size; + if( (ret = isom_write_box( file->bs, (isom_box_t *)moof )) < 0 ) + return ret; + if( file->fragment->first_moof_pos == FIRST_MOOF_POS_UNDETERMINED ) + file->fragment->first_moof_pos = moof->pos; + file->size += moof->size; + /* Output samples. */ + if( (ret = isom_output_fragment_media_data( file )) < 0 ) + return ret; + /* Revert the number of samples in track fragments to 0. */ + for( lsmash_entry_t *entry = moof->traf_list.head; entry; entry = entry->next ) + { + isom_traf_t *traf = (isom_traf_t *)entry->data; + assert( LSMASH_IS_EXISTING_BOX( traf ) ); + if( traf->cache->fragment ) + isom_fragment_reset_sample_counts( traf->cache ); + } + if( !(file->flags & LSMASH_FILE_MODE_INDEX) || file->max_isom_version < 6 ) + return 0; + return isom_make_segment_index_entry( file, moof ); +} + +#undef GET_MOST_USED + +static isom_trun_optional_row_t *isom_request_trun_optional_row( isom_trun_t *trun, isom_tfhd_t *tfhd, uint32_t sample_number ) +{ + isom_trun_optional_row_t *row = NULL; + if( !trun->optional ) + { + trun->optional = lsmash_list_create_simple(); + if( !trun->optional ) + return NULL; + } + if( trun->optional->entry_count < sample_number ) + { + while( trun->optional->entry_count < sample_number ) + { + row = lsmash_malloc( sizeof(isom_trun_optional_row_t) ); + if( !row ) + return NULL; + /* Copy from default. */ + row->sample_duration = tfhd->default_sample_duration; + row->sample_size = tfhd->default_sample_size; + row->sample_flags = tfhd->default_sample_flags; + row->sample_composition_time_offset = 0; + if( lsmash_list_add_entry( trun->optional, row ) < 0 ) + { + lsmash_free( row ); + return NULL; + } + } + return row; + } + uint32_t i = 0; + for( lsmash_entry_t *entry = trun->optional->head; entry; entry = entry->next ) + { + row = (isom_trun_optional_row_t *)entry->data; + if( !row ) + return NULL; + if( ++i == sample_number ) + return row; + } + return NULL; +} + +int lsmash_create_fragment_empty_duration +( + lsmash_root_t *root, + uint32_t track_ID, + uint32_t duration +) +{ + if( isom_check_initializer_present( root ) < 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_file_t *file = root->file; + if( !file->fragment + || !file->fragment->movie + || LSMASH_IS_NON_EXISTING_BOX( file->initializer->moov ) ) + return LSMASH_ERR_NAMELESS; + isom_trak_t *trak = isom_get_trak( file->initializer, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak->tkhd ) ) + return LSMASH_ERR_NAMELESS; + isom_trex_t *trex = isom_get_trex( file->initializer->moov->mvex, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trex ) ) + return LSMASH_ERR_NAMELESS; + isom_moof_t *moof = file->fragment->movie; + isom_traf_t *traf = isom_get_traf( moof, track_ID ); + if( LSMASH_IS_EXISTING_BOX( traf ) ) + return LSMASH_ERR_NAMELESS; + traf = isom_add_traf( moof ); + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_tfhd( traf ) ) ) + return LSMASH_ERR_NAMELESS; + isom_tfhd_t *tfhd = traf->tfhd; + tfhd->flags = ISOM_TF_FLAGS_DURATION_IS_EMPTY; /* no samples for this track fragment yet */ + tfhd->track_ID = trak->tkhd->track_ID; + tfhd->default_sample_duration = duration; + if( duration != trex->default_sample_duration ) + tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT; + traf->cache = trak->cache; + traf->cache->fragment->traf_number = moof->traf_list.entry_count; + traf->cache->fragment->last_duration += duration; /* The duration of the last sample includes this empty-duration. */ + return 0; +} + +int isom_set_fragment_last_duration +( + isom_traf_t *traf, + uint32_t last_duration +) +{ + isom_tfhd_t *tfhd = traf->tfhd; + if( !traf->trun_list.tail + || !traf->trun_list.tail->data ) + { + /* There are no track runs in this track fragment, so it is a empty-duration. */ + isom_trex_t *trex = isom_get_trex( traf->file->initializer->moov->mvex, tfhd->track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trex ) ) + return LSMASH_ERR_NAMELESS; + tfhd->flags |= ISOM_TF_FLAGS_DURATION_IS_EMPTY; + if( last_duration != trex->default_sample_duration ) + tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT; + tfhd->default_sample_duration = last_duration; + traf->cache->fragment->last_duration = last_duration; + return 0; + } + /* Update the last sample_duration if needed. */ + isom_trun_t *trun = (isom_trun_t *)traf->trun_list.tail->data; + if( trun->sample_count == 1 + && traf->trun_list.entry_count == 1 ) + { + isom_trex_t *trex = isom_get_trex( traf->file->initializer->moov->mvex, tfhd->track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trex ) ) + return LSMASH_ERR_NAMELESS; + if( last_duration != trex->default_sample_duration ) + tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT; + tfhd->default_sample_duration = last_duration; + } + else if( last_duration != tfhd->default_sample_duration ) + trun->flags |= ISOM_TR_FLAGS_SAMPLE_DURATION_PRESENT; + if( trun->flags ) + { + isom_trun_optional_row_t *row = isom_request_trun_optional_row( trun, tfhd, trun->sample_count ); + if( !row ) + return LSMASH_ERR_NAMELESS; + row->sample_duration = last_duration; + } + traf->cache->fragment->last_duration = last_duration; + return 0; +} + +int isom_append_fragment_track_run +( + lsmash_file_t *file, + isom_chunk_t *chunk +) +{ + if( !chunk->pool || chunk->pool->size == 0 ) + return 0; + isom_fragment_manager_t *frag_manager = file->fragment; + /* Move data in the pool of the current track fragment to the pool of the current movie fragment. + * Empty the pool of current track. We don't delete data of samples here. */ + if( lsmash_list_add_entry( frag_manager->pool, chunk->pool ) < 0 ) + return LSMASH_ERR_MEMORY_ALLOC; + frag_manager->sample_count += chunk->pool->sample_count; + frag_manager->pool_size += chunk->pool->size; + chunk->pool = isom_create_sample_pool( chunk->pool->size ); + return chunk->pool ? 0 : LSMASH_ERR_MEMORY_ALLOC; +} + +static int isom_output_fragment_cache( isom_traf_t *traf ) +{ + isom_cache_t *cache = traf->cache; + int ret = isom_append_fragment_track_run( traf->file, &cache->chunk ); + if( ret < 0 ) + return ret; + for( lsmash_entry_t *entry = traf->sgpd_list.head; entry; entry = entry->next ) + { + isom_sgpd_t *sgpd = (isom_sgpd_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( sgpd ) ) + return LSMASH_ERR_NAMELESS; + switch( sgpd->grouping_type ) + { + case ISOM_GROUP_TYPE_RAP : + { + isom_rap_group_t *group = cache->rap; + if( !group ) + { + if( traf->file->fragment ) + continue; + else + return LSMASH_ERR_NAMELESS; + } + if( !group->random_access ) + continue; + group->random_access->num_leading_samples_known = 1; + break; + } + case ISOM_GROUP_TYPE_ROLL : + case ISOM_GROUP_TYPE_PROL : + if( !cache->roll.pool ) + { + if( traf->file->fragment ) + continue; + else + return LSMASH_ERR_NAMELESS; + } + isom_sbgp_t *sbgp = isom_get_roll_recovery_sample_to_group( &traf->sbgp_list ); + if( LSMASH_IS_NON_EXISTING_BOX( sbgp ) ) + return LSMASH_ERR_NAMELESS; + if( (ret = isom_all_recovery_completed( sbgp, cache->roll.pool )) < 0 ) + return ret; + break; + default : + break; + } + } + return 0; +} + +int isom_flush_fragment_pooled_samples +( + lsmash_file_t *file, + uint32_t track_ID, + uint32_t last_sample_duration +) +{ + isom_traf_t *traf = isom_get_traf( file->fragment->movie, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( traf ) ) + /* No samples. We don't return as an error here since user might call the flushing function even if the + * current movie fragment has no track fragment with this track_ID. */ + return 0; + if( !traf->cache + || !traf->cache->fragment ) + return LSMASH_ERR_NAMELESS; + if( traf->trun_list.entry_count + && traf->trun_list.tail + && traf->trun_list.tail->data ) + { + /* Media Data Box preceded by Movie Fragment Box could change base_data_offsets in each track fragments later. + * We can't consider this here because the length of Movie Fragment Box is unknown at this step yet. */ + isom_trun_t *trun = (isom_trun_t *)traf->trun_list.tail->data; + if( file->fragment->pool_size ) + trun->flags |= ISOM_TR_FLAGS_DATA_OFFSET_PRESENT; + trun->data_offset = file->fragment->pool_size; + } + int ret = isom_output_fragment_cache( traf ); + if( ret < 0 ) + return ret; + return isom_set_fragment_last_duration( traf, last_sample_duration ); +} + +/* This function doesn't update sample_duration of the last sample in the previous movie fragment. + * Instead of this, isom_finish_movie_fragment undertakes this task. */ +static int isom_update_fragment_previous_sample_duration( isom_traf_t *traf, isom_trex_t *trex, uint32_t duration ) +{ + isom_tfhd_t *tfhd = traf->tfhd; + isom_trun_t *trun = (isom_trun_t *)traf->trun_list.tail->data; + int previous_run_has_previous_sample = 0; + if( trun->sample_count == 1 ) + { + if( traf->trun_list.entry_count == 1 ) + return 0; /* The previous track run belongs to the previous movie fragment if it exists. */ + if( !traf->trun_list.tail->prev + || !traf->trun_list.tail->prev->data ) + return LSMASH_ERR_NAMELESS; + /* OK. The previous sample exists in the previous track run in the same track fragment. */ + trun = (isom_trun_t *)traf->trun_list.tail->prev->data; + previous_run_has_previous_sample = 1; + } + /* Update default_sample_duration of the Track Fragment Header Box + * if this duration is what the first sample in the current track fragment owns. */ + if( (trun->sample_count == 2 && traf->trun_list.entry_count == 1) + || (trun->sample_count == 1 && traf->trun_list.entry_count == 2) ) + { + if( duration != trex->default_sample_duration ) + tfhd->flags |= ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT; + tfhd->default_sample_duration = duration; + } + /* Update the previous sample_duration if needed. */ + if( duration != tfhd->default_sample_duration ) + trun->flags |= ISOM_TR_FLAGS_SAMPLE_DURATION_PRESENT; + if( trun->flags ) + { + uint32_t sample_number = trun->sample_count - !previous_run_has_previous_sample; + isom_trun_optional_row_t *row = isom_request_trun_optional_row( trun, tfhd, sample_number ); + if( !row ) + return LSMASH_ERR_NAMELESS; + row->sample_duration = duration; + } + return 0; +} + +static isom_sample_flags_t isom_generate_fragment_sample_flags( lsmash_sample_t *sample ) +{ + isom_sample_flags_t flags; + flags.reserved = 0; + flags.is_leading = sample->prop.leading & 0x3; + flags.sample_depends_on = sample->prop.independent & 0x3; + flags.sample_is_depended_on = sample->prop.disposable & 0x3; + flags.sample_has_redundancy = sample->prop.redundant & 0x3; + flags.sample_padding_value = 0; + flags.sample_is_non_sync_sample = !(sample->prop.ra_flags & ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC); + flags.sample_degradation_priority = 0; + return flags; +} + +/* Set up the base media decode time of this track fragment. + * This feature is available under ISO Base Media version 6 or later. + * For DASH Media Segment, each Track Fragment Box shall contain a Track Fragment Base Media Decode Time Box. */ +static int isom_fragment_set_base_media_decode_time( lsmash_file_t *file, isom_traf_t *traf, uint64_t dts ) +{ + if( file->max_isom_version >= 6 || file->media_segment ) + { + assert( LSMASH_IS_NON_EXISTING_BOX( traf->tfdt ) ); + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_tfdt( traf ) ) ) + return LSMASH_ERR_NAMELESS; + if( dts > UINT32_MAX ) + traf->tfdt->version = 1; + traf->tfdt->baseMediaDecodeTime = dts; + } + return 0; +} + +/* Update the cache of SAP related information. */ +static void isom_fragment_update_cache_for_sap +( + isom_cache_t *cache, + lsmash_sample_t *sample +) +{ + isom_subsegment_t *subsegment = &cache->fragment->subsegment; + int non_output_sample = (sample->cts == LSMASH_TIMESTAMP_UNDEFINED); + if( !non_output_sample ) + { + if( cache->fragment->sample_count == 1 ) + { + assert( subsegment->first_sample_cts == LSMASH_TIMESTAMP_UNDEFINED ); + subsegment->first_sample_cts = sample->cts; + } + if( cache->fragment->output_sample_count > 1 ) + { + assert( subsegment->largest_cts != LSMASH_TIMESTAMP_UNDEFINED + && subsegment->smallest_cts != LSMASH_TIMESTAMP_UNDEFINED ); + subsegment->largest_cts = LSMASH_MAX( sample->cts, subsegment->largest_cts ); + subsegment->smallest_cts = LSMASH_MIN( sample->cts, subsegment->smallest_cts ); + } + else + { + assert( subsegment->largest_cts == LSMASH_TIMESTAMP_UNDEFINED + && subsegment->smallest_cts == LSMASH_TIMESTAMP_UNDEFINED ); + if( cache->fragment->output_sample_count == 1 ) + { + + subsegment->largest_cts = sample->cts; + subsegment->smallest_cts = sample->cts; + } + } + } + if( subsegment->first_ra_flags == ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE + && sample->prop.ra_flags != ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE ) + { + assert( subsegment->first_ra_number == 0 ); + subsegment->first_ra_flags = sample->prop.ra_flags; + subsegment->first_ra_number = cache->fragment->sample_count; + if( sample->prop.ra_flags & (ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC | ISOM_SAMPLE_RANDOM_ACCESS_FLAG_RAP) ) + subsegment->is_first_recovery_point = 1; + } + if( subsegment->is_first_recovery_point ) + { + assert( subsegment->first_rp_number == 0 ); + if( !non_output_sample ) + { + assert( subsegment->first_rp_cts == LSMASH_TIMESTAMP_UNDEFINED + && subsegment->first_ed_cts == LSMASH_TIMESTAMP_UNDEFINED ); + subsegment->first_rp_cts = sample->cts; + subsegment->first_ed_cts = sample->cts; + } + subsegment->first_rp_number = subsegment->first_ra_number; + subsegment->decodable = 1; + subsegment->is_first_recovery_point = 0; + } + else if( subsegment->decodable ) + { + if( (subsegment->first_ra_flags & (ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC | ISOM_SAMPLE_RANDOM_ACCESS_FLAG_RAP)) + ? (sample->prop.leading == ISOM_SAMPLE_IS_DECODABLE_LEADING) + : (subsegment->first_ra_flags & ISOM_SAMPLE_RANDOM_ACCESS_FLAG_POST_ROLL_START) ) + { + if( !non_output_sample ) + subsegment->first_ed_cts = LSMASH_MIN( sample->cts, subsegment->first_ed_cts ); + } + else + subsegment->decodable = 0; + } +} + +/* Update the cache of fragment related information. + * The cached sample_count is incremented here, so be careful to treat it. */ +static void isom_fragment_update_cache +( + isom_cache_t *cache, + lsmash_sample_t *sample, + lsmash_file_t *file +) +{ + cache->fragment->has_samples = 1; + cache->fragment->sample_count += 1; + cache->fragment->output_sample_count += sample->cts == LSMASH_TIMESTAMP_UNDEFINED ? 0 : 1; + assert( cache->fragment->sample_count >= cache->fragment->output_sample_count ); + if( (file->flags & LSMASH_FILE_MODE_INDEX) && (file->max_isom_version >= 6) ) + isom_fragment_update_cache_for_sap( cache, sample ); +} + +static int isom_fragment_update_sample_tables( isom_traf_t *traf, lsmash_sample_t *sample ) +{ + isom_tfhd_t *tfhd = traf->tfhd; + isom_trex_t *trex = isom_get_trex( traf->file->initializer->moov->mvex, tfhd->track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trex ) ) + return LSMASH_ERR_NAMELESS; + lsmash_file_t *file = traf->file; + isom_cache_t *cache = traf->cache; + isom_chunk_t *current = &cache->chunk; + if( !current->pool ) + { + /* Very initial settings, just once per track */ + current->pool = isom_create_sample_pool( 0 ); + if( !current->pool ) + return LSMASH_ERR_MEMORY_ALLOC; + } + /* Create a new track run if the duration exceeds max_chunk_duration. + * Old one will be appended to the pool of this movie fragment. */ + uint32_t media_timescale = lsmash_get_media_timescale( file->root, tfhd->track_ID ); + if( media_timescale == 0 ) + return LSMASH_ERR_NAMELESS; + int delimit = (file->max_chunk_duration < ((double)(sample->dts - current->first_dts) / media_timescale)) + || (file->max_chunk_size < (current->pool->size + sample->length)); + isom_trun_t *trun; + if( traf->trun_list.entry_count == 0 || delimit ) + { + if( delimit + && traf->trun_list.entry_count + && traf->trun_list.tail + && LSMASH_IS_EXISTING_BOX( (isom_trun_t *)traf->trun_list.tail->data ) ) + { + /* Media Data Box preceded by Movie Fragment Box could change base data offsets in each track fragments later. + * We can't consider this here because the length of Movie Fragment Box is unknown at this step yet. */ + trun = (isom_trun_t *)traf->trun_list.tail->data; + if( file->fragment->pool_size ) + trun->flags |= ISOM_TR_FLAGS_DATA_OFFSET_PRESENT; + trun->data_offset = file->fragment->pool_size; + } + trun = isom_add_trun( traf ); + if( LSMASH_IS_NON_EXISTING_BOX( trun ) ) + return LSMASH_ERR_NAMELESS; + } + else + { + if( !traf->trun_list.tail + || LSMASH_IS_NON_EXISTING_BOX( (isom_trun_t *)traf->trun_list.tail->data ) ) + return LSMASH_ERR_NAMELESS; + trun = (isom_trun_t *)traf->trun_list.tail->data; + } + isom_sample_flags_t sample_flags = isom_generate_fragment_sample_flags( sample ); + int non_output_sample = (sample->cts == LSMASH_TIMESTAMP_UNDEFINED); + if( ++trun->sample_count == 1 ) + { + if( traf->trun_list.entry_count == 1 ) + { + tfhd->sample_description_index = current->sample_description_index + = sample->index; + tfhd->default_sample_size = sample->length; + tfhd->default_sample_flags = sample_flags; /* Note: we decide an appropriate default value at the end of this movie fragment. */ + tfhd->flags &= ~ISOM_TF_FLAGS_DURATION_IS_EMPTY; /* This track fragment isn't empty-duration-fragment any more. */ + if( sample->index != trex->default_sample_description_index ) + tfhd->flags |= ISOM_TF_FLAGS_SAMPLE_DESCRIPTION_INDEX_PRESENT; + /* Set up random access information if this sample is a sync sample. + * We inform only the first sample in each movie fragment. */ + if( !non_output_sample + && LSMASH_IS_EXISTING_BOX( file->mfra ) + && (sample->prop.ra_flags & ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC) ) + { + isom_tfra_t *tfra = isom_get_tfra( file->mfra, tfhd->track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( tfra ) ) + { + tfra = isom_add_tfra( file->mfra ); + if( LSMASH_IS_NON_EXISTING_BOX( tfra ) ) + return LSMASH_ERR_NAMELESS; + tfra->track_ID = tfhd->track_ID; + } + if( !tfra->list ) + { + tfra->list = lsmash_list_create_simple(); + if( !tfra->list ) + return LSMASH_ERR_MEMORY_ALLOC; + } + isom_tfra_location_time_entry_t *rap = lsmash_malloc( sizeof(isom_tfra_location_time_entry_t) ); + if( !rap ) + return LSMASH_ERR_MEMORY_ALLOC; + rap->time = sample->cts; /* Set composition timestamp temporarily. + * At the end of the whole movie, this will be reset as presentation time. */ + rap->moof_offset = file->size; /* We place Movie Fragment Box in the head of each movie fragment. */ + rap->traf_number = cache->fragment->traf_number; + rap->trun_number = traf->trun_list.entry_count; + rap->sample_number = trun->sample_count; + if( lsmash_list_add_entry( tfra->list, rap ) < 0 ) + { + lsmash_free( rap ); + return LSMASH_ERR_MEMORY_ALLOC; + } + tfra->number_of_entry = tfra->list->entry_count; + int length; + for( length = 1; rap->traf_number >> (length * 8); length++ ); + tfra->length_size_of_traf_num = LSMASH_MAX( length - 1, tfra->length_size_of_traf_num ); + for( length = 1; rap->traf_number >> (length * 8); length++ ); + tfra->length_size_of_trun_num = LSMASH_MAX( length - 1, tfra->length_size_of_trun_num ); + for( length = 1; rap->sample_number >> (length * 8); length++ ); + tfra->length_size_of_sample_num = LSMASH_MAX( length - 1, tfra->length_size_of_sample_num ); + } + int err = isom_fragment_set_base_media_decode_time( file, traf, sample->dts ); + if( err < 0 ) + return err; + } + trun->first_sample_flags = sample_flags; + current->first_dts = sample->dts; + } + /* Update the optional rows in the current track run except for sample_duration if needed. */ + if( sample->length != tfhd->default_sample_size ) + trun->flags |= ISOM_TR_FLAGS_SAMPLE_SIZE_PRESENT; + if( isom_compare_sample_flags( &sample_flags, &tfhd->default_sample_flags ) ) + trun->flags |= ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT; + uint32_t sample_composition_time_offset = !non_output_sample ? sample->cts - sample->dts : ISOM_NON_OUTPUT_SAMPLE_OFFSET; + int32_t ctd_shift = cache->timestamp.ctd_shift; + if( sample_composition_time_offset ) + { + trun->flags |= ISOM_TR_FLAGS_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT; + /* Check if negative composition time offset is present. */ + if( !non_output_sample ) + { + if( (sample->cts + ctd_shift) < sample->dts ) + { + if( file->max_isom_version < 6 ) + return LSMASH_ERR_INVALID_DATA; /* Negative composition time offset is invalid. */ + if( (sample->dts - sample->cts) > INT32_MAX ) + return LSMASH_ERR_INVALID_DATA; /* Overflow */ + ctd_shift = sample->dts - sample->cts; + trun->version = 1; + } + } + else + { + if( file->max_isom_version < 6 ) + return LSMASH_ERR_INVALID_DATA; /* Negative composition time offset is invalid. */ + trun->version = 1; + } + } + if( trun->flags ) + { + isom_trun_optional_row_t *row = isom_request_trun_optional_row( trun, tfhd, trun->sample_count ); + if( !row ) + return LSMASH_ERR_NAMELESS; + row->sample_size = sample->length; + row->sample_flags = sample_flags; + row->sample_composition_time_offset = sample_composition_time_offset; + } + /* Set up the sample groupings for random access. */ + int ret; + if( (ret = isom_group_random_access( (isom_box_t *)traf, cache, sample )) < 0 + || (ret = isom_group_roll_recovery( (isom_box_t *)traf, cache, sample )) < 0 ) + return ret; + /* Set up the previous sample_duration if this sample is not the first sample in the overall movie. */ + uint32_t sample_duration; + if( cache->fragment->has_samples ) + { + /* Note: when using for live streaming, it is not good idea to return error by sample->dts < prev_dts + * since that's trivial for such semi-permanent presentation. */ + uint64_t prev_dts = cache->timestamp.dts; + if( sample->dts <= prev_dts + || sample->dts > prev_dts + UINT32_MAX ) + return LSMASH_ERR_INVALID_DATA; + sample_duration = sample->dts - prev_dts; + if( (ret = isom_update_fragment_previous_sample_duration( traf, trex, sample_duration )) < 0 ) + return ret; + } + else + sample_duration = cache->fragment->last_duration; + isom_update_cache_timestamp( cache, sample->dts, sample->cts, ctd_shift, sample_duration, non_output_sample ); + return delimit; +} + +static int isom_append_fragment_sample_internal_initial +( + isom_trak_t *trak, + lsmash_sample_t *sample, + isom_sample_entry_t *sample_entry +) +{ + /* Update the sample tables of this track fragment enclosing the initial movie. + * If a new chunk was created, append the previous one to the pool of this movie fragment. */ + uint32_t samples_per_packet; + int ret = isom_update_sample_tables( trak, sample, &samples_per_packet, sample_entry ); + if( ret < 0 ) + return ret; + else if( ret == 1 ) + isom_append_fragment_track_run( trak->file, &trak->cache->chunk ); + isom_fragment_update_cache( trak->cache, sample, trak->file ); + /* Add a new sample into the pool of this track fragment. */ + if( (ret = isom_pool_sample( trak->cache->chunk.pool, sample, samples_per_packet )) < 0 ) + return ret; + return 0; +} + +static int isom_append_fragment_sample_internal +( + isom_traf_t *traf, + lsmash_sample_t *sample, + isom_sample_entry_t *sample_entry /* unused */ +) +{ + /* Update the sample tables of this track fragment. + * If a new track run was created, append the previous one to the pool of this movie fragment. */ + int ret = isom_fragment_update_sample_tables( traf, sample ); + if( ret < 0 ) + return ret; + else if( ret == 1 ) + isom_append_fragment_track_run( traf->file, &traf->cache->chunk ); + isom_fragment_update_cache( traf->cache, sample, traf->file ); + /* Add a new sample into the pool of this track fragment. */ + if( (ret = isom_pool_sample( traf->cache->chunk.pool, sample, 1 )) < 0 ) + return ret; + return 0; +} + +int isom_append_fragment_sample +( + lsmash_file_t *file, + isom_trak_t *trak, + lsmash_sample_t *sample, + isom_sample_entry_t *sample_entry +) +{ + if( !trak->cache->fragment ) + return LSMASH_ERR_NAMELESS; + isom_fragment_manager_t *fragment = file->fragment; + assert( fragment && fragment->pool ); + /* Write the Segment Type Box here if required and if it was not written yet. */ + if( !(file->flags & LSMASH_FILE_MODE_INITIALIZATION) + && file->styp_list.head + && LSMASH_IS_NON_EXISTING_BOX( (isom_styp_t *)file->styp_list.head->data ) ) + { + isom_styp_t *styp = (isom_styp_t *)file->styp_list.head->data; + if( !(styp->manager & LSMASH_WRITTEN_BOX) ) + { + int ret = isom_write_box( file->bs, (isom_box_t *)styp ); + if( ret < 0 ) + return ret; + file->size += styp->size; + } + } + int (*func_append_sample)( void *, lsmash_sample_t *, isom_sample_entry_t * ) = NULL; + void *track_fragment; + if( LSMASH_IS_NON_EXISTING_BOX( fragment->movie ) ) + { + /* Forbid adding a sample into the initial movie if requiring compatibility with Media Segment. */ + if( file->media_segment ) + return LSMASH_ERR_NAMELESS; + func_append_sample = (int (*)( void *, lsmash_sample_t *, isom_sample_entry_t * ))isom_append_fragment_sample_internal_initial; + track_fragment = trak; + } + else + { + /* NOTE: there are codes to support non-output sample signaling in fragments but we disabled them currently since the + * ISOBMFF spec 5th edition mentions 'ctts' and 'cslg' in 'stbl' which exists in the initial movie only. Therefore, + * as a safety, reject non-output samples here. */ + if( sample->cts == LSMASH_TIMESTAMP_UNDEFINED ) + return LSMASH_ERR_INVALID_DATA; + isom_traf_t *traf = isom_get_traf( fragment->movie, trak->tkhd->track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( traf ) ) + { + traf = isom_add_traf( fragment->movie ); + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_tfhd( traf ) ) ) + return LSMASH_ERR_NAMELESS; + traf->tfhd->flags = ISOM_TF_FLAGS_DURATION_IS_EMPTY; /* no samples for this track fragment yet */ + traf->tfhd->track_ID = trak->tkhd->track_ID; + traf->cache = trak->cache; + traf->cache->fragment->traf_number = fragment->movie->traf_list.entry_count; + int ret; + if( (traf->cache->fragment->rap_grouping && (ret = isom_add_sample_grouping( (isom_box_t *)traf, ISOM_GROUP_TYPE_RAP )) < 0) + || (traf->cache->fragment->roll_grouping && (ret = isom_add_sample_grouping( (isom_box_t *)traf, ISOM_GROUP_TYPE_ROLL )) < 0) ) + return ret; + } + else if( LSMASH_IS_NON_EXISTING_BOX( traf->file->initializer->moov->mvex ) + || LSMASH_IS_NON_EXISTING_BOX( traf->tfhd ) + || !traf->cache ) + return LSMASH_ERR_NAMELESS; + func_append_sample = (int (*)( void *, lsmash_sample_t *, isom_sample_entry_t * ))isom_append_fragment_sample_internal; + track_fragment = traf; + } + return isom_append_sample_by_type( track_fragment, sample, sample_entry, func_append_sample ); +} diff --git a/3rdparty/l-smash/core/fragment.h b/3rdparty/l-smash/core/fragment.h new file mode 100644 index 000000000..24465cc38 --- /dev/null +++ b/3rdparty/l-smash/core/fragment.h @@ -0,0 +1,54 @@ +/***************************************************************************** + * fragment.h + ***************************************************************************** + * Copyright (C) 2011-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +int isom_finish_final_fragment_movie +( + lsmash_file_t *file, + lsmash_adhoc_remux_t *remux +); + +int isom_set_fragment_last_duration +( + isom_traf_t *traf, + uint32_t last_duration +); + +int isom_append_fragment_track_run +( + lsmash_file_t *file, + isom_chunk_t *chunk +); + +int isom_flush_fragment_pooled_samples +( + lsmash_file_t *file, + uint32_t track_ID, + uint32_t last_sample_duration +); + +int isom_append_fragment_sample +( + lsmash_file_t *file, + isom_trak_t *trak, + lsmash_sample_t *sample, + isom_sample_entry_t *sample_entry +); diff --git a/3rdparty/l-smash/core/isom.c b/3rdparty/l-smash/core/isom.c new file mode 100644 index 000000000..006c208e2 --- /dev/null +++ b/3rdparty/l-smash/core/isom.c @@ -0,0 +1,4220 @@ +/***************************************************************************** + * isom.c + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * Contributors: Takashi Hirata + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include +#include +#include + +#include "box.h" +#include "box_default.h" +#include "file.h" +#include "fragment.h" +#include "read.h" +#include "timeline.h" +#include "write.h" + +#include "codecs/mp4a.h" +#include "codecs/mp4sys.h" +#include "codecs/description.h" + +/*---- ----*/ + +#define RTP_SAMPLE_HEADER_SIZE 4 +#define RTP_PACKET_SIZE 12 /* a structure in Hint track sample */ +#define RTP_HEADER_SIZE 12 +#define RTP_CONSTRUCTOR_SIZE 16 + +int isom_check_initializer_present( lsmash_root_t *root ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( root ) + || LSMASH_IS_NON_EXISTING_BOX( root->file ) + || LSMASH_IS_NON_EXISTING_BOX( root->file->initializer ) ) + return LSMASH_ERR_NAMELESS; + return 0; +} + +isom_trak_t *isom_get_trak( lsmash_file_t *file, uint32_t track_ID ) +{ + if( track_ID == 0 + || LSMASH_IS_NON_EXISTING_BOX( file->moov ) + || file != file->initializer ) + return isom_non_existing_trak(); + for( lsmash_entry_t *entry = file->moov->trak_list.head; entry; entry = entry->next ) + { + isom_trak_t *trak = (isom_trak_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( trak ) + || LSMASH_IS_NON_EXISTING_BOX( trak->tkhd ) ) + return isom_non_existing_trak(); + if( trak->tkhd->track_ID == track_ID ) + return trak; + } + return isom_non_existing_trak(); +} + +isom_trex_t *isom_get_trex( isom_mvex_t *mvex, uint32_t track_ID ) +{ + if( track_ID == 0 || LSMASH_IS_NON_EXISTING_BOX( mvex ) ) + return isom_non_existing_trex(); + for( lsmash_entry_t *entry = mvex->trex_list.head; entry; entry = entry->next ) + { + isom_trex_t *trex = (isom_trex_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( trex ) ) + return isom_non_existing_trex(); + if( trex->track_ID == track_ID ) + return trex; + } + return isom_non_existing_trex(); +} + +isom_traf_t *isom_get_traf( isom_moof_t *moof, uint32_t track_ID ) +{ + if( track_ID == 0 || LSMASH_IS_NON_EXISTING_BOX( moof ) ) + return isom_non_existing_traf(); + for( lsmash_entry_t *entry = moof->traf_list.head; entry; entry = entry->next ) + { + isom_traf_t *traf = (isom_traf_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( traf ) + || LSMASH_IS_NON_EXISTING_BOX( traf->tfhd ) ) + return isom_non_existing_traf(); + if( traf->tfhd->track_ID == track_ID ) + return traf; + } + return isom_non_existing_traf(); +} + +isom_tfra_t *isom_get_tfra( isom_mfra_t *mfra, uint32_t track_ID ) +{ + if( track_ID == 0 || LSMASH_IS_NON_EXISTING_BOX( mfra ) ) + return isom_non_existing_tfra(); + for( lsmash_entry_t *entry = mfra->tfra_list.head; entry; entry = entry->next ) + { + isom_tfra_t *tfra = (isom_tfra_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( tfra ) ) + return isom_non_existing_tfra(); + if( tfra->track_ID == track_ID ) + return tfra; + } + return isom_non_existing_tfra(); +} + +static int isom_add_elst_entry( isom_elst_t *elst, uint64_t segment_duration, int64_t media_time, int32_t media_rate ) +{ + assert( LSMASH_IS_EXISTING_BOX( elst->file ) ); + isom_elst_entry_t *data = lsmash_malloc( sizeof(isom_elst_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + data->segment_duration = segment_duration; + data->media_time = media_time; + data->media_rate = media_rate; + if( lsmash_list_add_entry( elst->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + if( !elst->file->undefined_64_ver + && (data->segment_duration > UINT32_MAX + || data->media_time > INT32_MAX + || data->media_time < INT32_MIN) ) + elst->version = 1; + return 0; +} + +/* This function returns 0 if failed, sample_entry_number if succeeded. */ +int lsmash_add_sample_entry( lsmash_root_t *root, uint32_t track_ID, void *summary ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( root ) || !summary + || ((lsmash_summary_t *)summary)->data_ref_index == 0 + || ((lsmash_summary_t *)summary)->data_ref_index > UINT16_MAX ) + return 0; + isom_trak_t *trak = isom_get_trak( root->file, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak ) + || LSMASH_IS_NON_EXISTING_BOX( trak->file ) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->hdlr ) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->minf->stbl->stsd ) ) + return 0; + isom_stsd_t *stsd = trak->mdia->minf->stbl->stsd; + lsmash_media_type media_type = trak->mdia->hdlr->componentSubtype; + if( isom_setup_sample_description( stsd, media_type, (lsmash_summary_t *)summary ) < 0 ) + return 0; + else + return stsd->list.entry_count; +} + +static int isom_add_stts_entry( isom_stbl_t *stbl, uint32_t sample_delta ) +{ + assert( LSMASH_IS_EXISTING_BOX( stbl->stts ) ); + if( !stbl->stts->list ) + return LSMASH_ERR_NAMELESS; + isom_stts_entry_t *data = lsmash_malloc( sizeof(isom_stts_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + data->sample_count = 1; + data->sample_delta = sample_delta; + if( lsmash_list_add_entry( stbl->stts->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + return 0; +} + +static int isom_add_ctts_entry( isom_stbl_t *stbl, uint32_t sample_count, uint32_t sample_offset ) +{ + assert( LSMASH_IS_EXISTING_BOX( stbl->ctts ) ); + if( !stbl->ctts->list ) + return LSMASH_ERR_NAMELESS; + isom_ctts_entry_t *data = lsmash_malloc( sizeof(isom_ctts_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + data->sample_count = sample_count; + data->sample_offset = sample_offset; + if( lsmash_list_add_entry( stbl->ctts->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + return 0; +} + +static int isom_add_stsc_entry( isom_stbl_t *stbl, uint32_t first_chunk, uint32_t samples_per_chunk, uint32_t sample_description_index ) +{ + assert( LSMASH_IS_EXISTING_BOX( stbl->stsc ) ); + if( !stbl->stsc->list ) + return LSMASH_ERR_NAMELESS; + isom_stsc_entry_t *data = lsmash_malloc( sizeof(isom_stsc_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + data->first_chunk = first_chunk; + data->samples_per_chunk = samples_per_chunk; + data->sample_description_index = sample_description_index; + if( lsmash_list_add_entry( stbl->stsc->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + return 0; +} + +static int isom_add_stsz_entry( isom_stbl_t *stbl, uint32_t entry_size ) +{ + assert( LSMASH_IS_EXISTING_BOX( stbl ) ); + if( LSMASH_IS_NON_EXISTING_BOX( stbl->stsz ) ) + return LSMASH_ERR_NAMELESS; + isom_stsz_t *stsz = stbl->stsz; + /* retrieve initial sample_size */ + if( stsz->sample_count == 0 ) + stsz->sample_size = entry_size; + /* if it seems constant sample size at present, update sample_count only */ + if( !stsz->list && stsz->sample_size == entry_size ) + { + ++ stsz->sample_count; + return 0; + } + /* found sample_size varies, create sample_size list */ + if( !stsz->list ) + { + stsz->list = lsmash_list_create_simple(); + if( !stsz->list ) + return LSMASH_ERR_MEMORY_ALLOC; + for( uint32_t i = 0; i < stsz->sample_count; i++ ) + { + isom_stsz_entry_t *data = lsmash_malloc( sizeof(isom_stsz_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + data->entry_size = stsz->sample_size; + if( lsmash_list_add_entry( stsz->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + } + stsz->sample_size = 0; + } + isom_stsz_entry_t *data = lsmash_malloc( sizeof(isom_stsz_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + data->entry_size = entry_size; + if( lsmash_list_add_entry( stsz->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + ++ stsz->sample_count; + return 0; +} + +static int isom_add_stss_entry( isom_stbl_t *stbl, uint32_t sample_number ) +{ + assert( LSMASH_IS_EXISTING_BOX( stbl->stss ) ); + if( !stbl->stss->list ) + return LSMASH_ERR_NAMELESS; + isom_stss_entry_t *data = lsmash_malloc( sizeof(isom_stss_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + data->sample_number = sample_number; + if( lsmash_list_add_entry( stbl->stss->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + return 0; +} + +static int isom_add_stps_entry( isom_stbl_t *stbl, uint32_t sample_number ) +{ + assert( LSMASH_IS_EXISTING_BOX( stbl->stps ) ); + if( !stbl->stps->list ) + return LSMASH_ERR_NAMELESS; + isom_stps_entry_t *data = lsmash_malloc( sizeof(isom_stps_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + data->sample_number = sample_number; + if( lsmash_list_add_entry( stbl->stps->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + return 0; +} + +/* Between ISOBMFF and QTFF, the most significant 2-bit has different meaning. + * For the maximum compatibility, we set 0 to the most significant 2-bit when compatible with + * both ISOBMFF with AVCFF extensions and QTFF. + * compatibility == 0 -> neither AVCFF extensions nor QTFF compatible + * compatibility == 1 -> AVCFF extensions compatible + * compatibility == 2 -> QTFF compatible + * compatibility == 3 -> both AVCFF extensions and QTFF compatible */ +static int isom_add_sdtp_entry( isom_box_t *parent, lsmash_sample_property_t *prop, int compatibility ) +{ + if( !prop || LSMASH_IS_NON_EXISTING_BOX( parent ) ) + return LSMASH_ERR_NAMELESS; + isom_sdtp_t *sdtp = isom_non_existing_sdtp(); + if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STBL ) ) + sdtp = ((isom_stbl_t *)parent)->sdtp; + else if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TRAF ) ) + sdtp = ((isom_traf_t *)parent)->sdtp; + else + assert( 0 ); + if( LSMASH_IS_NON_EXISTING_BOX( sdtp ) + || !sdtp->list ) + return LSMASH_ERR_NAMELESS; + isom_sdtp_entry_t *data = lsmash_malloc( sizeof(isom_sdtp_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + if( compatibility == 1 ) + data->is_leading = prop->leading & 0x03; + else if( compatibility == 2 ) + data->is_leading = prop->allow_earlier & 0x03; + else + { + data->is_leading = 0; + assert( compatibility == 3 ); + } + data->sample_depends_on = prop->independent & 0x03; + data->sample_is_depended_on = prop->disposable & 0x03; + data->sample_has_redundancy = prop->redundant & 0x03; + if( lsmash_list_add_entry( sdtp->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + return 0; +} + +static int isom_add_co64_entry( isom_stbl_t *stbl, uint64_t chunk_offset ) +{ + assert( LSMASH_IS_EXISTING_BOX( stbl->stco ) ); + if( !stbl->stco->list ) + return LSMASH_ERR_NAMELESS; + isom_co64_entry_t *data = lsmash_malloc( sizeof(isom_co64_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + data->chunk_offset = chunk_offset; + if( lsmash_list_add_entry( stbl->stco->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + return 0; +} + +static int isom_convert_stco_to_co64( isom_stbl_t *stbl ) +{ + assert( LSMASH_IS_EXISTING_BOX( stbl->stco ) ); + /* backup stco */ + int err = 0; + isom_stco_t *stco = stbl->stco; + LSMASH_MAKE_BOX_NON_EXISTING( stbl->stco ); + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_co64( stbl ) ) ) + { + err = LSMASH_ERR_NAMELESS; + goto fail; + } + /* move chunk_offset to co64 from stco */ + for( lsmash_entry_t *entry = stco->list->head; entry; entry = entry->next ) + { + isom_stco_entry_t *data = (isom_stco_entry_t*)entry->data; + if( (err = isom_add_co64_entry( stbl, data->chunk_offset )) < 0 ) + goto fail; + } +fail: + isom_remove_box_by_itself( stco ); + return err; +} + +static int isom_add_stco_entry( isom_stbl_t *stbl, uint64_t chunk_offset ) +{ + if( !stbl->stco->list ) + return LSMASH_ERR_NAMELESS; + if( stbl->stco->large_presentation ) + return isom_add_co64_entry( stbl, chunk_offset ); + if( chunk_offset > UINT32_MAX ) + { + int err = isom_convert_stco_to_co64( stbl ); + if( err < 0 ) + return err; + return isom_add_co64_entry( stbl, chunk_offset ); + } + isom_stco_entry_t *data = lsmash_malloc( sizeof(isom_stco_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + data->chunk_offset = (uint32_t)chunk_offset; + if( lsmash_list_add_entry( stbl->stco->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + return 0; +} + +static isom_sgpd_t *isom_get_sample_group_description_common( lsmash_entry_list_t *list, uint32_t grouping_type ) +{ + for( lsmash_entry_t *entry = list->head; entry; entry = entry->next ) + { + isom_sgpd_t *sgpd = (isom_sgpd_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( sgpd ) + || !sgpd->list ) + return isom_non_existing_sgpd(); + if( sgpd->grouping_type == grouping_type ) + return sgpd; + } + return isom_non_existing_sgpd(); +} + +static isom_sbgp_t *isom_get_sample_to_group_common( lsmash_entry_list_t *list, uint32_t grouping_type ) +{ + for( lsmash_entry_t *entry = list->head; entry; entry = entry->next ) + { + isom_sbgp_t *sbgp = (isom_sbgp_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( sbgp ) + || !sbgp->list ) + return isom_non_existing_sbgp(); + if( sbgp->grouping_type == grouping_type ) + return sbgp; + } + return isom_non_existing_sbgp(); +} + +isom_sgpd_t *isom_get_sample_group_description( isom_stbl_t *stbl, uint32_t grouping_type ) +{ + return isom_get_sample_group_description_common( &stbl->sgpd_list, grouping_type ); +} + +isom_sbgp_t *isom_get_sample_to_group( isom_stbl_t *stbl, uint32_t grouping_type ) +{ + return isom_get_sample_to_group_common( &stbl->sbgp_list, grouping_type ); +} + +isom_sgpd_t *isom_get_roll_recovery_sample_group_description( lsmash_entry_list_t *list ) +{ + isom_sgpd_t *sgpd; + if( ((sgpd = isom_get_sample_group_description_common( list, ISOM_GROUP_TYPE_ROLL )), LSMASH_IS_EXISTING_BOX( sgpd )) + || ((sgpd = isom_get_sample_group_description_common( list, ISOM_GROUP_TYPE_PROL )), LSMASH_IS_EXISTING_BOX( sgpd )) ) + return sgpd; + return isom_non_existing_sgpd(); +} + +isom_sbgp_t *isom_get_roll_recovery_sample_to_group( lsmash_entry_list_t *list ) +{ + isom_sbgp_t *sbgp; + if( ((sbgp = isom_get_sample_to_group_common( list, ISOM_GROUP_TYPE_ROLL )), LSMASH_IS_EXISTING_BOX( sbgp )) + || ((sbgp = isom_get_sample_to_group_common( list, ISOM_GROUP_TYPE_PROL )), LSMASH_IS_EXISTING_BOX( sbgp )) ) + return sbgp; + return isom_non_existing_sbgp(); +} + +isom_sgpd_t *isom_get_fragment_sample_group_description( isom_traf_t *traf, uint32_t grouping_type ) +{ + return isom_get_sample_group_description_common( &traf->sgpd_list, grouping_type ); +} + +isom_sbgp_t *isom_get_fragment_sample_to_group( isom_traf_t *traf, uint32_t grouping_type ) +{ + return isom_get_sample_to_group_common( &traf->sbgp_list, grouping_type ); +} + +static isom_rap_entry_t *isom_add_rap_group_entry( isom_sgpd_t *sgpd ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( sgpd ) ) + return NULL; + isom_rap_entry_t *data = lsmash_malloc( sizeof(isom_rap_entry_t) ); + if( !data ) + return NULL; + data->description_length = 0; + data->num_leading_samples_known = 0; + data->num_leading_samples = 0; + if( lsmash_list_add_entry( sgpd->list, data ) < 0 ) + { + lsmash_free( data ); + return NULL; + } + return data; +} + +static isom_roll_entry_t *isom_add_roll_group_entry( isom_sgpd_t *sgpd, int16_t roll_distance ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( sgpd ) ) + return NULL; + isom_roll_entry_t *data = lsmash_malloc( sizeof(isom_roll_entry_t) ); + if( !data ) + return NULL; + data->description_length = 0; + data->roll_distance = roll_distance; + if( lsmash_list_add_entry( sgpd->list, data ) < 0 ) + { + lsmash_free( data ); + return NULL; + } + return data; +} + +static isom_group_assignment_entry_t *isom_add_group_assignment_entry( isom_sbgp_t *sbgp, uint32_t sample_count, uint32_t group_description_index ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( sbgp ) ) + return NULL; + isom_group_assignment_entry_t *data = lsmash_malloc( sizeof(isom_group_assignment_entry_t) ); + if( !data ) + return NULL; + data->sample_count = sample_count; + data->group_description_index = group_description_index; + if( lsmash_list_add_entry( sbgp->list, data ) < 0 ) + { + lsmash_free( data ); + return NULL; + } + return data; +} + +static uint32_t isom_get_sample_count_from_sample_table( isom_stbl_t *stbl ) +{ + if( LSMASH_IS_EXISTING_BOX( stbl->stsz ) ) + return stbl->stsz->sample_count; + else if( LSMASH_IS_EXISTING_BOX( stbl->stz2 ) ) + return stbl->stz2->sample_count; + else + return 0; +} + +uint32_t isom_get_sample_count( isom_trak_t *trak ) +{ + return isom_get_sample_count_from_sample_table( trak->mdia->minf->stbl ); +} + +static uint64_t isom_get_dts( isom_stts_t *stts, uint32_t sample_number ) +{ + if( !stts->list ) + return 0; + uint64_t dts = 0; + uint32_t i = 1; + lsmash_entry_t *entry; + isom_stts_entry_t *data = NULL; + for( entry = stts->list->head; entry; entry = entry->next ) + { + data = (isom_stts_entry_t *)entry->data; + if( !data ) + return 0; + if( i + data->sample_count > sample_number ) + break; + dts += (uint64_t)data->sample_delta * data->sample_count; + i += data->sample_count; + } + if( !entry ) + return 0; + dts += (uint64_t)data->sample_delta * (sample_number - i); + return dts; +} + +#if 0 +static uint64_t isom_get_cts( isom_stts_t *stts, isom_ctts_t *ctts, uint32_t sample_number ) +{ + if( !stts->list ) + return 0; + if( LSMASH_IS_NON_EXISTING_BOX( ctts ) ) + return isom_get_dts( stts, sample_number ); + uint32_t i = 1; /* This can be 0 (and then condition below shall be changed) but I dare use same algorithm with isom_get_dts. */ + lsmash_entry_t *entry; + isom_ctts_entry_t *data = NULL; + if( sample_number == 0 ) + return 0; + for( entry = ctts->list->head; entry; entry = entry->next ) + { + data = (isom_ctts_entry_t *)entry->data; + if( !data ) + return 0; + if( i + data->sample_count > sample_number ) + break; + i += data->sample_count; + } + if( !entry ) + return 0; + return isom_get_dts( stts, sample_number ) + data->sample_offset; +} +#endif + +static int isom_replace_last_sample_delta( isom_stbl_t *stbl, uint32_t sample_delta ) +{ + assert( LSMASH_IS_EXISTING_BOX( stbl->stts ) ); + if( !stbl->stts->list + || !stbl->stts->list->tail + || !stbl->stts->list->tail->data ) + return LSMASH_ERR_NAMELESS; + isom_stts_entry_t *last_stts_data = (isom_stts_entry_t *)stbl->stts->list->tail->data; + if( sample_delta != last_stts_data->sample_delta ) + { + if( last_stts_data->sample_count > 1 ) + { + last_stts_data->sample_count -= 1; + int err = isom_add_stts_entry( stbl, sample_delta ); + if( err < 0 ) + return err; + } + else + last_stts_data->sample_delta = sample_delta; + } + return 0; +} + +static int isom_update_mdhd_duration( isom_trak_t *trak, uint32_t last_sample_delta ) +{ + assert( LSMASH_IS_EXISTING_BOX( trak ) ); + if( LSMASH_IS_NON_EXISTING_BOX( trak->file ) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->mdhd ) + || !trak->cache + || !trak->mdia->minf->stbl->stts->list ) + return LSMASH_ERR_INVALID_DATA; + lsmash_file_t *file = trak->file; + isom_mdhd_t *mdhd = trak->mdia->mdhd; + isom_stbl_t *stbl = trak->mdia->minf->stbl; + isom_stts_t *stts = stbl->stts; + isom_ctts_t *ctts = stbl->ctts; + isom_cslg_t *cslg = stbl->cslg; + mdhd->duration = 0; + uint32_t sample_count = isom_get_sample_count( trak ); + if( sample_count == 0 ) + { + /* Return error if non-fragmented movie has no samples. */ + if( !file->fragment && !stts->list->entry_count ) + return LSMASH_ERR_INVALID_DATA; + return 0; + } + /* Now we have at least 1 sample, so do stts_entry. */ + lsmash_entry_t *last_stts = stts->list->tail; + isom_stts_entry_t *last_stts_data = (isom_stts_entry_t *)last_stts->data; + if( sample_count == 1 ) + mdhd->duration = last_stts_data->sample_delta; + /* Now we have at least 2 samples, + * but dunno whether 1 stts_entry which has 2 samples or 2 stts_entry which has 1 samle each. */ + else if( LSMASH_IS_NON_EXISTING_BOX( ctts ) ) + { + /* use dts instead of cts */ + mdhd->duration = isom_get_dts( stts, sample_count ); + int err; + if( last_sample_delta ) + { + mdhd->duration += last_sample_delta; + if( (err = isom_replace_last_sample_delta( stbl, last_sample_delta )) < 0 ) + return err; + } + else if( last_stts_data->sample_count > 1 ) + mdhd->duration += last_stts_data->sample_delta; /* no need to update last_stts_data->sample_delta */ + else + { + /* Remove the last entry. */ + if( (err = lsmash_list_remove_entry_tail( stts->list )) < 0 ) + return err; + /* copy the previous sample_delta. */ + ++ ((isom_stts_entry_t *)stts->list->tail->data)->sample_count; + mdhd->duration += ((isom_stts_entry_t *)stts->list->tail->data)->sample_delta; + } + } + else + { + if( !ctts->list + || ctts->list->entry_count == 0 ) + return LSMASH_ERR_INVALID_DATA; + uint64_t dts = 0; + uint64_t max_cts = 0; + uint64_t max2_cts = 0; + uint64_t min_cts = LSMASH_TIMESTAMP_UNDEFINED; + int64_t max_offset = 0; + int64_t min_offset = UINT32_MAX; + int32_t ctd_shift = trak->cache->timestamp.ctd_shift; + uint32_t j = 0; + uint32_t k = 0; + lsmash_entry_t *stts_entry = stts->list->head; + lsmash_entry_t *ctts_entry = ctts->list->head; + for( uint32_t i = 0; i < sample_count; i++ ) + { + if( !ctts_entry || !stts_entry ) + return LSMASH_ERR_INVALID_DATA; + isom_stts_entry_t *stts_data = (isom_stts_entry_t *)stts_entry->data; + isom_ctts_entry_t *ctts_data = (isom_ctts_entry_t *)ctts_entry->data; + if( !stts_data || !ctts_data ) + return LSMASH_ERR_INVALID_DATA; + if( ctts_data->sample_offset != ISOM_NON_OUTPUT_SAMPLE_OFFSET ) + { + uint64_t cts; + if( ctd_shift ) + { + /* Anyway, add composition to decode timeline shift for calculating maximum and minimum CTS correctly. */ + int64_t sample_offset = (int32_t)ctts_data->sample_offset; + cts = dts + sample_offset + ctd_shift; + max_offset = LSMASH_MAX( max_offset, sample_offset ); + min_offset = LSMASH_MIN( min_offset, sample_offset ); + } + else + { + cts = dts + ctts_data->sample_offset; + max_offset = LSMASH_MAX( max_offset, ctts_data->sample_offset ); + min_offset = LSMASH_MIN( min_offset, ctts_data->sample_offset ); + } + min_cts = LSMASH_MIN( min_cts, cts ); + if( max_cts < cts ) + { + max2_cts = max_cts; + max_cts = cts; + } + else if( max2_cts < cts ) + max2_cts = cts; + } + dts += stts_data->sample_delta; + /* If finished sample_count of current entry, move to next. */ + if( ++j == ctts_data->sample_count ) + { + ctts_entry = ctts_entry->next; + j = 0; + } + if( ++k == stts_data->sample_count ) + { + stts_entry = stts_entry->next; + k = 0; + } + } + dts -= last_stts_data->sample_delta; + if( file->fragment ) + /* Overall presentation is extended exceeding this initial movie. + * So, any players shall display the movie exceeding the durations + * indicated in Movie Header Box, Track Header Boxes and Media Header Boxes. + * Samples up to the duration indicated in Movie Extends Header Box shall be displayed. + * In the absence of Movie Extends Header Box, all samples shall be displayed. */ + mdhd->duration += dts + last_sample_delta; + else + { + if( !last_sample_delta ) + { + /* The spec allows an arbitrary value for the duration of the last sample. So, we pick last-1 sample's. */ + last_sample_delta = max_cts - max2_cts; + } + if( min_cts != LSMASH_TIMESTAMP_UNDEFINED ) + mdhd->duration = max_cts - min_cts + last_sample_delta; + /* To match dts and media duration, update stts and mdhd relatively. */ + if( mdhd->duration > dts ) + last_sample_delta = mdhd->duration - dts; + else + mdhd->duration = dts + last_sample_delta; /* media duration must not less than last dts. */ + } + int err = isom_replace_last_sample_delta( stbl, last_sample_delta ); + if( err < 0 ) + return err; + /* Explicit composition information and timeline shifting */ + if( LSMASH_IS_EXISTING_BOX( cslg ) || file->qt_compatible || file->max_isom_version >= 4 ) + { + if( ctd_shift ) + { + /* Remove composition to decode timeline shift. */ + max_cts -= ctd_shift; + max2_cts -= ctd_shift; + min_cts -= ctd_shift; + } + int64_t composition_end_time = max_cts + (max_cts - max2_cts); + if( !file->fragment + && min_cts != LSMASH_TIMESTAMP_UNDEFINED + && (min_offset <= INT32_MAX) && (min_offset >= INT32_MIN) + && (max_offset <= INT32_MAX) && (max_offset >= INT32_MIN) + && ((int64_t)min_cts <= INT32_MAX) && (composition_end_time <= INT32_MAX) ) + { + if( LSMASH_IS_NON_EXISTING_BOX( cslg ) ) + { + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_cslg( trak->mdia->minf->stbl ) ) ) + return LSMASH_ERR_NAMELESS; + cslg = stbl->cslg; + } + cslg->compositionToDTSShift = ctd_shift; + cslg->leastDecodeToDisplayDelta = min_offset; + cslg->greatestDecodeToDisplayDelta = max_offset; + cslg->compositionStartTime = min_cts; + cslg->compositionEndTime = composition_end_time; + } + else + isom_remove_box_by_itself( cslg ); + } + } + if( mdhd->duration > UINT32_MAX && !file->undefined_64_ver ) + mdhd->version = 1; + return 0; +} + +static int isom_update_mvhd_duration( isom_moov_t *moov ) +{ + assert( LSMASH_IS_EXISTING_BOX( moov ) ); + if( LSMASH_IS_NON_EXISTING_BOX( moov->mvhd->file ) ) + return LSMASH_ERR_INVALID_DATA; + isom_mvhd_t *mvhd = moov->mvhd; + mvhd->duration = 0; + for( lsmash_entry_t *entry = moov->trak_list.head; entry; entry = entry->next ) + { + /* We pick maximum track duration as movie duration. */ + isom_trak_t *trak = (isom_trak_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( trak ) + || LSMASH_IS_NON_EXISTING_BOX( trak->tkhd ) ) + return LSMASH_ERR_INVALID_DATA; + mvhd->duration = entry != moov->trak_list.head + ? LSMASH_MAX( mvhd->duration, trak->tkhd->duration ) + : trak->tkhd->duration; + } + if( mvhd->duration > UINT32_MAX && !mvhd->file->undefined_64_ver ) + mvhd->version = 1; + return 0; +} + +int isom_update_tkhd_duration( isom_trak_t *trak ) +{ + assert( LSMASH_IS_EXISTING_BOX( trak ) ); + if( LSMASH_IS_NON_EXISTING_BOX( trak->tkhd ) + || LSMASH_IS_NON_EXISTING_BOX( trak->file->moov->mvhd ) ) + return LSMASH_ERR_INVALID_DATA; + lsmash_file_t *file = trak->file; + isom_tkhd_t *tkhd = trak->tkhd; + tkhd->duration = 0; + if( file->fragment + || LSMASH_IS_NON_EXISTING_BOX( trak->edts->elst ) ) + { + /* If this presentation might be extended or this track doesn't have edit list, calculate track duration from media duration. */ + if( LSMASH_IS_NON_EXISTING_BOX( trak->mdia->mdhd ) + || trak->mdia->mdhd->timescale == 0 ) + return LSMASH_ERR_INVALID_DATA; + if( trak->mdia->mdhd->duration == 0 ) + { + int err = isom_update_mdhd_duration( trak, 0 ); + if( err < 0 ) + return err; + } + tkhd->duration = trak->mdia->mdhd->duration * ((double)file->moov->mvhd->timescale / trak->mdia->mdhd->timescale); + } + else + { + /* If the presentation won't be extended and this track has any edit, then track duration is just the sum of the segment_duartions. */ + for( lsmash_entry_t *entry = trak->edts->elst->list->head; entry; entry = entry->next ) + { + isom_elst_entry_t *data = (isom_elst_entry_t *)entry->data; + if( !data ) + return LSMASH_ERR_INVALID_DATA; + tkhd->duration += data->segment_duration; + } + } + if( tkhd->duration > UINT32_MAX && !file->undefined_64_ver ) + tkhd->version = 1; + if( !file->fragment && tkhd->duration == 0 ) + tkhd->duration = tkhd->version == 1 ? 0xffffffffffffffff : 0xffffffff; + return isom_update_mvhd_duration( file->moov ); +} + +int lsmash_update_track_duration( lsmash_root_t *root, uint32_t track_ID, uint32_t last_sample_delta ) +{ + if( isom_check_initializer_present( root ) < 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_file_t *file = root->file; + isom_trak_t *trak = isom_get_trak( file, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak ) ) + return LSMASH_ERR_NAMELESS; + int err = isom_update_mdhd_duration( trak, last_sample_delta ); + if( err < 0 ) + return err; + /* If the presentation won't be extended and this track has any edit, we don't change or update duration in tkhd. */ + if( !file->fragment + && LSMASH_IS_EXISTING_BOX( trak->edts ) + && LSMASH_IS_EXISTING_BOX( trak->edts->elst ) ) + err = isom_update_mvhd_duration( file->moov ); /* Only update movie duration. */ + else + err = isom_update_tkhd_duration( trak ); /* Also update movie duration internally. */ + return err; +} + +static inline int isom_increment_sample_number_in_entry +( + uint32_t *sample_number_in_entry, + uint32_t sample_count_in_entry, + lsmash_entry_t **entry +) +{ + if( *sample_number_in_entry != sample_count_in_entry ) + { + *sample_number_in_entry += 1; + return 0; + } + /* Precede the next entry. */ + *sample_number_in_entry = 1; + if( *entry ) + { + *entry = (*entry)->next; + if( *entry && !(*entry)->data ) + return LSMASH_ERR_INVALID_DATA; + } + return 0; +} + +int isom_calculate_bitrate_description +( + isom_stbl_t *stbl, + isom_mdhd_t *mdhd, + uint32_t *bufferSizeDB, + uint32_t *maxBitrate, + uint32_t *avgBitrate, + uint32_t sample_description_index +) +{ + isom_stsz_t *stsz = stbl->stsz; + lsmash_entry_list_t *stsz_list = LSMASH_IS_EXISTING_BOX( stsz ) ? stsz->list : stbl->stz2->list; + lsmash_entry_t *stsz_entry = stsz_list ? stsz_list->head : NULL; + lsmash_entry_t *stts_entry = stbl->stts->list->head; + lsmash_entry_t *stsc_entry = NULL; + lsmash_entry_t *next_stsc_entry = stbl->stsc->list->head; + isom_stts_entry_t *stts_data = NULL; + isom_stsc_entry_t *stsc_data = NULL; + if( next_stsc_entry && !next_stsc_entry->data ) + return LSMASH_ERR_INVALID_DATA; + uint32_t rate = 0; + uint64_t dts = 0; + uint32_t time_wnd = 0; + uint32_t chunk_number = 0; + uint32_t sample_number_in_stts = 1; + uint32_t sample_number_in_chunk = 1; + uint32_t constant_sample_size = LSMASH_IS_EXISTING_BOX( stsz ) ? stsz->sample_size : 0; + *bufferSizeDB = 0; + *maxBitrate = 0; + *avgBitrate = 0; + while( stts_entry ) + { + int err; + if( !stsc_data || sample_number_in_chunk == stsc_data->samples_per_chunk ) + { + /* Move the next chunk. */ + sample_number_in_chunk = 1; + ++chunk_number; + /* Check if the next entry is broken. */ + while( next_stsc_entry && ((isom_stsc_entry_t *)next_stsc_entry->data)->first_chunk < chunk_number ) + { + /* Just skip broken next entry. */ + next_stsc_entry = next_stsc_entry->next; + if( next_stsc_entry && !next_stsc_entry->data ) + return LSMASH_ERR_INVALID_DATA; + } + /* Check if the next chunk belongs to the next sequence of chunks. */ + if( next_stsc_entry && ((isom_stsc_entry_t *)next_stsc_entry->data)->first_chunk == chunk_number ) + { + stsc_entry = next_stsc_entry; + next_stsc_entry = next_stsc_entry->next; + if( next_stsc_entry && !next_stsc_entry->data ) + return LSMASH_ERR_INVALID_DATA; + stsc_data = (isom_stsc_entry_t *)stsc_entry->data; + /* Check if the next contiguous chunks belong to given sample description. */ + if( stsc_data->sample_description_index != sample_description_index ) + { + /* Skip chunks which don't belong to given sample description. */ + uint32_t number_of_skips = 0; + uint32_t first_chunk = stsc_data->first_chunk; + uint32_t samples_per_chunk = stsc_data->samples_per_chunk; + while( next_stsc_entry ) + { + if( ((isom_stsc_entry_t *)next_stsc_entry->data)->sample_description_index != sample_description_index ) + { + stsc_data = (isom_stsc_entry_t *)next_stsc_entry->data; + number_of_skips += (stsc_data->first_chunk - first_chunk) * samples_per_chunk; + first_chunk = stsc_data->first_chunk; + samples_per_chunk = stsc_data->samples_per_chunk; + } + else if( ((isom_stsc_entry_t *)next_stsc_entry->data)->first_chunk <= first_chunk ) + ; /* broken entry */ + else + break; + /* Just skip the next entry. */ + next_stsc_entry = next_stsc_entry->next; + if( next_stsc_entry && !next_stsc_entry->data ) + return LSMASH_ERR_INVALID_DATA; + } + if( !next_stsc_entry ) + break; /* There is no more chunks which don't belong to given sample description. */ + number_of_skips += (((isom_stsc_entry_t *)next_stsc_entry->data)->first_chunk - first_chunk) * samples_per_chunk; + for( uint32_t i = 0; i < number_of_skips; i++ ) + { + if( stsz_list ) + { + if( !stsz_entry ) + break; + stsz_entry = stsz_entry->next; + } + if( !stts_entry ) + break; + if( (err = isom_increment_sample_number_in_entry( &sample_number_in_stts, + ((isom_stts_entry_t *)stts_entry->data)->sample_count, + &stts_entry )) < 0 ) + return err; + } + if( (stsz_list && !stsz_entry) || !stts_entry ) + break; + chunk_number = stsc_data->first_chunk; + } + } + } + else + ++sample_number_in_chunk; + /* Get current sample's size. */ + uint32_t size; + if( stsz_list ) + { + if( !stsz_entry ) + break; + isom_stsz_entry_t *stsz_data = (isom_stsz_entry_t *)stsz_entry->data; + if( !stsz_data ) + return LSMASH_ERR_INVALID_DATA; + size = stsz_data->entry_size; + stsz_entry = stsz_entry->next; + } + else + size = constant_sample_size; + /* Get current sample's DTS. */ + if( stts_data ) + dts += stts_data->sample_delta; + stts_data = (isom_stts_entry_t *)stts_entry->data; + if( !stts_data ) + return LSMASH_ERR_INVALID_DATA; + if( (err = isom_increment_sample_number_in_entry( &sample_number_in_stts, stts_data->sample_count, &stts_entry )) < 0 ) + return err; + /* Calculate bitrate description. */ + if( *bufferSizeDB < size ) + *bufferSizeDB = size; + *avgBitrate += size; + rate += size; + if( dts > time_wnd + mdhd->timescale ) + { + if( rate > *maxBitrate ) + *maxBitrate = rate; + time_wnd = dts; + rate = 0; + } + } + double duration = (double)mdhd->duration / mdhd->timescale; + *avgBitrate = (uint32_t)(*avgBitrate / duration); + if( *maxBitrate == 0 ) + *maxBitrate = *avgBitrate; + /* Convert to bits per second. */ + *maxBitrate *= 8; + *avgBitrate *= 8; + return 0; +} + +int isom_is_variable_size( isom_stbl_t *stbl ) +{ + if( (LSMASH_IS_EXISTING_BOX( stbl->stz2 ) && stbl->stz2->sample_count > 1) + || (LSMASH_IS_EXISTING_BOX( stbl->stsz ) && stbl->stsz->sample_count > 1 && stbl->stsz->sample_size == 0) ) + return 1; + else + return 0; +} + +uint32_t isom_get_first_sample_size( isom_stbl_t *stbl ) +{ + if( LSMASH_IS_EXISTING_BOX( stbl->stsz ) ) + { + /* 'stsz' */ + if( stbl->stsz->sample_size ) + return stbl->stsz->sample_size; + else if( stbl->stsz->list && stbl->stsz->list->head && stbl->stsz->list->head->data ) + return ((isom_stsz_entry_t *)stbl->stsz->list->head->data)->entry_size; + else + return 0; + } + else if( LSMASH_IS_EXISTING_BOX( stbl->stz2 ) ) + { + /* stz2 */ + if( stbl->stz2->list && stbl->stz2->list->head && stbl->stz2->list->head->data ) + return ((isom_stsz_entry_t *)stbl->stz2->list->head->data)->entry_size; + else + return 0; + } + else + return 0; +} + +int isom_update_bitrate_description( isom_mdia_t *mdia ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( mdia->mdhd ) ) + return LSMASH_ERR_INVALID_DATA; + isom_stbl_t *stbl = mdia->minf->stbl; + if( LSMASH_IS_NON_EXISTING_BOX( stbl->stsd ) + || (LSMASH_IS_NON_EXISTING_BOX( stbl->stsz ) && LSMASH_IS_NON_EXISTING_BOX( stbl->stz2 )) + || !stbl->stsc->list + || !stbl->stts->list ) + return LSMASH_ERR_INVALID_DATA; + uint32_t sample_description_index = 0; + for( lsmash_entry_t *entry = stbl->stsd->list.head; entry; entry = entry->next ) + { + isom_sample_entry_t *sample_entry = (isom_sample_entry_t *)entry->data; + if( !sample_entry ) + return LSMASH_ERR_INVALID_DATA; + ++sample_description_index; + isom_bitrate_updater_t func_update_bitrate = isom_get_bitrate_updater( sample_entry ); + if( func_update_bitrate ) + { + int err = func_update_bitrate( stbl, mdia->mdhd, sample_description_index ); + if( err < 0 ) + return err; + } + } + return sample_description_index ? 0 : LSMASH_ERR_INVALID_DATA; +} + +static inline uint64_t isom_get_current_mp4time( void ) +{ + return (uint64_t)time( NULL ) + ISOM_MAC_EPOCH_OFFSET; +} + +static int isom_set_media_creation_time( isom_trak_t *trak, uint64_t current_mp4time ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( trak->mdia->mdhd ) ) + return LSMASH_ERR_NAMELESS; + isom_mdhd_t *mdhd = trak->mdia->mdhd; + if( mdhd->creation_time == 0 ) + mdhd->creation_time = mdhd->modification_time = current_mp4time; + return 0; +} + +static int isom_set_track_creation_time( isom_trak_t *trak, uint64_t current_mp4time ) +{ + assert( LSMASH_IS_EXISTING_BOX( trak ) ); + if( LSMASH_IS_NON_EXISTING_BOX( trak->tkhd ) ) + return LSMASH_ERR_NAMELESS; + isom_tkhd_t *tkhd = trak->tkhd; + if( tkhd->creation_time == 0 ) + tkhd->creation_time = tkhd->modification_time = current_mp4time; + return isom_set_media_creation_time( trak, current_mp4time ); +} + +static int isom_set_movie_creation_time( lsmash_file_t *file ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( file->moov->mvhd ) ) + return LSMASH_ERR_NAMELESS; + uint64_t current_mp4time = isom_get_current_mp4time(); + for( lsmash_entry_t *entry = file->moov->trak_list.head; entry; entry = entry->next ) + { + isom_trak_t *trak = (isom_trak_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( trak ) ) + return LSMASH_ERR_INVALID_DATA; + int err = isom_set_track_creation_time( trak, current_mp4time ); + if( err < 0 ) + return err; + } + isom_mvhd_t *mvhd = file->moov->mvhd; + if( mvhd->creation_time == 0 ) + mvhd->creation_time = mvhd->modification_time = current_mp4time; + return 0; +} + +int isom_setup_handler_reference( isom_hdlr_t *hdlr, uint32_t media_type ) +{ + assert( LSMASH_IS_EXISTING_BOX( hdlr ) ); + isom_box_t *parent = hdlr->parent; + lsmash_file_t *file = hdlr->file; + if( LSMASH_IS_NON_EXISTING_BOX( parent ) + || LSMASH_IS_NON_EXISTING_BOX( file ) ) + return LSMASH_ERR_NAMELESS; + isom_mdia_t *mdia = lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MDIA ) ? (isom_mdia_t *)parent : isom_non_existing_mdia(); + isom_meta_t *meta = lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_META ) ? (isom_meta_t *)parent + : lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_META ) ? (isom_meta_t *)parent : isom_non_existing_meta(); + uint32_t type = LSMASH_IS_EXISTING_BOX( mdia ) ? (file->qt_compatible ? QT_HANDLER_TYPE_MEDIA : 0) + : (LSMASH_IS_EXISTING_BOX( meta ) ? 0 : QT_HANDLER_TYPE_DATA); + uint32_t subtype = media_type; + hdlr->componentType = type; + hdlr->componentSubtype = subtype; + char *type_name = NULL; + char *subtype_name = NULL; + uint8_t type_name_length = 0; + uint8_t subtype_name_length = 0; + if( mdia ) + type_name = "Media "; + else if( meta ) + type_name = "Metadata "; + else /* if( minf ) */ + type_name = "Data "; + type_name_length = strlen( type_name ); + struct + { + uint32_t subtype; + char *subtype_name; + uint8_t subtype_name_length; + } subtype_table[] = + { + { ISOM_MEDIA_HANDLER_TYPE_AUDIO_TRACK, "Sound ", 6 }, + { ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK, "Video ", 6 }, + { ISOM_MEDIA_HANDLER_TYPE_HINT_TRACK, "Hint ", 5 }, + { ISOM_MEDIA_HANDLER_TYPE_TIMED_METADATA_TRACK, "Metadata ", 9 }, + { ISOM_MEDIA_HANDLER_TYPE_TEXT_TRACK, "Text ", 5 }, + { ISOM_META_HANDLER_TYPE_ITUNES_METADATA, "iTunes ", 7 }, + { QT_REFERENCE_HANDLER_TYPE_ALIAS, "Alias ", 6 }, + { QT_REFERENCE_HANDLER_TYPE_RESOURCE, "Resource ", 9 }, + { QT_REFERENCE_HANDLER_TYPE_URL, "URL ", 4 }, + { subtype, "Unknown ", 8 } + }; + for( int i = 0; subtype_table[i].subtype; i++ ) + if( subtype == subtype_table[i].subtype ) + { + subtype_name = subtype_table[i].subtype_name; + subtype_name_length = subtype_table[i].subtype_name_length; + break; + } + uint32_t name_length = 15 + subtype_name_length + type_name_length + file->isom_compatible + file->qt_compatible; + uint8_t *name = lsmash_malloc( name_length ); + if( !name ) + return LSMASH_ERR_MEMORY_ALLOC; + if( file->qt_compatible ) + name[0] = name_length & 0xff; + memcpy( name + file->qt_compatible, "L-SMASH ", 8 ); + memcpy( name + file->qt_compatible + 8, subtype_name, subtype_name_length ); + memcpy( name + file->qt_compatible + 8 + subtype_name_length, type_name, type_name_length ); + memcpy( name + file->qt_compatible + 8 + subtype_name_length + type_name_length, "Handler", 7 ); + if( file->isom_compatible ) + name[name_length - 1] = 0; + hdlr->componentName = name; + hdlr->componentName_length = name_length; + return 0; +} + +isom_trak_t *isom_track_create( lsmash_file_t *file, lsmash_media_type media_type ) +{ + /* Don't allow to create a new track if the initial movie is already written. */ + if( (file->fragment && file->fragment->movie) + || (LSMASH_IS_EXISTING_BOX( file->moov ) && (file->moov->manager & LSMASH_WRITTEN_BOX)) ) + return isom_non_existing_trak(); + isom_trak_t *trak = isom_add_trak( file->moov ); + if( LSMASH_IS_NON_EXISTING_BOX( trak->file->moov->mvhd ) ) + goto fail; + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_tkhd( trak ) ) + || LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_mdia( trak ) ) + || LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_mdhd( trak->mdia ) ) + || LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_minf( trak->mdia ) ) + || LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_dinf( trak->mdia->minf ) ) + || LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_dref( trak->mdia->minf->dinf ) ) + || LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_stbl( trak->mdia->minf ) ) + || LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_stsd( trak->mdia->minf->stbl ) ) + || LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_stts( trak->mdia->minf->stbl ) ) + || LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_stsc( trak->mdia->minf->stbl ) ) + || LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_stco( trak->mdia->minf->stbl ) ) + || LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_stsz( trak->mdia->minf->stbl ) ) ) + goto fail; + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_hdlr( trak->mdia ) ) + || isom_setup_handler_reference( trak->mdia->hdlr, media_type ) < 0 ) + goto fail; + if( file->qt_compatible ) + { + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_hdlr( trak->mdia->minf ) ) + || isom_setup_handler_reference( trak->mdia->minf->hdlr, QT_REFERENCE_HANDLER_TYPE_URL ) < 0 ) + goto fail; + } + switch( media_type ) + { + case ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK : + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_vmhd( trak->mdia->minf ) ) ) + goto fail; + trak->mdia->minf->vmhd->flags = 0x000001; + break; + case ISOM_MEDIA_HANDLER_TYPE_AUDIO_TRACK : + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_smhd( trak->mdia->minf ) ) ) + goto fail; + trak->cache->is_audio = 1; + break; + case ISOM_MEDIA_HANDLER_TYPE_HINT_TRACK : + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_hmhd( trak->mdia->minf ) ) ) + goto fail; + trak->mdia->minf->hmhd->combinedPDUsize = 0; + trak->mdia->minf->hmhd->PDUcount = 0; + trak->mdia->minf->hmhd->maxPDUsize = 0; + break; + case ISOM_MEDIA_HANDLER_TYPE_TEXT_TRACK : + if( file->qt_compatible || file->itunes_movie ) + { + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_gmhd( trak->mdia->minf ) ) + || LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_gmin( trak->mdia->minf->gmhd ) ) + || LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_text( trak->mdia->minf->gmhd ) ) ) + return 0; + /* Default Text Media Information Box. */ + { + isom_text_t *text = trak->mdia->minf->gmhd->text; + text->matrix[0] = 0x00010000; + text->matrix[4] = 0x00010000; + text->matrix[8] = 0x40000000; + } + } + else + goto fail; /* We support only reference text media track for chapter yet. */ + break; + default : + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_nmhd( trak->mdia->minf ) ) ) + goto fail; + break; + } + /* Default Track Header Box. */ + { + isom_tkhd_t *tkhd = trak->tkhd; + if( media_type == ISOM_MEDIA_HANDLER_TYPE_AUDIO_TRACK ) + tkhd->volume = 0x0100; + tkhd->matrix[0] = 0x00010000; + tkhd->matrix[4] = 0x00010000; + tkhd->matrix[8] = 0x40000000; + tkhd->duration = 0xffffffff; + tkhd->track_ID = trak->file->moov->mvhd->next_track_ID ++; + } + trak->mdia->mdhd->language = file->qt_compatible ? 0 : ISOM_LANGUAGE_CODE_UNDEFINED; + return trak; +fail: + isom_remove_box_by_itself( trak ); + return isom_non_existing_trak(); +} + +isom_moov_t *isom_movie_create( lsmash_file_t *file ) +{ + isom_moov_t *moov = isom_add_moov( file ); + isom_mvhd_t *mvhd = isom_add_mvhd( moov ); + if( LSMASH_IS_NON_EXISTING_BOX( mvhd ) ) + { + isom_remove_box_by_itself( moov ); + return isom_non_existing_moov(); + } + /* Default Movie Header Box. */ + mvhd->rate = 0x00010000; + mvhd->volume = 0x0100; + mvhd->matrix[0] = 0x00010000; + mvhd->matrix[4] = 0x00010000; + mvhd->matrix[8] = 0x40000000; + mvhd->next_track_ID = 1; + file->initializer = file; + return moov; +} + +/******************************* + public interfaces +*******************************/ + +/*---- track manipulators ----*/ + +void lsmash_delete_track( lsmash_root_t *root, uint32_t track_ID ) +{ + if( isom_check_initializer_present( root ) < 0 ) + return; + for( lsmash_entry_t *entry = root->file->initializer->moov->trak_list.head; entry; entry = entry->next ) + { + isom_trak_t *trak = (isom_trak_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( trak ) + || LSMASH_IS_NON_EXISTING_BOX( trak->tkhd ) ) + return; + if( trak->tkhd->track_ID == track_ID ) + { + isom_remove_box_by_itself( trak ); + return; + } + } +} + +uint32_t lsmash_create_track( lsmash_root_t *root, lsmash_media_type media_type ) +{ + if( isom_check_initializer_present( root ) < 0 ) + return 0; + isom_trak_t *trak = isom_track_create( root->file, media_type ); + if( LSMASH_IS_NON_EXISTING_BOX( trak ) + || LSMASH_IS_NON_EXISTING_BOX( trak->tkhd ) ) + return 0; + return trak->tkhd->track_ID; +} + +uint32_t lsmash_get_track_ID( lsmash_root_t *root, uint32_t track_number ) +{ + if( isom_check_initializer_present( root ) < 0 + || LSMASH_IS_NON_EXISTING_BOX( root->file->initializer->moov ) ) + return 0; + isom_trak_t *trak = (isom_trak_t *)lsmash_list_get_entry_data( &root->file->initializer->moov->trak_list, track_number ); + if( LSMASH_IS_NON_EXISTING_BOX( trak ) + || LSMASH_IS_NON_EXISTING_BOX( trak->tkhd ) ) + return 0; + return trak->tkhd->track_ID; +} + +void lsmash_initialize_track_parameters( lsmash_track_parameters_t *param ) +{ + memset( param, 0, sizeof(lsmash_track_parameters_t) ); + param->audio_volume = 0x0100; + param->matrix[0] = 0x00010000; + param->matrix[4] = 0x00010000; + param->matrix[8] = 0x40000000; +} + +int lsmash_set_track_parameters( lsmash_root_t *root, uint32_t track_ID, lsmash_track_parameters_t *param ) +{ + if( isom_check_initializer_present( root ) < 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_file_t *file = root->file; + isom_trak_t *trak = isom_get_trak( file, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak->tkhd ) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->hdlr ) + || LSMASH_IS_NON_EXISTING_BOX( file->moov->mvhd ) ) + return LSMASH_ERR_NAMELESS; + /* Prepare Track Aperture Modes if required. */ + if( file->qt_compatible && param->aperture_modes ) + { + if( LSMASH_IS_NON_EXISTING_BOX( trak->tapt ) && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_tapt( trak ) ) ) + return LSMASH_ERR_NAMELESS; + isom_tapt_t *tapt = trak->tapt; + if( (LSMASH_IS_NON_EXISTING_BOX( tapt->clef ) && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_clef( tapt ) )) + || (LSMASH_IS_NON_EXISTING_BOX( tapt->prof ) && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_prof( tapt ) )) + || (LSMASH_IS_NON_EXISTING_BOX( tapt->enof ) && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_enof( tapt ) )) ) + return LSMASH_ERR_NAMELESS; + } + else + isom_remove_box_by_itself( trak->tapt ); + /* Set up Track Header. */ + uint32_t media_type = trak->mdia->hdlr->componentSubtype; + isom_tkhd_t *tkhd = trak->tkhd; + tkhd->flags = param->mode; + tkhd->track_ID = param->track_ID ? param->track_ID : tkhd->track_ID; + tkhd->duration = LSMASH_IS_NON_EXISTING_BOX( trak->edts->elst ) ? param->duration : tkhd->duration; + /* Template fields + * alternate_group, layer, volume and matrix + * According to 14496-14, these value are all set to defaut values in 14496-12. + * And when a file is read as an MPEG-4 file, these values shall be ignored. + * If a file complies with other specifications, then those fields may have non-default values + * as required by those other specifications. */ + if( param->alternate_group ) + { + if( file->qt_compatible || file->itunes_movie || file->max_3gpp_version >= 4 ) + tkhd->alternate_group = param->alternate_group; + else + { + tkhd->alternate_group = 0; + lsmash_log( NULL, LSMASH_LOG_WARNING, + "alternate_group is specified but not compatible with any of the brands. It won't be set.\n" ); + } + } + else + tkhd->alternate_group = 0; + if( file->qt_compatible || file->itunes_movie ) + { + tkhd->layer = media_type == ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK ? param->video_layer : 0; + tkhd->volume = media_type == ISOM_MEDIA_HANDLER_TYPE_AUDIO_TRACK ? param->audio_volume : 0; + if( media_type == ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK ) + for( int i = 0; i < 9; i++ ) + tkhd->matrix[i] = param->matrix[i]; + else + for( int i = 0; i < 9; i++ ) + tkhd->matrix[i] = 0; + } + else + { + tkhd->layer = 0; + tkhd->volume = media_type == ISOM_MEDIA_HANDLER_TYPE_AUDIO_TRACK ? 0x0100 : 0; + tkhd->matrix[0] = 0x00010000; + tkhd->matrix[1] = 0; + tkhd->matrix[2] = 0; + tkhd->matrix[3] = 0; + tkhd->matrix[4] = 0x00010000; + tkhd->matrix[5] = 0; + tkhd->matrix[6] = 0; + tkhd->matrix[7] = 0; + tkhd->matrix[8] = 0x40000000; + } + /* visual presentation size */ + tkhd->width = media_type == ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK ? param->display_width : 0; + tkhd->height = media_type == ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK ? param->display_height : 0; + /* Update next_track_ID if needed. */ + if( file->moov->mvhd->next_track_ID <= tkhd->track_ID ) + file->moov->mvhd->next_track_ID = tkhd->track_ID + 1; + return 0; +} + +int lsmash_get_track_parameters( lsmash_root_t *root, uint32_t track_ID, lsmash_track_parameters_t *param ) +{ + if( isom_check_initializer_present( root ) < 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + isom_trak_t *trak = isom_get_trak( root->file->initializer, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak->tkhd ) ) + return LSMASH_ERR_NAMELESS; + isom_tkhd_t *tkhd = trak->tkhd; + param->mode = tkhd->flags; + param->track_ID = tkhd->track_ID; + param->duration = tkhd->duration; + param->video_layer = tkhd->layer; + param->alternate_group = tkhd->alternate_group; + param->audio_volume = tkhd->volume; + for( int i = 0; i < 9; i++ ) + param->matrix[i] = tkhd->matrix[i]; + param->display_width = tkhd->width; + param->display_height = tkhd->height; + param->aperture_modes = LSMASH_IS_EXISTING_BOX( trak->tapt ); + return 0; +} + +static inline int check_dref_presence( isom_trak_t *trak ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( trak->mdia->minf->dinf->dref ) ) + return LSMASH_ERR_NAMELESS; + return 0; +} + +uint32_t lsmash_count_data_reference +( + lsmash_root_t *root, + uint32_t track_ID +) +{ + if( isom_check_initializer_present( root ) < 0 ) + return 0; + isom_trak_t *trak = isom_get_trak( root->file->initializer, track_ID ); + if( check_dref_presence( trak ) < 0 ) + return 0; + return trak->mdia->minf->dinf->dref->list.entry_count; +} + +int lsmash_get_data_reference +( + lsmash_root_t *root, + uint32_t track_ID, + lsmash_data_reference_t *data_ref +) +{ + if( isom_check_initializer_present( root ) < 0 || !data_ref ) + return LSMASH_ERR_FUNCTION_PARAM; + isom_trak_t *trak = isom_get_trak( root->file->initializer, track_ID ); + if( check_dref_presence( trak ) < 0 ) + return LSMASH_ERR_NAMELESS; + isom_dref_entry_t *url = lsmash_list_get_entry_data( &trak->mdia->minf->dinf->dref->list, data_ref->index ); + if( LSMASH_IS_NON_EXISTING_BOX( url ) ) + return LSMASH_ERR_NAMELESS; + if( !(url->flags & 0x000001) && url->location ) + { + int length = strlen( url->location ); + char *location = lsmash_malloc( length + 1 ); + if( !location ) + return LSMASH_ERR_MEMORY_ALLOC; + memcpy( location, url->location, length ); + location[length] = '\0'; + data_ref->location = location; + } + else + data_ref->location = NULL; + return 0; +} + +void lsmash_cleanup_data_reference +( + lsmash_data_reference_t *data_ref +) +{ + if( !data_ref ) + return; + lsmash_freep( &data_ref->location ); +} + +int lsmash_create_data_reference +( + lsmash_root_t *root, + uint32_t track_ID, + lsmash_data_reference_t *data_ref, + lsmash_file_t *file +) +{ + /* At present, we don't support external data references for movie fragments. + * Note that, for external media data, default-base-is-moof is meaningless since relative + * offsets from Movie Fragment Boxes make no sense. + * In the future, the condition of !(file->flags & LSMASH_FILE_MODE_WRITE) may be removed + * for the implementation which does not write actually and does reference read-only file. */ + if( LSMASH_IS_NON_EXISTING_BOX( root ) + || LSMASH_IS_NON_EXISTING_BOX( file ) + || file->root != root + || (!(file->flags & LSMASH_FILE_MODE_MEDIA) && !(file->flags & LSMASH_FILE_MODE_INITIALIZATION)) + || !(file->flags & LSMASH_FILE_MODE_WRITE) + || (root->file != file && ((file->flags & LSMASH_FILE_MODE_FRAGMENTED) || file->fragment)) + || !data_ref ) + return LSMASH_ERR_FUNCTION_PARAM; + isom_trak_t *trak = isom_get_trak( root->file, track_ID ); + if( check_dref_presence( trak ) < 0 ) + return LSMASH_ERR_NAMELESS; + isom_dref_entry_t *url = isom_add_dref_entry( trak->mdia->minf->dinf->dref, ISOM_BOX_TYPE_URL ); + if( LSMASH_IS_NON_EXISTING_BOX( url ) ) + return LSMASH_ERR_NAMELESS; + if( !data_ref->location || root->file == file ) + { + /* Media data is in the same file. */ + url->flags = 0x000001; + url->ref_file = root->file; + } + else + { + /* Set the location of the file. */ + int length = strlen( data_ref->location ); + url->location = lsmash_malloc( length + 1 ); + if( !url->location ) + { + isom_remove_box_by_itself( url ); + return LSMASH_ERR_MEMORY_ALLOC; + } + memcpy( url->location, data_ref->location, length ); + url->location[length] = '\0'; + url->location_length = length + 1; + url->ref_file = file; + } + data_ref->index = trak->mdia->minf->dinf->dref->list.entry_count; + return 0; +} + +int lsmash_assign_data_reference +( + lsmash_root_t *root, + uint32_t track_ID, + uint32_t data_ref_index, + lsmash_file_t *file +) +{ + if( isom_check_initializer_present( root ) < 0 + || !file || file->root != root + || !(file->flags & LSMASH_FILE_MODE_MEDIA) + || !(file->flags & LSMASH_FILE_MODE_READ) + || data_ref_index == 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + isom_trak_t *trak = isom_get_trak( root->file->initializer, track_ID ); + if( check_dref_presence( trak ) < 0 ) + return LSMASH_ERR_NAMELESS; + isom_dref_entry_t *url = (isom_dref_entry_t *)lsmash_list_get_entry_data( &trak->mdia->minf->dinf->dref->list, data_ref_index ); + if( LSMASH_IS_NON_EXISTING_BOX( url ) ) + return LSMASH_ERR_NAMELESS; + if( !(url->flags & 0x000001) ) + /* Reference an external media data. */ + url->ref_file = file; + return 0; +} + +static int isom_set_media_handler_name( lsmash_file_t *file, uint32_t track_ID, char *handler_name ) +{ + isom_trak_t *trak = isom_get_trak( file, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak->mdia->hdlr ) ) + return LSMASH_ERR_NAMELESS; + isom_hdlr_t *hdlr = trak->mdia->hdlr; + uint8_t *name = NULL; + uint32_t name_length = strlen( handler_name ) + file->isom_compatible + file->qt_compatible; + if( file->qt_compatible ) + name_length = LSMASH_MIN( name_length, 255 ); + if( name_length > hdlr->componentName_length && hdlr->componentName ) + name = lsmash_realloc( hdlr->componentName, name_length ); + else if( !hdlr->componentName ) + name = lsmash_malloc( name_length ); + else + name = hdlr->componentName; + if( !name ) + return LSMASH_ERR_MEMORY_ALLOC; + if( file->qt_compatible ) + name[0] = name_length & 0xff; + memcpy( name + file->qt_compatible, handler_name, strlen( handler_name ) ); + if( file->isom_compatible ) + name[name_length - 1] = 0; + hdlr->componentName = name; + hdlr->componentName_length = name_length; + return 0; +} + +static int isom_set_data_handler_name( lsmash_file_t *file, uint32_t track_ID, char *handler_name ) +{ + isom_trak_t *trak = isom_get_trak( file, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak->mdia->minf->hdlr ) ) + return LSMASH_ERR_NAMELESS; + isom_hdlr_t *hdlr = trak->mdia->minf->hdlr; + uint8_t *name = NULL; + uint32_t name_length = strlen( handler_name ) + file->isom_compatible + file->qt_compatible; + if( file->qt_compatible ) + name_length = LSMASH_MIN( name_length, 255 ); + if( name_length > hdlr->componentName_length && hdlr->componentName ) + name = lsmash_realloc( hdlr->componentName, name_length ); + else if( !hdlr->componentName ) + name = lsmash_malloc( name_length ); + else + name = hdlr->componentName; + if( !name ) + return LSMASH_ERR_MEMORY_ALLOC; + if( file->qt_compatible ) + name[0] = name_length & 0xff; + memcpy( name + file->qt_compatible, handler_name, strlen( handler_name ) ); + if( file->isom_compatible ) + name[name_length - 1] = 0; + hdlr->componentName = name; + hdlr->componentName_length = name_length; + return 0; +} + +uint32_t lsmash_get_media_timescale( lsmash_root_t *root, uint32_t track_ID ) +{ + if( isom_check_initializer_present( root ) < 0 ) + return 0; + isom_trak_t *trak = isom_get_trak( root->file->initializer, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak->mdia->mdhd ) ) + return 0; + return trak->mdia->mdhd->timescale; +} + +uint64_t lsmash_get_media_duration( lsmash_root_t *root, uint32_t track_ID ) +{ + if( isom_check_initializer_present( root ) < 0 ) + return 0; + isom_trak_t *trak = isom_get_trak( root->file->initializer, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak->mdia->mdhd ) ) + return 0; + return trak->mdia->mdhd->duration; +} + +uint64_t lsmash_get_track_duration( lsmash_root_t *root, uint32_t track_ID ) +{ + if( isom_check_initializer_present( root ) < 0 ) + return 0; + isom_trak_t *trak = isom_get_trak( root->file->initializer, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak->tkhd ) ) + return 0; + return trak->tkhd->duration; +} + +uint32_t lsmash_get_last_sample_delta( lsmash_root_t *root, uint32_t track_ID ) +{ + if( isom_check_initializer_present( root ) < 0 ) + return 0; + isom_trak_t *trak = isom_get_trak( root->file, track_ID ); + if( !trak->mdia->minf->stbl->stts->list + || !trak->mdia->minf->stbl->stts->list->tail + || !trak->mdia->minf->stbl->stts->list->tail->data ) + return 0; + return ((isom_stts_entry_t *)trak->mdia->minf->stbl->stts->list->tail->data)->sample_delta; +} + +uint32_t lsmash_get_start_time_offset( lsmash_root_t *root, uint32_t track_ID ) +{ + if( isom_check_initializer_present( root ) < 0 ) + return 0; + isom_trak_t *trak = isom_get_trak( root->file, track_ID ); + if( !trak->mdia->minf->stbl->ctts->list + || !trak->mdia->minf->stbl->ctts->list->head + || !trak->mdia->minf->stbl->ctts->list->head->data ) + return 0; + return ((isom_ctts_entry_t *)trak->mdia->minf->stbl->ctts->list->head->data)->sample_offset; +} + +uint32_t lsmash_get_composition_to_decode_shift( lsmash_root_t *root, uint32_t track_ID ) +{ + if( isom_check_initializer_present( root ) < 0 ) + return 0; + lsmash_file_t *file = root->file->initializer; + isom_trak_t *trak = isom_get_trak( file, track_ID ); + uint32_t sample_count = isom_get_sample_count( trak ); + if( sample_count == 0 ) + return 0; + isom_stbl_t *stbl = trak->mdia->minf->stbl; + if( !stbl->stts->list + || !stbl->ctts->list ) + return 0; + if( !(file->max_isom_version >= 4 && stbl->ctts->version == 1) && !file->qt_compatible ) + return 0; /* This movie shall not have composition to decode timeline shift. */ + lsmash_entry_t *stts_entry = stbl->stts->list->head; + lsmash_entry_t *ctts_entry = stbl->ctts->list->head; + if( !stts_entry || !ctts_entry ) + return 0; + uint64_t dts = 0; + uint64_t cts = 0; + uint32_t ctd_shift = 0; + uint32_t i = 0; + uint32_t j = 0; + for( uint32_t k = 0; k < sample_count; k++ ) + { + isom_stts_entry_t *stts_data = (isom_stts_entry_t *)stts_entry->data; + isom_ctts_entry_t *ctts_data = (isom_ctts_entry_t *)ctts_entry->data; + if( !stts_data || !ctts_data ) + return 0; + if( ctts_data->sample_offset != ISOM_NON_OUTPUT_SAMPLE_OFFSET ) + { + cts = dts + (int32_t)ctts_data->sample_offset; + if( dts > cts + ctd_shift ) + ctd_shift = dts - cts; + } + dts += stts_data->sample_delta; + if( ++i == stts_data->sample_count ) + { + stts_entry = stts_entry->next; + if( !stts_entry ) + return 0; + i = 0; + } + if( ++j == ctts_data->sample_count ) + { + ctts_entry = ctts_entry->next; + if( !ctts_entry ) + return 0; + j = 0; + } + } + return ctd_shift; +} + +uint16_t lsmash_pack_iso_language( char *iso_language ) +{ + if( !iso_language || strlen( iso_language ) != 3 ) + return 0; + return (uint16_t)LSMASH_PACK_ISO_LANGUAGE( iso_language[0], iso_language[1], iso_language[2] ); +} + +static int isom_iso2mac_language( uint16_t ISO_language, uint16_t *MAC_language ) +{ + assert( MAC_language ); + int i = 0; + for( ; isom_languages[i].iso_name; i++ ) + if( ISO_language == isom_languages[i].iso_name ) + break; + if( !isom_languages[i].iso_name ) + return LSMASH_ERR_NAMELESS; + *MAC_language = isom_languages[i].mac_value; + return 0; +} + +static int isom_mac2iso_language( uint16_t MAC_language, uint16_t *ISO_language ) +{ + assert( ISO_language ); + int i = 0; + for( ; isom_languages[i].iso_name; i++ ) + if( MAC_language == isom_languages[i].mac_value ) + break; + *ISO_language = isom_languages[i].iso_name ? isom_languages[i].iso_name : ISOM_LANGUAGE_CODE_UNDEFINED; + return 0; +} + +static int isom_set_media_language( lsmash_file_t *file, uint32_t track_ID, uint16_t ISO_language, uint16_t MAC_language ) +{ + isom_trak_t *trak = isom_get_trak( file, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak->mdia->mdhd ) ) + return LSMASH_ERR_NAMELESS; + uint16_t language = 0; + if( file->isom_compatible ) + { + if( ISO_language ) + language = ISO_language; + else if( MAC_language ) + { + int err = isom_mac2iso_language( MAC_language, &language ); + if( err ) + return err; + } + else + language = ISOM_LANGUAGE_CODE_UNDEFINED; + } + else if( file->qt_compatible ) + { + if( ISO_language ) + { + int err = isom_iso2mac_language( ISO_language, &language ); + if( err ) + return err; + } + else + language = MAC_language; + } + else + return LSMASH_ERR_INVALID_DATA; + trak->mdia->mdhd->language = language; + return 0; +} + +int isom_add_sample_grouping( isom_box_t *parent, isom_grouping_type grouping_type ) +{ + isom_sgpd_t *sgpd; + isom_sbgp_t *sbgp; + if( ((sgpd = isom_add_sgpd( parent )), LSMASH_IS_NON_EXISTING_BOX( sgpd )) + || ((sbgp = isom_add_sbgp( parent )), LSMASH_IS_NON_EXISTING_BOX( sbgp )) ) + return LSMASH_ERR_NAMELESS; + sbgp->grouping_type = grouping_type; + sgpd->grouping_type = grouping_type; + sgpd->version = 1; /* We use version 1 for Sample Group Description Box because it is recommended in the spec. */ + switch( grouping_type ) + { + case ISOM_GROUP_TYPE_RAP : + sgpd->default_length = 1; + break; + case ISOM_GROUP_TYPE_ROLL : + case ISOM_GROUP_TYPE_PROL : + sgpd->default_length = 2; + break; + default : + /* We don't consider other grouping types currently. */ + break; + } + return 0; +} + +static int isom_create_sample_grouping( isom_trak_t *trak, isom_grouping_type grouping_type ) +{ + assert( LSMASH_IS_EXISTING_BOX( trak ) ); + lsmash_file_t *file = trak->file; + switch( grouping_type ) + { + case ISOM_GROUP_TYPE_RAP : + assert( file->max_isom_version >= 6 ); + break; + case ISOM_GROUP_TYPE_ROLL : + case ISOM_GROUP_TYPE_PROL : + assert( file->avc_extensions || file->qt_compatible ); + break; + default : + assert( 0 ); + break; + } + int err = isom_add_sample_grouping( (isom_box_t *)trak->mdia->minf->stbl, grouping_type ); + if( err < 0 ) + return err; + if( trak->cache->fragment && file->max_isom_version >= 6 ) + switch( grouping_type ) + { + case ISOM_GROUP_TYPE_RAP : + trak->cache->fragment->rap_grouping = 1; + break; + case ISOM_GROUP_TYPE_ROLL : + case ISOM_GROUP_TYPE_PROL : + trak->cache->fragment->roll_grouping = 1; + break; + default : + /* We don't consider other grouping types currently. */ + break; + } + return 0; +} + +static int isom_compress_sample_size_table( isom_stbl_t *stbl ) +{ + if( stbl->file->max_3gpp_version ) + /* 3GPP: Limitations to the ISO base media file format + * - compact sample sizes ('stz2') shall not be used for tracks containing H.263, MPEG-4 video, AMR, AMR-WB, AAC or Timed text. + * Note that 'mp4a' check is incomplete here since this restriction is not applied to Enhanced aacPlus audio (HE-AAC v2). */ + for( lsmash_entry_t *entry = stbl->stsd->list.head; entry; entry = entry->next ) + { + isom_sample_entry_t *sample_entry = (isom_sample_entry_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( sample_entry ) ) + return LSMASH_ERR_INVALID_DATA; + lsmash_codec_type_t sample_type = sample_entry->type; + if( lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_S263_VIDEO ) + || lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_MP4V_VIDEO ) + || lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_MP4A_AUDIO ) + || lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_SAMR_AUDIO ) + || lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_SAWB_AUDIO ) + || lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_TX3G_TEXT ) ) + return 0; + } + if( LSMASH_IS_EXISTING_BOX( stbl->stsz ) && isom_is_variable_size( stbl ) ) + { + int max_num_bits = 0; + for( lsmash_entry_t *entry = stbl->stsz->list->head; entry; entry = entry->next ) + { + isom_stsz_entry_t *data = (isom_stsz_entry_t *)entry->data; + if( !data ) + return LSMASH_ERR_INVALID_DATA; + int num_bits; + for( num_bits = 1; data->entry_size >> num_bits; num_bits++ ); + if( max_num_bits < num_bits ) + { + max_num_bits = num_bits; + if( max_num_bits > 16 ) + return 0; /* not compressible */ + } + } + if( max_num_bits <= 16 && LSMASH_IS_BOX_ADDITION_SUCCESS( isom_add_stz2( stbl ) ) ) + { + /* The sample size table can be compressed by using 'stz2'. */ + isom_stsz_t *stsz = stbl->stsz; + isom_stz2_t *stz2 = stbl->stz2; + stz2->sample_count = stsz->sample_count; + if( max_num_bits <= 4 ) + stz2->field_size = 4; + else if( max_num_bits <= 8 ) + stz2->field_size = 8; + else + stz2->field_size = 16; + lsmash_list_move_entries( stz2->list, stsz->list ); + isom_remove_box_by_itself( stsz ); + } + } + return 0; +} + +static int isom_add_dependency_type( isom_stbl_t *stbl, lsmash_file_t *file, lsmash_sample_property_t *prop ) +{ + if( !file->qt_compatible && !file->avc_extensions ) + return 0; + int compatibility = file->avc_extensions && file->qt_compatible ? 3 + : file->qt_compatible ? 2 + : file->avc_extensions ? 1 + : 0; + if( LSMASH_IS_EXISTING_BOX( stbl->sdtp ) ) + return isom_add_sdtp_entry( (isom_box_t *)stbl, prop, compatibility ); + /* no null check for prop */ + if( !prop->allow_earlier + && !prop->leading + && !prop->independent + && !prop->disposable + && !prop->redundant ) + return 0; + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_sdtp( (isom_box_t *)stbl ) ) ) + return LSMASH_ERR_NAMELESS; + uint32_t count = isom_get_sample_count_from_sample_table( stbl ); + /* fill past samples with ISOM_SAMPLE_*_UNKNOWN */ + lsmash_sample_property_t null_prop = { 0 }; + for( uint32_t i = 1; i < count; i++ ) + { + int err = isom_add_sdtp_entry( (isom_box_t *)stbl, &null_prop, compatibility ); + if( err < 0 ) + return err; + } + return isom_add_sdtp_entry( (isom_box_t *)stbl, prop, compatibility ); +} + +void lsmash_initialize_media_parameters( lsmash_media_parameters_t *param ) +{ + memset( param, 0, sizeof(lsmash_media_parameters_t) ); + param->timescale = 1; +} + +int lsmash_set_media_parameters( lsmash_root_t *root, uint32_t track_ID, lsmash_media_parameters_t *param ) +{ + if( isom_check_initializer_present( root ) < 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_file_t *file = root->file; + isom_trak_t *trak = isom_get_trak( file, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak->mdia->mdhd ) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->minf->stbl ) ) + return LSMASH_ERR_NAMELESS; + trak->mdia->mdhd->timescale = param->timescale; + int err = isom_set_media_language( file, track_ID, param->ISO_language, param->MAC_language ); + if( err < 0 ) + return err; + if( param->media_handler_name + && (err = isom_set_media_handler_name( file, track_ID, param->media_handler_name )) < 0 ) + return err; + if( file->qt_compatible && param->data_handler_name + && (err = isom_set_data_handler_name( file, track_ID, param->data_handler_name )) < 0 ) + return err; + if( (file->avc_extensions || file->qt_compatible) && param->roll_grouping + && (err = isom_create_sample_grouping( trak, ISOM_GROUP_TYPE_ROLL )) < 0 ) + return err; + if( (file->max_isom_version >= 6) && param->rap_grouping + && (err = isom_create_sample_grouping( trak, ISOM_GROUP_TYPE_RAP )) < 0 ) + return err; + if( !file->qt_compatible && param->compact_sample_size_table ) + trak->mdia->minf->stbl->compress_sample_size_table = isom_compress_sample_size_table; + if( !param->no_sample_dependency_table ) + trak->mdia->minf->stbl->add_dependency_type = isom_add_dependency_type; + return 0; +} + +static uint32_t get_actual_handler_name_length( isom_hdlr_t *hdlr, lsmash_file_t *file ) +{ + if( hdlr->componentName_length == 0 ) + return 0; + uint32_t length; + uint8_t *name; + if( file->qt_compatible ) + { + length = LSMASH_MIN( hdlr->componentName[0], hdlr->componentName_length - 1 ); + if( !file->isom_compatible ) + return length; + name = &hdlr->componentName[1]; + } + else + { + length = hdlr->componentName_length; + name = hdlr->componentName; + } + /* Considering fool-proof such as not terminated by '\0'. */ + uint32_t i = 0; + while( i < length && name[i] ) + ++i; + return i; +} + +int lsmash_get_media_parameters( lsmash_root_t *root, uint32_t track_ID, lsmash_media_parameters_t *param ) +{ + if( isom_check_initializer_present( root ) < 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_file_t *file = root->file->initializer; + isom_trak_t *trak = isom_get_trak( file, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak->mdia->mdhd ) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->hdlr ) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->minf->stbl ) ) + return LSMASH_ERR_NAMELESS; + isom_mdhd_t *mdhd = trak->mdia->mdhd; + isom_stbl_t *stbl = trak->mdia->minf->stbl; + param->timescale = mdhd->timescale; + param->handler_type = trak->mdia->hdlr->componentSubtype; + param->duration = mdhd->duration; + /* Whether sample grouping present. */ + { + isom_sbgp_t *sbgp; + isom_sgpd_t *sgpd; + sbgp = isom_get_sample_to_group ( stbl, ISOM_GROUP_TYPE_RAP ); + sgpd = isom_get_sample_group_description( stbl, ISOM_GROUP_TYPE_RAP ); + param->rap_grouping = LSMASH_IS_EXISTING_BOX( sbgp ) && LSMASH_IS_EXISTING_BOX( sgpd ); + sbgp = isom_get_roll_recovery_sample_to_group ( &stbl->sbgp_list ); + sgpd = isom_get_roll_recovery_sample_group_description( &stbl->sgpd_list ); + param->roll_grouping = LSMASH_IS_EXISTING_BOX( sbgp ) && LSMASH_IS_EXISTING_BOX( sgpd ); + } + /* Get media language. */ + if( mdhd->language >= 0x800 ) + { + param->MAC_language = 0; + param->ISO_language = mdhd->language; + } + else + { + param->MAC_language = mdhd->language; + param->ISO_language = 0; + } + /* Get handler name(s). */ + isom_hdlr_t *hdlr = trak->mdia->hdlr; + uint32_t actual_length = get_actual_handler_name_length( hdlr, file ); + uint32_t length = LSMASH_MIN( 255, actual_length ); + if( length ) + { + memcpy( param->media_handler_name_shadow, hdlr->componentName + file->qt_compatible, length ); + param->media_handler_name_shadow[length] = '\0'; + param->media_handler_name = param->media_handler_name_shadow; + } + else + { + param->media_handler_name = NULL; + memset( param->media_handler_name_shadow, 0, sizeof(param->media_handler_name_shadow) ); + } + if( LSMASH_IS_EXISTING_BOX( trak->mdia->minf->hdlr ) ) + { + hdlr = trak->mdia->minf->hdlr; + actual_length = get_actual_handler_name_length( hdlr, file ); + length = LSMASH_MIN( 255, actual_length ); + if( length ) + { + memcpy( param->data_handler_name_shadow, hdlr->componentName + file->qt_compatible, length ); + param->data_handler_name_shadow[length] = '\0'; + param->data_handler_name = param->data_handler_name_shadow; + } + else + { + param->data_handler_name = NULL; + memset( param->data_handler_name_shadow, 0, sizeof(param->data_handler_name_shadow) ); + } + } + else + { + param->data_handler_name = NULL; + memset( param->data_handler_name_shadow, 0, sizeof(param->data_handler_name_shadow) ); + } + param->compact_sample_size_table = LSMASH_IS_EXISTING_BOX( stbl->stz2 ); + param->no_sample_dependency_table = LSMASH_IS_NON_EXISTING_BOX( stbl->sdtp ); + param->reserved[0] = param->reserved[1] = 0; + return 0; +} + +/*---- movie manipulators ----*/ + +void lsmash_initialize_movie_parameters( lsmash_movie_parameters_t *param ) +{ + memset( param, 0, sizeof(lsmash_movie_parameters_t) ); + param->timescale = 600; + param->playback_rate = 0x00010000; + param->playback_volume = 0x0100; +} + +int lsmash_set_movie_parameters( lsmash_root_t *root, lsmash_movie_parameters_t *param ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( root ) ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_file_t *file = root->file; + if( LSMASH_IS_NON_EXISTING_BOX( file->moov->mvhd ) ) + return LSMASH_ERR_NAMELESS; + isom_mvhd_t *mvhd = file->moov->mvhd; + mvhd->timescale = param->timescale; + if( file->qt_compatible || file->itunes_movie ) + { + mvhd->rate = param->playback_rate; + mvhd->volume = param->playback_volume; + mvhd->previewTime = param->preview_time; + mvhd->previewDuration = param->preview_duration; + mvhd->posterTime = param->poster_time; + } + else + { + mvhd->rate = 0x00010000; + mvhd->volume = 0x0100; + mvhd->previewTime = 0; + mvhd->previewDuration = 0; + mvhd->posterTime = 0; + } + return 0; +} + +int lsmash_get_movie_parameters( lsmash_root_t *root, lsmash_movie_parameters_t *param ) +{ + if( isom_check_initializer_present( root ) < 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_file_t *file = root->file->initializer; + if( LSMASH_IS_NON_EXISTING_BOX( file->moov->mvhd ) ) + return LSMASH_ERR_NAMELESS; + isom_mvhd_t *mvhd = file->moov->mvhd; + param->timescale = mvhd->timescale; + param->duration = mvhd->duration; + param->playback_rate = mvhd->rate; + param->playback_volume = mvhd->volume; + param->preview_time = mvhd->previewTime; + param->preview_duration = mvhd->previewDuration; + param->poster_time = mvhd->posterTime; + param->number_of_tracks = file->moov->trak_list.entry_count; + return 0; +} + +uint32_t lsmash_get_movie_timescale( lsmash_root_t *root ) +{ + if( isom_check_initializer_present( root ) < 0 ) + return 0; + return root->file->initializer->moov->mvhd->timescale; +} + +int lsmash_reserve_media_data_size +( + lsmash_root_t *root, + uint64_t media_data_size +) +{ + if( isom_check_initializer_present( root ) < 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_file_t *file = root->file->initializer; + if( LSMASH_IS_EXISTING_BOX( file->mdat ) /* whether the Media Data Box is already written or not */ + || file->fragment ) /* For fragmented movies, this function makes no sense. */ + return LSMASH_ERR_NAMELESS; + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_mdat( file ) ) ) + return LSMASH_ERR_NAMELESS; + file->mdat->reserved_size = media_data_size; + return 0; +} + +static int isom_scan_trak_profileLevelIndication +( + isom_trak_t *trak, + mp4a_audioProfileLevelIndication *audio_pli, + mp4sys_visualProfileLevelIndication *visual_pli +) +{ + isom_stsd_t *stsd = trak->mdia->minf->stbl->stsd; + if( !stsd->list.head ) + return LSMASH_ERR_INVALID_DATA; + for( lsmash_entry_t *entry = stsd->list.head; entry; entry = entry->next ) + { + isom_sample_entry_t *sample_entry = (isom_sample_entry_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( sample_entry ) ) + return LSMASH_ERR_INVALID_DATA; + lsmash_codec_type_t sample_type = sample_entry->type; + if( LSMASH_IS_EXISTING_BOX( trak->mdia->minf->vmhd ) ) + { + if( lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_AVC1_VIDEO ) + || lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_AVC2_VIDEO ) + || lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_AVC3_VIDEO ) + || lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_AVC4_VIDEO ) + || lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_AVCP_VIDEO ) + || lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_SVC1_VIDEO ) + || lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_MVC1_VIDEO ) + || lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_MVC2_VIDEO ) ) + { + /* FIXME: Do we have to arbitrate like audio? */ + if( *visual_pli == MP4SYS_VISUAL_PLI_NONE_REQUIRED ) + *visual_pli = MP4SYS_VISUAL_PLI_H264_AVC; + } + else + *visual_pli = MP4SYS_VISUAL_PLI_NOT_SPECIFIED; + } + else if( LSMASH_IS_EXISTING_BOX( trak->mdia->minf->smhd ) ) + { + if( lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_MP4A_AUDIO ) ) + { + isom_audio_entry_t *audio = (isom_audio_entry_t *)sample_entry; + isom_esds_t *esds = (isom_esds_t *)isom_get_extension_box_format( &audio->extensions, ISOM_BOX_TYPE_ESDS ); + if( LSMASH_IS_NON_EXISTING_BOX( esds ) || !esds->ES ) + return LSMASH_ERR_INVALID_DATA; + lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_create_summary( LSMASH_SUMMARY_TYPE_AUDIO ); + if( !summary ) + continue; + if( mp4sys_setup_summary_from_DecoderSpecificInfo( summary, esds->ES ) < 0 ) + *audio_pli = MP4A_AUDIO_PLI_NOT_SPECIFIED; + else + *audio_pli = mp4a_max_audioProfileLevelIndication( *audio_pli, mp4a_get_audioProfileLevelIndication( summary ) ); + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + } + else + /* NOTE: Audio CODECs other than 'mp4a' does not have appropriate pli. */ + *audio_pli = MP4A_AUDIO_PLI_NOT_SPECIFIED; + } + else + ; /* FIXME: Do we have to set OD_profileLevelIndication? */ + } + return 0; +} + +int isom_setup_iods( isom_moov_t *moov ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( moov->iods ) && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_iods( moov ) ) ) + return LSMASH_ERR_NAMELESS; + isom_iods_t *iods = moov->iods; + int err = LSMASH_ERR_NAMELESS; + iods->OD = mp4sys_create_ObjectDescriptor( 1 ); /* NOTE: Use 1 for ObjectDescriptorID of IOD. */ + if( !iods->OD ) + goto fail; + mp4a_audioProfileLevelIndication audio_pli = MP4A_AUDIO_PLI_NONE_REQUIRED; + mp4sys_visualProfileLevelIndication visual_pli = MP4SYS_VISUAL_PLI_NONE_REQUIRED; + for( lsmash_entry_t *entry = moov->trak_list.head; entry; entry = entry->next ) + { + isom_trak_t *trak = (isom_trak_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( trak ) + || LSMASH_IS_NON_EXISTING_BOX( trak->tkhd ) ) + goto fail; + if( (err = isom_scan_trak_profileLevelIndication( trak, &audio_pli, &visual_pli )) < 0 ) + goto fail; + if( (err = mp4sys_create_ES_ID_Inc( iods->OD, trak->tkhd->track_ID )) < 0 ) + goto fail; + } + if( (err = mp4sys_to_InitialObjectDescriptor( iods->OD, + 0, /* FIXME: I'm not quite sure what the spec says. */ + MP4SYS_OD_PLI_NONE_REQUIRED, MP4SYS_SCENE_PLI_NONE_REQUIRED, + audio_pli, visual_pli, + MP4SYS_GRAPHICS_PLI_NONE_REQUIRED )) < 0 ) + goto fail; + return 0; +fail: + isom_remove_box_by_itself( iods ); + return err; +} + +int lsmash_create_object_descriptor( lsmash_root_t *root ) +{ + if( isom_check_initializer_present( root ) < 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_file_t *file = root->file; + /* Return error if this file is not compatible with MP4 file format. */ + if( !file->mp4_version1 + && !file->mp4_version2 ) + return LSMASH_ERR_FUNCTION_PARAM; + return isom_setup_iods( file->moov ); +} + +/*---- finishing functions ----*/ + +int isom_complement_data_reference( isom_minf_t *minf ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( minf->dinf->dref ) ) + return LSMASH_ERR_INVALID_DATA; + /* Complement data referece if absent. */ + if( !minf->dinf->dref->list.head ) + { + isom_dref_entry_t *url = isom_add_dref_entry( minf->dinf->dref, ISOM_BOX_TYPE_URL ); + if( LSMASH_IS_NON_EXISTING_BOX( url ) ) + return LSMASH_ERR_NAMELESS; + url->flags = 0x000001; /* Media data is in the same file. */ + } + return 0; +} + +static lsmash_file_t *isom_get_written_media_file +( + isom_trak_t *trak, + uint32_t sample_description_index +) +{ + isom_minf_t *minf = trak->mdia->minf; + isom_sample_entry_t *description = (isom_sample_entry_t *)lsmash_list_get_entry_data( &minf->stbl->stsd->list, sample_description_index ); + isom_dref_entry_t *dref_entry = (isom_dref_entry_t *)lsmash_list_get_entry_data( &minf->dinf->dref->list, description ? description->data_reference_index : 1 ); + lsmash_file_t *file = (!dref_entry || LSMASH_IS_NON_EXISTING_BOX( dref_entry->ref_file )) ? trak->file : dref_entry->ref_file; + if( !(file->flags & LSMASH_FILE_MODE_MEDIA) + || !(file->flags & LSMASH_FILE_MODE_WRITE) ) + return trak->file; + return file; +} + +int isom_check_large_offset_requirement +( + isom_moov_t *moov, + uint64_t meta_size +) +{ + for( lsmash_entry_t *entry = moov->trak_list.head; entry; ) + { + isom_trak_t *trak = (isom_trak_t *)entry->data; + isom_stco_t *stco = trak->mdia->minf->stbl->stco; + if( !stco->list->tail /* no samples */ + || stco->large_presentation + || (((isom_stco_entry_t *)stco->list->tail->data)->chunk_offset + moov->size + meta_size) <= UINT32_MAX ) + { + entry = entry->next; + continue; /* no need to convert stco into co64 */ + } + /* stco->co64 conversion */ + int err = isom_convert_stco_to_co64( trak->mdia->minf->stbl ); + if( err < 0 ) + return err; + if( isom_update_box_size( moov ) == 0 ) + return LSMASH_ERR_INVALID_DATA; + entry = moov->trak_list.head; /* whenever any conversion, re-check all traks */ + } + return 0; +} + +void isom_add_preceding_box_size +( + isom_moov_t *moov, + uint64_t preceding_size +) +{ + for( lsmash_entry_t *entry = moov->trak_list.head; entry; entry = entry->next ) + { + /* Apply to the chunks in the same file. */ + isom_trak_t *trak = (isom_trak_t *)entry->data; + isom_stsc_t *stsc = trak->mdia->minf->stbl->stsc; + isom_stco_t *stco = trak->mdia->minf->stbl->stco; + lsmash_entry_t *stsc_entry = stsc->list->head; + isom_stsc_entry_t *stsc_data = stsc_entry ? (isom_stsc_entry_t *)stsc_entry->data : NULL; + uint32_t chunk_number = 1; + for( lsmash_entry_t *stco_entry = stco->list->head; stco_entry; ) + { + if( stsc_data + && stsc_data->first_chunk == chunk_number ) + { + lsmash_file_t *ref_file = isom_get_written_media_file( trak, stsc_data->sample_description_index ); + stsc_entry = stsc_entry->next; + stsc_data = stsc_entry ? (isom_stsc_entry_t *)stsc_entry->data : NULL; + if( ref_file != trak->file ) + { + /* The chunks are not contained in the same file. Skip applying the offset. + * If no more stsc entries, the rest of the chunks is not contained in the same file. */ + if( !stsc_entry || !stsc_data ) + break; + while( stco_entry && chunk_number < stsc_data->first_chunk ) + { + stco_entry = stco_entry->next; + ++chunk_number; + } + continue; + } + } + if( stco->large_presentation ) + ((isom_co64_entry_t *)stco_entry->data)->chunk_offset += preceding_size; + else + ((isom_stco_entry_t *)stco_entry->data)->chunk_offset += preceding_size; + stco_entry = stco_entry->next; + ++chunk_number; + } + } +} + +int isom_establish_movie( lsmash_file_t *file ) +{ + assert( file == file->initializer ); + int err; + if( (err = isom_check_mandatory_boxes( file )) < 0 + || (err = isom_set_movie_creation_time( file )) < 0 ) + return err; + if( isom_update_box_size( file->moov ) == 0 ) + return LSMASH_ERR_INVALID_DATA; + return 0; +} + +int lsmash_finish_movie +( + lsmash_root_t *root, + lsmash_adhoc_remux_t *remux +) +{ + if( isom_check_initializer_present( root ) < 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_file_t *file = root->file; + if( !file->bs + || LSMASH_IS_NON_EXISTING_BOX( file->initializer->moov ) ) + return LSMASH_ERR_INVALID_DATA; + if( file->fragment ) + return isom_finish_final_fragment_movie( file, remux ); + if( file != file->initializer ) + return LSMASH_ERR_INVALID_DATA; + int err; + isom_moov_t *moov = file->moov; + for( lsmash_entry_t *entry = moov->trak_list.head; entry; entry = entry->next ) + { + isom_trak_t *trak = (isom_trak_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( trak ) + || LSMASH_IS_NON_EXISTING_BOX( trak->tkhd ) + || !trak->cache + || !trak->mdia->minf->stbl->stsd->list.head + || !trak->mdia->minf->stbl->stsd->list.head->data + || !trak->mdia->minf->stbl->stco->list + || !trak->mdia->minf->stbl->stco->list->tail ) + return LSMASH_ERR_INVALID_DATA; + if( (err = isom_complement_data_reference( trak->mdia->minf )) < 0 ) + return err; + uint32_t track_ID = trak->tkhd->track_ID; + uint32_t related_track_ID = trak->related_track_ID; + /* Disable the track if the track is a track reference chapter. */ + if( trak->is_chapter ) + trak->tkhd->flags &= ~ISOM_TRACK_ENABLED; + if( trak->is_chapter && related_track_ID ) + { + /* In order that the track duration of the chapter track doesn't exceed that of the related track. */ + lsmash_edit_t edit; + edit.duration = LSMASH_MIN( trak->tkhd->duration, lsmash_get_track_duration( root, related_track_ID ) ); + edit.start_time = 0; + edit.rate = ISOM_EDIT_MODE_NORMAL; + if( (err = lsmash_create_explicit_timeline_map( root, track_ID, edit )) < 0 ) + return err; + } + isom_stbl_t *stbl = trak->mdia->minf->stbl; + /* Compress sample size table. */ + if( stbl->compress_sample_size_table + && (err = stbl->compress_sample_size_table( stbl )) < 0 ) + return err; + /* Add stss box if any samples aren't sync sample. */ + if( !trak->cache->all_sync && !stbl->stss && !isom_add_stss( stbl ) ) + return LSMASH_ERR_NAMELESS; + if( (err = isom_update_tkhd_duration( trak )) < 0 + || (err = isom_update_bitrate_description( trak->mdia )) < 0 ) + return err; + } + if( file->mp4_version1 == 1 && (err = isom_setup_iods( moov )) < 0 ) + return err; + if( (err = isom_establish_movie( file )) < 0 ) + return err; + /* Write the size of Media Data Box here. */ + lsmash_bs_t *bs = file->bs; + file->mdat->manager &= ~LSMASH_INCOMPLETE_BOX; + if( (err = isom_write_box( bs, (isom_box_t *)file->mdat )) < 0 ) + return err; + /* Write the Movie Box and a Meta Box if no optimization for progressive download. */ + uint64_t meta_size = LSMASH_IS_EXISTING_BOX( file->meta ) ? file->meta->size : 0; + if( !remux ) + { + if( (err = isom_write_box( bs, (isom_box_t *)file->moov )) < 0 + || (err = isom_write_box( bs, (isom_box_t *)file->meta )) < 0 ) + return err; + file->size += moov->size + meta_size; + return 0; + } + /* stco->co64 conversion, depending on last chunk's offset */ + if( (err = isom_check_large_offset_requirement( moov, meta_size )) < 0 ) + return err; + /* now the amount of offset is fixed. */ + uint64_t mtf_size = moov->size + meta_size; /* sum of size of boxes moved to front */ + /* buffer size must be at least mtf_size * 2 */ + remux->buffer_size = LSMASH_MAX( remux->buffer_size, mtf_size * 2 ); + /* Split to 2 buffers. */ + uint8_t *buf[2] = { NULL, NULL }; + if( (buf[0] = (uint8_t*)lsmash_malloc( remux->buffer_size )) == NULL ) + return LSMASH_ERR_MEMORY_ALLOC; /* NOTE: I think we still can fallback to "return isom_write_moov();" here. */ + size_t size = remux->buffer_size / 2; + buf[1] = buf[0] + size; + /* Now, the amount of the offset is fixed. apply it to stco/co64 */ + isom_add_preceding_box_size( moov, mtf_size ); + /* Backup starting area of mdat and write moov + meta there instead. */ + isom_mdat_t *mdat = file->mdat; + uint64_t total = file->size + mtf_size; + uint64_t placeholder_pos = mdat->pos; + if( (err = lsmash_bs_write_seek( bs, placeholder_pos, SEEK_SET )) < 0 ) + goto fail; + size_t read_num = size; + lsmash_bs_read_data( bs, buf[0], &read_num ); + uint64_t read_pos = bs->offset; + /* Write moov + meta there instead. */ + if( (err = lsmash_bs_write_seek( bs, placeholder_pos, SEEK_SET )) < 0 + || (err = isom_write_box( bs, (isom_box_t *)file->moov )) < 0 + || (err = isom_write_box( bs, (isom_box_t *)file->meta )) < 0 ) + goto fail; + uint64_t write_pos = bs->offset; + /* Update the positions */ + mdat->pos += mtf_size; + /* Move Media Data Box. */ + if( (err = isom_rearrange_data( file, remux, buf, read_num, size, read_pos, write_pos, total )) < 0 ) + goto fail; + file->size += mtf_size; + lsmash_free( buf[0] ); + return 0; +fail: + lsmash_free( buf[0] ); + return err; +} + +int lsmash_set_last_sample_delta( lsmash_root_t *root, uint32_t track_ID, uint32_t sample_delta ) +{ + if( isom_check_initializer_present( root ) < 0 || track_ID == 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_file_t *file = root->file; + if( file->fragment + && file->fragment->movie ) + { + isom_traf_t *traf = isom_get_traf( file->fragment->movie, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( traf ) + || LSMASH_IS_NON_EXISTING_BOX( traf->tfhd ) + || !traf->cache ) + return LSMASH_ERR_NAMELESS; + return isom_set_fragment_last_duration( traf, sample_delta ); + } + if( file != file->initializer ) + return LSMASH_ERR_INVALID_DATA; + isom_trak_t *trak = isom_get_trak( file, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak->mdia->mdhd ) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->minf->stbl->stsd ) + || (LSMASH_IS_NON_EXISTING_BOX( trak->mdia->minf->stbl->stsz ) + && LSMASH_IS_NON_EXISTING_BOX( trak->mdia->minf->stbl->stz2 )) + || !trak->cache + || !trak->mdia->minf->stbl->stts->list ) + return LSMASH_ERR_NAMELESS; + isom_stbl_t *stbl = trak->mdia->minf->stbl; + isom_stts_t *stts = stbl->stts; + uint32_t sample_count = isom_get_sample_count( trak ); + int err; + if( !stts->list->tail ) + { + if( sample_count == 0 ) + return 0; /* no samples */ + if( sample_count > 1 ) + return LSMASH_ERR_INVALID_DATA; /* irregular sample_count */ + /* Set the duration of the first sample. + * This duration is also the duration of the last sample. */ + if( (err = isom_add_stts_entry( stbl, sample_delta )) < 0 ) + return err; + return lsmash_update_track_duration( root, track_ID, 0 ); + } + uint32_t i = 0; + for( lsmash_entry_t *entry = stts->list->head; entry; entry = entry->next ) + i += ((isom_stts_entry_t *)entry->data)->sample_count; + if( sample_count < i ) + return LSMASH_ERR_INVALID_DATA; + int no_last = (sample_count > i); + isom_stts_entry_t *last_stts_data = (isom_stts_entry_t *)stts->list->tail->data; + if( !last_stts_data ) + return LSMASH_ERR_INVALID_DATA; + /* Consider QuikcTime fixed compression audio. */ + isom_audio_entry_t *audio = (isom_audio_entry_t *)lsmash_list_get_entry_data( &trak->mdia->minf->stbl->stsd->list, + trak->cache->chunk.sample_description_index ); + if( LSMASH_IS_NON_EXISTING_BOX( audio ) ) + return LSMASH_ERR_INVALID_DATA; + if( (audio->manager & LSMASH_AUDIO_DESCRIPTION) + && (audio->manager & LSMASH_QTFF_BASE) + && (audio->version == 1) + && (audio->compression_ID != QT_AUDIO_COMPRESSION_ID_VARIABLE_COMPRESSION) ) + { + if( audio->samplesPerPacket == 0 ) + return LSMASH_ERR_INVALID_DATA; + int exclude_last_sample = no_last ? 0 : 1; + uint32_t j = audio->samplesPerPacket; + for( lsmash_entry_t *entry = stts->list->tail; entry && j > 1; entry = entry->prev ) + { + isom_stts_entry_t *stts_data = (isom_stts_entry_t *)entry->data; + if( !stts_data ) + return LSMASH_ERR_INVALID_DATA; + for( uint32_t k = exclude_last_sample; k < stts_data->sample_count && j > 1; k++ ) + { + sample_delta -= stts_data->sample_delta; + --j; + } + exclude_last_sample = 0; + } + } + /* Set sample_delta. */ + if( no_last ) + { + /* The duration of the last sample is not set yet. */ + if( sample_count - i > 1 ) + return LSMASH_ERR_INVALID_DATA; + /* Add a sample_delta. */ + if( sample_delta == last_stts_data->sample_delta ) + ++ last_stts_data->sample_count; + else if( (err = isom_add_stts_entry( stbl, sample_delta )) < 0 ) + return err; + } + /* The duration of the last sample is already set. Replace it with a new one. */ + else if( (err = isom_replace_last_sample_delta( stbl, sample_delta )) < 0 ) + return err; + return lsmash_update_track_duration( root, track_ID, sample_delta ); +} + +/*---- timeline manipulator ----*/ + +int lsmash_modify_explicit_timeline_map( lsmash_root_t *root, uint32_t track_ID, uint32_t edit_number, lsmash_edit_t edit ) +{ + if( isom_check_initializer_present( root ) < 0 + || edit.start_time < -1 ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_file_t *file = root->file->initializer; + isom_trak_t *trak = isom_get_trak( file, track_ID ); + if( !trak->edts->elst->list ) + return LSMASH_ERR_NAMELESS; + isom_elst_t *elst = trak->edts->elst; + isom_elst_entry_t *data = (isom_elst_entry_t *)lsmash_list_get_entry_data( elst->list, edit_number ); + if( !data ) + return LSMASH_ERR_NAMELESS; + data->segment_duration = edit.duration; + data->media_time = edit.start_time; + data->media_rate = edit.rate; + if( elst->pos == 0 || !file->fragment || file->bs->unseekable ) + return isom_update_tkhd_duration( trak ); + /* Rewrite the specified entry. + * Note: we don't update the version of the Edit List Box. */ + lsmash_bs_t *bs = file->bs; + uint64_t current_pos = bs->offset; + uint64_t entry_pos = elst->pos + ISOM_LIST_FULLBOX_COMMON_SIZE + ((uint64_t)edit_number - 1) * (elst->version == 1 ? 20 : 12); + lsmash_bs_write_seek( bs, entry_pos, SEEK_SET ); + if( elst->version ) + { + lsmash_bs_put_be64( bs, data->segment_duration ); + lsmash_bs_put_be64( bs, data->media_time ); + } + else + { + lsmash_bs_put_be32( bs, (uint32_t)LSMASH_MIN( data->segment_duration, UINT32_MAX ) ); + lsmash_bs_put_be32( bs, (uint32_t)data->media_time ); + } + lsmash_bs_put_be32( bs, data->media_rate ); + int ret = lsmash_bs_flush_buffer( bs ); + lsmash_bs_write_seek( bs, current_pos, SEEK_SET ); + return ret; +} + +int lsmash_create_explicit_timeline_map( lsmash_root_t *root, uint32_t track_ID, lsmash_edit_t edit ) +{ + if( isom_check_initializer_present( root ) < 0 || edit.start_time < -1 ) + return LSMASH_ERR_FUNCTION_PARAM; + isom_trak_t *trak = isom_get_trak( root->file, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak->tkhd ) ) + return LSMASH_ERR_NAMELESS; + edit.duration = (edit.duration || root->file->fragment) ? edit.duration + : trak->tkhd->duration ? trak->tkhd->duration + : isom_update_tkhd_duration( trak ) < 0 ? 0 + : trak->tkhd->duration; + if( (LSMASH_IS_NON_EXISTING_BOX( trak->edts ) && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_edts( trak ) )) + || (LSMASH_IS_NON_EXISTING_BOX( trak->edts->elst ) && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_elst( trak->edts ) )) ) + return LSMASH_ERR_NAMELESS; + int err = isom_add_elst_entry( trak->edts->elst, edit.duration, edit.start_time, edit.rate ); + if( err < 0 ) + return err; + return isom_update_tkhd_duration( trak ); +} + +int lsmash_get_explicit_timeline_map( lsmash_root_t *root, uint32_t track_ID, uint32_t edit_number, lsmash_edit_t *edit ) +{ + if( isom_check_initializer_present( root ) < 0 || !edit ) + return LSMASH_ERR_FUNCTION_PARAM; + isom_elst_entry_t *data; + isom_trak_t *trak = isom_get_trak( root->file->initializer, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak ) ) + data = isom_timelime_get_explicit_timeline_map( root, track_ID, edit_number ); + else + { + if( LSMASH_IS_NON_EXISTING_BOX( trak->edts->elst ) ) + { + /* no edits */ + edit->duration = 0; + edit->start_time = 0; + edit->rate = 0; + return 0; + } + data = (isom_elst_entry_t *)lsmash_list_get_entry_data( trak->edts->elst->list, edit_number ); + } + if( !data ) + return LSMASH_ERR_NAMELESS; + edit->duration = data->segment_duration; + edit->start_time = data->media_time; + edit->rate = data->media_rate; + return 0; +} + +uint32_t lsmash_count_explicit_timeline_map( lsmash_root_t *root, uint32_t track_ID ) +{ + if( isom_check_initializer_present( root ) < 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + isom_trak_t *trak = isom_get_trak( root->file->initializer, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak ) ) + return isom_timelime_count_explicit_timeline_map( root, track_ID ); + else + return trak->edts->elst->list ? trak->edts->elst->list->entry_count : 0; +} + +/*---- create / modification time fields manipulators ----*/ + +int lsmash_update_media_modification_time( lsmash_root_t *root, uint32_t track_ID ) +{ + if( isom_check_initializer_present( root ) < 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + isom_trak_t *trak = isom_get_trak( root->file->initializer, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak->mdia->mdhd ) ) + return LSMASH_ERR_NAMELESS; + isom_mdhd_t *mdhd = trak->mdia->mdhd; + mdhd->modification_time = isom_get_current_mp4time(); + /* overwrite strange creation_time */ + if( mdhd->creation_time > mdhd->modification_time ) + mdhd->creation_time = mdhd->modification_time; + return 0; +} + +int lsmash_update_track_modification_time( lsmash_root_t *root, uint32_t track_ID ) +{ + if( isom_check_initializer_present( root ) < 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + isom_trak_t *trak = isom_get_trak( root->file->initializer, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak->tkhd ) ) + return LSMASH_ERR_NAMELESS; + isom_tkhd_t *tkhd = trak->tkhd; + tkhd->modification_time = isom_get_current_mp4time(); + /* overwrite strange creation_time */ + if( tkhd->creation_time > tkhd->modification_time ) + tkhd->creation_time = tkhd->modification_time; + return 0; +} + +int lsmash_update_movie_modification_time( lsmash_root_t *root ) +{ + if( isom_check_initializer_present( root ) < 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_file_t *file = root->file->initializer; + if( LSMASH_IS_NON_EXISTING_BOX( file->moov->mvhd ) ) + return LSMASH_ERR_INVALID_DATA; + isom_mvhd_t *mvhd = file->moov->mvhd; + mvhd->modification_time = isom_get_current_mp4time(); + /* overwrite strange creation_time */ + if( mvhd->creation_time > mvhd->modification_time ) + mvhd->creation_time = mvhd->modification_time; + return 0; +} + +/*---- sample manipulators ----*/ +lsmash_sample_t *lsmash_create_sample( uint32_t size ) +{ + lsmash_sample_t *sample = lsmash_malloc_zero( sizeof(lsmash_sample_t) ); + if( !sample ) + return NULL; + if( size == 0 ) + return sample; + sample->data = lsmash_malloc( size ); + if( !sample->data ) + { + lsmash_free( sample ); + return NULL; + } + sample->length = size; + return sample; +} + +int lsmash_sample_alloc( lsmash_sample_t *sample, uint32_t size ) +{ + if( !sample ) + return LSMASH_ERR_FUNCTION_PARAM; + if( size == 0 ) + { + lsmash_free( sample->data ); + sample->data = NULL; + sample->length = 0; + return 0; + } + if( size == sample->length ) + return 0; + uint8_t *data; + if( !sample->data ) + data = lsmash_malloc( size ); + else + data = lsmash_realloc( sample->data, size ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + sample->data = data; + sample->length = size; + return 0; +} + +void lsmash_delete_sample( lsmash_sample_t *sample ) +{ + if( !sample ) + return; + lsmash_free( sample->data ); + lsmash_free( sample ); +} + +isom_sample_pool_t *isom_create_sample_pool( uint64_t size ) +{ + isom_sample_pool_t *pool = lsmash_malloc_zero( sizeof(isom_sample_pool_t) ); + if( !pool ) + return NULL; + if( size == 0 ) + return pool; + pool->data = lsmash_malloc( size ); + if( !pool->data ) + { + lsmash_free( pool ); + return NULL; + } + pool->alloc = size; + return pool; +} + +void isom_remove_sample_pool( isom_sample_pool_t *pool ) +{ + if( !pool ) + return; + lsmash_free( pool->data ); + lsmash_free( pool ); +} + +static uint32_t isom_add_size( isom_stbl_t *stbl, uint32_t sample_size ) +{ + if( isom_add_stsz_entry( stbl, sample_size ) < 0 ) + return 0; + return isom_get_sample_count_from_sample_table( stbl ); +} + +static uint32_t isom_add_dts( isom_stbl_t *stbl, uint64_t dts, uint64_t prev_dts ) +{ + isom_stts_t *stts = stbl->stts; + if( stts->list->entry_count == 0 ) + return isom_add_stts_entry( stbl, dts ) < 0 ? 0 : dts; + if( dts <= prev_dts ) + return 0; + uint32_t sample_delta = dts - prev_dts; + isom_stts_entry_t *data = (isom_stts_entry_t *)stts->list->tail->data; + if( data->sample_delta == sample_delta ) + ++ data->sample_count; + else if( isom_add_stts_entry( stbl, sample_delta ) < 0 ) + return 0; + return sample_delta; +} + +/* Add ctts box and the first ctts entry. */ +static int isom_add_initial_sample_offset( isom_stbl_t *stbl, uint32_t sample_offset ) +{ + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_ctts( stbl ) ) ) + return LSMASH_ERR_NAMELESS; + if( sample_offset == ISOM_NON_OUTPUT_SAMPLE_OFFSET ) + stbl->ctts->version = 1; + uint32_t sample_count = isom_get_sample_count_from_sample_table( stbl ); + if( sample_count > 1 ) + { + /* Set all prior samples' sample_offset to 0. */ + int err = isom_add_ctts_entry( stbl, sample_count - 1, 0 ); + if( err < 0 ) + return err; + } + return isom_add_ctts_entry( stbl, 1, sample_offset ); +} + +static int isom_add_sample_offset( isom_stbl_t *stbl, uint32_t sample_offset ) +{ + if( !stbl->ctts->list ) + return LSMASH_ERR_INVALID_DATA; + isom_ctts_entry_t *data = (isom_ctts_entry_t *)stbl->ctts->list->tail->data; + if( data->sample_offset == sample_offset ) + ++ data->sample_count; + else + { + int err = isom_add_ctts_entry( stbl, 1, sample_offset ); + if( err < 0 ) + return err; + } + return 0; +} + +static int isom_add_cts( isom_stbl_t *stbl, uint64_t dts, uint64_t cts, int non_output_sample ) +{ + uint32_t sample_offset = !non_output_sample ? cts - dts : ISOM_NON_OUTPUT_SAMPLE_OFFSET; + if( LSMASH_IS_EXISTING_BOX( stbl->ctts ) ) + return isom_add_sample_offset( stbl, sample_offset ); + return sample_offset != 0 ? isom_add_initial_sample_offset( stbl, sample_offset ) : 0; +} + +static int isom_check_sample_offset_compatibility( lsmash_file_t *file, uint64_t dts, uint64_t cts, int non_output_sample ) +{ + if( non_output_sample ) + { + if( file->min_isom_version < 4 ) + return LSMASH_ERR_INVALID_DATA; /* Non-output sample can be indicated under 'iso4' or later brands. */ + } + else + { + if( file->isom_compatible && file->qt_compatible && (((cts >= dts) ? (cts - dts) : (dts - cts)) > INT32_MAX) ) + return LSMASH_ERR_INVALID_DATA; /* sample_offset is not compatible with both ISOBMFF and QTFF. */ + } + if( non_output_sample || cts < dts ) + { + /* Negative sample offset is required. */ + if( file->max_isom_version < 4 && !file->qt_compatible ) + return LSMASH_ERR_INVALID_DATA; /* Negative sample offset is not supported in both ISOBMFF and QTFF. */ + if( file->max_isom_version >= 4 && file->qt_compatible ) + return LSMASH_ERR_INVALID_DATA; /* ctts version 1 is not defined in QTFF. */ + } + return 0; +} + +void isom_update_cache_timestamp +( + isom_cache_t *cache, + uint64_t dts, + uint64_t cts, + int32_t ctd_shift, + uint32_t sample_duration, + int non_output_sample +) +{ + cache->timestamp.dts = dts; + cache->timestamp.cts = non_output_sample ? cache->timestamp.cts : cts; + cache->timestamp.ctd_shift = ctd_shift; + if( cache->fragment ) + { + cache->fragment->last_duration = sample_duration; + if( !non_output_sample ) + cache->fragment->largest_cts + = cache->fragment->largest_cts != LSMASH_TIMESTAMP_UNDEFINED + ? LSMASH_MAX( cache->timestamp.cts, cache->fragment->largest_cts ) + : cache->timestamp.cts; + } +} + +static int isom_add_timestamp( isom_stbl_t *stbl, isom_cache_t *cache, lsmash_file_t *file, uint64_t dts, uint64_t cts ) +{ + if( !cache || !stbl->stts->list ) + return LSMASH_ERR_INVALID_DATA; + int non_output_sample = (cts == LSMASH_TIMESTAMP_UNDEFINED); + int err = isom_check_sample_offset_compatibility( file, dts, cts, non_output_sample ); + if( err < 0 ) + return err; + uint32_t sample_count = isom_get_sample_count_from_sample_table( stbl ); + uint32_t sample_delta = sample_count > 1 ? isom_add_dts( stbl, dts, cache->timestamp.dts ) : 0; + if( sample_count > 1 && sample_delta == 0 ) + return LSMASH_ERR_INVALID_DATA; + if( (err = isom_add_cts( stbl, dts, cts, non_output_sample )) < 0 ) + return err; + int32_t ctd_shift = cache->timestamp.ctd_shift; + if( !non_output_sample && ((cts + ctd_shift) < dts) ) + { + /* Check overflow of composition to decode timeline shift. */ + if( (dts - cts) > INT32_MAX ) + return LSMASH_ERR_INVALID_DATA; + assert( LSMASH_IS_EXISTING_BOX( stbl->ctts ) ); + if( stbl->ctts->version == 0 && !file->qt_compatible ) + stbl->ctts->version = 1; + ctd_shift = dts - cts; + } + isom_update_cache_timestamp( cache, dts, cts, ctd_shift, sample_delta, non_output_sample ); + return 0; +} + +static int isom_add_sync_point( isom_stbl_t *stbl, isom_cache_t *cache, uint32_t sample_number, lsmash_sample_property_t *prop ) +{ + if( !(prop->ra_flags & ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC) ) /* no null check for prop */ + { + if( !cache->all_sync ) + return 0; + if( LSMASH_IS_NON_EXISTING_BOX( stbl->stss ) + && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_stss( stbl ) ) ) + return LSMASH_ERR_NAMELESS; + int err = isom_add_stss_entry( stbl, 1 ); + if( err < 0 ) /* Declare here the first sample is a sync sample. */ + return err; + cache->all_sync = 0; + return 0; + } + if( cache->all_sync ) /* We don't need stss box if all samples are sync sample. */ + return 0; + if( LSMASH_IS_NON_EXISTING_BOX( stbl->stss ) ) + { + if( isom_get_sample_count_from_sample_table( stbl ) == 1 ) + { + cache->all_sync = 1; /* Also the first sample is a sync sample. */ + return 0; + } + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_stss( stbl ) ) ) + return LSMASH_ERR_NAMELESS; + } + return isom_add_stss_entry( stbl, sample_number ); +} + +static int isom_add_partial_sync( isom_stbl_t *stbl, lsmash_file_t *file, uint32_t sample_number, lsmash_sample_property_t *prop ) +{ + if( !file->qt_compatible ) + return 0; + if( !(prop->ra_flags & QT_SAMPLE_RANDOM_ACCESS_FLAG_PARTIAL_SYNC) ) + return 0; + /* This sample is a partial sync sample. */ + if( LSMASH_IS_NON_EXISTING_BOX( stbl->stps ) + && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_stps( stbl ) ) ) + return LSMASH_ERR_NAMELESS; + return isom_add_stps_entry( stbl, sample_number ); +} + +int isom_rap_grouping_established( isom_rap_group_t *group, int num_leading_samples_known, isom_sgpd_t *sgpd, int is_fragment ) +{ + isom_rap_entry_t *rap = group->random_access; + if( !rap ) + return 0; + assert( rap == (isom_rap_entry_t *)sgpd->list->tail->data ); + rap->num_leading_samples_known = num_leading_samples_known; + /* Avoid duplication of sample group descriptions. */ + uint32_t group_description_index = is_fragment ? 0x10001 : 1; + for( lsmash_entry_t *entry = sgpd->list->head; entry != sgpd->list->tail; entry = entry->next ) + { + isom_rap_entry_t *data = (isom_rap_entry_t *)entry->data; + if( !data ) + return LSMASH_ERR_INVALID_DATA; + if( rap->num_leading_samples_known == data->num_leading_samples_known + && rap->num_leading_samples == data->num_leading_samples ) + { + /* The same description already exists. + * Remove the latest random access entry. */ + lsmash_list_remove_entry_tail( sgpd->list ); + /* Replace assigned group_description_index with the one corresponding the same description. */ + if( group->assignment->group_description_index == 0 ) + { + /* We don't create consecutive sample groups not assigned to 'rap '. + * So the previous sample group shall be a group of 'rap ' if any. */ + if( group->prev_assignment ) + { + assert( group->prev_assignment->group_description_index ); + group->prev_assignment->group_description_index = group_description_index; + } + } + else + group->assignment->group_description_index = group_description_index; + break; + } + ++group_description_index; + } + group->random_access = NULL; + return 0; +} + +int isom_group_random_access( isom_box_t *parent, isom_cache_t *cache, lsmash_sample_t *sample ) +{ + if( parent->file->max_isom_version < 6 ) + return 0; + isom_sbgp_t *sbgp; + isom_sgpd_t *sgpd; + uint32_t sample_count; + int is_fragment; + if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STBL ) ) + { + isom_stbl_t *stbl = (isom_stbl_t *)parent; + sbgp = isom_get_sample_to_group ( stbl, ISOM_GROUP_TYPE_RAP ); + sgpd = isom_get_sample_group_description( stbl, ISOM_GROUP_TYPE_RAP ); + sample_count = isom_get_sample_count_from_sample_table( stbl ); + is_fragment = 0; + } + else if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TRAF ) ) + { + isom_traf_t *traf = (isom_traf_t *)parent; + sbgp = isom_get_fragment_sample_to_group ( traf, ISOM_GROUP_TYPE_RAP ); + sgpd = isom_get_fragment_sample_group_description( traf, ISOM_GROUP_TYPE_RAP ); + sample_count = cache->fragment->sample_count + 1; /* Cached sample_count is incremented later in isom_fragment_update_cache(). */ + is_fragment = 1; + } + else + { + assert( 0 ); + sbgp = isom_non_existing_sbgp(); + sgpd = isom_non_existing_sgpd(); + /* redundant initializations to suppress warnings from unclever compilers */ + sample_count = 0; + is_fragment = 0; + } + if( LSMASH_IS_NON_EXISTING_BOX( sbgp ) + || LSMASH_IS_NON_EXISTING_BOX( sgpd ) ) + return 0; + lsmash_sample_property_t *prop = &sample->prop; + uint8_t is_rap = (prop->ra_flags & ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC) + || (prop->ra_flags & QT_SAMPLE_RANDOM_ACCESS_FLAG_PARTIAL_SYNC) + || (prop->ra_flags & ISOM_SAMPLE_RANDOM_ACCESS_FLAG_RAP) + || (LSMASH_IS_POST_ROLL_START( prop->ra_flags ) && prop->post_roll.identifier == prop->post_roll.complete); + isom_rap_group_t *group = cache->rap; + if( !group ) + { + /* This sample is the first sample, create a grouping cache. */ + assert( sample_count == 1 ); + group = lsmash_malloc( sizeof(isom_rap_group_t) ); + if( !group ) + return LSMASH_ERR_MEMORY_ALLOC; + if( is_rap ) + { + group->random_access = isom_add_rap_group_entry( sgpd ); + group->assignment = isom_add_group_assignment_entry( sbgp, 1, sgpd->list->entry_count + (is_fragment ? 0x10000 : 0) ); + } + else + { + /* The first sample is not always a random access point. */ + group->random_access = NULL; + group->assignment = isom_add_group_assignment_entry( sbgp, 1, 0 ); + } + if( !group->assignment ) + { + lsmash_free( group ); + return LSMASH_ERR_MEMORY_ALLOC; + } + group->prev_assignment = NULL; + group->is_prev_rap = is_rap; + cache->rap = group; + return 0; + } + int err; + if( group->is_prev_rap ) + { + /* OK. here, the previous sample is a menber of 'rap '. */ + if( !is_rap ) + { + /* This sample isn't a member of 'rap ' and the previous sample is. + * So we create a new group and set 0 on its group_description_index. */ + group->prev_assignment = group->assignment; + group->assignment = isom_add_group_assignment_entry( sbgp, 1, 0 ); + if( !group->assignment ) + { + lsmash_free( group ); + return LSMASH_ERR_MEMORY_ALLOC; + } + } + else if( !LSMASH_IS_CLOSED_RAP( prop->ra_flags ) ) + { + /* Create a new group since there is the possibility the next sample is a leading sample. + * This sample is a member of 'rap ', so we set appropriate value on its group_description_index. */ + if( (err = isom_rap_grouping_established( group, 1, sgpd, is_fragment )) < 0 ) + return err; + group->random_access = isom_add_rap_group_entry( sgpd ); + group->prev_assignment = group->assignment; + group->assignment = isom_add_group_assignment_entry( sbgp, 1, sgpd->list->entry_count + (is_fragment ? 0x10000 : 0) ); + if( !group->assignment ) + { + lsmash_free( group ); + return LSMASH_ERR_MEMORY_ALLOC; + } + } + else /* The previous and current sample are a member of 'rap ', and the next sample must not be a leading sample. */ + ++ group->assignment->sample_count; + } + else if( is_rap ) + { + /* This sample is a member of 'rap ' and the previous sample isn't. + * So we create a new group and set appropriate value on its group_description_index. */ + if( (err = isom_rap_grouping_established( group, 1, sgpd, is_fragment )) < 0 ) + return err; + group->random_access = isom_add_rap_group_entry( sgpd ); + group->prev_assignment = group->assignment; + group->assignment = isom_add_group_assignment_entry( sbgp, 1, sgpd->list->entry_count + (is_fragment ? 0x10000 : 0) ); + if( !group->assignment ) + { + lsmash_free( group ); + return LSMASH_ERR_MEMORY_ALLOC; + } + } + else /* The previous and current sample aren't a member of 'rap '. */ + ++ group->assignment->sample_count; + /* Obtain the property of the latest random access point group. */ + if( !is_rap && group->random_access ) + { + if( prop->leading == ISOM_SAMPLE_LEADING_UNKNOWN ) + { + /* We can no longer know num_leading_samples in this group. */ + if( (err = isom_rap_grouping_established( group, 0, sgpd, is_fragment )) < 0 ) + return err; + } + else + { + if( prop->leading == ISOM_SAMPLE_IS_UNDECODABLE_LEADING + || prop->leading == ISOM_SAMPLE_IS_DECODABLE_LEADING ) + ++ group->random_access->num_leading_samples; + /* no more consecutive leading samples in this group */ + else if( (err = isom_rap_grouping_established( group, 1, sgpd, is_fragment )) < 0 ) + return err; + } + } + group->is_prev_rap = is_rap; + return 0; +} + +static int isom_roll_grouping_established( isom_roll_group_t *group ) +{ + /* Avoid duplication of sample group descriptions. */ + isom_sgpd_t *sgpd = group->sgpd; + uint32_t group_description_index = group->is_fragment ? 0x10001 : 1; + for( lsmash_entry_t *entry = sgpd->list->head; entry; entry = entry->next ) + { + isom_roll_entry_t *data = (isom_roll_entry_t *)entry->data; + if( !data ) + return LSMASH_ERR_INVALID_DATA; + if( group->roll_distance == data->roll_distance ) + { + /* The same description already exists. + * Set the group_description_index corresponding the same description. */ + group->assignment->group_description_index = group_description_index; + return 0; + } + ++group_description_index; + } + /* Add a new roll recovery description. */ + if( !isom_add_roll_group_entry( sgpd, group->roll_distance ) ) + return LSMASH_ERR_MEMORY_ALLOC; + group->assignment->group_description_index = sgpd->list->entry_count + (group->is_fragment ? 0x10000 : 0); + return 0; +} + +static int isom_deduplicate_roll_group( isom_sbgp_t *sbgp, lsmash_entry_list_t *pool ) +{ + /* Deduplication */ + uint32_t current_group_number = sbgp->list->entry_count - pool->entry_count + 1; + isom_group_assignment_entry_t *prev_assignment = (isom_group_assignment_entry_t *)lsmash_list_get_entry_data( sbgp->list, current_group_number - 1 ); + for( lsmash_entry_t *entry = pool->head; entry; ) + { + isom_roll_group_t *group = (isom_roll_group_t *)entry->data; + if( !group + || !group->assignment ) + return LSMASH_ERR_INVALID_DATA; + if( !group->delimited || group->described != ROLL_DISTANCE_DETERMINED ) + return 0; + if( prev_assignment && prev_assignment->group_description_index == group->assignment->group_description_index ) + { + /* Merge the current group with the previous. */ + lsmash_entry_t *next_entry = entry->next; + prev_assignment->sample_count += group->assignment->sample_count; + int err; + if( (err = lsmash_list_remove_entry( sbgp->list, current_group_number )) < 0 + || (err = lsmash_list_remove_entry_direct( pool, entry )) < 0 ) + return err; + entry = next_entry; + } + else + { + entry = entry->next; + prev_assignment = group->assignment; + ++current_group_number; + } + } + return 0; +} + +/* Remove pooled caches that has become unnecessary. */ +static int isom_clean_roll_pool( lsmash_entry_list_t *pool ) +{ + for( lsmash_entry_t *entry = pool->head; entry; entry = pool->head ) + { + isom_roll_group_t *group = (isom_roll_group_t *)entry->data; + if( !group ) + return LSMASH_ERR_INVALID_DATA; + if( !group->delimited || group->described != ROLL_DISTANCE_DETERMINED ) + return 0; + int err = lsmash_list_remove_entry_direct( pool, entry ); + if( err < 0 ) + return err; + } + return 0; +} + +static int isom_flush_roll_pool( isom_sbgp_t *sbgp, lsmash_entry_list_t *pool ) +{ + int err; + for( lsmash_entry_t *entry = pool->head; entry; entry = entry->next ) + { + isom_roll_group_t *group = (isom_roll_group_t *)entry->data; + if( !group ) + return LSMASH_ERR_INVALID_DATA; + if( group->delimited + && group->described == ROLL_DISTANCE_DETERMINED + && group->roll_distance != 0 + && (err = isom_roll_grouping_established( group )) < 0 ) + return err; + } + if( (err = isom_deduplicate_roll_group( sbgp, pool )) < 0 ) + return err; + return isom_clean_roll_pool( pool ); +} + +static int isom_all_recovery_described( isom_sbgp_t *sbgp, lsmash_entry_list_t *pool ) +{ + for( lsmash_entry_t *entry = pool->head; entry; entry = entry->next ) + { + isom_roll_group_t *group = (isom_roll_group_t *)entry->data; + if( !group ) + return LSMASH_ERR_INVALID_DATA; + group->described = ROLL_DISTANCE_DETERMINED; + } + return isom_flush_roll_pool( sbgp, pool ); +} + +int isom_all_recovery_completed( isom_sbgp_t *sbgp, lsmash_entry_list_t *pool ) +{ + for( lsmash_entry_t *entry = pool->head; entry; entry = entry->next ) + { + isom_roll_group_t *group = (isom_roll_group_t *)entry->data; + if( !group ) + return LSMASH_ERR_INVALID_DATA; + group->described = ROLL_DISTANCE_DETERMINED; + group->delimited = 1; + } + return isom_flush_roll_pool( sbgp, pool ); +} + +static isom_roll_entry_t *isom_get_roll_description +( + isom_roll_group_t *group +) +{ + uint32_t group_description_index = group->assignment->group_description_index; + if( group_description_index && group->is_fragment ) + { + assert( group_description_index > 0x10000 ); + group_description_index -= 0x10000; + } + return (isom_roll_entry_t *)lsmash_list_get_entry_data( group->sgpd->list, group_description_index ); +} + +int isom_group_roll_recovery( isom_box_t *parent, isom_cache_t *cache, lsmash_sample_t *sample ) +{ + if( !parent->file->avc_extensions + && !parent->file->qt_compatible ) + return 0; + uint32_t sample_count; + int is_fragment; + lsmash_entry_list_t *sbgp_list; + lsmash_entry_list_t *sgpd_list; + if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STBL ) ) + { + isom_stbl_t *stbl = (isom_stbl_t *)parent; + sbgp_list = &stbl->sbgp_list; + sgpd_list = &stbl->sgpd_list; + sample_count = isom_get_sample_count_from_sample_table( stbl ); + is_fragment = 0; + } + else if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TRAF ) ) + { + if( parent->file->max_isom_version < 6 ) + return 0; + isom_traf_t *traf = (isom_traf_t *)parent; + sbgp_list = &traf->sbgp_list; + sgpd_list = &traf->sgpd_list; + sample_count = cache->fragment->sample_count + 1; /* Cached sample_count is incremented later in isom_fragment_update_cache(). */ + is_fragment = 1; + } + else + { + assert( 0 ); + return LSMASH_ERR_INVALID_DATA; + } + isom_sbgp_t *sbgp = isom_get_roll_recovery_sample_to_group ( sbgp_list ); + isom_sgpd_t *sgpd = isom_get_roll_recovery_sample_group_description( sgpd_list ); + if( LSMASH_IS_NON_EXISTING_BOX( sbgp ) + || LSMASH_IS_NON_EXISTING_BOX( sgpd ) + || sbgp->grouping_type != sgpd->grouping_type ) + return 0; + /* Check if 'roll' -> 'prol' conversion is needed. */ + if( cache->is_audio + && sbgp->grouping_type == ISOM_GROUP_TYPE_ROLL + && !(sample->prop.ra_flags & ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC) ) + { + /* Since not every samples is a sync sample, change grouping_type into 'prol'. */ + sbgp->grouping_type = ISOM_GROUP_TYPE_PROL; + sgpd->grouping_type = ISOM_GROUP_TYPE_PROL; + } + lsmash_entry_list_t *pool = cache->roll.pool; + if( !pool ) + { + pool = lsmash_list_create_simple(); + if( !pool ) + return LSMASH_ERR_MEMORY_ALLOC; + cache->roll.pool = pool; + } + lsmash_sample_property_t *prop = &sample->prop; + isom_roll_group_t *group = (isom_roll_group_t *)lsmash_list_get_entry_data( pool, pool->entry_count ); + int is_recovery_start = LSMASH_IS_POST_ROLL_START( prop->ra_flags ); + int valid_pre_roll = !is_recovery_start + && (prop->ra_flags != ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE) + && (prop->pre_roll.distance > 0) + && (prop->pre_roll.distance <= -INT16_MIN); + int new_group = !group || is_recovery_start || (group->prev_is_recovery_start != is_recovery_start); + if( !new_group ) + { + /* Check pre-roll distance. */ + assert( group->assignment && group->sgpd ); + isom_roll_entry_t *prev_roll = isom_get_roll_description( group ); + if( !prev_roll ) + new_group = valid_pre_roll; + else if( !valid_pre_roll || (prop->pre_roll.distance != -prev_roll->roll_distance) ) + /* Pre-roll distance is different from the previous. */ + new_group = 1; + } + if( new_group ) + { + if( group ) + group->delimited = 1; + else + assert( sample_count == 1 ); + /* Create a new group. */ + group = lsmash_malloc_zero( sizeof(isom_roll_group_t) ); + if( !group ) + return LSMASH_ERR_MEMORY_ALLOC; + group->sgpd = sgpd; + group->prev_is_recovery_start = is_recovery_start; + group->is_fragment = is_fragment; + group->assignment = isom_add_group_assignment_entry( sbgp, 1, 0 ); + if( !group->assignment || lsmash_list_add_entry( pool, group ) < 0 ) + { + lsmash_free( group ); + return LSMASH_ERR_MEMORY_ALLOC; + } + if( is_recovery_start ) + { + /* a member of non-roll or post-roll group */ + group->first_sample = sample_count; + group->recovery_point = prop->post_roll.complete; + } + else + { + group->described = ROLL_DISTANCE_DETERMINED; + if( valid_pre_roll ) + { + /* a member of pre-roll group */ + group->roll_distance = -(signed)prop->pre_roll.distance; + int err = isom_roll_grouping_established( group ); + if( err < 0 ) + return err; + } + else + /* a member of non-roll group */ + group->roll_distance = 0; + } + } + else + { + group->prev_is_recovery_start = is_recovery_start; + group->assignment->sample_count += 1; + } + /* If encountered a RAP, all recovery is completed here. */ + if( prop->ra_flags & (ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC + | ISOM_SAMPLE_RANDOM_ACCESS_FLAG_RAP + | QT_SAMPLE_RANDOM_ACCESS_FLAG_PARTIAL_SYNC) ) + return isom_all_recovery_described( sbgp, pool ); + /* Check whether this sample is a random access recovery point or not. */ + for( lsmash_entry_t *entry = pool->head; entry; entry = entry->next ) + { + group = (isom_roll_group_t *)entry->data; + if( !group ) + return LSMASH_ERR_INVALID_DATA; + if( group->described == ROLL_DISTANCE_DETERMINED ) + continue; + if( group->described == ROLL_DISTANCE_INITIALIZED ) + { + /* Let's consider the following picture sequence. + * coded order : P[0] P[1] P[2] P[3] P[4] P[5] + * DTS : 0 1 2 3 4 5 + * CTS : 2 4 3 6 7 5 + * Here, P[0] conveys a recovery point SEI and P[3] is the recovery point. + * Correctness of decoded pictures is specified by recovery point in output order for both AVC and HEVC. + * Therefore, as follows, + * output order : P[0] P[2] P[1] P[5]|P[3] P[4] + * ---(incorrect?)--->| + * there is no guarantee that P[5] is decoded and output correctly. + * From this, it can be said that the roll_distance of this sequence is equal to 5. */ + isom_roll_entry_t *post_roll = isom_get_roll_description( group ); + if( post_roll && post_roll->roll_distance > 0 ) + { + if( sample->cts != LSMASH_TIMESTAMP_UNDEFINED + && group->rp_cts != LSMASH_TIMESTAMP_UNDEFINED + && group->rp_cts > sample->cts ) + /* Updated roll_distance for composition reordering. */ + post_roll->roll_distance = sample_count - group->first_sample; + if( ++ group->wait_and_see_count >= MAX_ROLL_WAIT_AND_SEE_COUNT ) + group->described = ROLL_DISTANCE_DETERMINED; + } + } + else if( prop->post_roll.identifier == group->recovery_point ) + { + int16_t distance = sample_count - group->first_sample; + group->rp_cts = sample->cts; + group->roll_distance = distance; + /* Add a roll recovery entry only when roll_distance isn't zero since roll_distance = 0 must not be used. */ + if( distance ) + { + /* Now, this group is a 'roll'. + * The roll_distance may be updated later because of composition reordering. */ + group->described = ROLL_DISTANCE_INITIALIZED; + group->wait_and_see_count = 0; + /* All groups with uninitialized roll_distance before the current group are described. */ + lsmash_entry_t *current = entry; + for( entry = pool->head; entry != current; entry = entry->next ) + { + group = (isom_roll_group_t *)entry->data; + if( !group || group->described != ROLL_DISTANCE_INITIALIZED ) + continue; + group->described = ROLL_DISTANCE_DETERMINED; + } + /* Cache the mark of the first recovery point in a subsegment. */ + if( cache->fragment + && cache->fragment->subsegment.first_rp_number == 0 ) + cache->fragment->subsegment.is_first_recovery_point = 1; + } + else + /* Random Accessible Point */ + return isom_all_recovery_described( sbgp, pool ); + } + } + return isom_flush_roll_pool( sbgp, pool ); +} + +static int isom_update_chunk_tables +( + isom_stbl_t *stbl, + lsmash_file_t *media_file, + isom_chunk_t *current +) +{ + isom_stsc_entry_t *last_stsc_data = stbl->stsc->list->tail ? (isom_stsc_entry_t *)stbl->stsc->list->tail->data : NULL; + /* Create a new chunk sequence in this track if needed. */ + int err; + if( (!last_stsc_data + || current->pool->sample_count != last_stsc_data->samples_per_chunk + || current->sample_description_index != last_stsc_data->sample_description_index) + && (err = isom_add_stsc_entry( stbl, current->chunk_number, + current->pool->sample_count, + current->sample_description_index )) < 0 ) + return err; + /* Add a new chunk offset in this track. */ + uint64_t offset = media_file->size; + if( media_file->fragment ) + offset += ISOM_BASEBOX_COMMON_SIZE + media_file->fragment->pool_size; + return isom_add_stco_entry( stbl, offset ); +} + +/* This function decides to put a give sample on the current chunk or the next new one. + * Returns 1 if pooled samples must be flushed. + * FIXME: I wonder if this function should have a extra argument which indicates force_to_flush_cached_chunk. + * see lsmash_append_sample for detail. */ +static int isom_add_sample_to_chunk +( + isom_trak_t *trak, + lsmash_sample_t *sample +) +{ + if( LSMASH_IS_NON_EXISTING_BOX( trak->file ) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->mdhd ) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->minf->dinf->dref ) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->minf->stbl->stsd ) + || !trak->cache + || trak->mdia->mdhd->timescale == 0 + || !trak->mdia->minf->stbl->stsc->list ) + return LSMASH_ERR_INVALID_DATA; + isom_chunk_t *current = &trak->cache->chunk; + if( !current->pool ) + { + /* Very initial settings, just once per track */ + current->pool = isom_create_sample_pool( 0 ); + if( !current->pool ) + return LSMASH_ERR_MEMORY_ALLOC; + } + if( current->pool->sample_count == 0 ) + { + /* Cannot decide whether we should flush the current sample or not here yet. */ + current->chunk_number += 1; + current->sample_description_index = sample->index; + current->first_dts = sample->dts; + return 0; + } + if( sample->dts < current->first_dts ) + return LSMASH_ERR_INVALID_DATA; /* easy error check. */ + lsmash_file_t *media_file = isom_get_written_media_file( trak, current->sample_description_index ); + if( (current->sample_description_index == sample->index) + && (media_file->max_chunk_duration >= ((double)(sample->dts - current->first_dts) / trak->mdia->mdhd->timescale)) + && (media_file->max_chunk_size >= current->pool->size + sample->length) ) + return 0; /* No need to flush current cached chunk, the current sample must be put into that. */ + /* NOTE: chunk relative stuff must be pushed into file after a chunk is fully determined with its contents. + * Now the current cached chunk is fixed, actually add the chunk relative properties to its file accordingly. */ + int err = isom_update_chunk_tables( trak->mdia->minf->stbl, media_file, current ); + if( err < 0 ) + return err; + /* Update and re-initialize cache, using the current sample */ + current->chunk_number += 1; + current->sample_description_index = sample->index; + current->first_dts = sample->dts; + /* current->pool must be flushed in isom_append_sample_internal() */ + return 1; +} + +static int isom_write_pooled_samples( lsmash_file_t *file, isom_sample_pool_t *pool ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( file ) + || !file->bs + || !file->bs->stream + || !(file->flags & LSMASH_FILE_MODE_WRITE) + || !(file->flags & LSMASH_FILE_MODE_MEDIA) + || ((file->flags & LSMASH_FILE_MODE_BOX) && LSMASH_IS_NON_EXISTING_BOX( file->mdat )) ) + return LSMASH_ERR_INVALID_DATA; + lsmash_bs_put_bytes( file->bs, pool->size, pool->data ); + int err = lsmash_bs_flush_buffer( file->bs ); + if( err < 0 ) + return err; + if( LSMASH_IS_EXISTING_BOX( file->mdat ) ) + file->mdat->media_size += pool->size; + file->size += pool->size; + pool->sample_count = 0; + pool->size = 0; + return 0; +} + +int isom_update_sample_tables +( + isom_trak_t *trak, + lsmash_sample_t *sample, + uint32_t *samples_per_packet, + isom_sample_entry_t *sample_entry +) +{ + int err; + isom_audio_entry_t *audio = (isom_audio_entry_t *)sample_entry; + if( (audio->manager & LSMASH_AUDIO_DESCRIPTION) + && (audio->manager & LSMASH_QTFF_BASE) + && (audio->version == 1) + && (audio->compression_ID != QT_AUDIO_COMPRESSION_ID_VARIABLE_COMPRESSION) ) + { + /* Add entries of the sample table for each uncompressed sample. */ + uint64_t sample_duration = trak->mdia->mdhd->timescale / (audio->samplerate >> 16); + if( audio->samplesPerPacket == 0 || sample_duration == 0 || sample->cts == LSMASH_TIMESTAMP_UNDEFINED ) + return LSMASH_ERR_INVALID_DATA; + uint64_t sample_dts = sample->dts; + uint64_t sample_cts = sample->cts; + isom_stbl_t *stbl = trak->mdia->minf->stbl; + for( uint32_t i = 0; i < audio->samplesPerPacket; i++ ) + { + /* Add a size of uncomressed audio and increment sample_count. + * This points to individual uncompressed audio samples, each one byte in size, within the compressed frames. */ + uint32_t sample_count = isom_add_size( stbl, 1 ); + if( sample_count == 0 ) + return LSMASH_ERR_NAMELESS; + /* Add a decoding timestamp and a composition timestamp. */ + if( (err = isom_add_timestamp( stbl, trak->cache, trak->file, sample_dts, sample_cts )) < 0 ) + return err; + sample_dts += sample_duration; + sample_cts += sample_duration; + } + *samples_per_packet = audio->samplesPerPacket; + } + else + { + isom_stbl_t *stbl = trak->mdia->minf->stbl; + /* Add a sample_size and increment sample_count. */ + uint32_t sample_count = isom_add_size( stbl, sample->length ); + if( sample_count == 0 ) + return LSMASH_ERR_NAMELESS; + /* Add a decoding timestamp and a composition timestamp. */ + if( (err = isom_add_timestamp( stbl, trak->cache, trak->file, sample->dts, sample->cts )) < 0 ) + return err; + /* Add a sync point if needed. */ + if( (err = isom_add_sync_point( stbl, trak->cache, sample_count, &sample->prop )) < 0 ) + return err; + /* Add a partial sync point if needed. */ + if( (err = isom_add_partial_sync( stbl, trak->file, sample_count, &sample->prop )) < 0 ) + return err; + /* Add leading, independent, disposable and redundant information if needed. */ + if( stbl->add_dependency_type + && (err = stbl->add_dependency_type( stbl, trak->file, &sample->prop )) < 0 ) + return err; + /* Group samples into random access point type if needed. */ + if( (err = isom_group_random_access( (isom_box_t *)stbl, trak->cache, sample )) < 0 ) + return err; + /* Group samples into random access recovery point type if needed. */ + if( (err = isom_group_roll_recovery( (isom_box_t *)stbl, trak->cache, sample )) < 0 ) + return err; + *samples_per_packet = 1; + } + /* Add a chunk if needed. */ + return isom_add_sample_to_chunk( trak, sample ); +} + +static int isom_output_cached_chunk( isom_trak_t *trak ) +{ + isom_chunk_t *chunk = &trak->cache->chunk; + isom_stbl_t *stbl = trak->mdia->minf->stbl; + isom_stsc_entry_t *last_stsc_data = stbl->stsc->list->tail ? (isom_stsc_entry_t *)stbl->stsc->list->tail->data : NULL; + /* Create a new chunk sequence in this track if needed. */ + int err; + if( (!last_stsc_data + || chunk->pool->sample_count != last_stsc_data->samples_per_chunk + || chunk->sample_description_index != last_stsc_data->sample_description_index) + && (err = isom_add_stsc_entry( stbl, chunk->chunk_number, + chunk->pool->sample_count, + chunk->sample_description_index )) < 0 ) + return err; + lsmash_file_t *file = isom_get_written_media_file( trak, chunk->sample_description_index ); + if( file->fragment ) + { + /* Add a new chunk offset in this track. */ + if( (err = isom_add_stco_entry( stbl, file->size + ISOM_BASEBOX_COMMON_SIZE + file->fragment->pool_size )) < 0 ) + return err; + return isom_append_fragment_track_run( file, chunk ); + } + /* Add a new chunk offset in this track. */ + if( (err = isom_add_stco_entry( stbl, file->size )) < 0 ) + return err; + /* Output pooled samples in this track. */ + return isom_write_pooled_samples( file, chunk->pool ); +} + +int isom_pool_sample( isom_sample_pool_t *pool, lsmash_sample_t *sample, uint32_t samples_per_packet ) +{ + uint64_t pool_size = pool->size + sample->length; + if( pool->alloc < pool_size ) + { + uint8_t *data; + uint64_t alloc = pool_size + (1<<16); + if( !pool->data ) + data = lsmash_malloc( alloc ); + else + data = lsmash_realloc( pool->data, alloc ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + pool->data = data; + pool->alloc = alloc; + } + memcpy( pool->data + pool->size, sample->data, sample->length ); + pool->size = pool_size; + pool->sample_count += samples_per_packet; + lsmash_delete_sample( sample ); + return 0; +} + +static int isom_append_sample_internal +( + isom_trak_t *trak, + lsmash_sample_t *sample, + isom_sample_entry_t *sample_entry +) +{ + uint32_t samples_per_packet; + int ret = isom_update_sample_tables( trak, sample, &samples_per_packet, sample_entry ); + if( ret < 0 ) + return ret; + /* ret == 1 means pooled samples must be flushed. */ + isom_sample_pool_t *current_pool = trak->cache->chunk.pool; + if( ret == 1 ) + { + /* The sample_description_index in the cache is one of the next written chunk. + * Therefore, it cannot be referenced here. */ + lsmash_entry_list_t *stsc_list = trak->mdia->minf->stbl->stsc->list; + isom_stsc_entry_t *last_stsc_data = (isom_stsc_entry_t *)stsc_list->tail->data; + lsmash_file_t *file = isom_get_written_media_file( trak, last_stsc_data->sample_description_index ); + if( (ret = isom_write_pooled_samples( file, current_pool )) < 0 ) + return ret; + } + /* Arbitration system between tracks with extremely scattering dts. + * Here, we check whether asynchronization between the tracks exceeds the tolerance. + * If a track has too old "first DTS" in its cached chunk than current sample's DTS, then its pooled samples must be flushed. + * We don't consider presentation of media since any edit can pick an arbitrary portion of media in track. + * Note: you needn't read this loop until you grasp the basic handling of chunks. */ + lsmash_file_t *file = trak->file; + double tolerance = file->max_async_tolerance; + for( lsmash_entry_t *entry = file->moov->trak_list.head; entry; entry = entry->next ) + { + isom_trak_t *other = (isom_trak_t *)entry->data; + if( trak == other ) + continue; + if( LSMASH_IS_NON_EXISTING_BOX( other ) + || LSMASH_IS_NON_EXISTING_BOX( other->mdia->mdhd ) + || !other->cache + || other->mdia->mdhd->timescale == 0 + || !other->mdia->minf->stbl->stsc->list ) + return LSMASH_ERR_INVALID_DATA; + isom_chunk_t *chunk = &other->cache->chunk; + if( !chunk->pool || chunk->pool->sample_count == 0 ) + continue; + double diff = ((double)sample->dts / trak->mdia->mdhd->timescale) + - ((double)chunk->first_dts / other->mdia->mdhd->timescale); + if( diff > tolerance && (ret = isom_output_cached_chunk( other )) < 0 ) + return ret; + /* Note: we don't flush the cached chunk in the current track and the current sample here + * even if the conditional expression of '-diff > tolerance' meets. + * That's useless because appending a sample to another track would be a good equivalent. + * It's even harmful because it causes excess chunk division by calling + * isom_output_cached_chunk() which always generates a new chunk. + * Anyway some excess chunk division will be there, but rather less without it. + * To completely avoid this, we need to observe at least whether the current sample will be placed + * right next to the previous chunk of the same track or not. */ + } + /* anyway the current sample must be pooled. */ + return isom_pool_sample( current_pool, sample, samples_per_packet ); +} + +int isom_append_sample_by_type +( + void *track, + lsmash_sample_t *sample, + isom_sample_entry_t *sample_entry, + int (*func_append_sample)( void *, lsmash_sample_t *, isom_sample_entry_t * ) +) +{ + if( isom_is_lpcm_audio( sample_entry ) ) + { + uint32_t frame_size = ((isom_audio_entry_t *)sample_entry)->constBytesPerAudioPacket; + if( sample->length == frame_size ) + return func_append_sample( track, sample, sample_entry ); + else if( sample->length < frame_size || sample->cts == LSMASH_TIMESTAMP_UNDEFINED ) + return LSMASH_ERR_INVALID_DATA; + /* Append samples splitted into each LPCMFrame. */ + uint64_t dts = sample->dts; + uint64_t cts = sample->cts; + for( uint32_t offset = 0; offset < sample->length; offset += frame_size ) + { + lsmash_sample_t *lpcm_sample = lsmash_create_sample( frame_size ); + if( !lpcm_sample ) + return LSMASH_ERR_MEMORY_ALLOC; + memcpy( lpcm_sample->data, sample->data + offset, frame_size ); + lpcm_sample->dts = dts++; + lpcm_sample->cts = cts++; + lpcm_sample->prop = sample->prop; + lpcm_sample->index = sample->index; + int err = func_append_sample( track, lpcm_sample, sample_entry ); + if( err < 0 ) + { + lsmash_delete_sample( lpcm_sample ); + return err; + } + } + lsmash_delete_sample( sample ); + return 0; + } + else if( lsmash_check_codec_type_identical( sample_entry->type, ISOM_CODEC_TYPE_RTP_HINT ) + || lsmash_check_codec_type_identical( sample_entry->type, ISOM_CODEC_TYPE_RRTP_HINT ) ) + { + /* calculate PDU statistics for hmhd box. + * It requires accessing sample data to get the number of packets per sample. */ + isom_trak_t *trak = (isom_trak_t*)track; + isom_hmhd_t *hmhd = trak->mdia->minf->hmhd; + uint16_t packetcount = LSMASH_GET_BE16( sample->data ); + uint8_t *data = sample->data + RTP_SAMPLE_HEADER_SIZE + RTP_PACKET_SIZE; + /* Calculate only packet headers and packet payloads sizes int PDU size. Later use these two to get avgPDUsize. */ + hmhd->combinedPDUsize += sample->length - (packetcount * RTP_CONSTRUCTOR_SIZE) - RTP_SAMPLE_HEADER_SIZE; + hmhd->PDUcount += packetcount; + for( unsigned int i = 0; i < packetcount; i++ ) + { + /* constructor type */ + if( *data == 2 ) + { + /* payload size*/ + uint16_t length = *(data + 2); + /* Check if this packet is larger than any of the previous ones. */ + hmhd->maxPDUsize = hmhd->maxPDUsize > length + RTP_HEADER_SIZE ? hmhd->maxPDUsize : length + RTP_HEADER_SIZE; + data += RTP_CONSTRUCTOR_SIZE + RTP_PACKET_SIZE; + } /* TODO: other constructor types */ + } + } /* TODO: add other hint tracks that have a hmhd box */ + return func_append_sample( track, sample, sample_entry ); +} + +/* This function is for non-fragmented movie. */ +static int isom_append_sample +( + lsmash_file_t *file, + isom_trak_t *trak, + lsmash_sample_t *sample, + isom_sample_entry_t *sample_entry +) +{ + /* If there is no available Media Data Box to write samples, add and write a new one before any chunk offset is decided. */ + int err; + int mdat_absent = LSMASH_IS_NON_EXISTING_BOX( file->mdat ); + if( mdat_absent || !(file->mdat->manager & LSMASH_INCOMPLETE_BOX) ) + { + if( mdat_absent && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_mdat( file ) ) ) + return LSMASH_ERR_NAMELESS; + file->mdat->manager |= LSMASH_PLACEHOLDER; + if( (err = isom_write_box( file->bs, (isom_box_t *)file->mdat )) < 0 ) + return err; + file->size += file->mdat->size; + } + return isom_append_sample_by_type( trak, sample, sample_entry, (int (*)( void *, lsmash_sample_t *, isom_sample_entry_t * ))isom_append_sample_internal ); +} + +static int isom_output_cache( isom_trak_t *trak ) +{ + int err; + isom_cache_t *cache = trak->cache; + if( cache->chunk.pool + && cache->chunk.pool->sample_count + && (err = isom_output_cached_chunk( trak )) < 0 ) + return err; + isom_stbl_t *stbl = trak->mdia->minf->stbl; + for( lsmash_entry_t *entry = stbl->sgpd_list.head; entry; entry = entry->next ) + { + isom_sgpd_t *sgpd = (isom_sgpd_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( sgpd ) ) + return LSMASH_ERR_INVALID_DATA; + switch( sgpd->grouping_type ) + { + case ISOM_GROUP_TYPE_RAP : + { + isom_rap_group_t *group = cache->rap; + if( !group ) + { + if( stbl->file->fragment ) + continue; + else + return LSMASH_ERR_NAMELESS; + } + if( !group->random_access ) + continue; + group->random_access->num_leading_samples_known = 1; + break; + } + case ISOM_GROUP_TYPE_ROLL : + case ISOM_GROUP_TYPE_PROL : + if( !cache->roll.pool ) + { + if( stbl->file->fragment ) + continue; + else + return LSMASH_ERR_NAMELESS; + } + isom_sbgp_t *sbgp = isom_get_roll_recovery_sample_to_group( &stbl->sbgp_list ); + if( LSMASH_IS_NON_EXISTING_BOX( sbgp ) ) + return LSMASH_ERR_NAMELESS; + if( (err = isom_all_recovery_completed( sbgp, cache->roll.pool )) < 0 ) + return err; + break; + default : + break; + } + } + return 0; +} + +int lsmash_flush_pooled_samples( lsmash_root_t *root, uint32_t track_ID, uint32_t last_sample_delta ) +{ + if( isom_check_initializer_present( root ) < 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_file_t *file = root->file; + if( file->fragment + && file->fragment->movie ) + return isom_flush_fragment_pooled_samples( file, track_ID, last_sample_delta ); + if( file != file->initializer ) + return LSMASH_ERR_INVALID_DATA; + isom_trak_t *trak = isom_get_trak( file, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak ) + || !trak->cache + || !trak->mdia->minf->stbl->stsc->list ) + return LSMASH_ERR_NAMELESS; + int err = isom_output_cache( trak ); + if( err < 0 ) + return err; + return lsmash_set_last_sample_delta( root, track_ID, last_sample_delta ); +} + +int lsmash_append_sample( lsmash_root_t *root, uint32_t track_ID, lsmash_sample_t *sample ) +{ + if( isom_check_initializer_present( root ) < 0 + || track_ID == 0 + || sample == NULL + || sample->data == NULL + || sample->dts == LSMASH_TIMESTAMP_UNDEFINED ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_file_t *file = root->file; + /* We think max_chunk_duration == 0, which means all samples will be cached on memory, should be prevented. + * This means removal of a feature that we used to have, but anyway very alone chunk does not make sense. */ + if( !file->bs + || !(file->flags & LSMASH_FILE_MODE_BOX) + || file->max_chunk_duration == 0 + || file->max_async_tolerance == 0 ) + return LSMASH_ERR_NAMELESS; + /* Write File Type Box here if it was not written yet. */ + if( file->flags & LSMASH_FILE_MODE_INITIALIZATION ) + { + if( LSMASH_IS_EXISTING_BOX( file->ftyp ) && !(file->ftyp->manager & LSMASH_WRITTEN_BOX) ) + { + int err = isom_write_box( file->bs, (isom_box_t *)file->ftyp ); + if( err < 0 ) + return err; + file->size += file->ftyp->size; + } + } + /* Get a sample initializer. */ + isom_trak_t *trak = isom_get_trak( file->initializer, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak->file ) + || LSMASH_IS_NON_EXISTING_BOX( trak->tkhd ) + || trak->mdia->mdhd->timescale == 0 + || !trak->cache + || !trak->mdia->minf->stbl->stsc->list ) + return LSMASH_ERR_NAMELESS; + isom_sample_entry_t *sample_entry = (isom_sample_entry_t *)lsmash_list_get_entry_data( &trak->mdia->minf->stbl->stsd->list, sample->index ); + if( LSMASH_IS_NON_EXISTING_BOX( sample_entry ) ) + return LSMASH_ERR_NAMELESS; + /* Append a sample. */ + if( (file->flags & LSMASH_FILE_MODE_FRAGMENTED) + && file->fragment + && file->fragment->pool ) + return isom_append_fragment_sample( file, trak, sample, sample_entry ); + if( file != file->initializer ) + return LSMASH_ERR_INVALID_DATA; + return isom_append_sample( file, trak, sample, sample_entry ); +} + +/*---- misc functions ----*/ + +int lsmash_delete_explicit_timeline_map( lsmash_root_t *root, uint32_t track_ID ) +{ + if( isom_check_initializer_present( root ) < 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + isom_trak_t *trak = isom_get_trak( root->file->initializer, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak ) ) + return LSMASH_ERR_NAMELESS; + isom_remove_box_by_itself( trak->edts ); + return isom_update_tkhd_duration( trak ); +} + +void lsmash_delete_tyrant_chapter( lsmash_root_t *root ) +{ + if( isom_check_initializer_present( root ) < 0 + || LSMASH_IS_NON_EXISTING_BOX( root->file->initializer->moov->udta ) ) + return; + isom_remove_box_by_itself( root->file->moov->udta->chpl ); +} + +int lsmash_set_sdp( lsmash_root_t *root, uint32_t track_ID, char *sdptext ) +{ + if( isom_check_initializer_present( root ) < 0 || sdptext == NULL ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_file_t *file = root->file; + if( LSMASH_IS_NON_EXISTING_BOX( file->moov ) || !file->isom_compatible ) + return LSMASH_ERR_NAMELESS; + isom_udta_t *udta; + if( track_ID ) + { + isom_trak_t *trak = isom_get_trak( file, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak ) ) + return LSMASH_ERR_NAMELESS; + if( LSMASH_IS_NON_EXISTING_BOX( trak->udta ) + && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_udta( trak ) ) ) + return LSMASH_ERR_NAMELESS; + udta = trak->udta; + } + else + { + if( LSMASH_IS_NON_EXISTING_BOX( file->moov->udta ) + && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_udta( file->moov ) ) ) + return LSMASH_ERR_NAMELESS; + udta = file->moov->udta; + } + assert( LSMASH_IS_EXISTING_BOX( udta ) ); + if( LSMASH_IS_NON_EXISTING_BOX( udta->hnti ) + && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_hnti( udta ) ) ) + return LSMASH_ERR_NAMELESS; + isom_hnti_t *hnti = udta->hnti; + /* If track ID is given, text is meant for track hnti box, + * otherwise it is meant for movie 'hnti' box. */ + if( track_ID ) + { + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_sdp( hnti ) ) ) + return LSMASH_ERR_NAMELESS; + isom_sdp_t *sdp = hnti->sdp; + sdp->sdp_length = strlen( sdptext ); /* leaves \0 out*/ + sdp->sdptext = lsmash_memdup( sdptext, sdp->sdp_length ); + if( !sdp->sdptext ) + return LSMASH_ERR_MEMORY_ALLOC; + } + else + { + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_rtp( hnti ) ) ) + return LSMASH_ERR_NAMELESS; + isom_rtp_t *rtp = hnti->rtp; + rtp->descriptionformat = LSMASH_4CC( 's', 'd', 'p', ' ' ); + rtp->sdp_length = strlen( sdptext ); /* leaves \0 out */ + rtp->sdptext = lsmash_memdup( sdptext, rtp->sdp_length ); + if( !rtp->sdptext ) + return LSMASH_ERR_MEMORY_ALLOC; + } + return 0; +} + +int lsmash_set_copyright( lsmash_root_t *root, uint32_t track_ID, uint16_t ISO_language, char *notice ) +{ + if( isom_check_initializer_present( root ) < 0 + || (ISO_language && ISO_language < 0x800) + || !notice ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_file_t *file = root->file; + if( !file->isom_compatible ) + return LSMASH_ERR_NAMELESS; + isom_udta_t *udta; + if( track_ID ) + { + isom_trak_t *trak = isom_get_trak( file, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak->udta ) && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_udta( trak ) ) ) + return LSMASH_ERR_NAMELESS; + udta = trak->udta; + } + else + { + if( LSMASH_IS_NON_EXISTING_BOX( file->moov->udta ) && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_udta( file->moov ) ) ) + return LSMASH_ERR_NAMELESS; + udta = file->moov->udta; + } + assert( LSMASH_IS_EXISTING_BOX( udta ) ); + for( lsmash_entry_t *entry = udta->cprt_list.head; entry; entry = entry->next ) + { + isom_cprt_t *cprt = (isom_cprt_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( cprt ) || cprt->language == ISO_language ) + return LSMASH_ERR_NAMELESS; + } + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_cprt( udta ) ) ) + return LSMASH_ERR_NAMELESS; + isom_cprt_t *cprt = (isom_cprt_t *)udta->cprt_list.tail->data; + cprt->language = ISO_language; + cprt->notice_length = strlen( notice ) + 1; + cprt->notice = lsmash_memdup( notice, cprt->notice_length ); + return 0; +} diff --git a/3rdparty/l-smash/core/meta.c b/3rdparty/l-smash/core/meta.c new file mode 100644 index 000000000..dc8718723 --- /dev/null +++ b/3rdparty/l-smash/core/meta.c @@ -0,0 +1,500 @@ +/***************************************************************************** + * meta.c + ***************************************************************************** + * Copyright (C) 2012-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include +#include + +#include "box.h" +#include "box_default.h" + +static isom_data_t *isom_add_metadata( lsmash_file_t *file, + lsmash_itunes_metadata_item item, + char *meaning_string, char *name_string ) +{ + assert( LSMASH_IS_EXISTING_BOX( file->moov ) ); + if( ((item == ITUNES_METADATA_ITEM_CUSTOM) && (!meaning_string || !meaning_string[0]) ) + || (LSMASH_IS_NON_EXISTING_BOX( file->moov->udta ) + && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_udta( file->moov ) )) + || (LSMASH_IS_NON_EXISTING_BOX( file->moov->udta->meta ) + && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_meta( file->moov->udta ) )) + || (LSMASH_IS_NON_EXISTING_BOX( file->moov->udta->meta->ilst ) + && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_ilst( file->moov->udta->meta ) )) ) + return isom_non_existing_data(); + if( LSMASH_IS_NON_EXISTING_BOX( file->moov->udta->meta->hdlr ) ) + { + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_hdlr( file->moov->udta->meta ) ) + || isom_setup_handler_reference( file->moov->udta->meta->hdlr, ISOM_META_HANDLER_TYPE_ITUNES_METADATA ) < 0 ) + return isom_non_existing_data(); + } + isom_ilst_t *ilst = file->moov->udta->meta->ilst; + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_metaitem( ilst, item ) ) ) + return isom_non_existing_data(); + isom_metaitem_t *metaitem = (isom_metaitem_t *)ilst->metaitem_list.tail->data; + if( item == ITUNES_METADATA_ITEM_CUSTOM ) + { + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_mean( metaitem ) ) ) + goto fail; + isom_mean_t *mean = metaitem->mean; + mean->meaning_string_length = strlen( meaning_string ); /* No null terminator */ + mean->meaning_string = lsmash_memdup( meaning_string, mean->meaning_string_length ); + if( !mean->meaning_string ) + goto fail; + if( name_string && name_string[0] ) + { + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_name( metaitem ) ) ) + goto fail; + isom_name_t *name = metaitem->name; + name->name_length = strlen( name_string ); /* No null terminator */ + name->name = lsmash_memdup( name_string, name->name_length ); + if( !name->name ) + goto fail; + } + } + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_data( metaitem ) ) ) + goto fail; + return metaitem->data; +fail: + isom_remove_box_by_itself( metaitem ); + return isom_non_existing_data(); +} + +static int isom_set_itunes_metadata_string( lsmash_file_t *file, + lsmash_itunes_metadata_item item, + lsmash_itunes_metadata_value_t value, char *meaning, char *name ) +{ + uint32_t value_length = strlen( value.string ); + if( item == ITUNES_METADATA_ITEM_DESCRIPTION && value_length > 255 ) + item = ITUNES_METADATA_ITEM_LONG_DESCRIPTION; + isom_data_t *data = isom_add_metadata( file, item, meaning, name ); + if( LSMASH_IS_NON_EXISTING_BOX( data ) ) + return LSMASH_ERR_NAMELESS; + data->type_code = ITUNES_METADATA_SUBTYPE_UTF8; + data->value_length = value_length; /* No null terminator */ + data->value = lsmash_memdup( value.string, data->value_length ); + if( !data->value ) + { + isom_ilst_t *ilst = file->moov->udta->meta->ilst; + isom_remove_box_by_itself( ilst->metaitem_list.tail->data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + return 0; +} + +static int isom_set_itunes_metadata_integer( lsmash_file_t *file, + lsmash_itunes_metadata_item item, + lsmash_itunes_metadata_value_t value, char *meaning, char *name ) +{ + static const struct + { + lsmash_itunes_metadata_item item; + int length; + } metadata_code_type_table[] = + { + { ITUNES_METADATA_ITEM_EPISODE_GLOBAL_ID, 1 }, + { ITUNES_METADATA_ITEM_PREDEFINED_GENRE, 2 }, + { ITUNES_METADATA_ITEM_CONTENT_RATING, 1 }, + { ITUNES_METADATA_ITEM_MEDIA_TYPE, 1 }, + { ITUNES_METADATA_ITEM_BEATS_PER_MINUTE, 2 }, + { ITUNES_METADATA_ITEM_TV_EPISODE, 4 }, + { ITUNES_METADATA_ITEM_TV_SEASON, 4 }, + { ITUNES_METADATA_ITEM_ITUNES_ACCOUNT_TYPE, 1 }, + { ITUNES_METADATA_ITEM_ITUNES_ARTIST_ID, 4 }, + { ITUNES_METADATA_ITEM_ITUNES_COMPOSER_ID, 4 }, + { ITUNES_METADATA_ITEM_ITUNES_CATALOG_ID, 4 }, + { ITUNES_METADATA_ITEM_ITUNES_TV_GENRE_ID, 4 }, + { ITUNES_METADATA_ITEM_ITUNES_PLAYLIST_ID, 8 }, + { ITUNES_METADATA_ITEM_ITUNES_COUNTRY_CODE, 4 }, + { ITUNES_METADATA_ITEM_CUSTOM, 8 }, + { 0, 0 } + }; + int i; + for( i = 0; metadata_code_type_table[i].item; i++ ) + if( item == metadata_code_type_table[i].item ) + break; + if( metadata_code_type_table[i].length == 0 ) + return LSMASH_ERR_NAMELESS; + isom_data_t *data = isom_add_metadata( file, item, meaning, name ); + if( LSMASH_IS_NON_EXISTING_BOX( data ) ) + return LSMASH_ERR_NAMELESS; + if( item == ITUNES_METADATA_ITEM_PREDEFINED_GENRE ) + data->type_code = ITUNES_METADATA_SUBTYPE_IMPLICIT; + else + data->type_code = ITUNES_METADATA_SUBTYPE_INTEGER; + data->value_length = metadata_code_type_table[i].length; + uint8_t temp[8]; + for( i = 0; i < data->value_length; i++ ) + { + int shift = (data->value_length - i - 1) * 8; + temp[i] = (value.integer >> shift) & 0xff; + } + data->value = lsmash_memdup( temp, data->value_length ); + if( !data->value ) + { + isom_ilst_t *ilst = file->moov->udta->meta->ilst; + isom_remove_box_by_itself( ilst->metaitem_list.tail->data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + return 0; +} + +static int isom_set_itunes_metadata_boolean( lsmash_file_t *file, + lsmash_itunes_metadata_item item, + lsmash_itunes_metadata_value_t value, char *meaning, char *name ) +{ + isom_data_t *data = isom_add_metadata( file, item, meaning, name ); + if( LSMASH_IS_NON_EXISTING_BOX( data ) ) + return LSMASH_ERR_NAMELESS; + data->type_code = ITUNES_METADATA_SUBTYPE_INTEGER; + data->value_length = 1; + uint8_t temp = (uint8_t)value.boolean; + data->value = lsmash_memdup( &temp, 1 ); + if( !data->value ) + { + isom_ilst_t *ilst = file->moov->udta->meta->ilst; + isom_remove_box_by_itself( ilst->metaitem_list.tail->data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + return 0; +} + +static int isom_set_itunes_metadata_binary( lsmash_file_t *file, + lsmash_itunes_metadata_item item, + lsmash_itunes_metadata_value_t value, char *meaning, char *name ) +{ + isom_data_t *data = isom_add_metadata( file, item, meaning, name ); + if( LSMASH_IS_NON_EXISTING_BOX( data ) ) + return LSMASH_ERR_NAMELESS; + switch( item ) + { + case ITUNES_METADATA_ITEM_COVER_ART : + if( value.binary.subtype != ITUNES_METADATA_SUBTYPE_JPEG + && value.binary.subtype != ITUNES_METADATA_SUBTYPE_PNG + && value.binary.subtype != ITUNES_METADATA_SUBTYPE_BMP ) + return LSMASH_ERR_FUNCTION_PARAM; + break; + case ITUNES_METADATA_ITEM_DISC_NUMBER : + case ITUNES_METADATA_ITEM_TRACK_NUMBER : + value.binary.subtype = ITUNES_METADATA_SUBTYPE_IMPLICIT; + break; + default : + break; + } + switch( value.binary.subtype ) + { + case ITUNES_METADATA_SUBTYPE_UUID : + if( value.binary.size != 16 ) + return LSMASH_ERR_FUNCTION_PARAM; + break; + case ITUNES_METADATA_SUBTYPE_DURATION : + if( value.binary.size != 4 ) + return LSMASH_ERR_FUNCTION_PARAM; + break; + case ITUNES_METADATA_SUBTYPE_TIME : + if( value.binary.size != 4 && value.binary.size != 8 ) + return LSMASH_ERR_FUNCTION_PARAM; + break; + case ITUNES_METADATA_SUBTYPE_INTEGER : + if( value.binary.size != 1 && value.binary.size != 2 + && value.binary.size != 3 && value.binary.size != 4 + && value.binary.size != 8 ) + return LSMASH_ERR_FUNCTION_PARAM; + break; + case ITUNES_METADATA_SUBTYPE_RIAAPA : + if( value.binary.size != 1 ) + return LSMASH_ERR_FUNCTION_PARAM; + break; + default : + break; + } + data->type_code = value.binary.subtype; + data->value_length = value.binary.size; + data->value = lsmash_memdup( value.binary.data, value.binary.size ); + if( !data->value ) + { + isom_ilst_t *ilst = file->moov->udta->meta->ilst; + isom_remove_box_by_itself( ilst->metaitem_list.tail->data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + return 0; +} + +int lsmash_set_itunes_metadata( lsmash_root_t *root, lsmash_itunes_metadata_t metadata ) +{ + if( isom_check_initializer_present( root ) < 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + static const struct + { + lsmash_itunes_metadata_item item; + int (*func_set_itunes_metadata)( lsmash_file_t *, lsmash_itunes_metadata_item, lsmash_itunes_metadata_value_t, char *, char * ); + } itunes_metadata_function_mapping[] = + { + { ITUNES_METADATA_ITEM_ALBUM_NAME, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_ARTIST, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_USER_COMMENT, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_RELEASE_DATE, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_ENCODED_BY, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_USER_GENRE, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_GROUPING, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_LYRICS, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_TITLE, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_TRACK_SUBTITLE, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_ENCODING_TOOL, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_COMPOSER, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_ALBUM_ARTIST, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_PODCAST_CATEGORY, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_COPYRIGHT, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_DESCRIPTION, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_GROUPING_DRAFT, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_PODCAST_KEYWORD, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_LONG_DESCRIPTION, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_PURCHASE_DATE, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_TV_EPISODE_ID, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_TV_NETWORK, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_TV_SHOW_NAME, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_ITUNES_PURCHASE_ACCOUNT_ID, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_ITUNES_SORT_ALBUM, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_ITUNES_SORT_ARTIST, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_ITUNES_SORT_ALBUM_ARTIST, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_ITUNES_SORT_COMPOSER, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_ITUNES_SORT_NAME, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_ITUNES_SORT_SHOW, isom_set_itunes_metadata_string }, + { ITUNES_METADATA_ITEM_EPISODE_GLOBAL_ID, isom_set_itunes_metadata_integer }, + { ITUNES_METADATA_ITEM_PREDEFINED_GENRE, isom_set_itunes_metadata_integer }, + { ITUNES_METADATA_ITEM_CONTENT_RATING, isom_set_itunes_metadata_integer }, + { ITUNES_METADATA_ITEM_MEDIA_TYPE, isom_set_itunes_metadata_integer }, + { ITUNES_METADATA_ITEM_BEATS_PER_MINUTE, isom_set_itunes_metadata_integer }, + { ITUNES_METADATA_ITEM_TV_EPISODE, isom_set_itunes_metadata_integer }, + { ITUNES_METADATA_ITEM_TV_SEASON, isom_set_itunes_metadata_integer }, + { ITUNES_METADATA_ITEM_ITUNES_ACCOUNT_TYPE, isom_set_itunes_metadata_integer }, + { ITUNES_METADATA_ITEM_ITUNES_ARTIST_ID, isom_set_itunes_metadata_integer }, + { ITUNES_METADATA_ITEM_ITUNES_COMPOSER_ID, isom_set_itunes_metadata_integer }, + { ITUNES_METADATA_ITEM_ITUNES_CATALOG_ID, isom_set_itunes_metadata_integer }, + { ITUNES_METADATA_ITEM_ITUNES_TV_GENRE_ID, isom_set_itunes_metadata_integer }, + { ITUNES_METADATA_ITEM_ITUNES_PLAYLIST_ID, isom_set_itunes_metadata_integer }, + { ITUNES_METADATA_ITEM_ITUNES_COUNTRY_CODE, isom_set_itunes_metadata_integer }, + { ITUNES_METADATA_ITEM_DISC_COMPILATION, isom_set_itunes_metadata_boolean }, + { ITUNES_METADATA_ITEM_HIGH_DEFINITION_VIDEO, isom_set_itunes_metadata_boolean }, + { ITUNES_METADATA_ITEM_PODCAST, isom_set_itunes_metadata_boolean }, + { ITUNES_METADATA_ITEM_GAPLESS_PLAYBACK, isom_set_itunes_metadata_boolean }, + { ITUNES_METADATA_ITEM_COVER_ART, isom_set_itunes_metadata_binary }, + { ITUNES_METADATA_ITEM_DISC_NUMBER, isom_set_itunes_metadata_binary }, + { ITUNES_METADATA_ITEM_TRACK_NUMBER, isom_set_itunes_metadata_binary }, + { 0, NULL } + }; + lsmash_file_t *file = root->file; + for( int i = 0; itunes_metadata_function_mapping[i].func_set_itunes_metadata; i++ ) + if( metadata.item == itunes_metadata_function_mapping[i].item ) + return itunes_metadata_function_mapping[i].func_set_itunes_metadata( file, metadata.item, metadata.value, metadata.meaning, metadata.name ); + if( metadata.item == ITUNES_METADATA_ITEM_CUSTOM ) + switch( metadata.type ) + { + case ITUNES_METADATA_TYPE_STRING : + return isom_set_itunes_metadata_string( file, metadata.item, metadata.value, metadata.meaning, metadata.name ); + case ITUNES_METADATA_TYPE_INTEGER : + return isom_set_itunes_metadata_integer( file, metadata.item, metadata.value, metadata.meaning, metadata.name ); + case ITUNES_METADATA_TYPE_BOOLEAN : + return isom_set_itunes_metadata_boolean( file, metadata.item, metadata.value, metadata.meaning, metadata.name ); + case ITUNES_METADATA_TYPE_BINARY : + return isom_set_itunes_metadata_binary( file, metadata.item, metadata.value, metadata.meaning, metadata.name ); + default : + break; + } + return LSMASH_ERR_FUNCTION_PARAM; +} + +static lsmash_itunes_metadata_type isom_get_itunes_metadata_type( lsmash_itunes_metadata_item item ) +{ + static const struct + { + lsmash_itunes_metadata_item item; + lsmash_itunes_metadata_type type; + } itunes_metadata_item_type_mapping[] = + { + { ITUNES_METADATA_ITEM_ALBUM_NAME, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_ARTIST, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_USER_COMMENT, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_RELEASE_DATE, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_ENCODED_BY, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_USER_GENRE, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_GROUPING, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_LYRICS, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_TITLE, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_TRACK_SUBTITLE, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_ENCODING_TOOL, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_COMPOSER, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_ALBUM_ARTIST, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_PODCAST_CATEGORY, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_COPYRIGHT, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_DESCRIPTION, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_GROUPING_DRAFT, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_PODCAST_KEYWORD, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_LONG_DESCRIPTION, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_PURCHASE_DATE, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_TV_EPISODE_ID, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_TV_NETWORK, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_TV_SHOW_NAME, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_ITUNES_PURCHASE_ACCOUNT_ID, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_ITUNES_SORT_ALBUM, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_ITUNES_SORT_ARTIST, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_ITUNES_SORT_ALBUM_ARTIST, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_ITUNES_SORT_COMPOSER, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_ITUNES_SORT_NAME, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_ITUNES_SORT_SHOW, ITUNES_METADATA_TYPE_STRING }, + { ITUNES_METADATA_ITEM_EPISODE_GLOBAL_ID, ITUNES_METADATA_TYPE_INTEGER }, + { ITUNES_METADATA_ITEM_PREDEFINED_GENRE, ITUNES_METADATA_TYPE_INTEGER }, + { ITUNES_METADATA_ITEM_CONTENT_RATING, ITUNES_METADATA_TYPE_INTEGER }, + { ITUNES_METADATA_ITEM_MEDIA_TYPE, ITUNES_METADATA_TYPE_INTEGER }, + { ITUNES_METADATA_ITEM_BEATS_PER_MINUTE, ITUNES_METADATA_TYPE_INTEGER }, + { ITUNES_METADATA_ITEM_TV_EPISODE, ITUNES_METADATA_TYPE_INTEGER }, + { ITUNES_METADATA_ITEM_TV_SEASON, ITUNES_METADATA_TYPE_INTEGER }, + { ITUNES_METADATA_ITEM_ITUNES_ACCOUNT_TYPE, ITUNES_METADATA_TYPE_INTEGER }, + { ITUNES_METADATA_ITEM_ITUNES_ARTIST_ID, ITUNES_METADATA_TYPE_INTEGER }, + { ITUNES_METADATA_ITEM_ITUNES_COMPOSER_ID, ITUNES_METADATA_TYPE_INTEGER }, + { ITUNES_METADATA_ITEM_ITUNES_CATALOG_ID, ITUNES_METADATA_TYPE_INTEGER }, + { ITUNES_METADATA_ITEM_ITUNES_TV_GENRE_ID, ITUNES_METADATA_TYPE_INTEGER }, + { ITUNES_METADATA_ITEM_ITUNES_PLAYLIST_ID, ITUNES_METADATA_TYPE_INTEGER }, + { ITUNES_METADATA_ITEM_ITUNES_COUNTRY_CODE, ITUNES_METADATA_TYPE_INTEGER }, + { ITUNES_METADATA_ITEM_DISC_COMPILATION, ITUNES_METADATA_TYPE_BOOLEAN }, + { ITUNES_METADATA_ITEM_HIGH_DEFINITION_VIDEO, ITUNES_METADATA_TYPE_BOOLEAN }, + { ITUNES_METADATA_ITEM_PODCAST, ITUNES_METADATA_TYPE_BOOLEAN }, + { ITUNES_METADATA_ITEM_GAPLESS_PLAYBACK, ITUNES_METADATA_TYPE_BOOLEAN }, + { ITUNES_METADATA_ITEM_COVER_ART, ITUNES_METADATA_TYPE_BINARY }, + { ITUNES_METADATA_ITEM_DISC_NUMBER, ITUNES_METADATA_TYPE_BINARY }, + { ITUNES_METADATA_ITEM_TRACK_NUMBER, ITUNES_METADATA_TYPE_BINARY }, + { 0, ITUNES_METADATA_TYPE_NONE } + }; + for( int i = 0; itunes_metadata_item_type_mapping[i].type != ITUNES_METADATA_TYPE_NONE; i++ ) + if( item == itunes_metadata_item_type_mapping[i].item ) + return itunes_metadata_item_type_mapping[i].type; + return ITUNES_METADATA_TYPE_NONE; +} + +int lsmash_get_itunes_metadata( lsmash_root_t *root, uint32_t metadata_number, lsmash_itunes_metadata_t *metadata ) +{ + if( isom_check_initializer_present( root ) < 0 || !metadata ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_file_t *file = root->file->initializer; + if( !file->moov->udta->meta->ilst ) + return LSMASH_ERR_NAMELESS; + isom_ilst_t *ilst = file->moov->udta->meta->ilst; + isom_metaitem_t *metaitem = (isom_metaitem_t *)lsmash_list_get_entry_data( &ilst->metaitem_list, metadata_number ); + if( LSMASH_IS_NON_EXISTING_BOX( metaitem ) + || LSMASH_IS_NON_EXISTING_BOX( metaitem->data ) + || metaitem->data->value == NULL + || metaitem->data->value_length == 0 ) + return LSMASH_ERR_NAMELESS; + metadata->item = metaitem->type.fourcc; + metadata->type = isom_get_itunes_metadata_type( metadata->item ); + /* Get 'meaning'. */ + int err = LSMASH_ERR_MEMORY_ALLOC; + isom_mean_t *mean = metaitem->mean; + if( LSMASH_IS_NON_EXISTING_BOX( mean ) ) + { + uint8_t *temp = lsmash_malloc( mean->meaning_string_length + 1 ); + if( !temp ) + goto fail; + memcpy( temp, mean->meaning_string, mean->meaning_string_length ); + temp[ mean->meaning_string_length ] = 0; + metadata->meaning = (char *)temp; + } + else + metadata->meaning = NULL; + /* Get 'name'. */ + isom_name_t *name = metaitem->name; + if( LSMASH_IS_NON_EXISTING_BOX( name ) ) + { + uint8_t *temp = lsmash_malloc( name->name_length + 1 ); + if( !temp ) + goto fail; + memcpy( temp, name->name, name->name_length ); + temp[ name->name_length ] = 0; + metadata->name = (char *)temp; + } + else + metadata->name = NULL; + /* Get 'value'. */ + isom_data_t *data = metaitem->data; + switch( metadata->type ) + { + case ITUNES_METADATA_TYPE_STRING : + { + uint8_t *temp = lsmash_malloc( data->value_length + 1 ); + if( !temp ) + goto fail; + memcpy( temp, data->value, data->value_length ); + temp[ data->value_length ] = 0; + metadata->value.string = (char *)temp; + break; + } + case ITUNES_METADATA_TYPE_INTEGER : + if( data->value_length > 8 ) + { + err = LSMASH_ERR_INVALID_DATA; + goto fail; + } + metadata->value.integer = 0; + for( uint32_t i = 0; i < data->value_length; i++ ) + { + int shift = (data->value_length - i - 1) * 8; + metadata->value.integer |= (uint64_t)data->value[i] << shift; + } + break; + case ITUNES_METADATA_TYPE_BOOLEAN : + metadata->value.boolean = !!data->value[0]; + break; + default : + metadata->type = ITUNES_METADATA_TYPE_BINARY; + metadata->value.binary.subtype = data->type_code; + metadata->value.binary.size = data->value_length; + metadata->value.binary.data = lsmash_memdup( data->value, data->value_length ); + if( !metadata->value.binary.data ) + goto fail; + break; + } + return 0; +fail: + lsmash_freep( &metadata->meaning ); + lsmash_freep( &metadata->name ); + return err; +} + +uint32_t lsmash_count_itunes_metadata( lsmash_root_t *root ) +{ + if( isom_check_initializer_present( root ) < 0 + || LSMASH_IS_NON_EXISTING_BOX( root->file->initializer->moov->udta->meta->ilst ) ) + return 0; + return root->file->initializer->moov->udta->meta->ilst->metaitem_list.entry_count; +} + +void lsmash_cleanup_itunes_metadata( lsmash_itunes_metadata_t *metadata ) +{ + if( !metadata ) + return; + lsmash_freep( &metadata->meaning ); + lsmash_freep( &metadata->name ); + if( metadata->type == ITUNES_METADATA_TYPE_STRING ) + lsmash_freep( &metadata->value.string ); + else if( metadata->type == ITUNES_METADATA_TYPE_BINARY ) + lsmash_freep( &metadata->value.binary.data ); +} diff --git a/3rdparty/l-smash/core/print.c b/3rdparty/l-smash/core/print.c new file mode 100644 index 000000000..a34d1bd0a --- /dev/null +++ b/3rdparty/l-smash/core/print.c @@ -0,0 +1,2625 @@ +/***************************************************************************** + * print.c + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include +#include +#include +#include /* for isom_iprintf */ + +#include "box.h" + + +typedef int (*isom_print_box_t)( FILE *, lsmash_file_t *, isom_box_t *, int ); + +typedef struct +{ + int level; + isom_box_t *box; + isom_print_box_t func; +} isom_print_entry_t; + +static void isom_ifprintf_duration( FILE *fp, int indent, char *field_name, uint64_t duration, uint32_t timescale ) +{ + if( !timescale ) + { + lsmash_ifprintf( fp, indent, "duration = %"PRIu64"\n", duration ); + return; + } + int dur = duration / timescale; + int hour = dur / 3600; + int min = (dur / 60) % 60; + int sec = dur % 60; + int ms = ((double)duration / timescale - (hour * 3600 + min * 60 + sec)) * 1e3 + 0.5; + static char str[32]; + sprintf( str, "%02d:%02d:%02d.%03d", hour, min, sec, ms ); + lsmash_ifprintf( fp, indent, "%s = %"PRIu64" (%s)\n", field_name, duration, str ); +} + +static char *isom_mp4time2utc( uint64_t mp4time ) +{ + int year_offset = mp4time / 31536000; + int leap_years = year_offset / 4 + ((mp4time / 86400) > 366); /* 1904 itself is leap year */ + int day = (mp4time / 86400) - (year_offset * 365) - leap_years + 1; + while( day < 1 ) + { + --year_offset; + leap_years = year_offset / 4 + ((mp4time / 86400) > 366); + day = (mp4time / 86400) - (year_offset * 365) - leap_years + 1; + } + int year = 1904 + year_offset; + int is_leap = (!(year % 4) && (year % 100)) || !(year % 400); + static const int month_days[13] = { 29, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + int month; + for( month = 1; month <= 12; month++ ) + { + int i = (month == 2 && is_leap) ? 0 : month; + if( day <= month_days[i] ) + break; + day -= month_days[i]; + } + int hour = (mp4time / 3600) % 24; + int min = (mp4time / 60) % 60; + int sec = mp4time % 60; + static char utc[64]; + sprintf( utc, "UTC %d/%02d/%02d, %02d:%02d:%02d\n", year, month, day, hour, min, sec ); + return utc; +} + +static void isom_ifprintf_matrix( FILE *fp, int indent, int32_t *matrix ) +{ + lsmash_ifprintf( fp, indent, "| a, b, u | | %f, %f, %f |\n", lsmash_fixed2double( matrix[0], 16 ), + lsmash_fixed2double( matrix[1], 16 ), + lsmash_fixed2double( matrix[2], 30 ) ); + lsmash_ifprintf( fp, indent, "| c, d, v | = | %f, %f, %f |\n", lsmash_fixed2double( matrix[3], 16 ), + lsmash_fixed2double( matrix[4], 16 ), + lsmash_fixed2double( matrix[5], 30 ) ); + lsmash_ifprintf( fp, indent, "| x, y, w | | %f, %f, %f |\n", lsmash_fixed2double( matrix[6], 16 ), + lsmash_fixed2double( matrix[7], 16 ), + lsmash_fixed2double( matrix[8], 30 ) ); +} + +static void isom_ifprintf_rgb_color( FILE *fp, int indent, uint16_t *color ) +{ + lsmash_ifprintf( fp, indent, "{ R, G, B } = { %"PRIu16", %"PRIu16", %"PRIu16" }\n", color[0], color[1], color[2] ); +} + +static void isom_ifprintf_rgba_color( FILE *fp, int indent, uint8_t *color ) +{ + lsmash_ifprintf( fp, indent, "{ R, G, B, A } = { %"PRIu8", %"PRIu8", %"PRIu8", %"PRIu8" }\n", color[0], color[1], color[2], color[3] ); +} + +static char *isom_unpack_iso_language( uint16_t language ) +{ + static char unpacked[4]; + unpacked[0] = ((language >> 10) & 0x1f) + 0x60; + unpacked[1] = ((language >> 5) & 0x1f) + 0x60; + unpacked[2] = ( language & 0x1f) + 0x60; + unpacked[3] = 0; + return unpacked; +} + +static void isom_ifprintf_sample_description_common_reserved( FILE *fp, int indent, uint8_t *reserved ) +{ + uint64_t temp = ((uint64_t)reserved[0] << 40) + | ((uint64_t)reserved[1] << 32) + | ((uint64_t)reserved[2] << 24) + | ((uint64_t)reserved[3] << 16) + | ((uint64_t)reserved[4] << 8) + | (uint64_t)reserved[5]; + lsmash_ifprintf( fp, indent, "reserved = 0x%012"PRIx64"\n", temp ); +} + +static void isom_ifprintf_sample_flags( FILE *fp, int indent, char *field_name, isom_sample_flags_t *flags ) +{ + uint32_t temp = (flags->reserved << 28) + | (flags->is_leading << 26) + | (flags->sample_depends_on << 24) + | (flags->sample_is_depended_on << 22) + | (flags->sample_has_redundancy << 20) + | (flags->sample_padding_value << 17) + | (flags->sample_is_non_sync_sample << 16) + | flags->sample_degradation_priority; + lsmash_ifprintf( fp, indent++, "%s = 0x%08"PRIx32"\n", field_name, temp ); + if ( flags->is_leading == ISOM_SAMPLE_IS_UNDECODABLE_LEADING ) lsmash_ifprintf( fp, indent, "undecodable leading\n" ); + else if( flags->is_leading == ISOM_SAMPLE_IS_NOT_LEADING ) lsmash_ifprintf( fp, indent, "non-leading\n" ); + else if( flags->is_leading == ISOM_SAMPLE_IS_DECODABLE_LEADING ) lsmash_ifprintf( fp, indent, "decodable leading\n" ); + if ( flags->sample_depends_on == ISOM_SAMPLE_IS_INDEPENDENT ) lsmash_ifprintf( fp, indent, "independent\n" ); + else if( flags->sample_depends_on == ISOM_SAMPLE_IS_NOT_INDEPENDENT ) lsmash_ifprintf( fp, indent, "dependent\n" ); + if ( flags->sample_is_depended_on == ISOM_SAMPLE_IS_NOT_DISPOSABLE ) lsmash_ifprintf( fp, indent, "non-disposable\n" ); + else if( flags->sample_is_depended_on == ISOM_SAMPLE_IS_DISPOSABLE ) lsmash_ifprintf( fp, indent, "disposable\n" ); + if ( flags->sample_has_redundancy == ISOM_SAMPLE_HAS_REDUNDANCY ) lsmash_ifprintf( fp, indent, "redundant\n" ); + else if( flags->sample_has_redundancy == ISOM_SAMPLE_HAS_NO_REDUNDANCY ) lsmash_ifprintf( fp, indent, "non-redundant\n" ); + if( flags->sample_padding_value ) + lsmash_ifprintf( fp, indent, "padding_bits = %"PRIu8"\n", flags->sample_padding_value ); + lsmash_ifprintf( fp, indent, flags->sample_is_non_sync_sample ? "non-sync sample\n" : "sync sample\n" ); + lsmash_ifprintf( fp, indent, "degradation_priority = %"PRIu16"\n", flags->sample_degradation_priority ); +} + +static inline int isom_print_simple( FILE *fp, isom_box_t *box, int level, char *name ) +{ + int indent = level; + if( box->type.fourcc != ISOM_BOX_TYPE_UUID.fourcc ) + { + lsmash_ifprintf( fp, indent++, "[%s: %s]\n", isom_4cc2str( box->type.fourcc ), name ); + lsmash_ifprintf( fp, indent, "position = %"PRIu64"\n", box->pos ); + lsmash_ifprintf( fp, indent, "size = %"PRIu64"\n", box->size ); + } + else + { + lsmash_ifprintf( fp, indent++, "[uuid: UUID Box]\n" ); + lsmash_ifprintf( fp, indent, "position = %"PRIu64"\n", box->pos ); + lsmash_ifprintf( fp, indent, "size = %"PRIu64"\n", box->size ); + lsmash_ifprintf( fp, indent++, "usertype\n" ); + if( isom_is_printable_4cc( box->type.user.fourcc ) ) + lsmash_ifprintf( fp, indent, "type = %s\n", isom_4cc2str( box->type.user.fourcc ) ); + lsmash_ifprintf( fp, indent, "name = %s\n", name ); + lsmash_ifprintf( fp, indent, "uuid = 0x%08"PRIx32"-%04"PRIx16"-%04"PRIx16"-%04"PRIx16"-%04"PRIx16"0x%08"PRIx32"\n", + box->type.user.fourcc, + (box->type.user.id[0] << 8) | box->type.user.id[1], (box->type.user.id[2] << 8) | box->type.user.id[3], + (box->type.user.id[4] << 8) | box->type.user.id[5], (box->type.user.id[6] << 8) | box->type.user.id[7], + (box->type.user.id[8] << 24) | (box->type.user.id[9] << 16) | (box->type.user.id[10] << 8) | box->type.user.id[11] ); + } + return 0; +} + +static void isom_print_basebox_common( FILE *fp, int indent, isom_box_t *box, char *name ) +{ + isom_print_simple( fp, box, indent, name ); +} + +static void isom_print_fullbox_common( FILE *fp, int indent, isom_box_t *box, char *name ) +{ + isom_print_simple( fp, box, indent++, name ); + lsmash_ifprintf( fp, indent, "version = %"PRIu8"\n", box->version ); + lsmash_ifprintf( fp, indent, "flags = 0x%06"PRIx32"\n", box->flags & 0x00ffffff ); +} + +static void isom_print_box_common( FILE *fp, int indent, isom_box_t *box, char *name ) +{ + isom_box_t *parent = box->parent; + if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STSD ) ) + { + isom_print_basebox_common( fp, indent, box, name ); + return; + } + if( isom_is_fullbox( box ) ) + isom_print_fullbox_common( fp, indent, box, name ); + else + isom_print_basebox_common( fp, indent, box, name ); +} + +static int isom_print_unknown( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + int indent = level; + if( box->type.fourcc != ISOM_BOX_TYPE_UUID.fourcc ) + { + lsmash_ifprintf( fp, indent++, "[%s]\n", isom_4cc2str( box->type.fourcc ) ); + lsmash_ifprintf( fp, indent, "position = %"PRIu64"\n", box->pos ); + lsmash_ifprintf( fp, indent, "size = %"PRIu64"\n", box->size ); + } + else + { + lsmash_ifprintf( fp, indent++, "[uuid: UUID Box]\n" ); + lsmash_ifprintf( fp, indent, "position = %"PRIu64"\n", box->pos ); + lsmash_ifprintf( fp, indent, "size = %"PRIu64"\n", box->size ); + lsmash_ifprintf( fp, indent++, "usertype\n" ); + if( isom_is_printable_4cc( box->type.user.fourcc ) ) + lsmash_ifprintf( fp, indent, "type = %s\n", isom_4cc2str( box->type.user.fourcc ) ); + lsmash_ifprintf( fp, indent, "uuid = 0x%08"PRIx32"-%04"PRIx16"-%04"PRIx16"-%04"PRIx16"-%04"PRIx16"%08"PRIx32"\n", + box->type.user.fourcc, + (box->type.user.id[0] << 8) | box->type.user.id[1], (box->type.user.id[2] << 8) | box->type.user.id[3], + (box->type.user.id[4] << 8) | box->type.user.id[5], (box->type.user.id[6] << 8) | box->type.user.id[7], + (box->type.user.id[8] << 24) | (box->type.user.id[9] << 16) | (box->type.user.id[10] << 8) | box->type.user.id[11] ); + } + return 0; +} + +static void isom_print_brand_description( FILE *fp, lsmash_brand_type brand ) +{ + if( brand == 0 ) + return; + static const struct + { + lsmash_brand_type brand; + char *description; + } brand_description_table[] = + { + { ISOM_BRAND_TYPE_3G2A, "3GPP2" }, + { ISOM_BRAND_TYPE_3GE6, "3GPP Release 6 Extended Presentation Profile" }, + { ISOM_BRAND_TYPE_3GE9, "3GPP Release 9 Extended Presentation Profile" }, + { ISOM_BRAND_TYPE_3GF9, "3GPP Release 9 File-delivery Server Profile" }, + { ISOM_BRAND_TYPE_3GG6, "3GPP Release 6 General Profile" }, + { ISOM_BRAND_TYPE_3GG9, "3GPP Release 9 General Profile" }, + { ISOM_BRAND_TYPE_3GH9, "3GPP Release 9 Adaptive Streaming Profile" }, + { ISOM_BRAND_TYPE_3GM9, "3GPP Release 9 Media Segment Profile" }, + { ISOM_BRAND_TYPE_3GP4, "3GPP Release 4" }, + { ISOM_BRAND_TYPE_3GP5, "3GPP Release 5" }, + { ISOM_BRAND_TYPE_3GP6, "3GPP Release 6 Basic Profile" }, + { ISOM_BRAND_TYPE_3GP7, "3GPP Release 7" }, + { ISOM_BRAND_TYPE_3GP8, "3GPP Release 8" }, + { ISOM_BRAND_TYPE_3GP9, "3GPP Release 9 Basic Profile" }, + { ISOM_BRAND_TYPE_3GR6, "3GPP Release 6 Progressive Download Profile" }, + { ISOM_BRAND_TYPE_3GR9, "3GPP Release 9 Progressive Download Profile" }, + { ISOM_BRAND_TYPE_3GS6, "3GPP Release 6 Streaming Server Profile" }, + { ISOM_BRAND_TYPE_3GS9, "3GPP Release 9 Streaming Server Profile" }, + { ISOM_BRAND_TYPE_3GT9, "3GPP Release 9 Media Stream Recording Profile" }, + { ISOM_BRAND_TYPE_ARRI, "ARRI Digital Camera" }, + { ISOM_BRAND_TYPE_CAEP, "Canon Digital Camera" }, + { ISOM_BRAND_TYPE_CDES, "Convergent Designs" }, + { ISOM_BRAND_TYPE_LCAG, "Leica digital camera" }, + { ISOM_BRAND_TYPE_M4A , "iTunes MPEG-4 audio protected or not" }, + { ISOM_BRAND_TYPE_M4B , "iTunes AudioBook protected or not" }, + { ISOM_BRAND_TYPE_M4P , "MPEG-4 protected audio" }, + { ISOM_BRAND_TYPE_M4V , "MPEG-4 protected audio+video" }, + { ISOM_BRAND_TYPE_MFSM, "Media File for Samsung video Metadata" }, + { ISOM_BRAND_TYPE_MPPI, "Photo Player Multimedia Application Format" }, + { ISOM_BRAND_TYPE_ROSS, "Ross Video" }, + { ISOM_BRAND_TYPE_AVC1, "Advanced Video Coding extensions" }, + { ISOM_BRAND_TYPE_BBXM, "Blinkbox Master File" }, + { ISOM_BRAND_TYPE_CAQV, "Casio Digital Camera" }, + { ISOM_BRAND_TYPE_CCFF, "Common container file format" }, + { ISOM_BRAND_TYPE_DA0A, "DMB AF" }, + { ISOM_BRAND_TYPE_DA0B, "DMB AF" }, + { ISOM_BRAND_TYPE_DA1A, "DMB AF" }, + { ISOM_BRAND_TYPE_DA1B, "DMB AF" }, + { ISOM_BRAND_TYPE_DA2A, "DMB AF" }, + { ISOM_BRAND_TYPE_DA2B, "DMB AF" }, + { ISOM_BRAND_TYPE_DA3A, "DMB AF" }, + { ISOM_BRAND_TYPE_DA3B, "DMB AF" }, + { ISOM_BRAND_TYPE_DASH, "Indexed self-initializing Media Segment" }, + { ISOM_BRAND_TYPE_DBY1, "MP4 files with Dolby content" }, + { ISOM_BRAND_TYPE_DMB1, "DMB AF" }, + { ISOM_BRAND_TYPE_DSMS, "Self-initializing Media Segment" }, + { ISOM_BRAND_TYPE_DV1A, "DMB AF" }, + { ISOM_BRAND_TYPE_DV1B, "DMB AF" }, + { ISOM_BRAND_TYPE_DV2A, "DMB AF" }, + { ISOM_BRAND_TYPE_DV2B, "DMB AF" }, + { ISOM_BRAND_TYPE_DV3A, "DMB AF" }, + { ISOM_BRAND_TYPE_DV3B, "DMB AF" }, + { ISOM_BRAND_TYPE_DVR1, "DVB RTP" }, + { ISOM_BRAND_TYPE_DVT1, "DVB Transport Stream" }, + { ISOM_BRAND_TYPE_IFRM, "Apple iFrame" }, + { ISOM_BRAND_TYPE_ISC2, "Files encrypted according to ISMACryp 2.0" }, + { ISOM_BRAND_TYPE_ISO2, "ISO Base Media file format version 2" }, + { ISOM_BRAND_TYPE_ISO3, "ISO Base Media file format version 3" }, + { ISOM_BRAND_TYPE_ISO4, "ISO Base Media file format version 4" }, + { ISOM_BRAND_TYPE_ISO5, "ISO Base Media file format version 5" }, + { ISOM_BRAND_TYPE_ISO6, "ISO Base Media file format version 6" }, + { ISOM_BRAND_TYPE_ISO7, "ISO Base Media file format version 7" }, + { ISOM_BRAND_TYPE_ISOM, "ISO Base Media file format version 1" }, + { ISOM_BRAND_TYPE_JPSI, "The JPSearch data interchange format" }, + { ISOM_BRAND_TYPE_LMSG, "last Media Segment indicator" }, + { ISOM_BRAND_TYPE_MJ2S, "Motion JPEG 2000 simple profile" }, + { ISOM_BRAND_TYPE_MJP2, "Motion JPEG 2000, general profile" }, + { ISOM_BRAND_TYPE_MP21, "MPEG-21" }, + { ISOM_BRAND_TYPE_MP41, "MP4 version 1" }, + { ISOM_BRAND_TYPE_MP42, "MP4 version 2" }, + { ISOM_BRAND_TYPE_MP71, "MPEG-7 file-level metadata" }, + { ISOM_BRAND_TYPE_MSDH, "Media Segment" }, + { ISOM_BRAND_TYPE_MSIX, "Indexed Media Segment" }, + { ISOM_BRAND_TYPE_NIKO, "Nikon Digital Camera" }, + { ISOM_BRAND_TYPE_ODCF, "OMA DCF" }, + { ISOM_BRAND_TYPE_OPF2, "OMA PDCF" }, + { ISOM_BRAND_TYPE_OPX2, "OMA Adapted PDCF" }, + { ISOM_BRAND_TYPE_PANA, "Panasonic Digital Camera" }, + { ISOM_BRAND_TYPE_PIFF, "Protected Interoperable File Format" }, + { ISOM_BRAND_TYPE_PNVI, "Panasonic Video Intercom" }, + { ISOM_BRAND_TYPE_QT , "QuickTime file format" }, + { ISOM_BRAND_TYPE_RISX, "Representation Index Segment" }, + { ISOM_BRAND_TYPE_SDV , "SD Video" }, + { ISOM_BRAND_TYPE_SIMS, "Sub-Indexed Media Segment" }, + { ISOM_BRAND_TYPE_SISX, "Single Index Segment" }, + { ISOM_BRAND_TYPE_SSSS, "Subsegment Index Segment" }, + { 0, NULL } + }; + for( int i = 0; brand_description_table[i].description; i++ ) + if( brand == brand_description_table[i].brand ) + { + fprintf( fp, " : %s\n", brand_description_table[i].description ); + return; + } + fprintf( fp, "\n" ); +} + +static void isom_print_file_type +( + FILE *fp, + int indent, + uint32_t major_brand, + uint32_t minor_version, + uint32_t brand_count, + uint32_t *compatible_brands +) +{ + lsmash_ifprintf( fp, indent, "major_brand = %s", isom_4cc2str( major_brand ) ); + isom_print_brand_description( fp, major_brand ); + lsmash_ifprintf( fp, indent, "minor_version = %"PRIu32"\n", minor_version ); + lsmash_ifprintf( fp, indent++, "compatible_brands\n" ); + for( uint32_t i = 0; i < brand_count; i++ ) + { + if( compatible_brands[i] ) + { + lsmash_ifprintf( fp, indent, "brand[%"PRIu32"] = %s", i, isom_4cc2str( compatible_brands[i] ) ); + isom_print_brand_description( fp, compatible_brands[i] ); + } + else + lsmash_ifprintf( fp, indent, "brand[%"PRIu32"] = (void)\n", i ); + } +} + +static int isom_print_ftyp( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_ftyp_t *ftyp = (isom_ftyp_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "File Type Box" ); + isom_print_file_type( fp, indent, ftyp->major_brand, ftyp->minor_version, ftyp->brand_count, ftyp->compatible_brands ); + return 0; +} + +static int isom_print_styp( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + /* Print 'valid' if this box is the first box in a file. */ + int valid; + if( file->print + && file->print->head + && file->print->head->data ) + valid = (box == ((isom_print_entry_t *)file->print->head->data)->box); + else + valid = 0; + char *name = valid ? "Segment Type Box (valid)" : "Segment Type Box"; + isom_styp_t *styp = (isom_styp_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, name ); + isom_print_file_type( fp, indent, styp->major_brand, styp->minor_version, styp->brand_count, styp->compatible_brands ); + return 0; +} + +static int isom_print_sidx( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + if( !((isom_sidx_t *)box)->list ) + return -1; + isom_sidx_t *sidx = (isom_sidx_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Segment Index Box" ); + lsmash_ifprintf( fp, indent, "reference_ID = %"PRIu32"\n", sidx->reference_ID ); + lsmash_ifprintf( fp, indent, "timescale = %"PRIu32"\n", sidx->timescale ); + lsmash_ifprintf( fp, indent, "earliest_presentation_time = %"PRIu64"\n", sidx->earliest_presentation_time ); + lsmash_ifprintf( fp, indent, "first_offset = %"PRIu64"\n", sidx->first_offset ); + lsmash_ifprintf( fp, indent, "reserved = %"PRIu16"\n", sidx->reserved ); + lsmash_ifprintf( fp, indent, "reference_count = %"PRIu16"\n", sidx->reference_count ); + uint32_t i = 0; + for( lsmash_entry_t *entry = sidx->list->head; entry; entry = entry->next ) + { + isom_sidx_referenced_item_t *data = (isom_sidx_referenced_item_t *)entry->data; + lsmash_ifprintf( fp, indent++, "entry[%"PRIu32"]\n", i++ ); + lsmash_ifprintf( fp, indent, "reference_type = %"PRIu8" (%s)\n", data->reference_type, data->reference_type ? "index" : "media" ); + lsmash_ifprintf( fp, indent, "reference_size = %"PRIu32"\n", data->reference_size ); + lsmash_ifprintf( fp, indent, "subsegment_duration = %"PRIu32"\n", data->subsegment_duration ); + lsmash_ifprintf( fp, indent, "starts_with_SAP = %"PRIu8"%s\n", data->starts_with_SAP, data->starts_with_SAP ? " (yes)" : "" ); + lsmash_ifprintf( fp, indent, "SAP_type = %"PRIu8"%s\n", data->SAP_type, data->SAP_type == 0 ? " (unknown)" : "" ); + lsmash_ifprintf( fp, indent--, "SAP_delta_time = %"PRIu32"\n", data->SAP_delta_time ); + } + return 0; +} + +static int isom_print_moov( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + return isom_print_simple( fp, box, level, "Movie Box" ); +} + +static int isom_print_mvhd( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_mvhd_t *mvhd = (isom_mvhd_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Movie Header Box" ); + lsmash_ifprintf( fp, indent, "creation_time = %s", isom_mp4time2utc( mvhd->creation_time ) ); + lsmash_ifprintf( fp, indent, "modification_time = %s", isom_mp4time2utc( mvhd->modification_time ) ); + lsmash_ifprintf( fp, indent, "timescale = %"PRIu32"\n", mvhd->timescale ); + isom_ifprintf_duration( fp, indent, "duration", mvhd->duration, mvhd->timescale ); + lsmash_ifprintf( fp, indent, "rate = %f\n", lsmash_fixed2double( mvhd->rate, 16 ) ); + lsmash_ifprintf( fp, indent, "volume = %f\n", lsmash_fixed2double( mvhd->volume, 8 ) ); + lsmash_ifprintf( fp, indent, "reserved = 0x%04"PRIx16"\n", mvhd->reserved ); + if( file->qt_compatible ) + { + lsmash_ifprintf( fp, indent, "preferredLong1 = 0x%08"PRIx32"\n", mvhd->preferredLong[0] ); + lsmash_ifprintf( fp, indent, "preferredLong2 = 0x%08"PRIx32"\n", mvhd->preferredLong[1] ); + lsmash_ifprintf( fp, indent, "transformation matrix\n" ); + isom_ifprintf_matrix( fp, indent + 1, mvhd->matrix ); + lsmash_ifprintf( fp, indent, "previewTime = %"PRId32"\n", mvhd->previewTime ); + lsmash_ifprintf( fp, indent, "previewDuration = %"PRId32"\n", mvhd->previewDuration ); + lsmash_ifprintf( fp, indent, "posterTime = %"PRId32"\n", mvhd->posterTime ); + lsmash_ifprintf( fp, indent, "selectionTime = %"PRId32"\n", mvhd->selectionTime ); + lsmash_ifprintf( fp, indent, "selectionDuration = %"PRId32"\n", mvhd->selectionDuration ); + lsmash_ifprintf( fp, indent, "currentTime = %"PRId32"\n", mvhd->currentTime ); + } + else + { + lsmash_ifprintf( fp, indent, "reserved = 0x%08"PRIx32"\n", mvhd->preferredLong[0] ); + lsmash_ifprintf( fp, indent, "reserved = 0x%08"PRIx32"\n", mvhd->preferredLong[1] ); + lsmash_ifprintf( fp, indent, "transformation matrix\n" ); + isom_ifprintf_matrix( fp, indent + 1, mvhd->matrix ); + lsmash_ifprintf( fp, indent, "pre_defined = 0x%08"PRIx32"\n", mvhd->previewTime ); + lsmash_ifprintf( fp, indent, "pre_defined = 0x%08"PRIx32"\n", mvhd->previewDuration ); + lsmash_ifprintf( fp, indent, "pre_defined = 0x%08"PRIx32"\n", mvhd->posterTime ); + lsmash_ifprintf( fp, indent, "pre_defined = 0x%08"PRIx32"\n", mvhd->selectionTime ); + lsmash_ifprintf( fp, indent, "pre_defined = 0x%08"PRIx32"\n", mvhd->selectionDuration ); + lsmash_ifprintf( fp, indent, "pre_defined = 0x%08"PRIx32"\n", mvhd->currentTime ); + } + lsmash_ifprintf( fp, indent, "next_track_ID = %"PRIu32"\n", mvhd->next_track_ID ); + return 0; +} + +static void isom_pring_qt_color_table( FILE *fp, int indent, isom_qt_color_table_t *color_table ) +{ + isom_qt_color_array_t *array = color_table->array; + if( !array ) + return; + lsmash_ifprintf( fp, indent, "ctSeed = %"PRIu32"\n", color_table->seed ); + lsmash_ifprintf( fp, indent, "ctFlags = 0x%04"PRIx16"\n", color_table->flags ); + lsmash_ifprintf( fp, indent, "ctSize = %"PRIu16"\n", color_table->size ); + lsmash_ifprintf( fp, indent++, "ctTable\n" ); + for( uint16_t i = 0; i <= color_table->size; i++ ) + lsmash_ifprintf( fp, indent, + "color[%"PRIu16"] = { 0x%04"PRIx16", 0x%04"PRIx16", 0x%04"PRIx16", 0x%04"PRIx16" }\n", + i, array[i].value, array[i].r, array[i].g, array[i].b ); +} + +static int isom_print_ctab( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_ctab_t *ctab = (isom_ctab_t *)box; + int indent = level; + isom_print_box_common( fp, indent, box, "Color Table Box" ); + isom_pring_qt_color_table( fp, indent + 1, &ctab->color_table ); + return 0; +} + +static int isom_print_iods( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + extern void mp4sys_print_descriptor( FILE *, void *, int ); + isom_iods_t *iods = (isom_iods_t *)box; + int indent = level; + isom_print_box_common( fp, indent, box, "Object Descriptor Box" ); + mp4sys_print_descriptor( fp, iods->OD, indent + 1 ); + return 0; +} + +static int isom_print_trak( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + return isom_print_simple( fp, box, level, "Track Box" ); +} + +static int isom_print_tkhd( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_tkhd_t *tkhd = (isom_tkhd_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Track Header Box" ); + ++indent; + if( tkhd->flags & ISOM_TRACK_ENABLED ) + lsmash_ifprintf( fp, indent, "Track enabled\n" ); + else + lsmash_ifprintf( fp, indent, "Track disabled\n" ); + if( tkhd->flags & ISOM_TRACK_IN_MOVIE ) + lsmash_ifprintf( fp, indent, "Track in movie\n" ); + if( tkhd->flags & ISOM_TRACK_IN_PREVIEW ) + lsmash_ifprintf( fp, indent, "Track in preview\n" ); + if( file->qt_compatible && (tkhd->flags & QT_TRACK_IN_POSTER) ) + lsmash_ifprintf( fp, indent, "Track in poster\n" ); + lsmash_ifprintf( fp, --indent, "creation_time = %s", isom_mp4time2utc( tkhd->creation_time ) ); + lsmash_ifprintf( fp, indent, "modification_time = %s", isom_mp4time2utc( tkhd->modification_time ) ); + lsmash_ifprintf( fp, indent, "track_ID = %"PRIu32"\n", tkhd->track_ID ); + lsmash_ifprintf( fp, indent, "reserved = 0x%08"PRIx32"\n", tkhd->reserved1 ); + if( file->moov && file->moov->mvhd ) + isom_ifprintf_duration( fp, indent, "duration", tkhd->duration, file->moov->mvhd->timescale ); + else + isom_ifprintf_duration( fp, indent, "duration", tkhd->duration, 0 ); + lsmash_ifprintf( fp, indent, "reserved = 0x%08"PRIx32"\n", tkhd->reserved2[0] ); + lsmash_ifprintf( fp, indent, "reserved = 0x%08"PRIx32"\n", tkhd->reserved2[1] ); + lsmash_ifprintf( fp, indent, "layer = %"PRId16"\n", tkhd->layer ); + lsmash_ifprintf( fp, indent, "alternate_group = %"PRId16"\n", tkhd->alternate_group ); + lsmash_ifprintf( fp, indent, "volume = %f\n", lsmash_fixed2double( tkhd->volume, 8 ) ); + lsmash_ifprintf( fp, indent, "reserved = 0x%04"PRIx16"\n", tkhd->reserved3 ); + lsmash_ifprintf( fp, indent, "transformation matrix\n" ); + isom_ifprintf_matrix( fp, indent + 1, tkhd->matrix ); + lsmash_ifprintf( fp, indent, "width = %f\n", lsmash_fixed2double( tkhd->width, 16 ) ); + lsmash_ifprintf( fp, indent, "height = %f\n", lsmash_fixed2double( tkhd->height, 16 ) ); + return 0; +} + +static int isom_print_tapt( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + return isom_print_simple( fp, box, level, "Track Aperture Mode Dimensions Box" ); +} + +static int isom_print_clef( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_clef_t *clef = (isom_clef_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Track Clean Aperture Dimensions Box" ); + lsmash_ifprintf( fp, indent, "width = %f\n", lsmash_fixed2double( clef->width, 16 ) ); + lsmash_ifprintf( fp, indent, "height = %f\n", lsmash_fixed2double( clef->height, 16 ) ); + return 0; +} + +static int isom_print_prof( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_prof_t *prof = (isom_prof_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Track Production Aperture Dimensions Box" ); + lsmash_ifprintf( fp, indent, "width = %f\n", lsmash_fixed2double( prof->width, 16 ) ); + lsmash_ifprintf( fp, indent, "height = %f\n", lsmash_fixed2double( prof->height, 16 ) ); + return 0; +} + +static int isom_print_enof( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_enof_t *enof = (isom_enof_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Track Encoded Pixels Dimensions Box" ); + lsmash_ifprintf( fp, indent, "width = %f\n", lsmash_fixed2double( enof->width, 16 ) ); + lsmash_ifprintf( fp, indent, "height = %f\n", lsmash_fixed2double( enof->height, 16 ) ); + return 0; +} + +static int isom_print_edts( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + return isom_print_simple( fp, box, level, "Edit Box" ); +} + +static int isom_print_elst( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_elst_t *elst = (isom_elst_t *)box; + int indent = level; + uint32_t i = 0; + isom_print_box_common( fp, indent++, box, "Edit List Box" ); + lsmash_ifprintf( fp, indent, "entry_count = %"PRIu32"\n", elst->list->entry_count ); + for( lsmash_entry_t *entry = elst->list->head; entry; entry = entry->next ) + { + isom_elst_entry_t *data = (isom_elst_entry_t *)entry->data; + lsmash_ifprintf( fp, indent++, "entry[%"PRIu32"]\n", i++ ); + lsmash_ifprintf( fp, indent, "segment_duration = %"PRIu64"\n", data->segment_duration ); + lsmash_ifprintf( fp, indent, "media_time = %"PRId64"\n", data->media_time ); + lsmash_ifprintf( fp, indent--, "media_rate = %f\n", lsmash_fixed2double( data->media_rate, 16 ) ); + } + return 0; +} + +static int isom_print_tref( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + return isom_print_simple( fp, box, level, "Track Reference Box" ); +} + +static int isom_print_track_reference_type( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_tref_type_t *ref = (isom_tref_type_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Track Reference Type Box" ); + for( uint32_t i = 0; i < ref->ref_count; i++ ) + lsmash_ifprintf( fp, indent, "track_ID[%"PRIu32"] = %"PRIu32"\n", i, ref->track_ID[i] ); + return 0; +} + +static int isom_print_mdia( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + return isom_print_simple( fp, box, level, "Media Box" ); +} + +static int isom_print_mdhd( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_mdhd_t *mdhd = (isom_mdhd_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Media Header Box" ); + lsmash_ifprintf( fp, indent, "creation_time = %s", isom_mp4time2utc( mdhd->creation_time ) ); + lsmash_ifprintf( fp, indent, "modification_time = %s", isom_mp4time2utc( mdhd->modification_time ) ); + lsmash_ifprintf( fp, indent, "timescale = %"PRIu32"\n", mdhd->timescale ); + isom_ifprintf_duration( fp, indent, "duration", mdhd->duration, mdhd->timescale ); + if( mdhd->language >= 0x800 ) + lsmash_ifprintf( fp, indent, "language = %s\n", isom_unpack_iso_language( mdhd->language ) ); + else + lsmash_ifprintf( fp, indent, "language = %"PRIu16"\n", mdhd->language ); + if( file->qt_compatible ) + lsmash_ifprintf( fp, indent, "quality = %"PRId16"\n", mdhd->quality ); + else + lsmash_ifprintf( fp, indent, "pre_defined = 0x%04"PRIx16"\n", mdhd->quality ); + return 0; +} + +static int isom_print_hdlr( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_hdlr_t *hdlr = (isom_hdlr_t *)box; + int indent = level; + char *str = lsmash_malloc( hdlr->componentName_length + 1 ); + if( !str ) + return LSMASH_ERR_MEMORY_ALLOC; + memcpy( str, hdlr->componentName, hdlr->componentName_length ); + str[hdlr->componentName_length] = 0; + isom_print_box_common( fp, indent++, box, "Handler Reference Box" ); + if( file->qt_compatible ) + { + lsmash_ifprintf( fp, indent, "componentType = %s\n", isom_4cc2str( hdlr->componentType ) ); + lsmash_ifprintf( fp, indent, "componentSubtype = %s\n", isom_4cc2str( hdlr->componentSubtype ) ); + lsmash_ifprintf( fp, indent, "componentManufacturer = %s\n", isom_4cc2str( hdlr->componentManufacturer ) ); + lsmash_ifprintf( fp, indent, "componentFlags = 0x%08"PRIx32"\n", hdlr->componentFlags ); + lsmash_ifprintf( fp, indent, "componentFlagsMask = 0x%08"PRIx32"\n", hdlr->componentFlagsMask ); + if( hdlr->componentName_length ) + lsmash_ifprintf( fp, indent, "componentName = %s\n", &str[1] ); + else + lsmash_ifprintf( fp, indent, "componentName = \n" ); + } + else + { + lsmash_ifprintf( fp, indent, "pre_defined = 0x%08"PRIx32"\n", hdlr->componentType ); + lsmash_ifprintf( fp, indent, "handler_type = %s\n", isom_4cc2str( hdlr->componentSubtype ) ); + lsmash_ifprintf( fp, indent, "reserved = 0x%08"PRIx32"\n", hdlr->componentManufacturer ); + lsmash_ifprintf( fp, indent, "reserved = 0x%08"PRIx32"\n", hdlr->componentFlags ); + lsmash_ifprintf( fp, indent, "reserved = 0x%08"PRIx32"\n", hdlr->componentFlagsMask ); + lsmash_ifprintf( fp, indent, "name = %s\n", str ); + } + lsmash_free( str ); + return 0; +} + +static int isom_print_minf( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + return isom_print_simple( fp, box, level, "Media Information Box" ); +} + +static int isom_print_vmhd( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_vmhd_t *vmhd = (isom_vmhd_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Video Media Header Box" ); + lsmash_ifprintf( fp, indent, "graphicsmode = %"PRIu16"\n", vmhd->graphicsmode ); + lsmash_ifprintf( fp, indent, "opcolor\n" ); + isom_ifprintf_rgb_color( fp, indent + 1, vmhd->opcolor ); + return 0; +} + +static int isom_print_smhd( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_smhd_t *smhd = (isom_smhd_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Sound Media Header Box" ); + lsmash_ifprintf( fp, indent, "balance = %f\n", lsmash_fixed2double( smhd->balance, 8 ) ); + lsmash_ifprintf( fp, indent, "reserved = 0x%04"PRIx16"\n", smhd->reserved ); + return 0; +} + +static int isom_print_hmhd( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_hmhd_t *hmhd = (isom_hmhd_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Hint Media Header Box" ); + lsmash_ifprintf( fp, indent, "maxPDUsize = %"PRIu16"\n", hmhd->maxPDUsize ); + lsmash_ifprintf( fp, indent, "avgPDUsize = %"PRIu16"\n", hmhd->avgPDUsize ); + lsmash_ifprintf( fp, indent, "maxbitrate = %"PRIu32"\n", hmhd->maxbitrate ); + lsmash_ifprintf( fp, indent, "avgbitrate = %"PRIu32"\n", hmhd->avgbitrate ); + lsmash_ifprintf( fp, indent, "reserved = 0x%08"PRIx32"\n", hmhd->reserved ); + return 0; +} + +static int isom_print_nmhd( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_print_box_common( fp, level, box, "Null Media Header Box" ); + return 0; +} + +static int isom_print_gmhd( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + return isom_print_simple( fp, box, level, "Generic Media Information Header Box" ); +} + +static int isom_print_gmin( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_gmin_t *gmin = (isom_gmin_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Generic Media Information Box" ); + lsmash_ifprintf( fp, indent, "graphicsmode = %"PRIu16"\n", gmin->graphicsmode ); + lsmash_ifprintf( fp, indent, "opcolor\n" ); + isom_ifprintf_rgb_color( fp, indent + 1, gmin->opcolor ); + lsmash_ifprintf( fp, indent, "balance = %f\n", lsmash_fixed2double( gmin->balance, 8 ) ); + lsmash_ifprintf( fp, indent, "reserved = 0x%04"PRIx16"\n", gmin->reserved ); + return 0; +} + +static int isom_print_text( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_text_t *text = (isom_text_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Text Media Information Box" ); + lsmash_ifprintf( fp, indent, "Unknown matrix\n" ); + isom_ifprintf_matrix( fp, indent + 1, text->matrix ); + return 0; +} + +static int isom_print_dinf( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + return isom_print_simple( fp, box, level, "Data Information Box" ); +} + +static int isom_print_dref( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_dref_t *dref = (isom_dref_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Data Reference Box" ); + lsmash_ifprintf( fp, indent, "entry_count = %"PRIu16"\n", dref->list.entry_count ); + return 0; +} + +static int isom_print_url( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_dref_entry_t *url = (isom_dref_entry_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Data Entry Url Box" ); + if( url->flags & 0x000001 ) + lsmash_ifprintf( fp, indent, "location = in the same file\n" ); + else + lsmash_ifprintf( fp, indent, "location = %s\n", url->location ); + return 0; +} + +static int isom_print_stbl( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + return isom_print_simple( fp, box, level, "Sample Table Box" ); +} + +static int isom_print_stsd( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_stsd_t *stsd = (isom_stsd_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Sample Description Box" ); + lsmash_ifprintf( fp, indent, "entry_count = %"PRIu32"\n", stsd->entry_count ); + return 0; +} + +static int isom_print_visual_description( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_visual_entry_t *visual = (isom_visual_entry_t *)box; + int indent = level; + lsmash_ifprintf( fp, indent++, "[%s: Visual Description]\n", isom_4cc2str( visual->type.fourcc ) ); + lsmash_ifprintf( fp, indent, "position = %"PRIu64"\n", visual->pos ); + lsmash_ifprintf( fp, indent, "size = %"PRIu64"\n", visual->size ); + isom_ifprintf_sample_description_common_reserved( fp, indent, visual->reserved ); + lsmash_ifprintf( fp, indent, "data_reference_index = %"PRIu16"\n", visual->data_reference_index ); + if( file->qt_compatible ) + { + lsmash_ifprintf( fp, indent, "version = %"PRId16"\n", visual->version ); + lsmash_ifprintf( fp, indent, "revision_level = %"PRId16"\n", visual->revision_level ); + lsmash_ifprintf( fp, indent, "vendor = %s\n", isom_4cc2str( visual->vendor ) ); + lsmash_ifprintf( fp, indent, "temporalQuality = %"PRIu32"\n", visual->temporalQuality ); + lsmash_ifprintf( fp, indent, "spatialQuality = %"PRIu32"\n", visual->spatialQuality ); + lsmash_ifprintf( fp, indent, "width = %"PRIu16"\n", visual->width ); + lsmash_ifprintf( fp, indent, "height = %"PRIu16"\n", visual->height ); + lsmash_ifprintf( fp, indent, "horizresolution = %f\n", lsmash_fixed2double( visual->horizresolution, 16 ) ); + lsmash_ifprintf( fp, indent, "vertresolution = %f\n", lsmash_fixed2double( visual->vertresolution, 16 ) ); + lsmash_ifprintf( fp, indent, "dataSize = %"PRIu32"\n", visual->dataSize ); + lsmash_ifprintf( fp, indent, "frame_count = %"PRIu16"\n", visual->frame_count ); + lsmash_ifprintf( fp, indent, "compressorname_length = %"PRIu8"\n", visual->compressorname[0] ); + lsmash_ifprintf( fp, indent, "compressorname = %s\n", visual->compressorname + 1 ); + lsmash_ifprintf( fp, indent, "depth = 0x%04"PRIx16, visual->depth ); + if( visual->depth == 32 ) + fprintf( fp, " (colour with alpha)\n" ); + else if( visual->depth >= 33 && visual->depth <= 40 ) + fprintf( fp, " (grayscale with no alpha)\n" ); + else + fprintf( fp, "\n" ); + lsmash_ifprintf( fp, indent, "color_table_ID = %"PRId16"\n", visual->color_table_ID ); + if( visual->color_table_ID == 0 ) + isom_pring_qt_color_table( fp, indent, &visual->color_table ); + } + else + { + lsmash_ifprintf( fp, indent, "pre_defined = 0x%04"PRIx16"\n", visual->version ); + lsmash_ifprintf( fp, indent, "reserved = 0x%04"PRIx16"\n", visual->revision_level ); + lsmash_ifprintf( fp, indent, "pre_defined = 0x%08"PRIx32"\n", visual->vendor ); + lsmash_ifprintf( fp, indent, "pre_defined = 0x%08"PRIx32"\n", visual->temporalQuality ); + lsmash_ifprintf( fp, indent, "pre_defined = 0x%08"PRIx32"\n", visual->spatialQuality ); + lsmash_ifprintf( fp, indent, "width = %"PRIu16"\n", visual->width ); + lsmash_ifprintf( fp, indent, "height = %"PRIu16"\n", visual->height ); + lsmash_ifprintf( fp, indent, "horizresolution = %f\n", lsmash_fixed2double( visual->horizresolution, 16 ) ); + lsmash_ifprintf( fp, indent, "vertresolution = %f\n", lsmash_fixed2double( visual->vertresolution, 16 ) ); + lsmash_ifprintf( fp, indent, "reserved = 0x%08"PRIx32"\n", visual->dataSize ); + lsmash_ifprintf( fp, indent, "frame_count = %"PRIu16"\n", visual->frame_count ); + lsmash_ifprintf( fp, indent, "compressorname_length = %"PRIu8"\n", visual->compressorname[0] ); + lsmash_ifprintf( fp, indent, "compressorname = %s\n", visual->compressorname + 1 ); + lsmash_ifprintf( fp, indent, "depth = 0x%04"PRIx16, visual->depth ); + if( visual->depth == 0x0018 ) + fprintf( fp, " (colour with no alpha)\n" ); + else if( visual->depth == 0x0028 ) + fprintf( fp, " (grayscale with no alpha)\n" ); + else if( visual->depth == 0x0020 ) + fprintf( fp, " (gray or colour with alpha)\n" ); + else + fprintf( fp, "\n" ); + lsmash_ifprintf( fp, indent, "pre_defined = 0x%04"PRIx16"\n", visual->color_table_ID ); + } + return 0; +} + +static int isom_print_glbl( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_glbl_t *glbl = (isom_glbl_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Global Header Box" ); + if( glbl->header_data ) + { + lsmash_ifprintf( fp, indent, "global_header[]\n" ); + for( uint32_t i = 0; i < glbl->header_size; i += 8 ) + { + lsmash_ifprintf( fp, indent + 1, "" ); + for( uint32_t j = 0; ; j++ ) + if( j == 7 || (i + j == glbl->header_size - 1) ) + { + fprintf( fp, "0x%02"PRIx8"\n", glbl->header_data[i + j] ); + break; + } + else + fprintf( fp, "0x%02"PRIx8" ", glbl->header_data[i + j] ); + } + } + return 0; +} + +static int isom_print_clap( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_clap_t *clap = (isom_clap_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Clean Aperture Box" ); + lsmash_ifprintf( fp, indent, "cleanApertureWidthN = %"PRIu32"\n", clap->cleanApertureWidthN ); + lsmash_ifprintf( fp, indent, "cleanApertureWidthD = %"PRIu32"\n", clap->cleanApertureWidthD ); + lsmash_ifprintf( fp, indent, "cleanApertureHeightN = %"PRIu32"\n", clap->cleanApertureHeightN ); + lsmash_ifprintf( fp, indent, "cleanApertureHeightD = %"PRIu32"\n", clap->cleanApertureHeightD ); + lsmash_ifprintf( fp, indent, "horizOffN = %"PRId32"\n", clap->horizOffN ); + lsmash_ifprintf( fp, indent, "horizOffD = %"PRIu32"\n", clap->horizOffD ); + lsmash_ifprintf( fp, indent, "vertOffN = %"PRId32"\n", clap->vertOffN ); + lsmash_ifprintf( fp, indent, "vertOffD = %"PRIu32"\n", clap->vertOffD ); + return 0; +} + +static int isom_print_pasp( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_pasp_t *pasp = (isom_pasp_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Pixel Aspect Ratio Box" ); + lsmash_ifprintf( fp, indent, "hSpacing = %"PRIu32"\n", pasp->hSpacing ); + lsmash_ifprintf( fp, indent, "vSpacing = %"PRIu32"\n", pasp->vSpacing ); + return 0; +} + +static int isom_print_colr( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_colr_t *colr = (isom_colr_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, colr->manager & LSMASH_QTFF_BASE ? "Color Parameter Box" : "Colour Information Box" ); + lsmash_ifprintf( fp, indent, "color_parameter_type = %s\n", isom_4cc2str( colr->color_parameter_type ) ); + if( colr->color_parameter_type == QT_COLOR_PARAMETER_TYPE_NCLC + || colr->color_parameter_type == ISOM_COLOR_PARAMETER_TYPE_NCLX ) + { + lsmash_ifprintf( fp, indent, "primaries_index = %"PRIu16"\n", colr->primaries_index ); + lsmash_ifprintf( fp, indent, "transfer_function_index = %"PRIu16"\n", colr->transfer_function_index ); + lsmash_ifprintf( fp, indent, "matrix_index = %"PRIu16"\n", colr->matrix_index ); + if( colr->color_parameter_type == ISOM_COLOR_PARAMETER_TYPE_NCLX ) + { + if( colr->manager & LSMASH_INCOMPLETE_BOX ) + { + lsmash_ifprintf( fp, indent, "full_range_flag = N/A\n" ); + lsmash_ifprintf( fp, indent, "reserved = N/A\n" ); + } + else + { + lsmash_ifprintf( fp, indent, "full_range_flag = %"PRIu8"\n", colr->full_range_flag ); + lsmash_ifprintf( fp, indent, "reserved = 0x%08"PRIx8"\n", colr->reserved ); + } + } + } + return 0; +} + +static int isom_print_gama( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_gama_t *gama = (isom_gama_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Gamma Level Box" ); + if( gama->level == 0x00023333 ) + lsmash_ifprintf( fp, indent, "level = 2.2 (standard television video gamma)\n" ); + else + { + lsmash_ifprintf( fp, indent, "level = %f", lsmash_fixed2double( gama->level, 16 ) ); + if( gama->level == 0 ) + fprintf( fp, " (platform's standard gamma)" ); + else if( gama->level == 0xffffffff ) + fprintf( fp, " (no gamma-correction)" ); + fprintf( fp, "\n" ); + } + return 0; +} + +static int isom_print_fiel( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_fiel_t *fiel = (isom_fiel_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Field/Frame Information Box" ); + lsmash_ifprintf( fp, indent, "fields = %"PRIu8" (%s)\n", fiel->fields, fiel->fields > 1 ? "interlaced" : "progressive scan" ); + lsmash_ifprintf( fp, indent, "detail = %"PRIu8, fiel->detail ); + if( fiel->fields > 1 ) + { + static const char *field_orderings[5] = + { "unknown", "temporal top first", "temporal bottom first", "spatial first line early", "spatial first line late" }; + int ordering = 0; + if( fiel->fields == 2 ) + { + if( fiel->detail == QT_FIELD_ORDERINGS_TEMPORAL_TOP_FIRST ) + ordering = 1; + else if( fiel->detail == QT_FIELD_ORDERINGS_TEMPORAL_BOTTOM_FIRST ) + ordering = 2; + else if( fiel->detail == QT_FIELD_ORDERINGS_SPATIAL_FIRST_LINE_EARLY ) + ordering = 3; + else if( fiel->detail == QT_FIELD_ORDERINGS_SPATIAL_FIRST_LINE_LATE ) + ordering = 4; + } + fprintf( fp, " (%s)\n", field_orderings[ordering] ); + } + else + fprintf( fp, "\n" ); + return 0; +} + +static int isom_print_clli( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) { + isom_clli_t *clli = (isom_clli_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Content Light Level Box" ); + lsmash_ifprintf( fp, indent, "max_content_light_level = %"PRIu16"\n", clli->max_content_light_level ); + lsmash_ifprintf( fp, indent, "max_pic_average_light_level = %"PRIu16"\n", clli->max_pic_average_light_level ); + return 0; +} + +static int isom_print_mdcv( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) { + isom_mdcv_t *mdcv = (isom_mdcv_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Mastering Display Color Volume Box" ); + lsmash_ifprintf( fp, indent, "display_primaries_g_x = %"PRIu16"\n", mdcv->display_primaries_g_x ); + lsmash_ifprintf( fp, indent, "display_primaries_g_y = %"PRIu16"\n", mdcv->display_primaries_g_y ); + lsmash_ifprintf( fp, indent, "display_primaries_b_x = %"PRIu16"\n", mdcv->display_primaries_b_x ); + lsmash_ifprintf( fp, indent, "display_primaries_b_y = %"PRIu16"\n", mdcv->display_primaries_b_y ); + lsmash_ifprintf( fp, indent, "display_primaries_r_x = %"PRIu16"\n", mdcv->display_primaries_r_x ); + lsmash_ifprintf( fp, indent, "display_primaries_r_y = %"PRIu16"\n", mdcv->display_primaries_r_y ); + lsmash_ifprintf( fp, indent, "white_point_x = %"PRIu16"\n", mdcv->white_point_x ); + lsmash_ifprintf( fp, indent, "white_point_y = %"PRIu16"\n", mdcv->white_point_y ); + lsmash_ifprintf( fp, indent, "max_display_mastering_luminance = %"PRIu32"\n", mdcv->max_display_mastering_luminance ); + lsmash_ifprintf( fp, indent, "min_display_mastering_luminance = %"PRIu32"\n", mdcv->min_display_mastering_luminance ); + return 0; +} + +static int isom_print_cspc( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_cspc_t *cspc = (isom_cspc_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Colorspace Box" ); + static const struct + { + lsmash_qt_pixel_format pixel_format; + char *description; + } unprintable_pixel_format_table[] = + { + { QT_PIXEL_FORMAT_TYPE_1_MONOCHROME, "1 bit indexed" }, + { QT_PIXEL_FORMAT_TYPE_2_INDEXED, "2 bit indexed" }, + { QT_PIXEL_FORMAT_TYPE_4_INDEXED, "4 bit indexed" }, + { QT_PIXEL_FORMAT_TYPE_8_INDEXED, "8 bit indexed" }, + { QT_PIXEL_FORMAT_TYPE_1_INDEXED_GRAY_WHITE_IS_ZERO, "1 bit indexed gray, white is zero" }, + { QT_PIXEL_FORMAT_TYPE_2_INDEXED_GRAY_WHITE_IS_ZERO, "2 bit indexed gray, white is zero" }, + { QT_PIXEL_FORMAT_TYPE_4_INDEXED_GRAY_WHITE_IS_ZERO, "4 bit indexed gray, white is zero" }, + { QT_PIXEL_FORMAT_TYPE_8_INDEXED_GRAY_WHITE_IS_ZERO, "8 bit indexed gray, white is zero" }, + { QT_PIXEL_FORMAT_TYPE_16BE555, "16 bit BE RGB 555" }, + { QT_PIXEL_FORMAT_TYPE_24RGB, "24 bit RGB" }, + { QT_PIXEL_FORMAT_TYPE_32ARGB, "32 bit ARGB" }, + { 0, NULL } + }; + for( int i = 0; unprintable_pixel_format_table[i].pixel_format; i++ ) + if( cspc->pixel_format == unprintable_pixel_format_table[i].pixel_format ) + { + lsmash_ifprintf( fp, indent, "pixel_format = 0x%08"PRIx32" (%s)\n", cspc->pixel_format, unprintable_pixel_format_table[i].description ); + return 0; + } + lsmash_ifprintf( fp, indent, "pixel_format = %s\n", isom_4cc2str( cspc->pixel_format ) ); + return 0; +} + +static int isom_print_sgbt( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_sgbt_t *sgbt = (isom_sgbt_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Significant Bits Box" ); + lsmash_ifprintf( fp, indent, "significantBits = %"PRIu8"\n", sgbt->significantBits ); + return 0; +} + +static int isom_print_stsl( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_stsl_t *stsl = (isom_stsl_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Sample Scale Box" ); + lsmash_ifprintf( fp, indent, "constraint_flag = %s\n", (stsl->constraint_flag & 0x01) ? "on" : "off" ); + lsmash_ifprintf( fp, indent, "scale_method = " ); + if( stsl->scale_method == ISOM_SCALE_METHOD_FILL ) + fprintf( fp, "'fill'\n" ); + else if( stsl->scale_method == ISOM_SCALE_METHOD_HIDDEN ) + fprintf( fp, "'hidden'\n" ); + else if( stsl->scale_method == ISOM_SCALE_METHOD_MEET ) + fprintf( fp, "'meet'\n" ); + else if( stsl->scale_method == ISOM_SCALE_METHOD_SLICE_X ) + fprintf( fp, "'slice' in the x-coodinate\n" ); + else if( stsl->scale_method == ISOM_SCALE_METHOD_SLICE_Y ) + fprintf( fp, "'slice' in the y-coodinate\n" ); + lsmash_ifprintf( fp, indent, "display_center_x = %"PRIu16"\n", stsl->display_center_x ); + lsmash_ifprintf( fp, indent, "display_center_y = %"PRIu16"\n", stsl->display_center_y ); + return 0; +} + +static int isom_print_audio_description( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_audio_entry_t *audio = (isom_audio_entry_t *)box; + int indent = level; + lsmash_ifprintf( fp, indent++, "[%s: Audio Description]\n", isom_4cc2str( audio->type.fourcc ) ); + lsmash_ifprintf( fp, indent, "position = %"PRIu64"\n", audio->pos ); + lsmash_ifprintf( fp, indent, "size = %"PRIu64"\n", audio->size ); + isom_ifprintf_sample_description_common_reserved( fp, indent, audio->reserved ); + lsmash_ifprintf( fp, indent, "data_reference_index = %"PRIu16"\n", audio->data_reference_index ); + if( file->qt_compatible ) + { + lsmash_ifprintf( fp, indent, "version = %"PRId16"\n", audio->version ); + lsmash_ifprintf( fp, indent, "revision_level = %"PRId16"\n", audio->revision_level ); + lsmash_ifprintf( fp, indent, "vendor = %s\n", isom_4cc2str( audio->vendor ) ); + lsmash_ifprintf( fp, indent, "channelcount = %"PRIu16"\n", audio->channelcount ); + lsmash_ifprintf( fp, indent, "samplesize = %"PRIu16"\n", audio->samplesize ); + lsmash_ifprintf( fp, indent, "compression_ID = %"PRId16"\n", audio->compression_ID ); + lsmash_ifprintf( fp, indent, "packet_size = %"PRIu16"\n", audio->packet_size ); + } + else + { + lsmash_ifprintf( fp, indent, "reserved = 0x%04"PRIx16"\n", audio->version ); + lsmash_ifprintf( fp, indent, "reserved = 0x%04"PRIx16"\n", audio->revision_level ); + lsmash_ifprintf( fp, indent, "reserved = 0x%08"PRIx32"\n", audio->vendor ); + lsmash_ifprintf( fp, indent, "channelcount = %"PRIu16"\n", audio->channelcount ); + lsmash_ifprintf( fp, indent, "samplesize = %"PRIu16"\n", audio->samplesize ); + lsmash_ifprintf( fp, indent, "pre_defined = %"PRId16"\n", audio->compression_ID ); + lsmash_ifprintf( fp, indent, "reserved = %"PRIu16"\n", audio->packet_size ); + } + lsmash_ifprintf( fp, indent, "samplerate = %f\n", lsmash_fixed2double( audio->samplerate, 16 ) ); + if( audio->version == 1 && (audio->manager & LSMASH_QTFF_BASE) ) + { + lsmash_ifprintf( fp, indent, "samplesPerPacket = %"PRIu32"\n", audio->samplesPerPacket ); + lsmash_ifprintf( fp, indent, "bytesPerPacket = %"PRIu32"\n", audio->bytesPerPacket ); + lsmash_ifprintf( fp, indent, "bytesPerFrame = %"PRIu32"\n", audio->bytesPerFrame ); + lsmash_ifprintf( fp, indent, "bytesPerSample = %"PRIu32"\n", audio->bytesPerSample ); + } + else if( audio->version == 2 ) + { + lsmash_ifprintf( fp, indent, "sizeOfStructOnly = %"PRIu32"\n", audio->sizeOfStructOnly ); + lsmash_ifprintf( fp, indent, "audioSampleRate = %lf\n", lsmash_int2float64( audio->audioSampleRate ) ); + lsmash_ifprintf( fp, indent, "numAudioChannels = %"PRIu32"\n", audio->numAudioChannels ); + lsmash_ifprintf( fp, indent, "always7F000000 = 0x%08"PRIx32"\n", audio->always7F000000 ); + lsmash_ifprintf( fp, indent, "constBitsPerChannel = %"PRIu32"\n", audio->constBitsPerChannel ); + lsmash_ifprintf( fp, indent++, "formatSpecificFlags = 0x%08"PRIx32"\n", audio->formatSpecificFlags ); + if( isom_is_lpcm_audio( audio ) ) + { + lsmash_ifprintf( fp, indent, "sample format: " ); + if( audio->formatSpecificFlags & QT_LPCM_FORMAT_FLAG_FLOAT ) + fprintf( fp, "floating point\n" ); + else + { + fprintf( fp, "integer\n" ); + lsmash_ifprintf( fp, indent, "signedness: " ); + fprintf( fp, audio->formatSpecificFlags & QT_LPCM_FORMAT_FLAG_SIGNED_INTEGER ? "signed\n" : "unsigned\n" ); + } + if( audio->constBytesPerAudioPacket != 1 ) + { + lsmash_ifprintf( fp, indent, "endianness: " ); + fprintf( fp, audio->formatSpecificFlags & QT_LPCM_FORMAT_FLAG_BIG_ENDIAN ? "big\n" : "little\n" ); + } + lsmash_ifprintf( fp, indent, "packed: " ); + if( audio->formatSpecificFlags & QT_LPCM_FORMAT_FLAG_PACKED ) + fprintf( fp, "yes\n" ); + else + { + fprintf( fp, "no\n" ); + lsmash_ifprintf( fp, indent, "alignment: " ); + fprintf( fp, audio->formatSpecificFlags & QT_LPCM_FORMAT_FLAG_ALIGNED_HIGH ? "high\n" : "low\n" ); + } + if( audio->numAudioChannels > 1 ) + { + lsmash_ifprintf( fp, indent, "interleved: " ); + fprintf( fp, audio->formatSpecificFlags & QT_LPCM_FORMAT_FLAG_NON_INTERLEAVED ? "no\n" : "yes\n" ); + } + } + lsmash_ifprintf( fp, --indent, "constBytesPerAudioPacket = %"PRIu32"\n", audio->constBytesPerAudioPacket ); + lsmash_ifprintf( fp, indent, "constLPCMFramesPerAudioPacket = %"PRIu32"\n", audio->constLPCMFramesPerAudioPacket ); + } + return 0; +} + +static int isom_print_wave( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + return isom_print_simple( fp, box, level, "Sound Information Decompression Parameters Box" ); +} + +static int isom_print_frma( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_frma_t *frma = (isom_frma_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Format Box" ); + lsmash_ifprintf( fp, indent, "data_format = %s\n", isom_4cc2str( frma->data_format ) ); + return 0; +} + +static int isom_print_enda( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_enda_t *enda = (isom_enda_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Audio Endian Box" ); + lsmash_ifprintf( fp, indent, "littleEndian = %s\n", enda->littleEndian ? "yes" : "no" ); + return 0; +} + +static int isom_print_terminator( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_terminator_t *terminator = (isom_terminator_t *)box; + int indent = level; + lsmash_ifprintf( fp, indent++, "[0x00000000: Terminator Box]\n" ); + lsmash_ifprintf( fp, indent, "position = %"PRIu64"\n", terminator->pos ); + lsmash_ifprintf( fp, indent, "size = %"PRIu64"\n", terminator->size ); + return 0; +} + +static int isom_print_chan( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_chan_t *chan = (isom_chan_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Audio Channel Layout Box" ); + lsmash_ifprintf( fp, indent, "channelLayoutTag = 0x%08"PRIx32"\n", chan->channelLayoutTag ); + lsmash_ifprintf( fp, indent, "channelBitmap = 0x%08"PRIx32"\n", chan->channelBitmap ); + lsmash_ifprintf( fp, indent, "numberChannelDescriptions = %"PRIu32"\n", chan->numberChannelDescriptions ); + if( chan->numberChannelDescriptions ) + { + isom_channel_description_t *desc = chan->channelDescriptions; + for( uint32_t i = 0; i < chan->numberChannelDescriptions; i++ ) + { + lsmash_ifprintf( fp, indent++, "ChannelDescriptions[%"PRIu32"]\n", i ); + lsmash_ifprintf( fp, indent, "channelLabel = 0x%08"PRIx32"\n", desc->channelLabel ); + lsmash_ifprintf( fp, indent, "channelFlags = 0x%08"PRIx32"\n", desc->channelFlags ); + for( int j = 0; j < 3; j++ ) + lsmash_ifprintf( fp, indent, "coordinates[%d] = %f\n", j, lsmash_int2float32( desc->coordinates[j] ) ); + --indent; + } + } + return 0; +} + +static int isom_print_srat( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_srat_t *srat = (isom_srat_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Sampling Rate Box" ); + lsmash_ifprintf( fp, indent, "sampling_rate = %"PRIu32"\n", srat->sampling_rate ); + return 0; +} + +static int isom_print_text_description( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_qt_text_entry_t *text = (isom_qt_text_entry_t *)box; + int indent = level; + lsmash_ifprintf( fp, indent++, "[text: QuickTime Text Description]\n" ); + lsmash_ifprintf( fp, indent, "position = %"PRIu64"\n", text->pos ); + lsmash_ifprintf( fp, indent, "size = %"PRIu64"\n", text->size ); + isom_ifprintf_sample_description_common_reserved( fp, indent, text->reserved ); + lsmash_ifprintf( fp, indent, "data_reference_index = %"PRIu16"\n", text->data_reference_index ); + lsmash_ifprintf( fp, indent, "displayFlags = 0x%08"PRId32"\n", text->displayFlags ); + lsmash_ifprintf( fp, indent, "textJustification = %"PRId32"\n", text->textJustification ); + lsmash_ifprintf( fp, indent, "bgColor\n" ); + isom_ifprintf_rgb_color( fp, indent + 1, text->bgColor ); + lsmash_ifprintf( fp, indent, "top = %"PRId16"\n", text->top ); + lsmash_ifprintf( fp, indent, "left = %"PRId16"\n", text->left ); + lsmash_ifprintf( fp, indent, "bottom = %"PRId16"\n", text->bottom ); + lsmash_ifprintf( fp, indent, "right = %"PRId16"\n", text->right ); + lsmash_ifprintf( fp, indent, "scrpStartChar = %"PRId32"\n", text->scrpStartChar ); + lsmash_ifprintf( fp, indent, "scrpHeight = %"PRId16"\n", text->scrpHeight ); + lsmash_ifprintf( fp, indent, "scrpAscent = %"PRId16"\n", text->scrpAscent ); + lsmash_ifprintf( fp, indent, "scrpFont = %"PRId16"\n", text->scrpFont ); + lsmash_ifprintf( fp, indent, "scrpFace = %"PRIu16"\n", text->scrpFace ); + lsmash_ifprintf( fp, indent, "scrpSize = %"PRId16"\n", text->scrpSize ); + lsmash_ifprintf( fp, indent, "scrpColor\n" ); + isom_ifprintf_rgb_color( fp, indent + 1, text->scrpColor ); + if( text->font_name_length ) + lsmash_ifprintf( fp, indent, "font_name = %s\n", text->font_name ); + return 0; +} + +static int isom_print_tx3g_description( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_tx3g_entry_t *tx3g = (isom_tx3g_entry_t *)box; + int indent = level; + lsmash_ifprintf( fp, indent++, "[tx3g: Timed Text Description]\n" ); + lsmash_ifprintf( fp, indent, "position = %"PRIu64"\n", tx3g->pos ); + lsmash_ifprintf( fp, indent, "size = %"PRIu64"\n", tx3g->size ); + isom_ifprintf_sample_description_common_reserved( fp, indent, tx3g->reserved ); + lsmash_ifprintf( fp, indent, "data_reference_index = %"PRIu16"\n", tx3g->data_reference_index ); + lsmash_ifprintf( fp, indent, "displayFlags = 0x%08"PRId32"\n", tx3g->displayFlags ); + lsmash_ifprintf( fp, indent, "horizontal_justification = %"PRId8"\n", tx3g->horizontal_justification ); + lsmash_ifprintf( fp, indent, "vertical_justification = %"PRId8"\n", tx3g->vertical_justification ); + lsmash_ifprintf( fp, indent, "background_color_rgba\n" ); + isom_ifprintf_rgba_color( fp, indent + 1, tx3g->background_color_rgba ); + lsmash_ifprintf( fp, indent, "top = %"PRId16"\n", tx3g->top ); + lsmash_ifprintf( fp, indent, "left = %"PRId16"\n", tx3g->left ); + lsmash_ifprintf( fp, indent, "bottom = %"PRId16"\n", tx3g->bottom ); + lsmash_ifprintf( fp, indent, "right = %"PRId16"\n", tx3g->right ); + lsmash_ifprintf( fp, indent, "startChar = %"PRIu16"\n", tx3g->startChar ); + lsmash_ifprintf( fp, indent, "endChar = %"PRIu16"\n", tx3g->endChar ); + lsmash_ifprintf( fp, indent, "font_ID = %"PRIu16"\n", tx3g->font_ID ); + lsmash_ifprintf( fp, indent, "face_style_flags = %"PRIu8"\n", tx3g->face_style_flags ); + lsmash_ifprintf( fp, indent, "font_size = %"PRIu8"\n", tx3g->font_size ); + lsmash_ifprintf( fp, indent, "text_color_rgba\n" ); + isom_ifprintf_rgba_color( fp, indent + 1, tx3g->text_color_rgba ); + return 0; +} + +static int isom_print_ftab( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + if( !((isom_ftab_t *)box)->list ) + return LSMASH_ERR_INVALID_DATA; + isom_ftab_t *ftab = (isom_ftab_t *)box; + int indent = level; + uint16_t i = 0; + isom_print_box_common( fp, indent++, box, "Font Table Box" ); + lsmash_ifprintf( fp, indent, "entry_count = %"PRIu16"\n", ftab->list->entry_count ); + for( lsmash_entry_t *entry = ftab->list->head; entry; entry = entry->next ) + { + isom_font_record_t *data = (isom_font_record_t *)entry->data; + lsmash_ifprintf( fp, indent++, "entry[%"PRIu16"]\n", i++ ); + lsmash_ifprintf( fp, indent, "font_ID = %"PRIu16"\n", data->font_ID ); + if( data->font_name_length ) + lsmash_ifprintf( fp, indent, "font_name = %s\n", data->font_name ); + --indent; + } + return 0; +} + +static int isom_print_mp4s_description( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_mp4s_entry_t *mp4s = (isom_mp4s_entry_t *)box; + int indent = level; + lsmash_ifprintf( fp, indent++, "[%s: MPEG-4 Systems Description]\n", isom_4cc2str( mp4s->type.fourcc ) ); + lsmash_ifprintf( fp, indent, "position = %"PRIu64"\n", mp4s->pos ); + lsmash_ifprintf( fp, indent, "size = %"PRIu64"\n", mp4s->size ); + isom_ifprintf_sample_description_common_reserved( fp, indent, mp4s->reserved ); + lsmash_ifprintf( fp, indent, "data_reference_index = %"PRIu16"\n", mp4s->data_reference_index ); + return 0; +} + +static int isom_print_sample_description_extesion( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + extern int mp4sys_print_codec_specific( FILE *, lsmash_file_t *, isom_box_t *, int ); + extern int h264_print_codec_specific( FILE *, lsmash_file_t *, isom_box_t *, int ); + extern int hevc_print_codec_specific( FILE *, lsmash_file_t *, isom_box_t *, int ); + extern int h264_print_bitrate( FILE *, lsmash_file_t *, isom_box_t *, int ); + extern int vc1_print_codec_specific( FILE *, lsmash_file_t *, isom_box_t *, int ); + extern int ac3_print_codec_specific( FILE *, lsmash_file_t *, isom_box_t *, int ); + extern int eac3_print_codec_specific( FILE *, lsmash_file_t *, isom_box_t *, int ); + extern int dts_print_codec_specific( FILE *, lsmash_file_t *, isom_box_t *, int ); + extern int alac_print_codec_specific( FILE *, lsmash_file_t *, isom_box_t *, int ); + extern int wma_print_codec_specific( FILE *, lsmash_file_t *, isom_box_t *, int ); + static struct print_description_extension_table_tag + { + lsmash_box_type_t type; + int (*print_func)( FILE *, lsmash_file_t *, isom_box_t *, int ); + } print_description_extension_table[32] = { { LSMASH_BOX_TYPE_INITIALIZER, NULL } }; + if( !print_description_extension_table[0].print_func ) + { + /* Initialize the table. */ + int i = 0; +#define ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( type, func ) \ + print_description_extension_table[i++] = (struct print_description_extension_table_tag){ type, func } + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( ISOM_BOX_TYPE_CLAP, isom_print_clap ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( ISOM_BOX_TYPE_PASP, isom_print_pasp ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( ISOM_BOX_TYPE_STSL, isom_print_stsl ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( ISOM_BOX_TYPE_COLR, isom_print_colr ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( QT_BOX_TYPE_COLR, isom_print_colr ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( QT_BOX_TYPE_GAMA, isom_print_gama ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( QT_BOX_TYPE_FIEL, isom_print_fiel ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( QT_BOX_TYPE_CLLI, isom_print_clli ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( QT_BOX_TYPE_MDCV, isom_print_mdcv ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( QT_BOX_TYPE_CSPC, isom_print_cspc ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( QT_BOX_TYPE_SGBT, isom_print_sgbt ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( QT_BOX_TYPE_CTAB, isom_print_ctab ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( QT_BOX_TYPE_GLBL, isom_print_glbl ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( QT_BOX_TYPE_WAVE, isom_print_wave ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( QT_BOX_TYPE_CHAN, isom_print_chan ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( ISOM_BOX_TYPE_ESDS, mp4sys_print_codec_specific ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( ISOM_BOX_TYPE_AVCC, h264_print_codec_specific ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( ISOM_BOX_TYPE_BTRT, h264_print_bitrate ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( ISOM_BOX_TYPE_HVCC, hevc_print_codec_specific ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( ISOM_BOX_TYPE_DVC1, vc1_print_codec_specific ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( ISOM_BOX_TYPE_DAC3, ac3_print_codec_specific ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( ISOM_BOX_TYPE_DEC3, eac3_print_codec_specific ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( ISOM_BOX_TYPE_DDTS, dts_print_codec_specific ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( ISOM_BOX_TYPE_ALAC, alac_print_codec_specific ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( ISOM_BOX_TYPE_WFEX, wma_print_codec_specific ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( ISOM_BOX_TYPE_FTAB, isom_print_ftab ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( QT_BOX_TYPE_ESDS, mp4sys_print_codec_specific ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( QT_BOX_TYPE_ALAC, alac_print_codec_specific ); + ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT( LSMASH_BOX_TYPE_UNSPECIFIED, NULL ); +#undef ADD_PRINT_DESCRIPTION_EXTENSION_TABLE_ELEMENT + } + for( int i = 0; print_description_extension_table[i].print_func; i++ ) + if( lsmash_check_box_type_identical( box->type, print_description_extension_table[i].type ) ) + return print_description_extension_table[i].print_func( fp, file, box, level ); + return isom_print_unknown( fp, file, box, level ); +} + +static int isom_print_stts( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + if( !((isom_stts_t *)box)->list ) + return LSMASH_ERR_INVALID_DATA; + isom_stts_t *stts = (isom_stts_t *)box; + int indent = level; + uint32_t i = 0; + isom_print_box_common( fp, indent++, box, "Decoding Time to Sample Box" ); + lsmash_ifprintf( fp, indent, "entry_count = %"PRIu32"\n", stts->list->entry_count ); + for( lsmash_entry_t *entry = stts->list->head; entry; entry = entry->next ) + { + isom_stts_entry_t *data = (isom_stts_entry_t *)entry->data; + lsmash_ifprintf( fp, indent++, "entry[%"PRIu32"]\n", i++ ); + lsmash_ifprintf( fp, indent, "sample_count = %"PRIu32"\n", data->sample_count ); + lsmash_ifprintf( fp, indent--, "sample_delta = %"PRIu32"\n", data->sample_delta ); + } + return 0; +} + +static int isom_print_ctts( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + if( !((isom_ctts_t *)box)->list ) + return LSMASH_ERR_INVALID_DATA; + isom_ctts_t *ctts = (isom_ctts_t *)box; + int indent = level; + uint32_t i = 0; + isom_print_box_common( fp, indent++, box, "Composition Time to Sample Box" ); + lsmash_ifprintf( fp, indent, "entry_count = %"PRIu32"\n", ctts->list->entry_count ); + if( file->qt_compatible || ctts->version == 1 ) + for( lsmash_entry_t *entry = ctts->list->head; entry; entry = entry->next ) + { + isom_ctts_entry_t *data = (isom_ctts_entry_t *)entry->data; + lsmash_ifprintf( fp, indent++, "entry[%"PRIu32"]\n", i++ ); + lsmash_ifprintf( fp, indent, "sample_count = %"PRIu32"\n", data->sample_count ); + if( data->sample_offset != ISOM_NON_OUTPUT_SAMPLE_OFFSET ) + lsmash_ifprintf( fp, indent--, "sample_offset = %"PRId32"\n", (union {uint32_t ui; int32_t si;}){ data->sample_offset }.si ); + else + lsmash_ifprintf( fp, indent--, "sample_offset = -2^31 (non-output sample)\n" ); + } + else + for( lsmash_entry_t *entry = ctts->list->head; entry; entry = entry->next ) + { + isom_ctts_entry_t *data = (isom_ctts_entry_t *)entry->data; + lsmash_ifprintf( fp, indent++, "entry[%"PRIu32"]\n", i++ ); + lsmash_ifprintf( fp, indent, "sample_count = %"PRIu32"\n", data->sample_count ); + lsmash_ifprintf( fp, indent--, "sample_offset = %"PRIu32"\n", data->sample_offset ); + } + return 0; +} + +static int isom_print_cslg( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_cslg_t *cslg = (isom_cslg_t *)box; + int indent = level; + if( file->qt_compatible ) + { + isom_print_box_common( fp, indent++, box, "Composition Shift Least Greatest Box" ); + lsmash_ifprintf( fp, indent, "compositionOffsetToDTDDeltaShift = %"PRId32"\n", cslg->compositionToDTSShift ); + lsmash_ifprintf( fp, indent, "leastDecodeToDisplayDelta = %"PRId32"\n", cslg->leastDecodeToDisplayDelta ); + lsmash_ifprintf( fp, indent, "greatestDecodeToDisplayDelta = %"PRId32"\n", cslg->greatestDecodeToDisplayDelta ); + lsmash_ifprintf( fp, indent, "displayStartTime = %"PRId32"\n", cslg->compositionStartTime ); + lsmash_ifprintf( fp, indent, "displayEndTime = %"PRId32"\n", cslg->compositionEndTime ); + } + else + { + isom_print_box_common( fp, indent++, box, "Composition to Decode Box" ); + lsmash_ifprintf( fp, indent, "compositionToDTSShift = %"PRId32"\n", cslg->compositionToDTSShift ); + lsmash_ifprintf( fp, indent, "leastDecodeToDisplayDelta = %"PRId32"\n", cslg->leastDecodeToDisplayDelta ); + lsmash_ifprintf( fp, indent, "greatestDecodeToDisplayDelta = %"PRId32"\n", cslg->greatestDecodeToDisplayDelta ); + lsmash_ifprintf( fp, indent, "compositionStartTime = %"PRId32"\n", cslg->compositionStartTime ); + lsmash_ifprintf( fp, indent, "compositionEndTime = %"PRId32"\n", cslg->compositionEndTime ); + } + return 0; +} + +static int isom_print_stss( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + if( !((isom_stss_t *)box)->list ) + return LSMASH_ERR_INVALID_DATA; + isom_stss_t *stss = (isom_stss_t *)box; + int indent = level; + uint32_t i = 0; + isom_print_box_common( fp, indent++, box, "Sync Sample Box" ); + lsmash_ifprintf( fp, indent, "entry_count = %"PRIu32"\n", stss->list->entry_count ); + for( lsmash_entry_t *entry = stss->list->head; entry; entry = entry->next ) + lsmash_ifprintf( fp, indent, "sample_number[%"PRIu32"] = %"PRIu32"\n", i++, ((isom_stss_entry_t *)entry->data)->sample_number ); + return 0; +} + +static int isom_print_stps( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + if( !((isom_stps_t *)box)->list ) + return LSMASH_ERR_INVALID_DATA; + isom_stps_t *stps = (isom_stps_t *)box; + int indent = level; + uint32_t i = 0; + isom_print_box_common( fp, indent++, box, "Partial Sync Sample Box" ); + lsmash_ifprintf( fp, indent, "entry_count = %"PRIu32"\n", stps->list->entry_count ); + for( lsmash_entry_t *entry = stps->list->head; entry; entry = entry->next ) + lsmash_ifprintf( fp, indent, "sample_number[%"PRIu32"] = %"PRIu32"\n", i++, ((isom_stps_entry_t *)entry->data)->sample_number ); + return 0; +} + +static int isom_print_sdtp( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + if( !((isom_sdtp_t *)box)->list ) + return LSMASH_ERR_INVALID_DATA; + isom_sdtp_t *sdtp = (isom_sdtp_t *)box; + int indent = level; + uint32_t i = 0; + isom_print_box_common( fp, indent++, box, "Independent and Disposable Samples Box" ); + for( lsmash_entry_t *entry = sdtp->list->head; entry; entry = entry->next ) + { + isom_sdtp_entry_t *data = (isom_sdtp_entry_t *)entry->data; + lsmash_ifprintf( fp, indent++, "entry[%"PRIu32"]\n", i++ ); + if( data->is_leading || data->sample_depends_on || data->sample_is_depended_on || data->sample_has_redundancy ) + { + if( file->avc_extensions ) + { + if( data->is_leading == ISOM_SAMPLE_IS_UNDECODABLE_LEADING ) + lsmash_ifprintf( fp, indent, "undecodable leading\n" ); + else if( data->is_leading == ISOM_SAMPLE_IS_NOT_LEADING ) + lsmash_ifprintf( fp, indent, "non-leading\n" ); + else if( data->is_leading == ISOM_SAMPLE_IS_DECODABLE_LEADING ) + lsmash_ifprintf( fp, indent, "decodable leading\n" ); + } + else if( data->is_leading == QT_SAMPLE_EARLIER_PTS_ALLOWED ) + lsmash_ifprintf( fp, indent, "early display times allowed\n" ); + if( data->sample_depends_on == ISOM_SAMPLE_IS_INDEPENDENT ) + lsmash_ifprintf( fp, indent, "independent\n" ); + else if( data->sample_depends_on == ISOM_SAMPLE_IS_NOT_INDEPENDENT ) + lsmash_ifprintf( fp, indent, "dependent\n" ); + if( data->sample_is_depended_on == ISOM_SAMPLE_IS_NOT_DISPOSABLE ) + lsmash_ifprintf( fp, indent, "non-disposable\n" ); + else if( data->sample_is_depended_on == ISOM_SAMPLE_IS_DISPOSABLE ) + lsmash_ifprintf( fp, indent, "disposable\n" ); + if( data->sample_has_redundancy == ISOM_SAMPLE_HAS_REDUNDANCY ) + lsmash_ifprintf( fp, indent, "redundant\n" ); + else if( data->sample_has_redundancy == ISOM_SAMPLE_HAS_NO_REDUNDANCY ) + lsmash_ifprintf( fp, indent, "non-redundant\n" ); + } + else + lsmash_ifprintf( fp, indent, "no description\n" ); + --indent; + } + return 0; +} + +static int isom_print_stsc( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + if( !((isom_stsc_t *)box)->list ) + return LSMASH_ERR_INVALID_DATA; + isom_stsc_t *stsc = (isom_stsc_t *)box; + int indent = level; + uint32_t i = 0; + isom_print_box_common( fp, indent++, box, "Sample To Chunk Box" ); + lsmash_ifprintf( fp, indent, "entry_count = %"PRIu32"\n", stsc->list->entry_count ); + for( lsmash_entry_t *entry = stsc->list->head; entry; entry = entry->next ) + { + isom_stsc_entry_t *data = (isom_stsc_entry_t *)entry->data; + lsmash_ifprintf( fp, indent++, "entry[%"PRIu32"]\n", i++ ); + lsmash_ifprintf( fp, indent, "first_chunk = %"PRIu32"\n", data->first_chunk ); + lsmash_ifprintf( fp, indent, "samples_per_chunk = %"PRIu32"\n", data->samples_per_chunk ); + lsmash_ifprintf( fp, indent--, "sample_description_index = %"PRIu32"\n", data->sample_description_index ); + } + return 0; +} + +static int isom_print_stsz( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_stsz_t *stsz = (isom_stsz_t *)box; + int indent = level; + uint32_t i = 0; + isom_print_box_common( fp, indent++, box, "Sample Size Box" ); + if( !stsz->sample_size ) + lsmash_ifprintf( fp, indent, "sample_size = 0 (variable)\n" ); + else + lsmash_ifprintf( fp, indent, "sample_size = %"PRIu32" (constant)\n", stsz->sample_size ); + lsmash_ifprintf( fp, indent, "sample_count = %"PRIu32"\n", stsz->sample_count ); + if( !stsz->sample_size && stsz->list ) + for( lsmash_entry_t *entry = stsz->list->head; entry; entry = entry->next ) + { + isom_stsz_entry_t *data = (isom_stsz_entry_t *)entry->data; + lsmash_ifprintf( fp, indent, "entry_size[%"PRIu32"] = %"PRIu32"\n", i++, data->entry_size ); + } + return 0; +} + +static int isom_print_stz2( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_stz2_t *stz2 = (isom_stz2_t *)box; + int indent = level; + uint32_t i = 0; + isom_print_box_common( fp, indent++, box, "Compact Sample Size Box" ); + lsmash_ifprintf( fp, indent, "reserved = 0x%06"PRIx32"\n", stz2->reserved ); + lsmash_ifprintf( fp, indent, "field_size = %"PRIu8"\n", stz2->field_size ); + lsmash_ifprintf( fp, indent, "sample_count = %"PRIu32"\n", stz2->sample_count ); + for( lsmash_entry_t *entry = stz2->list->head; entry; entry = entry->next ) + { + isom_stsz_entry_t *data = (isom_stsz_entry_t *)entry->data; + lsmash_ifprintf( fp, indent, "entry_size[%"PRIu32"] = %"PRIu32"\n", i++, data->entry_size ); + } + return 0; +} + +static int isom_print_stco( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + if( !((isom_stco_t *)box)->list ) + return LSMASH_ERR_INVALID_DATA; + isom_stco_t *stco = (isom_stco_t *)box; + int indent = level; + uint32_t i = 0; + isom_print_box_common( fp, indent++, box, "Chunk Offset Box" ); + lsmash_ifprintf( fp, indent, "entry_count = %"PRIu32"\n", stco->list->entry_count ); + if( lsmash_check_box_type_identical( stco->type, ISOM_BOX_TYPE_STCO ) ) + { + for( lsmash_entry_t *entry = stco->list->head; entry; entry = entry->next ) + lsmash_ifprintf( fp, indent, "chunk_offset[%"PRIu32"] = %"PRIu32"\n", i++, ((isom_stco_entry_t *)entry->data)->chunk_offset ); + } + else + { + for( lsmash_entry_t *entry = stco->list->head; entry; entry = entry->next ) + lsmash_ifprintf( fp, indent, "chunk_offset[%"PRIu32"] = %"PRIu64"\n", i++, ((isom_co64_entry_t *)entry->data)->chunk_offset ); + } + return 0; +} + +static int isom_print_sgpd( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + if( !((isom_sgpd_t *)box)->list ) + return LSMASH_ERR_INVALID_DATA; + isom_sgpd_t *sgpd = (isom_sgpd_t *)box; + int indent = level; + uint32_t i = 0; + isom_print_box_common( fp, indent++, box, "Sample Group Description Box" ); + lsmash_ifprintf( fp, indent, "grouping_type = %s\n", isom_4cc2str( sgpd->grouping_type ) ); + if( sgpd->version == 1 ) + { + lsmash_ifprintf( fp, indent, "default_length = %"PRIu32, sgpd->default_length ); + fprintf( fp, " %s\n", sgpd->default_length ? "(constant)" : "(variable)" ); + } + lsmash_ifprintf( fp, indent, "entry_count = %"PRIu32"\n", sgpd->list->entry_count ); + switch( sgpd->grouping_type ) + { + case ISOM_GROUP_TYPE_RAP : + for( lsmash_entry_t *entry = sgpd->list->head; entry; entry = entry->next ) + { + if( sgpd->version == 1 && !sgpd->default_length ) + lsmash_ifprintf( fp, indent, "description_length[%"PRIu32"] = %"PRIu32"\n", i++, ((isom_rap_entry_t *)entry->data)->description_length ); + else + { + isom_rap_entry_t *rap = (isom_rap_entry_t *)entry->data; + lsmash_ifprintf( fp, indent++, "entry[%"PRIu32"]\n", i++ ); + lsmash_ifprintf( fp, indent, "num_leading_samples_known = %"PRIu8"\n", rap->num_leading_samples_known ); + lsmash_ifprintf( fp, indent--, "num_leading_samples = %"PRIu8"\n", rap->num_leading_samples ); + } + } + break; + case ISOM_GROUP_TYPE_ROLL : + case ISOM_GROUP_TYPE_PROL : + for( lsmash_entry_t *entry = sgpd->list->head; entry; entry = entry->next ) + { + if( sgpd->version == 1 && !sgpd->default_length ) + lsmash_ifprintf( fp, indent, "description_length[%"PRIu32"] = %"PRIu32"\n", i++, ((isom_roll_entry_t *)entry->data)->description_length ); + else + lsmash_ifprintf( fp, indent, "roll_distance[%"PRIu32"] = %"PRId16"\n", i++, ((isom_roll_entry_t *)entry->data)->roll_distance ); + } + break; + default : + break; + } + return 0; +} + +static int isom_print_sbgp( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + if( !((isom_sbgp_t *)box)->list ) + return LSMASH_ERR_INVALID_DATA; + isom_sbgp_t *sbgp = (isom_sbgp_t *)box; + int indent = level; + int is_fragment = lsmash_check_box_type_identical( sbgp->parent->type, ISOM_BOX_TYPE_TRAF ); + uint32_t i = 0; + isom_print_box_common( fp, indent++, box, "Sample to Group Box" ); + lsmash_ifprintf( fp, indent, "grouping_type = %s\n", isom_4cc2str( sbgp->grouping_type ) ); + if( sbgp->version == 1 ) + lsmash_ifprintf( fp, indent, "grouping_type_parameter = %s\n", isom_4cc2str( sbgp->grouping_type_parameter ) ); + lsmash_ifprintf( fp, indent, "entry_count = %"PRIu32"\n", sbgp->list->entry_count ); + for( lsmash_entry_t *entry = sbgp->list->head; entry; entry = entry->next ) + { + isom_group_assignment_entry_t *data = (isom_group_assignment_entry_t *)entry->data; + lsmash_ifprintf( fp, indent++, "entry[%"PRIu32"]\n", i++ ); + lsmash_ifprintf( fp, indent, "sample_count = %"PRIu32"\n", data->sample_count ); + lsmash_ifprintf( fp, indent--, "group_description_index = %"PRIu32, data->group_description_index ); + if( is_fragment && data->group_description_index >= 0x10000 ) + fprintf( fp, " (i.e. %"PRIu32" for this fragment-local group)", data->group_description_index - 0x10000 ); + if( !data->group_description_index ) + fprintf( fp, " (not in this grouping type)\n" ); + else + fprintf( fp, "\n" ); + } + return 0; +} + +static int isom_print_udta( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + return isom_print_simple( fp, box, level, "User Data Box" ); +} + +static int isom_print_chpl( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_chpl_t *chpl = (isom_chpl_t *)box; + uint32_t timescale; + if( !chpl->version ) + { + if( !file->moov || !file->moov->mvhd ) + return LSMASH_ERR_INVALID_DATA; + timescale = file->moov->mvhd->timescale; + } + else + timescale = 10000000; + int indent = level; + uint32_t i = 0; + isom_print_box_common( fp, indent++, box, "Chapter List Box" ); + if( chpl->version == 1 ) + { + lsmash_ifprintf( fp, indent, "unknown = 0x%02"PRIx8"\n", chpl->unknown ); + lsmash_ifprintf( fp, indent, "entry_count = %"PRIu32"\n", chpl->list->entry_count ); + } + else + lsmash_ifprintf( fp, indent, "entry_count = %"PRIu8"\n", (uint8_t)chpl->list->entry_count ); + for( lsmash_entry_t *entry = chpl->list->head; entry; entry = entry->next ) + { + isom_chpl_entry_t *data = (isom_chpl_entry_t *)entry->data; + int64_t start_time = data->start_time / timescale; + int hh = start_time / 3600; + int mm = (start_time / 60) % 60; + int ss = start_time % 60; + int ms = ((data->start_time / (double)timescale) - hh * 3600 - mm * 60 - ss) * 1e3 + 0.5; + int with_bom = 0; + if( !memcmp( data->chapter_name, "\xEF\xBB\xBF", 3 ) ) /* detect BOM */ + { + data->chapter_name += 3; + with_bom = 1; + } + lsmash_ifprintf( fp, indent++, "chapter[%"PRIu32"]\n", i++ ); + lsmash_ifprintf( fp, indent, "start_time = %02d:%02d:%02d.%03d\n", hh, mm, ss, ms ); + lsmash_ifprintf( fp, indent--, with_bom ? "chapter_name = %s ( it has BOM in it )\n" : "chapter_name = %s\n", data->chapter_name ); + } + return 0; +} + +static int isom_print_meta( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + int indent = level; + if( !(box->manager & LSMASH_QTFF_BASE) ) + { + isom_print_basebox_common( fp, indent++, box, "Meta Box" ); + lsmash_ifprintf( fp, indent, "version = %"PRIu8"\n", box->version ); + lsmash_ifprintf( fp, indent, "flags = 0x%06"PRIx32"\n", box->flags & 0x00ffffff ); + } + else + isom_print_basebox_common( fp, indent, box, "Metadata Box" ); + return 0; +} + +static int isom_print_keys( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + if( !((isom_keys_t *)box)->list ) + return LSMASH_ERR_INVALID_DATA; + isom_keys_t *keys = (isom_keys_t *)box; + int indent = level; + uint32_t i = 1; + isom_print_box_common( fp, indent++, box, "Metadata Item Keys Box" ); + lsmash_ifprintf( fp, indent, "entry_count = %"PRIu32"\n", keys->list->entry_count ); + for( lsmash_entry_t *entry = keys->list->head; entry; entry = entry->next ) + { + isom_keys_entry_t *data = (isom_keys_entry_t *)entry->data; + lsmash_ifprintf( fp, indent++, "[key %"PRIu32"]\n", i++ ); + lsmash_ifprintf( fp, indent, "key_size = %"PRIu32"\n", data->key_size ); + lsmash_ifprintf( fp, indent, "key_namespace = %s\n", isom_4cc2str( data->key_namespace ) ); + uint32_t value_length = data->key_size - 8; + char *str = lsmash_malloc( value_length + 1 ); + if( !str ) + return LSMASH_ERR_MEMORY_ALLOC; + memcpy( str, data->key_value, value_length ); + str[value_length] = 0; + lsmash_ifprintf( fp, indent--, "key_value = %s\n", str ); + lsmash_free( str ); + } + return 0; +} + +static int isom_print_ilst( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + return isom_print_simple( fp, box, level, "Metadata Item List Box" ); +} + +static int isom_print_metaitem( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_metaitem_t *metaitem = (isom_metaitem_t *)box; + if( box->parent->parent->manager & LSMASH_QTFF_BASE ) + { + int indent = level; + lsmash_ifprintf( fp, indent++, "[key_index %"PRIu32": Metadata Item Box]\n", box->type.fourcc ); + lsmash_ifprintf( fp, indent, "position = %"PRIu64"\n", box->pos ); + lsmash_ifprintf( fp, indent, "size = %"PRIu64"\n", box->size ); + return 0; + } + static const struct + { + lsmash_itunes_metadata_item item; + char *name; + } metaitem_table[] = + { + { ITUNES_METADATA_ITEM_ALBUM_NAME, "Album Name" }, + { ITUNES_METADATA_ITEM_ARTIST, "Artist" }, + { ITUNES_METADATA_ITEM_USER_COMMENT, "User Comment" }, + { ITUNES_METADATA_ITEM_RELEASE_DATE, "Release Date" }, + { ITUNES_METADATA_ITEM_ENCODED_BY, "Encoded By" }, + { ITUNES_METADATA_ITEM_USER_GENRE, "User Genre" }, + { ITUNES_METADATA_ITEM_GROUPING, "Grouping" }, + { ITUNES_METADATA_ITEM_LYRICS, "Lyrics" }, + { ITUNES_METADATA_ITEM_TITLE, "Title" }, + { ITUNES_METADATA_ITEM_ENCODING_TOOL, "Encoding Tool" }, + { ITUNES_METADATA_ITEM_COMPOSER, "Composer" }, + { ITUNES_METADATA_ITEM_ALBUM_ARTIST, "Album Artist" }, + { ITUNES_METADATA_ITEM_PODCAST_CATEGORY, "Podcast Category" }, + { ITUNES_METADATA_ITEM_COVER_ART, "Cover Art" }, + { ITUNES_METADATA_ITEM_DISC_COMPILATION, "Disc Compilation" }, + { ITUNES_METADATA_ITEM_COPYRIGHT, "Copyright" }, + { ITUNES_METADATA_ITEM_DESCRIPTION, "Description" }, + { ITUNES_METADATA_ITEM_DISC_NUMBER, "Disc Number" }, + { ITUNES_METADATA_ITEM_EPISODE_GLOBAL_ID, "Episode Global Unique ID" }, + { ITUNES_METADATA_ITEM_PREDEFINED_GENRE, "Pre-defined Genre" }, + { ITUNES_METADATA_ITEM_GROUPING_DRAFT, "Grouping (Overall work like TIT1 in ID3)" }, + { ITUNES_METADATA_ITEM_HIGH_DEFINITION_VIDEO, "High Definition Video" }, + { ITUNES_METADATA_ITEM_PODCAST_KEYWORD, "Podcast Keyword" }, + { ITUNES_METADATA_ITEM_LONG_DESCRIPTION, "Long Description" }, + { ITUNES_METADATA_ITEM_PODCAST, "Podcast" }, + { ITUNES_METADATA_ITEM_GAPLESS_PLAYBACK, "Gapless Playback" }, + { ITUNES_METADATA_ITEM_PURCHASE_DATE, "Purchase Date" }, + { ITUNES_METADATA_ITEM_PODCAST_URL, "Podcast URL" }, + { ITUNES_METADATA_ITEM_CONTENT_RATING, "Content Rating" }, + { ITUNES_METADATA_ITEM_MEDIA_TYPE, "Media Type" }, + { ITUNES_METADATA_ITEM_BEATS_PER_MINUTE, "Beats Per Minute" }, + { ITUNES_METADATA_ITEM_TRACK_NUMBER, "Track Number" }, + { ITUNES_METADATA_ITEM_TV_EPISODE_ID, "TV Episode ID" }, + { ITUNES_METADATA_ITEM_TV_EPISODE, "TV Episode" }, + { ITUNES_METADATA_ITEM_TV_NETWORK, "TV Network" }, + { ITUNES_METADATA_ITEM_TV_SHOW_NAME, "TV Show Name" }, + { ITUNES_METADATA_ITEM_TV_SEASON, "TV Season" }, + { ITUNES_METADATA_ITEM_ITUNES_PURCHASE_ACCOUNT_ID, "iTunes Account Used for Purchase" }, + { ITUNES_METADATA_ITEM_ITUNES_ACCOUNT_TYPE, "iTunes Account Type" }, + { ITUNES_METADATA_ITEM_ITUNES_ARTIST_ID, "iTunes Artist ID" }, + { ITUNES_METADATA_ITEM_ITUNES_COMPOSER_ID, "iTunes Composer ID" }, + { ITUNES_METADATA_ITEM_ITUNES_CATALOG_ID, "iTunes Catalog ID" }, + { ITUNES_METADATA_ITEM_ITUNES_TV_GENRE_ID, "iTunes TV Genre ID" }, + { ITUNES_METADATA_ITEM_ITUNES_PLAYLIST_ID, "iTunes Playlist ID" }, + { ITUNES_METADATA_ITEM_ITUNES_COUNTRY_CODE, "iTunes Country Code" }, + { ITUNES_METADATA_ITEM_ITUNES_SORT_ALBUM, "Sort Album" }, + { ITUNES_METADATA_ITEM_ITUNES_SORT_ARTIST, "Sort Artist" }, + { ITUNES_METADATA_ITEM_ITUNES_SORT_ALBUM_ARTIST, "Sort Album Artist" }, + { ITUNES_METADATA_ITEM_ITUNES_SORT_COMPOSER, "Sort Composer" }, + { ITUNES_METADATA_ITEM_ITUNES_SORT_NAME, "Sort Name" }, + { ITUNES_METADATA_ITEM_ITUNES_SORT_SHOW, "Sort Show" }, + { ITUNES_METADATA_ITEM_CUSTOM, "Custom Metadata Item" }, + { 0, NULL } + }; + char *name = NULL; + int i; + for( i = 0; metaitem_table[i].name; i++ ) + if( metaitem->type.fourcc == metaitem_table[i].item ) + { + name = metaitem_table[i].name; + break; + } + if( !name ) + name = "Unknown"; + uint32_t name_length = strlen( name ); + uint32_t display_name_length = name_length + 20; + char *display_name = lsmash_malloc( display_name_length + 1 ); + if( !display_name ) + return LSMASH_ERR_MEMORY_ALLOC; + memcpy( display_name, "Metadata Item Box (", 19 ); + memcpy( display_name + 19, name, name_length ); + display_name[display_name_length - 1] = ')'; + display_name[display_name_length] = 0; + int ret = isom_print_simple( fp, box, level, display_name ); + lsmash_free( display_name ); + return ret; +} + +static int isom_print_name( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_name_t *name = (isom_name_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Name Box" ); + char *str = lsmash_malloc( name->name_length + 1 ); + if( !str ) + return LSMASH_ERR_MEMORY_ALLOC; + memcpy( str, name->name, name->name_length ); + str[name->name_length] = 0; + lsmash_ifprintf( fp, indent, "name = %s\n", str ); + lsmash_free( str ); + return 0; +} + +static int isom_print_mean( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_mean_t *mean = (isom_mean_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Mean Box" ); + char *str = lsmash_malloc( mean->meaning_string_length + 1 ); + if( !str ) + return LSMASH_ERR_MEMORY_ALLOC; + memcpy( str, mean->meaning_string, mean->meaning_string_length ); + str[mean->meaning_string_length] = 0; + lsmash_ifprintf( fp, indent, "meaning_string = %s\n", str ); + lsmash_free( str ); + return 0; +} + +static int isom_print_data( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_data_t *data = (isom_data_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Data Box" ); + if( box->parent->parent->parent->manager & LSMASH_QTFF_BASE ) + { + uint32_t type_set_indicator = data->reserved >> 8; + uint32_t well_known_type = ((data->reserved << 16) | (data->type_set_identifier << 8) | data->type_code) & 0xffffff; + char *well_known_type_name = "Unknown"; + static const struct + { + uint32_t type; + char *name; + } well_known_type_table[] = + { + { 0, "reserved" }, + { 1, "UTF-8" }, + { 2, "UTF-16 BE" }, + { 3, "S/JIS" }, + { 4, "UTF-8 sort" }, + { 5, "UTF-16 sort" }, + { 13, "JPEG in a JFIF wrapper" }, + { 14, "PNG in a PNG wrapper" }, + { 21, "BE Signed Integer" }, + { 22, "BE Unsigned Integer" }, + { 23, "BE Float32" }, + { 24, "BE Float64" }, + { 27, "BMP (Windows bitmap format graphics)" }, + { 28, "QuickTime Metadata box" }, + { UINT32_MAX } + }; + int table_index; + for( table_index = 0; well_known_type_table[table_index].type != UINT32_MAX; table_index++ ) + if( well_known_type == well_known_type_table[table_index].type ) + { + well_known_type_name = well_known_type_table[table_index].name; + break; + } + lsmash_ifprintf( fp, indent, "type_set_indicator = %"PRIu8"\n", type_set_indicator ); + lsmash_ifprintf( fp, indent, "well_known_type = %"PRIu32" (%s)\n", well_known_type, well_known_type_name ); + lsmash_ifprintf( fp, indent, "locale_indicator = %"PRIu32"\n", data->the_locale ); + if( data->value_length == 0 ) + { + lsmash_ifprintf( fp, indent, "value = (null)\n" ); + return 0; + } + if( well_known_type == 1 ) + { + /* UTF-8 without any count or null terminator */ + char *str = lsmash_malloc( data->value_length + 1 ); + if( !str ) + return LSMASH_ERR_MEMORY_ALLOC; + memcpy( str, data->value, data->value_length ); + str[data->value_length] = 0; + lsmash_ifprintf( fp, indent, "value = %s\n", str ); + lsmash_free( str ); + } + else if( well_known_type == 13 || well_known_type == 14 || well_known_type == 27 ) + lsmash_ifprintf( fp, indent, "value = (binary data)\n" ); + else if( well_known_type == 21 && data->value_length <= 4 ) + /* a big-endian signed integer in 1,2,3 or 4 bytes */ + goto show_in_signed_integer; + else if( well_known_type == 22 && data->value_length <= 4 ) + { + /* a big-endian unsigned integer in 1,2,3 or 4 bytes */ + uint32_t integer = data->value[0]; + for( uint32_t i = 1; i < data->value_length; i++ ) + integer = (integer << 8) | data->value[i]; + lsmash_ifprintf( fp, indent, "value = %"PRIu32"\n", integer ); + } + else if( well_known_type == 23 && data->value_length == 4 ) + { + /* a big-endian 32-bit floating point value (IEEE754) */ + uint32_t float32 = LSMASH_GET_BE32( data->value ); + lsmash_ifprintf( fp, indent, "value = %f\n", lsmash_int2float32( float32 ) ); + } + else if( well_known_type == 24 && data->value_length == 8 ) + { + /* a big-endian 64-bit floating point value (IEEE754) */ + uint64_t float64 = LSMASH_GET_BE64( data->value ); + lsmash_ifprintf( fp, indent, "value = %lf\n", lsmash_int2float64( float64 ) ); + } + else + goto show_in_binary; + } + else + { + char *basic_data_type_name = "Unknown"; + static const struct + { + uint32_t type; + char *name; + } basic_data_type_table[] = + { + { 0, "Implicit" }, + { 1, "UTF-8" }, + { 2, "UTF-16 BE" }, + { 3, "S/JIS" }, + { 6, "HTML" }, + { 7, "XML" }, + { 8, "UUID" }, + { 9, "ISRC" }, + { 10, "MI3P" }, + { 12, "GIF" }, + { 13, "JPEG in a JFIF wrapper" }, + { 14, "PNG in a PNG wrapper" }, + { 15, "URL" }, + { 16, "duration" }, + { 17, "date/time" }, + { 18, "Genres" }, + { 21, "BE Signed Integer" }, + { 24, "RIAA-PA (RIAA Parental advisory)" }, + { 25, "UPC (Universal Product Code)" }, + { 27, "BMP (Windows bitmap format graphics)" }, + { UINT32_MAX } + }; + int table_index; + for( table_index = 0; basic_data_type_table[table_index].type != UINT32_MAX; table_index++ ) + if( data->type_code == basic_data_type_table[table_index].type ) + { + basic_data_type_name = basic_data_type_table[table_index].name; + break; + } + lsmash_ifprintf( fp, indent, "reserved = %"PRIu16"\n", data->reserved ); + lsmash_ifprintf( fp, indent, "type_set_identifier = %"PRIu8"%s\n", + data->type_set_identifier, + data->type_set_identifier ? "" : " (basic type set)" ); + lsmash_ifprintf( fp, indent, "type_code = %"PRIu8" (%s)\n", data->type_code, basic_data_type_name ); + lsmash_ifprintf( fp, indent, "the_locale = %"PRIu32"\n", data->the_locale ); + if( data->value_length == 0 ) + { + lsmash_ifprintf( fp, indent, "value = (null)\n" ); + return 0; + } + if( data->type_code == 6 || data->type_code == 7 + || data->type_code == 12 || data->type_code == 13 + || data->type_code == 14 || data->type_code == 27 ) + lsmash_ifprintf( fp, indent, "value = (binary data)\n" ); + else if( data->type_code == 8 && data->value_length == 16 ) + /* UUID */ + lsmash_ifprintf( fp, indent, "value = 0x%08"PRIx32"-%04"PRIx16"-%04"PRIx16"-%04"PRIx16"-%04"PRIx16"0x%08"PRIx32"\n", + LSMASH_GET_BE32( &data->value[ 0] ), + LSMASH_GET_BE16( &data->value[ 4] ), + LSMASH_GET_BE16( &data->value[ 6] ), + LSMASH_GET_BE16( &data->value[ 8] ), + LSMASH_GET_BE16( &data->value[10] ), + LSMASH_GET_BE32( &data->value[12] ) ); + else if( data->type_code == 16 && data->value_length == 4 ) + { + /* duration in milliseconds */ + uint32_t duration = LSMASH_GET_BE32( data->value ); + lsmash_ifprintf( fp, indent, "value = %"PRIu32" milliseconds\n", duration ); + } + else if( data->type_code == 17 && (data->value_length == 4 || data->value_length == 8) ) + { + /* UTC, counting seconds since midnight on 1 January, 1904 */ + uint64_t mp4time = data->value_length == 8 + ? LSMASH_GET_BE64( data->value ) + : LSMASH_GET_BE32( data->value ); + isom_mp4time2utc( mp4time ); + } + else if( data->type_code == 21 && data->value_length <= 8 ) + /* a big-endian signed integer in 1,2,3,4 or 8 bytes */ + goto show_in_signed_integer; + else if( data->type_code == 24 ) + { + /* RIAA-PA (RIAA Parental advisory) 8-bit integer */ + lsmash_ifprintf( fp, indent, "value = %"PRIu8, data->value[0] ); + if( data->value[0] == (uint8_t)-1 ) + fprintf( fp, " (no)" ); + else if( data->value[0] == 1 ) + fprintf( fp, " (yes)" ); + else if( data->value[0] == 0 ) + fprintf( fp, " (unspecified)" ); + fprintf( fp, "\n" ); + } + else if( data->type_code == 1 || data->type_code == 2 || data->type_code == 3 + || data->type_code == 9 || data->type_code == 10 || data->type_code == 15 + || data->type_code == 25 ) + { + /* String */ + char *str = lsmash_malloc( data->value_length + 1 ); + if( !str ) + return LSMASH_ERR_MEMORY_ALLOC; + memcpy( str, data->value, data->value_length ); + str[data->value_length] = 0; + lsmash_ifprintf( fp, indent, "value = %s\n", str ); + lsmash_free( str ); + } + else + goto show_in_binary; + } + return 0; +show_in_signed_integer:; + uint64_t integer = data->value[0]; + uint64_t max_value = 0xff; + for( uint32_t i = 1; i < data->value_length; i++ ) + { + integer = (integer << 8) | data->value[i]; + max_value = (max_value << 8) | 0xff; + } + lsmash_ifprintf( fp, indent, "value = %"PRId64"\n", (int64_t)(integer | (integer > (max_value >> 1) ? ~max_value : 0)) ); + return 0; +show_in_binary: + lsmash_ifprintf( fp, indent, "value = " ); + if( data->value_length ) + { + fprintf( fp, "0x" ); + for( uint32_t i = 0; i < data->value_length; i++ ) + fprintf( fp, "%02"PRIx8, data->value[i] ); + } + fprintf( fp, "\n" ); + return 0; +} + +static int isom_print_WLOC( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_WLOC_t *WLOC = (isom_WLOC_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Window Location Box" ); + lsmash_ifprintf( fp, indent, "x = %"PRIu16"\n", WLOC->x ); + lsmash_ifprintf( fp, indent, "y = %"PRIu16"\n", WLOC->y ); + return 0; +} + +static int isom_print_LOOP( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_LOOP_t *LOOP = (isom_LOOP_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Looping Box" ); + lsmash_ifprintf( fp, indent, "looping_mode = %"PRIu32, LOOP->looping_mode ); + switch( LOOP->looping_mode ) + { + case 0 : + fprintf( fp, " (none)\n" ); + break; + case 1 : + fprintf( fp, " (looping)\n" ); + break; + case 2 : + fprintf( fp, " (palindromic looping)\n" ); + break; + default : + fprintf( fp, "\n" ); + break; + } + return 0; +} + +static int isom_print_SelO( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_SelO_t *SelO = (isom_SelO_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Play Selection Only Box" ); + lsmash_ifprintf( fp, indent, "selection_only = %"PRIu8"\n", SelO->selection_only ); + return 0; +} + +static int isom_print_AllF( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_AllF_t *AllF = (isom_AllF_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Play All Frames Box" ); + lsmash_ifprintf( fp, indent, "play_all_frames = %"PRIu8"\n", AllF->play_all_frames ); + return 0; +} + +static int isom_print_cprt( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_cprt_t *cprt = (isom_cprt_t *)box; + int indent = level; + char *str = lsmash_malloc( cprt->notice_length + 1 ); + if( !str ) + return LSMASH_ERR_MEMORY_ALLOC; + memcpy( str, cprt->notice, cprt->notice_length ); + str[cprt->notice_length] = 0; + isom_print_box_common( fp, indent++, box, "Copyright Box" ); + lsmash_ifprintf( fp, indent, "language = %s\n", isom_unpack_iso_language( cprt->language ) ); + lsmash_ifprintf( fp, indent, "notice = %s\n", str ); + lsmash_free( str ); + return 0; +} + +static int isom_print_mvex( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + return isom_print_simple( fp, box, level, "Movie Extends Box" ); +} + +static int isom_print_mehd( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_mehd_t *mehd = (isom_mehd_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Movie Extends Header Box" ); + if( file->moov && file->moov->mvhd ) + isom_ifprintf_duration( fp, indent, "fragment_duration", mehd->fragment_duration, file->moov->mvhd->timescale ); + else + isom_ifprintf_duration( fp, indent, "fragment_duration", mehd->fragment_duration, 0 ); + return 0; +} + +static int isom_print_trex( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_trex_t *trex = (isom_trex_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Track Extends Box" ); + lsmash_ifprintf( fp, indent, "track_ID = %"PRIu32"\n", trex->track_ID ); + lsmash_ifprintf( fp, indent, "default_sample_description_index = %"PRIu32"\n", trex->default_sample_description_index ); + lsmash_ifprintf( fp, indent, "default_sample_duration = %"PRIu32"\n", trex->default_sample_duration ); + lsmash_ifprintf( fp, indent, "default_sample_size = %"PRIu32"\n", trex->default_sample_size ); + isom_ifprintf_sample_flags( fp, indent, "default_sample_flags", &trex->default_sample_flags ); + return 0; +} + +static int isom_print_moof( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + return isom_print_simple( fp, box, level, "Movie Fragment Box" ); +} + +static int isom_print_mfhd( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_mfhd_t *mfhd = (isom_mfhd_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Movie Fragment Header Box" ); + lsmash_ifprintf( fp, indent, "sequence_number = %"PRIu32"\n", mfhd->sequence_number ); + return 0; +} + +static int isom_print_traf( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + return isom_print_simple( fp, box, level, "Track Fragment Box" ); +} + +static int isom_print_tfhd( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_tfhd_t *tfhd = (isom_tfhd_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Track Fragment Header Box" ); + ++indent; + if( tfhd->flags & ISOM_TF_FLAGS_BASE_DATA_OFFSET_PRESENT ) lsmash_ifprintf( fp, indent, "base-data-offset-present\n" ); + if( tfhd->flags & ISOM_TF_FLAGS_SAMPLE_DESCRIPTION_INDEX_PRESENT ) lsmash_ifprintf( fp, indent, "sample-description-index-present\n" ); + if( tfhd->flags & ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT ) lsmash_ifprintf( fp, indent, "default-sample-duration-present\n" ); + if( tfhd->flags & ISOM_TF_FLAGS_DEFAULT_SAMPLE_SIZE_PRESENT ) lsmash_ifprintf( fp, indent, "default-sample-size-present\n" ); + if( tfhd->flags & ISOM_TF_FLAGS_DEFAULT_SAMPLE_FLAGS_PRESENT ) lsmash_ifprintf( fp, indent, "default-sample-flags-present\n" ); + if( tfhd->flags & ISOM_TF_FLAGS_DEFAULT_BASE_IS_MOOF ) lsmash_ifprintf( fp, indent, "default-base-is-moof\n" ); + lsmash_ifprintf( fp, --indent, "track_ID = %"PRIu32"\n", tfhd->track_ID ); + if( tfhd->flags & ISOM_TF_FLAGS_BASE_DATA_OFFSET_PRESENT ) + lsmash_ifprintf( fp, indent, "base_data_offset = %"PRIu64"\n", tfhd->base_data_offset ); + if( tfhd->flags & ISOM_TF_FLAGS_SAMPLE_DESCRIPTION_INDEX_PRESENT ) + lsmash_ifprintf( fp, indent, "sample_description_index = %"PRIu32"\n", tfhd->sample_description_index ); + if( tfhd->flags & ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT ) + lsmash_ifprintf( fp, indent, "default_sample_duration = %"PRIu32"\n", tfhd->default_sample_duration ); + if( tfhd->flags & ISOM_TF_FLAGS_DEFAULT_SAMPLE_SIZE_PRESENT ) + lsmash_ifprintf( fp, indent, "default_sample_size = %"PRIu32"\n", tfhd->default_sample_size ); + if( tfhd->flags & ISOM_TF_FLAGS_DEFAULT_SAMPLE_FLAGS_PRESENT ) + isom_ifprintf_sample_flags( fp, indent, "default_sample_flags", &tfhd->default_sample_flags ); + return 0; +} + +static int isom_print_tfdt( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_tfdt_t *tfdt = (isom_tfdt_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Track Fragment Base Media Decode Time Box" ); + lsmash_ifprintf( fp, indent, "baseMediaDecodeTime = %"PRIu64"\n", tfdt->baseMediaDecodeTime ); + return 0; +} + +static int isom_print_trun( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_trun_t *trun = (isom_trun_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Track Fragment Run Box" ); + ++indent; + if( trun->flags & ISOM_TR_FLAGS_DATA_OFFSET_PRESENT ) lsmash_ifprintf( fp, indent, "data-offset-present\n" ); + if( trun->flags & ISOM_TR_FLAGS_FIRST_SAMPLE_FLAGS_PRESENT ) lsmash_ifprintf( fp, indent, "first-sample-flags-present\n" ); + if( trun->flags & ISOM_TR_FLAGS_SAMPLE_DURATION_PRESENT ) lsmash_ifprintf( fp, indent, "sample-duration-present\n" ); + if( trun->flags & ISOM_TR_FLAGS_SAMPLE_SIZE_PRESENT ) lsmash_ifprintf( fp, indent, "sample-size-present\n" ); + if( trun->flags & ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT ) lsmash_ifprintf( fp, indent, "sample-flags-present\n" ); + if( trun->flags & ISOM_TR_FLAGS_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT ) lsmash_ifprintf( fp, indent, "sample-composition-time-offsets-present\n" ); + lsmash_ifprintf( fp, --indent, "sample_count = %"PRIu32"\n", trun->sample_count ); + if( trun->flags & ISOM_TR_FLAGS_DATA_OFFSET_PRESENT ) + lsmash_ifprintf( fp, indent, "data_offset = %"PRId32"\n", trun->data_offset ); + if( trun->flags & ISOM_TR_FLAGS_FIRST_SAMPLE_FLAGS_PRESENT ) + isom_ifprintf_sample_flags( fp, indent, "first_sample_flags", &trun->first_sample_flags ); + if( trun->optional ) + { + uint32_t i = 0; + for( lsmash_entry_t *entry = trun->optional->head; entry; entry = entry->next ) + { + isom_trun_optional_row_t *row = (isom_trun_optional_row_t *)entry->data; + lsmash_ifprintf( fp, indent++, "sample[%"PRIu32"]\n", i++ ); + if( trun->flags & ISOM_TR_FLAGS_SAMPLE_DURATION_PRESENT ) + lsmash_ifprintf( fp, indent, "sample_duration = %"PRIu32"\n", row->sample_duration ); + if( trun->flags & ISOM_TR_FLAGS_SAMPLE_SIZE_PRESENT ) + lsmash_ifprintf( fp, indent, "sample_size = %"PRIu32"\n", row->sample_size ); + if( trun->flags & ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT ) + isom_ifprintf_sample_flags( fp, indent, "sample_flags", &row->sample_flags ); + if( trun->flags & ISOM_TR_FLAGS_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT ) + { + if( trun->version == 0 ) + lsmash_ifprintf( fp, indent, "sample_composition_time_offset = %"PRIu32"\n", + row->sample_composition_time_offset ); + else + lsmash_ifprintf( fp, indent, "sample_composition_time_offset = %"PRId32"\n", + (union {uint32_t ui; int32_t si;}){ row->sample_composition_time_offset }.si ); + } + --indent; + } + } + return 0; +} + +static int isom_print_free( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + return isom_print_simple( fp, box, level, "Free Space Box" ); +} + +static int isom_print_mdat( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + return isom_print_simple( fp, box, level, "Media Data Box" ); +} + +static int isom_print_mfra( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + return isom_print_simple( fp, box, level, "Movie Fragment Random Access Box" ); +} + +static int isom_print_tfra( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_tfra_t *tfra = (isom_tfra_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Track Fragment Random Access Box" ); + lsmash_ifprintf( fp, indent, "track_ID = %"PRIu32"\n", tfra->track_ID ); + lsmash_ifprintf( fp, indent, "reserved = 0x%08"PRIx32"\n", tfra->reserved ); + lsmash_ifprintf( fp, indent, "length_size_of_traf_num = %"PRIu8"\n", tfra->length_size_of_traf_num ); + lsmash_ifprintf( fp, indent, "length_size_of_trun_num = %"PRIu8"\n", tfra->length_size_of_trun_num ); + lsmash_ifprintf( fp, indent, "length_size_of_sample_num = %"PRIu8"\n", tfra->length_size_of_sample_num ); + lsmash_ifprintf( fp, indent, "number_of_entry = %"PRIu32"\n", tfra->number_of_entry ); + if( tfra->list ) + { + uint32_t i = 0; + for( lsmash_entry_t *entry = tfra->list->head; entry; entry = entry->next ) + { + isom_tfra_location_time_entry_t *data = (isom_tfra_location_time_entry_t *)entry->data; + lsmash_ifprintf( fp, indent++, "entry[%"PRIu32"]\n", i++ ); + lsmash_ifprintf( fp, indent, "time = %"PRIu64"\n", data->time ); + lsmash_ifprintf( fp, indent, "moof_offset = %"PRIu64"\n", data->moof_offset ); + lsmash_ifprintf( fp, indent, "traf_number = %"PRIu32"\n", data->traf_number ); + lsmash_ifprintf( fp, indent, "trun_number = %"PRIu32"\n", data->trun_number ); + lsmash_ifprintf( fp, indent, "sample_number = %"PRIu32"\n", data->sample_number ); + --indent; + } + } + return 0; +} + +static int isom_print_mfro( FILE *fp, lsmash_file_t *file, isom_box_t *box, int level ) +{ + isom_mfro_t *mfro = (isom_mfro_t *)box; + int indent = level; + isom_print_box_common( fp, indent++, box, "Movie Fragment Random Access Offset Box" ); + lsmash_ifprintf( fp, indent, "size = %"PRIu32"\n", mfro->length ); + return 0; +} + +int lsmash_print_movie( lsmash_root_t *root, const char *filename ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( root ) ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_file_t *file = root->file; + if( !file->print || !(file->flags & LSMASH_FILE_MODE_DUMP) ) + return LSMASH_ERR_FUNCTION_PARAM; + FILE *destination; + if( !strcmp( filename, "-" ) ) + destination = stdout; + else + { + destination = lsmash_fopen( filename, "wb" ); + if( !destination ) + return LSMASH_ERR_NAMELESS; + } + fprintf( destination, "[File]\n" ); + fprintf( destination, " size = %"PRIu64"\n", file->size ); + for( lsmash_entry_t *entry = file->print->head; entry; entry = entry->next ) + { + isom_print_entry_t *data = (isom_print_entry_t *)entry->data; + if( !data || !data->box ) + { + fclose( destination ); + return LSMASH_ERR_NAMELESS; + } + int ret = data->func( destination, file, data->box, data->level ); + if( ret < 0 ) + { + fclose( destination ); + return ret; + } + } + fclose( destination ); + return 0; +} + +static isom_print_box_t isom_select_print_func( isom_box_t *box ) +{ + if( box->manager & LSMASH_UNKNOWN_BOX ) + return isom_print_unknown; + if( LSMASH_IS_EXISTING_BOX( box->parent ) ) + { + isom_box_t *parent = box->parent; + if( !lsmash_check_box_type_identical( box->type, ISOM_BOX_TYPE_FREE ) + && !lsmash_check_box_type_identical( box->type, ISOM_BOX_TYPE_SKIP ) + && lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STSD ) ) + { + /* OK, this box is a sample entry. + * Here, determine the suitable sample entry printer by media type if possible. */ + if( isom_check_media_hdlr_from_stsd( (isom_stsd_t *)parent ) ) + { + lsmash_media_type media_type = isom_get_media_type_from_stsd( (isom_stsd_t *)parent ); + if( media_type == ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK ) + return isom_print_visual_description; + else if( media_type == ISOM_MEDIA_HANDLER_TYPE_AUDIO_TRACK ) + return isom_print_audio_description; + else if( media_type == ISOM_MEDIA_HANDLER_TYPE_TEXT_TRACK ) + { + if( lsmash_check_box_type_identical( box->type, QT_CODEC_TYPE_TEXT_TEXT ) ) + return isom_print_text_description; + else if( lsmash_check_box_type_identical( box->type, ISOM_CODEC_TYPE_TX3G_TEXT ) ) + return isom_print_tx3g_description; + } + else if( lsmash_check_box_type_identical( box->type, ISOM_CODEC_TYPE_MP4S_SYSTEM ) ) + return isom_print_mp4s_description; + } + return isom_print_unknown; + } + if( lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_WAVE ) ) + { + if( lsmash_check_box_type_identical( box->type, QT_BOX_TYPE_FRMA ) ) + return isom_print_frma; + else if( lsmash_check_box_type_identical( box->type, QT_BOX_TYPE_ENDA ) ) + return isom_print_enda; + else if( lsmash_check_box_type_identical( box->type, QT_BOX_TYPE_TERMINATOR ) ) + return isom_print_terminator; + else + return isom_print_sample_description_extesion; + } + if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TREF ) ) + return isom_print_track_reference_type; + if( LSMASH_IS_EXISTING_BOX( parent->parent ) ) + { + if( lsmash_check_box_type_identical( parent->parent->type, ISOM_BOX_TYPE_STSD ) ) + return isom_print_sample_description_extesion; + else if( lsmash_check_box_type_identical( parent->parent->type, ISOM_BOX_TYPE_ILST ) + || lsmash_check_box_type_identical( parent->parent->type, QT_BOX_TYPE_ILST ) ) + { + if( parent->type.fourcc == LSMASH_4CC( '-', '-', '-', '-' ) ) + { + if( lsmash_check_box_type_identical( box->type, ISOM_BOX_TYPE_MEAN ) ) + return isom_print_mean; + if( lsmash_check_box_type_identical( box->type, ISOM_BOX_TYPE_NAME ) ) + return isom_print_name; + } + if( lsmash_check_box_type_identical( box->type, ISOM_BOX_TYPE_DATA ) ) + return isom_print_data; + } + } + if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_ILST ) + || lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_ILST ) ) + return isom_print_metaitem; + } + static struct print_box_table_tag + { + lsmash_box_type_t type; + isom_print_box_t func; + } print_box_table[128] = { { LSMASH_BOX_TYPE_INITIALIZER, NULL } }; + if( !print_box_table[0].func ) + { + /* Initialize the table. */ + int i = 0; +#define ADD_PRINT_BOX_TABLE_ELEMENT( type, func ) print_box_table[i++] = (struct print_box_table_tag){ type, func } + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_FTYP, isom_print_ftyp ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_STYP, isom_print_styp ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_SIDX, isom_print_sidx ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_MOOV, isom_print_moov ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_MVHD, isom_print_mvhd ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_IODS, isom_print_iods ); + ADD_PRINT_BOX_TABLE_ELEMENT( QT_BOX_TYPE_CTAB, isom_print_ctab ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_TRAK, isom_print_trak ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_TKHD, isom_print_tkhd ); + ADD_PRINT_BOX_TABLE_ELEMENT( QT_BOX_TYPE_TAPT, isom_print_tapt ); + ADD_PRINT_BOX_TABLE_ELEMENT( QT_BOX_TYPE_CLEF, isom_print_clef ); + ADD_PRINT_BOX_TABLE_ELEMENT( QT_BOX_TYPE_PROF, isom_print_prof ); + ADD_PRINT_BOX_TABLE_ELEMENT( QT_BOX_TYPE_ENOF, isom_print_enof ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_EDTS, isom_print_edts ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_ELST, isom_print_elst ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_TREF, isom_print_tref ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_MDIA, isom_print_mdia ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_MDHD, isom_print_mdhd ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_HDLR, isom_print_hdlr ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_MINF, isom_print_minf ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_VMHD, isom_print_vmhd ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_SMHD, isom_print_smhd ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_HMHD, isom_print_hmhd ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_NMHD, isom_print_nmhd ); + ADD_PRINT_BOX_TABLE_ELEMENT( QT_BOX_TYPE_GMHD, isom_print_gmhd ); + ADD_PRINT_BOX_TABLE_ELEMENT( QT_BOX_TYPE_GMIN, isom_print_gmin ); + ADD_PRINT_BOX_TABLE_ELEMENT( QT_BOX_TYPE_TEXT, isom_print_text ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_DINF, isom_print_dinf ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_DREF, isom_print_dref ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_URL, isom_print_url ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_STBL, isom_print_stbl ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_STSD, isom_print_stsd ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_CLAP, isom_print_clap ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_PASP, isom_print_pasp ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_COLR, isom_print_colr ); + ADD_PRINT_BOX_TABLE_ELEMENT( QT_BOX_TYPE_COLR, isom_print_colr ); + ADD_PRINT_BOX_TABLE_ELEMENT( QT_BOX_TYPE_GLBL, isom_print_glbl ); + ADD_PRINT_BOX_TABLE_ELEMENT( QT_BOX_TYPE_GAMA, isom_print_gama ); + ADD_PRINT_BOX_TABLE_ELEMENT( QT_BOX_TYPE_FIEL, isom_print_fiel ); + ADD_PRINT_BOX_TABLE_ELEMENT( QT_BOX_TYPE_CLLI, isom_print_clli ); + ADD_PRINT_BOX_TABLE_ELEMENT( QT_BOX_TYPE_MDCV, isom_print_mdcv ); + ADD_PRINT_BOX_TABLE_ELEMENT( QT_BOX_TYPE_CSPC, isom_print_cspc ); + ADD_PRINT_BOX_TABLE_ELEMENT( QT_BOX_TYPE_SGBT, isom_print_sgbt ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_STSL, isom_print_stsl ); + ADD_PRINT_BOX_TABLE_ELEMENT( QT_BOX_TYPE_WAVE, isom_print_wave ); + ADD_PRINT_BOX_TABLE_ELEMENT( QT_BOX_TYPE_CHAN, isom_print_chan ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_SRAT, isom_print_srat ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_FTAB, isom_print_ftab ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_STTS, isom_print_stts ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_CTTS, isom_print_ctts ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_CSLG, isom_print_cslg ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_STSS, isom_print_stss ); + ADD_PRINT_BOX_TABLE_ELEMENT( QT_BOX_TYPE_STPS, isom_print_stps ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_SDTP, isom_print_sdtp ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_STSC, isom_print_stsc ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_STSZ, isom_print_stsz ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_STZ2, isom_print_stz2 ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_STCO, isom_print_stco ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_CO64, isom_print_stco ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_SGPD, isom_print_sgpd ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_SBGP, isom_print_sbgp ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_UDTA, isom_print_udta ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_CHPL, isom_print_chpl ); + ADD_PRINT_BOX_TABLE_ELEMENT( QT_BOX_TYPE_WLOC, isom_print_WLOC ); + ADD_PRINT_BOX_TABLE_ELEMENT( QT_BOX_TYPE_LOOP, isom_print_LOOP ); + ADD_PRINT_BOX_TABLE_ELEMENT( QT_BOX_TYPE_SELO, isom_print_SelO ); + ADD_PRINT_BOX_TABLE_ELEMENT( QT_BOX_TYPE_ALLF, isom_print_AllF ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_CPRT, isom_print_cprt ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_MVEX, isom_print_mvex ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_MEHD, isom_print_mehd ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_TREX, isom_print_trex ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_MOOF, isom_print_moof ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_MFHD, isom_print_mfhd ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_TRAF, isom_print_traf ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_TFHD, isom_print_tfhd ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_TFDT, isom_print_tfdt ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_TRUN, isom_print_trun ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_FREE, isom_print_free ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_SKIP, isom_print_free ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_MDAT, isom_print_mdat ); + ADD_PRINT_BOX_TABLE_ELEMENT( QT_BOX_TYPE_KEYS, isom_print_keys ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_META, isom_print_meta ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_ILST, isom_print_ilst ); + ADD_PRINT_BOX_TABLE_ELEMENT( QT_BOX_TYPE_ILST, isom_print_ilst ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_MFRA, isom_print_mfra ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_TFRA, isom_print_tfra ); + ADD_PRINT_BOX_TABLE_ELEMENT( ISOM_BOX_TYPE_MFRO, isom_print_mfro ); + ADD_PRINT_BOX_TABLE_ELEMENT( LSMASH_BOX_TYPE_UNSPECIFIED, NULL ); +#undef ADD_PRINT_BOX_TABLE_ELEMENT + } + for( int i = 0; print_box_table[i].func; i++ ) + if( lsmash_check_box_type_identical( box->type, print_box_table[i].type ) ) + return print_box_table[i].func; + return isom_print_unknown; +} + +static inline void isom_print_remove_plastic_box( isom_box_t *box ) +{ + if( box->manager & LSMASH_ABSENT_IN_FILE ) + /* free flagged box */ + isom_remove_box_by_itself( box ); +} + +int isom_add_print_func( lsmash_file_t *file, void *box, int level ) +{ + if( !(file->flags & LSMASH_FILE_MODE_DUMP) ) + { + isom_print_remove_plastic_box( box ); + return 0; + } + isom_print_entry_t *data = lsmash_malloc( sizeof(isom_print_entry_t) ); + if( !data ) + { + isom_print_remove_plastic_box( box ); + return LSMASH_ERR_MEMORY_ALLOC; + } + data->level = level; + data->box = (isom_box_t *)box; + data->func = isom_select_print_func( (isom_box_t *)box ); + assert( data->func ); + if( lsmash_list_add_entry( file->print, data ) < 0 ) + { + isom_print_remove_plastic_box( data->box ); + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + return 0; +} + +static void isom_remove_print_func( isom_print_entry_t *data ) +{ + if( !data || !data->box ) + return; + isom_print_remove_plastic_box( data->box ); + lsmash_free( data ); +} + +void isom_printer_destory_list( lsmash_file_t *file ) +{ + lsmash_list_destroy( file->print ); + file->print = NULL; +} + +lsmash_entry_list_t *isom_printer_create_list( void ) +{ + return lsmash_list_create( isom_remove_print_func ); +} diff --git a/3rdparty/l-smash/core/print.h b/3rdparty/l-smash/core/print.h new file mode 100644 index 000000000..d8e5fc3e4 --- /dev/null +++ b/3rdparty/l-smash/core/print.h @@ -0,0 +1,30 @@ +/***************************************************************************** + * print.h + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#ifndef LSMASH_PRINT_H +#define LSMASH_PRINT_H + +int isom_add_print_func( lsmash_file_t *file, void *box, int level ); +void isom_printer_destory_list( lsmash_file_t *file ); +lsmash_entry_list_t *isom_printer_create_list( void ); + +#endif /* LSMASH_PRINT_H */ diff --git a/3rdparty/l-smash/core/read.c b/3rdparty/l-smash/core/read.c new file mode 100644 index 000000000..a754b5d6d --- /dev/null +++ b/3rdparty/l-smash/core/read.c @@ -0,0 +1,2996 @@ +/***************************************************************************** + * read.c + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include +#include +#include + +#include "box.h" +#include "box_default.h" +#include "file.h" +#include "print.h" +#include "read.h" +#include "write.h" + +#include "codecs/mp4a.h" +#include "codecs/mp4sys.h" +#include "codecs/description.h" + +static int isom_bs_read_box_common( lsmash_bs_t *bs, isom_box_t *box ) +{ + assert( bs && box && box->file ); + /* Reset the counter so that we can use it to get position within the box. */ + lsmash_bs_reset_counter( bs ); + /* Read the common fields of box. */ + box->pos = lsmash_bs_get_stream_pos( bs ); + if( bs->eob ) + /* No more read. */ + return 1; + /* Read size and type. */ + box->size = lsmash_bs_get_be32( bs ); + box->type.fourcc = lsmash_bs_get_be32( bs ); + /* If size is set to 1, the actual size is repersented in the next 8 bytes. + * If size is set to 0, this box ends at the end of the stream. */ + if( box->size == 1 ) + box->size = lsmash_bs_get_be64( bs ); + if( box->size == 0 ) + { + /* This box is the last box in the stream. */ + box->manager |= LSMASH_LAST_BOX; + if( !bs->unseekable ) + box->size = bs->written - (lsmash_bs_get_stream_pos( bs ) - lsmash_bs_count( bs )); + else + /* We haven't known the box size yet. + * To get the box size, read the stream until the end of the stream. */ + while( 1 ) + { + int ret = lsmash_bs_read( bs, 1 ); + if( bs->eof || ret < 0 ) + { + /* OK, now we know the box size. */ + box->size = lsmash_bs_count( bs ) + lsmash_bs_get_remaining_buffer_size( bs ); + if( ret < 0 ) + /* This box may end incompletely at the end of the stream. */ + box->manager |= LSMASH_INCOMPLETE_BOX; + break; + } + } + } + /* Here, we don't set up extended box type fields if this box is not a UUID Box. */ + if( box->type.fourcc == ISOM_BOX_TYPE_UUID.fourcc + && box->size >= lsmash_bs_count( bs ) + 16 ) + { + /* Get UUID. */ + lsmash_box_type_t *type = &box->type; + uint64_t temp64 = lsmash_bs_get_be64( bs ); + type->user.fourcc = (temp64 >> 32) & 0xffffffff; + LSMASH_SET_BE32( &type->user.id[0], temp64 ); + temp64 = lsmash_bs_get_be64( bs ); + LSMASH_SET_BE64( &type->user.id[4], temp64 ); + } + return bs->eob; +} + +static int isom_read_fullbox_common_extension( lsmash_bs_t *bs, isom_box_t *box ) +{ + if( !isom_is_fullbox( box ) ) + return 0; + /* Get version and flags. */ + box->version = lsmash_bs_get_byte( bs ); + box->flags = lsmash_bs_get_be24( bs ); + box->manager |= LSMASH_FULLBOX; + return 0; +} + +/* Don't copy destructor since a destructor is defined as box specific. */ +static void isom_basebox_common_copy( isom_box_t *dst, const isom_box_t *src ) +{ + dst->root = src->root; + dst->file = src->file; + dst->parent = src->parent; + dst->manager = src->manager; + dst->pos = src->pos; + dst->size = src->size; + dst->type = src->type; +} + +static void isom_fullbox_common_copy( isom_box_t *dst, const isom_box_t *src ) +{ + dst->root = src->root; + dst->file = src->file; + dst->parent = src->parent; + dst->manager = src->manager; + dst->pos = src->pos; + dst->size = src->size; + dst->type = src->type; + dst->version = src->version; + dst->flags = src->flags; +} + +static void isom_box_common_copy( void *dst, const void *src ) +{ + assert( LSMASH_IS_EXISTING_BOX( (isom_box_t *)dst ) + && LSMASH_IS_EXISTING_BOX( (isom_box_t *)src ) ); + if( lsmash_check_box_type_identical( ((isom_box_t *)src)->type, ISOM_BOX_TYPE_STSD ) ) + { + isom_basebox_common_copy( (isom_box_t *)dst, (isom_box_t *)src ); + return; + } + if( isom_is_fullbox( src ) ) + isom_fullbox_common_copy( (isom_box_t *)dst, (isom_box_t *)src ); + else + isom_basebox_common_copy( (isom_box_t *)dst, (isom_box_t *)src ); +} + +static void isom_skip_box_rest( lsmash_bs_t *bs, isom_box_t *box ) +{ + if( box->manager & LSMASH_LAST_BOX ) + { + box->size = (box->manager & LSMASH_FULLBOX) ? ISOM_FULLBOX_COMMON_SIZE : ISOM_BASEBOX_COMMON_SIZE; + uint64_t start = lsmash_bs_get_stream_pos( bs ); + if( !bs->unseekable ) + lsmash_bs_read_seek( bs, 0, SEEK_END ); + else + while( !bs->eob ) + lsmash_bs_skip_bytes( bs, UINT32_MAX ); + uint64_t end = lsmash_bs_get_stream_pos( bs ); + box->size += end - start; + return; + } + uint64_t skip_bytes = box->size - lsmash_bs_count( bs ); + if( !bs->unseekable ) + { + /* The stream is seekable. So, skip by seeking the stream. */ + uint64_t start = lsmash_bs_get_stream_pos( bs ); + lsmash_bs_read_seek( bs, skip_bytes, SEEK_CUR ); + uint64_t end = lsmash_bs_get_stream_pos( bs ); + if( end - start != skip_bytes ) + /* not match size */ + box->manager |= LSMASH_INCOMPLETE_BOX; + return; + } + /* The stream is unseekable. So, skip by reading the stream. */ + lsmash_bs_skip_bytes_64( bs, skip_bytes ); + if( box->size > lsmash_bs_count( bs ) ) + box->manager |= LSMASH_INCOMPLETE_BOX; +} + +static void isom_validate_box_size( lsmash_bs_t *bs, isom_box_t *box ) +{ + uint64_t pos = lsmash_bs_count( bs ); + if( box->manager & LSMASH_LAST_BOX ) + { + box->size = pos; + return; + } + if( box->size < pos ) + { + fprintf( stderr, "[%s] box has less bytes than expected: %"PRId64"\n", isom_4cc2str( box->type.fourcc ), pos - box->size ); + box->size = pos; + } + else if( box->size > pos ) + { + /* The box probably has extra padding bytes at the end. */ + fprintf( stderr, "[%s] box has more bytes than expected: %"PRId64"\n", isom_4cc2str( box->type.fourcc ), box->size - pos ); + isom_skip_box_rest( bs, box ); + } +} + +static int isom_read_children( lsmash_file_t *file, isom_box_t *box, void *parent, int level ) +{ + int ret; + lsmash_bs_t *bs = file->bs; + isom_box_t *parent_box = (isom_box_t *)parent; + uint64_t parent_pos = lsmash_bs_count( bs ); + while( !(ret = isom_read_box( file, box, parent_box, parent_pos, level )) ) + { + parent_pos += box->size; + if( parent_box->size <= parent_pos || bs->eob || bs->error ) + break; + } + box->size = parent_pos; /* for file size */ + return ret; +} + +static int isom_read_leaf_box_common_last_process( lsmash_file_t *file, isom_box_t *box, int level, void *instance ) +{ + isom_validate_box_size( file->bs, box ); + isom_box_common_copy( instance, box ); + return isom_add_print_func( file, instance, level ); +} + +static int isom_read_unknown_box( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + lsmash_bs_t *bs = file->bs; + uint64_t read_size = box->size - lsmash_bs_count( bs ); + if( box->manager & LSMASH_INCOMPLETE_BOX ) + return LSMASH_ERR_INVALID_DATA; + isom_unknown_box_t *unknown = ALLOCATE_BOX( unknown ); + if( LSMASH_IS_NON_EXISTING_BOX( unknown ) ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( &parent->extensions, unknown ) < 0 ) + { + isom_remove_box_by_itself( unknown ); + return LSMASH_ERR_MEMORY_ALLOC; + } + isom_box_common_copy( unknown, box ); + unknown->manager |= LSMASH_UNKNOWN_BOX; + unknown->destruct = (isom_extension_destructor_t)isom_remove_unknown_box; + isom_set_box_writer( (isom_box_t *)unknown ); + if( read_size ) + { + unknown->unknown_field = lsmash_bs_get_bytes( bs, read_size ); + if( unknown->unknown_field ) + unknown->unknown_size = read_size; + else + unknown->manager |= LSMASH_INCOMPLETE_BOX; + } + if( !(file->flags & LSMASH_FILE_MODE_DUMP) ) + return 0; + /* Create a dummy for dump. */ + isom_dummy_t *dummy = ALLOCATE_BOX( dummy ); + if( LSMASH_IS_NON_EXISTING_BOX( dummy ) ) + return LSMASH_ERR_MEMORY_ALLOC; + box->manager |= LSMASH_ABSENT_IN_FILE | LSMASH_UNKNOWN_BOX; + isom_box_common_copy( dummy, box ); + int ret = isom_add_print_func( file, dummy, level ); + if( ret < 0 ) + { + isom_remove_box_by_itself( dummy ); + return ret; + } + return 0; +} + +#define ADD_BOX( box_name, parent_type ) \ + isom_##box_name##_t *box_name = isom_add_##box_name( (parent_type *)parent ); \ + if( LSMASH_IS_NON_EXISTING_BOX( box_name ) ) \ + return LSMASH_ERR_NAMELESS + +static int isom_read_ftyp( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, LSMASH_BOX_TYPE_UNSPECIFIED ) + || LSMASH_IS_EXISTING_BOX( ((lsmash_file_t *)parent)->ftyp ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( ftyp, lsmash_file_t ); + lsmash_bs_t *bs = file->bs; + ftyp->major_brand = lsmash_bs_get_be32( bs ); + ftyp->minor_version = lsmash_bs_get_be32( bs ); + uint64_t pos = lsmash_bs_count( bs ); + ftyp->brand_count = box->size > pos ? (box->size - pos) / sizeof(uint32_t) : 0; + size_t alloc_size = ftyp->brand_count * sizeof(uint32_t); + ftyp->compatible_brands = ftyp->brand_count ? lsmash_malloc( alloc_size ) : NULL; + if( ftyp->brand_count && !ftyp->compatible_brands ) + return LSMASH_ERR_MEMORY_ALLOC; + for( uint32_t i = 0; i < ftyp->brand_count; i++ ) + ftyp->compatible_brands[i] = lsmash_bs_get_be32( bs ); + if( !file->compatible_brands && ftyp->compatible_brands ) + { + file->compatible_brands = lsmash_memdup( ftyp->compatible_brands, alloc_size ); + if( !file->compatible_brands ) + return LSMASH_ERR_MEMORY_ALLOC; + file->brand_count = ftyp->brand_count; + } + return isom_read_leaf_box_common_last_process( file, box, level, ftyp ); +} + +static int isom_read_styp( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, LSMASH_BOX_TYPE_UNSPECIFIED ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( styp, lsmash_file_t ); + lsmash_bs_t *bs = file->bs; + styp->major_brand = lsmash_bs_get_be32( bs ); + styp->minor_version = lsmash_bs_get_be32( bs ); + uint64_t pos = lsmash_bs_count( bs ); + styp->brand_count = box->size > pos ? (box->size - pos) / sizeof(uint32_t) : 0; + size_t alloc_size = styp->brand_count * sizeof(uint32_t); + styp->compatible_brands = styp->brand_count ? lsmash_malloc( alloc_size ) : NULL; + if( styp->brand_count && !styp->compatible_brands ) + return LSMASH_ERR_MEMORY_ALLOC; + for( uint32_t i = 0; i < styp->brand_count; i++ ) + styp->compatible_brands[i] = lsmash_bs_get_be32( bs ); + if( !file->compatible_brands && styp->compatible_brands ) + { + file->compatible_brands = lsmash_memdup( styp->compatible_brands, alloc_size ); + if( !file->compatible_brands ) + return LSMASH_ERR_MEMORY_ALLOC; + file->brand_count = styp->brand_count; + } + file->flags |= LSMASH_FILE_MODE_SEGMENT; + return isom_read_leaf_box_common_last_process( file, box, level, styp ); +} + +static int isom_read_sidx( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, LSMASH_BOX_TYPE_UNSPECIFIED ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( sidx, lsmash_file_t ); + lsmash_bs_t *bs = file->bs; + sidx->reference_ID = lsmash_bs_get_be32( bs ); + sidx->timescale = lsmash_bs_get_be32( bs ); + if( box->version == 0 ) + { + sidx->earliest_presentation_time = lsmash_bs_get_be32( bs ); + sidx->first_offset = lsmash_bs_get_be32( bs ); + } + else + { + sidx->earliest_presentation_time = lsmash_bs_get_be64( bs ); + sidx->first_offset = lsmash_bs_get_be64( bs ); + } + sidx->reserved = lsmash_bs_get_be16( bs ); + sidx->reference_count = lsmash_bs_get_be16( bs ); + for( uint64_t pos = lsmash_bs_count( bs ); + pos < box->size && sidx->list->entry_count < sidx->reference_count; + pos = lsmash_bs_count( bs ) ) + { + isom_sidx_referenced_item_t *data = lsmash_malloc( sizeof(isom_sidx_referenced_item_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( sidx->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + uint32_t temp32; + temp32 = lsmash_bs_get_be32( bs ); + data->reference_type = (temp32 >> 31) & 0x00000001; + data->reference_size = temp32 & 0x7FFFFFFF; + data->subsegment_duration = lsmash_bs_get_be32( bs ); + temp32 = lsmash_bs_get_be32( bs ); + data->starts_with_SAP = (temp32 >> 31) & 0x00000001; + data->SAP_type = (temp32 >> 28) & 0x00000007; + data->SAP_delta_time = temp32 & 0x0FFFFFFF; + } + file->flags |= LSMASH_FILE_MODE_INDEX; + return isom_read_leaf_box_common_last_process( file, box, level, sidx ); +} + +static int isom_read_moov( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, LSMASH_BOX_TYPE_UNSPECIFIED ) + || LSMASH_IS_EXISTING_BOX( ((lsmash_file_t *)parent)->moov ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( moov, lsmash_file_t ); + file->flags |= LSMASH_FILE_MODE_INITIALIZATION; + file->initializer = file; + isom_box_common_copy( moov, box ); + int ret = isom_add_print_func( file, moov, level ); + if( ret < 0 ) + return ret; + return isom_read_children( file, box, moov, level ); +} + +static int isom_read_mvhd( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MOOV ) + || LSMASH_IS_EXISTING_BOX( ((isom_moov_t *)parent)->mvhd ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( mvhd, isom_moov_t ); + lsmash_bs_t *bs = file->bs; + if( box->version ) + { + mvhd->creation_time = lsmash_bs_get_be64( bs ); + mvhd->modification_time = lsmash_bs_get_be64( bs ); + mvhd->timescale = lsmash_bs_get_be32( bs ); + mvhd->duration = lsmash_bs_get_be64( bs ); + } + else + { + mvhd->creation_time = lsmash_bs_get_be32( bs ); + mvhd->modification_time = lsmash_bs_get_be32( bs ); + mvhd->timescale = lsmash_bs_get_be32( bs ); + mvhd->duration = lsmash_bs_get_be32( bs ); + } + mvhd->rate = lsmash_bs_get_be32( bs ); + mvhd->volume = lsmash_bs_get_be16( bs ); + mvhd->reserved = lsmash_bs_get_be16( bs ); + mvhd->preferredLong[0] = lsmash_bs_get_be32( bs ); + mvhd->preferredLong[1] = lsmash_bs_get_be32( bs ); + for( int i = 0; i < 9; i++ ) + mvhd->matrix[i] = lsmash_bs_get_be32( bs ); + mvhd->previewTime = lsmash_bs_get_be32( bs ); + mvhd->previewDuration = lsmash_bs_get_be32( bs ); + mvhd->posterTime = lsmash_bs_get_be32( bs ); + mvhd->selectionTime = lsmash_bs_get_be32( bs ); + mvhd->selectionDuration = lsmash_bs_get_be32( bs ); + mvhd->currentTime = lsmash_bs_get_be32( bs ); + mvhd->next_track_ID = lsmash_bs_get_be32( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, mvhd ); +} + +static int isom_read_iods( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MOOV ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( iods, isom_moov_t ); + lsmash_bs_t *bs = file->bs; + iods->OD = mp4sys_get_descriptor( bs, NULL ); + if( !iods->OD ) + return LSMASH_ERR_INVALID_DATA; + return isom_read_leaf_box_common_last_process( file, box, level, iods ); +} + +static int isom_read_qt_color_table( lsmash_bs_t *bs, isom_qt_color_table_t *color_table ) +{ + color_table->seed = lsmash_bs_get_be32( bs ); + color_table->flags = lsmash_bs_get_be16( bs ); + color_table->size = lsmash_bs_get_be16( bs ); + if( bs->eob ) + return LSMASH_ERR_INVALID_DATA; + isom_qt_color_array_t *array = lsmash_malloc_zero( (color_table->size + 1) * sizeof(isom_qt_color_array_t) ); + if( !array ) + return LSMASH_ERR_MEMORY_ALLOC; + color_table->array = array; + for( uint16_t i = 0; i <= color_table->size; i++ ) + { + uint64_t color = lsmash_bs_get_be64( bs ); + array[i].value = (color >> 48) & 0xffff; + array[i].r = (color >> 32) & 0xffff; + array[i].g = (color >> 16) & 0xffff; + array[i].b = color & 0xffff; + } + return 0; +} + +static int isom_read_ctab( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + ADD_BOX( ctab, isom_moov_t ); + lsmash_bs_t *bs = file->bs; + int ret = isom_read_qt_color_table( bs, &ctab->color_table ); + if( ret < 0 ) + return ret; + return isom_read_leaf_box_common_last_process( file, box, level, ctab ); +} + +static int isom_read_trak( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MOOV ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( trak, isom_moov_t ); + box->parent = parent; + box->root = file->root; + box->file = file; + isom_box_common_copy( trak, box ); + int ret = isom_add_print_func( file, trak, level ); + if( ret < 0 ) + return ret; + return isom_read_children( file, box, trak, level ); +} + +static int isom_read_tkhd( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TRAK ) + || LSMASH_IS_EXISTING_BOX( ((isom_trak_t *)parent)->tkhd ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( tkhd, isom_trak_t ); + lsmash_bs_t *bs = file->bs; + if( box->version ) + { + tkhd->creation_time = lsmash_bs_get_be64( bs ); + tkhd->modification_time = lsmash_bs_get_be64( bs ); + tkhd->track_ID = lsmash_bs_get_be32( bs ); + tkhd->reserved1 = lsmash_bs_get_be32( bs ); + tkhd->duration = lsmash_bs_get_be64( bs ); + } + else + { + tkhd->creation_time = lsmash_bs_get_be32( bs ); + tkhd->modification_time = lsmash_bs_get_be32( bs ); + tkhd->track_ID = lsmash_bs_get_be32( bs ); + tkhd->reserved1 = lsmash_bs_get_be32( bs ); + tkhd->duration = lsmash_bs_get_be32( bs ); + } + tkhd->reserved2[0] = lsmash_bs_get_be32( bs ); + tkhd->reserved2[1] = lsmash_bs_get_be32( bs ); + tkhd->layer = lsmash_bs_get_be16( bs ); + tkhd->alternate_group = lsmash_bs_get_be16( bs ); + tkhd->volume = lsmash_bs_get_be16( bs ); + tkhd->reserved3 = lsmash_bs_get_be16( bs ); + for( int i = 0; i < 9; i++ ) + tkhd->matrix[i] = lsmash_bs_get_be32( bs ); + tkhd->width = lsmash_bs_get_be32( bs ); + tkhd->height = lsmash_bs_get_be32( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, tkhd ); +} + +static int isom_read_tapt( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TRAK ) + || LSMASH_IS_EXISTING_BOX( ((isom_trak_t *)parent)->tapt ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( tapt, isom_trak_t ); + isom_box_common_copy( tapt, box ); + int ret = isom_add_print_func( file, tapt, level ); + if( ret < 0 ) + return ret; + return isom_read_children( file, box, tapt, level ); +} + +static int isom_read_clef( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_TAPT ) + || LSMASH_IS_EXISTING_BOX( ((isom_tapt_t *)parent)->clef ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( clef, isom_tapt_t ); + lsmash_bs_t *bs = file->bs; + clef->width = lsmash_bs_get_be32( bs ); + clef->height = lsmash_bs_get_be32( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, clef ); +} + +static int isom_read_prof( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_TAPT ) + || LSMASH_IS_EXISTING_BOX( ((isom_tapt_t *)parent)->prof ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( prof, isom_tapt_t ); + lsmash_bs_t *bs = file->bs; + prof->width = lsmash_bs_get_be32( bs ); + prof->height = lsmash_bs_get_be32( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, prof ); +} + +static int isom_read_enof( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_TAPT ) + || LSMASH_IS_EXISTING_BOX( ((isom_tapt_t *)parent)->enof ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( enof, isom_tapt_t ); + lsmash_bs_t *bs = file->bs; + enof->width = lsmash_bs_get_be32( bs ); + enof->height = lsmash_bs_get_be32( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, enof ); +} + +static int isom_read_edts( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TRAK ) + || LSMASH_IS_EXISTING_BOX( ((isom_trak_t *)parent)->edts ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( edts, isom_trak_t ); + isom_box_common_copy( edts, box ); + if( isom_add_print_func( file, edts, level ) < 0 ) + return -1; + return isom_read_children( file, box, edts, level ); +} + +static int isom_read_elst( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_EDTS ) + || LSMASH_IS_EXISTING_BOX( ((isom_edts_t *)parent)->elst ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( elst, isom_edts_t ); + lsmash_bs_t *bs = file->bs; + uint32_t entry_count = lsmash_bs_get_be32( bs ); + for( uint64_t pos = lsmash_bs_count( bs ); pos < box->size && elst->list->entry_count < entry_count; pos = lsmash_bs_count( bs ) ) + { + isom_elst_entry_t *data = lsmash_malloc( sizeof(isom_elst_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( elst->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + if( box->version == 1 ) + { + data->segment_duration = lsmash_bs_get_be64( bs ); + data->media_time = (int64_t)lsmash_bs_get_be64( bs ); + } + else + { + data->segment_duration = lsmash_bs_get_be32( bs ); + data->media_time = (int32_t)lsmash_bs_get_be32( bs ); + } + data->media_rate = lsmash_bs_get_be32( bs ); + } + return isom_read_leaf_box_common_last_process( file, box, level, elst ); +} + +static int isom_read_tref( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TRAK ) + || LSMASH_IS_EXISTING_BOX( ((isom_trak_t *)parent)->tref ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( tref, isom_trak_t ); + isom_box_common_copy( tref, box ); + int ret = isom_add_print_func( file, tref, level ); + if( ret < 0 ) + return ret; + return isom_read_children( file, box, tref, level ); +} + +static int isom_read_track_reference_type( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TREF ) ) + return isom_read_unknown_box( file, box, parent, level ); + isom_tref_type_t *ref = isom_add_track_reference_type( (isom_tref_t *)parent, box->type.fourcc ); + if( !ref ) + return LSMASH_ERR_NAMELESS; + lsmash_bs_t *bs = file->bs; + ref->ref_count = (box->size - lsmash_bs_count( bs ) ) / sizeof(uint32_t); + if( ref->ref_count ) + { + ref->track_ID = lsmash_malloc( ref->ref_count * sizeof(uint32_t) ); + if( !ref->track_ID ) + { + ref->ref_count = 0; + return LSMASH_ERR_MEMORY_ALLOC; + } + for( uint32_t i = 0; i < ref->ref_count; i++ ) + ref->track_ID[i] = lsmash_bs_get_be32( bs ); + } + return isom_read_leaf_box_common_last_process( file, box, level, ref ); +} + +static int isom_read_mdia( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TRAK ) + || LSMASH_IS_EXISTING_BOX( ((isom_trak_t *)parent)->mdia ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( mdia, isom_trak_t ); + isom_box_common_copy( mdia, box ); + int ret = isom_add_print_func( file, mdia, level ); + if( ret < 0 ) + return ret; + return isom_read_children( file, box, mdia, level ); +} + +static int isom_read_mdhd( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MDIA ) + || LSMASH_IS_EXISTING_BOX( ((isom_mdia_t *)parent)->mdhd ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( mdhd, isom_mdia_t ); + lsmash_bs_t *bs = file->bs; + if( box->version ) + { + mdhd->creation_time = lsmash_bs_get_be64( bs ); + mdhd->modification_time = lsmash_bs_get_be64( bs ); + mdhd->timescale = lsmash_bs_get_be32( bs ); + mdhd->duration = lsmash_bs_get_be64( bs ); + } + else + { + mdhd->creation_time = lsmash_bs_get_be32( bs ); + mdhd->modification_time = lsmash_bs_get_be32( bs ); + mdhd->timescale = lsmash_bs_get_be32( bs ); + mdhd->duration = lsmash_bs_get_be32( bs ); + } + mdhd->language = lsmash_bs_get_be16( bs ); + mdhd->quality = lsmash_bs_get_be16( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, mdhd ); +} + +static int isom_read_hdlr( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( (!lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MDIA ) + && !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_META ) + && !lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_META ) + && !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MINF )) + || (lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MDIA ) && LSMASH_IS_EXISTING_BOX( ((isom_mdia_t *)parent)->hdlr )) + || (lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_META ) && LSMASH_IS_EXISTING_BOX( ((isom_meta_t *)parent)->hdlr )) + || (lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_META ) && LSMASH_IS_EXISTING_BOX( ((isom_meta_t *)parent)->hdlr )) + || (lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MINF ) && LSMASH_IS_EXISTING_BOX( ((isom_minf_t *)parent)->hdlr )) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( hdlr, void ); + lsmash_bs_t *bs = file->bs; + hdlr->componentType = lsmash_bs_get_be32( bs ); + hdlr->componentSubtype = lsmash_bs_get_be32( bs ); + hdlr->componentManufacturer = lsmash_bs_get_be32( bs ); + hdlr->componentFlags = lsmash_bs_get_be32( bs ); + hdlr->componentFlagsMask = lsmash_bs_get_be32( bs ); + uint64_t pos = lsmash_bs_count( bs ); + hdlr->componentName_length = box->size - pos; + if( hdlr->componentName_length ) + { + hdlr->componentName = lsmash_malloc( hdlr->componentName_length ); + if( !hdlr->componentName ) + return LSMASH_ERR_MEMORY_ALLOC; + for( uint32_t i = 0; pos < box->size; pos = lsmash_bs_count( bs ) ) + hdlr->componentName[i++] = lsmash_bs_get_byte( bs ); + } + return isom_read_leaf_box_common_last_process( file, box, level, hdlr ); +} + +static int isom_read_minf( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MDIA ) + || LSMASH_IS_EXISTING_BOX( ((isom_mdia_t *)parent)->minf ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( minf, isom_mdia_t ); + isom_box_common_copy( minf, box ); + int ret = isom_add_print_func( file, minf, level ); + if( ret < 0 ) + return ret; + return isom_read_children( file, box, minf, level ); +} + +static int isom_read_vmhd( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MINF ) + || LSMASH_IS_EXISTING_BOX( ((isom_minf_t *)parent)->vmhd ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( vmhd, isom_minf_t ); + lsmash_bs_t *bs = file->bs; + vmhd->graphicsmode = lsmash_bs_get_be16( bs ); + for( int i = 0; i < 3; i++ ) + vmhd->opcolor[i] = lsmash_bs_get_be16( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, vmhd ); +} + +static int isom_read_smhd( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MINF ) + || LSMASH_IS_EXISTING_BOX( ((isom_minf_t *)parent)->smhd ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( smhd, isom_minf_t ); + lsmash_bs_t *bs = file->bs; + smhd->balance = lsmash_bs_get_be16( bs ); + smhd->reserved = lsmash_bs_get_be16( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, smhd ); +} + +static int isom_read_hmhd( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MINF ) + || LSMASH_IS_EXISTING_BOX( ((isom_minf_t *)parent)->hmhd ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( hmhd, isom_minf_t ); + lsmash_bs_t *bs = file->bs; + hmhd->maxPDUsize = lsmash_bs_get_be16( bs ); + hmhd->avgPDUsize = lsmash_bs_get_be16( bs ); + hmhd->maxbitrate = lsmash_bs_get_be32( bs ); + hmhd->avgbitrate = lsmash_bs_get_be32( bs ); + hmhd->reserved = lsmash_bs_get_be32( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, hmhd ); +} + +static int isom_read_nmhd( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MINF ) + || LSMASH_IS_EXISTING_BOX( ((isom_minf_t *)parent)->nmhd ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( nmhd, isom_minf_t ); + return isom_read_leaf_box_common_last_process( file, box, level, nmhd ); +} + +static int isom_read_gmhd( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MINF ) + || LSMASH_IS_EXISTING_BOX( ((isom_minf_t *)parent)->gmhd ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( gmhd, isom_minf_t ); + isom_box_common_copy( gmhd, box ); + int ret = isom_add_print_func( file, gmhd, level ); + if( ret < 0 ) + return ret; + return isom_read_children( file, box, gmhd, level ); +} + +static int isom_read_gmin( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_GMHD ) + || LSMASH_IS_EXISTING_BOX( ((isom_gmhd_t *)parent)->gmin ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( gmin, isom_gmhd_t ); + lsmash_bs_t *bs = file->bs; + gmin->graphicsmode = lsmash_bs_get_be16( bs ); + for( int i = 0; i < 3; i++ ) + gmin->opcolor[i] = lsmash_bs_get_be16( bs ); + gmin->balance = lsmash_bs_get_be16( bs ); + gmin->reserved = lsmash_bs_get_be16( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, gmin ); +} + +static int isom_read_text( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_GMHD ) + || LSMASH_IS_EXISTING_BOX( ((isom_gmhd_t *)parent)->text ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( text, isom_gmhd_t ); + lsmash_bs_t *bs = file->bs; + for( int i = 0; i < 9; i++ ) + text->matrix[i] = lsmash_bs_get_be32( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, text ); +} + +static int isom_read_dinf( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( (!lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MINF ) + && !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_META ) + && !lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_META )) + || (lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MINF ) && LSMASH_IS_EXISTING_BOX( ((isom_minf_t *)parent)->dinf )) + || (lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_META ) && LSMASH_IS_EXISTING_BOX( ((isom_meta_t *)parent)->dinf )) + || (lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_META ) && LSMASH_IS_EXISTING_BOX( ((isom_meta_t *)parent)->dinf )) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( dinf, void ); + isom_box_common_copy( dinf, box ); + int ret = isom_add_print_func( file, dinf, level ); + if( ret < 0 ) + return ret; + return isom_read_children( file, box, dinf, level ); +} + +static int isom_read_dref( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_DINF ) + || LSMASH_IS_EXISTING_BOX( ((isom_dinf_t *)parent)->dref ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( dref, isom_dinf_t ); + lsmash_bs_t *bs = file->bs; + dref->list.entry_count = lsmash_bs_get_be32( bs ); + isom_box_common_copy( dref, box ); + int ret = isom_add_print_func( file, dref, level ); + if( ret < 0 ) + return ret; + return isom_read_children( file, box, dref, level ); +} + +static int isom_read_dref_entry( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_DREF ) ) + return isom_read_unknown_box( file, box, parent, level ); + isom_dref_t *dref = (isom_dref_t *)parent; + if( !dref->list.head ) + dref->list.entry_count = 0; /* discard entry_count gotten from the file */ + isom_dref_entry_t *ref = isom_add_dref_entry( dref, box->type ); + if( !ref ) + return LSMASH_ERR_NAMELESS; + lsmash_bs_t *bs = file->bs; + if( lsmash_check_box_type_identical( ref->type, ISOM_BOX_TYPE_URL ) ) + { + uint64_t pos = lsmash_bs_count( bs ); + ref->location_length = box->size - pos; + if( ref->location_length ) + { + ref->location = lsmash_malloc( ref->location_length ); + if( !ref->location ) + return LSMASH_ERR_MEMORY_ALLOC; + for( uint32_t i = 0; pos < box->size; pos = lsmash_bs_count( bs ) ) + ref->location[i++] = lsmash_bs_get_byte( bs ); + } + } + if( box->flags & 0x000001 ) + ref->ref_file = ref->file; + box->parent = parent; + return isom_read_leaf_box_common_last_process( file, box, level, ref ); +} + +static int isom_read_stbl( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MINF ) + || LSMASH_IS_EXISTING_BOX( ((isom_minf_t *)parent)->stbl ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( stbl, isom_minf_t ); + isom_box_common_copy( stbl, box ); + int ret = isom_add_print_func( file, stbl, level ); + if( ret < 0 ) + return ret; + return isom_read_children( file, box, stbl, level ); +} + +static int isom_read_stsd( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STBL ) + || LSMASH_IS_EXISTING_BOX( ((isom_stbl_t *)parent)->stsd ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( stsd, isom_stbl_t ); + lsmash_bs_t *bs = file->bs; + stsd->entry_count = lsmash_bs_get_be32( bs ); + isom_box_common_copy( stsd, box ); + int ret = isom_add_print_func( file, stsd, level ); + if( ret < 0 ) + return ret; + uint64_t stsd_pos = lsmash_bs_count( bs ); + for( uint32_t i = 0; i < stsd->entry_count || (stsd_pos + ISOM_BASEBOX_COMMON_SIZE) <= stsd->size; i++ ) + { + if( (ret = isom_read_box( file, box, (isom_box_t *)stsd, stsd_pos, level )) != 0 ) + break; + stsd_pos += box->size; + if( stsd->size <= stsd_pos || bs->eob || bs->error ) + break; + } + if( stsd->size < stsd_pos ) + { + fprintf( stderr, "[stsd] box has extra bytes: %"PRId64"\n", stsd_pos - stsd->size ); + stsd->size = stsd_pos; + } + box->size = stsd->size; + return ret; +} + +static int isom_read_codec_specific( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + lsmash_bs_t *bs = file->bs; + uint64_t opaque_pos = lsmash_bs_count( bs ); + uint64_t exdata_length = box->size - opaque_pos; + if( exdata_length > UINT32_MAX ) + return LSMASH_ERR_MEMORY_ALLOC; + uint8_t *exdata = lsmash_malloc( box->size ); + if( !exdata ) + return LSMASH_ERR_MEMORY_ALLOC; + int ret = lsmash_bs_get_bytes_ex( bs, exdata_length, exdata + (uintptr_t)opaque_pos ); + if( ret < 0 ) + goto fail; + LSMASH_SET_BE32( &exdata[0], box->size ); + LSMASH_SET_BE32( &exdata[4], box->type.fourcc ); + uintptr_t i = 8; + if( box->type.fourcc == ISOM_BOX_TYPE_UUID.fourcc ) + { + LSMASH_SET_BE32( &exdata[8], box->type.user.fourcc ); + memcpy( &exdata[12], box->type.user.id, 12 ); + i += 16; + } + if( box->manager & LSMASH_FULLBOX ) + { + LSMASH_SET_BYTE( &exdata[i], box->version ); + i += 1; + LSMASH_SET_BE24( &exdata[i], box->flags ); + i += 3; + } + if( i != opaque_pos ) + { + ret = LSMASH_ERR_INVALID_DATA; + goto fail; + } + if( (ret = isom_add_extension_binary( parent, box->type, LSMASH_BOX_PRECEDENCE_N, exdata, box->size )) < 0 ) + goto fail; + isom_box_t *ext = (isom_box_t *)parent->extensions.tail->data; + box->manager |= ext->manager; + isom_validate_box_size( file->bs, box ); + isom_basebox_common_copy( ext, box ); + return isom_add_print_func( file, ext, level ); +fail: + lsmash_free( exdata ); + return ret; +} + +static void *isom_sample_description_alloc( lsmash_codec_type_t sample_type, isom_stsd_t *stsd ) +{ + assert( isom_check_media_hdlr_from_stsd( stsd ) ); + void *sample_desc = NULL; + lsmash_media_type media_type = ((isom_mdia_t *)stsd->parent->parent->parent)->hdlr->componentSubtype; + if( media_type == ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK ) + sample_desc = ALLOCATE_BOX( visual_entry ); + else if( media_type == ISOM_MEDIA_HANDLER_TYPE_AUDIO_TRACK ) + sample_desc = ALLOCATE_BOX( audio_entry ); + else if( media_type == ISOM_MEDIA_HANDLER_TYPE_TEXT_TRACK ) + { + if( lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_TX3G_TEXT ) ) + sample_desc = ALLOCATE_BOX( tx3g_entry ); + else if( lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_TEXT_TEXT ) ) + sample_desc = ALLOCATE_BOX( qt_text_entry ); + } + else if( lsmash_check_codec_type_identical( sample_type, ISOM_CODEC_TYPE_MP4S_SYSTEM ) ) + sample_desc = ALLOCATE_BOX( mp4s_entry ); + if( !sample_desc ) + return NULL; + ((isom_box_t *)sample_desc)->offset_in_parent = offsetof( isom_stsd_t, list ); + ((isom_box_t *)sample_desc)->destruct = (isom_extension_destructor_t)isom_remove_sample_description; + return sample_desc; +} + +static void *isom_add_description( lsmash_codec_type_t sample_type, isom_stsd_t *stsd ) +{ + void *sample_desc = isom_sample_description_alloc( sample_type, stsd ); + if( !sample_desc ) + return NULL; + if( lsmash_list_add_entry( &stsd->list, sample_desc ) < 0 ) + { + lsmash_free( sample_desc ); + return NULL; + } + if( lsmash_list_add_entry( &stsd->extensions, sample_desc ) < 0 ) + { + lsmash_list_remove_entry_tail( &stsd->list ); + return NULL; + } + return sample_desc; +} + +static int isom_read_visual_description( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STSD ) ) + return isom_read_unknown_box( file, box, parent, level ); + isom_visual_entry_t *visual = (isom_visual_entry_t *)isom_add_description( box->type, (isom_stsd_t *)parent ); + if( LSMASH_IS_NON_EXISTING_BOX( visual ) ) + return LSMASH_ERR_MEMORY_ALLOC; + lsmash_bs_t *bs = file->bs; + for( int i = 0; i < 6; i++ ) + visual->reserved[i] = lsmash_bs_get_byte( bs ); + visual->data_reference_index = lsmash_bs_get_be16( bs ); + visual->version = lsmash_bs_get_be16( bs ); + visual->revision_level = lsmash_bs_get_be16( bs ); + visual->vendor = lsmash_bs_get_be32( bs ); + visual->temporalQuality = lsmash_bs_get_be32( bs ); + visual->spatialQuality = lsmash_bs_get_be32( bs ); + visual->width = lsmash_bs_get_be16( bs ); + visual->height = lsmash_bs_get_be16( bs ); + visual->horizresolution = lsmash_bs_get_be32( bs ); + visual->vertresolution = lsmash_bs_get_be32( bs ); + visual->dataSize = lsmash_bs_get_be32( bs ); + visual->frame_count = lsmash_bs_get_be16( bs ); + for( int i = 0; i < 32; i++ ) + visual->compressorname[i] = lsmash_bs_get_byte( bs ); + visual->depth = lsmash_bs_get_be16( bs ); + visual->color_table_ID = lsmash_bs_get_be16( bs ); + int ret; + if( visual->color_table_ID == 0 + && lsmash_bs_get_pos( bs ) < box->size + && (ret = isom_read_qt_color_table( bs, &visual->color_table )) < 0 ) + return ret; + box->parent = parent; + box->manager |= LSMASH_VIDEO_DESCRIPTION; + isom_box_common_copy( visual, box ); + if( (ret = isom_add_print_func( file, visual, level )) < 0 ) + return ret; + return isom_read_children( file, box, visual, level ); +} + +static int isom_read_esds( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_CODEC_TYPE_MP4V_VIDEO ) + && !lsmash_check_box_type_identical( parent->type, ISOM_CODEC_TYPE_MP4A_AUDIO ) + && !lsmash_check_box_type_identical( parent->type, ISOM_CODEC_TYPE_ENCA_AUDIO ) + && !lsmash_check_box_type_identical( parent->type, ISOM_CODEC_TYPE_M4AE_AUDIO ) + && !lsmash_check_box_type_identical( parent->type, ISOM_CODEC_TYPE_MP4S_SYSTEM ) + && !lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_WAVE ) ) + return isom_read_codec_specific( file, box, parent, level ); + if( lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_WAVE ) ) + { + box->type = QT_BOX_TYPE_ESDS; + assert( LSMASH_IS_EXISTING_BOX( parent->parent ) ); + if( lsmash_check_box_type_identical( parent->parent->type, ISOM_CODEC_TYPE_MP4A_AUDIO ) ) + parent->parent->type = QT_CODEC_TYPE_MP4A_AUDIO; + } + else + box->type = ISOM_BOX_TYPE_ESDS; + ADD_BOX( esds, void ); + lsmash_bs_t *bs = file->bs; + esds->ES = mp4sys_get_descriptor( bs, NULL ); + if( !esds->ES ) + return LSMASH_ERR_INVALID_DATA; + return isom_read_leaf_box_common_last_process( file, box, level, esds ); +} + +static int isom_read_btrt( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + ADD_BOX( btrt, isom_visual_entry_t ); + lsmash_bs_t *bs = file->bs; + btrt->bufferSizeDB = lsmash_bs_get_be32( bs ); + btrt->maxBitrate = lsmash_bs_get_be32( bs ); + btrt->avgBitrate = lsmash_bs_get_be32( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, btrt ); +} + +static int isom_read_glbl( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + ADD_BOX( glbl, isom_visual_entry_t ); + lsmash_bs_t *bs = file->bs; + uint32_t header_size = box->size - ISOM_BASEBOX_COMMON_SIZE; + if( header_size ) + { + glbl->header_data = lsmash_malloc( header_size ); + if( !glbl->header_data ) + return LSMASH_ERR_MEMORY_ALLOC; + for( uint32_t i = 0; i < header_size; i++ ) + glbl->header_data[i] = lsmash_bs_get_byte( bs ); + } + glbl->header_size = header_size; + return isom_read_leaf_box_common_last_process( file, box, level, glbl ); +} + +static int isom_read_clap( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + ADD_BOX( clap, isom_visual_entry_t ); + lsmash_bs_t *bs = file->bs; + clap->cleanApertureWidthN = lsmash_bs_get_be32( bs ); + clap->cleanApertureWidthD = lsmash_bs_get_be32( bs ); + clap->cleanApertureHeightN = lsmash_bs_get_be32( bs ); + clap->cleanApertureHeightD = lsmash_bs_get_be32( bs ); + clap->horizOffN = lsmash_bs_get_be32( bs ); + clap->horizOffD = lsmash_bs_get_be32( bs ); + clap->vertOffN = lsmash_bs_get_be32( bs ); + clap->vertOffD = lsmash_bs_get_be32( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, clap ); +} + +static int isom_read_pasp( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + ADD_BOX( pasp, isom_visual_entry_t ); + lsmash_bs_t *bs = file->bs; + pasp->hSpacing = lsmash_bs_get_be32( bs ); + pasp->vSpacing = lsmash_bs_get_be32( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, pasp ); +} + +static int isom_read_colr( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + ADD_BOX( colr, isom_visual_entry_t ); + lsmash_bs_t *bs = file->bs; + colr->color_parameter_type = lsmash_bs_get_be32( bs ); + if( colr->color_parameter_type == QT_COLOR_PARAMETER_TYPE_NCLC + || colr->color_parameter_type == ISOM_COLOR_PARAMETER_TYPE_NCLX ) + { + colr->primaries_index = lsmash_bs_get_be16( bs ); + colr->transfer_function_index = lsmash_bs_get_be16( bs ); + colr->matrix_index = lsmash_bs_get_be16( bs ); + if( colr->color_parameter_type == ISOM_COLOR_PARAMETER_TYPE_NCLX ) + { + if( lsmash_bs_count( bs ) < box->size ) + { + uint8_t temp8 = lsmash_bs_get_byte( bs ); + colr->full_range_flag = (temp8 >> 7) & 0x01; + colr->reserved = temp8 & 0x7f; + } + else + { + /* It seems this box is broken or incomplete. */ + box->manager |= LSMASH_INCOMPLETE_BOX; + colr->full_range_flag = 0; + colr->reserved = 0; + } + } + else + box->manager |= LSMASH_QTFF_BASE; + } + box->type = (box->manager & LSMASH_QTFF_BASE) ? QT_BOX_TYPE_COLR : ISOM_BOX_TYPE_COLR; + return isom_read_leaf_box_common_last_process( file, box, level, colr ); +} + +static int isom_read_gama( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + ADD_BOX( gama, isom_visual_entry_t ); + lsmash_bs_t *bs = file->bs; + gama->level = lsmash_bs_get_be32( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, gama ); +} + +static int isom_read_fiel( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + ADD_BOX( fiel, isom_visual_entry_t ); + lsmash_bs_t *bs = file->bs; + fiel->fields = lsmash_bs_get_byte( bs ); + fiel->detail = lsmash_bs_get_byte( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, fiel ); +} + +static int isom_read_clli( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + ADD_BOX( clli, isom_visual_entry_t ); + lsmash_bs_t *bs = file->bs; + clli->max_content_light_level = lsmash_bs_get_be16( bs ); + clli->max_pic_average_light_level = lsmash_bs_get_be16( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, clli ); +} + +static int isom_read_mdcv( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + ADD_BOX( mdcv, isom_visual_entry_t ); + lsmash_bs_t *bs = file->bs; + mdcv->display_primaries_g_x = lsmash_bs_get_be16( bs ); + mdcv->display_primaries_g_y = lsmash_bs_get_be16( bs ); + mdcv->display_primaries_b_x = lsmash_bs_get_be16( bs ); + mdcv->display_primaries_b_y = lsmash_bs_get_be16( bs ); + mdcv->display_primaries_r_x = lsmash_bs_get_be16( bs ); + mdcv->display_primaries_r_y = lsmash_bs_get_be16( bs ); + mdcv->white_point_x = lsmash_bs_get_be16( bs ); + mdcv->white_point_y = lsmash_bs_get_be16( bs ); + mdcv->max_display_mastering_luminance = lsmash_bs_get_be32( bs ); + mdcv->min_display_mastering_luminance = lsmash_bs_get_be32( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, mdcv ); +} + +static int isom_read_cspc( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + ADD_BOX( cspc, isom_visual_entry_t ); + lsmash_bs_t *bs = file->bs; + cspc->pixel_format = lsmash_bs_get_be32( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, cspc ); +} + +static int isom_read_sgbt( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + ADD_BOX( sgbt, isom_visual_entry_t ); + lsmash_bs_t *bs = file->bs; + sgbt->significantBits = lsmash_bs_get_byte( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, sgbt ); +} + +static int isom_read_stsl( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + ADD_BOX( stsl, isom_visual_entry_t ); + lsmash_bs_t *bs = file->bs; + stsl->constraint_flag = lsmash_bs_get_byte( bs ); + stsl->scale_method = lsmash_bs_get_byte( bs ); + stsl->display_center_x = lsmash_bs_get_be16( bs ); + stsl->display_center_y = lsmash_bs_get_be16( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, stsl ); +} + +static int isom_read_audio_description( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STSD ) ) + return isom_read_unknown_box( file, box, parent, level ); + isom_audio_entry_t *audio = (isom_audio_entry_t *)isom_add_description( box->type, (isom_stsd_t *)parent ); + if( LSMASH_IS_NON_EXISTING_BOX( audio ) ) + return LSMASH_ERR_MEMORY_ALLOC; + lsmash_bs_t *bs = file->bs; + for( int i = 0; i < 6; i++ ) + audio->reserved[i] = lsmash_bs_get_byte( bs ); + audio->data_reference_index = lsmash_bs_get_be16( bs ); + audio->version = lsmash_bs_get_be16( bs ); + audio->revision_level = lsmash_bs_get_be16( bs ); + audio->vendor = lsmash_bs_get_be32( bs ); + audio->channelcount = lsmash_bs_get_be16( bs ); + audio->samplesize = lsmash_bs_get_be16( bs ); + audio->compression_ID = lsmash_bs_get_be16( bs ); + audio->packet_size = lsmash_bs_get_be16( bs ); + audio->samplerate = lsmash_bs_get_be32( bs ); + if( audio->version == 0 && isom_is_qt_audio( box->type ) ) + { + /* Skip weird extra bytes. + * About QTFF, extensions were first added with Sound Sample Description v1. */ + while( lsmash_bs_count( bs ) + ISOM_BASEBOX_COMMON_SIZE <= box->size ) + { + uint32_t size = lsmash_bs_show_be32( bs, 0 ); + if( size == 0 || lsmash_bs_count( bs ) + size > box->size ) + lsmash_bs_skip_bytes( bs, 1 ); + else + break; + } + } + else if( audio->version == 1 ) + { + if( ((isom_stsd_t *)parent)->version == 0 ) + { + audio->samplesPerPacket = lsmash_bs_get_be32( bs ); + audio->bytesPerPacket = lsmash_bs_get_be32( bs ); + audio->bytesPerFrame = lsmash_bs_get_be32( bs ); + audio->bytesPerSample = lsmash_bs_get_be32( bs ); + box->manager |= LSMASH_QTFF_BASE; + } + else + /* AudioSampleEntryV1 has no additional fields. */ + box->manager &= ~LSMASH_QTFF_BASE; + } + else if( audio->version == 2 ) + { + audio->sizeOfStructOnly = lsmash_bs_get_be32( bs ); + audio->audioSampleRate = lsmash_bs_get_be64( bs ); + audio->numAudioChannels = lsmash_bs_get_be32( bs ); + audio->always7F000000 = lsmash_bs_get_be32( bs ); + audio->constBitsPerChannel = lsmash_bs_get_be32( bs ); + audio->formatSpecificFlags = lsmash_bs_get_be32( bs ); + audio->constBytesPerAudioPacket = lsmash_bs_get_be32( bs ); + audio->constLPCMFramesPerAudioPacket = lsmash_bs_get_be32( bs ); + box->manager |= LSMASH_QTFF_BASE; + } + box->parent = parent; + box->manager |= LSMASH_AUDIO_DESCRIPTION; + isom_box_common_copy( audio, box ); + int ret = isom_add_print_func( file, audio, level ); + if( ret < 0 ) + return ret; + return isom_read_children( file, box, audio, level ); +} + +static int isom_read_wave( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + ADD_BOX( wave, isom_audio_entry_t ); + isom_box_common_copy( wave, box ); + int ret = isom_add_print_func( file, wave, level ); + if( ret < 0 ) + return ret; + return isom_read_children( file, box, wave, level ); +} + +static int isom_read_frma( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_WAVE ) + || LSMASH_IS_EXISTING_BOX( ((isom_wave_t *)parent)->frma ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( frma, isom_wave_t ); + lsmash_bs_t *bs = file->bs; + frma->data_format = lsmash_bs_get_be32( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, frma ); +} + +static int isom_read_enda( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_WAVE ) + || LSMASH_IS_EXISTING_BOX( ((isom_wave_t *)parent)->enda ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( enda, isom_wave_t ); + lsmash_bs_t *bs = file->bs; + enda->littleEndian = lsmash_bs_get_be16( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, enda ); +} + +static int isom_read_terminator( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_WAVE ) + || LSMASH_IS_EXISTING_BOX( ((isom_wave_t *)parent)->terminator ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( terminator, isom_wave_t ); + return isom_read_leaf_box_common_last_process( file, box, level, terminator ); +} + +static int isom_read_chan( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + ADD_BOX( chan, isom_audio_entry_t ); + lsmash_bs_t *bs = file->bs; + chan->channelLayoutTag = lsmash_bs_get_be32( bs ); + chan->channelBitmap = lsmash_bs_get_be32( bs ); + chan->numberChannelDescriptions = lsmash_bs_get_be32( bs ); + if( chan->numberChannelDescriptions ) + { + isom_channel_description_t *desc = lsmash_malloc( chan->numberChannelDescriptions * sizeof(isom_channel_description_t) ); + if( !desc ) + return LSMASH_ERR_MEMORY_ALLOC; + chan->channelDescriptions = desc; + for( uint32_t i = 0; i < chan->numberChannelDescriptions; i++ ) + { + desc->channelLabel = lsmash_bs_get_be32( bs ); + desc->channelFlags = lsmash_bs_get_be32( bs ); + for( int j = 0; j < 3; j++ ) + desc->coordinates[j] = lsmash_bs_get_be32( bs ); + } + } + return isom_read_leaf_box_common_last_process( file, box, level, chan ); +} + +static int isom_read_srat( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + ADD_BOX( srat, isom_audio_entry_t ); + lsmash_bs_t *bs = file->bs; + srat->sampling_rate = lsmash_bs_get_be32( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, srat ); +} + +static int isom_read_qt_text_description( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STSD ) ) + return isom_read_unknown_box( file, box, parent, level ); + isom_qt_text_entry_t *text = (isom_qt_text_entry_t *)isom_add_description( box->type, (isom_stsd_t *)parent ); + if( LSMASH_IS_NON_EXISTING_BOX( text ) ) + return LSMASH_ERR_MEMORY_ALLOC; + lsmash_bs_t *bs = file->bs; + for( int i = 0; i < 6; i++ ) + text->reserved[i] = lsmash_bs_get_byte( bs ); + text->data_reference_index = lsmash_bs_get_be16( bs ); + text->displayFlags = lsmash_bs_get_be32( bs ); + text->textJustification = lsmash_bs_get_be32( bs ); + for( int i = 0; i < 3; i++ ) + text->bgColor[i] = lsmash_bs_get_be16( bs ); + text->top = lsmash_bs_get_be16( bs ); + text->left = lsmash_bs_get_be16( bs ); + text->bottom = lsmash_bs_get_be16( bs ); + text->right = lsmash_bs_get_be16( bs ); + text->scrpStartChar = lsmash_bs_get_be32( bs ); + text->scrpHeight = lsmash_bs_get_be16( bs ); + text->scrpAscent = lsmash_bs_get_be16( bs ); + text->scrpFont = lsmash_bs_get_be16( bs ); + text->scrpFace = lsmash_bs_get_be16( bs ); + text->scrpSize = lsmash_bs_get_be16( bs ); + for( int i = 0; i < 3; i++ ) + text->scrpColor[i] = lsmash_bs_get_be16( bs ); + text->font_name_length = lsmash_bs_get_byte( bs ); + if( text->font_name_length ) + { + text->font_name = lsmash_malloc( text->font_name_length + 1 ); + if( !text->font_name ) + return LSMASH_ERR_MEMORY_ALLOC; + for( uint8_t i = 0; i < text->font_name_length; i++ ) + text->font_name[i] = lsmash_bs_get_byte( bs ); + text->font_name[text->font_name_length] = '\0'; + } + box->parent = parent; + isom_box_common_copy( text, box ); + int ret = isom_add_print_func( file, text, level ); + if( ret < 0 ) + return ret; + return isom_read_children( file, box, text, level ); +} + +static int isom_read_tx3g_description( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STSD ) ) + return isom_read_unknown_box( file, box, parent, level ); + isom_tx3g_entry_t *tx3g = (isom_tx3g_entry_t *)isom_add_description( box->type, (isom_stsd_t *)parent ); + if( LSMASH_IS_NON_EXISTING_BOX( tx3g ) ) + return LSMASH_ERR_MEMORY_ALLOC; + lsmash_bs_t *bs = file->bs; + for( int i = 0; i < 6; i++ ) + tx3g->reserved[i] = lsmash_bs_get_byte( bs ); + tx3g->data_reference_index = lsmash_bs_get_be16( bs ); + tx3g->displayFlags = lsmash_bs_get_be32( bs ); + tx3g->horizontal_justification = lsmash_bs_get_byte( bs ); + tx3g->vertical_justification = lsmash_bs_get_byte( bs ); + for( int i = 0; i < 4; i++ ) + tx3g->background_color_rgba[i] = lsmash_bs_get_byte( bs ); + tx3g->top = lsmash_bs_get_be16( bs ); + tx3g->left = lsmash_bs_get_be16( bs ); + tx3g->bottom = lsmash_bs_get_be16( bs ); + tx3g->right = lsmash_bs_get_be16( bs ); + tx3g->startChar = lsmash_bs_get_be16( bs ); + tx3g->endChar = lsmash_bs_get_be16( bs ); + tx3g->font_ID = lsmash_bs_get_be16( bs ); + tx3g->face_style_flags = lsmash_bs_get_byte( bs ); + tx3g->font_size = lsmash_bs_get_byte( bs ); + for( int i = 0; i < 4; i++ ) + tx3g->text_color_rgba[i] = lsmash_bs_get_byte( bs ); + box->parent = parent; + isom_box_common_copy( tx3g, box ); + int ret = isom_add_print_func( file, tx3g, level ); + if( ret < 0 ) + return ret; + return isom_read_children( file, box, tx3g, level ); +} + +static int isom_read_text_description( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( lsmash_check_codec_type_identical( box->type, QT_CODEC_TYPE_TEXT_TEXT ) ) + return isom_read_qt_text_description( file, box, parent, level ); + else if( lsmash_check_codec_type_identical( box->type, ISOM_CODEC_TYPE_TX3G_TEXT ) ) + return isom_read_tx3g_description( file, box, parent, level ); + assert( 0 ); + return isom_read_unknown_box( file, box, parent, level ); +} + +static int isom_read_ftab( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_CODEC_TYPE_TX3G_TEXT ) + || LSMASH_IS_EXISTING_BOX( ((isom_tx3g_entry_t *)parent)->ftab ) ) + return isom_read_codec_specific( file, box, parent, level ); + ADD_BOX( ftab, isom_tx3g_entry_t ); + lsmash_bs_t *bs = file->bs; + uint32_t entry_count = lsmash_bs_get_be16( bs ); + for( uint64_t pos = lsmash_bs_count( bs ); pos < box->size && ftab->list->entry_count < entry_count; pos = lsmash_bs_count( bs ) ) + { + isom_font_record_t *data = lsmash_malloc_zero( sizeof(isom_font_record_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( ftab->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + data->font_ID = lsmash_bs_get_be16( bs ); + data->font_name_length = lsmash_bs_get_byte( bs ); + if( data->font_name_length ) + { + data->font_name = lsmash_malloc( data->font_name_length + 1 ); + if( !data->font_name ) + return LSMASH_ERR_MEMORY_ALLOC; + for( uint8_t i = 0; i < data->font_name_length; i++ ) + data->font_name[i] = lsmash_bs_get_byte( bs ); + data->font_name[data->font_name_length] = '\0'; + } + } + return isom_read_leaf_box_common_last_process( file, box, level, ftab ); +} + +static int isom_read_mp4s_description( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STSD ) ) + return isom_read_unknown_box( file, box, parent, level ); + isom_mp4s_entry_t *mp4s = (isom_mp4s_entry_t *)isom_add_description( box->type, (isom_stsd_t *)parent ); + if( LSMASH_IS_NON_EXISTING_BOX( mp4s ) ) + return LSMASH_ERR_MEMORY_ALLOC; + lsmash_bs_t *bs = file->bs; + for( int i = 0; i < 6; i++ ) + mp4s->reserved[i] = lsmash_bs_get_byte( bs ); + mp4s->data_reference_index = lsmash_bs_get_be16( bs ); + box->parent = parent; + isom_box_common_copy( mp4s, box ); + int ret = isom_add_print_func( file, mp4s, level ); + if( ret < 0 ) + return ret; + return isom_read_children( file, box, mp4s, level ); +} + +static int isom_read_other_description( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( lsmash_check_codec_type_identical( box->type, ISOM_CODEC_TYPE_MP4S_SYSTEM ) ) + return isom_read_mp4s_description( file, box, parent, level ); + return isom_read_unknown_box( file, box, parent, level ); +} + +static int isom_read_stts( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STBL ) + || LSMASH_IS_EXISTING_BOX( ((isom_stbl_t *)parent)->stts ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( stts, isom_stbl_t ); + lsmash_bs_t *bs = file->bs; + uint32_t entry_count = lsmash_bs_get_be32( bs ); + for( uint64_t pos = lsmash_bs_count( bs ); pos < box->size && stts->list->entry_count < entry_count; pos = lsmash_bs_count( bs ) ) + { + isom_stts_entry_t *data = lsmash_malloc( sizeof(isom_stts_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( stts->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + data->sample_count = lsmash_bs_get_be32( bs ); + data->sample_delta = lsmash_bs_get_be32( bs ); + } + return isom_read_leaf_box_common_last_process( file, box, level, stts ); +} + +static int isom_read_ctts( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STBL ) + || LSMASH_IS_EXISTING_BOX( ((isom_stbl_t *)parent)->ctts ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( ctts, isom_stbl_t ); + lsmash_bs_t *bs = file->bs; + uint32_t entry_count = lsmash_bs_get_be32( bs ); + for( uint64_t pos = lsmash_bs_count( bs ); pos < box->size && ctts->list->entry_count < entry_count; pos = lsmash_bs_count( bs ) ) + { + isom_ctts_entry_t *data = lsmash_malloc( sizeof(isom_ctts_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( ctts->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + data->sample_count = lsmash_bs_get_be32( bs ); + data->sample_offset = lsmash_bs_get_be32( bs ); + } + return isom_read_leaf_box_common_last_process( file, box, level, ctts ); +} + +static int isom_read_cslg( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STBL ) + || LSMASH_IS_EXISTING_BOX( ((isom_stbl_t *)parent)->cslg ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( cslg, isom_stbl_t ); + lsmash_bs_t *bs = file->bs; + cslg->compositionToDTSShift = lsmash_bs_get_be32( bs ); + cslg->leastDecodeToDisplayDelta = lsmash_bs_get_be32( bs ); + cslg->greatestDecodeToDisplayDelta = lsmash_bs_get_be32( bs ); + cslg->compositionStartTime = lsmash_bs_get_be32( bs ); + cslg->compositionEndTime = lsmash_bs_get_be32( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, cslg ); +} + +static int isom_read_stss( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STBL ) + || LSMASH_IS_EXISTING_BOX( ((isom_stbl_t *)parent)->stss ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( stss, isom_stbl_t ); + lsmash_bs_t *bs = file->bs; + uint32_t entry_count = lsmash_bs_get_be32( bs ); + for( uint64_t pos = lsmash_bs_count( bs ); pos < box->size && stss->list->entry_count < entry_count; pos = lsmash_bs_count( bs ) ) + { + isom_stss_entry_t *data = lsmash_malloc( sizeof(isom_stss_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( stss->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + data->sample_number = lsmash_bs_get_be32( bs ); + } + return isom_read_leaf_box_common_last_process( file, box, level, stss ); +} + +static int isom_read_stps( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STBL ) + || LSMASH_IS_EXISTING_BOX( ((isom_stbl_t *)parent)->stps ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( stps, isom_stbl_t ); + lsmash_bs_t *bs = file->bs; + uint32_t entry_count = lsmash_bs_get_be32( bs ); + for( uint64_t pos = lsmash_bs_count( bs ); pos < box->size && stps->list->entry_count < entry_count; pos = lsmash_bs_count( bs ) ) + { + isom_stps_entry_t *data = lsmash_malloc( sizeof(isom_stps_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( stps->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + data->sample_number = lsmash_bs_get_be32( bs ); + } + return isom_read_leaf_box_common_last_process( file, box, level, stps ); +} + +static int isom_read_sdtp( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( (!lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STBL ) + && !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TRAF )) + || (lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STBL ) && LSMASH_IS_EXISTING_BOX( ((isom_stbl_t *)parent)->sdtp )) + || (lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TRAF ) && LSMASH_IS_EXISTING_BOX( ((isom_traf_t *)parent)->sdtp )) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( sdtp, isom_box_t ); + lsmash_bs_t *bs = file->bs; + for( uint64_t pos = lsmash_bs_count( bs ); pos < box->size; pos = lsmash_bs_count( bs ) ) + { + isom_sdtp_entry_t *data = lsmash_malloc( sizeof(isom_sdtp_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( sdtp->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + uint8_t temp = lsmash_bs_get_byte( bs ); + data->is_leading = (temp >> 6) & 0x3; + data->sample_depends_on = (temp >> 4) & 0x3; + data->sample_is_depended_on = (temp >> 2) & 0x3; + data->sample_has_redundancy = temp & 0x3; + } + return isom_read_leaf_box_common_last_process( file, box, level, sdtp ); +} + +static int isom_read_stsc( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STBL ) + || LSMASH_IS_EXISTING_BOX( ((isom_stbl_t *)parent)->stsc ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( stsc, isom_stbl_t ); + lsmash_bs_t *bs = file->bs; + uint32_t entry_count = lsmash_bs_get_be32( bs ); + for( uint64_t pos = lsmash_bs_count( bs ); pos < box->size && stsc->list->entry_count < entry_count; pos = lsmash_bs_count( bs ) ) + { + isom_stsc_entry_t *data = lsmash_malloc( sizeof(isom_stsc_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( stsc->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + data->first_chunk = lsmash_bs_get_be32( bs ); + data->samples_per_chunk = lsmash_bs_get_be32( bs ); + data->sample_description_index = lsmash_bs_get_be32( bs ); + } + return isom_read_leaf_box_common_last_process( file, box, level, stsc ); +} + +static int isom_read_stsz( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STBL ) + || LSMASH_IS_EXISTING_BOX( ((isom_stbl_t *)parent)->stsz ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( stsz, isom_stbl_t ); + lsmash_bs_t *bs = file->bs; + stsz->sample_size = lsmash_bs_get_be32( bs ); + stsz->sample_count = lsmash_bs_get_be32( bs ); + uint64_t pos = lsmash_bs_count( bs ); + if( pos < box->size ) + { + stsz->list = lsmash_list_create_simple(); + if( !stsz->list ) + return LSMASH_ERR_MEMORY_ALLOC; + for( ; pos < box->size && stsz->list->entry_count < stsz->sample_count; pos = lsmash_bs_count( bs ) ) + { + isom_stsz_entry_t *data = lsmash_malloc( sizeof(isom_stsz_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( stsz->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + data->entry_size = lsmash_bs_get_be32( bs ); + } + } + return isom_read_leaf_box_common_last_process( file, box, level, stsz ); +} + +static int isom_read_stz2( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STBL ) + || LSMASH_IS_EXISTING_BOX( ((isom_stbl_t *)parent)->stz2 ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( stz2, isom_stbl_t ); + lsmash_bs_t *bs = file->bs; + uint32_t temp32 = lsmash_bs_get_be32( bs ); + stz2->reserved = temp32 >> 24; + stz2->field_size = temp32 & 0xff; + stz2->sample_count = lsmash_bs_get_be32( bs ); + uint64_t pos = lsmash_bs_count( bs ); + if( pos < box->size ) + { + if( stz2->field_size == 16 || stz2->field_size == 8 ) + { + uint64_t (*bs_get_funcs[2])( lsmash_bs_t * ) = + { + lsmash_bs_get_byte_to_64, + lsmash_bs_get_be16_to_64 + }; + uint64_t (*bs_get_entry_size)( lsmash_bs_t * ) = bs_get_funcs[ stz2->field_size == 16 ? 1 : 0 ]; + for( ; pos < box->size && stz2->list->entry_count < stz2->sample_count; pos = lsmash_bs_count( bs ) ) + { + isom_stsz_entry_t *data = lsmash_malloc( sizeof(isom_stsz_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( stz2->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + data->entry_size = bs_get_entry_size( bs ); + } + } + else if( stz2->field_size == 4 ) + { + int parity = 1; + uint8_t temp8; + while( pos < box->size && stz2->list->entry_count < stz2->sample_count ) + { + isom_stsz_entry_t *data = lsmash_malloc( sizeof(isom_stsz_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( stz2->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + /* Read a byte by two entries. */ + if( parity ) + { + temp8 = lsmash_bs_get_byte( bs ); + data->entry_size = (temp8 >> 4) & 0xf; + } + else + { + pos = lsmash_bs_count( bs ); + data->entry_size = temp8 & 0xf; + } + parity ^= 1; + } + } + else + return LSMASH_ERR_INVALID_DATA; + } + return isom_read_leaf_box_common_last_process( file, box, level, stz2 ); +} + +static int isom_read_stco( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STBL ) + || LSMASH_IS_EXISTING_BOX( ((isom_stbl_t *)parent)->stco ) ) + return isom_read_unknown_box( file, box, parent, level ); + box->type = lsmash_form_iso_box_type( box->type.fourcc ); + int is_stco = lsmash_check_box_type_identical( box->type, ISOM_BOX_TYPE_STCO ); + isom_stco_t *stco = is_stco + ? isom_add_stco( (isom_stbl_t *)parent ) + : isom_add_co64( (isom_stbl_t *)parent ); + if( !stco ) + return LSMASH_ERR_NAMELESS; + lsmash_bs_t *bs = file->bs; + uint32_t entry_count = lsmash_bs_get_be32( bs ); + if( is_stco ) + for( uint64_t pos = lsmash_bs_count( bs ); pos < box->size && stco->list->entry_count < entry_count; pos = lsmash_bs_count( bs ) ) + { + isom_stco_entry_t *data = lsmash_malloc( sizeof(isom_stco_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( stco->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + data->chunk_offset = lsmash_bs_get_be32( bs ); + } + else + { + for( uint64_t pos = lsmash_bs_count( bs ); pos < box->size && stco->list->entry_count < entry_count; pos = lsmash_bs_count( bs ) ) + { + isom_co64_entry_t *data = lsmash_malloc( sizeof(isom_co64_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( stco->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + data->chunk_offset = lsmash_bs_get_be64( bs ); + } + } + return isom_read_leaf_box_common_last_process( file, box, level, stco ); +} + +static int isom_read_sgpd( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STBL ) + && !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TRAF ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( sgpd, void ); + lsmash_bs_t *bs = file->bs; + sgpd->grouping_type = lsmash_bs_get_be32( bs ); + if( box->version == 1 ) + sgpd->default_length = lsmash_bs_get_be32( bs ); + uint32_t entry_count = lsmash_bs_get_be32( bs ); + switch( sgpd->grouping_type ) + { + case ISOM_GROUP_TYPE_RAP : + { + for( uint64_t pos = lsmash_bs_count( bs ); pos < box->size && sgpd->list->entry_count < entry_count; pos = lsmash_bs_count( bs ) ) + { + isom_rap_entry_t *data = lsmash_malloc( sizeof(isom_rap_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( sgpd->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + memset( data, 0, sizeof(isom_rap_entry_t) ); + /* We don't know groups decided by variable description length. If encountering, skip getting of bytes of it. */ + if( box->version == 1 && !sgpd->default_length ) + data->description_length = lsmash_bs_get_be32( bs ); + else + { + uint8_t temp = lsmash_bs_get_byte( bs ); + data->num_leading_samples_known = (temp >> 7) & 0x01; + data->num_leading_samples = temp & 0x7f; + } + } + break; + } + case ISOM_GROUP_TYPE_ROLL : + case ISOM_GROUP_TYPE_PROL : + { + for( uint64_t pos = lsmash_bs_count( bs ); pos < box->size && sgpd->list->entry_count < entry_count; pos = lsmash_bs_count( bs ) ) + { + isom_roll_entry_t *data = lsmash_malloc( sizeof(isom_roll_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( sgpd->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + memset( data, 0, sizeof(isom_roll_entry_t) ); + /* We don't know groups decided by variable description length. If encountering, skip getting of bytes of it. */ + if( box->version == 1 && !sgpd->default_length ) + data->description_length = lsmash_bs_get_be32( bs ); + else + data->roll_distance = lsmash_bs_get_be16( bs ); + } + break; + } + default : + break; + } + return isom_read_leaf_box_common_last_process( file, box, level, sgpd ); +} + +static int isom_read_sbgp( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STBL ) + && !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TRAF ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( sbgp, void ); + lsmash_bs_t *bs = file->bs; + sbgp->grouping_type = lsmash_bs_get_be32( bs ); + if( box->version == 1 ) + sbgp->grouping_type_parameter = lsmash_bs_get_be32( bs ); + uint32_t entry_count = lsmash_bs_get_be32( bs ); + for( uint64_t pos = lsmash_bs_count( bs ); pos < box->size && sbgp->list->entry_count < entry_count; pos = lsmash_bs_count( bs ) ) + { + isom_group_assignment_entry_t *data = lsmash_malloc( sizeof(isom_group_assignment_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( sbgp->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + data->sample_count = lsmash_bs_get_be32( bs ); + data->group_description_index = lsmash_bs_get_be32( bs ); + } + return isom_read_leaf_box_common_last_process( file, box, level,sbgp ); +} + +static int isom_read_udta( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( (!lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MOOV ) + && !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TRAK )) + || (lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MOOV ) && LSMASH_IS_EXISTING_BOX( ((isom_moov_t *)parent)->udta )) + || (lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TRAK ) && LSMASH_IS_EXISTING_BOX( ((isom_trak_t *)parent)->udta )) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( udta, void ); + isom_box_common_copy( udta, box ); + int ret = isom_add_print_func( file, udta, level ); + if( ret < 0 ) + return ret; + return isom_read_children( file, box, udta, level ); +} + +static int isom_read_chpl( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_UDTA ) + || LSMASH_IS_EXISTING_BOX( ((isom_udta_t *)parent)->chpl ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( chpl, isom_udta_t ); + lsmash_bs_t *bs = file->bs; + uint32_t entry_count; + if( box->version == 1 ) + { + chpl->unknown = lsmash_bs_get_byte( bs ); + entry_count = lsmash_bs_get_be32( bs ); + } + else + entry_count = lsmash_bs_get_byte( bs ); + for( uint64_t pos = lsmash_bs_count( bs ); pos < box->size && chpl->list->entry_count < entry_count; pos = lsmash_bs_count( bs ) ) + { + isom_chpl_entry_t *data = lsmash_malloc( sizeof(isom_chpl_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( chpl->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + data->start_time = lsmash_bs_get_be64( bs ); + data->chapter_name_length = lsmash_bs_get_byte( bs ); + data->chapter_name = lsmash_malloc( data->chapter_name_length + 1 ); + if( !data->chapter_name ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + for( uint8_t i = 0; i < data->chapter_name_length; i++ ) + data->chapter_name[i] = lsmash_bs_get_byte( bs ); + data->chapter_name[data->chapter_name_length] = '\0'; + } + return isom_read_leaf_box_common_last_process( file, box, level, chpl ); +} + +static int isom_read_mvex( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MOOV ) + || LSMASH_IS_EXISTING_BOX( ((isom_moov_t *)parent)->mvex ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( mvex, isom_moov_t ); + file->flags |= LSMASH_FILE_MODE_FRAGMENTED; + isom_box_common_copy( mvex, box ); + int ret = isom_add_print_func( file, mvex, level ); + if( ret < 0 ) + return ret; + return isom_read_children( file, box, mvex, level ); +} + +static int isom_read_mehd( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MVEX ) + || LSMASH_IS_EXISTING_BOX( ((isom_mvex_t *)parent)->mehd ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( mehd, isom_mvex_t ); + lsmash_bs_t *bs = file->bs; + if( box->version == 1 ) + mehd->fragment_duration = lsmash_bs_get_be64( bs ); + else + mehd->fragment_duration = lsmash_bs_get_be32( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, mehd ); +} + +static isom_sample_flags_t isom_bs_get_sample_flags( lsmash_bs_t *bs ) +{ + uint32_t temp = lsmash_bs_get_be32( bs ); + isom_sample_flags_t flags; + flags.reserved = (temp >> 28) & 0xf; + flags.is_leading = (temp >> 26) & 0x3; + flags.sample_depends_on = (temp >> 24) & 0x3; + flags.sample_is_depended_on = (temp >> 22) & 0x3; + flags.sample_has_redundancy = (temp >> 20) & 0x3; + flags.sample_padding_value = (temp >> 17) & 0x7; + flags.sample_is_non_sync_sample = (temp >> 16) & 0x1; + flags.sample_degradation_priority = temp & 0xffff; + return flags; +} + +static int isom_read_trex( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MVEX ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( trex, isom_mvex_t ); + box->parent = parent; + lsmash_bs_t *bs = file->bs; + trex->track_ID = lsmash_bs_get_be32( bs ); + trex->default_sample_description_index = lsmash_bs_get_be32( bs ); + trex->default_sample_duration = lsmash_bs_get_be32( bs ); + trex->default_sample_size = lsmash_bs_get_be32( bs ); + trex->default_sample_flags = isom_bs_get_sample_flags( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, trex ); +} + +static int isom_read_moof( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, LSMASH_BOX_TYPE_UNSPECIFIED ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( moof, lsmash_file_t ); + box->parent = parent; + isom_box_common_copy( moof, box ); + int ret = isom_add_print_func( file, moof, level ); + if( ret < 0 ) + return ret; + return isom_read_children( file, box, moof, level ); +} + +static int isom_read_mfhd( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MOOF ) + || LSMASH_IS_EXISTING_BOX( ((isom_moof_t *)parent)->mfhd ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( mfhd, isom_moof_t ); + lsmash_bs_t *bs = file->bs; + mfhd->sequence_number = lsmash_bs_get_be32( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, mfhd ); +} + +static int isom_read_traf( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MOOF ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( traf, isom_moof_t ); + box->parent = parent; + isom_box_common_copy( traf, box ); + int ret = isom_add_print_func( file, traf, level ); + if( ret < 0 ) + return ret; + return isom_read_children( file, box, traf, level ); +} + +static int isom_read_tfhd( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TRAF ) + || LSMASH_IS_EXISTING_BOX( ((isom_traf_t *)parent)->tfhd ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( tfhd, isom_traf_t ); + lsmash_bs_t *bs = file->bs; + tfhd->track_ID = lsmash_bs_get_be32( bs ); + if( box->flags & ISOM_TF_FLAGS_BASE_DATA_OFFSET_PRESENT ) tfhd->base_data_offset = lsmash_bs_get_be64( bs ); + if( box->flags & ISOM_TF_FLAGS_SAMPLE_DESCRIPTION_INDEX_PRESENT ) tfhd->sample_description_index = lsmash_bs_get_be32( bs ); + if( box->flags & ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT ) tfhd->default_sample_duration = lsmash_bs_get_be32( bs ); + if( box->flags & ISOM_TF_FLAGS_DEFAULT_SAMPLE_SIZE_PRESENT ) tfhd->default_sample_size = lsmash_bs_get_be32( bs ); + if( box->flags & ISOM_TF_FLAGS_DEFAULT_SAMPLE_FLAGS_PRESENT ) tfhd->default_sample_flags = isom_bs_get_sample_flags( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, tfhd ); +} + +static int isom_read_tfdt( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TRAF ) + || LSMASH_IS_EXISTING_BOX( ((isom_traf_t *)parent)->tfdt ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( tfdt, isom_traf_t ); + lsmash_bs_t *bs = file->bs; + if( box->version == 1 ) + tfdt->baseMediaDecodeTime = lsmash_bs_get_be64( bs ); + else + tfdt->baseMediaDecodeTime = lsmash_bs_get_be32( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, tfdt ); +} + +static int isom_read_trun( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TRAF ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( trun, isom_traf_t ); + box->parent = parent; + lsmash_bs_t *bs = file->bs; + int has_optional_rows = ISOM_TR_FLAGS_SAMPLE_DURATION_PRESENT + | ISOM_TR_FLAGS_SAMPLE_SIZE_PRESENT + | ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT + | ISOM_TR_FLAGS_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT; + has_optional_rows &= box->flags; + trun->sample_count = lsmash_bs_get_be32( bs ); + if( box->flags & ISOM_TR_FLAGS_DATA_OFFSET_PRESENT ) trun->data_offset = lsmash_bs_get_be32( bs ); + if( box->flags & ISOM_TR_FLAGS_FIRST_SAMPLE_FLAGS_PRESENT ) trun->first_sample_flags = isom_bs_get_sample_flags( bs ); + if( trun->sample_count && has_optional_rows ) + { + trun->optional = lsmash_list_create_simple(); + if( !trun->optional ) + return LSMASH_ERR_MEMORY_ALLOC; + for( uint32_t i = 0; i < trun->sample_count; i++ ) + { + isom_trun_optional_row_t *data = lsmash_malloc( sizeof(isom_trun_optional_row_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( trun->optional, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + if( box->flags & ISOM_TR_FLAGS_SAMPLE_DURATION_PRESENT ) data->sample_duration = lsmash_bs_get_be32( bs ); + if( box->flags & ISOM_TR_FLAGS_SAMPLE_SIZE_PRESENT ) data->sample_size = lsmash_bs_get_be32( bs ); + if( box->flags & ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT ) data->sample_flags = isom_bs_get_sample_flags( bs ); + if( box->flags & ISOM_TR_FLAGS_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT ) data->sample_composition_time_offset = lsmash_bs_get_be32( bs ); + } + } + return isom_read_leaf_box_common_last_process( file, box, level, trun ); +} + +static int isom_read_free( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( file->fake_file_mode ) + return isom_read_unknown_box( file, box, parent, level ); + isom_skip_t *skip = ALLOCATE_BOX( skip ); + if( LSMASH_IS_NON_EXISTING_BOX( skip ) ) + return LSMASH_ERR_MEMORY_ALLOC; + isom_skip_box_rest( file->bs, box ); + box->manager |= LSMASH_ABSENT_IN_FILE; + isom_box_common_copy( skip, box ); + int ret = isom_add_print_func( file, skip, level ); + if( ret < 0 ) + { + isom_remove_box_by_itself( skip ); + return ret; + } + return 0; +} + +static int isom_read_mdat( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( file->fake_file_mode || !lsmash_check_box_type_identical( parent->type, LSMASH_BOX_TYPE_UNSPECIFIED ) ) + return isom_read_unknown_box( file, box, parent, level ); + isom_mdat_t *mdat = ALLOCATE_BOX( mdat ); + if( LSMASH_IS_NON_EXISTING_BOX( mdat ) ) + return LSMASH_ERR_MEMORY_ALLOC; + isom_skip_box_rest( file->bs, box ); + box->manager |= LSMASH_ABSENT_IN_FILE; + file->flags |= LSMASH_FILE_MODE_MEDIA; + isom_box_common_copy( mdat, box ); + int ret = isom_add_print_func( file, mdat, level ); + if( ret < 0 ) + { + isom_remove_box_by_itself( mdat ); + return ret; + } + return 0; +} + +static int isom_read_meta( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( (!lsmash_check_box_type_identical( parent->type, LSMASH_BOX_TYPE_UNSPECIFIED ) + && !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MOOV ) + && !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TRAK ) + && !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_UDTA )) + || (lsmash_check_box_type_identical( parent->type, LSMASH_BOX_TYPE_UNSPECIFIED ) && LSMASH_IS_EXISTING_BOX( ((lsmash_file_t *)parent)->meta )) + || (lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MOOV ) && LSMASH_IS_EXISTING_BOX( ((isom_moov_t *)parent)->meta )) + || (lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TRAK ) && LSMASH_IS_EXISTING_BOX( ((isom_trak_t *)parent)->meta )) + || (lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_UDTA ) && LSMASH_IS_EXISTING_BOX( ((isom_udta_t *)parent)->meta )) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( meta, void ); + isom_box_common_copy( meta, box ); + int is_qtff = lsmash_check_box_type_identical( box->type, QT_BOX_TYPE_META ); + if( is_qtff ) + { + box->manager |= LSMASH_QTFF_BASE; + meta->manager |= LSMASH_QTFF_BASE; + } + int ret = isom_add_print_func( file, meta, level ); + if( ret < 0 ) + return ret; + return isom_read_children( file, box, meta, level ); +} + +static int isom_read_keys( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( (!lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_META ) && !(parent->manager & LSMASH_QTFF_BASE)) + || LSMASH_IS_EXISTING_BOX( ((isom_meta_t *)parent)->keys ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( keys, isom_meta_t ); + lsmash_bs_t *bs = file->bs; + uint32_t entry_count = lsmash_bs_get_be32( bs ); + for( uint64_t pos = lsmash_bs_count( bs ); pos < box->size && keys->list->entry_count < entry_count; pos = lsmash_bs_count( bs ) ) + { + isom_keys_entry_t *data = lsmash_malloc( sizeof(isom_keys_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( keys->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + data->key_size = lsmash_bs_get_be32( bs ); + data->key_namespace = lsmash_bs_get_be32( bs ); + if( data->key_size > 8 ) + { + data->key_value = lsmash_bs_get_bytes( bs, data->key_size - 8 ); + if( !data->key_value ) + return LSMASH_ERR_NAMELESS; + } + else + data->key_value = NULL; + } + return isom_read_leaf_box_common_last_process( file, box, level, keys ); +} + +static int isom_read_ilst( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( (!lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_META ) + && !lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_META )) + || LSMASH_IS_EXISTING_BOX( ((isom_meta_t *)parent)->ilst ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( ilst, isom_meta_t ); + isom_box_common_copy( ilst, box ); + int ret = isom_add_print_func( file, ilst, level ); + if( ret < 0 ) + return ret; + return isom_read_children( file, box, ilst, level ); +} + +static int isom_read_metaitem( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_ILST ) + && !lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_ILST ) ) + return isom_read_unknown_box( file, box, parent, level ); + isom_metaitem_t *metaitem = isom_add_metaitem( (isom_ilst_t *)parent, box->type.fourcc ); + if( !metaitem ) + return -1; + box->parent = parent; + isom_box_common_copy( metaitem, box ); + int ret = isom_add_print_func( file, metaitem, level ); + if( ret < 0 ) + return ret; + return isom_read_children( file, box, metaitem, level ); +} + +static int isom_read_mean( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( parent->type.fourcc != ITUNES_METADATA_ITEM_CUSTOM + || LSMASH_IS_EXISTING_BOX( ((isom_metaitem_t *)parent)->mean ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( mean, isom_metaitem_t ); + lsmash_bs_t *bs = file->bs; + mean->meaning_string_length = box->size - lsmash_bs_count( bs ); + mean->meaning_string = lsmash_bs_get_bytes( bs, mean->meaning_string_length ); + if( !mean->meaning_string ) + return LSMASH_ERR_NAMELESS; + return isom_read_leaf_box_common_last_process( file, box, level, mean ); +} + +static int isom_read_name( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( parent->type.fourcc != ITUNES_METADATA_ITEM_CUSTOM + || LSMASH_IS_EXISTING_BOX( ((isom_metaitem_t *)parent)->name ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( name, isom_metaitem_t ); + lsmash_bs_t *bs = file->bs; + name->name_length = box->size - lsmash_bs_count( bs ); + name->name = lsmash_bs_get_bytes( bs, name->name_length ); + if( !name->name ) + return LSMASH_ERR_NAMELESS; + return isom_read_leaf_box_common_last_process( file, box, level, name ); +} + +static int isom_read_data( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( LSMASH_IS_EXISTING_BOX( ((isom_metaitem_t *)parent)->data ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( data, isom_metaitem_t ); + lsmash_bs_t *bs = file->bs; + data->value_length = box->size - lsmash_bs_count( bs ) - 8; + data->reserved = lsmash_bs_get_be16( bs ); + data->type_set_identifier = lsmash_bs_get_byte( bs ); + data->type_code = lsmash_bs_get_byte( bs ); + data->the_locale = lsmash_bs_get_be32( bs ); + if( data->value_length ) + { + data->value = lsmash_bs_get_bytes( bs, data->value_length ); + if( !data->value ) + return LSMASH_ERR_NAMELESS; + } + return isom_read_leaf_box_common_last_process( file, box, level, data ); +} + +static int isom_read_WLOC( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_UDTA ) + || LSMASH_IS_EXISTING_BOX( ((isom_udta_t *)parent)->WLOC ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( WLOC, isom_udta_t ); + lsmash_bs_t *bs = file->bs; + WLOC->x = lsmash_bs_get_be16( bs ); + WLOC->y = lsmash_bs_get_be16( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, WLOC ); +} + +static int isom_read_LOOP( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_UDTA ) + || LSMASH_IS_EXISTING_BOX( ((isom_udta_t *)parent)->LOOP ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( LOOP, isom_udta_t ); + lsmash_bs_t *bs = file->bs; + LOOP->looping_mode = lsmash_bs_get_be32( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, LOOP ); +} + +static int isom_read_SelO( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_UDTA ) + || LSMASH_IS_EXISTING_BOX( ((isom_udta_t *)parent)->SelO ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( SelO, isom_udta_t ); + lsmash_bs_t *bs = file->bs; + SelO->selection_only = lsmash_bs_get_byte( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, SelO ); +} + +static int isom_read_AllF( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_UDTA ) + || LSMASH_IS_EXISTING_BOX( ((isom_udta_t *)parent)->AllF ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( AllF, isom_udta_t ); + lsmash_bs_t *bs = file->bs; + AllF->play_all_frames = lsmash_bs_get_byte( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, AllF ); +} + +static int isom_read_cprt( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_UDTA ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( cprt, isom_udta_t ); + box->parent = parent; + lsmash_bs_t *bs = file->bs; + cprt->language = lsmash_bs_get_be16( bs ); + cprt->notice_length = box->size - (ISOM_FULLBOX_COMMON_SIZE + 2); + if( cprt->notice_length ) + { + cprt->notice = lsmash_bs_get_bytes( bs, cprt->notice_length ); + if( !cprt->notice ) + { + cprt->notice_length = 0; + return LSMASH_ERR_NAMELESS; + } + } + return isom_read_leaf_box_common_last_process( file, box, level, cprt ); +} + +static int isom_read_mfra( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, LSMASH_BOX_TYPE_UNSPECIFIED ) + || LSMASH_IS_EXISTING_BOX( ((lsmash_file_t *)parent)->mfra ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( mfra, lsmash_file_t ); + isom_box_common_copy( mfra, box ); + int ret = isom_add_print_func( file, mfra, level ); + if( ret < 0 ) + return ret; + return isom_read_children( file, box, mfra, level ); +} + +static int isom_read_tfra( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MFRA ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( tfra, isom_mfra_t ); + box->parent = parent; + lsmash_bs_t *bs = file->bs; + tfra->track_ID = lsmash_bs_get_be32( bs ); + uint32_t temp = lsmash_bs_get_be32( bs ); + tfra->number_of_entry = lsmash_bs_get_be32( bs ); + tfra->reserved = (temp >> 6) & 0x3ffffff; + tfra->length_size_of_traf_num = (temp >> 4) & 0x3; + tfra->length_size_of_trun_num = (temp >> 2) & 0x3; + tfra->length_size_of_sample_num = temp & 0x3; + if( tfra->number_of_entry ) + { + tfra->list = lsmash_list_create_simple(); + if( !tfra->list ) + return LSMASH_ERR_MEMORY_ALLOC; + uint64_t (*bs_get_funcs[5])( lsmash_bs_t * ) = + { + lsmash_bs_get_byte_to_64, + lsmash_bs_get_be16_to_64, + lsmash_bs_get_be24_to_64, + lsmash_bs_get_be32_to_64, + lsmash_bs_get_be64 + }; + uint64_t (*bs_put_time) ( lsmash_bs_t * ) = bs_get_funcs[ 3 + (box->version == 1) ]; + uint64_t (*bs_put_moof_offset) ( lsmash_bs_t * ) = bs_get_funcs[ 3 + (box->version == 1) ]; + uint64_t (*bs_put_traf_number) ( lsmash_bs_t * ) = bs_get_funcs[ tfra->length_size_of_traf_num ]; + uint64_t (*bs_put_trun_number) ( lsmash_bs_t * ) = bs_get_funcs[ tfra->length_size_of_trun_num ]; + uint64_t (*bs_put_sample_number)( lsmash_bs_t * ) = bs_get_funcs[ tfra->length_size_of_sample_num ]; + for( uint32_t i = 0; i < tfra->number_of_entry; i++ ) + { + isom_tfra_location_time_entry_t *data = lsmash_malloc( sizeof(isom_tfra_location_time_entry_t) ); + if( !data ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( tfra->list, data ) < 0 ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + data->time = bs_put_time ( bs ); + data->moof_offset = bs_put_moof_offset ( bs ); + data->traf_number = bs_put_traf_number ( bs ); + data->trun_number = bs_put_trun_number ( bs ); + data->sample_number = bs_put_sample_number( bs ); + } + } + return isom_read_leaf_box_common_last_process( file, box, level, tfra ); +} + +static int isom_read_mfro( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, int level ) +{ + if( !lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_MFRA ) + || LSMASH_IS_EXISTING_BOX( ((isom_mfra_t *)parent)->mfro ) ) + return isom_read_unknown_box( file, box, parent, level ); + ADD_BOX( mfro, isom_mfra_t ); + lsmash_bs_t *bs = file->bs; + mfro->length = lsmash_bs_get_be32( bs ); + return isom_read_leaf_box_common_last_process( file, box, level, mfro ); +} + +static void isom_read_skip_extra_bytes( lsmash_bs_t *bs, uint64_t size ) +{ + if( !bs->unseekable ) + { + /* lsmash_bs_read_seek() could fail on offset=INT64_MAX, so use (INT64_MAX >> 1) instead. */ + while( size > (INT64_MAX >> 1) ) + { + lsmash_bs_read_seek( bs, INT64_MAX >> 1, SEEK_CUR ); + if( lsmash_bs_is_end( bs, 0 ) ) + return; + size -= (INT64_MAX >> 1); + } + lsmash_bs_read_seek( bs, size, SEEK_CUR ); + } + else + lsmash_bs_skip_bytes_64( bs, size ); +} + +static int isom_read_skip_box_extra_bytes( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, uint64_t parent_pos ) +{ + lsmash_bs_t *bs = file->bs; + /* Skip extra bytes of the parent box if any. */ + if( parent->size < parent_pos + ISOM_BASEBOX_COMMON_SIZE ) + { + uint64_t extra_bytes = parent->size - parent_pos; + isom_read_skip_extra_bytes( bs, extra_bytes ); + /* This is not the size of a box but makes sense in isom_read_children(). */ + box->size = extra_bytes; + return 1; + } + /* Check if the size is valid or not. */ + if( lsmash_bs_is_end( bs, 3 ) == 0 ) + { + uint64_t size = (uint64_t)lsmash_bs_show_be32( bs, 0 ); + if( size > 1 + && size < ISOM_BASEBOX_COMMON_SIZE ) + { + /* It's not a valid size of any box, therefore, it seems we are still within the box considered as the previous. + * Skip bytes up to the next box of the parent box. */ + uint64_t extra_bytes = parent->size - parent_pos; + isom_read_skip_extra_bytes( bs, extra_bytes ); + box->size = extra_bytes; + return 1; + } + if( size == 1 && lsmash_bs_is_end( bs, 15 ) == 0 ) + size = lsmash_bs_show_be64( bs, 8 ); /* large size */ + if( size == 0 && parent != (isom_box_t *)file ) + { + /* Check if this box is actually the last box or not. */ + uint64_t extra_bytes = parent->size - parent_pos; + if( !bs->unseekable ) + size = bs->written - lsmash_bs_get_stream_pos( bs ); + else + { + size = lsmash_bs_get_remaining_buffer_size( bs ); + while( size <= extra_bytes ) + { + int ret = lsmash_bs_read( bs, 1 ); + if( bs->eof || ret < 0 ) + break; + size = lsmash_bs_get_remaining_buffer_size( bs ); + } + } + if( size != extra_bytes ) + { + /* This is not the size of the last box. + * It seems we are still within the box considered as the previous or the parent box. + * Skip bytes up to the next box. */ + if( box->size > lsmash_bs_count( bs ) ) + { + /* within the previous */ + isom_read_skip_extra_bytes( bs, box->size - lsmash_bs_count( bs ) ); + box->size = 0; /* already added to the size of the parent box */ + } + else + { + /* within the parent */ + isom_read_skip_extra_bytes( bs, extra_bytes ); + box->size = extra_bytes; + } + return 1; + } + } + } + return 0; +} + +int isom_read_box( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, uint64_t parent_pos, int level ) +{ + assert( parent && parent->root && parent->file ); + if( isom_read_skip_box_extra_bytes( file, box, parent, parent_pos ) != 0 ) + return 0; + memset( box, 0, sizeof(isom_box_t) ); + box->root = parent->root; + box->file = parent->file; + box->parent = parent; + lsmash_bs_t *bs = file->bs; + int ret = isom_bs_read_box_common( bs, box ); + if( !!ret ) + return ret; /* return if reached EOF */ + ++level; + lsmash_box_type_t (*form_box_type_func)( lsmash_compact_box_type_t ) = NULL; + int (*reader_func)( lsmash_file_t *, isom_box_t *, isom_box_t *, int ) = NULL; + if( box->type.fourcc != ISOM_BOX_TYPE_FREE.fourcc + && box->type.fourcc != ISOM_BOX_TYPE_SKIP.fourcc + && lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STSD ) ) + { + /* OK, this box is a sample entry. + * Here, determine the suitable sample entry reader by media type if possible. */ + if( !isom_check_media_hdlr_from_stsd( (isom_stsd_t *)parent ) ) + goto read_box; + lsmash_media_type media_type = isom_get_media_type_from_stsd( (isom_stsd_t *)parent ); + if( media_type == ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK ) + reader_func = isom_read_visual_description; + else if( media_type == ISOM_MEDIA_HANDLER_TYPE_AUDIO_TRACK ) + reader_func = isom_read_audio_description; + else if( media_type == ISOM_MEDIA_HANDLER_TYPE_TEXT_TRACK ) + reader_func = isom_read_text_description; + else + reader_func = isom_read_other_description; + /* Determine either of file formats the sample type is defined in; ISOBMFF or QTFF. */ + static struct description_reader_table_tag + { + lsmash_compact_box_type_t fourcc; + lsmash_box_type_t (*form_box_type_func)( lsmash_compact_box_type_t ); + } description_reader_table[160] = { { 0, NULL } }; + if( !description_reader_table[0].form_box_type_func ) + { + /* Initialize the table. */ + int i = 0; +#define ADD_DESCRIPTION_READER_TABLE_ELEMENT( type, form_box_type_func ) \ + description_reader_table[i++] = (struct description_reader_table_tag){ type.fourcc, form_box_type_func } + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_AVC1_VIDEO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_AVC2_VIDEO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_AVC3_VIDEO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_AVC4_VIDEO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_AVCP_VIDEO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_DRAC_VIDEO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_ENCV_VIDEO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_HVC1_VIDEO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_HEV1_VIDEO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_MJP2_VIDEO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_MP4V_VIDEO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_MVC1_VIDEO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_MVC2_VIDEO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_S263_VIDEO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_SVC1_VIDEO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_VC_1_VIDEO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_2VUY_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_CFHD_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_DV10_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_DVOO_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_DVOR_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_DVTV_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_DVVT_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_HD10_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_M105_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_PNTG_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_SVQ1_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_SVQ3_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_SHR0_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_SHR1_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_SHR2_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_SHR3_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_SHR4_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_WRLE_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_APCH_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_APCN_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_APCS_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_APCO_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_AP4H_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_AP4X_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_CIVD_VIDEO, lsmash_form_qtff_box_type ); + //ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_DRAC_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_DVC_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_DVCP_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_DVPP_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_DV5N_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_DV5P_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_DVH2_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_DVH3_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_DVH5_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_DVH6_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_DVHP_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_DVHQ_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_FLIC_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_GIF_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_H261_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_H263_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_JPEG_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_MJPA_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_MJPB_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_PNG_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_RLE_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_RPZA_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_TGA_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_TIFF_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_ULRA_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_ULRG_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_ULY2_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_ULY0_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_ULH2_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_ULH0_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_UQY2_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_V210_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_V216_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_V308_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_V408_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_V410_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_YUV2_VIDEO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_AC_3_AUDIO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_ALAC_AUDIO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_DRA1_AUDIO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_DTSEL_AUDIO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_DTSDL_AUDIO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_DTSC_AUDIO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_DTSE_AUDIO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_DTSH_AUDIO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_DTSL_AUDIO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_DTSX_AUDIO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_EC_3_AUDIO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_ENCA_AUDIO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_G719_AUDIO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_G726_AUDIO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_M4AE_AUDIO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_MLPA_AUDIO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_MP4A_AUDIO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_SAMR_AUDIO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_SAWB_AUDIO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_SAWP_AUDIO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_SEVC_AUDIO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_SQCP_AUDIO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_SSMV_AUDIO, lsmash_form_iso_box_type ); + //ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_TWOS_AUDIO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_WMA_AUDIO, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_23NI_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_MAC3_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_MAC6_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_NONE_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_QDM2_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_QDMC_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_QCLP_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_AGSM_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_ALAW_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_CDX2_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_CDX4_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_DVCA_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_DVI_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_FL32_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_FL64_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_IMA4_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_IN24_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_IN32_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_LPCM_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_SOWT_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_TWOS_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_ULAW_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_VDVA_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_FULLMP3_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_MP3_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_ADPCM2_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_ADPCM17_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_GSM49_AUDIO, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_NOT_SPECIFIED, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( QT_CODEC_TYPE_TEXT_TEXT, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_TX3G_TEXT, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( ISOM_CODEC_TYPE_MP4S_SYSTEM, lsmash_form_iso_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( LSMASH_CODEC_TYPE_RAW, lsmash_form_qtff_box_type ); + ADD_DESCRIPTION_READER_TABLE_ELEMENT( LSMASH_CODEC_TYPE_UNSPECIFIED, NULL ); + assert( sizeof(description_reader_table) >= (size_t)i * sizeof(description_reader_table[0]) ); +#undef ADD_DESCRIPTION_READER_TABLE_ELEMENT + } + for( int i = 0; description_reader_table[i].form_box_type_func; i++ ) + if( box->type.fourcc == description_reader_table[i].fourcc ) + { + form_box_type_func = description_reader_table[i].form_box_type_func; + goto read_box; + } + goto read_box; + } + if( lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_WAVE ) ) + { + form_box_type_func = lsmash_form_qtff_box_type; + if( box->type.fourcc == QT_BOX_TYPE_FRMA.fourcc ) reader_func = isom_read_frma; + else if( box->type.fourcc == QT_BOX_TYPE_ENDA.fourcc ) reader_func = isom_read_enda; + else if( box->type.fourcc == QT_BOX_TYPE_ESDS.fourcc ) reader_func = isom_read_esds; + else if( box->type.fourcc == QT_BOX_TYPE_CHAN.fourcc ) reader_func = isom_read_chan; + else if( box->type.fourcc == QT_BOX_TYPE_TERMINATOR.fourcc ) reader_func = isom_read_terminator; + else reader_func = isom_read_codec_specific; + goto read_box; + } + if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TREF ) ) + { + form_box_type_func = lsmash_form_iso_box_type; + reader_func = isom_read_track_reference_type; + goto read_box; + } + if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_DREF ) ) + { + if( box->type.fourcc == ISOM_BOX_TYPE_URL.fourcc + || box->type.fourcc == ISOM_BOX_TYPE_URN.fourcc ) + form_box_type_func = lsmash_form_iso_box_type; + else if( box->type.fourcc == QT_BOX_TYPE_ALIS.fourcc + || box->type.fourcc == QT_BOX_TYPE_RSRC.fourcc ) + form_box_type_func = lsmash_form_qtff_box_type; + reader_func = isom_read_dref_entry; + goto read_box; + } + static struct box_reader_table_tag + { + lsmash_compact_box_type_t fourcc; + lsmash_box_type_t (*form_box_type_func)( lsmash_compact_box_type_t ); + int (*reader_func)( lsmash_file_t *, isom_box_t *, isom_box_t *, int ); + } box_reader_table[128] = { { 0, NULL, NULL } }; + if( !box_reader_table[0].reader_func ) + { + /* Initialize the table. */ + int i = 0; +#define ADD_BOX_READER_TABLE_ELEMENT( type, form_box_type_func, reader_func ) \ + box_reader_table[i++] = (struct box_reader_table_tag){ type.fourcc, form_box_type_func, reader_func } + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_FTYP, lsmash_form_iso_box_type, isom_read_ftyp ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_STYP, lsmash_form_iso_box_type, isom_read_styp ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_SIDX, lsmash_form_iso_box_type, isom_read_sidx ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_MOOV, lsmash_form_iso_box_type, isom_read_moov ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_MVHD, lsmash_form_iso_box_type, isom_read_mvhd ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_IODS, lsmash_form_iso_box_type, isom_read_iods ); + ADD_BOX_READER_TABLE_ELEMENT( QT_BOX_TYPE_CTAB, lsmash_form_qtff_box_type, isom_read_ctab ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_TRAK, lsmash_form_iso_box_type, isom_read_trak ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_TKHD, lsmash_form_iso_box_type, isom_read_tkhd ); + ADD_BOX_READER_TABLE_ELEMENT( QT_BOX_TYPE_TAPT, lsmash_form_qtff_box_type, isom_read_tapt ); + ADD_BOX_READER_TABLE_ELEMENT( QT_BOX_TYPE_CLEF, lsmash_form_qtff_box_type, isom_read_clef ); + ADD_BOX_READER_TABLE_ELEMENT( QT_BOX_TYPE_PROF, lsmash_form_qtff_box_type, isom_read_prof ); + ADD_BOX_READER_TABLE_ELEMENT( QT_BOX_TYPE_ENOF, lsmash_form_qtff_box_type, isom_read_enof ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_EDTS, lsmash_form_iso_box_type, isom_read_edts ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_ELST, lsmash_form_iso_box_type, isom_read_elst ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_TREF, lsmash_form_iso_box_type, isom_read_tref ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_MDIA, lsmash_form_iso_box_type, isom_read_mdia ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_MDHD, lsmash_form_iso_box_type, isom_read_mdhd ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_HDLR, lsmash_form_iso_box_type, isom_read_hdlr ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_MINF, lsmash_form_iso_box_type, isom_read_minf ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_VMHD, lsmash_form_iso_box_type, isom_read_vmhd ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_SMHD, lsmash_form_iso_box_type, isom_read_smhd ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_HMHD, lsmash_form_iso_box_type, isom_read_hmhd ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_NMHD, lsmash_form_iso_box_type, isom_read_nmhd ); + ADD_BOX_READER_TABLE_ELEMENT( QT_BOX_TYPE_GMHD, lsmash_form_qtff_box_type, isom_read_gmhd ); + ADD_BOX_READER_TABLE_ELEMENT( QT_BOX_TYPE_GMIN, lsmash_form_qtff_box_type, isom_read_gmin ); + ADD_BOX_READER_TABLE_ELEMENT( QT_BOX_TYPE_TEXT, lsmash_form_qtff_box_type, isom_read_text ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_DINF, lsmash_form_iso_box_type, isom_read_dinf ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_DREF, lsmash_form_iso_box_type, isom_read_dref ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_STBL, lsmash_form_iso_box_type, isom_read_stbl ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_STSD, lsmash_form_iso_box_type, isom_read_stsd ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_STTS, lsmash_form_iso_box_type, isom_read_stts ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_CTTS, lsmash_form_iso_box_type, isom_read_ctts ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_CSLG, lsmash_form_iso_box_type, isom_read_cslg ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_STSS, lsmash_form_iso_box_type, isom_read_stss ); + ADD_BOX_READER_TABLE_ELEMENT( QT_BOX_TYPE_STPS, lsmash_form_qtff_box_type, isom_read_stps ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_SDTP, lsmash_form_iso_box_type, isom_read_sdtp ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_STSC, lsmash_form_iso_box_type, isom_read_stsc ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_STSZ, lsmash_form_iso_box_type, isom_read_stsz ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_STZ2, lsmash_form_iso_box_type, isom_read_stz2 ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_STCO, lsmash_form_iso_box_type, isom_read_stco ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_CO64, lsmash_form_iso_box_type, isom_read_stco ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_SGPD, lsmash_form_iso_box_type, isom_read_sgpd ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_SBGP, lsmash_form_iso_box_type, isom_read_sbgp ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_UDTA, lsmash_form_iso_box_type, isom_read_udta ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_CHPL, lsmash_form_iso_box_type, isom_read_chpl ); + ADD_BOX_READER_TABLE_ELEMENT( QT_BOX_TYPE_WLOC, lsmash_form_qtff_box_type, isom_read_WLOC ); + ADD_BOX_READER_TABLE_ELEMENT( QT_BOX_TYPE_LOOP, lsmash_form_qtff_box_type, isom_read_LOOP ); + ADD_BOX_READER_TABLE_ELEMENT( QT_BOX_TYPE_SELO, lsmash_form_qtff_box_type, isom_read_SelO ); + ADD_BOX_READER_TABLE_ELEMENT( QT_BOX_TYPE_ALLF, lsmash_form_qtff_box_type, isom_read_AllF ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_MVEX, lsmash_form_iso_box_type, isom_read_mvex ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_MEHD, lsmash_form_iso_box_type, isom_read_mehd ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_TREX, lsmash_form_iso_box_type, isom_read_trex ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_MOOF, lsmash_form_iso_box_type, isom_read_moof ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_MFHD, lsmash_form_iso_box_type, isom_read_mfhd ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_TRAF, lsmash_form_iso_box_type, isom_read_traf ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_TFHD, lsmash_form_iso_box_type, isom_read_tfhd ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_TFDT, lsmash_form_iso_box_type, isom_read_tfdt ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_TRUN, lsmash_form_iso_box_type, isom_read_trun ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_FREE, lsmash_form_iso_box_type, isom_read_free ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_SKIP, lsmash_form_iso_box_type, isom_read_free ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_MDAT, lsmash_form_iso_box_type, isom_read_mdat ); + ADD_BOX_READER_TABLE_ELEMENT( QT_BOX_TYPE_KEYS, lsmash_form_qtff_box_type, isom_read_keys ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_MFRA, lsmash_form_iso_box_type, isom_read_mfra ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_TFRA, lsmash_form_iso_box_type, isom_read_tfra ); + ADD_BOX_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_MFRO, lsmash_form_iso_box_type, isom_read_mfro ); + ADD_BOX_READER_TABLE_ELEMENT( LSMASH_BOX_TYPE_UNSPECIFIED, NULL, NULL ); + assert( sizeof(box_reader_table) >= (size_t)i * sizeof(box_reader_table[0]) ); +#undef ADD_BOX_READER_TABLE_ELEMENT + } + for( int i = 0; box_reader_table[i].reader_func; i++ ) + if( box->type.fourcc == box_reader_table[i].fourcc ) + { + form_box_type_func = box_reader_table[i].form_box_type_func; + reader_func = box_reader_table[i].reader_func; + goto read_box; + } + if( box->type.fourcc == ISOM_BOX_TYPE_META.fourcc ) + { + if( lsmash_bs_is_end ( bs, 3 ) == 0 + && lsmash_bs_show_be32( bs, 0 ) == 0 ) + form_box_type_func = lsmash_form_iso_box_type; + else + form_box_type_func = lsmash_form_qtff_box_type; + reader_func = isom_read_meta; + goto read_box; + } + if( box->type.fourcc == ISOM_BOX_TYPE_ILST.fourcc ) + { + if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_META ) ) + form_box_type_func = lsmash_form_iso_box_type; + else if( lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_META ) ) + form_box_type_func = lsmash_form_qtff_box_type; + if( form_box_type_func ) + { + reader_func = isom_read_ilst; + goto read_box; + } + } + if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_ILST ) ) + form_box_type_func = lsmash_form_iso_box_type; + else if( lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_ILST ) ) + form_box_type_func = lsmash_form_qtff_box_type; + if( form_box_type_func ) + { + reader_func = isom_read_metaitem; + goto read_box; + } + if( parent->parent && parent->parent->type.fourcc == ISOM_BOX_TYPE_ILST.fourcc ) + { + if( box->type.fourcc == ISOM_BOX_TYPE_MEAN.fourcc ) + reader_func = isom_read_mean; + else if( box->type.fourcc == ISOM_BOX_TYPE_NAME.fourcc ) + reader_func = isom_read_name; + else if( box->type.fourcc == ISOM_BOX_TYPE_DATA.fourcc ) + reader_func = isom_read_data; + if( reader_func ) + { + form_box_type_func = lsmash_form_iso_box_type; + goto read_box; + } + } + else if( box->type.fourcc == ISOM_BOX_TYPE_CPRT.fourcc ) + { + /* Avoid confusing udta.cprt with ilst.cprt. */ + form_box_type_func = lsmash_form_iso_box_type; + reader_func = isom_read_cprt; + goto read_box; + } + if( parent->parent && lsmash_check_box_type_identical( parent->parent->type, ISOM_BOX_TYPE_STSD ) ) + { + static struct sample_description_extension_reader_table_tag + { + lsmash_compact_box_type_t fourcc; + lsmash_box_type_t (*form_box_type_func)( lsmash_compact_box_type_t ); + int (*reader_func)( lsmash_file_t *, isom_box_t *, isom_box_t *, int ); + } extension_reader_table[32] = { { 0, NULL, NULL } }; + if( !extension_reader_table[0].reader_func ) + { + /* Initialize the table. */ + int i = 0; +#define ADD_EXTENSION_READER_TABLE_ELEMENT( type, form_box_type_func, reader_func ) \ + extension_reader_table[i++] = (struct sample_description_extension_reader_table_tag){ type.fourcc, form_box_type_func, reader_func } + /* Audio */ + ADD_EXTENSION_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_ALAC, lsmash_form_iso_box_type, isom_read_codec_specific ); + ADD_EXTENSION_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_DAC3, lsmash_form_iso_box_type, isom_read_codec_specific ); + ADD_EXTENSION_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_DAMR, lsmash_form_iso_box_type, isom_read_codec_specific ); + ADD_EXTENSION_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_DDTS, lsmash_form_iso_box_type, isom_read_codec_specific ); + ADD_EXTENSION_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_DEC3, lsmash_form_iso_box_type, isom_read_codec_specific ); + ADD_EXTENSION_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_SRAT, lsmash_form_iso_box_type, isom_read_srat ); + ADD_EXTENSION_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_WFEX, lsmash_form_iso_box_type, isom_read_codec_specific ); + ADD_EXTENSION_READER_TABLE_ELEMENT( QT_BOX_TYPE_CHAN, lsmash_form_qtff_box_type, isom_read_chan ); + ADD_EXTENSION_READER_TABLE_ELEMENT( QT_BOX_TYPE_WAVE, lsmash_form_qtff_box_type, isom_read_wave ); + /* Video */ + ADD_EXTENSION_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_AVCC, lsmash_form_iso_box_type, isom_read_codec_specific ); + ADD_EXTENSION_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_BTRT, lsmash_form_iso_box_type, isom_read_btrt ); + ADD_EXTENSION_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_COLR, lsmash_form_iso_box_type, isom_read_colr ); + ADD_EXTENSION_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_CLAP, lsmash_form_iso_box_type, isom_read_clap ); + ADD_EXTENSION_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_DVC1, lsmash_form_iso_box_type, isom_read_codec_specific ); + ADD_EXTENSION_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_HVCC, lsmash_form_iso_box_type, isom_read_codec_specific ); + ADD_EXTENSION_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_PASP, lsmash_form_iso_box_type, isom_read_pasp ); + ADD_EXTENSION_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_STSL, lsmash_form_iso_box_type, isom_read_stsl ); + ADD_EXTENSION_READER_TABLE_ELEMENT( QT_BOX_TYPE_CLLI, lsmash_form_qtff_box_type, isom_read_clli ); + ADD_EXTENSION_READER_TABLE_ELEMENT( QT_BOX_TYPE_CSPC, lsmash_form_qtff_box_type, isom_read_cspc ); + ADD_EXTENSION_READER_TABLE_ELEMENT( QT_BOX_TYPE_MDCV, lsmash_form_qtff_box_type, isom_read_mdcv ); + ADD_EXTENSION_READER_TABLE_ELEMENT( QT_BOX_TYPE_FIEL, lsmash_form_qtff_box_type, isom_read_fiel ); + ADD_EXTENSION_READER_TABLE_ELEMENT( QT_BOX_TYPE_GAMA, lsmash_form_qtff_box_type, isom_read_gama ); + ADD_EXTENSION_READER_TABLE_ELEMENT( QT_BOX_TYPE_GLBL, lsmash_form_qtff_box_type, isom_read_glbl ); + ADD_EXTENSION_READER_TABLE_ELEMENT( QT_BOX_TYPE_SGBT, lsmash_form_qtff_box_type, isom_read_sgbt ); + /* Others */ + ADD_EXTENSION_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_ESDS, lsmash_form_iso_box_type, isom_read_esds ); + ADD_EXTENSION_READER_TABLE_ELEMENT( ISOM_BOX_TYPE_FTAB, lsmash_form_iso_box_type, isom_read_ftab ); + ADD_EXTENSION_READER_TABLE_ELEMENT( LSMASH_BOX_TYPE_UNSPECIFIED, NULL, NULL ); + assert( sizeof(extension_reader_table) >= (size_t)i * sizeof(extension_reader_table[0]) ); +#undef ADD_EXTENSION_READER_TABLE_ELEMENT + } + for( int i = 0; extension_reader_table[i].reader_func; i++ ) + if( box->type.fourcc == extension_reader_table[i].fourcc ) + { + form_box_type_func = extension_reader_table[i].form_box_type_func; + reader_func = extension_reader_table[i].reader_func; + goto read_box; + } + reader_func = isom_read_codec_specific; + } +read_box: + if( form_box_type_func ) + box->type = form_box_type_func( box->type.fourcc ); + if( (ret = isom_read_fullbox_common_extension( bs, box )) < 0 ) + return ret; + return reader_func + ? reader_func( file, box, parent, level ) + : isom_read_unknown_box( file, box, parent, level ); +} + +int isom_read_file( lsmash_file_t *file ) +{ + lsmash_bs_t *bs = file->bs; + if( !bs ) + return LSMASH_ERR_NAMELESS; + /* Reset the counter so that we can use it to get position within the box. */ + lsmash_bs_reset_counter( bs ); + if( file->flags & LSMASH_FILE_MODE_DUMP ) + { + file->print = isom_printer_create_list(); + if( !file->print ) + return LSMASH_ERR_MEMORY_ALLOC; + } + file->size = UINT64_MAX; + isom_box_t box; + int ret = isom_read_children( file, &box, file, 0 ); + file->size = box.size; + lsmash_bs_empty( bs ); + bs->error = 0; /* Clear error flag. */ + if( ret < 0 ) + return ret; + return isom_check_compatibility( file ); +} diff --git a/3rdparty/l-smash/core/read.h b/3rdparty/l-smash/core/read.h new file mode 100644 index 000000000..dc81014af --- /dev/null +++ b/3rdparty/l-smash/core/read.h @@ -0,0 +1,29 @@ +/***************************************************************************** + * read.h + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#ifndef LSMASH_READ_H +#define LSMASH_READ_H + +int isom_read_file( lsmash_file_t *file ); +int isom_read_box( lsmash_file_t *file, isom_box_t *box, isom_box_t *parent, uint64_t parent_pos, int level ); + +#endif /* LSMASH_READ_H */ diff --git a/3rdparty/l-smash/core/summary.c b/3rdparty/l-smash/core/summary.c new file mode 100644 index 000000000..320e33960 --- /dev/null +++ b/3rdparty/l-smash/core/summary.c @@ -0,0 +1,359 @@ +/***************************************************************************** + * summary.c + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Takashi Hirata + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include +#include + +#include "box.h" + +#include "codecs/mp4a.h" +#include "codecs/mp4sys.h" +#include "codecs/description.h" + +/*************************************************************************** + summary and AudioSpecificConfig relative tools +***************************************************************************/ + +/* create AudioSpecificConfig as memory block from summary, and set it into that summary itself */ +int lsmash_setup_AudioSpecificConfig( lsmash_audio_summary_t *summary ) +{ + if( !summary || !summary->opaque ) + return LSMASH_ERR_FUNCTION_PARAM; + /* Remove an old one. */ + for( lsmash_entry_t *entry = summary->opaque->list.head; entry; ) + { + lsmash_entry_t *old_entry = entry; + entry = entry->next; + lsmash_codec_specific_t *cs = (lsmash_codec_specific_t *)old_entry->data; + if( !cs || cs->type != LSMASH_CODEC_SPECIFIC_DATA_TYPE_MP4SYS_DECODER_CONFIG ) + continue; + lsmash_list_remove_entry_direct( &summary->opaque->list, old_entry ); + } + /* Create and add a new one. */ + uint32_t data_length; + uint8_t *data = mp4a_export_AudioSpecificConfig( summary->aot, + summary->frequency, + summary->channels, + summary->sbr_mode, + NULL, /* FIXME */ + 0, /* FIXME */ + &data_length ); + if( !data ) + return LSMASH_ERR_NAMELESS; + lsmash_codec_specific_t *cs = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_MP4SYS_DECODER_CONFIG, + LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !cs ) + { + lsmash_free( data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + lsmash_mp4sys_decoder_parameters_t *param = (lsmash_mp4sys_decoder_parameters_t *)cs->data.structured; + param->objectTypeIndication = MP4SYS_OBJECT_TYPE_Audio_ISO_14496_3; + param->streamType = MP4SYS_STREAM_TYPE_AudioStream; + int err = lsmash_set_mp4sys_decoder_specific_info( param, data, data_length ); + lsmash_free( data ); + if( err < 0 || (err = lsmash_list_add_entry( &summary->opaque->list, cs )) < 0 ) + { + lsmash_destroy_codec_specific_data( cs ); + return err; + } + return 0; +} + +static lsmash_codec_specific_list_t *summary_allocate_opaque( void ) +{ + lsmash_codec_specific_list_t *opaque = (lsmash_codec_specific_list_t *)lsmash_malloc_zero( sizeof(lsmash_codec_specific_list_t) ); + if( opaque ) + lsmash_list_init( &opaque->list, lsmash_destroy_codec_specific_data ); + return opaque; +} + +static void summary_deallocate_opaque +( + lsmash_codec_specific_list_t *opaque +) +{ + if( !opaque ) + return; + lsmash_list_remove_entries( &opaque->list ); + lsmash_free( opaque ); +} + +lsmash_summary_t *lsmash_create_summary( lsmash_summary_type summary_type ) +{ + size_t summary_size; + switch( summary_type ) + { + case LSMASH_SUMMARY_TYPE_VIDEO : + summary_size = sizeof(lsmash_video_summary_t); + break; + case LSMASH_SUMMARY_TYPE_AUDIO : + summary_size = sizeof(lsmash_audio_summary_t); + break; + case LSMASH_SUMMARY_TYPE_HINT: + summary_size = sizeof(lsmash_hint_summary_t); + break; + default : + /* 'summary_size = sizeof(lsmash_summary_t);' is a dead assignment here. */ + return NULL; + } + lsmash_summary_t *summary = (lsmash_summary_t *)lsmash_malloc_zero( summary_size ); + if( !summary ) + return NULL; + summary->opaque = summary_allocate_opaque(); + if( !summary->opaque ) + { + lsmash_free( summary ); + return NULL; + } + summary->summary_type = summary_type; + summary->data_ref_index = 1; + return summary; +} + +void lsmash_cleanup_summary( lsmash_summary_t *summary ) +{ + if( !summary ) + return; + summary_deallocate_opaque( summary->opaque ); + lsmash_free( summary ); +} + +int lsmash_add_codec_specific_data( lsmash_summary_t *summary, lsmash_codec_specific_t *specific ) +{ + if( !summary || !summary->opaque || !specific ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_codec_specific_t *dup = isom_duplicate_codec_specific_data( specific ); + if( !dup ) + return LSMASH_ERR_NAMELESS; + if( lsmash_list_add_entry( &summary->opaque->list, dup ) < 0 ) + { + lsmash_destroy_codec_specific_data( dup ); + return LSMASH_ERR_MEMORY_ALLOC; + } + return 0; +} + +uint32_t lsmash_count_summary( lsmash_root_t *root, uint32_t track_ID ) +{ + if( isom_check_initializer_present( root ) < 0 || track_ID == 0 ) + return 0; + isom_trak_t *trak = isom_get_trak( root->file->initializer, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak ) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->mdhd ) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->hdlr ) ) + return 0; + return trak->mdia->minf->stbl->stsd->list.entry_count; +} + +lsmash_summary_t *lsmash_get_summary( lsmash_root_t *root, uint32_t track_ID, uint32_t description_number ) +{ + if( isom_check_initializer_present( root ) < 0 || track_ID == 0 || description_number == 0 ) + return NULL; + isom_trak_t *trak = isom_get_trak( root->file->initializer, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak->mdia->mdhd ) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->hdlr ) ) + return NULL; + isom_minf_t *minf = trak->mdia->minf; + isom_stsd_t *stsd = minf->stbl->stsd; + uint32_t i = 1; + for( lsmash_entry_t *entry = stsd->list.head; entry; entry = entry->next ) + { + if( i != description_number ) + { + ++i; + continue; + } + isom_sample_entry_t *sample_entry = entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( sample_entry ) ) + return NULL; + if( LSMASH_IS_EXISTING_BOX( minf->vmhd ) ) + return isom_create_video_summary_from_description( sample_entry ); + else if( LSMASH_IS_EXISTING_BOX( minf->smhd ) ) + return isom_create_audio_summary_from_description( sample_entry ); + else + return NULL; + } + return NULL; +} + +int lsmash_compare_summary( lsmash_summary_t *a, lsmash_summary_t *b ) +{ + if( !a || !b ) + return LSMASH_ERR_FUNCTION_PARAM; + if( a->summary_type != b->summary_type + || !lsmash_check_box_type_identical( a->sample_type, b->sample_type ) ) + return 1; + if( a->summary_type == LSMASH_SUMMARY_TYPE_VIDEO ) + { + lsmash_video_summary_t *in_video = (lsmash_video_summary_t *)a; + lsmash_video_summary_t *out_video = (lsmash_video_summary_t *)b; + if( in_video->width != out_video->width + || in_video->height != out_video->height + || in_video->depth != out_video->depth + || in_video->par_h != out_video->par_h + || in_video->par_v != out_video->par_v + || memcmp( in_video->compressorname, out_video->compressorname, strlen( in_video->compressorname ) ) + || in_video->clap.width.n != out_video->clap.width.n + || in_video->clap.width.d != out_video->clap.width.d + || in_video->clap.height.n != out_video->clap.height.n + || in_video->clap.height.d != out_video->clap.height.d + || in_video->clap.horizontal_offset.n != out_video->clap.horizontal_offset.n + || in_video->clap.horizontal_offset.d != out_video->clap.horizontal_offset.d + || in_video->clap.vertical_offset.n != out_video->clap.vertical_offset.n + || in_video->clap.vertical_offset.d != out_video->clap.vertical_offset.d + || in_video->color.primaries_index != out_video->color.primaries_index + || in_video->color.transfer_index != out_video->color.transfer_index + || in_video->color.matrix_index != out_video->color.matrix_index + || in_video->color.full_range != out_video->color.full_range ) + return 1; + } + else if( a->summary_type == LSMASH_SUMMARY_TYPE_AUDIO ) + { + lsmash_audio_summary_t *in_audio = (lsmash_audio_summary_t *)a; + lsmash_audio_summary_t *out_audio = (lsmash_audio_summary_t *)b; + if( in_audio->frequency != out_audio->frequency + || in_audio->channels != out_audio->channels + || in_audio->sample_size != out_audio->sample_size + || in_audio->samples_in_frame != out_audio->samples_in_frame ) + return 1; + } + else if( a->summary_type == LSMASH_SUMMARY_TYPE_HINT ) + { + lsmash_hint_summary_t *in_hint = (lsmash_hint_summary_t *)a; + lsmash_hint_summary_t *out_hint = (lsmash_hint_summary_t *)b; + if( in_hint->version != out_hint->version + || in_hint->highestcompatibleversion != out_hint->highestcompatibleversion + || in_hint->maxpacketsize != out_hint->maxpacketsize ) + return 1; + } + return isom_compare_opaque_extensions( a, b ); +} + +lsmash_codec_support_flag lsmash_check_codec_support( lsmash_codec_type_t sample_type ) +{ + static struct codec_support_table_tag + { + lsmash_codec_type_t type; + lsmash_codec_support_flag flags; + } codec_support_table[160] = { { LSMASH_CODEC_TYPE_INITIALIZER, LSMASH_CODEC_SUPPORT_FLAG_NONE } }; + if( lsmash_check_codec_type_identical( codec_support_table[0].type, LSMASH_CODEC_TYPE_UNSPECIFIED ) ) + { + /* Initialize the table. */ + int i = 0; +#define ADD_CODEC_SUPPORT_TABLE_ELEMENT( type, flags ) \ + codec_support_table[i++] = (struct codec_support_table_tag){ type, flags } + ADD_CODEC_SUPPORT_TABLE_ELEMENT( ISOM_CODEC_TYPE_AC_3_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( ISOM_CODEC_TYPE_ALAC_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( ISOM_CODEC_TYPE_DTSC_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( ISOM_CODEC_TYPE_DTSE_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( ISOM_CODEC_TYPE_DTSH_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( ISOM_CODEC_TYPE_DTSL_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( ISOM_CODEC_TYPE_DTSX_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( ISOM_CODEC_TYPE_EC_3_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( ISOM_CODEC_TYPE_MP4A_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( ISOM_CODEC_TYPE_SAMR_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( ISOM_CODEC_TYPE_SAWB_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_23NI_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_MAC3_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_MAC6_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_NONE_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_QCLP_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_DEMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_AGSM_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_ALAC_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_ALAW_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_FL32_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_FL64_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_IN24_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_IN32_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_LPCM_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_MP4A_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_RAW_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_SOWT_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_TWOS_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_ULAW_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_FULLMP3_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_ADPCM2_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_ADPCM17_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_GSM49_AUDIO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_NOT_SPECIFIED, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( ISOM_CODEC_TYPE_AVC1_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( ISOM_CODEC_TYPE_AVC3_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( ISOM_CODEC_TYPE_HVC1_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( ISOM_CODEC_TYPE_HEV1_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( ISOM_CODEC_TYPE_MP4V_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_MUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( ISOM_CODEC_TYPE_VC_1_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_2VUY_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_DV10_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_DVOO_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_APCH_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_APCN_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_APCS_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_APCO_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_AP4H_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_AP4X_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_DVC_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_DVCP_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_DVPP_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_DV5N_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_DV5P_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_DVH2_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_DVH3_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_DVH5_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_DVH6_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_DVHP_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_DVHQ_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_FLIC_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_H261_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_H263_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_JPEG_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_MJPA_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_MJPB_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_PNG_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_RAW_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_RLE_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_RPZA_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_TGA_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_TIFF_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_ULRA_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_ULRG_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_ULY0_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_ULY2_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_ULH0_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_ULH2_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_UQY2_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_V210_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_V216_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_V308_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_V408_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_V410_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( QT_CODEC_TYPE_YUV2_VIDEO, LSMASH_CODEC_SUPPORT_FLAG_REMUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( ISOM_CODEC_TYPE_RRTP_HINT, LSMASH_CODEC_SUPPORT_FLAG_MUX ); + ADD_CODEC_SUPPORT_TABLE_ELEMENT( LSMASH_CODEC_TYPE_UNSPECIFIED, LSMASH_CODEC_SUPPORT_FLAG_NONE ); + } + for( int i = 0; !lsmash_check_codec_type_identical( codec_support_table[i].type, LSMASH_CODEC_TYPE_UNSPECIFIED ); i++ ) + if( lsmash_check_codec_type_identical( sample_type, codec_support_table[i].type ) ) + return codec_support_table[i].flags; + return LSMASH_CODEC_SUPPORT_FLAG_NONE; +} diff --git a/3rdparty/l-smash/core/timeline.c b/3rdparty/l-smash/core/timeline.c new file mode 100644 index 000000000..b64b75591 --- /dev/null +++ b/3rdparty/l-smash/core/timeline.c @@ -0,0 +1,2081 @@ +/***************************************************************************** + * timeline.c + ***************************************************************************** + * Copyright (C) 2011-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include +#include +#include + +#include "box.h" +#include "timeline.h" + +#include "codecs/mp4a.h" +#include "codecs/mp4sys.h" +#include "codecs/description.h" + +#include "importer/importer.h" + +#define NO_RANDOM_ACCESS_POINT 0xffffffff + +typedef struct +{ + uint64_t pos; + uint32_t duration; + uint32_t offset; + uint32_t length; + uint32_t index; + isom_portable_chunk_t *chunk; + lsmash_sample_property_t prop; +} isom_sample_info_t; + +static const lsmash_class_t lsmash_timeline_class = +{ + "timeline" +}; + +struct isom_timeline_tag +{ + const lsmash_class_t *class; + uint32_t track_ID; + uint32_t movie_timescale; + uint32_t media_timescale; + uint32_t sample_count; + uint32_t max_sample_size; + uint32_t ctd_shift; /* shift from composition to decode timeline */ + uint64_t media_duration; + uint64_t track_duration; + uint32_t last_accessed_sample_number; + uint64_t last_accessed_sample_dts; + uint32_t last_accessed_lpcm_bunch_number; + uint32_t last_accessed_lpcm_bunch_duration; + uint32_t last_accessed_lpcm_bunch_sample_count; + uint32_t last_accessed_lpcm_bunch_first_sample_number; + uint64_t last_accessed_lpcm_bunch_dts; + lsmash_entry_list_t edit_list [1]; /* list of edits */ + lsmash_entry_list_t chunk_list[1]; /* list of chunks */ + lsmash_entry_list_t info_list [1]; /* list of sample info */ + lsmash_entry_list_t bunch_list[1]; /* list of LPCM bunch */ + int (*get_dts)( isom_timeline_t *timeline, uint32_t sample_number, uint64_t *dts ); + int (*get_cts)( isom_timeline_t *timeline, uint32_t sample_number, uint64_t *cts ); + int (*get_sample_duration)( isom_timeline_t *timeline, uint32_t sample_number, uint32_t *sample_duration ); + lsmash_sample_t *(*get_sample)( isom_timeline_t *timeline, uint32_t sample_number ); + int (*get_sample_info)( isom_timeline_t *timeline, uint32_t sample_number, lsmash_sample_t *sample ); + int (*get_sample_property)( isom_timeline_t *timeline, uint32_t sample_number, lsmash_sample_property_t *prop ); + int (*check_sample_existence)( isom_timeline_t *timeline, uint32_t sample_number ); +}; + +isom_timeline_t *isom_get_timeline( lsmash_root_t *root, uint32_t track_ID ) +{ + if( isom_check_initializer_present( root ) < 0 + || track_ID == 0 + || !root->file->timeline ) + return NULL; + for( lsmash_entry_t *entry = root->file->timeline->head; entry; entry = entry->next ) + { + isom_timeline_t *timeline = (isom_timeline_t *)entry->data; + if( !timeline ) + return NULL; + if( timeline->track_ID == track_ID ) + return timeline; + } + return NULL; +} + +isom_timeline_t *isom_timeline_create( void ) +{ + isom_timeline_t *timeline = lsmash_malloc_zero( sizeof(isom_timeline_t) ); + if( !timeline ) + return NULL; + timeline->class = &lsmash_timeline_class; + lsmash_list_init_simple( timeline->edit_list ); + lsmash_list_init_simple( timeline->chunk_list ); + lsmash_list_init_simple( timeline->info_list ); + lsmash_list_init_simple( timeline->bunch_list ); + return timeline; +} + +void isom_timeline_destroy( isom_timeline_t *timeline ) +{ + if( !timeline ) + return; + lsmash_list_remove_entries( timeline->edit_list ); + lsmash_list_remove_entries( timeline->chunk_list ); /* chunk data must be already freed. */ + lsmash_list_remove_entries( timeline->info_list ); + lsmash_list_remove_entries( timeline->bunch_list ); + lsmash_free( timeline ); +} + +void isom_remove_timelines( lsmash_file_t *file ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( file ) || !file->timeline ) + return; + lsmash_list_destroy( file->timeline ); +} + +void lsmash_destruct_timeline( lsmash_root_t *root, uint32_t track_ID ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( root ) + || track_ID == 0 + || !root->file->timeline ) + return; + for( lsmash_entry_t *entry = root->file->timeline->head; entry; entry = entry->next ) + { + isom_timeline_t *timeline = (isom_timeline_t *)entry->data; + if( !timeline ) + continue; + if( timeline->track_ID == track_ID ) + { + lsmash_list_remove_entry_direct( root->file->timeline, entry ); + break; + } + } +} + +int isom_timeline_set_track_ID +( + isom_timeline_t *timeline, + uint32_t track_ID +) +{ + if( !timeline || track_ID == 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + timeline->track_ID = track_ID; + return 0; +} + +int isom_timeline_set_movie_timescale +( + isom_timeline_t *timeline, + uint32_t movie_timescale +) +{ + if( !timeline || movie_timescale == 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + timeline->movie_timescale = movie_timescale; + return 0; +} + +int isom_timeline_set_media_timescale +( + isom_timeline_t *timeline, + uint32_t media_timescale +) +{ + if( !timeline || media_timescale == 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + timeline->media_timescale = media_timescale; + return 0; +} + +int isom_timeline_set_sample_count +( + isom_timeline_t *timeline, + uint32_t sample_count +) +{ + if( !timeline || sample_count == 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + timeline->sample_count = sample_count; + return 0; +} + +int isom_timeline_set_max_sample_size +( + isom_timeline_t *timeline, + uint32_t max_sample_size +) +{ + if( !timeline || max_sample_size == 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + timeline->max_sample_size = max_sample_size; + return 0; +} + +int isom_timeline_set_media_duration +( + isom_timeline_t *timeline, + uint32_t media_duration +) +{ + if( !timeline || media_duration == 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + timeline->media_duration = media_duration; + return 0; +} + +int isom_timeline_set_track_duration +( + isom_timeline_t *timeline, + uint32_t track_duration +) +{ + if( !timeline || track_duration == 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + timeline->track_duration = track_duration; + return 0; +} + +static void isom_get_qt_fixed_comp_audio_sample_quants +( + isom_timeline_t *timeline, + isom_sample_entry_t *description, + uint32_t *samples_per_packet, + uint32_t *constant_sample_size +) +{ + isom_audio_entry_t *audio = (isom_audio_entry_t *)description; + if( audio->version == 0 ) + { + uint32_t dummy; + if( !isom_get_implicit_qt_fixed_comp_audio_sample_quants( audio, samples_per_packet, constant_sample_size, &dummy ) ) + { + /* LPCM */ + if( !isom_is_lpcm_audio( audio ) ) + lsmash_log( timeline, LSMASH_LOG_WARNING, "unsupported implicit sample table!\n" ); + *samples_per_packet = 1; + *constant_sample_size = (audio->samplesize * audio->channelcount) / 8; + } + } + else if( audio->version == 1 ) + { + *samples_per_packet = audio->samplesPerPacket; + *constant_sample_size = audio->bytesPerFrame; + } + else /* if( audio->version == 2 ) */ + { + *samples_per_packet = audio->constLPCMFramesPerAudioPacket; + *constant_sample_size = audio->constBytesPerAudioPacket; + } +} + +static int isom_is_qt_fixed_compressed_audio +( + isom_sample_entry_t *description +) +{ + if( (description->manager & LSMASH_VIDEO_DESCRIPTION) || !isom_is_qt_audio( description->type ) ) + return 0; + /* LPCM is a special case of fixed compression. */ + return (((isom_audio_entry_t *)description)->compression_ID != QT_AUDIO_COMPRESSION_ID_VARIABLE_COMPRESSION); +} + +static int isom_add_sample_info_entry( isom_timeline_t *timeline, isom_sample_info_t *src_info ) +{ + isom_sample_info_t *dst_info = lsmash_malloc( sizeof(isom_sample_info_t) ); + if( !dst_info ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( timeline->info_list, dst_info ) < 0 ) + { + lsmash_free( dst_info ); + return LSMASH_ERR_MEMORY_ALLOC; + } + *dst_info = *src_info; + return 0; +} + +int isom_add_lpcm_bunch_entry( isom_timeline_t *timeline, isom_lpcm_bunch_t *src_bunch ) +{ + isom_lpcm_bunch_t *dst_bunch = lsmash_malloc( sizeof(isom_lpcm_bunch_t) ); + if( !dst_bunch ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( timeline->bunch_list, dst_bunch ) < 0 ) + { + lsmash_free( dst_bunch ); + return LSMASH_ERR_MEMORY_ALLOC; + } + *dst_bunch = *src_bunch; + return 0; +} + +static int isom_add_portable_chunk_entry( isom_timeline_t *timeline, isom_portable_chunk_t *src_chunk ) +{ + isom_portable_chunk_t *dst_chunk = lsmash_malloc( sizeof(isom_portable_chunk_t) ); + if( !dst_chunk ) + return LSMASH_ERR_MEMORY_ALLOC; + if( lsmash_list_add_entry( timeline->chunk_list, dst_chunk ) < 0 ) + { + lsmash_free( dst_chunk ); + return LSMASH_ERR_MEMORY_ALLOC; + } + *dst_chunk = *src_chunk; + return 0; +} + +static int isom_compare_lpcm_sample_info( isom_lpcm_bunch_t *bunch, isom_sample_info_t *info ) +{ + return info->duration != bunch->duration + || info->offset != bunch->offset + || info->length != bunch->length + || info->index != bunch->index + || info->chunk != bunch->chunk; +} + +static void isom_update_bunch( isom_lpcm_bunch_t *bunch, isom_sample_info_t *info ) +{ + bunch->pos = info->pos; + bunch->duration = info->duration; + bunch->offset = info->offset; + bunch->length = info->length; + bunch->index = info->index; + bunch->chunk = info->chunk; + bunch->prop = info->prop; + bunch->sample_count = 1; +} + +static isom_lpcm_bunch_t *isom_get_bunch( isom_timeline_t *timeline, uint32_t sample_number ) +{ + if( sample_number >= timeline->last_accessed_lpcm_bunch_first_sample_number + && sample_number < timeline->last_accessed_lpcm_bunch_first_sample_number + timeline->last_accessed_lpcm_bunch_sample_count ) + /* Get from the last accessed LPCM bunch. */ + return (isom_lpcm_bunch_t *)lsmash_list_get_entry_data( timeline->bunch_list, timeline->last_accessed_lpcm_bunch_number ); + uint32_t first_sample_number_in_next_bunch; + uint32_t bunch_number = 1; + uint64_t bunch_dts; + if( timeline->last_accessed_lpcm_bunch_first_sample_number + && timeline->last_accessed_lpcm_bunch_first_sample_number <= sample_number ) + { + first_sample_number_in_next_bunch = timeline->last_accessed_lpcm_bunch_first_sample_number + timeline->last_accessed_lpcm_bunch_sample_count; + bunch_number += timeline->last_accessed_lpcm_bunch_number; + bunch_dts = timeline->last_accessed_lpcm_bunch_dts + + timeline->last_accessed_lpcm_bunch_duration * timeline->last_accessed_lpcm_bunch_sample_count; + } + else + { + /* Seek from the first LPCM bunch. */ + first_sample_number_in_next_bunch = 1; + bunch_dts = 0; + } + isom_lpcm_bunch_t *bunch = (isom_lpcm_bunch_t *)lsmash_list_get_entry_data( timeline->bunch_list, bunch_number++ ); + if( !bunch ) + return NULL; + first_sample_number_in_next_bunch += bunch->sample_count; + while( sample_number >= first_sample_number_in_next_bunch ) + { + bunch_dts += bunch->duration * bunch->sample_count; + bunch = (isom_lpcm_bunch_t *)lsmash_list_get_entry_data( timeline->bunch_list, bunch_number++ ); + if( !bunch ) + return NULL; + first_sample_number_in_next_bunch += bunch->sample_count; + } + timeline->last_accessed_lpcm_bunch_dts = bunch_dts; + timeline->last_accessed_lpcm_bunch_number = bunch_number - 1; + timeline->last_accessed_lpcm_bunch_duration = bunch->duration; + timeline->last_accessed_lpcm_bunch_sample_count = bunch->sample_count; + timeline->last_accessed_lpcm_bunch_first_sample_number = first_sample_number_in_next_bunch - bunch->sample_count; + return bunch; +} + +static int isom_get_dts_from_info_list( isom_timeline_t *timeline, uint32_t sample_number, uint64_t *dts ) +{ + if( sample_number == timeline->last_accessed_sample_number ) + *dts = timeline->last_accessed_sample_dts; + else if( sample_number == 1 ) + *dts = 0; + else if( sample_number == timeline->last_accessed_sample_number + 1 ) + { + isom_sample_info_t *info = (isom_sample_info_t *)lsmash_list_get_entry_data( timeline->info_list, timeline->last_accessed_sample_number ); + if( !info ) + return LSMASH_ERR_NAMELESS; + *dts = timeline->last_accessed_sample_dts + info->duration; + } + else if( sample_number == timeline->last_accessed_sample_number - 1 ) + { + isom_sample_info_t *info = (isom_sample_info_t *)lsmash_list_get_entry_data( timeline->info_list, timeline->last_accessed_sample_number - 1 ); + if( !info ) + return LSMASH_ERR_NAMELESS; + *dts = timeline->last_accessed_sample_dts - info->duration; + } + else + { + *dts = 0; + uint32_t distance = sample_number - 1; + lsmash_entry_t *entry; + for( entry = timeline->info_list->head; entry; entry = entry->next ) + { + isom_sample_info_t *info = (isom_sample_info_t *)entry->data; + if( !info ) + return LSMASH_ERR_NAMELESS; + if( distance-- == 0 ) + break; + *dts += info->duration; + } + if( !entry ) + return LSMASH_ERR_NAMELESS; + } + /* Note: last_accessed_sample_number is always updated together with last_accessed_sample_dts, and vice versa. */ + timeline->last_accessed_sample_dts = *dts; + timeline->last_accessed_sample_number = sample_number; + return 0; +} + +static int isom_get_cts_from_info_list( isom_timeline_t *timeline, uint32_t sample_number, uint64_t *cts ) +{ + int ret = isom_get_dts_from_info_list( timeline, sample_number, cts ); + if( ret < 0 ) + return ret; + isom_sample_info_t *info = (isom_sample_info_t *)lsmash_list_get_entry_data( timeline->info_list, sample_number ); + if( !info ) + return LSMASH_ERR_NAMELESS; + *cts = isom_make_cts( *cts, info->offset, timeline->ctd_shift ); + return 0; +} + +static int isom_get_dts_from_bunch_list( isom_timeline_t *timeline, uint32_t sample_number, uint64_t *dts ) +{ + isom_lpcm_bunch_t *bunch = isom_get_bunch( timeline, sample_number ); + if( !bunch ) + return LSMASH_ERR_NAMELESS; + *dts = timeline->last_accessed_lpcm_bunch_dts + (sample_number - timeline->last_accessed_lpcm_bunch_first_sample_number) * bunch->duration; + return 0; +} + +static int isom_get_cts_from_bunch_list( isom_timeline_t *timeline, uint32_t sample_number, uint64_t *cts ) +{ + isom_lpcm_bunch_t *bunch = isom_get_bunch( timeline, sample_number ); + if( !bunch ) + return LSMASH_ERR_NAMELESS; + *cts = timeline->last_accessed_lpcm_bunch_dts + (sample_number - timeline->last_accessed_lpcm_bunch_first_sample_number) * bunch->duration + bunch->offset; + return 0; +} + +static int isom_get_sample_duration_from_info_list( isom_timeline_t *timeline, uint32_t sample_number, uint32_t *sample_duration ) +{ + isom_sample_info_t *info = (isom_sample_info_t *)lsmash_list_get_entry_data( timeline->info_list, sample_number ); + if( !info ) + return LSMASH_ERR_NAMELESS; + *sample_duration = info->duration; + return 0; +} + +static int isom_get_sample_duration_from_bunch_list( isom_timeline_t *timeline, uint32_t sample_number, uint32_t *sample_duration ) +{ + isom_lpcm_bunch_t *bunch = isom_get_bunch( timeline, sample_number ); + if( !bunch ) + return LSMASH_ERR_NAMELESS; + *sample_duration = bunch->duration; + return 0; +} + +static int isom_check_sample_existence_in_info_list( isom_timeline_t *timeline, uint32_t sample_number ) +{ + isom_sample_info_t *info = (isom_sample_info_t *)lsmash_list_get_entry_data( timeline->info_list, sample_number ); + if( !info || !info->chunk ) + return 0; + return !!info->chunk->file; +} + +static int isom_check_sample_existence_in_bunch_list( isom_timeline_t *timeline, uint32_t sample_number ) +{ + isom_lpcm_bunch_t *bunch = isom_get_bunch( timeline, sample_number ); + if( !bunch || !bunch->chunk ) + return 0; + return !!bunch->chunk->file; +} + +static lsmash_sample_t *isom_read_sample_data_from_stream +( + lsmash_file_t *file, + isom_timeline_t *timeline, + uint32_t sample_length, + uint64_t sample_pos +) +{ + if( !file ) + return NULL; + lsmash_sample_t *sample = lsmash_create_sample( 0 ); + if( !sample ) + return NULL; + lsmash_bs_t *bs = file->bs; + lsmash_bs_read_seek( bs, sample_pos, SEEK_SET ); + sample->data = lsmash_bs_get_bytes( bs, sample_length ); + if( !sample->data ) + { + lsmash_delete_sample( sample ); + return NULL; + } + return sample; +} + +static lsmash_sample_t *isom_get_lpcm_sample_from_media_timeline( isom_timeline_t *timeline, uint32_t sample_number ) +{ + isom_lpcm_bunch_t *bunch = isom_get_bunch( timeline, sample_number ); + if( !bunch + || !bunch->chunk ) + return NULL; + /* Get data of a sample from the stream. */ + uint64_t sample_number_offset = sample_number - timeline->last_accessed_lpcm_bunch_first_sample_number; + uint64_t sample_pos = bunch->pos + sample_number_offset * bunch->length; + lsmash_sample_t *sample = isom_read_sample_data_from_stream( bunch->chunk->file, timeline, bunch->length, sample_pos ); + if( !sample ) + return NULL; + /* Get sample info. */ + sample->dts = timeline->last_accessed_lpcm_bunch_dts + sample_number_offset * bunch->duration; + sample->cts = isom_make_cts( sample->dts, bunch->offset, timeline->ctd_shift ); + sample->pos = sample_pos; + sample->length = bunch->length; + sample->index = bunch->index; + sample->prop = bunch->prop; + return sample; +} + +static lsmash_sample_t *isom_get_sample_from_media_timeline( isom_timeline_t *timeline, uint32_t sample_number ) +{ + uint64_t dts; + if( isom_get_dts_from_info_list( timeline, sample_number, &dts ) < 0 ) + return NULL; + isom_sample_info_t *info = (isom_sample_info_t *)lsmash_list_get_entry_data( timeline->info_list, sample_number ); + if( !info + || !info->chunk ) + return NULL; + /* Get data of a sample from the stream. */ + lsmash_sample_t *sample = isom_read_sample_data_from_stream( info->chunk->file, timeline, info->length, info->pos ); + if( !sample ) + return NULL; + /* Get sample info. */ + sample->dts = dts; + sample->cts = isom_make_cts( dts, info->offset, timeline->ctd_shift ); + sample->pos = info->pos; + sample->length = info->length; + sample->index = info->index; + sample->prop = info->prop; + return sample; +} + +static int isom_get_lpcm_sample_info_from_media_timeline( isom_timeline_t *timeline, uint32_t sample_number, lsmash_sample_t *sample ) +{ + isom_lpcm_bunch_t *bunch = isom_get_bunch( timeline, sample_number ); + if( !bunch ) + return LSMASH_ERR_NAMELESS; + uint64_t sample_number_offset = sample_number - timeline->last_accessed_lpcm_bunch_first_sample_number; + sample->dts = timeline->last_accessed_lpcm_bunch_dts + sample_number_offset * bunch->duration; + sample->cts = isom_make_cts( sample->dts, bunch->offset, timeline->ctd_shift ); + sample->pos = bunch->pos + sample_number_offset * bunch->length; + sample->length = bunch->length; + sample->index = bunch->index; + sample->prop = bunch->prop; + return 0; +} + +static int isom_get_sample_info_from_media_timeline( isom_timeline_t *timeline, uint32_t sample_number, lsmash_sample_t *sample ) +{ + uint64_t dts; + int ret = isom_get_dts_from_info_list( timeline, sample_number, &dts ); + if( ret < 0 ) + return ret; + isom_sample_info_t *info = (isom_sample_info_t *)lsmash_list_get_entry_data( timeline->info_list, sample_number ); + if( !info ) + return LSMASH_ERR_NAMELESS; + sample->dts = dts; + sample->cts = isom_make_cts( dts, info->offset, timeline->ctd_shift ); + sample->pos = info->pos; + sample->length = info->length; + sample->index = info->index; + sample->prop = info->prop; + return 0; +} + +static int isom_get_lpcm_sample_property_from_media_timeline( isom_timeline_t *timeline, uint32_t sample_number, lsmash_sample_property_t *prop ) +{ + memset( prop, 0, sizeof(lsmash_sample_property_t) ); + prop->ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; + return 0; +} + +static int isom_get_sample_property_from_media_timeline( isom_timeline_t *timeline, uint32_t sample_number, lsmash_sample_property_t *prop ) +{ + isom_sample_info_t *info = (isom_sample_info_t *)lsmash_list_get_entry_data( timeline->info_list, sample_number ); + if( !info ) + return LSMASH_ERR_NAMELESS; + *prop = info->prop; + return 0; +} + +static void isom_timeline_set_sample_getter_funcs +( + isom_timeline_t *timeline +) +{ + timeline->get_dts = isom_get_dts_from_info_list; + timeline->get_cts = isom_get_cts_from_info_list; + timeline->get_sample_duration = isom_get_sample_duration_from_info_list; + timeline->check_sample_existence = isom_check_sample_existence_in_info_list; + timeline->get_sample = isom_get_sample_from_media_timeline; + timeline->get_sample_info = isom_get_sample_info_from_media_timeline; + timeline->get_sample_property = isom_get_sample_property_from_media_timeline; +} + +void isom_timeline_set_lpcm_sample_getter_funcs +( + isom_timeline_t *timeline +) +{ + timeline->get_dts = isom_get_dts_from_bunch_list; + timeline->get_cts = isom_get_cts_from_bunch_list; + timeline->get_sample_duration = isom_get_sample_duration_from_bunch_list; + timeline->check_sample_existence = isom_check_sample_existence_in_bunch_list; + timeline->get_sample = isom_get_lpcm_sample_from_media_timeline; + timeline->get_sample_info = isom_get_lpcm_sample_info_from_media_timeline; + timeline->get_sample_property = isom_get_lpcm_sample_property_from_media_timeline; +} + +static inline void isom_increment_sample_number_in_entry +( + uint32_t *sample_number_in_entry, + lsmash_entry_t **entry, + uint32_t sample_count +) +{ + if( *sample_number_in_entry == sample_count ) + { + *sample_number_in_entry = 1; + *entry = (*entry)->next; + } + else + *sample_number_in_entry += 1; +} + +static inline isom_sgpd_t *isom_select_appropriate_sgpd +( + isom_sgpd_t *sgpd, + isom_sgpd_t *sgpd_frag, + uint32_t *group_description_index +) +{ + if( LSMASH_IS_EXISTING_BOX( sgpd_frag ) && *group_description_index >= 0x10000 ) + { + /* The specification doesn't define 0x10000 explicitly, however says that there must be fewer than + * 65536 group definitions for this track and grouping type in the sample table in the Movie Box. + * So, we assume 0x10000 is equivalent to 0. */ + *group_description_index -= 0x10000; + return sgpd_frag; + } + else + return sgpd; +} + +static int isom_get_roll_recovery_grouping_info +( + isom_timeline_t *timeline, + lsmash_entry_t **sbgp_roll_entry, + isom_sgpd_t *sgpd_roll, + isom_sgpd_t *sgpd_frag_roll, + uint32_t *sample_number_in_sbgp_roll_entry, + isom_sample_info_t *info, + uint32_t sample_number +) +{ + isom_group_assignment_entry_t *assignment = (isom_group_assignment_entry_t *)(*sbgp_roll_entry)->data; + if( !assignment ) + return LSMASH_ERR_NAMELESS; + if( assignment->group_description_index ) + { + uint32_t group_description_index = assignment->group_description_index; + isom_sgpd_t *sgpd = isom_select_appropriate_sgpd( sgpd_roll, sgpd_frag_roll, &group_description_index ); + isom_roll_entry_t *roll_data = (isom_roll_entry_t *)lsmash_list_get_entry_data( sgpd->list, group_description_index ); + if( roll_data ) + { + if( roll_data->roll_distance > 0 ) + { + /* post-roll */ + info->prop.post_roll.complete = sample_number + roll_data->roll_distance; + if( info->prop.ra_flags == ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE ) + info->prop.ra_flags |= ISOM_SAMPLE_RANDOM_ACCESS_FLAG_POST_ROLL_START; + } + else if( roll_data->roll_distance < 0 ) + { + /* pre-roll */ + info->prop.pre_roll.distance = -roll_data->roll_distance; + if( info->prop.ra_flags == ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE ) + info->prop.ra_flags |= ISOM_SAMPLE_RANDOM_ACCESS_FLAG_PRE_ROLL_END; + } + } + else if( *sample_number_in_sbgp_roll_entry == 1 && group_description_index ) + lsmash_log( timeline, LSMASH_LOG_WARNING, "a description of roll recoveries is not found in the Sample Group Description Box.\n" ); + } + isom_increment_sample_number_in_entry( sample_number_in_sbgp_roll_entry, sbgp_roll_entry, assignment->sample_count ); + return 0; +} + +static int isom_get_random_access_point_grouping_info +( + isom_timeline_t *timeline, + lsmash_entry_t **sbgp_rap_entry, + isom_sgpd_t *sgpd_rap, + isom_sgpd_t *sgpd_frag_rap, + uint32_t *sample_number_in_sbgp_rap_entry, + isom_sample_info_t *info, + uint32_t *distance +) +{ + isom_group_assignment_entry_t *assignment = (isom_group_assignment_entry_t *)(*sbgp_rap_entry)->data; + if( !assignment ) + return LSMASH_ERR_NAMELESS; + if( assignment->group_description_index && (info->prop.ra_flags == ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE) ) + { + uint32_t group_description_index = assignment->group_description_index; + isom_sgpd_t *sgpd = isom_select_appropriate_sgpd( sgpd_rap, sgpd_frag_rap, &group_description_index ); + isom_rap_entry_t *rap_data = (isom_rap_entry_t *)lsmash_list_get_entry_data( sgpd->list, group_description_index ); + if( rap_data ) + { + /* If this is not an open RAP, we treat it as an unknown RAP since non-IDR sample could make a closed GOP. */ + info->prop.ra_flags |= (rap_data->num_leading_samples_known && !!rap_data->num_leading_samples) + ? ISOM_SAMPLE_RANDOM_ACCESS_FLAG_OPEN_RAP + : ISOM_SAMPLE_RANDOM_ACCESS_FLAG_RAP; + *distance = 0; + } + else if( *sample_number_in_sbgp_rap_entry == 1 && group_description_index ) + lsmash_log( timeline, LSMASH_LOG_WARNING, "a description of random access points is not found in the Sample Group Description Box.\n" ); + } + isom_increment_sample_number_in_entry( sample_number_in_sbgp_rap_entry, sbgp_rap_entry, assignment->sample_count ); + return 0; +} + +int isom_timeline_construct( lsmash_root_t *root, uint32_t track_ID ) +{ + if( isom_check_initializer_present( root ) < 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_file_t *file = root->file; + if( LSMASH_IS_NON_EXISTING_BOX( file->moov->mvhd ) + || file->moov->mvhd->timescale == 0 ) + return LSMASH_ERR_INVALID_DATA; + /* Get track by track_ID. */ + isom_trak_t *trak = isom_get_trak( file, track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trak->tkhd ) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->mdhd ) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->minf->stbl->stco ) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->minf->stbl->stsd ) + || (LSMASH_IS_NON_EXISTING_BOX( trak->mdia->minf->stbl->stsz ) && LSMASH_IS_NON_EXISTING_BOX( trak->mdia->minf->stbl->stz2 )) + || trak->mdia->mdhd->timescale == 0 ) + return LSMASH_ERR_INVALID_DATA; + /* Create a timeline list if it doesn't exist. */ + if( !file->timeline ) + { + file->timeline = lsmash_list_create( isom_timeline_destroy ); + if( !file->timeline ) + return LSMASH_ERR_MEMORY_ALLOC; + } + /* Create a timeline. */ + isom_timeline_t *timeline = isom_timeline_create(); + if( !timeline ) + return LSMASH_ERR_MEMORY_ALLOC; + timeline->track_ID = track_ID; + timeline->movie_timescale = file->moov->mvhd->timescale; + timeline->media_timescale = trak->mdia->mdhd->timescale; + timeline->track_duration = trak->tkhd->duration; + /* Preparation for construction. */ + isom_elst_t *elst = trak->edts->elst; + isom_minf_t *minf = trak->mdia->minf; + isom_dref_t *dref = minf->dinf->dref; + isom_stbl_t *stbl = minf->stbl; + isom_stsd_t *stsd = stbl->stsd; + isom_stts_t *stts = stbl->stts; + isom_ctts_t *ctts = stbl->ctts; + isom_stss_t *stss = stbl->stss; + isom_stps_t *stps = stbl->stps; + isom_sdtp_t *sdtp = stbl->sdtp; + isom_stsc_t *stsc = stbl->stsc; + isom_stsz_t *stsz = stbl->stsz; + isom_stz2_t *stz2 = stbl->stz2; + isom_stco_t *stco = stbl->stco; + isom_sgpd_t *sgpd_rap = isom_get_sample_group_description( stbl, ISOM_GROUP_TYPE_RAP ); + isom_sbgp_t *sbgp_rap = isom_get_sample_to_group ( stbl, ISOM_GROUP_TYPE_RAP ); + isom_sgpd_t *sgpd_roll = isom_get_roll_recovery_sample_group_description( &stbl->sgpd_list ); + isom_sbgp_t *sbgp_roll = isom_get_roll_recovery_sample_to_group ( &stbl->sbgp_list ); + lsmash_entry_t *elst_entry = elst->list ? elst->list->head : NULL; + lsmash_entry_t *stts_entry = stts->list ? stts->list->head : NULL; + lsmash_entry_t *ctts_entry = ctts->list ? ctts->list->head : NULL; + lsmash_entry_t *stss_entry = stss->list ? stss->list->head : NULL; + lsmash_entry_t *stps_entry = stps->list ? stps->list->head : NULL; + lsmash_entry_t *sdtp_entry = sdtp->list ? sdtp->list->head : NULL; + lsmash_entry_t *stsz_entry = LSMASH_IS_EXISTING_BOX( stsz ) ? (stsz->list ? stsz->list->head : NULL) + : (stz2->list ? stz2->list->head : NULL); + lsmash_entry_t *stsc_entry = stsc->list ? stsc->list->head : NULL; + lsmash_entry_t *stco_entry = stco->list ? stco->list->head : NULL; + lsmash_entry_t *sbgp_roll_entry = sbgp_roll->list ? sbgp_roll->list->head : NULL; + lsmash_entry_t *sbgp_rap_entry = sbgp_rap->list ? sbgp_rap->list->head : NULL; + lsmash_entry_t *next_stsc_entry = stsc_entry ? stsc_entry->next : NULL; + isom_stsc_entry_t *stsc_data = stsc_entry ? (isom_stsc_entry_t *)stsc_entry->data : NULL; + int err = LSMASH_ERR_INVALID_DATA; + int movie_fragments_present = (LSMASH_IS_EXISTING_BOX( file->moov->mvex ) && file->moof_list.head); + if( !movie_fragments_present && (!stts_entry || !stsc_entry || !stco_entry || !stco_entry->data || (next_stsc_entry && !next_stsc_entry->data)) ) + goto fail; + isom_sample_entry_t *description = (isom_sample_entry_t *)lsmash_list_get_entry_data( &stsd->list, stsc_data ? stsc_data->sample_description_index : 1 ); + if( LSMASH_IS_NON_EXISTING_BOX( description ) ) + goto fail; + lsmash_entry_list_t *dref_list = LSMASH_IS_EXISTING_BOX( dref ) ? &dref->list : NULL; + isom_dref_entry_t *dref_entry = (isom_dref_entry_t *)lsmash_list_get_entry_data( dref_list, description->data_reference_index ); + int all_sync = LSMASH_IS_NON_EXISTING_BOX( stss ); + int large_presentation = stco->large_presentation || lsmash_check_box_type_identical( stco->type, ISOM_BOX_TYPE_CO64 ); + int is_lpcm_audio = isom_is_lpcm_audio( description ); + int is_qt_fixed_comp_audio = isom_is_qt_fixed_compressed_audio( description ); + int iso_sdtp = file->max_isom_version >= 2 || file->avc_extensions; + int allow_negative_sample_offset = ctts && ((file->max_isom_version >= 4 && ctts->version == 1) || file->qt_compatible); + uint32_t sample_number_in_stts_entry = 1; + uint32_t sample_number_in_ctts_entry = 1; + uint32_t sample_number_in_sbgp_roll_entry = 1; + uint32_t sample_number_in_sbgp_rap_entry = 1; + uint64_t dts = 0; + uint32_t chunk_number = 1; + uint64_t offset_from_chunk = 0; + uint64_t data_offset = stco_entry && stco_entry->data + ? large_presentation + ? ((isom_co64_entry_t *)stco_entry->data)->chunk_offset + : ((isom_stco_entry_t *)stco_entry->data)->chunk_offset + : 0; + uint32_t initial_movie_sample_count = LSMASH_IS_EXISTING_BOX( stsz ) ? stsz->sample_count : stz2->sample_count; + uint32_t samples_per_packet; + uint32_t constant_sample_size; + if( is_qt_fixed_comp_audio ) + isom_get_qt_fixed_comp_audio_sample_quants( timeline, description, &samples_per_packet, &constant_sample_size ); + else + { + samples_per_packet = 1; + constant_sample_size = stsz ? stsz->sample_size : 0; + } + uint32_t sample_number = samples_per_packet; + uint32_t sample_number_in_chunk = samples_per_packet; + /* Copy edits. */ + while( elst_entry ) + { + isom_elst_entry_t *edit = (isom_elst_entry_t *)lsmash_memdup( elst_entry->data, sizeof(isom_elst_entry_t) ); + if( !edit + || lsmash_list_add_entry( timeline->edit_list, edit ) < 0 ) + { + err = LSMASH_ERR_MEMORY_ALLOC; + goto fail; + } + elst_entry = elst_entry->next; + } + /* Check what the first 2-bits of sample dependency means. + * This check is for chimera of ISO Base Media and QTFF. */ + if( iso_sdtp && sdtp_entry ) + { + while( sdtp_entry ) + { + isom_sdtp_entry_t *sdtp_data = (isom_sdtp_entry_t *)sdtp_entry->data; + if( !sdtp_data ) + goto fail; + if( sdtp_data->is_leading > 1 ) + break; /* Apparently, it's defined under ISO Base Media. */ + if( (sdtp_data->is_leading == 1) && (sdtp_data->sample_depends_on == ISOM_SAMPLE_IS_INDEPENDENT) ) + { + /* Obviously, it's not defined under ISO Base Media. */ + iso_sdtp = 0; + break; + } + sdtp_entry = sdtp_entry->next; + } + sdtp_entry = sdtp->list->head; + } + /**--- Construct media timeline. ---**/ + isom_portable_chunk_t chunk; + chunk.data_offset = data_offset; + chunk.length = 0; + chunk.number = chunk_number; + chunk.file = (!dref_entry || LSMASH_IS_NON_EXISTING_BOX( dref_entry->ref_file )) ? NULL : dref_entry->ref_file; + if( (err = isom_add_portable_chunk_entry( timeline, &chunk )) < 0 ) + goto fail; + uint32_t distance = NO_RANDOM_ACCESS_POINT; + uint32_t last_duration = UINT32_MAX; + uint32_t packet_number = 1; + isom_lpcm_bunch_t bunch = { 0 }; + while( sample_number <= initial_movie_sample_count ) + { + isom_sample_info_t info = { 0 }; + /* Get sample duration and sample offset. */ + for( uint32_t i = 0; i < samples_per_packet; i++ ) + { + /* sample duration */ + if( stts_entry ) + { + isom_stts_entry_t *stts_data = (isom_stts_entry_t *)stts_entry->data; + if( !stts_data ) + goto fail; + isom_increment_sample_number_in_entry( &sample_number_in_stts_entry, &stts_entry, stts_data->sample_count ); + last_duration = stts_data->sample_delta; + } + info.duration += last_duration; + dts += last_duration; + /* sample offset */ + uint32_t sample_offset; + if( ctts_entry ) + { + isom_ctts_entry_t *ctts_data = (isom_ctts_entry_t *)ctts_entry->data; + if( !ctts_data ) + goto fail; + isom_increment_sample_number_in_entry( &sample_number_in_ctts_entry, &ctts_entry, ctts_data->sample_count ); + sample_offset = ctts_data->sample_offset; + if( allow_negative_sample_offset && sample_offset != ISOM_NON_OUTPUT_SAMPLE_OFFSET ) + { + uint64_t cts = dts + (int32_t)sample_offset; + if( (cts + timeline->ctd_shift) < dts ) + timeline->ctd_shift = dts - cts; + } + } + else + sample_offset = 0; + if( i == 0 ) + info.offset = sample_offset; + } + timeline->media_duration += info.duration; + if( !is_qt_fixed_comp_audio ) + { + /* Check whether sync sample or not. */ + if( stss_entry ) + { + isom_stss_entry_t *stss_data = (isom_stss_entry_t *)stss_entry->data; + if( !stss_data ) + goto fail; + if( sample_number == stss_data->sample_number ) + { + info.prop.ra_flags |= ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; + stss_entry = stss_entry->next; + distance = 0; + } + } + else if( all_sync ) + /* Don't reset distance as 0 since MDCT-based audio frames need pre-roll for correct presentation + * though all of them could be marked as a sync sample. */ + info.prop.ra_flags |= ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; + /* Check whether partial sync sample or not. */ + if( stps_entry ) + { + isom_stps_entry_t *stps_data = (isom_stps_entry_t *)stps_entry->data; + if( !stps_data ) + goto fail; + if( sample_number == stps_data->sample_number ) + { + info.prop.ra_flags |= QT_SAMPLE_RANDOM_ACCESS_FLAG_PARTIAL_SYNC | QT_SAMPLE_RANDOM_ACCESS_FLAG_RAP; + stps_entry = stps_entry->next; + distance = 0; + } + } + /* Get sample dependency info. */ + if( sdtp_entry ) + { + isom_sdtp_entry_t *sdtp_data = (isom_sdtp_entry_t *)sdtp_entry->data; + if( !sdtp_data ) + goto fail; + if( iso_sdtp ) + info.prop.leading = sdtp_data->is_leading; + else + info.prop.allow_earlier = sdtp_data->is_leading; + info.prop.independent = sdtp_data->sample_depends_on; + info.prop.disposable = sdtp_data->sample_is_depended_on; + info.prop.redundant = sdtp_data->sample_has_redundancy; + sdtp_entry = sdtp_entry->next; + } + /* Get roll recovery grouping info. */ + if( sbgp_roll_entry + && isom_get_roll_recovery_grouping_info( timeline, + &sbgp_roll_entry, sgpd_roll, NULL, + &sample_number_in_sbgp_roll_entry, + &info, sample_number ) < 0 ) + goto fail; + info.prop.post_roll.identifier = sample_number; + /* Get random access point grouping info. */ + if( sbgp_rap_entry + && isom_get_random_access_point_grouping_info( timeline, + &sbgp_rap_entry, sgpd_rap, NULL, + &sample_number_in_sbgp_rap_entry, + &info, &distance ) < 0 ) + goto fail; + /* Set up distance from the previous random access point. */ + if( distance != NO_RANDOM_ACCESS_POINT ) + { + if( info.prop.pre_roll.distance == 0 ) + info.prop.pre_roll.distance = distance; + ++distance; + } + } + else + /* All uncompressed and non-variable compressed audio frame is a sync sample. */ + info.prop.ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; + /* Get size of sample in the stream. */ + if( is_qt_fixed_comp_audio || !stsz_entry ) + info.length = constant_sample_size; + else + { + if( !stsz_entry->data ) + goto fail; + info.length = ((isom_stsz_entry_t *)stsz_entry->data)->entry_size; + stsz_entry = stsz_entry->next; + } + timeline->max_sample_size = LSMASH_MAX( timeline->max_sample_size, info.length ); + /* Get chunk info. */ + info.pos = data_offset; + info.index = stsc_data->sample_description_index; + info.chunk = (isom_portable_chunk_t *)timeline->chunk_list->tail->data; + offset_from_chunk += info.length; + if( sample_number_in_chunk == stsc_data->samples_per_chunk ) + { + /* Set the length of the last chunk. */ + if( info.chunk ) + info.chunk->length = offset_from_chunk; + /* Move the next chunk. */ + if( stco_entry ) + stco_entry = stco_entry->next; + if( stco_entry + && stco_entry->data ) + data_offset = large_presentation + ? ((isom_co64_entry_t *)stco_entry->data)->chunk_offset + : ((isom_stco_entry_t *)stco_entry->data)->chunk_offset; + chunk.data_offset = data_offset; + chunk.length = 0; + chunk.number = ++chunk_number; + offset_from_chunk = 0; + /* Check if the next entry is broken. */ + while( next_stsc_entry && chunk_number > ((isom_stsc_entry_t *)next_stsc_entry->data)->first_chunk ) + { + /* Just skip broken next entry. */ + lsmash_log( timeline, LSMASH_LOG_WARNING, "ignore broken entry in Sample To Chunk Box.\n" ); + lsmash_log( timeline, LSMASH_LOG_WARNING, "timeline might be corrupted.\n" ); + next_stsc_entry = next_stsc_entry->next; + if( next_stsc_entry + && !next_stsc_entry->data ) + goto fail; + } + /* Check if the next chunk belongs to the next sequence of chunks. */ + if( next_stsc_entry && chunk_number == ((isom_stsc_entry_t *)next_stsc_entry->data)->first_chunk ) + { + stsc_entry = next_stsc_entry; + next_stsc_entry = next_stsc_entry->next; + if( next_stsc_entry + && !next_stsc_entry->data ) + goto fail; + stsc_data = (isom_stsc_entry_t *)stsc_entry->data; + /* Update sample description. */ + description = (isom_sample_entry_t *)lsmash_list_get_entry_data( &stsd->list, stsc_data->sample_description_index ); + is_lpcm_audio = LSMASH_IS_EXISTING_BOX( description ) ? isom_is_lpcm_audio( description ) : 0; + is_qt_fixed_comp_audio = LSMASH_IS_EXISTING_BOX( description ) ? isom_is_qt_fixed_compressed_audio( description ) : 0; + if( is_qt_fixed_comp_audio ) + isom_get_qt_fixed_comp_audio_sample_quants( timeline, description, &samples_per_packet, &constant_sample_size ); + else + { + samples_per_packet = 1; + constant_sample_size = stsz ? stsz->sample_size : 0; + } + /* Reference media data. */ + dref_entry = (isom_dref_entry_t *)lsmash_list_get_entry_data( dref_list, LSMASH_IS_EXISTING_BOX( description ) ? description->data_reference_index : 0 ); + chunk.file = (!dref_entry || LSMASH_IS_NON_EXISTING_BOX( dref_entry->ref_file )) ? NULL : dref_entry->ref_file; + } + sample_number_in_chunk = samples_per_packet; + if( (err = isom_add_portable_chunk_entry( timeline, &chunk )) < 0 ) + goto fail; + } + else + { + data_offset += info.length; + sample_number_in_chunk += samples_per_packet; + } + /* OK. Let's add its info. */ + if( is_lpcm_audio ) + { + if( sample_number == samples_per_packet ) + isom_update_bunch( &bunch, &info ); + else if( isom_compare_lpcm_sample_info( &bunch, &info ) ) + { + if( (err = isom_add_lpcm_bunch_entry( timeline, &bunch )) < 0 ) + goto fail; + isom_update_bunch( &bunch, &info ); + } + else + ++ bunch.sample_count; + } + else if( (err = isom_add_sample_info_entry( timeline, &info )) < 0 ) + goto fail; + if( timeline->info_list->entry_count && timeline->bunch_list->entry_count ) + { + lsmash_log( timeline, LSMASH_LOG_ERROR, "LPCM + non-LPCM track is not supported.\n" ); + err = LSMASH_ERR_PATCH_WELCOME; + goto fail; + } + sample_number += samples_per_packet; + packet_number += 1; + } + isom_portable_chunk_t *last_chunk = lsmash_list_get_entry_data( timeline->chunk_list, timeline->chunk_list->entry_count ); + if( last_chunk ) + { + if( offset_from_chunk ) + last_chunk->length = offset_from_chunk; + else + { + /* Remove the last invalid chunk. */ + lsmash_list_remove_entry( timeline->chunk_list, timeline->chunk_list->entry_count ); + --chunk_number; + } + } + uint32_t sample_count = packet_number - 1; + if( movie_fragments_present ) + { + isom_tfra_t *tfra = isom_get_tfra( file->mfra, track_ID ); + lsmash_entry_t *tfra_entry = tfra->list ? tfra->list->head : NULL; + isom_tfra_location_time_entry_t *rap = tfra_entry ? (isom_tfra_location_time_entry_t *)tfra_entry->data : NULL; + chunk.data_offset = 0; + chunk.length = 0; + /* Movie fragments */ + for( lsmash_entry_t *moof_entry = file->moof_list.head; moof_entry; moof_entry = moof_entry->next ) + { + isom_moof_t *moof = (isom_moof_t *)moof_entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( moof ) ) + goto fail; + uint64_t last_sample_end_pos = 0; + /* Track fragments */ + uint32_t traf_number = 1; + for( lsmash_entry_t *traf_entry = moof->traf_list.head; traf_entry; traf_entry = traf_entry->next ) + { + isom_traf_t *traf = (isom_traf_t *)traf_entry->data; + isom_tfhd_t *tfhd = traf->tfhd; + isom_trex_t *trex = isom_get_trex( file->moov->mvex, tfhd->track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( trex ) ) + goto fail; + /* Ignore ISOM_TF_FLAGS_DURATION_IS_EMPTY flag even if set. */ + if( !traf->trun_list.head ) + { + ++traf_number; + continue; + } + /* Get base_data_offset. */ + uint64_t base_data_offset; + if( tfhd->flags & ISOM_TF_FLAGS_BASE_DATA_OFFSET_PRESENT ) + base_data_offset = tfhd->base_data_offset; + else if( (tfhd->flags & ISOM_TF_FLAGS_DEFAULT_BASE_IS_MOOF) || traf_entry == moof->traf_list.head ) + base_data_offset = moof->pos; + else + base_data_offset = last_sample_end_pos; + /* sample grouping */ + isom_sgpd_t *sgpd_frag_rap; + isom_sgpd_t *sgpd_frag_roll; + sgpd_frag_rap = isom_get_fragment_sample_group_description( traf, ISOM_GROUP_TYPE_RAP ); + sbgp_rap = isom_get_fragment_sample_to_group ( traf, ISOM_GROUP_TYPE_RAP ); + sbgp_rap_entry = sbgp_rap->list ? sbgp_rap->list->head : NULL; + sgpd_frag_roll = isom_get_roll_recovery_sample_group_description( &traf->sgpd_list ); + sbgp_roll = isom_get_roll_recovery_sample_to_group ( &traf->sbgp_list ); + sbgp_roll_entry = sbgp_roll->list ? sbgp_roll->list->head : NULL; + int need_data_offset_only = (tfhd->track_ID != track_ID); + /* Track runs */ + uint32_t trun_number = 1; + for( lsmash_entry_t *trun_entry = traf->trun_list.head; trun_entry; trun_entry = trun_entry->next ) + { + isom_trun_t *trun = (isom_trun_t *)trun_entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( trun ) ) + goto fail; + if( trun->sample_count == 0 ) + { + ++trun_number; + continue; + } + /* Get data_offset. */ + if( trun->flags & ISOM_TR_FLAGS_DATA_OFFSET_PRESENT ) + data_offset = trun->data_offset + base_data_offset; + else if( trun_entry == traf->trun_list.head ) + data_offset = base_data_offset; + else + data_offset = last_sample_end_pos; + /* */ + uint32_t sample_description_index = 0; + isom_sdtp_entry_t *sdtp_data = NULL; + if( !need_data_offset_only ) + { + /* Get sample_description_index of this track fragment. */ + if( tfhd->flags & ISOM_TF_FLAGS_SAMPLE_DESCRIPTION_INDEX_PRESENT ) + sample_description_index = tfhd->sample_description_index; + else + sample_description_index = trex->default_sample_description_index; + description = (isom_sample_entry_t *)lsmash_list_get_entry_data( &stsd->list, sample_description_index ); + is_lpcm_audio = LSMASH_IS_EXISTING_BOX( description ) ? isom_is_lpcm_audio( description ) : 0; + /* Reference media data. */ + dref_entry = (isom_dref_entry_t *)lsmash_list_get_entry_data( dref_list, LSMASH_IS_EXISTING_BOX( description ) ? description->data_reference_index : 0 ); + lsmash_file_t *ref_file = (!dref_entry || LSMASH_IS_NON_EXISTING_BOX( dref_entry->ref_file )) ? NULL : dref_entry->ref_file; + /* Each track run can be considered as a chunk. + * Here, we consider physically consecutive track runs as one chunk. */ + if( chunk.data_offset + chunk.length != data_offset || chunk.file != ref_file ) + { + chunk.data_offset = data_offset; + chunk.length = 0; + chunk.number = ++chunk_number; + chunk.file = ref_file; + if( (err = isom_add_portable_chunk_entry( timeline, &chunk )) < 0 ) + goto fail; + } + /* Get dependency info for this track fragment. */ + sdtp_entry = traf->sdtp->list ? traf->sdtp->list->head : NULL; + sdtp_data = sdtp_entry && sdtp_entry->data ? (isom_sdtp_entry_t *)sdtp_entry->data : NULL; + } + /* Get info of each sample. */ + lsmash_entry_t *row_entry = trun->optional && trun->optional->head ? trun->optional->head : NULL; + sample_number = 1; + while( sample_number <= trun->sample_count ) + { + isom_sample_info_t info = { 0 }; + isom_trun_optional_row_t *row = row_entry && row_entry->data ? (isom_trun_optional_row_t *)row_entry->data : NULL; + /* Get sample_size */ + if( row && (trun->flags & ISOM_TR_FLAGS_SAMPLE_SIZE_PRESENT) ) + info.length = row->sample_size; + else if( tfhd->flags & ISOM_TF_FLAGS_DEFAULT_SAMPLE_SIZE_PRESENT ) + info.length = tfhd->default_sample_size; + else + info.length = trex->default_sample_size; + if( !need_data_offset_only ) + { + info.pos = data_offset; + info.index = sample_description_index; + info.chunk = (isom_portable_chunk_t *)timeline->chunk_list->tail->data; + info.chunk->length += info.length; + /* Get sample_duration. */ + if( row && (trun->flags & ISOM_TR_FLAGS_SAMPLE_DURATION_PRESENT) ) + info.duration = row->sample_duration; + else if( tfhd->flags & ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT ) + info.duration = tfhd->default_sample_duration; + else + info.duration = trex->default_sample_duration; + /* Get composition time offset. */ + if( row && (trun->flags & ISOM_TR_FLAGS_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT) ) + { + info.offset = row->sample_composition_time_offset; + /* Check composition to decode timeline shift. */ + if( file->max_isom_version >= 6 && trun->version != 0 && info.offset != ISOM_NON_OUTPUT_SAMPLE_OFFSET ) + { + uint64_t cts = dts + (int32_t)info.offset; + if( (cts + timeline->ctd_shift) < dts ) + timeline->ctd_shift = dts - cts; + } + } + else + info.offset = 0; + dts += info.duration; + /* Update media duration and maximun sample size. */ + timeline->media_duration += info.duration; + timeline->max_sample_size = LSMASH_MAX( timeline->max_sample_size, info.length ); + if( !is_lpcm_audio ) + { + /* Get sample_flags. */ + isom_sample_flags_t sample_flags; + if( sample_number == 1 && (trun->flags & ISOM_TR_FLAGS_FIRST_SAMPLE_FLAGS_PRESENT) ) + sample_flags = trun->first_sample_flags; + else if( row && (trun->flags & ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT) ) + sample_flags = row->sample_flags; + else if( tfhd->flags & ISOM_TF_FLAGS_DEFAULT_SAMPLE_FLAGS_PRESENT ) + sample_flags = tfhd->default_sample_flags; + else + sample_flags = trex->default_sample_flags; + if( sdtp_data ) + { + /* Independent and Disposable Samples Box overrides the information from sample_flags. + * There is no description in the specification about this, but the intention should be such a thing. + * The ground is that sample_flags is placed in media layer + * while Independent and Disposable Samples Box is placed in track or presentation layer. */ + info.prop.leading = sdtp_data->is_leading; + info.prop.independent = sdtp_data->sample_depends_on; + info.prop.disposable = sdtp_data->sample_is_depended_on; + info.prop.redundant = sdtp_data->sample_has_redundancy; + if( sdtp_entry ) + sdtp_entry = sdtp_entry->next; + sdtp_data = sdtp_entry ? (isom_sdtp_entry_t *)sdtp_entry->data : NULL; + } + else + { + info.prop.leading = sample_flags.is_leading; + info.prop.independent = sample_flags.sample_depends_on; + info.prop.disposable = sample_flags.sample_is_depended_on; + info.prop.redundant = sample_flags.sample_has_redundancy; + } + /* Check this sample is a sync sample or not. + * Note: all sync sample shall be independent. */ + if( !sample_flags.sample_is_non_sync_sample + && info.prop.independent != ISOM_SAMPLE_IS_NOT_INDEPENDENT ) + { + info.prop.ra_flags |= ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; + distance = 0; + } + /* Get roll recovery grouping info. */ + uint32_t roll_id = sample_count + sample_number; + if( sbgp_roll_entry + && isom_get_roll_recovery_grouping_info( timeline, + &sbgp_roll_entry, sgpd_roll, sgpd_frag_roll, + &sample_number_in_sbgp_roll_entry, + &info, roll_id ) < 0 ) + goto fail; + info.prop.post_roll.identifier = roll_id; + /* Get random access point grouping info. */ + if( sbgp_rap_entry + && isom_get_random_access_point_grouping_info( timeline, + &sbgp_rap_entry, sgpd_rap, sgpd_frag_rap, + &sample_number_in_sbgp_rap_entry, + &info, &distance ) < 0 ) + goto fail; + /* Get the location of the sync sample from 'tfra' if it is not set up yet. + * Note: there is no guarantee that its entries are placed in a specific order. */ + if( LSMASH_IS_EXISTING_BOX( tfra ) ) + { + if( tfra->number_of_entry == 0 + && info.prop.ra_flags == ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE ) + info.prop.ra_flags |= ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; + if( rap + && rap->moof_offset == moof->pos + && rap->traf_number == traf_number + && rap->trun_number == trun_number + && rap->sample_number == sample_number ) + { + if( info.prop.ra_flags == ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE ) + info.prop.ra_flags |= ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; + if( tfra_entry ) + tfra_entry = tfra_entry->next; + rap = tfra_entry ? (isom_tfra_location_time_entry_t *)tfra_entry->data : NULL; + } + } + /* Set up distance from the previous random access point. */ + if( distance != NO_RANDOM_ACCESS_POINT ) + { + if( info.prop.pre_roll.distance == 0 ) + info.prop.pre_roll.distance = distance; + ++distance; + } + /* OK. Let's add its info. */ + if( (err = isom_add_sample_info_entry( timeline, &info )) < 0 ) + goto fail; + } + else + { + /* All LPCMFrame is a sync sample. */ + info.prop.ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; + /* OK. Let's add its info. */ + if( sample_count == 0 && sample_number == 1 ) + isom_update_bunch( &bunch, &info ); + else if( isom_compare_lpcm_sample_info( &bunch, &info ) ) + { + if( (err = isom_add_lpcm_bunch_entry( timeline, &bunch )) < 0 ) + goto fail; + isom_update_bunch( &bunch, &info ); + } + else + ++ bunch.sample_count; + } + if( timeline-> info_list->entry_count + && timeline->bunch_list->entry_count ) + { + lsmash_log( timeline, LSMASH_LOG_ERROR, "LPCM + non-LPCM track is not supported.\n" ); + err = LSMASH_ERR_PATCH_WELCOME; + goto fail; + } + } + data_offset += info.length; + last_sample_end_pos = data_offset; + if( row_entry ) + row_entry = row_entry->next; + ++sample_number; + } + if( !need_data_offset_only ) + sample_count += sample_number - 1; + ++trun_number; + } /* Track runs */ + ++traf_number; + } /* Track fragments */ + } /* Movie fragments */ + } + else if( timeline->chunk_list->entry_count == 0 ) + goto fail; /* No samples in this track. */ + if( bunch.sample_count && (err = isom_add_lpcm_bunch_entry( timeline, &bunch )) < 0 ) + goto fail; + if( (err = lsmash_list_add_entry( file->timeline, timeline )) < 0 ) + goto fail; + /* Finish timeline construction. */ + timeline->sample_count = sample_count; + if( timeline->info_list->entry_count ) + isom_timeline_set_sample_getter_funcs( timeline ); + else + isom_timeline_set_lpcm_sample_getter_funcs( timeline ); + return 0; +fail: + isom_timeline_destroy( timeline ); + return err; +} + +int lsmash_construct_timeline( lsmash_root_t *root, uint32_t track_ID ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( root ) + || LSMASH_IS_NON_EXISTING_BOX( root->file ) + || track_ID == 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + uint32_t track_number; + if( LSMASH_IS_EXISTING_BOX( root->file->initializer ) ) + { + if( LSMASH_IS_NON_EXISTING_BOX( root->file->initializer->moov ) ) + return LSMASH_ERR_INVALID_DATA; + track_number = 1; + int track_found = 0; + for( lsmash_entry_t *entry = root->file->initializer->moov->trak_list.head; entry; entry = entry->next ) + { + isom_trak_t *trak = (isom_trak_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( trak ) + || LSMASH_IS_NON_EXISTING_BOX( trak->tkhd ) ) + continue; + if( trak->tkhd->track_ID == track_ID ) + { + track_found = 1; + break; + } + ++track_number; + } + if( !track_found ) + return LSMASH_ERR_NAMELESS; + } + else + track_number = track_ID; + return lsmash_importer_construct_timeline( root->file->importer, track_number ); +} + +int lsmash_get_dts_from_media_timeline( lsmash_root_t *root, uint32_t track_ID, uint32_t sample_number, uint64_t *dts ) +{ + if( !sample_number || !dts ) + return LSMASH_ERR_FUNCTION_PARAM; + isom_timeline_t *timeline = isom_get_timeline( root, track_ID ); + if( !timeline || sample_number > timeline->sample_count ) + return LSMASH_ERR_NAMELESS; + return timeline->get_dts( timeline, sample_number, dts ); +} + +int lsmash_get_cts_from_media_timeline( lsmash_root_t *root, uint32_t track_ID, uint32_t sample_number, uint64_t *cts ) +{ + if( !sample_number || !cts ) + return LSMASH_ERR_FUNCTION_PARAM; + isom_timeline_t *timeline = isom_get_timeline( root, track_ID ); + if( !timeline || sample_number > timeline->sample_count ) + return LSMASH_ERR_NAMELESS; + return timeline->get_cts( timeline, sample_number, cts ); +} + +lsmash_sample_t *lsmash_get_sample_from_media_timeline( lsmash_root_t *root, uint32_t track_ID, uint32_t sample_number ) +{ + isom_timeline_t *timeline = isom_get_timeline( root, track_ID ); + return timeline ? timeline->get_sample( timeline, sample_number ) : NULL; +} + +int lsmash_get_sample_info_from_media_timeline( lsmash_root_t *root, uint32_t track_ID, uint32_t sample_number, lsmash_sample_t *sample ) +{ + if( !sample ) + return LSMASH_ERR_FUNCTION_PARAM; + isom_timeline_t *timeline = isom_get_timeline( root, track_ID ); + return timeline ? timeline->get_sample_info( timeline, sample_number, sample ) : -1; +} + +int lsmash_get_sample_property_from_media_timeline( lsmash_root_t *root, uint32_t track_ID, uint32_t sample_number, lsmash_sample_property_t *prop ) +{ + if( !prop ) + return LSMASH_ERR_FUNCTION_PARAM; + isom_timeline_t *timeline = isom_get_timeline( root, track_ID ); + return timeline ? timeline->get_sample_property( timeline, sample_number, prop ) : -1; +} + +int lsmash_get_composition_to_decode_shift_from_media_timeline( lsmash_root_t *root, uint32_t track_ID, uint32_t *ctd_shift ) +{ + if( !ctd_shift ) + return LSMASH_ERR_FUNCTION_PARAM; + isom_timeline_t *timeline = isom_get_timeline( root, track_ID ); + if( !timeline ) + return LSMASH_ERR_NAMELESS; + *ctd_shift = timeline->ctd_shift; + return 0; +} + +static int isom_get_closest_past_random_accessible_point_from_media_timeline( isom_timeline_t *timeline, uint32_t sample_number, uint32_t *rap_number ) +{ + lsmash_entry_t *entry = lsmash_list_get_entry( timeline->info_list, sample_number-- ); + if( !entry + || !entry->data ) + return LSMASH_ERR_NAMELESS; + isom_sample_info_t *info = (isom_sample_info_t *)entry->data; + while( info->prop.ra_flags == ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE ) + { + entry = entry->prev; + if( !entry + || !entry->data ) + return LSMASH_ERR_NAMELESS; + info = (isom_sample_info_t *)entry->data; + --sample_number; + } + *rap_number = sample_number + 1; + return 0; +} + +static inline int isom_get_closest_future_random_accessible_point_from_media_timeline( isom_timeline_t *timeline, uint32_t sample_number, uint32_t *rap_number ) +{ + lsmash_entry_t *entry = lsmash_list_get_entry( timeline->info_list, sample_number++ ); + if( !entry + || !entry->data ) + return LSMASH_ERR_NAMELESS; + isom_sample_info_t *info = (isom_sample_info_t *)entry->data; + while( info->prop.ra_flags == ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE ) + { + entry = entry->next; + if( !entry + || !entry->data ) + return LSMASH_ERR_NAMELESS; + info = (isom_sample_info_t *)entry->data; + ++sample_number; + } + *rap_number = sample_number - 1; + return 0; +} + +static int isom_get_closest_random_accessible_point_from_media_timeline_internal( isom_timeline_t *timeline, uint32_t sample_number, uint32_t *rap_number ) +{ + if( !timeline ) + return LSMASH_ERR_NAMELESS; + int ret; + if( (ret = isom_get_closest_past_random_accessible_point_from_media_timeline( timeline, sample_number, rap_number )) < 0 + && (ret = isom_get_closest_future_random_accessible_point_from_media_timeline( timeline, sample_number + 1, rap_number )) < 0 ) + return ret; + return 0; +} + +int lsmash_get_closest_random_accessible_point_from_media_timeline( lsmash_root_t *root, uint32_t track_ID, uint32_t sample_number, uint32_t *rap_number ) +{ + if( sample_number == 0 || !rap_number ) + return LSMASH_ERR_FUNCTION_PARAM; + isom_timeline_t *timeline = isom_get_timeline( root, track_ID ); + if( !timeline ) + return LSMASH_ERR_NAMELESS; + if( timeline->info_list->entry_count == 0 ) + { + *rap_number = sample_number; /* All LPCM is sync sample. */ + return 0; + } + return isom_get_closest_random_accessible_point_from_media_timeline_internal( timeline, sample_number, rap_number ); +} + +int lsmash_get_closest_random_accessible_point_detail_from_media_timeline( lsmash_root_t *root, uint32_t track_ID, uint32_t sample_number, + uint32_t *rap_number, lsmash_random_access_flag *ra_flags, uint32_t *leading, uint32_t *distance ) +{ + if( sample_number == 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + isom_timeline_t *timeline = isom_get_timeline( root, track_ID ); + if( !timeline ) + return LSMASH_ERR_NAMELESS; + if( timeline->info_list->entry_count == 0 ) + { + /* All LPCM is sync sample. */ + *rap_number = sample_number; + if( ra_flags ) + *ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; + if( leading ) + *leading = 0; + if( distance ) + *distance = 0; + return 0; + } + int ret = isom_get_closest_random_accessible_point_from_media_timeline_internal( timeline, sample_number, rap_number ); + if( ret < 0 ) + return ret; + isom_sample_info_t *info = (isom_sample_info_t *)lsmash_list_get_entry_data( timeline->info_list, *rap_number ); + if( !info ) + return LSMASH_ERR_NAMELESS; + if( ra_flags ) + *ra_flags = info->prop.ra_flags; + if( leading ) + *leading = 0; + if( distance ) + *distance = 0; + if( sample_number < *rap_number ) + /* Impossible to desire to decode the sample of given number correctly. */ + return 0; + else if( !(info->prop.ra_flags & ISOM_SAMPLE_RANDOM_ACCESS_FLAG_GDR) ) + { + if( leading ) + { + /* Count leading samples. */ + uint32_t current_sample_number = *rap_number + 1; + uint64_t dts; + if( (ret = isom_get_dts_from_info_list( timeline, *rap_number, &dts )) < 0 ) + return ret; + uint64_t rap_cts = isom_make_cts_adjust( dts, info->offset, timeline->ctd_shift ); + do + { + dts += info->duration; + if( rap_cts <= dts ) + break; /* leading samples of this random accessible point must not be present more. */ + info = (isom_sample_info_t *)lsmash_list_get_entry_data( timeline->info_list, current_sample_number++ ); + if( !info ) + break; + uint64_t cts = isom_make_cts_adjust( dts, info->offset, timeline->ctd_shift ); + if( rap_cts != LSMASH_TIMESTAMP_UNDEFINED && rap_cts > cts ) + ++ *leading; + } while( 1 ); + } + if( !distance || sample_number == *rap_number ) + return 0; + /* Measure distance from the first closest non-recovery random accessible point to the second. */ + uint32_t prev_rap_number = *rap_number; + do + { + if( isom_get_closest_past_random_accessible_point_from_media_timeline( timeline, prev_rap_number - 1, &prev_rap_number ) < 0 ) + /* The previous random accessible point is not present. */ + return 0; + info = (isom_sample_info_t *)lsmash_list_get_entry_data( timeline->info_list, prev_rap_number ); + if( !info ) + return LSMASH_ERR_NAMELESS; + if( !(info->prop.ra_flags & ISOM_SAMPLE_RANDOM_ACCESS_FLAG_GDR) ) + { + /* Decode shall already complete at the first closest non-recovery random accessible point if starting to decode from the second. */ + *distance = *rap_number - prev_rap_number; + return 0; + } + } while( 1 ); + } + if( !distance ) + return 0; + /* Calculate roll-distance. */ + if( info->prop.pre_roll.distance ) + { + /* Pre-roll recovery */ + uint32_t prev_rap_number = *rap_number; + do + { + if( isom_get_closest_past_random_accessible_point_from_media_timeline( timeline, prev_rap_number - 1, &prev_rap_number ) < 0 + && *rap_number < info->prop.pre_roll.distance ) + { + /* The previous random accessible point is not present. + * And sample of given number might be not able to decoded correctly. */ + *distance = 0; + return 0; + } + if( prev_rap_number + info->prop.pre_roll.distance <= *rap_number ) + { + /* + * |<---- pre-roll distance ---->| + * |<--------- distance -------->| + * media +++++++++++++++++++++++++ *** +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * ^ ^ ^ ^ + * random accessible point starting point random accessible point given sample + * (complete) + */ + *distance = info->prop.pre_roll.distance; + return 0; + } + else if( !(info->prop.ra_flags & ISOM_SAMPLE_RANDOM_ACCESS_FLAG_GDR) ) + { + /* + * |<------------ pre-roll distance ------------------>| + * |<------ distance ------->| + * media ++++++++++++++++ *** ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * ^ ^ ^ ^ + * random accessible point random accessible point given sample + * (starting point) (complete) + */ + *distance = *rap_number - prev_rap_number; + return 0; + } + } while( 1 ); + } + /* Post-roll recovery */ + if( sample_number >= info->prop.post_roll.complete ) + /* + * |<----- post-roll distance ----->| + * (distance = 0) + * media +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * ^ ^ ^ + * random accessible point complete given sample + * (starting point) + */ + return 0; + uint32_t prev_rap_number = *rap_number; + do + { + if( isom_get_closest_past_random_accessible_point_from_media_timeline( timeline, prev_rap_number - 1, &prev_rap_number ) < 0 ) + /* The previous random accessible point is not present. */ + return 0; + info = (isom_sample_info_t *)lsmash_list_get_entry_data( timeline->info_list, prev_rap_number ); + if( !info ) + return LSMASH_ERR_NAMELESS; + if( !(info->prop.ra_flags & ISOM_SAMPLE_RANDOM_ACCESS_FLAG_GDR) || sample_number >= info->prop.post_roll.complete ) + { + *distance = *rap_number - prev_rap_number; + return 0; + } + } while( 1 ); +} + +int lsmash_check_sample_existence_in_media_timeline( lsmash_root_t *root, uint32_t track_ID, uint32_t sample_number ) +{ + isom_timeline_t *timeline = isom_get_timeline( root, track_ID ); + return timeline ? timeline->check_sample_existence( timeline, sample_number ) : 0; +} + +int lsmash_get_last_sample_delta_from_media_timeline( lsmash_root_t *root, uint32_t track_ID, uint32_t *last_sample_delta ) +{ + if( !last_sample_delta ) + return LSMASH_ERR_FUNCTION_PARAM; + isom_timeline_t *timeline = isom_get_timeline( root, track_ID ); + return timeline ? timeline->get_sample_duration( timeline, timeline->sample_count, last_sample_delta ) : -1; +} + +int lsmash_get_sample_delta_from_media_timeline( lsmash_root_t *root, uint32_t track_ID, uint32_t sample_number, uint32_t *sample_delta ) +{ + if( !sample_delta ) + return LSMASH_ERR_FUNCTION_PARAM; + isom_timeline_t *timeline = isom_get_timeline( root, track_ID ); + return timeline ? timeline->get_sample_duration( timeline, sample_number, sample_delta ) : -1; +} + +uint32_t lsmash_get_sample_count_in_media_timeline( lsmash_root_t *root, uint32_t track_ID ) +{ + isom_timeline_t *timeline = isom_get_timeline( root, track_ID ); + if( !timeline ) + return 0; + return timeline->sample_count; +} + +uint32_t lsmash_get_max_sample_size_in_media_timeline( lsmash_root_t *root, uint32_t track_ID ) +{ + isom_timeline_t *timeline = isom_get_timeline( root, track_ID ); + if( !timeline ) + return 0; + return timeline->max_sample_size; +} + +uint64_t lsmash_get_media_duration_from_media_timeline( lsmash_root_t *root, uint32_t track_ID ) +{ + isom_timeline_t *timeline = isom_get_timeline( root, track_ID ); + if( !timeline ) + return 0; + return timeline->media_duration; +} + +isom_elst_entry_t *isom_timelime_get_explicit_timeline_map +( + lsmash_root_t *root, + uint32_t track_ID, + uint32_t edit_number +) +{ + isom_timeline_t *timeline = isom_get_timeline( root, track_ID ); + if( !timeline ) + return NULL; + return lsmash_list_get_entry_data( timeline->edit_list, edit_number ); +} + +uint32_t isom_timelime_count_explicit_timeline_map +( + lsmash_root_t *root, + uint32_t track_ID +) +{ + isom_timeline_t *timeline = isom_get_timeline( root, track_ID ); + if( !timeline ) + return 0; + return timeline->edit_list->entry_count; +} + +int lsmash_copy_timeline_map( lsmash_root_t *dst, uint32_t dst_track_ID, lsmash_root_t *src, uint32_t src_track_ID ) +{ + if( isom_check_initializer_present( dst ) < 0 + || isom_check_initializer_present( src ) < 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_file_t *dst_file = dst->file->initializer; + isom_trak_t *dst_trak = isom_get_trak( dst_file, dst_track_ID ); + if( LSMASH_IS_NON_EXISTING_BOX( dst_file->moov->mvhd ) + || LSMASH_IS_NON_EXISTING_BOX( dst_trak->mdia->mdhd ) + || LSMASH_IS_NON_EXISTING_BOX( dst_trak->mdia->minf->stbl ) + || dst_file->moov->mvhd->timescale == 0 + || dst_trak->mdia->mdhd->timescale == 0 ) + return LSMASH_ERR_NAMELESS; + if( LSMASH_IS_EXISTING_BOX( dst_trak->edts->elst ) ) + lsmash_list_remove_entries( dst_trak->edts->elst->list ); + uint32_t src_movie_timescale; + uint32_t src_media_timescale; + uint64_t src_track_duration; + uint64_t src_media_duration; + int32_t src_ctd_shift; /* Add timeline shift difference between src and dst to each media_time. + * Therefore, call this function as later as possible. */ + lsmash_entry_t *src_entry = NULL; + lsmash_file_t *src_file = src->file->initializer; + isom_trak_t *src_trak = isom_get_trak( src_file, src_track_ID ); + int src_fragmented = !!(src_file->flags & LSMASH_FILE_MODE_FRAGMENTED); + if( !src_trak->edts->elst->list + || src_fragmented ) + { + /* Get from constructed timeline instead of boxes. */ + isom_timeline_t *src_timeline = isom_get_timeline( src, src_track_ID ); + if( src_timeline + && src_timeline->movie_timescale + && src_timeline->media_timescale ) + { + src_entry = src_timeline->edit_list->head; + if( !src_entry ) + return 0; + src_movie_timescale = src_timeline->movie_timescale; + src_media_timescale = src_timeline->media_timescale; + src_track_duration = src_timeline->track_duration; + src_media_duration = src_timeline->media_duration; + src_ctd_shift = src_timeline->ctd_shift; + } + else if( !src_fragmented ) + return LSMASH_ERR_NAMELESS; + } + if( !src_entry ) + { + if( LSMASH_IS_NON_EXISTING_BOX( src_file->moov->mvhd ) + || LSMASH_IS_NON_EXISTING_BOX( src_trak->tkhd ) + || LSMASH_IS_NON_EXISTING_BOX( src_trak->mdia->mdhd ) + || LSMASH_IS_NON_EXISTING_BOX( src_trak->mdia->minf->stbl ) + || src_file->moov->mvhd->timescale == 0 + || src_trak->mdia->mdhd->timescale == 0 ) + return LSMASH_ERR_NAMELESS; + if( !src_trak->edts->elst->list + || !src_trak->edts->elst->list->head ) + return 0; + src_entry = src_trak->edts->elst->list->head; + src_movie_timescale = src_file->moov->mvhd->timescale; + src_media_timescale = src_trak->mdia->mdhd->timescale; + src_track_duration = src_trak->tkhd->duration; + src_media_duration = src_trak->mdia->mdhd->duration; + src_ctd_shift = src_trak->mdia->minf->stbl->cslg->compositionToDTSShift; + } + /* Generate the edit list if absent in the destination. */ + if( (LSMASH_IS_NON_EXISTING_BOX( dst_trak->edts ) && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_edts( dst_trak ) )) + || (LSMASH_IS_NON_EXISTING_BOX( dst_trak->edts->elst ) && LSMASH_IS_BOX_ADDITION_FAILURE( isom_add_elst( dst_trak->edts ) )) ) + return LSMASH_ERR_NAMELESS; + uint32_t dst_movie_timescale = dst_file->moov->mvhd->timescale; + uint32_t dst_media_timescale = dst_trak->mdia->mdhd->timescale; + int32_t dst_ctd_shift = dst_trak->mdia->minf->stbl->cslg->compositionToDTSShift; + int32_t media_time_shift = src_ctd_shift - dst_ctd_shift; + lsmash_entry_list_t *dst_list = dst_trak->edts->elst->list; + while( src_entry ) + { + isom_elst_entry_t *src_data = (isom_elst_entry_t *)src_entry->data; + if( !src_data ) + return LSMASH_ERR_NAMELESS; + isom_elst_entry_t *dst_data = (isom_elst_entry_t *)lsmash_malloc( sizeof(isom_elst_entry_t) ); + if( !dst_data ) + return LSMASH_ERR_MEMORY_ALLOC; + uint64_t segment_duration; + if( src_data->segment_duration == 0 && !dst_file->fragment ) + /* The implicit duration edit is not suitable for non-fragmented movie file. + * Set an appropriate duration from the source track. */ + segment_duration = src_fragmented + ? (uint64_t)(src_media_duration * ((double)src_movie_timescale / src_media_timescale)) + : src_track_duration; + else + segment_duration = src_data->segment_duration; + dst_data->segment_duration = segment_duration * ((double)dst_movie_timescale / src_movie_timescale) + 0.5; + dst_data->media_rate = src_data->media_rate; + if( src_data->media_time != ISOM_EDIT_MODE_EMPTY ) + dst_data->media_time = (src_data->media_time + media_time_shift) * ((double)dst_media_timescale / src_media_timescale) + 0.5; + else + dst_data->media_time = ISOM_EDIT_MODE_EMPTY; + if( lsmash_list_add_entry( dst_list, dst_data ) < 0 ) + { + lsmash_free( dst_data ); + return LSMASH_ERR_MEMORY_ALLOC; + } + src_entry = src_entry->next; + } + return 0; +} + +int lsmash_set_media_timestamps( lsmash_root_t *root, uint32_t track_ID, lsmash_media_ts_list_t *ts_list ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( root ) + || LSMASH_IS_NON_EXISTING_BOX( root->file ) + || !ts_list ) + return -1; + isom_timeline_t *timeline = isom_get_timeline( root, track_ID ); + if( !timeline ) + return LSMASH_ERR_NAMELESS; + if( timeline->info_list->entry_count == 0 ) + { + lsmash_log( timeline, LSMASH_LOG_ERROR, "Changing timestamps of LPCM track is not supported.\n" ); + return LSMASH_ERR_PATCH_WELCOME; + } + if( ts_list->sample_count != timeline->info_list->entry_count ) + return LSMASH_ERR_INVALID_DATA; /* Number of samples must be same. */ + lsmash_media_ts_t *ts = ts_list->timestamp; + if( ts[0].dts ) + return LSMASH_ERR_INVALID_DATA; /* DTS must start from value zero. */ + /* Update DTSs. */ + uint32_t sample_count = ts_list->sample_count; + uint32_t i; + if( timeline->info_list->entry_count > 1 ) + { + i = 1; + lsmash_entry_t *entry = timeline->info_list->head; + isom_sample_info_t *info = NULL; + while( i < sample_count ) + { + info = (isom_sample_info_t *)entry->data; + if( !info || (ts[i].dts < ts[i - 1].dts) ) + return LSMASH_ERR_INVALID_DATA; + info->duration = ts[i].dts - ts[i - 1].dts; + entry = entry->next; + ++i; + } + if( i > 1 ) + { + if( !entry + || !entry->data ) + return LSMASH_ERR_INVALID_DATA; + /* Copy the previous duration. */ + ((isom_sample_info_t *)entry->data)->duration = info->duration; + } + else + return LSMASH_ERR_INVALID_DATA; /* Irregular case: sample_count this timeline has is incorrect. */ + } + else /* still image */ + ((isom_sample_info_t *)timeline->info_list->head->data)->duration = UINT32_MAX; + /* Update CTSs. + * ToDo: hint track must not have any sample_offset. */ + i = 0; + timeline->ctd_shift = 0; + for( lsmash_entry_t *entry = timeline->info_list->head; entry; entry = entry->next ) + { + isom_sample_info_t *info = (isom_sample_info_t *)entry->data; + if( ts[i].cts != LSMASH_TIMESTAMP_UNDEFINED ) + { + if( (ts[i].cts + timeline->ctd_shift) < ts[i].dts ) + timeline->ctd_shift = ts[i].dts - ts[i].cts; + info->offset = ts[i].cts - ts[i].dts; + } + else + info->offset = ISOM_NON_OUTPUT_SAMPLE_OFFSET; + ++i; + } + if( timeline->ctd_shift && (!root->file->qt_compatible || root->file->max_isom_version < 4) ) + return LSMASH_ERR_INVALID_DATA; /* Don't allow composition to decode timeline shift. */ + return 0; +} + +int lsmash_get_media_timestamps( lsmash_root_t *root, uint32_t track_ID, lsmash_media_ts_list_t *ts_list ) +{ + if( !ts_list ) + return LSMASH_ERR_FUNCTION_PARAM; + isom_timeline_t *timeline = isom_get_timeline( root, track_ID ); + if( !timeline ) + return LSMASH_ERR_NAMELESS; + uint32_t sample_count = timeline->info_list->entry_count; + if( sample_count == 0 ) + { + ts_list->sample_count = 0; + ts_list->timestamp = NULL; + return 0; + } + lsmash_media_ts_t *ts = lsmash_malloc( sample_count * sizeof(lsmash_media_ts_t) ); + if( !ts ) + return LSMASH_ERR_MEMORY_ALLOC; + uint64_t dts = 0; + uint32_t i = 0; + if( timeline->info_list->entry_count ) + for( lsmash_entry_t *entry = timeline->info_list->head; entry; entry = entry->next ) + { + isom_sample_info_t *info = (isom_sample_info_t *)entry->data; + if( !info ) + { + lsmash_free( ts ); + return LSMASH_ERR_NAMELESS; + } + ts[i].dts = dts; + ts[i].cts = isom_make_cts( dts, info->offset, timeline->ctd_shift ); + dts += info->duration; + ++i; + } + else + for( lsmash_entry_t *entry = timeline->bunch_list->head; entry; entry = entry->next ) + { + isom_lpcm_bunch_t *bunch = (isom_lpcm_bunch_t *)entry->data; + if( !bunch ) + { + lsmash_free( ts ); + return LSMASH_ERR_NAMELESS; + } + for( uint32_t j = 0; j < bunch->sample_count; j++ ) + { + ts[i].dts = dts; + ts[i].cts = isom_make_cts( dts, bunch->offset, timeline->ctd_shift ); + dts += bunch->duration; + ++i; + } + } + ts_list->sample_count = sample_count; + ts_list->timestamp = ts; + return 0; +} + +void lsmash_delete_media_timestamps( lsmash_media_ts_list_t *ts_list ) +{ + if( !ts_list ) + return; + lsmash_freep( &ts_list->timestamp ); + ts_list->sample_count = 0; +} + +static int isom_compare_dts( const lsmash_media_ts_t *a, const lsmash_media_ts_t *b ) +{ + int64_t diff = (int64_t)(a->dts - b->dts); + return diff > 0 ? 1 : (diff == 0 ? 0 : -1); +} + +void lsmash_sort_timestamps_decoding_order( lsmash_media_ts_list_t *ts_list ) +{ + if( !ts_list ) + return; + qsort( ts_list->timestamp, ts_list->sample_count, sizeof(lsmash_media_ts_t), (int(*)( const void *, const void * ))isom_compare_dts ); +} + +static int isom_compare_cts( const lsmash_media_ts_t *a, const lsmash_media_ts_t *b ) +{ + int64_t diff = (int64_t)(a->cts - b->cts); + return diff > 0 ? 1 : (diff == 0 ? 0 : -1); +} + +void lsmash_sort_timestamps_composition_order( lsmash_media_ts_list_t *ts_list ) +{ + if( !ts_list ) + return; + qsort( ts_list->timestamp, ts_list->sample_count, sizeof(lsmash_media_ts_t), (int(*)( const void *, const void * ))isom_compare_cts ); +} + +int lsmash_get_max_sample_delay( lsmash_media_ts_list_t *ts_list, uint32_t *max_sample_delay ) +{ + if( !ts_list || !max_sample_delay ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_media_ts_t *orig_ts = ts_list->timestamp; + lsmash_media_ts_t *ts = lsmash_malloc( ts_list->sample_count * sizeof(lsmash_media_ts_t) ); + if( !ts ) + return LSMASH_ERR_MEMORY_ALLOC; + ts_list->timestamp = ts; + *max_sample_delay = 0; + for( uint32_t i = 0; i < ts_list->sample_count; i++ ) + { + ts[i].cts = orig_ts[i].cts; /* for sorting */ + ts[i].dts = i; + } + lsmash_sort_timestamps_composition_order( ts_list ); + for( uint32_t i = 0; i < ts_list->sample_count; i++ ) + if( i < ts[i].dts ) + { + uint32_t sample_delay = ts[i].dts - i; + *max_sample_delay = LSMASH_MAX( *max_sample_delay, sample_delay ); + } + lsmash_free( ts ); + ts_list->timestamp = orig_ts; + return 0; +} diff --git a/3rdparty/l-smash/core/timeline.h b/3rdparty/l-smash/core/timeline.h new file mode 100644 index 000000000..bd439065d --- /dev/null +++ b/3rdparty/l-smash/core/timeline.h @@ -0,0 +1,138 @@ +/***************************************************************************** + * timeline.h + ***************************************************************************** + * Copyright (C) 2011-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#ifndef LSMASH_TIMELINE_H +#define LSMASH_TIMELINE_H + +typedef struct isom_timeline_tag isom_timeline_t; + +typedef struct +{ + uint64_t data_offset; + uint64_t length; + uint32_t number; /* useless at present */ + lsmash_file_t *file; +} isom_portable_chunk_t; + +typedef struct +{ + uint64_t pos; /* position of the first sample in this bunch */ + uint32_t duration; /* duration in media timescale each sample has */ + uint32_t offset; /* offset between composition time and decoding time each sample has */ + uint32_t length; /* data size each sample has */ + uint32_t index; /* sample_description_index applied to each sample */ + isom_portable_chunk_t *chunk; /* chunk samples belong to */ + lsmash_sample_property_t prop; /* property applied to each sample */ + uint32_t sample_count; /* number of samples in this bunch */ +} isom_lpcm_bunch_t; + +isom_timeline_t *isom_timeline_create( void ); + +void isom_timeline_destroy +( + isom_timeline_t *timeline +); + +int isom_timeline_set_track_ID +( + isom_timeline_t *timeline, + uint32_t track_ID +); + +int isom_timeline_set_movie_timescale +( + isom_timeline_t *timeline, + uint32_t movie_timescale +); + +int isom_timeline_set_media_timescale +( + isom_timeline_t *timeline, + uint32_t media_timescale +); + +int isom_timeline_set_sample_count +( + isom_timeline_t *timeline, + uint32_t sample_count +); + +int isom_timeline_set_max_sample_size +( + isom_timeline_t *timeline, + uint32_t max_sample_size +); + +int isom_timeline_set_media_duration +( + isom_timeline_t *timeline, + uint32_t media_duration +); + +int isom_timeline_set_track_duration +( + isom_timeline_t *timeline, + uint32_t track_duration +); + +void isom_timeline_set_lpcm_sample_getter_funcs +( + isom_timeline_t *timeline +); + +isom_timeline_t *isom_get_timeline +( + lsmash_root_t *root, + uint32_t track_ID +); + +void isom_remove_timelines +( + lsmash_file_t *file +); + +int isom_timeline_construct +( + lsmash_root_t *root, + uint32_t track_ID +); + +int isom_add_lpcm_bunch_entry +( + isom_timeline_t *timeline, + isom_lpcm_bunch_t *src_bunch +); + +isom_elst_entry_t *isom_timelime_get_explicit_timeline_map +( + lsmash_root_t *root, + uint32_t track_ID, + uint32_t edit_number +); + +uint32_t isom_timelime_count_explicit_timeline_map +( + lsmash_root_t *root, + uint32_t track_ID +); + +#endif /* LSMASH_TIMELINE_H */ diff --git a/3rdparty/l-smash/core/write.c b/3rdparty/l-smash/core/write.c new file mode 100644 index 000000000..4f5c4f4a9 --- /dev/null +++ b/3rdparty/l-smash/core/write.c @@ -0,0 +1,1761 @@ +/***************************************************************************** + * write.c + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include +#include +#include + +#include "box.h" +#include "write.h" + +#include "codecs/mp4a.h" +#include "codecs/mp4sys.h" +#include "codecs/description.h" + +static int isom_write_children( lsmash_bs_t *bs, isom_box_t *box ) +{ + for( lsmash_entry_t *entry = box->extensions.head; entry; entry = entry->next ) + { + isom_box_t *child = (isom_box_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( child ) ) + continue; + int ret = isom_write_box( bs, child ); + if( ret < 0 ) + return ret; + } + return 0; +} + +static int isom_write_binary_coded_box( lsmash_bs_t *bs, isom_box_t *box ) +{ + lsmash_bs_put_bytes( bs, box->size, box->binary ); + return 0; +} + +static int isom_write_unknown_box( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_unknown_box_t *unknown_box = (isom_unknown_box_t *)box; + isom_bs_put_box_common( bs, unknown_box ); + if( unknown_box->unknown_field + && unknown_box->unknown_size ) + lsmash_bs_put_bytes( bs, unknown_box->unknown_size, unknown_box->unknown_field ); + return 0; +} + +static void isom_bs_put_qt_color_table( lsmash_bs_t *bs, isom_qt_color_table_t *color_table ) +{ + lsmash_bs_put_be32( bs, color_table->seed ); + lsmash_bs_put_be16( bs, color_table->flags ); + lsmash_bs_put_be16( bs, color_table->size ); + isom_qt_color_array_t *array = color_table->array; + if( array ) + for( uint16_t i = 0; i <= color_table->size; i++ ) + { + lsmash_bs_put_be16( bs, array[i].value ); + lsmash_bs_put_be16( bs, array[i].r ); + lsmash_bs_put_be16( bs, array[i].g ); + lsmash_bs_put_be16( bs, array[i].b ); + } +} + +static int isom_write_ctab( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_ctab_t *ctab = (isom_ctab_t *)box; + isom_bs_put_box_common( bs, ctab ); + isom_bs_put_qt_color_table( bs, &ctab->color_table ); + return 0; +} + +static int isom_write_tkhd( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_tkhd_t *tkhd = (isom_tkhd_t *)box; + /* Check the version. */ + if( (LSMASH_IS_EXISTING_BOX( tkhd->file ) && !tkhd->file->undefined_64_ver) + && (tkhd->creation_time > UINT32_MAX + || tkhd->modification_time > UINT32_MAX + || tkhd->duration > UINT32_MAX) ) + tkhd->version = 1; + else + tkhd->version = 0; + /* Write. */ + isom_bs_put_box_common( bs, tkhd ); + if( tkhd->version ) + { + lsmash_bs_put_be64( bs, tkhd->creation_time ); + lsmash_bs_put_be64( bs, tkhd->modification_time ); + lsmash_bs_put_be32( bs, tkhd->track_ID ); + lsmash_bs_put_be32( bs, tkhd->reserved1 ); + lsmash_bs_put_be64( bs, tkhd->duration ); + } + else + { + lsmash_bs_put_be32( bs, LSMASH_MIN( tkhd->creation_time, UINT32_MAX ) ); + lsmash_bs_put_be32( bs, LSMASH_MIN( tkhd->modification_time, UINT32_MAX ) ); + lsmash_bs_put_be32( bs, tkhd->track_ID ); + lsmash_bs_put_be32( bs, tkhd->reserved1 ); + lsmash_bs_put_be32( bs, LSMASH_MIN( tkhd->duration, UINT32_MAX ) ); + } + lsmash_bs_put_be32( bs, tkhd->reserved2[0] ); + lsmash_bs_put_be32( bs, tkhd->reserved2[1] ); + lsmash_bs_put_be16( bs, tkhd->layer ); + lsmash_bs_put_be16( bs, tkhd->alternate_group ); + lsmash_bs_put_be16( bs, tkhd->volume ); + lsmash_bs_put_be16( bs, tkhd->reserved3 ); + for( uint32_t i = 0; i < 9; i++ ) + lsmash_bs_put_be32( bs, tkhd->matrix[i] ); + lsmash_bs_put_be32( bs, tkhd->width ); + lsmash_bs_put_be32( bs, tkhd->height ); + return 0; +} + +static int isom_write_clef( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_clef_t *clef = (isom_clef_t *)box; + isom_bs_put_box_common( bs, clef ); + lsmash_bs_put_be32( bs, clef->width ); + lsmash_bs_put_be32( bs, clef->height ); + return 0; +} + +static int isom_write_prof( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_prof_t *prof = (isom_prof_t *)box; + isom_bs_put_box_common( bs, prof ); + lsmash_bs_put_be32( bs, prof->width ); + lsmash_bs_put_be32( bs, prof->height ); + return 0; +} + +static int isom_write_enof( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_enof_t *enof = (isom_enof_t *)box; + isom_bs_put_box_common( bs, enof ); + lsmash_bs_put_be32( bs, enof->width ); + lsmash_bs_put_be32( bs, enof->height ); + return 0; +} + +static int isom_write_tapt( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_bs_put_box_common( bs, box ); + return 0; +} + +static int isom_write_elst( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_elst_t *elst = (isom_elst_t *)box; + assert( elst->list ); + if( elst->list->entry_count == 0 ) + return 0; + elst->version = 0; + lsmash_file_t *file = elst->file; + if( LSMASH_IS_EXISTING_BOX( file ) ) + { + /* Check the version. */ + if( !file->undefined_64_ver ) + for( lsmash_entry_t *entry = elst->list->head; entry; entry = entry->next ) + { + isom_elst_entry_t *data = (isom_elst_entry_t *)entry->data; + if( !data ) + return LSMASH_ERR_NAMELESS; + if( data->segment_duration > UINT32_MAX + || data->media_time > INT32_MAX + || data->media_time < INT32_MIN ) + elst->version = 1; + } + /* Remember to rewrite entries. */ + if( file->fragment && !file->bs->unseekable ) + elst->pos = file->bs->written; + } + /* Write. */ + isom_bs_put_box_common( bs, elst ); + lsmash_bs_put_be32( bs, elst->list->entry_count ); + for( lsmash_entry_t *entry = elst->list->head; entry; entry = entry->next ) + { + isom_elst_entry_t *data = (isom_elst_entry_t *)entry->data; + if( elst->version ) + { + lsmash_bs_put_be64( bs, data->segment_duration ); + lsmash_bs_put_be64( bs, data->media_time ); + } + else + { + lsmash_bs_put_be32( bs, LSMASH_MIN( data->segment_duration, UINT32_MAX ) ); + lsmash_bs_put_be32( bs, data->media_time < 0 ? (uint32_t)data->media_time : LSMASH_MIN( data->media_time, INT32_MAX ) ); + } + lsmash_bs_put_be32( bs, data->media_rate ); + } + return 0; +} + +static int isom_write_edts( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_bs_put_box_common( bs, box ); + return 0; +} + +static int isom_write_tref( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_bs_put_box_common( bs, box ); + return 0; +} + +static int isom_write_track_reference_type( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_tref_type_t *ref = (isom_tref_type_t *)box; + isom_bs_put_box_common( bs, ref ); + for( uint32_t i = 0; i < ref->ref_count; i++ ) + lsmash_bs_put_be32( bs, ref->track_ID[i] ); + return 0; +} + +static int isom_write_mdhd( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_mdhd_t *mdhd = (isom_mdhd_t *)box; + /* Check the version. */ + if( (LSMASH_IS_EXISTING_BOX( mdhd->file ) && !mdhd->file->undefined_64_ver) + && (mdhd->creation_time > UINT32_MAX + || mdhd->modification_time > UINT32_MAX + || mdhd->duration > UINT32_MAX) ) + mdhd->version = 1; + else + mdhd->version = 0; + /* Write. */ + isom_bs_put_box_common( bs, mdhd ); + if( mdhd->version ) + { + lsmash_bs_put_be64( bs, mdhd->creation_time ); + lsmash_bs_put_be64( bs, mdhd->modification_time ); + lsmash_bs_put_be32( bs, mdhd->timescale ); + lsmash_bs_put_be64( bs, mdhd->duration ); + } + else + { + lsmash_bs_put_be32( bs, LSMASH_MIN( mdhd->creation_time, UINT32_MAX ) ); + lsmash_bs_put_be32( bs, LSMASH_MIN( mdhd->modification_time, UINT32_MAX ) ); + lsmash_bs_put_be32( bs, mdhd->timescale ); + lsmash_bs_put_be32( bs, LSMASH_MIN( mdhd->duration, UINT32_MAX ) ); + } + lsmash_bs_put_be16( bs, mdhd->language ); + lsmash_bs_put_be16( bs, mdhd->quality ); + return 0; +} + +static int isom_write_hdlr( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_hdlr_t *hdlr = (isom_hdlr_t *)box; + isom_bs_put_box_common( bs, hdlr ); + lsmash_bs_put_be32( bs, hdlr->componentType ); + lsmash_bs_put_be32( bs, hdlr->componentSubtype ); + lsmash_bs_put_be32( bs, hdlr->componentManufacturer ); + lsmash_bs_put_be32( bs, hdlr->componentFlags ); + lsmash_bs_put_be32( bs, hdlr->componentFlagsMask ); + lsmash_bs_put_bytes( bs, hdlr->componentName_length, hdlr->componentName ); + return 0; +} + +static int isom_write_vmhd( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_vmhd_t *vmhd = (isom_vmhd_t *)box; + isom_bs_put_box_common( bs, vmhd ); + lsmash_bs_put_be16( bs, vmhd->graphicsmode ); + for( uint32_t i = 0; i < 3; i++ ) + lsmash_bs_put_be16( bs, vmhd->opcolor[i] ); + return 0; +} + +static int isom_write_smhd( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_smhd_t *smhd = (isom_smhd_t *)box; + isom_bs_put_box_common( bs, smhd ); + lsmash_bs_put_be16( bs, smhd->balance ); + lsmash_bs_put_be16( bs, smhd->reserved ); + return 0; +} + +static int isom_write_hmhd( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_hmhd_t *hmhd = (isom_hmhd_t *)box; + isom_bs_put_box_common( bs, hmhd ); + lsmash_bs_put_be16( bs, hmhd->maxPDUsize ); + lsmash_bs_put_be16( bs, hmhd->avgPDUsize ); + lsmash_bs_put_be32( bs, hmhd->maxbitrate ); + lsmash_bs_put_be32( bs, hmhd->avgbitrate ); + lsmash_bs_put_be32( bs, hmhd->reserved ); + return 0; +} + +static int isom_write_nmhd( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_nmhd_t *nmhd = (isom_nmhd_t *)box; + isom_bs_put_box_common( bs, nmhd ); + return 0; +} + +static int isom_write_gmin( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_gmin_t *gmin = (isom_gmin_t *)box; + isom_bs_put_box_common( bs, gmin ); + lsmash_bs_put_be16( bs, gmin->graphicsmode ); + for( uint32_t i = 0; i < 3; i++ ) + lsmash_bs_put_be16( bs, gmin->opcolor[i] ); + lsmash_bs_put_be16( bs, gmin->balance ); + lsmash_bs_put_be16( bs, gmin->reserved ); + return 0; +} + +static int isom_write_text( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_text_t *text = (isom_text_t *)box; + isom_bs_put_box_common( bs, text ); + for( uint32_t i = 0; i < 9; i++ ) + lsmash_bs_put_be32( bs, text->matrix[i] ); + return 0; +} + +static int isom_write_gmhd( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_bs_put_box_common( bs, box ); + return 0; +} + +static int isom_write_dref( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_dref_t *dref = (isom_dref_t *)box; + isom_bs_put_box_common( bs, dref ); + lsmash_bs_put_be32( bs, dref->list.entry_count ); + return 0; +} + +static int isom_write_url( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_dref_entry_t *url = (isom_dref_entry_t *)box; + isom_bs_put_box_common( bs, url ); + lsmash_bs_put_bytes( bs, url->location_length, url->location ); + return 0; +} + +static int isom_write_dinf( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_bs_put_box_common( bs, box ); + return 0; +} + +static int isom_write_pasp( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_pasp_t *pasp = (isom_pasp_t *)box; + isom_bs_put_box_common( bs, pasp ); + lsmash_bs_put_be32( bs, pasp->hSpacing ); + lsmash_bs_put_be32( bs, pasp->vSpacing ); + return 0; +} + +static int isom_write_clap( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_clap_t *clap = (isom_clap_t *)box; + isom_bs_put_box_common( bs, clap ); + lsmash_bs_put_be32( bs, clap->cleanApertureWidthN ); + lsmash_bs_put_be32( bs, clap->cleanApertureWidthD ); + lsmash_bs_put_be32( bs, clap->cleanApertureHeightN ); + lsmash_bs_put_be32( bs, clap->cleanApertureHeightD ); + lsmash_bs_put_be32( bs, clap->horizOffN ); + lsmash_bs_put_be32( bs, clap->horizOffD ); + lsmash_bs_put_be32( bs, clap->vertOffN ); + lsmash_bs_put_be32( bs, clap->vertOffD ); + return 0; +} + +static int isom_write_colr( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_colr_t *colr = (isom_colr_t *)box; + if( colr->color_parameter_type != ISOM_COLOR_PARAMETER_TYPE_NCLX + && colr->color_parameter_type != QT_COLOR_PARAMETER_TYPE_NCLC ) + return 0; + isom_bs_put_box_common( bs, colr ); + lsmash_bs_put_be32( bs, colr->color_parameter_type ); + lsmash_bs_put_be16( bs, colr->primaries_index ); + lsmash_bs_put_be16( bs, colr->transfer_function_index ); + lsmash_bs_put_be16( bs, colr->matrix_index ); + if( colr->color_parameter_type == ISOM_COLOR_PARAMETER_TYPE_NCLX ) + lsmash_bs_put_byte( bs, (colr->full_range_flag << 7) | colr->reserved ); + return 0; +} + +static int isom_write_gama( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_gama_t *gama = (isom_gama_t *)box; + if( !gama->parent ) + return 0; + /* Note: 'gama' box is superseded by 'colr' box. + * Therefore, writers of QTFF should never write both 'colr' and 'gama' box into an Image Description. */ + if( isom_get_extension_box_format( &((isom_visual_entry_t *)gama->parent)->extensions, QT_BOX_TYPE_COLR ) ) + return 0; + isom_bs_put_box_common( bs, gama ); + lsmash_bs_put_be32( bs, gama->level ); + return 0; +} + +static int isom_write_fiel( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_fiel_t *fiel = (isom_fiel_t *)box; + isom_bs_put_box_common( bs, fiel ); + lsmash_bs_put_byte( bs, fiel->fields ); + lsmash_bs_put_byte( bs, fiel->detail ); + return 0; +} + +static int isom_write_clli( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_clli_t *clli = (isom_clli_t *)box; + isom_bs_put_box_common( bs, clli ); + lsmash_bs_put_be16( bs, clli->max_content_light_level ); + lsmash_bs_put_be16( bs, clli->max_pic_average_light_level ); + return 0; +} + +static int isom_write_mdcv( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_mdcv_t *mdcv = (isom_mdcv_t *)box; + isom_bs_put_box_common( bs, mdcv ); + lsmash_bs_put_be16( bs, mdcv->display_primaries_g_x ); + lsmash_bs_put_be16( bs, mdcv->display_primaries_g_y ); + lsmash_bs_put_be16( bs, mdcv->display_primaries_b_x ); + lsmash_bs_put_be16( bs, mdcv->display_primaries_b_y ); + lsmash_bs_put_be16( bs, mdcv->display_primaries_r_x ); + lsmash_bs_put_be16( bs, mdcv->display_primaries_r_y ); + lsmash_bs_put_be16( bs, mdcv->white_point_x ); + lsmash_bs_put_be16( bs, mdcv->white_point_y ); + lsmash_bs_put_be32( bs, mdcv->max_display_mastering_luminance ); + lsmash_bs_put_be32( bs, mdcv->min_display_mastering_luminance ); + return 0; +} + +static int isom_write_cspc( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_cspc_t *cspc = (isom_cspc_t *)box; + isom_bs_put_box_common( bs, cspc ); + lsmash_bs_put_be32( bs, cspc->pixel_format ); + return 0; +} + +static int isom_write_sgbt( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_sgbt_t *sgbt = (isom_sgbt_t *)box; + isom_bs_put_box_common( bs, sgbt ); + lsmash_bs_put_byte( bs, sgbt->significantBits ); + return 0; +} + +static int isom_write_stsl( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_stsl_t *stsl = (isom_stsl_t *)box; + isom_bs_put_box_common( bs, stsl ); + lsmash_bs_put_byte( bs, stsl->constraint_flag ); + lsmash_bs_put_byte( bs, stsl->scale_method ); + lsmash_bs_put_be16( bs, stsl->display_center_x ); + lsmash_bs_put_be16( bs, stsl->display_center_y ); + return 0; +} + +static int isom_write_esds( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_esds_t *esds = (isom_esds_t *)box; + isom_bs_put_box_common( bs, esds ); + mp4sys_update_descriptor_size( esds->ES ); + return mp4sys_write_descriptor( bs, esds->ES ); +} + +static int isom_write_btrt( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_btrt_t *btrt = (isom_btrt_t *)box; + isom_bs_put_box_common( bs, btrt ); + lsmash_bs_put_be32( bs, btrt->bufferSizeDB ); + lsmash_bs_put_be32( bs, btrt->maxBitrate ); + lsmash_bs_put_be32( bs, btrt->avgBitrate ); + return 0; +} + +static int isom_write_tims( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_tims_t *tims = (isom_tims_t *)box; + isom_bs_put_box_common( bs, tims ); + lsmash_bs_put_be32( bs, tims->timescale ); + return 0; +} + +static int isom_write_tsro( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_tsro_t *tsro = (isom_tsro_t *)box; + isom_bs_put_box_common( bs, tsro ); + lsmash_bs_put_be32( bs, tsro->offset ); + return 0; +} + +static int isom_write_tssy( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_tssy_t *tssy = (isom_tssy_t *)box; + isom_bs_put_box_common( bs, tssy ); + uint8_t data = 0; + data = tssy->reserved << 2; + data |= tssy->timestamp_sync; + lsmash_bs_put_byte( bs, data ); + return 0; +} + +static int isom_write_glbl( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_glbl_t *glbl = (isom_glbl_t *)box; + isom_bs_put_box_common( bs, glbl ); + if( glbl->header_data && glbl->header_size ) + lsmash_bs_put_bytes( bs, glbl->header_size, glbl->header_data ); + return 0; +} + +static int isom_write_frma( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_frma_t *frma = (isom_frma_t *)box; + isom_bs_put_box_common( bs, frma ); + lsmash_bs_put_be32( bs, frma->data_format ); + return 0; +} + +static int isom_write_enda( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_enda_t *enda = (isom_enda_t *)box; + isom_bs_put_box_common( bs, enda ); + lsmash_bs_put_be16( bs, enda->littleEndian ); + return 0; +} + +static int isom_write_mp4a( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_mp4a_t *mp4a = (isom_mp4a_t *)box; + isom_bs_put_box_common( bs, mp4a ); + lsmash_bs_put_be32( bs, mp4a->unknown ); + return 0; +} + +static int isom_write_chan( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_chan_t *chan = (isom_chan_t *)box; + isom_bs_put_box_common( bs, chan ); + lsmash_bs_put_be32( bs, chan->channelLayoutTag ); + lsmash_bs_put_be32( bs, chan->channelBitmap ); + lsmash_bs_put_be32( bs, chan->numberChannelDescriptions ); + if( chan->channelDescriptions ) + for( uint32_t i = 0; i < chan->numberChannelDescriptions; i++ ) + { + isom_channel_description_t *channelDescriptions = (isom_channel_description_t *)(&chan->channelDescriptions[i]); + lsmash_bs_put_be32( bs, channelDescriptions->channelLabel ); + lsmash_bs_put_be32( bs, channelDescriptions->channelFlags ); + lsmash_bs_put_be32( bs, channelDescriptions->coordinates[0] ); + lsmash_bs_put_be32( bs, channelDescriptions->coordinates[1] ); + lsmash_bs_put_be32( bs, channelDescriptions->coordinates[2] ); + } + return 0; +} + +static int isom_write_terminator( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_bs_put_box_common( bs, box ); + return 0; +} + +static int isom_write_wave( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_bs_put_box_common( bs, box ); + return 0; +} + +static int isom_write_visual_description( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_visual_entry_t *data = (isom_visual_entry_t *)box; + if( LSMASH_IS_NON_EXISTING_BOX( data ) ) + return LSMASH_ERR_NAMELESS; + isom_bs_put_box_common( bs, data ); + lsmash_bs_put_bytes( bs, 6, data->reserved ); + lsmash_bs_put_be16( bs, data->data_reference_index ); + lsmash_bs_put_be16( bs, data->version ); + lsmash_bs_put_be16( bs, data->revision_level ); + lsmash_bs_put_be32( bs, data->vendor ); + lsmash_bs_put_be32( bs, data->temporalQuality ); + lsmash_bs_put_be32( bs, data->spatialQuality ); + lsmash_bs_put_be16( bs, data->width ); + lsmash_bs_put_be16( bs, data->height ); + lsmash_bs_put_be32( bs, data->horizresolution ); + lsmash_bs_put_be32( bs, data->vertresolution ); + lsmash_bs_put_be32( bs, data->dataSize ); + lsmash_bs_put_be16( bs, data->frame_count ); + lsmash_bs_put_bytes( bs, 32, data->compressorname ); + lsmash_bs_put_be16( bs, data->depth ); + lsmash_bs_put_be16( bs, data->color_table_ID ); + if( data->color_table_ID == 0 ) + isom_bs_put_qt_color_table( bs, &data->color_table ); + return 0; +} + +static int isom_write_audio_description( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_audio_entry_t *data = (isom_audio_entry_t *)box; + if( LSMASH_IS_NON_EXISTING_BOX( data ) ) + return LSMASH_ERR_NAMELESS; + isom_bs_put_box_common( bs, data ); + lsmash_bs_put_bytes( bs, 6, data->reserved ); + lsmash_bs_put_be16( bs, data->data_reference_index ); + lsmash_bs_put_be16( bs, data->version ); + lsmash_bs_put_be16( bs, data->revision_level ); + lsmash_bs_put_be32( bs, data->vendor ); + lsmash_bs_put_be16( bs, data->channelcount ); + lsmash_bs_put_be16( bs, data->samplesize ); + lsmash_bs_put_be16( bs, data->compression_ID ); + lsmash_bs_put_be16( bs, data->packet_size ); + lsmash_bs_put_be32( bs, data->samplerate ); + if( data->version == 1 ) + { + lsmash_bs_put_be32( bs, data->samplesPerPacket ); + lsmash_bs_put_be32( bs, data->bytesPerPacket ); + lsmash_bs_put_be32( bs, data->bytesPerFrame ); + lsmash_bs_put_be32( bs, data->bytesPerSample ); + } + else if( data->version == 2 ) + { + lsmash_bs_put_be32( bs, data->sizeOfStructOnly ); + lsmash_bs_put_be64( bs, data->audioSampleRate ); + lsmash_bs_put_be32( bs, data->numAudioChannels ); + lsmash_bs_put_be32( bs, data->always7F000000 ); + lsmash_bs_put_be32( bs, data->constBitsPerChannel ); + lsmash_bs_put_be32( bs, data->formatSpecificFlags ); + lsmash_bs_put_be32( bs, data->constBytesPerAudioPacket ); + lsmash_bs_put_be32( bs, data->constLPCMFramesPerAudioPacket ); + } + return 0; +} + +static int isom_write_hint_description( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_hint_entry_t *data = (isom_hint_entry_t *)box; + if( LSMASH_IS_NON_EXISTING_BOX( data ) ) + return LSMASH_ERR_NAMELESS; + isom_bs_put_box_common( bs, data ); + lsmash_bs_put_bytes( bs, 6, data->reserved ); + lsmash_bs_put_be16( bs, data->hinttrackversion ); + lsmash_bs_put_be16( bs, data->highestcompatibleversion ); + lsmash_bs_put_be32( bs, data->maxpacketsize ); + return 0; +} + +#if 0 +static int isom_write_metadata_description( lsmash_bs_t *bs, lsmash_entry_t *entry ) +{ + isom_metadata_entry_t *data = (isom_metadata_entry_t *)entry->data; + if( LSMASH_IS_NON_EXISTING_BOX( data ) ) + return LSMASH_ERR_NAMELESS; + isom_bs_put_box_common( bs, data ); + lsmash_bs_put_bytes( bs, 6, data->reserved ); + lsmash_bs_put_be16( bs, data->data_reference_index ); + return 0; +} +#endif + +static int isom_write_qt_text_description( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_qt_text_entry_t *data = (isom_qt_text_entry_t *)box; + if( LSMASH_IS_NON_EXISTING_BOX( data ) ) + return LSMASH_ERR_NAMELESS; + isom_bs_put_box_common( bs, data ); + lsmash_bs_put_bytes( bs, 6, data->reserved ); + lsmash_bs_put_be16( bs, data->data_reference_index ); + lsmash_bs_put_be32( bs, data->displayFlags ); + lsmash_bs_put_be32( bs, data->textJustification ); + for( uint32_t i = 0; i < 3; i++ ) + lsmash_bs_put_be16( bs, data->bgColor[i] ); + lsmash_bs_put_be16( bs, data->top ); + lsmash_bs_put_be16( bs, data->left ); + lsmash_bs_put_be16( bs, data->bottom ); + lsmash_bs_put_be16( bs, data->right ); + lsmash_bs_put_be32( bs, data->scrpStartChar ); + lsmash_bs_put_be16( bs, data->scrpHeight ); + lsmash_bs_put_be16( bs, data->scrpAscent ); + lsmash_bs_put_be16( bs, data->scrpFont ); + lsmash_bs_put_be16( bs, data->scrpFace ); + lsmash_bs_put_be16( bs, data->scrpSize ); + for( uint32_t i = 0; i < 3; i++ ) + lsmash_bs_put_be16( bs, data->scrpColor[i] ); + lsmash_bs_put_byte( bs, data->font_name_length ); + if( data->font_name && data->font_name_length ) + lsmash_bs_put_bytes( bs, data->font_name_length, data->font_name ); + return 0; +} + +static int isom_write_ftab( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_ftab_t *ftab = (isom_ftab_t *)box; + assert( ftab->list ); + isom_bs_put_box_common( bs, ftab ); + lsmash_bs_put_be16( bs, ftab->list->entry_count ); + for( lsmash_entry_t *entry = ftab->list->head; entry; entry = entry->next ) + { + isom_font_record_t *data = (isom_font_record_t *)entry->data; + if( !data ) + return LSMASH_ERR_NAMELESS; + lsmash_bs_put_be16( bs, data->font_ID ); + lsmash_bs_put_byte( bs, data->font_name_length ); + if( data->font_name && data->font_name_length ) + lsmash_bs_put_bytes( bs, data->font_name_length, data->font_name ); + } + return 0; +} + +static int isom_write_tx3g_description( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_tx3g_entry_t *data = (isom_tx3g_entry_t *)box; + if( LSMASH_IS_NON_EXISTING_BOX( data ) ) + return LSMASH_ERR_NAMELESS; + isom_bs_put_box_common( bs, data ); + lsmash_bs_put_bytes( bs, 6, data->reserved ); + lsmash_bs_put_be16( bs, data->data_reference_index ); + lsmash_bs_put_be32( bs, data->displayFlags ); + lsmash_bs_put_byte( bs, data->horizontal_justification ); + lsmash_bs_put_byte( bs, data->vertical_justification ); + for( uint32_t i = 0; i < 4; i++ ) + lsmash_bs_put_byte( bs, data->background_color_rgba[i] ); + lsmash_bs_put_be16( bs, data->top ); + lsmash_bs_put_be16( bs, data->left ); + lsmash_bs_put_be16( bs, data->bottom ); + lsmash_bs_put_be16( bs, data->right ); + lsmash_bs_put_be16( bs, data->startChar ); + lsmash_bs_put_be16( bs, data->endChar ); + lsmash_bs_put_be16( bs, data->font_ID ); + lsmash_bs_put_byte( bs, data->face_style_flags ); + lsmash_bs_put_byte( bs, data->font_size ); + for( uint32_t i = 0; i < 4; i++ ) + lsmash_bs_put_byte( bs, data->text_color_rgba[i] ); + return 0; +} + +static int isom_write_stsd( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_stsd_t *stsd = (isom_stsd_t *)box; + isom_bs_put_box_common( bs, stsd ); + lsmash_bs_put_be32( bs, stsd->list.entry_count ); + return 0; +} + +static int isom_write_stts( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_stts_t *stts = (isom_stts_t *)box; + assert( stts->list ); + isom_bs_put_box_common( bs, stts ); + lsmash_bs_put_be32( bs, stts->list->entry_count ); + for( lsmash_entry_t *entry = stts->list->head; entry; entry = entry->next ) + { + isom_stts_entry_t *data = (isom_stts_entry_t *)entry->data; + if( !data ) + return LSMASH_ERR_NAMELESS; + lsmash_bs_put_be32( bs, data->sample_count ); + lsmash_bs_put_be32( bs, data->sample_delta ); + } + return 0; +} + +static int isom_write_ctts( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_ctts_t *ctts = (isom_ctts_t *)box; + assert( ctts->list ); + isom_bs_put_box_common( bs, ctts ); + lsmash_bs_put_be32( bs, ctts->list->entry_count ); + for( lsmash_entry_t *entry = ctts->list->head; entry; entry = entry->next ) + { + isom_ctts_entry_t *data = (isom_ctts_entry_t *)entry->data; + if( !data ) + return LSMASH_ERR_NAMELESS; + lsmash_bs_put_be32( bs, data->sample_count ); + lsmash_bs_put_be32( bs, data->sample_offset ); + } + return 0; +} + +static int isom_write_cslg( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_cslg_t *cslg = (isom_cslg_t *)box; + isom_bs_put_box_common( bs, cslg ); + lsmash_bs_put_be32( bs, cslg->compositionToDTSShift ); + lsmash_bs_put_be32( bs, cslg->leastDecodeToDisplayDelta ); + lsmash_bs_put_be32( bs, cslg->greatestDecodeToDisplayDelta ); + lsmash_bs_put_be32( bs, cslg->compositionStartTime ); + lsmash_bs_put_be32( bs, cslg->compositionEndTime ); + return 0; +} + +static int isom_write_stsz( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_stsz_t *stsz = (isom_stsz_t *)box; + isom_bs_put_box_common( bs, stsz ); + lsmash_bs_put_be32( bs, stsz->sample_size ); + lsmash_bs_put_be32( bs, stsz->sample_count ); + if( stsz->sample_size == 0 && stsz->list ) + for( lsmash_entry_t *entry = stsz->list->head; entry; entry = entry->next ) + { + isom_stsz_entry_t *data = (isom_stsz_entry_t *)entry->data; + if( !data ) + return LSMASH_ERR_NAMELESS; + lsmash_bs_put_be32( bs, data->entry_size ); + } + return 0; +} + +static int isom_write_stz2( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_stz2_t *stz2 = (isom_stz2_t *)box; + isom_bs_put_box_common( bs, stz2 ); + lsmash_bs_put_be32( bs, (stz2->reserved << 8) | stz2->field_size ); + lsmash_bs_put_be32( bs, stz2->sample_count ); + if( stz2->field_size == 16 ) + for( lsmash_entry_t *entry = stz2->list->head; entry; entry = entry->next ) + { + isom_stsz_entry_t *data = (isom_stsz_entry_t *)entry->data; + if( !data ) + return LSMASH_ERR_NAMELESS; + assert( data->entry_size <= 0xffff ); + lsmash_bs_put_be16( bs, data->entry_size ); + } + else if( stz2->field_size == 8 ) + for( lsmash_entry_t *entry = stz2->list->head; entry; entry = entry->next ) + { + isom_stsz_entry_t *data = (isom_stsz_entry_t *)entry->data; + if( !data ) + return LSMASH_ERR_NAMELESS; + assert( data->entry_size <= 0xff ); + lsmash_bs_put_byte( bs, data->entry_size ); + } + else if( stz2->field_size == 4 ) + { + isom_stsz_entry_t zero_padding = { .entry_size = 0 }; + for( lsmash_entry_t *entry = stz2->list->head; entry; entry = entry->next ? entry->next->next : entry->next ) + { + isom_stsz_entry_t *data_o = (isom_stsz_entry_t *)entry->data; + isom_stsz_entry_t *data_e = (isom_stsz_entry_t *)(entry->next ? entry->next->data : &zero_padding); + if( !data_o || !data_e ) + return LSMASH_ERR_NAMELESS; + assert( data_o->entry_size <= 0xf && data_e->entry_size <= 0xf ); + lsmash_bs_put_byte( bs, (data_o->entry_size << 4) | data_e->entry_size ); + } + } + else + return LSMASH_ERR_NAMELESS; + return 0; +} + +static int isom_write_stss( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_stss_t *stss = (isom_stss_t *)box; + assert( stss->list ); + isom_bs_put_box_common( bs, stss ); + lsmash_bs_put_be32( bs, stss->list->entry_count ); + for( lsmash_entry_t *entry = stss->list->head; entry; entry = entry->next ) + { + isom_stss_entry_t *data = (isom_stss_entry_t *)entry->data; + if( !data ) + return LSMASH_ERR_NAMELESS; + lsmash_bs_put_be32( bs, data->sample_number ); + } + return 0; +} + +static int isom_write_stps( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_stps_t *stps = (isom_stps_t *)box; + assert( stps->list ); + isom_bs_put_box_common( bs, stps ); + lsmash_bs_put_be32( bs, stps->list->entry_count ); + for( lsmash_entry_t *entry = stps->list->head; entry; entry = entry->next ) + { + isom_stps_entry_t *data = (isom_stps_entry_t *)entry->data; + if( !data ) + return LSMASH_ERR_NAMELESS; + lsmash_bs_put_be32( bs, data->sample_number ); + } + return 0; +} + +static int isom_write_sdtp( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_sdtp_t *sdtp = (isom_sdtp_t *)box; + assert( sdtp->list ); + isom_bs_put_box_common( bs, sdtp ); + for( lsmash_entry_t *entry = sdtp->list->head; entry; entry = entry->next ) + { + isom_sdtp_entry_t *data = (isom_sdtp_entry_t *)entry->data; + if( !data ) + return LSMASH_ERR_NAMELESS; + uint8_t temp = (data->is_leading << 6) + | (data->sample_depends_on << 4) + | (data->sample_is_depended_on << 2) + | data->sample_has_redundancy; + lsmash_bs_put_byte( bs, temp ); + } + return 0; +} + +static int isom_write_stsc( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_stsc_t *stsc = (isom_stsc_t *)box; + assert( stsc->list ); + isom_bs_put_box_common( bs, stsc ); + lsmash_bs_put_be32( bs, stsc->list->entry_count ); + for( lsmash_entry_t *entry = stsc->list->head; entry; entry = entry->next ) + { + isom_stsc_entry_t *data = (isom_stsc_entry_t *)entry->data; + if( !data ) + return LSMASH_ERR_NAMELESS; + lsmash_bs_put_be32( bs, data->first_chunk ); + lsmash_bs_put_be32( bs, data->samples_per_chunk ); + lsmash_bs_put_be32( bs, data->sample_description_index ); + } + return 0; +} + +static int isom_write_co64( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_stco_t *co64 = (isom_stco_t *)box; + assert( co64->list ); + isom_bs_put_box_common( bs, co64 ); + lsmash_bs_put_be32( bs, co64->list->entry_count ); + for( lsmash_entry_t *entry = co64->list->head; entry; entry = entry->next ) + { + isom_co64_entry_t *data = (isom_co64_entry_t *)entry->data; + if( !data ) + return LSMASH_ERR_NAMELESS; + lsmash_bs_put_be64( bs, data->chunk_offset ); + } + return 0; +} + +static int isom_write_stco( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_stco_t *stco = (isom_stco_t *)box; + if( stco->large_presentation ) + return isom_write_co64( bs, box ); + assert( stco->list ); + isom_bs_put_box_common( bs, stco ); + lsmash_bs_put_be32( bs, stco->list->entry_count ); + for( lsmash_entry_t *entry = stco->list->head; entry; entry = entry->next ) + { + isom_stco_entry_t *data = (isom_stco_entry_t *)entry->data; + if( !data ) + return LSMASH_ERR_NAMELESS; + lsmash_bs_put_be32( bs, data->chunk_offset ); + } + return 0; +} + +static int isom_write_sgpd( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_sgpd_t *sgpd = (isom_sgpd_t *)box; + assert( sgpd->list ); + isom_bs_put_box_common( bs, sgpd ); + lsmash_bs_put_be32( bs, sgpd->grouping_type ); + if( sgpd->version == 1 ) + lsmash_bs_put_be32( bs, sgpd->default_length ); + lsmash_bs_put_be32( bs, sgpd->list->entry_count ); + for( lsmash_entry_t *entry = sgpd->list->head; entry; entry = entry->next ) + { + if( !entry->data ) + return LSMASH_ERR_NAMELESS; + switch( sgpd->grouping_type ) + { + case ISOM_GROUP_TYPE_RAP : + { + isom_rap_entry_t *rap = (isom_rap_entry_t *)entry->data; + uint8_t temp = (rap->num_leading_samples_known << 7) + | rap->num_leading_samples; + lsmash_bs_put_byte( bs, temp ); + break; + } + case ISOM_GROUP_TYPE_ROLL : + case ISOM_GROUP_TYPE_PROL : + lsmash_bs_put_be16( bs, ((isom_roll_entry_t *)entry->data)->roll_distance ); + break; + default : + /* We don't consider other grouping types currently. */ + // if( sgpd->version == 1 && !sgpd->default_length ) + // lsmash_bs_put_be32( bs, ((isom_sgpd_t *)entry->data)->description_length ); + break; + } + } + return 0; +} + +static int isom_write_sbgp( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_sbgp_t *sbgp = (isom_sbgp_t *)box; + assert( sbgp->list ); + isom_bs_put_box_common( bs, sbgp ); + lsmash_bs_put_be32( bs, sbgp->grouping_type ); + if( sbgp->version == 1 ) + lsmash_bs_put_be32( bs, sbgp->grouping_type_parameter ); + lsmash_bs_put_be32( bs, sbgp->list->entry_count ); + for( lsmash_entry_t *entry = sbgp->list->head; entry; entry = entry->next ) + { + isom_group_assignment_entry_t *data = (isom_group_assignment_entry_t *)entry->data; + if( !data ) + return LSMASH_ERR_NAMELESS; + lsmash_bs_put_be32( bs, data->sample_count ); + lsmash_bs_put_be32( bs, data->group_description_index ); + } + return 0; +} + +static int isom_write_stbl( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_bs_put_box_common( bs, box ); + return 0; +} + +static int isom_write_minf( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_bs_put_box_common( bs, box ); + return 0; +} + +static int isom_write_mdia( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_bs_put_box_common( bs, box ); + return 0; +} + +static int isom_write_chpl( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_chpl_t *chpl = (isom_chpl_t *)box; + assert( chpl->list ); + isom_bs_put_box_common( bs, chpl ); + if( chpl->version == 1 ) + { + lsmash_bs_put_byte( bs, chpl->unknown ); + lsmash_bs_put_be32( bs, chpl->list->entry_count ); + } + else /* chpl->version == 0 */ + lsmash_bs_put_byte( bs, (uint8_t)chpl->list->entry_count ); + for( lsmash_entry_t *entry = chpl->list->head; entry; entry = entry->next ) + { + isom_chpl_entry_t *data = (isom_chpl_entry_t *)entry->data; + if( !data ) + return LSMASH_ERR_NAMELESS; + lsmash_bs_put_be64( bs, data->start_time ); + lsmash_bs_put_byte( bs, data->chapter_name_length ); + lsmash_bs_put_bytes( bs, data->chapter_name_length, data->chapter_name ); + } + return 0; +} + +static int isom_write_mean( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_mean_t *mean = (isom_mean_t *)box; + isom_bs_put_box_common( bs, mean ); + if( mean->meaning_string && mean->meaning_string_length ) + lsmash_bs_put_bytes( bs, mean->meaning_string_length, mean->meaning_string ); + return 0; +} + +static int isom_write_name( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_name_t *name = (isom_name_t *)box; + isom_bs_put_box_common( bs, name ); + if( name->name && name->name_length ) + lsmash_bs_put_bytes( bs, name->name_length, name->name ); + return 0; +} + +static int isom_write_data( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_data_t *data = (isom_data_t *)box; + isom_bs_put_box_common( bs, data ); + lsmash_bs_put_be16( bs, data->reserved ); + lsmash_bs_put_byte( bs, data->type_set_identifier ); + lsmash_bs_put_byte( bs, data->type_code ); + lsmash_bs_put_be32( bs, data->the_locale ); + if( data->value && data->value_length ) + lsmash_bs_put_bytes( bs, data->value_length, data->value ); + return 0; +} + +static int isom_write_metaitem( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_bs_put_box_common( bs, box ); + return 0; +} + +static int isom_write_ilst( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_bs_put_box_common( bs, box ); + return 0; +} + +static int isom_write_meta( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_bs_put_box_common( bs, box ); + return 0; +} + +static int isom_write_cprt( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_cprt_t *cprt = (isom_cprt_t *)box; + isom_bs_put_box_common( bs, cprt ); + lsmash_bs_put_be16( bs, cprt->language ); + lsmash_bs_put_bytes( bs, cprt->notice_length, cprt->notice ); + return 0; +} + +static int isom_write_udta( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_bs_put_box_common( bs, box ); + return 0; +} + +static int isom_write_hnti( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_bs_put_box_common( bs, box ); + return 0; +} + +static int isom_write_rtp( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_rtp_t *rtp = (isom_rtp_t *)box; + isom_bs_put_box_common( bs, rtp ); + lsmash_bs_put_be32( bs, rtp->descriptionformat ); + lsmash_bs_put_bytes( bs, rtp->sdp_length, rtp->sdptext ); + return 0; +} +static int isom_write_sdp( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_sdp_t *sdp = (isom_sdp_t *)box; + isom_bs_put_box_common( bs, sdp ); + lsmash_bs_put_bytes( bs, sdp->sdp_length, sdp->sdptext ); + return 0; +} + +static int isom_write_trak( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_bs_put_box_common( bs, box ); + return 0; +} + +static int isom_write_iods( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_iods_t *iods = (isom_iods_t *)box; + isom_bs_put_box_common( bs, iods ); + mp4sys_update_descriptor_size( iods->OD ); + return mp4sys_write_descriptor( bs, iods->OD ); +} + +static int isom_write_mvhd( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_mvhd_t *mvhd = (isom_mvhd_t *)box; + /* Check the version. */ + if( (LSMASH_IS_EXISTING_BOX( mvhd->file ) && !mvhd->file->undefined_64_ver) + && (mvhd->creation_time > UINT32_MAX + || mvhd->modification_time > UINT32_MAX + || mvhd->duration > UINT32_MAX) ) + mvhd->version = 1; + else + mvhd->version = 0; + /* Write. */ + isom_bs_put_box_common( bs, mvhd ); + if( mvhd->version ) + { + lsmash_bs_put_be64( bs, mvhd->creation_time ); + lsmash_bs_put_be64( bs, mvhd->modification_time ); + lsmash_bs_put_be32( bs, mvhd->timescale ); + lsmash_bs_put_be64( bs, mvhd->duration ); + } + else + { + lsmash_bs_put_be32( bs, LSMASH_MIN( mvhd->creation_time, UINT32_MAX ) ); + lsmash_bs_put_be32( bs, LSMASH_MIN( mvhd->modification_time, UINT32_MAX ) ); + lsmash_bs_put_be32( bs, mvhd->timescale ); + lsmash_bs_put_be32( bs, LSMASH_MIN( mvhd->duration, UINT32_MAX ) ); + } + lsmash_bs_put_be32( bs, mvhd->rate ); + lsmash_bs_put_be16( bs, mvhd->volume ); + lsmash_bs_put_be16( bs, mvhd->reserved ); + lsmash_bs_put_be32( bs, mvhd->preferredLong[0] ); + lsmash_bs_put_be32( bs, mvhd->preferredLong[1] ); + for( int i = 0; i < 9; i++ ) + lsmash_bs_put_be32( bs, mvhd->matrix[i] ); + lsmash_bs_put_be32( bs, mvhd->previewTime ); + lsmash_bs_put_be32( bs, mvhd->previewDuration ); + lsmash_bs_put_be32( bs, mvhd->posterTime ); + lsmash_bs_put_be32( bs, mvhd->selectionTime ); + lsmash_bs_put_be32( bs, mvhd->selectionDuration ); + lsmash_bs_put_be32( bs, mvhd->currentTime ); + lsmash_bs_put_be32( bs, mvhd->next_track_ID ); + return 0; +} + +static void isom_bs_put_sample_flags( lsmash_bs_t *bs, isom_sample_flags_t *flags ) +{ + uint32_t temp = (flags->reserved << 28) + | (flags->is_leading << 26) + | (flags->sample_depends_on << 24) + | (flags->sample_is_depended_on << 22) + | (flags->sample_has_redundancy << 20) + | (flags->sample_padding_value << 17) + | (flags->sample_is_non_sync_sample << 16) + | flags->sample_degradation_priority; + lsmash_bs_put_be32( bs, temp ); +} + +static int isom_write_mehd( lsmash_bs_t *bs, isom_box_t *box ) +{ + if( box->manager & LSMASH_PLACEHOLDER ) + { + /* Movie Extends Header Box is not written immediately. + * It's done after finishing all movie fragments. + * The following will be overwritten by Movie Extends Header Box. + * We use version 1 Movie Extends Header Box since it causes extra 4 bytes region + * we cannot replace with empty Free Space Box as we place version 0 one. */ + box->pos = box->file->bs->written; + lsmash_bs_put_be32( bs, ISOM_BASEBOX_COMMON_SIZE + 12 ); + lsmash_bs_put_be32( bs, ISOM_BOX_TYPE_FREE.fourcc ); + lsmash_bs_put_be32( bs, 0 ); + lsmash_bs_put_be64( bs, 0 ); + } + else + { + isom_mehd_t *mehd = (isom_mehd_t *)box; + //mehd->version = mehd->fragment_duration > UINT32_MAX ? 1 : 0; + isom_bs_put_box_common( bs, mehd ); + if( mehd->version == 1 ) + lsmash_bs_put_be64( bs, mehd->fragment_duration ); + else + lsmash_bs_put_be32( bs, LSMASH_MIN( mehd->fragment_duration, UINT32_MAX ) ); + } + return 0; +} + +static int isom_write_trex( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_trex_t *trex = (isom_trex_t *)box; + isom_bs_put_box_common( bs, trex ); + lsmash_bs_put_be32( bs, trex->track_ID ); + lsmash_bs_put_be32( bs, trex->default_sample_description_index ); + lsmash_bs_put_be32( bs, trex->default_sample_duration ); + lsmash_bs_put_be32( bs, trex->default_sample_size ); + isom_bs_put_sample_flags( bs, &trex->default_sample_flags ); + return 0; +} + +static int isom_write_mvex( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_bs_put_box_common( bs, box ); + return 0; +} + +static int isom_write_mfhd( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_mfhd_t *mfhd = (isom_mfhd_t *)box; + isom_bs_put_box_common( bs, mfhd ); + lsmash_bs_put_be32( bs, mfhd->sequence_number ); + return 0; +} + +static int isom_write_tfhd( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_tfhd_t *tfhd = (isom_tfhd_t *)box; + isom_bs_put_box_common( bs, tfhd ); + lsmash_bs_put_be32( bs, tfhd->track_ID ); + if( tfhd->flags & ISOM_TF_FLAGS_BASE_DATA_OFFSET_PRESENT ) lsmash_bs_put_be64( bs, tfhd->base_data_offset ); + if( tfhd->flags & ISOM_TF_FLAGS_SAMPLE_DESCRIPTION_INDEX_PRESENT ) lsmash_bs_put_be32( bs, tfhd->sample_description_index ); + if( tfhd->flags & ISOM_TF_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT ) lsmash_bs_put_be32( bs, tfhd->default_sample_duration ); + if( tfhd->flags & ISOM_TF_FLAGS_DEFAULT_SAMPLE_SIZE_PRESENT ) lsmash_bs_put_be32( bs, tfhd->default_sample_size ); + if( tfhd->flags & ISOM_TF_FLAGS_DEFAULT_SAMPLE_FLAGS_PRESENT ) isom_bs_put_sample_flags( bs, &tfhd->default_sample_flags ); + return 0; +} + +static int isom_write_tfdt( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_tfdt_t *tfdt = (isom_tfdt_t *)box; + /* Check the version. */ + tfdt->version = tfdt->baseMediaDecodeTime > UINT32_MAX ? 1 : 0; + /* Write. */ + isom_bs_put_box_common( bs, tfdt ); + if( tfdt->version == 1 ) + lsmash_bs_put_be64( bs, tfdt->baseMediaDecodeTime ); + else + lsmash_bs_put_be32( bs, tfdt->baseMediaDecodeTime ); + return 0; +} + +static int isom_write_trun( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_trun_t *trun = (isom_trun_t *)box; + isom_bs_put_box_common( bs, trun ); + lsmash_bs_put_be32( bs, trun->sample_count ); + if( trun->flags & ISOM_TR_FLAGS_DATA_OFFSET_PRESENT ) lsmash_bs_put_be32( bs, trun->data_offset ); + if( trun->flags & ISOM_TR_FLAGS_FIRST_SAMPLE_FLAGS_PRESENT ) isom_bs_put_sample_flags( bs, &trun->first_sample_flags ); + if( trun->optional ) + for( lsmash_entry_t *entry = trun->optional->head; entry; entry = entry->next ) + { + isom_trun_optional_row_t *data = (isom_trun_optional_row_t *)entry->data; + if( !data ) + return LSMASH_ERR_NAMELESS; + if( trun->flags & ISOM_TR_FLAGS_SAMPLE_DURATION_PRESENT ) lsmash_bs_put_be32( bs, data->sample_duration ); + if( trun->flags & ISOM_TR_FLAGS_SAMPLE_SIZE_PRESENT ) lsmash_bs_put_be32( bs, data->sample_size ); + if( trun->flags & ISOM_TR_FLAGS_SAMPLE_FLAGS_PRESENT ) isom_bs_put_sample_flags( bs, &data->sample_flags ); + if( trun->flags & ISOM_TR_FLAGS_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT ) lsmash_bs_put_be32( bs, data->sample_composition_time_offset ); + } + return 0; +} + +static int isom_write_traf( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_bs_put_box_common( bs, box ); + return 0; +} + +static int isom_write_moof( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_bs_put_box_common( bs, box ); + return 0; +} + +static int isom_write_tfra( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_tfra_t *tfra = (isom_tfra_t *)box; + isom_bs_put_box_common( bs, tfra ); + uint32_t temp = (tfra->reserved << 6) + | (tfra->length_size_of_traf_num << 4) + | (tfra->length_size_of_trun_num << 2) + | tfra->length_size_of_sample_num; + lsmash_bs_put_be32( bs, tfra->track_ID ); + lsmash_bs_put_be32( bs, temp ); + lsmash_bs_put_be32( bs, tfra->number_of_entry ); + if( tfra->list ) + { + void (*bs_put_funcs[5])( lsmash_bs_t *, uint64_t ) = + { + lsmash_bs_put_byte_from_64, + lsmash_bs_put_be16_from_64, + lsmash_bs_put_be24_from_64, + lsmash_bs_put_be32_from_64, + lsmash_bs_put_be64 + }; + void (*bs_put_time) ( lsmash_bs_t *, uint64_t ) = bs_put_funcs[ tfra->version == 1 ? 4 : 3 ]; + void (*bs_put_moof_offset) ( lsmash_bs_t *, uint64_t ) = bs_put_funcs[ tfra->version == 1 ? 4 : 3 ]; + void (*bs_put_traf_number) ( lsmash_bs_t *, uint64_t ) = bs_put_funcs[ tfra->length_size_of_traf_num ]; + void (*bs_put_trun_number) ( lsmash_bs_t *, uint64_t ) = bs_put_funcs[ tfra->length_size_of_trun_num ]; + void (*bs_put_sample_number)( lsmash_bs_t *, uint64_t ) = bs_put_funcs[ tfra->length_size_of_sample_num ]; + for( lsmash_entry_t *entry = tfra->list->head; entry; entry = entry->next ) + { + isom_tfra_location_time_entry_t *data = (isom_tfra_location_time_entry_t *)entry->data; + if( !data ) + return LSMASH_ERR_NAMELESS; + bs_put_time ( bs, data->time ); + bs_put_moof_offset ( bs, data->moof_offset ); + bs_put_traf_number ( bs, data->traf_number ); + bs_put_trun_number ( bs, data->trun_number ); + bs_put_sample_number( bs, data->sample_number ); + } + } + return 0; +} + +static int isom_write_mfro( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_mfro_t *mfro = (isom_mfro_t *)box; + isom_bs_put_box_common( bs, mfro ); + lsmash_bs_put_be32( bs, mfro->length ); /* determined at isom_write_mfra(). */ + return 0; +} + +static int isom_write_mfra( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_mfra_t *mfra = (isom_mfra_t *)box; + if( mfra->mfro ) + mfra->mfro->length = mfra->size; + isom_bs_put_box_common( bs, mfra ); + return 0; +} + +static int isom_write_mdat( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_mdat_t *mdat = (isom_mdat_t *)box; + lsmash_file_t *file = mdat->file; + /* If any fragment, write the Media Data Box all at once. */ + if( file->fragment ) + { + /* Write the size and type fields of the Media Data Box. */ + mdat->size = ISOM_BASEBOX_COMMON_SIZE + file->fragment->pool_size; + if( mdat->size > UINT32_MAX ) + mdat->size += 8; /* large_size */ + isom_bs_put_box_common( bs, mdat ); + /* Write the samples in the current movie fragment. */ + for( lsmash_entry_t *entry = file->fragment->pool->head; entry; entry = entry->next ) + { + isom_sample_pool_t *pool = (isom_sample_pool_t *)entry->data; + if( !pool ) + return LSMASH_ERR_NAMELESS; + lsmash_bs_put_bytes( bs, pool->size, pool->data ); + } + mdat->media_size = file->fragment->pool_size; + return 0; + } + if( mdat->manager & LSMASH_PLACEHOLDER ) + { + /* Write an incomplete Media Data Box. + * Braindead implementation might check box order and return an error if an expected box does not come the + * next. Placement of eight 0x00 byte string as a simple large_size placeholder passes such silly box order + * checks. This placement is more compatible than placement of a Free Space Box ('free' or 'skip') or a + * Placeholder Atom ('wide') as a large_size placeholder since Media Data Box can store any data and any + * implementation surely do not check what contents are stored in it until taking samples out according to + * chunk offsets, and the placeholder is placed before any chunk offset thus it won't be touched. */ + mdat->pos = bs->offset; + mdat->size = ISOM_BASEBOX_COMMON_SIZE + 8 + mdat->reserved_size; + mdat->manager |= LSMASH_INCOMPLETE_BOX; + mdat->manager &= ~LSMASH_PLACEHOLDER; + isom_bs_put_box_common( bs, mdat ); + if( mdat->size <= UINT32_MAX ) + lsmash_bs_put_be64( bs, 0x0000000000000000 ); + mdat->size = ISOM_BASEBOX_COMMON_SIZE + 8; + return 0; + } + assert( !(mdat->manager & (LSMASH_INCOMPLETE_BOX | LSMASH_PLACEHOLDER)) ); + uint64_t actual_size = ISOM_BASEBOX_COMMON_SIZE + 8 + mdat->media_size; + uint64_t reserved_size = ISOM_BASEBOX_COMMON_SIZE + 8 + mdat->reserved_size; + if( actual_size < reserved_size ) + { + /* Write padding zero bytes until end of this box. + * This code path is invoked when the size of a Media Data Box was reserved. */ + mdat->size = reserved_size; + int err = lsmash_bs_flush_buffer( bs ); + if( err ) + return err; + uint64_t padding_size = reserved_size - actual_size; + static const uint8_t zero_bytes[64] = { 0 }; + while( padding_size > sizeof(zero_bytes) ) + { + if( (err = lsmash_bs_write_data( bs, zero_bytes, sizeof(zero_bytes) )) < 0 ) + return err; + padding_size -= sizeof(zero_bytes); + } + return lsmash_bs_write_data( bs, zero_bytes, padding_size ); + } + if( !bs->unseekable ) + { + /* Write the actual size. */ + uint64_t current_pos = bs->offset; + mdat->size = actual_size; + lsmash_bs_write_seek( bs, mdat->pos, SEEK_SET ); + isom_bs_put_box_common( bs, mdat ); + /* isom_write_box() also calls lsmash_bs_flush_buffer() but it must do nothing. */ + int ret = lsmash_bs_flush_buffer( bs ); + lsmash_bs_write_seek( bs, current_pos, SEEK_SET ); + return ret; + } + return LSMASH_ERR_NAMELESS; +} + +static int isom_write_ftyp( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_ftyp_t *ftyp = (isom_ftyp_t *)box; + if( ftyp->brand_count == 0 ) + return 0; + isom_bs_put_box_common( bs, ftyp ); + lsmash_bs_put_be32( bs, ftyp->major_brand ); + lsmash_bs_put_be32( bs, ftyp->minor_version ); + for( uint32_t i = 0; i < ftyp->brand_count; i++ ) + lsmash_bs_put_be32( bs, ftyp->compatible_brands[i] ); + return 0; +} + +static int isom_write_moov( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_bs_put_box_common( bs, box ); + return 0; +} + +static int isom_write_free( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_free_t *skip = (isom_free_t *)box; + isom_bs_put_box_common( bs, skip ); + if( skip->data && skip->length ) + lsmash_bs_put_bytes( bs, skip->length, skip->data ); + return 0; +} + +static int isom_write_sidx( lsmash_bs_t *bs, isom_box_t *box ) +{ + isom_sidx_t *sidx = (isom_sidx_t *)box; + /* Check the version. */ + if( sidx->earliest_presentation_time > UINT32_MAX + || sidx->first_offset > UINT32_MAX ) + sidx->version = 1; + else + sidx->version = 0; + /* Write. */ + isom_bs_put_box_common( bs, sidx ); + lsmash_bs_put_be32( bs, sidx->reference_ID ); + lsmash_bs_put_be32( bs, sidx->timescale ); + if( sidx->version == 0 ) + { + lsmash_bs_put_be32( bs, LSMASH_MIN( sidx->earliest_presentation_time, UINT32_MAX ) ); + lsmash_bs_put_be32( bs, LSMASH_MIN( sidx->first_offset, UINT32_MAX ) ); + } + else + { + lsmash_bs_put_be64( bs, sidx->earliest_presentation_time ); + lsmash_bs_put_be64( bs, sidx->first_offset ); + } + lsmash_bs_put_be16( bs, sidx->reserved ); + lsmash_bs_put_be16( bs, sidx->reference_count ); + for( lsmash_entry_t *entry = sidx->list->head; entry; entry = entry->next ) + { + isom_sidx_referenced_item_t *data = (isom_sidx_referenced_item_t *)entry->data; + if( !data ) + return LSMASH_ERR_NAMELESS; + uint32_t temp32; + temp32 = (data->reference_type << 31) + | data->reference_size; + lsmash_bs_put_be32( bs, temp32 ); + lsmash_bs_put_be32( bs, data->subsegment_duration ); + temp32 = (data->starts_with_SAP << 31) + | (data->SAP_type << 28) + | data->SAP_delta_time; + lsmash_bs_put_be32( bs, temp32 ); + } + return 0; +} + +int isom_write_box( lsmash_bs_t *bs, isom_box_t *box ) +{ + assert( bs ); + /* Don't write any incomplete or already written box to a file. */ + if( LSMASH_IS_NON_EXISTING_BOX( box ) + || !box->write + || (bs->stream && (box->manager & (LSMASH_INCOMPLETE_BOX | LSMASH_WRITTEN_BOX))) ) + return 0; + int ret = box->write( bs, box ); + if( ret < 0 ) + return ret; + if( bs->stream ) + { + if( (ret = lsmash_bs_flush_buffer( bs )) < 0 ) + return ret; + /* Don't write any child box if this box is a placeholder or an incomplete box. */ + if( box->manager & (LSMASH_PLACEHOLDER | LSMASH_INCOMPLETE_BOX) ) + return 0; + else + box->manager |= LSMASH_WRITTEN_BOX; + } + return isom_write_children( bs, box ); +} + +void isom_set_box_writer( isom_box_t *box ) +{ + if( box->manager & LSMASH_BINARY_CODED_BOX ) + { + box->write = isom_write_binary_coded_box; + return; + } + else if( box->manager & LSMASH_UNKNOWN_BOX ) + { + box->write = isom_write_unknown_box; + return; + } + assert( LSMASH_IS_EXISTING_BOX( box->parent ) ); + isom_box_t *parent = box->parent; + if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_STSD ) ) + { + /* OK, this box is a sample entry. + * Here, determine the suitable sample entry writer by media type if possible. */ + if( !isom_check_media_hdlr_from_stsd( (isom_stsd_t *)parent ) ) + return; + lsmash_media_type media_type = isom_get_media_type_from_stsd( (isom_stsd_t *)parent ); + if( media_type == ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK ) + box->write = isom_write_visual_description; + else if( media_type == ISOM_MEDIA_HANDLER_TYPE_AUDIO_TRACK ) + box->write = isom_write_audio_description; + else if( media_type == ISOM_MEDIA_HANDLER_TYPE_HINT_TRACK ) + box->write = isom_write_hint_description; + else if( media_type == ISOM_MEDIA_HANDLER_TYPE_TEXT_TRACK ) + { + if( lsmash_check_box_type_identical( box->type, QT_CODEC_TYPE_TEXT_TEXT ) ) + box->write = isom_write_qt_text_description; + else if( lsmash_check_box_type_identical( box->type, ISOM_CODEC_TYPE_TX3G_TEXT ) ) + box->write = isom_write_tx3g_description; + } + if( box->write ) + return; + } + if( lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_WAVE ) ) + { + if( lsmash_check_box_type_identical( box->type, QT_BOX_TYPE_FRMA ) ) box->write = isom_write_frma; + else if( lsmash_check_box_type_identical( box->type, QT_BOX_TYPE_ENDA ) ) box->write = isom_write_enda; + else if( lsmash_check_box_type_identical( box->type, QT_BOX_TYPE_MP4A ) ) box->write = isom_write_mp4a; + else if( lsmash_check_box_type_identical( box->type, QT_BOX_TYPE_ESDS ) ) box->write = isom_write_esds; + else if( lsmash_check_box_type_identical( box->type, QT_BOX_TYPE_CHAN ) ) box->write = isom_write_chan; + else if( lsmash_check_box_type_identical( box->type, QT_BOX_TYPE_TERMINATOR ) ) box->write = isom_write_terminator; + else box->write = NULL; + return; + } + if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_TREF ) ) + { + box->write = isom_write_track_reference_type; + return; + } + static struct box_writer_table_tag + { + lsmash_box_type_t type; + isom_extension_writer_t writer_func; + } box_writer_table[128] = { { LSMASH_BOX_TYPE_INITIALIZER, NULL } }; + if( !box_writer_table[0].writer_func ) + { + /* Initialize the table. */ + int i = 0; +#define ADD_BOX_WRITER_TABLE_ELEMENT( type, reader_func ) \ + box_writer_table[i++] = (struct box_writer_table_tag){ type, reader_func } + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_FTYP, isom_write_ftyp ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_STYP, isom_write_ftyp ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_SIDX, isom_write_sidx ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_MOOV, isom_write_moov ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_MVHD, isom_write_mvhd ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_IODS, isom_write_iods ); + ADD_BOX_WRITER_TABLE_ELEMENT( QT_BOX_TYPE_CTAB, isom_write_ctab ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_ESDS, isom_write_esds ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_TRAK, isom_write_trak ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_TKHD, isom_write_tkhd ); + ADD_BOX_WRITER_TABLE_ELEMENT( QT_BOX_TYPE_TAPT, isom_write_tapt ); + ADD_BOX_WRITER_TABLE_ELEMENT( QT_BOX_TYPE_CLEF, isom_write_clef ); + ADD_BOX_WRITER_TABLE_ELEMENT( QT_BOX_TYPE_PROF, isom_write_prof ); + ADD_BOX_WRITER_TABLE_ELEMENT( QT_BOX_TYPE_ENOF, isom_write_enof ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_EDTS, isom_write_edts ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_ELST, isom_write_elst ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_TREF, isom_write_tref ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_MDIA, isom_write_mdia ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_MDHD, isom_write_mdhd ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_HDLR, isom_write_hdlr ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_MINF, isom_write_minf ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_VMHD, isom_write_vmhd ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_SMHD, isom_write_smhd ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_HMHD, isom_write_hmhd ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_NMHD, isom_write_nmhd ); + ADD_BOX_WRITER_TABLE_ELEMENT( QT_BOX_TYPE_GMHD, isom_write_gmhd ); + ADD_BOX_WRITER_TABLE_ELEMENT( QT_BOX_TYPE_GMIN, isom_write_gmin ); + ADD_BOX_WRITER_TABLE_ELEMENT( QT_BOX_TYPE_TEXT, isom_write_text ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_DINF, isom_write_dinf ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_DREF, isom_write_dref ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_URL, isom_write_url ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_STBL, isom_write_stbl ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_STSD, isom_write_stsd ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_BTRT, isom_write_btrt ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_TIMS, isom_write_tims ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_TSRO, isom_write_tsro ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_TSSY, isom_write_tssy ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_COLR, isom_write_colr ); + ADD_BOX_WRITER_TABLE_ELEMENT( QT_BOX_TYPE_COLR, isom_write_colr ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_CLAP, isom_write_clap ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_PASP, isom_write_pasp ); + ADD_BOX_WRITER_TABLE_ELEMENT( QT_BOX_TYPE_GLBL, isom_write_glbl ); + ADD_BOX_WRITER_TABLE_ELEMENT( QT_BOX_TYPE_GAMA, isom_write_gama ); + ADD_BOX_WRITER_TABLE_ELEMENT( QT_BOX_TYPE_FIEL, isom_write_fiel ); + ADD_BOX_WRITER_TABLE_ELEMENT( QT_BOX_TYPE_CLLI, isom_write_clli ); + ADD_BOX_WRITER_TABLE_ELEMENT( QT_BOX_TYPE_MDCV, isom_write_mdcv ); + ADD_BOX_WRITER_TABLE_ELEMENT( QT_BOX_TYPE_CSPC, isom_write_cspc ); + ADD_BOX_WRITER_TABLE_ELEMENT( QT_BOX_TYPE_SGBT, isom_write_sgbt ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_STSL, isom_write_stsl ); + ADD_BOX_WRITER_TABLE_ELEMENT( QT_BOX_TYPE_WAVE, isom_write_wave ); + ADD_BOX_WRITER_TABLE_ELEMENT( QT_BOX_TYPE_MP4A, isom_write_mp4a ); + ADD_BOX_WRITER_TABLE_ELEMENT( QT_BOX_TYPE_CHAN, isom_write_chan ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_FTAB, isom_write_ftab ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_STTS, isom_write_stts ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_CTTS, isom_write_ctts ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_CSLG, isom_write_cslg ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_STSS, isom_write_stss ); + ADD_BOX_WRITER_TABLE_ELEMENT( QT_BOX_TYPE_STPS, isom_write_stps ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_SDTP, isom_write_sdtp ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_STSC, isom_write_stsc ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_STSZ, isom_write_stsz ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_STZ2, isom_write_stz2 ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_STCO, isom_write_stco ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_CO64, isom_write_stco ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_SGPD, isom_write_sgpd ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_SBGP, isom_write_sbgp ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_UDTA, isom_write_udta ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_HNTI, isom_write_hnti ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_RTP, isom_write_rtp ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_SDP, isom_write_sdp ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_CHPL, isom_write_chpl ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_MVEX, isom_write_mvex ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_MEHD, isom_write_mehd ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_TREX, isom_write_trex ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_MOOF, isom_write_moof ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_MFHD, isom_write_mfhd ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_TRAF, isom_write_traf ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_TFHD, isom_write_tfhd ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_TFDT, isom_write_tfdt ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_TRUN, isom_write_trun ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_MDAT, isom_write_mdat ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_FREE, isom_write_free ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_SKIP, isom_write_free ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_META, isom_write_meta ); + ADD_BOX_WRITER_TABLE_ELEMENT( QT_BOX_TYPE_META, isom_write_meta ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_ILST, isom_write_ilst ); + ADD_BOX_WRITER_TABLE_ELEMENT( QT_BOX_TYPE_ILST, isom_write_ilst ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_MFRA, isom_write_mfra ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_TFRA, isom_write_tfra ); + ADD_BOX_WRITER_TABLE_ELEMENT( ISOM_BOX_TYPE_MFRO, isom_write_mfro ); + ADD_BOX_WRITER_TABLE_ELEMENT( LSMASH_BOX_TYPE_UNSPECIFIED, NULL ); +#undef ADD_BOX_WRITER_TABLE_ELEMENT + } + for( int i = 0; box_writer_table[i].writer_func; i++ ) + if( lsmash_check_box_type_identical( box->type, box_writer_table[i].type ) ) + { + box->write = box_writer_table[i].writer_func; + return; + } + if( lsmash_check_box_type_identical( parent->type, ISOM_BOX_TYPE_ILST ) + || lsmash_check_box_type_identical( parent->type, QT_BOX_TYPE_ILST ) ) + { + box->write = isom_write_metaitem; + return; + } + if( lsmash_check_box_type_identical( parent->parent->type, ISOM_BOX_TYPE_ILST ) ) + { + if( lsmash_check_box_type_identical( box->type, ISOM_BOX_TYPE_MEAN ) ) + box->write = isom_write_mean; + else if( lsmash_check_box_type_identical( box->type, ISOM_BOX_TYPE_NAME ) ) + box->write = isom_write_name; + else if( lsmash_check_box_type_identical( box->type, ISOM_BOX_TYPE_DATA ) ) + box->write = isom_write_data; + if( box->write ) + return; + } + else if( lsmash_check_box_type_identical( box->type, ISOM_BOX_TYPE_CPRT ) ) + { + /* Avoid confusing udta.cprt with ilst.cprt. */ + box->write = isom_write_cprt; + return; + } + box->write = isom_write_unknown_box; +} diff --git a/3rdparty/l-smash/core/write.h b/3rdparty/l-smash/core/write.h new file mode 100644 index 000000000..6d265f10d --- /dev/null +++ b/3rdparty/l-smash/core/write.h @@ -0,0 +1,29 @@ +/***************************************************************************** + * write.h + ***************************************************************************** + * Copyright (C) 2011-2017 L-SMASH project + * + * Authors: Hiroki Taniura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#ifndef LSMASH_WRITE_H +#define LSMASH_WRITE_H + +int isom_write_box( lsmash_bs_t *bs, isom_box_t *box ); +void isom_set_box_writer( isom_box_t *box ); + +#endif diff --git a/3rdparty/l-smash/importer/a52_imp.c b/3rdparty/l-smash/importer/a52_imp.c new file mode 100644 index 000000000..e6b00159e --- /dev/null +++ b/3rdparty/l-smash/importer/a52_imp.c @@ -0,0 +1,655 @@ +/***************************************************************************** + * a52_imp.c + ***************************************************************************** + * Copyright (C) 2011-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include + +#define LSMASH_IMPORTER_INTERNAL +#include "importer.h" + +/*************************************************************************** + AC-3 importer + ETSI TS 102 366 V1.2.1 (2008-08) +***************************************************************************/ +#include "codecs/a52.h" + +#define AC3_SAMPLE_DURATION 1536 /* 256 (samples per audio block) * 6 (audio blocks) */ + +typedef struct +{ + ac3_info_t info; + uint64_t next_frame_pos; + uint8_t *next_dac3; + uint8_t buffer[AC3_MAX_SYNCFRAME_LENGTH]; + uint32_t au_number; +} ac3_importer_t; + +static void remove_ac3_importer( ac3_importer_t *ac3_imp ) +{ + if( !ac3_imp ) + return; + lsmash_bits_cleanup( ac3_imp->info.bits ); + lsmash_free( ac3_imp ); +} + +static ac3_importer_t *create_ac3_importer( importer_t *importer ) +{ + ac3_importer_t *ac3_imp = (ac3_importer_t *)lsmash_malloc_zero( sizeof(ac3_importer_t) ); + if( !ac3_imp ) + return NULL; + ac3_imp->info.bits = lsmash_bits_create( importer->bs ); + if( !ac3_imp->info.bits ) + { + lsmash_free( ac3_imp ); + return NULL; + } + return ac3_imp; +} + +static void ac3_importer_cleanup( importer_t *importer ) +{ + debug_if( importer && importer->info ) + remove_ac3_importer( importer->info ); +} + +static const uint32_t ac3_frame_size_table[19][3] = +{ + /* 48, 44.1, 32 */ + { 128, 138, 192 }, + { 160, 174, 240 }, + { 192, 208, 288 }, + { 224, 242, 336 }, + { 256, 278, 384 }, + { 320, 348, 480 }, + { 384, 416, 576 }, + { 448, 486, 672 }, + { 512, 556, 768 }, + { 640, 696, 960 }, + { 768, 834, 1152 }, + { 896, 974, 1344 }, + { 1024, 1114, 1536 }, + { 1280, 1392, 1920 }, + { 1536, 1670, 2304 }, + { 1792, 1950, 2688 }, + { 2048, 2228, 3072 }, + { 2304, 2506, 3456 }, + { 2560, 2786, 3840 } +}; + +static lsmash_audio_summary_t *ac3_create_summary( ac3_info_t *info ) +{ + lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_create_summary( LSMASH_SUMMARY_TYPE_AUDIO ); + if( !summary ) + return NULL; + lsmash_ac3_specific_parameters_t *param = &info->dac3_param; + lsmash_codec_specific_t *cs = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_AC_3, + LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED ); + if( !cs ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return NULL; + } + cs->data.unstructured = lsmash_create_ac3_specific_info( &info->dac3_param, &cs->size ); + if( !cs->data.unstructured + || lsmash_list_add_entry( &summary->opaque->list, cs ) < 0 ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + lsmash_destroy_codec_specific_data( cs ); + return NULL; + } + summary->sample_type = ISOM_CODEC_TYPE_AC_3_AUDIO; + summary->max_au_length = AC3_MAX_SYNCFRAME_LENGTH; + summary->aot = MP4A_AUDIO_OBJECT_TYPE_NULL; /* no effect */ + summary->frequency = ac3_get_sample_rate( param ); + summary->channels = ac3_get_channel_count( param ); + summary->sample_size = 16; /* no effect */ + summary->samples_in_frame = AC3_SAMPLE_DURATION; + summary->sbr_mode = MP4A_AAC_SBR_NOT_SPECIFIED; /* no effect */ + return summary; +} + +static int ac3_compare_specific_param( lsmash_ac3_specific_parameters_t *a, lsmash_ac3_specific_parameters_t *b ) +{ + return (a->fscod != b->fscod) + || (a->bsid != b->bsid) + || (a->bsmod != b->bsmod) + || (a->acmod != b->acmod) + || (a->lfeon != b->lfeon) + || ((a->frmsizecod >> 1) != (b->frmsizecod >> 1)); +} + +static int ac3_buffer_frame( uint8_t *buffer, lsmash_bs_t *bs ) +{ + uint64_t remain_size = lsmash_bs_get_remaining_buffer_size( bs ); + if( remain_size < AC3_MAX_SYNCFRAME_LENGTH ) + { + if( bs->buffer.pos > AC3_MAX_SYNCFRAME_LENGTH * 1000 ) + lsmash_bs_dispose_past_data( bs ); + int err = lsmash_bs_read( bs, bs->buffer.max_size ); + if( err < 0 ) + return err; + remain_size = lsmash_bs_get_remaining_buffer_size( bs ); + } + uint64_t copy_size = LSMASH_MIN( remain_size, AC3_MAX_SYNCFRAME_LENGTH ); + memcpy( buffer, lsmash_bs_get_buffer_data( bs ), copy_size ); + return 0; +} + +static int ac3_importer_get_accessunit( importer_t *importer, uint32_t track_number, lsmash_sample_t **p_sample ) +{ + if( !importer->info ) + return LSMASH_ERR_NAMELESS; + if( track_number != 1 ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_list_get_entry_data( importer->summaries, track_number ); + if( !summary ) + return LSMASH_ERR_NAMELESS; + ac3_importer_t *ac3_imp = (ac3_importer_t *)importer->info; + ac3_info_t *info = &ac3_imp->info; + importer_status current_status = importer->status; + if( current_status == IMPORTER_ERROR ) + return LSMASH_ERR_NAMELESS; + if( current_status == IMPORTER_EOF ) + return IMPORTER_EOF; + lsmash_ac3_specific_parameters_t *param = &info->dac3_param; + uint32_t frame_size = ac3_frame_size_table[ param->frmsizecod >> 1 ][ param->fscod ]; + if( param->fscod == 0x1 && param->frmsizecod & 0x1 ) + frame_size += 2; + if( current_status == IMPORTER_CHANGE ) + { + lsmash_codec_specific_t *cs = isom_get_codec_specific( summary->opaque, LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_AC_3 ); + if( cs ) + { + cs->destruct( cs->data.unstructured ); + cs->data.unstructured = ac3_imp->next_dac3; + } + summary->frequency = ac3_get_sample_rate( param ); + summary->channels = ac3_get_channel_count( param ); + //summary->layout_tag = ac3_channel_layout_table[ param->acmod ][ param->lfeon ]; + } + lsmash_sample_t *sample = lsmash_create_sample( frame_size ); + if( !sample ) + return LSMASH_ERR_MEMORY_ALLOC; + *p_sample = sample; + memcpy( sample->data, ac3_imp->buffer, frame_size ); + sample->length = frame_size; + sample->dts = ac3_imp->au_number++ * summary->samples_in_frame; + sample->cts = sample->dts; + sample->prop.ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; + sample->prop.pre_roll.distance = 1; /* MDCT */ + lsmash_bs_t *bs = info->bits->bs; + ac3_imp->next_frame_pos += frame_size; + lsmash_bs_read_seek( bs, ac3_imp->next_frame_pos, SEEK_SET ); + uint8_t syncword[2] = + { + lsmash_bs_show_byte( bs, 0 ), + lsmash_bs_show_byte( bs, 1 ) + }; + if( bs->eob || (bs->eof && 0 == lsmash_bs_get_remaining_buffer_size( bs )) ) + importer->status = IMPORTER_EOF; + else + { + /* Parse the next syncframe header. */ + if( syncword[0] != 0x0b + || syncword[1] != 0x77 + || ac3_buffer_frame( ac3_imp->buffer, bs ) < 0 ) + { + importer->status = IMPORTER_ERROR; + return current_status; + } + lsmash_ac3_specific_parameters_t current_param = info->dac3_param; + ac3_parse_syncframe_header( info ); + if( ac3_compare_specific_param( ¤t_param, &info->dac3_param ) ) + { + uint32_t dummy; + uint8_t *dac3 = lsmash_create_ac3_specific_info( &info->dac3_param, &dummy ); + if( !dac3 ) + { + importer->status = IMPORTER_ERROR; + return current_status; + } + ac3_imp->next_dac3 = dac3; + importer->status = IMPORTER_CHANGE; + } + else + importer->status = IMPORTER_OK; + } + return current_status; +} + +static int ac3_importer_probe( importer_t *importer ) +{ + ac3_importer_t *ac3_imp = create_ac3_importer( importer ); + if( !ac3_imp ) + return LSMASH_ERR_MEMORY_ALLOC; + lsmash_bits_t *bits = ac3_imp->info.bits; + lsmash_bs_t *bs = bits->bs; + bs->buffer.max_size = AC3_MAX_SYNCFRAME_LENGTH; + /* Check the syncword and parse the syncframe header */ + int err; + if( lsmash_bs_show_byte( bs, 0 ) != 0x0b + || lsmash_bs_show_byte( bs, 1 ) != 0x77 ) + { + err = LSMASH_ERR_INVALID_DATA; + goto fail; + } + if( (err = ac3_buffer_frame( ac3_imp->buffer, bs )) < 0 + || (err = ac3_parse_syncframe_header( &ac3_imp->info )) < 0 ) + goto fail; + lsmash_audio_summary_t *summary = ac3_create_summary( &ac3_imp->info ); + if( !summary ) + { + err = LSMASH_ERR_NAMELESS; + goto fail; + } + if( lsmash_list_add_entry( importer->summaries, summary ) < 0 ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + err = LSMASH_ERR_MEMORY_ALLOC; + goto fail; + } + ac3_imp->au_number = 0; + importer->info = ac3_imp; + importer->status = IMPORTER_OK; + return 0; +fail: + remove_ac3_importer( ac3_imp ); + return err; +} + +static uint32_t ac3_importer_get_last_delta( importer_t *importer, uint32_t track_number ) +{ + debug_if( !importer || !importer->info ) + return 0; + ac3_importer_t *ac3_imp = (ac3_importer_t *)importer->info; + if( !ac3_imp || track_number != 1 || importer->status != IMPORTER_EOF ) + return 0; + return AC3_SAMPLE_DURATION; +} + +const importer_functions ac3_importer = +{ + { "AC-3" }, + 1, + ac3_importer_probe, + ac3_importer_get_accessunit, + ac3_importer_get_last_delta, + ac3_importer_cleanup +}; + +/*************************************************************************** + Enhanced AC-3 importer + ETSI TS 102 366 V1.2.1 (2008-08) +***************************************************************************/ +#define EAC3_MIN_SAMPLE_DURATION 256 + +typedef struct +{ + eac3_info_t info; + uint64_t next_frame_pos; + uint32_t next_dec3_length; + uint8_t *next_dec3; + uint8_t current_fscod2; + uint8_t buffer[EAC3_MAX_SYNCFRAME_LENGTH]; + lsmash_multiple_buffers_t *au_buffers; + uint8_t *au; + uint8_t *incomplete_au; + uint32_t au_length; + uint32_t incomplete_au_length; + uint32_t au_number; + uint32_t syncframe_count_in_au; +} eac3_importer_t; + +static void remove_eac3_importer( eac3_importer_t *eac3_imp ) +{ + if( !eac3_imp ) + return; + lsmash_destroy_multiple_buffers( eac3_imp->au_buffers ); + lsmash_bits_cleanup( eac3_imp->info.bits ); + lsmash_free( eac3_imp ); +} + +static eac3_importer_t *create_eac3_importer( importer_t *importer ) +{ + eac3_importer_t *eac3_imp = (eac3_importer_t *)lsmash_malloc_zero( sizeof(eac3_importer_t) ); + if( !eac3_imp ) + return NULL; + eac3_info_t *info = &eac3_imp->info; + info->bits = lsmash_bits_create( importer->bs ); + if( !info->bits ) + { + lsmash_free( eac3_imp ); + return NULL; + } + eac3_imp->au_buffers = lsmash_create_multiple_buffers( 2, EAC3_MAX_SYNCFRAME_LENGTH ); + if( !eac3_imp->au_buffers ) + { + lsmash_bits_cleanup( info->bits ); + lsmash_free( eac3_imp ); + return NULL; + } + eac3_imp->au = lsmash_withdraw_buffer( eac3_imp->au_buffers, 1 ); + eac3_imp->incomplete_au = lsmash_withdraw_buffer( eac3_imp->au_buffers, 2 ); + return eac3_imp; +} + +static void eac3_importer_cleanup( importer_t *importer ) +{ + debug_if( importer && importer->info ) + remove_eac3_importer( importer->info ); +} + +static int eac3_importer_get_next_accessunit_internal( importer_t *importer ) +{ + int au_completed = 0; + eac3_importer_t *eac3_imp = (eac3_importer_t *)importer->info; + eac3_info_t *info = &eac3_imp->info; + lsmash_bs_t *bs = info->bits->bs; + while( !au_completed ) + { + /* Read data from the stream if needed. */ + eac3_imp->next_frame_pos += info->frame_size; + lsmash_bs_read_seek( bs, eac3_imp->next_frame_pos, SEEK_SET ); + uint64_t remain_size = lsmash_bs_get_remaining_buffer_size( bs ); + if( remain_size < EAC3_MAX_SYNCFRAME_LENGTH ) + { + if( bs->buffer.pos > EAC3_MAX_SYNCFRAME_LENGTH * 1000 ) + lsmash_bs_dispose_past_data( bs ); + int err = lsmash_bs_read( bs, bs->buffer.max_size ); + if( err < 0 ) + { + lsmash_log( importer, LSMASH_LOG_ERROR, "failed to read data from the stream.\n" ); + return err; + } + remain_size = lsmash_bs_get_remaining_buffer_size( bs ); + } + uint64_t copy_size = LSMASH_MIN( remain_size, EAC3_MAX_SYNCFRAME_LENGTH ); + memcpy( eac3_imp->buffer, lsmash_bs_get_buffer_data( bs ), copy_size ); + /* Check the remainder length of the buffer. + * If there is enough length, then parse the syncframe in it. + * The length 5 is the required byte length to get frame size. */ + if( bs->eob || (bs->eof && remain_size < 5) ) + { + /* Reached the end of stream. + * According to ETSI TS 102 366 V1.2.1 (2008-08), + * one access unit consists of 6 audio blocks and begins with independent substream 0. + * The specification doesn't mention the case where a enhanced AC-3 stream ends at non-mod6 audio blocks. + * At the end of the stream, therefore, we might make an access unit which has less than 6 audio blocks anyway. */ + importer->status = IMPORTER_EOF; + au_completed = !!eac3_imp->incomplete_au_length; + if( !au_completed ) + { + /* No more access units in the stream. */ + if( lsmash_bs_get_remaining_buffer_size( bs ) ) + { + lsmash_log( importer, LSMASH_LOG_WARNING, "the stream is truncated at the end.\n" ); + return LSMASH_ERR_INVALID_DATA; + } + return 0; + } + if( !info->dec3_param_initialized ) + eac3_update_specific_param( info ); + } + else + { + /* Check the syncword. */ + if( lsmash_bs_show_byte( bs, 0 ) != 0x0b + || lsmash_bs_show_byte( bs, 1 ) != 0x77 ) + { + lsmash_log( importer, LSMASH_LOG_ERROR, "a syncword is not found.\n" ); + return LSMASH_ERR_INVALID_DATA; + } + /* Parse syncframe. */ + info->frame_size = 0; + int err = eac3_parse_syncframe( info ); + if( err < 0 ) + { + lsmash_log( importer, LSMASH_LOG_ERROR, "failed to parse syncframe.\n" ); + return err; + } + if( remain_size < info->frame_size ) + { + lsmash_log( importer, LSMASH_LOG_ERROR, "a frame is truncated.\n" ); + return LSMASH_ERR_INVALID_DATA; + } + int independent = info->strmtyp != 0x1; + if( independent && info->substreamid == 0x0 ) + { + if( info->number_of_audio_blocks == 6 ) + { + /* Encountered the first syncframe of the next access unit. */ + info->number_of_audio_blocks = 0; + au_completed = 1; + } + else if( info->number_of_audio_blocks > 6 ) + { + lsmash_log( importer, LSMASH_LOG_ERROR, "greater than 6 consecutive independent substreams.\n" ); + return LSMASH_ERR_INVALID_DATA; + } + info->number_of_audio_blocks += eac3_audio_block_table[ info->numblkscod ]; + info->number_of_independent_substreams = 0; + eac3_imp->current_fscod2 = info->fscod2; + } + else if( info->syncframe_count == 0 ) + { + /* The first syncframe in an AU must be independent and assigned substream ID 0. */ + lsmash_log( importer, LSMASH_LOG_ERROR, "the first syncframe is NOT an independent substream.\n" ); + return LSMASH_ERR_INVALID_DATA; + } + if( independent ) + info->independent_info[info->number_of_independent_substreams ++].num_dep_sub = 0; + else + ++ info->independent_info[info->number_of_independent_substreams - 1].num_dep_sub; + } + if( au_completed ) + { + memcpy( eac3_imp->au, eac3_imp->incomplete_au, eac3_imp->incomplete_au_length ); + eac3_imp->au_length = eac3_imp->incomplete_au_length; + eac3_imp->incomplete_au_length = 0; + eac3_imp->syncframe_count_in_au = info->syncframe_count; + info->syncframe_count = 0; + if( importer->status == IMPORTER_EOF ) + break; + } + /* Increase buffer size to store AU if short. */ + if( eac3_imp->incomplete_au_length + info->frame_size > eac3_imp->au_buffers->buffer_size ) + { + lsmash_multiple_buffers_t *temp = lsmash_resize_multiple_buffers( eac3_imp->au_buffers, + eac3_imp->au_buffers->buffer_size + EAC3_MAX_SYNCFRAME_LENGTH ); + if( !temp ) + return LSMASH_ERR_MEMORY_ALLOC; + eac3_imp->au_buffers = temp; + eac3_imp->au = lsmash_withdraw_buffer( eac3_imp->au_buffers, 1 ); + eac3_imp->incomplete_au = lsmash_withdraw_buffer( eac3_imp->au_buffers, 2 ); + } + /* Append syncframe data. */ + memcpy( eac3_imp->incomplete_au + eac3_imp->incomplete_au_length, eac3_imp->buffer, info->frame_size ); + eac3_imp->incomplete_au_length += info->frame_size; + ++ info->syncframe_count; + } + return bs->error ? LSMASH_ERR_NAMELESS : 0; +} + +static int eac3_importer_get_accessunit( importer_t *importer, uint32_t track_number, lsmash_sample_t **p_sample ) +{ + if( !importer->info ) + return LSMASH_ERR_NAMELESS; + if( track_number != 1 ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_list_get_entry_data( importer->summaries, track_number ); + if( !summary ) + return LSMASH_ERR_NAMELESS; + eac3_importer_t *eac3_imp = (eac3_importer_t *)importer->info; + eac3_info_t *info = &eac3_imp->info; + importer_status current_status = importer->status; + if( current_status == IMPORTER_ERROR ) + return LSMASH_ERR_NAMELESS; + if( current_status == IMPORTER_EOF && eac3_imp->au_length == 0 ) + return IMPORTER_EOF; + if( current_status == IMPORTER_CHANGE ) + { + lsmash_codec_specific_t *cs = isom_get_codec_specific( summary->opaque, LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_EC_3 ); + if( cs ) + { + cs->destruct( cs->data.unstructured ); + cs->data.unstructured = eac3_imp->next_dec3; + cs->size = eac3_imp->next_dec3_length; + } + summary->max_au_length = eac3_imp->syncframe_count_in_au * EAC3_MAX_SYNCFRAME_LENGTH; + eac3_update_sample_rate( &summary->frequency, &info->dec3_param, &eac3_imp->current_fscod2 ); + eac3_update_channel_count( &summary->channels, &info->dec3_param ); + } + lsmash_sample_t *sample = lsmash_create_sample( eac3_imp->au_length ); + if( !sample ) + return LSMASH_ERR_MEMORY_ALLOC; + *p_sample = sample; + memcpy( sample->data, eac3_imp->au, eac3_imp->au_length ); + sample->length = eac3_imp->au_length; + sample->dts = eac3_imp->au_number++ * summary->samples_in_frame; + sample->cts = sample->dts; + sample->prop.ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; + sample->prop.pre_roll.distance = 1; /* MDCT */ + if( importer->status == IMPORTER_EOF ) + { + eac3_imp->au_length = 0; + return 0; + } + uint32_t old_syncframe_count_in_au = eac3_imp->syncframe_count_in_au; + if( eac3_importer_get_next_accessunit_internal( importer ) < 0 ) + { + importer->status = IMPORTER_ERROR; + return current_status; + } + if( eac3_imp->syncframe_count_in_au ) + { + /* Check sample description change. */ + uint32_t new_length; + uint8_t *dec3 = lsmash_create_eac3_specific_info( &info->dec3_param, &new_length ); + if( !dec3 ) + { + importer->status = IMPORTER_ERROR; + return current_status; + } + lsmash_codec_specific_t *cs = isom_get_codec_specific( summary->opaque, LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_EC_3 ); + if( (eac3_imp->syncframe_count_in_au > old_syncframe_count_in_au) + || (cs && (new_length != cs->size || memcmp( dec3, cs->data.unstructured, cs->size ))) ) + { + importer->status = IMPORTER_CHANGE; + eac3_imp->next_dec3 = dec3; + eac3_imp->next_dec3_length = new_length; + } + else + { + if( importer->status != IMPORTER_EOF ) + importer->status = IMPORTER_OK; + lsmash_free( dec3 ); + } + } + return current_status; +} + +static lsmash_audio_summary_t *eac3_create_summary( eac3_importer_t *eac3_imp ) +{ + lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_create_summary( LSMASH_SUMMARY_TYPE_AUDIO ); + if( !summary ) + return NULL; + eac3_info_t *info = &eac3_imp->info; + lsmash_codec_specific_t *cs = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_EC_3, + LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED ); + if( !cs ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return NULL; + } + cs->data.unstructured = lsmash_create_eac3_specific_info( &info->dec3_param, &cs->size ); + if( !cs->data.unstructured + || lsmash_list_add_entry( &summary->opaque->list, cs ) < 0 ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + lsmash_destroy_codec_specific_data( cs ); + return NULL; + } + summary->sample_type = ISOM_CODEC_TYPE_EC_3_AUDIO; + summary->max_au_length = eac3_imp->syncframe_count_in_au * EAC3_MAX_SYNCFRAME_LENGTH; + summary->aot = MP4A_AUDIO_OBJECT_TYPE_NULL; /* no effect */ + summary->sample_size = 16; /* no effect */ + summary->samples_in_frame = EAC3_MIN_SAMPLE_DURATION * 6; /* 256 (samples per audio block) * 6 (audio blocks) */ + summary->sbr_mode = MP4A_AAC_SBR_NOT_SPECIFIED; /* no effect */ + eac3_update_sample_rate( &summary->frequency, &info->dec3_param, &eac3_imp->current_fscod2 ); + eac3_update_channel_count( &summary->channels, &info->dec3_param ); + return summary; +} + +static int eac3_importer_probe( importer_t *importer ) +{ + eac3_importer_t *eac3_imp = create_eac3_importer( importer ); + if( !eac3_imp ) + return LSMASH_ERR_MEMORY_ALLOC; + lsmash_bits_t *bits = eac3_imp->info.bits; + lsmash_bs_t *bs = bits->bs; + bs->buffer.max_size = EAC3_MAX_SYNCFRAME_LENGTH; + importer->info = eac3_imp; + int err = eac3_importer_get_next_accessunit_internal( importer ); + if( err < 0 ) + goto fail; + lsmash_audio_summary_t *summary = eac3_create_summary( eac3_imp ); + if( !summary ) + { + err = LSMASH_ERR_NAMELESS; + goto fail; + } + if( importer->status != IMPORTER_EOF ) + importer->status = IMPORTER_OK; + eac3_imp->au_number = 0; + if( lsmash_list_add_entry( importer->summaries, summary ) < 0 ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + err = LSMASH_ERR_MEMORY_ALLOC; + goto fail; + } + return 0; +fail: + remove_eac3_importer( eac3_imp ); + importer->info = NULL; + return err; +} + +static uint32_t eac3_importer_get_last_delta( importer_t *importer, uint32_t track_number ) +{ + debug_if( !importer || !importer->info ) + return 0; + eac3_importer_t *eac3_imp = (eac3_importer_t *)importer->info; + if( !eac3_imp || track_number != 1 || importer->status != IMPORTER_EOF || eac3_imp->au_length ) + return 0; + return EAC3_MIN_SAMPLE_DURATION * eac3_imp->info.number_of_audio_blocks; +} + +const importer_functions eac3_importer = +{ + { "Enhanced AC-3", offsetof( importer_t, log_level ) }, + 1, + eac3_importer_probe, + eac3_importer_get_accessunit, + eac3_importer_get_last_delta, + eac3_importer_cleanup +}; diff --git a/3rdparty/l-smash/importer/adts_imp.c b/3rdparty/l-smash/importer/adts_imp.c new file mode 100644 index 000000000..699d35c68 --- /dev/null +++ b/3rdparty/l-smash/importer/adts_imp.c @@ -0,0 +1,524 @@ +/***************************************************************************** + * adts_imp.c + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Takashi Hirata + * Contributors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#define LSMASH_IMPORTER_INTERNAL +#include "importer.h" + +/*************************************************************************** + ADTS importer +***************************************************************************/ +#include "codecs/mp4a.h" + +#define MP4SYS_ADTS_FIXED_HEADER_LENGTH 4 /* this is partly a lie. actually 28 bits. */ +#define MP4SYS_ADTS_BASIC_HEADER_LENGTH 7 +#define MP4SYS_ADTS_MAX_FRAME_LENGTH (( 1 << 13 ) - 1) +#define MP4SYS_ADTS_MAX_RAW_DATA_BLOCKS 4 + +typedef struct +{ + uint16_t syncword; /* 12; */ + uint8_t ID; /* 1; */ + uint8_t layer; /* 2; */ + uint8_t protection_absent; /* 1; */ + uint8_t profile_ObjectType; /* 2; */ + uint8_t sampling_frequency_index; /* 4; */ +// uint8_t private_bit; /* 1; we don't care. */ + uint8_t channel_configuration; /* 3; */ +// uint8_t original_copy; /* 1; we don't care. */ +// uint8_t home; /* 1; we don't care. */ + +} mp4sys_adts_fixed_header_t; + +typedef struct +{ +// uint8_t copyright_identification_bit; /* 1; we don't care. */ +// uint8_t copyright_identification_start; /* 1; we don't care. */ + uint16_t frame_length; /* 13; */ +// uint16_t adts_buffer_fullness; /* 11; we don't care. */ + uint8_t number_of_raw_data_blocks_in_frame; /* 2; */ +// uint16_t adts_error_check; /* we don't support */ +// uint16_t raw_data_block_position[MP4SYS_ADTS_MAX_RAW_DATA_BLOCKS-1]; /* we don't use this directly, and... */ + uint16_t raw_data_block_size[MP4SYS_ADTS_MAX_RAW_DATA_BLOCKS]; /* use this instead of above. */ +// uint16_t adts_header_error_check; /* we don't support, actually crc_check within this */ +// uint16_t adts_raw_data_block_error_check[MP4SYS_ADTS_MAX_RAW_DATA_BLOCKS]; /* we don't support */ +} mp4sys_adts_variable_header_t; + +typedef struct +{ + unsigned int raw_data_block_idx; + mp4sys_adts_fixed_header_t header; + mp4sys_adts_variable_header_t variable_header; + uint32_t samples_in_frame; + uint32_t au_number; +} mp4sys_adts_importer_t; + +static void remove_mp4sys_adts_importer +( + mp4sys_adts_importer_t *adts_imp +) +{ + lsmash_free( adts_imp ); +} + +static mp4sys_adts_importer_t *create_mp4sys_adts_importer +( + importer_t *importer +) +{ + return (mp4sys_adts_importer_t *)lsmash_malloc_zero( sizeof(mp4sys_adts_importer_t) ); +} + +static void mp4sys_adts_cleanup( importer_t *importer ) +{ + debug_if( importer && importer->info ) + remove_mp4sys_adts_importer( importer->info ); +} + +static void mp4sys_adts_parse_fixed_header +( + uint8_t *buf, + mp4sys_adts_fixed_header_t *header +) +{ + /* FIXME: should we rewrite these code using bitstream reader? */ + header->syncword = (buf[0] << 4) | (buf[1] >> 4); + header->ID = (buf[1] >> 3) & 0x1; + header->layer = (buf[1] >> 1) & 0x3; + header->protection_absent = buf[1] & 0x1; + header->profile_ObjectType = buf[2] >> 6; + header->sampling_frequency_index = (buf[2] >> 2) & 0xF; +// header->private_bit = (buf[2] >> 1) & 0x1; /* we don't care currently. */ + header->channel_configuration = ((buf[2] << 2) | (buf[3] >> 6)) & 0x07; +// header->original_copy = (buf[3] >> 5) & 0x1; /* we don't care currently. */ +// header->home = (buf[3] >> 4) & 0x1; /* we don't care currently. */ +} + +static int mp4sys_adts_check_fixed_header +( + mp4sys_adts_fixed_header_t *header +) +{ + if( header->syncword != 0xFFF ) return LSMASH_ERR_INVALID_DATA; +// if( header->ID != 0x0 ) return LSMASH_ERR_NAMELESS; /* we don't care. */ + if( header->layer != 0x0 ) return LSMASH_ERR_INVALID_DATA; /* must be 0b00 for any type of AAC */ +// if( header->protection_absent != 0x1 ) return LSMASH_ERR_NAMELESS; /* we don't care. */ + if( header->profile_ObjectType != 0x1 ) return LSMASH_ERR_PATCH_WELCOME; /* FIXME: 0b00=Main, 0b01=LC, 0b10=SSR, 0b11=LTP. */ + if( header->sampling_frequency_index > 0xB ) return LSMASH_ERR_INVALID_DATA; /* must not be > 0xB. */ + if( header->channel_configuration == 0x0 ) return LSMASH_ERR_PATCH_WELCOME; /* FIXME: we do not support 0b000 currently. */ + if( header->profile_ObjectType == 0x3 && header->ID != 0x0 ) return LSMASH_ERR_INVALID_DATA; /* LTP is valid only if ID==0. */ + return 0; +} + +static int mp4sys_adts_parse_variable_header +( + lsmash_bs_t *bs, + uint8_t *buf, + unsigned int protection_absent, + mp4sys_adts_variable_header_t *header +) +{ + /* FIXME: should we rewrite these code using bitstream reader? */ +// header->copyright_identification_bit = (buf[3] >> 3) & 0x1; /* we don't care. */ +// header->copyright_identification_start = (buf[3] >> 2) & 0x1; /* we don't care. */ + header->frame_length = ((buf[3] << 11) | (buf[4] << 3) | (buf[5] >> 5)) & 0x1FFF ; +// header->adts_buffer_fullness = ((buf[5] << 6) | (buf[6] >> 2)) 0x7FF ; /* we don't care. */ + header->number_of_raw_data_blocks_in_frame = buf[6] & 0x3; + + if( header->frame_length <= MP4SYS_ADTS_BASIC_HEADER_LENGTH + 2 * (protection_absent == 0) ) + return LSMASH_ERR_INVALID_DATA; /* easy error check */ + + /* protection_absent and number_of_raw_data_blocks_in_frame relatives */ + + uint8_t buf2[2]; + unsigned int number_of_blocks = header->number_of_raw_data_blocks_in_frame; + if( number_of_blocks == 0 ) + { + header->raw_data_block_size[0] = header->frame_length - MP4SYS_ADTS_BASIC_HEADER_LENGTH; + /* skip adts_error_check() and subtract that from block_size */ + if( protection_absent == 0 ) + { + header->raw_data_block_size[0] -= 2; + if( lsmash_bs_get_bytes_ex( bs, 2, buf2 ) != 2 ) + return LSMASH_ERR_INVALID_DATA; + } + return 0; + } + + /* now we have multiple raw_data_block()s, so evaluate adts_header_error_check() */ + + uint16_t raw_data_block_position[MP4SYS_ADTS_MAX_RAW_DATA_BLOCKS]; + uint16_t first_offset = MP4SYS_ADTS_BASIC_HEADER_LENGTH; + if( protection_absent == 0 ) + { + /* process adts_header_error_check() */ + for( int i = 0 ; i < number_of_blocks ; i++ ) /* 1-based in the spec, but we use 0-based */ + { + if( lsmash_bs_get_bytes_ex( bs, 2, buf2 ) != 2 ) + return LSMASH_ERR_INVALID_DATA; + raw_data_block_position[i] = LSMASH_GET_BE16( buf2 ); + } + /* skip crc_check in adts_header_error_check(). + Or might be sizeof( adts_error_check() ) if we share with the case number_of_raw_data_blocks_in_frame == 0 */ + if( lsmash_bs_get_bytes_ex( bs, 2, buf2 ) != 2 ) + return LSMASH_ERR_INVALID_DATA; + first_offset += ( 2 * number_of_blocks ) + 2; /* according to above */ + } + else + { + /* + * NOTE: We never support the case where number_of_raw_data_blocks_in_frame != 0 && protection_absent != 0, + * because we have to parse the raw AAC bitstream itself to find boundaries of raw_data_block()s in this case. + * Which is to say, that braindamaged spec requires us (mp4 muxer) to decode AAC once to split frames. + * L-SMASH is NOT AAC DECODER, so that we've just given up for this case. + * This is ISO/IEC 13818-7's sin which defines ADTS format originally. + */ + return LSMASH_ERR_NAMELESS; + } + + /* convert raw_data_block_position --> raw_data_block_size */ + + /* do conversion for first */ + header->raw_data_block_size[0] = raw_data_block_position[0] - first_offset; + /* set dummy offset to tail for loop, do coversion for rest. */ + raw_data_block_position[number_of_blocks] = header->frame_length; + for( int i = 1 ; i <= number_of_blocks ; i++ ) + header->raw_data_block_size[i] = raw_data_block_position[i] - raw_data_block_position[i-1]; + + /* adjustment for adts_raw_data_block_error_check() */ + if( protection_absent == 0 && number_of_blocks != 0 ) + for( int i = 0 ; i <= number_of_blocks ; i++ ) + header->raw_data_block_size[i] -= 2; + + return 0; +} + +static int mp4sys_adts_parse_headers +( + lsmash_bs_t *bs, + uint8_t *buf, + mp4sys_adts_fixed_header_t *header, + mp4sys_adts_variable_header_t *variable_header +) +{ + mp4sys_adts_parse_fixed_header( buf, header ); + int err = mp4sys_adts_check_fixed_header( header ); + if( err < 0 ) + return err; + /* get payload length & skip extra(crc) header */ + return mp4sys_adts_parse_variable_header( bs, buf, header->protection_absent, variable_header ); +} + +static lsmash_audio_summary_t *mp4sys_adts_create_summary +( + mp4sys_adts_fixed_header_t *header +) +{ + lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_create_summary( LSMASH_SUMMARY_TYPE_AUDIO ); + if( !summary ) + return NULL; + summary->sample_type = ISOM_CODEC_TYPE_MP4A_AUDIO; + summary->max_au_length = MP4SYS_ADTS_MAX_FRAME_LENGTH; + summary->frequency = mp4a_sampling_frequency_table[header->sampling_frequency_index][1]; + summary->channels = header->channel_configuration + ( header->channel_configuration == 0x07 ); /* 0x07 means 7.1ch */ + summary->sample_size = 16; + summary->samples_in_frame = 1024; + summary->aot = header->profile_ObjectType + MP4A_AUDIO_OBJECT_TYPE_AAC_MAIN; + summary->sbr_mode = MP4A_AAC_SBR_NOT_SPECIFIED; +#if 0 /* FIXME: This is very unstable. Many players crash with this. */ + if( header->ID != 0 ) + { + /* + * NOTE: This ADTS seems of ISO/IEC 13818-7 (MPEG-2 AAC). + * It has special object_type_indications, depending on it's profile (Legacy Interface). + * If ADIF header is not available, it should not have decoder specific information, so AudioObjectType neither. + * see ISO/IEC 14496-1, DecoderSpecificInfo and 14496-3 Subpart 9: MPEG-1/2 Audio in MPEG-4. + */ + summary->object_type_indication = header->profile_ObjectType + MP4SYS_OBJECT_TYPE_Audio_ISO_13818_7_Main_Profile; + summary->aot = MP4A_AUDIO_OBJECT_TYPE_NULL; + summary->asc = NULL; + summary->asc_length = 0; + // summary->sbr_mode = MP4A_AAC_SBR_NONE; /* MPEG-2 AAC should not be HE-AAC, but we forgive them. */ + return summary; + } +#endif + uint32_t data_length; + uint8_t *data = mp4a_export_AudioSpecificConfig( header->profile_ObjectType + MP4A_AUDIO_OBJECT_TYPE_AAC_MAIN, + summary->frequency, summary->channels, summary->sbr_mode, + NULL, 0, &data_length ); + if( !data ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return NULL; + } + lsmash_codec_specific_t *specific = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_MP4SYS_DECODER_CONFIG, + LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !specific ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + lsmash_free( data ); + return NULL; + } + lsmash_mp4sys_decoder_parameters_t *param = (lsmash_mp4sys_decoder_parameters_t *)specific->data.structured; + param->objectTypeIndication = MP4SYS_OBJECT_TYPE_Audio_ISO_14496_3; + param->streamType = MP4SYS_STREAM_TYPE_AudioStream; + if( lsmash_set_mp4sys_decoder_specific_info( param, data, data_length ) < 0 ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + lsmash_destroy_codec_specific_data( specific ); + lsmash_free( data ); + return NULL; + } + lsmash_free( data ); + if( lsmash_list_add_entry( &summary->opaque->list, specific ) < 0 ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + lsmash_destroy_codec_specific_data( specific ); + return NULL; + } + return summary; +} + +static int mp4sys_adts_get_accessunit +( + importer_t *importer, + uint32_t track_number, + lsmash_sample_t **p_sample +) +{ + if( !importer->info ) + return LSMASH_ERR_NAMELESS; + if( track_number != 1 ) + return LSMASH_ERR_FUNCTION_PARAM; + mp4sys_adts_importer_t *adts_imp = (mp4sys_adts_importer_t *)importer->info; + importer_status current_status = importer->status; + uint16_t raw_data_block_size = adts_imp->variable_header.raw_data_block_size[ adts_imp->raw_data_block_idx ]; + if( current_status == IMPORTER_ERROR ) + return LSMASH_ERR_NAMELESS; + if( current_status == IMPORTER_EOF ) + return IMPORTER_EOF; + if( current_status == IMPORTER_CHANGE ) + { + lsmash_entry_t *entry = lsmash_list_get_entry( importer->summaries, track_number ); + if( !entry || !entry->data ) + return LSMASH_ERR_NAMELESS; + lsmash_audio_summary_t *summary = mp4sys_adts_create_summary( &adts_imp->header ); + if( !summary ) + return LSMASH_ERR_NAMELESS; + lsmash_cleanup_summary( entry->data ); + entry->data = summary; + adts_imp->samples_in_frame = summary->samples_in_frame; + } + lsmash_bs_t *bs = importer->bs; + /* read a raw_data_block(), typically == payload of a ADTS frame */ + lsmash_sample_t *sample = lsmash_create_sample( raw_data_block_size ); + if( !sample ) + return LSMASH_ERR_MEMORY_ALLOC; + *p_sample = sample; + if( lsmash_bs_get_bytes_ex( bs, raw_data_block_size, sample->data ) != raw_data_block_size ) + { + importer->status = IMPORTER_ERROR; + return LSMASH_ERR_INVALID_DATA; + } + sample->length = raw_data_block_size; + sample->dts = adts_imp->au_number ++ * adts_imp->samples_in_frame; + sample->cts = sample->dts; + sample->prop.ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; + sample->prop.pre_roll.distance = 1; /* MDCT */ + + /* now we succeeded to read current frame, so "return" takes 0 always below. */ + + /* skip adts_raw_data_block_error_check() */ + if( adts_imp->header.protection_absent == 0 + && adts_imp->variable_header.number_of_raw_data_blocks_in_frame != 0 + && lsmash_bs_get_bytes_ex( bs, 2, sample->data ) != 2 ) + { + importer->status = IMPORTER_ERROR; + return 0; + } + /* current adts_frame() has any more raw_data_block()? */ + if( adts_imp->raw_data_block_idx < adts_imp->variable_header.number_of_raw_data_blocks_in_frame ) + { + adts_imp->raw_data_block_idx++; + importer->status = IMPORTER_OK; + return 0; + } + adts_imp->raw_data_block_idx = 0; + + /* preparation for next frame */ + + uint8_t buf[MP4SYS_ADTS_MAX_FRAME_LENGTH]; + int64_t ret = lsmash_bs_get_bytes_ex( bs, MP4SYS_ADTS_BASIC_HEADER_LENGTH, buf ); + if( ret == 0 ) + { + importer->status = IMPORTER_EOF; + return 0; + } + if( ret != MP4SYS_ADTS_BASIC_HEADER_LENGTH ) + { + importer->status = IMPORTER_ERROR; + return 0; + } + /* + * NOTE: About the spec of ADTS headers. + * By the spec definition, ADTS's fixed header cannot change in the middle of stream. + * But spec of MP4 allows that a stream(track) changes its properties in the middle of it. + */ + /* + * NOTE: About detailed check for ADTS headers. + * We do not ommit detailed check for fixed header by simply testing bits' identification, + * because there're some flags which does not matter to audio_summary (so AudioSpecificConfig neither) + * so that we can take them as no change and never make new ObjectDescriptor. + * I know that can be done with/by bitmask also and that should be fast, but L-SMASH project prefers + * even foolishly straightforward way. + */ + /* + * NOTE: About our reading algorithm for ADTS. + * It's rather simple if we retrieve payload of ADTS (i.e. raw AAC frame) at the same time to + * retrieve headers. + * But then we have to cache and memcpy every frame so that it requires more clocks and memory. + * To avoid them, I adopted this separate retrieving method. + */ + mp4sys_adts_fixed_header_t header = { 0 }; + mp4sys_adts_variable_header_t variable_header = { 0 }; + if( mp4sys_adts_parse_headers( bs, buf, &header, &variable_header ) < 0 ) + { + importer->status = IMPORTER_ERROR; + return 0; + } + adts_imp->variable_header = variable_header; + /* + * NOTE: About our support for change(s) of properties within an ADTS stream. + * We have to modify these conditions depending on the features we support. + * For example, if we support copyright_identification_* in any way within any feature + * defined by/in any specs, such as ISO/IEC 14496-1 (MPEG-4 Systems), like... + * "8.3 Intellectual Property Management and Protection (IPMP)", or something similar, + * we have to check copyright_identification_* and treat them in audio_summary. + * "Change(s)" may result in MP4SYS_IMPORTER_ERROR or MP4SYS_IMPORTER_CHANGE + * depending on the features we support, and what the spec allows. + * Sometimes the "change(s)" can be allowed, while sometimes they're forbidden. + */ + /* currently UNsupported "change(s)". */ + if( adts_imp->header.profile_ObjectType != header.profile_ObjectType /* currently unsupported. */ + || adts_imp->header.ID != header.ID /* In strict, this means change of object_type_indication. */ + || adts_imp->header.sampling_frequency_index != header.sampling_frequency_index ) /* This may change timebase. */ + { + importer->status = IMPORTER_ERROR; + return 0; + } + /* currently supported "change(s)". */ + if( adts_imp->header.channel_configuration != header.channel_configuration ) + { + /* + * FIXME: About conditions of VALID "change(s)". + * we have to check whether any "change(s)" affect to audioProfileLevelIndication + * in InitialObjectDescriptor (MP4_IOD) or not. + * If another type or upper level is required by the change(s), that is forbidden. + * Because ObjectDescriptor does not have audioProfileLevelIndication, + * so that it seems impossible to change audioProfileLevelIndication in the middle of the stream. + * Note also any other properties, such as AudioObjectType, object_type_indication. + */ + /* + * NOTE: updating summary must be done on next call, + * because user may retrieve summary right after this function call of this time, + * and that should be of current, before change, one. + */ + adts_imp->header = header; + importer->status = IMPORTER_CHANGE; + return 0; + } + /* no change which matters to mp4 muxing was found */ + importer->status = IMPORTER_OK; + return 0; +} + +/* returns 0 if it seems adts. */ +static int mp4sys_adts_probe +( + importer_t *importer +) +{ + mp4sys_adts_importer_t *adts_imp = create_mp4sys_adts_importer( importer ); + if( !adts_imp ) + return LSMASH_ERR_MEMORY_ALLOC; + int err; + uint8_t buf[MP4SYS_ADTS_MAX_FRAME_LENGTH]; + if( lsmash_bs_get_bytes_ex( importer->bs, MP4SYS_ADTS_BASIC_HEADER_LENGTH, buf ) != MP4SYS_ADTS_BASIC_HEADER_LENGTH ) + { + err = LSMASH_ERR_INVALID_DATA; + goto fail; + } + mp4sys_adts_fixed_header_t header = { 0 }; + mp4sys_adts_variable_header_t variable_header = { 0 }; + if( (err = mp4sys_adts_parse_headers( importer->bs, buf, &header, &variable_header )) < 0 ) + goto fail; + /* now the stream seems valid ADTS */ + lsmash_audio_summary_t *summary = mp4sys_adts_create_summary( &header ); + if( !summary ) + { + err = LSMASH_ERR_NAMELESS; + goto fail; + } + /* importer status */ + if( lsmash_list_add_entry( importer->summaries, summary ) < 0 ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + err = LSMASH_ERR_MEMORY_ALLOC; + goto fail; + } + adts_imp->raw_data_block_idx = 0; + adts_imp->header = header; + adts_imp->variable_header = variable_header; + adts_imp->samples_in_frame = summary->samples_in_frame; + importer->info = adts_imp; + importer->status = IMPORTER_OK; + return 0; +fail: + remove_mp4sys_adts_importer( adts_imp ); + return err; +} + +static uint32_t mp4sys_adts_get_last_delta +( + importer_t *importer, + uint32_t track_number +) +{ + debug_if( !importer || !importer->info ) + return 0; + mp4sys_adts_importer_t *adts_imp = (mp4sys_adts_importer_t *)importer->info; + if( !adts_imp || track_number != 1 || importer->status != IMPORTER_EOF ) + return 0; + return adts_imp->samples_in_frame; +} + +const importer_functions mp4sys_adts_importer = +{ + { "adts" }, + 1, + mp4sys_adts_probe, + mp4sys_adts_get_accessunit, + mp4sys_adts_get_last_delta, + mp4sys_adts_cleanup +}; diff --git a/3rdparty/l-smash/importer/als_imp.c b/3rdparty/l-smash/importer/als_imp.c new file mode 100644 index 000000000..f2705c20a --- /dev/null +++ b/3rdparty/l-smash/importer/als_imp.c @@ -0,0 +1,383 @@ +/***************************************************************************** + * als_imp.c + ***************************************************************************** + * Copyright (C) 2011-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include + +#define LSMASH_IMPORTER_INTERNAL +#include "importer.h" + +/*************************************************************************** + MPEG-4 ALS importer + ISO/IEC 14496-3 2009 Fourth edition +***************************************************************************/ +#include "codecs/mp4a.h" + +#define ALSSC_TWELVE_LENGTH 22 + +typedef struct +{ + uint32_t size; + uint32_t samp_freq; + uint32_t samples; + uint32_t channels; + uint16_t frame_length; + uint8_t resolution; + uint8_t random_access; + uint8_t ra_flag; + uint32_t access_unit_size; + uint32_t number_of_ra_units; + uint32_t *ra_unit_size; + uint8_t *sc_data; + size_t alloc; +} als_specific_config_t; + +typedef struct +{ + als_specific_config_t alssc; + uint32_t samples_in_frame; + uint32_t au_number; +} mp4a_als_importer_t; + +static void remove_mp4a_als_importer( mp4a_als_importer_t *als_imp ) +{ + lsmash_free( als_imp->alssc.ra_unit_size ); + lsmash_free( als_imp->alssc.sc_data ); + lsmash_free( als_imp ); +} + +static void mp4a_als_importer_cleanup( importer_t *importer ) +{ + debug_if( importer && importer->info ) + remove_mp4a_als_importer( importer->info ); +} + +static mp4a_als_importer_t *create_mp4a_als_importer( importer_t *importer ) +{ + return (mp4a_als_importer_t *)lsmash_malloc_zero( sizeof(mp4a_als_importer_t) ); +} + +static void als_copy_from_buffer( als_specific_config_t *alssc, lsmash_bs_t *bs, uint64_t size ) +{ + if( alssc->alloc < size ) + { + size_t alloc = alssc->alloc ? (alssc->alloc << 1) : (1 << 10); + uint8_t *temp = lsmash_realloc( alssc->sc_data, alloc ); + if( !temp ) + return; + alssc->sc_data = temp; + alssc->alloc = alloc; + } + memcpy( alssc->sc_data + alssc->size, lsmash_bs_get_buffer_data( bs ), size ); + alssc->size += size; + lsmash_bs_read_seek( bs, size, SEEK_CUR ); +} + +static int als_parse_specific_config( lsmash_bs_t *bs, mp4a_als_importer_t *als_imp ) +{ + /* Check ALS identifier( = 0x414C5300). */ + if( 0x414C5300 != lsmash_bs_show_be32( bs, 0 ) ) + return LSMASH_ERR_INVALID_DATA; + als_specific_config_t *alssc = &als_imp->alssc; + alssc->samp_freq = lsmash_bs_show_be32( bs, 4 ); + alssc->samples = lsmash_bs_show_be32( bs, 8 ); + if( alssc->samples == 0xffffffff ) + return LSMASH_ERR_PATCH_WELCOME; /* We don't support this case. */ + alssc->channels = lsmash_bs_show_be16( bs, 12 ); + alssc->resolution = (lsmash_bs_show_byte( bs, 14 ) & 0x1c) >> 2; + if( alssc->resolution > 3 ) + return LSMASH_ERR_NAMELESS; /* reserved */ + alssc->frame_length = lsmash_bs_show_be16( bs, 15 ); + alssc->random_access = lsmash_bs_show_byte( bs, 17 ); + alssc->ra_flag = (lsmash_bs_show_byte( bs, 18 ) & 0xc0) >> 6; + if( alssc->ra_flag == 0 ) + return LSMASH_ERR_PATCH_WELCOME; /* We don't support this case. */ +#if 0 + if( alssc->samples == 0xffffffff && alssc->ra_flag == 2 ) + return LSMASH_ERR_NAMELESS; +#endif + uint8_t temp8 = lsmash_bs_show_byte( bs, 20 ); + int chan_sort = !!(temp8 & 0x1); + if( alssc->channels == 0 ) + { + if( temp8 & 0x8 ) + return LSMASH_ERR_INVALID_DATA; /* If channels = 0 (mono), joint_stereo = 0. */ + else if( temp8 & 0x4 ) + return LSMASH_ERR_INVALID_DATA; /* If channels = 0 (mono), mc_coding = 0. */ + else if( chan_sort ) + return LSMASH_ERR_INVALID_DATA; /* If channels = 0 (mono), chan_sort = 0. */ + } + int chan_config = !!(temp8 & 0x2); + temp8 = lsmash_bs_show_byte( bs, 21 ); + int crc_enabled = !!(temp8 & 0x80); + int aux_data_enabled = !!(temp8 & 0x1); + als_copy_from_buffer( alssc, bs, ALSSC_TWELVE_LENGTH ); + if( chan_config ) + { + /* chan_config_info */ + lsmash_bs_read( bs, 2 ); + als_copy_from_buffer( alssc, bs, 2 ); + } + if( chan_sort ) + { + uint32_t ChBits = lsmash_ceil_log2( alssc->channels + 1 ); + uint32_t chan_pos_length = (alssc->channels + 1) * ChBits; + chan_pos_length = ((uint64_t)chan_pos_length + 7) / 8; /* byte_align */ + lsmash_bs_read( bs, chan_pos_length ); + als_copy_from_buffer( alssc, bs, chan_pos_length ); + } + /* orig_header, orig_trailer and crc. */ + { + uint32_t header_size = lsmash_bs_show_be32( bs, 0 ); + uint32_t trailer_size = lsmash_bs_show_be32( bs, 4 ); + als_copy_from_buffer( alssc, bs, 8 ); + if( header_size != 0xffffffff ) + { + lsmash_bs_read( bs, header_size ); + als_copy_from_buffer( alssc, bs, header_size ); + } + if( trailer_size != 0xffffffff ) + { + lsmash_bs_read( bs, trailer_size ); + als_copy_from_buffer( alssc, bs, trailer_size ); + } + if( crc_enabled ) + { + lsmash_bs_read( bs, 4 ); + als_copy_from_buffer( alssc, bs, 4 ); + } + } + /* Random access units */ + { + uint32_t number_of_frames = ((alssc->samples + alssc->frame_length) / (alssc->frame_length + 1)); + if( alssc->random_access != 0 ) + alssc->number_of_ra_units = ((uint64_t)number_of_frames + alssc->random_access - 1) / alssc->random_access; + else + alssc->number_of_ra_units = 0; + if( alssc->ra_flag == 2 && alssc->random_access != 0 ) + { + /* We don't copy all ra_unit_size into alssc->sc_data. */ + int64_t read_size = (int64_t)alssc->number_of_ra_units * 4; + int64_t end_offset = lsmash_bs_get_stream_pos( bs ) + read_size; + alssc->ra_unit_size = lsmash_malloc( read_size ); + if( !alssc->ra_unit_size ) + return LSMASH_ERR_MEMORY_ALLOC; + uint32_t max_ra_unit_size = 0; + for( uint32_t i = 0; i < alssc->number_of_ra_units; i++ ) + { + alssc->ra_unit_size[i] = lsmash_bs_get_be32( bs ); + max_ra_unit_size = LSMASH_MAX( max_ra_unit_size, alssc->ra_unit_size[i] ); + } + lsmash_bs_read_seek( bs, end_offset, SEEK_SET ); + } + else + alssc->ra_unit_size = NULL; + } + /* auxiliary data */ + if( aux_data_enabled ) + { + uint32_t aux_size = lsmash_bs_show_be32( bs, 0 ); + als_copy_from_buffer( alssc, bs, 4 ); + if( aux_size && aux_size != 0xffffffff ) + { + lsmash_bs_read( bs, aux_size ); + als_copy_from_buffer( alssc, bs, aux_size ); + } + } + /* Set 0 to ra_flag. We will remove ra_unit_size in each access unit. */ + alssc->sc_data[18] &= 0x3f; + return 0; +} + +static int mp4a_als_importer_get_accessunit( importer_t *importer, uint32_t track_number, lsmash_sample_t **p_sample ) +{ + if( !importer->info ) + return LSMASH_ERR_NAMELESS; + if( track_number != 1 ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_list_get_entry_data( importer->summaries, track_number ); + if( !summary ) + return LSMASH_ERR_NAMELESS; + mp4a_als_importer_t *als_imp = (mp4a_als_importer_t *)importer->info; + importer_status current_status = importer->status; + if( current_status == IMPORTER_EOF ) + return IMPORTER_EOF; + lsmash_bs_t *bs = importer->bs; + als_specific_config_t *alssc = &als_imp->alssc; + if( alssc->number_of_ra_units == 0 ) + { + lsmash_sample_t *sample = lsmash_create_sample( alssc->access_unit_size ); + if( !sample ) + return LSMASH_ERR_MEMORY_ALLOC; + *p_sample = sample; + memcpy( sample->data, lsmash_bs_get_buffer_data( bs ), alssc->access_unit_size ); + sample->length = alssc->access_unit_size; + sample->cts = 0; + sample->dts = 0; + sample->prop.ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; + importer->status = IMPORTER_EOF; + return 0; + } + uint32_t au_length; + if( alssc->ra_flag == 2 ) + au_length = alssc->ra_unit_size[ als_imp->au_number ]; + else /* if( alssc->ra_flag == 1 ) */ + /* We don't export ra_unit_size into a sample. */ + au_length = lsmash_bs_get_be32( bs ); + lsmash_sample_t *sample = lsmash_create_sample( au_length ); + if( !sample ) + return LSMASH_ERR_MEMORY_ALLOC; + *p_sample = sample; + if( lsmash_bs_get_bytes_ex( bs, au_length, sample->data ) != au_length ) + { + lsmash_log( importer, LSMASH_LOG_WARNING, "failed to read an access unit.\n" ); + importer->status = IMPORTER_ERROR; + return LSMASH_ERR_INVALID_DATA; + } + sample->length = au_length; + sample->dts = als_imp->au_number ++ * als_imp->samples_in_frame; + sample->cts = sample->dts; + sample->prop.ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; + if( als_imp->au_number == alssc->number_of_ra_units ) + importer->status = IMPORTER_EOF; + return 0; +} + +#undef CHECK_UPDATE + +static lsmash_audio_summary_t *als_create_summary( lsmash_bs_t *bs, als_specific_config_t *alssc ) +{ + lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_create_summary( LSMASH_SUMMARY_TYPE_AUDIO ); + if( !summary ) + return NULL; + summary->sample_type = ISOM_CODEC_TYPE_MP4A_AUDIO; + summary->aot = MP4A_AUDIO_OBJECT_TYPE_ALS; + summary->frequency = alssc->samp_freq; + summary->channels = alssc->channels + 1; + summary->sample_size = (alssc->resolution + 1) * 8; + summary->sbr_mode = MP4A_AAC_SBR_NOT_SPECIFIED; /* no effect */ + if( alssc->random_access != 0 ) + { + summary->samples_in_frame = (alssc->frame_length + 1) * alssc->random_access; + summary->max_au_length = summary->channels * (summary->sample_size / 8) * summary->samples_in_frame; + } + else + { + /* Read the remainder of overall stream as an access unit. */ + alssc->access_unit_size = lsmash_bs_get_remaining_buffer_size( bs ); + while( !bs->eof ) + { + if( lsmash_bs_read( bs, bs->buffer.max_size ) < 0 ) + goto fail; + alssc->access_unit_size = lsmash_bs_get_remaining_buffer_size( bs ); + } + summary->max_au_length = alssc->access_unit_size; + summary->samples_in_frame = 0; /* hack for mp4sys_als_importer_get_last_delta() */ + } + uint32_t data_length; + uint8_t *data = mp4a_export_AudioSpecificConfig( MP4A_AUDIO_OBJECT_TYPE_ALS, + summary->frequency, summary->channels, summary->sbr_mode, + alssc->sc_data, alssc->size, &data_length ); + if( !data ) + goto fail; + lsmash_codec_specific_t *specific = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_MP4SYS_DECODER_CONFIG, + LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !specific ) + { + lsmash_free( data ); + goto fail; + } + lsmash_mp4sys_decoder_parameters_t *param = (lsmash_mp4sys_decoder_parameters_t *)specific->data.structured; + param->objectTypeIndication = MP4SYS_OBJECT_TYPE_Audio_ISO_14496_3; + param->streamType = MP4SYS_STREAM_TYPE_AudioStream; + if( lsmash_set_mp4sys_decoder_specific_info( param, data, data_length ) < 0 ) + { + lsmash_destroy_codec_specific_data( specific ); + lsmash_free( data ); + goto fail; + } + lsmash_free( data ); + if( lsmash_list_add_entry( &summary->opaque->list, specific ) < 0 ) + { + lsmash_destroy_codec_specific_data( specific ); + goto fail; + } + return summary; +fail: + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return NULL; +} + +static int mp4a_als_importer_probe( importer_t *importer ) +{ + mp4a_als_importer_t *als_imp = create_mp4a_als_importer( importer ); + if( !als_imp ) + return LSMASH_ERR_MEMORY_ALLOC; + /* Parse ALS specific configuration. */ + int err = als_parse_specific_config( importer->bs, als_imp ); + if( err < 0 ) + goto fail; + lsmash_audio_summary_t *summary = als_create_summary( importer->bs, &als_imp->alssc ); + if( !summary ) + { + err = LSMASH_ERR_NAMELESS; + goto fail; + } + /* importer status */ + als_imp->samples_in_frame = summary->samples_in_frame; + if( lsmash_list_add_entry( importer->summaries, summary ) < 0 ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + err = LSMASH_ERR_MEMORY_ALLOC; + goto fail; + } + importer->info = als_imp; + importer->status = IMPORTER_OK; + return 0; +fail: + remove_mp4a_als_importer( als_imp ); + return err; +} + +static uint32_t mp4a_als_importer_get_last_delta( importer_t *importer, uint32_t track_number ) +{ + debug_if( !importer || !importer->info ) + return 0; + mp4a_als_importer_t *als_imp = (mp4a_als_importer_t *)importer->info; + if( !als_imp || track_number != 1 || importer->status != IMPORTER_EOF ) + return 0; + als_specific_config_t *alssc = &als_imp->alssc; + /* If alssc->number_of_ra_units == 0, then the last sample duration is just alssc->samples + * since als_create_summary sets 0 to summary->samples_in_frame i.e. als_imp->samples_in_frame. */ + return alssc->samples - (alssc->number_of_ra_units - 1) * als_imp->samples_in_frame; +} + +const importer_functions mp4a_als_importer = +{ + { "MPEG-4 ALS", offsetof( importer_t, log_level ) }, + 1, + mp4a_als_importer_probe, + mp4a_als_importer_get_accessunit, + mp4a_als_importer_get_last_delta, + mp4a_als_importer_cleanup +}; diff --git a/3rdparty/l-smash/importer/amr_imp.c b/3rdparty/l-smash/importer/amr_imp.c new file mode 100644 index 000000000..7a1dca152 --- /dev/null +++ b/3rdparty/l-smash/importer/amr_imp.c @@ -0,0 +1,288 @@ +/***************************************************************************** + * amr_imp.c + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Takashi Hirata + * Contributors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include + +#define LSMASH_IMPORTER_INTERNAL +#include "importer.h" + +/*************************************************************************** + AMR-NB/WB storage format importer + 3GPP TS 26.101 V11.0.0 (2012-9) + 3GPP TS 26.201 V11.0.0 (2012-9) + 3GPP TS 26.244 V12.3.0 (2014-03) + http://www.ietf.org/rfc/rfc3267.txt (Obsoleted) + http://www.ietf.org/rfc/rfc4867.txt +***************************************************************************/ +typedef struct +{ + int wb; /* 0: AMR-NB, 1: AMR-WB */ + uint32_t samples_in_frame; + uint32_t au_number; +} amr_importer_t; + +static void remove_amr_importer +( + amr_importer_t *amr_imp +) +{ + lsmash_free( amr_imp ); +} + +static amr_importer_t *create_amr_importer +( + importer_t *importer +) +{ + return (amr_importer_t *)lsmash_malloc_zero( sizeof(amr_importer_t) ); +} + +static void amr_cleanup +( + importer_t *importer +) +{ + debug_if( importer && importer->info ) + remove_amr_importer( importer->info ); +} + +static int amr_get_accessunit +( + importer_t *importer, + uint32_t track_number, + lsmash_sample_t **p_sample +) +{ + if( !importer->info ) + return LSMASH_ERR_NAMELESS; + if( track_number != 1 ) + return LSMASH_ERR_FUNCTION_PARAM; + amr_importer_t *amr_imp = (amr_importer_t *)importer->info; + lsmash_bs_t *bs = importer->bs; + if( importer->status == IMPORTER_EOF || lsmash_bs_is_end( bs, 0 ) ) + { + /* EOF */ + importer->status = IMPORTER_EOF; + return IMPORTER_EOF; + } + /* Each speech frame consists of one speech frame header and one speech data. + * At the end of each speech data, octet alignment if needed. + * Speech frame header + * 0 1 2 3 4 5 6 7 + * +-+-------+-+-+-+ + * |P| FT |Q|P|P| + * +-+-------+-+-+-+ + * FT: Frame type index + * Q : Frame quality indicator + * P : Must be set to 0 + * FT= 9, 10 and 11 for AMR-NB shall not be used in the file format. + * FT=12, 13 and 14 for AMR-NB are not defined yet in the file format. + * FT=10, 11, 12 and 13 for AMR-WB are not defined yet in the file format. + * FT determines the size of the speech frame starting with it. + */ + uint8_t FT = (lsmash_bs_show_byte( bs, 0 ) >> 3) & 0x0F; + const int frame_size[2][16] = + { + { 13, 14, 16, 18, 20, 21, 27, 32, 6, -1, -1, -1, 0, 0, 0, 1 }, + { 18, 24, 33, 37, 41, 47, 51, 59, 61, 6, 0, 0, 0, 0, 1, 1 } + }; + int read_size = frame_size[ amr_imp->wb ][FT]; + if( read_size <= 0 ) + { + lsmash_log( importer, LSMASH_LOG_ERROR, "an %s speech frame is detected.\n", read_size < 0 ? "invalid" : "unknown" ); + importer->status = IMPORTER_ERROR; + return read_size < 0 ? LSMASH_ERR_INVALID_DATA : LSMASH_ERR_NAMELESS; + } + lsmash_sample_t *sample = lsmash_create_sample( read_size ); + if( !sample ) + return LSMASH_ERR_MEMORY_ALLOC; + *p_sample = sample; + if( lsmash_bs_get_bytes_ex( bs, read_size, sample->data ) != read_size ) + { + lsmash_log( importer, LSMASH_LOG_WARNING, "the stream is truncated at the end.\n" ); + importer->status = IMPORTER_EOF; + return LSMASH_ERR_INVALID_DATA; + } + sample->length = read_size; + sample->dts = amr_imp->au_number ++ * amr_imp->samples_in_frame; + sample->cts = sample->dts; + sample->prop.ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; + return 0; +} + +static int amr_check_magic_number +( + lsmash_bs_t *bs +) +{ +#define AMR_STORAGE_MAGIC_LENGTH 6 +#define AMR_AMRWB_EX_MAGIC_LENGTH 3 + /* Check the magic number for single-channel AMR-NB/AMR-WB files. + * For AMR-NB, "#!AMR\n" (or 0x2321414d520a in hexadecimal). + * For AMR-WB, "#!AMR-WB\n" (or 0x2321414d522d57420a in hexadecimal). + * Note that AMR-NB and AMR-WB data is stored in the 3GPP/3GPP2 file format according to + * the AMR-NB and AMR-WB storage format for single channel header without the AMR magic numbers. */ + uint8_t buf[AMR_STORAGE_MAGIC_LENGTH]; + if( lsmash_bs_get_bytes_ex( bs, AMR_STORAGE_MAGIC_LENGTH, buf ) != AMR_STORAGE_MAGIC_LENGTH + || memcmp( buf, "#!AMR", AMR_STORAGE_MAGIC_LENGTH - 1 ) ) + return LSMASH_ERR_INVALID_DATA; + if( buf[AMR_STORAGE_MAGIC_LENGTH - 1] == '\n' ) + /* single-channel AMR-NB file */ + return 0; + if( buf[AMR_STORAGE_MAGIC_LENGTH - 1] != '-' + || lsmash_bs_get_bytes_ex( bs, AMR_AMRWB_EX_MAGIC_LENGTH, buf ) != AMR_AMRWB_EX_MAGIC_LENGTH + || memcmp( buf, "WB\n", AMR_AMRWB_EX_MAGIC_LENGTH ) ) + return LSMASH_ERR_INVALID_DATA; + /* single-channel AMR-WB file */ + return 1; +#undef AMR_STORAGE_MAGIC_LENGTH +#undef AMR_AMRWB_EX_MAGIC_LENGTH +} + +static int amr_create_damr +( + lsmash_audio_summary_t *summary, + int wb +) +{ +#define AMR_DAMR_LENGTH 17 + lsmash_bs_t *bs = lsmash_bs_create(); + if( !bs ) + return LSMASH_ERR_MEMORY_ALLOC; + lsmash_bs_put_be32( bs, AMR_DAMR_LENGTH ); + lsmash_bs_put_be32( bs, ISOM_BOX_TYPE_DAMR.fourcc ); + /* NOTE: These are specific to each codec vendor, but we're surely not a vendor. + * Using dummy data. */ + lsmash_bs_put_be32( bs, 0x20202020 ); /* vendor */ + lsmash_bs_put_byte( bs, 0 ); /* decoder_version */ + /* NOTE: Using safe value for these settings, maybe sub-optimal. */ + lsmash_bs_put_be16( bs, wb ? 0xC3FF : 0x81FF ); /* mode_set, represents for all possibly existing and supported frame-types. */ + lsmash_bs_put_byte( bs, 1 ); /* mode_change_period */ + lsmash_bs_put_byte( bs, 1 ); /* frames_per_sample */ + lsmash_codec_specific_t *cs = lsmash_malloc_zero( sizeof(lsmash_codec_specific_t) ); + if( !cs ) + { + lsmash_bs_cleanup( bs ); + return LSMASH_ERR_MEMORY_ALLOC; + } + cs->type = LSMASH_CODEC_SPECIFIC_DATA_TYPE_UNKNOWN; + cs->format = LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED; + cs->destruct = (lsmash_codec_specific_destructor_t)lsmash_free; + cs->data.unstructured = lsmash_bs_export_data( bs, &cs->size ); + cs->size = AMR_DAMR_LENGTH; + lsmash_bs_cleanup( bs ); + if( !cs->data.unstructured + || lsmash_list_add_entry( &summary->opaque->list, cs ) < 0 ) + { + lsmash_destroy_codec_specific_data( cs ); + return LSMASH_ERR_MEMORY_ALLOC; + } + return 0; +#undef AMR_DAMR_LENGTH +} + +static lsmash_audio_summary_t *amr_create_summary +( + importer_t *importer, + int wb +) +{ + /* Establish an audio summary for AMR-NB or AMR-WB stream. */ + lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_create_summary( LSMASH_SUMMARY_TYPE_AUDIO ); + if( !summary ) + return NULL; + summary->sample_type = wb ? ISOM_CODEC_TYPE_SAWB_AUDIO : ISOM_CODEC_TYPE_SAMR_AUDIO; + summary->max_au_length = wb ? 61 : 32; + summary->aot = MP4A_AUDIO_OBJECT_TYPE_NULL; /* no effect */ + summary->frequency = (8000 << wb); + summary->channels = 1; /* always single channel */ + summary->sample_size = 16; + summary->samples_in_frame = (160 << wb); + summary->sbr_mode = MP4A_AAC_SBR_NOT_SPECIFIED; /* no effect */ + if( amr_create_damr( summary, wb ) < 0 + || lsmash_list_add_entry( importer->summaries, summary ) < 0 ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return NULL; + } + return summary; +} + +static int amr_probe +( + importer_t *importer +) +{ + + amr_importer_t *amr_imp = create_amr_importer( importer ); + if( !amr_imp ) + return LSMASH_ERR_MEMORY_ALLOC; + int err; + int wb = amr_check_magic_number( importer->bs ); + if( wb < 0 ) + { + err = wb; + goto fail; + } + lsmash_audio_summary_t *summary = amr_create_summary( importer, wb ); + if( !summary ) + { + err = LSMASH_ERR_NAMELESS; + goto fail; + } + amr_imp->wb = wb; + amr_imp->samples_in_frame = summary->samples_in_frame; + amr_imp->au_number = 0; + importer->info = amr_imp; + importer->status = IMPORTER_OK; + return 0; +fail: + remove_amr_importer( amr_imp ); + return err; +} + +static uint32_t amr_get_last_delta +( + importer_t *importer, + uint32_t track_number +) +{ + debug_if( !importer || !importer->info ) + return 0; + amr_importer_t *amr_imp = (amr_importer_t *)importer->info; + if( !amr_imp || track_number != 1 ) + return 0; + return amr_imp->samples_in_frame; +} + +const importer_functions amr_importer = +{ + { "AMR", offsetof( importer_t, log_level ) }, + 1, + amr_probe, + amr_get_accessunit, + amr_get_last_delta, + amr_cleanup +}; diff --git a/3rdparty/l-smash/importer/dts_imp.c b/3rdparty/l-smash/importer/dts_imp.c new file mode 100644 index 000000000..b7cec1df9 --- /dev/null +++ b/3rdparty/l-smash/importer/dts_imp.c @@ -0,0 +1,366 @@ +/***************************************************************************** + * dts_imp.c + ***************************************************************************** + * Copyright (C) 2012-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include + +#define LSMASH_IMPORTER_INTERNAL +#include "importer.h" + +/*************************************************************************** + DTS importer + ETSI TS 102 114 V1.2.1 (2002-12) + ETSI TS 102 114 V1.3.1 (2011-08) + ETSI TS 102 114 V1.4.1 (2012-09) +***************************************************************************/ +#include "codecs/dts.h" + +typedef struct +{ + dts_info_t info; + uint64_t next_frame_pos; + uint8_t buffer[DTS_MAX_EXSS_SIZE]; + lsmash_multiple_buffers_t *au_buffers; + uint8_t *au; + uint32_t au_length; + uint8_t *incomplete_au; + uint32_t incomplete_au_length; + uint32_t au_number; +} dts_importer_t; + +static void remove_dts_importer( dts_importer_t *dts_imp ) +{ + if( !dts_imp ) + return; + lsmash_destroy_multiple_buffers( dts_imp->au_buffers ); + lsmash_bits_cleanup( dts_imp->info.bits ); + lsmash_free( dts_imp ); +} + +static dts_importer_t *create_dts_importer( importer_t *importer ) +{ + dts_importer_t *dts_imp = (dts_importer_t *)lsmash_malloc_zero( sizeof(dts_importer_t) ); + if( !dts_imp ) + return NULL; + dts_info_t *dts_info = &dts_imp->info; + dts_info->bits = lsmash_bits_create( importer->bs ); + if( !dts_info->bits ) + { + lsmash_free( dts_imp ); + return NULL; + } + dts_imp->au_buffers = lsmash_create_multiple_buffers( 2, DTS_MAX_EXSS_SIZE ); + if( !dts_imp->au_buffers ) + { + lsmash_bits_cleanup( dts_info->bits ); + lsmash_free( dts_imp ); + return NULL; + } + dts_imp->au = lsmash_withdraw_buffer( dts_imp->au_buffers, 1 ); + dts_imp->incomplete_au = lsmash_withdraw_buffer( dts_imp->au_buffers, 2 ); + dts_setup_parser( dts_info ); + return dts_imp; +} + +static void dts_importer_cleanup( importer_t *importer ) +{ + debug_if( importer && importer->info ) + remove_dts_importer( importer->info ); +} + +static int dts_importer_get_next_accessunit_internal( importer_t *importer ) +{ + int au_completed = 0; + dts_importer_t *dts_imp = (dts_importer_t *)importer->info; + dts_info_t *info = &dts_imp->info; + lsmash_bs_t *bs = info->bits->bs; + while( !au_completed ) + { + /* Read data from the stream if needed. */ + dts_imp->next_frame_pos += info->frame_size; + lsmash_bs_read_seek( bs, dts_imp->next_frame_pos, SEEK_SET ); + uint64_t remain_size = lsmash_bs_get_remaining_buffer_size( bs ); + if( remain_size < DTS_MAX_EXSS_SIZE ) + { + if( bs->buffer.pos > DTS_MAX_EXSS_SIZE * 100 ) + lsmash_bs_dispose_past_data( bs ); + int err = lsmash_bs_read( bs, bs->buffer.max_size ); + if( err < 0 ) + { + lsmash_log( importer, LSMASH_LOG_ERROR, "failed to read data from the stream.\n" ); + return err; + } + remain_size = lsmash_bs_get_remaining_buffer_size( bs ); + } + memcpy( dts_imp->buffer, lsmash_bs_get_buffer_data( bs ), LSMASH_MIN( remain_size, DTS_MAX_EXSS_SIZE ) ); + /* Check the remainder length of the buffer. + * If there is enough length, then parse the frame in it. + * The length 10 is the required byte length to get frame size. */ + if( bs->eob || (bs->eof && remain_size < 10) ) + { + /* Reached the end of stream. */ + importer->status = IMPORTER_EOF; + au_completed = !!dts_imp->incomplete_au_length; + if( !au_completed ) + { + /* No more access units in the stream. */ + if( lsmash_bs_get_remaining_buffer_size( bs ) ) + { + lsmash_log( importer, LSMASH_LOG_WARNING, "the stream is truncated at the end.\n" ); + return LSMASH_ERR_INVALID_DATA; + } + return 0; + } + if( !info->ddts_param_initialized ) + dts_update_specific_param( info ); + } + else + { + /* Parse substream frame. */ + dts_substream_type prev_substream_type = info->substream_type; + info->substream_type = dts_get_substream_type( info ); + int err; + int (*dts_parse_frame)( dts_info_t * ) = NULL; + switch( info->substream_type ) + { + /* Decide substream frame parser and check if this frame and the previous frame belong to the same AU. */ + case DTS_SUBSTREAM_TYPE_CORE : + if( prev_substream_type != DTS_SUBSTREAM_TYPE_NONE ) + au_completed = 1; + dts_parse_frame = dts_parse_core_substream; + break; + case DTS_SUBSTREAM_TYPE_EXTENSION : + { + uint8_t prev_exss_index = info->exss_index; + if( (err = dts_get_exss_index( info, &info->exss_index )) < 0 ) + { + lsmash_log( importer, LSMASH_LOG_ERROR, "failed to get the index of an extension substream.\n" ); + return err; + } + if( prev_substream_type == DTS_SUBSTREAM_TYPE_EXTENSION + && info->exss_index <= prev_exss_index ) + au_completed = 1; + dts_parse_frame = dts_parse_extension_substream; + break; + } + default : + lsmash_log( importer, LSMASH_LOG_ERROR, "unknown substream type is detected.\n" ); + return LSMASH_ERR_NAMELESS; + } + if( !info->ddts_param_initialized && au_completed ) + dts_update_specific_param( info ); + info->frame_size = 0; + if( (err = dts_parse_frame( info )) < 0 ) + { + lsmash_log( importer, LSMASH_LOG_ERROR, "failed to parse a frame.\n" ); + return err; + } + } + if( au_completed ) + { + memcpy( dts_imp->au, dts_imp->incomplete_au, dts_imp->incomplete_au_length ); + dts_imp->au_length = dts_imp->incomplete_au_length; + dts_imp->incomplete_au_length = 0; + info->exss_count = (info->substream_type == DTS_SUBSTREAM_TYPE_EXTENSION); + if( importer->status == IMPORTER_EOF ) + break; + } + /* Increase buffer size to store AU if short. */ + if( dts_imp->incomplete_au_length + info->frame_size > dts_imp->au_buffers->buffer_size ) + { + lsmash_multiple_buffers_t *temp = lsmash_resize_multiple_buffers( dts_imp->au_buffers, + dts_imp->au_buffers->buffer_size + DTS_MAX_EXSS_SIZE ); + if( !temp ) + return LSMASH_ERR_MEMORY_ALLOC; + dts_imp->au_buffers = temp; + dts_imp->au = lsmash_withdraw_buffer( dts_imp->au_buffers, 1 ); + dts_imp->incomplete_au = lsmash_withdraw_buffer( dts_imp->au_buffers, 2 ); + } + /* Append frame data. */ + memcpy( dts_imp->incomplete_au + dts_imp->incomplete_au_length, dts_imp->buffer, info->frame_size ); + dts_imp->incomplete_au_length += info->frame_size; + } + return bs->error ? LSMASH_ERR_NAMELESS : 0; +} + +static int dts_importer_get_accessunit( importer_t *importer, uint32_t track_number, lsmash_sample_t **p_sample ) +{ + if( !importer->info ) + return LSMASH_ERR_NAMELESS; + if( track_number != 1 ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_list_get_entry_data( importer->summaries, track_number ); + if( !summary ) + return LSMASH_ERR_NAMELESS; + dts_importer_t *dts_imp = (dts_importer_t *)importer->info; + dts_info_t *info = &dts_imp->info; + importer_status current_status = importer->status; + if( current_status == IMPORTER_ERROR ) + return LSMASH_ERR_NAMELESS; + if( current_status == IMPORTER_EOF && dts_imp->au_length == 0 ) + return IMPORTER_EOF; + if( current_status == IMPORTER_CHANGE ) + summary->max_au_length = 0; + lsmash_sample_t *sample = lsmash_create_sample( dts_imp->au_length ); + if( !sample ) + return LSMASH_ERR_MEMORY_ALLOC; + *p_sample = sample; + memcpy( sample->data, dts_imp->au, dts_imp->au_length ); + sample->length = dts_imp->au_length; + sample->dts = dts_imp->au_number++ * summary->samples_in_frame; + sample->cts = sample->dts; + sample->prop.ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; + sample->prop.pre_roll.distance = !!(info->flags & DTS_EXT_SUBSTREAM_LBR_FLAG); /* MDCT */ + if( importer->status == IMPORTER_EOF ) + { + dts_imp->au_length = 0; + return 0; + } + if( dts_importer_get_next_accessunit_internal( importer ) < 0 ) + importer->status = IMPORTER_ERROR; + return current_status; +} + +static lsmash_audio_summary_t *dts_create_summary( dts_info_t *info ) +{ + lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_create_summary( LSMASH_SUMMARY_TYPE_AUDIO ); + if( !summary ) + return NULL; + lsmash_dts_specific_parameters_t *param = &info->ddts_param; + lsmash_codec_specific_t *specific = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_DTS, + LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED ); + if( !specific ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return NULL; + } + specific->data.unstructured = lsmash_create_dts_specific_info( param, &specific->size ); + if( !specific->data.unstructured + || lsmash_list_add_entry( &summary->opaque->list, specific ) < 0 ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + lsmash_destroy_codec_specific_data( specific ); + return NULL; + } + /* The CODEC identifiers probably should not be the combination of 'mp4a' and + * the objectTypeIndications for DTS audio since there is no public specification + * which defines the encapsulation of the stream as the MPEG-4 Audio context yet. + * In the world, there are muxers which is using such doubtful implementation. + * The objectTypeIndications are registered at MP4RA, but this does not always + * mean we can mux by using those objectTypeIndications. + * If available, there shall be the specification which defines the existence of + * DecoderSpecificInfo and its semantics, and what access unit consists of. */ + summary->sample_type = lsmash_dts_get_codingname( param ); + summary->aot = MP4A_AUDIO_OBJECT_TYPE_NULL; /* make no sense */ + summary->sbr_mode = MP4A_AAC_SBR_NOT_SPECIFIED; /* make no sense */ + switch( param->DTSSamplingFrequency ) + { + case 12000 : /* Invalid? (No reference in the spec) */ + case 24000 : + case 48000 : + case 96000 : + case 192000 : + case 384000 : /* Invalid? (No reference in the spec) */ + summary->frequency = 48000; + break; + case 22050 : + case 44100 : + case 88200 : + case 176400 : + case 352800 : /* Invalid? (No reference in the spec) */ + summary->frequency = 44100; + break; + case 8000 : /* Invalid? (No reference in the spec) */ + case 16000 : + case 32000 : + case 64000 : + case 128000 : + summary->frequency = 32000; + break; + default : + summary->frequency = 0; + break; + } + summary->samples_in_frame = (summary->frequency * info->frame_duration) / param->DTSSamplingFrequency; + summary->max_au_length = DTS_MAX_CORE_SIZE + DTS_MAX_NUM_EXSS * DTS_MAX_EXSS_SIZE; + summary->sample_size = param->pcmSampleDepth; + summary->channels = dts_get_max_channel_count( info ); + return summary; +} + +static int dts_importer_probe( importer_t *importer ) +{ + dts_importer_t *dts_imp = create_dts_importer( importer ); + if( !dts_imp ) + return LSMASH_ERR_MEMORY_ALLOC; + lsmash_bits_t *bits = dts_imp->info.bits; + lsmash_bs_t *bs = bits->bs; + bs->buffer.max_size = DTS_MAX_EXSS_SIZE; + importer->info = dts_imp; + int err = dts_importer_get_next_accessunit_internal( importer ); + if( err < 0 ) + goto fail; + lsmash_audio_summary_t *summary = dts_create_summary( &dts_imp->info ); + if( !summary ) + { + err = LSMASH_ERR_NAMELESS; + goto fail; + } + if( importer->status != IMPORTER_EOF ) + importer->status = IMPORTER_OK; + dts_imp->au_number = 0; + if( lsmash_list_add_entry( importer->summaries, summary ) < 0 ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + err = LSMASH_ERR_MEMORY_ALLOC; + goto fail; + } + return 0; +fail: + remove_dts_importer( dts_imp ); + importer->info = NULL; + return err; +} + +static uint32_t dts_importer_get_last_delta( importer_t* importer, uint32_t track_number ) +{ + debug_if( !importer || !importer->info ) + return 0; + dts_importer_t *dts_imp = (dts_importer_t *)importer->info; + if( !dts_imp || track_number != 1 || importer->status != IMPORTER_EOF || dts_imp->au_length ) + return 0; + lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_list_get_entry_data( importer->summaries, track_number ); + if( !summary ) + return 0; + return (summary->frequency * dts_imp->info.frame_duration) / dts_imp->info.ddts_param.DTSSamplingFrequency; +} + +const importer_functions dts_importer = +{ + { "DTS Coherent Acoustics", offsetof( importer_t, log_level ) }, + 1, + dts_importer_probe, + dts_importer_get_accessunit, + dts_importer_get_last_delta, + dts_importer_cleanup +}; diff --git a/3rdparty/l-smash/importer/importer.c b/3rdparty/l-smash/importer/importer.c new file mode 100644 index 000000000..f7701d3a9 --- /dev/null +++ b/3rdparty/l-smash/importer/importer.c @@ -0,0 +1,323 @@ +/***************************************************************************** + * importer.c + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Takashi Hirata + * Contributors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include + +#define LSMASH_IMPORTER_INTERNAL +#include "importer.h" + +/*************************************************************************** + importer classes +***************************************************************************/ +static const lsmash_class_t lsmash_importer_class = +{ + "importer", + offsetof( importer_t, log_level ) +}; + +extern const importer_functions mp4sys_adts_importer; +extern const importer_functions mp4sys_mp3_importer; +extern const importer_functions amr_importer; +extern const importer_functions ac3_importer; +extern const importer_functions eac3_importer; +extern const importer_functions mp4a_als_importer; +extern const importer_functions dts_importer; +extern const importer_functions wave_importer; +extern const importer_functions h264_importer; +extern const importer_functions hevc_importer; +extern const importer_functions vc1_importer; +extern const importer_functions isobm_importer; + +/******** importer listing table ********/ +static const importer_functions *importer_func_table[] = +{ + &mp4sys_adts_importer, + &mp4sys_mp3_importer, + &amr_importer, + &ac3_importer, + &eac3_importer, + &mp4a_als_importer, + &dts_importer, + &wave_importer, + &h264_importer, + &hevc_importer, + &vc1_importer, + &isobm_importer, + NULL, +}; + +/*************************************************************************** + importer public interfaces +***************************************************************************/ + +/******** importer public functions ********/ +importer_t *lsmash_importer_alloc( lsmash_root_t *root ) +{ + if( LSMASH_IS_NON_EXISTING_BOX( root ) ) + return NULL; + importer_t *importer = (importer_t *)lsmash_malloc_zero( sizeof(importer_t) ); + if( !importer ) + return NULL; + importer->root = root; + importer->summaries = lsmash_list_create( lsmash_cleanup_summary ); + if( !importer->summaries ) + { + lsmash_destroy_root( importer->root ); + lsmash_free( importer ); + return NULL; + } + return importer; +} + +void lsmash_importer_destroy( importer_t *importer ) +{ + if( !importer ) + return; + lsmash_file_t *file = importer->file; + if( importer->funcs.cleanup ) + importer->funcs.cleanup( importer ); + lsmash_list_destroy( importer->summaries ); + lsmash_free( importer ); + /* Prevent freeing this already freed importer in file's destructor again. */ + if( file && file->importer ) + file->importer = NULL; +} + +int lsmash_importer_set_file( importer_t *importer, lsmash_file_t *file ) +{ + if( !importer + || lsmash_activate_file( importer->root, file ) < 0 + || !file->bs ) + return LSMASH_ERR_NAMELESS; + importer->file = file; + importer->bs = file->bs; + file->importer = importer; + return 0; +} + +void lsmash_importer_close( importer_t *importer ) +{ + if( !importer ) + return; + lsmash_close_file( &importer->file_param ); + lsmash_importer_destroy( importer ); +} + +int lsmash_importer_find( importer_t *importer, const char *format, int auto_detect ) +{ + importer->log_level = LSMASH_LOG_QUIET; /* Any error log is confusing for the probe step. */ + const importer_functions *funcs; + int err = LSMASH_ERR_NAMELESS; + if( auto_detect ) + { + /* just rely on detector. */ + for( int i = 0; (funcs = importer_func_table[i]) != NULL; i++ ) + { + importer->class = &funcs->class; + if( !funcs->detectable ) + continue; + if( (err = funcs->probe( importer )) == 0 + || lsmash_bs_read_seek( importer->bs, 0, SEEK_SET ) != 0 ) + break; + } + } + else + { + /* needs name matching. */ + for( int i = 0; (funcs = importer_func_table[i]) != NULL; i++ ) + { + importer->class = &funcs->class; + if( strcmp( importer->class->name, format ) ) + continue; + if( (err = funcs->probe( importer )) < 0 ) + funcs = NULL; + break; + } + } + importer->log_level = LSMASH_LOG_INFO; + if( !funcs ) + { + importer->class = &lsmash_importer_class; + lsmash_log( importer, LSMASH_LOG_ERROR, "failed to find the matched importer.\n" ); + } + else + importer->funcs = *funcs; + return err; +} + +importer_t *lsmash_importer_open( lsmash_root_t *root, const char *identifier, const char *format ) +{ + if( identifier == NULL ) + return NULL; + int auto_detect = (format == NULL || !strcmp( format, "auto" )); + importer_t *importer = lsmash_importer_alloc( root ); + if( !importer ) + return NULL; + importer->is_adhoc_open = 1; + /* Open an input 'stream'. */ + if( !strcmp( identifier, "-" ) ) + { + /* special treatment for stdin */ + if( auto_detect ) + { + lsmash_log( importer, LSMASH_LOG_ERROR, "auto importer detection on stdin is not supported.\n" ); + goto fail; + } + } + if( lsmash_open_file( identifier, 1, &importer->file_param ) < 0 ) + { + lsmash_log( importer, LSMASH_LOG_ERROR, "failed to open %s.\n", identifier ); + goto fail; + } + lsmash_file_t *file = lsmash_set_file( root, &importer->file_param ); + if( LSMASH_IS_NON_EXISTING_BOX( file ) ) + { + lsmash_log( importer, LSMASH_LOG_ERROR, "failed to set opened file.\n" ); + goto fail; + } + lsmash_importer_set_file( importer, file ); + if( lsmash_importer_find( importer, format, auto_detect ) < 0 ) + goto fail; + return importer; +fail: + lsmash_importer_close( importer ); + return NULL; +} + +/* 0 if success, positive if changed, negative if failed */ +int lsmash_importer_get_access_unit( importer_t *importer, uint32_t track_number, lsmash_sample_t **p_sample ) +{ + if( !importer || !p_sample ) + return LSMASH_ERR_FUNCTION_PARAM; + if( !importer->funcs.get_accessunit ) + return LSMASH_ERR_NAMELESS; + *p_sample = NULL; + return importer->funcs.get_accessunit( importer, track_number, p_sample ); +} + +/* Return 0 if failed, otherwise succeeded. */ +uint32_t lsmash_importer_get_last_delta( importer_t *importer, uint32_t track_number ) +{ + if( !importer || !importer->funcs.get_last_delta ) + return 0; + return importer->funcs.get_last_delta( importer, track_number ); +} + +int lsmash_importer_construct_timeline( importer_t *importer, uint32_t track_number ) +{ + if( !importer ) + return LSMASH_ERR_FUNCTION_PARAM; + if( !importer->funcs.construct_timeline ) + return LSMASH_ERR_PATCH_WELCOME; + return importer->funcs.construct_timeline( importer, track_number ); +} + +uint32_t lsmash_importer_get_track_count( importer_t *importer ) +{ + if( !importer || !importer->summaries ) + return 0; + return importer->summaries->entry_count; +} + +lsmash_summary_t *lsmash_duplicate_summary( importer_t *importer, uint32_t track_number ) +{ + if( !importer ) + return NULL; + lsmash_summary_t *src_summary = lsmash_list_get_entry_data( importer->summaries, track_number ); + if( !src_summary ) + return NULL; + lsmash_summary_t *summary = lsmash_create_summary( src_summary->summary_type ); + if( !summary ) + return NULL; + lsmash_codec_specific_list_t *opaque = summary->opaque; + switch( src_summary->summary_type ) + { + case LSMASH_SUMMARY_TYPE_VIDEO : + *(lsmash_video_summary_t *)summary = *(lsmash_video_summary_t *)src_summary; + break; + case LSMASH_SUMMARY_TYPE_AUDIO : + *(lsmash_audio_summary_t *)summary = *(lsmash_audio_summary_t *)src_summary; + break; + case LSMASH_SUMMARY_TYPE_HINT: + *(lsmash_hint_summary_t *)summary = *(lsmash_hint_summary_t *)src_summary; + break; + default : + lsmash_cleanup_summary( summary ); + return NULL; + } + summary->opaque = opaque; + for( lsmash_entry_t *entry = src_summary->opaque->list.head; entry; entry = entry->next ) + { + lsmash_codec_specific_t *src_specific = (lsmash_codec_specific_t *)entry->data; + if( !src_specific ) + continue; + lsmash_codec_specific_t *dup = isom_duplicate_codec_specific_data( src_specific ); + if( lsmash_list_add_entry( &summary->opaque->list, dup ) < 0 ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + lsmash_destroy_codec_specific_data( dup ); + return NULL; + } + } + return summary; +} + +int lsmash_importer_make_fake_movie( importer_t *importer ) +{ + if( !importer || (importer->file->flags & LSMASH_FILE_MODE_BOX) ) + return LSMASH_ERR_FUNCTION_PARAM; + if( LSMASH_IS_BOX_ADDITION_FAILURE( isom_movie_create( importer->file ) ) ) + return LSMASH_ERR_NAMELESS; + return 0; +} + +int lsmash_importer_make_fake_track( importer_t *importer, lsmash_media_type media_type, uint32_t *track_ID ) +{ + if( !importer || (importer->file->flags & LSMASH_FILE_MODE_BOX) || track_ID == 0 ) + return LSMASH_ERR_FUNCTION_PARAM; + isom_trak_t *trak = isom_track_create( importer->file, media_type ); + int err; + if( LSMASH_IS_NON_EXISTING_BOX( trak->tkhd ) + || LSMASH_IS_NON_EXISTING_BOX( trak->mdia->minf ) + || trak->tkhd->track_ID == 0 ) + { + err = LSMASH_ERR_NAMELESS; + goto fail; + } + if( (err = isom_complement_data_reference( trak->mdia->minf )) < 0 ) + goto fail; + *track_ID = trak->tkhd->track_ID; + return 0; +fail: + isom_remove_box_by_itself( trak ); + return err; +} + +void lsmash_importer_break_fake_movie( importer_t *importer ) +{ + if( !importer ) + return; + isom_remove_box_by_itself( importer->file->moov ); +} diff --git a/3rdparty/l-smash/importer/importer.h b/3rdparty/l-smash/importer/importer.h new file mode 100644 index 000000000..668cdf8cb --- /dev/null +++ b/3rdparty/l-smash/importer/importer.h @@ -0,0 +1,168 @@ +/***************************************************************************** + * importer.h + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Takashi Hirata + * Contributors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#ifndef LSMASH_IMPORTER_H +#define LSMASH_IMPORTER_H + +/*************************************************************************** + importer +***************************************************************************/ +typedef struct importer_tag importer_t; + +#ifdef LSMASH_IMPORTER_INTERNAL + +#include "core/box.h" +#include "codecs/description.h" + +typedef void ( *importer_cleanup ) ( importer_t * ); +typedef int ( *importer_get_accessunit ) ( importer_t *, uint32_t, lsmash_sample_t ** ); +typedef int ( *importer_probe ) ( importer_t * ); +typedef uint32_t ( *importer_get_last_duration ) ( importer_t *, uint32_t ); +typedef int ( *importer_construct_timeline )( importer_t *, uint32_t ); + +typedef enum +{ + IMPORTER_ERROR = -1, + IMPORTER_OK = 0, + IMPORTER_CHANGE = 1, + IMPORTER_EOF = 2, +} importer_status; + +typedef struct +{ + lsmash_class_t class; + int detectable; + importer_probe probe; + importer_get_accessunit get_accessunit; + importer_get_last_duration get_last_delta; + importer_cleanup cleanup; + importer_construct_timeline construct_timeline; +} importer_functions; + +struct importer_tag +{ + const lsmash_class_t *class; + lsmash_log_level log_level; + importer_status status; + lsmash_root_t *root; /* pointer to a referenced ROOT */ + lsmash_file_t *file; /* pointer to a file which this importer handles */ + lsmash_bs_t *bs; + lsmash_file_parameters_t file_param; + void *info; /* importer internal status information. */ + importer_functions funcs; + lsmash_entry_list_t *summaries; + int is_adhoc_open; /* If set to 1, it means this importer is not allocated by lsmash_read_file(). + * This is a poor design due to historical implementation between the importer + * framework and ISOBMFF demuxer framework. The importer shall be hidden inside + * the file handling abstraction layer. That'll achieve integration of the muxer + * and the remuxer CLIs. */ +}; + +int lsmash_importer_make_fake_movie +( + importer_t *importer +); + +int lsmash_importer_make_fake_track +( + importer_t *importer, + lsmash_media_type media_type, + uint32_t *track_ID +); + +void lsmash_importer_break_fake_movie +( + importer_t *importer +); + +#else + +int lsmash_importer_set_file +( + importer_t *importer, + lsmash_file_t *file +); + +/* importing functions */ +importer_t *lsmash_importer_alloc +( + lsmash_root_t *root +); + +void lsmash_importer_destroy +( + importer_t *importer +); + +int lsmash_importer_find +( + importer_t *importer, + const char *format, + int auto_detect +); + +importer_t *lsmash_importer_open +( + lsmash_root_t *root, + const char *identifier, + const char *format +); + +void lsmash_importer_close +( + importer_t *importer +); + +int lsmash_importer_get_access_unit +( + importer_t *importer, + uint32_t track_number, + lsmash_sample_t **p_sample +); + +uint32_t lsmash_importer_get_last_delta +( + importer_t *importer, + uint32_t track_number +); + +int lsmash_importer_construct_timeline +( + importer_t *importer, + uint32_t track_number +); + +uint32_t lsmash_importer_get_track_count +( + importer_t *importer +); + +lsmash_summary_t *lsmash_duplicate_summary +( + importer_t *importer, + uint32_t track_number +); + +#endif /* #ifdef LSMASH_IMPORTER_INTERNAL */ + +#endif /* #ifndef LSMASH_IMPORTER_H */ diff --git a/3rdparty/l-smash/importer/isobm_imp.c b/3rdparty/l-smash/importer/isobm_imp.c new file mode 100644 index 000000000..7e856bcf4 --- /dev/null +++ b/3rdparty/l-smash/importer/isobm_imp.c @@ -0,0 +1,242 @@ +/***************************************************************************** + * isobm_imp.c + ***************************************************************************** + * Copyright (C) 2014-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include + +#define LSMASH_IMPORTER_INTERNAL +#include "importer.h" + +/********************************************************************************* + ISO Base Media File Format (ISOBMFF) / QuickTime File Format (QTFF) importer + + TODO: Make this importer work for multiple tracks. +**********************************************************************************/ +#include "core/read.h" +#include "core/timeline.h" + +typedef struct +{ + uint64_t timebase; + uint32_t track_ID; + uint32_t current_sample_description_index; + uint32_t au_number; +} isobm_importer_t; + +static void remove_isobm_importer( isobm_importer_t *isobm_imp ) +{ + if( !isobm_imp ) + return; + lsmash_free( isobm_imp ); +} + +static isobm_importer_t *create_isobm_importer( importer_t *importer ) +{ + isobm_importer_t *isobm_imp = (isobm_importer_t *)lsmash_malloc_zero( sizeof(isobm_importer_t) ); + if( isobm_imp ) + { + isobm_imp->timebase = 1; + return isobm_imp; + } + else + return NULL; +} + +static void isobm_importer_cleanup( importer_t *importer ) +{ + debug_if( importer && importer->info ) + remove_isobm_importer( importer->info ); +} + +static int isobm_importer_get_accessunit( importer_t *importer, uint32_t track_number, lsmash_sample_t **p_sample ) +{ + if( !importer->info ) + return LSMASH_ERR_NAMELESS; + if( track_number != 1 ) + return LSMASH_ERR_FUNCTION_PARAM; + importer_status current_status = importer->status; + if( current_status == IMPORTER_ERROR ) + return LSMASH_ERR_NAMELESS; + if( current_status == IMPORTER_EOF ) + return IMPORTER_EOF; + isobm_importer_t *isobm_imp = (isobm_importer_t *)importer->info; + lsmash_root_t *root = importer->root; + uint32_t track_ID = lsmash_get_track_ID( root, track_number ); + if( track_ID != isobm_imp->track_ID ) + return LSMASH_ERR_PATCH_WELCOME; + lsmash_sample_t *sample = lsmash_get_sample_from_media_timeline( root, track_ID, isobm_imp->au_number + 1 ); + if( !sample ) + { + if( lsmash_check_sample_existence_in_media_timeline( root, track_ID, isobm_imp->au_number + 1 ) ) + return LSMASH_ERR_NAMELESS; + else + { + /* No more samples. */ + importer->status = IMPORTER_EOF; + return IMPORTER_EOF; + } + } + sample->dts /= isobm_imp->timebase; + sample->cts /= isobm_imp->timebase; + if( sample->index != isobm_imp->current_sample_description_index ) + { + /* Update the active summary. */ + lsmash_summary_t *summary = lsmash_get_summary( root, track_ID, sample->index ); + if( !summary ) + { + lsmash_delete_sample( sample ); + return LSMASH_ERR_NAMELESS; + } + lsmash_list_remove_entry( importer->summaries, track_number ); + if( lsmash_list_add_entry( importer->summaries, summary ) < 0 ) + { + lsmash_delete_sample( sample ); + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return LSMASH_ERR_MEMORY_ALLOC; + } + isobm_imp->current_sample_description_index = sample->index; + importer->status = IMPORTER_OK; + current_status = IMPORTER_CHANGE; + } + *p_sample = sample; + ++ isobm_imp->au_number; + return current_status; +} + +static int isobm_importer_probe( importer_t *importer ) +{ + isobm_importer_t *isobm_imp = create_isobm_importer( importer ); + if( !isobm_imp ) + return LSMASH_ERR_MEMORY_ALLOC; + int err = 0; + /* Get the file size if seekable when reading. */ + lsmash_bs_t *bs = importer->bs; + if( !bs->unseekable ) + { + int64_t ret = lsmash_bs_read_seek( bs, 0, SEEK_END ); + if( ret < 0 ) + { + err = ret; + goto fail; + } + bs->written = ret; + lsmash_bs_read_seek( bs, 0, SEEK_SET ); + } + if( (err = isom_read_file( importer->file )) < 0 ) + goto fail; + if( !(importer->file->flags & (LSMASH_FILE_MODE_BOX + | LSMASH_FILE_MODE_FRAGMENTED + | LSMASH_FILE_MODE_INITIALIZATION + | LSMASH_FILE_MODE_MEDIA + | LSMASH_FILE_MODE_INDEX + | LSMASH_FILE_MODE_SEGMENT)) ) + { + err = LSMASH_ERR_INVALID_DATA; + goto fail; + } + importer->file->flags |= LSMASH_FILE_MODE_BOX; + if( importer->is_adhoc_open ) + { + lsmash_root_t *root = importer->root; + if( (isobm_imp->track_ID = lsmash_get_track_ID( root, 1 )) == 0 ) + { + err = LSMASH_ERR_PATCH_WELCOME; + goto fail; + } + lsmash_summary_t *summary = lsmash_get_summary( root, isobm_imp->track_ID, 1 ); + if( (err = lsmash_list_add_entry( importer->summaries, summary )) < 0 ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + goto fail; + } + isobm_imp->current_sample_description_index = 1; + } + importer->info = isobm_imp; + importer->status = IMPORTER_OK; + return 0; +fail: + remove_isobm_importer( isobm_imp ); + return err; +} + +static uint32_t isobm_importer_get_last_delta( importer_t *importer, uint32_t track_number ) +{ + debug_if( !importer || !importer->info ) + return 0; + isobm_importer_t *isobm_imp = (isobm_importer_t *)importer->info; + if( !isobm_imp || track_number != 1 ) + return 0; + uint32_t last_sample_delta; + if( lsmash_get_last_sample_delta_from_media_timeline( importer->root, isobm_imp->track_ID, &last_sample_delta ) < 0 ) + return 0; + return last_sample_delta / isobm_imp->timebase; +} + +static int isobm_importer_construct_timeline( importer_t *importer, uint32_t track_number ) +{ + lsmash_root_t *root = importer->root; + uint32_t track_ID = lsmash_get_track_ID( root, track_number ); + int err = isom_timeline_construct( root, track_ID ); + if( err < 0 ) + return err; + if( importer->is_adhoc_open ) + { + lsmash_summary_t *summary = lsmash_list_get_entry_data( importer->summaries, track_number ); + if( !summary ) + return LSMASH_ERR_NAMELESS; + summary->max_au_length = lsmash_get_max_sample_size_in_media_timeline( root, track_ID ); + if( summary->summary_type == LSMASH_SUMMARY_TYPE_VIDEO ) + { + lsmash_media_ts_list_t ts_list; + if( (err = lsmash_get_media_timestamps( root, track_ID, &ts_list )) < 0 ) + return err; + uint32_t last_sample_delta; + if( (err = lsmash_get_last_sample_delta_from_media_timeline( root, track_ID, &last_sample_delta )) < 0 ) + return err; + isobm_importer_t *isobm_imp = (isobm_importer_t *)importer->info; + isobm_imp->timebase = last_sample_delta; + for( uint32_t i = 1; i < ts_list.sample_count; i++ ) + isobm_imp->timebase = lsmash_get_gcd( isobm_imp->timebase, ts_list.timestamp[i].dts - ts_list.timestamp[i - 1].dts ); + lsmash_sort_timestamps_composition_order( &ts_list ); + for( uint32_t i = 1; i < ts_list.sample_count; i++ ) + isobm_imp->timebase = lsmash_get_gcd( isobm_imp->timebase, ts_list.timestamp[i].cts - ts_list.timestamp[i - 1].cts ); + lsmash_delete_media_timestamps( &ts_list ); + if( isobm_imp->timebase == 0 ) + isobm_imp->timebase = 1; + ((lsmash_video_summary_t *)summary)->timebase = isobm_imp->timebase; + ((lsmash_video_summary_t *)summary)->timescale = lsmash_get_media_timescale( root, track_ID ); + } + } + return 0; +} + +const importer_functions isobm_importer = +{ + { "ISOBMFF/QTFF", offsetof( importer_t, log_level ) }, + 1, + isobm_importer_probe, + isobm_importer_get_accessunit, + isobm_importer_get_last_delta, + isobm_importer_cleanup, + isobm_importer_construct_timeline +}; diff --git a/3rdparty/l-smash/importer/mp3_imp.c b/3rdparty/l-smash/importer/mp3_imp.c new file mode 100644 index 000000000..5b7521268 --- /dev/null +++ b/3rdparty/l-smash/importer/mp3_imp.c @@ -0,0 +1,513 @@ +/***************************************************************************** + * mp3_imp.c + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Takashi Hirata + * Contributors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include + +#define LSMASH_IMPORTER_INTERNAL +#include "importer.h" + +/*************************************************************************** + mp3 (Legacy Interface) importer +***************************************************************************/ +#include "codecs/mp4a.h" + +#define USE_MP4SYS_LEGACY_INTERFACE 1 + +#define MP4SYS_MP3_MAX_FRAME_LENGTH (1152*(16/8)*2) +#define MP4SYS_MP3_HEADER_LENGTH 4 +#define MP4SYS_MODE_IS_2CH( mode ) ((mode)!=3) +#define MP4SYS_LAYER_III 0x1 +#define MP4SYS_LAYER_II 0x2 +#define MP4SYS_LAYER_I 0x3 + +typedef struct +{ + uint16_t syncword; /* <12> */ + uint8_t ID; /* <1> */ + uint8_t layer; /* <2> */ + uint8_t protection_bit; /* <1> */ + uint8_t bitrate_index; /* <4> */ + uint8_t sampling_frequency; /* <2> */ + uint8_t padding_bit; /* <1> */ +// uint8_t private_bit; /* <1> don't care. */ + uint8_t mode; /* <2> */ +// uint8_t mode_extension; /* <2> don't care. */ +// uint8_t copyright; /* <1> don't care. */ +// uint8_t original_copy; /* <1> don't care. */ + uint8_t emphasis; /* <2> for error check only. */ +} mp4sys_mp3_header_t; + +typedef struct +{ + mp4sys_mp3_header_t header; + uint8_t raw_header[MP4SYS_MP3_HEADER_LENGTH]; + uint32_t samples_in_frame; + uint32_t au_number; + uint16_t main_data_size[32]; /* size of main_data of the last 32 frames, FIFO */ + uint16_t prev_preroll_count; /* number of dependent frames of *previous* frame */ + uint16_t enc_delay; + uint16_t padding; + uint64_t valid_samples; +} mp4sys_mp3_importer_t; + +static void remove_mp4sys_mp3_importer +( + mp4sys_mp3_importer_t *mp3_imp +) +{ + lsmash_free( mp3_imp ); +} + +static mp4sys_mp3_importer_t *create_mp4sys_mp3_importer +( + importer_t *importer +) +{ + return (mp4sys_mp3_importer_t *)lsmash_malloc_zero( sizeof(mp4sys_mp3_importer_t) ); +} + +static void mp4sys_mp3_cleanup( importer_t *importer ) +{ + debug_if( importer && importer->info ) + remove_mp4sys_mp3_importer( importer->info ); +} + +static int mp4sys_mp3_parse_header( uint8_t *buf, mp4sys_mp3_header_t *header ) +{ + /* FIXME: should we rewrite these code using bitstream reader? */ + uint32_t data = LSMASH_GET_BE32( buf ); + header->syncword = (data >> 20) & 0xFFF; /* NOTE: don't consider what is called MPEG2.5, which last bit is 0. */ + header->ID = (data >> 19) & 0x1; + header->layer = (data >> 17) & 0x3; + header->protection_bit = (data >> 16) & 0x1; + header->bitrate_index = (data >> 12) & 0xF; + header->sampling_frequency = (data >> 10) & 0x3; + header->padding_bit = (data >> 9) & 0x1; +// header->private_bit = (data >> 8) & 0x1; /* don't care. */ + header->mode = (data >> 6) & 0x3; +// header->mode_extension = (data >> 4) & 0x3; +// header->copyright = (data >> 3) & 0x1; /* don't care. */ +// header->original_copy = (data >> 2) & 0x1; /* don't care. */ + header->emphasis = data & 0x3; /* for error check only. */ + if( header->syncword != 0xFFF ) return LSMASH_ERR_INVALID_DATA; + if( header->layer == 0x0 ) return LSMASH_ERR_NAMELESS; /* 0b00: reserved */ + if( header->bitrate_index == 0x0 ) return LSMASH_ERR_PATCH_WELCOME; /* FIXME: "free" bitrate is unsupported currently. */ + if( header->bitrate_index == 0xF ) return LSMASH_ERR_INVALID_DATA; /* Forbidden */ + if( header->sampling_frequency == 0x3 ) return LSMASH_ERR_NAMELESS; /* 0b11: reserved */ + if( header->emphasis == 0x2 ) return LSMASH_ERR_NAMELESS; /* 0b10: reserved */ + return 0; +} + +static const uint32_t mp4sys_mp3_frequency_tbl[2][3] = +{ + { 22050, 24000, 16000 }, /* MPEG-2 BC audio */ + { 44100, 48000, 32000 } /* MPEG-1 audio */ +}; + +static int mp4sys_mp3_samples_in_frame( mp4sys_mp3_header_t *header ) +{ + if( header->layer == MP4SYS_LAYER_I ) + return 384; + else if( header->ID == 1 || header->layer == MP4SYS_LAYER_II ) + return 1152; + else + return 576; +} + +static lsmash_audio_summary_t *mp4sys_mp3_create_summary( mp4sys_mp3_header_t *header, int legacy_mode ) +{ + lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_create_summary( LSMASH_SUMMARY_TYPE_AUDIO ); + if( !summary ) + return NULL; + summary->sample_type = ISOM_CODEC_TYPE_MP4A_AUDIO; + summary->max_au_length = MP4SYS_MP3_MAX_FRAME_LENGTH; + summary->frequency = mp4sys_mp3_frequency_tbl[header->ID][header->sampling_frequency]; + summary->channels = MP4SYS_MODE_IS_2CH( header->mode ) + 1; + summary->sample_size = 16; + summary->samples_in_frame = mp4sys_mp3_samples_in_frame( header ); + summary->aot = MP4A_AUDIO_OBJECT_TYPE_Layer_1 + (MP4SYS_LAYER_I - header->layer); /* no effect with Legacy Interface. */ + summary->sbr_mode = MP4A_AAC_SBR_NOT_SPECIFIED; /* no effect */ +#if !USE_MP4SYS_LEGACY_INTERFACE /* FIXME: This is very unstable. Many players crash with this. */ + if( !legacy_mode ) + { + summary->object_type_indication = MP4SYS_OBJECT_TYPE_Audio_ISO_14496_3; + if( lsmash_setup_AudioSpecificConfig( summary ) < 0 ) + { + lsmash_cleanup_summary( summary ); + return NULL; + } + } + uint32_t data_length; + uint8_t *data = mp4a_export_AudioSpecificConfig( MP4A_AUDIO_OBJECT_TYPE_Layer_1 + (MP4SYS_LAYER_I - header->layer), + summary->frequency, summary->channels, summary->sbr_mode, + NULL, 0, &data_length ); + if( !data ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return NULL; + } +#endif + lsmash_codec_specific_t *specific = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_MP4SYS_DECODER_CONFIG, + LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !specific ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); +#if !USE_MP4SYS_LEGACY_INTERFACE + lsmash_free( data ); +#endif + return NULL; + } + lsmash_mp4sys_decoder_parameters_t *param = (lsmash_mp4sys_decoder_parameters_t *)specific->data.structured; + param->objectTypeIndication = header->ID ? MP4SYS_OBJECT_TYPE_Audio_ISO_11172_3 : MP4SYS_OBJECT_TYPE_Audio_ISO_13818_3; + param->streamType = MP4SYS_STREAM_TYPE_AudioStream; +#if !USE_MP4SYS_LEGACY_INTERFACE + if( lsmash_set_mp4sys_decoder_specific_info( param, data, data_length ) < 0 ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + lsmash_destroy_codec_specific_data( specific ); + lsmash_free( data ); + return NULL; + } + lsmash_free( data ); +#endif + if( lsmash_list_add_entry( &summary->opaque->list, specific ) < 0 ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + lsmash_destroy_codec_specific_data( specific ); + return NULL; + } + return summary; +} + +static int parse_xing_info_header( mp4sys_mp3_importer_t *mp3_imp, mp4sys_mp3_header_t *header, uint8_t *frame ) +{ + unsigned int sip = header->protection_bit ? 4 : 6; + unsigned int side_info_size; + if( header->ID == 1 ) + side_info_size = MP4SYS_MODE_IS_2CH( header->mode ) ? 32 : 17; + else + side_info_size = MP4SYS_MODE_IS_2CH( header->mode ) ? 17 : 9; + + uint8_t *mdp = frame + sip + side_info_size; + if( memcmp( mdp, "Info", 4 ) + && memcmp( mdp, "Xing", 4 ) ) + return 0; + uint32_t flags = LSMASH_GET_BE32( &mdp[4] ); + uint32_t off = 8; + uint32_t frame_count = 0; + if( flags & 1 ) + { + frame_count = LSMASH_GET_BE32( &mdp[8] ); + mp3_imp->valid_samples = (uint64_t)frame_count * mp4sys_mp3_samples_in_frame( header ); + off += 4; + } + if( flags & 2 ) off += 4; /* file size */ + if( flags & 4 ) off += 100; /* TOC */ + if( flags & 8 ) off += 4; /* VBR quality */ + + if( mdp[off] == 'L' ) + { /* LAME header present */ + unsigned int v = LSMASH_GET_BE24( &mdp[off + 21] ); + mp3_imp->enc_delay = v >> 12; + mp3_imp->padding = v & 0xfff; + if( frame_count ) + mp3_imp->valid_samples -= mp3_imp->enc_delay + mp3_imp->padding; + } + return 1; +} + +static int parse_vbri_header( mp4sys_mp3_importer_t *mp3_imp, mp4sys_mp3_header_t *header, uint8_t *frame ) +{ + return memcmp( frame + 36, "VBRI", 4 ) == 0; +} + +static int mp4sys_mp3_get_accessunit( importer_t *importer, uint32_t track_number, lsmash_sample_t **p_sample ) +{ + if( !importer->info ) + return LSMASH_ERR_NAMELESS; + if( track_number != 1 ) + return LSMASH_ERR_FUNCTION_PARAM; + mp4sys_mp3_importer_t *mp3_imp = (mp4sys_mp3_importer_t *)importer->info; + mp4sys_mp3_header_t *header = (mp4sys_mp3_header_t *)&mp3_imp->header; + importer_status current_status = importer->status; + /* bitrate */ + const uint32_t bitrate_tbl[2][3][16] = + { + { /* MPEG-2 BC audio */ + { 1, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, /* Layer III */ + { 1, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, /* Layer II */ + { 1, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0 } /* Layer I */ + }, + { /* MPEG-1 audio */ + { 1, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 }, /* Layer III */ + { 1, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0 }, /* Layer II */ + { 1, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0 } /* Layer I */ + } + }; + uint32_t bitrate = bitrate_tbl[ header->ID ][ header->layer - 1 ][ header->bitrate_index ]; + if( bitrate == 0 ) + return LSMASH_ERR_INVALID_DATA; + else if( bitrate == 1 ) + return LSMASH_ERR_PATCH_WELCOME; /* free format */ + /* sampling frequency */ + uint32_t frequency = mp4sys_mp3_frequency_tbl[header->ID][header->sampling_frequency]; + if( frequency == 0 ) + return LSMASH_ERR_NAMELESS; /* reserved */ + /* frame size */ + uint32_t frame_size; + if( header->layer == MP4SYS_LAYER_I ) + /* mp1's 'slot' is 4 bytes unit. see 11172-3, Audio Sequence General. */ + frame_size = (12 * 1000 * bitrate / frequency + header->padding_bit) * 4; + else + { + /* mp2/3's 'slot' is 1 bytes unit. */ + uint32_t div = frequency; + if( header->layer == MP4SYS_LAYER_III && header->ID == 0 ) + div <<= 1; + frame_size = 144 * 1000 * bitrate / div + header->padding_bit; + } + if( frame_size <= 4 ) + return LSMASH_ERR_INVALID_DATA; + if( current_status == IMPORTER_ERROR ) + return LSMASH_ERR_NAMELESS; + if( current_status == IMPORTER_EOF ) + return IMPORTER_EOF; + if( current_status == IMPORTER_CHANGE ) + { + lsmash_entry_t *entry = lsmash_list_get_entry( importer->summaries, track_number ); + if( !entry || !entry->data ) + return LSMASH_ERR_NAMELESS; + lsmash_audio_summary_t *summary = mp4sys_mp3_create_summary( header, 1 ); /* FIXME: use legacy mode. */ + if( !summary ) + return LSMASH_ERR_NAMELESS; + lsmash_cleanup_summary( entry->data ); + entry->data = summary; + mp3_imp->samples_in_frame = summary->samples_in_frame; + } + /* read a frame's data. */ + lsmash_sample_t *sample = *p_sample; + if( !sample ) + { + sample = lsmash_create_sample( MP4SYS_MP3_MAX_FRAME_LENGTH ); + if( !sample ) + return LSMASH_ERR_MEMORY_ALLOC; + *p_sample = sample; + } + uint8_t *frame_data = sample->data; + memcpy( frame_data, mp3_imp->raw_header, MP4SYS_MP3_HEADER_LENGTH ); + frame_size -= MP4SYS_MP3_HEADER_LENGTH; + if( lsmash_bs_get_bytes_ex( importer->bs, frame_size, frame_data + MP4SYS_MP3_HEADER_LENGTH ) != frame_size ) + { + importer->status = IMPORTER_ERROR; + return LSMASH_ERR_INVALID_DATA; + } + sample->length = MP4SYS_MP3_HEADER_LENGTH + frame_size; + sample->dts = mp3_imp->au_number ++ * mp3_imp->samples_in_frame; + sample->cts = sample->dts; + sample->prop.ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; + sample->prop.pre_roll.distance = header->layer == MP4SYS_LAYER_III ? 1 : 0; /* Layer III uses MDCT */ + + int vbr_header_present = 0; + if( mp3_imp->au_number == 1 + && (parse_xing_info_header( mp3_imp, header, frame_data ) + || parse_vbri_header( mp3_imp, header, frame_data )) ) + { + vbr_header_present = 1; + mp3_imp->au_number--; + } + + /* handle additional inter-frame dependency due to bit reservoir */ + if( !vbr_header_present && header->layer == MP4SYS_LAYER_III ) + { + /* position of side_info */ + unsigned int sip = header->protection_bit ? 4 : 6; + unsigned int main_data_begin = frame_data[sip]; + if( header->ID == 1 ) + { + main_data_begin <<= 1; + main_data_begin |= (frame_data[sip + 1] >> 7); + } + if( main_data_begin > 0 ) + { + /* main_data_begin is a backpointer to the start of + * bit reservoir data for this frame. + * it contains total amount of bytes required from + * preceding frames. + * we just add up main_data size from history until it reaches + * the required amount. + */ + unsigned int reservoir_data = 0; + unsigned int i; + for( i = 0; i < 32 && reservoir_data < main_data_begin; ++i ) + { + reservoir_data += mp3_imp->main_data_size[i]; + if( mp3_imp->main_data_size[i] == 0 ) + break; + } + sample->prop.pre_roll.distance += mp3_imp->prev_preroll_count; + mp3_imp->prev_preroll_count = i; + } + uint16_t side_info_size; + if( header->ID == 1 ) + side_info_size = MP4SYS_MODE_IS_2CH( header->mode ) ? 32 : 17; + else + side_info_size = MP4SYS_MODE_IS_2CH( header->mode ) ? 17 : 9; + + /* pop back main_data_size[] and push main_data size of this frame + * to the front */ + memmove( mp3_imp->main_data_size + 1, mp3_imp->main_data_size, sizeof(mp3_imp->main_data_size) - sizeof( mp3_imp->main_data_size[0] ) ); + mp3_imp->main_data_size[0] = frame_size - sip - side_info_size; + } + /* now we succeeded to read current frame, so "return" takes 0 always below. */ + /* preparation for next frame */ + + uint8_t buf[MP4SYS_MP3_HEADER_LENGTH]; + int64_t ret = lsmash_bs_get_bytes_ex( importer->bs, MP4SYS_MP3_HEADER_LENGTH, buf ); + if( ret == 0 ) + { + importer->status = IMPORTER_EOF; + return 0; + } + if( ret >= 2 && (!memcmp( buf, "TA", 2 ) || !memcmp( buf, "AP", 2 )) ) + { + /* ID3v1 or APE tag */ + importer->status = IMPORTER_EOF; + return 0; + } + if( ret == 1 && *buf == 0x00 ) + { + /* NOTE: ugly hack for mp1 stream created with SCMPX. */ + importer->status = IMPORTER_EOF; + return 0; + } + if( ret != MP4SYS_MP3_HEADER_LENGTH ) + { + importer->status = IMPORTER_ERROR; + return 0; + } + + mp4sys_mp3_header_t new_header = { 0 }; + if( mp4sys_mp3_parse_header( buf, &new_header ) < 0 ) + { + importer->status = IMPORTER_ERROR; + return 0; + } + memcpy( mp3_imp->raw_header, buf, MP4SYS_MP3_HEADER_LENGTH ); + + /* currently UNsupported "change(s)". */ + if( header->layer != new_header.layer /* This means change of object_type_indication with Legacy Interface. */ + || header->sampling_frequency != new_header.sampling_frequency ) /* This may change timescale. */ + { + importer->status = IMPORTER_ERROR; + return 0; + } + + /* currently supported "change(s)". */ + if( MP4SYS_MODE_IS_2CH( header->mode ) != MP4SYS_MODE_IS_2CH( new_header.mode ) ) + importer->status = IMPORTER_CHANGE; + else + importer->status = IMPORTER_OK; /* no change which matters to mp4 muxing was found */ + mp3_imp->header = new_header; + + if( vbr_header_present ) + return mp4sys_mp3_get_accessunit( importer, track_number, &sample ); + return 0; +} + +static int mp4sys_mp3_probe( importer_t *importer ) +{ + mp4sys_mp3_importer_t *mp3_imp = create_mp4sys_mp3_importer( importer ); + if( !mp3_imp ) + return LSMASH_ERR_MEMORY_ALLOC; + lsmash_bs_t *bs = importer->bs; + /* Multiple ID3 tags could be present, loop to skip them first. */ + while( lsmash_bs_show_byte( bs, 0 ) == 'I' + && lsmash_bs_show_byte( bs, 1 ) == 'D' + && lsmash_bs_show_byte( bs, 2 ) == '3' ) + { + lsmash_bs_read_seek( bs, 6, SEEK_CUR ); + uint32_t size = 0; + for( int i = 0 ; i < 4; i++ ) + { + size <<= 7; + size |= lsmash_bs_get_byte( bs ); + } + lsmash_bs_read_seek( bs, size, SEEK_CUR ); + } + /* Parse the header. */ + int err; + uint8_t buf[MP4SYS_MP3_HEADER_LENGTH]; + if( lsmash_bs_get_bytes_ex( bs, MP4SYS_MP3_HEADER_LENGTH, buf ) != MP4SYS_MP3_HEADER_LENGTH ) + { + err = LSMASH_ERR_INVALID_DATA; + goto fail; + } + mp4sys_mp3_header_t header = { 0 }; + if( (err = mp4sys_mp3_parse_header( buf, &header )) < 0 ) + goto fail; + /* Now, the stream seems valid mp3. */ + lsmash_audio_summary_t *summary = mp4sys_mp3_create_summary( &header, 1 ); + if( !summary ) + { + err = LSMASH_ERR_NAMELESS; + goto fail; + } + /* importer status */ + if( lsmash_list_add_entry( importer->summaries, summary ) < 0 ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + err = LSMASH_ERR_MEMORY_ALLOC; + goto fail; + } + mp3_imp->header = header; + mp3_imp->samples_in_frame = summary->samples_in_frame; + memcpy( mp3_imp->raw_header, buf, MP4SYS_MP3_HEADER_LENGTH ); + importer->info = mp3_imp; + importer->status = IMPORTER_OK; + return 0; +fail: + remove_mp4sys_mp3_importer( mp3_imp ); + return err; +} + +static uint32_t mp4sys_mp3_get_last_delta( importer_t *importer, uint32_t track_number ) +{ + debug_if( !importer || !importer->info ) + return 0; + mp4sys_mp3_importer_t *mp3_imp = (mp4sys_mp3_importer_t *)importer->info; + if( !mp3_imp || track_number != 1 || importer->status != IMPORTER_EOF ) + return 0; + return mp3_imp->samples_in_frame; +} + +const importer_functions mp4sys_mp3_importer = +{ + { "MPEG-1/2BC Audio Legacy" }, + 1, + mp4sys_mp3_probe, + mp4sys_mp3_get_accessunit, + mp4sys_mp3_get_last_delta, + mp4sys_mp3_cleanup +}; diff --git a/3rdparty/l-smash/importer/nalu_imp.c b/3rdparty/l-smash/importer/nalu_imp.c new file mode 100644 index 000000000..409d22dc1 --- /dev/null +++ b/3rdparty/l-smash/importer/nalu_imp.c @@ -0,0 +1,1566 @@ +/***************************************************************************** + * nalu_imp.c + ***************************************************************************** + * Copyright (C) 2011-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include +#include +#include + +#define LSMASH_IMPORTER_INTERNAL +#include "importer.h" + +/*************************************************************************** + H.264 importer + ITU-T Recommendation H.264 (04/13) + ISO/IEC 14496-15:2010 +***************************************************************************/ +#include "codecs/h264.h" +#include "codecs/nalu.h" + +typedef struct +{ + h264_info_t info; + lsmash_entry_list_t avcC_list[1]; /* stored as lsmash_codec_specific_t */ + lsmash_media_ts_list_t ts_list; + uint32_t max_au_length; + uint32_t num_undecodable; + uint32_t avcC_number; + uint32_t last_delta; + uint64_t last_intra_cts; + uint64_t last_sync_cts; + uint64_t sc_head_pos; + uint8_t composition_reordering_present; + uint8_t field_pic_present; +} h264_importer_t; + +typedef struct +{ + int64_t poc; + uint32_t delta; + uint16_t poc_delta; + uint16_t reset; +} nal_pic_timing_t; + +static void remove_h264_importer( h264_importer_t *h264_imp ) +{ + if( !h264_imp ) + return; + lsmash_list_remove_entries( h264_imp->avcC_list ); + h264_cleanup_parser( &h264_imp->info ); + lsmash_free( h264_imp->ts_list.timestamp ); + lsmash_free( h264_imp ); +} + +static void h264_importer_cleanup( importer_t *importer ) +{ + debug_if( importer && importer->info ) + remove_h264_importer( importer->info ); +} + +static h264_importer_t *create_h264_importer( importer_t *importer ) +{ + h264_importer_t *h264_imp = lsmash_malloc_zero( sizeof(h264_importer_t) ); + if( !h264_imp ) + return NULL; + if( h264_setup_parser( &h264_imp->info, 0 ) < 0 ) + { + remove_h264_importer( h264_imp ); + return NULL; + } + lsmash_list_init( h264_imp->avcC_list, lsmash_destroy_codec_specific_data ); + return h264_imp; +} + +static inline int h264_complete_au( h264_access_unit_t *au, int probe ) +{ + if( !au->picture.has_primary || au->incomplete_length == 0 ) + return 0; + if( !probe ) + memcpy( au->data, au->incomplete_data, au->incomplete_length ); + au->length = au->incomplete_length; + au->incomplete_length = 0; + au->picture.has_primary = 0; + return 1; +} + +static void h264_append_nalu_to_au( h264_access_unit_t *au, uint8_t *src_nalu, uint32_t nalu_length, int probe ) +{ + if( !probe ) + { + uint8_t *dst_nalu = au->incomplete_data + au->incomplete_length + NALU_DEFAULT_NALU_LENGTH_SIZE; + for( int i = NALU_DEFAULT_NALU_LENGTH_SIZE; i; i-- ) + *(dst_nalu - i) = (nalu_length >> ((i - 1) * 8)) & 0xff; + memcpy( dst_nalu, src_nalu, nalu_length ); + } + /* Note: au->incomplete_length shall be 0 immediately after AU has completed. + * Therefore, possible_au_length in h264_get_access_unit_internal() can't be used here + * to avoid increasing AU length monotonously through the entire stream. */ + au->incomplete_length += NALU_DEFAULT_NALU_LENGTH_SIZE + nalu_length; +} + +static int h264_get_au_internal_succeeded( h264_importer_t *h264_imp, h264_access_unit_t *au ) +{ + au->number += 1; + return 0; +} + +static int h264_get_au_internal_failed( h264_importer_t *h264_imp, h264_access_unit_t *au, int complete_au, int ret ) +{ + if( complete_au ) + au->number += 1; + return ret; +} + +static lsmash_video_summary_t *h264_create_summary +( + lsmash_h264_specific_parameters_t *param, + h264_sps_t *sps, + uint32_t max_au_length +) +{ + lsmash_video_summary_t *summary = (lsmash_video_summary_t *)lsmash_create_summary( LSMASH_SUMMARY_TYPE_VIDEO ); + if( !summary ) + return NULL; + /* Update summary here. + * max_au_length is set at the last of mp4sys_h264_probe function. */ + lsmash_codec_specific_t *cs = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_H264, + LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED ); + if( !cs ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return NULL; + } + cs->data.unstructured = lsmash_create_h264_specific_info( param, &cs->size ); + if( !cs->data.unstructured + || lsmash_list_add_entry( &summary->opaque->list, cs ) < 0 ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + lsmash_destroy_codec_specific_data( cs ); + return NULL; + } + summary->sample_type = ISOM_CODEC_TYPE_AVC1_VIDEO; + summary->max_au_length = max_au_length; + summary->timescale = sps->vui.time_scale; + summary->timebase = sps->vui.num_units_in_tick; + summary->vfr = !sps->vui.fixed_frame_rate_flag; + summary->sample_per_field = 0; + summary->width = sps->cropped_width; + summary->height = sps->cropped_height; + summary->par_h = sps->vui.sar_width; + summary->par_v = sps->vui.sar_height; + summary->color.primaries_index = sps->vui.colour_primaries; + summary->color.transfer_index = sps->vui.transfer_characteristics; + summary->color.matrix_index = sps->vui.matrix_coefficients; + summary->color.full_range = sps->vui.video_full_range_flag; + return summary; +} + +static int h264_store_codec_specific +( + h264_importer_t *h264_imp, + lsmash_h264_specific_parameters_t *avcC_param +) +{ + lsmash_codec_specific_t *src_cs = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_H264, + LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !src_cs ) + return LSMASH_ERR_NAMELESS; + lsmash_h264_specific_parameters_t *src_param = (lsmash_h264_specific_parameters_t *)src_cs->data.structured; + *src_param = *avcC_param; + lsmash_codec_specific_t *dst_cs = lsmash_convert_codec_specific_format( src_cs, LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + src_param->parameter_sets = NULL; /* Avoid freeing parameter sets within avcC_param. */ + lsmash_destroy_codec_specific_data( src_cs ); + if( !dst_cs ) + { + lsmash_destroy_codec_specific_data( dst_cs ); + return LSMASH_ERR_NAMELESS; + } + if( lsmash_list_add_entry( h264_imp->avcC_list, dst_cs ) < 0 ) + { + lsmash_destroy_codec_specific_data( dst_cs ); + return LSMASH_ERR_MEMORY_ALLOC; + } + return 0; +} + +static inline void h264_new_access_unit +( + h264_access_unit_t *au +) +{ + au->length = 0; + au->picture.type = H264_PICTURE_TYPE_NONE; + au->picture.random_accessible = 0; + au->picture.recovery_frame_cnt = 0; + au->picture.has_mmco5 = 0; + au->picture.has_redundancy = 0; + au->picture.broken_link_flag = 0; +} + +/* If probe equals 0, don't get the actual data (EBPS) of an access unit and only parse NALU. + * Currently, you can get AU of AVC video elemental stream only, not AVC parameter set elemental stream defined in 14496-15. */ +static int h264_get_access_unit_internal +( + importer_t *importer, + int probe +) +{ + h264_importer_t *h264_imp = (h264_importer_t *)importer->info; + h264_info_t *info = &h264_imp->info; + h264_slice_info_t *slice = &info->slice; + h264_access_unit_t *au = &info->au; + h264_picture_info_t *picture = &au->picture; + h264_stream_buffer_t *sb = &info->buffer; + lsmash_bs_t *bs = importer->bs; + int complete_au = 0; + h264_new_access_unit( au ); + while( 1 ) + { + h264_nalu_header_t nuh; + uint64_t start_code_length; + uint64_t trailing_zero_bytes; + uint64_t nalu_length = h264_find_next_start_code( bs, &nuh, &start_code_length, &trailing_zero_bytes ); + if( nalu_length == NALU_NO_START_CODE_FOUND ) + { + /* For the last NALU. + * This NALU already has been appended into the latest access unit and parsed. */ + h264_update_picture_info( info, picture, slice, &info->sei ); + complete_au = h264_complete_au( au, probe ); + if( complete_au ) + return h264_get_au_internal_succeeded( h264_imp, au ); + else + return h264_get_au_internal_failed( h264_imp, au, complete_au, LSMASH_ERR_INVALID_DATA ); + } + uint8_t nalu_type = nuh.nal_unit_type; + uint64_t next_sc_head_pos = h264_imp->sc_head_pos + + start_code_length + + nalu_length + + trailing_zero_bytes; +#if 0 + if( probe ) + { + fprintf( stderr, "NALU type: %"PRIu8" \n", nalu_type ); + fprintf( stderr, " NALU header position: %"PRIx64" \n", h264_imp->sc_head_pos + start_code_length ); + fprintf( stderr, " EBSP position: %"PRIx64" \n", h264_imp->sc_head_pos + start_code_length + nuh.length ); + fprintf( stderr, " EBSP length: %"PRIx64" (%"PRIu64") \n", nalu_length - nuh.length, nalu_length - nuh.length ); + fprintf( stderr, " trailing_zero_bytes: %"PRIx64" \n", trailing_zero_bytes ); + fprintf( stderr, " Next start code position: %"PRIx64"\n", next_sc_head_pos ); + } +#endif + if( nalu_type == H264_NALU_TYPE_FD ) + { + /* We don't support streams with both filler and HRD yet. + * Otherwise, just skip filler because 'avc1' and 'avc2' samples are forbidden to use filler. */ + if( info->sps.vui.hrd.present ) + return h264_get_au_internal_failed( h264_imp, au, complete_au, LSMASH_ERR_PATCH_WELCOME ); + } + else if( (nalu_type >= H264_NALU_TYPE_SLICE_N_IDR && nalu_type <= H264_NALU_TYPE_SPS_EXT) + || nalu_type == H264_NALU_TYPE_SLICE_AUX ) + { + int err; + /* Increase the buffer if needed. */ + uint64_t possible_au_length = au->incomplete_length + NALU_DEFAULT_NALU_LENGTH_SIZE + nalu_length; + if( sb->bank->buffer_size < possible_au_length + && (err = h264_supplement_buffer( sb, au, 2 * possible_au_length )) < 0 ) + { + lsmash_log( importer, LSMASH_LOG_ERROR, "failed to increase the buffer size.\n" ); + return h264_get_au_internal_failed( h264_imp, au, complete_au, err ); + } + /* Get the EBSP of the current NALU here. + * AVC elemental stream defined in 14496-15 can recognizes from 0 to 13, and 19 of nal_unit_type. + * We don't support SVC and MVC elemental stream defined in 14496-15 yet. */ + uint8_t *nalu = lsmash_bs_get_buffer_data( bs ) + start_code_length; + if( nalu_type >= H264_NALU_TYPE_SLICE_N_IDR && nalu_type <= H264_NALU_TYPE_SLICE_IDR ) + { + /* VCL NALU (slice) */ + h264_slice_info_t prev_slice = *slice; + if( (err = h264_parse_slice( info, &nuh, sb->rbsp, nalu + nuh.length, nalu_length - nuh.length )) < 0 ) + return h264_get_au_internal_failed( h264_imp, au, complete_au, err ); + if( probe && info->avcC_pending ) + { + /* Copy and append a Codec Specific info. */ + if( (err = h264_store_codec_specific( h264_imp, &info->avcC_param )) < 0 ) + return err; + } + if( (err = h264_move_pending_avcC_param( info )) < 0 ) + return err; + if( prev_slice.present ) + { + /* Check whether the AU that contains the previous VCL NALU completed or not. */ + if( h264_find_au_delimit_by_slice_info( slice, &prev_slice ) ) + { + /* The current NALU is the first VCL NALU of the primary coded picture of an new AU. + * Therefore, the previous slice belongs to the AU you want at this time. */ + h264_update_picture_info( info, picture, &prev_slice, &info->sei ); + complete_au = h264_complete_au( au, probe ); + } + else + h264_update_picture_info_for_slice( info, picture, &prev_slice ); + } + h264_append_nalu_to_au( au, nalu, nalu_length, probe ); + slice->present = 1; + } + else + { + if( h264_find_au_delimit_by_nalu_type( nalu_type, info->prev_nalu_type ) ) + { + /* The last slice belongs to the AU you want at this time. */ + h264_update_picture_info( info, picture, slice, &info->sei ); + complete_au = h264_complete_au( au, probe ); + } + switch( nalu_type ) + { + case H264_NALU_TYPE_SEI : + { + if( (err = h264_parse_sei( info->bits, &info->sps, &info->sei, sb->rbsp, + nalu + nuh.length, + nalu_length - nuh.length )) < 0 ) + return h264_get_au_internal_failed( h264_imp, au, complete_au, err ); + h264_append_nalu_to_au( au, nalu, nalu_length, probe ); + break; + } + case H264_NALU_TYPE_SPS : + if( (err = h264_try_to_append_parameter_set( info, H264_PARAMETER_SET_TYPE_SPS, nalu, nalu_length )) < 0 ) + return h264_get_au_internal_failed( h264_imp, au, complete_au, err ); + break; + case H264_NALU_TYPE_PPS : + if( (err = h264_try_to_append_parameter_set( info, H264_PARAMETER_SET_TYPE_PPS, nalu, nalu_length )) < 0 ) + return h264_get_au_internal_failed( h264_imp, au, complete_au, err ); + break; + case H264_NALU_TYPE_AUD : /* We drop access unit delimiters. */ + break; + case H264_NALU_TYPE_SPS_EXT : + if( (err = h264_try_to_append_parameter_set( info, H264_PARAMETER_SET_TYPE_SPSEXT, nalu, nalu_length )) < 0 ) + return h264_get_au_internal_failed( h264_imp, au, complete_au, err ); + break; + default : + h264_append_nalu_to_au( au, nalu, nalu_length, probe ); + break; + } + if( info->avcC_pending ) + importer->status = IMPORTER_CHANGE; + } + } + /* Move to the first byte of the next start code. */ + info->prev_nalu_type = nalu_type; + if( lsmash_bs_read_seek( bs, next_sc_head_pos, SEEK_SET ) != next_sc_head_pos ) + { + lsmash_log( importer, LSMASH_LOG_ERROR, "failed to seek the next start code.\n" ); + return h264_get_au_internal_failed( h264_imp, au, complete_au, LSMASH_ERR_NAMELESS ); + } + /* Check if no more data to read from the stream. */ + if( !lsmash_bs_is_end( bs, NALU_SHORT_START_CODE_LENGTH ) ) + h264_imp->sc_head_pos = next_sc_head_pos; + /* If there is no more data in the stream, and flushed chunk of NALUs, flush it as complete AU here. */ + else if( au->incomplete_length && au->length == 0 ) + { + h264_update_picture_info( info, picture, slice, &info->sei ); + h264_complete_au( au, probe ); + return h264_get_au_internal_succeeded( h264_imp, au ); + } + if( complete_au ) + return h264_get_au_internal_succeeded( h264_imp, au ); + } +} + +static inline void h264_importer_check_eof( importer_t *importer, h264_access_unit_t *au ) +{ + /* AVC byte stream NALU consists of at least 4 bytes (start-code + NALU-header). */ + if( lsmash_bs_is_end( importer->bs, NALU_SHORT_START_CODE_LENGTH ) && au->incomplete_length == 0 ) + importer->status = IMPORTER_EOF; + else if( importer->status != IMPORTER_CHANGE ) + importer->status = IMPORTER_OK; +} + +static int h264_importer_get_accessunit +( + importer_t *importer, + uint32_t track_number, + lsmash_sample_t **p_sample +) +{ + if( !importer->info ) + return LSMASH_ERR_NAMELESS; + if( track_number != 1 ) + return LSMASH_ERR_FUNCTION_PARAM; + h264_importer_t *h264_imp = (h264_importer_t *)importer->info; + h264_info_t *info = &h264_imp->info; + importer_status current_status = importer->status; + if( current_status == IMPORTER_ERROR ) + return LSMASH_ERR_NAMELESS; + if( current_status == IMPORTER_EOF ) + return IMPORTER_EOF; + int err = h264_get_access_unit_internal( importer, 0 ); + if( err < 0 ) + { + importer->status = IMPORTER_ERROR; + return err; + } + h264_importer_check_eof( importer, &info->au ); + if( importer->status == IMPORTER_CHANGE && !info->avcC_pending ) + current_status = IMPORTER_CHANGE; + if( current_status == IMPORTER_CHANGE ) + { + /* Update the active summary. */ + lsmash_codec_specific_t *cs = (lsmash_codec_specific_t *)lsmash_list_get_entry_data( h264_imp->avcC_list, ++ h264_imp->avcC_number ); + if( !cs ) + return LSMASH_ERR_NAMELESS; + lsmash_h264_specific_parameters_t *avcC_param = (lsmash_h264_specific_parameters_t *)cs->data.structured; + lsmash_video_summary_t *summary = h264_create_summary( avcC_param, &info->sps, h264_imp->max_au_length ); + if( !summary ) + return LSMASH_ERR_NAMELESS; + lsmash_list_remove_entry( importer->summaries, track_number ); + if( lsmash_list_add_entry( importer->summaries, summary ) < 0 ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return LSMASH_ERR_MEMORY_ALLOC; + } + importer->status = IMPORTER_OK; + } + lsmash_sample_t *sample = lsmash_create_sample( h264_imp->max_au_length ); + if( !sample ) + return LSMASH_ERR_MEMORY_ALLOC; + *p_sample = sample; + h264_access_unit_t *au = &info->au; + h264_picture_info_t *picture = &au->picture; + sample->dts = h264_imp->ts_list.timestamp[ au->number - 1 ].dts; + sample->cts = h264_imp->ts_list.timestamp[ au->number - 1 ].cts; + if( au->number < h264_imp->num_undecodable ) + sample->prop.leading = ISOM_SAMPLE_IS_UNDECODABLE_LEADING; + else + sample->prop.leading = + picture->independent ? ISOM_SAMPLE_IS_NOT_LEADING + : sample->cts >= h264_imp->last_intra_cts ? ISOM_SAMPLE_IS_NOT_LEADING + : sample->cts < h264_imp->last_sync_cts ? ISOM_SAMPLE_IS_DECODABLE_LEADING + : ISOM_SAMPLE_IS_UNDECODABLE_LEADING; + if( h264_imp->composition_reordering_present && !picture->disposable && !picture->idr ) + sample->prop.allow_earlier = QT_SAMPLE_EARLIER_PTS_ALLOWED; + sample->prop.independent = picture->independent ? ISOM_SAMPLE_IS_INDEPENDENT : ISOM_SAMPLE_IS_NOT_INDEPENDENT; + sample->prop.disposable = picture->disposable ? ISOM_SAMPLE_IS_DISPOSABLE : ISOM_SAMPLE_IS_NOT_DISPOSABLE; + sample->prop.redundant = picture->has_redundancy ? ISOM_SAMPLE_HAS_REDUNDANCY : ISOM_SAMPLE_HAS_NO_REDUNDANCY; + sample->prop.post_roll.identifier = picture->frame_num; + if( picture->random_accessible ) + { + if( picture->idr ) + sample->prop.ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; + else if( picture->recovery_frame_cnt ) + { + sample->prop.ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_POST_ROLL_START; + sample->prop.post_roll.complete = (picture->frame_num + picture->recovery_frame_cnt) % info->sps.MaxFrameNum; + } + else + { + sample->prop.ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_RAP; + if( !picture->broken_link_flag ) + sample->prop.ra_flags |= QT_SAMPLE_RANDOM_ACCESS_FLAG_PARTIAL_SYNC; + } + } + if( picture->independent ) + h264_imp->last_intra_cts = sample->cts; + if( picture->idr ) + h264_imp->last_sync_cts = sample->cts; + sample->length = au->length; + memcpy( sample->data, au->data, au->length ); + return current_status; +} + +static void nalu_deduplicate_poc +( + nal_pic_timing_t *npt, + uint32_t *max_composition_delay, + uint32_t num_access_units, + uint32_t max_num_reorder_pics +) +{ + /* Deduplicate POCs. */ + int64_t poc_offset = 0; + int64_t poc_min = 0; + int64_t invalid_poc_min = 0; + uint32_t last_poc_reset = UINT32_MAX; + uint32_t invalid_poc_start = 0; + int invalid_poc_present = 0; + for( uint32_t i = 0; ; i++ ) + { + if( i < num_access_units && npt[i].poc != 0 && !npt[i].reset ) + { + /* poc_offset is not added to each POC here. + * It is done when we encounter the next coded video sequence. */ + if( npt[i].poc < 0 ) + { + /* Pictures with negative POC shall precede IDR-picture in composition order. + * The minimum POC is added to poc_offset when we encounter the next coded video sequence. */ + if( last_poc_reset == UINT32_MAX || i > last_poc_reset + max_num_reorder_pics ) + { + if( !invalid_poc_present ) + { + invalid_poc_present = 1; + invalid_poc_start = i; + } + if( invalid_poc_min > npt[i].poc ) + invalid_poc_min = npt[i].poc; + } + else if( poc_min > npt[i].poc ) + { + poc_min = npt[i].poc; + *max_composition_delay = LSMASH_MAX( *max_composition_delay, i - last_poc_reset ); + } + } + continue; + } + /* Encountered a new coded video sequence or no more POCs. + * Add poc_offset to each POC of the previous coded video sequence. */ + poc_offset -= poc_min; + int64_t poc_max = 0; + for( uint32_t j = last_poc_reset; j < i + !!npt[i].reset; j++ ) + if( npt[j].poc >= 0 || (j <= last_poc_reset + max_num_reorder_pics) ) + { + npt[j].poc += poc_offset; + if( poc_max < npt[j].poc ) + poc_max = npt[j].poc; + } + poc_offset = poc_max + 1; + if( invalid_poc_present ) + { + /* Pictures with invalid negative POC is probably supposed to be composited + * both before the next coded video sequence and after the current one. */ + poc_offset -= invalid_poc_min; + for( uint32_t j = invalid_poc_start; j < i + !!npt[i].reset; j++ ) + if( npt[j].poc < 0 ) + { + npt[j].poc += poc_offset; + if( poc_max < npt[j].poc ) + poc_max = npt[j].poc; + } + invalid_poc_present = 0; + invalid_poc_start = 0; + invalid_poc_min = 0; + poc_offset = poc_max + 1; + } + if( i < num_access_units ) + { + if( npt[i].reset ) + npt[i].poc = 0; + poc_min = 0; + last_poc_reset = i; + } + else + break; /* no more POCs */ + } +} + +static void nalu_generate_timestamps_from_poc +( + importer_t *importer, + lsmash_media_ts_t *timestamp, + nal_pic_timing_t *npt, + uint8_t *composition_reordering_present, + uint32_t *last_delta, + uint32_t max_composition_delay, + uint32_t num_access_units +) +{ + /* Check if composition delay derived from reordering is present. */ + if( max_composition_delay == 0 ) + { + *composition_reordering_present = 0; + for( uint32_t i = 1; i < num_access_units; i++ ) + if( npt[i].poc < npt[i - 1].poc ) + { + *composition_reordering_present = 1; + break; + } + } + else + *composition_reordering_present = 1; + if( *composition_reordering_present ) + { + /* Generate timestamps. + * Here, DTSs and CTSs are temporary values for sort. */ + for( uint32_t i = 0; i < num_access_units; i++ ) + { + timestamp[i].cts = (uint64_t)npt[i].poc; + timestamp[i].dts = (uint64_t)i; + } + qsort( timestamp, num_access_units, sizeof(lsmash_media_ts_t), (int(*)( const void *, const void * ))lsmash_compare_cts ); + /* Check POC gap in output order. */ + lsmash_class_t *logger = &(lsmash_class_t){ .name = importer->class->name }; + for( uint32_t i = 1; i < num_access_units; i++ ) + if( timestamp[i].cts > timestamp[i - 1].cts + npt[i - 1].poc_delta ) + lsmash_log( &logger, LSMASH_LOG_WARNING, + "POC gap is detected at picture %"PRIu64". Maybe some pictures are lost.\n", timestamp[i].dts ); + /* Get the maximum composition delay derived from reordering. */ + for( uint32_t i = 0; i < num_access_units; i++ ) + if( i < timestamp[i].dts ) + { + uint32_t composition_delay = timestamp[i].dts - i; + max_composition_delay = LSMASH_MAX( max_composition_delay, composition_delay ); + } + } + /* Generate timestamps. */ + if( max_composition_delay ) + { + uint64_t *ts_buffer = (uint64_t *)lsmash_malloc( (num_access_units + max_composition_delay) * sizeof(uint64_t) ); + if( !ts_buffer ) + { + /* It seems that there is no enough memory to generate more appropriate timestamps. + * Anyway, generate CTSs and DTSs. */ + for( uint32_t i = 0; i < num_access_units; i++ ) + timestamp[i].cts = i + max_composition_delay; + qsort( timestamp, num_access_units, sizeof(lsmash_media_ts_t), (int(*)( const void *, const void * ))lsmash_compare_dts ); + *last_delta = 1; + return; + } + uint64_t *reorder_cts = ts_buffer; + uint64_t *prev_reorder_cts = ts_buffer + num_access_units; + *last_delta = npt[num_access_units - 1].delta; + /* Generate CTSs. */ + timestamp[0].cts = 0; + for( uint32_t i = 1; i < num_access_units; i++ ) + timestamp[i].cts = timestamp[i - 1].cts + npt[i - 1].delta; + int64_t composition_delay_time = timestamp[max_composition_delay].cts; + for( uint32_t i = 0; i < num_access_units; i++ ) + { + timestamp[i].cts += composition_delay_time; + reorder_cts[i] = timestamp[i].cts; + } + /* Generate DTSs. */ + qsort( timestamp, num_access_units, sizeof(lsmash_media_ts_t), (int(*)( const void *, const void * ))lsmash_compare_dts ); + for( uint32_t i = 0; i < num_access_units; i++ ) + { + timestamp[i].dts = i <= max_composition_delay + ? reorder_cts[i] - composition_delay_time + : prev_reorder_cts[(i - max_composition_delay) % max_composition_delay]; + prev_reorder_cts[i % max_composition_delay] = reorder_cts[i]; + } + lsmash_free( ts_buffer ); +#if 0 + fprintf( stderr, "max_composition_delay=%"PRIu32", composition_delay_time=%"PRIu64"\n", + max_composition_delay, composition_delay_time ); +#endif + } + else + { + timestamp[0].dts = 0; + timestamp[0].cts = 0; + for( uint32_t i = 1; i < num_access_units; i++ ) + { + timestamp[i].dts = timestamp[i - 1].dts + npt[i - 1].delta; + timestamp[i].cts = timestamp[i - 1].cts + npt[i - 1].delta; + } + *last_delta = npt[num_access_units - 1].delta; + } +} + +static void nalu_reduce_timescale +( + lsmash_media_ts_t *timestamp, + nal_pic_timing_t *npt, + uint32_t *last_delta, + uint32_t *timescale, + uint32_t num_access_units +) +{ + uint64_t gcd_delta = *timescale; + for( uint32_t i = 0; i < num_access_units && gcd_delta > 1; i++ ) + gcd_delta = lsmash_get_gcd( gcd_delta, npt[i].delta ); + if( gcd_delta > 1 ) + { + for( uint32_t i = 0; i < num_access_units; i++ ) + { + timestamp[i].dts /= gcd_delta; + timestamp[i].cts /= gcd_delta; + } + *last_delta /= gcd_delta; + *timescale /= gcd_delta; + } +#if 0 + for( uint32_t i = 0; i < num_access_units; i++ ) + fprintf( stderr, "Timestamp[%"PRIu32"]: POC=%"PRId64", DTS=%"PRIu64", CTS=%"PRIu64"\n", + i, npt[i].poc, timestamp[i].dts, timestamp[i].cts ); +#endif +} + +static lsmash_video_summary_t *h264_setup_first_summary +( + importer_t *importer +) +{ + h264_importer_t *h264_imp = (h264_importer_t *)importer->info; + lsmash_codec_specific_t *cs = (lsmash_codec_specific_t *)lsmash_list_get_entry_data( h264_imp->avcC_list, ++ h264_imp->avcC_number ); + if( !cs || !cs->data.structured ) + { + lsmash_destroy_codec_specific_data( cs ); + return NULL; + } + lsmash_video_summary_t *summary = h264_create_summary( (lsmash_h264_specific_parameters_t *)cs->data.structured, + &h264_imp->info.sps, h264_imp->max_au_length ); + if( !summary ) + { + lsmash_destroy_codec_specific_data( cs ); + return NULL; + } + if( lsmash_list_add_entry( importer->summaries, summary ) < 0 ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return NULL; + } + summary->sample_per_field = h264_imp->field_pic_present; + return summary; +} + +static int h264_analyze_whole_stream +( + importer_t *importer +) +{ + /* Parse all NALU in the stream for preparation of calculating timestamps. */ + uint32_t npt_alloc = (1 << 12) * sizeof(nal_pic_timing_t); + nal_pic_timing_t *npt = lsmash_malloc( npt_alloc ); + if( !npt ) + return LSMASH_ERR_MEMORY_ALLOC; + uint32_t picture_stats[H264_PICTURE_TYPE_NONE + 1] = { 0 }; + uint32_t num_access_units = 0; + lsmash_class_t *logger = &(lsmash_class_t){ "H.264" }; + lsmash_log( &logger, LSMASH_LOG_INFO, "Analyzing stream as H.264\r" ); + h264_importer_t *h264_imp = (h264_importer_t *)importer->info; + h264_info_t *info = &h264_imp->info; + importer->status = IMPORTER_OK; + int err = LSMASH_ERR_MEMORY_ALLOC; + while( importer->status != IMPORTER_EOF ) + { +#if 0 + lsmash_log( &logger, LSMASH_LOG_INFO, "Analyzing stream as H.264: %"PRIu32"\n", num_access_units + 1 ); +#endif + h264_picture_info_t *picture = &info->au.picture; + h264_picture_info_t prev_picture = *picture; + if( (err = h264_get_access_unit_internal( importer, 1 )) < 0 + || (err = h264_calculate_poc( info, picture, &prev_picture )) < 0 ) + goto fail; + h264_importer_check_eof( importer, &info->au ); + if( npt_alloc <= num_access_units * sizeof(nal_pic_timing_t) ) + { + uint32_t alloc = 2 * num_access_units * sizeof(nal_pic_timing_t); + nal_pic_timing_t *temp = (nal_pic_timing_t *)lsmash_realloc( npt, alloc ); + if( !temp ) + goto fail; + npt = temp; + npt_alloc = alloc; + } + h264_imp->field_pic_present |= picture->field_pic_flag; + npt[num_access_units].poc = picture->PicOrderCnt; + npt[num_access_units].delta = picture->delta; + npt[num_access_units].poc_delta = picture->field_pic_flag ? 1 : 2; + npt[num_access_units].reset = picture->has_mmco5; + ++num_access_units; + h264_imp->max_au_length = LSMASH_MAX( info->au.length, h264_imp->max_au_length ); + if( picture->idr ) + ++picture_stats[H264_PICTURE_TYPE_IDR]; + else if( picture->type >= H264_PICTURE_TYPE_NONE ) + ++picture_stats[H264_PICTURE_TYPE_NONE]; + else + ++picture_stats[ picture->type ]; + } + lsmash_log_refresh_line( &logger ); + lsmash_log( &logger, LSMASH_LOG_INFO, + "IDR: %"PRIu32", I: %"PRIu32", P: %"PRIu32", B: %"PRIu32", " + "SI: %"PRIu32", SP: %"PRIu32", Unknown: %"PRIu32"\n", + picture_stats[H264_PICTURE_TYPE_IDR ], + picture_stats[H264_PICTURE_TYPE_I ], + picture_stats[H264_PICTURE_TYPE_I_P ], + picture_stats[H264_PICTURE_TYPE_I_P_B ], + picture_stats[H264_PICTURE_TYPE_SI ] + + picture_stats[H264_PICTURE_TYPE_I_SI ], + picture_stats[H264_PICTURE_TYPE_SI_SP ] + + picture_stats[H264_PICTURE_TYPE_I_SI_P_SP ] + + picture_stats[H264_PICTURE_TYPE_I_SI_P_SP_B], + picture_stats[H264_PICTURE_TYPE_NONE ] ); + /* Copy and append the last Codec Specific info. */ + if( (err = h264_store_codec_specific( h264_imp, &info->avcC_param )) < 0 ) + goto fail; + /* Set up the first summary. */ + lsmash_video_summary_t *summary = h264_setup_first_summary( importer ); + if( !summary ) + { + err = LSMASH_ERR_NAMELESS; + goto fail; + } + /* Allocate timestamps. */ + lsmash_media_ts_t *timestamp = lsmash_malloc( num_access_units * sizeof(lsmash_media_ts_t) ); + if( !timestamp ) + goto fail; + /* Count leading samples that are undecodable. */ + for( uint32_t i = 0; i < num_access_units; i++ ) + { + if( npt[i].poc == 0 ) + break; + ++ h264_imp->num_undecodable; + } + /* Deduplicate POCs. */ + uint32_t max_composition_delay = 0; + nalu_deduplicate_poc( npt, &max_composition_delay, num_access_units, 32 ); + /* Generate timestamps. */ + nalu_generate_timestamps_from_poc( importer, timestamp, npt, + &h264_imp->composition_reordering_present, + &h264_imp->last_delta, + max_composition_delay, num_access_units ); + nalu_reduce_timescale( timestamp, npt, &h264_imp->last_delta, &summary->timescale, num_access_units ); + lsmash_free( npt ); + h264_imp->ts_list.sample_count = num_access_units; + h264_imp->ts_list.timestamp = timestamp; + return 0; +fail: + lsmash_log_refresh_line( &logger ); + lsmash_free( npt ); + return err; +} + +static int h264_importer_probe( importer_t *importer ) +{ + /* Find the first start code. */ + h264_importer_t *h264_imp = create_h264_importer( importer ); + if( !h264_imp ) + return LSMASH_ERR_MEMORY_ALLOC; + lsmash_bs_t *bs = importer->bs; + uint64_t first_sc_head_pos = nalu_find_first_start_code( bs ); + int err; + if( first_sc_head_pos == NALU_NO_START_CODE_FOUND ) + { + err = LSMASH_ERR_INVALID_DATA; + goto fail; + } + else if( first_sc_head_pos == NALU_IO_ERROR ) + { + err = LSMASH_ERR_IO; + goto fail; + } + /* OK. It seems the stream has a long start code of H.264. */ + importer->info = h264_imp; + h264_info_t *info = &h264_imp->info; + lsmash_bs_read_seek( bs, first_sc_head_pos, SEEK_SET ); + h264_imp->sc_head_pos = first_sc_head_pos; + if( (err = h264_analyze_whole_stream( importer )) < 0 ) + goto fail; + /* Go back to the start code of the first NALU. */ + importer->status = IMPORTER_OK; + lsmash_bs_read_seek( bs, first_sc_head_pos, SEEK_SET ); + h264_imp->sc_head_pos = first_sc_head_pos; + info->prev_nalu_type = H264_NALU_TYPE_UNSPECIFIED0; + uint8_t *temp_au = info->au.data; + uint8_t *temp_incomplete_au = info->au.incomplete_data; + memset( &info->au, 0, sizeof(h264_access_unit_t) ); + info->au.data = temp_au; + info->au.incomplete_data = temp_incomplete_au; + memset( &info->slice, 0, sizeof(h264_slice_info_t) ); + memset( &info->sps, 0, sizeof(h264_sps_t) ); + memset( &info->pps, 0, sizeof(h264_pps_t) ); + lsmash_list_remove_entries( info->avcC_param.parameter_sets->sps_list ); + lsmash_list_remove_entries( info->avcC_param.parameter_sets->pps_list ); + lsmash_list_remove_entries( info->avcC_param.parameter_sets->spsext_list ); + lsmash_destroy_h264_parameter_sets( &info->avcC_param_next ); + return 0; +fail: + remove_h264_importer( h264_imp ); + importer->info = NULL; + lsmash_list_remove_entries( importer->summaries ); + return err; +} + +static uint32_t h264_importer_get_last_delta( importer_t *importer, uint32_t track_number ) +{ + debug_if( !importer || !importer->info ) + return 0; + h264_importer_t *h264_imp = (h264_importer_t *)importer->info; + if( !h264_imp || track_number != 1 || importer->status != IMPORTER_EOF ) + return 0; + return h264_imp->ts_list.sample_count + ? h264_imp->last_delta + : UINT32_MAX; /* arbitrary */ +} + +const importer_functions h264_importer = +{ + { "H.264", offsetof( importer_t, log_level ) }, + 1, + h264_importer_probe, + h264_importer_get_accessunit, + h264_importer_get_last_delta, + h264_importer_cleanup +}; + +/*************************************************************************** + HEVC importer + ITU-T Recommendation H.265 (04/13) + ISO/IEC 14496-15:2014 +***************************************************************************/ +#include "codecs/hevc.h" + +typedef struct +{ + hevc_info_t info; + lsmash_entry_list_t hvcC_list[1]; /* stored as lsmash_codec_specific_t */ + lsmash_media_ts_list_t ts_list; + uint32_t max_au_length; + uint32_t num_undecodable; + uint32_t hvcC_number; + uint32_t last_delta; + uint64_t last_intra_cts; + uint64_t sc_head_pos; + uint8_t composition_reordering_present; + uint8_t field_pic_present; + uint8_t max_TemporalId; +} hevc_importer_t; + +static void remove_hevc_importer( hevc_importer_t *hevc_imp ) +{ + if( !hevc_imp ) + return; + lsmash_list_remove_entries( hevc_imp->hvcC_list ); + hevc_cleanup_parser( &hevc_imp->info ); + lsmash_free( hevc_imp->ts_list.timestamp ); + lsmash_free( hevc_imp ); +} + +static void hevc_importer_cleanup( importer_t *importer ) +{ + debug_if( importer && importer->info ) + remove_hevc_importer( importer->info ); +} + +static hevc_importer_t *create_hevc_importer( importer_t *importer ) +{ + hevc_importer_t *hevc_imp = lsmash_malloc_zero( sizeof(hevc_importer_t) ); + if( !hevc_imp ) + return NULL; + if( hevc_setup_parser( &hevc_imp->info, 0 ) < 0 ) + { + remove_hevc_importer( hevc_imp ); + return NULL; + } + lsmash_list_init( hevc_imp->hvcC_list, lsmash_destroy_codec_specific_data ); + hevc_imp->info.eos = 1; + return hevc_imp; +} + +static inline int hevc_complete_au( hevc_access_unit_t *au, int probe ) +{ + if( !au->picture.has_primary || au->incomplete_length == 0 ) + return 0; + if( !probe ) + memcpy( au->data, au->incomplete_data, au->incomplete_length ); + au->TemporalId = au->picture.TemporalId; + au->length = au->incomplete_length; + au->incomplete_length = 0; + au->picture.has_primary = 0; + return 1; +} + +static void hevc_append_nalu_to_au( hevc_access_unit_t *au, uint8_t *src_nalu, uint32_t nalu_length, int probe ) +{ + if( !probe ) + { + uint8_t *dst_nalu = au->incomplete_data + au->incomplete_length + NALU_DEFAULT_NALU_LENGTH_SIZE; + for( int i = NALU_DEFAULT_NALU_LENGTH_SIZE; i; i-- ) + *(dst_nalu - i) = (nalu_length >> ((i - 1) * 8)) & 0xff; + memcpy( dst_nalu, src_nalu, nalu_length ); + } + /* Note: picture->incomplete_au_length shall be 0 immediately after AU has completed. + * Therefore, possible_au_length in hevc_get_access_unit_internal() can't be used here + * to avoid increasing AU length monotonously through the entire stream. */ + au->incomplete_length += NALU_DEFAULT_NALU_LENGTH_SIZE + nalu_length; +} + +static int hevc_get_au_internal_succeeded( hevc_importer_t *hevc_imp, hevc_access_unit_t *au ) +{ + au->number += 1; + return 0; +} + +static int hevc_get_au_internal_failed( hevc_importer_t *hevc_imp, hevc_access_unit_t *au, int complete_au, int ret ) +{ + if( complete_au ) + au->number += 1; + return ret; +} + +static lsmash_video_summary_t *hevc_create_summary +( + lsmash_hevc_specific_parameters_t *param, + hevc_sps_t *sps, + uint32_t max_au_length +) +{ + lsmash_video_summary_t *summary = (lsmash_video_summary_t *)lsmash_create_summary( LSMASH_SUMMARY_TYPE_VIDEO ); + if( !summary ) + return NULL; + /* Update summary here. + * max_au_length is set at the last of hevc_importer_probe function. */ + lsmash_codec_specific_t *specific = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_HEVC, + LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED ); + if( !specific ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return NULL; + } + specific->data.unstructured = lsmash_create_hevc_specific_info( param, &specific->size ); + if( !specific->data.unstructured + || lsmash_list_add_entry( &summary->opaque->list, specific ) < 0 ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + lsmash_destroy_codec_specific_data( specific ); + return NULL; + } + summary->sample_type = ISOM_CODEC_TYPE_HVC1_VIDEO; + summary->max_au_length = max_au_length; + summary->timescale = sps->vui.time_scale; + summary->timebase = sps->vui.num_units_in_tick; + summary->vfr = (param->constantFrameRate == 0); + summary->sample_per_field = 0; + summary->width = sps->cropped_width; + summary->height = sps->cropped_height; + summary->par_h = sps->vui.sar_width; + summary->par_v = sps->vui.sar_height; + summary->color.primaries_index = sps->vui.colour_primaries != 2 ? sps->vui.colour_primaries : 0; + summary->color.transfer_index = sps->vui.transfer_characteristics != 2 ? sps->vui.transfer_characteristics : 0; + summary->color.matrix_index = sps->vui.matrix_coeffs != 2 ? sps->vui.matrix_coeffs : 0; + summary->color.full_range = sps->vui.video_full_range_flag; + lsmash_convert_crop_into_clap( sps->vui.def_disp_win_offset, summary->width, summary->height, &summary->clap ); + return summary; +} + +static int hevc_store_codec_specific +( + hevc_importer_t *hevc_imp, + lsmash_hevc_specific_parameters_t *hvcC_param +) +{ + lsmash_codec_specific_t *src_cs = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_HEVC, + LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !src_cs ) + return LSMASH_ERR_NAMELESS; + lsmash_hevc_specific_parameters_t *src_param = (lsmash_hevc_specific_parameters_t *)src_cs->data.structured; + *src_param = *hvcC_param; + lsmash_codec_specific_t *dst_cs = lsmash_convert_codec_specific_format( src_cs, LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + src_param->parameter_arrays = NULL; /* Avoid freeing parameter arrays within hvcC_param. */ + lsmash_destroy_codec_specific_data( src_cs ); + if( !dst_cs ) + { + lsmash_destroy_codec_specific_data( dst_cs ); + return LSMASH_ERR_NAMELESS; + } + if( lsmash_list_add_entry( hevc_imp->hvcC_list, dst_cs ) < 0 ) + { + lsmash_destroy_codec_specific_data( dst_cs ); + return LSMASH_ERR_MEMORY_ALLOC; + } + return 0; +} + +static inline void hevc_new_access_unit( hevc_access_unit_t *au ) +{ + au->length = 0; + au->picture.type = HEVC_PICTURE_TYPE_NONE; + au->picture.random_accessible = 0; + au->picture.recovery_poc_cnt = 0; +} + +/* If probe equals 0, don't get the actual data (EBPS) of an access unit and only parse NALU. */ +static int hevc_get_access_unit_internal +( + importer_t *importer, + int probe +) +{ + hevc_importer_t *hevc_imp = (hevc_importer_t *)importer->info; + hevc_info_t *info = &hevc_imp->info; + hevc_slice_info_t *slice = &info->slice; + hevc_access_unit_t *au = &info->au; + hevc_picture_info_t *picture = &au->picture; + hevc_stream_buffer_t *sb = &info->buffer; + lsmash_bs_t *bs = importer->bs; + int complete_au = 0; + hevc_new_access_unit( au ); + while( 1 ) + { + hevc_nalu_header_t nuh; + uint64_t start_code_length; + uint64_t trailing_zero_bytes; + uint64_t nalu_length = hevc_find_next_start_code( bs, &nuh, &start_code_length, &trailing_zero_bytes ); + if( nalu_length == NALU_NO_START_CODE_FOUND ) + { + /* For the last NALU. + * This NALU already has been appended into the latest access unit and parsed. */ + hevc_update_picture_info( info, picture, slice, &info->sps, &info->sei ); + complete_au = hevc_complete_au( au, probe ); + if( complete_au ) + return hevc_get_au_internal_succeeded( hevc_imp, au ); + else + return hevc_get_au_internal_failed( hevc_imp, au, complete_au, LSMASH_ERR_INVALID_DATA ); + } + uint8_t nalu_type = nuh.nal_unit_type; + uint64_t next_sc_head_pos = hevc_imp->sc_head_pos + + start_code_length + + nalu_length + + trailing_zero_bytes; +#if 0 + if( probe ) + { + fprintf( stderr, "NALU type: %"PRIu8" \n", nalu_type ); + fprintf( stderr, " NALU header position: %"PRIx64" \n", hevc_imp->sc_head_pos + start_code_length ); + fprintf( stderr, " EBSP position: %"PRIx64" \n", hevc_imp->sc_head_pos + start_code_length + nuh.length ); + fprintf( stderr, " EBSP length: %"PRIx64" (%"PRIu64") \n", nalu_length - nuh.length, nalu_length - nuh.length ); + fprintf( stderr, " trailing_zero_bytes: %"PRIx64" \n", trailing_zero_bytes ); + fprintf( stderr, " Next start code position: %"PRIx64"\n", next_sc_head_pos ); + } +#endif + /* Check if the end of sequence. Used for POC calculation. */ + info->eos |= info->prev_nalu_type == HEVC_NALU_TYPE_EOS + || info->prev_nalu_type == HEVC_NALU_TYPE_EOB; + /* Process the current NALU by its type. */ + if( nalu_type == HEVC_NALU_TYPE_FD ) + { + /* We don't support streams with both filler and HRD yet. Otherwise, just skip filler. */ + if( info->sps.vui.hrd.present ) + return hevc_get_au_internal_failed( hevc_imp, au, complete_au, LSMASH_ERR_PATCH_WELCOME ); + } + else if( nalu_type <= HEVC_NALU_TYPE_RASL_R + || (nalu_type >= HEVC_NALU_TYPE_BLA_W_LP && nalu_type <= HEVC_NALU_TYPE_CRA) + || (nalu_type >= HEVC_NALU_TYPE_VPS && nalu_type <= HEVC_NALU_TYPE_SUFFIX_SEI) ) + { + int err; + /* Increase the buffer if needed. */ + uint64_t possible_au_length = au->incomplete_length + NALU_DEFAULT_NALU_LENGTH_SIZE + nalu_length; + if( sb->bank->buffer_size < possible_au_length + && (err = hevc_supplement_buffer( sb, au, 2 * possible_au_length )) < 0 ) + { + lsmash_log( importer, LSMASH_LOG_ERROR, "failed to increase the buffer size.\n" ); + return hevc_get_au_internal_failed( hevc_imp, au, complete_au, err ); + } + /* Get the EBSP of the current NALU here. */ + uint8_t *nalu = lsmash_bs_get_buffer_data( bs ) + start_code_length; + if( nalu_type <= HEVC_NALU_TYPE_RSV_VCL31 ) + { + /* VCL NALU (slice) */ + hevc_slice_info_t prev_slice = *slice; + if( (err = hevc_parse_slice_segment_header( info, &nuh, sb->rbsp, + nalu + nuh.length, + nalu_length - nuh.length )) < 0 ) + return hevc_get_au_internal_failed( hevc_imp, au, complete_au, err ); + if( probe && info->hvcC_pending ) + { + /* Copy and append a Codec Specific info. */ + if( (err = hevc_store_codec_specific( hevc_imp, &info->hvcC_param )) < 0 ) + return err; + } + if( (err = hevc_move_pending_hvcC_param( info )) < 0 ) + return err; + if( prev_slice.present ) + { + /* Check whether the AU that contains the previous VCL NALU completed or not. */ + if( hevc_find_au_delimit_by_slice_info( info, slice, &prev_slice ) ) + { + /* The current NALU is the first VCL NALU of the primary coded picture of a new AU. + * Therefore, the previous slice belongs to the AU you want at this time. */ + hevc_update_picture_info( info, picture, &prev_slice, &info->sps, &info->sei ); + complete_au = hevc_complete_au( au, probe ); + } + else + hevc_update_picture_info_for_slice( info, picture, &prev_slice ); + } + hevc_append_nalu_to_au( au, nalu, nalu_length, probe ); + slice->present = 1; + } + else + { + if( hevc_find_au_delimit_by_nalu_type( nalu_type, info->prev_nalu_type ) ) + { + /* The last slice belongs to the AU you want at this time. */ + hevc_update_picture_info( info, picture, slice, &info->sps, &info->sei ); + complete_au = hevc_complete_au( au, probe ); + } + switch( nalu_type ) + { + case HEVC_NALU_TYPE_PREFIX_SEI : + case HEVC_NALU_TYPE_SUFFIX_SEI : + { + if( (err = hevc_parse_sei( info->bits, &info->vps, &info->sps, &info->sei, &nuh, + sb->rbsp, nalu + nuh.length, nalu_length - nuh.length )) < 0 ) + return hevc_get_au_internal_failed( hevc_imp, au, complete_au, err ); + hevc_append_nalu_to_au( au, nalu, nalu_length, probe ); + break; + } + case HEVC_NALU_TYPE_VPS : + if( (err = hevc_try_to_append_dcr_nalu( info, HEVC_DCR_NALU_TYPE_VPS, nalu, nalu_length )) < 0 ) + return hevc_get_au_internal_failed( hevc_imp, au, complete_au, err ); + break; + case HEVC_NALU_TYPE_SPS : + if( (err = hevc_try_to_append_dcr_nalu( info, HEVC_DCR_NALU_TYPE_SPS, nalu, nalu_length )) < 0 ) + return hevc_get_au_internal_failed( hevc_imp, au, complete_au, err ); + break; + case HEVC_NALU_TYPE_PPS : + if( (err = hevc_try_to_append_dcr_nalu( info, HEVC_DCR_NALU_TYPE_PPS, nalu, nalu_length )) < 0 ) + return hevc_get_au_internal_failed( hevc_imp, au, complete_au, err ); + break; + case HEVC_NALU_TYPE_AUD : /* We drop access unit delimiters. */ + break; + default : + hevc_append_nalu_to_au( au, nalu, nalu_length, probe ); + break; + } + if( info->hvcC_pending ) + importer->status = IMPORTER_CHANGE; + } + } + /* Move to the first byte of the next start code. */ + info->prev_nalu_type = nalu_type; + if( lsmash_bs_read_seek( bs, next_sc_head_pos, SEEK_SET ) != next_sc_head_pos ) + { + lsmash_log( importer, LSMASH_LOG_ERROR, "failed to seek the next start code.\n" ); + return hevc_get_au_internal_failed( hevc_imp, au, complete_au, LSMASH_ERR_NAMELESS ); + } + if( !lsmash_bs_is_end( bs, NALU_SHORT_START_CODE_LENGTH ) ) + hevc_imp->sc_head_pos = next_sc_head_pos; + /* If there is no more data in the stream, and flushed chunk of NALUs, flush it as complete AU here. */ + else if( au->incomplete_length && au->length == 0 ) + { + hevc_update_picture_info( info, picture, slice, &info->sps, &info->sei ); + hevc_complete_au( au, probe ); + return hevc_get_au_internal_succeeded( hevc_imp, au ); + } + if( complete_au ) + return hevc_get_au_internal_succeeded( hevc_imp, au ); + } +} + +static inline void hevc_importer_check_eof( importer_t *importer, hevc_access_unit_t *au ) +{ + /* HEVC byte stream NALU consists of at least 5 bytes (start-code + NALU-header). */ + if( lsmash_bs_is_end( importer->bs, NALU_SHORT_START_CODE_LENGTH + 1 ) && au->incomplete_length == 0 ) + importer->status = IMPORTER_EOF; + else if( importer->status != IMPORTER_CHANGE ) + importer->status = IMPORTER_OK; +} + +static int hevc_importer_get_accessunit( importer_t *importer, uint32_t track_number, lsmash_sample_t **p_sample ) +{ + if( !importer->info ) + return LSMASH_ERR_NAMELESS; + if( track_number != 1 ) + return LSMASH_ERR_FUNCTION_PARAM; + hevc_importer_t *hevc_imp = (hevc_importer_t *)importer->info; + hevc_info_t *info = &hevc_imp->info; + importer_status current_status = importer->status; + if( current_status == IMPORTER_ERROR ) + return LSMASH_ERR_NAMELESS; + if( current_status == IMPORTER_EOF ) + return IMPORTER_EOF; + int err = hevc_get_access_unit_internal( importer, 0 ); + if( err < 0 ) + { + importer->status = IMPORTER_ERROR; + return err; + } + hevc_importer_check_eof( importer, &info->au ); + if( importer->status == IMPORTER_CHANGE && !info->hvcC_pending ) + current_status = IMPORTER_CHANGE; + if( current_status == IMPORTER_CHANGE ) + { + /* Update the active summary. */ + lsmash_codec_specific_t *cs = (lsmash_codec_specific_t *)lsmash_list_get_entry_data( hevc_imp->hvcC_list, ++ hevc_imp->hvcC_number ); + if( !cs ) + return LSMASH_ERR_NAMELESS; + lsmash_hevc_specific_parameters_t *hvcC_param = (lsmash_hevc_specific_parameters_t *)cs->data.structured; + lsmash_video_summary_t *summary = hevc_create_summary( hvcC_param, &info->sps, hevc_imp->max_au_length ); + if( !summary ) + return LSMASH_ERR_NAMELESS; + lsmash_list_remove_entry( importer->summaries, track_number ); + if( lsmash_list_add_entry( importer->summaries, summary ) < 0 ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return LSMASH_ERR_MEMORY_ALLOC; + } + importer->status = IMPORTER_OK; + } + lsmash_sample_t *sample = lsmash_create_sample( hevc_imp->max_au_length ); + if( !sample ) + return LSMASH_ERR_MEMORY_ALLOC; + *p_sample = sample; + hevc_access_unit_t *au = &info->au; + hevc_picture_info_t *picture = &au->picture; + sample->dts = hevc_imp->ts_list.timestamp[ au->number - 1 ].dts; + sample->cts = hevc_imp->ts_list.timestamp[ au->number - 1 ].cts; + /* Set property of disposability. */ + if( picture->sublayer_nonref && au->TemporalId == hevc_imp->max_TemporalId ) + /* Sub-layer non-reference pictures are not referenced by subsequent pictures of + * the same sub-layer in decoding order. */ + sample->prop.disposable = ISOM_SAMPLE_IS_DISPOSABLE; + else + sample->prop.disposable = ISOM_SAMPLE_IS_NOT_DISPOSABLE; + /* Set property of leading. */ + if( picture->radl || picture->rasl ) + sample->prop.leading = picture->radl ? ISOM_SAMPLE_IS_DECODABLE_LEADING : ISOM_SAMPLE_IS_UNDECODABLE_LEADING; + else + { + if( au->number < hevc_imp->num_undecodable ) + sample->prop.leading = ISOM_SAMPLE_IS_UNDECODABLE_LEADING; + else + { + if( picture->independent || sample->cts >= hevc_imp->last_intra_cts ) + sample->prop.leading = ISOM_SAMPLE_IS_NOT_LEADING; + else + sample->prop.leading = ISOM_SAMPLE_IS_UNDECODABLE_LEADING; + } + } + if( picture->independent ) + hevc_imp->last_intra_cts = sample->cts; + /* Set property of independence. */ + sample->prop.independent = picture->independent ? ISOM_SAMPLE_IS_INDEPENDENT : ISOM_SAMPLE_IS_NOT_INDEPENDENT; + sample->prop.redundant = ISOM_SAMPLE_HAS_NO_REDUNDANCY; + sample->prop.post_roll.identifier = picture->poc; + if( picture->random_accessible ) + { + if( picture->irap ) + { + sample->prop.ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; + if( picture->closed_rap ) + sample->prop.ra_flags |= ISOM_SAMPLE_RANDOM_ACCESS_FLAG_CLOSED_RAP; + else + sample->prop.ra_flags |= ISOM_SAMPLE_RANDOM_ACCESS_FLAG_RAP; + } + else if( picture->recovery_poc_cnt ) + { + sample->prop.ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_POST_ROLL_START; + sample->prop.post_roll.complete = picture->poc + picture->recovery_poc_cnt; + } + else + sample->prop.ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_RAP; + } + sample->length = au->length; + memcpy( sample->data, au->data, au->length ); + return current_status; +} + +static lsmash_video_summary_t *hevc_setup_first_summary +( + importer_t *importer +) +{ + hevc_importer_t *hevc_imp = (hevc_importer_t *)importer->info; + lsmash_codec_specific_t *cs = (lsmash_codec_specific_t *)lsmash_list_get_entry_data( hevc_imp->hvcC_list, ++ hevc_imp->hvcC_number ); + if( !cs || !cs->data.structured ) + { + lsmash_destroy_codec_specific_data( cs ); + return NULL; + } + lsmash_video_summary_t *summary = hevc_create_summary( (lsmash_hevc_specific_parameters_t *)cs->data.structured, + &hevc_imp->info.sps, hevc_imp->max_au_length ); + if( !summary ) + { + lsmash_destroy_codec_specific_data( cs ); + return NULL; + } + if( lsmash_list_add_entry( importer->summaries, summary ) < 0 ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return NULL; + } + summary->sample_per_field = hevc_imp->field_pic_present; + return summary; +} + +static int hevc_analyze_whole_stream +( + importer_t *importer +) +{ + /* Parse all NALU in the stream for preparation of calculating timestamps. */ + uint32_t npt_alloc = (1 << 12) * sizeof(nal_pic_timing_t); + nal_pic_timing_t *npt = (nal_pic_timing_t *)lsmash_malloc( npt_alloc ); + if( !npt ) + return LSMASH_ERR_MEMORY_ALLOC; + uint32_t picture_stats[HEVC_PICTURE_TYPE_NONE + 1] = { 0 }; + uint32_t num_access_units = 0; + lsmash_class_t *logger = &(lsmash_class_t){ "HEVC" }; + lsmash_log( &logger, LSMASH_LOG_INFO, "Analyzing stream as HEVC\r" ); + hevc_importer_t *hevc_imp = (hevc_importer_t *)importer->info; + hevc_info_t *info = &hevc_imp->info; + importer->status = IMPORTER_OK; + int err = LSMASH_ERR_MEMORY_ALLOC; + while( importer->status != IMPORTER_EOF ) + { +#if 0 + lsmash_log( &logger, LSMASH_LOG_INFO, "Analyzing stream as HEVC: %"PRIu32"\n", num_access_units + 1 ); +#endif + hevc_picture_info_t *picture = &info->au.picture; + hevc_picture_info_t prev_picture = *picture; + if( (err = hevc_get_access_unit_internal( importer, 1 )) < 0 + || (err = hevc_calculate_poc( info, &info->au.picture, &prev_picture )) < 0 ) + goto fail; + hevc_importer_check_eof( importer, &info->au ); + if( npt_alloc <= num_access_units * sizeof(nal_pic_timing_t) ) + { + uint32_t alloc = 2 * num_access_units * sizeof(nal_pic_timing_t); + nal_pic_timing_t *temp = (nal_pic_timing_t *)lsmash_realloc( npt, alloc ); + if( !temp ) + goto fail; + npt = temp; + npt_alloc = alloc; + } + hevc_imp->field_pic_present |= picture->field_coded; + npt[num_access_units].poc = picture->poc; + npt[num_access_units].delta = picture->delta; + npt[num_access_units].poc_delta = 1; + npt[num_access_units].reset = 0; + ++num_access_units; + hevc_imp->max_au_length = LSMASH_MAX( hevc_imp->max_au_length, info->au.length ); + hevc_imp->max_TemporalId = LSMASH_MAX( hevc_imp->max_TemporalId, info->au.TemporalId ); + if( picture->idr ) + ++picture_stats[HEVC_PICTURE_TYPE_IDR]; + else if( picture->irap ) + ++picture_stats[ picture->broken_link ? HEVC_PICTURE_TYPE_BLA : HEVC_PICTURE_TYPE_CRA ]; + else if( picture->type >= HEVC_PICTURE_TYPE_NONE ) + ++picture_stats[HEVC_PICTURE_TYPE_NONE]; + else + ++picture_stats[ picture->type ]; + } + lsmash_log_refresh_line( &logger ); + lsmash_log( &logger, LSMASH_LOG_INFO, + "IDR: %"PRIu32", CRA: %"PRIu32", BLA: %"PRIu32", I: %"PRIu32", P: %"PRIu32", B: %"PRIu32", Unknown: %"PRIu32"\n", + picture_stats[HEVC_PICTURE_TYPE_IDR], picture_stats[HEVC_PICTURE_TYPE_CRA], + picture_stats[HEVC_PICTURE_TYPE_BLA], picture_stats[HEVC_PICTURE_TYPE_I], + picture_stats[HEVC_PICTURE_TYPE_I_P], picture_stats[HEVC_PICTURE_TYPE_I_P_B], + picture_stats[HEVC_PICTURE_TYPE_NONE]); + /* Copy and append the last Codec Specific info. */ + if( (err = hevc_store_codec_specific( hevc_imp, &info->hvcC_param )) < 0 ) + goto fail; + /* Set up the first summary. */ + lsmash_video_summary_t *summary = hevc_setup_first_summary( importer ); + if( !summary ) + { + err = LSMASH_ERR_NAMELESS; + goto fail; + } + /* */ + lsmash_media_ts_t *timestamp = lsmash_malloc( num_access_units * sizeof(lsmash_media_ts_t) ); + if( !timestamp ) + goto fail; + /* Count leading samples that are undecodable. */ + for( uint32_t i = 0; i < num_access_units; i++ ) + { + if( npt[i].poc == 0 ) + break; + ++ hevc_imp->num_undecodable; + } + /* Deduplicate POCs. */ + uint32_t max_composition_delay = 0; + nalu_deduplicate_poc( npt, &max_composition_delay, num_access_units, 15 ); + /* Generate timestamps. */ + nalu_generate_timestamps_from_poc( importer, timestamp, npt, + &hevc_imp->composition_reordering_present, + &hevc_imp->last_delta, + max_composition_delay, num_access_units ); + summary->timescale *= 2; /* We assume that picture timing is in field level. + * For HEVC, it seems time_scale is set in frame level basically. + * So multiply by 2 for reducing timebase and timescale. */ + nalu_reduce_timescale( timestamp, npt, &hevc_imp->last_delta, &summary->timescale, num_access_units ); + lsmash_free( npt ); + hevc_imp->ts_list.sample_count = num_access_units; + hevc_imp->ts_list.timestamp = timestamp; + return 0; +fail: + lsmash_log_refresh_line( &logger ); + lsmash_free( npt ); + return err; +} + +static int hevc_importer_probe( importer_t *importer ) +{ + /* Find the first start code. */ + hevc_importer_t *hevc_imp = create_hevc_importer( importer ); + if( !hevc_imp ) + return LSMASH_ERR_MEMORY_ALLOC; + lsmash_bs_t *bs = importer->bs; + uint64_t first_sc_head_pos = nalu_find_first_start_code( bs ); + int err; + if( first_sc_head_pos == NALU_NO_START_CODE_FOUND ) + { + err = LSMASH_ERR_INVALID_DATA; + goto fail; + } + else if( first_sc_head_pos == NALU_IO_ERROR ) + { + err = LSMASH_ERR_IO; + goto fail; + } + /* OK. It seems the stream has a long start code of HEVC. */ + importer->info = hevc_imp; + hevc_info_t *info = &hevc_imp->info; + lsmash_bs_read_seek( bs, first_sc_head_pos, SEEK_SET ); + hevc_imp->sc_head_pos = first_sc_head_pos; + if( (err = hevc_analyze_whole_stream( importer )) < 0 ) + goto fail; + /* Go back to the start code of the first NALU. */ + importer->status = IMPORTER_OK; + lsmash_bs_read_seek( bs, first_sc_head_pos, SEEK_SET ); + hevc_imp->sc_head_pos = first_sc_head_pos; + info->prev_nalu_type = HEVC_NALU_TYPE_UNKNOWN; + uint8_t *temp_au = info->au.data; + uint8_t *temp_incomplete_au = info->au.incomplete_data; + memset( &info->au, 0, sizeof(hevc_access_unit_t) ); + info->au.data = temp_au; + info->au.incomplete_data = temp_incomplete_au; + memset( &info->slice, 0, sizeof(hevc_slice_info_t) ); + memset( &info->vps, 0, sizeof(hevc_vps_t) ); + memset( &info->sps, 0, sizeof(hevc_sps_t) ); + memset( &info->pps, 0, SIZEOF_PPS_EXCLUDING_HEAP ); + for( int i = 0; i < HEVC_DCR_NALU_TYPE_NUM; i++ ) + lsmash_list_remove_entries( info->hvcC_param.parameter_arrays->ps_array[i].list ); + lsmash_destroy_hevc_parameter_arrays( &info->hvcC_param_next ); + return 0; +fail: + remove_hevc_importer( hevc_imp ); + importer->info = NULL; + lsmash_list_remove_entries( importer->summaries ); + return err; +} + +static uint32_t hevc_importer_get_last_delta( importer_t *importer, uint32_t track_number ) +{ + debug_if( !importer || !importer->info ) + return 0; + hevc_importer_t *hevc_imp = (hevc_importer_t *)importer->info; + if( !hevc_imp || track_number != 1 || importer->status != IMPORTER_EOF ) + return 0; + return hevc_imp->ts_list.sample_count + ? hevc_imp->last_delta + : UINT32_MAX; /* arbitrary */ +} + +const importer_functions hevc_importer = +{ + { "HEVC", offsetof( importer_t, log_level ) }, + 1, + hevc_importer_probe, + hevc_importer_get_accessunit, + hevc_importer_get_last_delta, + hevc_importer_cleanup +}; diff --git a/3rdparty/l-smash/importer/vc1_imp.c b/3rdparty/l-smash/importer/vc1_imp.c new file mode 100644 index 000000000..aad8c161a --- /dev/null +++ b/3rdparty/l-smash/importer/vc1_imp.c @@ -0,0 +1,547 @@ +/***************************************************************************** + * vc1_imp.c + ***************************************************************************** + * Copyright (C) 2011-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include +#include + +#define LSMASH_IMPORTER_INTERNAL +#include "importer.h" + +/*************************************************************************** + SMPTE VC-1 importer (only for Advanced Profile) + SMPTE 421M-2006 + SMPTE RP 2025-2007 +***************************************************************************/ +#include "codecs/vc1.h" + +typedef struct +{ + vc1_info_t info; + vc1_sequence_header_t first_sequence; + lsmash_media_ts_list_t ts_list; + uint8_t composition_reordering_present; + uint32_t max_au_length; + uint32_t num_undecodable; + uint64_t last_ref_intra_cts; +} vc1_importer_t; + +static void remove_vc1_importer( vc1_importer_t *vc1_imp ) +{ + if( !vc1_imp ) + return; + vc1_cleanup_parser( &vc1_imp->info ); + lsmash_free( vc1_imp->ts_list.timestamp ); + lsmash_free( vc1_imp ); +} + +static void vc1_importer_cleanup( importer_t *importer ) +{ + debug_if( importer && importer->info ) + remove_vc1_importer( importer->info ); +} + +static vc1_importer_t *create_vc1_importer( importer_t *importer ) +{ + vc1_importer_t *vc1_imp = lsmash_malloc_zero( sizeof(vc1_importer_t) ); + if( !vc1_imp ) + return NULL; + if( vc1_setup_parser( &vc1_imp->info, 0 ) < 0 ) + { + remove_vc1_importer( vc1_imp ); + return NULL; + } + return vc1_imp; +} + +static inline int vc1_complete_au( vc1_access_unit_t *access_unit, vc1_picture_info_t *picture, int probe ) +{ + if( !picture->present ) + return 0; + if( !probe ) + memcpy( access_unit->data, access_unit->incomplete_data, access_unit->incomplete_data_length ); + access_unit->data_length = access_unit->incomplete_data_length; + access_unit->incomplete_data_length = 0; + vc1_update_au_property( access_unit, picture ); + return 1; +} + +static inline void vc1_append_ebdu_to_au( vc1_access_unit_t *access_unit, uint8_t *ebdu, uint32_t ebdu_length, int probe ) +{ + if( !probe ) + memcpy( access_unit->incomplete_data + access_unit->incomplete_data_length, ebdu, ebdu_length ); + /* Note: access_unit->incomplete_data_length shall be 0 immediately after AU has completed. + * Therefore, possible_au_length in vc1_get_access_unit_internal() can't be used here + * to avoid increasing AU length monotonously through the entire stream. */ + access_unit->incomplete_data_length += ebdu_length; +} + +static int vc1_get_au_internal_succeeded( vc1_importer_t *vc1_imp ) +{ + vc1_access_unit_t *access_unit = &vc1_imp->info.access_unit; + access_unit->number += 1; + return 0; +} + +static int vc1_get_au_internal_failed( vc1_importer_t *vc1_imp, int complete_au, int ret ) +{ + vc1_access_unit_t *access_unit = &vc1_imp->info.access_unit; + if( complete_au ) + access_unit->number += 1; + return ret; +} + +static int vc1_importer_get_access_unit_internal( importer_t *importer, int probe ) +{ + vc1_importer_t *vc1_imp = (vc1_importer_t *)importer->info; + vc1_info_t *info = &vc1_imp->info; + vc1_stream_buffer_t *sb = &info->buffer; + vc1_access_unit_t *access_unit = &info->access_unit; + lsmash_bs_t *bs = importer->bs; + int complete_au = 0; + access_unit->data_length = 0; + while( 1 ) + { + int err; + uint8_t bdu_type; + uint64_t trailing_zero_bytes; + uint64_t ebdu_length = vc1_find_next_start_code_prefix( bs, &bdu_type, &trailing_zero_bytes ); + if( ebdu_length <= VC1_START_CODE_LENGTH && lsmash_bs_is_end( bs, ebdu_length ) ) + { + /* For the last EBDU. + * This EBDU already has been appended into the latest access unit and parsed. */ + vc1_complete_au( access_unit, &info->picture, probe ); + return vc1_get_au_internal_succeeded( vc1_imp ); + } + else if( bdu_type == 0xFF ) + { + lsmash_log( importer, LSMASH_LOG_ERROR, "a forbidden BDU type is detected.\n" ); + return vc1_get_au_internal_failed( vc1_imp, complete_au, LSMASH_ERR_INVALID_DATA ); + } + uint64_t next_ebdu_head_pos = info->ebdu_head_pos + + ebdu_length + + trailing_zero_bytes; +#if 0 + if( probe ) + { + fprintf( stderr, "BDU type: %"PRIu8" \n", bdu_type ); + fprintf( stderr, " EBDU position: %"PRIx64" \n", info->ebdu_head_pos ); + fprintf( stderr, " EBDU length: %"PRIx64" (%"PRIu64")\n", ebdu_length, ebdu_length ); + fprintf( stderr, " trailing_zero_bytes: %"PRIx64" \n", trailing_zero_bytes ); + fprintf( stderr, " Next EBDU position: %"PRIx64" \n", next_ebdu_head_pos ); + } +#endif + if( bdu_type >= 0x0A && bdu_type <= 0x0F ) + { + /* Complete the current access unit if encountered delimiter of current access unit. */ + if( vc1_find_au_delimit_by_bdu_type( bdu_type, info->prev_bdu_type ) ) + /* The last video coded EBDU belongs to the access unit you want at this time. */ + complete_au = vc1_complete_au( access_unit, &info->picture, probe ); + /* Increase the buffer if needed. */ + uint64_t possible_au_length = access_unit->incomplete_data_length + ebdu_length; + if( sb->bank->buffer_size < possible_au_length + && (err = vc1_supplement_buffer( sb, access_unit, 2 * possible_au_length )) < 0 ) + { + lsmash_log( importer, LSMASH_LOG_ERROR, "failed to increase the buffer size.\n" ); + return vc1_get_au_internal_failed( vc1_imp, complete_au, err ); + } + /* Process EBDU by its BDU type and append it to access unit. */ + uint8_t *ebdu = lsmash_bs_get_buffer_data( bs ); + switch( bdu_type ) + { + /* FRM_SC: Frame start code + * FLD_SC: Field start code + * SLC_SC: Slice start code + * SEQ_SC: Sequence header start code + * EP_SC: Entry-point start code + * PIC_L: Picture layer + * SLC_L: Slice layer + * SEQ_L: Sequence layer + * EP_L: Entry-point layer */ + case 0x0D : /* Frame + * For the Progressive or Frame Interlace mode, shall signal the beginning of a new video frame. + * For the Field Interlace mode, shall signal the beginning of a sequence of two independently coded video fields. + * [FRM_SC][PIC_L][[FLD_SC][PIC_L] (optional)][[SLC_SC][SLC_L] (optional)] ... */ + if( (err = vc1_parse_advanced_picture( info->bits, &info->sequence, &info->picture, sb->rbdu, ebdu, ebdu_length )) < 0 ) + { + lsmash_log( importer, LSMASH_LOG_ERROR, "failed to parse a frame.\n" ); + return vc1_get_au_internal_failed( vc1_imp, complete_au, err ); + } + case 0x0C : /* Field + * Shall only be used for Field Interlaced frames + * and shall only be used to signal the beginning of the second field of the frame. + * [FRM_SC][PIC_L][FLD_SC][PIC_L][[SLC_SC][SLC_L] (optional)] ... + * Field start code is followed by INTERLACE_FIELD_PICTURE_FIELD2() which doesn't have info of its field picture type.*/ + break; + case 0x0B : /* Slice + * Shall not be used for start code of the first slice of a frame. + * Shall not be used for start code of the first slice of an interlace field coded picture. + * [FRM_SC][PIC_L][[FLD_SC][PIC_L] (optional)][SLC_SC][SLC_L][[SLC_SC][SLC_L] (optional)] ... + * Slice layer may repeat frame header. We just ignore it. */ + info->dvc1_param.slice_present = 1; + break; + case 0x0E : /* Entry-point header + * Entry-point indicates the direct followed frame is a start of group of frames. + * Entry-point doesn't indicates the frame is a random access point when multiple sequence headers are present, + * since it is necessary to decode sequence header which subsequent frames belong to for decoding them. + * Entry point shall be followed by + * 1. I-picture - progressive or frame interlace + * 2. I/I-picture, I/P-picture, or P/I-picture - field interlace + * [[SEQ_SC][SEQ_L] (optional)][EP_SC][EP_L][FRM_SC][PIC_L] ... */ + if( (err = vc1_parse_entry_point_header( info, ebdu, ebdu_length, probe )) < 0 ) + { + lsmash_log( importer, LSMASH_LOG_ERROR, "failed to parse an entry point.\n" ); + return vc1_get_au_internal_failed( vc1_imp, complete_au, err ); + } + /* Signal random access type of the frame that follows this entry-point header. */ + info->picture.closed_gop = info->entry_point.closed_entry_point; + info->picture.random_accessible = info->dvc1_param.multiple_sequence ? info->picture.start_of_sequence : 1; + break; + case 0x0F : /* Sequence header + * [SEQ_SC][SEQ_L][EP_SC][EP_L][FRM_SC][PIC_L] ... */ + if( (err = vc1_parse_sequence_header( info, ebdu, ebdu_length, probe )) < 0 ) + { + lsmash_log( importer, LSMASH_LOG_ERROR, "failed to parse a sequence header.\n" ); + return vc1_get_au_internal_failed( vc1_imp, complete_au, err ); + } + /* The frame that is the first frame after this sequence header shall be a random accessible point. */ + info->picture.start_of_sequence = 1; + if( probe && !vc1_imp->first_sequence.present ) + vc1_imp->first_sequence = info->sequence; + break; + default : /* End-of-sequence (0x0A) */ + break; + } + /* Append the current EBDU into the end of an incomplete access unit. */ + vc1_append_ebdu_to_au( access_unit, ebdu, ebdu_length, probe ); + } + else /* We don't support other BDU types such as user data yet. */ + return vc1_get_au_internal_failed( vc1_imp, complete_au, LSMASH_ERR_PATCH_WELCOME ); + /* Move to the first byte of the next EBDU. */ + info->prev_bdu_type = bdu_type; + if( lsmash_bs_read_seek( bs, next_ebdu_head_pos, SEEK_SET ) != next_ebdu_head_pos ) + { + lsmash_log( importer, LSMASH_LOG_ERROR, "failed to seek the next start code suffix.\n" ); + return vc1_get_au_internal_failed( vc1_imp, complete_au, LSMASH_ERR_NAMELESS ); + } + /* Check if no more data to read from the stream. */ + if( !lsmash_bs_is_end( bs, VC1_START_CODE_PREFIX_LENGTH ) ) + info->ebdu_head_pos = next_ebdu_head_pos; + /* If there is no more data in the stream, and flushed chunk of EBDUs, flush it as complete AU here. */ + else if( access_unit->incomplete_data_length && access_unit->data_length == 0 ) + { + vc1_complete_au( access_unit, &info->picture, probe ); + return vc1_get_au_internal_succeeded( vc1_imp ); + } + if( complete_au ) + return vc1_get_au_internal_succeeded( vc1_imp ); + } +} + +static inline void vc1_importer_check_eof( importer_t *importer, vc1_access_unit_t *access_unit ) +{ + if( lsmash_bs_is_end( importer->bs, 0 ) && access_unit->incomplete_data_length == 0 ) + importer->status = IMPORTER_EOF; + else + importer->status = IMPORTER_OK; +} + +static int vc1_importer_get_accessunit( importer_t *importer, uint32_t track_number, lsmash_sample_t **p_sample ) +{ + if( !importer->info ) + return LSMASH_ERR_NAMELESS; + if( track_number != 1 ) + return LSMASH_ERR_FUNCTION_PARAM; + vc1_importer_t *vc1_imp = (vc1_importer_t *)importer->info; + vc1_info_t *info = &vc1_imp->info; + importer_status current_status = importer->status; + if( current_status == IMPORTER_ERROR ) + return LSMASH_ERR_NAMELESS; + if( current_status == IMPORTER_EOF ) + return IMPORTER_EOF; + int err = vc1_importer_get_access_unit_internal( importer, 0 ); + if( err < 0 ) + { + importer->status = IMPORTER_ERROR; + return err; + } + lsmash_sample_t *sample = lsmash_create_sample( vc1_imp->max_au_length ); + if( !sample ) + return LSMASH_ERR_MEMORY_ALLOC; + *p_sample = sample; + vc1_access_unit_t *access_unit = &info->access_unit; + vc1_importer_check_eof( importer, access_unit ); + sample->dts = vc1_imp->ts_list.timestamp[ access_unit->number - 1 ].dts; + sample->cts = vc1_imp->ts_list.timestamp[ access_unit->number - 1 ].cts; + sample->prop.leading = access_unit->independent + || access_unit->non_bipredictive + || sample->cts >= vc1_imp->last_ref_intra_cts + ? ISOM_SAMPLE_IS_NOT_LEADING : ISOM_SAMPLE_IS_UNDECODABLE_LEADING; + if( access_unit->independent && !access_unit->disposable ) + vc1_imp->last_ref_intra_cts = sample->cts; + if( vc1_imp->composition_reordering_present && !access_unit->disposable && !access_unit->closed_gop ) + sample->prop.allow_earlier = QT_SAMPLE_EARLIER_PTS_ALLOWED; + sample->prop.independent = access_unit->independent ? ISOM_SAMPLE_IS_INDEPENDENT : ISOM_SAMPLE_IS_NOT_INDEPENDENT; + sample->prop.disposable = access_unit->disposable ? ISOM_SAMPLE_IS_DISPOSABLE : ISOM_SAMPLE_IS_NOT_DISPOSABLE; + sample->prop.redundant = ISOM_SAMPLE_HAS_NO_REDUNDANCY; + if( access_unit->random_accessible ) + /* All random access point is a sync sample even if it's an open RAP. */ + sample->prop.ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; + sample->length = access_unit->data_length; + memcpy( sample->data, access_unit->data, access_unit->data_length ); + return current_status; +} + +static lsmash_video_summary_t *vc1_create_summary( vc1_info_t *info, vc1_sequence_header_t *sequence, uint32_t max_au_length ) +{ + if( !info->sequence.present || !info->entry_point.present ) + return NULL; + lsmash_video_summary_t *summary = (lsmash_video_summary_t *)lsmash_create_summary( LSMASH_SUMMARY_TYPE_VIDEO ); + if( !summary ) + return NULL; + lsmash_codec_specific_t *specific = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_VC_1, + LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED ); + if( !specific ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return NULL; + } + specific->data.unstructured = lsmash_create_vc1_specific_info( &info->dvc1_param, &specific->size ); + if( !specific->data.unstructured + || lsmash_list_add_entry( &summary->opaque->list, specific ) < 0 ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + lsmash_destroy_codec_specific_data( specific ); + return NULL; + } + summary->sample_type = ISOM_CODEC_TYPE_VC_1_VIDEO; + summary->max_au_length = max_au_length; + summary->timescale = sequence->framerate_numerator; + summary->timebase = sequence->framerate_denominator; + summary->vfr = !sequence->framerate_flag; + summary->sample_per_field = 0; + summary->width = sequence->disp_horiz_size; + summary->height = sequence->disp_vert_size; + summary->par_h = sequence->aspect_width; + summary->par_v = sequence->aspect_height; + summary->color.primaries_index = sequence->color_prim; + summary->color.transfer_index = sequence->transfer_char; + summary->color.matrix_index = sequence->matrix_coef; + return summary; +} + +static int vc1_analyze_whole_stream +( + importer_t *importer +) +{ + /* Parse all EBDU in the stream for preparation of calculating timestamps. */ + uint32_t cts_alloc = (1 << 12) * sizeof(uint64_t); + uint64_t *cts = lsmash_malloc( cts_alloc ); + if( !cts ) + return LSMASH_ERR_MEMORY_ALLOC; /* Failed to allocate CTS list */ + uint32_t num_access_units = 0; + uint32_t num_consecutive_b = 0; + lsmash_class_t *logger = &(lsmash_class_t){ "VC-1" }; + lsmash_log( &logger, LSMASH_LOG_INFO, "Analyzing stream as VC-1\r" ); + vc1_importer_t *vc1_imp = (vc1_importer_t *)importer->info; + vc1_info_t *info = &vc1_imp->info; + importer->status = IMPORTER_OK; + int err; + while( importer->status != IMPORTER_EOF ) + { +#if 0 + lsmash_log( &logger, LSMASH_LOG_INFO, "Analyzing stream as VC-1: %"PRIu32"\n", num_access_units + 1 ); +#endif + if( (err = vc1_importer_get_access_unit_internal( importer, 1 )) < 0 ) + goto fail; + vc1_importer_check_eof( importer, &info->access_unit ); + /* In the case where B-pictures exist + * Decode order + * I[0]P[1]P[2]B[3]B[4]P[5]... + * DTS + * 0 1 2 3 4 5 ... + * Composition order + * I[0]P[1]B[3]B[4]P[2]P[5]... + * CTS + * 1 2 3 4 5 6 ... + * We assumes B or BI-pictures always be present in the stream here. */ + if( !info->access_unit.disposable ) + { + /* Apply CTS of the last B-picture plus 1 to the last non-B-picture. */ + if( num_access_units > num_consecutive_b ) + cts[ num_access_units - num_consecutive_b - 1 ] = num_access_units; + num_consecutive_b = 0; + } + else /* B or BI-picture */ + { + /* B and BI-pictures shall be output or displayed in the same order as they are encoded. */ + cts[ num_access_units ] = num_access_units; + ++num_consecutive_b; + info->dvc1_param.bframe_present = 1; + } + if( cts_alloc <= num_access_units * sizeof(uint64_t) ) + { + uint32_t alloc = 2 * num_access_units * sizeof(uint64_t); + uint64_t *temp = lsmash_realloc( cts, alloc ); + if( !temp ) + { + err = LSMASH_ERR_MEMORY_ALLOC; + goto fail; /* Failed to re-allocate CTS list */ + } + cts = temp; + cts_alloc = alloc; + } + vc1_imp->max_au_length = LSMASH_MAX( info->access_unit.data_length, vc1_imp->max_au_length ); + ++num_access_units; + } + if( num_access_units > num_consecutive_b ) + cts[ num_access_units - num_consecutive_b - 1 ] = num_access_units; + else + { + err = LSMASH_ERR_INVALID_DATA; + goto fail; + } + /* Construct timestamps. */ + lsmash_media_ts_t *timestamp = lsmash_malloc( num_access_units * sizeof(lsmash_media_ts_t) ); + if( !timestamp ) + { + err = LSMASH_ERR_MEMORY_ALLOC; + goto fail; /* Failed to allocate timestamp list */ + } + for( uint32_t i = 1; i < num_access_units; i++ ) + if( cts[i] < cts[i - 1] ) + { + vc1_imp->composition_reordering_present = 1; + break; + } + if( vc1_imp->composition_reordering_present ) + for( uint32_t i = 0; i < num_access_units; i++ ) + { + timestamp[i].cts = cts[i]; + timestamp[i].dts = i; + } + else + for( uint32_t i = 0; i < num_access_units; i++ ) + timestamp[i].cts = timestamp[i].dts = i; + lsmash_free( cts ); + lsmash_log_refresh_line( &logger ); +#if 0 + for( uint32_t i = 0; i < num_access_units; i++ ) + fprintf( stderr, "Timestamp[%"PRIu32"]: DTS=%"PRIu64", CTS=%"PRIu64"\n", i, timestamp[i].dts, timestamp[i].cts ); +#endif + vc1_imp->ts_list.sample_count = num_access_units; + vc1_imp->ts_list.timestamp = timestamp; + return 0; +fail: + lsmash_log_refresh_line( &logger ); + lsmash_free( cts ); + return err; +} + +static int vc1_importer_probe( importer_t *importer ) +{ + /* Find the first start code. */ + vc1_importer_t *vc1_imp = create_vc1_importer( importer ); + if( !vc1_imp ) + return LSMASH_ERR_MEMORY_ALLOC; + lsmash_bs_t *bs = importer->bs; + uint64_t first_ebdu_head_pos = 0; + int err; + while( 1 ) + { + if( lsmash_bs_is_error( bs ) ) + { + err = LSMASH_ERR_IO; + goto fail; + } + /* The first EBDU in decoding order of the stream shall have start code (0x000001). */ + if( 0x000001 == lsmash_bs_show_be24( bs, first_ebdu_head_pos ) ) + break; + /* Invalid if encountered any value of non-zero before the first start code. */ + if( lsmash_bs_show_byte( bs, first_ebdu_head_pos ) ) + { + err = LSMASH_ERR_INVALID_DATA; + goto fail; + } + ++first_ebdu_head_pos; + } + /* OK. It seems the stream has a sequence header of VC-1. */ + importer->info = vc1_imp; + vc1_info_t *info = &vc1_imp->info; + lsmash_bs_read_seek( bs, first_ebdu_head_pos, SEEK_SET ); + info->ebdu_head_pos = first_ebdu_head_pos; + if( (err = vc1_analyze_whole_stream( importer )) < 0 ) + goto fail; + lsmash_video_summary_t *summary = vc1_create_summary( info, &vc1_imp->first_sequence, vc1_imp->max_au_length ); + if( !summary ) + { + err = LSMASH_ERR_NAMELESS; + goto fail; + } + if( lsmash_list_add_entry( importer->summaries, summary ) < 0 ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + err = LSMASH_ERR_MEMORY_ALLOC; + goto fail; + } + /* Go back to layer of the first EBDU. */ + importer->status = IMPORTER_OK; + lsmash_bs_read_seek( bs, first_ebdu_head_pos, SEEK_SET ); + info->prev_bdu_type = 0xFF; /* 0xFF is a forbidden value. */ + info->ebdu_head_pos = first_ebdu_head_pos; + uint8_t *temp_access_unit = info->access_unit.data; + uint8_t *temp_incomplete_access_unit = info->access_unit.incomplete_data; + memset( &info->access_unit, 0, sizeof(vc1_access_unit_t) ); + info->access_unit.data = temp_access_unit; + info->access_unit.incomplete_data = temp_incomplete_access_unit; + memset( &info->picture, 0, sizeof(vc1_picture_info_t) ); + return 0; +fail: + remove_vc1_importer( vc1_imp ); + importer->info = NULL; + lsmash_list_remove_entries( importer->summaries ); + return err; +} + +static uint32_t vc1_importer_get_last_delta( importer_t *importer, uint32_t track_number ) +{ + debug_if( !importer || !importer->info ) + return 0; + vc1_importer_t *vc1_imp = (vc1_importer_t *)importer->info; + if( !vc1_imp || track_number != 1 || importer->status != IMPORTER_EOF ) + return 0; + return vc1_imp->ts_list.sample_count + ? 1 + : UINT32_MAX; /* arbitrary */ +} + +const importer_functions vc1_importer = +{ + { "VC-1", offsetof( importer_t, log_level ) }, + 1, + vc1_importer_probe, + vc1_importer_get_accessunit, + vc1_importer_get_last_delta, + vc1_importer_cleanup +}; diff --git a/3rdparty/l-smash/importer/wave_imp.c b/3rdparty/l-smash/importer/wave_imp.c new file mode 100644 index 000000000..2c0057069 --- /dev/null +++ b/3rdparty/l-smash/importer/wave_imp.c @@ -0,0 +1,446 @@ +/***************************************************************************** + * wave_imp.c + ***************************************************************************** + * Copyright (C) 2014-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#include "common/internal.h" /* must be placed first */ + +#include + +#define LSMASH_IMPORTER_INTERNAL +#include "importer.h" + +/********************************************************************************* + Waveform Audio File Format (WAVE) importer + + References + Multimedia Programming Interface and Data Specifications 1.0 + New Multimedia Data Types and Data Techniques April 15, 1994 Revision: 3.0 + Multiple channel audio data and WAVE files March 7, 2007 + Microsoft Windows SDK MMReg.h +**********************************************************************************/ +#include "core/timeline.h" + +#define WAVE_MIN_FILESIZE 45 + +#define WAVE_FORMAT_TYPE_ID_PCM 0x0001 /* WAVE_FORMAT_PCM */ +#define WAVE_FORMAT_TYPE_ID_EXTENSIBLE 0xFFFE /* WAVE_FORMAT_EXTENSIBLE */ + +#define PASS_GUID( _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15 ) \ + { _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15 } +#define DEFINE_WAVEFORMAT_EXTENSIBLE_SUBTYPE_GUID( name, value ) \ + static const uint8_t name[16] = value + +/* KSDATAFORMAT_SUBTYPE_PCM := 00000001-0000-0010-8000-00aa00389b71 */ +DEFINE_WAVEFORMAT_EXTENSIBLE_SUBTYPE_GUID +( + WAVEFORMAT_EXTENSIBLE_SUBTYPE_GUID_PCM, + PASS_GUID( 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 ) +); + +typedef struct +{ + uint16_t wFormatTag; + uint16_t nChannels; + uint32_t nSamplesPerSec; + uint32_t nAvgBytesPerSec; + uint16_t nBlockAlign; + uint16_t wBitsPerSample; + uint16_t cbSize; +} waveformat_extended_t; + +typedef struct +{ + waveformat_extended_t wfx; + union + { + uint16_t wValidBitsPerSample; + uint16_t wSamplesPerBlock; + uint16_t wReserved; + } Samples; + uint32_t dwChannelMask; + uint8_t guid[16]; +} waveformat_extensible_t; + +typedef struct +{ + uint32_t number_of_samples; + uint32_t au_length; + uint32_t au_number; + waveformat_extensible_t fmt; + isom_portable_chunk_t chunk; +} wave_importer_t; + +static void remove_wave_importer( wave_importer_t *wave_imp ) +{ + if( !wave_imp ) + return; + lsmash_free( wave_imp ); +} + +static wave_importer_t *create_wave_importer( importer_t *importer ) +{ + return (wave_importer_t *)lsmash_malloc_zero( sizeof(wave_importer_t) ); +} + +static void wave_importer_cleanup( importer_t *importer ) +{ + debug_if( importer && importer->info ) + remove_wave_importer( importer->info ); +} + +static int wave_importer_get_accessunit( importer_t *importer, uint32_t track_number, lsmash_sample_t **p_sample ) +{ + if( !importer->info ) + return LSMASH_ERR_NAMELESS; + if( track_number != 1 ) + return LSMASH_ERR_FUNCTION_PARAM; + lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_list_get_entry_data( importer->summaries, track_number ); + if( !summary ) + return LSMASH_ERR_NAMELESS; + wave_importer_t *wave_imp = (wave_importer_t *)importer->info; + importer_status current_status = importer->status; + if( current_status == IMPORTER_ERROR ) + return LSMASH_ERR_NAMELESS; + if( current_status == IMPORTER_EOF ) + return IMPORTER_EOF; + if( wave_imp->number_of_samples / summary->samples_in_frame > wave_imp->au_number ) + wave_imp->au_length = summary->bytes_per_frame; + else + { + wave_imp->au_length = wave_imp->fmt.wfx.nBlockAlign * (wave_imp->number_of_samples % summary->samples_in_frame); + importer->status = IMPORTER_EOF; + if( wave_imp->au_length == 0 ) + return IMPORTER_EOF; + } + lsmash_sample_t *sample = lsmash_create_sample( wave_imp->au_length ); + if( !sample ) + return LSMASH_ERR_MEMORY_ALLOC; + *p_sample = sample; + if( lsmash_bs_get_bytes_ex( importer->bs, wave_imp->au_length, sample->data ) != wave_imp->au_length ) + { + importer->status = IMPORTER_ERROR; + return LSMASH_ERR_INVALID_DATA; + } + sample->length = wave_imp->au_length; + sample->dts = wave_imp->au_number ++ * summary->samples_in_frame; + sample->cts = sample->dts; + sample->prop.ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; + return current_status; +} + +static inline int wave_fmt_subtype_cmp( const waveformat_extensible_t *fmt, const uint8_t guid[16] ) +{ + return memcmp( fmt->guid, guid, 16 ); +} + +static int wave_parse_fmt_chunk( wave_importer_t *wave_imp, lsmash_bs_t *bs ) +{ + waveformat_extensible_t *fmt = &wave_imp->fmt; + waveformat_extended_t *wfx = &fmt->wfx; + wfx->wFormatTag = lsmash_bs_get_le16( bs ); + wfx->nChannels = lsmash_bs_get_le16( bs ); + wfx->nSamplesPerSec = lsmash_bs_get_le32( bs ); + wfx->nAvgBytesPerSec = lsmash_bs_get_le32( bs ); + wfx->nBlockAlign = lsmash_bs_get_le16( bs ); + wfx->wBitsPerSample = lsmash_bs_get_le16( bs ); + switch( wfx->wFormatTag ) + { + case WAVE_FORMAT_TYPE_ID_PCM : + return 0; + case WAVE_FORMAT_TYPE_ID_EXTENSIBLE : + wfx->cbSize = lsmash_bs_get_le16( bs ); + if( wfx->cbSize < 22 ) + return LSMASH_ERR_INVALID_DATA; + fmt->Samples.wValidBitsPerSample = lsmash_bs_get_le16( bs ); + fmt->dwChannelMask = lsmash_bs_get_le32( bs ); + if( lsmash_bs_get_bytes_ex( bs, 16, fmt->guid ) != 16 ) + return LSMASH_ERR_NAMELESS; + /* We support only PCM audio currently. */ + if( wave_fmt_subtype_cmp( fmt, WAVEFORMAT_EXTENSIBLE_SUBTYPE_GUID_PCM ) ) + return LSMASH_ERR_INVALID_DATA; + return 0; + default : + return LSMASH_ERR_NAMELESS; + } +} + +static lsmash_audio_summary_t *wave_create_summary( waveformat_extensible_t *fmt ) +{ + lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_create_summary( LSMASH_SUMMARY_TYPE_AUDIO ); + if( !summary ) + return NULL; + waveformat_extended_t *wfx = &fmt->wfx; + summary->sample_type = QT_CODEC_TYPE_LPCM_AUDIO; + summary->aot = MP4A_AUDIO_OBJECT_TYPE_NULL; + summary->frequency = wfx->nSamplesPerSec; + summary->channels = wfx->nChannels; + summary->sample_size = wfx->wFormatTag == WAVE_FORMAT_TYPE_ID_EXTENSIBLE + ? fmt->Samples.wValidBitsPerSample + : wfx->wBitsPerSample; + summary->samples_in_frame = 1000; /* arbitrary */ + summary->sbr_mode = MP4A_AAC_SBR_NOT_SPECIFIED; + summary->bytes_per_frame = wfx->nBlockAlign * summary->samples_in_frame; + summary->max_au_length = summary->bytes_per_frame; + lsmash_codec_specific_t *cs = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_FORMAT_SPECIFIC_FLAGS, + LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !cs ) + goto fail; + lsmash_qt_audio_format_specific_flags_t *lpcm = (lsmash_qt_audio_format_specific_flags_t *)cs->data.structured; + if( (summary->sample_size & 7) == 0 ) + lpcm->format_flags |= QT_AUDIO_FORMAT_FLAG_PACKED; + else + lpcm->format_flags |= QT_AUDIO_FORMAT_FLAG_ALIGNED_HIGH; + if( summary->sample_size > 8 ) + lpcm->format_flags |= QT_AUDIO_FORMAT_FLAG_SIGNED_INTEGER; + if( lsmash_list_add_entry( &summary->opaque->list, cs ) < 0 ) + { + lsmash_destroy_codec_specific_data( cs ); + goto fail; + } + if( wfx->wFormatTag == WAVE_FORMAT_TYPE_ID_EXTENSIBLE || wfx->nChannels > 2 ) + { + cs = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_CHANNEL_LAYOUT, + LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); + if( !cs ) + goto fail; + lsmash_qt_audio_channel_layout_t *layout = (lsmash_qt_audio_channel_layout_t *)cs->data.structured; + if( wfx->wFormatTag == WAVE_FORMAT_TYPE_ID_EXTENSIBLE ) + { + layout->channelLayoutTag = QT_CHANNEL_LAYOUT_USE_CHANNEL_BITMAP; + layout->channelBitmap = fmt->dwChannelMask; + } + else + { + layout->channelLayoutTag = QT_CHANNEL_LAYOUT_UNKNOWN | wfx->nChannels; + layout->channelBitmap = 0; + } + if( lsmash_list_add_entry( &summary->opaque->list, cs ) < 0 ) + { + lsmash_destroy_codec_specific_data( cs ); + goto fail; + } + } + return summary; +fail: + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + return NULL; +} + +static int wave_importer_probe( importer_t *importer ) +{ + wave_importer_t *wave_imp = create_wave_importer( importer ); + if( !wave_imp ) + return LSMASH_ERR_MEMORY_ALLOC; + int err = 0; + uint32_t filesize; + lsmash_bs_t *bs = importer->bs; + if( lsmash_bs_get_be32( bs ) != LSMASH_4CC( 'R', 'I', 'F', 'F' ) + || ((filesize = lsmash_bs_get_le32( bs ) + 8) < WAVE_MIN_FILESIZE && filesize > 8) + || lsmash_bs_get_be32( bs ) != LSMASH_4CC( 'W', 'A', 'V', 'E' ) ) + { + err = LSMASH_ERR_INVALID_DATA; + goto fail; + } + int fmt_chunk_present = 0; + int data_chunk_present = 0; + while( !bs->eob && !(fmt_chunk_present && data_chunk_present) ) + { + uint32_t ckID = lsmash_bs_get_be32( bs ); + uint32_t ckSize = lsmash_bs_get_le32( bs ); + lsmash_bs_reset_counter( bs ); + switch( ckID ) + { + case LSMASH_4CC( 'f', 'm', 't', ' ' ) : + if( ckSize < 16 ) + { + err = LSMASH_ERR_INVALID_DATA; + goto fail; + } + if( (err = wave_parse_fmt_chunk( wave_imp, bs )) < 0 ) + goto fail; + fmt_chunk_present = 1; + break; + case LSMASH_4CC( 'd', 'a', 't', 'a' ) : + if( !fmt_chunk_present ) + { + /* The 'fmt ' chunk must be present before the 'data' chunk. */ + err = LSMASH_ERR_INVALID_DATA; + goto fail; + } + wave_imp->chunk.data_offset = lsmash_bs_get_stream_pos( bs ); + wave_imp->chunk.length = ckSize; + wave_imp->chunk.number = 1; + wave_imp->chunk.file = importer->file; + wave_imp->number_of_samples = ckSize / wave_imp->fmt.wfx.nBlockAlign; + data_chunk_present = 1; + break; + default : + break; + } + if( !data_chunk_present ) + { + /* Skip the rest of this chunk. + * Note that ckData is word-aligned even if ckSize is an odd number. */ + uint32_t skip_size = ckSize; + if( skip_size & 1 ) + skip_size++; + if( skip_size > lsmash_bs_count( bs ) ) + { + skip_size -= lsmash_bs_count( bs ); + lsmash_bs_read_seek( bs, skip_size, SEEK_CUR ); + } + } + } + if( !(fmt_chunk_present && data_chunk_present) ) + { + err = LSMASH_ERR_INVALID_DATA; + goto fail; + } + /* Make fake movie. + * Treat WAVE file format as if it's QuickTime file format. */ + uint32_t track_ID; + lsmash_movie_parameters_t movie_param = { 0 }; + lsmash_track_parameters_t track_param = { 0 }; + lsmash_media_parameters_t media_param = { 0 }; + importer->file->qt_compatible = 1; + if( (err = lsmash_importer_make_fake_movie( importer )) < 0 + || (err = lsmash_importer_make_fake_track( importer, ISOM_MEDIA_HANDLER_TYPE_AUDIO_TRACK, &track_ID )) < 0 + || (err = lsmash_get_movie_parameters( importer->root, &movie_param )) < 0 + || (err = lsmash_get_track_parameters( importer->root, track_ID, &track_param )) < 0 + || (err = lsmash_get_media_parameters( importer->root, track_ID, &media_param )) < 0 ) + goto fail; + movie_param.timescale = wave_imp->fmt.wfx.nSamplesPerSec; + media_param.timescale = wave_imp->fmt.wfx.nSamplesPerSec; + if( (err = lsmash_set_movie_parameters( importer->root, &movie_param )) < 0 + || (err = lsmash_set_track_parameters( importer->root, track_ID, &track_param )) < 0 + || (err = lsmash_set_media_parameters( importer->root, track_ID, &media_param )) < 0 ) + goto fail; + lsmash_audio_summary_t *summary = wave_create_summary( &wave_imp->fmt ); + if( !summary || lsmash_add_sample_entry( importer->root, track_ID, summary ) != 1 ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + err = LSMASH_ERR_NAMELESS; + goto fail; + } + if( (err = lsmash_list_add_entry( importer->summaries, summary )) < 0 ) + { + lsmash_cleanup_summary( (lsmash_summary_t *)summary ); + goto fail; + } + importer->info = wave_imp; + importer->status = IMPORTER_OK; + return 0; +fail: + lsmash_importer_break_fake_movie( importer ); + remove_wave_importer( wave_imp ); + importer->file->qt_compatible = 0; + importer->info = NULL; + return err; +} + +static uint32_t wave_importer_get_last_delta( importer_t *importer, uint32_t track_number ) +{ + debug_if( !importer || !importer->info ) + return 0; + wave_importer_t *wave_imp = (wave_importer_t *)importer->info; + if( !wave_imp || track_number != 1 || importer->status != IMPORTER_EOF ) + return 0; + lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_list_get_entry_data( importer->summaries, track_number ); + if( !summary ) + return 0; + return wave_imp->number_of_samples / summary->samples_in_frame >= wave_imp->au_number + ? summary->samples_in_frame + : (wave_imp->number_of_samples % summary->samples_in_frame); +} + +static int wave_importer_construct_timeline( importer_t *importer, uint32_t track_number ) +{ + lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_list_get_entry_data( importer->summaries, track_number ); + if( !summary ) + return LSMASH_ERR_NAMELESS; + isom_timeline_t *timeline = isom_timeline_create(); + if( !timeline ) + return LSMASH_ERR_NAMELESS; + int err; + lsmash_file_t *file = importer->file; + if( !file->timeline ) + { + /* TODO: this is not a proper place. It should be enclosed in isom_timeline_create(). */ + file->timeline = lsmash_list_create( isom_timeline_destroy ); + if( !file->timeline ) + { + err = LSMASH_ERR_MEMORY_ALLOC; + goto fail; + } + } + wave_importer_t *wave_imp = (wave_importer_t *)importer->info; + if( (err = isom_timeline_set_track_ID( timeline, 1 )) < 0 + || (err = isom_timeline_set_movie_timescale( timeline, wave_imp->fmt.wfx.nSamplesPerSec )) < 0 + || (err = isom_timeline_set_media_timescale( timeline, wave_imp->fmt.wfx.nSamplesPerSec )) < 0 + || (err = isom_timeline_set_sample_count( timeline, wave_imp->number_of_samples )) < 0 + || (err = isom_timeline_set_max_sample_size( timeline, summary->max_au_length )) < 0 + || (err = isom_timeline_set_media_duration( timeline, wave_imp->number_of_samples )) < 0 + || (err = isom_timeline_set_track_duration( timeline, wave_imp->number_of_samples )) < 0 ) + goto fail; + isom_timeline_set_lpcm_sample_getter_funcs( timeline ); + uint64_t data_offset = wave_imp->chunk.data_offset; + for( uint32_t samples = 0; samples < wave_imp->number_of_samples; samples += summary->samples_in_frame ) + { + uint32_t duration; + if( wave_imp->number_of_samples - samples >= summary->samples_in_frame ) + duration = summary->samples_in_frame; + else + duration = wave_imp->number_of_samples - samples; + isom_lpcm_bunch_t bunch = + { + .pos = data_offset, + .duration = 1, + .offset = 0, + .length = wave_imp->fmt.wfx.nBlockAlign, + .index = 1, /* no changes */ + .chunk = &wave_imp->chunk, + .prop = (lsmash_sample_property_t){ .ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC }, + .sample_count = duration + }; + data_offset += duration * wave_imp->fmt.wfx.nBlockAlign; + if( (err = isom_add_lpcm_bunch_entry( timeline, &bunch )) < 0 ) + goto fail; + } + if( (err = lsmash_list_add_entry( file->timeline, timeline )) < 0 ) + goto fail; + return 0; +fail: + isom_timeline_destroy( timeline ); + isom_remove_timelines( file ); + return err; +} + +const importer_functions wave_importer = +{ + { "WAVE", offsetof( importer_t, log_level ) }, + 1, + wave_importer_probe, + wave_importer_get_accessunit, + wave_importer_get_last_delta, + wave_importer_cleanup, + wave_importer_construct_timeline +}; diff --git a/3rdparty/l-smash/lsmash.h b/3rdparty/l-smash/lsmash.h new file mode 100644 index 000000000..289c75058 --- /dev/null +++ b/3rdparty/l-smash/lsmash.h @@ -0,0 +1,4198 @@ +/***************************************************************************** + * lsmash.h + ***************************************************************************** + * Copyright (C) 2010-2017 L-SMASH project + * + * Authors: Yusuke Nakamura + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + *****************************************************************************/ + +/* This file is available under an ISC license. */ + +#ifndef LSMASH_H +#define LSMASH_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#define PRIVATE /* If this declaration is placed at a variable, any user shall NOT use it. */ + +/************************************************************************************** + * Handle windows dll imports. The user must define this before including this header. + **************************************************************************************/ +#ifdef LSMASH_API_IMPORTS +#define LSMASH_API __declspec(dllimport) +#else +#define LSMASH_API +#endif + +#define LSMASH_4CC( a, b, c, d ) (((a)<<24) | ((b)<<16) | ((c)<<8) | (d)) + +/**************************************************************************** + * Version + ****************************************************************************/ +#define LSMASH_VERSION_MAJOR 2 +#define LSMASH_VERSION_MINOR 16 +#define LSMASH_VERSION_MICRO 1 + +#define LSMASH_VERSION_INT( a, b, c ) (((a) << 16) | ((b) << 8) | (c)) + +#define LIBLSMASH_VERSION_INT LSMASH_VERSION_INT( LSMASH_VERSION_MAJOR, \ + LSMASH_VERSION_MINOR, \ + LSMASH_VERSION_MICRO ) + +/**************************************************************************** + * Error Values + ****************************************************************************/ +enum +{ + LSMASH_ERR_NAMELESS = -1, /* An error but not assigned to any following errors */ + LSMASH_ERR_MEMORY_ALLOC = -2, /* There is not enough room in the heap. */ + LSMASH_ERR_INVALID_DATA = -3, /* Invalid data was found. */ + LSMASH_ERR_FUNCTION_PARAM = -4, /* An error in the parameter list of the function */ + LSMASH_ERR_PATCH_WELCOME = -5, /* Not implemented yet, so patches welcome. */ + LSMASH_ERR_UNKNOWN = -6, /* Unknown error occured. */ + LSMASH_ERR_IO = -7, /* I/O error occurred. */ +}; + +/**************************************************************************** + * ROOT + * The top-level opaque handler for whole file handling. + ****************************************************************************/ +typedef struct lsmash_root_tag lsmash_root_t; + +/* Allocate a ROOT. + * The allocated ROOT can be deallocate by lsmash_destroy_root(). + * + * Return the address of an allocated ROOT if successful. + * Return NULL otherwise. */ +lsmash_root_t *lsmash_create_root +( + void +); + +/* Deallocate a given ROOT. */ +void lsmash_destroy_root +( + lsmash_root_t *root /* the address of a ROOT you want to deallocate */ +); + +/**************************************************************************** + * File Layer + ****************************************************************************/ +typedef struct lsmash_file_tag lsmash_file_t; + +typedef enum +{ + LSMASH_FILE_MODE_WRITE = 1, /* output/muxing */ + LSMASH_FILE_MODE_READ = 1<<1, /* input/demuxing */ + LSMASH_FILE_MODE_FRAGMENTED = 1<<2, /* movie fragments */ + LSMASH_FILE_MODE_DUMP = 1<<3, + LSMASH_FILE_MODE_BOX = 1<<4, /* box structure */ + LSMASH_FILE_MODE_INITIALIZATION = 1<<5, /* movie sample table */ + LSMASH_FILE_MODE_MEDIA = 1<<6, /* media data */ + LSMASH_FILE_MODE_INDEX = 1<<7, + LSMASH_FILE_MODE_SEGMENT = 1<<8, /* segment */ + LSMASH_FILE_MODE_WRITE_FRAGMENTED = LSMASH_FILE_MODE_WRITE | LSMASH_FILE_MODE_FRAGMENTED, /* deprecated */ +} lsmash_file_mode; + +typedef enum +{ + ISOM_BRAND_TYPE_3G2A = LSMASH_4CC( '3', 'g', '2', 'a' ), /* 3GPP2 */ + ISOM_BRAND_TYPE_3GE6 = LSMASH_4CC( '3', 'g', 'e', '6' ), /* 3GPP Release 6 Extended Presentation Profile */ + ISOM_BRAND_TYPE_3GE9 = LSMASH_4CC( '3', 'g', 'e', '9' ), /* 3GPP Release 9 Extended Presentation Profile */ + ISOM_BRAND_TYPE_3GF9 = LSMASH_4CC( '3', 'g', 'f', '9' ), /* 3GPP Release 9 File-delivery Server Profile */ + ISOM_BRAND_TYPE_3GG6 = LSMASH_4CC( '3', 'g', 'g', '6' ), /* 3GPP Release 6 General Profile */ + ISOM_BRAND_TYPE_3GG9 = LSMASH_4CC( '3', 'g', 'g', '9' ), /* 3GPP Release 9 General Profile */ + ISOM_BRAND_TYPE_3GH9 = LSMASH_4CC( '3', 'g', 'h', '9' ), /* 3GPP Release 9 Adaptive Streaming Profile */ + ISOM_BRAND_TYPE_3GM9 = LSMASH_4CC( '3', 'g', 'm', '9' ), /* 3GPP Release 9 Media Segment Profile */ + ISOM_BRAND_TYPE_3GP4 = LSMASH_4CC( '3', 'g', 'p', '4' ), /* 3GPP Release 4 */ + ISOM_BRAND_TYPE_3GP5 = LSMASH_4CC( '3', 'g', 'p', '5' ), /* 3GPP Release 5 */ + ISOM_BRAND_TYPE_3GP6 = LSMASH_4CC( '3', 'g', 'p', '6' ), /* 3GPP Release 6 Basic Profile */ + ISOM_BRAND_TYPE_3GP7 = LSMASH_4CC( '3', 'g', 'p', '7' ), /* 3GPP Release 7 */ + ISOM_BRAND_TYPE_3GP8 = LSMASH_4CC( '3', 'g', 'p', '8' ), /* 3GPP Release 8 */ + ISOM_BRAND_TYPE_3GP9 = LSMASH_4CC( '3', 'g', 'p', '9' ), /* 3GPP Release 9 Basic Profile */ + ISOM_BRAND_TYPE_3GR6 = LSMASH_4CC( '3', 'g', 'r', '6' ), /* 3GPP Release 6 Progressive Download Profile */ + ISOM_BRAND_TYPE_3GR9 = LSMASH_4CC( '3', 'g', 'r', '9' ), /* 3GPP Release 9 Progressive Download Profile */ + ISOM_BRAND_TYPE_3GS6 = LSMASH_4CC( '3', 'g', 's', '6' ), /* 3GPP Release 6 Streaming Server Profile */ + ISOM_BRAND_TYPE_3GS9 = LSMASH_4CC( '3', 'g', 's', '9' ), /* 3GPP Release 9 Streaming Server Profile */ + ISOM_BRAND_TYPE_3GT9 = LSMASH_4CC( '3', 'g', 't', '9' ), /* 3GPP Release 9 Media Stream Recording Profile */ + ISOM_BRAND_TYPE_ARRI = LSMASH_4CC( 'A', 'R', 'R', 'I' ), /* ARRI Digital Camera */ + ISOM_BRAND_TYPE_CAEP = LSMASH_4CC( 'C', 'A', 'E', 'P' ), /* Canon Digital Camera */ + ISOM_BRAND_TYPE_CDES = LSMASH_4CC( 'C', 'D', 'e', 's' ), /* Convergent Designs */ + ISOM_BRAND_TYPE_LCAG = LSMASH_4CC( 'L', 'C', 'A', 'G' ), /* Leica digital camera */ + ISOM_BRAND_TYPE_M4A = LSMASH_4CC( 'M', '4', 'A', ' ' ), /* iTunes MPEG-4 audio protected or not */ + ISOM_BRAND_TYPE_M4B = LSMASH_4CC( 'M', '4', 'B', ' ' ), /* iTunes AudioBook protected or not */ + ISOM_BRAND_TYPE_M4P = LSMASH_4CC( 'M', '4', 'P', ' ' ), /* MPEG-4 protected audio */ + ISOM_BRAND_TYPE_M4V = LSMASH_4CC( 'M', '4', 'V', ' ' ), /* MPEG-4 protected audio+video */ + ISOM_BRAND_TYPE_MFSM = LSMASH_4CC( 'M', 'F', 'S', 'M' ), /* Media File for Samsung video Metadata */ + ISOM_BRAND_TYPE_MPPI = LSMASH_4CC( 'M', 'P', 'P', 'I' ), /* Photo Player Multimedia Application Format */ + ISOM_BRAND_TYPE_ROSS = LSMASH_4CC( 'R', 'O', 'S', 'S' ), /* Ross Video */ + ISOM_BRAND_TYPE_AVC1 = LSMASH_4CC( 'a', 'v', 'c', '1' ), /* Advanced Video Coding extensions */ + ISOM_BRAND_TYPE_BBXM = LSMASH_4CC( 'b', 'b', 'x', 'm' ), /* Blinkbox Master File */ + ISOM_BRAND_TYPE_CAQV = LSMASH_4CC( 'c', 'a', 'q', 'v' ), /* Casio Digital Camera */ + ISOM_BRAND_TYPE_CCFF = LSMASH_4CC( 'c', 'c', 'f', 'f' ), /* Common container file format */ + ISOM_BRAND_TYPE_DA0A = LSMASH_4CC( 'd', 'a', '0', 'a' ), /* DMB AF */ + ISOM_BRAND_TYPE_DA0B = LSMASH_4CC( 'd', 'a', '0', 'b' ), /* DMB AF */ + ISOM_BRAND_TYPE_DA1A = LSMASH_4CC( 'd', 'a', '1', 'a' ), /* DMB AF */ + ISOM_BRAND_TYPE_DA1B = LSMASH_4CC( 'd', 'a', '1', 'b' ), /* DMB AF */ + ISOM_BRAND_TYPE_DA2A = LSMASH_4CC( 'd', 'a', '2', 'a' ), /* DMB AF */ + ISOM_BRAND_TYPE_DA2B = LSMASH_4CC( 'd', 'a', '2', 'b' ), /* DMB AF */ + ISOM_BRAND_TYPE_DA3A = LSMASH_4CC( 'd', 'a', '3', 'a' ), /* DMB AF */ + ISOM_BRAND_TYPE_DA3B = LSMASH_4CC( 'd', 'a', '3', 'b' ), /* DMB AF */ + ISOM_BRAND_TYPE_DASH = LSMASH_4CC( 'd', 'a', 's', 'h' ), /* Indexed self-initializing Media Segment */ + ISOM_BRAND_TYPE_DBY1 = LSMASH_4CC( 'd', 'b', 'y', '1' ), /* MP4 files with Dolby content */ + ISOM_BRAND_TYPE_DMB1 = LSMASH_4CC( 'd', 'm', 'b', '1' ), /* DMB AF */ + ISOM_BRAND_TYPE_DSMS = LSMASH_4CC( 'd', 's', 'm', 's' ), /* Self-initializing Media Segment */ + ISOM_BRAND_TYPE_DV1A = LSMASH_4CC( 'd', 'v', '1', 'a' ), /* DMB AF */ + ISOM_BRAND_TYPE_DV1B = LSMASH_4CC( 'd', 'v', '1', 'b' ), /* DMB AF */ + ISOM_BRAND_TYPE_DV2A = LSMASH_4CC( 'd', 'v', '2', 'a' ), /* DMB AF */ + ISOM_BRAND_TYPE_DV2B = LSMASH_4CC( 'd', 'v', '2', 'b' ), /* DMB AF */ + ISOM_BRAND_TYPE_DV3A = LSMASH_4CC( 'd', 'v', '3', 'a' ), /* DMB AF */ + ISOM_BRAND_TYPE_DV3B = LSMASH_4CC( 'd', 'v', '3', 'b' ), /* DMB AF */ + ISOM_BRAND_TYPE_DVR1 = LSMASH_4CC( 'd', 'v', 'r', '1' ), /* DVB RTP */ + ISOM_BRAND_TYPE_DVT1 = LSMASH_4CC( 'd', 'v', 't', '1' ), /* DVB Transport Stream */ + ISOM_BRAND_TYPE_IFRM = LSMASH_4CC( 'i', 'f', 'r', 'm' ), /* Apple iFrame */ + ISOM_BRAND_TYPE_ISC2 = LSMASH_4CC( 'i', 's', 'c', '2' ), /* Files encrypted according to ISMACryp 2.0 */ + ISOM_BRAND_TYPE_ISO2 = LSMASH_4CC( 'i', 's', 'o', '2' ), /* ISO Base Media file format version 2 */ + ISOM_BRAND_TYPE_ISO3 = LSMASH_4CC( 'i', 's', 'o', '3' ), /* ISO Base Media file format version 3 */ + ISOM_BRAND_TYPE_ISO4 = LSMASH_4CC( 'i', 's', 'o', '4' ), /* ISO Base Media file format version 4 */ + ISOM_BRAND_TYPE_ISO5 = LSMASH_4CC( 'i', 's', 'o', '5' ), /* ISO Base Media file format version 5 */ + ISOM_BRAND_TYPE_ISO6 = LSMASH_4CC( 'i', 's', 'o', '6' ), /* ISO Base Media file format version 6 */ + ISOM_BRAND_TYPE_ISO7 = LSMASH_4CC( 'i', 's', 'o', '7' ), /* ISO Base Media file format version 7 */ + ISOM_BRAND_TYPE_ISOM = LSMASH_4CC( 'i', 's', 'o', 'm' ), /* ISO Base Media file format version 1 */ + ISOM_BRAND_TYPE_JPSI = LSMASH_4CC( 'j', 'p', 's', 'i' ), /* The JPSearch data interchange format */ + ISOM_BRAND_TYPE_LMSG = LSMASH_4CC( 'l', 'm', 's', 'g' ), /* last Media Segment indicator */ + ISOM_BRAND_TYPE_MJ2S = LSMASH_4CC( 'm', 'j', '2', 's' ), /* Motion JPEG 2000 simple profile */ + ISOM_BRAND_TYPE_MJP2 = LSMASH_4CC( 'm', 'j', 'p', '2' ), /* Motion JPEG 2000, general profile */ + ISOM_BRAND_TYPE_MP21 = LSMASH_4CC( 'm', 'p', '2', '1' ), /* MPEG-21 */ + ISOM_BRAND_TYPE_MP41 = LSMASH_4CC( 'm', 'p', '4', '1' ), /* MP4 version 1 */ + ISOM_BRAND_TYPE_MP42 = LSMASH_4CC( 'm', 'p', '4', '2' ), /* MP4 version 2 */ + ISOM_BRAND_TYPE_MP71 = LSMASH_4CC( 'm', 'p', '7', '1' ), /* MPEG-7 file-level metadata */ + ISOM_BRAND_TYPE_MSDH = LSMASH_4CC( 'm', 's', 'd', 'h' ), /* Media Segment */ + ISOM_BRAND_TYPE_MSIX = LSMASH_4CC( 'm', 's', 'i', 'x' ), /* Indexed Media Segment */ + ISOM_BRAND_TYPE_NIKO = LSMASH_4CC( 'n', 'i', 'k', 'o' ), /* Nikon Digital Camera */ + ISOM_BRAND_TYPE_ODCF = LSMASH_4CC( 'o', 'd', 'c', 'f' ), /* OMA DCF */ + ISOM_BRAND_TYPE_OPF2 = LSMASH_4CC( 'o', 'p', 'f', '2' ), /* OMA PDCF */ + ISOM_BRAND_TYPE_OPX2 = LSMASH_4CC( 'o', 'p', 'x', '2' ), /* OMA Adapted PDCF */ + ISOM_BRAND_TYPE_PANA = LSMASH_4CC( 'p', 'a', 'n', 'a' ), /* Panasonic Digital Camera */ + ISOM_BRAND_TYPE_PIFF = LSMASH_4CC( 'p', 'i', 'f', 'f' ), /* Protected Interoperable File Format */ + ISOM_BRAND_TYPE_PNVI = LSMASH_4CC( 'p', 'n', 'v', 'i' ), /* Panasonic Video Intercom */ + ISOM_BRAND_TYPE_QT = LSMASH_4CC( 'q', 't', ' ', ' ' ), /* QuickTime file format */ + ISOM_BRAND_TYPE_RISX = LSMASH_4CC( 'r', 'i', 's', 'x' ), /* Representation Index Segment */ + ISOM_BRAND_TYPE_SDV = LSMASH_4CC( 's', 'd', 'v', ' ' ), /* SD Video */ + ISOM_BRAND_TYPE_SIMS = LSMASH_4CC( 's', 'i', 'm', 's' ), /* Sub-Indexed Media Segment */ + ISOM_BRAND_TYPE_SISX = LSMASH_4CC( 's', 'i', 's', 'x' ), /* Single Index Segment */ + ISOM_BRAND_TYPE_SSSS = LSMASH_4CC( 's', 's', 's', 's' ), /* Subsegment Index Segment */ +} lsmash_brand_type; + +typedef struct +{ + lsmash_file_mode mode; /* file modes */ + /** custom I/O stuff **/ + void *opaque; /* custom I/O opaque handler used for the following callback functions */ + /* Attempt to read up to 'size' bytes from the file referenced by 'opaque' into the buffer starting at 'buf'. + * + * Return the number of bytes read if successful. + * Return 0 if no more read. + * Return a negative value otherwise. */ + int (*read) + ( + void *opaque, + uint8_t *buf, + int size + ); + /* Write up to 'size' bytes to the file referenced by 'opaque' from the buffer starting at 'buf'. + * + * Return the number of bytes written if successful. + * Return a negative value otherwise. */ + int (*write) + ( + void *opaque, + uint8_t *buf, + int size + ); + /* Change the location of the read/write pointer of 'opaque'. + * The offset of the pointer is determined according to the directive 'whence' as follows: + * If 'whence' is set to SEEK_SET, the offset is set to 'offset' bytes. + * If 'whence' is set to SEEK_CUR, the offset is set to its current location plus 'offset' bytes. + * If 'whence' is set to SEEK_END, the offset is set to the size of the file plus 'offset' bytes. + * + * Return the resulting offset of the location in bytes from the beginning of the file if successful. + * Return a negative value otherwise. */ + int64_t (*seek) + ( + void *opaque, + int64_t offset, + int whence + ); + /** file types or segment types **/ + lsmash_brand_type major_brand; /* the best used brand */ + lsmash_brand_type *brands; /* the list of compatible brands */ + uint32_t brand_count; /* the number of compatible brands used in the file */ + uint32_t minor_version; /* minor version of the best used brand + * minor_version is informative only i.e. not specifying requirements but merely providing information. + * It must not be used to determine the conformance of a file to a standard. */ + /** muxing only **/ + double max_chunk_duration; /* max duration per chunk in seconds. 0.5 is default value. */ + double max_async_tolerance; /* max tolerance, in seconds, for amount of interleaving asynchronization between tracks. + * 2.0 is default value. At least twice of max_chunk_duration is used. */ + uint64_t max_chunk_size; /* max size per chunk in bytes. 4*1024*1024 (4MiB) is default value. */ + /** demuxing only **/ + uint64_t max_read_size; /* max size of reading from the file at a time. 4*1024*1024 (4MiB) is default value. */ +} lsmash_file_parameters_t; + +typedef int (*lsmash_adhoc_remux_callback)( void *param, uint64_t done, uint64_t total ); +typedef struct +{ + uint64_t buffer_size; + lsmash_adhoc_remux_callback func; + void *param; +} lsmash_adhoc_remux_t; + +/* Open a file where the path is given. + * And if successful, set up the parameters by 'open_mode'. + * Here, the 'open_mode' parameter is either 0 or 1 as follows: + * 0: Create a file for output/muxing operations. + * If a file with the same name already exists, its contents are discarded and the file is treated as a new file. + * If user specifies "-" for 'filename', operations are done on stdout. + * The file types or segment types are set up as specified in 'param'. + * 1: Open a file for input/demuxing operations. The file must exist. + * If user specifies "-" for 'filename', operations are done on stdin. + * + * This function sets up file modes minimally. + * User can add additional modes and/or remove modes already set later. + * The other parameters except for the custom I/O stuff are set to a default. + * User shall not touch the custom I/O stuff for the opened file if using this function. + * + * The opened file can be closed by lsmash_close_file(). + * + * Note: + * 'filename' must be encoded by UTF-8 if 'open_mode' is equal to 0. + * On Windows, lsmash_convert_ansi_to_utf8() may help you. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_open_file +( + const char *filename, + int open_mode, + lsmash_file_parameters_t *param +); + +/* Close a file opened by lsmash_open_file(). + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_close_file +( + lsmash_file_parameters_t *param +); + +/* Associate a file with a ROOT and allocate the handle of that file. + * The all allocated handles can be deallocated by lsmash_destroy_root(). + * If the ROOT has no associated file yet, the first associated file is activated. + * + * Return the address of the allocated handle of the added file if successful. + * Return NULL otherwise. */ +lsmash_file_t *lsmash_set_file +( + lsmash_root_t *root, + lsmash_file_parameters_t *param +); + +/* Read whole boxes in a given file. + * You can also get file modes and file types or segment types by this function. + * + * Return the file size (if seekable) or 0 if successful. + * Return a negative value otherwise. */ +int64_t lsmash_read_file +( + lsmash_file_t *file, + lsmash_file_parameters_t *param +); + +/* Deallocate all boxes within the current active file in a given ROOT. */ +void lsmash_discard_boxes +( + lsmash_root_t *root /* the address of a ROOT you want to deallocate all boxes within the active file in it */ +); + +/* Activate a file associated with a ROOT. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_activate_file +( + lsmash_root_t *root, + lsmash_file_t *file +); + +/* Switch from the current segment file to the following media segment file. + * After switching, the followed segment file can be closed unless that file is an initialization segment. + * + * The first followed segment file must be also an initialization segment. + * The second or later segment files must not be an initialization segment. + * For media segment files flagging LSMASH_FILE_MODE_INDEX, 'remux' must be set. + * + * Users shall call lsmash_flush_pooled_samples() for each track before calling this function. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_switch_media_segment +( + lsmash_root_t *root, + lsmash_file_t *successor, + lsmash_adhoc_remux_t *remux +); + +/**************************************************************************** + * Basic Types + ****************************************************************************/ +/* rational types */ +typedef struct +{ + uint32_t n; /* numerator */ + uint32_t d; /* denominator */ +} lsmash_rational_u32_t; + +typedef struct +{ + int32_t n; /* numerator */ + uint32_t d; /* denominator */ +} lsmash_rational_s32_t; + +typedef enum +{ + LSMASH_BOOLEAN_FALSE = 0, + LSMASH_BOOLEAN_TRUE = 1 +} lsmash_boolean_t; + +/**************************************************************************** + * Allocation + ****************************************************************************/ +/* Allocate a memory block. + * The allocated memory block can be deallocate by lsmash_free(). + * + * Return the address to the beginning of a memory block if successful. + * Return NULL otherwise. */ +void *lsmash_malloc +( + size_t size /* size of a memory block, in bytes */ +); + +/* Allocate a memory block. + * The allocated memory block shall be initialized to all bits 0. + * The allocated memory block can be deallocate by lsmash_free(). + * + * Return the address to the beginning of a memory block if successful. + * Return NULL otherwise. */ +void *lsmash_malloc_zero +( + size_t size /* size of a memory block, in bytes */ +); + +/* Reallocate a memory block. + * The reallocated memory block can be deallocate by lsmash_free(). + * If this function succeed, the given memory block is deallocated and the address is invalid. + * If this function fails, the address to the given memory block is still valid and the memory block is unchanged. + * + * Return the address to the beginning of a memory block if successful. + * Return NULL otherwise. */ +void *lsmash_realloc +( + void *ptr, /* an address to a memory block previously allocated with + * lsmash_malloc(), lsmash_malloc_zero(), lsmash_realloc() or lsmash_memdup() + * Alternatively, NULL makes this function to allocate a new memory block. */ + size_t size /* size of a memory block, in bytes */ +); + +/* Allocate a memory block and copy all bits from a given memory block. + * The allocated memory block can be deallocate by lsmash_free(). + * + * Return the address to the beginning of an allocated memory block if successful. + * Return NULL otherwise. */ +void *lsmash_memdup +( + const void *ptr, /* an address to the source of data to be copied */ + size_t size /* number of bytes to copy */ +); + +/* Deallocate a given memory block. + * If the given address to a memory block is NULL, this function does nothing. */ +void lsmash_free +( + void *ptr /* an address to a memory block previously allocated with + * lsmash_malloc(), lsmash_malloc_zero(), lsmash_realloc() or lsmash_memdup() */ +); + +/* Deallocate a given memory block. + * If the given address to a memory block is NULL, this function does nothing. + * Set NULL to the pointer to the memory block after deallocating. + * + * As an example of usage. + * Let's say you allocate a memory block and set the address to the beginning of it to the pointer 'ptr'. + * You can deallocate the memory block and set NULL to 'ptr' by lsmash_freep( &ptr ). + */ +void lsmash_freep +( + void *ptrptr /* the address to a pointer to a memory block previously allocated with + * lsmash_malloc(), lsmash_malloc_zero(), lsmash_realloc() or lsmash_memdup() */ +); + +/**************************************************************************** + * Box + ****************************************************************************/ +typedef struct lsmash_box_tag lsmash_box_t; +typedef uint32_t lsmash_compact_box_type_t; + +/* An UUID structure for extended box type */ +typedef struct +{ + uint32_t fourcc; /* four characters codes that identify extended box type partially + * If the box is not a UUID box, this field shall be the same as the box type. + * Note: characters in this field aren't always printable. */ + uint8_t id[12]; /* If the box is not a UUID box, this field shall be set to 12-byte ISO reserved value + * { 0x00, 0x11, 0x00, 0x10, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 } + * and shall not be written into the stream together with above-defined four characters codes. + * As an exception, we could set the value + * { 0x0F, 0x11, 0x4D, 0xA5, 0xBF, 0x4E, 0xF2, 0xC4, 0x8C, 0x6A, 0xA1, 0x1E } + * to indicate the box is derived from QuickTime file format. */ +} lsmash_extended_box_type_t; + +typedef struct +{ + lsmash_compact_box_type_t fourcc; /* four characters codes that identify box type + * Note: characters in this field aren't always printable. */ + lsmash_extended_box_type_t user; /* Universal Unique IDentifier, i.e. UUID */ + /* If 'fourcc' doesn't equal 'uuid', ignore this field. */ +} lsmash_box_type_t; + +typedef struct +{ + lsmash_box_type_t type; /* box type */ + uint32_t number; /* the number of box in ascending order excluding boxes unspecified by 'type' within + * the same level of the box nested structure. */ +} lsmash_box_path_t; + +#define LSMASH_BOX_TYPE_INITIALIZER { 0x00000000, { 0x00000000, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } } +#define LSMASH_BOX_TYPE_UNSPECIFIED static_lsmash_box_type_unspecified +LSMASH_API extern const lsmash_box_type_t static_lsmash_box_type_unspecified; + +/* Return extended box type that consists of combination of given FourCC and 12-byte ID. */ +lsmash_extended_box_type_t lsmash_form_extended_box_type +( + uint32_t fourcc, + const uint8_t id[12] +); + +/* Return box type that consists of combination of given compact and extended box type. */ +lsmash_box_type_t lsmash_form_box_type +( + lsmash_compact_box_type_t type, + lsmash_extended_box_type_t user +); + +#define LSMASH_ISO_BOX_TYPE_INITIALIZER( x ) { x, { x, { 0x00, 0x11, 0x00, 0x10, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 } } } +#define LSMASH_QTFF_BOX_TYPE_INITIALIZER( x ) { x, { x, { 0x0F, 0x11, 0x4D, 0xA5, 0xBF, 0x4E, 0xF2, 0xC4, 0x8C, 0x6A, 0xA1, 0x1E } } } +lsmash_box_type_t lsmash_form_iso_box_type +( + lsmash_compact_box_type_t type +); + +lsmash_box_type_t lsmash_form_qtff_box_type +( + lsmash_compact_box_type_t type +); + +/* precedence of the box position + * Box with higher value will precede physically other boxes with lower one. + * The lower 32-bits are intended to determine order of boxes with the same box type. */ +#define LSMASH_BOX_PRECEDENCE_L 0x0000000000800000ULL /* Lowest */ +#define LSMASH_BOX_PRECEDENCE_LP 0x000FFFFF00000000ULL /* Lowest+ */ +#define LSMASH_BOX_PRECEDENCE_N 0x0080000000000000ULL /* Normal */ +#define LSMASH_BOX_PRECEDENCE_HM 0xFFEEEEEE00000000ULL /* Highest- */ +#define LSMASH_BOX_PRECEDENCE_H 0xFFFFFFFF00800000ULL /* Highest */ +#define LSMASH_BOX_PRECEDENCE_S 0x0000010000000000ULL /* Step */ + +/* Check if the type of two boxes is identical or not. + * + * Return 1 if the both box types are identical. + * Return 0 otherwise. */ +int lsmash_check_box_type_identical +( + lsmash_box_type_t a, + lsmash_box_type_t b +); + +/* Check if the type of a given box is already specified or not. + * + * Return 1 if the box type is specified. + * Return 0 otherwise, i.e. LSMASH_BOX_TYPE_UNSPECIFIED. */ +int lsmash_check_box_type_specified +( + const lsmash_box_type_t *box_type +); + +/* Allocate a box. + * The allocated box can be deallocated by lsmash_destroy_box(). + * + * Return the address of an allocated box if successful. + * Return NULL otherwise. */ +lsmash_box_t *lsmash_create_box +( + lsmash_box_type_t type, + uint8_t *data, + uint32_t size, + uint64_t precedence +); + +/* Get a box under a given 'parent' box. + * The path of a box must be terminated by LSMASH_BOX_TYPE_UNSPECIFIED. + * + * Return the address of the box specified by 'box_path'. + * Return NULL otherwise. */ +lsmash_box_t *lsmash_get_box +( + lsmash_box_t *parent, + const lsmash_box_path_t box_path[] +); + +/* Add a box into 'parent' box as a child box. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_add_box +( + lsmash_box_t *parent, + lsmash_box_t *box +); + +/* Add a box into 'parent' box as a child box. + * If the adding child box is known and its children (if any) are known, expand them into known + * struct formats for the internal references within the L-SMASH library. + * If this function succeed, the adding child box is deallocated and the address is invalid. + * Instead of that, this function replaces the invalid address with the valid one of the new + * allocated memory block representing the added and expanded child box. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_add_box_ex +( + lsmash_box_t *parent, + lsmash_box_t **box +); + +/* Deallocate a given box and its children. */ +void lsmash_destroy_box +( + lsmash_box_t *box +); + +/* Deallocate all children of a given box. */ +void lsmash_destroy_children +( + lsmash_box_t *box +); + +/* Get the precedence of a given box. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_get_box_precedence +( + lsmash_box_t *box, + uint64_t *precedence +); + +/* This function allows you to handle a ROOT as if it is a box. + * Of course, you can deallocate the ROOT by lsmash_destroy_box(). + * + * Return the address of a given ROOT as a box. */ +lsmash_box_t *lsmash_root_as_box +( + lsmash_root_t *root +); + +/* This function allows you to handle the handle of a file as if it is a box. + * Of course, you can deallocate the handle of the file by lsmash_destroy_box(). + * + * Return the address of the handle of a given file as a box. */ +lsmash_box_t *lsmash_file_as_box +( + lsmash_file_t *file +); + +/* Write a top level box and its children already added to a file. + * WARNING: + * You should not use this function as long as media data is incompletely written. + * That is before starting to write a media data or after finishing of writing that. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_write_top_level_box +( + lsmash_box_t *box +); + +/* Export the data of a given box and its children as the binary string. + * The returned address is of the beginning of an allocated memory block. + * You can deallocate the memory block by lsmash_free(). + * + * Note that some boxes cannot be exported since L-SMASH might skip the cache for them. + * Media Data Box is an unexportable example. + * + * Return the address to the beginning of the binary string if successful. + * Return NULL otherwise. */ +uint8_t *lsmash_export_box +( + lsmash_box_t *box, + uint32_t *size +); + +/**************************************************************************** + * CODEC identifiers + ****************************************************************************/ +typedef lsmash_box_type_t lsmash_codec_type_t; + +#define LSMASH_CODEC_TYPE_INITIALIZER LSMASH_BOX_TYPE_INITIALIZER +#define LSMASH_CODEC_TYPE_UNSPECIFIED LSMASH_BOX_TYPE_UNSPECIFIED + +#ifndef LSMASH_INITIALIZE_CODEC_ID_HERE +#define DEFINE_ISOM_CODEC_TYPE( BOX_TYPE_NAME, BOX_TYPE_FOURCC ) \ + LSMASH_API extern const lsmash_codec_type_t BOX_TYPE_NAME +#define DEFINE_QTFF_CODEC_TYPE( BOX_TYPE_NAME, BOX_TYPE_FOURCC ) \ + LSMASH_API extern const lsmash_codec_type_t BOX_TYPE_NAME +#else +#define DEFINE_ISOM_CODEC_TYPE( BOX_TYPE_NAME, BOX_TYPE_FOURCC ) \ + const lsmash_codec_type_t BOX_TYPE_NAME = LSMASH_ISO_BOX_TYPE_INITIALIZER( BOX_TYPE_FOURCC ) +#define DEFINE_QTFF_CODEC_TYPE( BOX_TYPE_NAME, BOX_TYPE_FOURCC ) \ + const lsmash_codec_type_t BOX_TYPE_NAME = LSMASH_QTFF_BOX_TYPE_INITIALIZER( BOX_TYPE_FOURCC ) +#endif + +/* Audio CODEC identifiers */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_AC_3_AUDIO, LSMASH_4CC( 'a', 'c', '-', '3' ) ); /* AC-3 audio */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_ALAC_AUDIO, LSMASH_4CC( 'a', 'l', 'a', 'c' ) ); /* Apple lossless audio codec */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_DRA1_AUDIO, LSMASH_4CC( 'd', 'r', 'a', '1' ) ); /* DRA Audio */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_DTSEL_AUDIO, LSMASH_4CC( 'd', 't', 's', '+' ) ); /* Enhancement layer for DTS layered audio */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_DTSDL_AUDIO, LSMASH_4CC( 'd', 't', 's', '-' ) ); /* Dependent base layer for DTS layered audio */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_DTSC_AUDIO, LSMASH_4CC( 'd', 't', 's', 'c' ) ); /* DTS Coherent Acoustics audio */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_DTSE_AUDIO, LSMASH_4CC( 'd', 't', 's', 'e' ) ); /* DTS Express low bit rate audio, also known as DTS LBR */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_DTSH_AUDIO, LSMASH_4CC( 'd', 't', 's', 'h' ) ); /* DTS-HD High Resolution Audio */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_DTSL_AUDIO, LSMASH_4CC( 'd', 't', 's', 'l' ) ); /* DTS-HD Master Audio */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_DTSX_AUDIO, LSMASH_4CC( 'd', 't', 's', 'x' ) ); /* DTS:X */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_EC_3_AUDIO, LSMASH_4CC( 'e', 'c', '-', '3' ) ); /* Enhanced AC-3 audio */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_ENCA_AUDIO, LSMASH_4CC( 'e', 'n', 'c', 'a' ) ); /* Encrypted/Protected audio */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_G719_AUDIO, LSMASH_4CC( 'g', '7', '1', '9' ) ); /* ITU-T Recommendation G.719 (2008) ); */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_G726_AUDIO, LSMASH_4CC( 'g', '7', '2', '6' ) ); /* ITU-T Recommendation G.726 (1990) ); */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_M4AE_AUDIO, LSMASH_4CC( 'm', '4', 'a', 'e' ) ); /* MPEG-4 Audio Enhancement */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_MLPA_AUDIO, LSMASH_4CC( 'm', 'l', 'p', 'a' ) ); /* MLP Audio */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_MP4A_AUDIO, LSMASH_4CC( 'm', 'p', '4', 'a' ) ); /* MPEG-4 Audio */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_RAW_AUDIO, LSMASH_4CC( 'r', 'a', 'w', ' ' ) ); /* Uncompressed audio */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_SAMR_AUDIO, LSMASH_4CC( 's', 'a', 'm', 'r' ) ); /* Narrowband AMR voice */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_SAWB_AUDIO, LSMASH_4CC( 's', 'a', 'w', 'b' ) ); /* Wideband AMR voice */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_SAWP_AUDIO, LSMASH_4CC( 's', 'a', 'w', 'p' ) ); /* Extended AMR-WB (AMR-WB+) ); */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_SEVC_AUDIO, LSMASH_4CC( 's', 'e', 'v', 'c' ) ); /* EVRC Voice */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_SQCP_AUDIO, LSMASH_4CC( 's', 'q', 'c', 'p' ) ); /* 13K Voice */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_SSMV_AUDIO, LSMASH_4CC( 's', 's', 'm', 'v' ) ); /* SMV Voice */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_TWOS_AUDIO, LSMASH_4CC( 't', 'w', 'o', 's' ) ); /* Uncompressed 16-bit audio */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_WMA_AUDIO, LSMASH_4CC( 'w', 'm', 'a', ' ' ) ); /* Windows Media Audio V2 or V3 (not registered at MP4RA) */ + +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_23NI_AUDIO, LSMASH_4CC( '2', '3', 'n', 'i' ) ); /* 32-bit little endian integer uncompressed */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_MAC3_AUDIO, LSMASH_4CC( 'M', 'A', 'C', '3' ) ); /* MACE 3:1 */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_MAC6_AUDIO, LSMASH_4CC( 'M', 'A', 'C', '6' ) ); /* MACE 6:1 */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_NONE_AUDIO, LSMASH_4CC( 'N', 'O', 'N', 'E' ) ); /* either 'raw ' or 'twos' */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_QDM2_AUDIO, LSMASH_4CC( 'Q', 'D', 'M', '2' ) ); /* Qdesign music 2 */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_QDMC_AUDIO, LSMASH_4CC( 'Q', 'D', 'M', 'C' ) ); /* Qdesign music 1 */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_QCLP_AUDIO, LSMASH_4CC( 'Q', 'c', 'l', 'p' ) ); /* Qualcomm PureVoice */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_AC_3_AUDIO, LSMASH_4CC( 'a', 'c', '-', '3' ) ); /* Digital Audio Compression Standard (AC-3, Enhanced AC-3) */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_AGSM_AUDIO, LSMASH_4CC( 'a', 'g', 's', 'm' ) ); /* GSM */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_ALAC_AUDIO, LSMASH_4CC( 'a', 'l', 'a', 'c' ) ); /* Apple lossless audio codec */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_ALAW_AUDIO, LSMASH_4CC( 'a', 'l', 'a', 'w' ) ); /* a-Law 2:1 */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_CDX2_AUDIO, LSMASH_4CC( 'c', 'd', 'x', '2' ) ); /* CD/XA 2:1 */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_CDX4_AUDIO, LSMASH_4CC( 'c', 'd', 'x', '4' ) ); /* CD/XA 4:1 */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_DVCA_AUDIO, LSMASH_4CC( 'd', 'v', 'c', 'a' ) ); /* DV Audio */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_DVI_AUDIO, LSMASH_4CC( 'd', 'v', 'i', ' ' ) ); /* DVI (as used in RTP, 4:1 compression) */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_FL32_AUDIO, LSMASH_4CC( 'f', 'l', '3', '2' ) ); /* 32-bit float */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_FL64_AUDIO, LSMASH_4CC( 'f', 'l', '6', '4' ) ); /* 64-bit float */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_IMA4_AUDIO, LSMASH_4CC( 'i', 'm', 'a', '4' ) ); /* IMA (International Multimedia Assocation, defunct, 4:1) */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_IN24_AUDIO, LSMASH_4CC( 'i', 'n', '2', '4' ) ); /* 24-bit integer uncompressed */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_IN32_AUDIO, LSMASH_4CC( 'i', 'n', '3', '2' ) ); /* 32-bit integer uncompressed */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_LPCM_AUDIO, LSMASH_4CC( 'l', 'p', 'c', 'm' ) ); /* Uncompressed audio (various integer and float formats) */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_MP4A_AUDIO, LSMASH_4CC( 'm', 'p', '4', 'a' ) ); /* MPEG-4 Audio */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_RAW_AUDIO, LSMASH_4CC( 'r', 'a', 'w', ' ' ) ); /* 8-bit offset-binary uncompressed */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_SOWT_AUDIO, LSMASH_4CC( 's', 'o', 'w', 't' ) ); /* 16-bit little endian uncompressed */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_TWOS_AUDIO, LSMASH_4CC( 't', 'w', 'o', 's' ) ); /* 8-bit or 16-bit big endian uncompressed */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_ULAW_AUDIO, LSMASH_4CC( 'u', 'l', 'a', 'w' ) ); /* uLaw 2:1 */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_VDVA_AUDIO, LSMASH_4CC( 'v', 'd', 'v', 'a' ) ); /* DV audio (variable duration per video frame) */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_FULLMP3_AUDIO, LSMASH_4CC( '.', 'm', 'p', '3' ) ); /* MPEG-1 layer 3, CBR & VBR (QT4.1 and later) */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_MP3_AUDIO, 0x6D730055 ); /* MPEG-1 layer 3, CBR only (pre-QT4.1) */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_ADPCM2_AUDIO, 0x6D730002 ); /* Microsoft ADPCM - ACM code 2 */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_ADPCM17_AUDIO, 0x6D730011 ); /* DVI/Intel IMA ADPCM - ACM code 17 */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_GSM49_AUDIO, 0x6D730031 ); /* Microsoft GSM 6.10 - ACM code 49 */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_NOT_SPECIFIED, 0x00000000 ); /* either 'raw ' or 'twos' */ + +/* Video CODEC identifiers */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_AVC1_VIDEO, LSMASH_4CC( 'a', 'v', 'c', '1' ) ); /* Advanced Video Coding + * Any sample must not contain any paramerter set and filler data. */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_AVC2_VIDEO, LSMASH_4CC( 'a', 'v', 'c', '2' ) ); /* Advanced Video Coding + * Any sample must not contain any paramerter set and filler data. + * May only be used when Extractors or Aggregators are required to be supported. */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_AVC3_VIDEO, LSMASH_4CC( 'a', 'v', 'c', '3' ) ); /* Advanced Video Coding + * It is allowed that sample contains parameter sets and filler data. */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_AVC4_VIDEO, LSMASH_4CC( 'a', 'v', 'c', '4' ) ); /* Advanced Video Coding + * It is allowed that sample contains parameter sets and filler data. + * May only be used when Extractors or Aggregators are required to be supported. */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_AVCP_VIDEO, LSMASH_4CC( 'a', 'v', 'c', 'p' ) ); /* Advanced Video Coding Parameters */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_DRAC_VIDEO, LSMASH_4CC( 'd', 'r', 'a', 'c' ) ); /* Dirac Video Coder */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_ENCV_VIDEO, LSMASH_4CC( 'e', 'n', 'c', 'v' ) ); /* Encrypted/protected video */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_HVC1_VIDEO, LSMASH_4CC( 'h', 'v', 'c', '1' ) ); /* High Efficiency Video Coding + * The default and mandatory value of array_completeness is 1 for arrays of + * all types of parameter sets, and 0 for all other arrays. + * This means any sample must not contain any paramerter set and filler data. */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_HEV1_VIDEO, LSMASH_4CC( 'h', 'e', 'v', '1' ) ); /* High Efficiency Video Coding + * The default value of array_completeness is 0 for all arrays. + * It is allowed that sample contains parameter sets and filler data. */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_MJP2_VIDEO, LSMASH_4CC( 'm', 'j', 'p', '2' ) ); /* Motion JPEG 2000 */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_MP4V_VIDEO, LSMASH_4CC( 'm', 'p', '4', 'v' ) ); /* MPEG-4 Visual */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_MVC1_VIDEO, LSMASH_4CC( 'm', 'v', 'c', '1' ) ); /* Multiview coding */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_MVC2_VIDEO, LSMASH_4CC( 'm', 'v', 'c', '2' ) ); /* Multiview coding */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_S263_VIDEO, LSMASH_4CC( 's', '2', '6', '3' ) ); /* ITU H.263 video (3GPP format) */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_SVC1_VIDEO, LSMASH_4CC( 's', 'v', 'c', '1' ) ); /* Scalable Video Coding */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_VC_1_VIDEO, LSMASH_4CC( 'v', 'c', '-', '1' ) ); /* SMPTE VC-1 */ + +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_2VUY_VIDEO, LSMASH_4CC( '2', 'v', 'u', 'y' ) ); /* Uncompressed Y'CbCr, 8-bit-per-component 4:2:2 + * |Cb(8)|Y'0(8)|Cr(8)|Y'1(8)| */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_CFHD_VIDEO, LSMASH_4CC( 'C', 'F', 'H', 'D' ) ); /* CineForm High-Definition (HD) wavelet codec */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_DV10_VIDEO, LSMASH_4CC( 'D', 'V', '1', '0' ) ); /* Digital Voodoo 10 bit Uncompressed 4:2:2 codec */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_DVOO_VIDEO, LSMASH_4CC( 'D', 'V', 'O', 'O' ) ); /* Digital Voodoo 8 bit Uncompressed 4:2:2 codec */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_DVOR_VIDEO, LSMASH_4CC( 'D', 'V', 'O', 'R' ) ); /* Digital Voodoo intermediate raw */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_DVTV_VIDEO, LSMASH_4CC( 'D', 'V', 'T', 'V' ) ); /* Digital Voodoo intermediate 2vuy */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_DVVT_VIDEO, LSMASH_4CC( 'D', 'V', 'V', 'T' ) ); /* Digital Voodoo intermediate v210 */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_HD10_VIDEO, LSMASH_4CC( 'H', 'D', '1', '0' ) ); /* Digital Voodoo 10 bit Uncompressed 4:2:2 HD codec */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_M105_VIDEO, LSMASH_4CC( 'M', '1', '0', '5' ) ); /* Internal format of video data supported by Matrox hardware; pixel organization is proprietary*/ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_PNTG_VIDEO, LSMASH_4CC( 'P', 'N', 'T', 'G' ) ); /* Apple MacPaint image format */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_SVQ1_VIDEO, LSMASH_4CC( 'S', 'V', 'Q', '1' ) ); /* Sorenson Video 1 video */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_SVQ3_VIDEO, LSMASH_4CC( 'S', 'V', 'Q', '3' ) ); /* Sorenson Video 3 video */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_SHR0_VIDEO, LSMASH_4CC( 'S', 'h', 'r', '0' ) ); /* Generic SheerVideo codec */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_SHR1_VIDEO, LSMASH_4CC( 'S', 'h', 'r', '1' ) ); /* SheerVideo RGB[A] 8b - at 8 bits/channel */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_SHR2_VIDEO, LSMASH_4CC( 'S', 'h', 'r', '2' ) ); /* SheerVideo Y'CbCr[A] 8bv 4:4:4[:4] - at 8 bits/channel, in ITU-R BT.601-4 video range */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_SHR3_VIDEO, LSMASH_4CC( 'S', 'h', 'r', '3' ) ); /* SheerVideo Y'CbCr 8bv 4:2:2 - 2:1 chroma subsampling, at 8 bits/channel, in ITU-R BT.601-4 video range */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_SHR4_VIDEO, LSMASH_4CC( 'S', 'h', 'r', '4' ) ); /* SheerVideo Y'CbCr 8bw 4:2:2 - 2:1 chroma subsampling, at 8 bits/channel, with full-range luma and wide-range two's-complement chroma */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_WRLE_VIDEO, LSMASH_4CC( 'W', 'R', 'L', 'E' ) ); /* Windows BMP image format */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_APCH_VIDEO, LSMASH_4CC( 'a', 'p', 'c', 'h' ) ); /* Apple ProRes 422 High Quality */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_APCN_VIDEO, LSMASH_4CC( 'a', 'p', 'c', 'n' ) ); /* Apple ProRes 422 Standard Definition */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_APCS_VIDEO, LSMASH_4CC( 'a', 'p', 'c', 's' ) ); /* Apple ProRes 422 LT */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_APCO_VIDEO, LSMASH_4CC( 'a', 'p', 'c', 'o' ) ); /* Apple ProRes 422 Proxy */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_AP4H_VIDEO, LSMASH_4CC( 'a', 'p', '4', 'h' ) ); /* Apple ProRes 4444 */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_AP4X_VIDEO, LSMASH_4CC( 'a', 'p', '4', 'x' ) ); /* Apple ProRes 4444 XQ */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_CIVD_VIDEO, LSMASH_4CC( 'c', 'i', 'v', 'd' ) ); /* Cinepak Video */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_DRAC_VIDEO, LSMASH_4CC( 'd', 'r', 'a', 'c' ) ); /* Dirac Video Coder */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_DVC_VIDEO, LSMASH_4CC( 'd', 'v', 'c', ' ' ) ); /* DV NTSC format */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_DVCP_VIDEO, LSMASH_4CC( 'd', 'v', 'c', 'p' ) ); /* DV PAL format */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_DVPP_VIDEO, LSMASH_4CC( 'd', 'v', 'p', 'p' ) ); /* Panasonic DVCPro PAL format */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_DV5N_VIDEO, LSMASH_4CC( 'd', 'v', '5', 'n' ) ); /* Panasonic DVCPro-50 NTSC format */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_DV5P_VIDEO, LSMASH_4CC( 'd', 'v', '5', 'p' ) ); /* Panasonic DVCPro-50 PAL format */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_DVH2_VIDEO, LSMASH_4CC( 'd', 'v', 'h', '2' ) ); /* Panasonic DVCPro-HD 1080p25 format */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_DVH3_VIDEO, LSMASH_4CC( 'd', 'v', 'h', '3' ) ); /* Panasonic DVCPro-HD 1080p30 format */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_DVH5_VIDEO, LSMASH_4CC( 'd', 'v', 'h', '5' ) ); /* Panasonic DVCPro-HD 1080i50 format */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_DVH6_VIDEO, LSMASH_4CC( 'd', 'v', 'h', '6' ) ); /* Panasonic DVCPro-HD 1080i60 format */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_DVHP_VIDEO, LSMASH_4CC( 'd', 'v', 'h', 'p' ) ); /* Panasonic DVCPro-HD 720p60 format */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_DVHQ_VIDEO, LSMASH_4CC( 'd', 'v', 'h', 'q' ) ); /* Panasonic DVCPro-HD 720p50 format */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_FLIC_VIDEO, LSMASH_4CC( 'f', 'l', 'i', 'c' ) ); /* Autodesk FLIC animation format */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_GIF_VIDEO, LSMASH_4CC( 'g', 'i', 'f', ' ' ) ); /* GIF image format */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_H261_VIDEO, LSMASH_4CC( 'h', '2', '6', '1' ) ); /* ITU H.261 video */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_H263_VIDEO, LSMASH_4CC( 'h', '2', '6', '3' ) ); /* ITU H.263 video */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_JPEG_VIDEO, LSMASH_4CC( 'j', 'p', 'e', 'g' ) ); /* JPEG image format */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_MJPA_VIDEO, LSMASH_4CC( 'm', 'j', 'p', 'a' ) ); /* Motion-JPEG (format A) */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_MJPB_VIDEO, LSMASH_4CC( 'm', 'j', 'p', 'b' ) ); /* Motion-JPEG (format B) */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_PNG_VIDEO, LSMASH_4CC( 'p', 'n', 'g', ' ' ) ); /* W3C Portable Network Graphics (PNG) */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_RAW_VIDEO, LSMASH_4CC( 'r', 'a', 'w', ' ' ) ); /* Uncompressed RGB */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_RLE_VIDEO, LSMASH_4CC( 'r', 'l', 'e', ' ' ) ); /* Apple animation codec */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_RPZA_VIDEO, LSMASH_4CC( 'r', 'p', 'z', 'a' ) ); /* Apple simple video 'road pizza' compression */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_TGA_VIDEO, LSMASH_4CC( 't', 'g', 'a', ' ' ) ); /* Truvision Targa video format */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_TIFF_VIDEO, LSMASH_4CC( 't', 'i', 'f', 'f' ) ); /* Tagged Image File Format (Adobe) */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_ULRA_VIDEO, LSMASH_4CC( 'U', 'L', 'R', 'A' ) ); /* Ut Video RGBA 4:4:4:4 8bit full-range */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_ULRG_VIDEO, LSMASH_4CC( 'U', 'L', 'R', 'G' ) ); /* Ut Video RGB 4:4:4 8bit full-range */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_ULY0_VIDEO, LSMASH_4CC( 'U', 'L', 'Y', '0' ) ); /* Ut Video YCbCr (BT.601) 4:2:0 8bit limited */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_ULY2_VIDEO, LSMASH_4CC( 'U', 'L', 'Y', '2' ) ); /* Ut Video YCbCr (BT.601) 4:2:2 8bit limited */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_ULH0_VIDEO, LSMASH_4CC( 'U', 'L', 'H', '0' ) ); /* Ut Video YCbCr (BT.709) 4:2:0 8bit limited */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_ULH2_VIDEO, LSMASH_4CC( 'U', 'L', 'H', '2' ) ); /* Ut Video YCbCr (BT.709) 4:2:2 8bit limited */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_UQY2_VIDEO, LSMASH_4CC( 'U', 'Q', 'Y', '2' ) ); /* Ut Video Pro YCbCr 4:2:2 10bit */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_V210_VIDEO, LSMASH_4CC( 'v', '2', '1', '0' ) ); /* Uncompressed Y'CbCr, 10-bit-per-component 4:2:2 + * |Cb0(10)|Y'0(10)|Cr0(10)|XX(2)| + * |Y'1(10)|Cb1(10)|Y'2(10)|XX(2)| + * |Cr1(10)|Y'3(10)|Cb2(10)|XX(2)| + * |Y'4(10)|Cr2(10)|Y'5(10)|XX(2)| (X is a zero bit) */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_V216_VIDEO, LSMASH_4CC( 'v', '2', '1', '6' ) ); /* Uncompressed Y'CbCr, 10, 12, 14, or 16-bit-per-component 4:2:2 + * |Cb(16 LE)|Y'0(16 LE)|Cr(16 LE)|Y'1(16 LE)| */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_V308_VIDEO, LSMASH_4CC( 'v', '3', '0', '8' ) ); /* Uncompressed Y'CbCr, 8-bit-per-component 4:4:4 + * |Cr(8)|Y'(8)|Cb(8)| */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_V408_VIDEO, LSMASH_4CC( 'v', '4', '0', '8' ) ); /* Uncompressed Y'CbCrA, 8-bit-per-component 4:4:4:4 + * |Cb(8)|Y'(8)|Cr(8)|A(8)| */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_V410_VIDEO, LSMASH_4CC( 'v', '4', '1', '0' ) ); /* Uncompressed Y'CbCr, 10-bit-per-component 4:4:4 + * |XX(2)|Cb(10)|Y'(10)|Cr(10)| (X is a zero bit) */ +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_YUV2_VIDEO, LSMASH_4CC( 'y', 'u', 'v', '2' ) ); /* Uncompressed Y'CbCr, 8-bit-per-component 4:2:2 + * |Y'0(8)|Cb(8)|Y'1(8)|Cr(8)| */ + +/* Text CODEC identifiers */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_ENCT_TEXT, LSMASH_4CC( 'e', 'n', 'c', 't' ) ); /* Encrypted Text */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_STPP_TEXT, LSMASH_4CC( 's', 't', 'p', 'p' ) ); /* Sub-titles (Timed Text) */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_TX3G_TEXT, LSMASH_4CC( 't', 'x', '3', 'g' ) ); /* 3GPP Timed Text stream */ + +DEFINE_QTFF_CODEC_TYPE( QT_CODEC_TYPE_TEXT_TEXT, LSMASH_4CC( 't', 'e', 'x', 't' ) ); /* QuickTime Text Media */ + +/* Hint CODEC identifiers */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_FDP_HINT, LSMASH_4CC( 'f', 'd', 'p', ' ' ) ); /* File delivery hints */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_M2TS_HINT, LSMASH_4CC( 'm', '2', 't', 's' ) ); /* MPEG-2 transport stream for DMB */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_PM2T_HINT, LSMASH_4CC( 'p', 'm', '2', 't' ) ); /* Protected MPEG-2 Transport */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_PRTP_HINT, LSMASH_4CC( 'p', 'r', 't', 'p' ) ); /* Protected RTP Reception */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_RM2T_HINT, LSMASH_4CC( 'r', 'm', '2', 't' ) ); /* MPEG-2 Transport Reception */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_RRTP_HINT, LSMASH_4CC( 'r', 'r', 't', 'p' ) ); /* RTP reception */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_RSRP_HINT, LSMASH_4CC( 'r', 's', 'r', 'p' ) ); /* SRTP Reception */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_RTP_HINT, LSMASH_4CC( 'r', 't', 'p', ' ' ) ); /* RTP Hints */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_SM2T_HINT, LSMASH_4CC( 's', 'm', '2', 't' ) ); /* MPEG-2 Transport Server */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_SRTP_HINT, LSMASH_4CC( 's', 'r', 't', 'p' ) ); /* SRTP Hints */ + +/* Metadata CODEC identifiers */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_IXSE_META, LSMASH_4CC( 'i', 'x', 's', 'e' ) ); /* DVB Track Level Index Track */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_METT_META, LSMASH_4CC( 'm', 'e', 't', 't' ) ); /* Text timed metadata */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_METX_META, LSMASH_4CC( 'm', 'e', 't', 'x' ) ); /* XML timed metadata */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_MLIX_META, LSMASH_4CC( 'm', 'l', 'i', 'x' ) ); /* DVB Movie level index track */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_OKSD_META, LSMASH_4CC( 'o', 'k', 's', 'd' ) ); /* OMA Keys */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_SVCM_META, LSMASH_4CC( 's', 'v', 'c', 'M' ) ); /* SVC metadata */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_TEXT_META, LSMASH_4CC( 't', 'e', 'x', 't' ) ); /* Textual meta-data with MIME type */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_URIM_META, LSMASH_4CC( 'u', 'r', 'i', 'm' ) ); /* URI identified timed metadata */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_XML_META, LSMASH_4CC( 'x', 'm', 'l', ' ' ) ); /* XML-formatted meta-data */ + +/* Other CODEC identifiers */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_ENCS_SYSTEM, LSMASH_4CC( 'e', 'n', 'c', 's' ) ); /* Encrypted Systems stream */ +DEFINE_ISOM_CODEC_TYPE( ISOM_CODEC_TYPE_MP4S_SYSTEM, LSMASH_4CC( 'm', 'p', '4', 's' ) ); /* MPEG-4 Systems */ + +DEFINE_QTFF_CODEC_TYPE( LSMASH_CODEC_TYPE_RAW, LSMASH_4CC( 'r', 'a', 'w', ' ' ) ); /* Either video or audio */ + +/* Check if the identifier of two CODECs is identical or not. + * + * Return 1 if the both CODEC identifiers are identical. + * Return 0 otherwise. */ +int lsmash_check_codec_type_identical +( + lsmash_codec_type_t a, + lsmash_codec_type_t b +); + +/**************************************************************************** + * Summary of Stream Configuration + * This is L-SMASH's original structure. + ****************************************************************************/ +typedef enum +{ + LSMASH_SUMMARY_TYPE_UNKNOWN = 0, + LSMASH_SUMMARY_TYPE_VIDEO, + LSMASH_SUMMARY_TYPE_AUDIO, + LSMASH_SUMMARY_TYPE_HINT, +} lsmash_summary_type; + +typedef enum +{ + LSMASH_CODEC_SPECIFIC_DATA_TYPE_UNSPECIFIED = -1, /* must be LSMASH_CODEC_SPECIFIC_FORMAT_UNSPECIFIED */ + + LSMASH_CODEC_SPECIFIC_DATA_TYPE_UNKNOWN = 0, /* must be LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED */ + + LSMASH_CODEC_SPECIFIC_DATA_TYPE_MP4SYS_DECODER_CONFIG, + + LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_H264, + LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_HEVC, + LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_VC_1, + LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_AC_3, + LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_EC_3, + LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_DTS, + LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_AUDIO_ALAC, + + LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_SAMPLE_SCALE, + LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_H264_BITRATE, + LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_RTP_HINT_COMMON, + + LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_COMMON, /* must be LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED */ + LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_COMMON, /* must be LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED */ + LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_FORMAT_SPECIFIC_FLAGS, /* must be LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED */ + LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_DECOMPRESSION_PARAMETERS, /* must be LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED */ + + LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_FIELD_INFO, + LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_PIXEL_FORMAT, + LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_SIGNIFICANT_BITS, + LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_GAMMA_LEVEL, + LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_CONTENT_LIGHT_LEVEL_INFO, + LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_VIDEO_MASTERING_DISPLAY_COLOR_VOLUME, + LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_CHANNEL_LAYOUT, + + LSMASH_CODEC_SPECIFIC_DATA_TYPE_CODEC_GLOBAL_HEADER, +} lsmash_codec_specific_data_type; + +typedef enum +{ + LSMASH_CODEC_SPECIFIC_FORMAT_UNSPECIFIED = -1, + LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED = 0, + LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED = 1 +} lsmash_codec_specific_format; + +typedef union +{ + void *always_null; /* LSMASH_CODEC_SPECIFIC_FORMAT_UNSPECIFIED */ + void *structured; /* LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED */ + uint8_t *unstructured; /* LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED */ +} lsmash_codec_specific_data_t; + +typedef void (*lsmash_codec_specific_destructor_t)( void * ); +typedef struct +{ + lsmash_codec_specific_data_type type; + lsmash_codec_specific_format format; + lsmash_codec_specific_data_t data; + uint32_t size; + lsmash_codec_specific_destructor_t destruct; +} lsmash_codec_specific_t; + +typedef struct lsmash_codec_specific_list_tag lsmash_codec_specific_list_t; + +typedef enum +{ + LSMASH_CODEC_SUPPORT_FLAG_NONE = 0, + LSMASH_CODEC_SUPPORT_FLAG_MUX = 1 << 0, /* It's expected that L-SMASH can mux CODEC stream properly. + * If not flagged, L-SMASH may recognize and/or handle CODEC specific info incorrectly when muxing. */ + LSMASH_CODEC_SUPPORT_FLAG_DEMUX = 1 << 1, /* It's expected that L-SMASH can demux CODEC stream properly. + * If not flagged, L-SMASH may recognize and/or handle CODEC specific info incorrectly when demuxing. */ + LSMASH_CODEC_SUPPORT_FLAG_REMUX = LSMASH_CODEC_SUPPORT_FLAG_MUX | LSMASH_CODEC_SUPPORT_FLAG_DEMUX, +} lsmash_codec_support_flag; + +#define LSMASH_BASE_SUMMARY \ + lsmash_summary_type summary_type; \ + lsmash_codec_type_t sample_type; \ + lsmash_codec_specific_list_t *opaque; \ + uint32_t max_au_length; /* buffer length for 1 access unit, \ + * typically max size of 1 audio/video frame */ \ + uint32_t data_ref_index; /* the index of a data reference */ + +typedef struct +{ + LSMASH_BASE_SUMMARY +} lsmash_summary_t; + +/* Allocate a summary by 'summary_type'. + * The allocated summary can be deallocated by lsmash_cleanup_summary(). + * + * Return the address of an allocated summary if successful. + * Return NULL otherwise. */ +lsmash_summary_t *lsmash_create_summary +( + lsmash_summary_type summary_type /* a type of summary you want */ +); + +/* Deallocate a given summary. */ +void lsmash_cleanup_summary +( + lsmash_summary_t *summary /* the address of a summary you want to deallocate */ +); + +/* Allocate and append a new sample description to a track by 'summary'. + * + * Return the index of an allocated and appended sample description if successful. + * Return 0 otherwise. */ +int lsmash_add_sample_entry +( + lsmash_root_t *root, /* the address of the ROOT containing a track to which you want to append a new sample description */ + uint32_t track_ID, /* the track_ID of a track to which you want to append a new sample description */ + void *summary /* the summary of a sample description you want to append */ +); + +/* Count the number of summaries in a track. + * + * Return the number of summaries in a track if no error. + * Return 0 otherwise. */ +uint32_t lsmash_count_summary +( + lsmash_root_t *root, /* the address of the ROOT containing a track in which you want to count the number of summaries */ + uint32_t track_ID /* the track_ID of a track in which you want to count the number of summaries */ +); + +/* Get the summary of a sample description you want in a track. + * The summary returned by this function is allocated internally, and can be deallocate by lsmash_cleanup_summary(). + * + * Return the address of an allocated summary you want if successful. + * Return NULL otherwise. */ +lsmash_summary_t *lsmash_get_summary +( + lsmash_root_t *root, /* the address of the ROOT containing a track which contains a sample description you want */ + uint32_t track_ID, /* the track_ID of a track containing a sample description you want */ + uint32_t description_number /* the index of a sample description you want */ +); + +/* Allocate and initialize a CODEC specific configuration by 'type' and 'format'. + * The allocated CODEC specific configuration can be deallocated by lsmash_destroy_codec_specific_data(). + * + * Return the address of an allocated and initialized CODEC specific configuration if successful. + * Return NULL otherwise. */ +lsmash_codec_specific_t *lsmash_create_codec_specific_data +( + lsmash_codec_specific_data_type type, + lsmash_codec_specific_format format +); + +/* Deallocate a CODEC specific configuration. */ +void lsmash_destroy_codec_specific_data +( + lsmash_codec_specific_t *specific /* the address of a CODEC specific configuration you want to deallocate */ +); + +/* Allocate a CODEC specific configuration which is a copy of 'specific', and append it to 'summary'. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_add_codec_specific_data +( + lsmash_summary_t *summary, + lsmash_codec_specific_t *specific +); + +/* Count the number of CODEC specific configuration in a summary. + * + * Return the number of CODEC specific configuration in a summary if successful. + * Return 0 otherwise. */ +uint32_t lsmash_count_codec_specific_data +( + lsmash_summary_t *summary /* the address of a summary in which you want to count the number of CODEC specific configuration */ +); + +/* Get a CODEC specific configuration you want in a summary. + * + * Return the address of a CODEC specific configuration if successful. + * Return NULL otherwise. */ +lsmash_codec_specific_t *lsmash_get_codec_specific_data +( + lsmash_summary_t *summary, + uint32_t extension_number +); + +/* Convert a data format of CODEC specific configuration into another. + * User can specify the same data format for the destination. + * If so, a returned CODEC specific configuration is a copy of the source. + * + * Return an allocated CODEC specific configuration by specified 'format' from 'specific' if successful. + * Return NULL otherwise. */ +lsmash_codec_specific_t *lsmash_convert_codec_specific_format +( + lsmash_codec_specific_t *specific, /* the address of a CODEC specific configuration as the source */ + lsmash_codec_specific_format format /* a data format of the destination */ +); + +/* Compare two summaries. + * + * Return 0 if the two summaries are identical. + * Return 1 if the two summaries are different. + * Return a negative value if there is any error. */ +int lsmash_compare_summary +( + lsmash_summary_t *a, + lsmash_summary_t *b +); + +/* Check status of CODEC support. + * + * Return support flags of a given CODEC. */ +lsmash_codec_support_flag lsmash_check_codec_support +( + lsmash_codec_type_t codec_type +); + +/**************************************************************************** + * Audio Description Layer + ****************************************************************************/ +/* Audio Object Types */ +typedef enum +{ + MP4A_AUDIO_OBJECT_TYPE_NULL = 0, + MP4A_AUDIO_OBJECT_TYPE_AAC_MAIN = 1, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_AAC_LC = 2, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_AAC_SSR = 3, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_AAC_LTP = 4, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_SBR = 5, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_AAC_scalable = 6, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_TwinVQ = 7, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_CELP = 8, /* ISO/IEC 14496-3 subpart 3 */ + MP4A_AUDIO_OBJECT_TYPE_HVXC = 9, /* ISO/IEC 14496-3 subpart 2 */ + MP4A_AUDIO_OBJECT_TYPE_TTSI = 12, /* ISO/IEC 14496-3 subpart 6 */ + MP4A_AUDIO_OBJECT_TYPE_Main_synthetic = 13, /* ISO/IEC 14496-3 subpart 5 */ + MP4A_AUDIO_OBJECT_TYPE_Wavetable_synthesis = 14, /* ISO/IEC 14496-3 subpart 5 */ + MP4A_AUDIO_OBJECT_TYPE_General_MIDI = 15, /* ISO/IEC 14496-3 subpart 5 */ + MP4A_AUDIO_OBJECT_TYPE_Algorithmic_Synthesis_Audio_FX = 16, /* ISO/IEC 14496-3 subpart 5 */ + MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LC = 17, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LTP = 19, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_ER_AAC_scalable = 20, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_ER_Twin_VQ = 21, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_ER_BSAC = 22, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_ER_AAC_LD = 23, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_ER_CELP = 24, /* ISO/IEC 14496-3 subpart 3 */ + MP4A_AUDIO_OBJECT_TYPE_ER_HVXC = 25, /* ISO/IEC 14496-3 subpart 2 */ + MP4A_AUDIO_OBJECT_TYPE_ER_HILN = 26, /* ISO/IEC 14496-3 subpart 7 */ + MP4A_AUDIO_OBJECT_TYPE_ER_Parametric = 27, /* ISO/IEC 14496-3 subpart 2 and 7 */ + MP4A_AUDIO_OBJECT_TYPE_SSC = 28, /* ISO/IEC 14496-3 subpart 8 */ + MP4A_AUDIO_OBJECT_TYPE_PS = 29, /* ISO/IEC 14496-3 subpart 8 */ + MP4A_AUDIO_OBJECT_TYPE_MPEG_Surround = 30, /* ISO/IEC 23003-1 */ + MP4A_AUDIO_OBJECT_TYPE_ESCAPE = 31, + MP4A_AUDIO_OBJECT_TYPE_Layer_1 = 32, /* ISO/IEC 14496-3 subpart 9 */ + MP4A_AUDIO_OBJECT_TYPE_Layer_2 = 33, /* ISO/IEC 14496-3 subpart 9 */ + MP4A_AUDIO_OBJECT_TYPE_Layer_3 = 34, /* ISO/IEC 14496-3 subpart 9 */ + MP4A_AUDIO_OBJECT_TYPE_DST = 35, /* ISO/IEC 14496-3 subpart 10 */ + MP4A_AUDIO_OBJECT_TYPE_ALS = 36, /* ISO/IEC 14496-3 subpart 11 */ + MP4A_AUDIO_OBJECT_TYPE_SLS = 37, /* ISO/IEC 14496-3 subpart 12 */ + MP4A_AUDIO_OBJECT_TYPE_SLS_non_core = 38, /* ISO/IEC 14496-3 subpart 12 */ + MP4A_AUDIO_OBJECT_TYPE_ER_AAC_ELD = 39, /* ISO/IEC 14496-3 subpart 4 */ + MP4A_AUDIO_OBJECT_TYPE_SMR_Simple = 40, /* ISO/IEC 14496-23 */ + MP4A_AUDIO_OBJECT_TYPE_SMR_Main = 41, /* ISO/IEC 14496-23 */ + MP4A_AUDIO_OBJECT_TYPE_SAOC = 43, /* ISO/IEC 23003-2 */ +} lsmash_mp4a_AudioObjectType; + +/* See ISO/IEC 14496-3 Signaling of SBR, SBR Signaling and Corresponding Decoder Behavior */ +typedef enum +{ + MP4A_AAC_SBR_NOT_SPECIFIED = 0x0, /* not mention to SBR presence. Implicit signaling. */ + MP4A_AAC_SBR_NONE, /* explicitly signals SBR does not present. Useless in general. */ + MP4A_AAC_SBR_BACKWARD_COMPATIBLE, /* explicitly signals SBR present. Recommended method to signal SBR. */ + MP4A_AAC_SBR_HIERARCHICAL /* SBR exists. SBR dedicated method. */ +} lsmash_mp4a_aac_sbr_mode; + +typedef struct +{ + LSMASH_BASE_SUMMARY + lsmash_mp4a_AudioObjectType aot; /* detailed codec type + * If neither ISOM_CODEC_TYPE_MP4A_AUDIO nor QT_CODEC_TYPE_MP4A_AUDIO, just ignored. */ + uint32_t frequency; /* the audio sampling rate (in Hz) at the default output playback + * For some audio, this field is used as a nominal value. + * For HE-AAC v1/SBR stream, this is base AAC's one. + * For ISOM_CODEC_TYPE_AC_3_AUDIO and ISOM_CODEC_TYPE_EC_3_AUDIO, this shall be + * equal to the sampling rate (in Hz) of the stream and the media timescale. */ + uint32_t channels; /* the number of audio channels at the default output playback + * Even if the stream is HE-AAC v2/SBR+PS, this is base AAC's one. */ + uint32_t sample_size; /* For uncompressed audio, + * the number of bits in each uncompressed sample for a single channel. + * For some compressed audio, such as audio that uses MDCT, + * N/A (not applicable), and may be set to 16. */ + uint32_t samples_in_frame; /* the number of decoded PCM samples in an audio frame at 'frequency' + * Even if the stream is HE-AAC/aacPlus/SBR(+PS), this is base AAC's one, so 1024. */ + lsmash_mp4a_aac_sbr_mode sbr_mode; /* SBR treatment + * Currently we always set this as mp4a_AAC_SBR_NOT_SPECIFIED (Implicit signaling). + * User can set this for treatment in other way. */ + uint32_t bytes_per_frame; /* the number of bytes per audio frame + * If variable, shall be set to 0. */ +} lsmash_audio_summary_t; + +/* Facilitate to make exdata (typically DecoderSpecificInfo or AudioSpecificConfig). */ +int lsmash_setup_AudioSpecificConfig +( + lsmash_audio_summary_t* summary +); + +/**************************************************************************** + * Video Description Layer + ****************************************************************************/ +/* Clean Aperture */ +typedef struct +{ + lsmash_rational_u32_t width; + lsmash_rational_u32_t height; + lsmash_rational_s32_t horizontal_offset; + lsmash_rational_s32_t vertical_offset; +} lsmash_clap_t; + +typedef struct +{ + lsmash_rational_u32_t top; + lsmash_rational_u32_t left; + lsmash_rational_u32_t bottom; + lsmash_rational_u32_t right; +} lsmash_crop_t; + +/* Video depth */ +typedef enum +{ + ISOM_DEPTH_TEMPLATE = 0x0018, + + /* H.264/AVC */ + AVC_DEPTH_COLOR_WITH_NO_ALPHA = 0x0018, /* color with no alpha */ + AVC_DEPTH_GRAYSCALE_WITH_NO_ALPHA = 0x0028, /* grayscale with no alpha */ + AVC_DEPTH_WITH_ALPHA = 0x0020, /* gray or color with alpha */ + + /* QuickTime Video + * (1-32) or (33-40 grayscale) */ + QT_VIDEO_DEPTH_COLOR_1 = 0x0001, + QT_VIDEO_DEPTH_COLOR_2 = 0x0002, + QT_VIDEO_DEPTH_COLOR_4 = 0x0004, + QT_VIDEO_DEPTH_COLOR_8 = 0x0008, + QT_VIDEO_DEPTH_COLOR_16 = 0x0010, + QT_VIDEO_DEPTH_COLOR_24 = 0x0018, + QT_VIDEO_DEPTH_COLOR_32 = 0x0020, + QT_VIDEO_DEPTH_GRAYSCALE_1 = 0x0021, + QT_VIDEO_DEPTH_GRAYSCALE_2 = 0x0022, + QT_VIDEO_DEPTH_GRAYSCALE_4 = 0x0024, + QT_VIDEO_DEPTH_GRAYSCALE_8 = 0x0028, + + /* QuickTime Uncompressed RGB */ + QT_VIDEO_DEPTH_555RGB = 0x0010, + QT_VIDEO_DEPTH_24RGB = 0x0018, + QT_VIDEO_DEPTH_32ARGB = 0x0020, +} lsmash_video_depth; + +/* Index for the chromaticity coordinates of the color primaries */ +enum +{ + /* for ISO Base Media file format */ + ISOM_PRIMARIES_INDEX_ITU_R709_5 = 1, /* ITU-R BT.709-2/5, ITU-R BT.1361, + * SMPTE 274M-1995, SMPTE 296M-1997, + * IEC 61966-2-1 (sRGB or sYCC), IEC 61966-2-4 (xvYCC), + * SMPTE RP 177M-1993 Annex B + * green x = 0.300 y = 0.600 + * blue x = 0.150 y = 0.060 + * red x = 0.640 y = 0.330 + * white x = 0.3127 y = 0.3290 (CIE III. D65) */ + ISOM_PRIMARIES_INDEX_UNSPECIFIED = 2, /* Unspecified */ + ISOM_PRIMARIES_INDEX_ITU_R470M = 4, /* ITU-R BT.470-6 System M + * green x = 0.21 y = 0.71 + * blue x = 0.14 y = 0.08 + * red x = 0.67 y = 0.33 + * white x = 0.310 y = 0.316 */ + ISOM_PRIMARIES_INDEX_ITU_R470BG = 5, /* EBU Tech. 3213 (1981), ITU-R BT.470-6 System B, G, + * ITU-R BT.601-6 625, ITU-R BT.1358 625, + * ITU-R BT.1700 625 PAL and 625 SECAM + * green x = 0.29 y = 0.60 + * blue x = 0.15 y = 0.06 + * red x = 0.64 y = 0.33 + * white x = 0.3127 y = 0.3290 (CIE III. D65) */ + ISOM_PRIMARIES_INDEX_SMPTE_170M_2004 = 6, /* SMPTE C Primaries from SMPTE RP 145-1993, SMPTE 170M-2004, + * ITU-R BT.601-6 525, ITU-R BT.1358 525, + * ITU-R BT.1700 NTSC, SMPTE 170M-2004 + * green x = 0.310 y = 0.595 + * blue x = 0.155 y = 0.070 + * red x = 0.630 y = 0.340 + * white x = 0.3127 y = 0.3290 (CIE III. D65) */ + ISOM_PRIMARIES_INDEX_SMPTE_240M_1999 = 7, /* SMPTE 240M-1999 + * functionally the same as the value ISOM_PRIMARIES_INDEX_SMPTE_170M_2004 */ + + /* for QuickTime file format */ + QT_PRIMARIES_INDEX_ITU_R709_2 = 1, /* the same as the value ISOM_PRIMARIES_INDEX_ITU_R709_5 */ + QT_PRIMARIES_INDEX_UNSPECIFIED = 2, /* Unspecified */ + QT_PRIMARIES_INDEX_EBU_3213 = 5, /* the same as the value ISOM_PRIMARIES_INDEX_ITU_R470BG */ + QT_PRIMARIES_INDEX_SMPTE_C = 6, /* the same as the value ISOM_PRIMARIES_INDEX_SMPTE_170M_2004 */ +}; + +/* Index for the opto-electronic transfer characteristic of the image color components */ +enum +{ + /* for ISO Base Media file format */ + ISOM_TRANSFER_INDEX_ITU_R709_5 = 1, /* ITU-R BT.709-2/5, ITU-R BT.1361 + * SMPTE 274M-1995, SMPTE 296M-1997, + * SMPTE 293M-1996, SMPTE 170M-1994 + * vV = 1.099 * vLc^0.45 - 0.099 for 1 >= vLc >= 0.018 + * vV = 4.500 * vLc for 0.018 > vLc >= 0 */ + ISOM_TRANSFER_INDEX_UNSPECIFIED = 2, /* Unspecified */ + ISOM_TRANSFER_INDEX_ITU_R470M = 4, /* ITU-R BT.470-6 System M, ITU-R BT.1700 625 PAL and 625 SECAM + * Assumed display gamma 2.2 */ + ISOM_TRANSFER_INDEX_ITU_R470BG = 5, /* ITU-R BT.470-6 System B, G + * Assumed display gamma 2.8 */ + ISOM_TRANSFER_INDEX_SMPTE_170M_2004 = 6, /* ITU-R BT.601-6 525 or 625, ITU-R BT.1358 525 or 625, + * ITU-R BT.1700 NTSC, SMPTE 170M-2004 + * functionally the same as the value ISOM_TRANSFER_INDEX_ITU_R709_5 + * vV = 1.099 * vLc^0.45 - 0.099 for 1 >= vLc >= 0.018 + * vV = 4.500 * vLc for 0.018 > vLc >= 0 */ + ISOM_TRANSFER_INDEX_SMPTE_240M_1999 = 7, /* SMPTE 240M-1995/1999, interim color implementation of SMPTE 274M-1995 + * vV = 1.1115 * vLc^0.45 - 0.1115 for 1 >= vLc >= 0.0228 + * vV = 4.0 * vLc for 0.0228 > vLc >= 0 */ + ISOM_TRANSFER_INDEX_LINEAR = 8, /* Linear transfer characteristics */ + ISOM_TRANSFER_INDEX_XVYCC = 11, /* IEC 61966-2-4 (xvYCC) + * vV = 1.099 * vLc^0.45 - 0.099 for vLc >= 0.018 + * vV = 4.500 * vLc for 0.018 > vLc > -0.018 + * vV = -1.099 * (-vLc)^0.45 + 0.099 for -0.018 >= vLc */ + ISOM_TRANSFER_INDEX_ITU_R1361 = 12, /* ITU-R BT.1361 + * vV = 1.099 * vLc^0.45 - 0.099 for 1.33 > vLc >= 0.018 + * vV = 4.500 * vLc for 0.018 > vLc >= -0.0045 + * vV = -(1.099 * (-4 * vLc)^0.45 + 0.099) / 4 for -0.0045 > vLc >= -0.25 */ + ISOM_TRANSFER_INDEX_SRGB = 13, /* IEC 61966-2-1 (sRGB or sYCC) + * vV = 1.055 * vLc^(1/2.4) - 0.055 for 1 > vLc >= 0.0031308 + * vV = 12.92 * vLc for 0.0031308 > vLc >= 0 */ + + /* for QuickTime file format */ + QT_TRANSFER_INDEX_ITU_R709_2 = 1, /* the same as the value ISOM_TRANSFER_INDEX_ITU_R709_5 */ + QT_TRANSFER_INDEX_UNSPECIFIED = 2, /* Unspecified */ + QT_TRANSFER_INDEX_SMPTE_240M_1995 = 7, /* the same as the value ISOM_TRANSFER_INDEX_SMPTE_240M_1999 */ +}; + +/* Index for the matrix coefficients associated with derivation of luma and chroma signals from the green, blue, and red primaries */ +enum +{ + /* for ISO Base Media file format */ + ISOM_MATRIX_INDEX_NO_MATRIX = 0, /* No matrix transformation + * IEC 61966-2-1 (sRGB) */ + ISOM_MATRIX_INDEX_ITU_R_709_5 = 1, /* ITU-R BT.709-2/5, ITU-R BT.1361, + * SMPTE 274M-1995, SMPTE 296M-1997 + * IEC 61966-2-1 (sYCC), IEC 61966-2-4 xvYCC_709, + * SMPTE RP 177M-1993 Annex B + * vKr = 0.2126; vKb = 0.0722 */ + ISOM_MATRIX_INDEX_UNSPECIFIED = 2, /* Unspecified */ + ISOM_MATRIX_INDEX_USFCCT_47_CFR = 4, /* United States Federal Communications Commission Title 47 Code of Federal Regulations + * vKr = 0.30; vKb = 0.11 */ + ISOM_MATRIX_INDEX_ITU_R470BG = 5, /* ITU-R BT.470-6 System B, G, + * ITU-R BT.601-4/6 625, ITU-R BT.1358 625, + * ITU-R BT.1700 625 PAL and 625 SECAM, IEC 61966-2-4 xvYCC601 + * vKr = 0.299; vKb = 0.114 */ + ISOM_MATRIX_INDEX_SMPTE_170M_2004 = 6, /* ITU-R BT.601-4/6 525, ITU-R BT.1358 525, + * ITU-R BT.1700 NTSC, + * SMPTE 170M-1994, SMPTE 293M-1996 + * functionally the same as the value ISOM_MATRIX_INDEX_ITU_R470BG + * vKr = 0.299; vKb = 0.114 */ + ISOM_MATRIX_INDEX_SMPTE_240M_1999 = 7, /* SMPTE 240M-1995, interim color implementation of SMPTE 274M-1995 + * vKr = 0.212; vKb = 0.087 */ + ISOM_MATRIX_INDEX_YCGCO = 8, /* YCoCg */ + + /* for QuickTime file format */ + QT_MATRIX_INDEX_ITU_R_709_2 = 1, /* the same as the value ISOM_MATRIX_INDEX_ITU_R_709_5 */ + QT_MATRIX_INDEX_UNSPECIFIED = 2, /* Unspecified */ + QT_MATRIX_INDEX_ITU_R_601_4 = 6, /* the same as the value ISOM_MATRIX_INDEX_SMPTE_170M_2004 */ + QT_MATRIX_INDEX_SMPTE_240M_1995 = 7 /* the same as the value ISOM_MATRIX_INDEX_SMPTE_240M_1999 */ +}; + +typedef struct +{ + LSMASH_BASE_SUMMARY + // lsmash_mp4v_VideoObjectType vot; /* Detailed codec type. If not mp4v, just ignored. */ + uint32_t timescale; /* media timescale + * User can't set this parameter manually. */ + uint32_t timebase; /* increment unit of timestamp + * User can't set this parameter manually. */ + uint8_t vfr; /* whether a stream is assumed as variable frame rate + * User can't set this parameter manually. */ + uint8_t sample_per_field; /* whether a stream may have a sample per field + * User can't set this parameter manually. */ + uint32_t width; /* pixel counts of width samples have */ + uint32_t height; /* pixel counts of height samples have */ + char compressorname[33]; /* a 32-byte Pascal string containing the name of the compressor that created the image */ + lsmash_video_depth depth; /* data size of a pixel */ + lsmash_clap_t clap; /* clean aperture */ + uint32_t par_h; /* horizontal factor of pixel aspect ratio */ + uint32_t par_v; /* vertical factor of pixel aspect ratio */ + struct + { + /* To omit to write these field, set zero value to all them. */ + uint16_t primaries_index; /* the chromaticity coordinates of the color primaries */ + uint16_t transfer_index; /* the opto-electronic transfer characteristic of the image color components */ + uint16_t matrix_index; /* the matrix coefficients associated with derivation of luma and chroma signals from the green, blue, and red primaries */ + uint8_t full_range; + } color; +} lsmash_video_summary_t; + +int lsmash_convert_crop_into_clap +( + lsmash_crop_t crop, + uint32_t width, + uint32_t height, + lsmash_clap_t *clap +); + +int lsmash_convert_clap_into_crop +( + lsmash_clap_t clap, + uint32_t width, + uint32_t height, + lsmash_crop_t *crop +); + +/**************************************************************************** +* Hint Description Layer +****************************************************************************/ + +typedef struct +{ + LSMASH_BASE_SUMMARY + uint16_t version; /* = 1 */ + uint16_t highestcompatibleversion; /* = 1 */ + uint32_t maxpacketsize; /* maximum size of one packet */ +} lsmash_hint_summary_t; + +/**************************************************************************** + * Media Sample + ****************************************************************************/ +typedef enum +{ + /* allow_ealier */ + QT_SAMPLE_EARLIER_PTS_ALLOWED = 1, + /* leading */ + ISOM_SAMPLE_LEADING_UNKNOWN = 0, + ISOM_SAMPLE_IS_UNDECODABLE_LEADING = 1, + ISOM_SAMPLE_IS_NOT_LEADING = 2, + ISOM_SAMPLE_IS_DECODABLE_LEADING = 3, + /* independent */ + ISOM_SAMPLE_INDEPENDENCY_UNKNOWN = 0, + ISOM_SAMPLE_IS_NOT_INDEPENDENT = 1, + ISOM_SAMPLE_IS_INDEPENDENT = 2, + /* disposable */ + ISOM_SAMPLE_DISPOSABLE_UNKNOWN = 0, + ISOM_SAMPLE_IS_NOT_DISPOSABLE = 1, + ISOM_SAMPLE_IS_DISPOSABLE = 2, + /* redundant */ + ISOM_SAMPLE_REDUNDANCY_UNKNOWN = 0, + ISOM_SAMPLE_HAS_REDUNDANCY = 1, + ISOM_SAMPLE_HAS_NO_REDUNDANCY = 2, +} lsmash_sample_dependency; + +typedef enum +{ + /* flags for ISO Base Media file format */ + ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE = 0, + ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC = 1 << 0, /* a sync sample */ + ISOM_SAMPLE_RANDOM_ACCESS_FLAG_RAP = 1 << 2, /* the first sample of a closed or an open GOP */ + ISOM_SAMPLE_RANDOM_ACCESS_FLAG_CLOSED = 1 << 3, /* a sample in a closed GOP + * This flag shall be set together with ISOM_SAMPLE_RANDOM_ACCESS_FLAG_RAP. */ + ISOM_SAMPLE_RANDOM_ACCESS_FLAG_OPEN = 1 << 4, /* a sample in an open GOP + * This flag shall be set together with ISOM_SAMPLE_RANDOM_ACCESS_FLAG_RAP. */ + ISOM_SAMPLE_RANDOM_ACCESS_FLAG_GDR = 1 << 5, /* a sample on gradual decoder refresh or random access recovery */ + ISOM_SAMPLE_RANDOM_ACCESS_FLAG_GDR_START = 1 << 6, /* a sample that is the starting point of gradual decoder refresh or random access recovery + * This flag shall be set together with ISOM_SAMPLE_RANDOM_ACCESS_FLAG_GDR. */ + ISOM_SAMPLE_RANDOM_ACCESS_FLAG_GDR_END = 1 << 7, /* a sample that is the ending point of gradual decoder refresh or random access recovery + * This flag shall be set together with ISOM_SAMPLE_RANDOM_ACCESS_FLAG_GDR. */ + + ISOM_SAMPLE_RANDOM_ACCESS_FLAG_CLOSED_RAP /* the first sample of a closed GOP */ + = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_RAP + | ISOM_SAMPLE_RANDOM_ACCESS_FLAG_CLOSED, + ISOM_SAMPLE_RANDOM_ACCESS_FLAG_OPEN_RAP /* the first sample of an open GOP */ + = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_RAP + | ISOM_SAMPLE_RANDOM_ACCESS_FLAG_OPEN, + ISOM_SAMPLE_RANDOM_ACCESS_FLAG_POST_ROLL_START /* the post-roll starting point of random access recovery */ + = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_GDR + | ISOM_SAMPLE_RANDOM_ACCESS_FLAG_GDR_START, + ISOM_SAMPLE_RANDOM_ACCESS_FLAG_PRE_ROLL_END /* the pre-roll ending point of random access recovery */ + = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_GDR + | ISOM_SAMPLE_RANDOM_ACCESS_FLAG_GDR_END, + + /* flags for QuickTime file format */ + QT_SAMPLE_RANDOM_ACCESS_FLAG_NONE = 0, /* alias of ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE */ + QT_SAMPLE_RANDOM_ACCESS_FLAG_SYNC = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC, + QT_SAMPLE_RANDOM_ACCESS_FLAG_PARTIAL_SYNC = 1 << 1, /* partial sync sample + * Partial sync sample is a sample + * such that this sample and samples following in decoding order can be correctly decoded + * using the first sample of the previous GOP and samples following in decoding order, + * in addition, this sample and non-leading samples following in decoding order can be correctly decoded from this. */ + QT_SAMPLE_RANDOM_ACCESS_FLAG_RAP = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_RAP, + QT_SAMPLE_RANDOM_ACCESS_FLAG_CLOSED = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_CLOSED, + QT_SAMPLE_RANDOM_ACCESS_FLAG_OPEN = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_OPEN, + + QT_SAMPLE_RANDOM_ACCESS_FLAG_CLOSED_RAP /* the first sample of a closed GOP */ + = QT_SAMPLE_RANDOM_ACCESS_FLAG_RAP + | QT_SAMPLE_RANDOM_ACCESS_FLAG_CLOSED, + QT_SAMPLE_RANDOM_ACCESS_FLAG_OPEN_RAP /* the first sample of an open GOP */ + = QT_SAMPLE_RANDOM_ACCESS_FLAG_RAP + | QT_SAMPLE_RANDOM_ACCESS_FLAG_OPEN, +} lsmash_random_access_flag; + +#define LSMASH_FLAGS_SATISFIED( x, y ) (((x) & (y)) == (y)) +#define LSMASH_IS_CLOSED_RAP( x ) LSMASH_FLAGS_SATISFIED( (x), ISOM_SAMPLE_RANDOM_ACCESS_FLAG_CLOSED_RAP ) +#define LSMASH_IS_OPEN_RAP( x ) LSMASH_FLAGS_SATISFIED( (x), ISOM_SAMPLE_RANDOM_ACCESS_FLAG_OPEN_RAP ) +#define LSMASH_IS_POST_ROLL_START( x ) LSMASH_FLAGS_SATISFIED( (x), ISOM_SAMPLE_RANDOM_ACCESS_FLAG_POST_ROLL_START ) +#define LSMASH_IS_PRE_ROLL_END( x ) LSMASH_FLAGS_SATISFIED( (x), ISOM_SAMPLE_RANDOM_ACCESS_FLAG_PRE_ROLL_END ) + +typedef struct +{ + uint32_t identifier; /* the identifier of sample + * If this identifier equals a certain identifier of random access recovery point, + * then this sample is the random access recovery point of the earliest unestablished post-roll group. */ + uint32_t complete; /* the identifier of future random access recovery point, which is necessary for the recovery from its starting point to be completed + * For muxing, this value is used only if (ra_flags & ISOM_SAMPLE_RANDOM_ACCESS_TYPE_POST_ROLL_START) is true. + * The following is an example of use for gradual decoder refresh of H.264/AVC. + * For each sample, set 'frame_num' to the 'identifier'. + * For samples with recovery point SEI message, add ISOM_SAMPLE_RANDOM_ACCESS_TYPE_POST_ROLL_START to ra_flags + * and set '(frame_num + recovery_frame_cnt) % MaxFrameNum' to the 'complete'. + * The above-mentioned values are set appropriately, then L-SMASH will establish appropriate post-roll grouping. */ +} lsmash_post_roll_t; + +typedef struct +{ + uint32_t distance; /* the distance from the previous random access point or pre-roll starting point + * of the random access recovery point to this sample. + * For muxing, this value is used only if ra_flags is not set to ISOM_SAMPLE_RANDOM_ACCESS_TYPE_NONE + * and LSMASH_IS_POST_ROLL_START( ra_flags ) is false. + * Some derived specifications forbid using pre-roll settings and use post-roll settings instead (e.g. AVC uses only post-roll). + * The following is an example of pre-roll distance for representing audio decoder delay derived from composition. + * Typical AAC encoding uses a transform over consecutive sets of 2048 audio samples, + * applied every 1024 audio samples (MDCTs are overlapped). + * For correct audio to be decoded, both transforms for any period of 1024 audio samples are needed. + * For this AAC stream, therefore, 'distance' of each sample shall be set to 1 (one AAC access unit). + * Note: the number of priming audio sample i.e. encoder delay shall be represented by 'start_time' in an edit. */ +} lsmash_pre_roll_t; + +typedef struct +{ + lsmash_random_access_flag ra_flags; /* random access flags */ + lsmash_post_roll_t post_roll; + lsmash_pre_roll_t pre_roll; + uint8_t allow_earlier; /* only for QuickTime file format */ + uint8_t leading; + uint8_t independent; + uint8_t disposable; + uint8_t redundant; + uint8_t reserved[3]; /* broken link + * ??? */ +} lsmash_sample_property_t; + +typedef struct +{ +#define LSMASH_TIMESTAMP_UNDEFINED UINT64_MAX + uint32_t length; /* size of sample data + * Note: this is NOT always an allocated size. */ + uint8_t *data; /* sample data */ + uint64_t dts; /* Decoding TimeStamp in units of media timescale + * No two samples in the same track have the same Decoding TimeStamp. + * Any user must not set Decoding TimeStamp of any sample to LSMASH_TIMESTAMP_UNDEFINED. */ + uint64_t cts; /* Composition TimeStamp in units of media timescale + * No two samples in the same track have the same Composition TimeStamp. + * If sample is a non-output sample i.e. decoded but not used, set its Composition TimeStamp to LSMASH_TIMESTAMP_UNDEFINED. + * Composition TimeStamp of any non-output sample makes no sense. Note that explicit timeline maps shall be used to exclude + * non-output samples from presentation timeline. */ + uint64_t pos; /* absolute file offset of sample data (read-only) */ + uint32_t index; /* index of sample description */ + lsmash_sample_property_t prop; +} lsmash_sample_t; + +typedef struct +{ + uint64_t dts; /* Decoding TimeStamp in units of media timescale */ + uint64_t cts; /* Composition TimeStamp in units of media timescale */ +} lsmash_media_ts_t; + +typedef struct +{ + uint32_t sample_count; + lsmash_media_ts_t *timestamp; +} lsmash_media_ts_list_t; + +/* Allocate a sample and then allocate data of the allocated sample by 'size'. + * If 'size' is set to 0, data of the allocated sample won't be allocated and will be set to NULL instead. + * The allocated sample can be deallocated by lsmash_delete_sample(). + * + * Return the address of an allocated sample if successful. + * Return NULL otherwise. */ +lsmash_sample_t *lsmash_create_sample +( + uint32_t size /* size of sample data you request */ +); + +/* Allocate data of a given allocated sample by 'size'. + * If the sample data is already allocated, reallocate it by 'size'. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_sample_alloc +( + lsmash_sample_t *sample, /* the address of a sample you want to allocate its sample data */ + uint32_t size /* size of sample data you request */ +); + +/* Deallocate a given sample. */ +void lsmash_delete_sample +( + lsmash_sample_t *sample /* the address of a sample you want to deallocate */ +); + +/* Append a sample to a track. + * Note: + * The appended sample will be deleted by lsmash_delete_sample() internally. + * Users shall not deallocate the sample by lsmash_delete_sample() if successful to append the sample. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_append_sample +( + lsmash_root_t *root, + uint32_t track_ID, + lsmash_sample_t *sample +); + +/**************************************************************************** + * Media Layer + ****************************************************************************/ +/* Media handler types */ +typedef enum +{ + ISOM_MEDIA_HANDLER_TYPE_3GPP_SCENE_DESCRIPTION = LSMASH_4CC( '3', 'g', 's', 'd' ), + ISOM_MEDIA_HANDLER_TYPE_ID3_VERSION2_METADATA = LSMASH_4CC( 'I', 'D', '3', '2' ), + ISOM_MEDIA_HANDLER_TYPE_AUXILIARY_VIDEO_TRACK = LSMASH_4CC( 'a', 'u', 'x', 'v' ), + ISOM_MEDIA_HANDLER_TYPE_CPCM_AUXILIARY_METADATA = LSMASH_4CC( 'c', 'p', 'a', 'd' ), + ISOM_MEDIA_HANDLER_TYPE_CLOCK_REFERENCE_STREAM = LSMASH_4CC( 'c', 'r', 's', 'm' ), + ISOM_MEDIA_HANDLER_TYPE_DVB_MANDATORY_BASIC_DESCRIPTION = LSMASH_4CC( 'd', 'm', 'b', 'd' ), + ISOM_MEDIA_HANDLER_TYPE_TV_ANYTIME = LSMASH_4CC( 'd', 't', 'v', 'a' ), + ISOM_MEDIA_HANDLER_TYPE_BROADBAND_CONTENT_GUIDE = LSMASH_4CC( 'd', 't', 'v', 'a' ), + ISOM_MEDIA_HANDLER_TYPE_FONT_DATA_STREAM = LSMASH_4CC( 'f', 'd', 's', 'm' ), + ISOM_MEDIA_HANDLER_TYPE_GENERAL_MPEG4_SYSTEM_STREAM = LSMASH_4CC( 'g', 'e', 's', 'm' ), + ISOM_MEDIA_HANDLER_TYPE_HINT_TRACK = LSMASH_4CC( 'h', 'i', 'n', 't' ), + ISOM_MEDIA_HANDLER_TYPE_IPDC_ELECTRONIC_SERVICE_GUIDE = LSMASH_4CC( 'i', 'p', 'd', 'c' ), + ISOM_MEDIA_HANDLER_TYPE_IPMP_STREAM = LSMASH_4CC( 'i', 'p', 's', 'm' ), + ISOM_MEDIA_HANDLER_TYPE_MPEG7_STREAM = LSMASH_4CC( 'm', '7', 's', 'm' ), + ISOM_MEDIA_HANDLER_TYPE_TIMED_METADATA_TRACK = LSMASH_4CC( 'm', 'e', 't', 'a' ), + ISOM_MEDIA_HANDLER_TYPE_MPEGJ_STREAM = LSMASH_4CC( 'm', 'j', 's', 'm' ), + ISOM_MEDIA_HANDLER_TYPE_MPEG21_DIGITAL_ITEM = LSMASH_4CC( 'm', 'p', '2', '1' ), + ISOM_MEDIA_HANDLER_TYPE_OBJECT_CONTENT_INFO_STREAM = LSMASH_4CC( 'o', 'c', 's', 'm' ), + ISOM_MEDIA_HANDLER_TYPE_OBJECT_DESCRIPTOR_STREAM = LSMASH_4CC( 'o', 'd', 's', 'm' ), + ISOM_MEDIA_HANDLER_TYPE_SCENE_DESCRIPTION_STREAM = LSMASH_4CC( 's', 'd', 's', 'm' ), + ISOM_MEDIA_HANDLER_TYPE_KEY_MANAGEMENT_MESSAGES = LSMASH_4CC( 's', 'k', 'm', 'm' ), + ISOM_MEDIA_HANDLER_TYPE_AUDIO_TRACK = LSMASH_4CC( 's', 'o', 'u', 'n' ), + ISOM_MEDIA_HANDLER_TYPE_TEXT_TRACK = LSMASH_4CC( 't', 'e', 'x', 't' ), + ISOM_MEDIA_HANDLER_TYPE_PROPRIETARY_DESCRIPTIVE_METADATA = LSMASH_4CC( 'u', 'r', 'i', ' ' ), + ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK = LSMASH_4CC( 'v', 'i', 'd', 'e' ), +} lsmash_media_type; + +/* ISO language codes */ +typedef enum +{ +#define LSMASH_PACK_ISO_LANGUAGE( a, b, c ) ((((a-0x60)&0x1f)<<10) | (((b-0x60)&0x1f)<<5) | ((c-0x60)&0x1f)) + ISOM_LANGUAGE_CODE_ENGLISH = LSMASH_PACK_ISO_LANGUAGE( 'e', 'n', 'g' ), + ISOM_LANGUAGE_CODE_FRENCH = LSMASH_PACK_ISO_LANGUAGE( 'f', 'r', 'a' ), + ISOM_LANGUAGE_CODE_GERMAN = LSMASH_PACK_ISO_LANGUAGE( 'd', 'e', 'u' ), + ISOM_LANGUAGE_CODE_ITALIAN = LSMASH_PACK_ISO_LANGUAGE( 'i', 't', 'a' ), + ISOM_LANGUAGE_CODE_DUTCH_M = LSMASH_PACK_ISO_LANGUAGE( 'd', 'u', 'm' ), + ISOM_LANGUAGE_CODE_SWEDISH = LSMASH_PACK_ISO_LANGUAGE( 's', 'w', 'e' ), + ISOM_LANGUAGE_CODE_SPANISH = LSMASH_PACK_ISO_LANGUAGE( 's', 'p', 'a' ), + ISOM_LANGUAGE_CODE_DANISH = LSMASH_PACK_ISO_LANGUAGE( 'd', 'a', 'n' ), + ISOM_LANGUAGE_CODE_PORTUGUESE = LSMASH_PACK_ISO_LANGUAGE( 'p', 'o', 'r' ), + ISOM_LANGUAGE_CODE_NORWEGIAN = LSMASH_PACK_ISO_LANGUAGE( 'n', 'o', 'r' ), + ISOM_LANGUAGE_CODE_HEBREW = LSMASH_PACK_ISO_LANGUAGE( 'h', 'e', 'b' ), + ISOM_LANGUAGE_CODE_JAPANESE = LSMASH_PACK_ISO_LANGUAGE( 'j', 'p', 'n' ), + ISOM_LANGUAGE_CODE_ARABIC = LSMASH_PACK_ISO_LANGUAGE( 'a', 'r', 'a' ), + ISOM_LANGUAGE_CODE_FINNISH = LSMASH_PACK_ISO_LANGUAGE( 'f', 'i', 'n' ), + ISOM_LANGUAGE_CODE_GREEK = LSMASH_PACK_ISO_LANGUAGE( 'e', 'l', 'l' ), + ISOM_LANGUAGE_CODE_ICELANDIC = LSMASH_PACK_ISO_LANGUAGE( 'i', 's', 'l' ), + ISOM_LANGUAGE_CODE_MALTESE = LSMASH_PACK_ISO_LANGUAGE( 'm', 'l', 't' ), + ISOM_LANGUAGE_CODE_TURKISH = LSMASH_PACK_ISO_LANGUAGE( 't', 'u', 'r' ), + ISOM_LANGUAGE_CODE_CROATIAN = LSMASH_PACK_ISO_LANGUAGE( 'h', 'r', 'v' ), + ISOM_LANGUAGE_CODE_CHINESE = LSMASH_PACK_ISO_LANGUAGE( 'z', 'h', 'o' ), + ISOM_LANGUAGE_CODE_URDU = LSMASH_PACK_ISO_LANGUAGE( 'u', 'r', 'd' ), + ISOM_LANGUAGE_CODE_HINDI = LSMASH_PACK_ISO_LANGUAGE( 'h', 'i', 'n' ), + ISOM_LANGUAGE_CODE_THAI = LSMASH_PACK_ISO_LANGUAGE( 't', 'h', 'a' ), + ISOM_LANGUAGE_CODE_KOREAN = LSMASH_PACK_ISO_LANGUAGE( 'k', 'o', 'r' ), + ISOM_LANGUAGE_CODE_LITHUANIAN = LSMASH_PACK_ISO_LANGUAGE( 'l', 'i', 't' ), + ISOM_LANGUAGE_CODE_POLISH = LSMASH_PACK_ISO_LANGUAGE( 'p', 'o', 'l' ), + ISOM_LANGUAGE_CODE_HUNGARIAN = LSMASH_PACK_ISO_LANGUAGE( 'h', 'u', 'n' ), + ISOM_LANGUAGE_CODE_ESTONIAN = LSMASH_PACK_ISO_LANGUAGE( 'e', 's', 't' ), + ISOM_LANGUAGE_CODE_LATVIAN = LSMASH_PACK_ISO_LANGUAGE( 'l', 'a', 'v' ), + ISOM_LANGUAGE_CODE_SAMI = LSMASH_PACK_ISO_LANGUAGE( 's', 'm', 'i' ), + ISOM_LANGUAGE_CODE_FAROESE = LSMASH_PACK_ISO_LANGUAGE( 'f', 'a', 'o' ), + ISOM_LANGUAGE_CODE_RUSSIAN = LSMASH_PACK_ISO_LANGUAGE( 'r', 'u', 's' ), + ISOM_LANGUAGE_CODE_DUTCH = LSMASH_PACK_ISO_LANGUAGE( 'n', 'l', 'd' ), + ISOM_LANGUAGE_CODE_IRISH = LSMASH_PACK_ISO_LANGUAGE( 'g', 'l', 'e' ), + ISOM_LANGUAGE_CODE_ALBANIAN = LSMASH_PACK_ISO_LANGUAGE( 's', 'q', 'i' ), + ISOM_LANGUAGE_CODE_ROMANIAN = LSMASH_PACK_ISO_LANGUAGE( 'r', 'o', 'n' ), + ISOM_LANGUAGE_CODE_CZECH = LSMASH_PACK_ISO_LANGUAGE( 'c', 'e', 's' ), + ISOM_LANGUAGE_CODE_SLOVAK = LSMASH_PACK_ISO_LANGUAGE( 's', 'l', 'k' ), + ISOM_LANGUAGE_CODE_SLOVENIA = LSMASH_PACK_ISO_LANGUAGE( 's', 'l', 'v' ), + ISOM_LANGUAGE_CODE_YIDDISH = LSMASH_PACK_ISO_LANGUAGE( 'y', 'i', 'd' ), + ISOM_LANGUAGE_CODE_SERBIAN = LSMASH_PACK_ISO_LANGUAGE( 's', 'r', 'p' ), + ISOM_LANGUAGE_CODE_MACEDONIAN = LSMASH_PACK_ISO_LANGUAGE( 'm', 'k', 'd' ), + ISOM_LANGUAGE_CODE_BULGARIAN = LSMASH_PACK_ISO_LANGUAGE( 'b', 'u', 'l' ), + ISOM_LANGUAGE_CODE_UKRAINIAN = LSMASH_PACK_ISO_LANGUAGE( 'u', 'k', 'r' ), + ISOM_LANGUAGE_CODE_BELARUSIAN = LSMASH_PACK_ISO_LANGUAGE( 'b', 'e', 'l' ), + ISOM_LANGUAGE_CODE_UZBEK = LSMASH_PACK_ISO_LANGUAGE( 'u', 'z', 'b' ), + ISOM_LANGUAGE_CODE_KAZAKH = LSMASH_PACK_ISO_LANGUAGE( 'k', 'a', 'z' ), + ISOM_LANGUAGE_CODE_AZERBAIJANI = LSMASH_PACK_ISO_LANGUAGE( 'a', 'z', 'e' ), + ISOM_LANGUAGE_CODE_ARMENIAN = LSMASH_PACK_ISO_LANGUAGE( 'h', 'y', 'e' ), + ISOM_LANGUAGE_CODE_GEORGIAN = LSMASH_PACK_ISO_LANGUAGE( 'k', 'a', 't' ), + ISOM_LANGUAGE_CODE_MOLDAVIAN = LSMASH_PACK_ISO_LANGUAGE( 'r', 'o', 'n' ), + ISOM_LANGUAGE_CODE_KIRGHIZ = LSMASH_PACK_ISO_LANGUAGE( 'k', 'i', 'r' ), + ISOM_LANGUAGE_CODE_TAJIK = LSMASH_PACK_ISO_LANGUAGE( 't', 'g', 'k' ), + ISOM_LANGUAGE_CODE_TURKMEN = LSMASH_PACK_ISO_LANGUAGE( 't', 'u', 'k' ), + ISOM_LANGUAGE_CODE_MONGOLIAN = LSMASH_PACK_ISO_LANGUAGE( 'm', 'o', 'n' ), + ISOM_LANGUAGE_CODE_PASHTO = LSMASH_PACK_ISO_LANGUAGE( 'p', 'u', 's' ), + ISOM_LANGUAGE_CODE_KURDISH = LSMASH_PACK_ISO_LANGUAGE( 'k', 'u', 'r' ), + ISOM_LANGUAGE_CODE_KASHMIRI = LSMASH_PACK_ISO_LANGUAGE( 'k', 'a', 's' ), + ISOM_LANGUAGE_CODE_SINDHI = LSMASH_PACK_ISO_LANGUAGE( 's', 'n', 'd' ), + ISOM_LANGUAGE_CODE_TIBETAN = LSMASH_PACK_ISO_LANGUAGE( 'b', 'o', 'd' ), + ISOM_LANGUAGE_CODE_NEPALI = LSMASH_PACK_ISO_LANGUAGE( 'n', 'e', 'p' ), + ISOM_LANGUAGE_CODE_SANSKRIT = LSMASH_PACK_ISO_LANGUAGE( 's', 'a', 'n' ), + ISOM_LANGUAGE_CODE_MARATHI = LSMASH_PACK_ISO_LANGUAGE( 'm', 'a', 'r' ), + ISOM_LANGUAGE_CODE_BENGALI = LSMASH_PACK_ISO_LANGUAGE( 'b', 'e', 'n' ), + ISOM_LANGUAGE_CODE_ASSAMESE = LSMASH_PACK_ISO_LANGUAGE( 'a', 's', 'm' ), + ISOM_LANGUAGE_CODE_GUJARATI = LSMASH_PACK_ISO_LANGUAGE( 'g', 'u', 'j' ), + ISOM_LANGUAGE_CODE_PUNJABI = LSMASH_PACK_ISO_LANGUAGE( 'p', 'a', 'n' ), + ISOM_LANGUAGE_CODE_ORIYA = LSMASH_PACK_ISO_LANGUAGE( 'o', 'r', 'i' ), + ISOM_LANGUAGE_CODE_MALAYALAM = LSMASH_PACK_ISO_LANGUAGE( 'm', 'a', 'l' ), + ISOM_LANGUAGE_CODE_KANNADA = LSMASH_PACK_ISO_LANGUAGE( 'k', 'a', 'n' ), + ISOM_LANGUAGE_CODE_TAMIL = LSMASH_PACK_ISO_LANGUAGE( 't', 'a', 'm' ), + ISOM_LANGUAGE_CODE_TELUGU = LSMASH_PACK_ISO_LANGUAGE( 't', 'e', 'l' ), + ISOM_LANGUAGE_CODE_SINHALESE = LSMASH_PACK_ISO_LANGUAGE( 's', 'i', 'n' ), + ISOM_LANGUAGE_CODE_BURMESE = LSMASH_PACK_ISO_LANGUAGE( 'm', 'y', 'a' ), + ISOM_LANGUAGE_CODE_KHMER = LSMASH_PACK_ISO_LANGUAGE( 'k', 'h', 'm' ), + ISOM_LANGUAGE_CODE_LAO = LSMASH_PACK_ISO_LANGUAGE( 'l', 'a', 'o' ), + ISOM_LANGUAGE_CODE_VIETNAMESE = LSMASH_PACK_ISO_LANGUAGE( 'v', 'i', 'e' ), + ISOM_LANGUAGE_CODE_INDONESIAN = LSMASH_PACK_ISO_LANGUAGE( 'i', 'n', 'd' ), + ISOM_LANGUAGE_CODE_TAGALOG = LSMASH_PACK_ISO_LANGUAGE( 't', 'g', 'l' ), + ISOM_LANGUAGE_CODE_MALAY_ROMAN = LSMASH_PACK_ISO_LANGUAGE( 'm', 's', 'a' ), + ISOM_LANGUAGE_CODE_MAYAY_ARABIC = LSMASH_PACK_ISO_LANGUAGE( 'm', 's', 'a' ), + ISOM_LANGUAGE_CODE_AMHARIC = LSMASH_PACK_ISO_LANGUAGE( 'a', 'm', 'h' ), + ISOM_LANGUAGE_CODE_OROMO = LSMASH_PACK_ISO_LANGUAGE( 'o', 'r', 'm' ), + ISOM_LANGUAGE_CODE_SOMALI = LSMASH_PACK_ISO_LANGUAGE( 's', 'o', 'm' ), + ISOM_LANGUAGE_CODE_SWAHILI = LSMASH_PACK_ISO_LANGUAGE( 's', 'w', 'a' ), + ISOM_LANGUAGE_CODE_KINYARWANDA = LSMASH_PACK_ISO_LANGUAGE( 'k', 'i', 'n' ), + ISOM_LANGUAGE_CODE_RUNDI = LSMASH_PACK_ISO_LANGUAGE( 'r', 'u', 'n' ), + ISOM_LANGUAGE_CODE_CHEWA = LSMASH_PACK_ISO_LANGUAGE( 'n', 'y', 'a' ), + ISOM_LANGUAGE_CODE_MALAGASY = LSMASH_PACK_ISO_LANGUAGE( 'm', 'l', 'g' ), + ISOM_LANGUAGE_CODE_ESPERANTO = LSMASH_PACK_ISO_LANGUAGE( 'e', 'p', 'o' ), + ISOM_LANGUAGE_CODE_WELSH = LSMASH_PACK_ISO_LANGUAGE( 'c', 'y', 'm' ), + ISOM_LANGUAGE_CODE_BASQUE = LSMASH_PACK_ISO_LANGUAGE( 'e', 'u', 's' ), + ISOM_LANGUAGE_CODE_CATALAN = LSMASH_PACK_ISO_LANGUAGE( 'c', 'a', 't' ), + ISOM_LANGUAGE_CODE_LATIN = LSMASH_PACK_ISO_LANGUAGE( 'l', 'a', 't' ), + ISOM_LANGUAGE_CODE_QUECHUA = LSMASH_PACK_ISO_LANGUAGE( 'q', 'u', 'e' ), + ISOM_LANGUAGE_CODE_GUARANI = LSMASH_PACK_ISO_LANGUAGE( 'g', 'r', 'n' ), + ISOM_LANGUAGE_CODE_AYMARA = LSMASH_PACK_ISO_LANGUAGE( 'a', 'y', 'm' ), + ISOM_LANGUAGE_CODE_TATAR = LSMASH_PACK_ISO_LANGUAGE( 'c', 'r', 'h' ), + ISOM_LANGUAGE_CODE_UIGHUR = LSMASH_PACK_ISO_LANGUAGE( 'u', 'i', 'g' ), + ISOM_LANGUAGE_CODE_DZONGKHA = LSMASH_PACK_ISO_LANGUAGE( 'd', 'z', 'o' ), + ISOM_LANGUAGE_CODE_JAVANESE = LSMASH_PACK_ISO_LANGUAGE( 'j', 'a', 'v' ), + ISOM_LANGUAGE_CODE_UNDEFINED = LSMASH_PACK_ISO_LANGUAGE( 'u', 'n', 'd' ), +} lsmash_iso_language_code; + +typedef struct +{ + lsmash_media_type handler_type; /* the nature of the media + * You can't change handler_type through this parameter manually. */ + uint32_t timescale; /* media timescale: timescale for this media */ + uint64_t duration; /* the duration of this media, expressed in the media timescale + * You can't set this parameter manually. */ + uint8_t roll_grouping; /* roll recovery grouping present + * Require 'avc1' brand, or ISO Base Media File Format version 2 or later. */ + uint8_t rap_grouping; /* random access point grouping present + * Require ISO Base Media File Format version 6 or later. */ + /* Use either type of language code. */ + uint16_t MAC_language; /* Macintosh language code for this media */ + uint16_t ISO_language; /* ISO 639-2/T language code for this media */ + /* human-readable name for the track type (for debugging and inspection purposes) */ + char *media_handler_name; + char *data_handler_name; + /* Any user shouldn't use the following parameters. */ + PRIVATE char media_handler_name_shadow[256]; + PRIVATE char data_handler_name_shadow[256]; + /* */ + uint8_t compact_sample_size_table; /* 1: Use compact size table if possible. (output/muxing mode) + * Compact sample size table is used. (input/demuxing mode) */ + uint8_t no_sample_dependency_table; /* 1: Don't write sample dependency type table. (output/muxing mode) + * Sample dependency type table is absent. (input/demuxing mode) */ + uint8_t reserved[2]; +} lsmash_media_parameters_t; + +typedef struct +{ + uint32_t index; /* the index of a data reference */ + char *location; /* URL; location of referenced media file */ + /* Probably, additional string fields such as thing to indicate URN will be added in the future. */ +} lsmash_data_reference_t; + +/* Set all the given media parameters to default. */ +void lsmash_initialize_media_parameters +( + lsmash_media_parameters_t *param /* the address of the media parameters to which you want to set default value */ +); + +/* Set media parameters to a track. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_set_media_parameters +( + lsmash_root_t *root, /* the address of a ROOT containing a track to which you want to set the media parameters */ + uint32_t track_ID, /* the track_ID of a track to which you want to set the media parameters */ + lsmash_media_parameters_t *param /* the address of the media parameters you want to set to a track. */ +); + +/* Set the duration of the last sample to a track. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_set_last_sample_delta +( + lsmash_root_t *root, + uint32_t track_ID, + uint32_t sample_delta +); + +/* Flush samples in the internal pool in a track. + * Users shall call this function for each track before calling lsmash_finish_movie() or lsmash_create_fragment_movie(). + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_flush_pooled_samples +( + lsmash_root_t *root, + uint32_t track_ID, + uint32_t last_sample_delta +); + +/* Update the modification time of a media to the most recent. + * If the creation time of that media is larger than the modification time, + * then override the creation one with the modification one. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_update_media_modification_time +( + lsmash_root_t *root, + uint32_t track_ID +); + +/* Get the media parameters in a track. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_get_media_parameters +( + lsmash_root_t *root, + uint32_t track_ID, + lsmash_media_parameters_t *param +); + +/* Get the duration of a media. + * + * Return the duration of a media if successful. + * Return 0 otherwise. */ +uint64_t lsmash_get_media_duration +( + lsmash_root_t *root, + uint32_t track_ID +); + +/* Get the timescale of a media. + * + * Return the timescale of a media if successful. + * Return 0 otherwise. */ +uint32_t lsmash_get_media_timescale +( + lsmash_root_t *root, + uint32_t track_ID +); + +/* Get the duration of the last sample in a track. + * + * Return the duration of the last sample in a track if successful. + * Return 0 otherwise. */ +uint32_t lsmash_get_last_sample_delta +( + lsmash_root_t *root, + uint32_t track_ID +); + +/* Get the composition time offset of the first sample in a track. + * + * Return the composition time offset of the first sample in a track if successful. + * Return 0 otherwise. */ +uint32_t lsmash_get_start_time_offset +( + lsmash_root_t *root, + uint32_t track_ID +); + +/* Get the shift of composition timeline to decode timeline in a track. + * + * Return the shift of composition timeline to decode timeline in a track. if successful. + * Return 0 otherwise. */ +uint32_t lsmash_get_composition_to_decode_shift +( + lsmash_root_t *root, + uint32_t track_ID +); + +/* Pack a string of ISO 639-2/T language code into 16-bit data. + * + * Return a packed 16-bit ISO 639-2/T language if successful. + * Return 0 otherwise. */ +uint16_t lsmash_pack_iso_language +( + char *iso_language /* a string of ISO 639-2/T language code */ +); + +/* Count the number of data references in a track. + * + * Return the number of data references in a track if no error. + * Return 0 otherwise. */ +uint32_t lsmash_count_data_reference +( + lsmash_root_t *root, + uint32_t track_ID +); + +/* Get the location of a data reference in a track by specifying the index in 'data_ref'. + * The string fields in 'data_ref' may be allocated if referencing external media data. + * If referencing self-contained media data, the all string fields are set to NULL. + * You can deallocate the allocated fields by lsmash_free(). + * Also you can deallocate all of the allocated fields by lsmash_cleanup_data_reference() at a time. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_get_data_reference +( + lsmash_root_t *root, + uint32_t track_ID, + lsmash_data_reference_t *data_ref +); + +/* Deallocate all of allocated fields in a given data reference at a time. + * The deallocated fields are set to NULL. */ +void lsmash_cleanup_data_reference +( + lsmash_data_reference_t *data_ref +); + +/* Create a data reference in a track and specify its location on playback for writing. + * If no settings for data references in a track, the location of the first data reference is specified to + * the location of the same file implicitly. + * Note that referenced files shall be used as a media, i.e. LSMASH_FILE_MODE_MEDIA shall be set to the 'mode' + * in the lsmash_file_parameters_t before calling lsmash_set_file(). + * + * As restrictions of the libary, + * WARNING1: The box structured media files cannot be used as a reference data yet. + * WARNING2: The external media files cannot be used as a reference data for movie fragments yet. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_create_data_reference +( + lsmash_root_t *root, + uint32_t track_ID, + lsmash_data_reference_t *data_ref, + lsmash_file_t *file +); + +/* Assign a data reference in a track to a read file. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_assign_data_reference +( + lsmash_root_t *root, + uint32_t track_ID, + uint32_t data_ref_index, + lsmash_file_t *file +); + +/**************************************************************************** + * Track Layer + ****************************************************************************/ +/* Track mode */ +typedef enum +{ + /* In MP4 and/or ISO base media file format, if in a presentation all tracks have neither track_in_movie nor track_in_preview set, + * then all tracks shall be treated as if both flags were set on all tracks. */ + ISOM_TRACK_ENABLED = 0x000001, /* Track_enabled: Indicates that the track is enabled. + * A disabled track is treated as if it were not present. */ + ISOM_TRACK_IN_MOVIE = 0x000002, /* Track_in_movie: Indicates that the track is used in the presentation. */ + ISOM_TRACK_IN_PREVIEW = 0x000004, /* Track_in_preview: Indicates that the track is used when previewing the presentation. */ + + QT_TRACK_IN_POSTER = 0x000008, /* Track_in_poster: Indicates that the track is used in the movie's poster. (only defined in QuickTime file format) */ +} lsmash_track_mode; + +typedef struct +{ + lsmash_track_mode mode; + uint32_t track_ID; /* an integer that uniquely identifies the track + * Don't set to value already used except for zero value. + * Zero value don't override established track_ID. */ + uint64_t duration; /* the duration of this track expressed in the movie timescale units + * If there is any edit, your setting is ignored. */ + int16_t alternate_group; /* an integer that specifies a group or collection of tracks + * If this field is not 0, it should be the same for tracks that contain alternate data for one another + * and different for tracks belonging to different such groups. + * Only one track within an alternate group should be played or streamed at any one time. + * Note: alternate_group is ignored when a file is read as an MPEG-4. */ + /* The following parameters are ignored when a file is read as an MPEG-4 or 3GPP file format. */ + int16_t video_layer; /* the front-to-back ordering of video tracks; tracks with lower numbers are closer to the viewer. */ + int16_t audio_volume; /* fixed point 8.8 number. 0x0100 is full volume. */ + int32_t matrix[9]; /* transformation matrix for the video + * Each value represents, in order, a, b, u, c, d, v, x, y and w. + * All the values in a matrix are stored as 16.16 fixed-point values, + * except for u, v and w, which are stored as 2.30 fixed-point values. + * Not all derived specifications use matrices. + * If a matrix is used, the point (p, q) is transformed into (p', q') using the matrix as follows: + * | a b u | + * (p, q, 1) * | c d v | = z * (p', q', 1) + * | x y w | + * p' = (a * p + c * q + x) / z; q' = (b * p + d * q + y) / z; z = u * p + v * q + w + * Note: transformation matrix is applied after scaling to display size up to display_width and display_height. */ + /* visual presentation region size */ + uint32_t display_width; /* visual presentation region size of horizontal direction as fixed point 16.16 number. */ + uint32_t display_height; /* visual presentation region size of vertical direction as fixed point 16.16 number. */ + /* */ + uint8_t aperture_modes; /* track aperture modes present + * This feature is only available under QuickTime file format. + * Automatically disabled if multiple sample description is present or scaling method is specified. */ +} lsmash_track_parameters_t; + +/* Explicit Timeline Map (Edit) + * There are two types of timeline; one is the media timeline, the other is the presentation timeline (or the movie timeline). + * An edit maps the presentation timeline to the media timeline. + * Therefore, an edit can select any portion within the media and specify its playback speed. + * The media within the track is played through the presentation timeline, so you can construct any complex presentation from a media by edits. + * In the absence of any edit, there is an implicit one-to-one mapping of these timelines, and the presentation of a track starts at the beginning of the presentation. + * Note: any edit doesn't restrict decoding and composition. So, if a sample in an edit need to decode from a sample in outside of that edit, + * the decoder shall start to decode from there but player shall not display any sample in outside of that edit. */ +#define ISOM_EDIT_MODE_NORMAL (1<<16) +#define ISOM_EDIT_MODE_DWELL 0 +#define ISOM_EDIT_MODE_EMPTY -1 +#define ISOM_EDIT_DURATION_UNKNOWN32 0xffffffff +#define ISOM_EDIT_DURATION_UNKNOWN64 0xffffffffffffffff +#define ISOM_EDIT_DURATION_IMPLICIT 0 + +typedef struct +{ + uint64_t duration; /* the duration of this edit expressed in the movie timescale units + * An edit can refer to the media within fragmented tracks. + * The duration can be unknown at the time of creation of the initial movie due to various limiting factors that include + * real-time generation of content, such as live streaming. In such a case it is recommended that the duration is set to + * either ISOM_EDIT_DURATION_UNKNOWN32 (the maximum 32-bit unsigned integer), ISOM_EDIT_DURATION_UNKNOWN64 (the maximum + * 64-bit unsigned integer) or ISOM_EDIT_DURATION_IMPLICIT. + * If you have no interest in the duration of this edit but want to set the offset from media composition time to movie + * presentation time, ISOM_EDIT_DURATION_IMPLICIT is useful for the provision of the offset for the movie and subsequent + * movie fragments. The duration is expected to be constructed by demuxer. */ + int64_t start_time; /* the starting composition time within the media of this edit + * If set to ISOM_EDIT_MODE_EMPTY (-1), it construct an empty edit, which doesn't select any portion within the media. */ + int32_t rate; /* the relative rate at which to play the media corresponding to this edit, expressed as 16.16 fixed-point number + * If set to ISOM_EDIT_MODE_NORMAL (0x00010000), there is no rate change for timeline mapping. + * If set to ISOM_EDIT_MODE_DWELL (0), the media at start_time is presented for the duration. */ +} lsmash_edit_t; + +/* Create a track in a movie. + * Users can destroy the created track by lsmash_delete_track(). + * When a track is created, its track_ID is assigned automatically so that any duplication of track_ID may be avoided. + * + * Return the current track_ID of a track created by this function if successful. + * Return 0 otherwise. */ +uint32_t lsmash_create_track +( + lsmash_root_t *root, + lsmash_media_type media_type +); + +/* Destroy the track of a given track_ID in a movie. */ +void lsmash_delete_track +( + lsmash_root_t *root, + uint32_t track_ID +); + +/* Set all the given track parameters to default. */ +void lsmash_initialize_track_parameters +( + lsmash_track_parameters_t *param +); + +/* Set track parameters to a track. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_set_track_parameters +( + lsmash_root_t *root, + uint32_t track_ID, + lsmash_track_parameters_t *param +); + +/* Update the duration of a track with a new duration of its last sample. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_update_track_duration +( + lsmash_root_t *root, + uint32_t track_ID, + uint32_t last_sample_delta +); + +/* Update the modification time of a track to the most recent. + * If the creation time of that track is larger than the modification time, + * then override the creation one with the modification one. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_update_track_modification_time +( + lsmash_root_t *root, + uint32_t track_ID +); + +/* Get a track_ID by a track number. + * A track number is given in created order in a movie. + * If a track is removed, the track number of tracks with higher track number than one of just removed track will be decremented. + * + * Return a track_ID if successful. + * Return 0 otherwise. */ +uint32_t lsmash_get_track_ID +( + lsmash_root_t *root, + uint32_t track_number +); + +/* Get the track parameters in a track. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_get_track_parameters +( + lsmash_root_t *root, + uint32_t track_ID, + lsmash_track_parameters_t *param +); + +/* Get the duration of a track. + * + * Return the duration of a track if successful. + * Return 0 otherwise. */ +uint64_t lsmash_get_track_duration +( + lsmash_root_t *root, + uint32_t track_ID +); + +/* Create an explicit timeline map (edit) and append it into a track. + * Users can destroy ALL created edits in a track by lsmash_delete_explicit_timeline_map(). + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_create_explicit_timeline_map +( + lsmash_root_t *root, + uint32_t track_ID, + lsmash_edit_t edit +); + +/* Destroy ALL created edits in a track. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_delete_explicit_timeline_map +( + lsmash_root_t *root, + uint32_t track_ID +); + +/* Count the number of edits in a track. + * + * Return the number of edits in a track if successful. + * Return 0 otherwise. */ +uint32_t lsmash_count_explicit_timeline_map +( + lsmash_root_t *root, + uint32_t track_ID +); + +/* Get an edit in a track by an edit number. + * An edit number is given in created order in a track. + * If an edit is removed, the edit number of edits with higher edit number than one of just removed edit will be decremented. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_get_explicit_timeline_map +( + lsmash_root_t *root, + uint32_t track_ID, + uint32_t edit_number, + lsmash_edit_t *edit +); + +/* Modify an edit in a track by an edit number. + * An edit number is given in created order in a track. + * If an edit is removed, the edit number of edits with higher edit number than one of just removed edit will be decremented. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_modify_explicit_timeline_map +( + lsmash_root_t *root, + uint32_t track_ID, + uint32_t edit_number, + lsmash_edit_t edit +); + +/**************************************************************************** + * Movie Layer + ****************************************************************************/ +typedef struct +{ + uint32_t timescale; /* movie timescale: timescale for the entire presentation */ + uint64_t duration; /* the duration, expressed in movie timescale, of the longest track + * You can't set this parameter manually. */ + uint32_t number_of_tracks; /* the number of tracks in the movie + * You can't set this parameter manually. */ + /* The following parameters are recognized only when a file is read as an Apple MPEG-4 or QuickTime file format. */ + int32_t playback_rate; /* fixed point 16.16 number. 0x00010000 is normal forward playback and default value. */ + int32_t playback_volume; /* fixed point 8.8 number. 0x0100 is full volume and default value. */ + int32_t preview_time; /* the time value in the movie at which the preview begins */ + int32_t preview_duration; /* the duration of the movie preview in movie timescale units */ + int32_t poster_time; /* the time value of the time of the movie poster */ +} lsmash_movie_parameters_t; + +/* Set all the given movie parameters to default. */ +void lsmash_initialize_movie_parameters +( + lsmash_movie_parameters_t *param +); + +/* Set movie parameters to a movie. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_set_movie_parameters +( + lsmash_root_t *root, + lsmash_movie_parameters_t *param +); + +/* Finalize a movie. + * If the movie is not fragmented and 'remux' is set to non-NULL, + * move overall necessary data to access and decode samples into the very front of the file at the end. + * This is useful for progressive downloading. + * Users shall call lsmash_flush_pooled_samples() for each track before calling this function. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_finish_movie +( + lsmash_root_t *root, + lsmash_adhoc_remux_t *remux +); + +/* Update the modification time of a movie to the most recent. + * If the creation time of that movie is larger than the modification time, + * then override the creation one with the modification one. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_update_movie_modification_time +( + lsmash_root_t *root +); + +/* Get the movie parameters in a movie. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_get_movie_parameters +( + lsmash_root_t *root, + lsmash_movie_parameters_t *param +); + +/* Get the timescale of a movie. + * + * Return the timescale of a movie if successful. + * Return 0 otherwise. */ +uint32_t lsmash_get_movie_timescale +( + lsmash_root_t *root +); + +/* Reserve the size of the media data region for a non-fragmented movie. + * This enables to get rid of requirement of seekability for rewriting the actual size of the media data region when finishing + * a non-fragmented movie. Note that the specified size is excluding the type and the size fields of the enclosing box and + * this function must be called before any lsmash_append_sample(). If the actual size is greater than the reserved size when + * finishing a non-fragmented movie, seek and rewrite the size of the box enclosing the media data region. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_reserve_media_data_size +( + lsmash_root_t *root, + uint64_t media_data_size +); + +/**************************************************************************** + * Chapter list + ****************************************************************************/ +/* Create a track as a chapter list referenced by another track. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_create_reference_chapter_track +( + lsmash_root_t *root, + uint32_t track_ID, + char *file_name +); + +/* Create and set a chapter list as a user data to a movie. + * The created chapter list in a movie can be destroyed by lsmash_delete_tyrant_chapter(). + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_set_tyrant_chapter +( + lsmash_root_t *root, + char *file_name, + int add_bom +); + +/* Destroy a chapter list as a user data in a movie. */ +void lsmash_delete_tyrant_chapter +( + lsmash_root_t *root +); + +/* Count chapters in the chapter list (moov.udta.chpl). */ +uint32_t lsmash_count_tyrant_chapter +( + lsmash_root_t *root +); + +/* Retrieve a chapter entry from the chapter list (moov.udta.chpl). + * Returned pointer is owned by the ROOT structure, so user shall not + * modify or free it. + * + * Return chapter title string if successful, otherwise NULL. + */ +char *lsmash_get_tyrant_chapter +( + lsmash_root_t *root, + uint32_t index, /* index of chapter ( >= 1) */ + double *timestamp /* timestamp of the chapter entry (in seconds) */ +); + +/**************************************************************************** + * Fragments + ****************************************************************************/ +/* Flush the current movie fragment and create a new movie fragment. + * Users shall call lsmash_flush_pooled_samples() for each track before calling this function. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_create_fragment_movie +( + lsmash_root_t *root +); + +/* Create an empty duration track in the current movie fragment. + * Don't specify track_ID any track fragment in the current movie fragment has. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_create_fragment_empty_duration +( + lsmash_root_t *root, + uint32_t track_ID, + uint32_t duration +); + +/**************************************************************************** + * Dump / Print + ****************************************************************************/ +/* Dump and print box structure of ROOT into the destination. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_print_movie +( + lsmash_root_t *root, /* the address of ROOT you want to dump and print */ + const char *filename /* the path of a file as the destination */ +); + +/* Print a chapter list written as a user data on stdout. + * This function might output BOM on Windows. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_print_chapter_list +( + lsmash_root_t *root +); + +/**************************************************************************** + * Timeline + ****************************************************************************/ +/* Copy all edits from the source track to the destination track. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_copy_timeline_map +( + lsmash_root_t *dst, + uint32_t dst_track_ID, + lsmash_root_t *src, + uint32_t src_track_ID +); + +/* Construct the timeline for a track. + * The constructed timeline can be destructed by lsmash_destruct_timeline(). + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_construct_timeline +( + lsmash_root_t *root, + uint32_t track_ID +); + +/* Destruct the timeline for a given track. */ +void lsmash_destruct_timeline +( + lsmash_root_t *root, + uint32_t track_ID +); + +/* Get the duration of the last sample from the media timeline for a track. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_get_last_sample_delta_from_media_timeline +( + lsmash_root_t *root, + uint32_t track_ID, + uint32_t *last_sample_delta /* the address of a variable to which the duration of the last sample will be set */ +); + +/* Get the duration of a sample from the media timeline for a track. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_get_sample_delta_from_media_timeline +( + lsmash_root_t *root, + uint32_t track_ID, + uint32_t sample_number, + uint32_t *sample_delta /* the address of a variable to which the duration of a sample will be set */ +); + +/* Get the decoding timestamp of a sample from the media timeline for a track. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_get_dts_from_media_timeline +( + lsmash_root_t *root, + uint32_t track_ID, + uint32_t sample_number, + uint64_t *dts /* the address of a variable to which a decoding timestamp will be set */ +); + +/* Get the composition timestamp of a sample from the media timeline for a track. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_get_cts_from_media_timeline +( + lsmash_root_t *root, + uint32_t track_ID, + uint32_t sample_number, + uint64_t *cts /* the address of a variable to which a composition timestamp will be set */ +); + +/* Get the shift of composition timeline to decode timeline from the media timeline for a track. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_get_composition_to_decode_shift_from_media_timeline +( + lsmash_root_t *root, + uint32_t track_ID, + uint32_t *ctd_shift /* the address of a variable to which the shift of composition timeline to decode timeline will be set */ +); + +/* Get the sample number which is the closest random accessible point to the sample + * corresponding to a given sample number from the media timeline for a track. + * This function tries to find the closest random accessible point from the past at the first. + * If not found, try to find it from the future. + * Note: + * the closest random accessible point doesn't always guarantee that + * the sample corresponding to a given number can be decodable correctly by decoding from there. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_get_closest_random_accessible_point_from_media_timeline +( + lsmash_root_t *root, + uint32_t track_ID, + uint32_t sample_number, + uint32_t *rap_number /* the address of a variable to which the sample number of the closest random accessible point will be set */ +); + +/* Get the detailed information of the closest random accessible point to the sample + * corresponding to a given sample number from the media timeline for a track. + * Note: + * the closest random accessible point doesn't always guarantee that + * the sample corresponding to a given number can be decodable correctly by decoding from there. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_get_closest_random_accessible_point_detail_from_media_timeline +( + lsmash_root_t *root, + uint32_t track_ID, + uint32_t sample_number, + uint32_t *rap_number, /* the address of a variable to which the sample number of the closest random accessible point will be set */ + lsmash_random_access_flag *ra_flags, /* the address of a variable to which the flags of the closest random accessible point will be set */ + uint32_t *leading, /* the address of a variable to which the number of leading samples will be set */ + uint32_t *distance /* the address of a variable to which a distance from the closest random accessible point to a point which guarantees + * that the sample corresponding to a given number can be decodable correctly by decoding from there will be set */ +); + +/* Get the number of samples in the media timeline for a track. + * + * Return the number of samples in a track if successful. + * Return 0 otherwise. */ +uint32_t lsmash_get_sample_count_in_media_timeline +( + lsmash_root_t *root, + uint32_t track_ID +); + +/* Get the maximum size of sample in the media timeline for a track. + * + * Return the maximum size of the samples in a track if successful. + * Return 0 otherwise. */ +uint32_t lsmash_get_max_sample_size_in_media_timeline +( + lsmash_root_t *root, + uint32_t track_ID +); + +/* Get the duration of the media from the media timeline for a track. + * + * Return the duration of the media in a track if successful. + * Return 0 otherwise. */ +uint64_t lsmash_get_media_duration_from_media_timeline +( + lsmash_root_t *root, + uint32_t track_ID +); + +/* Allocate and get the sample corresponding to a given sample number from the media timeline for a track. + * The allocated sample can be deallocated by lsmash_delete_sample(). + * + * Return the address of an allocated and gotten sample if successful. + * Return NULL otherwise. */ +lsmash_sample_t *lsmash_get_sample_from_media_timeline +( + lsmash_root_t *root, + uint32_t track_ID, + uint32_t sample_number +); + +/* Get the information of the sample correspondint to a given sample number from the media timeline for a track. + * The information includes the size, timestamps and properties of the sample. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_get_sample_info_from_media_timeline +( + lsmash_root_t *root, + uint32_t track_ID, + uint32_t sample_number, + lsmash_sample_t *sample +); + +/* Get the properties of the sample correspondint to a given sample number from the media timeline for a track. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_get_sample_property_from_media_timeline +( + lsmash_root_t *root, + uint32_t track_ID, + uint32_t sample_number, + lsmash_sample_property_t *prop +); + +/* Check if the sample corresponding to a given sample number exists in the media timeline for a track. + * + * Return 1 if the sample exists. + * Return 0 otherwise. */ +int lsmash_check_sample_existence_in_media_timeline +( + lsmash_root_t *root, + uint32_t track_ID, + uint32_t sample_number +); + +/* Set or change the decoding and composition timestamps in the media timeline for a track. + * This function doesn't support for any LPCM track currently. + * + * Return 0 if successful. + * Return a negative value othewise. */ +int lsmash_set_media_timestamps +( + lsmash_root_t *root, + uint32_t track_ID, + lsmash_media_ts_list_t *ts_list +); + +/* Allocate and get the decoding and composition timestamps from the media timeline for a track. + * The allocated decoding and composition timestamps can be deallocated by lsmash_delete_media_timestamps(). + * + * Return 0 if successful. + * Return a negative value othewise. */ +int lsmash_get_media_timestamps +( + lsmash_root_t *root, + uint32_t track_ID, + lsmash_media_ts_list_t *ts_list +); + +/* Deallocate the decoding and composition timestamps in a given media timestamp list. */ +void lsmash_delete_media_timestamps +( + lsmash_media_ts_list_t *ts_list +); + +/* Get the maximum composition delay derived from composition reordering. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_get_max_sample_delay +( + lsmash_media_ts_list_t *ts_list, + uint32_t *max_sample_delay +); + +/* Sort decoding and composition timestamps in decoding order. */ +void lsmash_sort_timestamps_decoding_order +( + lsmash_media_ts_list_t *ts_list +); + +/* Sort decoding and composition timestamps in composition order. */ +void lsmash_sort_timestamps_composition_order +( + lsmash_media_ts_list_t *ts_list +); + +/**************************************************************************** + * Tools for creating CODEC Specific Information Extensions (Magic Cookies) + ****************************************************************************/ +/* MPEG-4 Systems Specific Information + * Mandatory : + * ISOM_CODEC_TYPE_MP4A_AUDIO + * QT_CODEC_TYPE_MP4A_AUDIO + * ISOM_CODEC_TYPE_MP4V_AUDIO + * ISOM_CODEC_TYPE_MP4S_AUDIO */ +/* objectTypeIndication */ +typedef enum +{ + MP4SYS_OBJECT_TYPE_Forbidden = 0x00, /* Forbidden */ + MP4SYS_OBJECT_TYPE_Systems_ISO_14496_1 = 0x01, /* Systems ISO/IEC 14496-1 + * For all 14496-1 streams unless specifically indicated to the contrary. + * Scene Description scenes, which are identified with StreamType=0x03, using + * this object type value shall use the BIFSConfig. */ + MP4SYS_OBJECT_TYPE_Systems_ISO_14496_1_BIFSv2 = 0x02, /* Systems ISO/IEC 14496-1 + * This object type shall be used, with StreamType=0x03, for Scene + * Description streams that use the BIFSv2Config. + * Its use with other StreamTypes is reserved. */ + MP4SYS_OBJECT_TYPE_Interaction_Stream = 0x03, /* Interaction Stream */ + MP4SYS_OBJECT_TYPE_Extended_BIFS = 0x04, /* Extended BIFS + * Used, with StreamType=0x03, for Scene Description streams that use the BIFSConfigEx; + * its use with other StreamTypes is reserved. + * (Was previously reserved for MUCommandStream but not used for that purpose.) */ + MP4SYS_OBJECT_TYPE_AFX_Stream = 0x05, /* AFX Stream + * Used, with StreamType=0x03, for Scene Description streams that use the AFXConfig; + * its use with other StreamTypes is reserved. */ + MP4SYS_OBJECT_TYPE_Font_Data_Stream = 0x06, /* Font Data Stream */ + MP4SYS_OBJECT_TYPE_Synthetised_Texture = 0x07, /* Synthetised Texture */ + MP4SYS_OBJECT_TYPE_Text_Stream = 0x08, /* Text Stream */ + MP4SYS_OBJECT_TYPE_Visual_ISO_14496_2 = 0x20, /* Visual ISO/IEC 14496-2 + * The actual object types are within the DecoderSpecificInfo and defined in 14496-2. */ + MP4SYS_OBJECT_TYPE_Visual_H264_ISO_14496_10 = 0x21, /* Visual ITU-T Recommendation H.264 | ISO/IEC 14496-10 + * The actual object types are within the DecoderSpecificInfo and defined in H.264 | 14496-10. */ + MP4SYS_OBJECT_TYPE_Parameter_Sets_H_264_ISO_14496_10 = 0x22, /* Parameter Sets for ITU-T Recommendation H.264 | ISO/IEC 14496-10 + * The actual object types are within the DecoderSpecificInfo and defined in H.264 | 14496-10. */ + MP4SYS_OBJECT_TYPE_Audio_ISO_14496_3 = 0x40, /* Audio ISO/IEC 14496-3 (MPEG-4 Audio) + * The actual object types are defined in 14496-3 and are in the DecoderSpecificInfo as specified in 14496-3. */ + MP4SYS_OBJECT_TYPE_Visual_ISO_13818_2_Simple_Profile = 0x60, /* Visual ISO/IEC 13818-2 Simple Profile (MPEG-2 Video) */ + MP4SYS_OBJECT_TYPE_Visual_ISO_13818_2_Main_Profile = 0x61, /* Visual ISO/IEC 13818-2 Main Profile */ + MP4SYS_OBJECT_TYPE_Visual_ISO_13818_2_SNR_Profile = 0x62, /* Visual ISO/IEC 13818-2 SNR Profile */ + MP4SYS_OBJECT_TYPE_Visual_ISO_13818_2_Spatial_Profile = 0x63, /* Visual ISO/IEC 13818-2 Spatial Profile */ + MP4SYS_OBJECT_TYPE_Visual_ISO_13818_2_High_Profile = 0x64, /* Visual ISO/IEC 13818-2 High Profile */ + MP4SYS_OBJECT_TYPE_Visual_ISO_13818_2_422_Profile = 0x65, /* Visual ISO/IEC 13818-2 422 Profile */ + MP4SYS_OBJECT_TYPE_Audio_ISO_13818_7_Main_Profile = 0x66, /* Audio ISO/IEC 13818-7 Main Profile (MPEG-2 Audio)(AAC) */ + MP4SYS_OBJECT_TYPE_Audio_ISO_13818_7_LC_Profile = 0x67, /* Audio ISO/IEC 13818-7 LowComplexity Profile */ + MP4SYS_OBJECT_TYPE_Audio_ISO_13818_7_SSR_Profile = 0x68, /* Audio ISO/IEC 13818-7 Scaleable Sampling Rate Profile + * For streams kinda 13818-7 the decoder specific information consists of the ADIF header if present + * (or none if not present) and an access unit is a "raw_data_block()" as defined in 13818-7. */ + MP4SYS_OBJECT_TYPE_Audio_ISO_13818_3 = 0x69, /* Audio ISO/IEC 13818-3 (MPEG-2 BC-Audio)(redefined MPEG-1 Audio in MPEG-2) + * For streams kinda 13818-3 the decoder specific information is empty since all necessary data is in the bitstream frames itself. + * The access units in this case are the "frame()" bitstream element as is defined in 11172-3. */ + MP4SYS_OBJECT_TYPE_Visual_ISO_11172_2 = 0x6A, /* Visual ISO/IEC 11172-2 (MPEG-1 Video) */ + MP4SYS_OBJECT_TYPE_Audio_ISO_11172_3 = 0x6B, /* Audio ISO/IEC 11172-3 (MPEG-1 Audio) */ + MP4SYS_OBJECT_TYPE_Visual_ISO_10918_1 = 0x6C, /* Visual ISO/IEC 10918-1 (JPEG) */ + MP4SYS_OBJECT_TYPE_PNG = 0x6D, /* Portable Network Graphics */ + MP4SYS_OBJECT_TYPE_Visual_ISO_15444_1_JPEG2000 = 0x6E, /* Visual ISO/IEC 15444-1 (JPEG 2000) */ + + /* FIXME: rename these symbols to be explaining, rather than based on four cc */ + MP4SYS_OBJECT_TYPE_EVRC_AUDIO = 0xA0, /* EVRC Voice */ + MP4SYS_OBJECT_TYPE_SSMV_AUDIO = 0xA1, /* SMV Voice */ + MP4SYS_OBJECT_TYPE_3GPP2_CMF = 0xA2, /* 3GPP2 Compact Multimedia Format (CMF) */ + MP4SYS_OBJECT_TYPE_VC_1_VIDEO = 0xA3, /* SMPTE VC-1 Video */ + MP4SYS_OBJECT_TYPE_DRAC_VIDEO = 0xA4, /* Dirac Video Coder */ + MP4SYS_OBJECT_TYPE_AC_3_AUDIO = 0xA5, /* AC-3 Audio */ + MP4SYS_OBJECT_TYPE_EC_3_AUDIO = 0xA6, /* Enhanced AC-3 audio */ + MP4SYS_OBJECT_TYPE_DRA1_AUDIO = 0xA7, /* DRA Audio */ + MP4SYS_OBJECT_TYPE_G719_AUDIO = 0xA8, /* ITU G.719 Audio */ + MP4SYS_OBJECT_TYPE_DTSC_AUDIO = 0xA9, /* DTS Coherent Acoustics audio */ + MP4SYS_OBJECT_TYPE_DTSH_AUDIO = 0xAA, /* DTS-HD High Resolution Audio */ + MP4SYS_OBJECT_TYPE_DTSL_AUDIO = 0xAB, /* DTS-HD Master Audio */ + MP4SYS_OBJECT_TYPE_DTSE_AUDIO = 0xAC, /* DTS Express low bit rate audio, also known as DTS LBR */ + MP4SYS_OBJECT_TYPE_SQCP_AUDIO = 0xE1, /* 13K Voice */ + + MP4SYS_OBJECT_TYPE_NONE = 0xFF, /* no object type specified + * Streams with this value with a StreamType indicating a systems stream (values 1,2,3,6,7,8,9) + * shall be treated as if the ObjectTypeIndication had been set to 0x01. */ +} lsmash_mp4sys_object_type_indication; + +/* streamType */ +typedef enum +{ + MP4SYS_STREAM_TYPE_Forbidden = 0x00, /* Forbidden */ + MP4SYS_STREAM_TYPE_ObjectDescriptorStream = 0x01, /* ObjectDescriptorStream */ + MP4SYS_STREAM_TYPE_ClockReferenceStream = 0x02, /* ClockReferenceStream */ + MP4SYS_STREAM_TYPE_SceneDescriptionStream = 0x03, /* SceneDescriptionStream */ + MP4SYS_STREAM_TYPE_VisualStream = 0x04, /* VisualStream */ + MP4SYS_STREAM_TYPE_AudioStream = 0x05, /* AudioStream */ + MP4SYS_STREAM_TYPE_MPEG7Stream = 0x06, /* MPEG7Stream */ + MP4SYS_STREAM_TYPE_IPMPStream = 0x07, /* IPMPStream */ + MP4SYS_STREAM_TYPE_ObjectContentInfoStream = 0x08, /* ObjectContentInfoStream */ + MP4SYS_STREAM_TYPE_MPEGJStream = 0x09, /* MPEGJStream */ + MP4SYS_STREAM_TYPE_InteractionStream = 0x0A, /* Interaction Stream */ + MP4SYS_STREAM_TYPE_IPMPToolStream = 0x0B, /* IPMPToolStream */ + MP4SYS_STREAM_TYPE_FontDataStream = 0x0C, /* FontDataStream */ + MP4SYS_STREAM_TYPE_StreamingText = 0x0D, /* StreamingText */ +} lsmash_mp4sys_stream_type; + +/* MPEG-4 Systems Decoder Specific Information + * an opaque container with information for a specific media decoder + * The existence and semantics of decoder specific information depends on the values of streamType and objectTypeIndication. */ +typedef struct lsmash_mp4sys_decoder_specific_info_tag lsmash_mp4sys_decoder_specific_info_t; + +/* Note: bufferSizeDB, maxBitrate and avgBitrate are calculated internally when calling lsmash_finish_movie(). + * You need not to set up them manually when muxing streams by L-SMASH. */ +typedef struct +{ + lsmash_mp4sys_object_type_indication objectTypeIndication; + lsmash_mp4sys_stream_type streamType; + uint32_t bufferSizeDB; /* the size of the decoding buffer for this elementary stream in byte */ + uint32_t maxBitrate; /* the maximum bitrate in bits per second of the elementary stream in + * any time window of one second duration */ + uint32_t avgBitrate; /* the average bitrate in bits per second of the elementary stream + * Set to 0 if the stream is encoded as variable bitrate. */ + lsmash_mp4sys_decoder_specific_info_t *dsi; /* zero or one decoder specific information */ +} lsmash_mp4sys_decoder_parameters_t; + +int lsmash_set_mp4sys_decoder_specific_info +( + lsmash_mp4sys_decoder_parameters_t *param, + uint8_t *payload, + uint32_t payload_length +); + +void lsmash_destroy_mp4sys_decoder_specific_info +( + lsmash_mp4sys_decoder_parameters_t *param +); + +uint8_t *lsmash_create_mp4sys_decoder_config +( + lsmash_mp4sys_decoder_parameters_t *param, + uint32_t *data_length +); + +/* Return MP4SYS_OBJECT_TYPE_Forbidden if objectTypeIndication is not found or there is an error to find it. */ +lsmash_mp4sys_object_type_indication lsmash_mp4sys_get_object_type_indication +( + lsmash_summary_t *summary +); + +/* Return -1 if any error. + * Even if the decoder specific information is not found, it is not an error since no decoder specific information is allowed for some stream formats. */ +int lsmash_get_mp4sys_decoder_specific_info +( + lsmash_mp4sys_decoder_parameters_t *param, + uint8_t **payload, + uint32_t *payload_length +); + +/* AC-3 Specific Information + * Mandatory : + * ISOM_CODEC_TYPE_AC_3_AUDIO + * + * Unlike MPEG-4 Audio formats, the decoder does not require this for the initialization. + * Each AC-3 sample is self-contained. + * Users shall set the actual sample rate to 'frequency', which is a member of lsmash_audio_summary_t. */ +typedef struct +{ + uint8_t fscod; /* the same value as the fscod field in the AC-3 bitstream */ + uint8_t bsid; /* the same value as the bsid field in the AC-3 bitstream */ + uint8_t bsmod; /* the same value as the bsmod field in the AC-3 bitstream */ + uint8_t acmod; /* the same value as the acmod field in the AC-3 bitstream */ + uint8_t lfeon; /* the same value as the lfeon field in the AC-3 bitstream */ + uint8_t frmsizecod; /* the same value as the frmsizecod field in the AC-3 bitstream */ +} lsmash_ac3_specific_parameters_t; + +int lsmash_setup_ac3_specific_parameters_from_syncframe +( + lsmash_ac3_specific_parameters_t *param, + uint8_t *data, + uint32_t data_length +); + +uint8_t *lsmash_create_ac3_specific_info +( + lsmash_ac3_specific_parameters_t *param, + uint32_t *data_length +); + +/* Enhanced AC-3 Specific Information + * Mandatory : + * ISOM_CODEC_TYPE_EC_3_AUDIO + * + * Unlike MPEG-4 Audio formats, the decoder does not require this for the initialization. + * Each Enhanced AC-3 sample is self-contained. + * Note that this cannot document reduced sample rates (24000, 22050 or 16000 Hz). + * Therefore, users shall set the actual sample rate to 'frequency', which is a member of lsmash_audio_summary_t. */ +typedef struct +{ + uint8_t fscod; /* the same value as the fscod field in the independent substream */ + uint8_t bsid; /* the same value as the bsid field in the independent substream */ + uint8_t bsmod; /* the same value as the bsmod field in the independent substream + * If the bsmod field is not present in the independent substream, this field shall be set to 0. */ + uint8_t acmod; /* the same value as the acmod field in the independent substream */ + uint8_t lfeon; /* the same value as the lfeon field in the independent substream */ + uint8_t num_dep_sub; /* the number of dependent substreams that are associated with the independent substream */ + uint16_t chan_loc; /* channel locations of dependent substreams associated with the independent substream + * This information is extracted from the chanmap field of each dependent substream. */ +} lsmash_eac3_substream_info_t; + +typedef struct +{ + uint16_t data_rate; /* the data rate of the Enhanced AC-3 bitstream in kbit/s + * If the Enhanced AC-3 stream is variable bitrate, then this value indicates the maximum data rate of the stream. */ + uint8_t num_ind_sub; /* the number of independent substreams that are present in the Enhanced AC-3 bitstream + * The value of this field is one less than the number of independent substreams present + * and shall be in the range of 0 to 7, inclusive. */ + lsmash_eac3_substream_info_t independent_info[8]; +} lsmash_eac3_specific_parameters_t; + +int lsmash_setup_eac3_specific_parameters_from_frame +( + lsmash_eac3_specific_parameters_t *param, + uint8_t *data, + uint32_t data_length +); + +uint16_t lsmash_eac3_get_chan_loc_from_chanmap +( + uint16_t chanmap +); + +uint8_t *lsmash_create_eac3_specific_info +( + lsmash_eac3_specific_parameters_t *param, + uint32_t *data_length +); + +/* DTS Audio Specific Information + * Mandatory : + * ISOM_CODEC_TYPE_DTSC_AUDIO + * ISOM_CODEC_TYPE_DTSH_AUDIO + * ISOM_CODEC_TYPE_DTSL_AUDIO + * ISOM_CODEC_TYPE_DTSE_AUDIO + * + * Unlike MPEG-4 Audio formats, the decoder does not require this for the initialization. + * Each DTS Audio sample is self-contained. */ +typedef enum +{ + DTS_CORE_SUBSTREAM_CORE_FLAG = 0x00000001, + DTS_CORE_SUBSTREAM_XXCH_FLAG = 0x00000002, + DTS_CORE_SUBSTREAM_X96_FLAG = 0x00000004, + DTS_CORE_SUBSTREAM_XCH_FLAG = 0x00000008, + DTS_EXT_SUBSTREAM_CORE_FLAG = 0x00000010, + DTS_EXT_SUBSTREAM_XBR_FLAG = 0x00000020, + DTS_EXT_SUBSTREAM_XXCH_FLAG = 0x00000040, + DTS_EXT_SUBSTREAM_X96_FLAG = 0x00000080, + DTS_EXT_SUBSTREAM_LBR_FLAG = 0x00000100, + DTS_EXT_SUBSTREAM_XLL_FLAG = 0x00000200, +} lsmash_dts_construction_flag; + +typedef struct lsmash_dts_reserved_box_tag lsmash_dts_reserved_box_t; + +typedef struct +{ + uint32_t DTSSamplingFrequency; /* the maximum sampling frequency stored in the compressed audio stream + * 'frequency', which is a member of lsmash_audio_summary_t, shall be set according to DTSSamplingFrequency of either: + * 48000 for original sampling frequencies of 24000Hz, 48000Hz, 96000Hz or 192000Hz; + * 44100 for original sampling frequencies of 22050Hz, 44100Hz, 88200Hz or 176400Hz; + * 32000 for original sampling frequencies of 16000Hz, 32000Hz, 64000Hz or 128000Hz. */ + uint32_t maxBitrate; /* the peak bit rate, in bits per second, of the audio elementary stream for the duration of the track, + * including the core substream (if present) and all extension substreams. + * If the stream is a constant bit rate, this parameter shall have the same value as avgBitrate. + * If the maximum bit rate is unknown, this parameter shall be set to 0. */ + uint32_t avgBitrate; /* the average bit rate, in bits per second, of the audio elementary stream for the duration of the track, + * including the core substream and any extension substream that may be present. */ + uint8_t pcmSampleDepth; /* the bit depth of the rendered audio + * The value is 16 or 24 bits. */ + uint8_t FrameDuration; /* the number of audio samples decoded in a complete audio access unit at DTSSamplingFrequency + * 0: 512, 1: 1024, 2: 2048, 3: 4096 */ + uint8_t StreamConstruction; /* complete information on the existence and of location of extensions in any synchronized frame */ + uint8_t CoreLFEPresent; /* the presence of an LFE channel in the core + * 0: none + * 1: LFE exists */ + uint8_t CoreLayout; /* the channel layout of the core within the core substream + * If no core substream exists, this parameter shall be ignored and ChannelLayout or + * RepresentationType shall be used to determine channel configuration. */ + uint16_t CoreSize; /* The size of a core substream AU in bytes. + * If no core substream exists, CoreSize = 0. */ + uint8_t StereoDownmix; /* the presence of an embedded stereo downmix in the stream + * 0: none + * 1: embedded downmix present */ + uint8_t RepresentationType; /* This indicates special properties of the audio presentation. + * 0: Audio asset designated for mixing with another audio asset + * 2: Lt/Rt Encoded for matrix surround decoding + * 3: Audio processed for headphone playback + * otherwise: Reserved + * If ChannelLayout != 0, this value shall be ignored. */ + uint16_t ChannelLayout; /* complete information on channels coded in the audio stream including core and extensions */ + uint8_t MultiAssetFlag; /* This flag shall set if the stream contains more than one asset. + * 0: single asset + * 1: multiple asset + * When multiple assets exist, the remaining parameters only reflect the coding parameters of the first asset. */ + uint8_t LBRDurationMod; /* This flag indicates a special case of the LBR coding bandwidth, resulting in 1/3 or 2/3 band limiting. + * If set to 1, LBR frame duration is 50 % larger than indicated in FrameDuration */ + lsmash_dts_reserved_box_t *box; +} lsmash_dts_specific_parameters_t; + +int lsmash_setup_dts_specific_parameters_from_frame +( + lsmash_dts_specific_parameters_t *param, + uint8_t *data, + uint32_t data_length +); + +uint8_t lsmash_dts_get_stream_construction +( + lsmash_dts_construction_flag flags +); + +lsmash_dts_construction_flag lsmash_dts_get_construction_flags +( + uint8_t stream_construction +); + +lsmash_codec_type_t lsmash_dts_get_codingname +( + lsmash_dts_specific_parameters_t *param +); + +uint8_t *lsmash_create_dts_specific_info +( + lsmash_dts_specific_parameters_t *param, + uint32_t *data_length +); + +int lsmash_append_dts_reserved_box +( + lsmash_dts_specific_parameters_t *param, + const uint8_t *box_data, + uint32_t box_size +); + +void lsmash_remove_dts_reserved_box +( + lsmash_dts_specific_parameters_t *param +); + +/* Apple Lossless Audio Specific Information + * Mandatory : + * ISOM_CODEC_TYPE_ALAC_AUDIO + * QT_CODEC_TYPE_ALAC_AUDIO */ +typedef struct +{ + uint32_t frameLength; /* the frames per packet when no explicit frames per packet setting is present in the packet header + * The encoder frames per packet can be explicitly set but for maximum compatibility, + * the default encoder setting of 4096 should be used. */ + uint8_t bitDepth; /* the bit depth of the source PCM data (maximum value = 32) */ + uint8_t numChannels; /* the channel count (1 = mono, 2 = stereo, etc...) + * When channel layout info is not provided in the Channel Layout extension, + * a channel count > 2 describes a set of discreet channels with no specific ordering. */ + uint32_t maxFrameBytes; /* the maximum size of an Apple Lossless packet within the encoded stream + * Value of 0 indicates unknown. */ + uint32_t avgBitrate; /* the average bit rate in bits per second of the Apple Lossless stream + * Value of 0 indicates unknown. */ + uint32_t sampleRate; /* sample rate of the encoded stream */ +} lsmash_alac_specific_parameters_t; + +uint8_t *lsmash_create_alac_specific_info +( + lsmash_alac_specific_parameters_t *param, + uint32_t *data_length +); + +/* MPEG-4 Bitrate Information. + * Optional : + * ISOM_CODEC_TYPE_AVC1_VIDEO + * ISOM_CODEC_TYPE_AVC2_VIDEO + * ISOM_CODEC_TYPE_AVC3_VIDEO + * ISOM_CODEC_TYPE_AVC4_VIDEO + * ISOM_CODEC_TYPE_HVC1_VIDEO + * ISOM_CODEC_TYPE_HEV1_VIDEO + * + * Though you need not to set these fields manually since lsmash_finish_movie() calls the function + * that calculates these values internally, these fields are optional. + * Therefore, if you want to add this info, append this as an extension via LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_H264_BITRATE at least. */ +typedef struct +{ + uint32_t bufferSizeDB; /* the size of the decoding buffer for the elementary stream in bytes */ + uint32_t maxBitrate; /* the maximum rate in bits/second over any window of one second */ + uint32_t avgBitrate; /* the average rate in bits/second over the entire presentation */ +} lsmash_h264_bitrate_t; + +/* Appendability of NAL unit into Decoder Configuration Record */ +typedef enum +{ + DCR_NALU_APPEND_NEW_SAMPLE_ENTRY_REQUIRED = -3, /* A new sample description entry is needed because e.g. visual presentation size changes. */ + DCR_NALU_APPEND_NEW_DCR_REQUIRED = -2, /* A new Decoder Configuration Record is needed. */ + DCR_NALU_APPEND_ERROR = -1, /* something of errors */ + DCR_NALU_APPEND_DUPLICATED = 0, /* The same NAL unit is in the Decoder Configuration Record. */ + DCR_NALU_APPEND_POSSIBLE = 1, /* It is possible to append the NAL unit into the Decoder Configuration Record. */ +} lsmash_dcr_nalu_appendable; + +/* H.264/AVC Specific Information + * Mandatory : + * ISOM_CODEC_TYPE_AVC1_VIDEO + * ISOM_CODEC_TYPE_AVC2_VIDEO + * ISOM_CODEC_TYPE_AVC3_VIDEO + * ISOM_CODEC_TYPE_AVC4_VIDEO + * + * All members in lsmash_h264_specific_parameters_t except for lengthSizeMinusOne shall be automatically set up + * when appending SPS NAL units by calling lsmash_append_h264_parameter_set(). */ +typedef enum +{ + H264_PARAMETER_SET_TYPE_SPS = 0, /* SPS (Sequence Parameter Set) */ + H264_PARAMETER_SET_TYPE_PPS = 1, /* PPS (Picture Parameter Set) */ + H264_PARAMETER_SET_TYPE_SPSEXT = 2, /* SPS Ext (Sequence Parameter Set Extension) */ + /* The number of the NAL unit types defined for AVC Decoder Configuration Record */ + H264_PARAMETER_SET_TYPE_NUM +} lsmash_h264_parameter_set_type; + +typedef struct lsmash_h264_parameter_sets_tag lsmash_h264_parameter_sets_t; + +typedef struct +{ + uint8_t AVCProfileIndication; /* profile_idc in sequence parameter sets + * This field must indicate a profile to which the stream associated with + * this configuration record conforms. + * Note: there is no profile to which the entire stream conforms, then + * the entire stream must be split into two or more sub-streams with + * separate configuration records in which these rules can be met. */ + uint8_t profile_compatibility; /* constraint_set_flags in sequence parameter sets + * The each bit may only be set if all the included parameter sets set that flag. */ + uint8_t AVCLevelIndication; /* level_idc in sequence parameter sets + * This field must indicate a level of capability equal to or greater than + * the highest level indicated in the included parameter sets. */ + uint8_t lengthSizeMinusOne; /* the length in bytes of the NALUnitLength field prior to NAL unit + * The value of this field shall be one of 0, 1, or 3 + * corresponding to a length encoded with 1, 2, or 4 bytes, respectively. + * NALUnitLength indicates the size of a NAL unit measured in bytes, + * and includes the size of both the one byte NAL header and the EBSP payload + * but does not include the length field itself. */ + /* chroma format and bit depth information + * These fields must be identical in all the parameter sets. */ + uint8_t chroma_format; /* chroma_format_idc in sequence parameter sets */ + uint8_t bit_depth_luma_minus8; /* bit_depth_luma_minus8 in sequence parameter sets */ + uint8_t bit_depth_chroma_minus8; /* bit_depth_chroma_minus8 in sequence parameter sets */ + /* a set of arrays to carry initialization NAL units + * The NAL unit types are restricted to indicate SPS, PPS and SPS Ext NAL units only. */ + lsmash_h264_parameter_sets_t *parameter_sets; +} lsmash_h264_specific_parameters_t; + +int lsmash_setup_h264_specific_parameters_from_access_unit +( + lsmash_h264_specific_parameters_t *param, + uint8_t *data, + uint32_t data_length +); + +void lsmash_destroy_h264_parameter_sets +( + lsmash_h264_specific_parameters_t *param +); + +lsmash_dcr_nalu_appendable lsmash_check_h264_parameter_set_appendable +( + lsmash_h264_specific_parameters_t *param, + lsmash_h264_parameter_set_type ps_type, + void *ps_data, + uint32_t ps_length +); + +int lsmash_append_h264_parameter_set +( + lsmash_h264_specific_parameters_t *param, + lsmash_h264_parameter_set_type ps_type, + void *ps_data, + uint32_t ps_length +); + +uint8_t *lsmash_create_h264_specific_info +( + lsmash_h264_specific_parameters_t *param, + uint32_t *data_length +); + +/* H.265/HEVC Specific Information + * Mandatory : + * ISOM_CODEC_TYPE_HVC1_VIDEO + * ISOM_CODEC_TYPE_HEV1_VIDEO + * + * All members in lsmash_hevc_specific_parameters_t except for avgFrameRate and lengthSizeMinusOne shall be + * automatically set up when appending VPS and SPS NAL units by calling lsmash_append_hevc_dcr_nalu(). + * It is recommended that you should append VPS, SPS and PPS in this order so that a parameter set can reference + * another parameter set. */ +typedef enum +{ + /* Parameter sets + * When the CODEC identifier ISOM_CODEC_TYPE_HVC1_VIDEO is used, at least one parameter set for each array + * of parameter set shall be in the configuration record. */ + HEVC_DCR_NALU_TYPE_VPS = 0, /* VPS (Video Parameter Set) */ + HEVC_DCR_NALU_TYPE_SPS = 1, /* SPS (Sequence Parameter Set) */ + HEVC_DCR_NALU_TYPE_PPS = 2, /* PPS (Picture Parameter Set) */ + /* SEI (Supplemental Enhancement Information) + * Only the 'declarative' SEI messages that provide information about the stream as + * a whole are allowed because SEIs themselves basically have no identifier and + * no one can reference dynamically in a configuration record. */ + HEVC_DCR_NALU_TYPE_PREFIX_SEI = 3, /* Prefix SEI */ + HEVC_DCR_NALU_TYPE_SUFFIX_SEI = 4, /* Suffix SEI */ + /* The number of the NAL unit types defined for HEVC Decoder Configuration Record */ + HEVC_DCR_NALU_TYPE_NUM +} lsmash_hevc_dcr_nalu_type; + +typedef struct lsmash_hevc_parameter_arrays_tag lsmash_hevc_parameter_arrays_t; + +typedef struct +{ + /* general profile, tier and level from VPS and/or SPS */ + uint8_t general_profile_space; /* general_profile_space + * The value in all the parameter sets must be identical. */ + uint8_t general_tier_flag; /* general_tier_flag + * The value must indicate a tier equal to or greater than the highest + * tier indicated in all the parameter sets. */ + uint8_t general_profile_idc; /* general_profile_idc + * The value must indicate a profile to which the stream associated with + * this configuration record conforms. + * Note: there is no profile to which the entire stream conforms, then + * the entire stream must be split into two or more sub-streams with + * separate configuration records in which these rules can be met. */ + uint32_t general_profile_compatibility_flags; /* general_profile_compatibility_flag[j] for j from 0 to 31 + * The each bit may only be set if all the parameter sets set that bit. */ + uint64_t general_constraint_indicator_flags; /* the 6 bytes starting with the byte containing the general_progressive_source_flag + * The each bit may only be set if all the parameter sets set that bit. */ + uint8_t general_level_idc; /* general_level_idc + * The value must indicate a level of capability equal to or greater + * than the highest level indicated for the highest tier in all the + * parameter sets. */ + /* */ + uint16_t min_spatial_segmentation_idc; /* min_spatial_segmentation_idc in VUI (Video Usability Information) + * The value must indicate a level of spatial segmentation equal to + * or less than the lowest level of spatial segmentation indicated in + * all the parameter sets. */ + uint8_t parallelismType; /* parallelismType indicates the type of parallelism that is used to meet the + * restrictions imposed by min_spatial_segmentation_idc when the value of + * min_spatial_segmentation_idc is greater than 0. + * For the stream to which this configuration record applies, + * 0: mixed types of parallel decoding or parallelism type is unknown + * 1: slice based parallel decoding + * 2: tile based parallel decoding + * 3: entropy coding synchronization based parallel decoding + * (WPP: Wavefront Parallel Processing) */ + /* chroma format and bit depth information + * These fields must be identical in all the parameter sets. */ + uint8_t chromaFormat; /* chroma_format_idc in sequence parameter sets */ + uint8_t bitDepthLumaMinus8; /* bit_depth_luma_minus8 in sequence parameter sets */ + uint8_t bitDepthChromaMinus8; /* bit_depth_chroma_minus8 in sequence parameter sets */ + /* miscellaneous */ + uint16_t avgFrameRate; /* the average frame rate in units of frames/(256 seconds) + * Value 0 indicates an unspecified average frame rate. */ + uint8_t constantFrameRate; /* 0: the stream may or may not be of constant frame rate. + * 1: that the stream to which this configuration record applies is of + * constant frame rate + * 2: the representation of each temporal layer in the stream is of + * constant frame rate. */ + uint8_t numTemporalLayers; /* 0: it is unknown whether the stream is temporally scalable. + * 1: the stream is not temporally scalable. + * otherwise: the stream to which this configuration record applies is + * temporally scalable and the contained number of temporal layers + * (also referred to as temporal sublayer or sub-layer) is equal + * is equal to numTemporalLayers. */ + uint8_t temporalIdNested; /* 0: at least one of the SPSs that are activated when the stream to which + * this configuration record applies is decoded has sps_temporal_id_nesting_flag + * equal to 0. + * 1: all SPSs that are activated when the stream to which this configuration + * record applies is decoded have sps_temporal_id_nesting_flag equal to 1 + * and temporal sub-layer up-switching to any higher temporal layer can be + * performed at any sample. + * Any step-wise temporal sub-layer access picture shall not be present in + * the sequences to which this configuration record applies. */ + uint8_t lengthSizeMinusOne; /* the length in bytes of the NALUnitLength field prior to NAL unit + * The value of this field shall be one of 0, 1, or 3 + * corresponding to a length encoded with 1, 2, or 4 bytes, respectively. + * NALUnitLength indicates the size of a NAL unit measured in bytes, + * and includes the size of both the one byte NAL header and the EBSP payload + * but does not include the length field itself. */ + /* a set of arrays to carry initialization NAL units + * The NAL unit types are restricted to indicate VPS, SPS, PPS, and SEI NAL units only. */ + lsmash_hevc_parameter_arrays_t *parameter_arrays; +} lsmash_hevc_specific_parameters_t; + +int lsmash_setup_hevc_specific_parameters_from_access_unit +( + lsmash_hevc_specific_parameters_t *param, + uint8_t *data, + uint32_t data_length +); + +void lsmash_destroy_hevc_parameter_arrays +( + lsmash_hevc_specific_parameters_t *param +); + +lsmash_dcr_nalu_appendable lsmash_check_hevc_dcr_nalu_appendable +( + lsmash_hevc_specific_parameters_t *param, + lsmash_hevc_dcr_nalu_type ps_type, + void *ps_data, + uint32_t ps_length +); + +int lsmash_append_hevc_dcr_nalu +( + lsmash_hevc_specific_parameters_t *param, + lsmash_hevc_dcr_nalu_type ps_type, + void *ps_data, + uint32_t ps_length +); + +int lsmash_set_hevc_array_completeness +( + lsmash_hevc_specific_parameters_t *param, + lsmash_hevc_dcr_nalu_type ps_type, + int array_completeness +); + +int lsmash_get_hevc_array_completeness +( + lsmash_hevc_specific_parameters_t *param, + lsmash_hevc_dcr_nalu_type ps_type, + int *array_completeness +); + +uint8_t *lsmash_create_hevc_specific_info +( + lsmash_hevc_specific_parameters_t *param, + uint32_t *data_length +); + +/* VC-1 Specific Information + * Mandatory : + * ISOM_CODEC_TYPE_VC_1_VIDEO + * + * We support only advanced profile at present. */ +typedef struct lsmash_vc1_header_tag lsmash_vc1_header_t; + +typedef struct +{ + /* Note: multiple_sequence, multiple_entry, slice_present and bframe_present shall be decided through overall VC-1 bitstream. */ + uint8_t profile; /* the encoding profile used in the VC-1 bitstream + * 0: simple profile (not supported) + * 4: main profile (not supported) + * 12: advanced profile + * Currently, only 12 for advanced profile is available. */ + uint8_t level; /* the highest encoding level used in the VC-1 bitstream */ + uint8_t cbr; /* 0: non-constant bitrate model + * 1: constant bitrate model */ + uint8_t interlaced; /* 0: interlaced coding of frames is not used. + * 1: frames may use interlaced coding. */ + uint8_t multiple_sequence; /* 0: the track contains no sequence headers (stored only in VC-1 specific info structure), + * or + * all sequence headers in the track are identical to the sequence header that is specified in the seqhdr field. + * In this case, random access points are samples that contain an entry-point header. + * 1: the track may contain Sequence headers that are different from the sequence header specified in the seqhdr field. + * In this case, random access points are samples that contain both a sequence Header and an entry-point header. */ + uint8_t multiple_entry; /* 0: all entry-point headers in the track are identical to the entry-point header that is specified in the ephdr field. + * 1: the track may contain entry-point headers that are different from the entry-point header specified in the ephdr field. */ + uint8_t slice_present; /* 0: frames are not coded as multiple slices. + * 1: frames may be coded as multiple slices. */ + uint8_t bframe_present; /* 0: neither B-frames nor BI-frames are present in the track. + * 1: B-frames or BI-frames may be present in the track. */ + uint32_t framerate; /* the rounded frame rate (frames per second) of the track + * Should be set to 0xffffffff if the frame rate is not known, unspecified, or non-constant. */ + lsmash_vc1_header_t *seqhdr; /* a sequence header EBDU (mandatory) */ + lsmash_vc1_header_t *ephdr; /* an entry-point header EBDU (mandatory) */ +} lsmash_vc1_specific_parameters_t; + +int lsmash_setup_vc1_specific_parameters_from_access_unit +( + lsmash_vc1_specific_parameters_t *param, + uint8_t *data, + uint32_t data_length +); + +void lsmash_destroy_vc1_headers +( + lsmash_vc1_specific_parameters_t *param +); + +int lsmash_put_vc1_header +( + lsmash_vc1_specific_parameters_t *param, + void *hdr_data, + uint32_t hdr_length +); + +uint8_t *lsmash_create_vc1_specific_info +( + lsmash_vc1_specific_parameters_t *param, + uint32_t *data_length +); + +/* Sample scaling + * Without this extension, video samples are scaled into the visual presentation region to fill it. */ +typedef enum +{ + ISOM_SCALE_METHOD_FILL = 1, + ISOM_SCALE_METHOD_HIDDEN = 2, + ISOM_SCALE_METHOD_MEET = 3, + ISOM_SCALE_METHOD_SLICE_X = 4, + ISOM_SCALE_METHOD_SLICE_Y = 5, +} lsmash_scale_method; + +typedef struct +{ + uint8_t constraint_flag; /* Upper 7-bits are reserved. + * If this flag is set, all samples described by this sample entry shall be scaled + * according to the method specified by the field 'scale_method'. */ + lsmash_scale_method scale_method; /* The semantics of the values for scale_method are as specified for the 'fit' attribute of regions in SMIL 1.0. */ + int16_t display_center_x; + int16_t display_center_y; +} lsmash_isom_sample_scale_t; + +/* QuickTime Video CODEC tools */ +typedef enum +{ + QT_COMPRESSION_QUALITY_LOSSLESS = 0x00000400, /* only valid for spatial compression */ + QT_COMPRESSION_QUALITY_MAX = 0x000003FF, + QT_COMPRESSION_QUALITY_MIN = 0x00000000, + QT_COMPRESSION_QUALITY_LOW = 0x00000100, + QT_COMPRESSION_QUALITY_NORMAL = 0x00000200, + QT_COMPRESSION_QUALITY_HIGH = 0x00000300 +} lsmash_qt_compression_quality; + +typedef struct +{ + uint32_t seed; /* Must be set to 0. */ + uint16_t flags; /* Must be set to 0x8000. */ + uint16_t size; /* the number of colors in the following color array + * This is a zero-relative value; + * setting this field to 0 means that there is one color in the array. */ + /* Color array + * An array of colors. Each color is made of four unsigned 16-bit integers. + * We support up to 256 elements. */ + struct + { + uint16_t unused; /* Must be set to 0. */ + /* true color */ + uint16_t r; /* magnitude of red component */ + uint16_t g; /* magnitude of green component */ + uint16_t b; /* magnitude of blue component */ + } array[256]; +} lsmash_qt_color_table_t; + +typedef struct +{ + int16_t revision_level; /* version of the CODEC */ + int32_t vendor; /* whose CODEC */ + lsmash_qt_compression_quality temporalQuality; /* the temporal quality factor (0-1023) */ + lsmash_qt_compression_quality spatialQuality; /* the spatial quality factor (0-1024) */ + uint32_t horizontal_resolution; /* a 16.16 fixed-point number containing the horizontal resolution of the image in pixels per inch. */ + uint32_t vertical_resolution; /* a 16.16 fixed-point number containing the vertical resolution of the image in pixels per inch. */ + uint32_t dataSize; /* if known, the size of data for this descriptor */ + uint16_t frame_count; /* frame per sample */ + int16_t color_table_ID; /* color table ID + * If this field is set to -1, the default color table should be used for the specified depth + * If the color table ID is set to 0, a color table is contained within the sample description itself. + * The color table immediately follows the color table ID field. */ + lsmash_qt_color_table_t color_table; /* a list of preferred colors for displaying the movie on devices that support only 256 colors */ +} lsmash_qt_video_common_t; + +typedef struct +{ + uint32_t level; /* A fixed-point 16.16 number indicating the gamma level at which the image was captured. */ +} lsmash_qt_gamma_t; + +/* Content Light Level Info */ +typedef struct +{ + uint16_t max_content_light_level; + uint16_t max_pic_average_light_level; +} lsmash_qt_content_light_level_info_t; + +/* Mastering Display Color Volume */ +typedef struct +{ + uint16_t display_primaries_g_x; + uint16_t display_primaries_g_y; + uint16_t display_primaries_b_x; + uint16_t display_primaries_b_y; + uint16_t display_primaries_r_x; + uint16_t display_primaries_r_y; + uint16_t white_point_x; + uint16_t white_point_y; + uint32_t max_display_mastering_luminance; + uint32_t min_display_mastering_luminance; +} lsmash_qt_mastering_display_color_volume_t; + +typedef enum +{ + QT_FIELEDS_SCAN_PROGRESSIVE = 1, /* progressive scan */ + QT_FIELEDS_SCAN_INTERLACED = 2, /* 2:1 interlaced */ +} lsmash_qt_number_of_fields; + +/* field ordering for interlaced material */ +typedef enum +{ + QT_FIELD_ORDERINGS_UNKNOWN = 0, + QT_FIELD_ORDERINGS_TEMPORAL_TOP_FIRST = 1, + QT_FIELD_ORDERINGS_TEMPORAL_BOTTOM_FIRST = 6, + QT_FIELD_ORDERINGS_SPATIAL_FIRST_LINE_EARLY = 9, + QT_FIELD_ORDERINGS_SPATIAL_FIRST_LINE_LATE = 14 +} lsmash_qt_field_orderings; + +typedef struct +{ + lsmash_qt_number_of_fields fields; + lsmash_qt_field_orderings detail; +} lsmash_qt_field_info_t; + +/* the native pixel format */ +typedef enum +{ + QT_PIXEL_FORMAT_TYPE_1_MONOCHROME = 0x00000001, /* 1 bit indexed */ + QT_PIXEL_FORMAT_TYPE_2_INDEXED = 0x00000002, /* 2 bit indexed */ + QT_PIXEL_FORMAT_TYPE_4_INDEXED = 0x00000004, /* 4 bit indexed */ + QT_PIXEL_FORMAT_TYPE_8_INDEXED = 0x00000008, /* 8 bit indexed */ + QT_PIXEL_FORMAT_TYPE_1_INDEXED_GRAY_WHITE_IS_ZERO = 0x00000021, /* 1 bit indexed gray, white is zero */ + QT_PIXEL_FORMAT_TYPE_2_INDEXED_GRAY_WHITE_IS_ZERO = 0x00000022, /* 2 bit indexed gray, white is zero */ + QT_PIXEL_FORMAT_TYPE_4_INDEXED_GRAY_WHITE_IS_ZERO = 0x00000024, /* 4 bit indexed gray, white is zero */ + QT_PIXEL_FORMAT_TYPE_8_INDEXED_GRAY_WHITE_IS_ZERO = 0x00000028, /* 8 bit indexed gray, white is zero */ + QT_PIXEL_FORMAT_TYPE_16BE555 = 0x00000010, /* 16 bit BE RGB 555 */ + QT_PIXEL_FORMAT_TYPE_16LE555 = LSMASH_4CC( 'L', '5', '5', '5' ), /* 16 bit LE RGB 555 */ + QT_PIXEL_FORMAT_TYPE_16LE5551 = LSMASH_4CC( '5', '5', '5', '1' ), /* 16 bit LE RGB 5551 */ + QT_PIXEL_FORMAT_TYPE_16BE565 = LSMASH_4CC( 'B', '5', '6', '5' ), /* 16 bit BE RGB 565 */ + QT_PIXEL_FORMAT_TYPE_16LE565 = LSMASH_4CC( 'L', '5', '6', '5' ), /* 16 bit LE RGB 565 */ + QT_PIXEL_FORMAT_TYPE_24RGB = 0x00000018, /* 24 bit RGB */ + QT_PIXEL_FORMAT_TYPE_24BGR = LSMASH_4CC( '2', '4', 'B', 'G' ), /* 24 bit BGR */ + QT_PIXEL_FORMAT_TYPE_32ARGB = 0x00000020, /* 32 bit ARGB */ + QT_PIXEL_FORMAT_TYPE_32BGRA = LSMASH_4CC( 'B', 'G', 'R', 'A' ), /* 32 bit BGRA */ + QT_PIXEL_FORMAT_TYPE_32ABGR = LSMASH_4CC( 'A', 'B', 'G', 'R' ), /* 32 bit ABGR */ + QT_PIXEL_FORMAT_TYPE_32RGBA = LSMASH_4CC( 'R', 'G', 'B', 'A' ), /* 32 bit RGBA */ + QT_PIXEL_FORMAT_TYPE_64ARGB = LSMASH_4CC( 'b', '6', '4', 'a' ), /* 64 bit ARGB, 16-bit big-endian samples */ + QT_PIXEL_FORMAT_TYPE_48RGB = LSMASH_4CC( 'b', '4', '8', 'r' ), /* 48 bit RGB, 16-bit big-endian samples */ + QT_PIXEL_FORMAT_TYPE_32_ALPHA_GRAY = LSMASH_4CC( 'b', '3', '2', 'a' ), /* 32 bit AlphaGray, 16-bit big-endian samples, black is zero */ + QT_PIXEL_FORMAT_TYPE_16_GRAY = LSMASH_4CC( 'b', '1', '6', 'g' ), /* 16 bit Grayscale, 16-bit big-endian samples, black is zero */ + QT_PIXEL_FORMAT_TYPE_30RGB = LSMASH_4CC( 'R', '1', '0', 'k' ), /* 30 bit RGB, 10-bit big-endian samples, 2 unused padding bits (at least significant end) */ + QT_PIXEL_FORMAT_TYPE_422YpCbCr8 = LSMASH_4CC( '2', 'v', 'u', 'y' ), /* Component Y'CbCr 8-bit 4:2:2, ordered Cb Y'0 Cr Y'1 */ + QT_PIXEL_FORMAT_TYPE_4444YpCbCrA8 = LSMASH_4CC( 'v', '4', '0', '8' ), /* Component Y'CbCrA 8-bit 4:4:4:4, ordered Cb Y' Cr A */ + QT_PIXEL_FORMAT_TYPE_4444YpCbCrA8R = LSMASH_4CC( 'r', '4', '0', '8' ), /* Component Y'CbCrA 8-bit 4:4:4:4, rendering format. full range alpha, zero biased YUV, ordered A Y' Cb Cr */ + QT_PIXEL_FORMAT_TYPE_4444AYpCbCr8 = LSMASH_4CC( 'y', '4', '0', '8' ), /* Component Y'CbCrA 8-bit 4:4:4:4, ordered A Y' Cb Cr, full range alpha, video range Y'CbCr */ + QT_PIXEL_FORMAT_TYPE_4444AYpCbCr16 = LSMASH_4CC( 'y', '4', '1', '6' ), /* Component Y'CbCrA 16-bit 4:4:4:4, ordered A Y' Cb Cr, full range alpha, video range Y'CbCr, 16-bit little-endian samples */ + QT_PIXEL_FORMAT_TYPE_444YpCbCr8 = LSMASH_4CC( 'v', '3', '0', '8' ), /* Component Y'CbCr 8-bit 4:4:4 */ + QT_PIXEL_FORMAT_TYPE_422YpCbCr16 = LSMASH_4CC( 'v', '2', '1', '6' ), /* Component Y'CbCr 10,12,14,16-bit 4:2:2 */ + QT_PIXEL_FORMAT_TYPE_422YpCbCr10 = LSMASH_4CC( 'v', '2', '1', '0' ), /* Component Y'CbCr 10-bit 4:2:2 */ + QT_PIXEL_FORMAT_TYPE_444YpCbCr10 = LSMASH_4CC( 'v', '4', '1', '0' ), /* Component Y'CbCr 10-bit 4:4:4 */ + QT_PIXEL_FORMAT_TYPE_420YpCbCr8_PLANAR = LSMASH_4CC( 'y', '4', '2', '0' ), /* Planar Component Y'CbCr 8-bit 4:2:0 */ + QT_PIXEL_FORMAT_TYPE_420YpCbCr8_PLANAR_FULL_RANGE = LSMASH_4CC( 'f', '4', '2', '0' ), /* Planar Component Y'CbCr 8-bit 4:2:0, full range */ + QT_PIXEL_FORMAT_TYPE_422YpCbCr_4A_8_BIPLANAR = LSMASH_4CC( 'a', '2', 'v', 'y' ), /* First plane: Video-range Component Y'CbCr 8-bit 4:2:2, ordered Cb Y'0 Cr Y'1; second plane: alpha 8-bit 0-255 */ + QT_PIXEL_FORMAT_TYPE_420YpCbCr8_BIPLANAR_VIDEO_RANGE = LSMASH_4CC( '4', '2', '0', 'v' ), /* Bi-Planar Component Y'CbCr 8-bit 4:2:0, video-range (luma=[16,235] chroma=[16,240]) */ + QT_PIXEL_FORMAT_TYPE_420YpCbCr8_BIPLANAR_FULL_RANGE = LSMASH_4CC( '4', '2', '0', 'f' ), /* Bi-Planar Component Y'CbCr 8-bit 4:2:0, full-range (luma=[0,255] chroma=[1,255]) */ + QT_PIXEL_FORMAT_TYPE_422YpCbCr8_YUVS = LSMASH_4CC( 'y', 'u', 'v', 's' ), /* Component Y'CbCr 8-bit 4:2:2, ordered Y'0 Cb Y'1 Cr */ + QT_PIXEL_FORMAT_TYPE_422YpCbCr8_FULL_RANGE = LSMASH_4CC( 'y', 'u', 'v', 'f' ), /* Component Y'CbCr 8-bit 4:2:2, full range, ordered Y'0 Cb Y'1 Cr */ + + /* Developer specific FourCCs (from dispatch 20) */ + QT_PIXEL_FORMAT_TYPE_SOFTVOUT_SOFTCODEC = LSMASH_4CC( 's', 'o', 'f', 't' ), /* Intermediary pixel format used by SoftVout and SoftCodec */ + QT_PIXEL_FORMAT_TYPE_VIEW_GRAPHICS = LSMASH_4CC( 'v', 'w', 'g', 'r' ), /* Intermediary pixel format used by View Graphics */ + QT_PIXEL_FORMAT_TYPE_SGI = LSMASH_4CC( 'S', 'G', 'V', 'C' ), /* Intermediary pixel format used by SGI */ +} lsmash_qt_pixel_format; + +typedef struct +{ + lsmash_qt_pixel_format pixel_format; /* the native pixel format of an image */ +} lsmash_qt_pixel_format_t; + +/* Significant Bits Extension + * mandatory extension for 'v216' (Uncompressed Y'CbCr, 10, 12, 14, or 16-bit-per-component 4:2:2) */ +typedef struct +{ + uint8_t significantBits; /* the number of significant bits per component */ +} lsmash_qt_significant_bits_t; + +/* QuickTime Audio CODEC tools */ +typedef enum +{ + QT_AUDIO_COMPRESSION_ID_NOT_COMPRESSED = 0, + QT_AUDIO_COMPRESSION_ID_FIXED_COMPRESSION = -1, + QT_AUDIO_COMPRESSION_ID_VARIABLE_COMPRESSION = -2, + QT_AUDIO_COMPRESSION_ID_TWO_TO_ONE = 1, + QT_AUDIO_COMPRESSION_ID_EIGHT_TO_THREE = 2, + QT_AUDIO_COMPRESSION_ID_THREE_TO_ONE = 3, + QT_AUDIO_COMPRESSION_ID_SIX_TO_ONE = 4, + QT_AUDIO_COMPRESSION_ID_SIX_TO_ONE_PACKET_SIZE = 8, + QT_AUDIO_COMPRESSION_ID_THREE_TO_ONE_PACKET_SIZE = 16, +} lsmash_qt_audio_compression_id; + +typedef struct +{ + int16_t revision_level; /* version of the CODEC */ + int32_t vendor; /* whose CODEC */ + lsmash_qt_audio_compression_id compression_ID; +} lsmash_qt_audio_common_t; + +/* Audio Channel Layout + * This CODEC specific extension is for + * QuickTime Audio inside QuickTime file format + * and + * Apple Lossless Audio inside ISO Base Media file format. + * When audio stream has 3 or more number of channels, this extension shall be present. */ +typedef enum +{ + QT_CHANNEL_LABEL_UNKNOWN = (signed)0xffffffff, /* unknown or unspecified other use */ + QT_CHANNEL_LABEL_UNUSED = 0, /* channel is present, but has no intended use or destination */ + QT_CHANNEL_LABEL_USE_COORDINATES = 100, /* channel is described by the coordinates fields. */ + + QT_CHANNEL_LABEL_LEFT = 1, + QT_CHANNEL_LABEL_RIGHT = 2, + QT_CHANNEL_LABEL_CENTER = 3, + QT_CHANNEL_LABEL_LFE_SCREEN = 4, + QT_CHANNEL_LABEL_LEFT_SURROUND = 5, /* WAVE: "Back Left" */ + QT_CHANNEL_LABEL_RIGHT_SUROUND = 6, /* WAVE: "Back Right" */ + QT_CHANNEL_LABEL_LEFT_CENTER = 7, + QT_CHANNEL_LABEL_RIGHT_CENTER = 8, + QT_CHANNEL_LABEL_CENTER_SURROUND = 9, /* WAVE: "Back Center" or plain "Rear Surround" */ + QT_CHANNEL_LABEL_LEFT_SURROUND_DIRECT = 10, /* WAVE: "Side Left" */ + QT_CHANNEL_LABEL_RIGHT_SURROUND_DIRECT = 11, /* WAVE: "Side Right" */ + QT_CHANNEL_LABEL_TOP_CENTER_SURROUND = 12, + QT_CHANNEL_LABEL_VERTICAL_HEIGHT_LEFT = 13, /* WAVE: "Top Front Left" */ + QT_CHANNEL_LABEL_VERTICAL_HEIGHT_CENTER = 14, /* WAVE: "Top Front Center" */ + QT_CHANNEL_LABEL_VERTICAL_HEIGHT_RIGHT = 15, /* WAVE: "Top Front Right" */ + + QT_CHANNEL_LABEL_TOP_BACK_LEFT = 16, + QT_CHANNEL_LABEL_TOP_BACK_CENTER = 17, + QT_CHANNEL_LABEL_TOP_BACK_RIGHT = 18, + + QT_CHANNEL_LABEL_REAR_SURROUND_LEFT = 33, + QT_CHANNEL_LABEL_REAR_SURROUND_RIGHT = 34, + QT_CHANNEL_LABEL_LEFT_WIDE = 35, + QT_CHANNEL_LABEL_RIGHT_WIDE = 36, + QT_CHANNEL_LABEL_LFE2 = 37, + QT_CHANNEL_LABEL_LEFT_TOTAL = 38, /* matrix encoded 4 channels */ + QT_CHANNEL_LABEL_RIGHT_TOTAL = 39, /* matrix encoded 4 channels */ + QT_CHANNEL_LABEL_HEARING_IMPAIRED = 40, + QT_CHANNEL_LABEL_NARRATION = 41, + QT_CHANNEL_LABEL_MONO = 42, + QT_CHANNEL_LABEL_DIALOG_CENTRIC_MIX = 43, + + QT_CHANNEL_LABEL_CENTER_SURROUND_DIRECT = 44, /* back center, non diffuse */ + + QT_CHANNEL_LABEL_HAPTIC = 45, + + /* first order ambisonic channels */ + QT_CHANNEL_LABEL_AMBISONIC_W = 200, + QT_CHANNEL_LABEL_AMBISONIC_X = 201, + QT_CHANNEL_LABEL_AMBISONIC_Y = 202, + QT_CHANNEL_LABEL_AMBISONIC_Z = 203, + + /* Mid/Side Recording */ + QT_CHANNEL_LABEL_MS_MID = 204, + QT_CHANNEL_LABEL_MS_SIDE = 205, + + /* X-Y Recording */ + QT_CHANNEL_LABEL_XY_X = 206, + QT_CHANNEL_LABEL_XY_Y = 207, + + /* other */ + QT_CHANNEL_LABEL_HEADPHONES_LEFT = 301, + QT_CHANNEL_LABEL_HEADPHONES_RIGHT = 302, + QT_CHANNEL_LABEL_CLICK_TRACK = 304, + QT_CHANNEL_LABEL_FOREIGN_LANGUAGE = 305, + + /* generic discrete channel */ + QT_CHANNEL_LABEL_DISCRETE = 400, + + /* numbered discrete channel */ + QT_CHANNEL_LABEL_DISCRETE_0 = (1<<16), + QT_CHANNEL_LABEL_DISCRETE_1 = (1<<16) | 1, + QT_CHANNEL_LABEL_DISCRETE_2 = (1<<16) | 2, + QT_CHANNEL_LABEL_DISCRETE_3 = (1<<16) | 3, + QT_CHANNEL_LABEL_DISCRETE_4 = (1<<16) | 4, + QT_CHANNEL_LABEL_DISCRETE_5 = (1<<16) | 5, + QT_CHANNEL_LABEL_DISCRETE_6 = (1<<16) | 6, + QT_CHANNEL_LABEL_DISCRETE_7 = (1<<16) | 7, + QT_CHANNEL_LABEL_DISCRETE_8 = (1<<16) | 8, + QT_CHANNEL_LABEL_DISCRETE_9 = (1<<16) | 9, + QT_CHANNEL_LABEL_DISCRETE_10 = (1<<16) | 10, + QT_CHANNEL_LABEL_DISCRETE_11 = (1<<16) | 11, + QT_CHANNEL_LABEL_DISCRETE_12 = (1<<16) | 12, + QT_CHANNEL_LABEL_DISCRETE_13 = (1<<16) | 13, + QT_CHANNEL_LABEL_DISCRETE_14 = (1<<16) | 14, + QT_CHANNEL_LABEL_DISCRETE_15 = (1<<16) | 15, + QT_CHANNEL_LABEL_DISCRETE_65535 = (1<<16) | 65535, +} lsmash_channel_label; + +typedef enum +{ + QT_CHANNEL_BIT_LEFT = 1, + QT_CHANNEL_BIT_RIGHT = 1<<1, + QT_CHANNEL_BIT_CENTER = 1<<2, + QT_CHANNEL_BIT_LFE_SCREEN = 1<<3, + QT_CHANNEL_BIT_LEFT_SURROUND = 1<<4, /* WAVE: "Back Left" */ + QT_CHANNEL_BIT_RIGHT_SURROUND = 1<<5, /* WAVE: "Back Right" */ + QT_CHANNEL_BIT_LEFT_CENTER = 1<<6, + QT_CHANNEL_BIT_RIGHT_CENTER = 1<<7, + QT_CHANNEL_BIT_CENTER_SURROUND = 1<<8, /* WAVE: "Back Center" */ + QT_CHANNEL_BIT_LEFT_SURROUND_DIRECT = 1<<9, /* WAVE: "Side Left" */ + QT_CHANNEL_BIT_RIGHT_SURROUND_DIRECT = 1<<10, /* WAVE: "Side Right" */ + QT_CHANNEL_BIT_TOP_CENTER_SURROUND = 1<<11, + QT_CHANNEL_BIT_VERTICAL_HEIGHT_LEFT = 1<<12, /* WAVE: "Top Front Left" */ + QT_CHANNEL_BIT_VERTICAL_HEIGHT_CENTER = 1<<13, /* WAVE: "Top Front Center" */ + QT_CHANNEL_BIT_VERTICAL_HEIGHT_RIGHT = 1<<14, /* WAVE: "Top Front Right" */ + QT_CHANNEL_BIT_TOP_BACK_LEFT = 1<<15, + QT_CHANNEL_BIT_TOP_BACK_CENTER = 1<<16, + QT_CHANNEL_BIT_TOP_BACK_RIGHT = 1<<17, + QT_CHANNEL_BIT_FULL = 0x3ffff, +} lsmash_channel_bitmap; + +typedef enum +{ + QT_CHANNEL_FLAGS_ALL_OFF = 0, + QT_CHANNEL_FLAGS_RECTANGULAR_COORDINATES = 1, + QT_CHANNEL_FLAGS_SPHERICAL_COORDINATES = 1<<1, + QT_CHANNEL_FLAGS_METERS = 1<<2, +} lsmash_channel_flags; + +typedef enum +{ + /* indices for accessing the coordinates array in Channel Descriptions */ + /* for rectangulare coordinates */ + QT_CHANNEL_COORDINATES_LEFT_RIGHT = 0, /* Negative is left and positive is right. */ + QT_CHANNEL_COORDINATES_BACK_FRONT = 1, /* Negative is back and positive is front. */ + QT_CHANNEL_COORDINATES_DOWN_UP = 2, /* Negative is below ground level, 0 is ground level, and positive is above ground level. */ + /* for spherical coordinates */ + QT_CHANNEL_COORDINATES_AZIMUTH = 0, /* 0 is front center, positive is right, negative is left. This is measured in degrees. */ + QT_CHANNEL_COORDINATES_ELEVATION = 1, /* +90 is zenith, 0 is horizontal, -90 is nadir. This is measured in degrees. */ + QT_CHANNEL_COORDINATES_DISTANCE = 2, /* The units are described by flags. */ +} lsmash_channel_coordinates_index; + +typedef enum +{ + /* channel abbreviations: + * L - left + * R - right + * C - center + * Ls - left surround + * Rs - right surround + * Cs - center surround + * Rls - rear left surround + * Rrs - rear right surround + * Lw - left wide + * Rw - right wide + * Lsd - left surround direct + * Rsd - right surround direct + * Lc - left center + * Rc - right center + * Ts - top surround + * Vhl - vertical height left + * Vhc - vertical height center + * Vhr - vertical height right + * Lt - left matrix total. for matrix encoded stereo. + * Rt - right matrix total. for matrix encoded stereo. */ + + /* General layouts */ + QT_CHANNEL_LAYOUT_USE_CHANNEL_DESCRIPTIONS = 0, /* use the array of Channel Descriptions to define the mapping. */ + QT_CHANNEL_LAYOUT_USE_CHANNEL_BITMAP = 1<<16, /* use the bitmap to define the mapping. */ + + QT_CHANNEL_LAYOUT_MONO = (100<<16) | 1, /* a standard mono stream */ + QT_CHANNEL_LAYOUT_STEREO = (101<<16) | 2, /* a standard stereo stream (L R) - implied playback */ + QT_CHANNEL_LAYOUT_STEREO_HEADPHONES = (102<<16) | 2, /* a standard stereo stream (L R) - implied headphone playback */ + QT_CHANNEL_LAYOUT_MATRIX_STEREO = (103<<16) | 2, /* a matrix encoded stereo stream (Lt, Rt) */ + QT_CHANNEL_LAYOUT_MID_SIDE = (104<<16) | 2, /* mid/side recording */ + QT_CHANNEL_LAYOUT_XY = (105<<16) | 2, /* coincident mic pair (often 2 figure 8's) */ + QT_CHANNEL_LAYOUT_BINAURAL = (106<<16) | 2, /* binaural stereo (left, right) */ + QT_CHANNEL_LAYOUT_AMBISONIC_B_FORMAT = (107<<16) | 4, /* W, X, Y, Z */ + + QT_CHANNEL_LAYOUT_QUADRAPHONIC = (108<<16) | 4, /* front left, front right, back left, back right */ + + QT_CHANNEL_LAYOUT_PENTAGONAL = (109<<16) | 5, /* left, right, rear left, rear right, center */ + + QT_CHANNEL_LAYOUT_HEXAGONAL = (110<<16) | 6, /* left, right, rear left, rear right, center, rear */ + + QT_CHANNEL_LAYOUT_OCTAGONAL = (111<<16) | 8, /* front left, front right, rear left, rear right, + * front center, rear center, side left, side right */ + + QT_CHANNEL_LAYOUT_CUBE = (112<<16) | 8, /* left, right, rear left, rear right, + * top left, top right, top rear left, top rear right */ + + /* MPEG defined layouts */ + QT_CHANNEL_LAYOUT_MPEG_1_0 = QT_CHANNEL_LAYOUT_MONO, /* C */ + QT_CHANNEL_LAYOUT_MPEG_2_0 = QT_CHANNEL_LAYOUT_STEREO, /* L R */ + QT_CHANNEL_LAYOUT_MPEG_3_0_A = (113<<16) | 3, /* L R C */ + QT_CHANNEL_LAYOUT_MPEG_3_0_B = (114<<16) | 3, /* C L R */ + QT_CHANNEL_LAYOUT_MPEG_4_0_A = (115<<16) | 4, /* L R C Cs */ + QT_CHANNEL_LAYOUT_MPEG_4_0_B = (116<<16) | 4, /* C L R Cs */ + QT_CHANNEL_LAYOUT_MPEG_5_0_A = (117<<16) | 5, /* L R C Ls Rs */ + QT_CHANNEL_LAYOUT_MPEG_5_0_B = (118<<16) | 5, /* L R Ls Rs C */ + QT_CHANNEL_LAYOUT_MPEG_5_0_C = (119<<16) | 5, /* L C R Ls Rs */ + QT_CHANNEL_LAYOUT_MPEG_5_0_D = (120<<16) | 5, /* C L R Ls Rs */ + QT_CHANNEL_LAYOUT_MPEG_5_1_A = (121<<16) | 6, /* L R C LFE Ls Rs */ + QT_CHANNEL_LAYOUT_MPEG_5_1_B = (122<<16) | 6, /* L R Ls Rs C LFE */ + QT_CHANNEL_LAYOUT_MPEG_5_1_C = (123<<16) | 6, /* L C R Ls Rs LFE */ + QT_CHANNEL_LAYOUT_MPEG_5_1_D = (124<<16) | 6, /* C L R Ls Rs LFE */ + QT_CHANNEL_LAYOUT_MPEG_6_1_A = (125<<16) | 7, /* L R C LFE Ls Rs Cs */ + QT_CHANNEL_LAYOUT_MPEG_7_1_A = (126<<16) | 8, /* L R C LFE Ls Rs Lc Rc */ + QT_CHANNEL_LAYOUT_MPEG_7_1_B = (127<<16) | 8, /* C Lc Rc L R Ls Rs LFE (doc: IS-13818-7 MPEG2-AAC Table 3.1) */ + QT_CHANNEL_LAYOUT_MPEG_7_1_C = (128<<16) | 8, /* L R C LFE Ls Rs Rls Rrs */ + QT_CHANNEL_LAYOUT_EMAGIC_DEFAULT_7_1 = (129<<16) | 8, /* L R Ls Rs C LFE Lc Rc */ + QT_CHANNEL_LAYOUT_SMPTE_DTV = (130<<16) | 8, /* L R C LFE Ls Rs Lt Rt */ + + /* ITU defined layouts */ + QT_CHANNEL_LAYOUT_ITU_1_0 = QT_CHANNEL_LAYOUT_MONO, /* C */ + QT_CHANNEL_LAYOUT_ITU_2_0 = QT_CHANNEL_LAYOUT_STEREO, /* L R */ + + QT_CHANNEL_LAYOUT_ITU_2_1 = (131<<16) | 3, /* L R Cs */ + QT_CHANNEL_LAYOUT_ITU_2_2 = (132<<16) | 4, /* L R Ls Rs */ + QT_CHANNEL_LAYOUT_ITU_3_0 = QT_CHANNEL_LAYOUT_MPEG_3_0_A, /* L R C */ + QT_CHANNEL_LAYOUT_ITU_3_1 = QT_CHANNEL_LAYOUT_MPEG_4_0_A, /* L R C Cs */ + + QT_CHANNEL_LAYOUT_ITU_3_2 = QT_CHANNEL_LAYOUT_MPEG_5_0_A, /* L R C Ls Rs */ + QT_CHANNEL_LAYOUT_ITU_3_2_1 = QT_CHANNEL_LAYOUT_MPEG_5_1_A, /* L R C LFE Ls Rs */ + QT_CHANNEL_LAYOUT_ITU_3_4_1 = QT_CHANNEL_LAYOUT_MPEG_7_1_C, /* L R C LFE Ls Rs Rls Rrs */ + + /* DVD defined layouts */ + QT_CHANNEL_LAYOUT_DVD_0 = QT_CHANNEL_LAYOUT_MONO, /* C (mono) */ + QT_CHANNEL_LAYOUT_DVD_1 = QT_CHANNEL_LAYOUT_STEREO, /* L R */ + QT_CHANNEL_LAYOUT_DVD_2 = QT_CHANNEL_LAYOUT_ITU_2_1, /* L R Cs */ + QT_CHANNEL_LAYOUT_DVD_3 = QT_CHANNEL_LAYOUT_ITU_2_2, /* L R Ls Rs */ + QT_CHANNEL_LAYOUT_DVD_4 = (133<<16) | 3, /* L R LFE */ + QT_CHANNEL_LAYOUT_DVD_5 = (134<<16) | 4, /* L R LFE Cs */ + QT_CHANNEL_LAYOUT_DVD_6 = (135<<16) | 5, /* L R LFE Ls Rs */ + QT_CHANNEL_LAYOUT_DVD_7 = QT_CHANNEL_LAYOUT_MPEG_3_0_A, /* L R C */ + QT_CHANNEL_LAYOUT_DVD_8 = QT_CHANNEL_LAYOUT_MPEG_4_0_A, /* L R C Cs */ + QT_CHANNEL_LAYOUT_DVD_9 = QT_CHANNEL_LAYOUT_MPEG_5_0_A, /* L R C Ls Rs */ + QT_CHANNEL_LAYOUT_DVD_10 = (136<<16) | 4, /* L R C LFE */ + QT_CHANNEL_LAYOUT_DVD_11 = (137<<16) | 5, /* L R C LFE Cs */ + QT_CHANNEL_LAYOUT_DVD_12 = QT_CHANNEL_LAYOUT_MPEG_5_1_A, /* L R C LFE Ls Rs */ + /* 13 through 17 are duplicates of 8 through 12. */ + QT_CHANNEL_LAYOUT_DVD_13 = QT_CHANNEL_LAYOUT_DVD_8, /* L R C Cs */ + QT_CHANNEL_LAYOUT_DVD_14 = QT_CHANNEL_LAYOUT_DVD_9, /* L R C Ls Rs */ + QT_CHANNEL_LAYOUT_DVD_15 = QT_CHANNEL_LAYOUT_DVD_10, /* L R C LFE */ + QT_CHANNEL_LAYOUT_DVD_16 = QT_CHANNEL_LAYOUT_DVD_11, /* L R C LFE Cs */ + QT_CHANNEL_LAYOUT_DVD_17 = QT_CHANNEL_LAYOUT_DVD_12, /* L R C LFE Ls Rs */ + QT_CHANNEL_LAYOUT_DVD_18 = (138<<16) | 5, /* L R Ls Rs LFE */ + QT_CHANNEL_LAYOUT_DVD_19 = QT_CHANNEL_LAYOUT_MPEG_5_0_B, /* L R Ls Rs C */ + QT_CHANNEL_LAYOUT_DVD_20 = QT_CHANNEL_LAYOUT_MPEG_5_1_B, /* L R Ls Rs C LFE */ + + /* These are the symmetrical layouts. */ + QT_CHANNEL_LAYOUT_AUDIO_UNIT_4 = QT_CHANNEL_LAYOUT_QUADRAPHONIC, + QT_CHANNEL_LAYOUT_AUDIO_UNIT_5 = QT_CHANNEL_LAYOUT_PENTAGONAL, + QT_CHANNEL_LAYOUT_AUDIO_UNIT_6 = QT_CHANNEL_LAYOUT_HEXAGONAL, + QT_CHANNEL_LAYOUT_AUDIO_UNIT_8 = QT_CHANNEL_LAYOUT_OCTAGONAL, + /* These are the surround-based layouts. */ + QT_CHANNEL_LAYOUT_AUDIO_UNIT_5_0 = QT_CHANNEL_LAYOUT_MPEG_5_0_B, /* L R Ls Rs C */ + QT_CHANNEL_LAYOUT_AUDIO_UNIT_6_0 = (139<<16) | 6, /* L R Ls Rs C Cs */ + QT_CHANNEL_LAYOUT_AUDIO_UNIT_7_0 = (140<<16) | 7, /* L R Ls Rs C Rls Rrs */ + QT_CHANNEL_LAYOUT_AUDIO_UNIT_7_0_FRONT = (148<<16) | 7, /* L R Ls Rs C Lc Rc */ + QT_CHANNEL_LAYOUT_AUDIO_UNIT_5_1 = QT_CHANNEL_LAYOUT_MPEG_5_1_A, /* L R C LFE Ls Rs */ + QT_CHANNEL_LAYOUT_AUDIO_UNIT_6_1 = QT_CHANNEL_LAYOUT_MPEG_6_1_A, /* L R C LFE Ls Rs Cs */ + QT_CHANNEL_LAYOUT_AUDIO_UNIT_7_1 = QT_CHANNEL_LAYOUT_MPEG_7_1_C, /* L R C LFE Ls Rs Rls Rrs */ + QT_CHANNEL_LAYOUT_AUDIO_UNIT_7_1_FRONT = QT_CHANNEL_LAYOUT_MPEG_7_1_A, /* L R C LFE Ls Rs Lc Rc */ + + QT_CHANNEL_LAYOUT_AAC_3_0 = QT_CHANNEL_LAYOUT_MPEG_3_0_B, /* C L R */ + QT_CHANNEL_LAYOUT_AAC_QUADRAPHONIC = QT_CHANNEL_LAYOUT_QUADRAPHONIC, /* L R Ls Rs */ + QT_CHANNEL_LAYOUT_AAC_4_0 = QT_CHANNEL_LAYOUT_MPEG_4_0_B, /* C L R Cs */ + QT_CHANNEL_LAYOUT_AAC_5_0 = QT_CHANNEL_LAYOUT_MPEG_5_0_D, /* C L R Ls Rs */ + QT_CHANNEL_LAYOUT_AAC_5_1 = QT_CHANNEL_LAYOUT_MPEG_5_1_D, /* C L R Ls Rs LFE */ + QT_CHANNEL_LAYOUT_AAC_6_0 = (141<<16) | 6, /* C L R Ls Rs Cs */ + QT_CHANNEL_LAYOUT_AAC_6_1 = (142<<16) | 7, /* C L R Ls Rs Cs LFE */ + QT_CHANNEL_LAYOUT_AAC_7_0 = (143<<16) | 7, /* C L R Ls Rs Rls Rrs */ + QT_CHANNEL_LAYOUT_AAC_7_1 = QT_CHANNEL_LAYOUT_MPEG_7_1_B, /* C Lc Rc L R Ls Rs LFE */ + QT_CHANNEL_LAYOUT_AAC_OCTAGONAL = (144<<16) | 8, /* C L R Ls Rs Rls Rrs Cs */ + + QT_CHANNEL_LAYOUT_TMH_10_2_STD = (145<<16) | 16, /* L R C Vhc Lsd Rsd Ls Rs Vhl Vhr Lw Rw Csd Cs LFE1 LFE2 */ + QT_CHANNEL_LAYOUT_TMH_10_2_FULL = (146<<16) | 21, /* TMH_10_2_std plus: Lc Rc HI VI Haptic */ + + QT_CHANNEL_LAYOUT_AC3_1_0_1 = (149<<16) | 2, /* C LFE */ + QT_CHANNEL_LAYOUT_AC3_3_0 = (150<<16) | 3, /* L C R */ + QT_CHANNEL_LAYOUT_AC3_3_1 = (151<<16) | 4, /* L C R Cs */ + QT_CHANNEL_LAYOUT_AC3_3_0_1 = (152<<16) | 4, /* L C R LFE */ + QT_CHANNEL_LAYOUT_AC3_2_1_1 = (153<<16) | 4, /* L R Cs LFE */ + QT_CHANNEL_LAYOUT_AC3_3_1_1 = (154<<16) | 5, /* L C R Cs LFE */ + + QT_CHANNEL_LAYOUT_EAC_6_0_A = (155<<16) | 6, /* L C R Ls Rs Cs */ + QT_CHANNEL_LAYOUT_EAC_7_0_A = (156<<16) | 7, /* L C R Ls Rs Rls Rrs */ + + QT_CHANNEL_LAYOUT_EAC3_6_1_A = (157<<16) | 7, /* L C R Ls Rs LFE Cs */ + QT_CHANNEL_LAYOUT_EAC3_6_1_B = (158<<16) | 7, /* L C R Ls Rs LFE Ts */ + QT_CHANNEL_LAYOUT_EAC3_6_1_C = (159<<16) | 7, /* L C R Ls Rs LFE Vhc */ + QT_CHANNEL_LAYOUT_EAC3_7_1_A = (160<<16) | 8, /* L C R Ls Rs LFE Rls Rrs */ + QT_CHANNEL_LAYOUT_EAC3_7_1_B = (161<<16) | 8, /* L C R Ls Rs LFE Lc Rc */ + QT_CHANNEL_LAYOUT_EAC3_7_1_C = (162<<16) | 8, /* L C R Ls Rs LFE Lsd Rsd */ + QT_CHANNEL_LAYOUT_EAC3_7_1_D = (163<<16) | 8, /* L C R Ls Rs LFE Lw Rw */ + QT_CHANNEL_LAYOUT_EAC3_7_1_E = (164<<16) | 8, /* L C R Ls Rs LFE Vhl Vhr */ + + QT_CHANNEL_LAYOUT_EAC3_7_1_F = (165<<16) | 8, /* L C R Ls Rs LFE Cs Ts */ + QT_CHANNEL_LAYOUT_EAC3_7_1_G = (166<<16) | 8, /* L C R Ls Rs LFE Cs Vhc */ + QT_CHANNEL_LAYOUT_EAC3_7_1_H = (167<<16) | 8, /* L C R Ls Rs LFE Ts Vhc */ + + QT_CHANNEL_LAYOUT_DTS_3_1 = (168<<16) | 4, /* C L R LFE */ + QT_CHANNEL_LAYOUT_DTS_4_1 = (169<<16) | 5, /* C L R Cs LFE */ + QT_CHANNEL_LAYOUT_DTS_6_0_A = (170<<16) | 6, /* Lc Rc L R Ls Rs */ + QT_CHANNEL_LAYOUT_DTS_6_0_B = (171<<16) | 6, /* C L R Rls Rrs Ts */ + QT_CHANNEL_LAYOUT_DTS_6_0_C = (172<<16) | 6, /* C Cs L R Rls Rrs */ + QT_CHANNEL_LAYOUT_DTS_6_1_A = (173<<16) | 7, /* Lc Rc L R Ls Rs LFE */ + QT_CHANNEL_LAYOUT_DTS_6_1_B = (174<<16) | 7, /* C L R Rls Rrs Ts LFE */ + QT_CHANNEL_LAYOUT_DTS_6_1_C = (175<<16) | 7, /* C Cs L R Rls Rrs LFE */ + QT_CHANNEL_LAYOUT_DTS_7_0 = (176<<16) | 7, /* Lc C Rc L R Ls Rs */ + QT_CHANNEL_LAYOUT_DTS_7_1 = (177<<16) | 8, /* Lc C Rc L R Ls Rs LFE */ + QT_CHANNEL_LAYOUT_DTS_8_0_A = (178<<16) | 8, /* Lc Rc L R Ls Rs Rls Rrs */ + QT_CHANNEL_LAYOUT_DTS_8_0_B = (179<<16) | 8, /* Lc C Rc L R Ls Cs Rs */ + QT_CHANNEL_LAYOUT_DTS_8_1_A = (180<<16) | 9, /* Lc Rc L R Ls Rs Rls Rrs LFE */ + QT_CHANNEL_LAYOUT_DTS_8_1_B = (181<<16) | 9, /* Lc C Rc L R Ls Cs Rs LFE */ + QT_CHANNEL_LAYOUT_DTS_6_1_D = (182<<16) | 7, /* C L R Ls Rs LFE Cs */ + + QT_CHANNEL_LAYOUT_ALAC_MONO = QT_CHANNEL_LAYOUT_MONO, /* C */ + QT_CHANNEL_LAYOUT_ALAC_STEREO = QT_CHANNEL_LAYOUT_STEREO, /* L R */ + QT_CHANNEL_LAYOUT_ALAC_3_0 = QT_CHANNEL_LAYOUT_MPEG_3_0_B, /* C L R */ + QT_CHANNEL_LAYOUT_ALAC_4_0 = QT_CHANNEL_LAYOUT_MPEG_4_0_B, /* C L R Cs */ + QT_CHANNEL_LAYOUT_ALAC_5_0 = QT_CHANNEL_LAYOUT_MPEG_5_0_D, /* C L R Ls Rs */ + QT_CHANNEL_LAYOUT_ALAC_5_1 = QT_CHANNEL_LAYOUT_MPEG_5_1_D, /* C L R Ls Rs LFE */ + QT_CHANNEL_LAYOUT_ALAC_6_1 = QT_CHANNEL_LAYOUT_AAC_6_1, /* C L R Ls Rs Cs LFE */ + QT_CHANNEL_LAYOUT_ALAC_7_1 = QT_CHANNEL_LAYOUT_MPEG_7_1_B, /* C Lc Rc L R Ls Rs LFE */ + + QT_CHANNEL_LAYOUT_DISCRETE_IN_ORDER = 147<<16, /* needs to be ORed with the actual number of channels */ + QT_CHANNEL_LAYOUT_UNKNOWN = (signed)0xffff0000, /* needs to be ORed with the actual number of channels */ +} lsmash_channel_layout_tag; + +typedef struct +{ + lsmash_channel_layout_tag channelLayoutTag; /* channel layout */ + lsmash_channel_bitmap channelBitmap; /* Only available when layout_tag is set to QT_CHANNEL_LAYOUT_USE_CHANNEL_BITMAP. */ +} lsmash_qt_audio_channel_layout_t; + +/* QuickTime Audio Format Specific Flags + * Some values are ignored i.e. as if treated as unspecified when you specify certain CODECs. + * For instance, you specify QT_CODEC_TYPE_SOWT_AUDIO, then all these values are ignored. + * These values are basically used for QT_CODEC_TYPE_LPCM_AUDIO. + * The endiannes value can be used for QT_CODEC_TYPE_FL32_AUDIO, QT_CODEC_TYPE_FL64_AUDIO, QT_CODEC_TYPE_IN24_AUDIO and QT_CODEC_TYPE_IN32_AUDIO. */ +typedef enum +{ + QT_AUDIO_FORMAT_FLAG_FLOAT = 1, /* Set for floating point, clear for integer. */ + QT_AUDIO_FORMAT_FLAG_BIG_ENDIAN = 1<<1, /* Set for big endian, clear for little endian. */ + QT_AUDIO_FORMAT_FLAG_SIGNED_INTEGER = 1<<2, /* Set for signed integer, clear for unsigned integer. + * This is only valid if QT_AUDIO_FORMAT_FLAG_FLOAT is clear. */ + QT_AUDIO_FORMAT_FLAG_PACKED = 1<<3, /* Set if the sample bits occupy the entire available bits for the channel, + * clear if they are high or low aligned within the channel. */ + QT_AUDIO_FORMAT_FLAG_ALIGNED_HIGH = 1<<4, /* Set if the sample bits are placed into the high bits of the channel, clear for low bit placement. + * This is only valid if QT_AUDIO_FORMAT_FLAG_PACKED is clear. */ + QT_AUDIO_FORMAT_FLAG_NON_INTERLEAVED = 1<<5, /* Set if the samples for each channel are located contiguously and the channels are layed out end to end, + * clear if the samples for each frame are layed out contiguously and the frames layed out end to end. */ + QT_AUDIO_FORMAT_FLAG_NON_MIXABLE = 1<<6, /* Set to indicate when a format is non-mixable. + * Note that this flag is only used when interacting with the HAL's stream format information. + * It is not a valid flag for any other uses. */ + QT_AUDIO_FORMAT_FLAG_ALL_CLEAR = (int)(1u << 31), /* Set if all the flags would be clear in order to preserve 0 as the wild card value. */ + + QT_LPCM_FORMAT_FLAG_FLOAT = QT_AUDIO_FORMAT_FLAG_FLOAT, + QT_LPCM_FORMAT_FLAG_BIG_ENDIAN = QT_AUDIO_FORMAT_FLAG_BIG_ENDIAN, + QT_LPCM_FORMAT_FLAG_SIGNED_INTEGER = QT_AUDIO_FORMAT_FLAG_SIGNED_INTEGER, + QT_LPCM_FORMAT_FLAG_PACKED = QT_AUDIO_FORMAT_FLAG_PACKED, + QT_LPCM_FORMAT_FLAG_ALIGNED_HIGH = QT_AUDIO_FORMAT_FLAG_ALIGNED_HIGH, + QT_LPCM_FORMAT_FLAG_NON_INTERLEAVED = QT_AUDIO_FORMAT_FLAG_NON_INTERLEAVED, + QT_LPCM_FORMAT_FLAG_NON_MIXABLE = QT_AUDIO_FORMAT_FLAG_NON_MIXABLE, + QT_LPCM_FORMAT_FLAG_ALL_CLEAR = QT_AUDIO_FORMAT_FLAG_ALL_CLEAR, + + /* These flags are set for Apple Lossless data that was sourced from N bit native endian signed integer data. */ + QT_ALAC_FORMAT_FLAG_16BIT_SOURCE_DATA = 1, + QT_ALAC_FORMAT_FLAG_20BIT_SOURCE_DATA = 2, + QT_ALAC_FORMAT_FLAG_24BIT_SOURCE_DATA = 3, + QT_ALAC_FORMAT_FLAG_32BIT_SOURCE_DATA = 4, +} lsmash_qt_audio_format_specific_flag; + +typedef struct +{ + lsmash_qt_audio_format_specific_flag format_flags; +} lsmash_qt_audio_format_specific_flags_t; + +/* Global Header + * Ut Video inside QuickTime file format requires this extension for storing CODEC specific information. */ +typedef struct +{ + uint32_t header_size; + uint8_t *header_data; +} lsmash_codec_global_header_t; + +/**************************************************************************** + * iTunes Metadata + ****************************************************************************/ +typedef enum +{ + /* UTF String type */ + ITUNES_METADATA_ITEM_ALBUM_NAME = (int)LSMASH_4CC( 0xA9u, 'a', 'l', 'b' ), /* Album Name */ + ITUNES_METADATA_ITEM_ARTIST = (int)LSMASH_4CC( 0xA9u, 'A', 'R', 'T' ), /* Artist */ + ITUNES_METADATA_ITEM_USER_COMMENT = (int)LSMASH_4CC( 0xA9u, 'c', 'm', 't' ), /* User Comment */ + ITUNES_METADATA_ITEM_RELEASE_DATE = (int)LSMASH_4CC( 0xA9u, 'd', 'a', 'y' ), /* YYYY-MM-DD format string (may be incomplete, i.e. only year) */ + ITUNES_METADATA_ITEM_ENCODED_BY = (int)LSMASH_4CC( 0xA9u, 'e', 'n', 'c' ), /* Person or company that encoded the recording */ + ITUNES_METADATA_ITEM_USER_GENRE = (int)LSMASH_4CC( 0xA9u, 'g', 'e', 'n' ), /* User Genre user-specified string */ + ITUNES_METADATA_ITEM_GROUPING = (int)LSMASH_4CC( 0xA9u, 'g', 'r', 'p' ), /* Grouping */ + ITUNES_METADATA_ITEM_LYRICS = (int)LSMASH_4CC( 0xA9u, 'l', 'y', 'r' ), /* Lyrics */ + ITUNES_METADATA_ITEM_TITLE = (int)LSMASH_4CC( 0xA9u, 'n', 'a', 'm' ), /* Title / Song Name */ + ITUNES_METADATA_ITEM_TRACK_SUBTITLE = (int)LSMASH_4CC( 0xA9u, 's', 't', '3' ), /* Track Sub-Title */ + ITUNES_METADATA_ITEM_ENCODING_TOOL = (int)LSMASH_4CC( 0xA9u, 't', 'o', 'o' ), /* Software which encoded the recording */ + ITUNES_METADATA_ITEM_COMPOSER = (int)LSMASH_4CC( 0xA9u, 'w', 'r', 't' ), /* Composer */ + ITUNES_METADATA_ITEM_ALBUM_ARTIST = LSMASH_4CC( 'a', 'A', 'R', 'T' ), /* Artist for the whole album (if different than the individual tracks) */ + ITUNES_METADATA_ITEM_PODCAST_CATEGORY = LSMASH_4CC( 'c', 'a', 't', 'g' ), /* Podcast Category */ + ITUNES_METADATA_ITEM_COPYRIGHT = LSMASH_4CC( 'c', 'p', 'r', 't' ), /* Copyright */ + ITUNES_METADATA_ITEM_DESCRIPTION = LSMASH_4CC( 'd', 'e', 's', 'c' ), /* Description (limited to 255 bytes) */ + ITUNES_METADATA_ITEM_GROUPING_DRAFT = LSMASH_4CC( 'g', 'r', 'u', 'p' ), /* Grouping + * Note: This identifier is defined in + * iTunes Metadata Format Specification (Preliminary draft), + * but not used by iTunes actually it seems. + * We recommend you use ITUNES_METADATA_ITEM_GROUPING instead of this. */ + ITUNES_METADATA_ITEM_PODCAST_KEYWORD = LSMASH_4CC( 'k', 'e', 'y', 'w' ), /* Podcast Keywords */ + ITUNES_METADATA_ITEM_LONG_DESCRIPTION = LSMASH_4CC( 'l', 'd', 'e', 's' ), /* Long Description */ + ITUNES_METADATA_ITEM_PURCHASE_DATE = LSMASH_4CC( 'p', 'u', 'r', 'd' ), /* Purchase Date */ + ITUNES_METADATA_ITEM_TV_EPISODE_ID = LSMASH_4CC( 't', 'v', 'e', 'n' ), /* TV Episode ID */ + ITUNES_METADATA_ITEM_TV_NETWORK = LSMASH_4CC( 't', 'v', 'n', 'n' ), /* TV Network Name */ + ITUNES_METADATA_ITEM_TV_SHOW_NAME = LSMASH_4CC( 't', 'v', 's', 'h' ), /* TV Show Name */ + ITUNES_METADATA_ITEM_ITUNES_PURCHASE_ACCOUNT_ID = LSMASH_4CC( 'a', 'p', 'I', 'D' ), /* iTunes Account Used for Purchase */ + ITUNES_METADATA_ITEM_ITUNES_SORT_ALBUM = LSMASH_4CC( 's', 'o', 'a', 'l' ), /* Sort Album */ + ITUNES_METADATA_ITEM_ITUNES_SORT_ARTIST = LSMASH_4CC( 's', 'o', 'a', 'r' ), /* Sort Artist */ + ITUNES_METADATA_ITEM_ITUNES_SORT_ALBUM_ARTIST = LSMASH_4CC( 's', 'o', 'a', 'a' ), /* Sort Album Artist */ + ITUNES_METADATA_ITEM_ITUNES_SORT_COMPOSER = LSMASH_4CC( 's', 'o', 'c', 'o' ), /* Sort Composer */ + ITUNES_METADATA_ITEM_ITUNES_SORT_NAME = LSMASH_4CC( 's', 'o', 'n', 'm' ), /* Sort Name */ + ITUNES_METADATA_ITEM_ITUNES_SORT_SHOW = LSMASH_4CC( 's', 'o', 's', 'n' ), /* Sort Show */ + + /* Integer type + * (X): X means length of bytes */ + ITUNES_METADATA_ITEM_EPISODE_GLOBAL_ID = LSMASH_4CC( 'e', 'g', 'i', 'd' ), /* (1) Episode Global Unique ID */ + ITUNES_METADATA_ITEM_PREDEFINED_GENRE = LSMASH_4CC( 'g', 'n', 'r', 'e' ), /* (2) Pre-defined Genre / Enumerated value from ID3 tag set, plus 1 */ + ITUNES_METADATA_ITEM_PODCAST_URL = LSMASH_4CC( 'p', 'u', 'r', 'l' ), /* (?) Podcast URL */ + ITUNES_METADATA_ITEM_CONTENT_RATING = LSMASH_4CC( 'r', 't', 'n', 'g' ), /* (1) Content Rating / Does song have explicit content? 0: none, 2: clean, 4: explicit */ + ITUNES_METADATA_ITEM_MEDIA_TYPE = LSMASH_4CC( 's', 't', 'i', 'k' ), /* (1) Media Type */ + ITUNES_METADATA_ITEM_BEATS_PER_MINUTE = LSMASH_4CC( 't', 'm', 'p', 'o' ), /* (2) Beats Per Minute */ + ITUNES_METADATA_ITEM_TV_EPISODE = LSMASH_4CC( 't', 'v', 'e', 's' ), /* (4) TV Episode */ + ITUNES_METADATA_ITEM_TV_SEASON = LSMASH_4CC( 't', 'v', 's', 'n' ), /* (4) TV Season */ + ITUNES_METADATA_ITEM_ITUNES_ACCOUNT_TYPE = LSMASH_4CC( 'a', 'k', 'I', 'D' ), /* (1) iTunes Account Type / 0: iTunes, 1: AOL */ + ITUNES_METADATA_ITEM_ITUNES_ARTIST_ID = LSMASH_4CC( 'a', 't', 'I', 'D' ), /* (4) iTunes Artist ID */ + ITUNES_METADATA_ITEM_ITUNES_COMPOSER_ID = LSMASH_4CC( 'c', 'm', 'I', 'D' ), /* (4) iTunes Composer ID */ + ITUNES_METADATA_ITEM_ITUNES_CATALOG_ID = LSMASH_4CC( 'c', 'n', 'I', 'D' ), /* (4) iTunes Catalog ID */ + ITUNES_METADATA_ITEM_ITUNES_TV_GENRE_ID = LSMASH_4CC( 'g', 'e', 'I', 'D' ), /* (4) iTunes TV Genre ID */ + ITUNES_METADATA_ITEM_ITUNES_PLAYLIST_ID = LSMASH_4CC( 'p', 'l', 'I', 'D' ), /* (8) iTunes Playlist ID */ + ITUNES_METADATA_ITEM_ITUNES_COUNTRY_CODE = LSMASH_4CC( 's', 'f', 'I', 'D' ), /* (4) iTunes Country Code */ + + /* Boolean type */ + ITUNES_METADATA_ITEM_DISC_COMPILATION = LSMASH_4CC( 'c', 'p', 'i', 'l' ), /* Disc Compilation / Is disc part of a compilation? 0: No, 1: Yes */ + ITUNES_METADATA_ITEM_HIGH_DEFINITION_VIDEO = LSMASH_4CC( 'h', 'd', 'v', 'd' ), /* High Definition Video / 0: No, 1: Yes */ + ITUNES_METADATA_ITEM_PODCAST = LSMASH_4CC( 'p', 'c', 's', 't' ), /* Podcast / 0: No, 1: Yes */ + ITUNES_METADATA_ITEM_GAPLESS_PLAYBACK = LSMASH_4CC( 'p', 'g', 'a', 'p' ), /* Gapless Playback / 0: insert gap, 1: no gap */ + + /* Binary type */ + ITUNES_METADATA_ITEM_COVER_ART = LSMASH_4CC( 'c', 'o', 'v', 'r' ), /* One or more cover art images (JPEG/PNG/BMP data) */ + ITUNES_METADATA_ITEM_DISC_NUMBER = LSMASH_4CC( 'd', 'i', 's', 'k' ), /* Disc Number */ + ITUNES_METADATA_ITEM_TRACK_NUMBER = LSMASH_4CC( 't', 'r', 'k', 'n' ), /* Track Number */ + + /* Custom type */ + ITUNES_METADATA_ITEM_CUSTOM = LSMASH_4CC( '-', '-', '-', '-' ), /* Custom */ +} lsmash_itunes_metadata_item; + +typedef enum +{ + ITUNES_METADATA_TYPE_NONE = 0, + ITUNES_METADATA_TYPE_STRING = 1, + ITUNES_METADATA_TYPE_INTEGER = 2, + ITUNES_METADATA_TYPE_BOOLEAN = 3, + ITUNES_METADATA_TYPE_BINARY = 4, +} lsmash_itunes_metadata_type; + +typedef enum +{ + ITUNES_METADATA_SUBTYPE_IMPLICIT = 0, /* for use with tags for which no type needs to be indicated because only one type is allowed */ + ITUNES_METADATA_SUBTYPE_UTF8 = 1, /* without any count or null terminator */ + ITUNES_METADATA_SUBTYPE_UTF16 = 2, /* also known as UTF-16BE */ + ITUNES_METADATA_SUBTYPE_SJIS = 3, /* deprecated unless it is needed for special Japanese characters */ + ITUNES_METADATA_SUBTYPE_HTML = 6, /* the HTML file header specifies which HTML version */ + ITUNES_METADATA_SUBTYPE_XML = 7, /* the XML header must identify the DTD or schemas */ + ITUNES_METADATA_SUBTYPE_UUID = 8, /* also known as GUID; stored as 16 bytes in binary (valid as an ID) */ + ITUNES_METADATA_SUBTYPE_ISRC = 9, /* stored as UTF-8 text (valid as an ID) */ + ITUNES_METADATA_SUBTYPE_MI3P = 10, /* stored as UTF-8 text (valid as an ID) */ + ITUNES_METADATA_SUBTYPE_GIF = 12, /* (deprecated) a GIF image */ + ITUNES_METADATA_SUBTYPE_JPEG = 13, /* in a JFIF wrapper */ + ITUNES_METADATA_SUBTYPE_PNG = 14, /* in a PNG wrapper */ + ITUNES_METADATA_SUBTYPE_URL = 15, /* absolute, in UTF-8 characters */ + ITUNES_METADATA_SUBTYPE_DURATION = 16, /* in milliseconds, a 32-bit integer */ + ITUNES_METADATA_SUBTYPE_TIME = 17, /* in UTC, counting seconds since midnight on 1 January, 1904; 32 or 64 bits */ + ITUNES_METADATA_SUBTYPE_GENRES = 18, /* a list of values from the enumerated set */ + ITUNES_METADATA_SUBTYPE_INTEGER = 21, /* A signed big-endian integer in 1,2,3,4 or 8 bytes */ + ITUNES_METADATA_SUBTYPE_RIAAPA = 24, /* RIAA Parental advisory; -1=no, 1=yes, 0=unspecified. 8-bit integer */ + ITUNES_METADATA_SUBTYPE_UPC = 25, /* Universal Product Code, in text UTF-8 format (valid as an ID) */ + ITUNES_METADATA_SUBTYPE_BMP = 27, /* Windows bitmap format graphics */ +} lsmash_itunes_metadata_subtype; + +typedef union +{ + char *string; /* for ITUNES_METADATA_TYPE_STRING (UTF-8 string) */ + uint64_t integer; /* for ITUNES_METADATA_TYPE_INTEGER */ + lsmash_boolean_t boolean; /* for ITUNES_METADATA_TYPE_BOOLEAN */ + /* for ITUNES_METADATA_TYPE_BINARY */ + struct + { + lsmash_itunes_metadata_subtype subtype; + uint32_t size; + uint8_t *data; + } binary; +} lsmash_itunes_metadata_value_t; + +typedef struct +{ + /* When 'item' is specified as ITUNES_METADATA_ITEM_CUSTOM, 'type' and 'meaning' is mandatory while 'name' is optionally valid. + * Otherwise 'type', 'meaning' and 'name' are just ignored. 'value' is always mandatory. */ + lsmash_itunes_metadata_item item; + lsmash_itunes_metadata_type type; + lsmash_itunes_metadata_value_t value; + char *meaning; + char *name; +} lsmash_itunes_metadata_t; + +/* Append an iTunes metadata to a movie. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_set_itunes_metadata +( + lsmash_root_t *root, + lsmash_itunes_metadata_t metadata +); + +/* Count the number of iTunes metadata in a movie. + * + * Return the number of iTunes metadata in a movie if successful. + * Return 0 otherwise. */ +uint32_t lsmash_count_itunes_metadata +( + lsmash_root_t *root +); + +/* Get an iTunes metadata in a movie. + * String and/or binary fields in 'metadata' are allocated if successful. + * You can deallocate the allocated fields by lsmash_free(). + * Also you can deallocate all of the allocated fields by lsmash_cleanup_itunes_metadata() at a time. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_get_itunes_metadata +( + lsmash_root_t *root, + uint32_t metadata_number, + lsmash_itunes_metadata_t *metadata +); + +/* Deallocate all of allocated fields in a given iTunes metadata at a time. + * The deallocated fields are set to NULL. + * Note: the given iTunes metadata itself is NOT deallocated by this function. */ +void lsmash_cleanup_itunes_metadata +( + lsmash_itunes_metadata_t *metadata +); + +/**************************************************************************** + * Others + ****************************************************************************/ +/* Set a copyright declaration to a track. + * track_ID == 0 means copyright declaration applies to the entire presentation, not an entire track. + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_set_copyright +( + lsmash_root_t *root, + uint32_t track_ID, + uint16_t ISO_language, + char *notice +); + +int lsmash_create_object_descriptor +( + lsmash_root_t *root +); + +/* Set a session description protocol (SDP) to a track. + * track_ID == 0 means SDP applies to the entire presentation, not an entire track. + * Recommendation: Record session level section of SDP to movie and media-level sections to track + * + * Return 0 if successful. + * Return a negative value otherwise. */ +int lsmash_set_sdp +( + lsmash_root_t *root, + uint32_t track_ID, + char *sdptext +); + +/* Timestamp synchronization */ +typedef enum +{ + ISOM_SYNC_ANY = 0, + ISOM_SYNC_RECIEVED_RTP = 1, + ISOM_SYNC_OTHER_TRACKS = 2, + ISOM_SYNC_RESERVED = 3, +} timestamp_sync_t; + +typedef struct +{ + uint32_t timescale; /* timscale of this track */ + uint32_t time_offset; /* Beginning time stamp offset compared to */ + uint8_t reserved_timestamp_sync; /* first 6 bits = 0, last two indicate whether tracks have been synchronized */ +} lsmash_isom_rtp_reception_hint_t; + +#ifdef _WIN32 +/* Convert string encoded by ACP (ANSI CODE PAGE) to UTF-8. + * + * Return the size of converted string (bytes) if successful. + * Return 0 otherwise. */ +int lsmash_convert_ansi_to_utf8 +( + const char *string_ansi, /* string encoded by ACP */ + char *string_utf8, /* buffer for converted string from ACP */ + int max_length /* size of 'string_utf8' */ +); +#endif + +#undef PRIVATE + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/bindings/bf/bgfx.bf b/bindings/bf/bgfx.bf index 338a79e8c..87f201a60 100644 --- a/bindings/bf/bgfx.bf +++ b/bindings/bf/bgfx.bf @@ -743,6 +743,8 @@ public static class bgfx /// Texture is shared with other device or other process. /// ExternalShared = 0x0001000000000000, + ReservedShift = 60, + ReservedMask = 0xf000000000000000, /// /// Render target MSAAx2 mode. @@ -1177,10 +1179,15 @@ public static class bgfx /// VertexId = 0x0000000100000000, + /// + /// Hardware video decode is supported. + /// + VideoDecode = 0x0000000200000000, + /// /// Viewport layer is available in vertex shader. /// - ViewportLayerArray = 0x0000000200000000, + ViewportLayerArray = 0x0000000400000000, /// /// All texture compare modes are supported. @@ -1280,6 +1287,104 @@ public static class bgfx /// Texture format can be used as back buffer format. /// TextureBackbuffer = 0x00010000, + + /// + /// Texture format can be used as video decode destination. + /// + TextureVideoDecodeDst = 0x00020000, + } + + [AllowDuplicates] + public enum CapsVideoCodecFlags : uint32 + { + /// + /// Video codec is not supported. + /// + None = 0x00000000, + + /// + /// 8-bit sample depth is supported. + /// + Bit8 = 0x00000001, + + /// + /// 10-bit sample depth is supported. + /// + Bit10 = 0x00000002, + + /// + /// 12-bit sample depth is supported. + /// + Bit12 = 0x00000004, + + /// + /// 4:2:0 chroma subsampling is supported. + /// + Chroma420 = 0x00000008, + + /// + /// 4:2:2 chroma subsampling is supported. + /// + Chroma422 = 0x00000010, + + /// + /// 4:4:4 chroma subsampling is supported. + /// + Chroma444 = 0x00000020, + } + + [AllowDuplicates] + public enum VideoDecoderInitFlags : uint32 + { + /// + /// No flags. + /// + None = 0x00000000, + + /// + /// Cache submitted access units in driver-managed memory keyed by `ptsUs` so the + /// presentation clock can revisit / loop without re-streaming. The cache is + /// unbounded: the app picks the total cache size implicitly by choosing how + /// many access units to submit. Without this flag access units are decoded once + /// and dropped (streaming default). + /// + Retain = 0x00000001, + } + + [AllowDuplicates] + public enum VideoDecodeFrameFlags : uint32 + { + /// + /// No flags. + /// + None = 0x00000000, + + /// + /// First batch after a position change. The first access unit must be a clean IDR. + /// Driver flushes its DPB, queued access units, and reorder pool before decoding; + /// subsequent `presentationTimeUs` values may land anywhere (monotonicity is only + /// required between non-`Set` ticks). + /// + Set = 0x00000001, + + /// + /// Skip the picker dispatch for this call. Useful while bulk-loading access units + /// so the displayed picture isn't churned mid-load. + /// + NoBlit = 0x00000002, + + /// + /// Marks the last access unit of the clip; permits eager pre-decode in idle time + /// and lets the picker emit the final frame without lookahead stalling. + /// + Final = 0x00000004, + + /// + /// When `presentationTimeUs` runs past the highest cached `ptsUs`, the picker + /// wraps modulo the cached pts range. Without this flag the picker freezes on + /// the last displayable picture. + /// + Loop = 0x00000008, } [AllowDuplicates] @@ -2244,6 +2349,27 @@ public static class bgfx Count } + [AllowDuplicates] + public enum VideoCodec : uint32 + { + /// + /// H.264 / AVC. + /// + H264, + + /// + /// H.265 / HEVC. + /// + H265, + + /// + /// AV1. + /// + AV1, + + Count + } + [AllowDuplicates] public enum Topology : uint32 { @@ -2484,6 +2610,7 @@ public static class bgfx public GPU[4] gpu; public Limits limits; public uint32[100] formats; + public uint32[3] codecs; } [CRepr] @@ -2538,6 +2665,7 @@ public static class bgfx public uint8 debug; public uint8 profile; public uint8 fallback; + public uint8 videoDecode; public PlatformData platformData; public Resolution resolution; public Limits limits; @@ -2598,6 +2726,35 @@ public static class bgfx public uint8 cubeMap; } + [CRepr] + public struct VideoDecoderInit + { + public uint32 magic; + public VideoCodec codec; + public uint8_t* parameterSets; + public uint32 parameterSetsSize; + public uint32 cachedAuBytes; + public uint8 flags; + } + + [CRepr] + public struct VideoDecoderAu + { + public uint32 size; + public int64 ptsUs; + } + + [CRepr] + public struct VideoDecoderFrame + { + public uint32 magic; + public uint8_t* bitstream; + public VideoDecoderAu* aus; + public uint32 numAus; + public int64 presentationTimeUs; + public uint8 flags; + } + [CRepr] public struct UniformInfo { @@ -3509,6 +3666,24 @@ public static class bgfx [LinkName("bgfx_is_texture_valid")] public static extern bool is_texture_valid(uint16 _depth, bool _cubeMap, uint16 _numLayers, TextureFormat _format, uint64 _flags); + /// + /// Validate video codec parameters. Use to check whether the requested + /// combination of codec / bit depth / chroma / dimensions / DPB layout can + /// be hardware decoded on the current device. Coarse capability discovery + /// is `Caps::supported & BGFX_CAPS_VIDEO_DECODE` and `Caps::codecs[]`. + /// + /// + /// Video codec. See: `VideoCodec::Enum`. + /// Chroma subsampling. 0 = 4:2:0, 2 = 4:2:2, 4 = 4:4:4. + /// Bit depth per component. 8, 10 or 12. + /// Coded picture width (macroblock / CTU / superblock aligned). + /// Coded picture height. + /// Maximum decoded picture buffer slot count. + /// Maximum number of reference frames active at once. + /// + [LinkName("bgfx_is_video_codec_valid")] + public static extern bool is_video_codec_valid(VideoCodec _codec, uint8 _chroma, uint8 _bitDepth, uint16 _codedWidth, uint16 _codedHeight, uint8 _maxDpbSlots, uint8 _maxActiveReferences); + /// /// Validate frame buffer parameters. /// diff --git a/bindings/c3/bgfx.c3 b/bindings/c3/bgfx.c3 index 745b810f5..f7225f9ea 100644 --- a/bindings/c3/bgfx.c3 +++ b/bindings/c3/bgfx.c3 @@ -467,6 +467,8 @@ constdef TextureFlags : inline ulong // Texture is shared with other device or other process. EXTERNALSHARED = 0x0001000000000000, + RESERVEDSHIFT = 60, + RESERVEDMASK = 0xf000000000000000, // Render target MSAAx2 mode. RTMSAAX2 = 0x0000002000000000, @@ -744,8 +746,11 @@ constdef CapsFlags : inline ulong // Rendering with VertexID only is supported. VERTEXID = 0x0000000100000000, + // Hardware video decode is supported. + VIDEODECODE = 0x0000000200000000, + // Viewport layer is available in vertex shader. - VIEWPORTLAYERARRAY = 0x0000000200000000, + VIEWPORTLAYERARRAY = 0x0000000400000000, // All texture compare modes are supported. TEXTURECOMPAREALL = 0x0000000000180000, @@ -806,6 +811,71 @@ constdef CapsFormatFlags : inline uint // Texture format can be used as back buffer format. TEXTUREBACKBUFFER = 0x00010000, + + // Texture format can be used as video decode destination. + TEXTUREVIDEODECODEDST = 0x00020000, +} + +constdef CapsVideoCodecFlags : inline uint +{ + // Video codec is not supported. + NONE = 0x00000000, + + // 8-bit sample depth is supported. + BIT_8 = 0x00000001, + + // 10-bit sample depth is supported. + BIT_10 = 0x00000002, + + // 12-bit sample depth is supported. + BIT_12 = 0x00000004, + + // 4:2:0 chroma subsampling is supported. + CHROMA_420 = 0x00000008, + + // 4:2:2 chroma subsampling is supported. + CHROMA_422 = 0x00000010, + + // 4:4:4 chroma subsampling is supported. + CHROMA_444 = 0x00000020, +} + +constdef VideoDecoderInitFlags : inline uint +{ + // No flags. + NONE = 0x00000000, + + // Cache submitted access units in driver-managed memory keyed by `ptsUs` so the + // presentation clock can revisit / loop without re-streaming. The cache is + // unbounded: the app picks the total cache size implicitly by choosing how + // many access units to submit. Without this flag access units are decoded once + // and dropped (streaming default). + RETAIN = 0x00000001, +} + +constdef VideoDecodeFrameFlags : inline uint +{ + // No flags. + NONE = 0x00000000, + + // First batch after a position change. The first access unit must be a clean IDR. + // Driver flushes its DPB, queued access units, and reorder pool before decoding; + // subsequent `presentationTimeUs` values may land anywhere (monotonicity is only + // required between non-`Set` ticks). + SET = 0x00000001, + + // Skip the picker dispatch for this call. Useful while bulk-loading access units + // so the displayed picture isn't churned mid-load. + NOBLIT = 0x00000002, + + // Marks the last access unit of the clip; permits eager pre-decode in idle time + // and lets the picker emit the final frame without lookahead stalling. + FINAL = 0x00000004, + + // When `presentationTimeUs` runs past the highest cached `ptsUs`, the picker + // wraps modulo the cached pts range. Without this flag the picker freezes on + // the last displayable picture. + LOOP = 0x00000008, } constdef ResolveFlags : inline uint @@ -1404,6 +1474,20 @@ enum OcclusionQueryResult : uint COUNT } +enum VideoCodec : uint +{ + // H.264 / AVC. + H264, + + // H.265 / HEVC. + H265, + + // AV1. + AV1, + + COUNT +} + enum Topology : uint { // Triangle list. @@ -1643,7 +1727,20 @@ struct Caps // - `BGFX_CAPS_FORMAT_TEXTURE_MIP_AUTOGEN` - Texture format supports auto-generated // mips. // - `BGFX_CAPS_FORMAT_TEXTURE_BACKBUFFER` - Texture format can be used as back buffer format. + // - `BGFX_CAPS_FORMAT_TEXTURE_VIDEO_DECODE_DST` - Texture format can be used as video + // decode destination. uint[100] formats; + // Supported video codec capabilities flags. A non-zero entry means the codec is + // supported for hardware decode; bits describe sample depths and chroma + // subsamplings: + // - `BGFX_CAPS_VIDEO_CODEC_NONE` - Video codec is not supported. + // - `BGFX_CAPS_VIDEO_CODEC_BIT_8` - 8-bit sample depth is supported. + // - `BGFX_CAPS_VIDEO_CODEC_BIT_10` - 10-bit sample depth is supported. + // - `BGFX_CAPS_VIDEO_CODEC_BIT_12` - 12-bit sample depth is supported. + // - `BGFX_CAPS_VIDEO_CODEC_CHROMA_420` - 4:2:0 chroma subsampling is supported. + // - `BGFX_CAPS_VIDEO_CODEC_CHROMA_422` - 4:2:2 chroma subsampling is supported. + // - `BGFX_CAPS_VIDEO_CODEC_CHROMA_444` - 4:4:4 chroma subsampling is supported. + uint[3] codecs; } // Internal data. @@ -1742,6 +1839,8 @@ struct Init bool profile; // Enable fallback to next available renderer. bool fallback; + // Enable video decoding. + bool videoDecode; // Platform data. PlatformData platformData; // Backbuffer resolution and reset parameters. See: `bgfx::Resolution`. @@ -1840,6 +1939,75 @@ struct TextureInfo bool cubeMap; } +// Video decoder initialization. Serialized into the Memory passed to +// `createTexture2D`. When the memory blob begins with `magic`, bgfx +// infers the texture is a video decode destination (the caller need not set +// any extra texture flag). Everything else the renderer needs about the +// stream (chroma format, bit depth, profile, level, coded dimensions, DPB +// layout, color metadata) is parsed out of the codec parameter sets at +// create time. +struct VideoDecoderInit +{ + // Structure magic. Must be `BX_MAKEFOURCC('V', 'D', 'I', '0')`. + uint magic; + // Video codec. See: `VideoCodec::Enum`. + VideoCodec codec; + // Codec parameter sets (Annex B for H.264/H.265, OBUs for AV1). + uint8_t* parameterSets; + // Parameter sets size in bytes. + uint parameterSetsSize; + // Soft cap (in bytes) on the streaming access-unit FIFO (when + // `BGFX_VIDEO_DECODER_INIT_RETAIN` is NOT set). 0 selects the + // default. Ignored in RETAIN mode (the retain cache is unbounded). + uint cachedAuBytes; + // Decoder lifetime flags. See: `BGFX_VIDEO_DECODER_INIT_*`. + char flags; +} + +// One access unit entry inside a `VideoDecoderFrame` batch. The bitstream +// for the AU lives at offset `Σ aus[0..ii].size` inside the frame's +// `bitstream` buffer (access units are stored back-to-back in decode / +// submission order). +struct VideoDecoderAu +{ + // Access unit size in bytes. + uint size; + // Presentation timestamp in microseconds for this access unit (container-provided). + long ptsUs; +} + +// Video decoder per-frame submission. Serialized into the Memory passed +// to `updateTexture2D` for a video decode destination texture. The +// renderer parses the slice / tile-group header out of the bitstream and +// translates it to the backend-specific decoder arguments. +// +// A single call may submit a batch of access units: `bitstream` is the +// back-to-back concatenation of `numAus` access units, and `aus[ii]` +// holds the size and PTS of each. AUs are enqueued in array order +// (which is the codec's decode order). Set `numAus == 0` (and +// `bitstream == NULL`) for a presentation-only tick that only advances +// the playback clock. +// +// The `bitstream` and `aus` pointers must remain valid until bgfx has +// consumed the submission (`bgfx::copy` only deep-copies the +// `VideoDecoderFrame` struct itself, not the buffers it references). +struct VideoDecoderFrame +{ + // Structure magic. Must be `BX_MAKEFOURCC('V', 'D', 'F', '0')`. + uint magic; + // Concatenated access-unit bitstream (decode order). NULL for presentation-only ticks. + uint8_t* bitstream; + // Per-AU size and PTS array. NULL when `numAus == 0`. + VideoDecoderAu* aus; + // Number of access units in this batch. 0 for presentation-only ticks. + uint numAus; + // Current playback wall-clock time. Driver dispatches the picture whose `ptsUs` + // best matches. Must be monotonically non-decreasing between non-`SET` calls. + long presentationTimeUs; + // Per-frame submission flags. See: `BGFX_VIDEO_DECODE_FRAME_*`. + char flags; +} + // Uniform info. struct UniformInfo { @@ -2482,6 +2650,19 @@ extern fn void destroy_program(ProgramHandle _handle) @cname("bgfx_destroy_progr // _flags : `Texture flags. See `BGFX_TEXTURE_*`.` extern fn bool is_texture_valid(ushort _depth, bool _cubeMap, ushort _numLayers, TextureFormat _format, ulong _flags) @cname("bgfx_is_texture_valid"); +// Validate video codec parameters. Use to check whether the requested +// combination of codec / bit depth / chroma / dimensions / DPB layout can +// be hardware decoded on the current device. Coarse capability discovery +// is `Caps::supported & BGFX_CAPS_VIDEO_DECODE` and `Caps::codecs[]`. +// _codec : `Video codec. See: `VideoCodec::Enum`.` +// _chroma : `Chroma subsampling. 0 = 4:2:0, 2 = 4:2:2, 4 = 4:4:4.` +// _bitDepth : `Bit depth per component. 8, 10 or 12.` +// _codedWidth : `Coded picture width (macroblock / CTU / superblock aligned).` +// _codedHeight : `Coded picture height.` +// _maxDpbSlots : `Maximum decoded picture buffer slot count.` +// _maxActiveReferences : `Maximum number of reference frames active at once.` +extern fn bool is_video_codec_valid(VideoCodec _codec, char _chroma, char _bitDepth, ushort _codedWidth, ushort _codedHeight, char _maxDpbSlots, char _maxActiveReferences) @cname("bgfx_is_video_codec_valid"); + // Validate frame buffer parameters. // _num : `Number of attachments.` // _attachment : `Attachment texture info. See: `bgfx::Attachment`.` diff --git a/bindings/cs/bgfx.cs b/bindings/cs/bgfx.cs index 3571ca8f2..a23178c61 100644 --- a/bindings/cs/bgfx.cs +++ b/bindings/cs/bgfx.cs @@ -742,6 +742,8 @@ public static partial class bgfx /// Texture is shared with other device or other process. /// ExternalShared = 0x0001000000000000, + ReservedShift = 60, + ReservedMask = 0xf000000000000000, /// /// Render target MSAAx2 mode. @@ -1176,10 +1178,15 @@ public static partial class bgfx /// VertexId = 0x0000000100000000, + /// + /// Hardware video decode is supported. + /// + VideoDecode = 0x0000000200000000, + /// /// Viewport layer is available in vertex shader. /// - ViewportLayerArray = 0x0000000200000000, + ViewportLayerArray = 0x0000000400000000, /// /// All texture compare modes are supported. @@ -1279,6 +1286,104 @@ public static partial class bgfx /// Texture format can be used as back buffer format. /// TextureBackbuffer = 0x00010000, + + /// + /// Texture format can be used as video decode destination. + /// + TextureVideoDecodeDst = 0x00020000, + } + + [Flags] + public enum CapsVideoCodecFlags : uint + { + /// + /// Video codec is not supported. + /// + None = 0x00000000, + + /// + /// 8-bit sample depth is supported. + /// + Bit8 = 0x00000001, + + /// + /// 10-bit sample depth is supported. + /// + Bit10 = 0x00000002, + + /// + /// 12-bit sample depth is supported. + /// + Bit12 = 0x00000004, + + /// + /// 4:2:0 chroma subsampling is supported. + /// + Chroma420 = 0x00000008, + + /// + /// 4:2:2 chroma subsampling is supported. + /// + Chroma422 = 0x00000010, + + /// + /// 4:4:4 chroma subsampling is supported. + /// + Chroma444 = 0x00000020, + } + + [Flags] + public enum VideoDecoderInitFlags : uint + { + /// + /// No flags. + /// + None = 0x00000000, + + /// + /// Cache submitted access units in driver-managed memory keyed by `ptsUs` so the + /// presentation clock can revisit / loop without re-streaming. The cache is + /// unbounded: the app picks the total cache size implicitly by choosing how + /// many access units to submit. Without this flag access units are decoded once + /// and dropped (streaming default). + /// + Retain = 0x00000001, + } + + [Flags] + public enum VideoDecodeFrameFlags : uint + { + /// + /// No flags. + /// + None = 0x00000000, + + /// + /// First batch after a position change. The first access unit must be a clean IDR. + /// Driver flushes its DPB, queued access units, and reorder pool before decoding; + /// subsequent `presentationTimeUs` values may land anywhere (monotonicity is only + /// required between non-`Set` ticks). + /// + Set = 0x00000001, + + /// + /// Skip the picker dispatch for this call. Useful while bulk-loading access units + /// so the displayed picture isn't churned mid-load. + /// + NoBlit = 0x00000002, + + /// + /// Marks the last access unit of the clip; permits eager pre-decode in idle time + /// and lets the picker emit the final frame without lookahead stalling. + /// + Final = 0x00000004, + + /// + /// When `presentationTimeUs` runs past the highest cached `ptsUs`, the picker + /// wraps modulo the cached pts range. Without this flag the picker freezes on + /// the last displayable picture. + /// + Loop = 0x00000008, } [Flags] @@ -2233,6 +2338,26 @@ public static partial class bgfx Count } + public enum VideoCodec + { + /// + /// H.264 / AVC. + /// + H264, + + /// + /// H.265 / HEVC. + /// + H265, + + /// + /// AV1. + /// + AV1, + + Count + } + public enum Topology { /// @@ -2463,6 +2588,7 @@ public static partial class bgfx public fixed uint gpu[4]; public Limits limits; public fixed uint formats[100]; + public fixed uint codecs[3]; } public unsafe struct InternalData @@ -2512,6 +2638,7 @@ public static partial class bgfx public byte debug; public byte profile; public byte fallback; + public byte videoDecode; public PlatformData platformData; public Resolution resolution; public Limits limits; @@ -2567,6 +2694,32 @@ public static partial class bgfx public byte cubeMap; } + public unsafe struct VideoDecoderInit + { + public uint magic; + public VideoCodec codec; + public uint8_t* parameterSets; + public uint parameterSetsSize; + public uint cachedAuBytes; + public byte flags; + } + + public unsafe struct VideoDecoderAu + { + public uint size; + public long ptsUs; + } + + public unsafe struct VideoDecoderFrame + { + public uint magic; + public uint8_t* bitstream; + public VideoDecoderAu* aus; + public uint numAus; + public long presentationTimeUs; + public byte flags; + } + public unsafe struct UniformInfo { public fixed byte name[256]; @@ -3461,6 +3614,25 @@ public static partial class bgfx [return: MarshalAs(UnmanagedType.I1)] public static extern unsafe bool is_texture_valid(ushort _depth, bool _cubeMap, ushort _numLayers, TextureFormat _format, ulong _flags); + /// + /// Validate video codec parameters. Use to check whether the requested + /// combination of codec / bit depth / chroma / dimensions / DPB layout can + /// be hardware decoded on the current device. Coarse capability discovery + /// is `Caps::supported & BGFX_CAPS_VIDEO_DECODE` and `Caps::codecs[]`. + /// + /// + /// Video codec. See: `VideoCodec::Enum`. + /// Chroma subsampling. 0 = 4:2:0, 2 = 4:2:2, 4 = 4:4:4. + /// Bit depth per component. 8, 10 or 12. + /// Coded picture width (macroblock / CTU / superblock aligned). + /// Coded picture height. + /// Maximum decoded picture buffer slot count. + /// Maximum number of reference frames active at once. + /// + [DllImport(DllName, EntryPoint="bgfx_is_video_codec_valid", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + public static extern unsafe bool is_video_codec_valid(VideoCodec _codec, byte _chroma, byte _bitDepth, ushort _codedWidth, ushort _codedHeight, byte _maxDpbSlots, byte _maxActiveReferences); + /// /// Validate frame buffer parameters. /// diff --git a/bindings/d/impl.d b/bindings/d/impl.d index 46f7f3046..4ee67c0d4 100644 --- a/bindings/d/impl.d +++ b/bindings/d/impl.d @@ -69,6 +69,11 @@ extern(C++, "bgfx") package final abstract class OcclusionQueryResult{ invisible,visible,noResult,count } } +extern(C++, "bgfx") package final abstract class VideoCodec{ + enum Enum{ + h264,h265,av1,count + } +} extern(C++, "bgfx") package final abstract class Topology{ enum Enum{ triList,triStrip,lineList,lineStrip,pointList,count diff --git a/bindings/d/package.d b/bindings/d/package.d index 3764f1803..a2486f79a 100644 --- a/bindings/d/package.d +++ b/bindings/d/package.d @@ -9,7 +9,7 @@ import bindbc.common.types: c_int64, c_uint64, va_list; import bindbc.bgfx.config; static import bgfx.impl; -enum uint apiVersion = 145; +enum uint apiVersion = 146; alias ViewID = ushort; @@ -339,6 +339,13 @@ enum Texture: Texture_{ externalShared = 0x0001_0000_0000_0000, ///Texture is shared with other device or other process. } +///Do not use! Top nibble is reserved for internal texture flags (see bgfx_p.h). +alias TextureReserved_ = ulong; +enum TextureReserved: TextureReserved_{ + shift = 60, + mask = 0xF000_0000_0000_0000, +} + alias TextureRTMSAA_ = ulong; enum TextureRTMSAA: TextureRTMSAA_{ x2 = 0x0000_0020_0000_0000, ///Render target MSAAx2 mode. @@ -525,7 +532,8 @@ enum CapFlags: CapFlags_{ vertexAttribHalf = 0x0000_0000_4000_0000, ///Vertex attribute half-float is supported. vertexAttribUint10 = 0x0000_0000_8000_0000, ///Vertex attribute 10_10_10_2 is supported. vertexID = 0x0000_0001_0000_0000, ///Rendering with VertexID only is supported. - viewportLayerArray = 0x0000_0002_0000_0000, ///Viewport layer is available in vertex shader. + videoDecode = 0x0000_0002_0000_0000, ///Hardware video decode is supported. + viewportLayerArray = 0x0000_0004_0000_0000, ///Viewport layer is available in vertex shader. textureCompareAll = 0x0000_0000_0018_0000, ///All texture compare modes are supported. } @@ -549,6 +557,61 @@ enum CapsFormat: CapsFormat_{ textureMSAA = 0x0000_4000, ///Texture can be sampled as MSAA. textureMIPAutogen = 0x0000_8000, ///Texture format supports auto-generated mips. textureBackbuffer = 0x0001_0000, ///Texture format can be used as back buffer format. + textureVideoDecodeDst = 0x0002_0000, ///Texture format can be used as video decode destination. +} + +alias CapsVideoCodec_ = uint; +enum CapsVideoCodec: CapsVideoCodec_{ + none = 0x0000_0000, ///Video codec is not supported. + bit8 = 0x0000_0001, ///8-bit sample depth is supported. + bit10 = 0x0000_0002, ///10-bit sample depth is supported. + bit12 = 0x0000_0004, ///12-bit sample depth is supported. + chroma420 = 0x0000_0008, ///4:2:0 chroma subsampling is supported. + chroma422 = 0x0000_0010, ///4:2:2 chroma subsampling is supported. + chroma444 = 0x0000_0020, ///4:4:4 chroma subsampling is supported. +} + +///Video decoder lifetime flags (per `VideoDecoderInit::flags`). +alias VideoDecoderInit_ = ubyte; +enum VideoDecoderInit: VideoDecoderInit_{ + none = 0x00, ///No flags. + /** + Cache submitted access units in driver-managed memory keyed by `ptsUs` so the + presentation clock can revisit / loop without re-streaming. The cache is + unbounded: the app picks the total cache size implicitly by choosing how + many access units to submit. Without this flag access units are decoded once + and dropped (streaming default). + */ + retain = 0x01, +} + +///Video decoder per-frame submission flags (per `VideoDecoderFrame::flags`). +alias VideoDecodeFrame_ = ubyte; +enum VideoDecodeFrame: VideoDecodeFrame_{ + none = 0x00, ///No flags. + /** + First batch after a position change. The first access unit must be a clean IDR. + Driver flushes its DPB, queued access units, and reorder pool before decoding; + subsequent `presentationTimeUs` values may land anywhere (monotonicity is only + required between non-`Set` ticks). + */ + set = 0x01, + /** + Skip the picker dispatch for this call. Useful while bulk-loading access units + so the displayed picture isn't churned mid-load. + */ + noBlit = 0x02, + /** + Marks the last access unit of the clip; permits eager pre-decode in idle time + and lets the picker emit the final frame without lookahead stalling. + */ + final_ = 0x04, + /** + When `presentationTimeUs` runs past the highest cached `ptsUs`, the picker + wraps modulo the cached pts range. Without this flag the picker freezes on + the last displayable picture. + */ + loop = 0x08, } alias Resolve_ = ubyte; @@ -820,6 +883,14 @@ enum OcclusionQueryResult: bgfx.impl.OcclusionQueryResult.Enum{ count = bgfx.impl.OcclusionQueryResult.Enum.count, } +///Video codec enum. +enum VideoCodec: bgfx.impl.VideoCodec.Enum{ + h264 = bgfx.impl.VideoCodec.Enum.h264, + h265 = bgfx.impl.VideoCodec.Enum.h265, + av1 = bgfx.impl.VideoCodec.Enum.av1, + count = bgfx.impl.VideoCodec.Enum.count, +} + ///Primitive topology. enum Topology: bgfx.impl.Topology.Enum{ triList = bgfx.impl.Topology.Enum.triList, @@ -1115,8 +1186,24 @@ extern(C++, "bgfx") struct Caps{ - `BGFX_CAPS_FORMAT_TEXTURE_MIP_AUTOGEN` - Texture format supports auto-generated mips. - `BGFX_CAPS_FORMAT_TEXTURE_BACKBUFFER` - Texture format can be used as back buffer format. + - `BGFX_CAPS_FORMAT_TEXTURE_VIDEO_DECODE_DST` - Texture format can be used as video + decode destination. */ uint[TextureFormat.count] formats; + + /** + Supported video codec capabilities flags. A non-zero entry means the codec is + supported for hardware decode; bits describe sample depths and chroma + subsamplings: + - `BGFX_CAPS_VIDEO_CODEC_NONE` - Video codec is not supported. + - `BGFX_CAPS_VIDEO_CODEC_BIT_8` - 8-bit sample depth is supported. + - `BGFX_CAPS_VIDEO_CODEC_BIT_10` - 10-bit sample depth is supported. + - `BGFX_CAPS_VIDEO_CODEC_BIT_12` - 12-bit sample depth is supported. + - `BGFX_CAPS_VIDEO_CODEC_CHROMA_420` - 4:2:0 chroma subsampling is supported. + - `BGFX_CAPS_VIDEO_CODEC_CHROMA_422` - 4:2:2 chroma subsampling is supported. + - `BGFX_CAPS_VIDEO_CODEC_CHROMA_444` - 4:4:4 chroma subsampling is supported. + */ + uint[VideoCodec.count] codecs; } ///Internal data. @@ -1220,6 +1307,7 @@ extern(C++, "bgfx") struct Init{ bool debug_; ///Enable device for debugging. bool profile; ///Enable device for profiling. bool fallback; ///Enable fallback to next available renderer. + bool videoDecode; ///Enable video decoding. PlatformData platformData; ///Platform data. Resolution resolution; ///Backbuffer resolution and reset parameters. See: `bgfx::Resolution`. Limits limits; ///Configurable runtime limits parameters. @@ -1296,6 +1384,72 @@ extern(C++, "bgfx") struct TextureInfo{ bool cubeMap; ///Texture is cubemap. } +/** +Video decoder initialization. Serialized into the Memory passed to +`createTexture2D`. When the memory blob begins with `magic`, bgfx +infers the texture is a video decode destination (the caller need not set +any extra texture flag). Everything else the renderer needs about the +stream (chroma format, bit depth, profile, level, coded dimensions, DPB +layout, color metadata) is parsed out of the codec parameter sets at +create time. +*/ +extern(C++, "bgfx") struct VideoDecoderInit{ + uint magic; ///Structure magic. Must be `BX_MAKEFOURCC('V', 'D', 'I', '0')`. + VideoCodec codec; ///Video codec. See: `VideoCodec::Enum`. + const(ubyte)* parameterSets; ///Codec parameter sets (Annex B for H.264/H.265, OBUs for AV1). + uint parameterSetsSize; ///Parameter sets size in bytes. + + /** + Soft cap (in bytes) on the streaming access-unit FIFO (when + `BGFX_VIDEO_DECODER_INIT_RETAIN` is NOT set). 0 selects the + default. Ignored in RETAIN mode (the retain cache is unbounded). + */ + uint cachedAuBytes; + ubyte flags; ///Decoder lifetime flags. See: `BGFX_VIDEO_DECODER_INIT_*`. +} + +/** +One access unit entry inside a `VideoDecoderFrame` batch. The bitstream +for the AU lives at offset `Σ aus[0..ii].size` inside the frame's +`bitstream` buffer (access units are stored back-to-back in decode / +submission order). +*/ +extern(C++, "bgfx") struct VideoDecoderAu{ + uint size; ///Access unit size in bytes. + c_int64 ptsUs; ///Presentation timestamp in microseconds for this access unit (container-provided). +} + +/** +Video decoder per-frame submission. Serialized into the Memory passed +to `updateTexture2D` for a video decode destination texture. The +renderer parses the slice / tile-group header out of the bitstream and +translates it to the backend-specific decoder arguments. + +A single call may submit a batch of access units: `bitstream` is the +back-to-back concatenation of `numAus` access units, and `aus[ii]` +holds the size and PTS of each. AUs are enqueued in array order +(which is the codec's decode order). Set `numAus == 0` (and +`bitstream == NULL`) for a presentation-only tick that only advances +the playback clock. + +The `bitstream` and `aus` pointers must remain valid until bgfx has +consumed the submission (`bgfx::copy` only deep-copies the +`VideoDecoderFrame` struct itself, not the buffers it references). +*/ +extern(C++, "bgfx") struct VideoDecoderFrame{ + uint magic; ///Structure magic. Must be `BX_MAKEFOURCC('V', 'D', 'F', '0')`. + const(ubyte)* bitstream; ///Concatenated access-unit bitstream (decode order). NULL for presentation-only ticks. + const(VideoDecoderAu)* aus; ///Per-AU size and PTS array. NULL when `numAus == 0`. + uint numAus; ///Number of access units in this batch. 0 for presentation-only ticks. + + /** + Current playback wall-clock time. Driver dispatches the picture whose `ptsUs` + best matches. Must be monotonically non-decreasing between non-`SET` calls. + */ + c_int64 presentationTimeUs; + ubyte flags; ///Per-frame submission flags. See: `BGFX_VIDEO_DECODE_FRAME_*`. +} + ///Uniform info. extern(C++, "bgfx") struct UniformInfo{ char[256] name; ///Uniform name. @@ -2629,6 +2783,22 @@ mixin(joinFnBinds((){ */ {q{bool}, q{isTextureValid}, q{ushort depth, bool cubeMap, ushort numLayers, bgfx.impl.TextureFormat.Enum format, c_uint64 flags}, ext: `C++, "bgfx"`}, + /** + * Validate video codec parameters. Use to check whether the requested + * combination of codec / bit depth / chroma / dimensions / DPB layout can + * be hardware decoded on the current device. Coarse capability discovery + * is `Caps::supported & BGFX_CAPS_VIDEO_DECODE` and `Caps::codecs[]`. + Params: + codec = Video codec. See: `VideoCodec::Enum`. + chroma = Chroma subsampling. 0 = 4:2:0, 2 = 4:2:2, 4 = 4:4:4. + bitDepth = Bit depth per component. 8, 10 or 12. + codedWidth = Coded picture width (macroblock / CTU / superblock aligned). + codedHeight = Coded picture height. + maxDpbSlots = Maximum decoded picture buffer slot count. + maxActiveReferences = Maximum number of reference frames active at once. + */ + {q{bool}, q{isVideoCodecValid}, q{bgfx.impl.VideoCodec.Enum codec, ubyte chroma, ubyte bitDepth, ushort codedWidth, ushort codedHeight, ubyte maxDpbSlots, ubyte maxActiveReferences}, ext: `C++, "bgfx"`}, + /** * Validate frame buffer parameters. Params: diff --git a/bindings/zig/bgfx.zig b/bindings/zig/bgfx.zig index feaa44df4..814af0a97 100644 --- a/bindings/zig/bgfx.zig +++ b/bindings/zig/bgfx.zig @@ -455,6 +455,8 @@ pub const TextureFlags_ReadBack: TextureFlags = 0x0000800000000000 /// Texture is shared with other device or other process. pub const TextureFlags_ExternalShared: TextureFlags = 0x0001000000000000; +pub const TextureFlags_ReservedShift: TextureFlags = 60; +pub const TextureFlags_ReservedMask: TextureFlags = 0xf000000000000000; /// Render target MSAAx2 mode. pub const TextureFlags_RtMsaaX2: TextureFlags = 0x0000002000000000; @@ -726,8 +728,11 @@ pub const CapsFlags_VertexAttribUint10: CapsFlags = 0x0000000080000000; /// Rendering with VertexID only is supported. pub const CapsFlags_VertexId: CapsFlags = 0x0000000100000000; +/// Hardware video decode is supported. +pub const CapsFlags_VideoDecode: CapsFlags = 0x0000000200000000; + /// Viewport layer is available in vertex shader. -pub const CapsFlags_ViewportLayerArray: CapsFlags = 0x0000000200000000; +pub const CapsFlags_ViewportLayerArray: CapsFlags = 0x0000000400000000; /// All texture compare modes are supported. pub const CapsFlags_TextureCompareAll: CapsFlags = 0x0000000000180000; @@ -787,6 +792,65 @@ pub const CapsFormatFlags_TextureMipAutogen: CapsFormatFlags = 0x00008000; /// Texture format can be used as back buffer format. pub const CapsFormatFlags_TextureBackbuffer: CapsFormatFlags = 0x00010000; +/// Texture format can be used as video decode destination. +pub const CapsFormatFlags_TextureVideoDecodeDst: CapsFormatFlags = 0x00020000; + +pub const CapsVideoCodecFlags = u32; +/// Video codec is not supported. +pub const CapsVideoCodecFlags_None: CapsVideoCodecFlags = 0x00000000; + +/// 8-bit sample depth is supported. +pub const CapsVideoCodecFlags_Bit8: CapsVideoCodecFlags = 0x00000001; + +/// 10-bit sample depth is supported. +pub const CapsVideoCodecFlags_Bit10: CapsVideoCodecFlags = 0x00000002; + +/// 12-bit sample depth is supported. +pub const CapsVideoCodecFlags_Bit12: CapsVideoCodecFlags = 0x00000004; + +/// 4:2:0 chroma subsampling is supported. +pub const CapsVideoCodecFlags_Chroma420: CapsVideoCodecFlags = 0x00000008; + +/// 4:2:2 chroma subsampling is supported. +pub const CapsVideoCodecFlags_Chroma422: CapsVideoCodecFlags = 0x00000010; + +/// 4:4:4 chroma subsampling is supported. +pub const CapsVideoCodecFlags_Chroma444: CapsVideoCodecFlags = 0x00000020; + +pub const VideoDecoderInitFlags = u32; +/// No flags. +pub const VideoDecoderInitFlags_None: VideoDecoderInitFlags = 0x00000000; + +/// Cache submitted access units in driver-managed memory keyed by `ptsUs` so the +/// presentation clock can revisit / loop without re-streaming. The cache is +/// unbounded: the app picks the total cache size implicitly by choosing how +/// many access units to submit. Without this flag access units are decoded once +/// and dropped (streaming default). +pub const VideoDecoderInitFlags_Retain: VideoDecoderInitFlags = 0x00000001; + +pub const VideoDecodeFrameFlags = u32; +/// No flags. +pub const VideoDecodeFrameFlags_None: VideoDecodeFrameFlags = 0x00000000; + +/// First batch after a position change. The first access unit must be a clean IDR. +/// Driver flushes its DPB, queued access units, and reorder pool before decoding; +/// subsequent `presentationTimeUs` values may land anywhere (monotonicity is only +/// required between non-`Set` ticks). +pub const VideoDecodeFrameFlags_Set: VideoDecodeFrameFlags = 0x00000001; + +/// Skip the picker dispatch for this call. Useful while bulk-loading access units +/// so the displayed picture isn't churned mid-load. +pub const VideoDecodeFrameFlags_NoBlit: VideoDecodeFrameFlags = 0x00000002; + +/// Marks the last access unit of the clip; permits eager pre-decode in idle time +/// and lets the picker emit the final frame without lookahead stalling. +pub const VideoDecodeFrameFlags_Final: VideoDecodeFrameFlags = 0x00000004; + +/// When `presentationTimeUs` runs past the highest cached `ptsUs`, the picker +/// wraps modulo the cached pts range. Without this flag the picker freezes on +/// the last displayable picture. +pub const VideoDecodeFrameFlags_Loop: VideoDecodeFrameFlags = 0x00000008; + pub const ResolveFlags = u32; /// No resolve flags. pub const ResolveFlags_None: ResolveFlags = 0x00000000; @@ -1365,6 +1429,19 @@ pub const OcclusionQueryResult = enum(c_int) { Count }; +pub const VideoCodec = enum(c_int) { + /// H.264 / AVC. + H264, + + /// H.265 / HEVC. + H265, + + /// AV1. + AV1, + + Count +}; + pub const Topology = enum(c_int) { /// Triangle list. TriList, @@ -1531,6 +1608,7 @@ pub const Caps = extern struct { gpu: [4]GPU, limits: Limits, formats: [100]u32, + codecs: [3]u32, }; pub const InternalData = extern struct { @@ -1575,6 +1653,7 @@ pub const Init = extern struct { debug: bool, profile: bool, fallback: bool, + videoDecode: bool, platformData: PlatformData, resolution: Resolution, limits: Limits, @@ -1625,6 +1704,29 @@ pub const Init = extern struct { cubeMap: bool, }; + pub const VideoDecoderInit = extern struct { + magic: u32, + codec: VideoCodec, + parameterSets: [*c]const uint8_t, + parameterSetsSize: u32, + cachedAuBytes: u32, + flags: u8, + }; + + pub const VideoDecoderAu = extern struct { + size: u32, + ptsUs: i64, + }; + + pub const VideoDecoderFrame = extern struct { + magic: u32, + bitstream: [*c]const uint8_t, + aus: [*c]const VideoDecoderAu, + numAus: u32, + presentationTimeUs: i64, + flags: u8, + }; + pub const UniformInfo = extern struct { name: [256]u8, type: UniformType, @@ -2752,6 +2854,22 @@ pub inline fn isTextureValid(_depth: u16, _cubeMap: bool, _numLayers: u16, _form } extern fn bgfx_is_texture_valid(_depth: u16, _cubeMap: bool, _numLayers: u16, _format: TextureFormat, _flags: u64) bool; +/// Validate video codec parameters. Use to check whether the requested +/// combination of codec / bit depth / chroma / dimensions / DPB layout can +/// be hardware decoded on the current device. Coarse capability discovery +/// is `Caps::supported & BGFX_CAPS_VIDEO_DECODE` and `Caps::codecs[]`. +/// Video codec. See: `VideoCodec::Enum`. +/// Chroma subsampling. 0 = 4:2:0, 2 = 4:2:2, 4 = 4:4:4. +/// Bit depth per component. 8, 10 or 12. +/// Coded picture width (macroblock / CTU / superblock aligned). +/// Coded picture height. +/// Maximum decoded picture buffer slot count. +/// Maximum number of reference frames active at once. +pub inline fn isVideoCodecValid(_codec: VideoCodec, _chroma: u8, _bitDepth: u8, _codedWidth: u16, _codedHeight: u16, _maxDpbSlots: u8, _maxActiveReferences: u8) bool { + return bgfx_is_video_codec_valid(_codec, _chroma, _bitDepth, _codedWidth, _codedHeight, _maxDpbSlots, _maxActiveReferences); +} +extern fn bgfx_is_video_codec_valid(_codec: VideoCodec, _chroma: u8, _bitDepth: u8, _codedWidth: u16, _codedHeight: u16, _maxDpbSlots: u8, _maxActiveReferences: u8) bool; + /// Validate frame buffer parameters. /// Number of attachments. /// Attachment texture info. See: `bgfx::Attachment`. diff --git a/docs/bgfx.rst b/docs/bgfx.rst index 6fd9fa0f4..5f141b4f6 100644 --- a/docs/bgfx.rst +++ b/docs/bgfx.rst @@ -164,6 +164,7 @@ Individual capability flags. .. doxygendefine:: BGFX_CAPS_VERTEX_ATTRIB_HALF .. doxygendefine:: BGFX_CAPS_VERTEX_ATTRIB_UINT10 .. doxygendefine:: BGFX_CAPS_VERTEX_ID +.. doxygendefine:: BGFX_CAPS_VIDEO_DECODE .. doxygendefine:: BGFX_CAPS_VIEWPORT_LAYER_ARRAY .. doxygendefine:: BGFX_CAPS_TEXTURE_COMPARE_ALL @@ -609,7 +610,17 @@ Textures .. doxygenstruct:: bgfx::TextureInfo :members: +.. doxygenstruct:: bgfx::VideoDecoderInit + :members: + +.. doxygenstruct:: bgfx::VideoDecoderAu + :members: + +.. doxygenstruct:: bgfx::VideoDecoderFrame + :members: + .. doxygenfunction:: bgfx::isTextureValid +.. doxygenfunction:: bgfx::isVideoCodecValid .. doxygenfunction:: bgfx::calcTextureSize .. doxygenfunction:: bgfx::createTexture .. doxygenfunction:: bgfx::createTexture2D(uint16_t _width, uint16_t _height, bool _hasMips, uint16_t _numLayers, TextureFormat::Enum _format, uint64_t _flags = BGFX_TEXTURE_NONE|BGFX_SAMPLER_NONE, const Memory* _mem = NULL, uint64_t _external = 0) diff --git a/include/bgfx/bgfx.h b/include/bgfx/bgfx.h index 002b4333d..0f080eeca 100644 --- a/include/bgfx/bgfx.h +++ b/include/bgfx/bgfx.h @@ -348,6 +348,23 @@ namespace bgfx }; }; + /// Video codec enum. + /// + /// @attention C99's equivalent binding is `bgfx_video_codec_t`. + /// + struct VideoCodec + { + /// Video codecs: + enum Enum + { + H264, //!< H.264 / AVC. + H265, //!< H.265 / HEVC. + AV1, //!< AV1. + + Count + }; + }; + /// Primitive topology. /// /// @attention C99's equivalent binding is `bgfx_topology_t`. @@ -577,6 +594,18 @@ namespace bgfx /// - `BGFX_CAPS_FORMAT_TEXTURE_MIP_AUTOGEN` - Texture format supports auto-generated /// mips. /// - `BGFX_CAPS_FORMAT_TEXTURE_BACKBUFFER` - Texture format can be used as back buffer format. + /// - `BGFX_CAPS_FORMAT_TEXTURE_VIDEO_DECODE_DST` - Texture format can be used as video + /// decode destination. + uint32_t codecs[VideoCodec::Count]; //!< Supported video codec capabilities flags. A non-zero entry means the codec is + /// supported for hardware decode; bits describe sample depths and chroma + /// subsamplings: + /// - `BGFX_CAPS_VIDEO_CODEC_NONE` - Video codec is not supported. + /// - `BGFX_CAPS_VIDEO_CODEC_BIT_8` - 8-bit sample depth is supported. + /// - `BGFX_CAPS_VIDEO_CODEC_BIT_10` - 10-bit sample depth is supported. + /// - `BGFX_CAPS_VIDEO_CODEC_BIT_12` - 12-bit sample depth is supported. + /// - `BGFX_CAPS_VIDEO_CODEC_CHROMA_420` - 4:2:0 chroma subsampling is supported. + /// - `BGFX_CAPS_VIDEO_CODEC_CHROMA_422` - 4:2:2 chroma subsampling is supported. + /// - `BGFX_CAPS_VIDEO_CODEC_CHROMA_444` - 4:4:4 chroma subsampling is supported. }; /// Internal data. @@ -665,6 +694,7 @@ namespace bgfx bool debug; //!< Enable device for debugging. bool profile; //!< Enable device for profiling. bool fallback; //!< Enable fallback to next available renderer. + bool videoDecode; //!< Enable video decoding. PlatformData platformData; //!< Platform data. Resolution resolution; //!< Backbuffer resolution and reset parameters. See: `bgfx::Resolution`. Limits limits; //!< Configurable runtime limits parameters. @@ -745,6 +775,70 @@ namespace bgfx bool cubeMap; //!< Texture is cubemap. }; + /// Video decoder initialization. Serialized into the Memory passed to + /// `createTexture2D`. When the memory blob begins with `magic`, bgfx + /// infers the texture is a video decode destination (the caller need not set + /// any extra texture flag). Everything else the renderer needs about the + /// stream (chroma format, bit depth, profile, level, coded dimensions, DPB + /// layout, color metadata) is parsed out of the codec parameter sets at + /// create time. + /// + /// @attention C99's equivalent binding is `bgfx_video_decoder_init_t`. + /// + struct VideoDecoderInit + { + uint32_t magic; //!< Structure magic. Must be `BX_MAKEFOURCC('V', 'D', 'I', '0')`. + VideoCodec::Enum codec; //!< Video codec. See: `VideoCodec::Enum`. + const uint8_t* parameterSets; //!< Codec parameter sets (Annex B for H.264/H.265, OBUs for AV1). + uint32_t parameterSetsSize; //!< Parameter sets size in bytes. + uint32_t cachedAuBytes; //!< Soft cap (in bytes) on the streaming access-unit FIFO (when + /// `BGFX_VIDEO_DECODER_INIT_RETAIN` is NOT set). 0 selects the + /// default. Ignored in RETAIN mode (the retain cache is unbounded). + uint8_t flags; //!< Decoder lifetime flags. See: `BGFX_VIDEO_DECODER_INIT_*`. + }; + + /// One access unit entry inside a `VideoDecoderFrame` batch. The bitstream + /// for the AU lives at offset `Σ aus[0..ii].size` inside the frame's + /// `bitstream` buffer (access units are stored back-to-back in decode / + /// submission order). + /// + /// @attention C99's equivalent binding is `bgfx_video_decoder_au_t`. + /// + struct VideoDecoderAu + { + uint32_t size; //!< Access unit size in bytes. + int64_t ptsUs; //!< Presentation timestamp in microseconds for this access unit (container-provided). + }; + + /// Video decoder per-frame submission. Serialized into the Memory passed + /// to `updateTexture2D` for a video decode destination texture. The + /// renderer parses the slice / tile-group header out of the bitstream and + /// translates it to the backend-specific decoder arguments. + /// + /// A single call may submit a batch of access units: `bitstream` is the + /// back-to-back concatenation of `numAus` access units, and `aus[ii]` + /// holds the size and PTS of each. AUs are enqueued in array order + /// (which is the codec's decode order). Set `numAus == 0` (and + /// `bitstream == NULL`) for a presentation-only tick that only advances + /// the playback clock. + /// + /// The `bitstream` and `aus` pointers must remain valid until bgfx has + /// consumed the submission (`bgfx::copy` only deep-copies the + /// `VideoDecoderFrame` struct itself, not the buffers it references). + /// + /// @attention C99's equivalent binding is `bgfx_video_decoder_frame_t`. + /// + struct VideoDecoderFrame + { + uint32_t magic; //!< Structure magic. Must be `BX_MAKEFOURCC('V', 'D', 'F', '0')`. + const uint8_t* bitstream; //!< Concatenated access-unit bitstream (decode order). NULL for presentation-only ticks. + const VideoDecoderAu* aus; //!< Per-AU size and PTS array. NULL when `numAus == 0`. + uint32_t numAus; //!< Number of access units in this batch. 0 for presentation-only ticks. + int64_t presentationTimeUs; //!< Current playback wall-clock time. Driver dispatches the picture whose `ptsUs` + /// best matches. Must be monotonically non-decreasing between non-`SET` calls. + uint8_t flags; //!< Per-frame submission flags. See: `BGFX_VIDEO_DECODE_FRAME_*`. + }; + /// Uniform info. /// /// @attention C99's equivalent binding is `bgfx_uniform_info_t`. @@ -2860,6 +2954,33 @@ namespace bgfx , uint64_t _flags ); + /// Validate video codec parameters. Use to check whether the requested + /// combination of codec / bit depth / chroma / dimensions / DPB layout can + /// be hardware decoded on the current device. Coarse capability discovery + /// is `Caps::supported & BGFX_CAPS_VIDEO_DECODE` and `Caps::codecs[]`. + /// + /// @param[in] _codec Video codec. See: `VideoCodec::Enum`. + /// @param[in] _chroma Chroma subsampling. 0 = 4:2:0, 2 = 4:2:2, 4 = 4:4:4. + /// @param[in] _bitDepth Bit depth per component. 8, 10 or 12. + /// @param[in] _codedWidth Coded picture width (macroblock / CTU / superblock aligned). + /// @param[in] _codedHeight Coded picture height. + /// @param[in] _maxDpbSlots Maximum decoded picture buffer slot count. + /// @param[in] _maxActiveReferences Maximum number of reference frames active at once. + /// + /// @returns True if a video decoder with the same parameters can be created. + /// + /// @attention C99's equivalent binding is `bgfx_is_video_codec_valid`. + /// + bool isVideoCodecValid( + VideoCodec::Enum _codec + , uint8_t _chroma + , uint8_t _bitDepth + , uint16_t _codedWidth + , uint16_t _codedHeight + , uint8_t _maxDpbSlots + , uint8_t _maxActiveReferences + ); + /// Validate frame buffer parameters. /// /// @param[in] _num Number of attachments. diff --git a/include/bgfx/c99/bgfx.h b/include/bgfx/c99/bgfx.h index 373447d1b..d3fe46905 100644 --- a/include/bgfx/c99/bgfx.h +++ b/include/bgfx/c99/bgfx.h @@ -343,6 +343,20 @@ typedef enum bgfx_occlusion_query_result } bgfx_occlusion_query_result_t; +/** + * Video codec enum. + * + */ +typedef enum bgfx_video_codec +{ + BGFX_VIDEO_CODEC_H264, /** ( 0) H.264 / AVC. */ + BGFX_VIDEO_CODEC_H265, /** ( 1) H.265 / HEVC. */ + BGFX_VIDEO_CODEC_AV1, /** ( 2) AV1. */ + + BGFX_VIDEO_CODEC_COUNT + +} bgfx_video_codec_t; + /** * Primitive topology. * @@ -632,8 +646,24 @@ typedef struct bgfx_caps_s * - `BGFX_CAPS_FORMAT_TEXTURE_MIP_AUTOGEN` - Texture format supports auto-generated * mips. * - `BGFX_CAPS_FORMAT_TEXTURE_BACKBUFFER` - Texture format can be used as back buffer format. + * - `BGFX_CAPS_FORMAT_TEXTURE_VIDEO_DECODE_DST` - Texture format can be used as video + * decode destination. */ uint32_t formats[BGFX_TEXTURE_FORMAT_COUNT]; + + /** + * Supported video codec capabilities flags. A non-zero entry means the codec is + * supported for hardware decode; bits describe sample depths and chroma + * subsamplings: + * - `BGFX_CAPS_VIDEO_CODEC_NONE` - Video codec is not supported. + * - `BGFX_CAPS_VIDEO_CODEC_BIT_8` - 8-bit sample depth is supported. + * - `BGFX_CAPS_VIDEO_CODEC_BIT_10` - 10-bit sample depth is supported. + * - `BGFX_CAPS_VIDEO_CODEC_BIT_12` - 12-bit sample depth is supported. + * - `BGFX_CAPS_VIDEO_CODEC_CHROMA_420` - 4:2:0 chroma subsampling is supported. + * - `BGFX_CAPS_VIDEO_CODEC_CHROMA_422` - 4:2:2 chroma subsampling is supported. + * - `BGFX_CAPS_VIDEO_CODEC_CHROMA_444` - 4:4:4 chroma subsampling is supported. + */ + uint32_t codecs[BGFX_VIDEO_CODEC_COUNT]; } bgfx_caps_t; @@ -751,6 +781,7 @@ typedef struct bgfx_init_s bool debug; /** Enable device for debugging. */ bool profile; /** Enable device for profiling. */ bool fallback; /** Enable fallback to next available renderer. */ + bool videoDecode; /** Enable video decoding. */ bgfx_platform_data_t platformData; /** Platform data. */ bgfx_resolution_t resolution; /** Backbuffer resolution and reset parameters. See: `bgfx::Resolution`. */ bgfx_init_limits_t limits; /** Configurable runtime limits parameters. */ @@ -845,6 +876,81 @@ typedef struct bgfx_texture_info_s } bgfx_texture_info_t; +/** + * Video decoder initialization. Serialized into the Memory passed to + * `createTexture2D`. When the memory blob begins with `magic`, bgfx + * infers the texture is a video decode destination (the caller need not set + * any extra texture flag). Everything else the renderer needs about the + * stream (chroma format, bit depth, profile, level, coded dimensions, DPB + * layout, color metadata) is parsed out of the codec parameter sets at + * create time. + * + */ +typedef struct bgfx_video_decoder_init_s +{ + uint32_t magic; /** Structure magic. Must be `BX_MAKEFOURCC('V', 'D', 'I', '0')`. */ + bgfx_video_codec_t codec; /** Video codec. See: `VideoCodec::Enum`. */ + const uint8_t* parameterSets; /** Codec parameter sets (Annex B for H.264/H.265, OBUs for AV1). */ + uint32_t parameterSetsSize; /** Parameter sets size in bytes. */ + + /** + * Soft cap (in bytes) on the streaming access-unit FIFO (when + * `BGFX_VIDEO_DECODER_INIT_RETAIN` is NOT set). 0 selects the + * default. Ignored in RETAIN mode (the retain cache is unbounded). + */ + uint32_t cachedAuBytes; + uint8_t flags; /** Decoder lifetime flags. See: `BGFX_VIDEO_DECODER_INIT_*`. */ + +} bgfx_video_decoder_init_t; + +/** + * One access unit entry inside a `VideoDecoderFrame` batch. The bitstream + * for the AU lives at offset `Σ aus[0..ii].size` inside the frame's + * `bitstream` buffer (access units are stored back-to-back in decode / + * submission order). + * + */ +typedef struct bgfx_video_decoder_au_s +{ + uint32_t size; /** Access unit size in bytes. */ + int64_t ptsUs; /** Presentation timestamp in microseconds for this access unit (container-provided). */ + +} bgfx_video_decoder_au_t; + +/** + * Video decoder per-frame submission. Serialized into the Memory passed + * to `updateTexture2D` for a video decode destination texture. The + * renderer parses the slice / tile-group header out of the bitstream and + * translates it to the backend-specific decoder arguments. + * + * A single call may submit a batch of access units: `bitstream` is the + * back-to-back concatenation of `numAus` access units, and `aus[ii]` + * holds the size and PTS of each. AUs are enqueued in array order + * (which is the codec's decode order). Set `numAus == 0` (and + * `bitstream == NULL`) for a presentation-only tick that only advances + * the playback clock. + * + * The `bitstream` and `aus` pointers must remain valid until bgfx has + * consumed the submission (`bgfx::copy` only deep-copies the + * `VideoDecoderFrame` struct itself, not the buffers it references). + * + */ +typedef struct bgfx_video_decoder_frame_s +{ + uint32_t magic; /** Structure magic. Must be `BX_MAKEFOURCC('V', 'D', 'F', '0')`. */ + const uint8_t* bitstream; /** Concatenated access-unit bitstream (decode order). NULL for presentation-only ticks. */ + const bgfx_video_decoder_au_t* aus; /** Per-AU size and PTS array. NULL when `numAus == 0`. */ + uint32_t numAus; /** Number of access units in this batch. 0 for presentation-only ticks. */ + + /** + * Current playback wall-clock time. Driver dispatches the picture whose `ptsUs` + * best matches. Must be monotonically non-decreasing between non-`SET` calls. + */ + int64_t presentationTimeUs; + uint8_t flags; /** Per-frame submission flags. See: `BGFX_VIDEO_DECODE_FRAME_*`. */ + +} bgfx_video_decoder_frame_t; + /** * Uniform info. * @@ -1850,6 +1956,25 @@ BGFX_C_API void bgfx_destroy_program(bgfx_program_handle_t _handle); */ BGFX_C_API bool bgfx_is_texture_valid(uint16_t _depth, bool _cubeMap, uint16_t _numLayers, bgfx_texture_format_t _format, uint64_t _flags); +/** + * Validate video codec parameters. Use to check whether the requested + * combination of codec / bit depth / chroma / dimensions / DPB layout can + * be hardware decoded on the current device. Coarse capability discovery + * is `Caps::supported & BGFX_CAPS_VIDEO_DECODE` and `Caps::codecs[]`. + * + * @param[in] _codec Video codec. See: `VideoCodec::Enum`. + * @param[in] _chroma Chroma subsampling. 0 = 4:2:0, 2 = 4:2:2, 4 = 4:4:4. + * @param[in] _bitDepth Bit depth per component. 8, 10 or 12. + * @param[in] _codedWidth Coded picture width (macroblock / CTU / superblock aligned). + * @param[in] _codedHeight Coded picture height. + * @param[in] _maxDpbSlots Maximum decoded picture buffer slot count. + * @param[in] _maxActiveReferences Maximum number of reference frames active at once. + * + * @returns True if a video decoder with the same parameters can be created. + * + */ +BGFX_C_API bool bgfx_is_video_codec_valid(bgfx_video_codec_t _codec, uint8_t _chroma, uint8_t _bitDepth, uint16_t _codedWidth, uint16_t _codedHeight, uint8_t _maxDpbSlots, uint8_t _maxActiveReferences); + /** * Validate frame buffer parameters. * @@ -3863,6 +3988,7 @@ typedef enum bgfx_function_id BGFX_FUNCTION_ID_CREATE_COMPUTE_PROGRAM, BGFX_FUNCTION_ID_DESTROY_PROGRAM, BGFX_FUNCTION_ID_IS_TEXTURE_VALID, + BGFX_FUNCTION_ID_IS_VIDEO_CODEC_VALID, BGFX_FUNCTION_ID_IS_FRAME_BUFFER_VALID, BGFX_FUNCTION_ID_CALC_TEXTURE_SIZE, BGFX_FUNCTION_ID_CREATE_TEXTURE, @@ -4074,6 +4200,7 @@ struct bgfx_interface_vtbl bgfx_program_handle_t (*create_compute_program)(bgfx_shader_handle_t _csh, bool _destroyShaders); void (*destroy_program)(bgfx_program_handle_t _handle); bool (*is_texture_valid)(uint16_t _depth, bool _cubeMap, uint16_t _numLayers, bgfx_texture_format_t _format, uint64_t _flags); + bool (*is_video_codec_valid)(bgfx_video_codec_t _codec, uint8_t _chroma, uint8_t _bitDepth, uint16_t _codedWidth, uint16_t _codedHeight, uint8_t _maxDpbSlots, uint8_t _maxActiveReferences); bool (*is_frame_buffer_valid)(uint8_t _num, const bgfx_attachment_t* _attachment); void (*calc_texture_size)(bgfx_texture_info_t * _info, uint16_t _width, uint16_t _height, uint16_t _depth, bool _cubeMap, bool _hasMips, uint16_t _numLayers, bgfx_texture_format_t _format); bgfx_texture_handle_t (*create_texture)(const bgfx_memory_t* _mem, uint64_t _flags, uint8_t _skip, bgfx_texture_info_t* _info); diff --git a/include/bgfx/defines.h b/include/bgfx/defines.h index b2d758ce2..893a51f20 100644 --- a/include/bgfx/defines.h +++ b/include/bgfx/defines.h @@ -15,7 +15,7 @@ #ifndef BGFX_DEFINES_H_HEADER_GUARD #define BGFX_DEFINES_H_HEADER_GUARD -#define BGFX_API_VERSION UINT32_C(145) +#define BGFX_API_VERSION UINT32_C(146) /** * Color RGB/alpha/depth write. When it's not specified write will be disabled. @@ -313,6 +313,13 @@ #define BGFX_TEXTURE_READ_BACK UINT64_C(0x0000800000000000) //!< Texture will be used for read back from GPU. #define BGFX_TEXTURE_EXTERNAL_SHARED UINT64_C(0x0001000000000000) //!< Texture is shared with other device or other process. +/** + * Do not use! Top nibble is reserved for internal texture flags (see bgfx_p.h). + * + */ +#define BGFX_TEXTURE_RESERVED_SHIFT 60 +#define BGFX_TEXTURE_RESERVED_MASK UINT64_C(0xf000000000000000) + #define BGFX_TEXTURE_RT_MSAA_X2 UINT64_C(0x0000002000000000) //!< Render target MSAAx2 mode. #define BGFX_TEXTURE_RT_MSAA_X4 UINT64_C(0x0000003000000000) //!< Render target MSAAx4 mode. #define BGFX_TEXTURE_RT_MSAA_X8 UINT64_C(0x0000004000000000) //!< Render target MSAAx8 mode. @@ -478,7 +485,8 @@ #define BGFX_CAPS_VERTEX_ATTRIB_HALF UINT64_C(0x0000000040000000) //!< Vertex attribute half-float is supported. #define BGFX_CAPS_VERTEX_ATTRIB_UINT10 UINT64_C(0x0000000080000000) //!< Vertex attribute 10_10_10_2 is supported. #define BGFX_CAPS_VERTEX_ID UINT64_C(0x0000000100000000) //!< Rendering with VertexID only is supported. -#define BGFX_CAPS_VIEWPORT_LAYER_ARRAY UINT64_C(0x0000000200000000) //!< Viewport layer is available in vertex shader. +#define BGFX_CAPS_VIDEO_DECODE UINT64_C(0x0000000200000000) //!< Hardware video decode is supported. +#define BGFX_CAPS_VIEWPORT_LAYER_ARRAY UINT64_C(0x0000000400000000) //!< Viewport layer is available in vertex shader. /// All texture compare modes are supported. #define BGFX_CAPS_TEXTURE_COMPARE_ALL (0 \ | BGFX_CAPS_TEXTURE_COMPARE_RESERVED \ @@ -504,6 +512,53 @@ #define BGFX_CAPS_FORMAT_TEXTURE_MSAA UINT32_C(0x00004000) //!< Texture can be sampled as MSAA. #define BGFX_CAPS_FORMAT_TEXTURE_MIP_AUTOGEN UINT32_C(0x00008000) //!< Texture format supports auto-generated mips. #define BGFX_CAPS_FORMAT_TEXTURE_BACKBUFFER UINT32_C(0x00010000) //!< Texture format can be used as back buffer format. +#define BGFX_CAPS_FORMAT_TEXTURE_VIDEO_DECODE_DST UINT32_C(0x00020000) //!< Texture format can be used as video decode destination. + +#define BGFX_CAPS_VIDEO_CODEC_NONE UINT32_C(0x00000000) //!< Video codec is not supported. +#define BGFX_CAPS_VIDEO_CODEC_BIT_8 UINT32_C(0x00000001) //!< 8-bit sample depth is supported. +#define BGFX_CAPS_VIDEO_CODEC_BIT_10 UINT32_C(0x00000002) //!< 10-bit sample depth is supported. +#define BGFX_CAPS_VIDEO_CODEC_BIT_12 UINT32_C(0x00000004) //!< 12-bit sample depth is supported. +#define BGFX_CAPS_VIDEO_CODEC_CHROMA_420 UINT32_C(0x00000008) //!< 4:2:0 chroma subsampling is supported. +#define BGFX_CAPS_VIDEO_CODEC_CHROMA_422 UINT32_C(0x00000010) //!< 4:2:2 chroma subsampling is supported. +#define BGFX_CAPS_VIDEO_CODEC_CHROMA_444 UINT32_C(0x00000020) //!< 4:4:4 chroma subsampling is supported. + +/** + * Video decoder lifetime flags (per `VideoDecoderInit::flags`). + * + */ +#define BGFX_VIDEO_DECODER_INIT_NONE UINT8_C(0x00) //!< No flags. + +/// Cache submitted access units in driver-managed memory keyed by `ptsUs` so the +/// presentation clock can revisit / loop without re-streaming. The cache is +/// unbounded: the app picks the total cache size implicitly by choosing how +/// many access units to submit. Without this flag access units are decoded once +/// and dropped (streaming default). +#define BGFX_VIDEO_DECODER_INIT_RETAIN UINT8_C(0x01) + +/** + * Video decoder per-frame submission flags (per `VideoDecoderFrame::flags`). + * + */ +#define BGFX_VIDEO_DECODE_FRAME_NONE UINT8_C(0x00) //!< No flags. + +/// First batch after a position change. The first access unit must be a clean IDR. +/// Driver flushes its DPB, queued access units, and reorder pool before decoding; +/// subsequent `presentationTimeUs` values may land anywhere (monotonicity is only +/// required between non-`Set` ticks). +#define BGFX_VIDEO_DECODE_FRAME_SET UINT8_C(0x01) + +/// Skip the picker dispatch for this call. Useful while bulk-loading access units +/// so the displayed picture isn't churned mid-load. +#define BGFX_VIDEO_DECODE_FRAME_NO_BLIT UINT8_C(0x02) + +/// Marks the last access unit of the clip; permits eager pre-decode in idle time +/// and lets the picker emit the final frame without lookahead stalling. +#define BGFX_VIDEO_DECODE_FRAME_FINAL UINT8_C(0x04) + +/// When `presentationTimeUs` runs past the highest cached `ptsUs`, the picker +/// wraps modulo the cached pts range. Without this flag the picker freezes on +/// the last displayable picture. +#define BGFX_VIDEO_DECODE_FRAME_LOOP UINT8_C(0x08) #define BGFX_RESOLVE_NONE UINT8_C(0x00) //!< No resolve flags. #define BGFX_RESOLVE_AUTO_GEN_MIPS UINT8_C(0x01) //!< Auto-generate mip maps on resolve. diff --git a/scripts/bgfx.idl b/scripts/bgfx.idl index d4c0b0f78..06bc035eb 100644 --- a/scripts/bgfx.idl +++ b/scripts/bgfx.idl @@ -1,7 +1,7 @@ -- vim: syntax=lua -- bgfx interface -version(145) +version(146) typedef "bool" typedef "char" @@ -251,16 +251,19 @@ flag.Buffer { bits = 16, base = 8 } () flag.Texture { bits = 64, section = "Textures" } - .None (0) - .MsaaSample (36) --- Texture will be used for MSAA sampling. - .Rt (37) --- Render target no MSAA. - .ComputeWrite (45) --- Texture will be used for compute write. - .Srgb (46) --- Sample texture as sRGB. - .BlitDst (47) --- Texture will be used as blit destination. - .ReadBack (48) --- Texture will be used for read back from GPU. - .ExternalShared (49) --- Texture is shared with other device or other process. + .None (0) + .MsaaSample (36) --- Texture will be used for MSAA sampling. + .Rt (37) --- Render target no MSAA. + .ComputeWrite (45) --- Texture will be used for compute write. + .Srgb (46) --- Sample texture as sRGB. + .BlitDst (47) --- Texture will be used as blit destination. + .ReadBack (48) --- Texture will be used for read back from GPU. + .ExternalShared (49) --- Texture is shared with other device or other process. () +--- Do not use! Top nibble is reserved for internal texture flags (see bgfx_p.h). +flag.TextureReserved { bits = 64, shift = 60, range = 4 } + flag.TextureRtMsaa { bits = 64, shift = 36, range = 3, base = 2 } .X2 --- Render target MSAAx2 mode. .X4 --- Render target MSAAx4 mode. @@ -399,6 +402,7 @@ flag.Caps { bits = 64, base = 1, name = "Caps", section = "Available Caps" } .VertexAttribHalf --- Vertex attribute half-float is supported. .VertexAttribUint10 --- Vertex attribute 10_10_10_2 is supported. .VertexId --- Rendering with VertexID only is supported. + .VideoDecode --- Hardware video decode is supported. .ViewportLayerArray --- Viewport layer is available in vertex shader. .TextureCompareAll --- All texture compare modes are supported. @@ -424,6 +428,43 @@ flag.CapsFormat { bits = 32 } .TextureMsaa --- Texture can be sampled as MSAA. .TextureMipAutogen --- Texture format supports auto-generated mips. .TextureBackbuffer --- Texture format can be used as back buffer format. + .TextureVideoDecodeDst --- Texture format can be used as video decode destination. + () + +flag.CapsVideoCodec { bits = 32 } + .None --- Video codec is not supported. + .Bit_8 --- 8-bit sample depth is supported. + .Bit_10 --- 10-bit sample depth is supported. + .Bit_12 --- 12-bit sample depth is supported. + .Chroma_420 --- 4:2:0 chroma subsampling is supported. + .Chroma_422 --- 4:2:2 chroma subsampling is supported. + .Chroma_444 --- 4:4:4 chroma subsampling is supported. + () + +--- Video decoder lifetime flags (per `VideoDecoderInit::flags`). +flag.VideoDecoderInit { bits = 8 } + .None --- No flags. + .Retain --- Cache submitted access units in driver-managed memory keyed by `ptsUs` so the + --- presentation clock can revisit / loop without re-streaming. The cache is + --- unbounded: the app picks the total cache size implicitly by choosing how + --- many access units to submit. Without this flag access units are decoded once + --- and dropped (streaming default). + () + +--- Video decoder per-frame submission flags (per `VideoDecoderFrame::flags`). +flag.VideoDecodeFrame { bits = 8 } + .None --- No flags. + .Set --- First batch after a position change. The first access unit must be a clean IDR. + --- Driver flushes its DPB, queued access units, and reorder pool before decoding; + --- subsequent `presentationTimeUs` values may land anywhere (monotonicity is only + --- required between non-`Set` ticks). + .NoBlit --- Skip the picker dispatch for this call. Useful while bulk-loading access units + --- so the displayed picture isn't churned mid-load. + .Final --- Marks the last access unit of the clip; permits eager pre-decode in idle time + --- and lets the picker emit the final frame without lookahead stalling. + .Loop --- When `presentationTimeUs` runs past the highest cached `ptsUs`, the picker + --- wraps modulo the cached pts range. Without this flag the picker freezes on + --- the last displayable picture. () flag.Resolve { bits = 8 } @@ -673,6 +714,13 @@ enum.OcclusionQueryResult { comment = "Occlusion query results:", section = "Occ .NoResult --- Query result is not available yet. () +--- Video codec enum. +enum.VideoCodec { comment = "Video codecs:", section = "Video" } + .H264 --- H.264 / AVC. + .H265 --- H.265 / HEVC. + .AV1 --- AV1. + () + --- Primitive topology. enum.Topology { underscore, comment = "Primitive topology:" } .TriList --- Triangle list. @@ -810,6 +858,19 @@ struct.Caps { section = "Capabilities" } --- - `BGFX_CAPS_FORMAT_TEXTURE_MIP_AUTOGEN` - Texture format supports auto-generated --- mips. --- - `BGFX_CAPS_FORMAT_TEXTURE_BACKBUFFER` - Texture format can be used as back buffer format. + --- - `BGFX_CAPS_FORMAT_TEXTURE_VIDEO_DECODE_DST` - Texture format can be used as video + --- decode destination. + .codecs "uint32_t[VideoCodec::Count]" + --- Supported video codec capabilities flags. A non-zero entry means the codec is + --- supported for hardware decode; bits describe sample depths and chroma + --- subsamplings: + --- - `BGFX_CAPS_VIDEO_CODEC_NONE` - Video codec is not supported. + --- - `BGFX_CAPS_VIDEO_CODEC_BIT_8` - 8-bit sample depth is supported. + --- - `BGFX_CAPS_VIDEO_CODEC_BIT_10` - 10-bit sample depth is supported. + --- - `BGFX_CAPS_VIDEO_CODEC_BIT_12` - 12-bit sample depth is supported. + --- - `BGFX_CAPS_VIDEO_CODEC_CHROMA_420` - 4:2:0 chroma subsampling is supported. + --- - `BGFX_CAPS_VIDEO_CODEC_CHROMA_422` - 4:2:2 chroma subsampling is supported. + --- - `BGFX_CAPS_VIDEO_CODEC_CHROMA_444` - 4:4:4 chroma subsampling is supported. --- Internal data. struct.InternalData { section = "Platform specific" } @@ -876,6 +937,7 @@ struct.Init { ctor, section = "Initialization and Shutdown" } .debug "bool" --- Enable device for debugging. .profile "bool" --- Enable device for profiling. .fallback "bool" --- Enable fallback to next available renderer. + .videoDecode "bool" --- Enable video decoding. .platformData "PlatformData" --- Platform data. .resolution "Resolution" --- Backbuffer resolution and reset parameters. See: `bgfx::Resolution`. .limits "Limits" --- Configurable runtime limits parameters. @@ -931,6 +993,55 @@ struct.TextureInfo { section = "Textures" } .bitsPerPixel "uint8_t" --- Format bits per pixel. .cubeMap "bool" --- Texture is cubemap. +--- Video decoder initialization. Serialized into the Memory passed to +--- `createTexture2D`. When the memory blob begins with `magic`, bgfx +--- infers the texture is a video decode destination (the caller need not set +--- any extra texture flag). Everything else the renderer needs about the +--- stream (chroma format, bit depth, profile, level, coded dimensions, DPB +--- layout, color metadata) is parsed out of the codec parameter sets at +--- create time. +struct.VideoDecoderInit { section = "Textures" } + .magic "uint32_t" --- Structure magic. Must be `BX_MAKEFOURCC('V', 'D', 'I', '0')`. + .codec "VideoCodec::Enum" --- Video codec. See: `VideoCodec::Enum`. + .parameterSets "const uint8_t*" --- Codec parameter sets (Annex B for H.264/H.265, OBUs for AV1). + .parameterSetsSize "uint32_t" --- Parameter sets size in bytes. + .cachedAuBytes "uint32_t" --- Soft cap (in bytes) on the streaming access-unit FIFO (when + --- `BGFX_VIDEO_DECODER_INIT_RETAIN` is NOT set). 0 selects the + --- default. Ignored in RETAIN mode (the retain cache is unbounded). + .flags "uint8_t" --- Decoder lifetime flags. See: `BGFX_VIDEO_DECODER_INIT_*`. + +--- One access unit entry inside a `VideoDecoderFrame` batch. The bitstream +--- for the AU lives at offset `Σ aus[0..ii].size` inside the frame's +--- `bitstream` buffer (access units are stored back-to-back in decode / +--- submission order). +struct.VideoDecoderAu { section = "Textures" } + .size "uint32_t" --- Access unit size in bytes. + .ptsUs "int64_t" --- Presentation timestamp in microseconds for this access unit (container-provided). + +--- Video decoder per-frame submission. Serialized into the Memory passed +--- to `updateTexture2D` for a video decode destination texture. The +--- renderer parses the slice / tile-group header out of the bitstream and +--- translates it to the backend-specific decoder arguments. +--- +--- A single call may submit a batch of access units: `bitstream` is the +--- back-to-back concatenation of `numAus` access units, and `aus[ii]` +--- holds the size and PTS of each. AUs are enqueued in array order +--- (which is the codec's decode order). Set `numAus == 0` (and +--- `bitstream == NULL`) for a presentation-only tick that only advances +--- the playback clock. +--- +--- The `bitstream` and `aus` pointers must remain valid until bgfx has +--- consumed the submission (`bgfx::copy` only deep-copies the +--- `VideoDecoderFrame` struct itself, not the buffers it references). +struct.VideoDecoderFrame { section = "Textures" } + .magic "uint32_t" --- Structure magic. Must be `BX_MAKEFOURCC('V', 'D', 'F', '0')`. + .bitstream "const uint8_t*" --- Concatenated access-unit bitstream (decode order). NULL for presentation-only ticks. + .aus "const VideoDecoderAu*" --- Per-AU size and PTS array. NULL when `numAus == 0`. + .numAus "uint32_t" --- Number of access units in this batch. 0 for presentation-only ticks. + .presentationTimeUs "int64_t" --- Current playback wall-clock time. Driver dispatches the picture whose `ptsUs` + --- best matches. Must be monotonically non-decreasing between non-`SET` calls. + .flags "uint8_t" --- Per-frame submission flags. See: `BGFX_VIDEO_DECODE_FRAME_*`. + --- Uniform info. struct.UniformInfo { section = "Uniforms" } .name "char[256]" --- Uniform name. @@ -1694,6 +1805,20 @@ func.isTextureValid { section = "Textures" } .format "TextureFormat::Enum" --- Texture format. See: `TextureFormat::Enum`. .flags "uint64_t" --- Texture flags. See `BGFX_TEXTURE_*`. +--- Validate video codec parameters. Use to check whether the requested +--- combination of codec / bit depth / chroma / dimensions / DPB layout can +--- be hardware decoded on the current device. Coarse capability discovery +--- is `Caps::supported & BGFX_CAPS_VIDEO_DECODE` and `Caps::codecs[]`. +func.isVideoCodecValid { section = "Textures" } + "bool" --- True if a video decoder with the same parameters can be created. + .codec "VideoCodec::Enum" --- Video codec. See: `VideoCodec::Enum`. + .chroma "uint8_t" --- Chroma subsampling. 0 = 4:2:0, 2 = 4:2:2, 4 = 4:4:4. + .bitDepth "uint8_t" --- Bit depth per component. 8, 10 or 12. + .codedWidth "uint16_t" --- Coded picture width (macroblock / CTU / superblock aligned). + .codedHeight "uint16_t" --- Coded picture height. + .maxDpbSlots "uint8_t" --- Maximum decoded picture buffer slot count. + .maxActiveReferences "uint8_t" --- Maximum number of reference frames active at once. + --- Validate frame buffer parameters. func.isFrameBufferValid { section = "Frame Buffers" } "bool" --- True if a frame buffer with the same parameters can be created. diff --git a/scripts/bgfx.lua b/scripts/bgfx.lua index 236fc2a5b..09dd51f24 100644 --- a/scripts/bgfx.lua +++ b/scripts/bgfx.lua @@ -145,6 +145,9 @@ function bgfxProjectBase(_kind, _defines) "-framework QuartzCore", "-weak_framework Metal", "-weak_framework MetalKit", + "-weak_framework VideoToolbox", + "-weak_framework CoreMedia", + "-weak_framework CoreVideo", } configuration { "not NX32", "not NX64" } diff --git a/scripts/genie.lua b/scripts/genie.lua index 6116b11fc..8a8c04bcb 100644 --- a/scripts/genie.lua +++ b/scripts/genie.lua @@ -387,6 +387,9 @@ function exampleProjectDefaults() "-framework OpenGL", "-framework QuartzCore", "-weak_framework Metal", + "-weak_framework VideoToolbox", + "-weak_framework CoreMedia", + "-weak_framework CoreVideo", } configuration { "ios* or tvos*" } @@ -399,6 +402,9 @@ function exampleProjectDefaults() "-framework QuartzCore", "-framework UIKit", "-weak_framework Metal", + "-weak_framework VideoToolbox", + "-weak_framework CoreMedia", + "-weak_framework CoreVideo", } configuration { "xcode*", "ios" } diff --git a/scripts/geometryv.lua b/scripts/geometryv.lua index ec6d2ffe9..82445ee85 100644 --- a/scripts/geometryv.lua +++ b/scripts/geometryv.lua @@ -140,6 +140,9 @@ project ("geometryv") "-framework Metal", "-framework OpenGL", "-framework QuartzCore", + "-weak_framework VideoToolbox", + "-weak_framework CoreMedia", + "-weak_framework CoreVideo", } configuration { "ios*" } diff --git a/scripts/texturev.lua b/scripts/texturev.lua index bf71554af..59a6d60b5 100644 --- a/scripts/texturev.lua +++ b/scripts/texturev.lua @@ -3,6 +3,57 @@ -- License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE -- +group "tools/texturev" + +project "l-smash" + kind "StaticLib" + + removeflags { "FatalWarnings" } + + includedirs { + path.join(MODULE_DIR, "3rdparty/l-smash"), + } + + files { + path.join(MODULE_DIR, "3rdparty/l-smash/**.c"), + path.join(MODULE_DIR, "3rdparty/l-smash/**.h"), + } + + configuration { "vs*" } + buildoptions { + "/wd4005", -- warning C4005: '_CRT_SECURE_NO_WARNINGS': macro redefinition + "/wd4018", -- warning C4018: '<=': signed/unsigned mismatch + "/wd4100", -- warning C4100: 'class': unreferenced parameter + "/wd4116", -- warning C4116: unnamed type definition in parentheses + "/wd4125", -- warning C4125: decimal digit terminates octal escape sequence + "/wd4133", -- warning C4133: '=': incompatible types - from 'lsmash_brand_type *' to 'uint32_t *' + "/wd4210", -- warning C4210: nonstandard extension used: function given file scope + "/wd4244", -- warning C4244: 'initializing': conversion from 'uint64_t' to 'uint32_t', possible loss of data + "/wd4245", -- warning C4245: '=': conversion from 'int' to 'uint32_t', signed/unsigned mismatch + "/wd4267", -- warning C4267: 'initializing': conversion from 'size_t' to 'int', possible loss of data + "/wd4389", -- warning C4389: '!=': signed/unsigned mismatch + "/wd4701", -- warning C4701: potentially uninitialized local variable 'src_movie_timescale' used + } + + configuration { "*-gcc or *-clang" } + buildoptions { + "-Wno-empty-body", + "-Wno-implicit-fallthrough", + "-Wno-missing-field-initializers", + "-Wno-pragmas", + "-Wno-sign-compare", + "-Wno-type-limits", + "-Wno-undef", + "-Wno-unused-function", + "-Wno-unused-parameter", + } + + configuration { "mingw*" } + buildoptions { + "-Wno-maybe-uninitialized", + "-Wno-stringop-overflow", + } + project "texturev" uuid (os.uuid("texturev") ) kind "ConsoleApp" @@ -23,6 +74,7 @@ project "texturev" } links { + "l-smash", "example-common", "bimg_decode", "bimg", @@ -148,6 +200,9 @@ project "texturev" "-framework Metal", "-framework OpenGL", "-framework QuartzCore", + "-weak_framework VideoToolbox", + "-weak_framework CoreMedia", + "-weak_framework CoreVideo", } configuration { "ios*" } @@ -174,3 +229,5 @@ project "texturev" configuration {} strip() + +group "tools" diff --git a/src/amalgamated.cpp b/src/amalgamated.cpp index e26f493c2..1514a313c 100644 --- a/src/amalgamated.cpp +++ b/src/amalgamated.cpp @@ -20,6 +20,10 @@ #include "renderer_nvn.cpp" #include "renderer_vk.cpp" #include "renderer_webgpu.cpp" +#include "video_d3d11.cpp" +#include "video_d3d12.cpp" +#include "video_mtl.cpp" +#include "video_vk.cpp" #include "shader.cpp" #include "topology.cpp" #include "vertexlayout.cpp" diff --git a/src/bgfx.cpp b/src/bgfx.cpp index 86e235bbf..4a0e12732 100644 --- a/src/bgfx.cpp +++ b/src/bgfx.cpp @@ -27,6 +27,7 @@ BX_ERROR_RESULT(BGFX_ERROR_TEXTURE_VALIDATION, BX_MAKEFOURCC('b', 'g', 0, 1) ); BX_ERROR_RESULT(BGFX_ERROR_FRAME_BUFFER_VALIDATION, BX_MAKEFOURCC('b', 'g', 0, 2) ); BX_ERROR_RESULT(BGFX_ERROR_IDENTIFIER_VALIDATION, BX_MAKEFOURCC('b', 'g', 0, 3) ); +BX_ERROR_RESULT(BGFX_ERROR_VIDEO_CODEC_VALIDATION, BX_MAKEFOURCC('b', 'g', 0, 4) ); namespace bgfx { @@ -583,6 +584,7 @@ namespace bgfx #include "cs_mipgen_oddx.bin.h" #include "cs_mipgen_oddy.bin.h" #include "cs_mipgen_oddxy.bin.h" +#include "cs_yuv_to_rgb.bin.h" static const EmbeddedShader s_embeddedShaders[] = { @@ -601,6 +603,7 @@ namespace bgfx BGFX_EMBEDDED_SHADER(cs_mipgen_oddx), BGFX_EMBEDDED_SHADER(cs_mipgen_oddy), BGFX_EMBEDDED_SHADER(cs_mipgen_oddxy), + BGFX_EMBEDDED_SHADER(cs_yuv_to_rgb), BGFX_EMBEDDED_SHADER_END() }; @@ -1119,6 +1122,64 @@ namespace bgfx } } + VideoDecode* g_videoDecode = NULL; + + void VideoDecode::init() + { + BGFX_CHECK_API_THREAD(); + + switch (g_caps.rendererType) + { + case RendererType::Metal: + case RendererType::Direct3D11: + case RendererType::Direct3D12: + case RendererType::Vulkan: + break; + + default: + return; + } + + ShaderHandle csh = createEmbeddedShader(s_embeddedShaders, g_caps.rendererType, "cs_yuv_to_rgb"); + BX_ASSERT(isValid(csh), "Failed to create video decode embedded compute shader."); + + if (isValid(csh) ) + { + m_program = createProgram(csh, true); + BX_ASSERT(isValid(m_program), "Failed to create video decode program."); + } + + s_texY = createUniform("bgfx_texY", bgfx::UniformType::Sampler); + s_texCbCr = createUniform("bgfx_texCbCr", bgfx::UniformType::Sampler); + + g_videoDecode = this; + } + + void VideoDecode::shutdown() + { + BGFX_CHECK_API_THREAD(); + + g_videoDecode = NULL; + + if (isValid(m_program) ) + { + destroy(m_program); + m_program = BGFX_INVALID_HANDLE; + } + + if (isValid(s_texY) ) + { + destroy(s_texY); + s_texY = BGFX_INVALID_HANDLE; + } + + if (isValid(s_texCbCr) ) + { + destroy(s_texCbCr); + s_texCbCr = BGFX_INVALID_HANDLE; + } + } + const char* s_uniformTypeName[] = { "sampler1", @@ -1758,6 +1819,7 @@ namespace bgfx CAPS_FLAGS(BGFX_CAPS_VERTEX_ATTRIB_HALF), CAPS_FLAGS(BGFX_CAPS_VERTEX_ATTRIB_UINT10), CAPS_FLAGS(BGFX_CAPS_VERTEX_ID), + CAPS_FLAGS(BGFX_CAPS_VIDEO_DECODE), CAPS_FLAGS(BGFX_CAPS_VIEWPORT_LAYER_ARRAY), #undef CAPS_FLAGS }; @@ -1931,6 +1993,37 @@ namespace bgfx } } + BX_TRACE(""); + BX_TRACE("Supported video decode codecs:"); + BX_TRACE("\t +----- 8-bit sample depth"); + BX_TRACE("\t |+---- 10-bit sample depth"); + BX_TRACE("\t ||+--- 12-bit sample depth"); + BX_TRACE("\t |||+-- 4:2:0 chroma subsampling"); + BX_TRACE("\t ||||+- 4:2:2 chroma subsampling"); + BX_TRACE("\t |||||+ 4:4:4 chroma subsampling"); + BX_TRACE("\t |||||| +-- name"); + static const char* s_videoCodecName[] = + { + "H264", + "H265", + "AV1", + }; + static_assert(BX_COUNTOF(s_videoCodecName) == VideoCodec::Count); + for (uint32_t ii = 0; ii < VideoCodec::Count; ++ii) + { + uint32_t flags = g_caps.codecs[ii]; + BX_TRACE("\t[%c%c%c%c%c%c] %s" + , flags & BGFX_CAPS_VIDEO_CODEC_BIT_8 ? '8' : ' ' + , flags & BGFX_CAPS_VIDEO_CODEC_BIT_10 ? 'a' : ' ' + , flags & BGFX_CAPS_VIDEO_CODEC_BIT_12 ? 'c' : ' ' + , flags & BGFX_CAPS_VIDEO_CODEC_CHROMA_420 ? '0' : ' ' + , flags & BGFX_CAPS_VIDEO_CODEC_CHROMA_422 ? '2' : ' ' + , flags & BGFX_CAPS_VIDEO_CODEC_CHROMA_444 ? '4' : ' ' + , s_videoCodecName[ii] + ); + BX_UNUSED(flags); + } + BX_TRACE(""); BX_TRACE("NDC depth [%d, 1], origin %s left." , g_caps.homogeneousDepth ? -1 : 0 @@ -2258,6 +2351,7 @@ namespace bgfx m_textVideoMemBlitter.init(m_init.resolution.debugTextScale); m_clearQuad.init(); m_mipGen.init(); + m_videoDecode.init(); m_submit->m_transientVb = createTransientVertexBuffer(_init.limits.maxTransientVbSize); m_submit->m_transientIb = createTransientIndexBuffer(_init.limits.maxTransientIbSize); @@ -2285,6 +2379,7 @@ namespace bgfx m_textVideoMemBlitter.shutdown(); m_clearQuad.shutdown(); m_mipGen.shutdown(); + m_videoDecode.shutdown(); frame(); if (BX_ENABLED(BGFX_CONFIG_MULTITHREADED) ) @@ -3787,6 +3882,7 @@ namespace bgfx , debug(BX_ENABLED(BGFX_CONFIG_DEBUG) ) , profile(BX_ENABLED(BGFX_CONFIG_DEBUG_ANNOTATION) ) , fallback(true) + , videoDecode(false) , callback(NULL) , allocator(NULL) { @@ -5138,6 +5234,121 @@ namespace bgfx return err.isOk(); } + static void isVideoCodecValid(VideoCodec::Enum _codec, uint8_t _chroma, uint8_t _bitDepth, uint16_t _codedWidth, uint16_t _codedHeight, uint8_t _maxDpbSlots, uint8_t _maxActiveReferences, bx::Error* _err) + { + BX_ERROR_SCOPE(_err, "Video codec validation"); + + BGFX_ERROR_CHECK(false + || 0 != (g_caps.supported & BGFX_CAPS_VIDEO_DECODE) + , _err + , BGFX_ERROR_VIDEO_CODEC_VALIDATION + , "Hardware video decode is not supported! " + "Use bgfx::getCaps to check `BGFX_CAPS_VIDEO_DECODE` backend renderer capabilities. " + "Init::videoDecode must be enabled at bgfx::init()." + , "" + ); + + BGFX_ERROR_CHECK(false + || _codec < VideoCodec::Count + , _err + , BGFX_ERROR_VIDEO_CODEC_VALIDATION + , "Invalid video codec." + , "Video codec: %d (Max: %d)." + , _codec + , VideoCodec::Count - 1 + ); + + const uint32_t codecCaps = g_caps.codecs[_codec]; + + uint32_t depthBit = 0; + switch (_bitDepth) + { + case 8: depthBit = BGFX_CAPS_VIDEO_CODEC_BIT_8; break; + case 10: depthBit = BGFX_CAPS_VIDEO_CODEC_BIT_10; break; + case 12: depthBit = BGFX_CAPS_VIDEO_CODEC_BIT_12; break; + default: break; + } + + BGFX_ERROR_CHECK(false + || 0 != depthBit + , _err + , BGFX_ERROR_VIDEO_CODEC_VALIDATION + , "Unsupported video bit depth (must be 8, 10 or 12)." + , "Bit depth: %d." + , _bitDepth + ); + + BGFX_ERROR_CHECK(false + || 0 != (codecCaps & depthBit) + , _err + , BGFX_ERROR_VIDEO_CODEC_VALIDATION + , "Video codec does not support requested bit depth on this device." + , "Codec: %d, bit depth: %d." + , _codec + , _bitDepth + ); + + uint32_t chromaBit = 0; + switch (_chroma) + { + case 0: chromaBit = BGFX_CAPS_VIDEO_CODEC_CHROMA_420; break; + case 2: chromaBit = BGFX_CAPS_VIDEO_CODEC_CHROMA_422; break; + case 4: chromaBit = BGFX_CAPS_VIDEO_CODEC_CHROMA_444; break; + default: break; + } + + BGFX_ERROR_CHECK(false + || 0 != chromaBit + , _err + , BGFX_ERROR_VIDEO_CODEC_VALIDATION + , "Invalid chroma subsampling value (must be 0 = 4:2:0, 2 = 4:2:2, 4 = 4:4:4)." + , "Chroma: %d." + , _chroma + ); + + BGFX_ERROR_CHECK(false + || 0 != (codecCaps & chromaBit) + , _err + , BGFX_ERROR_VIDEO_CODEC_VALIDATION + , "Video codec does not support requested chroma subsampling on this device." + , "Codec: %d, chroma: %d." + , _codec + , _chroma + ); + + BGFX_ERROR_CHECK(false + || (0 != _codedWidth + && 0 != _codedHeight + && _codedWidth <= g_caps.limits.maxTextureSize + && _codedHeight <= g_caps.limits.maxTextureSize) + , _err + , BGFX_ERROR_VIDEO_CODEC_VALIDATION + , "Coded picture dimensions are invalid or above the `maxTextureSize` limit." + , "Coded width x height requested %d x %d (Max: %d)." + , _codedWidth + , _codedHeight + , g_caps.limits.maxTextureSize + ); + + BGFX_ERROR_CHECK(false + || (0 != _maxDpbSlots + && _maxActiveReferences <= _maxDpbSlots) + , _err + , BGFX_ERROR_VIDEO_CODEC_VALIDATION + , "Invalid DPB layout: maxDpbSlots must be > 0 and maxActiveReferences must be <= maxDpbSlots." + , "maxDpbSlots: %d, maxActiveReferences: %d." + , _maxDpbSlots + , _maxActiveReferences + ); + } + + bool isVideoCodecValid(VideoCodec::Enum _codec, uint8_t _chroma, uint8_t _bitDepth, uint16_t _codedWidth, uint16_t _codedHeight, uint8_t _maxDpbSlots, uint8_t _maxActiveReferences) + { + bx::Error err; + isVideoCodecValid(_codec, _chroma, _bitDepth, _codedWidth, _codedHeight, _maxDpbSlots, _maxActiveReferences, &err); + return err.isOk(); + } + void isIdentifierValid(const bx::StringView& _name, bx::Error* _err) { BX_ERROR_SCOPE(_err, "Uniform identifier validation"); @@ -5236,7 +5447,22 @@ namespace bgfx } bx::ErrorAssert err; - isTextureValid(_width, _height, 0, false, _numLayers, _format, _flags, &err); + + uint64_t flags = _flags; + if (NULL != _mem + && _mem->size >= sizeof(VideoDecoderInit) ) + { + VideoDecoderInit init; + bx::memCopy(&init, _mem->data, sizeof(init) ); + + if (kVideoDecoderInitMagic == init.magic + && init.codec < VideoCodec::Count) + { + flags |= BGFX_TEXTURE_INTERNAL_VIDEO_DECODE_DST; + } + } + + isTextureValid(_width, _height, 0, false, _numLayers, _format, flags, &err); if (!err.isOk() ) { @@ -5247,7 +5473,8 @@ namespace bgfx _numLayers = bx::max(_numLayers, 1); if (BX_ENABLED(BGFX_CONFIG_DEBUG) - && NULL != _mem) + && NULL != _mem + && 0 == (flags & BGFX_TEXTURE_INTERNAL_VIDEO_DECODE_DST) ) { TextureInfo ti; calcTextureSize(ti, _width, _height, 1, false, _hasMips, _numLayers, _format); @@ -5275,7 +5502,11 @@ namespace bgfx tc.m_mem = _mem; bx::write(&writer, tc, bx::ErrorAssert{}); - return s_ctx->createTexture(mem, _flags, 0, NULL, _ratio, NULL != _mem, _external); + const bool immutable = true + && NULL != _mem + && 0 == (flags & BGFX_TEXTURE_INTERNAL_VIDEO_DECODE_DST) + ; + return s_ctx->createTexture(mem, flags, 0, NULL, _ratio, immutable, _external); } TextureHandle createTexture2D(uint16_t _width, uint16_t _height, bool _hasMips, uint16_t _numLayers, TextureFormat::Enum _format, uint64_t _flags, const Memory* _mem, uint64_t _external) diff --git a/src/bgfx.idl.inl b/src/bgfx.idl.inl index efc1acfd8..48f47e2f4 100644 --- a/src/bgfx.idl.inl +++ b/src/bgfx.idl.inl @@ -445,6 +445,11 @@ BGFX_C_API bool bgfx_is_texture_valid(uint16_t _depth, bool _cubeMap, uint16_t _ return bgfx::isTextureValid(_depth, _cubeMap, _numLayers, (bgfx::TextureFormat::Enum)_format, _flags); } +BGFX_C_API bool bgfx_is_video_codec_valid(bgfx_video_codec_t _codec, uint8_t _chroma, uint8_t _bitDepth, uint16_t _codedWidth, uint16_t _codedHeight, uint8_t _maxDpbSlots, uint8_t _maxActiveReferences) +{ + return bgfx::isVideoCodecValid((bgfx::VideoCodec::Enum)_codec, _chroma, _bitDepth, _codedWidth, _codedHeight, _maxDpbSlots, _maxActiveReferences); +} + BGFX_C_API bool bgfx_is_frame_buffer_valid(uint8_t _num, const bgfx_attachment_t* _attachment) { return bgfx::isFrameBufferValid(_num, (const bgfx::Attachment*)_attachment); @@ -1396,6 +1401,7 @@ BGFX_C_API bgfx_interface_vtbl_t* bgfx_get_interface(uint32_t _version) bgfx_create_compute_program, bgfx_destroy_program, bgfx_is_texture_valid, + bgfx_is_video_codec_valid, bgfx_is_frame_buffer_valid, bgfx_calc_texture_size, bgfx_create_texture, diff --git a/src/bgfx_p.h b/src/bgfx_p.h index 3bd496cd8..e9a042417 100644 --- a/src/bgfx_p.h +++ b/src/bgfx_p.h @@ -218,13 +218,15 @@ namespace stl = std; #define BGFX_MAX_COMPUTE_BINDINGS BGFX_CONFIG_MAX_TEXTURE_SAMPLERS -#define BGFX_SAMPLER_INTERNAL_DEFAULT UINT32_C(0x10000000) -#define BGFX_SAMPLER_INTERNAL_SHARED UINT32_C(0x20000000) +#define BGFX_SAMPLER_INTERNAL_DEFAULT UINT32_C(0x10000000) +#define BGFX_SAMPLER_INTERNAL_SHARED UINT32_C(0x20000000) -#define BGFX_RESET_INTERNAL_FORCE UINT32_C(0x80000000) +#define BGFX_TEXTURE_INTERNAL_VIDEO_DECODE_DST UINT64_C(0x1000000000000000) -#define BGFX_STATE_INTERNAL_SCISSOR UINT64_C(0x2000000000000000) -#define BGFX_STATE_INTERNAL_OCCLUSION_QUERY UINT64_C(0x4000000000000000) +#define BGFX_RESET_INTERNAL_FORCE UINT32_C(0x80000000) + +#define BGFX_STATE_INTERNAL_SCISSOR UINT64_C(0x2000000000000000) +#define BGFX_STATE_INTERNAL_OCCLUSION_QUERY UINT64_C(0x4000000000000000) #define BGFX_SUBMIT_INTERNAL_NONE UINT8_C(0x00) #define BGFX_SUBMIT_INTERNAL_INDEX32 UINT8_C(0x40) @@ -285,7 +287,9 @@ namespace stl = std; namespace bgfx { - static constexpr uint32_t kChunkMagicTex = BX_MAKEFOURCC('T', 'E', 'X', 0x0); + static constexpr uint32_t kChunkMagicTex = BX_MAKEFOURCC('T', 'E', 'X', 0x0); + static constexpr uint32_t kVideoDecoderInitMagic = BX_MAKEFOURCC('V', 'D', 'I', 0x0); + static constexpr uint32_t kVideoDecoderFrameMagic = BX_MAKEFOURCC('V', 'D', 'F', 0x0); // Palette: // https://colorkit.co/color-palette-generator/a8e6cf-dcedc1-ffd3b6-76b4bd-bdeaee-8874a3-ff0000-ff8b94/ @@ -926,6 +930,26 @@ namespace bgfx UniformHandle s_texMipSrc; }; + /// + struct VideoDecode + { + VideoDecode() + : m_program(BGFX_INVALID_HANDLE) + , s_texY(BGFX_INVALID_HANDLE) + , s_texCbCr(BGFX_INVALID_HANDLE) + { + } + + void init(); + void shutdown(); + + ProgramHandle m_program; + UniformHandle s_texY; + UniformHandle s_texCbCr; + }; + + extern VideoDecode* g_videoDecode; + struct PredefinedUniform { enum Enum @@ -6166,6 +6190,7 @@ namespace bgfx TextVideoMemBlitter m_textVideoMemBlitter; ClearQuad m_clearQuad; MipGen m_mipGen; + VideoDecode m_videoDecode; RendererContextI* m_renderCtx; diff --git a/src/config.h b/src/config.h index 9281d5ecf..5a752b361 100644 --- a/src/config.h +++ b/src/config.h @@ -175,6 +175,33 @@ # error "Can't define both BGFX_CONFIG_RENDERER_OPENGL and BGFX_CONFIG_RENDERER_OPENGLES" #endif // BGFX_CONFIG_RENDERER_OPENGL && BGFX_CONFIG_RENDERER_OPENGLES +// Enable hardware video decoder. +#ifndef BGFX_CONFIG_VIDEO +# define BGFX_CONFIG_VIDEO 1 +#endif // BGFX_CONFIG_VIDEO + +#define BGFX_CONFIG_VIDEO_DIRECT3D11 (true \ + && BGFX_CONFIG_VIDEO \ + && BGFX_CONFIG_RENDERER_DIRECT3D11 \ + && !BX_PLATFORM_LINUX \ + ) + +#define BGFX_CONFIG_VIDEO_DIRECT3D12 (true \ + && BGFX_CONFIG_VIDEO \ + && BGFX_CONFIG_RENDERER_DIRECT3D12 \ + && !BX_PLATFORM_LINUX \ + ) + +#define BGFX_CONFIG_VIDEO_METAL (true \ + && BGFX_CONFIG_VIDEO \ + && BGFX_CONFIG_RENDERER_METAL \ + ) + +#define BGFX_CONFIG_VIDEO_VULKAN (true \ + && BGFX_CONFIG_VIDEO \ + && BGFX_CONFIG_RENDERER_VULKAN \ + ) + /// Enable use of renderer-specific API extensions (e.g. OpenGL extensions, /// Vulkan extensions). Default is 1 (enabled). #ifndef BGFX_CONFIG_RENDERER_USE_EXTENSIONS diff --git a/src/cs_yuv_to_rgb.bin.h b/src/cs_yuv_to_rgb.bin.h new file mode 100644 index 000000000..c2996e088 --- /dev/null +++ b/src/cs_yuv_to_rgb.bin.h @@ -0,0 +1,1305 @@ +static const uint8_t cs_yuv_to_rgb_glsl[4966] = +{ + 0x43, 0x53, 0x48, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x13, // CSH...........S. + 0x00, 0x00, 0x23, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x34, 0x33, 0x30, 0x0a, 0x66, // ..#version 430.f + 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x72, 0x63, 0x70, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, // loat rcp(float _ + 0x61, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x31, 0x2e, 0x30, 0x2f, // a) { return 1.0/ + 0x5f, 0x61, 0x3b, 0x20, 0x7d, 0x0a, 0x76, 0x65, 0x63, 0x32, 0x20, 0x72, 0x63, 0x70, 0x28, 0x76, // _a; }.vec2 rcp(v + 0x65, 0x63, 0x32, 0x20, 0x5f, 0x61, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, // ec2 _a) { return + 0x20, 0x76, 0x65, 0x63, 0x32, 0x28, 0x31, 0x2e, 0x30, 0x29, 0x2f, 0x5f, 0x61, 0x3b, 0x20, 0x7d, // vec2(1.0)/_a; } + 0x0a, 0x76, 0x65, 0x63, 0x33, 0x20, 0x72, 0x63, 0x70, 0x28, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, // .vec3 rcp(vec3 _ + 0x61, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x76, 0x65, 0x63, 0x33, // a) { return vec3 + 0x28, 0x31, 0x2e, 0x30, 0x29, 0x2f, 0x5f, 0x61, 0x3b, 0x20, 0x7d, 0x0a, 0x76, 0x65, 0x63, 0x34, // (1.0)/_a; }.vec4 + 0x20, 0x72, 0x63, 0x70, 0x28, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x61, 0x29, 0x20, 0x7b, 0x20, // rcp(vec4 _a) { + 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x31, 0x2e, 0x30, 0x29, // return vec4(1.0) + 0x2f, 0x5f, 0x61, 0x3b, 0x20, 0x7d, 0x0a, 0x76, 0x65, 0x63, 0x32, 0x20, 0x76, 0x65, 0x63, 0x32, // /_a; }.vec2 vec2 + 0x5f, 0x73, 0x70, 0x6c, 0x61, 0x74, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x78, 0x29, // _splat(float _x) + 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x76, 0x65, 0x63, 0x32, 0x28, 0x5f, // { return vec2(_ + 0x78, 0x2c, 0x20, 0x5f, 0x78, 0x29, 0x3b, 0x20, 0x7d, 0x0a, 0x76, 0x65, 0x63, 0x33, 0x20, 0x76, // x, _x); }.vec3 v + 0x65, 0x63, 0x33, 0x5f, 0x73, 0x70, 0x6c, 0x61, 0x74, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, // ec3_splat(float + 0x5f, 0x78, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x76, 0x65, 0x63, // _x) { return vec + 0x33, 0x28, 0x5f, 0x78, 0x2c, 0x20, 0x5f, 0x78, 0x2c, 0x20, 0x5f, 0x78, 0x29, 0x3b, 0x20, 0x7d, // 3(_x, _x, _x); } + 0x0a, 0x76, 0x65, 0x63, 0x34, 0x20, 0x76, 0x65, 0x63, 0x34, 0x5f, 0x73, 0x70, 0x6c, 0x61, 0x74, // .vec4 vec4_splat + 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x78, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, // (float _x) { ret + 0x75, 0x72, 0x6e, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x5f, 0x78, 0x2c, 0x20, 0x5f, 0x78, 0x2c, // urn vec4(_x, _x, + 0x20, 0x5f, 0x78, 0x2c, 0x20, 0x5f, 0x78, 0x29, 0x3b, 0x20, 0x7d, 0x0a, 0x75, 0x76, 0x65, 0x63, // _x, _x); }.uvec + 0x32, 0x20, 0x75, 0x76, 0x65, 0x63, 0x32, 0x5f, 0x73, 0x70, 0x6c, 0x61, 0x74, 0x28, 0x75, 0x69, // 2 uvec2_splat(ui + 0x6e, 0x74, 0x20, 0x5f, 0x78, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, // nt _x) { return + 0x75, 0x76, 0x65, 0x63, 0x32, 0x28, 0x5f, 0x78, 0x2c, 0x20, 0x5f, 0x78, 0x29, 0x3b, 0x20, 0x7d, // uvec2(_x, _x); } + 0x0a, 0x75, 0x76, 0x65, 0x63, 0x33, 0x20, 0x75, 0x76, 0x65, 0x63, 0x33, 0x5f, 0x73, 0x70, 0x6c, // .uvec3 uvec3_spl + 0x61, 0x74, 0x28, 0x75, 0x69, 0x6e, 0x74, 0x20, 0x5f, 0x78, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, // at(uint _x) { re + 0x74, 0x75, 0x72, 0x6e, 0x20, 0x75, 0x76, 0x65, 0x63, 0x33, 0x28, 0x5f, 0x78, 0x2c, 0x20, 0x5f, // turn uvec3(_x, _ + 0x78, 0x2c, 0x20, 0x5f, 0x78, 0x29, 0x3b, 0x20, 0x7d, 0x0a, 0x75, 0x76, 0x65, 0x63, 0x34, 0x20, // x, _x); }.uvec4 + 0x75, 0x76, 0x65, 0x63, 0x34, 0x5f, 0x73, 0x70, 0x6c, 0x61, 0x74, 0x28, 0x75, 0x69, 0x6e, 0x74, // uvec4_splat(uint + 0x20, 0x5f, 0x78, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x75, 0x76, // _x) { return uv + 0x65, 0x63, 0x34, 0x28, 0x5f, 0x78, 0x2c, 0x20, 0x5f, 0x78, 0x2c, 0x20, 0x5f, 0x78, 0x2c, 0x20, // ec4(_x, _x, _x, + 0x5f, 0x78, 0x29, 0x3b, 0x20, 0x7d, 0x0a, 0x6d, 0x61, 0x74, 0x34, 0x20, 0x6d, 0x74, 0x78, 0x46, // _x); }.mat4 mtxF + 0x72, 0x6f, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x28, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x30, 0x2c, // romRows(vec4 _0, + 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x31, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, // vec4 _1, vec4 _ + 0x32, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x33, 0x29, 0x0a, 0x7b, 0x0a, 0x72, 0x65, // 2, vec4 _3).{.re + 0x74, 0x75, 0x72, 0x6e, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x73, 0x65, 0x28, 0x6d, // turn transpose(m + 0x61, 0x74, 0x34, 0x28, 0x5f, 0x30, 0x2c, 0x20, 0x5f, 0x31, 0x2c, 0x20, 0x5f, 0x32, 0x2c, 0x20, // at4(_0, _1, _2, + 0x5f, 0x33, 0x29, 0x20, 0x29, 0x3b, 0x0a, 0x7d, 0x0a, 0x6d, 0x61, 0x74, 0x34, 0x20, 0x6d, 0x74, // _3) );.}.mat4 mt + 0x78, 0x46, 0x72, 0x6f, 0x6d, 0x43, 0x6f, 0x6c, 0x73, 0x28, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, // xFromCols(vec4 _ + 0x30, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x31, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x34, // 0, vec4 _1, vec4 + 0x20, 0x5f, 0x32, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x33, 0x29, 0x0a, 0x7b, 0x0a, // _2, vec4 _3).{. + 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x28, 0x5f, 0x30, 0x2c, 0x20, // return mat4(_0, + 0x5f, 0x31, 0x2c, 0x20, 0x5f, 0x32, 0x2c, 0x20, 0x5f, 0x33, 0x29, 0x3b, 0x0a, 0x7d, 0x0a, 0x6d, // _1, _2, _3);.}.m + 0x61, 0x74, 0x33, 0x20, 0x6d, 0x74, 0x78, 0x46, 0x72, 0x6f, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x28, // at3 mtxFromRows( + 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x30, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x31, // vec3 _0, vec3 _1 + 0x2c, 0x20, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x32, 0x29, 0x0a, 0x7b, 0x0a, 0x72, 0x65, 0x74, // , vec3 _2).{.ret + 0x75, 0x72, 0x6e, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x73, 0x65, 0x28, 0x6d, 0x61, // urn transpose(ma + 0x74, 0x33, 0x28, 0x5f, 0x30, 0x2c, 0x20, 0x5f, 0x31, 0x2c, 0x20, 0x5f, 0x32, 0x29, 0x20, 0x29, // t3(_0, _1, _2) ) + 0x3b, 0x0a, 0x7d, 0x0a, 0x6d, 0x61, 0x74, 0x33, 0x20, 0x6d, 0x74, 0x78, 0x46, 0x72, 0x6f, 0x6d, // ;.}.mat3 mtxFrom + 0x43, 0x6f, 0x6c, 0x73, 0x28, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x30, 0x2c, 0x20, 0x76, 0x65, // Cols(vec3 _0, ve + 0x63, 0x33, 0x20, 0x5f, 0x31, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x32, 0x29, 0x0a, // c3 _1, vec3 _2). + 0x7b, 0x0a, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6d, 0x61, 0x74, 0x33, 0x28, 0x5f, 0x30, // {.return mat3(_0 + 0x2c, 0x20, 0x5f, 0x31, 0x2c, 0x20, 0x5f, 0x32, 0x29, 0x3b, 0x0a, 0x7d, 0x0a, 0x6d, 0x61, 0x74, // , _1, _2);.}.mat + 0x34, 0x78, 0x33, 0x20, 0x6d, 0x74, 0x78, 0x46, 0x72, 0x6f, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x28, // 4x3 mtxFromRows( + 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x30, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x31, // vec4 _0, vec4 _1 + 0x2c, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x32, 0x29, 0x0a, 0x7b, 0x0a, 0x72, 0x65, 0x74, // , vec4 _2).{.ret + 0x75, 0x72, 0x6e, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x73, 0x65, 0x28, 0x6d, 0x61, // urn transpose(ma + 0x74, 0x33, 0x78, 0x34, 0x28, 0x5f, 0x30, 0x2c, 0x20, 0x5f, 0x31, 0x2c, 0x20, 0x5f, 0x32, 0x29, // t3x4(_0, _1, _2) + 0x20, 0x29, 0x3b, 0x0a, 0x7d, 0x0a, 0x76, 0x65, 0x63, 0x33, 0x20, 0x6d, 0x74, 0x78, 0x47, 0x65, // );.}.vec3 mtxGe + 0x74, 0x52, 0x6f, 0x77, 0x28, 0x6d, 0x61, 0x74, 0x33, 0x20, 0x5f, 0x6d, 0x74, 0x78, 0x2c, 0x20, // tRow(mat3 _mtx, + 0x69, 0x6e, 0x74, 0x20, 0x5f, 0x72, 0x6f, 0x77, 0x29, 0x0a, 0x7b, 0x0a, 0x72, 0x65, 0x74, 0x75, // int _row).{.retu + 0x72, 0x6e, 0x20, 0x76, 0x65, 0x63, 0x33, 0x28, 0x5f, 0x6d, 0x74, 0x78, 0x5b, 0x30, 0x5d, 0x5b, // rn vec3(_mtx[0][ + 0x5f, 0x72, 0x6f, 0x77, 0x5d, 0x2c, 0x20, 0x5f, 0x6d, 0x74, 0x78, 0x5b, 0x31, 0x5d, 0x5b, 0x5f, // _row], _mtx[1][_ + 0x72, 0x6f, 0x77, 0x5d, 0x2c, 0x20, 0x5f, 0x6d, 0x74, 0x78, 0x5b, 0x32, 0x5d, 0x5b, 0x5f, 0x72, // row], _mtx[2][_r + 0x6f, 0x77, 0x5d, 0x29, 0x3b, 0x0a, 0x7d, 0x0a, 0x76, 0x65, 0x63, 0x33, 0x20, 0x6d, 0x74, 0x78, // ow]);.}.vec3 mtx + 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x28, 0x6d, 0x61, 0x74, 0x33, 0x20, 0x5f, // GetColumn(mat3 _ + 0x6d, 0x74, 0x78, 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x5f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, // mtx, int _column + 0x29, 0x0a, 0x7b, 0x0a, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x76, 0x65, 0x63, 0x33, 0x28, // ).{.return vec3( + 0x5f, 0x6d, 0x74, 0x78, 0x5b, 0x5f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x5d, 0x29, 0x3b, 0x0a, // _mtx[_column]);. + 0x7d, 0x0a, 0x76, 0x65, 0x63, 0x34, 0x20, 0x6d, 0x74, 0x78, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x77, // }.vec4 mtxGetRow + 0x28, 0x6d, 0x61, 0x74, 0x34, 0x78, 0x33, 0x20, 0x5f, 0x6d, 0x74, 0x78, 0x2c, 0x20, 0x69, 0x6e, // (mat4x3 _mtx, in + 0x74, 0x20, 0x5f, 0x72, 0x6f, 0x77, 0x29, 0x0a, 0x7b, 0x0a, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, // t _row).{.return + 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x5f, 0x6d, 0x74, 0x78, 0x5b, 0x30, 0x5d, 0x5b, 0x5f, 0x72, // vec4(_mtx[0][_r + 0x6f, 0x77, 0x5d, 0x2c, 0x20, 0x5f, 0x6d, 0x74, 0x78, 0x5b, 0x31, 0x5d, 0x5b, 0x5f, 0x72, 0x6f, // ow], _mtx[1][_ro + 0x77, 0x5d, 0x2c, 0x20, 0x5f, 0x6d, 0x74, 0x78, 0x5b, 0x32, 0x5d, 0x5b, 0x5f, 0x72, 0x6f, 0x77, // w], _mtx[2][_row + 0x5d, 0x2c, 0x20, 0x5f, 0x6d, 0x74, 0x78, 0x5b, 0x33, 0x5d, 0x5b, 0x5f, 0x72, 0x6f, 0x77, 0x5d, // ], _mtx[3][_row] + 0x29, 0x3b, 0x0a, 0x7d, 0x0a, 0x76, 0x65, 0x63, 0x33, 0x20, 0x6d, 0x74, 0x78, 0x47, 0x65, 0x74, // );.}.vec3 mtxGet + 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x28, 0x6d, 0x61, 0x74, 0x34, 0x78, 0x33, 0x20, 0x5f, 0x6d, // Column(mat4x3 _m + 0x74, 0x78, 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x5f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x29, // tx, int _column) + 0x0a, 0x7b, 0x0a, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x76, 0x65, 0x63, 0x33, 0x28, 0x5f, // .{.return vec3(_ + 0x6d, 0x74, 0x78, 0x5b, 0x5f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x5d, 0x29, 0x3b, 0x0a, 0x7d, // mtx[_column]);.} + 0x0a, 0x76, 0x65, 0x63, 0x34, 0x20, 0x6d, 0x74, 0x78, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x77, 0x28, // .vec4 mtxGetRow( + 0x6d, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x6d, 0x74, 0x78, 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x5f, // mat4 _mtx, int _ + 0x72, 0x6f, 0x77, 0x29, 0x0a, 0x7b, 0x0a, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x76, 0x65, // row).{.return ve + 0x63, 0x34, 0x28, 0x5f, 0x6d, 0x74, 0x78, 0x5b, 0x30, 0x5d, 0x5b, 0x5f, 0x72, 0x6f, 0x77, 0x5d, // c4(_mtx[0][_row] + 0x2c, 0x20, 0x5f, 0x6d, 0x74, 0x78, 0x5b, 0x31, 0x5d, 0x5b, 0x5f, 0x72, 0x6f, 0x77, 0x5d, 0x2c, // , _mtx[1][_row], + 0x20, 0x5f, 0x6d, 0x74, 0x78, 0x5b, 0x32, 0x5d, 0x5b, 0x5f, 0x72, 0x6f, 0x77, 0x5d, 0x2c, 0x20, // _mtx[2][_row], + 0x5f, 0x6d, 0x74, 0x78, 0x5b, 0x33, 0x5d, 0x5b, 0x5f, 0x72, 0x6f, 0x77, 0x5d, 0x29, 0x3b, 0x0a, // _mtx[3][_row]);. + 0x7d, 0x0a, 0x76, 0x65, 0x63, 0x34, 0x20, 0x6d, 0x74, 0x78, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, // }.vec4 mtxGetCol + 0x75, 0x6d, 0x6e, 0x28, 0x6d, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x6d, 0x74, 0x78, 0x2c, 0x20, 0x69, // umn(mat4 _mtx, i + 0x6e, 0x74, 0x20, 0x5f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x29, 0x0a, 0x7b, 0x0a, 0x72, 0x65, // nt _column).{.re + 0x74, 0x75, 0x72, 0x6e, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x5f, 0x6d, 0x74, 0x78, 0x5b, 0x5f, // turn vec4(_mtx[_ + 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x5d, 0x29, 0x3b, 0x0a, 0x7d, 0x0a, 0x66, 0x6c, 0x6f, 0x61, // column]);.}.floa + 0x74, 0x20, 0x6d, 0x74, 0x78, 0x47, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x28, // t mtxGetElement( + 0x6d, 0x61, 0x74, 0x33, 0x20, 0x5f, 0x6d, 0x74, 0x78, 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x5f, // mat3 _mtx, int _ + 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x5f, 0x72, 0x6f, 0x77, // column, int _row + 0x29, 0x0a, 0x7b, 0x0a, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x5f, 0x6d, 0x74, 0x78, 0x5b, // ).{.return _mtx[ + 0x5f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x5d, 0x5b, 0x5f, 0x72, 0x6f, 0x77, 0x5d, 0x3b, 0x0a, // _column][_row];. + 0x7d, 0x0a, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x6d, 0x74, 0x78, 0x47, 0x65, 0x74, 0x45, 0x6c, // }.float mtxGetEl + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x28, 0x6d, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x6d, 0x74, 0x78, 0x2c, // ement(mat4 _mtx, + 0x20, 0x69, 0x6e, 0x74, 0x20, 0x5f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x2c, 0x20, 0x69, 0x6e, // int _column, in + 0x74, 0x20, 0x5f, 0x72, 0x6f, 0x77, 0x29, 0x0a, 0x7b, 0x0a, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, // t _row).{.return + 0x20, 0x5f, 0x6d, 0x74, 0x78, 0x5b, 0x5f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x5d, 0x5b, 0x5f, // _mtx[_column][_ + 0x72, 0x6f, 0x77, 0x5d, 0x3b, 0x0a, 0x7d, 0x0a, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x73, 0x65, // row];.}.float se + 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, 0x6f, 0x6f, 0x6c, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, // lect(bool _cond, + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x66, 0x6c, // float _true, fl + 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, // oat _false) { re + 0x74, 0x75, 0x72, 0x6e, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x3f, 0x20, 0x5f, 0x74, 0x72, // turn _cond ? _tr + 0x75, 0x65, 0x20, 0x3a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x3b, 0x20, 0x7d, 0x0a, 0x76, // ue : _false; }.v + 0x65, 0x63, 0x32, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, 0x6f, 0x6f, 0x6c, 0x20, // ec2 select(bool + 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x74, 0x72, 0x75, // _cond, vec2 _tru + 0x65, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, // e, vec2 _false) + 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x3f, // { return _cond ? + 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x20, 0x3a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x3b, // _true : _false; + 0x20, 0x7d, 0x0a, 0x76, 0x65, 0x63, 0x33, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, // }.vec3 select(b + 0x6f, 0x6f, 0x6c, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x33, 0x20, // ool _cond, vec3 + 0x5f, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x66, 0x61, 0x6c, // _true, vec3 _fal + 0x73, 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x5f, 0x63, 0x6f, // se) { return _co + 0x6e, 0x64, 0x20, 0x3f, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x20, 0x3a, 0x20, 0x5f, 0x66, 0x61, // nd ? _true : _fa + 0x6c, 0x73, 0x65, 0x3b, 0x20, 0x7d, 0x0a, 0x76, 0x65, 0x63, 0x34, 0x20, 0x73, 0x65, 0x6c, 0x65, // lse; }.vec4 sele + 0x63, 0x74, 0x28, 0x62, 0x6f, 0x6f, 0x6c, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x76, // ct(bool _cond, v + 0x65, 0x63, 0x34, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, // ec4 _true, vec4 + 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, // _false) { return + 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x3f, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x20, 0x3a, // _cond ? _true : + 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x3b, 0x20, 0x7d, 0x0a, 0x76, 0x65, 0x63, 0x32, 0x20, // _false; }.vec2 + 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x63, 0x6f, // select(bvec2 _co + 0x6e, 0x64, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, // nd, vec2 _true, + 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, // vec2 _false) { r + 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x28, 0x76, 0x65, 0x63, 0x32, 0x28, 0x5f, 0x63, 0x6f, 0x6e, // eturn (vec2(_con + 0x64, 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x29, 0x20, 0x2b, 0x20, 0x28, 0x76, // d) * _true) + (v + 0x65, 0x63, 0x32, 0x28, 0x6e, 0x6f, 0x74, 0x28, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x29, 0x20, 0x29, // ec2(not(_cond) ) + 0x20, 0x2a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x3b, 0x20, 0x7d, 0x0a, 0x76, 0x65, // * _false); }.ve + 0x63, 0x33, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, 0x76, 0x65, 0x63, 0x33, 0x20, // c3 select(bvec3 + 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x74, 0x72, 0x75, // _cond, vec3 _tru + 0x65, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, // e, vec3 _false) + 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x28, 0x76, 0x65, 0x63, 0x33, 0x28, 0x5f, // { return (vec3(_ + 0x63, 0x6f, 0x6e, 0x64, 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x29, 0x20, 0x2b, // cond) * _true) + + 0x20, 0x28, 0x76, 0x65, 0x63, 0x33, 0x28, 0x6e, 0x6f, 0x74, 0x28, 0x5f, 0x63, 0x6f, 0x6e, 0x64, // (vec3(not(_cond + 0x29, 0x20, 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x3b, 0x20, 0x7d, // ) ) * _false); } + 0x0a, 0x76, 0x65, 0x63, 0x34, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, 0x76, 0x65, // .vec4 select(bve + 0x63, 0x34, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, // c4 _cond, vec4 _ + 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, // true, vec4 _fals + 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x28, 0x76, 0x65, 0x63, // e) { return (vec + 0x34, 0x28, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, // 4(_cond) * _true + 0x29, 0x20, 0x2b, 0x20, 0x28, 0x76, 0x65, 0x63, 0x34, 0x28, 0x6e, 0x6f, 0x74, 0x28, 0x5f, 0x63, // ) + (vec4(not(_c + 0x6f, 0x6e, 0x64, 0x29, 0x20, 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, // ond) ) * _false) + 0x3b, 0x20, 0x7d, 0x0a, 0x69, 0x6e, 0x74, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, // ; }.int select(b + 0x6f, 0x6f, 0x6c, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x5f, // ool _cond, int _ + 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, // true, int _false + 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, // ) { return _cond + 0x20, 0x3f, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x20, 0x3a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, // ? _true : _fals + 0x65, 0x3b, 0x20, 0x7d, 0x0a, 0x69, 0x76, 0x65, 0x63, 0x32, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, // e; }.ivec2 selec + 0x74, 0x28, 0x62, 0x6f, 0x6f, 0x6c, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x69, 0x76, // t(bool _cond, iv + 0x65, 0x63, 0x32, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x69, 0x76, 0x65, 0x63, 0x32, // ec2 _true, ivec2 + 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, // _false) { retur + 0x6e, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x3f, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x20, // n _cond ? _true + 0x3a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x3b, 0x20, 0x7d, 0x0a, 0x69, 0x76, 0x65, 0x63, // : _false; }.ivec + 0x33, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, 0x6f, 0x6f, 0x6c, 0x20, 0x5f, 0x63, // 3 select(bool _c + 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x69, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, // ond, ivec3 _true + 0x2c, 0x20, 0x69, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, // , ivec3 _false) + 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x3f, // { return _cond ? + 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x20, 0x3a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x3b, // _true : _false; + 0x20, 0x7d, 0x0a, 0x69, 0x76, 0x65, 0x63, 0x34, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, // }.ivec4 select( + 0x62, 0x6f, 0x6f, 0x6c, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x69, 0x76, 0x65, 0x63, // bool _cond, ivec + 0x34, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x69, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, // 4 _true, ivec4 _ + 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, // false) { return + 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x3f, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x20, 0x3a, 0x20, // _cond ? _true : + 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x3b, 0x20, 0x7d, 0x0a, 0x69, 0x76, 0x65, 0x63, 0x32, 0x20, // _false; }.ivec2 + 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x63, 0x6f, // select(bvec2 _co + 0x6e, 0x64, 0x2c, 0x20, 0x69, 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x2c, // nd, ivec2 _true, + 0x20, 0x69, 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, 0x7b, // ivec2 _false) { + 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x28, 0x69, 0x76, 0x65, 0x63, 0x32, 0x28, 0x5f, // return (ivec2(_ + 0x63, 0x6f, 0x6e, 0x64, 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x29, 0x20, 0x2b, // cond) * _true) + + 0x20, 0x28, 0x69, 0x76, 0x65, 0x63, 0x32, 0x28, 0x6e, 0x6f, 0x74, 0x28, 0x5f, 0x63, 0x6f, 0x6e, // (ivec2(not(_con + 0x64, 0x29, 0x20, 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x3b, 0x20, // d) ) * _false); + 0x7d, 0x0a, 0x69, 0x76, 0x65, 0x63, 0x33, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, // }.ivec3 select(b + 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x69, 0x76, 0x65, 0x63, // vec3 _cond, ivec + 0x33, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x69, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, // 3 _true, ivec3 _ + 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, // false) { return + 0x28, 0x69, 0x76, 0x65, 0x63, 0x33, 0x28, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x29, 0x20, 0x2a, 0x20, // (ivec3(_cond) * + 0x5f, 0x74, 0x72, 0x75, 0x65, 0x29, 0x20, 0x2b, 0x20, 0x28, 0x69, 0x76, 0x65, 0x63, 0x33, 0x28, // _true) + (ivec3( + 0x6e, 0x6f, 0x74, 0x28, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x29, 0x20, 0x29, 0x20, 0x2a, 0x20, 0x5f, // not(_cond) ) * _ + 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x3b, 0x20, 0x7d, 0x0a, 0x69, 0x76, 0x65, 0x63, 0x34, 0x20, // false); }.ivec4 + 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x63, 0x6f, // select(bvec4 _co + 0x6e, 0x64, 0x2c, 0x20, 0x69, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x2c, // nd, ivec4 _true, + 0x20, 0x69, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, 0x7b, // ivec4 _false) { + 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x28, 0x69, 0x76, 0x65, 0x63, 0x34, 0x28, 0x5f, // return (ivec4(_ + 0x63, 0x6f, 0x6e, 0x64, 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x29, 0x20, 0x2b, // cond) * _true) + + 0x20, 0x28, 0x69, 0x76, 0x65, 0x63, 0x34, 0x28, 0x6e, 0x6f, 0x74, 0x28, 0x5f, 0x63, 0x6f, 0x6e, // (ivec4(not(_con + 0x64, 0x29, 0x20, 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x3b, 0x20, // d) ) * _false); + 0x7d, 0x0a, 0x75, 0x69, 0x6e, 0x74, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, 0x6f, // }.uint select(bo + 0x6f, 0x6c, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x75, 0x69, 0x6e, 0x74, 0x20, 0x5f, // ol _cond, uint _ + 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x75, 0x69, 0x6e, 0x74, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, // true, uint _fals + 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x5f, 0x63, 0x6f, 0x6e, // e) { return _con + 0x64, 0x20, 0x3f, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x20, 0x3a, 0x20, 0x5f, 0x66, 0x61, 0x6c, // d ? _true : _fal + 0x73, 0x65, 0x3b, 0x20, 0x7d, 0x0a, 0x75, 0x76, 0x65, 0x63, 0x32, 0x20, 0x73, 0x65, 0x6c, 0x65, // se; }.uvec2 sele + 0x63, 0x74, 0x28, 0x62, 0x6f, 0x6f, 0x6c, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x75, // ct(bool _cond, u + 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x75, 0x76, 0x65, 0x63, // vec2 _true, uvec + 0x32, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, // 2 _false) { retu + 0x72, 0x6e, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x3f, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, // rn _cond ? _true + 0x20, 0x3a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x3b, 0x20, 0x7d, 0x0a, 0x75, 0x76, 0x65, // : _false; }.uve + 0x63, 0x33, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, 0x6f, 0x6f, 0x6c, 0x20, 0x5f, // c3 select(bool _ + 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x75, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x74, 0x72, 0x75, // cond, uvec3 _tru + 0x65, 0x2c, 0x20, 0x75, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, // e, uvec3 _false) + 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x20, // { return _cond + 0x3f, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x20, 0x3a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, // ? _true : _false + 0x3b, 0x20, 0x7d, 0x0a, 0x75, 0x76, 0x65, 0x63, 0x34, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, // ; }.uvec4 select + 0x28, 0x62, 0x6f, 0x6f, 0x6c, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x75, 0x76, 0x65, // (bool _cond, uve + 0x63, 0x34, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x75, 0x76, 0x65, 0x63, 0x34, 0x20, // c4 _true, uvec4 + 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, // _false) { return + 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x3f, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x20, 0x3a, // _cond ? _true : + 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x3b, 0x20, 0x7d, 0x0a, 0x75, 0x76, 0x65, 0x63, 0x32, // _false; }.uvec2 + 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x63, // select(bvec2 _c + 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x75, 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, // ond, uvec2 _true + 0x2c, 0x20, 0x75, 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, // , uvec2 _false) + 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x28, 0x75, 0x76, 0x65, 0x63, 0x32, 0x28, // { return (uvec2( + 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x29, 0x20, // _cond) * _true) + 0x2b, 0x20, 0x28, 0x75, 0x76, 0x65, 0x63, 0x32, 0x28, 0x6e, 0x6f, 0x74, 0x28, 0x5f, 0x63, 0x6f, // + (uvec2(not(_co + 0x6e, 0x64, 0x29, 0x20, 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x3b, // nd) ) * _false); + 0x20, 0x7d, 0x0a, 0x75, 0x76, 0x65, 0x63, 0x33, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, // }.uvec3 select( + 0x62, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x75, 0x76, 0x65, // bvec3 _cond, uve + 0x63, 0x33, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x75, 0x76, 0x65, 0x63, 0x33, 0x20, // c3 _true, uvec3 + 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, // _false) { return + 0x20, 0x28, 0x75, 0x76, 0x65, 0x63, 0x33, 0x28, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x29, 0x20, 0x2a, // (uvec3(_cond) * + 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x29, 0x20, 0x2b, 0x20, 0x28, 0x75, 0x76, 0x65, 0x63, 0x33, // _true) + (uvec3 + 0x28, 0x6e, 0x6f, 0x74, 0x28, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x29, 0x20, 0x29, 0x20, 0x2a, 0x20, // (not(_cond) ) * + 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x3b, 0x20, 0x7d, 0x0a, 0x75, 0x76, 0x65, 0x63, 0x34, // _false); }.uvec4 + 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x63, // select(bvec4 _c + 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x75, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, // ond, uvec4 _true + 0x2c, 0x20, 0x75, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, // , uvec4 _false) + 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x28, 0x75, 0x76, 0x65, 0x63, 0x34, 0x28, // { return (uvec4( + 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x29, 0x20, // _cond) * _true) + 0x2b, 0x20, 0x28, 0x75, 0x76, 0x65, 0x63, 0x34, 0x28, 0x6e, 0x6f, 0x74, 0x28, 0x5f, 0x63, 0x6f, // + (uvec4(not(_co + 0x6e, 0x64, 0x29, 0x20, 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x3b, // nd) ) * _false); + 0x20, 0x7d, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, // }.uniform vec4 + 0x75, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x52, 0x65, 0x63, 0x74, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, // u_viewRect;.unif + 0x6f, 0x72, 0x6d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x75, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x54, // orm vec4 u_viewT + 0x65, 0x78, 0x65, 0x6c, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6d, 0x61, // exel;.uniform ma + 0x74, 0x34, 0x20, 0x75, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, // t4 u_view;.unifo + 0x72, 0x6d, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x20, 0x75, 0x5f, 0x69, 0x6e, 0x76, 0x56, 0x69, 0x65, // rm mat4 u_invVie + 0x77, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x20, // w;.uniform mat4 + 0x75, 0x5f, 0x70, 0x72, 0x6f, 0x6a, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, // u_proj;.uniform + 0x6d, 0x61, 0x74, 0x34, 0x20, 0x75, 0x5f, 0x69, 0x6e, 0x76, 0x50, 0x72, 0x6f, 0x6a, 0x3b, 0x0a, // mat4 u_invProj;. + 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x20, 0x75, 0x5f, 0x76, // uniform mat4 u_v + 0x69, 0x65, 0x77, 0x50, 0x72, 0x6f, 0x6a, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, // iewProj;.uniform + 0x20, 0x6d, 0x61, 0x74, 0x34, 0x20, 0x75, 0x5f, 0x69, 0x6e, 0x76, 0x56, 0x69, 0x65, 0x77, 0x50, // mat4 u_invViewP + 0x72, 0x6f, 0x6a, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6d, 0x61, 0x74, // roj;.uniform mat + 0x34, 0x20, 0x75, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x56, 0x69, 0x65, 0x77, 0x3b, 0x0a, 0x75, // 4 u_modelView;.u + 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x20, 0x75, 0x5f, 0x69, 0x6e, // niform mat4 u_in + 0x76, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x56, 0x69, 0x65, 0x77, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, // vModelView;.unif + 0x6f, 0x72, 0x6d, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x20, 0x75, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, // orm mat4 u_model + 0x56, 0x69, 0x65, 0x77, 0x50, 0x72, 0x6f, 0x6a, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, // ViewProj;.unifor + 0x6d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x75, 0x5f, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x52, 0x65, // m vec4 u_alphaRe + 0x66, 0x34, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6d, 0x61, 0x74, 0x34, // f4;.uniform mat4 + 0x20, 0x75, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x5b, 0x31, 0x5d, 0x3b, 0x0a, 0x75, 0x6e, 0x69, // u_model[1];.uni + 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x32, 0x44, 0x20, 0x62, // form sampler2D b + 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, 0x59, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, // gfx_texY;.unifor + 0x6d, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x32, 0x44, 0x20, 0x62, 0x67, 0x66, 0x78, // m sampler2D bgfx + 0x5f, 0x74, 0x65, 0x78, 0x43, 0x62, 0x43, 0x72, 0x3b, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, // _texCbCr;.layout + 0x28, 0x72, 0x67, 0x62, 0x61, 0x38, 0x2c, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x3d, // (rgba8, binding= + 0x30, 0x29, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x75, 0x6e, 0x69, // 0) writeonly uni + 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x32, 0x44, 0x20, 0x62, 0x67, 0x66, // form image2D bgf + 0x78, 0x5f, 0x69, 0x6d, 0x67, 0x52, 0x67, 0x62, 0x61, 0x3b, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, // x_imgRgba;.layou + 0x74, 0x20, 0x28, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x78, 0x20, // t (local_size_x + 0x3d, 0x20, 0x38, 0x2c, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, // = 8, local_size_ + 0x79, 0x20, 0x3d, 0x20, 0x38, 0x2c, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x73, 0x69, 0x7a, // y = 8, local_siz + 0x65, 0x5f, 0x7a, 0x20, 0x3d, 0x20, 0x31, 0x29, 0x20, 0x69, 0x6e, 0x3b, 0x0a, 0x76, 0x6f, 0x69, // e_z = 1) in;.voi + 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x7b, 0x0a, 0x69, 0x76, 0x65, 0x63, 0x32, // d main().{.ivec2 + 0x20, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x20, 0x3d, 0x20, 0x69, 0x76, 0x65, 0x63, 0x32, 0x28, 0x67, // coord = ivec2(g + 0x6c, 0x5f, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x63, 0x61, 0x74, 0x69, // l_GlobalInvocati + 0x6f, 0x6e, 0x49, 0x44, 0x2e, 0x78, 0x79, 0x29, 0x3b, 0x0a, 0x69, 0x76, 0x65, 0x63, 0x32, 0x20, // onID.xy);.ivec2 + 0x73, 0x69, 0x7a, 0x65, 0x20, 0x3d, 0x20, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, // size = imageSize + 0x28, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x69, 0x6d, 0x67, 0x52, 0x67, 0x62, 0x61, 0x29, 0x3b, 0x0a, // (bgfx_imgRgba);. + 0x69, 0x66, 0x20, 0x28, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x78, 0x20, 0x3e, 0x3d, 0x20, 0x73, // if (coord.x >= s + 0x69, 0x7a, 0x65, 0x2e, 0x78, 0x0a, 0x7c, 0x7c, 0x20, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x79, // ize.x.|| coord.y + 0x20, 0x3e, 0x3d, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x2e, 0x79, 0x29, 0x0a, 0x7b, 0x0a, 0x72, 0x65, // >= size.y).{.re + 0x74, 0x75, 0x72, 0x6e, 0x3b, 0x0a, 0x7d, 0x0a, 0x76, 0x65, 0x63, 0x32, 0x20, 0x69, 0x6e, 0x76, // turn;.}.vec2 inv + 0x53, 0x69, 0x7a, 0x65, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x32, 0x28, 0x31, 0x2e, 0x30, 0x2c, // Size = vec2(1.0, + 0x20, 0x31, 0x2e, 0x30, 0x29, 0x20, 0x2f, 0x20, 0x76, 0x65, 0x63, 0x32, 0x28, 0x73, 0x69, 0x7a, // 1.0) / vec2(siz + 0x65, 0x29, 0x3b, 0x0a, 0x76, 0x65, 0x63, 0x32, 0x20, 0x75, 0x76, 0x20, 0x3d, 0x20, 0x28, 0x76, // e);.vec2 uv = (v + 0x65, 0x63, 0x32, 0x28, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x29, 0x20, 0x2b, 0x20, 0x76, 0x65, 0x63, // ec2(coord) + vec + 0x32, 0x28, 0x30, 0x2e, 0x35, 0x2c, 0x20, 0x30, 0x2e, 0x35, 0x29, 0x20, 0x29, 0x20, 0x2a, 0x20, // 2(0.5, 0.5) ) * + 0x69, 0x6e, 0x76, 0x53, 0x69, 0x7a, 0x65, 0x3b, 0x0a, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x79, // invSize;.float y + 0x20, 0x3d, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x4c, 0x6f, 0x64, 0x28, 0x62, 0x67, // = textureLod(bg + 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, 0x59, 0x2c, 0x20, 0x75, 0x76, 0x2c, 0x20, 0x30, 0x2e, 0x30, // fx_texY, uv, 0.0 + 0x29, 0x2e, 0x78, 0x3b, 0x0a, 0x76, 0x65, 0x63, 0x32, 0x20, 0x63, 0x62, 0x20, 0x3d, 0x20, 0x74, // ).x;.vec2 cb = t + 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x4c, 0x6f, 0x64, 0x28, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x74, // extureLod(bgfx_t + 0x65, 0x78, 0x43, 0x62, 0x43, 0x72, 0x2c, 0x20, 0x75, 0x76, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x29, // exCbCr, uv, 0.0) + 0x2e, 0x78, 0x79, 0x3b, 0x0a, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x79, 0x4c, 0x20, 0x3d, 0x20, // .xy;.float yL = + 0x28, 0x79, 0x20, 0x2d, 0x20, 0x31, 0x36, 0x2e, 0x30, 0x2f, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x29, // (y - 16.0/255.0) + 0x20, 0x2a, 0x20, 0x28, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x2f, 0x32, 0x31, 0x39, 0x2e, 0x30, 0x29, // * (255.0/219.0) + 0x3b, 0x0a, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x63, 0x62, 0x4c, 0x20, 0x3d, 0x20, 0x28, 0x63, // ;.float cbL = (c + 0x62, 0x2e, 0x78, 0x20, 0x2d, 0x20, 0x31, 0x32, 0x38, 0x2e, 0x30, 0x2f, 0x32, 0x35, 0x35, 0x2e, // b.x - 128.0/255. + 0x30, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x2f, 0x32, 0x32, 0x34, 0x2e, // 0) * (255.0/224. + 0x30, 0x29, 0x3b, 0x0a, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x63, 0x72, 0x4c, 0x20, 0x3d, 0x20, // 0);.float crL = + 0x28, 0x63, 0x62, 0x2e, 0x79, 0x20, 0x2d, 0x20, 0x31, 0x32, 0x38, 0x2e, 0x30, 0x2f, 0x32, 0x35, // (cb.y - 128.0/25 + 0x35, 0x2e, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x2f, 0x32, 0x32, // 5.0) * (255.0/22 + 0x34, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x76, 0x65, 0x63, 0x33, 0x20, 0x72, 0x67, 0x62, 0x3b, 0x0a, // 4.0);.vec3 rgb;. + 0x72, 0x67, 0x62, 0x2e, 0x72, 0x20, 0x3d, 0x20, 0x79, 0x4c, 0x20, 0x2b, 0x20, 0x31, 0x2e, 0x35, // rgb.r = yL + 1.5 + 0x37, 0x34, 0x38, 0x20, 0x2a, 0x20, 0x63, 0x72, 0x4c, 0x3b, 0x0a, 0x72, 0x67, 0x62, 0x2e, 0x67, // 748 * crL;.rgb.g + 0x20, 0x3d, 0x20, 0x79, 0x4c, 0x20, 0x2d, 0x20, 0x30, 0x2e, 0x31, 0x38, 0x37, 0x33, 0x20, 0x2a, // = yL - 0.1873 * + 0x20, 0x63, 0x62, 0x4c, 0x20, 0x2d, 0x20, 0x30, 0x2e, 0x34, 0x36, 0x38, 0x31, 0x20, 0x2a, 0x20, // cbL - 0.4681 * + 0x63, 0x72, 0x4c, 0x3b, 0x0a, 0x72, 0x67, 0x62, 0x2e, 0x62, 0x20, 0x3d, 0x20, 0x79, 0x4c, 0x20, // crL;.rgb.b = yL + 0x2b, 0x20, 0x31, 0x2e, 0x38, 0x35, 0x35, 0x36, 0x20, 0x2a, 0x20, 0x63, 0x62, 0x4c, 0x3b, 0x0a, // + 1.8556 * cbL;. + 0x69, 0x6d, 0x61, 0x67, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x28, 0x62, 0x67, 0x66, 0x78, 0x5f, // imageStore(bgfx_ + 0x69, 0x6d, 0x67, 0x52, 0x67, 0x62, 0x61, 0x2c, 0x20, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x2c, 0x20, // imgRgba, coord, + 0x76, 0x65, 0x63, 0x34, 0x28, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x72, 0x67, 0x62, 0x2c, 0x20, // vec4(clamp(rgb, + 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x20, // 0.0, 1.0), 1.0) + 0x29, 0x3b, 0x0a, 0x7d, 0x0a, 0x00, // );.}.. +}; +static const uint8_t cs_yuv_to_rgb_essl[4695] = +{ + 0x43, 0x53, 0x48, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x12, // CSH...........D. + 0x00, 0x00, 0x23, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x33, 0x31, 0x30, 0x20, 0x65, // ..#version 310 e + 0x73, 0x0a, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x72, 0x63, 0x70, 0x28, 0x66, 0x6c, 0x6f, 0x61, // s.float rcp(floa + 0x74, 0x20, 0x5f, 0x61, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x31, // t _a) { return 1 + 0x2e, 0x30, 0x2f, 0x5f, 0x61, 0x3b, 0x20, 0x7d, 0x0a, 0x76, 0x65, 0x63, 0x32, 0x20, 0x72, 0x63, // .0/_a; }.vec2 rc + 0x70, 0x28, 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x61, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, // p(vec2 _a) { ret + 0x75, 0x72, 0x6e, 0x20, 0x76, 0x65, 0x63, 0x32, 0x28, 0x31, 0x2e, 0x30, 0x29, 0x2f, 0x5f, 0x61, // urn vec2(1.0)/_a + 0x3b, 0x20, 0x7d, 0x0a, 0x76, 0x65, 0x63, 0x33, 0x20, 0x72, 0x63, 0x70, 0x28, 0x76, 0x65, 0x63, // ; }.vec3 rcp(vec + 0x33, 0x20, 0x5f, 0x61, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x76, // 3 _a) { return v + 0x65, 0x63, 0x33, 0x28, 0x31, 0x2e, 0x30, 0x29, 0x2f, 0x5f, 0x61, 0x3b, 0x20, 0x7d, 0x0a, 0x76, // ec3(1.0)/_a; }.v + 0x65, 0x63, 0x34, 0x20, 0x72, 0x63, 0x70, 0x28, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x61, 0x29, // ec4 rcp(vec4 _a) + 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x31, // { return vec4(1 + 0x2e, 0x30, 0x29, 0x2f, 0x5f, 0x61, 0x3b, 0x20, 0x7d, 0x0a, 0x76, 0x65, 0x63, 0x32, 0x20, 0x76, // .0)/_a; }.vec2 v + 0x65, 0x63, 0x32, 0x5f, 0x73, 0x70, 0x6c, 0x61, 0x74, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, // ec2_splat(float + 0x5f, 0x78, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x76, 0x65, 0x63, // _x) { return vec + 0x32, 0x28, 0x5f, 0x78, 0x2c, 0x20, 0x5f, 0x78, 0x29, 0x3b, 0x20, 0x7d, 0x0a, 0x76, 0x65, 0x63, // 2(_x, _x); }.vec + 0x33, 0x20, 0x76, 0x65, 0x63, 0x33, 0x5f, 0x73, 0x70, 0x6c, 0x61, 0x74, 0x28, 0x66, 0x6c, 0x6f, // 3 vec3_splat(flo + 0x61, 0x74, 0x20, 0x5f, 0x78, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, // at _x) { return + 0x76, 0x65, 0x63, 0x33, 0x28, 0x5f, 0x78, 0x2c, 0x20, 0x5f, 0x78, 0x2c, 0x20, 0x5f, 0x78, 0x29, // vec3(_x, _x, _x) + 0x3b, 0x20, 0x7d, 0x0a, 0x76, 0x65, 0x63, 0x34, 0x20, 0x76, 0x65, 0x63, 0x34, 0x5f, 0x73, 0x70, // ; }.vec4 vec4_sp + 0x6c, 0x61, 0x74, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x78, 0x29, 0x20, 0x7b, 0x20, // lat(float _x) { + 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x5f, 0x78, 0x2c, 0x20, // return vec4(_x, + 0x5f, 0x78, 0x2c, 0x20, 0x5f, 0x78, 0x2c, 0x20, 0x5f, 0x78, 0x29, 0x3b, 0x20, 0x7d, 0x0a, 0x75, // _x, _x, _x); }.u + 0x76, 0x65, 0x63, 0x32, 0x20, 0x75, 0x76, 0x65, 0x63, 0x32, 0x5f, 0x73, 0x70, 0x6c, 0x61, 0x74, // vec2 uvec2_splat + 0x28, 0x75, 0x69, 0x6e, 0x74, 0x20, 0x5f, 0x78, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, // (uint _x) { retu + 0x72, 0x6e, 0x20, 0x75, 0x76, 0x65, 0x63, 0x32, 0x28, 0x5f, 0x78, 0x2c, 0x20, 0x5f, 0x78, 0x29, // rn uvec2(_x, _x) + 0x3b, 0x20, 0x7d, 0x0a, 0x75, 0x76, 0x65, 0x63, 0x33, 0x20, 0x75, 0x76, 0x65, 0x63, 0x33, 0x5f, // ; }.uvec3 uvec3_ + 0x73, 0x70, 0x6c, 0x61, 0x74, 0x28, 0x75, 0x69, 0x6e, 0x74, 0x20, 0x5f, 0x78, 0x29, 0x20, 0x7b, // splat(uint _x) { + 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x75, 0x76, 0x65, 0x63, 0x33, 0x28, 0x5f, 0x78, // return uvec3(_x + 0x2c, 0x20, 0x5f, 0x78, 0x2c, 0x20, 0x5f, 0x78, 0x29, 0x3b, 0x20, 0x7d, 0x0a, 0x75, 0x76, 0x65, // , _x, _x); }.uve + 0x63, 0x34, 0x20, 0x75, 0x76, 0x65, 0x63, 0x34, 0x5f, 0x73, 0x70, 0x6c, 0x61, 0x74, 0x28, 0x75, // c4 uvec4_splat(u + 0x69, 0x6e, 0x74, 0x20, 0x5f, 0x78, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, // int _x) { return + 0x20, 0x75, 0x76, 0x65, 0x63, 0x34, 0x28, 0x5f, 0x78, 0x2c, 0x20, 0x5f, 0x78, 0x2c, 0x20, 0x5f, // uvec4(_x, _x, _ + 0x78, 0x2c, 0x20, 0x5f, 0x78, 0x29, 0x3b, 0x20, 0x7d, 0x0a, 0x6d, 0x61, 0x74, 0x34, 0x20, 0x6d, // x, _x); }.mat4 m + 0x74, 0x78, 0x46, 0x72, 0x6f, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x28, 0x76, 0x65, 0x63, 0x34, 0x20, // txFromRows(vec4 + 0x5f, 0x30, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x31, 0x2c, 0x20, 0x76, 0x65, 0x63, // _0, vec4 _1, vec + 0x34, 0x20, 0x5f, 0x32, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x33, 0x29, 0x0a, 0x7b, // 4 _2, vec4 _3).{ + 0x0a, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x73, // .return transpos + 0x65, 0x28, 0x6d, 0x61, 0x74, 0x34, 0x28, 0x5f, 0x30, 0x2c, 0x20, 0x5f, 0x31, 0x2c, 0x20, 0x5f, // e(mat4(_0, _1, _ + 0x32, 0x2c, 0x20, 0x5f, 0x33, 0x29, 0x20, 0x29, 0x3b, 0x0a, 0x7d, 0x0a, 0x6d, 0x61, 0x74, 0x34, // 2, _3) );.}.mat4 + 0x20, 0x6d, 0x74, 0x78, 0x46, 0x72, 0x6f, 0x6d, 0x43, 0x6f, 0x6c, 0x73, 0x28, 0x76, 0x65, 0x63, // mtxFromCols(vec + 0x34, 0x20, 0x5f, 0x30, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x31, 0x2c, 0x20, 0x76, // 4 _0, vec4 _1, v + 0x65, 0x63, 0x34, 0x20, 0x5f, 0x32, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x33, 0x29, // ec4 _2, vec4 _3) + 0x0a, 0x7b, 0x0a, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x28, 0x5f, // .{.return mat4(_ + 0x30, 0x2c, 0x20, 0x5f, 0x31, 0x2c, 0x20, 0x5f, 0x32, 0x2c, 0x20, 0x5f, 0x33, 0x29, 0x3b, 0x0a, // 0, _1, _2, _3);. + 0x7d, 0x0a, 0x6d, 0x61, 0x74, 0x33, 0x20, 0x6d, 0x74, 0x78, 0x46, 0x72, 0x6f, 0x6d, 0x52, 0x6f, // }.mat3 mtxFromRo + 0x77, 0x73, 0x28, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x30, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x33, // ws(vec3 _0, vec3 + 0x20, 0x5f, 0x31, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x32, 0x29, 0x0a, 0x7b, 0x0a, // _1, vec3 _2).{. + 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x73, 0x65, // return transpose + 0x28, 0x6d, 0x61, 0x74, 0x33, 0x28, 0x5f, 0x30, 0x2c, 0x20, 0x5f, 0x31, 0x2c, 0x20, 0x5f, 0x32, // (mat3(_0, _1, _2 + 0x29, 0x20, 0x29, 0x3b, 0x0a, 0x7d, 0x0a, 0x6d, 0x61, 0x74, 0x33, 0x20, 0x6d, 0x74, 0x78, 0x46, // ) );.}.mat3 mtxF + 0x72, 0x6f, 0x6d, 0x43, 0x6f, 0x6c, 0x73, 0x28, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x30, 0x2c, // romCols(vec3 _0, + 0x20, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x31, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, // vec3 _1, vec3 _ + 0x32, 0x29, 0x0a, 0x7b, 0x0a, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6d, 0x61, 0x74, 0x33, // 2).{.return mat3 + 0x28, 0x5f, 0x30, 0x2c, 0x20, 0x5f, 0x31, 0x2c, 0x20, 0x5f, 0x32, 0x29, 0x3b, 0x0a, 0x7d, 0x0a, // (_0, _1, _2);.}. + 0x76, 0x65, 0x63, 0x33, 0x20, 0x6d, 0x74, 0x78, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x77, 0x28, 0x6d, // vec3 mtxGetRow(m + 0x61, 0x74, 0x33, 0x20, 0x5f, 0x6d, 0x74, 0x78, 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x5f, 0x72, // at3 _mtx, int _r + 0x6f, 0x77, 0x29, 0x0a, 0x7b, 0x0a, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x76, 0x65, 0x63, // ow).{.return vec + 0x33, 0x28, 0x5f, 0x6d, 0x74, 0x78, 0x5b, 0x30, 0x5d, 0x5b, 0x5f, 0x72, 0x6f, 0x77, 0x5d, 0x2c, // 3(_mtx[0][_row], + 0x20, 0x5f, 0x6d, 0x74, 0x78, 0x5b, 0x31, 0x5d, 0x5b, 0x5f, 0x72, 0x6f, 0x77, 0x5d, 0x2c, 0x20, // _mtx[1][_row], + 0x5f, 0x6d, 0x74, 0x78, 0x5b, 0x32, 0x5d, 0x5b, 0x5f, 0x72, 0x6f, 0x77, 0x5d, 0x29, 0x3b, 0x0a, // _mtx[2][_row]);. + 0x7d, 0x0a, 0x76, 0x65, 0x63, 0x33, 0x20, 0x6d, 0x74, 0x78, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, // }.vec3 mtxGetCol + 0x75, 0x6d, 0x6e, 0x28, 0x6d, 0x61, 0x74, 0x33, 0x20, 0x5f, 0x6d, 0x74, 0x78, 0x2c, 0x20, 0x69, // umn(mat3 _mtx, i + 0x6e, 0x74, 0x20, 0x5f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x29, 0x0a, 0x7b, 0x0a, 0x72, 0x65, // nt _column).{.re + 0x74, 0x75, 0x72, 0x6e, 0x20, 0x76, 0x65, 0x63, 0x33, 0x28, 0x5f, 0x6d, 0x74, 0x78, 0x5b, 0x5f, // turn vec3(_mtx[_ + 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x5d, 0x29, 0x3b, 0x0a, 0x7d, 0x0a, 0x76, 0x65, 0x63, 0x34, // column]);.}.vec4 + 0x20, 0x6d, 0x74, 0x78, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x77, 0x28, 0x6d, 0x61, 0x74, 0x34, 0x20, // mtxGetRow(mat4 + 0x5f, 0x6d, 0x74, 0x78, 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x5f, 0x72, 0x6f, 0x77, 0x29, 0x0a, // _mtx, int _row). + 0x7b, 0x0a, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x5f, 0x6d, // {.return vec4(_m + 0x74, 0x78, 0x5b, 0x30, 0x5d, 0x5b, 0x5f, 0x72, 0x6f, 0x77, 0x5d, 0x2c, 0x20, 0x5f, 0x6d, 0x74, // tx[0][_row], _mt + 0x78, 0x5b, 0x31, 0x5d, 0x5b, 0x5f, 0x72, 0x6f, 0x77, 0x5d, 0x2c, 0x20, 0x5f, 0x6d, 0x74, 0x78, // x[1][_row], _mtx + 0x5b, 0x32, 0x5d, 0x5b, 0x5f, 0x72, 0x6f, 0x77, 0x5d, 0x2c, 0x20, 0x5f, 0x6d, 0x74, 0x78, 0x5b, // [2][_row], _mtx[ + 0x33, 0x5d, 0x5b, 0x5f, 0x72, 0x6f, 0x77, 0x5d, 0x29, 0x3b, 0x0a, 0x7d, 0x0a, 0x76, 0x65, 0x63, // 3][_row]);.}.vec + 0x34, 0x20, 0x6d, 0x74, 0x78, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x28, 0x6d, // 4 mtxGetColumn(m + 0x61, 0x74, 0x34, 0x20, 0x5f, 0x6d, 0x74, 0x78, 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x5f, 0x63, // at4 _mtx, int _c + 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x29, 0x0a, 0x7b, 0x0a, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, // olumn).{.return + 0x76, 0x65, 0x63, 0x34, 0x28, 0x5f, 0x6d, 0x74, 0x78, 0x5b, 0x5f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, // vec4(_mtx[_colum + 0x6e, 0x5d, 0x29, 0x3b, 0x0a, 0x7d, 0x0a, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x6d, 0x74, 0x78, // n]);.}.float mtx + 0x47, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x28, 0x6d, 0x61, 0x74, 0x33, 0x20, // GetElement(mat3 + 0x5f, 0x6d, 0x74, 0x78, 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x5f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, // _mtx, int _colum + 0x6e, 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x5f, 0x72, 0x6f, 0x77, 0x29, 0x0a, 0x7b, 0x0a, 0x72, // n, int _row).{.r + 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x5f, 0x6d, 0x74, 0x78, 0x5b, 0x5f, 0x63, 0x6f, 0x6c, 0x75, // eturn _mtx[_colu + 0x6d, 0x6e, 0x5d, 0x5b, 0x5f, 0x72, 0x6f, 0x77, 0x5d, 0x3b, 0x0a, 0x7d, 0x0a, 0x66, 0x6c, 0x6f, // mn][_row];.}.flo + 0x61, 0x74, 0x20, 0x6d, 0x74, 0x78, 0x47, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, // at mtxGetElement + 0x28, 0x6d, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x6d, 0x74, 0x78, 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x20, // (mat4 _mtx, int + 0x5f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x5f, 0x72, 0x6f, // _column, int _ro + 0x77, 0x29, 0x0a, 0x7b, 0x0a, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x5f, 0x6d, 0x74, 0x78, // w).{.return _mtx + 0x5b, 0x5f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x5d, 0x5b, 0x5f, 0x72, 0x6f, 0x77, 0x5d, 0x3b, // [_column][_row]; + 0x0a, 0x7d, 0x0a, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, // .}.float select( + 0x62, 0x6f, 0x6f, 0x6c, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, // bool _cond, floa + 0x74, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, // t _true, float _ + 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, // false) { return + 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x3f, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x20, 0x3a, 0x20, // _cond ? _true : + 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x3b, 0x20, 0x7d, 0x0a, 0x76, 0x65, 0x63, 0x32, 0x20, 0x73, // _false; }.vec2 s + 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, 0x6f, 0x6f, 0x6c, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, // elect(bool _cond + 0x2c, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x76, 0x65, // , vec2 _true, ve + 0x63, 0x32, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, // c2 _false) { ret + 0x75, 0x72, 0x6e, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x3f, 0x20, 0x5f, 0x74, 0x72, 0x75, // urn _cond ? _tru + 0x65, 0x20, 0x3a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x3b, 0x20, 0x7d, 0x0a, 0x76, 0x65, // e : _false; }.ve + 0x63, 0x33, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, 0x6f, 0x6f, 0x6c, 0x20, 0x5f, // c3 select(bool _ + 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, // cond, vec3 _true + 0x2c, 0x20, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, 0x7b, // , vec3 _false) { + 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x3f, 0x20, // return _cond ? + 0x5f, 0x74, 0x72, 0x75, 0x65, 0x20, 0x3a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x3b, 0x20, // _true : _false; + 0x7d, 0x0a, 0x76, 0x65, 0x63, 0x34, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, 0x6f, // }.vec4 select(bo + 0x6f, 0x6c, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, // ol _cond, vec4 _ + 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, // true, vec4 _fals + 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x5f, 0x63, 0x6f, 0x6e, // e) { return _con + 0x64, 0x20, 0x3f, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x20, 0x3a, 0x20, 0x5f, 0x66, 0x61, 0x6c, // d ? _true : _fal + 0x73, 0x65, 0x3b, 0x20, 0x7d, 0x0a, 0x76, 0x65, 0x63, 0x32, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, // se; }.vec2 selec + 0x74, 0x28, 0x62, 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x76, // t(bvec2 _cond, v + 0x65, 0x63, 0x32, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, // ec2 _true, vec2 + 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, // _false) { return + 0x20, 0x28, 0x76, 0x65, 0x63, 0x32, 0x28, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x29, 0x20, 0x2a, 0x20, // (vec2(_cond) * + 0x5f, 0x74, 0x72, 0x75, 0x65, 0x29, 0x20, 0x2b, 0x20, 0x28, 0x76, 0x65, 0x63, 0x32, 0x28, 0x6e, // _true) + (vec2(n + 0x6f, 0x74, 0x28, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x29, 0x20, 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x66, // ot(_cond) ) * _f + 0x61, 0x6c, 0x73, 0x65, 0x29, 0x3b, 0x20, 0x7d, 0x0a, 0x76, 0x65, 0x63, 0x33, 0x20, 0x73, 0x65, // alse); }.vec3 se + 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, // lect(bvec3 _cond + 0x2c, 0x20, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x76, 0x65, // , vec3 _true, ve + 0x63, 0x33, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, // c3 _false) { ret + 0x75, 0x72, 0x6e, 0x20, 0x28, 0x76, 0x65, 0x63, 0x33, 0x28, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x29, // urn (vec3(_cond) + 0x20, 0x2a, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x29, 0x20, 0x2b, 0x20, 0x28, 0x76, 0x65, 0x63, // * _true) + (vec + 0x33, 0x28, 0x6e, 0x6f, 0x74, 0x28, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x29, 0x20, 0x29, 0x20, 0x2a, // 3(not(_cond) ) * + 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x3b, 0x20, 0x7d, 0x0a, 0x76, 0x65, 0x63, 0x34, // _false); }.vec4 + 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x63, // select(bvec4 _c + 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x2c, // ond, vec4 _true, + 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, 0x7b, 0x20, // vec4 _false) { + 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x28, 0x76, 0x65, 0x63, 0x34, 0x28, 0x5f, 0x63, 0x6f, // return (vec4(_co + 0x6e, 0x64, 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x29, 0x20, 0x2b, 0x20, 0x28, // nd) * _true) + ( + 0x76, 0x65, 0x63, 0x34, 0x28, 0x6e, 0x6f, 0x74, 0x28, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x29, 0x20, // vec4(not(_cond) + 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x3b, 0x20, 0x7d, 0x0a, 0x69, // ) * _false); }.i + 0x6e, 0x74, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, 0x6f, 0x6f, 0x6c, 0x20, 0x5f, // nt select(bool _ + 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x2c, // cond, int _true, + 0x20, 0x69, 0x6e, 0x74, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, // int _false) { r + 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x3f, 0x20, 0x5f, 0x74, // eturn _cond ? _t + 0x72, 0x75, 0x65, 0x20, 0x3a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x3b, 0x20, 0x7d, 0x0a, // rue : _false; }. + 0x69, 0x76, 0x65, 0x63, 0x32, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, 0x6f, 0x6f, // ivec2 select(boo + 0x6c, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x69, 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, // l _cond, ivec2 _ + 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x69, 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x66, 0x61, 0x6c, // true, ivec2 _fal + 0x73, 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x5f, 0x63, 0x6f, // se) { return _co + 0x6e, 0x64, 0x20, 0x3f, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x20, 0x3a, 0x20, 0x5f, 0x66, 0x61, // nd ? _true : _fa + 0x6c, 0x73, 0x65, 0x3b, 0x20, 0x7d, 0x0a, 0x69, 0x76, 0x65, 0x63, 0x33, 0x20, 0x73, 0x65, 0x6c, // lse; }.ivec3 sel + 0x65, 0x63, 0x74, 0x28, 0x62, 0x6f, 0x6f, 0x6c, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, // ect(bool _cond, + 0x69, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x69, 0x76, 0x65, // ivec3 _true, ive + 0x63, 0x33, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, // c3 _false) { ret + 0x75, 0x72, 0x6e, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x3f, 0x20, 0x5f, 0x74, 0x72, 0x75, // urn _cond ? _tru + 0x65, 0x20, 0x3a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x3b, 0x20, 0x7d, 0x0a, 0x69, 0x76, // e : _false; }.iv + 0x65, 0x63, 0x34, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, 0x6f, 0x6f, 0x6c, 0x20, // ec4 select(bool + 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x69, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x74, 0x72, // _cond, ivec4 _tr + 0x75, 0x65, 0x2c, 0x20, 0x69, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, // ue, ivec4 _false + 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, // ) { return _cond + 0x20, 0x3f, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x20, 0x3a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, // ? _true : _fals + 0x65, 0x3b, 0x20, 0x7d, 0x0a, 0x69, 0x76, 0x65, 0x63, 0x32, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, // e; }.ivec2 selec + 0x74, 0x28, 0x62, 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x69, // t(bvec2 _cond, i + 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x69, 0x76, 0x65, 0x63, // vec2 _true, ivec + 0x32, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, // 2 _false) { retu + 0x72, 0x6e, 0x20, 0x28, 0x69, 0x76, 0x65, 0x63, 0x32, 0x28, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x29, // rn (ivec2(_cond) + 0x20, 0x2a, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x29, 0x20, 0x2b, 0x20, 0x28, 0x69, 0x76, 0x65, // * _true) + (ive + 0x63, 0x32, 0x28, 0x6e, 0x6f, 0x74, 0x28, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x29, 0x20, 0x29, 0x20, // c2(not(_cond) ) + 0x2a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x3b, 0x20, 0x7d, 0x0a, 0x69, 0x76, 0x65, // * _false); }.ive + 0x63, 0x33, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, 0x76, 0x65, 0x63, 0x33, 0x20, // c3 select(bvec3 + 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x69, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x74, 0x72, // _cond, ivec3 _tr + 0x75, 0x65, 0x2c, 0x20, 0x69, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, // ue, ivec3 _false + 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x28, 0x69, 0x76, 0x65, 0x63, // ) { return (ivec + 0x33, 0x28, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, // 3(_cond) * _true + 0x29, 0x20, 0x2b, 0x20, 0x28, 0x69, 0x76, 0x65, 0x63, 0x33, 0x28, 0x6e, 0x6f, 0x74, 0x28, 0x5f, // ) + (ivec3(not(_ + 0x63, 0x6f, 0x6e, 0x64, 0x29, 0x20, 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, // cond) ) * _false + 0x29, 0x3b, 0x20, 0x7d, 0x0a, 0x69, 0x76, 0x65, 0x63, 0x34, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, // ); }.ivec4 selec + 0x74, 0x28, 0x62, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x69, // t(bvec4 _cond, i + 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x69, 0x76, 0x65, 0x63, // vec4 _true, ivec + 0x34, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, // 4 _false) { retu + 0x72, 0x6e, 0x20, 0x28, 0x69, 0x76, 0x65, 0x63, 0x34, 0x28, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x29, // rn (ivec4(_cond) + 0x20, 0x2a, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x29, 0x20, 0x2b, 0x20, 0x28, 0x69, 0x76, 0x65, // * _true) + (ive + 0x63, 0x34, 0x28, 0x6e, 0x6f, 0x74, 0x28, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x29, 0x20, 0x29, 0x20, // c4(not(_cond) ) + 0x2a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x3b, 0x20, 0x7d, 0x0a, 0x75, 0x69, 0x6e, // * _false); }.uin + 0x74, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, 0x6f, 0x6f, 0x6c, 0x20, 0x5f, 0x63, // t select(bool _c + 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x75, 0x69, 0x6e, 0x74, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x2c, // ond, uint _true, + 0x20, 0x75, 0x69, 0x6e, 0x74, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, 0x7b, 0x20, // uint _false) { + 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x3f, 0x20, 0x5f, // return _cond ? _ + 0x74, 0x72, 0x75, 0x65, 0x20, 0x3a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x3b, 0x20, 0x7d, // true : _false; } + 0x0a, 0x75, 0x76, 0x65, 0x63, 0x32, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, 0x6f, // .uvec2 select(bo + 0x6f, 0x6c, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x75, 0x76, 0x65, 0x63, 0x32, 0x20, // ol _cond, uvec2 + 0x5f, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x75, 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x66, 0x61, // _true, uvec2 _fa + 0x6c, 0x73, 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x5f, 0x63, // lse) { return _c + 0x6f, 0x6e, 0x64, 0x20, 0x3f, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x20, 0x3a, 0x20, 0x5f, 0x66, // ond ? _true : _f + 0x61, 0x6c, 0x73, 0x65, 0x3b, 0x20, 0x7d, 0x0a, 0x75, 0x76, 0x65, 0x63, 0x33, 0x20, 0x73, 0x65, // alse; }.uvec3 se + 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, 0x6f, 0x6f, 0x6c, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, // lect(bool _cond, + 0x20, 0x75, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x75, 0x76, // uvec3 _true, uv + 0x65, 0x63, 0x33, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, // ec3 _false) { re + 0x74, 0x75, 0x72, 0x6e, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x3f, 0x20, 0x5f, 0x74, 0x72, // turn _cond ? _tr + 0x75, 0x65, 0x20, 0x3a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x3b, 0x20, 0x7d, 0x0a, 0x75, // ue : _false; }.u + 0x76, 0x65, 0x63, 0x34, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, 0x6f, 0x6f, 0x6c, // vec4 select(bool + 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x75, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x74, // _cond, uvec4 _t + 0x72, 0x75, 0x65, 0x2c, 0x20, 0x75, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, // rue, uvec4 _fals + 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x5f, 0x63, 0x6f, 0x6e, // e) { return _con + 0x64, 0x20, 0x3f, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x20, 0x3a, 0x20, 0x5f, 0x66, 0x61, 0x6c, // d ? _true : _fal + 0x73, 0x65, 0x3b, 0x20, 0x7d, 0x0a, 0x75, 0x76, 0x65, 0x63, 0x32, 0x20, 0x73, 0x65, 0x6c, 0x65, // se; }.uvec2 sele + 0x63, 0x74, 0x28, 0x62, 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, // ct(bvec2 _cond, + 0x75, 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x75, 0x76, 0x65, // uvec2 _true, uve + 0x63, 0x32, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, // c2 _false) { ret + 0x75, 0x72, 0x6e, 0x20, 0x28, 0x75, 0x76, 0x65, 0x63, 0x32, 0x28, 0x5f, 0x63, 0x6f, 0x6e, 0x64, // urn (uvec2(_cond + 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x29, 0x20, 0x2b, 0x20, 0x28, 0x75, 0x76, // ) * _true) + (uv + 0x65, 0x63, 0x32, 0x28, 0x6e, 0x6f, 0x74, 0x28, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x29, 0x20, 0x29, // ec2(not(_cond) ) + 0x20, 0x2a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x3b, 0x20, 0x7d, 0x0a, 0x75, 0x76, // * _false); }.uv + 0x65, 0x63, 0x33, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, 0x62, 0x76, 0x65, 0x63, 0x33, // ec3 select(bvec3 + 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, 0x75, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x74, // _cond, uvec3 _t + 0x72, 0x75, 0x65, 0x2c, 0x20, 0x75, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, // rue, uvec3 _fals + 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x28, 0x75, 0x76, 0x65, // e) { return (uve + 0x63, 0x33, 0x28, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x74, 0x72, 0x75, // c3(_cond) * _tru + 0x65, 0x29, 0x20, 0x2b, 0x20, 0x28, 0x75, 0x76, 0x65, 0x63, 0x33, 0x28, 0x6e, 0x6f, 0x74, 0x28, // e) + (uvec3(not( + 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x29, 0x20, 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, // _cond) ) * _fals + 0x65, 0x29, 0x3b, 0x20, 0x7d, 0x0a, 0x75, 0x76, 0x65, 0x63, 0x34, 0x20, 0x73, 0x65, 0x6c, 0x65, // e); }.uvec4 sele + 0x63, 0x74, 0x28, 0x62, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x2c, 0x20, // ct(bvec4 _cond, + 0x75, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x75, 0x76, 0x65, // uvec4 _true, uve + 0x63, 0x34, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, 0x7b, 0x20, 0x72, 0x65, 0x74, // c4 _false) { ret + 0x75, 0x72, 0x6e, 0x20, 0x28, 0x75, 0x76, 0x65, 0x63, 0x34, 0x28, 0x5f, 0x63, 0x6f, 0x6e, 0x64, // urn (uvec4(_cond + 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x74, 0x72, 0x75, 0x65, 0x29, 0x20, 0x2b, 0x20, 0x28, 0x75, 0x76, // ) * _true) + (uv + 0x65, 0x63, 0x34, 0x28, 0x6e, 0x6f, 0x74, 0x28, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x29, 0x20, 0x29, // ec4(not(_cond) ) + 0x20, 0x2a, 0x20, 0x5f, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x3b, 0x20, 0x7d, 0x0a, 0x75, 0x6e, // * _false); }.un + 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x75, 0x5f, 0x76, 0x69, 0x65, // iform vec4 u_vie + 0x77, 0x52, 0x65, 0x63, 0x74, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x76, // wRect;.uniform v + 0x65, 0x63, 0x34, 0x20, 0x75, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x54, 0x65, 0x78, 0x65, 0x6c, 0x3b, // ec4 u_viewTexel; + 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x20, 0x75, 0x5f, // .uniform mat4 u_ + 0x76, 0x69, 0x65, 0x77, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6d, 0x61, // view;.uniform ma + 0x74, 0x34, 0x20, 0x75, 0x5f, 0x69, 0x6e, 0x76, 0x56, 0x69, 0x65, 0x77, 0x3b, 0x0a, 0x75, 0x6e, // t4 u_invView;.un + 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x20, 0x75, 0x5f, 0x70, 0x72, 0x6f, // iform mat4 u_pro + 0x6a, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x20, // j;.uniform mat4 + 0x75, 0x5f, 0x69, 0x6e, 0x76, 0x50, 0x72, 0x6f, 0x6a, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, // u_invProj;.unifo + 0x72, 0x6d, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x20, 0x75, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x50, 0x72, // rm mat4 u_viewPr + 0x6f, 0x6a, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6d, 0x61, 0x74, 0x34, // oj;.uniform mat4 + 0x20, 0x75, 0x5f, 0x69, 0x6e, 0x76, 0x56, 0x69, 0x65, 0x77, 0x50, 0x72, 0x6f, 0x6a, 0x3b, 0x0a, // u_invViewProj;. + 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x20, 0x75, 0x5f, 0x6d, // uniform mat4 u_m + 0x6f, 0x64, 0x65, 0x6c, 0x56, 0x69, 0x65, 0x77, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, // odelView;.unifor + 0x6d, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x20, 0x75, 0x5f, 0x69, 0x6e, 0x76, 0x4d, 0x6f, 0x64, 0x65, // m mat4 u_invMode + 0x6c, 0x56, 0x69, 0x65, 0x77, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6d, // lView;.uniform m + 0x61, 0x74, 0x34, 0x20, 0x75, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x56, 0x69, 0x65, 0x77, 0x50, // at4 u_modelViewP + 0x72, 0x6f, 0x6a, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x76, 0x65, 0x63, // roj;.uniform vec + 0x34, 0x20, 0x75, 0x5f, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x52, 0x65, 0x66, 0x34, 0x3b, 0x0a, 0x75, // 4 u_alphaRef4;.u + 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x20, 0x75, 0x5f, 0x6d, 0x6f, // niform mat4 u_mo + 0x64, 0x65, 0x6c, 0x5b, 0x31, 0x5d, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, // del[1];.uniform + 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x32, 0x44, 0x20, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x74, // sampler2D bgfx_t + 0x65, 0x78, 0x59, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x73, 0x61, 0x6d, // exY;.uniform sam + 0x70, 0x6c, 0x65, 0x72, 0x32, 0x44, 0x20, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, 0x43, // pler2D bgfx_texC + 0x62, 0x43, 0x72, 0x3b, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, 0x72, 0x67, 0x62, 0x61, // bCr;.layout(rgba + 0x38, 0x2c, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x3d, 0x30, 0x29, 0x20, 0x77, 0x72, // 8, binding=0) wr + 0x69, 0x74, 0x65, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, // iteonly uniform + 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x32, 0x44, 0x20, 0x62, 0x67, // highp image2D bg + 0x66, 0x78, 0x5f, 0x69, 0x6d, 0x67, 0x52, 0x67, 0x62, 0x61, 0x3b, 0x0a, 0x6c, 0x61, 0x79, 0x6f, // fx_imgRgba;.layo + 0x75, 0x74, 0x20, 0x28, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x78, // ut (local_size_x + 0x20, 0x3d, 0x20, 0x38, 0x2c, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x73, 0x69, 0x7a, 0x65, // = 8, local_size + 0x5f, 0x79, 0x20, 0x3d, 0x20, 0x38, 0x2c, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x73, 0x69, // _y = 8, local_si + 0x7a, 0x65, 0x5f, 0x7a, 0x20, 0x3d, 0x20, 0x31, 0x29, 0x20, 0x69, 0x6e, 0x3b, 0x0a, 0x76, 0x6f, // ze_z = 1) in;.vo + 0x69, 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x7b, 0x0a, 0x69, 0x76, 0x65, 0x63, // id main().{.ivec + 0x32, 0x20, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x20, 0x3d, 0x20, 0x69, 0x76, 0x65, 0x63, 0x32, 0x28, // 2 coord = ivec2( + 0x67, 0x6c, 0x5f, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x63, 0x61, 0x74, // gl_GlobalInvocat + 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x2e, 0x78, 0x79, 0x29, 0x3b, 0x0a, 0x69, 0x76, 0x65, 0x63, 0x32, // ionID.xy);.ivec2 + 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x3d, 0x20, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, // size = imageSiz + 0x65, 0x28, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x69, 0x6d, 0x67, 0x52, 0x67, 0x62, 0x61, 0x29, 0x3b, // e(bgfx_imgRgba); + 0x0a, 0x69, 0x66, 0x20, 0x28, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x78, 0x20, 0x3e, 0x3d, 0x20, // .if (coord.x >= + 0x73, 0x69, 0x7a, 0x65, 0x2e, 0x78, 0x0a, 0x7c, 0x7c, 0x20, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x2e, // size.x.|| coord. + 0x79, 0x20, 0x3e, 0x3d, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x2e, 0x79, 0x29, 0x0a, 0x7b, 0x0a, 0x72, // y >= size.y).{.r + 0x65, 0x74, 0x75, 0x72, 0x6e, 0x3b, 0x0a, 0x7d, 0x0a, 0x76, 0x65, 0x63, 0x32, 0x20, 0x69, 0x6e, // eturn;.}.vec2 in + 0x76, 0x53, 0x69, 0x7a, 0x65, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x32, 0x28, 0x31, 0x2e, 0x30, // vSize = vec2(1.0 + 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x20, 0x2f, 0x20, 0x76, 0x65, 0x63, 0x32, 0x28, 0x73, 0x69, // , 1.0) / vec2(si + 0x7a, 0x65, 0x29, 0x3b, 0x0a, 0x76, 0x65, 0x63, 0x32, 0x20, 0x75, 0x76, 0x20, 0x3d, 0x20, 0x28, // ze);.vec2 uv = ( + 0x76, 0x65, 0x63, 0x32, 0x28, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x29, 0x20, 0x2b, 0x20, 0x76, 0x65, // vec2(coord) + ve + 0x63, 0x32, 0x28, 0x30, 0x2e, 0x35, 0x2c, 0x20, 0x30, 0x2e, 0x35, 0x29, 0x20, 0x29, 0x20, 0x2a, // c2(0.5, 0.5) ) * + 0x20, 0x69, 0x6e, 0x76, 0x53, 0x69, 0x7a, 0x65, 0x3b, 0x0a, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, // invSize;.float + 0x79, 0x20, 0x3d, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x4c, 0x6f, 0x64, 0x28, 0x62, // y = textureLod(b + 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, 0x59, 0x2c, 0x20, 0x75, 0x76, 0x2c, 0x20, 0x30, 0x2e, // gfx_texY, uv, 0. + 0x30, 0x29, 0x2e, 0x78, 0x3b, 0x0a, 0x76, 0x65, 0x63, 0x32, 0x20, 0x63, 0x62, 0x20, 0x3d, 0x20, // 0).x;.vec2 cb = + 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x4c, 0x6f, 0x64, 0x28, 0x62, 0x67, 0x66, 0x78, 0x5f, // textureLod(bgfx_ + 0x74, 0x65, 0x78, 0x43, 0x62, 0x43, 0x72, 0x2c, 0x20, 0x75, 0x76, 0x2c, 0x20, 0x30, 0x2e, 0x30, // texCbCr, uv, 0.0 + 0x29, 0x2e, 0x78, 0x79, 0x3b, 0x0a, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x79, 0x4c, 0x20, 0x3d, // ).xy;.float yL = + 0x20, 0x28, 0x79, 0x20, 0x2d, 0x20, 0x31, 0x36, 0x2e, 0x30, 0x2f, 0x32, 0x35, 0x35, 0x2e, 0x30, // (y - 16.0/255.0 + 0x29, 0x20, 0x2a, 0x20, 0x28, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x2f, 0x32, 0x31, 0x39, 0x2e, 0x30, // ) * (255.0/219.0 + 0x29, 0x3b, 0x0a, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x63, 0x62, 0x4c, 0x20, 0x3d, 0x20, 0x28, // );.float cbL = ( + 0x63, 0x62, 0x2e, 0x78, 0x20, 0x2d, 0x20, 0x31, 0x32, 0x38, 0x2e, 0x30, 0x2f, 0x32, 0x35, 0x35, // cb.x - 128.0/255 + 0x2e, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x2f, 0x32, 0x32, 0x34, // .0) * (255.0/224 + 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x63, 0x72, 0x4c, 0x20, 0x3d, // .0);.float crL = + 0x20, 0x28, 0x63, 0x62, 0x2e, 0x79, 0x20, 0x2d, 0x20, 0x31, 0x32, 0x38, 0x2e, 0x30, 0x2f, 0x32, // (cb.y - 128.0/2 + 0x35, 0x35, 0x2e, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x32, 0x35, 0x35, 0x2e, 0x30, 0x2f, 0x32, // 55.0) * (255.0/2 + 0x32, 0x34, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x76, 0x65, 0x63, 0x33, 0x20, 0x72, 0x67, 0x62, 0x3b, // 24.0);.vec3 rgb; + 0x0a, 0x72, 0x67, 0x62, 0x2e, 0x72, 0x20, 0x3d, 0x20, 0x79, 0x4c, 0x20, 0x2b, 0x20, 0x31, 0x2e, // .rgb.r = yL + 1. + 0x35, 0x37, 0x34, 0x38, 0x20, 0x2a, 0x20, 0x63, 0x72, 0x4c, 0x3b, 0x0a, 0x72, 0x67, 0x62, 0x2e, // 5748 * crL;.rgb. + 0x67, 0x20, 0x3d, 0x20, 0x79, 0x4c, 0x20, 0x2d, 0x20, 0x30, 0x2e, 0x31, 0x38, 0x37, 0x33, 0x20, // g = yL - 0.1873 + 0x2a, 0x20, 0x63, 0x62, 0x4c, 0x20, 0x2d, 0x20, 0x30, 0x2e, 0x34, 0x36, 0x38, 0x31, 0x20, 0x2a, // * cbL - 0.4681 * + 0x20, 0x63, 0x72, 0x4c, 0x3b, 0x0a, 0x72, 0x67, 0x62, 0x2e, 0x62, 0x20, 0x3d, 0x20, 0x79, 0x4c, // crL;.rgb.b = yL + 0x20, 0x2b, 0x20, 0x31, 0x2e, 0x38, 0x35, 0x35, 0x36, 0x20, 0x2a, 0x20, 0x63, 0x62, 0x4c, 0x3b, // + 1.8556 * cbL; + 0x0a, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x28, 0x62, 0x67, 0x66, 0x78, // .imageStore(bgfx + 0x5f, 0x69, 0x6d, 0x67, 0x52, 0x67, 0x62, 0x61, 0x2c, 0x20, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x2c, // _imgRgba, coord, + 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x72, 0x67, 0x62, 0x2c, // vec4(clamp(rgb, + 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, // 0.0, 1.0), 1.0) + 0x20, 0x29, 0x3b, 0x0a, 0x7d, 0x0a, 0x00, // );.}.. +}; +static const uint8_t cs_yuv_to_rgb_spv[2372] = +{ + 0x43, 0x53, 0x48, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x09, 0x62, // CSH............b + 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, 0x59, 0x20, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x02, // gfx_texY ....... + 0x26, 0x00, 0x0c, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, 0x43, 0x62, 0x43, 0x72, 0x20, // &..bgfx_texCbCr + 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x26, 0x00, 0x0c, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x69, // .......&..bgfx_i + 0x6d, 0x67, 0x52, 0x67, 0x62, 0x61, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x00, 0x02, 0x47, 0x00, // mgRgba........G. + 0xec, 0x08, 0x00, 0x00, 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x00, 0x08, 0x00, // ......#......... + 0x9e, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, // ................ + 0x11, 0x00, 0x02, 0x00, 0x32, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, // ....2........... + 0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30, 0x00, 0x00, 0x00, 0x00, // GLSL.std.450.... + 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x06, 0x00, // ................ + 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, // ........main.... + 0xd7, 0x00, 0x00, 0x00, 0x10, 0x00, 0x06, 0x00, 0x04, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, // ................ + 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, // ................ + 0x05, 0x00, 0x00, 0x00, 0xf4, 0x01, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, // ................ + 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x07, 0x00, 0x2e, 0x00, 0x00, 0x00, // main............ + 0x62, 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, 0x59, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, // bgfx_texYSampler + 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x07, 0x00, 0x31, 0x00, 0x00, 0x00, 0x62, 0x67, 0x66, 0x78, // ........1...bgfx + 0x5f, 0x74, 0x65, 0x78, 0x59, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x00, 0x00, 0x00, 0x00, // _texYTexture.... + 0x05, 0x00, 0x07, 0x00, 0x35, 0x00, 0x00, 0x00, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, // ....5...bgfx_tex + 0x43, 0x62, 0x43, 0x72, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x00, 0x05, 0x00, 0x07, 0x00, // CbCrSampler..... + 0x37, 0x00, 0x00, 0x00, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, 0x43, 0x62, 0x43, 0x72, // 7...bgfx_texCbCr + 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x00, 0x05, 0x00, 0x06, 0x00, 0x68, 0x00, 0x00, 0x00, // Texture.....h... + 0x62, 0x67, 0x66, 0x78, 0x5f, 0x69, 0x6d, 0x67, 0x52, 0x67, 0x62, 0x61, 0x00, 0x00, 0x00, 0x00, // bgfx_imgRgba.... + 0x05, 0x00, 0x08, 0x00, 0xd7, 0x00, 0x00, 0x00, 0x67, 0x6c, 0x5f, 0x47, 0x6c, 0x6f, 0x62, 0x61, // ........gl_Globa + 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x00, 0x00, 0x00, // lInvocationID... + 0x47, 0x00, 0x04, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, // G.......!....... + 0x47, 0x00, 0x04, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // G......."....... + 0x47, 0x00, 0x04, 0x00, 0x31, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, // G...1...!....... + 0x47, 0x00, 0x04, 0x00, 0x31, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // G...1..."....... + 0x47, 0x00, 0x04, 0x00, 0x35, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, // G...5...!....... + 0x47, 0x00, 0x04, 0x00, 0x35, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // G...5..."....... + 0x47, 0x00, 0x04, 0x00, 0x37, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, // G...7...!....... + 0x47, 0x00, 0x04, 0x00, 0x37, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // G...7..."....... + 0x47, 0x00, 0x03, 0x00, 0x68, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, // G...h.......G... + 0x68, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, // h...!.......G... + 0x68, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, // h...".......G... + 0xd7, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, // ................ + 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // ....!........... + 0x1a, 0x00, 0x02, 0x00, 0x06, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00, 0x07, 0x00, 0x00, 0x00, // ................ + 0x20, 0x00, 0x00, 0x00, 0x19, 0x00, 0x09, 0x00, 0x08, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, // ............... + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00, // ................ + 0x07, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x0e, 0x00, 0x00, 0x00, // ................ + 0x07, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, // ................ + 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, // ............... + 0x17, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x25, 0x00, 0x00, 0x00, // ............%... + 0x17, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x2d, 0x00, 0x00, 0x00, // ........ ...-... + 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x2d, 0x00, 0x00, 0x00, // ........;...-... + 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x30, 0x00, 0x00, 0x00, // ........ ...0... + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x30, 0x00, 0x00, 0x00, // ........;...0... + 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x2d, 0x00, 0x00, 0x00, // 1.......;...-... + 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x30, 0x00, 0x00, 0x00, // 5.......;...0... + 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x03, 0x00, 0x42, 0x00, 0x00, 0x00, // 7...........B... + 0x08, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, // ........N... ... + 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x4e, 0x00, 0x00, 0x00, // ........O...N... + 0x02, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, // ....+...N...U... + 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x09, 0x00, 0x66, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, // ........f....... + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x67, 0x00, 0x00, 0x00, // ........ ...g... + 0x00, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x67, 0x00, 0x00, 0x00, // ....f...;...g... + 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x02, 0x00, 0x71, 0x00, 0x00, 0x00, // h...........q... + 0x2b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, // +.......}......? + 0x2c, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x7d, 0x00, 0x00, 0x00, // ,.......~...}... + 0x7d, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x85, 0x00, 0x00, 0x00, // }...+........... + 0x00, 0x00, 0x00, 0x3f, 0x2c, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00, // ...?,........... + 0x85, 0x00, 0x00, 0x00, 0x85, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, // ........+....... + 0x8b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, // ........+....... + 0x9d, 0x00, 0x00, 0x00, 0x81, 0x80, 0x80, 0x3d, 0x2b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, // .......=+....... + 0x9f, 0x00, 0x00, 0x00, 0x85, 0x0a, 0x95, 0x3f, 0x2b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, // .......?+....... + 0xa4, 0x00, 0x00, 0x00, 0x81, 0x80, 0x00, 0x3f, 0x17, 0x00, 0x04, 0x00, 0xad, 0x00, 0x00, 0x00, // .......?........ + 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x06, 0x00, 0xad, 0x00, 0x00, 0x00, // ........,....... + 0xc8, 0x00, 0x00, 0x00, 0x8b, 0x00, 0x00, 0x00, 0x8b, 0x00, 0x00, 0x00, 0x8b, 0x00, 0x00, 0x00, // ................ + 0x2c, 0x00, 0x06, 0x00, 0xad, 0x00, 0x00, 0x00, 0xc9, 0x00, 0x00, 0x00, 0x7d, 0x00, 0x00, 0x00, // ,...........}... + 0x7d, 0x00, 0x00, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0xd6, 0x00, 0x00, 0x00, // }...}... ....... + 0x01, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0xd6, 0x00, 0x00, 0x00, // ....%...;....... + 0xd7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, // ........+....... + 0x99, 0x01, 0x00, 0x00, 0x8a, 0x78, 0xe5, 0x3f, 0x2b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, // .....x.?+....... + 0x9a, 0x01, 0x00, 0x00, 0x99, 0x56, 0x5a, 0x3e, 0x2b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, // .....VZ>+....... + 0x9b, 0x01, 0x00, 0x00, 0xef, 0x6a, 0x08, 0x3f, 0x2b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, // .....j.?+....... + 0x9c, 0x01, 0x00, 0x00, 0x97, 0x31, 0x07, 0x40, 0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, // .....1.@6....... + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, // ................ + 0x05, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, // ....=......./... + 0x2e, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, // ....=.......2... + 0x31, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, // 1...=.......6... + 0x35, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, // 5...=.......8... + 0x37, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x25, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00, // 7...=...%....... + 0xd7, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x03, 0x00, 0x55, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........U....... + 0xfb, 0x00, 0x03, 0x00, 0x55, 0x00, 0x00, 0x00, 0x0d, 0x01, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, // ....U........... + 0x0d, 0x01, 0x00, 0x00, 0x4f, 0x00, 0x07, 0x00, 0x18, 0x00, 0x00, 0x00, 0x0f, 0x01, 0x00, 0x00, // ....O........... + 0xd8, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // ................ + 0x3d, 0x00, 0x04, 0x00, 0x66, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, // =...f.......h... + 0x68, 0x00, 0x04, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x5b, 0x01, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, // h...O...[....... + 0x7c, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, 0x63, 0x01, 0x00, 0x00, 0x5b, 0x01, 0x00, 0x00, // |.......c...[... + 0x51, 0x00, 0x05, 0x00, 0x17, 0x00, 0x00, 0x00, 0x13, 0x01, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00, // Q............... + 0x00, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x17, 0x00, 0x00, 0x00, 0x15, 0x01, 0x00, 0x00, // ....Q........... + 0x63, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaf, 0x00, 0x05, 0x00, 0x71, 0x00, 0x00, 0x00, // c...........q... + 0x16, 0x01, 0x00, 0x00, 0x13, 0x01, 0x00, 0x00, 0x15, 0x01, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, // ............Q... + 0x17, 0x00, 0x00, 0x00, 0x18, 0x01, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // ................ + 0x51, 0x00, 0x05, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1a, 0x01, 0x00, 0x00, 0x63, 0x01, 0x00, 0x00, // Q...........c... + 0x01, 0x00, 0x00, 0x00, 0xaf, 0x00, 0x05, 0x00, 0x71, 0x00, 0x00, 0x00, 0x1b, 0x01, 0x00, 0x00, // ........q....... + 0x18, 0x01, 0x00, 0x00, 0x1a, 0x01, 0x00, 0x00, 0xa6, 0x00, 0x05, 0x00, 0x71, 0x00, 0x00, 0x00, // ............q... + 0x1c, 0x01, 0x00, 0x00, 0x16, 0x01, 0x00, 0x00, 0x1b, 0x01, 0x00, 0x00, 0xf7, 0x00, 0x03, 0x00, // ................ + 0x1e, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, 0x1c, 0x01, 0x00, 0x00, // ................ + 0x1d, 0x01, 0x00, 0x00, 0x1e, 0x01, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x1d, 0x01, 0x00, 0x00, // ................ + 0xf9, 0x00, 0x02, 0x00, 0x55, 0x01, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x1e, 0x01, 0x00, 0x00, // ....U........... + 0x6f, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x63, 0x01, 0x00, 0x00, // o....... ...c... + 0x88, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x21, 0x01, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, // ........!...~... + 0x20, 0x01, 0x00, 0x00, 0x6f, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x23, 0x01, 0x00, 0x00, // ...o.......#... + 0x0f, 0x01, 0x00, 0x00, 0x81, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x24, 0x01, 0x00, 0x00, // ............$... + 0x23, 0x01, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, 0x00, // #............... + 0x26, 0x01, 0x00, 0x00, 0x24, 0x01, 0x00, 0x00, 0x21, 0x01, 0x00, 0x00, 0x56, 0x00, 0x05, 0x00, // &...$...!...V... + 0x42, 0x00, 0x00, 0x00, 0x6a, 0x01, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, // B...j...2.../... + 0x58, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x6d, 0x01, 0x00, 0x00, 0x6a, 0x01, 0x00, 0x00, // X.......m...j... + 0x26, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x8b, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, // &...........Q... + 0x07, 0x00, 0x00, 0x00, 0x2a, 0x01, 0x00, 0x00, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ....*...m....... + 0x56, 0x00, 0x05, 0x00, 0x42, 0x00, 0x00, 0x00, 0x74, 0x01, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, // V...B...t...8... + 0x36, 0x00, 0x00, 0x00, 0x58, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x77, 0x01, 0x00, 0x00, // 6...X.......w... + 0x74, 0x01, 0x00, 0x00, 0x26, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x8b, 0x00, 0x00, 0x00, // t...&........... + 0x83, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x30, 0x01, 0x00, 0x00, 0x2a, 0x01, 0x00, 0x00, // ........0...*... + 0x9d, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x31, 0x01, 0x00, 0x00, // ............1... + 0x30, 0x01, 0x00, 0x00, 0x9f, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, // 0.......Q....... + 0x33, 0x01, 0x00, 0x00, 0x77, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, // 3...w........... + 0x07, 0x00, 0x00, 0x00, 0x34, 0x01, 0x00, 0x00, 0x33, 0x01, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x00, // ....4...3....... + 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x37, 0x01, 0x00, 0x00, 0x77, 0x01, 0x00, 0x00, // Q.......7...w... + 0x01, 0x00, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, // ............8... + 0x37, 0x01, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, // 7............... + 0x3c, 0x01, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x99, 0x01, 0x00, 0x00, 0x81, 0x00, 0x05, 0x00, // <...8........... + 0x07, 0x00, 0x00, 0x00, 0x3d, 0x01, 0x00, 0x00, 0x31, 0x01, 0x00, 0x00, 0x3c, 0x01, 0x00, 0x00, // ....=...1...<... + 0x85, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x41, 0x01, 0x00, 0x00, 0x34, 0x01, 0x00, 0x00, // ........A...4... + 0x9a, 0x01, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x42, 0x01, 0x00, 0x00, // ............B... + 0x31, 0x01, 0x00, 0x00, 0x41, 0x01, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, // 1...A........... + 0x44, 0x01, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x9b, 0x01, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, // D...8........... + 0x07, 0x00, 0x00, 0x00, 0x45, 0x01, 0x00, 0x00, 0x42, 0x01, 0x00, 0x00, 0x44, 0x01, 0x00, 0x00, // ....E...B...D... + 0x85, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x49, 0x01, 0x00, 0x00, 0x34, 0x01, 0x00, 0x00, // ........I...4... + 0x9c, 0x01, 0x00, 0x00, 0x81, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x4a, 0x01, 0x00, 0x00, // ............J... + 0x31, 0x01, 0x00, 0x00, 0x49, 0x01, 0x00, 0x00, 0x50, 0x00, 0x06, 0x00, 0xad, 0x00, 0x00, 0x00, // 1...I...P....... + 0x9d, 0x01, 0x00, 0x00, 0x3d, 0x01, 0x00, 0x00, 0x45, 0x01, 0x00, 0x00, 0x4a, 0x01, 0x00, 0x00, // ....=...E...J... + 0x0c, 0x00, 0x08, 0x00, 0xad, 0x00, 0x00, 0x00, 0x4d, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // ........M....... + 0x2b, 0x00, 0x00, 0x00, 0x9d, 0x01, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x00, 0xc9, 0x00, 0x00, 0x00, // +............... + 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x4e, 0x01, 0x00, 0x00, 0x4d, 0x01, 0x00, 0x00, // Q.......N...M... + 0x00, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x4f, 0x01, 0x00, 0x00, // ....Q.......O... + 0x4d, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, // M.......Q....... + 0x50, 0x01, 0x00, 0x00, 0x4d, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x50, 0x00, 0x07, 0x00, // P...M.......P... + 0x0e, 0x00, 0x00, 0x00, 0x51, 0x01, 0x00, 0x00, 0x4e, 0x01, 0x00, 0x00, 0x4f, 0x01, 0x00, 0x00, // ....Q...N...O... + 0x50, 0x01, 0x00, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x66, 0x00, 0x00, 0x00, // P...}...=...f... + 0x52, 0x01, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x63, 0x00, 0x04, 0x00, 0x52, 0x01, 0x00, 0x00, // R...h...c...R... + 0x0f, 0x01, 0x00, 0x00, 0x51, 0x01, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0x55, 0x01, 0x00, 0x00, // ....Q.......U... + 0xf8, 0x00, 0x02, 0x00, 0x55, 0x01, 0x00, 0x00, 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00, // ....U.......8... + 0x00, 0x00, 0x00, 0x00, // .... +}; +static const uint8_t cs_yuv_to_rgb_wgsl[1608] = +{ + 0x43, 0x53, 0x48, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x09, 0x62, // CSH............b + 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, 0x59, 0x20, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x02, // gfx_texY ....... + 0x26, 0x00, 0x0c, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, 0x43, 0x62, 0x43, 0x72, 0x20, // &..bgfx_texCbCr + 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x26, 0x00, 0x0c, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x69, // .......&..bgfx_i + 0x6d, 0x67, 0x52, 0x67, 0x62, 0x61, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x00, 0x02, 0x47, 0x00, // mgRgba........G. + 0xee, 0x05, 0x00, 0x00, 0x64, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x28, 0x6f, // ....diagnostic(o + 0x66, 0x66, 0x2c, 0x20, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x75, // ff, derivative_u + 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x69, 0x74, 0x79, 0x29, 0x3b, 0x0a, 0x64, 0x69, 0x61, 0x67, // niformity);.diag + 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x28, 0x6f, 0x66, 0x66, 0x2c, 0x20, 0x73, 0x75, 0x62, 0x67, // nostic(off, subg + 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x69, 0x74, 0x79, 0x29, // roup_uniformity) + 0x3b, 0x0a, 0x0a, 0x40, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x28, 0x30, 0x75, 0x29, 0x20, 0x40, 0x62, // ;..@group(0u) @b + 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x28, 0x31, 0x39, 0x75, 0x29, 0x20, 0x76, 0x61, 0x72, 0x20, // inding(19u) var + 0x62, 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, 0x59, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, // bgfx_texYSampler + 0x20, 0x3a, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x3b, 0x0a, 0x0a, 0x40, 0x67, 0x72, // : sampler;..@gr + 0x6f, 0x75, 0x70, 0x28, 0x30, 0x75, 0x29, 0x20, 0x40, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, // oup(0u) @binding + 0x28, 0x33, 0x75, 0x29, 0x20, 0x76, 0x61, 0x72, 0x20, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, // (3u) var bgfx_te + 0x78, 0x59, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x20, 0x3a, 0x20, 0x74, 0x65, 0x78, 0x74, // xYTexture : text + 0x75, 0x72, 0x65, 0x5f, 0x32, 0x64, 0x3c, 0x66, 0x33, 0x32, 0x3e, 0x3b, 0x0a, 0x0a, 0x40, 0x67, // ure_2d;..@g + 0x72, 0x6f, 0x75, 0x70, 0x28, 0x30, 0x75, 0x29, 0x20, 0x40, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, // roup(0u) @bindin + 0x67, 0x28, 0x32, 0x30, 0x75, 0x29, 0x20, 0x76, 0x61, 0x72, 0x20, 0x62, 0x67, 0x66, 0x78, 0x5f, // g(20u) var bgfx_ + 0x74, 0x65, 0x78, 0x43, 0x62, 0x43, 0x72, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x20, 0x3a, // texCbCrSampler : + 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x3b, 0x0a, 0x0a, 0x40, 0x67, 0x72, 0x6f, 0x75, // sampler;..@grou + 0x70, 0x28, 0x30, 0x75, 0x29, 0x20, 0x40, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x28, 0x34, // p(0u) @binding(4 + 0x75, 0x29, 0x20, 0x76, 0x61, 0x72, 0x20, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, 0x43, // u) var bgfx_texC + 0x62, 0x43, 0x72, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x20, 0x3a, 0x20, 0x74, 0x65, 0x78, // bCrTexture : tex + 0x74, 0x75, 0x72, 0x65, 0x5f, 0x32, 0x64, 0x3c, 0x66, 0x33, 0x32, 0x3e, 0x3b, 0x0a, 0x0a, 0x40, // ture_2d;..@ + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x28, 0x30, 0x75, 0x29, 0x20, 0x40, 0x62, 0x69, 0x6e, 0x64, 0x69, // group(0u) @bindi + 0x6e, 0x67, 0x28, 0x32, 0x75, 0x29, 0x20, 0x76, 0x61, 0x72, 0x20, 0x62, 0x67, 0x66, 0x78, 0x5f, // ng(2u) var bgfx_ + 0x69, 0x6d, 0x67, 0x52, 0x67, 0x62, 0x61, 0x20, 0x3a, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, // imgRgba : textur + 0x65, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x32, 0x64, 0x3c, 0x72, 0x67, 0x62, // e_storage_2d; + 0x0a, 0x0a, 0x40, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x20, 0x40, 0x77, 0x6f, 0x72, 0x6b, // ..@compute @work + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x28, 0x38, 0x75, 0x2c, 0x20, 0x38, // group_size(8u, 8 + 0x75, 0x2c, 0x20, 0x31, 0x75, 0x29, 0x0a, 0x66, 0x6e, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x40, // u, 1u).fn main(@ + 0x62, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x28, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x5f, 0x69, // builtin(global_i + 0x6e, 0x76, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x29, 0x20, 0x67, 0x6c, // nvocation_id) gl + 0x5f, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, // _GlobalInvocatio + 0x6e, 0x49, 0x44, 0x20, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x33, 0x3c, 0x75, 0x33, 0x32, 0x3e, 0x29, // nID : vec3) + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x76, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, // {. let v = vec + 0x33, 0x3c, 0x69, 0x33, 0x32, 0x3e, 0x28, 0x67, 0x6c, 0x5f, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, // 3(gl_Global + 0x49, 0x6e, 0x76, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x29, 0x3b, 0x0a, 0x20, // InvocationID);. + 0x20, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x28, 0x30, 0x75, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, // switch(0u) {. + 0x20, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, // default: {. + 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x76, 0x5f, 0x31, 0x20, 0x3d, 0x20, 0x76, 0x2e, 0x78, // let v_1 = v.x + 0x79, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x76, 0x5f, 0x32, // y;. let v_2 + 0x20, 0x3d, 0x20, 0x62, 0x69, 0x74, 0x63, 0x61, 0x73, 0x74, 0x3c, 0x76, 0x65, 0x63, 0x32, 0x3c, // = bitcast>(textureDim + 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x28, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x69, 0x6d, 0x67, // ensions(bgfx_img + 0x52, 0x67, 0x62, 0x61, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, // Rgba));. if + 0x20, 0x28, 0x28, 0x28, 0x76, 0x2e, 0x78, 0x20, 0x3e, 0x3d, 0x20, 0x76, 0x5f, 0x32, 0x2e, 0x78, // (((v.x >= v_2.x + 0x29, 0x20, 0x7c, 0x20, 0x28, 0x76, 0x2e, 0x79, 0x20, 0x3e, 0x3d, 0x20, 0x76, 0x5f, 0x32, 0x2e, // ) | (v.y >= v_2. + 0x79, 0x29, 0x29, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, // y))) {. b + 0x72, 0x65, 0x61, 0x6b, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, // reak;. }. + 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x76, 0x5f, 0x33, 0x20, 0x3d, 0x20, 0x28, 0x28, // let v_3 = (( + 0x76, 0x65, 0x63, 0x32, 0x3c, 0x66, 0x33, 0x32, 0x3e, 0x28, 0x76, 0x5f, 0x31, 0x29, 0x20, 0x2b, // vec2(v_1) + + 0x20, 0x76, 0x65, 0x63, 0x32, 0x3c, 0x66, 0x33, 0x32, 0x3e, 0x28, 0x30, 0x2e, 0x35, 0x66, 0x29, // vec2(0.5f) + 0x29, 0x20, 0x2a, 0x20, 0x28, 0x76, 0x65, 0x63, 0x32, 0x3c, 0x66, 0x33, 0x32, 0x3e, 0x28, 0x31, // ) * (vec2(1 + 0x2e, 0x30, 0x66, 0x29, 0x20, 0x2f, 0x20, 0x76, 0x65, 0x63, 0x32, 0x3c, 0x66, 0x33, 0x32, 0x3e, // .0f) / vec2 + 0x28, 0x76, 0x5f, 0x32, 0x29, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, // (v_2)));. l + 0x65, 0x74, 0x20, 0x76, 0x5f, 0x34, 0x20, 0x3d, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, // et v_4 = texture + 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x28, 0x62, 0x67, 0x66, 0x78, // SampleLevel(bgfx + 0x5f, 0x74, 0x65, 0x78, 0x59, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x2c, 0x20, 0x62, 0x67, // _texYTexture, bg + 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, 0x59, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x2c, 0x20, // fx_texYSampler, + 0x76, 0x5f, 0x33, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x66, 0x29, 0x2e, 0x78, 0x3b, 0x0a, 0x20, 0x20, // v_3, 0.0f).x;. + 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x76, 0x5f, 0x35, 0x20, 0x3d, 0x20, 0x74, 0x65, // let v_5 = te + 0x78, 0x74, 0x75, 0x72, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, // xtureSampleLevel + 0x28, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, 0x43, 0x62, 0x43, 0x72, 0x54, 0x65, 0x78, // (bgfx_texCbCrTex + 0x74, 0x75, 0x72, 0x65, 0x2c, 0x20, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, 0x43, 0x62, // ture, bgfx_texCb + 0x43, 0x72, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x2c, 0x20, 0x76, 0x5f, 0x33, 0x2c, 0x20, // CrSampler, v_3, + 0x30, 0x2e, 0x30, 0x66, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, // 0.0f);. let + 0x20, 0x76, 0x5f, 0x36, 0x20, 0x3d, 0x20, 0x28, 0x28, 0x76, 0x5f, 0x34, 0x20, 0x2d, 0x20, 0x30, // v_6 = ((v_4 - 0 + 0x2e, 0x30, 0x36, 0x32, 0x37, 0x34, 0x35, 0x31, 0x30, 0x31, 0x37, 0x34, 0x39, 0x38, 0x39, 0x37, // .062745101749897 + 0x30, 0x30, 0x33, 0x31, 0x37, 0x66, 0x29, 0x20, 0x2a, 0x20, 0x31, 0x2e, 0x31, 0x36, 0x34, 0x33, // 00317f) * 1.1643 + 0x38, 0x33, 0x35, 0x33, 0x30, 0x36, 0x31, 0x36, 0x37, 0x36, 0x30, 0x32, 0x35, 0x33, 0x39, 0x31, // 8353061676025391 + 0x66, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x76, 0x5f, // f);. let v_ + 0x37, 0x20, 0x3d, 0x20, 0x28, 0x76, 0x5f, 0x35, 0x2e, 0x78, 0x20, 0x2d, 0x20, 0x30, 0x2e, 0x35, // 7 = (v_5.x - 0.5 + 0x30, 0x31, 0x39, 0x36, 0x30, 0x38, 0x31, 0x33, 0x39, 0x39, 0x39, 0x31, 0x37, 0x36, 0x30, 0x32, // 0196081399917602 + 0x35, 0x33, 0x39, 0x66, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, // 539f);. let + 0x20, 0x76, 0x5f, 0x38, 0x20, 0x3d, 0x20, 0x28, 0x76, 0x5f, 0x35, 0x2e, 0x79, 0x20, 0x2d, 0x20, // v_8 = (v_5.y - + 0x30, 0x2e, 0x35, 0x30, 0x31, 0x39, 0x36, 0x30, 0x38, 0x31, 0x33, 0x39, 0x39, 0x39, 0x31, 0x37, // 0.50196081399917 + 0x36, 0x30, 0x32, 0x35, 0x33, 0x39, 0x66, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 602539f);. + 0x6c, 0x65, 0x74, 0x20, 0x76, 0x5f, 0x39, 0x20, 0x3d, 0x20, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, // let v_9 = clamp( + 0x76, 0x65, 0x63, 0x33, 0x3c, 0x66, 0x33, 0x32, 0x3e, 0x28, 0x28, 0x76, 0x5f, 0x36, 0x20, 0x2b, // vec3((v_6 + + 0x20, 0x28, 0x76, 0x5f, 0x38, 0x20, 0x2a, 0x20, 0x31, 0x2e, 0x37, 0x39, 0x32, 0x37, 0x34, 0x31, // (v_8 * 1.792741 + 0x30, 0x36, 0x30, 0x32, 0x35, 0x36, 0x39, 0x35, 0x38, 0x30, 0x30, 0x37, 0x38, 0x31, 0x66, 0x29, // 06025695800781f) + 0x29, 0x2c, 0x20, 0x28, 0x28, 0x76, 0x5f, 0x36, 0x20, 0x2d, 0x20, 0x28, 0x76, 0x5f, 0x37, 0x20, // ), ((v_6 - (v_7 + 0x2a, 0x20, 0x30, 0x2e, 0x32, 0x31, 0x33, 0x32, 0x32, 0x30, 0x39, 0x36, 0x38, 0x38, 0x34, 0x32, // * 0.213220968842 + 0x35, 0x30, 0x36, 0x34, 0x30, 0x38, 0x36, 0x39, 0x66, 0x29, 0x29, 0x20, 0x2d, 0x20, 0x28, 0x76, // 50640869f)) - (v + 0x5f, 0x38, 0x20, 0x2a, 0x20, 0x30, 0x2e, 0x35, 0x33, 0x32, 0x38, 0x38, 0x31, 0x36, 0x37, 0x37, // _8 * 0.532881677 + 0x31, 0x35, 0x30, 0x37, 0x32, 0x36, 0x33, 0x31, 0x38, 0x33, 0x36, 0x66, 0x29, 0x29, 0x2c, 0x20, // 15072631836f)), + 0x28, 0x76, 0x5f, 0x36, 0x20, 0x2b, 0x20, 0x28, 0x76, 0x5f, 0x37, 0x20, 0x2a, 0x20, 0x32, 0x2e, // (v_6 + (v_7 * 2. + 0x31, 0x31, 0x32, 0x34, 0x30, 0x31, 0x37, 0x32, 0x33, 0x38, 0x36, 0x31, 0x36, 0x39, 0x34, 0x33, // 1124017238616943 + 0x33, 0x35, 0x39, 0x34, 0x66, 0x29, 0x29, 0x29, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x33, 0x3c, 0x66, // 3594f))), vec3(), vec3 + 0x28, 0x31, 0x2e, 0x30, 0x66, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, // (1.0f));. t + 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x28, 0x62, 0x67, 0x66, 0x78, // extureStore(bgfx + 0x5f, 0x69, 0x6d, 0x67, 0x52, 0x67, 0x62, 0x61, 0x2c, 0x20, 0x76, 0x5f, 0x31, 0x2c, 0x20, 0x76, // _imgRgba, v_1, v + 0x65, 0x63, 0x34, 0x3c, 0x66, 0x33, 0x32, 0x3e, 0x28, 0x76, 0x5f, 0x39, 0x2e, 0x78, 0x2c, 0x20, // ec4(v_9.x, + 0x76, 0x5f, 0x39, 0x2e, 0x79, 0x2c, 0x20, 0x76, 0x5f, 0x39, 0x2e, 0x7a, 0x2c, 0x20, 0x31, 0x2e, // v_9.y, v_9.z, 1. + 0x30, 0x66, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x7d, 0x0a, // 0f));. }. }. + 0x7d, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // }....... +}; +static const uint8_t cs_yuv_to_rgb_dxbc[905] = +{ + 0x43, 0x53, 0x48, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x09, 0x62, // CSH............b + 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, 0x59, 0x20, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, // gfx_texY ....... + 0x00, 0x00, 0x0c, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, 0x43, 0x62, 0x43, 0x72, 0x20, // ...bgfx_texCbCr + 0x01, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x03, 0x00, 0x00, 0x44, 0x58, 0x42, // .........H...DXB + 0x43, 0x6c, 0x8d, 0xff, 0x99, 0x72, 0x83, 0x01, 0xbd, 0xe3, 0xc5, 0xc2, 0x1f, 0xea, 0x5f, 0x06, // Cl...r........_. + 0xa1, 0x01, 0x00, 0x00, 0x00, 0x48, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, // .....H.......,.. + 0x00, 0x3c, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x49, 0x53, 0x47, 0x4e, 0x08, 0x00, 0x00, // .<...L...ISGN... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x4f, 0x53, 0x47, 0x4e, 0x08, 0x00, 0x00, // .........OSGN... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x53, 0x48, 0x45, 0x58, 0xf4, 0x02, 0x00, // .........SHEX... + 0x00, 0x50, 0x00, 0x05, 0x00, 0xbd, 0x00, 0x00, 0x00, 0x6a, 0x08, 0x00, 0x01, 0x5a, 0x00, 0x00, // .P.......j...Z.. + 0x03, 0x00, 0x60, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x5a, 0x00, 0x00, 0x03, 0x00, 0x60, 0x10, // ..`......Z....`. + 0x00, 0x02, 0x00, 0x00, 0x00, 0x58, 0x18, 0x00, 0x04, 0x00, 0x70, 0x10, 0x00, 0x01, 0x00, 0x00, // .....X....p..... + 0x00, 0x55, 0x55, 0x00, 0x00, 0x58, 0x18, 0x00, 0x04, 0x00, 0x70, 0x10, 0x00, 0x02, 0x00, 0x00, // .UU..X....p..... + 0x00, 0x55, 0x55, 0x00, 0x00, 0x9c, 0x18, 0x00, 0x04, 0x00, 0xe0, 0x11, 0x00, 0x00, 0x00, 0x00, // .UU............. + 0x00, 0x11, 0x11, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x02, 0x32, 0x00, 0x02, 0x00, 0x68, 0x00, 0x00, // ....._...2...h.. + 0x02, 0x02, 0x00, 0x00, 0x00, 0x9b, 0x00, 0x00, 0x04, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, // ................ + 0x00, 0x01, 0x00, 0x00, 0x00, 0x3d, 0x10, 0x00, 0x89, 0xc2, 0x00, 0x00, 0x80, 0x43, 0x44, 0x04, // .....=.......CD. + 0x00, 0x32, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // .2........@..... + 0x00, 0x46, 0xee, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x06, 0xc2, 0x00, 0x10, // .F.......!...... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x04, 0x02, 0x00, 0x06, 0x04, 0x10, 0x00, 0x00, 0x00, 0x00, // ................ + 0x00, 0x3c, 0x00, 0x00, 0x07, 0x42, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, // .<...B.......:.. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x04, // .....*.......... + 0x03, 0x2a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x01, 0x15, 0x00, 0x00, // .*.......>...... + 0x01, 0x2b, 0x00, 0x00, 0x05, 0x32, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x10, // .+...2.......F.. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x0a, 0x32, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, // .........2...... + 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x80, // ..@.....?...?... + 0x3f, 0x00, 0x00, 0x80, 0x3f, 0x46, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, // ?...?F.......+.. + 0x04, 0xc2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00, // ................ + 0x0a, 0xc2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa6, 0x0e, 0x10, 0x00, 0x00, 0x00, 0x00, // ................ + 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ..@............. + 0x3f, 0x00, 0x00, 0x00, 0x3f, 0x38, 0x00, 0x00, 0x07, 0x32, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, // ?...?8...2...... + 0x00, 0x46, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe6, 0x0a, 0x10, 0x00, 0x00, 0x00, 0x00, // .F.............. + 0x00, 0x48, 0x00, 0x00, 0x8d, 0xc2, 0x00, 0x00, 0x80, 0x43, 0x55, 0x15, 0x00, 0x42, 0x00, 0x10, // .H.......CU..B.. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x96, 0x7c, 0x10, // .....F........|. + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x60, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, // ......`.......@. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x8d, 0xc2, 0x00, 0x00, 0x80, 0x43, 0x55, 0x15, // .....H.......CU. + 0x00, 0x32, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, // .2.......F...... + 0x00, 0x46, 0x7e, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x60, 0x10, 0x00, 0x02, 0x00, 0x00, // .F~.......`..... + 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x42, 0x00, 0x10, // ..@..........B.. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, // .....*........@. + 0x00, 0x81, 0x80, 0x80, 0xbd, 0x00, 0x00, 0x00, 0x0a, 0xf2, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, // ................ + 0x00, 0x16, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x81, 0x80, 0x00, // ..........@..... + 0xbf, 0x81, 0x80, 0x00, 0xbf, 0x81, 0x80, 0x00, 0xbf, 0x81, 0x80, 0x00, 0xbf, 0x38, 0x00, 0x00, // .............8.. + 0x0a, 0xb2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x0c, 0x10, 0x00, 0x01, 0x00, 0x00, // .........F...... + 0x00, 0x02, 0x40, 0x00, 0x00, 0x8a, 0x78, 0xe5, 0x3f, 0x99, 0x56, 0x5a, 0x3e, 0x00, 0x00, 0x00, // ..@...x.?.VZ>... + 0x00, 0x97, 0x31, 0x07, 0x40, 0x32, 0x00, 0x00, 0x0a, 0x22, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, // ..1.@2..."...... + 0x00, 0x2a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x85, 0x0a, 0x95, // .*........@..... + 0x3f, 0x1a, 0x00, 0x10, 0x80, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x20, 0x00, // ?....A.......2 . + 0x0a, 0x22, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x10, 0x80, 0x41, 0x00, 0x00, // .".......*...A.. + 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0xef, 0x6a, 0x08, 0x3f, 0x1a, 0x00, 0x10, // ......@...j.?... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x20, 0x00, 0x0c, 0x52, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, // .....2 ..R...... + 0x00, 0xa6, 0x0a, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x85, 0x0a, 0x95, // ..........@..... + 0x3f, 0x00, 0x00, 0x00, 0x00, 0x85, 0x0a, 0x95, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x06, 0x03, 0x10, // ?.......?....... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, 0x82, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, // .....6.......... + 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0xa4, 0x00, 0x00, 0x06, 0xf2, 0xe0, 0x11, // ..@.....?....... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x05, 0x02, 0x00, 0x46, 0x0e, 0x10, 0x00, 0x01, 0x00, 0x00, // .....F...F...... + 0x00, 0x3e, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, // .>....... +}; +static const uint8_t cs_yuv_to_rgb_dxil[4281] = +{ + 0x43, 0x53, 0x48, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x09, 0x62, // CSH............b + 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, 0x59, 0x20, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, // gfx_texY ....... + 0x00, 0x00, 0x0c, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, 0x43, 0x62, 0x43, 0x72, 0x20, // ...bgfx_texCbCr + 0x01, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x10, 0x00, 0x00, 0x44, 0x58, 0x42, // .........x...DXB + 0x43, 0x08, 0xf0, 0x13, 0xbe, 0xc9, 0xc8, 0xd6, 0xbf, 0x07, 0x57, 0x10, 0x47, 0xd4, 0x62, 0x28, // C.........W.G.b( + 0xee, 0x01, 0x00, 0x00, 0x00, 0x78, 0x10, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, // .....x.......<.. + 0x00, 0x4c, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x3c, 0x01, 0x00, // .L.......l...<.. + 0x00, 0x54, 0x08, 0x00, 0x00, 0x70, 0x08, 0x00, 0x00, 0x53, 0x46, 0x49, 0x30, 0x08, 0x00, 0x00, // .T...p...SFI0... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x53, 0x47, 0x31, 0x08, 0x00, 0x00, // .........ISG1... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x4f, 0x53, 0x47, 0x31, 0x08, 0x00, 0x00, // .........OSG1... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x50, 0x53, 0x56, 0x30, 0xc8, 0x00, 0x00, // .........PSV0... + 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .4.............. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x05, 0x00, 0x00, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, // ................ + 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, // ................ + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // ................ + 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ + 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // ................ + 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ + 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ + 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ + 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x6d, 0x61, // ..............ma + 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x54, 0x41, 0x54, 0x10, 0x07, 0x00, // in.......STAT... + 0x00, 0x60, 0x00, 0x05, 0x00, 0xc4, 0x01, 0x00, 0x00, 0x44, 0x58, 0x49, 0x4c, 0x00, 0x01, 0x00, // .`.......DXIL... + 0x00, 0x10, 0x00, 0x00, 0x00, 0xf8, 0x06, 0x00, 0x00, 0x42, 0x43, 0xc0, 0xde, 0x21, 0x0c, 0x00, // .........BC..!.. + 0x00, 0xbb, 0x01, 0x00, 0x00, 0x0b, 0x82, 0x20, 0x00, 0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, // ....... ........ + 0x00, 0x07, 0x81, 0x23, 0x91, 0x41, 0xc8, 0x04, 0x49, 0x06, 0x10, 0x32, 0x39, 0x92, 0x01, 0x84, // ...#.A..I..29... + 0x0c, 0x25, 0x05, 0x08, 0x19, 0x1e, 0x04, 0x8b, 0x62, 0x80, 0x18, 0x45, 0x02, 0x42, 0x92, 0x0b, // .%......b..E.B.. + 0x42, 0xc4, 0x10, 0x32, 0x14, 0x38, 0x08, 0x18, 0x4b, 0x0a, 0x32, 0x62, 0x88, 0x48, 0x90, 0x14, // B..2.8..K.2b.H.. + 0x20, 0x43, 0x46, 0x88, 0xa5, 0x00, 0x19, 0x32, 0x42, 0xe4, 0x48, 0x0e, 0x90, 0x11, 0x23, 0xc4, // CF....2B.H...#. + 0x50, 0x41, 0x51, 0x81, 0x8c, 0xe1, 0x83, 0xe5, 0x8a, 0x04, 0x31, 0x46, 0x06, 0x51, 0x18, 0x00, // PAQ.......1F.Q.. + 0x00, 0x08, 0x00, 0x00, 0x00, 0x1b, 0x8c, 0xe0, 0xff, 0xff, 0xff, 0xff, 0x07, 0x40, 0x02, 0xa8, // .............@.. + 0x0d, 0x86, 0xf0, 0xff, 0xff, 0xff, 0xff, 0x03, 0x20, 0x01, 0xd5, 0x06, 0x62, 0xf8, 0xff, 0xff, // ........ ...b... + 0xff, 0xff, 0x01, 0x90, 0x00, 0x49, 0x18, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x13, 0x82, 0x60, // .....I.........` + 0x42, 0x20, 0x4c, 0x08, 0x06, 0x00, 0x00, 0x00, 0x00, 0x89, 0x20, 0x00, 0x00, 0x5c, 0x00, 0x00, // B L....... ..... + 0x00, 0x32, 0x22, 0x88, 0x09, 0x20, 0x64, 0x85, 0x04, 0x13, 0x23, 0xa4, 0x84, 0x04, 0x13, 0x23, // .2".. d...#....# + 0xe3, 0x84, 0xa1, 0x90, 0x14, 0x12, 0x4c, 0x8c, 0x8c, 0x0b, 0x84, 0xc4, 0x4c, 0x10, 0x80, 0xc1, // ......L.....L... + 0x08, 0x40, 0x09, 0x00, 0x0a, 0xe6, 0x08, 0xc0, 0xa0, 0x0c, 0xc3, 0x30, 0x10, 0x31, 0x03, 0x50, // .@.........0.1.P + 0x08, 0x63, 0x30, 0x0c, 0x3a, 0x6e, 0x1a, 0x2e, 0x7f, 0xc2, 0x1e, 0x42, 0xf2, 0x3b, 0x84, 0x21, // .c0.:n.....B.;.! + 0x1a, 0x09, 0x71, 0x1a, 0x09, 0x11, 0xc3, 0x30, 0x0c, 0x73, 0x04, 0x08, 0x29, 0xf7, 0x0c, 0x97, // ..q....0.s..)... + 0x3f, 0x61, 0x0f, 0x21, 0xf9, 0x21, 0xd0, 0x0c, 0x0b, 0x81, 0x82, 0xa5, 0x10, 0xc9, 0xc0, 0x0c, // ?a.!.!.......... + 0xd4, 0xdc, 0x34, 0x5c, 0xfe, 0x84, 0x3d, 0x84, 0xe4, 0xaf, 0x84, 0xb4, 0x12, 0x93, 0x5f, 0xdc, // ..4...=......._. + 0x36, 0x2a, 0x0c, 0xc3, 0x30, 0x86, 0xc2, 0x3c, 0x03, 0xc3, 0x18, 0x86, 0x61, 0x0c, 0xc3, 0x60, // 6*..0..<....a..` + 0x10, 0x54, 0x16, 0x60, 0x60, 0x86, 0x61, 0x30, 0x0c, 0xc3, 0x50, 0x48, 0x9a, 0x23, 0x08, 0x8a, // .T.``.a0..PH.#.. + 0xc1, 0x0c, 0xca, 0x30, 0x50, 0x54, 0x0d, 0x04, 0x0c, 0x23, 0x10, 0xc6, 0x4c, 0x6d, 0x30, 0x0e, // ...0PT...#..Lm0. + 0xec, 0x10, 0x0e, 0xf3, 0x30, 0x0f, 0x6e, 0x40, 0x0b, 0xe5, 0x80, 0x0f, 0xf4, 0x50, 0x0f, 0xf2, // ....0.n@.....P.. + 0x50, 0x0e, 0x72, 0x40, 0x0a, 0x7c, 0x60, 0x0f, 0xe5, 0x30, 0x0e, 0xf4, 0xf0, 0x0e, 0xf2, 0xc0, // P.r@.|`..0...... + 0x07, 0xe6, 0xc0, 0x0e, 0xef, 0x10, 0x0e, 0xf4, 0xc0, 0x06, 0x60, 0x40, 0x07, 0x7e, 0x00, 0x06, // ..........`@.~.. + 0x7e, 0xa0, 0x07, 0x7a, 0xd0, 0x0e, 0xe9, 0x00, 0x0f, 0xf3, 0xf0, 0x0b, 0xf4, 0x90, 0x0f, 0xf0, // ~..z............ + 0x50, 0x0e, 0x28, 0x18, 0x66, 0x12, 0x83, 0x71, 0x60, 0x87, 0x70, 0x98, 0x87, 0x79, 0x70, 0x03, // P.(.f..q`.p..yp. + 0x5a, 0x28, 0x07, 0x7c, 0xa0, 0x87, 0x7a, 0x90, 0x87, 0x72, 0x90, 0x03, 0x52, 0xe0, 0x03, 0x7b, // Z(.|..z..r..R..{ + 0x28, 0x87, 0x71, 0xa0, 0x87, 0x77, 0x90, 0x07, 0x3e, 0x30, 0x07, 0x76, 0x78, 0x87, 0x70, 0xa0, // (.q..w..>0.vx.p. + 0x07, 0x36, 0x00, 0x03, 0x3a, 0xf0, 0x03, 0x30, 0xf0, 0x03, 0x24, 0x60, 0x19, 0x69, 0x33, 0x91, // .6..:..0..$`.i3. + 0xc1, 0x38, 0xb0, 0x43, 0x38, 0xcc, 0xc3, 0x3c, 0xb8, 0x81, 0x2c, 0xdc, 0x02, 0x2d, 0x94, 0x03, // .8.C8..<..,..-.. + 0x3e, 0xd0, 0x43, 0x3d, 0xc8, 0x43, 0x39, 0xc8, 0x01, 0x29, 0xf0, 0x81, 0x3d, 0x94, 0xc3, 0x38, // >.C=.C9..)..=..8 + 0xd0, 0xc3, 0x3b, 0xc8, 0x03, 0x1f, 0x98, 0x03, 0x3b, 0xbc, 0x43, 0x38, 0xd0, 0x03, 0x1b, 0x80, // ..;.....;.C8.... + 0x01, 0x1d, 0xf8, 0x01, 0x18, 0xf8, 0x01, 0x0a, 0x30, 0xe2, 0x6e, 0x92, 0xa6, 0x88, 0x12, 0x26, // ........0.n....& + 0x9f, 0x05, 0x98, 0x67, 0x21, 0x22, 0x76, 0x02, 0x26, 0x02, 0x05, 0x03, 0x79, 0x00, 0x00, 0x00, // ...g!"v.&...y... + 0x00, 0x13, 0x14, 0x72, 0xc0, 0x87, 0x74, 0x60, 0x87, 0x36, 0x68, 0x87, 0x79, 0x68, 0x03, 0x72, // ...r..t`.6h.yh.r + 0xc0, 0x87, 0x0d, 0xaf, 0x50, 0x0e, 0x6d, 0xd0, 0x0e, 0x7a, 0x50, 0x0e, 0x6d, 0x00, 0x0f, 0x7a, // ....P.m..zP.m..z + 0x30, 0x07, 0x72, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, 0x90, 0x0e, 0x71, 0xa0, 0x07, 0x73, 0x20, // 0.r..s .m..q..s + 0x07, 0x6d, 0x90, 0x0e, 0x78, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, 0x90, 0x0e, 0x71, 0x60, 0x07, // .m..x..s .m..q`. + 0x7a, 0x30, 0x07, 0x72, 0xd0, 0x06, 0xe9, 0x30, 0x07, 0x72, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, // z0.r...0.r..s .m + 0x90, 0x0e, 0x76, 0x40, 0x07, 0x7a, 0x60, 0x07, 0x74, 0xd0, 0x06, 0xe6, 0x10, 0x07, 0x76, 0xa0, // ..v@.z`.t.....v. + 0x07, 0x73, 0x20, 0x07, 0x6d, 0x60, 0x0e, 0x73, 0x20, 0x07, 0x7a, 0x30, 0x07, 0x72, 0xd0, 0x06, // .s .m`.s .z0.r.. + 0xe6, 0x60, 0x07, 0x74, 0xa0, 0x07, 0x76, 0x40, 0x07, 0x6d, 0xe0, 0x0e, 0x78, 0xa0, 0x07, 0x71, // .`.t..v@.m..x..q + 0x60, 0x07, 0x7a, 0x30, 0x07, 0x72, 0xa0, 0x07, 0x76, 0x40, 0x07, 0x43, 0x9e, 0x00, 0x08, 0x00, // `.z0.r..v@.C.... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86, 0x3c, 0x04, 0x10, 0x00, 0x01, 0x00, 0x00, // .........<...... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x79, 0x0e, 0x20, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // ......y. ....... + 0x00, 0x00, 0x18, 0xf2, 0x34, 0x40, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, // ....4@.........0 + 0xe4, 0x81, 0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xc8, 0x23, 0x01, // ............`.#. + 0x01, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x90, 0xa7, 0x02, 0x02, 0x40, 0x00, // .0............@. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x2c, 0x10, 0x0c, 0x00, 0x00, 0x00, 0x32, 0x1e, 0x98, // .......,.....2.. + 0x14, 0x19, 0x11, 0x4c, 0x90, 0x8c, 0x09, 0x26, 0x47, 0xc6, 0x04, 0x43, 0x1a, 0x4a, 0x60, 0x04, // ...L...&G..C.J`. + 0xa0, 0x18, 0x8a, 0xa0, 0x24, 0x8a, 0xa3, 0x10, 0x0a, 0xa2, 0x70, 0x8a, 0x8c, 0xa8, 0x11, 0x00, // ....$.....p..... + 0xda, 0x66, 0x00, 0xa8, 0x9b, 0x01, 0xa0, 0x6f, 0x06, 0x00, 0x00, 0x00, 0x00, 0x79, 0x18, 0x00, // .f.....o.....y.. + 0x00, 0x7e, 0x00, 0x00, 0x00, 0x1a, 0x03, 0x4c, 0x90, 0x46, 0x02, 0x13, 0x44, 0x8f, 0x0c, 0x6f, // .~.....L.F..D..o + 0xec, 0xed, 0x4d, 0x0c, 0x24, 0xc6, 0x05, 0xc7, 0x45, 0xa6, 0x06, 0xa6, 0xc6, 0x65, 0x46, 0x06, // ..M.$...E....eF. + 0x04, 0x45, 0x2c, 0x06, 0xc6, 0x26, 0x2c, 0xc7, 0x26, 0x86, 0x2c, 0x65, 0x43, 0x10, 0x4c, 0x10, // .E,..&,.&.,eC.L. + 0x86, 0x63, 0x82, 0x30, 0x20, 0x1b, 0x84, 0x81, 0xd8, 0x20, 0x10, 0x04, 0x85, 0xb1, 0xb9, 0x09, // .c.0 .... ...... + 0xc2, 0x90, 0x6c, 0x18, 0x0e, 0x84, 0x98, 0x20, 0x6c, 0x12, 0x21, 0xb1, 0x33, 0x33, 0xbc, 0x2f, // ..l.... l.!.33./ + 0xba, 0x32, 0xbc, 0x2c, 0xaa, 0x32, 0x3c, 0xba, 0x3a, 0xb9, 0xb2, 0x09, 0xc2, 0xa0, 0x4c, 0x10, // .2.,.2<.:.....L. + 0x86, 0x65, 0x83, 0x40, 0x34, 0x1b, 0x12, 0x42, 0x59, 0x88, 0x61, 0x60, 0x08, 0x87, 0x93, 0xd8, // .e.@4..BY.a`.... + 0x99, 0x19, 0xde, 0x17, 0x5d, 0x19, 0xde, 0x90, 0xd8, 0x90, 0x1c, 0x55, 0x19, 0x1e, 0x5d, 0x9d, // ....]......U..]. + 0x5c, 0xd9, 0x86, 0x64, 0x50, 0x20, 0x82, 0x19, 0x18, 0xc2, 0xd9, 0x20, 0x3c, 0xd1, 0x04, 0xa1, // ...dP ..... <... + 0x9b, 0xc8, 0x88, 0x9d, 0x99, 0xe1, 0x7d, 0xa5, 0xb5, 0x9d, 0x49, 0x9d, 0x89, 0x85, 0x4d, 0x10, // ......}...I...M. + 0xa8, 0x68, 0x82, 0x30, 0x30, 0x1b, 0x04, 0xc2, 0xda, 0xb0, 0x10, 0x13, 0x45, 0x10, 0x03, 0x53, // .h.00.......E..S + 0x55, 0xd5, 0xb5, 0x21, 0xc0, 0x26, 0x08, 0x1f, 0x45, 0x48, 0xec, 0xcc, 0x0c, 0xef, 0x8b, 0xae, // U..!.&..EH...... + 0x0c, 0x2f, 0x6b, 0x2a, 0xac, 0x0d, 0x8e, 0xad, 0x4c, 0x6e, 0x03, 0x42, 0x68, 0x1b, 0x31, 0x0c, // ./k*....Ln.Bh.1. + 0x04, 0xc0, 0x49, 0xec, 0xcc, 0x0c, 0xef, 0x8b, 0xae, 0x0c, 0x6f, 0x48, 0x6c, 0x48, 0x6e, 0x2a, // ..I.......oHlHn* + 0xac, 0x0d, 0x8e, 0xad, 0x4c, 0x6e, 0x03, 0x32, 0x68, 0x1d, 0xc1, 0x0c, 0x04, 0xb0, 0x41, 0xe0, // ....Ln.2h.....A. + 0xbc, 0x0d, 0x84, 0x94, 0x01, 0xdf, 0x04, 0x41, 0x00, 0x36, 0x00, 0x1b, 0x06, 0x42, 0x0c, 0xc4, // .......A.6...B.. + 0x60, 0x43, 0x30, 0x06, 0x1b, 0x86, 0x21, 0x0c, 0xc8, 0x80, 0x44, 0x5b, 0x58, 0x9a, 0xdb, 0x04, // `C0...!...D[X... + 0x61, 0x68, 0x26, 0x08, 0x83, 0xb3, 0x61, 0x40, 0x03, 0x34, 0x18, 0x36, 0x08, 0x67, 0x90, 0x06, // ah&...a@.4.6.g.. + 0x1b, 0x8a, 0x30, 0x30, 0x03, 0x00, 0x0c, 0xd4, 0x80, 0x88, 0x98, 0x5c, 0x98, 0xdb, 0x18, 0x5a, // ..00...........Z + 0xd9, 0x1c, 0x0d, 0x33, 0xb6, 0xb7, 0x30, 0xba, 0xb9, 0x09, 0xc2, 0xf0, 0xb0, 0x48, 0x73, 0x9b, // ...3..0......Hs. + 0xa3, 0x9b, 0x9b, 0x20, 0x0c, 0x10, 0x89, 0x34, 0x37, 0xba, 0x39, 0x22, 0x74, 0x65, 0x78, 0x5f, // ... ...47.9"tex_ + 0x6e, 0x6f, 0x72, 0x6d, 0x4c, 0xe8, 0xca, 0xf0, 0xbe, 0xe6, 0xe8, 0xde, 0xe4, 0xca, 0x58, 0xd4, // normL.........X. + 0xa5, 0xb9, 0xd1, 0xcd, 0x6d, 0x70, 0xd8, 0x60, 0x68, 0x03, 0x37, 0x78, 0x03, 0x38, 0x88, 0x03, // ....mp.`h.7x.8.. + 0x46, 0x0e, 0x98, 0x39, 0x18, 0xe8, 0x60, 0xa8, 0xc2, 0xc6, 0x66, 0xd7, 0xe6, 0x92, 0x46, 0x56, // F..9..`...f...FV + 0xe6, 0x46, 0x37, 0x25, 0x08, 0xaa, 0x90, 0xe1, 0xb9, 0xd8, 0x95, 0xc9, 0xcd, 0xa5, 0xbd, 0xb9, // .F7%............ + 0x4d, 0x09, 0x88, 0x26, 0x64, 0x78, 0x2e, 0x76, 0x61, 0x6c, 0x76, 0x65, 0x72, 0x53, 0x82, 0xa2, // M..&dx.valverS.. + 0x0e, 0x19, 0x9e, 0xcb, 0x1c, 0x5a, 0x18, 0x59, 0x99, 0x5c, 0xd3, 0x1b, 0x59, 0x19, 0xdb, 0x94, // .....Z.Y....Y... + 0x00, 0x29, 0x43, 0x86, 0xe7, 0x22, 0x57, 0x36, 0xf7, 0x56, 0x27, 0x37, 0x56, 0x36, 0x37, 0x25, // .)C.."W6.V'7V67% + 0xf8, 0x2a, 0x91, 0xe1, 0xb9, 0xd0, 0xe5, 0xc1, 0x95, 0x05, 0xb9, 0xb9, 0xbd, 0xd1, 0x85, 0xd1, // .*.............. + 0xa5, 0xbd, 0xb9, 0xcd, 0x4d, 0x09, 0xc8, 0xa0, 0x0e, 0x19, 0x9e, 0x4b, 0x99, 0x1b, 0x9d, 0x5c, // ....M......K.... + 0x1e, 0xd4, 0x5b, 0x9a, 0x1b, 0xdd, 0xdc, 0x94, 0x40, 0x0d, 0xba, 0x90, 0xe1, 0xb9, 0x8c, 0xbd, // ..[.....@....... + 0xd5, 0xb9, 0xd1, 0x95, 0xc9, 0xcd, 0x4d, 0x09, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x79, 0x18, 0x00, // ......M......y.. + 0x00, 0x51, 0x00, 0x00, 0x00, 0x33, 0x08, 0x80, 0x1c, 0xc4, 0xe1, 0x1c, 0x66, 0x14, 0x01, 0x3d, // .Q...3......f..= + 0x88, 0x43, 0x38, 0x84, 0xc3, 0x8c, 0x42, 0x80, 0x07, 0x79, 0x78, 0x07, 0x73, 0x98, 0x71, 0x0c, // .C8...B..yx.s.q. + 0xe6, 0x00, 0x0f, 0xed, 0x10, 0x0e, 0xf4, 0x80, 0x0e, 0x33, 0x0c, 0x42, 0x1e, 0xc2, 0xc1, 0x1d, // .........3.B.... + 0xce, 0xa1, 0x1c, 0x66, 0x30, 0x05, 0x3d, 0x88, 0x43, 0x38, 0x84, 0x83, 0x1b, 0xcc, 0x03, 0x3d, // ...f0.=.C8.....= + 0xc8, 0x43, 0x3d, 0x8c, 0x03, 0x3d, 0xcc, 0x78, 0x8c, 0x74, 0x70, 0x07, 0x7b, 0x08, 0x07, 0x79, // .C=..=.x.tp.{..y + 0x48, 0x87, 0x70, 0x70, 0x07, 0x7a, 0x70, 0x03, 0x76, 0x78, 0x87, 0x70, 0x20, 0x87, 0x19, 0xcc, // H.pp.zp.vx.p ... + 0x11, 0x0e, 0xec, 0x90, 0x0e, 0xe1, 0x30, 0x0f, 0x6e, 0x30, 0x0f, 0xe3, 0xf0, 0x0e, 0xf0, 0x50, // ......0.n0.....P + 0x0e, 0x33, 0x10, 0xc4, 0x1d, 0xde, 0x21, 0x1c, 0xd8, 0x21, 0x1d, 0xc2, 0x61, 0x1e, 0x66, 0x30, // .3....!..!..a.f0 + 0x89, 0x3b, 0xbc, 0x83, 0x3b, 0xd0, 0x43, 0x39, 0xb4, 0x03, 0x3c, 0xbc, 0x83, 0x3c, 0x84, 0x03, // .;..;.C9..<..<.. + 0x3b, 0xcc, 0xf0, 0x14, 0x76, 0x60, 0x07, 0x7b, 0x68, 0x07, 0x37, 0x68, 0x87, 0x72, 0x68, 0x07, // ;...v`.{h.7h.rh. + 0x37, 0x80, 0x87, 0x70, 0x90, 0x87, 0x70, 0x60, 0x07, 0x76, 0x28, 0x07, 0x76, 0xf8, 0x05, 0x76, // 7..p..p`.v(.v..v + 0x78, 0x87, 0x77, 0x80, 0x87, 0x5f, 0x08, 0x87, 0x71, 0x18, 0x87, 0x72, 0x98, 0x87, 0x79, 0x98, // x.w.._..q..r..y. + 0x81, 0x2c, 0xee, 0xf0, 0x0e, 0xee, 0xe0, 0x0e, 0xf5, 0xc0, 0x0e, 0xec, 0x30, 0x03, 0x62, 0xc8, // .,..........0.b. + 0xa1, 0x1c, 0xe4, 0xa1, 0x1c, 0xcc, 0xa1, 0x1c, 0xe4, 0xa1, 0x1c, 0xdc, 0x61, 0x1c, 0xca, 0x21, // ............a..! + 0x1c, 0xc4, 0x81, 0x1d, 0xca, 0x61, 0x06, 0xd6, 0x90, 0x43, 0x39, 0xc8, 0x43, 0x39, 0x98, 0x43, // .....a...C9.C9.C + 0x39, 0xc8, 0x43, 0x39, 0xb8, 0xc3, 0x38, 0x94, 0x43, 0x38, 0x88, 0x03, 0x3b, 0x94, 0xc3, 0x2f, // 9.C9..8.C8..;../ + 0xbc, 0x83, 0x3c, 0xfc, 0x82, 0x3b, 0xd4, 0x03, 0x3b, 0xb0, 0xc3, 0x0c, 0xc4, 0x21, 0x07, 0x7c, // ..<..;..;....!.| + 0x70, 0x03, 0x7a, 0x28, 0x87, 0x76, 0x80, 0x87, 0x19, 0xd1, 0x43, 0x0e, 0xf8, 0xe0, 0x06, 0xe4, // p.z(.v....C..... + 0x20, 0x0e, 0xe7, 0xe0, 0x06, 0xf6, 0x10, 0x0e, 0xf2, 0xc0, 0x0e, 0xe1, 0x90, 0x0f, 0xef, 0x50, // ..............P + 0x0f, 0xf4, 0x30, 0x83, 0x81, 0xc8, 0x01, 0x1f, 0xdc, 0x40, 0x1c, 0xe4, 0xa1, 0x1c, 0xc2, 0x61, // ..0......@.....a + 0x1d, 0xdc, 0x40, 0x1c, 0xe4, 0x01, 0x00, 0x00, 0x00, 0x71, 0x20, 0x00, 0x00, 0x1b, 0x00, 0x00, // ..@......q ..... + 0x00, 0x26, 0x00, 0x0d, 0x97, 0xef, 0x3c, 0x7e, 0x80, 0x34, 0x40, 0x84, 0xf9, 0xc5, 0x6d, 0x9b, // .&....<~.4@...m. + 0x81, 0x34, 0x5c, 0xbe, 0xf3, 0xf8, 0x42, 0x44, 0x00, 0x13, 0x11, 0x02, 0xcd, 0xb0, 0x10, 0x36, // .4....BD.......6 + 0x30, 0x0d, 0x97, 0xef, 0x3c, 0xbe, 0x41, 0x4c, 0x1d, 0xc2, 0x10, 0x8d, 0x84, 0x38, 0x8d, 0x64, // 0...<.AL.....8.d + 0x04, 0xd5, 0x70, 0xf9, 0xce, 0xe3, 0x4b, 0x00, 0xf3, 0x2c, 0x44, 0x49, 0x54, 0xc4, 0xe2, 0x17, // ..p...K..,DIT... + 0xb7, 0x6d, 0x05, 0xd6, 0x70, 0xf9, 0xce, 0xe3, 0x4f, 0xc4, 0x35, 0x51, 0x11, 0xc1, 0x4e, 0x4e, // .m..p...O.5Q..NN + 0x44, 0xf8, 0xc5, 0x6d, 0x5b, 0x80, 0x34, 0x5c, 0xbe, 0xf3, 0xf8, 0xd3, 0x11, 0x11, 0xc0, 0x20, // D..m[.4........ + 0x0e, 0x3e, 0x72, 0xdb, 0x06, 0x40, 0x30, 0x00, 0xd2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .>r..@0......... + 0x00, 0x48, 0x41, 0x53, 0x48, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xf5, 0x01, // .HASH........~.. + 0xb4, 0x27, 0x75, 0x15, 0xca, 0xed, 0x2d, 0xd0, 0x66, 0xcc, 0xee, 0x5d, 0x27, 0x44, 0x58, 0x49, // .'u...-.f..]'DXI + 0x4c, 0x00, 0x08, 0x00, 0x00, 0x60, 0x00, 0x05, 0x00, 0x00, 0x02, 0x00, 0x00, 0x44, 0x58, 0x49, // L....`.......DXI + 0x4c, 0x00, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xe8, 0x07, 0x00, 0x00, 0x42, 0x43, 0xc0, // L............BC. + 0xde, 0x21, 0x0c, 0x00, 0x00, 0xf7, 0x01, 0x00, 0x00, 0x0b, 0x82, 0x20, 0x00, 0x02, 0x00, 0x00, // .!......... .... + 0x00, 0x13, 0x00, 0x00, 0x00, 0x07, 0x81, 0x23, 0x91, 0x41, 0xc8, 0x04, 0x49, 0x06, 0x10, 0x32, // .......#.A..I..2 + 0x39, 0x92, 0x01, 0x84, 0x0c, 0x25, 0x05, 0x08, 0x19, 0x1e, 0x04, 0x8b, 0x62, 0x80, 0x18, 0x45, // 9....%......b..E + 0x02, 0x42, 0x92, 0x0b, 0x42, 0xc4, 0x10, 0x32, 0x14, 0x38, 0x08, 0x18, 0x4b, 0x0a, 0x32, 0x62, // .B..B..2.8..K.2b + 0x88, 0x48, 0x90, 0x14, 0x20, 0x43, 0x46, 0x88, 0xa5, 0x00, 0x19, 0x32, 0x42, 0xe4, 0x48, 0x0e, // .H.. CF....2B.H. + 0x90, 0x11, 0x23, 0xc4, 0x50, 0x41, 0x51, 0x81, 0x8c, 0xe1, 0x83, 0xe5, 0x8a, 0x04, 0x31, 0x46, // ..#.PAQ.......1F + 0x06, 0x51, 0x18, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x1b, 0x8c, 0xe0, 0xff, 0xff, 0xff, 0xff, // .Q.............. + 0x07, 0x40, 0x02, 0xa8, 0x0d, 0x86, 0xf0, 0xff, 0xff, 0xff, 0xff, 0x03, 0x20, 0x01, 0xd5, 0x06, // .@.......... ... + 0x62, 0xf8, 0xff, 0xff, 0xff, 0xff, 0x01, 0x90, 0x00, 0x49, 0x18, 0x00, 0x00, 0x03, 0x00, 0x00, // b........I...... + 0x00, 0x13, 0x82, 0x60, 0x42, 0x20, 0x4c, 0x08, 0x06, 0x00, 0x00, 0x00, 0x00, 0x89, 0x20, 0x00, // ...`B L....... . + 0x00, 0x5c, 0x00, 0x00, 0x00, 0x32, 0x22, 0x88, 0x09, 0x20, 0x64, 0x85, 0x04, 0x13, 0x23, 0xa4, // .....2".. d...#. + 0x84, 0x04, 0x13, 0x23, 0xe3, 0x84, 0xa1, 0x90, 0x14, 0x12, 0x4c, 0x8c, 0x8c, 0x0b, 0x84, 0xc4, // ...#......L..... + 0x4c, 0x10, 0x84, 0xc1, 0x08, 0x40, 0x09, 0x00, 0x0a, 0xe6, 0x08, 0xc0, 0xa0, 0x0c, 0xc3, 0x30, // L....@.........0 + 0x10, 0x31, 0x03, 0x50, 0x08, 0x63, 0x30, 0x0c, 0x3a, 0x6e, 0x1a, 0x2e, 0x7f, 0xc2, 0x1e, 0x42, // .1.P.c0.:n.....B + 0xf2, 0x3b, 0x84, 0x21, 0x1a, 0x09, 0x71, 0x1a, 0x09, 0x11, 0xc3, 0x30, 0x0c, 0x73, 0x04, 0x08, // .;.!..q....0.s.. + 0x29, 0xf7, 0x0c, 0x97, 0x3f, 0x61, 0x0f, 0x21, 0xf9, 0x21, 0xd0, 0x0c, 0x0b, 0x81, 0x82, 0xa5, // )...?a.!.!...... + 0x10, 0xc9, 0xc0, 0x0c, 0xd4, 0xdc, 0x34, 0x5c, 0xfe, 0x84, 0x3d, 0x84, 0xe4, 0xaf, 0x84, 0xb4, // ......4...=..... + 0x12, 0x93, 0x5f, 0xdc, 0x36, 0x2a, 0x0c, 0xc3, 0x30, 0x86, 0xc2, 0x3c, 0x03, 0xc3, 0x18, 0x86, // .._.6*..0..<.... + 0x61, 0x0c, 0xc3, 0x60, 0x10, 0x54, 0x16, 0x60, 0x60, 0x86, 0x61, 0x30, 0x0c, 0xc3, 0x50, 0x48, // a..`.T.``.a0..PH + 0x9a, 0x23, 0x08, 0x8a, 0xc1, 0x0c, 0xca, 0x30, 0x50, 0x54, 0x0d, 0x04, 0x0c, 0x23, 0x10, 0xc6, // .#.....0PT...#.. + 0x4c, 0x6d, 0x30, 0x0e, 0xec, 0x10, 0x0e, 0xf3, 0x30, 0x0f, 0x6e, 0x40, 0x0b, 0xe5, 0x80, 0x0f, // Lm0.....0.n@.... + 0xf4, 0x50, 0x0f, 0xf2, 0x50, 0x0e, 0x72, 0x40, 0x0a, 0x7c, 0x60, 0x0f, 0xe5, 0x30, 0x0e, 0xf4, // .P..P.r@.|`..0.. + 0xf0, 0x0e, 0xf2, 0xc0, 0x07, 0xe6, 0xc0, 0x0e, 0xef, 0x10, 0x0e, 0xf4, 0xc0, 0x06, 0x60, 0x40, // ..............`@ + 0x07, 0x7e, 0x00, 0x06, 0x7e, 0xa0, 0x07, 0x7a, 0xd0, 0x0e, 0xe9, 0x00, 0x0f, 0xf3, 0xf0, 0x0b, // .~..~..z........ + 0xf4, 0x90, 0x0f, 0xf0, 0x50, 0x0e, 0x28, 0x18, 0x66, 0x12, 0x83, 0x71, 0x60, 0x87, 0x70, 0x98, // ....P.(.f..q`.p. + 0x87, 0x79, 0x70, 0x03, 0x5a, 0x28, 0x07, 0x7c, 0xa0, 0x87, 0x7a, 0x90, 0x87, 0x72, 0x90, 0x03, // .yp.Z(.|..z..r.. + 0x52, 0xe0, 0x03, 0x7b, 0x28, 0x87, 0x71, 0xa0, 0x87, 0x77, 0x90, 0x07, 0x3e, 0x30, 0x07, 0x76, // R..{(.q..w..>0.v + 0x78, 0x87, 0x70, 0xa0, 0x07, 0x36, 0x00, 0x03, 0x3a, 0xf0, 0x03, 0x30, 0xf0, 0x03, 0x24, 0x60, // x.p..6..:..0..$` + 0x19, 0x69, 0x33, 0x91, 0xc1, 0x38, 0xb0, 0x43, 0x38, 0xcc, 0xc3, 0x3c, 0xb8, 0x81, 0x2c, 0xdc, // .i3..8.C8..<..,. + 0x02, 0x2d, 0x94, 0x03, 0x3e, 0xd0, 0x43, 0x3d, 0xc8, 0x43, 0x39, 0xc8, 0x01, 0x29, 0xf0, 0x81, // .-..>.C=.C9..).. + 0x3d, 0x94, 0xc3, 0x38, 0xd0, 0xc3, 0x3b, 0xc8, 0x03, 0x1f, 0x98, 0x03, 0x3b, 0xbc, 0x43, 0x38, // =..8..;.....;.C8 + 0xd0, 0x03, 0x1b, 0x80, 0x01, 0x1d, 0xf8, 0x01, 0x18, 0xf8, 0x01, 0x0a, 0x30, 0xe2, 0x6e, 0x92, // ............0.n. + 0xa6, 0x88, 0x12, 0x26, 0x9f, 0x05, 0x98, 0x67, 0x21, 0x22, 0x76, 0x02, 0x26, 0x02, 0x05, 0x03, // ...&...g!"v.&... + 0x79, 0x53, 0x00, 0x00, 0x00, 0x13, 0x14, 0x72, 0xc0, 0x87, 0x74, 0x60, 0x87, 0x36, 0x68, 0x87, // yS.....r..t`.6h. + 0x79, 0x68, 0x03, 0x72, 0xc0, 0x87, 0x0d, 0xaf, 0x50, 0x0e, 0x6d, 0xd0, 0x0e, 0x7a, 0x50, 0x0e, // yh.r....P.m..zP. + 0x6d, 0x00, 0x0f, 0x7a, 0x30, 0x07, 0x72, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, 0x90, 0x0e, 0x71, // m..z0.r..s .m..q + 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, 0x90, 0x0e, 0x78, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, 0x90, // ..s .m..x..s .m. + 0x0e, 0x71, 0x60, 0x07, 0x7a, 0x30, 0x07, 0x72, 0xd0, 0x06, 0xe9, 0x30, 0x07, 0x72, 0xa0, 0x07, // .q`.z0.r...0.r.. + 0x73, 0x20, 0x07, 0x6d, 0x90, 0x0e, 0x76, 0x40, 0x07, 0x7a, 0x60, 0x07, 0x74, 0xd0, 0x06, 0xe6, // s .m..v@.z`.t... + 0x10, 0x07, 0x76, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, 0x60, 0x0e, 0x73, 0x20, 0x07, 0x7a, 0x30, // ..v..s .m`.s .z0 + 0x07, 0x72, 0xd0, 0x06, 0xe6, 0x60, 0x07, 0x74, 0xa0, 0x07, 0x76, 0x40, 0x07, 0x6d, 0xe0, 0x0e, // .r...`.t..v@.m.. + 0x78, 0xa0, 0x07, 0x71, 0x60, 0x07, 0x7a, 0x30, 0x07, 0x72, 0xa0, 0x07, 0x76, 0x40, 0x07, 0x43, // x..q`.z0.r..v@.C + 0x9e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86, 0x3c, 0x04, 0x10, // .............<.. + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x79, 0x0e, 0x20, 0x00, 0x02, 0x00, // ..........y. ... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xf2, 0x34, 0x40, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, // ........4@...... + 0x00, 0x00, 0x00, 0x30, 0xe4, 0x81, 0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ...0............ + 0x60, 0xc8, 0x23, 0x01, 0x01, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x90, 0xa7, // `.#..0.......... + 0x02, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x2c, 0x10, 0x0b, 0x00, 0x00, // ..@........,.... + 0x00, 0x32, 0x1e, 0x98, 0x14, 0x19, 0x11, 0x4c, 0x90, 0x8c, 0x09, 0x26, 0x47, 0xc6, 0x04, 0x43, // .2.....L...&G..C + 0x1a, 0x4a, 0x60, 0x04, 0xa0, 0x24, 0x8a, 0xa1, 0x08, 0x8a, 0xa3, 0x10, 0x0a, 0x82, 0xa8, 0x11, // .J`..$.......... + 0x00, 0xda, 0x66, 0x00, 0xa8, 0x9b, 0x01, 0xa0, 0x6f, 0x06, 0x00, 0x00, 0x00, 0x79, 0x18, 0x00, // ..f.....o....y.. + 0x00, 0x46, 0x00, 0x00, 0x00, 0x1a, 0x03, 0x4c, 0x90, 0x46, 0x02, 0x13, 0x44, 0x8f, 0x0c, 0x6f, // .F.....L.F..D..o + 0xec, 0xed, 0x4d, 0x0c, 0x24, 0xc6, 0x05, 0xc7, 0x45, 0xa6, 0x06, 0xa6, 0xc6, 0x65, 0x46, 0x06, // ..M.$...E....eF. + 0x04, 0x45, 0x2c, 0x06, 0xc6, 0x26, 0x2c, 0xc7, 0x26, 0x86, 0x2c, 0x65, 0x43, 0x10, 0x4c, 0x10, // .E,..&,.&.,eC.L. + 0x86, 0x63, 0x82, 0x30, 0x20, 0x1b, 0x84, 0x81, 0x98, 0x20, 0x0c, 0xc9, 0x06, 0x61, 0x30, 0x28, // .c.0 .... ...a0( + 0x8c, 0xcd, 0x4d, 0x10, 0x06, 0x65, 0xc3, 0x80, 0x24, 0xc4, 0x04, 0x61, 0x83, 0x08, 0x4c, 0x10, // ..M..e..$..a..L. + 0x86, 0x65, 0x83, 0x40, 0x18, 0x1b, 0x12, 0x62, 0x61, 0x88, 0x61, 0x68, 0x08, 0x67, 0x43, 0x32, // .e.@...ba.ah.gC2 + 0x2c, 0x0c, 0xd1, 0x0c, 0x0d, 0xe1, 0x6c, 0x10, 0x1e, 0x68, 0x82, 0xd0, 0x45, 0x13, 0x04, 0xea, // ,.....l..h..E... + 0x99, 0x20, 0x0c, 0xcc, 0x06, 0x81, 0xa0, 0x36, 0x2c, 0x84, 0xc4, 0x10, 0xc4, 0xd0, 0x4c, 0xd3, // . .....6,.....L. + 0x54, 0x6d, 0x08, 0xac, 0x09, 0xc2, 0x27, 0x6d, 0x40, 0x08, 0x8c, 0x21, 0x86, 0x81, 0x00, 0x36, // Tm....'m@..!...6 + 0x20, 0x03, 0xc6, 0x10, 0xcd, 0x40, 0x00, 0x1b, 0x84, 0x4c, 0xdb, 0x40, 0x44, 0x17, 0xb0, 0x4d, // ....@...L.@D..M + 0x10, 0x04, 0x80, 0x44, 0x5b, 0x58, 0x9a, 0xdb, 0x04, 0x61, 0x68, 0x26, 0x08, 0x83, 0xb3, 0x61, // ...D[X...ah&...a + 0x00, 0x03, 0x30, 0x18, 0x36, 0x08, 0x5f, 0x18, 0x6c, 0x28, 0x3a, 0x0f, 0xe0, 0xc4, 0xa0, 0x0a, // ..0.6._.l(:..... + 0x1b, 0x9b, 0x5d, 0x9b, 0x4b, 0x1a, 0x59, 0x99, 0x1b, 0xdd, 0x94, 0x20, 0xa8, 0x42, 0x86, 0xe7, // ..].K.Y.... .B.. + 0x62, 0x57, 0x26, 0x37, 0x97, 0xf6, 0xe6, 0x36, 0x25, 0x20, 0x9a, 0x90, 0xe1, 0xb9, 0xd8, 0x85, // bW&7...6% ...... + 0xb1, 0xd9, 0x95, 0xc9, 0x4d, 0x09, 0x8c, 0x3a, 0x64, 0x78, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x65, // ....M..:dx.shade + 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x82, 0xa4, 0x0c, 0x19, 0x9e, 0x8b, 0x5c, 0xd9, 0xdc, // rModelS......... + 0x5b, 0x9d, 0xdc, 0x58, 0xd9, 0xdc, 0x94, 0x60, 0xab, 0x43, 0x86, 0xe7, 0x52, 0xe6, 0x46, 0x27, // [..X...`.C..R.F' + 0x97, 0x07, 0xf5, 0x96, 0xe6, 0x46, 0x37, 0x37, 0x25, 0x10, 0x03, 0x00, 0x00, 0x79, 0x18, 0x00, // .....F77%....y.. + 0x00, 0x51, 0x00, 0x00, 0x00, 0x33, 0x08, 0x80, 0x1c, 0xc4, 0xe1, 0x1c, 0x66, 0x14, 0x01, 0x3d, // .Q...3......f..= + 0x88, 0x43, 0x38, 0x84, 0xc3, 0x8c, 0x42, 0x80, 0x07, 0x79, 0x78, 0x07, 0x73, 0x98, 0x71, 0x0c, // .C8...B..yx.s.q. + 0xe6, 0x00, 0x0f, 0xed, 0x10, 0x0e, 0xf4, 0x80, 0x0e, 0x33, 0x0c, 0x42, 0x1e, 0xc2, 0xc1, 0x1d, // .........3.B.... + 0xce, 0xa1, 0x1c, 0x66, 0x30, 0x05, 0x3d, 0x88, 0x43, 0x38, 0x84, 0x83, 0x1b, 0xcc, 0x03, 0x3d, // ...f0.=.C8.....= + 0xc8, 0x43, 0x3d, 0x8c, 0x03, 0x3d, 0xcc, 0x78, 0x8c, 0x74, 0x70, 0x07, 0x7b, 0x08, 0x07, 0x79, // .C=..=.x.tp.{..y + 0x48, 0x87, 0x70, 0x70, 0x07, 0x7a, 0x70, 0x03, 0x76, 0x78, 0x87, 0x70, 0x20, 0x87, 0x19, 0xcc, // H.pp.zp.vx.p ... + 0x11, 0x0e, 0xec, 0x90, 0x0e, 0xe1, 0x30, 0x0f, 0x6e, 0x30, 0x0f, 0xe3, 0xf0, 0x0e, 0xf0, 0x50, // ......0.n0.....P + 0x0e, 0x33, 0x10, 0xc4, 0x1d, 0xde, 0x21, 0x1c, 0xd8, 0x21, 0x1d, 0xc2, 0x61, 0x1e, 0x66, 0x30, // .3....!..!..a.f0 + 0x89, 0x3b, 0xbc, 0x83, 0x3b, 0xd0, 0x43, 0x39, 0xb4, 0x03, 0x3c, 0xbc, 0x83, 0x3c, 0x84, 0x03, // .;..;.C9..<..<.. + 0x3b, 0xcc, 0xf0, 0x14, 0x76, 0x60, 0x07, 0x7b, 0x68, 0x07, 0x37, 0x68, 0x87, 0x72, 0x68, 0x07, // ;...v`.{h.7h.rh. + 0x37, 0x80, 0x87, 0x70, 0x90, 0x87, 0x70, 0x60, 0x07, 0x76, 0x28, 0x07, 0x76, 0xf8, 0x05, 0x76, // 7..p..p`.v(.v..v + 0x78, 0x87, 0x77, 0x80, 0x87, 0x5f, 0x08, 0x87, 0x71, 0x18, 0x87, 0x72, 0x98, 0x87, 0x79, 0x98, // x.w.._..q..r..y. + 0x81, 0x2c, 0xee, 0xf0, 0x0e, 0xee, 0xe0, 0x0e, 0xf5, 0xc0, 0x0e, 0xec, 0x30, 0x03, 0x62, 0xc8, // .,..........0.b. + 0xa1, 0x1c, 0xe4, 0xa1, 0x1c, 0xcc, 0xa1, 0x1c, 0xe4, 0xa1, 0x1c, 0xdc, 0x61, 0x1c, 0xca, 0x21, // ............a..! + 0x1c, 0xc4, 0x81, 0x1d, 0xca, 0x61, 0x06, 0xd6, 0x90, 0x43, 0x39, 0xc8, 0x43, 0x39, 0x98, 0x43, // .....a...C9.C9.C + 0x39, 0xc8, 0x43, 0x39, 0xb8, 0xc3, 0x38, 0x94, 0x43, 0x38, 0x88, 0x03, 0x3b, 0x94, 0xc3, 0x2f, // 9.C9..8.C8..;../ + 0xbc, 0x83, 0x3c, 0xfc, 0x82, 0x3b, 0xd4, 0x03, 0x3b, 0xb0, 0xc3, 0x0c, 0xc4, 0x21, 0x07, 0x7c, // ..<..;..;....!.| + 0x70, 0x03, 0x7a, 0x28, 0x87, 0x76, 0x80, 0x87, 0x19, 0xd1, 0x43, 0x0e, 0xf8, 0xe0, 0x06, 0xe4, // p.z(.v....C..... + 0x20, 0x0e, 0xe7, 0xe0, 0x06, 0xf6, 0x10, 0x0e, 0xf2, 0xc0, 0x0e, 0xe1, 0x90, 0x0f, 0xef, 0x50, // ..............P + 0x0f, 0xf4, 0x30, 0x83, 0x81, 0xc8, 0x01, 0x1f, 0xdc, 0x40, 0x1c, 0xe4, 0xa1, 0x1c, 0xc2, 0x61, // ..0......@.....a + 0x1d, 0xdc, 0x40, 0x1c, 0xe4, 0x01, 0x00, 0x00, 0x00, 0x71, 0x20, 0x00, 0x00, 0x1b, 0x00, 0x00, // ..@......q ..... + 0x00, 0x26, 0x00, 0x0d, 0x97, 0xef, 0x3c, 0x7e, 0x80, 0x34, 0x40, 0x84, 0xf9, 0xc5, 0x6d, 0x9b, // .&....<~.4@...m. + 0x81, 0x34, 0x5c, 0xbe, 0xf3, 0xf8, 0x42, 0x44, 0x00, 0x13, 0x11, 0x02, 0xcd, 0xb0, 0x10, 0x36, // .4....BD.......6 + 0x30, 0x0d, 0x97, 0xef, 0x3c, 0xbe, 0x41, 0x4c, 0x1d, 0xc2, 0x10, 0x8d, 0x84, 0x38, 0x8d, 0x64, // 0...<.AL.....8.d + 0x04, 0xd5, 0x70, 0xf9, 0xce, 0xe3, 0x4b, 0x00, 0xf3, 0x2c, 0x44, 0x49, 0x54, 0xc4, 0xe2, 0x17, // ..p...K..,DIT... + 0xb7, 0x6d, 0x05, 0xd6, 0x70, 0xf9, 0xce, 0xe3, 0x4f, 0xc4, 0x35, 0x51, 0x11, 0xc1, 0x4e, 0x4e, // .m..p...O.5Q..NN + 0x44, 0xf8, 0xc5, 0x6d, 0x5b, 0x80, 0x34, 0x5c, 0xbe, 0xf3, 0xf8, 0xd3, 0x11, 0x11, 0xc0, 0x20, // D..m[.4........ + 0x0e, 0x3e, 0x72, 0xdb, 0x06, 0x40, 0x30, 0x00, 0xd2, 0x00, 0x00, 0x00, 0x00, 0x61, 0x20, 0x00, // .>r..@0......a . + 0x00, 0x73, 0x00, 0x00, 0x00, 0x13, 0x04, 0x43, 0x2c, 0x10, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, // .s.....C,....... + 0x00, 0x34, 0x94, 0xdc, 0x0c, 0x40, 0x19, 0x15, 0x52, 0xe9, 0x06, 0x14, 0x5f, 0x41, 0x06, 0x94, // .4...@..R..._A.. + 0x61, 0x00, 0x29, 0xe5, 0x51, 0x02, 0x65, 0x30, 0x02, 0x40, 0xc6, 0x18, 0x41, 0x48, 0x82, 0x21, // a.).Q.e0.@..AH.! + 0xfc, 0x0b, 0x63, 0x04, 0x21, 0x09, 0x86, 0xb8, 0x2f, 0x8c, 0x11, 0x94, 0xb4, 0xa8, 0xe6, 0xdf, // ..c.!.../....... + 0x18, 0x81, 0x4a, 0xfa, 0xaa, 0xff, 0x8d, 0x11, 0xe4, 0x74, 0x4d, 0x97, 0xdf, 0x18, 0xc1, 0x7b, // ..J......tM....{ + 0xeb, 0x30, 0xfc, 0x8d, 0x11, 0xdc, 0x2c, 0xeb, 0x82, 0x60, 0x30, 0x46, 0x00, 0x82, 0x20, 0x08, // .0....,..`0F.. . + 0x7f, 0x33, 0x00, 0x23, 0x00, 0x63, 0x04, 0x20, 0x08, 0x82, 0xf8, 0x07, 0x00, 0x23, 0x06, 0x09, // .3.#.c. .....#.. + 0x00, 0x82, 0x60, 0x50, 0x91, 0xc1, 0xe5, 0x88, 0x81, 0x18, 0x6c, 0x23, 0x06, 0x09, 0x00, 0x82, // ..`P......l#.... + 0x60, 0x50, 0x95, 0x01, 0xd6, 0x90, 0x01, 0x18, 0x70, 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, 0x50, // `P......p#....`P + 0x99, 0x41, 0xe6, 0x90, 0x41, 0x19, 0x74, 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, 0x50, 0x9d, 0x81, // .A..A.t#....`P.. + 0x06, 0x99, 0x81, 0x18, 0x78, 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, 0x50, 0xa1, 0xc1, 0x16, 0x99, // ....x#....`P.... + 0xc1, 0x19, 0x7c, 0x23, 0x06, 0x06, 0x00, 0x82, 0x60, 0x40, 0xb8, 0x01, 0x76, 0x06, 0x23, 0x06, // ..|#....`@..v.#. + 0x06, 0x00, 0x82, 0x60, 0x40, 0xbc, 0x41, 0x96, 0x06, 0x23, 0x06, 0x07, 0x00, 0x82, 0x60, 0xd0, // ...`@.A..#....`. + 0xb8, 0x01, 0x76, 0xa4, 0xc1, 0x68, 0x42, 0x00, 0x8c, 0x26, 0x08, 0xc1, 0x70, 0x43, 0x21, 0x9c, // ..v..hB..&..pC!. + 0xc1, 0x70, 0x43, 0x21, 0x9c, 0x41, 0x09, 0xc1, 0xce, 0x32, 0x08, 0x41, 0x70, 0x85, 0x61, 0x57, // .pC!.A...2.Ap.aW + 0x18, 0x66, 0x90, 0x40, 0x1f, 0x8b, 0x04, 0xfa, 0x1c, 0x63, 0xd8, 0x31, 0x86, 0x99, 0x70, 0xc1, // .f.@.....c.1..p. + 0xc7, 0x04, 0x0c, 0x3e, 0x66, 0x08, 0xf2, 0x31, 0x43, 0x90, 0xcf, 0x88, 0xc1, 0x03, 0x80, 0x20, // ...>f..1C...... + 0x18, 0x40, 0x7d, 0x90, 0x06, 0xd5, 0x24, 0x04, 0x9a, 0x96, 0x07, 0x79, 0xd0, 0x06, 0xd9, 0x68, // .@}...$....y...h + 0x42, 0x00, 0x8c, 0x18, 0x3c, 0x00, 0x08, 0x82, 0x01, 0xf4, 0x07, 0x6b, 0x80, 0x59, 0xc4, 0xc0, // B...<......k.Y.. + 0x71, 0x7b, 0xb0, 0x07, 0x6f, 0xb0, 0x8d, 0x26, 0x04, 0xc0, 0x68, 0x82, 0x10, 0x18, 0x61, 0x06, // q{..o..&..h...a. + 0xf0, 0xb1, 0xc0, 0x0c, 0xe4, 0x63, 0x44, 0x1a, 0xc0, 0xc7, 0x08, 0x35, 0x80, 0x8f, 0x05, 0x68, // .....cD....5...h + 0x20, 0x1f, 0x0b, 0x08, 0xf8, 0x18, 0x91, 0x06, 0xf2, 0x31, 0x23, 0x88, 0x8f, 0x15, 0x6a, 0x20, // ........1#...j + 0x1f, 0x13, 0x82, 0xf8, 0x18, 0xb2, 0x06, 0xf2, 0xb1, 0x40, 0x81, 0xcf, 0x88, 0xc1, 0x01, 0x80, // .........@...... + 0x20, 0x18, 0x1c, 0xb0, 0xd0, 0x07, 0x87, 0x1a, 0x8c, 0x18, 0x1c, 0x00, 0x08, 0x82, 0xc1, 0x11, // ............... + 0x0b, 0x7e, 0x40, 0xac, 0xc1, 0x88, 0xc1, 0x01, 0x80, 0x20, 0x18, 0x1c, 0xb2, 0xf0, 0x07, 0x03, // .~@...... ...... + 0x1b, 0x8c, 0x18, 0x1c, 0x00, 0x08, 0x82, 0xc1, 0x31, 0x0b, 0x7f, 0x30, 0xb0, 0xc1, 0x88, 0xc1, // ........1..0.... + 0x01, 0x80, 0x20, 0x18, 0x1c, 0xb4, 0x00, 0x0a, 0x43, 0x1b, 0x8c, 0x18, 0x1c, 0x00, 0x08, 0x82, // .. .....C....... + 0xc1, 0x51, 0x0b, 0xa1, 0x30, 0xb8, 0xc1, 0x88, 0x81, 0x03, 0x80, 0x20, 0x18, 0x48, 0xb3, 0xe0, // .Q..0...... .H.. + 0x07, 0x6e, 0x90, 0x06, 0x68, 0x40, 0x0a, 0x83, 0x10, 0xbc, 0x41, 0x1f, 0xcc, 0x12, 0x08, 0x08, // .n..h@....A..... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ......... +}; +static const uint8_t cs_yuv_to_rgb_mtl[1617] = +{ + 0x43, 0x53, 0x48, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x10, 0x62, // CSH............b + 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, 0x59, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x01, // gfx_texYSampler. + 0x01, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x74, // ..........bgfx_t + 0x65, 0x78, 0x59, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x01, 0x01, 0xff, 0xff, 0x01, 0x00, // exYTexture...... + 0x00, 0x00, 0x00, 0x00, 0x13, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, 0x43, 0x62, 0x43, // .....bgfx_texCbC + 0x72, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x01, 0x01, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, // rSampler........ + 0x00, 0x00, 0x13, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, 0x43, 0x62, 0x43, 0x72, 0x54, // ...bgfx_texCbCrT + 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x01, 0x01, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // exture.......... + 0x0c, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x69, 0x6d, 0x67, 0x52, 0x67, 0x62, 0x61, 0x01, 0x01, 0xff, // .bgfx_imgRgba... + 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, // ........bgfx_tex + 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x62, 0x67, 0x66, 0x78, // Y...........bgfx + 0x5f, 0x74, 0x65, 0x78, 0x43, 0x62, 0x43, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // _texCbCr........ + 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x01, 0x00, 0x81, 0x05, 0x00, 0x00, 0x23, 0x69, 0x6e, 0x63, // ............#inc + 0x6c, 0x75, 0x64, 0x65, 0x20, 0x3c, 0x6d, 0x65, 0x74, 0x61, 0x6c, 0x5f, 0x73, 0x74, 0x64, 0x6c, // lude .#include ..usin + 0x67, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x20, 0x6d, 0x65, 0x74, 0x61, // g namespace meta + 0x6c, 0x3b, 0x0a, 0x0a, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x20, 0x76, 0x6f, 0x69, 0x64, 0x20, // l;..kernel void + 0x78, 0x6c, 0x61, 0x74, 0x4d, 0x74, 0x6c, 0x4d, 0x61, 0x69, 0x6e, 0x28, 0x74, 0x65, 0x78, 0x74, // xlatMtlMain(text + 0x75, 0x72, 0x65, 0x32, 0x64, 0x3c, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x2c, 0x20, 0x61, 0x63, 0x63, // ure2d bgfx + 0x5f, 0x69, 0x6d, 0x67, 0x52, 0x67, 0x62, 0x61, 0x20, 0x5b, 0x5b, 0x74, 0x65, 0x78, 0x74, 0x75, // _imgRgba [[textu + 0x72, 0x65, 0x28, 0x30, 0x29, 0x5d, 0x5d, 0x2c, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, // re(0)]], texture + 0x32, 0x64, 0x3c, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3e, 0x20, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x74, // 2d bgfx_t + 0x65, 0x78, 0x59, 0x20, 0x5b, 0x5b, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x31, 0x29, // exY [[texture(1) + 0x5d, 0x5d, 0x2c, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x32, 0x64, 0x3c, 0x66, 0x6c, // ]], texture2d bgfx_texCbC + 0x72, 0x20, 0x5b, 0x5b, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x32, 0x29, 0x5d, 0x5d, // r [[texture(2)]] + 0x2c, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x20, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x74, // , sampler bgfx_t + 0x65, 0x78, 0x59, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x20, 0x5b, 0x5b, 0x73, 0x61, 0x6d, // exYSampler [[sam + 0x70, 0x6c, 0x65, 0x72, 0x28, 0x31, 0x29, 0x5d, 0x5d, 0x2c, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, // pler(1)]], sampl + 0x65, 0x72, 0x20, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, 0x43, 0x62, 0x43, 0x72, 0x53, // er bgfx_texCbCrS + 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x20, 0x5b, 0x5b, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, // ampler [[sampler + 0x28, 0x32, 0x29, 0x5d, 0x5d, 0x2c, 0x20, 0x75, 0x69, 0x6e, 0x74, 0x33, 0x20, 0x67, 0x6c, 0x5f, // (2)]], uint3 gl_ + 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, // GlobalInvocation + 0x49, 0x44, 0x20, 0x5b, 0x5b, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x70, 0x6f, 0x73, 0x69, // ID [[thread_posi + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x5f, 0x67, 0x72, 0x69, 0x64, 0x5d, 0x5d, 0x29, 0x0a, // tion_in_grid]]). + 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, // {. do. {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x32, 0x20, 0x5f, 0x33, 0x35, 0x32, // int2 _352 + 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x32, 0x28, 0x75, 0x69, 0x6e, 0x74, 0x32, 0x28, 0x62, 0x67, // = int2(uint2(bg + 0x66, 0x78, 0x5f, 0x69, 0x6d, 0x67, 0x52, 0x67, 0x62, 0x61, 0x2e, 0x67, 0x65, 0x74, 0x5f, 0x77, // fx_imgRgba.get_w + 0x69, 0x64, 0x74, 0x68, 0x28, 0x29, 0x2c, 0x20, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x69, 0x6d, 0x67, // idth(), bgfx_img + 0x52, 0x67, 0x62, 0x61, 0x2e, 0x67, 0x65, 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x28, // Rgba.get_height( + 0x29, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, // )));. if + 0x28, 0x28, 0x69, 0x6e, 0x74, 0x33, 0x28, 0x67, 0x6c, 0x5f, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, // ((int3(gl_Global + 0x49, 0x6e, 0x76, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x29, 0x2e, 0x78, 0x20, // InvocationID).x + 0x3e, 0x3d, 0x20, 0x5f, 0x33, 0x35, 0x32, 0x2e, 0x78, 0x29, 0x20, 0x7c, 0x7c, 0x20, 0x28, 0x69, // >= _352.x) || (i + 0x6e, 0x74, 0x33, 0x28, 0x67, 0x6c, 0x5f, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x49, 0x6e, 0x76, // nt3(gl_GlobalInv + 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x29, 0x2e, 0x79, 0x20, 0x3e, 0x3d, 0x20, // ocationID).y >= + 0x5f, 0x33, 0x35, 0x32, 0x2e, 0x79, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // _352.y)). + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, // {. b + 0x72, 0x65, 0x61, 0x6b, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, // reak;. }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x20, 0x5f, // float2 _ + 0x32, 0x39, 0x31, 0x20, 0x3d, 0x20, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x28, 0x69, 0x6e, // 291 = (float2(in + 0x74, 0x33, 0x28, 0x67, 0x6c, 0x5f, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x49, 0x6e, 0x76, 0x6f, // t3(gl_GlobalInvo + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x29, 0x2e, 0x78, 0x79, 0x29, 0x20, 0x2b, 0x20, // cationID).xy) + + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x28, 0x30, 0x2e, 0x35, 0x29, 0x29, 0x20, 0x2a, 0x20, 0x28, // float2(0.5)) * ( + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x28, 0x31, 0x2e, 0x30, 0x29, 0x20, 0x2f, 0x20, 0x66, 0x6c, // float2(1.0) / fl + 0x6f, 0x61, 0x74, 0x32, 0x28, 0x5f, 0x33, 0x35, 0x32, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, // oat2(_352));. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x33, 0x37, 0x32, // float4 _372 + 0x20, 0x3d, 0x20, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, 0x43, 0x62, 0x43, 0x72, 0x2e, // = bgfx_texCbCr. + 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x28, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, 0x43, // sample(bgfx_texC + 0x62, 0x43, 0x72, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x2c, 0x20, 0x5f, 0x32, 0x39, 0x31, // bCrSampler, _291 + 0x2c, 0x20, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x28, 0x30, 0x2e, 0x30, 0x29, 0x29, 0x3b, 0x0a, 0x20, // , level(0.0));. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x33, 0x30, // float _30 + 0x32, 0x20, 0x3d, 0x20, 0x28, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, 0x59, 0x2e, 0x73, // 2 = (bgfx_texY.s + 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x28, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x74, 0x65, 0x78, 0x59, 0x53, // ample(bgfx_texYS + 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x2c, 0x20, 0x5f, 0x32, 0x39, 0x31, 0x2c, 0x20, 0x6c, 0x65, // ampler, _291, le + 0x76, 0x65, 0x6c, 0x28, 0x30, 0x2e, 0x30, 0x29, 0x29, 0x2e, 0x78, 0x20, 0x2d, 0x20, 0x30, 0x2e, // vel(0.0)).x - 0. + 0x30, 0x36, 0x32, 0x37, 0x34, 0x35, 0x31, 0x30, 0x31, 0x37, 0x34, 0x39, 0x38, 0x39, 0x37, 0x30, // 0627451017498970 + 0x30, 0x33, 0x31, 0x37, 0x33, 0x38, 0x32, 0x38, 0x31, 0x32, 0x35, 0x29, 0x20, 0x2a, 0x20, 0x31, // 03173828125) * 1 + 0x2e, 0x31, 0x36, 0x34, 0x33, 0x38, 0x33, 0x35, 0x33, 0x30, 0x36, 0x31, 0x36, 0x37, 0x36, 0x30, // .164383530616760 + 0x32, 0x35, 0x33, 0x39, 0x30, 0x36, 0x32, 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 25390625;. + 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x33, 0x30, 0x35, 0x20, 0x3d, 0x20, 0x5f, // float _305 = _ + 0x33, 0x37, 0x32, 0x2e, 0x78, 0x20, 0x2d, 0x20, 0x30, 0x2e, 0x35, 0x30, 0x31, 0x39, 0x36, 0x30, // 372.x - 0.501960 + 0x38, 0x31, 0x33, 0x39, 0x39, 0x39, 0x31, 0x37, 0x36, 0x30, 0x32, 0x35, 0x33, 0x39, 0x30, 0x36, // 8139991760253906 + 0x32, 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, // 25;. floa + 0x74, 0x20, 0x5f, 0x33, 0x30, 0x39, 0x20, 0x3d, 0x20, 0x5f, 0x33, 0x37, 0x32, 0x2e, 0x79, 0x20, // t _309 = _372.y + 0x2d, 0x20, 0x30, 0x2e, 0x35, 0x30, 0x31, 0x39, 0x36, 0x30, 0x38, 0x31, 0x33, 0x39, 0x39, 0x39, // - 0.501960813999 + 0x31, 0x37, 0x36, 0x30, 0x32, 0x35, 0x33, 0x39, 0x30, 0x36, 0x32, 0x35, 0x3b, 0x0a, 0x20, 0x20, // 176025390625;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x69, 0x6d, 0x67, 0x52, 0x67, // bgfx_imgRg + 0x62, 0x61, 0x2e, 0x77, 0x72, 0x69, 0x74, 0x65, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x28, // ba.write(float4( + 0x66, 0x61, 0x73, 0x74, 0x3a, 0x3a, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x66, 0x6c, 0x6f, 0x61, // fast::clamp(floa + 0x74, 0x33, 0x28, 0x5f, 0x33, 0x30, 0x32, 0x20, 0x2b, 0x20, 0x28, 0x5f, 0x33, 0x30, 0x39, 0x20, // t3(_302 + (_309 + 0x2a, 0x20, 0x31, 0x2e, 0x37, 0x39, 0x32, 0x37, 0x34, 0x31, 0x30, 0x36, 0x30, 0x32, 0x35, 0x36, // * 1.792741060256 + 0x39, 0x35, 0x38, 0x30, 0x30, 0x37, 0x38, 0x31, 0x32, 0x35, 0x29, 0x2c, 0x20, 0x28, 0x5f, 0x33, // 9580078125), (_3 + 0x30, 0x32, 0x20, 0x2d, 0x20, 0x28, 0x5f, 0x33, 0x30, 0x35, 0x20, 0x2a, 0x20, 0x30, 0x2e, 0x32, // 02 - (_305 * 0.2 + 0x31, 0x33, 0x32, 0x32, 0x30, 0x39, 0x36, 0x38, 0x38, 0x34, 0x32, 0x35, 0x30, 0x36, 0x34, 0x30, // 1322096884250640 + 0x38, 0x36, 0x39, 0x31, 0x34, 0x30, 0x36, 0x32, 0x35, 0x29, 0x29, 0x20, 0x2d, 0x20, 0x28, 0x5f, // 869140625)) - (_ + 0x33, 0x30, 0x39, 0x20, 0x2a, 0x20, 0x30, 0x2e, 0x35, 0x33, 0x32, 0x38, 0x38, 0x31, 0x36, 0x37, // 309 * 0.53288167 + 0x37, 0x31, 0x35, 0x30, 0x37, 0x32, 0x36, 0x33, 0x31, 0x38, 0x33, 0x35, 0x39, 0x33, 0x37, 0x35, // 7150726318359375 + 0x29, 0x2c, 0x20, 0x5f, 0x33, 0x30, 0x32, 0x20, 0x2b, 0x20, 0x28, 0x5f, 0x33, 0x30, 0x35, 0x20, // ), _302 + (_305 + 0x2a, 0x20, 0x32, 0x2e, 0x31, 0x31, 0x32, 0x34, 0x30, 0x31, 0x37, 0x32, 0x33, 0x38, 0x36, 0x31, // * 2.112401723861 + 0x36, 0x39, 0x34, 0x33, 0x33, 0x35, 0x39, 0x33, 0x37, 0x35, 0x29, 0x29, 0x2c, 0x20, 0x66, 0x6c, // 6943359375)), fl + 0x6f, 0x61, 0x74, 0x33, 0x28, 0x30, 0x2e, 0x30, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, // oat3(0.0), float + 0x33, 0x28, 0x31, 0x2e, 0x30, 0x29, 0x29, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x2c, 0x20, 0x75, // 3(1.0)), 1.0), u + 0x69, 0x6e, 0x74, 0x32, 0x28, 0x69, 0x6e, 0x74, 0x33, 0x28, 0x67, 0x6c, 0x5f, 0x47, 0x6c, 0x6f, // int2(int3(gl_Glo + 0x62, 0x61, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x29, // balInvocationID) + 0x2e, 0x78, 0x79, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, // .xy));. b + 0x72, 0x65, 0x61, 0x6b, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x20, 0x77, 0x68, 0x69, 0x6c, // reak;. } whil + 0x65, 0x28, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x00, 0x00, 0x50, // e(false);.}....P + 0x00, // . +}; +extern const uint8_t* cs_yuv_to_rgb_pssl; +extern const uint32_t cs_yuv_to_rgb_pssl_size; diff --git a/src/cs_yuv_to_rgb.sc b/src/cs_yuv_to_rgb.sc new file mode 100644 index 000000000..b4c5f1ecb --- /dev/null +++ b/src/cs_yuv_to_rgb.sc @@ -0,0 +1,41 @@ +/* + * Copyright 2011-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE + */ + +#include "bgfx_compute.sh" + +SAMPLER2D(bgfx_texY, 1); +SAMPLER2D(bgfx_texCbCr, 2); + +IMAGE2D_WO(bgfx_imgRgba, rgba8, 0); + +NUM_THREADS(8, 8, 1) +void main() +{ + ivec2 coord = ivec2(gl_GlobalInvocationID.xy); + ivec2 size = imageSize(bgfx_imgRgba); + + if (coord.x >= size.x + || coord.y >= size.y) + { + return; + } + + vec2 invSize = vec2(1.0, 1.0) / vec2(size); + vec2 uv = (vec2(coord) + vec2(0.5, 0.5) ) * invSize; + + float y = texture2DLod(bgfx_texY, uv, 0.0).x; + vec2 cb = texture2DLod(bgfx_texCbCr, uv, 0.0).xy; + + float yL = (y - 16.0/255.0) * (255.0/219.0); + float cbL = (cb.x - 128.0/255.0) * (255.0/224.0); + float crL = (cb.y - 128.0/255.0) * (255.0/224.0); + + vec3 rgb; + rgb.r = yL + 1.5748 * crL; + rgb.g = yL - 0.1873 * cbL - 0.4681 * crL; + rgb.b = yL + 1.8556 * cbL; + + imageStore(bgfx_imgRgba, coord, vec4(clamp(rgb, 0.0, 1.0), 1.0) ); +} diff --git a/src/makefile b/src/makefile index 9d6eec699..8c8ccbd27 100644 --- a/src/makefile +++ b/src/makefile @@ -23,6 +23,7 @@ BIN = vs_debugfont.bin.h \ cs_mipgen_oddx.bin.h \ cs_mipgen_oddy.bin.h \ cs_mipgen_oddxy.bin.h \ + cs_yuv_to_rgb.bin.h \ .PHONY: all all: $(BIN) @@ -112,6 +113,9 @@ cs_mipgen_oddy.bin.h : cs_mipgen_oddy.sc cs_mipgen.sh cs_mipgen_oddxy.bin.h : cs_mipgen_oddxy.sc cs_mipgen.sh $(call shader-embedded-compute) +cs_yuv_to_rgb.bin.h : cs_yuv_to_rgb.sc + $(call shader-embedded-compute) + .PHONY: clean clean: @echo Cleaning... diff --git a/src/renderer_d3d11.cpp b/src/renderer_d3d11.cpp index a43d443fd..69c260052 100644 --- a/src/renderer_d3d11.cpp +++ b/src/renderer_d3d11.cpp @@ -7,6 +7,7 @@ #if BGFX_CONFIG_RENDERER_DIRECT3D11 # include "renderer_d3d11.h" +# include "video_d3d11.h" # include namespace bgfx { namespace d3d11 @@ -1618,6 +1619,15 @@ namespace bgfx { namespace d3d11 g_caps.formats[ii] = support; } + if (_init.videoDecode) + { + initVideoDecoder( + { + .device = m_device, + .deviceCtx = m_deviceCtx, + }); + } + // Init reserved part of view name. for (uint32_t ii = 0; ii < BGFX_CONFIG_MAX_VIEWS; ++ii) { @@ -1728,6 +1738,7 @@ namespace bgfx { namespace d3d11 return false; } + void shutdown() { preReset(); @@ -3748,6 +3759,16 @@ namespace bgfx { namespace d3d11 static RendererContextD3D11* s_renderD3D11; + const ProgramD3D11& videoGetProgram(RendererContextD3D11* _renderer, ProgramHandle _handle) + { + return _renderer->m_program[_handle.idx]; + } + + ID3D11SamplerState* videoGetSamplerState(RendererContextD3D11* _renderer, uint32_t _flags) + { + return _renderer->getSamplerState(_flags, NULL); + } + RendererContextI* rendererCreate(const Init& _init) { s_renderD3D11 = BX_NEW(g_allocator, RendererContextD3D11); @@ -4388,7 +4409,10 @@ namespace bgfx { namespace d3d11 uint32_t kk = 0; const bool compressed = bimg::isCompressed(bimg::TextureFormat::Enum(m_textureFormat) ); - const bool swizzle = TextureFormat::BGRA8 == m_textureFormat && 0 != (m_flags&BGFX_TEXTURE_COMPUTE_WRITE); + const bool isVideoDecodeDst = 0 != (m_flags & BGFX_TEXTURE_INTERNAL_VIDEO_DECODE_DST); + const bool swizzle = TextureFormat::BGRA8 == m_textureFormat + && (0 != (m_flags&BGFX_TEXTURE_COMPUTE_WRITE) || isVideoDecodeDst) + ; BX_TRACE("Texture %3d: %s (requested: %s), layers %d, %dx%d%s%s%s." , getHandle() @@ -4403,7 +4427,7 @@ namespace bgfx { namespace d3d11 ); uint8_t* temp = NULL; - for (uint16_t side = 0; side < numSides; ++side) + for (uint16_t side = 0; !isVideoDecodeDst && side < numSides; ++side) { for (uint8_t lod = 0, num = ti.numMips; lod < num; ++lod) { @@ -4457,7 +4481,9 @@ namespace bgfx { namespace d3d11 } const bool writeOnly = 0 != (m_flags & (BGFX_TEXTURE_RT_WRITE_ONLY|BGFX_TEXTURE_READ_BACK) ); - const bool computeWrite = 0 != (m_flags & BGFX_TEXTURE_COMPUTE_WRITE); + const bool computeWrite = 0 != (m_flags & BGFX_TEXTURE_COMPUTE_WRITE) + || isVideoDecodeDst + ; const bool renderTarget = 0 != (m_flags & BGFX_TEXTURE_RT_MASK); const bool srgb = 0 != (m_flags & BGFX_TEXTURE_SRGB); const bool blit = 0 != (m_flags & BGFX_TEXTURE_BLIT_DST); @@ -4709,6 +4735,26 @@ namespace bgfx { namespace d3d11 DX_CHECK(s_renderD3D11->m_device->CreateUnorderedAccessView(m_ptr, NULL, &m_uav) ); } + if (isVideoDecodeDst) + { + BX_ASSERT(imageContainer.m_size >= sizeof(VideoDecoderInit) + , "VIDEO_DECODE_DST texture: Memory too small for VideoDecoderInit (got %d, want %zu)." + , imageContainer.m_size + , sizeof(VideoDecoderInit) + ); + const VideoDecoderInit* init = (const VideoDecoderInit*)imageContainer.m_data; + BX_ASSERT(kVideoDecoderInitMagic == init->magic + , "VIDEO_DECODE_DST texture: bad VideoDecoderInit magic (0x%08x)." + , init->magic + ); + + m_videoDecoder = videoDecoderCreate(*init, s_renderD3D11, uint16_t(ti.width), uint16_t(ti.height) ); + if (NULL == m_videoDecoder) + { + BX_TRACE("Failed to initialize hardware video decoder for texture."); + } + } + if (temp != NULL) { kk = 0; @@ -4728,6 +4774,9 @@ namespace bgfx { namespace d3d11 void TextureD3D11::destroy() { + videoDecoderDestroy(m_videoDecoder); + m_videoDecoder = NULL; + m_dar.destroy(); s_renderD3D11->m_srvUavLru.invalidateWithParent(getHandle().idx); @@ -4781,6 +4830,28 @@ namespace bgfx { namespace d3d11 void TextureD3D11::update(uint8_t _side, uint8_t _mip, const Rect& _rect, uint16_t _z, uint16_t _depth, uint16_t _pitch, const Memory* _mem) { + if (0 != (m_flags & BGFX_TEXTURE_INTERNAL_VIDEO_DECODE_DST) ) + { + BX_ASSERT(_mem->size >= sizeof(VideoDecoderFrame) + , "VIDEO_DECODE_DST update: Memory too small for VideoDecoderFrame (got %d, want %zu)." + , _mem->size + , sizeof(VideoDecoderFrame) + ); + + const VideoDecoderFrame* frame = (const VideoDecoderFrame*)_mem->data; + BX_ASSERT(kVideoDecoderFrameMagic == frame->magic + , "VIDEO_DECODE_DST update: bad VideoDecoderFrame magic (0x%08x)." + , frame->magic + ); + if (NULL != m_videoDecoder) + { + videoDecoderDecode(m_videoDecoder, *frame, *this); + } + + BX_UNUSED(_side, _mip, _rect, _z, _depth, _pitch); + return; + } + ID3D11DeviceContext* deviceCtx = s_renderD3D11->m_deviceCtx; D3D11_BOX box; diff --git a/src/renderer_d3d11.h b/src/renderer_d3d11.h index d87a7776c..d623a6992 100644 --- a/src/renderer_d3d11.h +++ b/src/renderer_d3d11.h @@ -263,6 +263,8 @@ namespace bgfx { namespace d3d11 IntelDirectAccessResourceDescriptor* m_descriptor; }; + struct VideoDecoderD3D11; + struct TextureD3D11 { enum Enum @@ -277,6 +279,7 @@ namespace bgfx { namespace d3d11 , m_rt(NULL) , m_srv(NULL) , m_uav(NULL) + , m_videoDecoder(NULL) , m_numMips(0) { } @@ -307,6 +310,7 @@ namespace bgfx { namespace d3d11 ID3D11ShaderResourceView* m_srv; ID3D11UnorderedAccessView* m_uav; + VideoDecoderD3D11* m_videoDecoder; uint64_t m_flags; uint32_t m_width; uint32_t m_height; diff --git a/src/renderer_d3d12.cpp b/src/renderer_d3d12.cpp index ac75f4550..5e6670bb3 100644 --- a/src/renderer_d3d12.cpp +++ b/src/renderer_d3d12.cpp @@ -7,6 +7,7 @@ #if BGFX_CONFIG_RENDERER_DIRECT3D12 # include "renderer_d3d12.h" +# include "video_d3d12.h" #if !BX_PLATFORM_WINDOWS && !BX_PLATFORM_LINUX # include @@ -482,7 +483,6 @@ namespace bgfx { namespace d3d12 static const GUID IID_ID3D12Resource2 = { 0xbe36ec3b, 0xea85, 0x4aeb, { 0xa4, 0x5a, 0xe9, 0xd7, 0x64, 0x04, 0xa4, 0x95 } }; static const GUID IID_ID3D12RootSignature = { 0xc54a6b66, 0x72df, 0x4ee8, { 0x8b, 0xe5, 0xa9, 0x46, 0xa1, 0x42, 0x92, 0x14 } }; static const GUID IID_ID3D12QueryHeap = { 0x0d9658ae, 0xed45, 0x469e, { 0xa6, 0x1d, 0x97, 0x0e, 0xc5, 0x83, 0xca, 0xb4 } }; - BX_PRAGMA_DIAGNOSTIC_POP(); static const GUID s_d3dDeviceIIDs[] = @@ -1858,6 +1858,22 @@ namespace bgfx { namespace d3d12 g_caps.formats[ii] = support; } + if (_init.videoDecode) + { + initVideoDecoder( + { + .device = m_device, + .computeRootSignature = m_computeRootSignature, + .samplerHeap = m_samplerAllocator.getHeap(), + .commandQueue = m_cmd.m_commandQueue, +#if !BX_PLATFORM_LINUX + .vendorId = uint16_t(m_dxgi.m_adapterDesc.VendorId), +#else + .vendorId = 0, +#endif // !BX_PLATFORM_LINUX + }); + } + // Init reserved part of view name. for (uint32_t ii = 0; ii < BGFX_CONFIG_MAX_VIEWS; ++ii) { @@ -1949,6 +1965,7 @@ namespace bgfx { namespace d3d12 return false; } + void shutdown() { m_cmd.finish(); @@ -3915,6 +3932,32 @@ namespace bgfx { namespace d3d12 static RendererContextD3D12* s_renderD3D12; + ID3D12PipelineState* videoGetPipelineState(RendererContextD3D12* _renderer, ProgramHandle _handle) + { + return _renderer->getPipelineState(_handle); + } + + ScratchBufferD3D12& videoGetScratchBuffer(RendererContextD3D12* _renderer) + { + return _renderer->m_scratchBuffer[_renderer->m_backBufferColorIdx]; + } + + D3D12_GPU_DESCRIPTOR_HANDLE videoGetSamplerHandle(RendererContextD3D12* _renderer, const uint32_t* _samplerFlags) + { + const uint16_t samplerStateIdx = _renderer->getSamplerState(_samplerFlags, BGFX_MAX_COMPUTE_BINDINGS, NULL); + return _renderer->m_samplerAllocator.get(samplerStateIdx); + } + + ID3D12GraphicsCommandList* videoGetCommandList(RendererContextD3D12* _renderer) + { + return _renderer->m_commandList; + } + + void videoReleaseResource(RendererContextD3D12* _renderer, ID3D12Resource* _resource) + { + _renderer->m_cmd.release(_resource); + } + RendererContextI* rendererCreate(const Init& _init) { s_renderD3D12 = BX_NEW(g_allocator, RendererContextD3D12); @@ -4135,13 +4178,25 @@ namespace bgfx { namespace d3d12 m_gpuHandle.ptr += m_incrementSize; } - void ScratchBufferD3D12::allocSrv(D3D12_GPU_DESCRIPTOR_HANDLE& _gpuHandle, BufferD3D12& _buffer) + void ScratchBufferD3D12::allocSrv(D3D12_GPU_DESCRIPTOR_HANDLE& _gpuHandle, ID3D12Resource* _resource, const D3D12_SHADER_RESOURCE_VIEW_DESC& _desc) { ID3D12Device* device = s_renderD3D12->m_device; - device->CreateShaderResourceView(_buffer.m_ptr - , &_buffer.m_srvd - , m_cpuHandle - ); + device->CreateShaderResourceView(_resource, &_desc, m_cpuHandle); + m_cpuHandle.ptr += m_incrementSize; + + _gpuHandle = m_gpuHandle; + m_gpuHandle.ptr += m_incrementSize; + } + + void ScratchBufferD3D12::allocSrv(D3D12_GPU_DESCRIPTOR_HANDLE& _gpuHandle, BufferD3D12& _buffer) + { + allocSrv(_gpuHandle, _buffer.m_ptr, _buffer.m_srvd); + } + + void ScratchBufferD3D12::allocUav(D3D12_GPU_DESCRIPTOR_HANDLE& _gpuHandle, ID3D12Resource* _resource, const D3D12_UNORDERED_ACCESS_VIEW_DESC& _desc) + { + ID3D12Device* device = s_renderD3D12->m_device; + device->CreateUnorderedAccessView(_resource, NULL, &_desc, m_cpuHandle); m_cpuHandle.ptr += m_incrementSize; _gpuHandle = m_gpuHandle; @@ -4150,16 +4205,7 @@ namespace bgfx { namespace d3d12 void ScratchBufferD3D12::allocUav(D3D12_GPU_DESCRIPTOR_HANDLE& _gpuHandle, BufferD3D12& _buffer) { - ID3D12Device* device = s_renderD3D12->m_device; - device->CreateUnorderedAccessView(_buffer.m_ptr - , NULL - , &_buffer.m_uavd - , m_cpuHandle - ); - m_cpuHandle.ptr += m_incrementSize; - - _gpuHandle = m_gpuHandle; - m_gpuHandle.ptr += m_incrementSize; + allocUav(_gpuHandle, _buffer.m_ptr, _buffer.m_uavd); } void ChunkedScratchBufferD3D12::createUniform(uint32_t _chunkSize, uint32_t _numChunks) @@ -5548,12 +5594,16 @@ namespace bgfx { namespace d3d12 uint32_t kk = 0; const bool compressed = bimg::isCompressed(bimg::TextureFormat::Enum(m_textureFormat) ); - const bool swizzle = TextureFormat::BGRA8 == m_textureFormat && 0 != (m_flags&BGFX_TEXTURE_COMPUTE_WRITE); + const bool isVideoDecodeDst = 0 != (m_flags & BGFX_TEXTURE_INTERNAL_VIDEO_DECODE_DST); + const bool swizzle = TextureFormat::BGRA8 == m_textureFormat + && (0 != (m_flags&BGFX_TEXTURE_COMPUTE_WRITE) || isVideoDecodeDst) + ; const bool writeOnly = 0 != (m_flags & BGFX_TEXTURE_RT_WRITE_ONLY); const bool renderTarget = 0 != (m_flags & BGFX_TEXTURE_RT_MASK); const bool computeWrite = 0 != (m_flags & BGFX_TEXTURE_COMPUTE_WRITE) || (renderTarget && 1 < ti.numMips) + || isVideoDecodeDst ; const bool blit = 0 != (m_flags & BGFX_TEXTURE_BLIT_DST); const bool externalShared = 0 != (m_flags & BGFX_TEXTURE_EXTERNAL_SHARED); @@ -5580,7 +5630,7 @@ namespace bgfx { namespace d3d12 , swizzle ? " (swizzle BGRA8 -> RGBA8)" : "" ); - for (uint16_t side = 0; side < numSides; ++side) + for (uint16_t side = 0; !isVideoDecodeDst && side < numSides; ++side) { for (uint8_t lod = 0; lod < ti.numMips; ++lod) { @@ -5945,6 +5995,29 @@ namespace bgfx { namespace d3d12 , D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE ); } + + if (isVideoDecodeDst) + { + BX_TRACE("TextureD3D12::create VIDEO_DECODE_DST imgSize=%u want=%zu" + , imageContainer.m_size, sizeof(VideoDecoderInit) ); + + BX_ASSERT(imageContainer.m_size >= sizeof(VideoDecoderInit) + , "VIDEO_DECODE_DST texture: Memory too small for VideoDecoderInit (got %d, want %zu)." + , imageContainer.m_size + , sizeof(VideoDecoderInit) + ); + const VideoDecoderInit* init = (const VideoDecoderInit*)imageContainer.m_data; + BX_ASSERT(kVideoDecoderInitMagic == init->magic + , "VIDEO_DECODE_DST texture: bad VideoDecoderInit magic (0x%08x)." + , init->magic + ); + + m_videoDecoder = videoDecoderCreate(*init, s_renderD3D12, uint16_t(ti.width), uint16_t(ti.height) ); + if (NULL == m_videoDecoder) + { + BX_TRACE("Failed to initialize hardware video decoder for texture."); + } + } } return m_directAccessPtr; @@ -5952,6 +6025,9 @@ namespace bgfx { namespace d3d12 void TextureD3D12::destroy() { + videoDecoderDestroy(m_videoDecoder); + m_videoDecoder = NULL; + if (NULL != m_ptr) { if (NULL != m_directAccessPtr) @@ -6001,6 +6077,27 @@ namespace bgfx { namespace d3d12 void TextureD3D12::update(ID3D12GraphicsCommandList* _commandList, uint8_t _side, uint8_t _mip, const Rect& _rect, uint16_t _z, uint16_t _depth, uint16_t _pitch, const Memory* _mem) { + if (0 != (m_flags & BGFX_TEXTURE_INTERNAL_VIDEO_DECODE_DST) ) + { + BX_ASSERT(_mem->size >= sizeof(VideoDecoderFrame) + , "VIDEO_DECODE_DST update: Memory too small for VideoDecoderFrame (got %d, want %zu)." + , _mem->size + , sizeof(VideoDecoderFrame) + ); + + const VideoDecoderFrame* frame = (const VideoDecoderFrame*)_mem->data; + BX_ASSERT(kVideoDecoderFrameMagic == frame->magic + , "VIDEO_DECODE_DST update: bad VideoDecoderFrame magic (0x%08x)." + , frame->magic + ); + if (NULL != m_videoDecoder) + { + videoDecoderDecode(m_videoDecoder, *frame, *this); + } + + return; + } + D3D12_RESOURCE_STATES state = setState(_commandList, D3D12_RESOURCE_STATE_COPY_DEST); const uint32_t bpp = bimg::getBitsPerPixel(bimg::TextureFormat::Enum(m_textureFormat) ); diff --git a/src/renderer_d3d12.h b/src/renderer_d3d12.h index bf88d142a..7c13298e4 100644 --- a/src/renderer_d3d12.h +++ b/src/renderer_d3d12.h @@ -13,6 +13,7 @@ #if BX_PLATFORM_LINUX || BX_PLATFORM_WINDOWS || BX_PLATFORM_WINRT # include +# include #else # if !BGFX_CONFIG_DEBUG # define D3DCOMPILE_NO_DEBUG 1 @@ -124,7 +125,10 @@ namespace bgfx { namespace d3d12 void allocSrv(D3D12_GPU_DESCRIPTOR_HANDLE& _gpuHandle, struct BufferD3D12& _buffer); void allocUav(D3D12_GPU_DESCRIPTOR_HANDLE& _gpuHandle, struct TextureD3D12& _texture, uint8_t _mip = 0); + void allocSrv(D3D12_GPU_DESCRIPTOR_HANDLE& _gpuHandle, ID3D12Resource* _resource, const D3D12_SHADER_RESOURCE_VIEW_DESC& _desc); + void allocUav(D3D12_GPU_DESCRIPTOR_HANDLE& _gpuHandle, struct BufferD3D12& _buffer); + void allocUav(D3D12_GPU_DESCRIPTOR_HANDLE& _gpuHandle, ID3D12Resource* _resource, const D3D12_UNORDERED_ACCESS_VIEW_DESC& _desc); void allocSrvArray(D3D12_GPU_DESCRIPTOR_HANDLE& _gpuHandle, struct TextureD3D12& _texture, uint32_t _numSlices); void allocUavArray(D3D12_GPU_DESCRIPTOR_HANDLE& _gpuHandle, struct TextureD3D12& _texture, uint8_t _mip, uint32_t _numSlices); @@ -325,6 +329,8 @@ namespace bgfx { namespace d3d12 uint8_t m_numPredefined; }; + struct VideoDecoderD3D12; + struct TextureD3D12 { enum Enum @@ -341,6 +347,7 @@ namespace bgfx { namespace d3d12 , m_directAccessPtr(NULL) , m_state(D3D12_RESOURCE_STATE_COMMON) , m_numMips(0) + , m_videoDecoder(NULL) { bx::memSet(&m_srvd, 0, sizeof(m_srvd) ); bx::memSet(&m_uavd, 0, sizeof(m_uavd) ); @@ -369,6 +376,7 @@ namespace bgfx { namespace d3d12 uint8_t m_requestedFormat; uint8_t m_textureFormat; uint8_t m_numMips; + VideoDecoderD3D12* m_videoDecoder; }; struct FrameBufferD3D12 diff --git a/src/renderer_mtl.cpp b/src/renderer_mtl.cpp index 1f8bb1daf..dbbe8ca2c 100644 --- a/src/renderer_mtl.cpp +++ b/src/renderer_mtl.cpp @@ -13,6 +13,7 @@ #include #include "renderer_mtl.h" +#include "video_mtl.h" #include "renderer.h" #include @@ -767,9 +768,6 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames #define SHADER_FUNCTION_NAME "xlatMtlMain" #define SHADER_UNIFORM_NAME "_mtl_u" - struct RendererContextMtl; - static RendererContextMtl* s_renderMtl; - struct ChunkedScratchBufferOffset { MTL::Buffer* buffer; @@ -792,6 +790,9 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames uint32_t currentFrameInFlight() const; }; + struct RendererContextMtl; + static RendererContextMtl* s_renderMtl; + struct RendererContextMtl : public RendererContextI { RendererContextMtl() @@ -1053,6 +1054,11 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames } } + if (_init.videoDecode) + { + initVideoDecoder(); + } + for (uint32_t ii = 1, last = 0; ii < BX_COUNTOF(s_msaa); ++ii) { const int32_t sampleCount = 1<getComputePipelineState(_handle); + } + + void videoEndEncoding(RendererContextMtl* _renderer) + { + _renderer->endEncoding(); + } + + MTL::CommandBuffer* videoEnsureCommandBuffer(RendererContextMtl* _renderer) + { + MTL::CommandBuffer* commandBuffer = _renderer->m_commandBuffer; + if (NULL == commandBuffer) + { + commandBuffer = _renderer->m_cmd.alloc(); + _renderer->m_commandBuffer = commandBuffer; + } + return commandBuffer; + } + + MTL::SamplerState* videoGetSamplerState(RendererContextMtl* _renderer, uint64_t _samplerFlags) + { + return _renderer->getSamplerState(_samplerFlags); + } + void ChunkedScratchBufferMtl::createUniform(uint32_t _chunkSize, uint32_t _numChunks) { create(_chunkSize, _numChunks, 256); @@ -3512,6 +3545,8 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames const uint32_t msaaQuality = bx::satSub(uint32_t( (_flags&BGFX_TEXTURE_RT_MSAA_MASK) >> BGFX_TEXTURE_RT_MSAA_SHIFT ), 1u); const int32_t sampleCount = s_msaa[msaaQuality]; + const bool isVideoDecodeDst = 0 != (_flags & BGFX_TEXTURE_INTERNAL_VIDEO_DECODE_DST); + const TextureFormatInfo& tfi = s_textureFormat[m_textureFormat]; MTL::PixelFormat format = MTL::PixelFormatInvalid; @@ -3554,8 +3589,9 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames MTL::TextureUsage usage = 0 | MTL::TextureUsageShaderRead - | (computeWrite ? MTL::TextureUsageShaderWrite : 0) - | (renderTarget ? MTL::TextureUsageRenderTarget : 0) + | (computeWrite ? MTL::TextureUsageShaderWrite : 0) + | (isVideoDecodeDst? MTL::TextureUsageShaderWrite : 0) + | (renderTarget ? MTL::TextureUsageRenderTarget : 0) ; desc->setUsage(usage); @@ -3592,6 +3628,29 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames m_ptrStencil = s_renderMtl->m_device->newTexture(desc); } + if (isVideoDecodeDst) + { + BX_ASSERT(imageContainer.m_size >= sizeof(VideoDecoderInit) + , "VIDEO_DECODE_DST texture: Memory too small for VideoDecoderInit (got %d, want %zu)." + , imageContainer.m_size + , sizeof(VideoDecoderInit) + ); + const VideoDecoderInit* init = (const VideoDecoderInit*)imageContainer.m_data; + BX_ASSERT(kVideoDecoderInitMagic == init->magic + , "VIDEO_DECODE_DST texture: bad VideoDecoderInit magic (0x%08x)." + , init->magic + ); + + m_videoDecoder = videoDecoderCreate(*init, s_renderMtl, s_renderMtl->m_device, uint16_t(ti.width), uint16_t(ti.height) ); + if (NULL == m_videoDecoder) + { + BX_TRACE("Failed to initialize hardware video decoder."); + } + + MTL_RELEASE(desc, 0); + return; + } + uint8_t* temp = NULL; if (convert) { @@ -3680,6 +3739,9 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames void TextureMtl::destroy() { + videoDecoderDestroy(m_videoDecoder); + m_videoDecoder = NULL; + if (0 == (m_flags & BGFX_SAMPLER_INTERNAL_SHARED) ) { MTL_RELEASE_W(m_ptr, 0); @@ -3710,6 +3772,25 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames void TextureMtl::update(uint8_t _side, uint8_t _mip, const Rect& _rect, uint16_t _z, uint16_t _depth, uint16_t _pitch, const Memory* _mem) { + if (0 != (m_flags & BGFX_TEXTURE_INTERNAL_VIDEO_DECODE_DST) ) + { + BX_ASSERT(_mem->size >= sizeof(VideoDecoderFrame) + , "VIDEO_DECODE_DST update: Memory too small for VideoDecoderFrame (got %d, want %zu)." + , _mem->size + , sizeof(VideoDecoderFrame) + ); + const VideoDecoderFrame* frame = (const VideoDecoderFrame*)_mem->data; + BX_ASSERT(kVideoDecoderFrameMagic == frame->magic + , "VIDEO_DECODE_DST update: bad VideoDecoderFrame magic (0x%08x)." + , frame->magic + ); + if (NULL != m_videoDecoder) + { + videoDecoderDecode(m_videoDecoder, *frame, m_ptr); + } + return; + } + const uint32_t bpp = bimg::getBitsPerPixel(bimg::TextureFormat::Enum(m_textureFormat) ); uint32_t rectpitch = _rect.m_width*bpp/8; if (bimg::isCompressed(bimg::TextureFormat::Enum(m_textureFormat) ) ) diff --git a/src/renderer_mtl.h b/src/renderer_mtl.h index f2f43b28d..d9c003117 100644 --- a/src/renderer_mtl.h +++ b/src/renderer_mtl.h @@ -400,11 +400,13 @@ namespace bgfx { namespace mtl MTL::ComputePipelineState* m_cps; }; - void release(PipelineStateMtl* _ptr) + inline void release(PipelineStateMtl* _ptr) { bx::deleteObject(g_allocator, _ptr); } + struct VideoDecoderMtl; + struct TextureMtl { enum Enum @@ -419,6 +421,7 @@ namespace bgfx { namespace mtl , m_ptrMsaa(NULL) , m_ptrStencil(NULL) , m_sampler(NULL) + , m_videoDecoder(NULL) , m_flags(0) , m_width(0) , m_height(0) @@ -466,6 +469,7 @@ namespace bgfx { namespace mtl MTL::Texture* m_ptrMips[14]; stl::unordered_map m_ptrViews; MTL::SamplerState* m_sampler; + VideoDecoderMtl* m_videoDecoder; uint64_t m_flags; uint32_t m_width; uint32_t m_height; diff --git a/src/renderer_vk.cpp b/src/renderer_vk.cpp index 138b494c4..fee9e316f 100644 --- a/src/renderer_vk.cpp +++ b/src/renderer_vk.cpp @@ -8,6 +8,7 @@ #if BGFX_CONFIG_RENDERER_VULKAN # include # include "renderer_vk.h" +# include "video_vk.h" #if BX_PLATFORM_OSX # import @@ -386,6 +387,11 @@ VK_IMPORT_DEVICE KHR_fragment_shading_rate, KHR_get_physical_device_properties2, KHR_get_surface_capabilities2, + KHR_video_queue, + KHR_video_decode_queue, + KHR_video_decode_h264, + KHR_video_decode_h265, + KHR_video_decode_av1, # if BX_PLATFORM_ANDROID KHR_android_surface, @@ -429,6 +435,11 @@ VK_IMPORT_DEVICE { "VK_KHR_fragment_shading_rate", 1, false, false, true, Layer::Count }, { "VK_KHR_get_physical_device_properties2", 1, false, false, true, Layer::Count }, { "VK_KHR_get_surface_capabilities2", 1, false, false, true, Layer::Count }, + { "VK_KHR_video_queue", 1, false, false, true, Layer::Count }, + { "VK_KHR_video_decode_queue", 1, false, false, true, Layer::Count }, + { "VK_KHR_video_decode_h264", 1, false, false, true, Layer::Count }, + { "VK_KHR_video_decode_h265", 1, false, false, true, Layer::Count }, + { "VK_KHR_video_decode_av1", 1, false, false, true, Layer::Count }, # if BX_PLATFORM_ANDROID { VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, 1, false, false, true, Layer::Count }, # elif BX_PLATFORM_LINUX @@ -1022,10 +1033,10 @@ VK_IMPORT_DEVICE , VkImageAspectFlags _aspectMask , VkImageLayout _oldLayout , VkImageLayout _newLayout - , uint32_t _baseMipLevel = 0 - , uint32_t _levelCount = VK_REMAINING_MIP_LEVELS - , uint32_t _baseArrayLayer = 0 - , uint32_t _layerCount = VK_REMAINING_ARRAY_LAYERS + , uint32_t _baseMipLevel + , uint32_t _levelCount + , uint32_t _baseArrayLayer + , uint32_t _layerCount ) { if (_newLayout == VK_IMAGE_LAYOUT_UNDEFINED @@ -1236,6 +1247,8 @@ VK_IMPORT_DEVICE bool imported = true; VkResult result; m_globalQueueFamily = UINT32_MAX; + m_videoDecodeQueueFamily = UINT32_MAX; + m_videoDecodeQueue = VK_NULL_HANDLE; if (_init.debug || _init.profile) @@ -1934,6 +1947,12 @@ VK_IMPORT_INSTANCE { m_globalQueueFamily = ii; } + + if (UINT32_MAX == m_videoDecodeQueueFamily + && 0 != (qfp.queueFlags & VK_QUEUE_VIDEO_DECODE_BIT_KHR) ) + { + m_videoDecodeQueueFamily = ii; + } } bx::free(g_allocator, queueFamilyPropertices); @@ -2008,21 +2027,35 @@ VK_IMPORT_INSTANCE BX_TRACE("\t%s", enabledExtension[ii]); } + if (UINT32_MAX == m_videoDecodeQueueFamily) + { + m_videoDecodeQueueFamily = m_globalQueueFamily; + } + const float queuePriorities[] = { 0.0f }; - VkDeviceQueueCreateInfo dcqi; - dcqi.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; - dcqi.pNext = NULL; - dcqi.flags = 0; - dcqi.queueFamilyIndex = m_globalQueueFamily; - dcqi.queueCount = BX_COUNTOF(queuePriorities); - dcqi.pQueuePriorities = queuePriorities; + VkDeviceQueueCreateInfo dcqi[2]; + bx::memSet(dcqi, 0, sizeof(dcqi) ); + dcqi[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + dcqi[0].pNext = NULL; + dcqi[0].flags = 0; + dcqi[0].queueFamilyIndex = m_globalQueueFamily; + dcqi[0].queueCount = 1; + dcqi[0].pQueuePriorities = queuePriorities; + + uint32_t numQueueCreateInfos = 1; + if (m_videoDecodeQueueFamily != m_globalQueueFamily) + { + dcqi[1] = dcqi[0]; + dcqi[1].queueFamilyIndex = m_videoDecodeQueueFamily; + numQueueCreateInfos = 2; + } VkDeviceCreateInfo dci; dci.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; dci.pNext = nextFeatures; dci.flags = 0; - dci.queueCreateInfoCount = 1; - dci.pQueueCreateInfos = &dcqi; + dci.queueCreateInfoCount = numQueueCreateInfos; + dci.pQueueCreateInfos = dcqi; dci.enabledLayerCount = numEnabledLayers; dci.ppEnabledLayerNames = enabledLayer; dci.enabledExtensionCount = numEnabledExtensions; @@ -2060,6 +2093,22 @@ VK_IMPORT_DEVICE } vkGetDeviceQueue(m_device, m_globalQueueFamily, 0, &m_globalQueue); + vkGetDeviceQueue(m_device, m_videoDecodeQueueFamily, 0, &m_videoDecodeQueue); + + if (_init.videoDecode) + { + initVideoDecoder(this + , { + .device = m_device, + .allocCb = m_allocatorCb, + .physicalDevice = m_physicalDevice, + .videoDecodeQueueFamily = m_videoDecodeQueueFamily, + .globalQueueFamily = m_globalQueueFamily, + .videoDecodeQueue = m_videoDecodeQueue, + .globalQueue = m_globalQueue, + .vendorId = uint16_t(m_deviceProperties.vendorID), + }); + } { m_maxFrameLatency = _init.resolution.maxFrameLatency == 0 @@ -2315,6 +2364,7 @@ VK_IMPORT_DEVICE return false; } + void shutdown() { VK_CHECK(vkDeviceWaitIdle(m_device) ); @@ -4811,6 +4861,8 @@ VK_IMPORT_DEVICE VkDevice m_externalDevice; uint32_t m_globalQueueFamily; VkQueue m_globalQueue; + uint32_t m_videoDecodeQueueFamily; + VkQueue m_videoDecodeQueue; VkDescriptorPool m_descriptorPool[BGFX_CONFIG_MAX_FRAME_LATENCY]; VkPipelineCache m_pipelineCache; @@ -4860,6 +4912,52 @@ VK_IMPORT_DEVICE static RendererContextVK* s_renderVK; + bool videoIsExtensionSupported(RendererContextVK* _renderer, const char* _name) + { + BX_UNUSED(_renderer); + + for (uint32_t ii = 0; ii < BX_COUNTOF(s_extension); ++ii) + { + const Extension& extension = s_extension[ii]; + if (0 == bx::strCmp(_name, extension.m_name) ) + { + return extension.m_supported; + } + } + + return false; + } + + int32_t videoSelectMemoryType(RendererContextVK* _renderer, uint32_t _typeBits, uint32_t _flags) + { + return _renderer->selectMemoryType(_typeBits, _flags); + } + + VkPipeline videoGetPipeline(RendererContextVK* _renderer, ProgramHandle _handle) + { + return _renderer->getPipeline(_handle); + } + + const ProgramVK& videoGetProgram(RendererContextVK* _renderer, ProgramHandle _handle) + { + return _renderer->m_program[_handle.idx]; + } + + VkCommandBuffer videoGetCommandBuffer(RendererContextVK* _renderer) + { + return _renderer->m_commandBuffer; + } + + VkSampler videoGetSampler(RendererContextVK* _renderer, uint64_t _samplerFlags, VkFormat _format) + { + return _renderer->getSampler(uint32_t(_samplerFlags), _format, NULL); + } + + void videoRelease(RendererContextVK* _renderer, VkImageView& _obj) + { + _renderer->release(_obj); + } + RendererContextI* rendererCreate(const Init& _init) { s_renderVK = BX_NEW(g_allocator, RendererContextVK); @@ -6335,7 +6433,6 @@ VK_DESTROY BX_ASSERT(m_numMips <= 1, "Can't create multisample image with mip chain."); } - // create texture and allocate its device memory VkImageCreateInfo ici; ici.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; ici.pNext = NULL; @@ -6364,7 +6461,7 @@ VK_DESTROY ) : 0 ) - | (m_flags & BGFX_TEXTURE_COMPUTE_WRITE ? VK_IMAGE_USAGE_STORAGE_BIT : 0) + | (m_flags & (BGFX_TEXTURE_COMPUTE_WRITE | BGFX_TEXTURE_INTERNAL_VIDEO_DECODE_DST) ? VK_IMAGE_USAGE_STORAGE_BIT : 0) ; ici.format = m_format; ici.samples = m_sampler.Sample; @@ -6427,7 +6524,7 @@ VK_DESTROY } } - m_sampledLayout = m_flags & BGFX_TEXTURE_COMPUTE_WRITE + m_sampledLayout = m_flags & (BGFX_TEXTURE_COMPUTE_WRITE | BGFX_TEXTURE_INTERNAL_VIDEO_DECODE_DST) ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL ; @@ -6508,6 +6605,12 @@ VK_DESTROY ; m_components = s_textureFormat[m_textureFormat].m_mapping; + if (TextureFormat::BGRA8 == m_textureFormat + && 0 != (m_flags & BGFX_TEXTURE_INTERNAL_VIDEO_DECODE_DST) ) + { + m_format = VK_FORMAT_R8G8B8A8_UNORM; + } + const bool convert = m_textureFormat != m_requestedFormat; const uint8_t bpp = bimg::getBitsPerPixel(bimg::TextureFormat::Enum(m_textureFormat) ); @@ -6542,7 +6645,8 @@ VK_DESTROY uint32_t kk = 0; const bool compressed = bimg::isCompressed(bimg::TextureFormat::Enum(m_textureFormat) ); - const bool swizzle = TextureFormat::BGRA8 == m_textureFormat && 0 != (m_flags & BGFX_TEXTURE_COMPUTE_WRITE); + const bool isVideoDecodeDst = 0 != (m_flags & BGFX_TEXTURE_INTERNAL_VIDEO_DECODE_DST); + const bool swizzle = TextureFormat::BGRA8 == m_textureFormat && 0 != (m_flags & (BGFX_TEXTURE_COMPUTE_WRITE | BGFX_TEXTURE_INTERNAL_VIDEO_DECODE_DST) ); const bool writeOnly = 0 != (m_flags & BGFX_TEXTURE_RT_WRITE_ONLY); const bool computeWrite = 0 != (m_flags & BGFX_TEXTURE_COMPUTE_WRITE); @@ -6569,6 +6673,27 @@ VK_DESTROY VK_CHECK(createImages(_commandBuffer, _external) ); + if (isVideoDecodeDst) + { + BX_ASSERT(imageContainer.m_size >= sizeof(VideoDecoderInit) + , "VIDEO_DECODE_DST texture: Memory too small for VideoDecoderInit (got %d, want %zu)." + , imageContainer.m_size + , sizeof(VideoDecoderInit) + ); + const VideoDecoderInit* init = (const VideoDecoderInit*)imageContainer.m_data; + BX_ASSERT(kVideoDecoderInitMagic == init->magic + , "VIDEO_DECODE_DST texture: bad VideoDecoderInit magic (0x%08x)." + , init->magic + ); + m_videoDecoder = videoDecoderCreate(*init, s_renderVK, uint16_t(ti.width), uint16_t(ti.height) ); + if (NULL == m_videoDecoder) + { + BX_TRACE("Failed to initialize Vulkan hardware video decoder for texture."); + } + m_readback.create(m_textureImage, m_width, m_height, TextureFormat::Enum(m_textureFormat) ); + return m_directAccessPtr; + } + // decode images struct ImageInfo { @@ -6773,6 +6898,9 @@ VK_DESTROY { BGFX_PROFILER_SCOPE("TextureVK::destroy", kColorResource); + videoDecoderDestroy(m_videoDecoder); + m_videoDecoder = NULL; + m_readback.destroy(); const bool external = 0 != (m_flags & BGFX_SAMPLER_INTERNAL_SHARED); @@ -6806,6 +6934,25 @@ VK_DESTROY { BGFX_PROFILER_SCOPE("TextureVK::update", kColorResource); + if (0 != (m_flags & BGFX_TEXTURE_INTERNAL_VIDEO_DECODE_DST) ) + { + BX_ASSERT(_mem->size >= sizeof(VideoDecoderFrame) + , "VIDEO_DECODE_DST update: Memory too small for VideoDecoderFrame (got %d, want %zu)." + , _mem->size + , sizeof(VideoDecoderFrame) + ); + const VideoDecoderFrame* frame = (const VideoDecoderFrame*)_mem->data; + BX_ASSERT(kVideoDecoderFrameMagic == frame->magic + , "VIDEO_DECODE_DST update: bad VideoDecoderFrame magic (0x%08x)." + , frame->magic + ); + if (NULL != m_videoDecoder) + { + videoDecoderDecode(m_videoDecoder, *frame, *this); + } + return; + } + const uint32_t bpp = bimg::getBitsPerPixel(bimg::TextureFormat::Enum(m_textureFormat) ); const bimg::ImageBlockInfo& blockInfo = bimg::getBlockInfo(bimg::TextureFormat::Enum(m_textureFormat) ); const bool compressed = bimg::isCompressed(bimg::TextureFormat::Enum(m_textureFormat) ); @@ -7407,6 +7554,13 @@ VK_DESTROY VkSurfaceCapabilitiesKHR surfaceCapabilities = {}; VkResult result = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, m_surface, &surfaceCapabilities); + // During window destruction min/maxImageExtent is populated by garbage/large values. But result is VK_SUCCESS. + const uint32_t surfaceExtentSanityMax = g_caps.limits.maxTextureSize; + const bool surfaceCapsSane = true + && surfaceCapabilities.maxImageExtent.width <= surfaceExtentSanityMax + && surfaceCapabilities.maxImageExtent.height <= surfaceExtentSanityMax + ; + const uint32_t width = bx::clamp( m_resolution.width , surfaceCapabilities.minImageExtent.width @@ -7422,7 +7576,8 @@ VK_DESTROY // on some platforms this happens when minimized if (width == 0 || height == 0 - || VK_SUCCESS != result) + || VK_SUCCESS != result + || !surfaceCapsSane) { m_sci.oldSwapchain = VK_NULL_HANDLE; s_renderVK->kick(true); @@ -7869,9 +8024,6 @@ VK_DESTROY sci.pNext = NULL; sci.flags = 0; - // We will make a fully filled pool of semaphores and cycle through those. - // This is to make sure we have enough, even in the case where there are - // more frames in flight than images on the swapchain. for (uint32_t ii = 0; ii < kMaxBackBuffers; ++ii) { if (VK_SUCCESS != vkCreateSemaphore(device, &sci, allocatorCb, &m_presentDoneSemaphore[ii]) @@ -8160,7 +8312,6 @@ VK_DESTROY const VkDevice device = s_renderVK->m_device; m_lastImageAcquiredSemaphore = m_presentDoneSemaphore[m_currentSemaphore]; - m_lastImageRenderedSemaphore = m_renderDoneSemaphore[m_currentSemaphore]; m_currentSemaphore = (m_currentSemaphore + 1) % kMaxBackBuffers; VkResult result; @@ -8218,6 +8369,8 @@ VK_DESTROY transitionImage(_commandBuffer); + m_lastImageRenderedSemaphore = m_renderDoneSemaphore[m_backBufferColorIdx]; + m_needPresent = true; } diff --git a/src/renderer_vk.h b/src/renderer_vk.h index 8c798eab8..1b7a1bf9f 100644 --- a/src/renderer_vk.h +++ b/src/renderer_vk.h @@ -98,6 +98,7 @@ VK_IMPORT_INSTANCE_FUNC(false, vkGetPhysicalDeviceImageFormatProperties); \ VK_IMPORT_INSTANCE_FUNC(false, vkGetPhysicalDeviceMemoryProperties); \ VK_IMPORT_INSTANCE_FUNC(false, vkGetPhysicalDeviceQueueFamilyProperties); \ + VK_IMPORT_INSTANCE_FUNC(true, vkGetPhysicalDeviceQueueFamilyProperties2KHR); \ VK_IMPORT_INSTANCE_FUNC(false, vkCreateDevice); \ VK_IMPORT_INSTANCE_FUNC(false, vkDestroyDevice); \ /* VK_KHR_surface */ \ @@ -115,6 +116,9 @@ VK_IMPORT_INSTANCE_FUNC(true, vkDestroyDebugReportCallbackEXT); \ /* VK_KHR_fragment_shading_rate */ \ VK_IMPORT_INSTANCE_FUNC(true, vkGetPhysicalDeviceFragmentShadingRatesKHR); \ + /* VK_KHR_video_queue */ \ + VK_IMPORT_INSTANCE_FUNC(true, vkGetPhysicalDeviceVideoCapabilitiesKHR); \ + VK_IMPORT_INSTANCE_FUNC(true, vkGetPhysicalDeviceVideoFormatPropertiesKHR);\ VK_IMPORT_INSTANCE_PLATFORM #define VK_IMPORT_DEVICE \ @@ -226,6 +230,19 @@ VK_IMPORT_DEVICE_FUNC(true, vkCmdDrawIndexedIndirectCountKHR); \ /* VK_KHR_fragment_shading_rate */ \ VK_IMPORT_DEVICE_FUNC(true, vkCmdSetFragmentShadingRateKHR); \ + /* VK_KHR_video_queue / VK_KHR_video_decode_queue */ \ + VK_IMPORT_DEVICE_FUNC(true, vkCmdBeginVideoCodingKHR); \ + VK_IMPORT_DEVICE_FUNC(true, vkCmdEndVideoCodingKHR); \ + VK_IMPORT_DEVICE_FUNC(true, vkCmdControlVideoCodingKHR); \ + VK_IMPORT_DEVICE_FUNC(true, vkCreateVideoSessionKHR); \ + VK_IMPORT_DEVICE_FUNC(true, vkDestroyVideoSessionKHR); \ + VK_IMPORT_DEVICE_FUNC(true, vkGetVideoSessionMemoryRequirementsKHR); \ + VK_IMPORT_DEVICE_FUNC(true, vkBindVideoSessionMemoryKHR); \ + VK_IMPORT_DEVICE_FUNC(true, vkCreateVideoSessionParametersKHR); \ + VK_IMPORT_DEVICE_FUNC(true, vkDestroyVideoSessionParametersKHR); \ + VK_IMPORT_DEVICE_FUNC(true, vkUpdateVideoSessionParametersKHR); \ + /* VK_KHR_video_decode_queue */ \ + VK_IMPORT_DEVICE_FUNC(true, vkCmdDecodeVideoKHR); \ #define VK_DESTROY \ VK_DESTROY_FUNC(Buffer); \ @@ -305,6 +322,17 @@ namespace bgfx { namespace vk { +#define VK_IMPORT_FUNC(_optional, _func) extern PFN_##_func _func +#define VK_IMPORT_INSTANCE_FUNC VK_IMPORT_FUNC +#define VK_IMPORT_DEVICE_FUNC VK_IMPORT_FUNC +VK_IMPORT +VK_IMPORT_INSTANCE +VK_IMPORT_DEVICE +#undef VK_IMPORT_DEVICE_FUNC +#undef VK_IMPORT_INSTANCE_FUNC +#undef VK_IMPORT_FUNC + + #define VK_DESTROY_FUNC(_name) \ struct Vk##_name \ { \ @@ -325,6 +353,18 @@ VK_DESTROY_FUNC(SurfaceKHR); VK_DESTROY_FUNC(DescriptorSet); #undef VK_DESTROY_FUNC + void setImageMemoryBarrier( + VkCommandBuffer _commandBuffer + , VkImage _image + , VkImageAspectFlags _aspectMask + , VkImageLayout _oldLayout + , VkImageLayout _newLayout + , uint32_t _baseMipLevel = 0 + , uint32_t _levelCount = VK_REMAINING_MIP_LEVELS + , uint32_t _baseArrayLayer = 0 + , uint32_t _layerCount = VK_REMAINING_ARRAY_LAYERS + ); + template void release(Ty) { @@ -653,6 +693,8 @@ VK_DESTROY_FUNC(DescriptorSet); TextureFormat::Enum m_format; }; + struct VideoDecoderVK; + struct TextureVK { TextureVK() @@ -667,6 +709,7 @@ VK_DESTROY_FUNC(DescriptorSet); , m_singleMsaaImage(VK_NULL_HANDLE) , m_singleMsaaDeviceMem() , m_currentSingleMsaaImageLayout(VK_IMAGE_LAYOUT_UNDEFINED) + , m_videoDecoder(NULL) { } @@ -713,6 +756,7 @@ VK_DESTROY_FUNC(DescriptorSet); VkImageLayout m_sampledLayout; ReadbackVK m_readback; + VideoDecoderVK* m_videoDecoder; private: VkResult createImages(VkCommandBuffer _commandBuffer, uint64_t _external = 0); diff --git a/src/video.h b/src/video.h new file mode 100644 index 000000000..42e3b87dc --- /dev/null +++ b/src/video.h @@ -0,0 +1,1175 @@ +/* + * Copyright 2011-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE + */ + +#ifndef BGFX_VIDEO_H_HEADER_GUARD +#define BGFX_VIDEO_H_HEADER_GUARD + +#include + +namespace bgfx +{ + inline uint32_t stripEmulationPrevention(uint8_t* _dst, const uint8_t* _src, uint32_t _size) + { + uint32_t dstIdx = 0; + uint32_t zeros = 0; + + for (uint32_t ii = 0; ii < _size; ++ii) + { + const uint8_t b = _src[ii]; + + if (zeros >= 2 && 0x03 == b) + { + zeros = 0; + continue; + } + + _dst[dstIdx++] = b; + zeros = (0 == b) ? zeros + 1 : 0; + } + + return dstIdx; + } + + inline uint32_t nalUnitSize(const uint8_t* _data, uint32_t _size) + { + if (_size < 3) + { + return _size; + } + + for (uint32_t ii = 0, end = _size - 2; ii < end; ++ii) + { + if (0 == _data[ii] && 0 == _data[ii+1] && 1 == _data[ii+2]) + { + uint32_t startSize = ii; + + if (startSize > 0 && 0 == _data[startSize-1]) + { + --startSize; + } + + return startSize; + } + } + + return _size; + } + + inline int32_t computePoc( + int32_t& _prevPocMsb + , int32_t& _prevPocLsb + , const h264::SPS& _sps + , const h264::SliceHeader& _sh + , const h264::NALHeader& _nal + , bool _isIdr + ) + { + if (2 == _sps.pic_order_cnt_type) + { + const int32_t maxFrameNum = 1 << (_sps.log2_max_frame_num_minus4 + 4); + int32_t frameNumOffset; + int32_t poc; + + if (_isIdr) + { + frameNumOffset = 0; + poc = 0; + } + else + { + if (_prevPocLsb > _sh.frame_num) + { + frameNumOffset = _prevPocMsb + maxFrameNum; + } + else + { + frameNumOffset = _prevPocMsb; + } + + poc = 2 * (frameNumOffset + _sh.frame_num); + + if (0 == _nal.idc) + { + poc -= 1; + } + } + + _prevPocMsb = frameNumOffset; + _prevPocLsb = _sh.frame_num; + + return poc; + } + + if (0 != _sps.pic_order_cnt_type) + { + return _sh.frame_num * 2; + } + + const int32_t maxPocLsb = 1 << (_sps.log2_max_pic_order_cnt_lsb_minus4 + 4); + int32_t prevPocMsb = _prevPocMsb; + int32_t prevPocLsb = _prevPocLsb; + + if (_isIdr) + { + prevPocMsb = 0; + prevPocLsb = 0; + } + + const int32_t pocLsb = _sh.pic_order_cnt_lsb; + int32_t pocMsb; + + if ( (pocLsb < prevPocLsb) && ( (prevPocLsb - pocLsb) >= (maxPocLsb / 2) ) ) + { + pocMsb = prevPocMsb + maxPocLsb; + } + else if ( (pocLsb > prevPocLsb) && ( (pocLsb - prevPocLsb) > (maxPocLsb / 2) ) ) + { + pocMsb = prevPocMsb - maxPocLsb; + } + else + { + pocMsb = prevPocMsb; + } + + const int32_t poc = pocMsb + pocLsb; + + if (0 != _nal.idc) + { + _prevPocMsb = pocMsb; + _prevPocLsb = pocLsb; + } + + return poc; + } + + inline bool parseParameterSets( + h264::SPS* _spsArray + , uint32_t _spsArrayLen + , h264::PPS* _ppsArray + , uint32_t _ppsArrayLen + , h264::SPS& _spsActive + , h264::PPS& _ppsActive + , const uint8_t* _data + , uint32_t _size + , bool* _spsValid = NULL + , bool* _ppsValid = NULL + ) + { + h264::Bitstream bs; + bs.init(_data, _size); + + bool sawSps = false; + bool sawPps = false; + + while (h264::find_next_nal(&bs) ) + { + const uint8_t* nalStart = bs.p; + const uint32_t nalRemaining = uint32_t(bs.end - bs.p); + const uint32_t nalSize = nalUnitSize(nalStart, nalRemaining); + + uint8_t rbsp[4096]; + const uint32_t rbspSize = nalSize < sizeof(rbsp) + ? stripEmulationPrevention(rbsp, nalStart, nalSize) + : nalSize + ; + const uint8_t* parseData = nalSize < sizeof(rbsp) ? rbsp : nalStart; + const uint32_t parseSize = nalSize < sizeof(rbsp) ? rbspSize : nalSize; + + h264::Bitstream nalBs; + nalBs.init(parseData, parseSize); + + h264::NALHeader nal = {}; + if (!h264::read_nal_header(&nal, &nalBs) ) + { + break; + } + + if (h264::NAL_UNIT_TYPE_SPS == nal.type) + { + h264::SPS sps = {}; + h264::read_sps(&sps, &nalBs); + + if (sps.seq_parameter_set_id < (int)_spsArrayLen) + { + _spsArray[sps.seq_parameter_set_id] = sps; + + if (NULL != _spsValid) + { + _spsValid[sps.seq_parameter_set_id] = true; + } + + _spsActive = sps; + sawSps = true; + } + } + else if (h264::NAL_UNIT_TYPE_PPS == nal.type) + { + h264::PPS pps = {}; + h264::read_pps(&pps, &nalBs); + + if (pps.pic_parameter_set_id < (int)_ppsArrayLen) + { + _ppsArray[pps.pic_parameter_set_id] = pps; + + if (NULL != _ppsValid) + { + _ppsValid[pps.pic_parameter_set_id] = true; + } + + _ppsActive = pps; + sawPps = true; + } + } + + bs.p = nalStart + nalSize; + bs.bits_left = 8; + } + + return sawSps && sawPps; + } + + inline bool peekAuIsIdr(const uint8_t* _data, uint32_t _size) + { + h264::Bitstream bs; + bs.init(_data, _size); + + while (h264::find_next_nal(&bs) ) + { + if (bs.p < bs.end) + { + const uint8_t nalType = uint8_t(*bs.p) & 0x1F; + if (5 == nalType) + { + return true; + } + } + + const uint32_t nalRemaining = uint32_t(bs.end - bs.p); + const uint32_t nalSize = nalUnitSize(bs.p, nalRemaining); + bs.p += nalSize; + bs.bits_left = 8; + } + + return false; + } + + inline uint32_t findFirstSliceNalOffset(const uint8_t* _data, uint32_t _size) + { + h264::Bitstream bs; + bs.init(_data, _size); + + while (h264::find_next_nal(&bs) ) + { + if (bs.p < bs.end) + { + const uint8_t nalType = uint8_t(*bs.p) & 0x1F; + if (h264::NAL_UNIT_TYPE_CODED_SLICE_NON_IDR == nalType + || h264::NAL_UNIT_TYPE_CODED_SLICE_IDR == nalType) + { + const uint8_t* startCode = bs.p - 3; + if (startCode > _data && 0 == startCode[-1]) + { + --startCode; + } + + return uint32_t(startCode - _data); + } + } + + const uint32_t nalRemaining = uint32_t(bs.end - bs.p); + const uint32_t nalSize = nalUnitSize(bs.p, nalRemaining); + bs.p += nalSize; + bs.bits_left = 8; + } + return 0; + } + + inline uint32_t enumerateSliceNalOffsets( + const uint8_t* _data + , uint32_t _size + , uint32_t* _offsets + , uint32_t _maxOffsets + ) + { + h264::Bitstream bs; + bs.init(_data, _size); + uint32_t count = 0; + + while (h264::find_next_nal(&bs) ) + { + if (bs.p < bs.end) + { + const uint8_t nalType = uint8_t(*bs.p) & 0x1F; + + if (h264::NAL_UNIT_TYPE_CODED_SLICE_NON_IDR == nalType + || h264::NAL_UNIT_TYPE_CODED_SLICE_IDR == nalType) + { + const uint8_t* startCode = bs.p - 3; + + if (startCode > _data && 0 == startCode[-1]) + { + --startCode; + } + + if (count < _maxOffsets) + { + _offsets[count] = uint32_t(startCode - _data); + } + + ++count; + } + } + + const uint32_t nalRemaining = uint32_t(bs.end - bs.p); + const uint32_t nalSize = nalUnitSize(bs.p, nalRemaining); + bs.p += nalSize; + bs.bits_left = 8; + } + return count; + } + + inline bool parseSliceFromAccessUnit( + const h264::SPS* _spsArray + , h264::PPS* _ppsArray + , const uint8_t* _data + , uint32_t _size + , h264::NALHeader& _outNal + , h264::SliceHeader& _outSlice + , bool& _outIsIdr + ) + { + h264::Bitstream bs; + bs.init(_data, _size); + + while (h264::find_next_nal(&bs) ) + { + const uint8_t* nalStart = bs.p; + const uint32_t nalRemaining = uint32_t(bs.end - bs.p); + const uint32_t nalSize = nalUnitSize(nalStart, nalRemaining); + + uint8_t rbsp[1024]; + const uint32_t stripIn = nalSize < uint32_t(sizeof(rbsp) ) ? nalSize : uint32_t(sizeof(rbsp) ); + const uint32_t rbspSize = stripEmulationPrevention(rbsp, nalStart, stripIn); + + h264::Bitstream nalBs; + nalBs.init(rbsp, rbspSize); + + h264::NALHeader nal = {}; + if (!h264::read_nal_header(&nal, &nalBs) ) + { + return false; + } + + if (h264::NAL_UNIT_TYPE_SPS == nal.type) + { + h264::SPS sps = {}; + h264::read_sps(&sps, &nalBs); + + if (sps.seq_parameter_set_id < 32) + { + const_cast(_spsArray)[sps.seq_parameter_set_id] = sps; + } + + bs.p = nalStart + nalSize; + bs.bits_left = 8; + + continue; + } + + if (h264::NAL_UNIT_TYPE_PPS == nal.type) + { + h264::PPS pps = {}; + h264::read_pps(&pps, &nalBs); + + if (pps.pic_parameter_set_id < 256) + { + _ppsArray[pps.pic_parameter_set_id] = pps; + } + + bs.p = nalStart + nalSize; + bs.bits_left = 8; + + continue; + } + + if (h264::NAL_UNIT_TYPE_CODED_SLICE_NON_IDR == nal.type + || h264::NAL_UNIT_TYPE_CODED_SLICE_IDR == nal.type) + { + h264::SliceHeader sh = {}; + h264::read_slice_header(&sh, &nal, _ppsArray, const_cast(_spsArray), &nalBs); + + _outNal = nal; + _outSlice = sh; + _outIsIdr = (h264::NAL_UNIT_TYPE_CODED_SLICE_IDR == nal.type); + + return true; + } + + bs.p = nalStart + nalSize; + bs.bits_left = 8; + } + + return false; + } + + inline void buildEffectiveScalingLists( + const h264::SPS& _sps + , const h264::PPS& _pps + , uint8_t (&_out4x4)[6][16] + , uint8_t (&_out8x8)[2][64] + ) + { + // H.264 default scaling lists in zig-zag scan order (table 7-3 + // for 4x4, table 7-4 for 8x8 with the 8x8 zig-zag scan applied). + static const uint8_t kDefault4x4Intra[16] = + { + 6,13,13,20, 20,20,28,28, 28,28,32,32, 32,37,37,42, + }; + + static const uint8_t kDefault4x4Inter[16] = + { + 10,14,14,20, 20,20,24,24, 24,24,27,27, 27,30,30,34, + }; + + static const uint8_t kDefault8x8Intra[64] = + { + 6,10,10,13, 11,13,16,16, 16,16,18,18, 18,18,18,23, + 23,23,23,23, 23,25,25,25, 25,25,25,25, 27,27,27,27, + 27,27,27,27, 29,29,29,29, 29,29,29,31, 31,31,31,31, + 31,33,33,33, 33,33,36,36, 36,36,38,38, 38,40,40,42, + }; + + static const uint8_t kDefault8x8Inter[64] = + { + 9,13,13,15, 13,15,17,17, 17,17,19,19, 19,19,19,21, + 21,21,21,21, 21,22,22,22, 22,22,22,22, 24,24,24,24, + 24,24,24,24, 25,25,25,25, 25,25,25,27, 27,27,27,27, + 27,28,28,28, 28,28,30,30, 30,30,32,32, 32,33,33,35, + }; + + uint8_t sps4[6][16] = {}; + uint8_t sps8[2][64] = {}; + + const bool spsHas = !!_sps.seq_scaling_matrix_present_flag; + + auto copyDefault4 = [](uint8_t* _dst, bool _intra) + { + const uint8_t* src = _intra ? kDefault4x4Intra : kDefault4x4Inter; + for (uint32_t k = 0; k < 16; ++k) { _dst[k] = src[k]; } + }; + + auto copyDefault8 = [](uint8_t* _dst, bool _intra) + { + const uint8_t* src = _intra ? kDefault8x8Intra : kDefault8x8Inter; + for (uint32_t k = 0; k < 64; ++k) { _dst[k] = src[k]; } + }; + + auto copy4 = [](uint8_t* _dst, const uint8_t* _src) + { + for (uint32_t k = 0; k < 16; ++k) { _dst[k] = _src[k]; } + }; + + auto copy8 = [](uint8_t* _dst, const uint8_t* _src) + { + for (uint32_t k = 0; k < 64; ++k) { _dst[k] = _src[k]; } + }; + + auto fromInt4 = [](uint8_t* _dst, const int* _src) + { + for (uint32_t k = 0; k < 16; ++k) { _dst[k] = uint8_t(_src[k]); } + }; + + auto fromInt8 = [](uint8_t* _dst, const int* _src) + { + for (uint32_t k = 0; k < 64; ++k) { _dst[k] = uint8_t(_src[k]); } + }; + + auto fillSps4 = [&](uint32_t _i, const uint8_t* _fallback) + { + if (spsHas && _sps.seq_scaling_list_present_flag[_i]) + { + if (_sps.UseDefaultScalingMatrix4x4Flag[_i]) + { + copyDefault4(sps4[_i], _i < 3); + } + else + { + fromInt4(sps4[_i], _sps.ScalingList4x4[_i]); + } + } + else + { + copy4(sps4[_i], _fallback); + } + }; + + fillSps4(0, kDefault4x4Intra); + fillSps4(1, sps4[0]); + fillSps4(2, sps4[1]); + fillSps4(3, kDefault4x4Inter); + fillSps4(4, sps4[3]); + fillSps4(5, sps4[4]); + + auto fillSps8 = [&](uint32_t _i, const uint8_t* _fallback) + { + if (spsHas && _sps.seq_scaling_list_present_flag[6 + _i]) + { + if (_sps.UseDefaultScalingMatrix8x8Flag[_i]) + { + copyDefault8(sps8[_i], 0 == _i); + } + else + { + fromInt8(sps8[_i], _sps.ScalingList8x8[_i]); + } + } + else + { + copy8(sps8[_i], _fallback); + } + }; + + fillSps8(0, kDefault8x8Intra); + fillSps8(1, kDefault8x8Inter); + + const bool ppsHas = !!_pps.pic_scaling_matrix_present_flag; + + auto fillPps4 = [&](uint32_t _i, const uint8_t* _fallback) + { + if (ppsHas && _pps.pic_scaling_list_present_flag[_i]) + { + if (_pps.UseDefaultScalingMatrix4x4Flag[_i]) + { + copyDefault4(_out4x4[_i], _i < 3); + } + else + { + fromInt4(_out4x4[_i], _pps.ScalingList4x4[_i]); + } + } + else + { + copy4(_out4x4[_i], _fallback); + } + }; + + fillPps4(0, sps4[0]); + fillPps4(1, _out4x4[0]); + fillPps4(2, _out4x4[1]); + fillPps4(3, sps4[3]); + fillPps4(4, _out4x4[3]); + fillPps4(5, _out4x4[4]); + + auto fillPps8 = [&](uint32_t _i, const uint8_t* _fallback) + { + if (ppsHas && _pps.pic_scaling_list_present_flag[6 + _i]) + { + if (_pps.UseDefaultScalingMatrix8x8Flag[_i]) + { + copyDefault8(_out8x8[_i], 0 == _i); + } + else + { + fromInt8(_out8x8[_i], _pps.ScalingList8x8[_i]); + } + } + else + { + copy8(_out8x8[_i], _fallback); + } + }; + + fillPps8(0, sps8[0]); + fillPps8(1, sps8[1]); + } + + inline uint32_t applyMmcoUnmarkShortTerm( + uint8_t* _referenceUsage + , uint16_t* _refFrameNums + , uint32_t _numActiveRefs + , int32_t _currFrameNum + , int32_t _maxFrameNum + , const h264::SliceHeader& _sh + ) + { + if (0 == _sh.drpm.adaptive_ref_pic_marking_mode_flag) + { + return _numActiveRefs; + } + + for (uint32_t kk = 0; kk < 64; ++kk) + { + const uint32_t op = _sh.drpm.memory_management_control_operation[kk]; + + if (0 == op) + { + break; + } + + if (1 == op) + { + const int32_t diff = int32_t(_sh.drpm.difference_of_pic_nums_minus1[kk]) + 1; + const int32_t picNumX = _currFrameNum - diff; + + for (uint32_t ii = 0; ii < _numActiveRefs; ++ii) + { + const int32_t fn = int32_t(_refFrameNums[ii]); + const int32_t fnWrap = fn > _currFrameNum ? fn - _maxFrameNum : fn; + + if (fnWrap == picNumX) + { + for (uint32_t jj = ii; jj + 1 < _numActiveRefs; ++jj) + { + _referenceUsage[jj] = _referenceUsage[jj + 1]; + _refFrameNums[jj] = _refFrameNums[jj + 1]; + } + + --_numActiveRefs; + + break; + } + } + } + else if (5 == op) + { + _numActiveRefs = 0; + } + } + + return _numActiveRefs; + } + + static constexpr uint32_t kVideoMaxReorderInFlight = 16; + static constexpr uint32_t kVideoMaxH264SpsCount = 32; + static constexpr uint32_t kVideoMaxH264PpsCount = 256; + static constexpr uint32_t kMaxSlicesPerPicture = 64; + + struct PendingAU + { + stl::vector data; + int64_t ptsUs; + int32_t gopId; + bool isIdr; + + void swap(PendingAU& _other) + { + data.swap(_other.data); + int64_t tmpPts = ptsUs; ptsUs = _other.ptsUs; _other.ptsUs = tmpPts; + int32_t tmpGop = gopId; gopId = _other.gopId; _other.gopId = tmpGop; + bool tmpIdr = isIdr; isIdr = _other.isIdr; _other.isIdr = tmpIdr; + } + }; + + class AuCache + { + public: + AuCache() + : m_head(0) + , m_softCapBytes(0) + , m_totalBytes(0) + , m_currentGopId(-1) + , m_minPts(INT64_MAX) + , m_maxPts(INT64_MIN) + , m_retain(false) + , m_seenAnyIdr(false) + { + } + + void configure(uint32_t _softCapBytes, bool _retain) + { + m_softCapBytes = _softCapBytes; + m_retain = _retain; + } + + void enqueue(const uint8_t* _bitstream, uint32_t _size, int64_t _ptsUs, bool _isIdr) + { + if (_isIdr) + { + ++m_currentGopId; + m_seenAnyIdr = true; + } + + m_items.emplace_back(); + + PendingAU& au = m_items.back(); + au.data.resize(_size); + + bx::memCopy(au.data.data(), _bitstream, _size); + + au.ptsUs = _ptsUs; + au.gopId = m_currentGopId; + au.isIdr = _isIdr; + + m_totalBytes += _size; + + m_minPts = bx::min(m_minPts, _ptsUs); + m_maxPts = bx::max(m_maxPts, _ptsUs); + } + + bool empty() const + { + return m_head >= m_items.size(); + } + + const PendingAU& front() const + { + return m_items[m_head]; + } + + void pop() + { + ++m_head; + } + + void compact() + { + if (m_retain) + { + return; + } + + if (m_head == m_items.size() && m_head > 0) + { + m_items.clear(); + m_head = 0; + m_totalBytes = 0; + m_minPts = INT64_MAX; + m_maxPts = INT64_MIN; + return; + } + + if (m_head > 64) + { + const size_t remaining = m_items.size() - m_head; + size_t freedBytes = 0; + + for (size_t ii = 0; ii < m_head; ++ii) + { + freedBytes += m_items[ii].data.size(); + } + + for (size_t ii = 0; ii < remaining; ++ii) + { + m_items[ii].data.swap(m_items[m_head + ii].data); + m_items[ii].ptsUs = m_items[m_head + ii].ptsUs; + m_items[ii].gopId = m_items[m_head + ii].gopId; + m_items[ii].isIdr = m_items[m_head + ii].isIdr; + } + + m_items.resize(remaining); + m_head = 0; + m_totalBytes -= uint32_t(freedBytes); + } + } + + void clear() + { + m_items.clear(); + m_head = 0; + m_totalBytes = 0; + m_minPts = INT64_MAX; + m_maxPts = INT64_MIN; + m_currentGopId = -1; + m_seenAnyIdr = false; + } + + uint32_t size() const + { + return uint32_t(m_items.size() ); + } + + const PendingAU& at(uint32_t _idx) const + { + return m_items[_idx]; + } + + int32_t findRunHeadForPts(int64_t _ptsUs) const + { + int32_t best = -1; + for (uint32_t ii = 0; ii < uint32_t(m_items.size() ); ++ii) + { + if (m_items[ii].isIdr + && m_items[ii].ptsUs <= _ptsUs) + { + best = int32_t(ii); + } + } + + return best; + } + + void rewindTo(uint32_t _idx) + { + m_head = _idx; + } + + uint32_t readCursor() const + { + return uint32_t(m_head); + } + + bool isRetaining() const { return m_retain; } + bool hasIdr() const { return m_seenAnyIdr; } + int64_t minPts() const { return m_minPts; } + int64_t maxPts() const { return m_maxPts; } + + private: + stl::vector m_items; + size_t m_head; + uint32_t m_softCapBytes; + uint32_t m_totalBytes; + int32_t m_currentGopId; + int64_t m_minPts; + int64_t m_maxPts; + bool m_retain; + bool m_seenAnyIdr; + }; + + typedef AuCache AuQueue; + + inline int64_t applyLoopWrap(int64_t _ptsUs, int64_t _minPts, int64_t _maxPts) + { + if (_minPts >= _maxPts + || _ptsUs <= _maxPts) + { + return _ptsUs; + } + + const int64_t span = _maxPts - _minPts + 1; + const int64_t wrapped = ( (_ptsUs - _minPts) % span) + _minPts; + return wrapped; + } + + template + inline bool reorderPoolCovers( + const ReorderEntryT* _pool + , uint32_t _count + , int64_t _ptsUs + , int64_t _toleranceUs + ) + { + for (uint32_t ii = 0; ii < _count; ++ii) + { + if (-1 != _pool[ii].displayOrder) + { + const int64_t diff = _pool[ii].ptsUs >= _ptsUs + ? _pool[ii].ptsUs - _ptsUs + : _ptsUs - _pool[ii].ptsUs + ; + + if (diff <= _toleranceUs) + { + return true; + } + } + } + return false; + } + + template + inline bool faultInFromCache( + AuCache& _cache + , int64_t _ptsUs + , ResetFn _reset + , DecodeOneFn _decodeOne + , HasSpaceFn _hasReorderSpace + ) + { + if (!_cache.isRetaining() + || !_cache.hasIdr() ) + { + return false; + } + + const int32_t idx = _cache.findRunHeadForPts(_ptsUs); + + if (idx < 0) + { + return false; + } + + _reset(); + _cache.rewindTo(uint32_t(idx) ); + + while (!_cache.empty() + && _hasReorderSpace() ) + { + const PendingAU& au = _cache.front(); + _decodeOne(au.data.data(), uint32_t(au.data.size() ), au.ptsUs); + + const bool reachedTarget = au.ptsUs >= _ptsUs; + _cache.pop(); + + if (reachedTarget) + { + break; + } + } + + return true; + } + + template + inline bool hasReorderSpace(const ReorderEntryT* _pool, uint32_t _count) + { + for (uint32_t ii = 0; ii < _count; ++ii) + { + if (-1 == _pool[ii].displayOrder) + { + return true; + } + } + + return false; + } + + template + inline uint32_t pickReorderSlotOrEvictOldest(const ReorderEntryT* _pool, uint32_t _count) + { + for (uint32_t ii = 0; ii < _count; ++ii) + { + if (-1 == _pool[ii].displayOrder) + { + return ii; + } + } + + uint32_t evict = 0; + + for (uint32_t ii = 1; ii < _count; ++ii) + { + if (_pool[ii].ptsUs < _pool[evict].ptsUs) + { + evict = ii; + } + } + + return evict; + } + + template + inline uint32_t pickFreeDpbSlot( + uint32_t _nextSlot + , uint32_t _numDpbSlots + , const uint8_t* _referenceUsage + , uint32_t _numActiveRefs + , IsPinnedFn _isPinned + ) + { + uint32_t pick = _nextSlot; + for (uint32_t attempt = 0; attempt < _numDpbSlots; ++attempt) + { + bool inUse = false; + + for (uint32_t ii = 0; ii < _numActiveRefs && !inUse; ++ii) + { + if (_referenceUsage[ii] == pick) + { + inUse = true; + } + } + + if (!inUse && _isPinned(pick) ) + { + inUse = true; + } + + if (!inUse) + { + return pick; + } + pick = (pick + 1) % _numDpbSlots; + } + return UINT32_MAX; + } + + inline uint32_t appendShortTermRef( + uint8_t* _referenceUsage + , uint32_t _numActiveRefs + , uint32_t _currentSlot + , uint32_t _maxRefs + ) + { + if (0 == _maxRefs) + { + return _numActiveRefs; + } + + if (_numActiveRefs < _maxRefs) + { + _referenceUsage[_numActiveRefs] = uint8_t(_currentSlot); + return _numActiveRefs + 1; + } + + bx::memMove(_referenceUsage, _referenceUsage + 1, (_maxRefs - 1) * sizeof(uint8_t) ); + _referenceUsage[_maxRefs - 1] = uint8_t(_currentSlot); + + return _maxRefs; + } + + template + inline void pickDisplaySlotForTime( + int64_t _presentationTimeUs + , ReorderEntryT* _pool + , uint32_t _poolCount + , int32_t& _displayedSlot + , int32_t& _prevDisplayedSlot + , bool _streamDrained + , DispatchFn _dispatch + ) + { + if (-1 == _displayedSlot) + { + int32_t bestSlot = -1; + int64_t minPts = INT64_MAX; + int64_t maxPts = INT64_MIN; + + for (uint32_t ii = 0; ii < _poolCount; ++ii) + { + if (-1 != _pool[ii].displayOrder) + { + if (_pool[ii].ptsUs < minPts) + { + minPts = _pool[ii].ptsUs; + bestSlot = int32_t(ii); + } + + if (_pool[ii].ptsUs > maxPts) + { + maxPts = _pool[ii].ptsUs; + } + } + } + + if (-1 == bestSlot) + { + return; + } + + if (minPts == maxPts + && !_streamDrained) + { + return; + } + + _displayedSlot = bestSlot; + _dispatch(uint32_t(_displayedSlot) ); + + return; + } + + const int64_t curPts = _pool[_displayedSlot].ptsUs; + + if (_presentationTimeUs + 500000 < curPts) + { + int32_t snapSlot = -1; + int64_t minPts = INT64_MAX; + for (uint32_t ii = 0; ii < _poolCount; ++ii) + { + if (-1 != _pool[ii].displayOrder + && _pool[ii].ptsUs < curPts + && _pool[ii].ptsUs < minPts) + { + minPts = _pool[ii].ptsUs; + snapSlot = int32_t(ii); + } + } + + if (-1 == snapSlot) + { + return; + } + + if (-1 != _prevDisplayedSlot) + { + _pool[_prevDisplayedSlot].displayOrder = -1; + } + + _prevDisplayedSlot = _displayedSlot; + _displayedSlot = snapSlot; + _dispatch(uint32_t(_displayedSlot) ); + + return; + } + + if (_presentationTimeUs > curPts + 500000) + { + int32_t jumpSlot = -1; + int64_t bestPts = curPts; + for (uint32_t ii = 0; ii < _poolCount; ++ii) + { + if (-1 != _pool[ii].displayOrder + && _pool[ii].ptsUs > bestPts + && _pool[ii].ptsUs <= _presentationTimeUs) + { + bestPts = _pool[ii].ptsUs; + jumpSlot = int32_t(ii); + } + } + + if (-1 != jumpSlot) + { + if (-1 != _prevDisplayedSlot) + { + _pool[_prevDisplayedSlot].displayOrder = -1; + } + + _prevDisplayedSlot = _displayedSlot; + _displayedSlot = jumpSlot; + _dispatch(uint32_t(_displayedSlot) ); + + return; + } + } + + int32_t nextSlot = -1; + int64_t nextPts = INT64_MAX; + int64_t maxPts = INT64_MIN; + + for (uint32_t ii = 0; ii < _poolCount; ++ii) + { + if (-1 != _pool[ii].displayOrder) + { + if (_pool[ii].ptsUs > maxPts) + { + maxPts = _pool[ii].ptsUs; + } + + if (_pool[ii].ptsUs > curPts + && _pool[ii].ptsUs < nextPts) + { + nextPts = _pool[ii].ptsUs; + nextSlot = int32_t(ii); + } + } + } + + if (-1 == nextSlot) + { + return; + } + + if (_presentationTimeUs < nextPts) + { + return; + } + + if (nextPts == maxPts && !_streamDrained) + { + return; + } + + if (-1 != _prevDisplayedSlot) + { + _pool[_prevDisplayedSlot].displayOrder = -1; + } + + _prevDisplayedSlot = _displayedSlot; + _displayedSlot = nextSlot; + _dispatch(uint32_t(_displayedSlot) ); + } +} + +#endif // BGFX_VIDEO_H_HEADER_GUARD diff --git a/src/video_d3d11.cpp b/src/video_d3d11.cpp new file mode 100644 index 000000000..244118d45 --- /dev/null +++ b/src/video_d3d11.cpp @@ -0,0 +1,1209 @@ +/* + * Copyright 2011-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE + */ + +#include "bgfx_p.h" + +#if BGFX_CONFIG_RENDERER_DIRECT3D11 +# include "renderer_d3d11.h" +# include "video_d3d11.h" + +# if BGFX_CONFIG_VIDEO_DIRECT3D11 +# include "video.h" +# include + +namespace bgfx { namespace d3d11 +{ + struct RendererContextD3D11; + + const ProgramD3D11& videoGetProgram(RendererContextD3D11* _renderer, ProgramHandle _handle); + ID3D11SamplerState* videoGetSamplerState(RendererContextD3D11* _renderer, uint32_t _flags); + + static VideoBindingD3D11 s_videoD3D11; + + static const GUID IID_ID3D11Device3 = { 0xa05c8c37, 0xd2c6, 0x4732, { 0xb3, 0xa0, 0x9c, 0xe0, 0xb0, 0xdc, 0x9a, 0xe6 } }; + static const GUID IID_ID3D11VideoDevice = { 0x10ec4d5b, 0x975a, 0x4689, { 0xb9, 0xe4, 0xd0, 0xaa, 0xc3, 0x0f, 0xe3, 0x33 } }; + static const GUID IID_ID3D11VideoContext = { 0x61f21c45, 0x3c0e, 0x4a74, { 0x9c, 0xea, 0x67, 0x10, 0x0d, 0x9a, 0xd5, 0xe4 } }; + static const GUID DXVA2_NoEncrypt = { 0x1b81bed0, 0xa0c7, 0x11d3, { 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5 } }; + static const GUID DXVA2_ModeH264_E = { 0x1b81be68, 0xa0c7, 0x11d3, { 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5 } }; + static const GUID DXVA2_ModeH264_VLD_FGT = { 0x1b81be69, 0xa0c7, 0x11d3, { 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5 } }; + static const GUID DXVA2_ModeHEVC_VLD_Main = { 0x5b11d51b, 0x2f4c, 0x4452, { 0xbc, 0xc3, 0x09, 0xf2, 0xa1, 0x16, 0x0c, 0xc0 } }; + static const GUID DXVA2_ModeHEVC_VLD_Main10 = { 0x107af0e0, 0xef1a, 0x4d19, { 0xab, 0xa8, 0x67, 0xa1, 0x63, 0x07, 0x3d, 0x13 } }; + static const GUID DXVA_ModeAV1_VLD_Profile0 = { 0xb8be4ccb, 0xcf53, 0x46ba, { 0x8d, 0x59, 0xd6, 0xb8, 0xa6, 0xda, 0x5d, 0x2a } }; + static const GUID DXVA_ModeAV1_VLD_Profile1 = { 0x6936ff0f, 0x45b1, 0x4163, { 0x9c, 0xc1, 0x64, 0x6e, 0xf6, 0x94, 0x6e, 0xea } }; + static const GUID DXVA_ModeAV1_VLD_Profile2 = { 0x0c5f2aa1, 0xe541, 0x4089, { 0xbb, 0x7b, 0x98, 0x11, 0x0a, 0x19, 0xd7, 0xc8 } }; + static const GUID DXVA_ModeAV1_VLD_12bit_Profile2 = { 0x17127009, 0xa00f, 0x4ce1, { 0x99, 0x4e, 0xbf, 0x40, 0x81, 0xf6, 0xf3, 0xf0 } }; + + struct VideoDecoderD3D11 + { + static constexpr uint32_t kNumDpbSlots = 32; + + struct ReorderedPicture + { + int32_t slotIndex; + int32_t displayOrder; + int64_t ptsUs; + }; + + struct DpbState + { + int32_t poc; + int32_t frameNum; + }; + + VideoDecoderD3D11() + : m_renderer(NULL) + , m_videoDevice(NULL) + , m_videoContext(NULL) + , m_decoder(NULL) + , m_sampler(NULL) + , m_codedWidth(0) + , m_codedHeight(0) + , m_dstWidth(0) + , m_dstHeight(0) + , m_numDpbSlots(kNumDpbSlots) + , m_nextSlot(0) + , m_currentSlot(0) + , m_numActiveRefs(0) + , m_prevPocLsb(0) + , m_prevPocMsb(0) + , m_displayedSlot(-1) + , m_prevDisplayedSlot(-1) + , m_statusReportFeedback(0) + , m_initFlags(0) + , m_cachedAuBytes(0) + { + m_dpbArray = NULL; + + bx::memSet(m_dpbViews, 0, sizeof(m_dpbViews) ); + bx::memSet(m_reorderTex, 0, sizeof(m_reorderTex) ); + bx::memSet(m_reorderSrvY, 0, sizeof(m_reorderSrvY) ); + bx::memSet(m_reorderSrvCbCr, 0, sizeof(m_reorderSrvCbCr) ); + bx::memSet(m_dpbState, 0, sizeof(m_dpbState) ); + bx::memSet(m_referenceUsage, 0, sizeof(m_referenceUsage) ); + bx::memSet(&m_spsActive, 0, sizeof(m_spsActive) ); + bx::memSet(m_spsArray, 0, sizeof(m_spsArray) ); + bx::memSet(m_ppsArray, 0, sizeof(m_ppsArray) ); + + for (uint32_t ii = 0; ii < kVideoMaxReorderInFlight; ++ii) + { + m_reorderPool[ii].slotIndex = -1; + m_reorderPool[ii].displayOrder = -1; + m_reorderPool[ii].ptsUs = 0; + } + } + + bool create(const VideoDecoderInit& _init, RendererContextD3D11* _renderer, uint16_t _width, uint16_t _height) + { + m_renderer = _renderer; + + m_initFlags = _init.flags; + m_cachedAuBytes = (0 != _init.cachedAuBytes) ? _init.cachedAuBytes : (4u << 20); + m_auQueue.configure(m_cachedAuBytes, 0 != (m_initFlags & BGFX_VIDEO_DECODER_INIT_RETAIN) ); + + if (VideoCodec::H264 != _init.codec) + { + BX_TRACE("VideoDecoderD3D11: codec %d not yet supported on D3D11.", _init.codec); + return false; + } + + if (NULL == _init.parameterSets + || 0 == _init.parameterSetsSize) + { + BX_TRACE("VideoDecoderD3D11: empty parameter sets."); + return false; + } + + h264::PPS ppsActive = {}; + + if (!parseParameterSets(m_spsArray, BX_COUNTOF(m_spsArray), m_ppsArray, BX_COUNTOF(m_ppsArray), m_spsActive, ppsActive, _init.parameterSets, _init.parameterSetsSize) ) + { + BX_TRACE("VideoDecoderD3D11: failed to parse SPS/PPS from parameter sets."); + return false; + } + + if (0 != m_spsActive.bit_depth_luma_minus8 + || 0 != m_spsActive.bit_depth_chroma_minus8 + || 1 != m_spsActive.chroma_format_idc) + { + BX_TRACE("VideoDecoderD3D11: only 8-bit 4:2:0 (NV12) is supported."); + return false; + } + + m_dstWidth = _width; + m_dstHeight = _height; + m_codedWidth = (m_spsActive.pic_width_in_mbs_minus1 + 1) * 16; + m_codedHeight = (m_spsActive.pic_height_in_map_units_minus1 + 1) * 16 * (m_spsActive.frame_mbs_only_flag ? 1 : 2); + + HRESULT hr = s_videoD3D11.device->QueryInterface(IID_ID3D11VideoDevice, (void**)&m_videoDevice); + + if (FAILED(hr) + || NULL == m_videoDevice) + { + BX_TRACE("VideoDecoderD3D11: ID3D11VideoDevice not available (hr=0x%08x).", hr); + return false; + } + + hr = s_videoD3D11.deviceCtx->QueryInterface(IID_ID3D11VideoContext, (void**)&m_videoContext); + + if (FAILED(hr) + || NULL == m_videoContext) + { + BX_TRACE("VideoDecoderD3D11: ID3D11VideoContext not available (hr=0x%08x).", hr); + return false; + } + + bool h264Supported = false; + const uint32_t decoderProfileCount = m_videoDevice->GetVideoDecoderProfileCount(); + + for (uint32_t ii = 0; ii < decoderProfileCount; ++ii) + { + GUID profile = {}; + if (SUCCEEDED(m_videoDevice->GetVideoDecoderProfile(ii, &profile) ) + && 0 == bx::memCmp(&profile, &DXVA2_ModeH264_E, sizeof(GUID) ) ) + { + h264Supported = true; + break; + } + } + + if (!h264Supported) + { + BX_TRACE("VideoDecoderD3D11: H.264 NoFGT decode profile not supported by hardware."); + return false; + } + + const uint32_t paddedWidth = bx::strideAlign(m_codedWidth, 16); + const uint32_t paddedHeight = bx::strideAlign(m_codedHeight, 16); + + D3D11_VIDEO_DECODER_DESC decoderDesc + { + .Guid = DXVA2_ModeH264_E, + .SampleWidth = paddedWidth, + .SampleHeight = paddedHeight, + .OutputFormat = DXGI_FORMAT_NV12, + }; + + uint32_t configCount = 0; + hr = m_videoDevice->GetVideoDecoderConfigCount(&decoderDesc, &configCount); + + if (FAILED(hr) || 0 == configCount) + { + BX_TRACE("VideoDecoderD3D11: GetVideoDecoderConfigCount failed (hr=0x%08x, count=%u).", hr, configCount); + return false; + } + + D3D11_VIDEO_DECODER_CONFIG decoderConfig = {}; + bool gotConfig = false; + + for (uint32_t pass = 0; pass < 2 && !gotConfig; ++pass) + { + const UCHAR want = (0 == pass) ? 2 : 1; + for (uint32_t ii = 0; ii < configCount; ++ii) + { + D3D11_VIDEO_DECODER_CONFIG cfg = {}; + if (FAILED(m_videoDevice->GetVideoDecoderConfig(&decoderDesc, ii, &cfg) ) ) + { + continue; + } + + const bool noEnc = true + && 0 == bx::memCmp(&cfg.guidConfigBitstreamEncryption, &DXVA2_NoEncrypt, sizeof(GUID) ) + && 0 == bx::memCmp(&cfg.guidConfigMBcontrolEncryption, &DXVA2_NoEncrypt, sizeof(GUID) ) + && 0 == bx::memCmp(&cfg.guidConfigResidDiffEncryption, &DXVA2_NoEncrypt, sizeof(GUID) ) + ; + + if (noEnc + && want == cfg.ConfigBitstreamRaw) + { + decoderConfig = cfg; + gotConfig = true; + break; + } + } + } + + if (!gotConfig) + { + BX_TRACE("VideoDecoderD3D11: no compatible D3D11_VIDEO_DECODER_CONFIG found."); + return false; + } + + hr = m_videoDevice->CreateVideoDecoder(&decoderDesc, &decoderConfig, &m_decoder); + + if (FAILED(hr) + || NULL == m_decoder) + { + BX_TRACE("VideoDecoderD3D11: CreateVideoDecoder failed (hr=0x%08x).", hr); + return false; + } + + { + D3D11_TEXTURE2D_DESC desc + { + .Width = paddedWidth, + .Height = paddedHeight, + .MipLevels = 1, + .ArraySize = m_numDpbSlots, + .Format = DXGI_FORMAT_NV12, + .SampleDesc = { .Count = 1, .Quality = 0 }, + .Usage = D3D11_USAGE_DEFAULT, + .BindFlags = D3D11_BIND_DECODER, + .CPUAccessFlags = 0, + .MiscFlags = 0, + }; + + hr = s_videoD3D11.device->CreateTexture2D(&desc, NULL, &m_dpbArray); + + if (FAILED(hr) || NULL == m_dpbArray) + { + BX_TRACE("VideoDecoderD3D11: DPB array texture create failed (hr=0x%08x).", hr); + return false; + } + } + + for (uint32_t slot = 0; slot < m_numDpbSlots; ++slot) + { + D3D11_VIDEO_DECODER_OUTPUT_VIEW_DESC viewDesc + { + .DecodeProfile = DXVA2_ModeH264_E, + .ViewDimension = D3D11_VDOV_DIMENSION_TEXTURE2D, + .Texture2D = { .ArraySlice = slot }, + }; + + hr = m_videoDevice->CreateVideoDecoderOutputView(m_dpbArray, &viewDesc, &m_dpbViews[slot]); + + if (FAILED(hr) + || NULL == m_dpbViews[slot]) + { + BX_TRACE("VideoDecoderD3D11: CreateVideoDecoderOutputView slot %u failed (hr=0x%08x).", slot, hr); + return false; + } + } + + { + const uint32_t reorderW = (m_dstWidth + 1) & ~1u; + const uint32_t reorderH = (m_dstHeight + 1) & ~1u; + + for (uint32_t ii = 0; ii < kVideoMaxReorderInFlight; ++ii) + { + D3D11_TEXTURE2D_DESC desc + { + .Width = reorderW, + .Height = reorderH, + .MipLevels = 1, + .ArraySize = 1, + .Format = DXGI_FORMAT_NV12, + .SampleDesc = { .Count = 1, .Quality = 0 }, + .Usage = D3D11_USAGE_DEFAULT, + .BindFlags = D3D11_BIND_SHADER_RESOURCE, + .CPUAccessFlags = 0, + .MiscFlags = 0, + }; + + hr = s_videoD3D11.device->CreateTexture2D(&desc, NULL, &m_reorderTex[ii]); + + if (FAILED(hr) + || NULL == m_reorderTex[ii]) + { + BX_TRACE("VideoDecoderD3D11: reorder texture %u create failed (hr=0x%08x).", ii, hr); + return false; + } + + ID3D11Device3* device3 = NULL; + hr = s_videoD3D11.device->QueryInterface(IID_ID3D11Device3, (void**)&device3); + + if (FAILED(hr) + || NULL == device3) + { + BX_TRACE("VideoDecoderD3D11: ID3D11Device3 not available, planar NV12 SRVs require it (hr=0x%08x).", hr); + return false; + } + + ID3D11ShaderResourceView1* srv1 = NULL; + + D3D11_SHADER_RESOURCE_VIEW_DESC1 srvDescY + { + .Format = DXGI_FORMAT_R8_UNORM, + .ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D, + .Texture2D = { .MostDetailedMip = 0, .MipLevels = 1, .PlaneSlice = 0 }, + }; + + hr = device3->CreateShaderResourceView1(m_reorderTex[ii], &srvDescY, &srv1); + + if (FAILED(hr) + || NULL == srv1) + { + BX_TRACE("VideoDecoderD3D11: reorder R8 SRV %u failed (hr=0x%08x).", ii, hr); + DX_RELEASE_I(device3); + return false; + } + + m_reorderSrvY[ii] = srv1; + srv1 = NULL; + + D3D11_SHADER_RESOURCE_VIEW_DESC1 srvDescCbCr + { + .Format = DXGI_FORMAT_R8G8_UNORM, + .ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D, + .Texture2D = { .MostDetailedMip = 0, .MipLevels = 1, .PlaneSlice = 1 }, + }; + + hr = device3->CreateShaderResourceView1(m_reorderTex[ii], &srvDescCbCr, &srv1); + + if (FAILED(hr) + || NULL == srv1) + { + BX_TRACE("VideoDecoderD3D11: reorder R8G8 SRV %u failed (hr=0x%08x).", ii, hr); + DX_RELEASE_I(device3); + return false; + } + + m_reorderSrvCbCr[ii] = srv1; + + DX_RELEASE_I(device3); + } + } + + m_sampler = videoGetSamplerState( + _renderer + , BGFX_SAMPLER_U_CLAMP + | BGFX_SAMPLER_V_CLAMP + ); + + if (NULL == m_sampler) + { + BX_TRACE("VideoDecoderD3D11: videoGetSamplerState failed."); + return false; + } + + BX_TRACE("VideoDecoderD3D11: H.264 decoder ready, coded %dx%d (padded %ux%u), dst %dx%d." + , m_codedWidth, m_codedHeight + , paddedWidth, paddedHeight + , m_dstWidth, m_dstHeight + ); + + return true; + } + + void destroy() + { + for (uint32_t ii = 0; ii < m_numDpbSlots; ++ii) + { + DX_RELEASE(m_dpbViews[ii], 0); + } + DX_RELEASE(m_dpbArray, 0); + + for (uint32_t ii = 0; ii < kVideoMaxReorderInFlight; ++ii) + { + DX_RELEASE(m_reorderSrvCbCr[ii], 0); + DX_RELEASE(m_reorderSrvY[ii], 0); + DX_RELEASE(m_reorderTex[ii], 0); + } + + DX_RELEASE(m_decoder, 0); + DX_RELEASE_I(m_videoContext); + DX_RELEASE_I(m_videoDevice); + + m_sampler = NULL; + + m_currentSlot = 0; + m_nextSlot = 0; + m_numActiveRefs = 0; + m_displayedSlot = -1; + m_statusReportFeedback = 0; + + for (uint32_t ii = 0; ii < kVideoMaxReorderInFlight; ++ii) + { + m_reorderPool[ii].slotIndex = -1; + m_reorderPool[ii].displayOrder = -1; + m_reorderPool[ii].ptsUs = 0; + } + } + + bool decode(const VideoDecoderFrame& _frame, TextureD3D11& _dst) + { + if (NULL == m_decoder) + { + return false; + } + + if (0 != (_frame.flags & BGFX_VIDEO_DECODE_FRAME_SET) ) + { + resetForSet(); + } + + if (NULL != _frame.bitstream && 0 != _frame.numAus) + { + const uint8_t* bs = _frame.bitstream; + for (uint32_t ii = 0; ii < _frame.numAus; ++ii) + { + const uint32_t auSize = _frame.aus[ii].size; + const bool isIdr = peekAuIsIdr(bs, auSize); + m_auQueue.enqueue(bs, auSize, _frame.aus[ii].ptsUs, isIdr); + bs += auSize; + } + } + + while (!m_auQueue.empty() + && hasReorderSpace(m_reorderPool, kVideoMaxReorderInFlight) ) + { + const PendingAU& au = m_auQueue.front(); + decodeOneAU(au.data.data(), uint32_t(au.data.size() ), au.ptsUs); + m_auQueue.pop(); + } + + m_auQueue.compact(); + + if (0 == _frame.numAus + && 0 == (_frame.flags & BGFX_VIDEO_DECODE_FRAME_NO_BLIT) ) + { + int64_t adjustedClock = _frame.presentationTimeUs; + + if (0 != (_frame.flags & BGFX_VIDEO_DECODE_FRAME_LOOP) + && m_auQueue.isRetaining() + && m_auQueue.minPts() < m_auQueue.maxPts() ) + { + adjustedClock = applyLoopWrap(adjustedClock, m_auQueue.minPts(), m_auQueue.maxPts() ); + + const bool poolCovers = reorderPoolCovers(m_reorderPool, kVideoMaxReorderInFlight, adjustedClock, 500000); + const bool forwardCanCatchUp = true + && !m_auQueue.empty() + && m_auQueue.front().ptsUs <= adjustedClock + 500000 + ; + + if (!poolCovers + && !forwardCanCatchUp) + { + faultInFromCache( + m_auQueue + , adjustedClock + , [this]() { resetForSet(); } + , [this](const uint8_t* _bs, uint32_t _bsSize, int64_t _pts) + { + decodeOneAU(_bs, _bsSize, _pts); + } + , [this]() + { + return hasReorderSpace(m_reorderPool, kVideoMaxReorderInFlight); + } + ); + } + } + + pickDisplaySlotForTime(adjustedClock, _dst); + } + + return true; + } + + void resetForSet() + { + if (m_auQueue.isRetaining() ) + { + m_auQueue.rewindTo(m_auQueue.size() ); + } + else + { + m_auQueue.clear(); + } + + for (uint32_t ii = 0; ii < kVideoMaxReorderInFlight; ++ii) + { + m_reorderPool[ii].slotIndex = -1; + m_reorderPool[ii].displayOrder = -1; + m_reorderPool[ii].ptsUs = 0; + } + + m_displayedSlot = -1; + m_prevDisplayedSlot = -1; + m_numActiveRefs = 0; + m_nextSlot = 0; + m_currentSlot = 0; + m_prevPocLsb = 0; + m_prevPocMsb = 0; + + bx::memSet(m_referenceUsage, 0, sizeof(m_referenceUsage) ); + bx::memSet(m_dpbState, 0, sizeof(m_dpbState) ); + + } + + bool decodeOneAU(const uint8_t* _bitstream, uint32_t _bitstreamSize, int64_t _ptsUs) + { + h264::NALHeader nal = {}; + h264::SliceHeader sh = {}; + bool isIdr = false; + + if (!parseSliceFromAccessUnit(m_spsArray, m_ppsArray, _bitstream, _bitstreamSize, nal, sh, isIdr) ) + { + BX_TRACE("VideoDecoderD3D11: no slice NAL found in access unit."); + return false; + } + + const h264::PPS& pps = m_ppsArray[sh.pic_parameter_set_id]; + const h264::SPS& sps = m_spsArray[pps.seq_parameter_set_id]; + + if (isIdr) + { + m_numActiveRefs = 0; + m_nextSlot = 0; + } + + const int32_t poc = computePoc(m_prevPocMsb, m_prevPocLsb, sps, sh, nal, isIdr); + const int32_t displayOrder = poc / 2; + + m_currentSlot = pickFreeDpbSlot( + m_nextSlot + , m_numDpbSlots + , m_referenceUsage + , m_numActiveRefs + , [this](uint32_t _slot) + { + for (uint32_t ii = 0; ii < kVideoMaxReorderInFlight; ++ii) + { + if (-1 != m_reorderPool[ii].displayOrder + && m_reorderPool[ii].slotIndex == int32_t(_slot) ) + { + return true; + } + } + + return false; + }); + + BX_ASSERT(m_currentSlot < m_numDpbSlots + , "DPB exhausted: m_numDpbSlots=%u m_numActiveRefs=%u. Cap maxRefs to m_numDpbSlots-1." + , m_numDpbSlots + , m_numActiveRefs + ); + + m_nextSlot = (m_currentSlot + 1) % m_numDpbSlots; + m_dpbState[m_currentSlot].poc = poc; + m_dpbState[m_currentSlot].frameNum = sh.frame_num; + + const uint32_t poolIdx = pickReorderSlotOrEvictOldest(m_reorderPool, kVideoMaxReorderInFlight); + m_reorderPool[poolIdx].slotIndex = int32_t(m_currentSlot); + m_reorderPool[poolIdx].displayOrder = displayOrder; + m_reorderPool[poolIdx].ptsUs = _ptsUs; + + DXVA_PicParams_H264 picParams = {}; + DXVA_Qmatrix_H264 qmatrix = {}; + + picParams.wFrameWidthInMbsMinus1 = uint16_t(sps.pic_width_in_mbs_minus1); + picParams.wFrameHeightInMbsMinus1 = uint16_t(sps.pic_height_in_map_units_minus1); + picParams.IntraPicFlag = isIdr ? 1 : 0; + picParams.MbaffFrameFlag = 0; + picParams.field_pic_flag = 0; + picParams.chroma_format_idc = 1; + picParams.bit_depth_chroma_minus8 = uint8_t(sps.bit_depth_chroma_minus8); + picParams.bit_depth_luma_minus8 = uint8_t(sps.bit_depth_luma_minus8); + picParams.residual_colour_transform_flag = uint8_t(sps.separate_colour_plane_flag); + picParams.CurrPic.AssociatedFlag = 0; + picParams.CurrPic.Index7Bits = uint8_t(m_currentSlot); + picParams.CurrFieldOrderCnt[0] = poc; + picParams.CurrFieldOrderCnt[1] = poc; + + for (uint32_t ii = 0; ii < 16; ++ii) + { + picParams.RefFrameList[ii].bPicEntry = 0xFF; + picParams.FieldOrderCntList[ii][0] = 0; + picParams.FieldOrderCntList[ii][1] = 0; + picParams.FrameNumList[ii] = 0; + } + + picParams.UsedForReferenceFlags = 0; + + for (uint32_t ii = 0; ii < m_numActiveRefs && ii < 16; ++ii) + { + const uint32_t refSlot = m_referenceUsage[ii]; + picParams.RefFrameList[ii].AssociatedFlag = 0; + picParams.RefFrameList[ii].Index7Bits = uint8_t(refSlot); + picParams.FieldOrderCntList[ii][0] = m_dpbState[refSlot].poc; + picParams.FieldOrderCntList[ii][1] = m_dpbState[refSlot].poc; + picParams.FrameNumList[ii] = uint16_t(m_dpbState[refSlot].frameNum); + picParams.UsedForReferenceFlags |= 1u << (ii * 2 + 0); + picParams.UsedForReferenceFlags |= 1u << (ii * 2 + 1); + } + + picParams.weighted_pred_flag = uint8_t(pps.weighted_pred_flag); + picParams.weighted_bipred_idc = uint8_t(pps.weighted_bipred_idc); + picParams.sp_for_switch_flag = 0; + picParams.transform_8x8_mode_flag = uint8_t(pps.transform_8x8_mode_flag); + picParams.constrained_intra_pred_flag = uint8_t(pps.constrained_intra_pred_flag); + picParams.num_ref_frames = uint8_t(sps.num_ref_frames); + picParams.MbsConsecutiveFlag = 1; + picParams.frame_mbs_only_flag = uint8_t(sps.frame_mbs_only_flag); + picParams.MinLumaBipredSize8x8Flag = sps.level_idc >= 31 ? 1 : 0; + picParams.RefPicFlag = (0 != nal.idc) ? 1 : 0; + picParams.frame_num = uint16_t(sh.frame_num); + picParams.pic_init_qp_minus26 = int8_t(pps.pic_init_qp_minus26); + picParams.pic_init_qs_minus26 = int8_t(pps.pic_init_qs_minus26); + picParams.chroma_qp_index_offset = int8_t(pps.chroma_qp_index_offset); + picParams.second_chroma_qp_index_offset = int8_t(pps.second_chroma_qp_index_offset); + picParams.log2_max_frame_num_minus4 = uint8_t(sps.log2_max_frame_num_minus4); + picParams.pic_order_cnt_type = uint8_t(sps.pic_order_cnt_type); + picParams.log2_max_pic_order_cnt_lsb_minus4 = uint8_t(sps.log2_max_pic_order_cnt_lsb_minus4); + picParams.delta_pic_order_always_zero_flag = uint8_t(sps.delta_pic_order_always_zero_flag); + picParams.direct_8x8_inference_flag = uint8_t(sps.direct_8x8_inference_flag); + picParams.entropy_coding_mode_flag = uint8_t(pps.entropy_coding_mode_flag); + picParams.pic_order_present_flag = uint8_t(pps.pic_order_present_flag); + picParams.num_slice_groups_minus1 = uint8_t(pps.num_slice_groups_minus1); + picParams.slice_group_map_type = uint8_t(pps.slice_group_map_type); + picParams.deblocking_filter_control_present_flag = uint8_t(pps.deblocking_filter_control_present_flag); + picParams.redundant_pic_cnt_present_flag = uint8_t(pps.redundant_pic_cnt_present_flag); + picParams.slice_group_change_rate_minus1 = uint16_t(pps.slice_group_change_rate_minus1); + picParams.Reserved16Bits = 3; + picParams.StatusReportFeedbackNumber = ++m_statusReportFeedback; + picParams.ContinuationFlag = 1; + picParams.num_ref_idx_l0_active_minus1 = uint8_t(pps.num_ref_idx_l0_active_minus1); + picParams.num_ref_idx_l1_active_minus1 = uint8_t(pps.num_ref_idx_l1_active_minus1); + + if (sps.seq_scaling_matrix_present_flag + || pps.pic_scaling_matrix_present_flag) + { + uint8_t lists4x4[6][16]; + uint8_t lists8x8[2][64]; + + buildEffectiveScalingLists(sps, pps, lists4x4, lists8x8); + + for (int i = 0; i < 6; ++i) + { + for (int j = 0; j < 16; ++j) + { + qmatrix.bScalingLists4x4[i][j] = lists4x4[i][j]; + } + } + + for (int i = 0; i < 64; ++i) + { + qmatrix.bScalingLists8x8[0][i] = lists8x8[0][i]; + qmatrix.bScalingLists8x8[1][i] = lists8x8[1][i]; + } + } + else + { + bx::memSet(&qmatrix, 16, sizeof(qmatrix) ); + } + + const uint32_t sliceOffsetInAu = findFirstSliceNalOffset(_bitstream, _bitstreamSize); + const uint8_t* sliceBitstream = _bitstream + sliceOffsetInAu; + const uint32_t sliceBitstreamSize = _bitstreamSize - sliceOffsetInAu; + + DXVA_Slice_H264_Short sliceCtl + { + .BSNALunitDataLocation = 0, + .SliceBytesInBuffer = sliceBitstreamSize, + .wBadSliceChopping = 0, + }; + + HRESULT hr = m_videoContext->DecoderBeginFrame(m_decoder, m_dpbViews[m_currentSlot], 0, NULL); + + if (FAILED(hr) ) + { + BX_TRACE("VideoDecoderD3D11: DecoderBeginFrame failed (hr=0x%08x).", hr); + return false; + } + + uint32_t bufSize = 0; + void* bufPtr = NULL; + + hr = m_videoContext->GetDecoderBuffer(m_decoder, D3D11_VIDEO_DECODER_BUFFER_BITSTREAM, &bufSize, &bufPtr); + + if (FAILED(hr) + || NULL == bufPtr + || bufSize < sliceBitstreamSize) + { + BX_TRACE("VideoDecoderD3D11: GetDecoderBuffer(BITSTREAM) failed (hr=0x%08x, sz=%u, need=%u).", hr, bufSize, sliceBitstreamSize); + m_videoContext->DecoderEndFrame(m_decoder); + return false; + } + + bx::memCopy(bufPtr, sliceBitstream, sliceBitstreamSize); + m_videoContext->ReleaseDecoderBuffer(m_decoder, D3D11_VIDEO_DECODER_BUFFER_BITSTREAM); + + hr = m_videoContext->GetDecoderBuffer(m_decoder, D3D11_VIDEO_DECODER_BUFFER_PICTURE_PARAMETERS, &bufSize, &bufPtr); + + if (FAILED(hr) + || NULL == bufPtr + || bufSize < sizeof(picParams) ) + { + BX_TRACE("VideoDecoderD3D11: GetDecoderBuffer(PICTURE_PARAMETERS) failed (hr=0x%08x).", hr); + m_videoContext->DecoderEndFrame(m_decoder); + return false; + } + + bx::memCopy(bufPtr, &picParams, sizeof(picParams) ); + m_videoContext->ReleaseDecoderBuffer(m_decoder, D3D11_VIDEO_DECODER_BUFFER_PICTURE_PARAMETERS); + + hr = m_videoContext->GetDecoderBuffer(m_decoder, D3D11_VIDEO_DECODER_BUFFER_INVERSE_QUANTIZATION_MATRIX, &bufSize, &bufPtr); + + if (FAILED(hr) + || NULL == bufPtr + || bufSize < sizeof(qmatrix) ) + { + BX_TRACE("VideoDecoderD3D11: GetDecoderBuffer(IQM) failed (hr=0x%08x).", hr); + m_videoContext->DecoderEndFrame(m_decoder); + return false; + } + + bx::memCopy(bufPtr, &qmatrix, sizeof(qmatrix) ); + m_videoContext->ReleaseDecoderBuffer(m_decoder, D3D11_VIDEO_DECODER_BUFFER_INVERSE_QUANTIZATION_MATRIX); + + hr = m_videoContext->GetDecoderBuffer(m_decoder, D3D11_VIDEO_DECODER_BUFFER_SLICE_CONTROL, &bufSize, &bufPtr); + + if (FAILED(hr) + || NULL == bufPtr + || bufSize < sizeof(sliceCtl) ) + { + BX_TRACE("VideoDecoderD3D11: GetDecoderBuffer(SLICE_CONTROL) failed (hr=0x%08x).", hr); + m_videoContext->DecoderEndFrame(m_decoder); + return false; + } + + bx::memCopy(bufPtr, &sliceCtl, sizeof(sliceCtl) ); + + m_videoContext->ReleaseDecoderBuffer(m_decoder, D3D11_VIDEO_DECODER_BUFFER_SLICE_CONTROL); + + D3D11_VIDEO_DECODER_BUFFER_DESC bd[4] + { + { + .BufferType = D3D11_VIDEO_DECODER_BUFFER_BITSTREAM, + .BufferIndex = 0, + .DataOffset = 0, + .DataSize = sliceBitstreamSize, + .FirstMBaddress = 0, + .NumMBsInBuffer = 0, + .Width = 0, + .Height = 0, + .Stride = 0, + .ReservedBits = 0, + .pIV = NULL, + .IVSize = 0, + .PartialEncryption = FALSE, + .EncryptedBlockInfo = {}, + }, + { + .BufferType = D3D11_VIDEO_DECODER_BUFFER_PICTURE_PARAMETERS, + .BufferIndex = 0, + .DataOffset = 0, + .DataSize = sizeof(picParams), + .FirstMBaddress = 0, + .NumMBsInBuffer = 0, + .Width = 0, + .Height = 0, + .Stride = 0, + .ReservedBits = 0, + .pIV = NULL, + .IVSize = 0, + .PartialEncryption = FALSE, + .EncryptedBlockInfo = {}, + }, + { + .BufferType = D3D11_VIDEO_DECODER_BUFFER_INVERSE_QUANTIZATION_MATRIX, + .BufferIndex = 0, + .DataOffset = 0, + .DataSize = sizeof(qmatrix), + .FirstMBaddress = 0, + .NumMBsInBuffer = 0, + .Width = 0, + .Height = 0, + .Stride = 0, + .ReservedBits = 0, + .pIV = NULL, + .IVSize = 0, + .PartialEncryption = FALSE, + .EncryptedBlockInfo = {}, + }, + { + .BufferType = D3D11_VIDEO_DECODER_BUFFER_SLICE_CONTROL, + .BufferIndex = 0, + .DataOffset = 0, + .DataSize = sizeof(sliceCtl), + .FirstMBaddress = 0, + .NumMBsInBuffer = 0, + .Width = 0, + .Height = 0, + .Stride = 0, + .ReservedBits = 0, + .pIV = NULL, + .IVSize = 0, + .PartialEncryption = FALSE, + .EncryptedBlockInfo = {}, + }, + }; + + hr = m_videoContext->SubmitDecoderBuffers(m_decoder, BX_COUNTOF(bd), bd); + + if (FAILED(hr) ) + { + BX_TRACE("VideoDecoderD3D11: SubmitDecoderBuffers failed (hr=0x%08x).", hr); + m_videoContext->DecoderEndFrame(m_decoder); + return false; + } + + hr = m_videoContext->DecoderEndFrame(m_decoder); + + if (FAILED(hr) ) + { + BX_TRACE("VideoDecoderD3D11: DecoderEndFrame failed (hr=0x%08x).", hr); + return false; + } + + { + const uint32_t copyW = (m_dstWidth + 1) & ~1u; + const uint32_t copyH = (m_dstHeight + 1) & ~1u; + const uint32_t srcW = bx::min(copyW, m_codedWidth); + const uint32_t srcH = bx::min(copyH, m_codedHeight); + + ID3D11DeviceContext* deviceCtx = s_videoD3D11.deviceCtx; + + D3D11_BOX boxY; + boxY.left = 0; + boxY.top = 0; + boxY.front = 0; + boxY.right = srcW; + boxY.bottom = srcH; + boxY.back = 1; + deviceCtx->CopySubresourceRegion( + m_reorderTex[poolIdx], 0 + , 0, 0, 0 + , m_dpbArray, m_currentSlot + , &boxY + ); + + D3D11_BOX boxUV; + boxUV.left = 0; + boxUV.top = 0; + boxUV.front = 0; + boxUV.right = srcW / 2; + boxUV.bottom = srcH / 2; + boxUV.back = 1; + deviceCtx->CopySubresourceRegion( + m_reorderTex[poolIdx], 1 + , 0, 0, 0 + , m_dpbArray, m_numDpbSlots + m_currentSlot + , &boxUV + ); + } + + if (0 != nal.idc) + { + if (0 != sh.drpm.adaptive_ref_pic_marking_mode_flag + && m_numActiveRefs > 0) + { + uint16_t refFrameNums[16]; + const uint32_t n = bx::min(m_numActiveRefs, 16); + + for (uint32_t ii = 0; ii < n; ++ii) + { + refFrameNums[ii] = uint16_t(m_dpbState[m_referenceUsage[ii]].frameNum); + } + + const int32_t maxFrameNum = 1 << (sps.log2_max_frame_num_minus4 + 4); + + m_numActiveRefs = applyMmcoUnmarkShortTerm( + m_referenceUsage + , refFrameNums + , n + , sh.frame_num + , maxFrameNum + , sh + ); + } + + const uint32_t maxRefs = bx::min( + bx::min(uint32_t(sps.num_ref_frames), 16) + , m_numDpbSlots - 1 + ); + + m_numActiveRefs = appendShortTermRef( + m_referenceUsage + , m_numActiveRefs + , m_currentSlot + , maxRefs + ); + } + + return true; + } + + void pickDisplaySlotForTime(int64_t _presentationTimeUs, TextureD3D11& _dst) + { + bgfx::pickDisplaySlotForTime( + _presentationTimeUs + , m_reorderPool + , kVideoMaxReorderInFlight + , m_displayedSlot + , m_prevDisplayedSlot + , m_auQueue.empty() + , [this, &_dst](uint32_t _slot) + { + dispatchYuvToRgb(_slot, _dst); + }); + } + + + void dispatchYuvToRgb(uint32_t _poolIdx, TextureD3D11& _dst) + { + BX_ASSERT(NULL != g_videoDecode && isValid(g_videoDecode->m_program), "Video decode program not initialized."); + BX_ASSERT(_poolIdx < kVideoMaxReorderInFlight, "Reorder pool index out of range."); + + ID3D11DeviceContext* deviceCtx = s_videoD3D11.deviceCtx; + const ProgramD3D11& program = videoGetProgram(m_renderer, g_videoDecode->m_program); + + if (NULL == program.m_vsh + || NULL == program.m_vsh->m_computeShader + || NULL == _dst.m_uav) + { + return; + } + + ID3D11ComputeShader* cs = program.m_vsh->m_computeShader; + + deviceCtx->CSSetShader(cs, NULL, 0); + + ID3D11ShaderResourceView* nullSrv = NULL; + deviceCtx->VSSetShaderResources(0, 1, &nullSrv); + deviceCtx->PSSetShaderResources(0, 1, &nullSrv); + + ID3D11ShaderResourceView* srvs[3] = { NULL, m_reorderSrvY[_poolIdx], m_reorderSrvCbCr[_poolIdx] }; + deviceCtx->CSSetShaderResources(0, 3, srvs); + + ID3D11SamplerState* samplers[3] = { NULL, m_sampler, m_sampler }; + deviceCtx->CSSetSamplers(0, 3, samplers); + + ID3D11UnorderedAccessView* uav = _dst.m_uav; + deviceCtx->CSSetUnorderedAccessViews(0, 1, &uav, NULL); + + deviceCtx->Dispatch( + bx::max( (m_dstWidth + 7) / 8, 1) + , bx::max( (m_dstHeight + 7) / 8, 1) + , 1 + ); + + ID3D11UnorderedAccessView* nullUav = NULL; + deviceCtx->CSSetUnorderedAccessViews(0, 1, &nullUav, NULL); + + ID3D11ShaderResourceView* nullSrvs[3] = { NULL, NULL, NULL }; + deviceCtx->CSSetShaderResources(0, 3, nullSrvs); + + ID3D11SamplerState* nullSamplers[3] = { NULL, NULL, NULL }; + deviceCtx->CSSetSamplers(0, 3, nullSamplers); + + deviceCtx->CSSetShader(NULL, NULL, 0); + } + + RendererContextD3D11* m_renderer; + + ID3D11VideoDevice* m_videoDevice; + ID3D11VideoContext* m_videoContext; + ID3D11VideoDecoder* m_decoder; + + ID3D11Texture2D* m_dpbArray; + ID3D11VideoDecoderOutputView* m_dpbViews[kNumDpbSlots]; + + ID3D11Texture2D* m_reorderTex[kVideoMaxReorderInFlight]; + ID3D11ShaderResourceView* m_reorderSrvY[kVideoMaxReorderInFlight]; + ID3D11ShaderResourceView* m_reorderSrvCbCr[kVideoMaxReorderInFlight]; + ID3D11SamplerState* m_sampler; + + DpbState m_dpbState[kNumDpbSlots]; + uint8_t m_referenceUsage[kNumDpbSlots]; + + uint32_t m_codedWidth; + uint32_t m_codedHeight; + uint32_t m_dstWidth; + uint32_t m_dstHeight; + uint32_t m_numDpbSlots; + uint32_t m_nextSlot; + uint32_t m_currentSlot; + uint32_t m_numActiveRefs; + + int32_t m_prevPocLsb; + int32_t m_prevPocMsb; + + ReorderedPicture m_reorderPool[kVideoMaxReorderInFlight]; + int32_t m_displayedSlot; + int32_t m_prevDisplayedSlot; + + uint32_t m_statusReportFeedback; + + AuQueue m_auQueue; + + uint32_t m_initFlags; + uint32_t m_cachedAuBytes; + + h264::SPS m_spsActive; + h264::SPS m_spsArray[kVideoMaxH264SpsCount]; + h264::PPS m_ppsArray[kVideoMaxH264PpsCount]; + }; + + VideoDecoderD3D11* videoDecoderCreate(const VideoDecoderInit& _init, RendererContextD3D11* _renderer, uint16_t _width, uint16_t _height) + { + VideoDecoderD3D11* decoder = BX_NEW(g_allocator, VideoDecoderD3D11); + + if (!decoder->create(_init, _renderer, _width, _height) ) + { + decoder->destroy(); + bx::deleteObject(g_allocator, decoder); + return NULL; + } + + return decoder; + } + + void videoDecoderDestroy(VideoDecoderD3D11* _decoder) + { + if (NULL != _decoder) + { + _decoder->destroy(); + bx::deleteObject(g_allocator, _decoder); + } + } + + bool videoDecoderDecode(VideoDecoderD3D11* _decoder, const VideoDecoderFrame& _frame, TextureD3D11& _dst) + { + return _decoder->decode(_frame, _dst); + } + + void initVideoDecoder(const VideoBindingD3D11& _binding) + { + s_videoD3D11 = _binding; + + ID3D11VideoDevice* videoDevice = NULL; + HRESULT hr = _binding.device->QueryInterface(IID_ID3D11VideoDevice, (void**)&videoDevice); + + if (FAILED(hr) + || NULL == videoDevice) + { + return; + } + + struct ProfileEntry + { + const GUID* guid; + VideoCodec::Enum codec; + uint32_t caps; + }; + + static const ProfileEntry s_profiles[] = + { + // H.264 + { &DXVA2_ModeH264_E, VideoCodec::H264, BGFX_CAPS_VIDEO_CODEC_BIT_8 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420 }, + { &DXVA2_ModeH264_VLD_FGT, VideoCodec::H264, BGFX_CAPS_VIDEO_CODEC_BIT_8 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420 }, + + // HEVC / H.265 + { &DXVA2_ModeHEVC_VLD_Main, VideoCodec::H265, BGFX_CAPS_VIDEO_CODEC_BIT_8 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420 }, + { &DXVA2_ModeHEVC_VLD_Main10, VideoCodec::H265, BGFX_CAPS_VIDEO_CODEC_BIT_10 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420 }, + + // AV1 + { &DXVA_ModeAV1_VLD_Profile0, VideoCodec::AV1, BGFX_CAPS_VIDEO_CODEC_BIT_8 + | BGFX_CAPS_VIDEO_CODEC_BIT_10 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420 }, + { &DXVA_ModeAV1_VLD_Profile1, VideoCodec::AV1, BGFX_CAPS_VIDEO_CODEC_BIT_8 + | BGFX_CAPS_VIDEO_CODEC_BIT_10 | BGFX_CAPS_VIDEO_CODEC_CHROMA_444 }, + { &DXVA_ModeAV1_VLD_Profile2, VideoCodec::AV1, BGFX_CAPS_VIDEO_CODEC_BIT_10 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420 + | BGFX_CAPS_VIDEO_CODEC_CHROMA_422 }, + { &DXVA_ModeAV1_VLD_12bit_Profile2, VideoCodec::AV1, BGFX_CAPS_VIDEO_CODEC_BIT_12 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420 + | BGFX_CAPS_VIDEO_CODEC_CHROMA_422 }, + }; + + const uint32_t supportedCount = videoDevice->GetVideoDecoderProfileCount(); + + bool anySupported = false; + + for (uint32_t ii = 0; ii < supportedCount; ++ii) + { + GUID profile; + + if (FAILED(videoDevice->GetVideoDecoderProfile(ii, &profile) ) ) + { + continue; + } + + for (uint32_t jj = 0; jj < BX_COUNTOF(s_profiles); ++jj) + { + if (0 == bx::memCmp(&profile, s_profiles[jj].guid, sizeof(GUID) ) ) + { + BOOL supported = false; + hr = videoDevice->CheckVideoDecoderFormat(s_profiles[jj].guid, DXGI_FORMAT_NV12, &supported); + + if (SUCCEEDED(hr) + && supported) + { + g_caps.codecs[s_profiles[jj].codec] |= s_profiles[jj].caps; + anySupported = true; + } + + supported = false; + hr = videoDevice->CheckVideoDecoderFormat(s_profiles[jj].guid, DXGI_FORMAT_P010, &supported); + + if (SUCCEEDED(hr) + && supported) + { + g_caps.codecs[s_profiles[jj].codec] |= s_profiles[jj].caps; + anySupported = true; + } + + break; + } + } + } + + if (anySupported) + { + g_caps.supported |= BGFX_CAPS_VIDEO_DECODE; + + const TextureFormat::Enum dstFormats[] = + { + TextureFormat::BGRA8, + TextureFormat::RGBA8, + TextureFormat::RGB10A2, + TextureFormat::RGBA16F, + }; + + for (uint32_t ii = 0; ii < BX_COUNTOF(dstFormats); ++ii) + { + const TextureFormat::Enum fmt = dstFormats[ii]; + + if (0 != (g_caps.formats[fmt] & BGFX_CAPS_FORMAT_TEXTURE_2D) ) + { + g_caps.formats[fmt] |= BGFX_CAPS_FORMAT_TEXTURE_VIDEO_DECODE_DST; + } + } + } + + DX_RELEASE_I(videoDevice); + } +} } // namespace bgfx::d3d11 + +# else + +namespace bgfx { namespace d3d11 +{ + void initVideoDecoder(const VideoBindingD3D11& _binding) + { + BX_UNUSED(_binding); + } + + VideoDecoderD3D11* videoDecoderCreate(const VideoDecoderInit& _init, RendererContextD3D11* _renderer, uint16_t _width, uint16_t _height) + { + BX_UNUSED(_init, _renderer, _width, _height); + return NULL; + } + + void videoDecoderDestroy(VideoDecoderD3D11* _decoder) + { + BX_UNUSED(_decoder); + } + + bool videoDecoderDecode(VideoDecoderD3D11* _decoder, const VideoDecoderFrame& _frame, TextureD3D11& _dst) + { + BX_UNUSED(_decoder, _frame, _dst); + return false; + } +} } // namespace bgfx::d3d11 + +# endif // BGFX_CONFIG_VIDEO_DIRECT3D11 + +#endif // BGFX_CONFIG_RENDERER_DIRECT3D11 diff --git a/src/video_d3d11.h b/src/video_d3d11.h new file mode 100644 index 000000000..606b5232a --- /dev/null +++ b/src/video_d3d11.h @@ -0,0 +1,32 @@ +/* + * Copyright 2011-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE + */ + +#ifndef BGFX_VIDEO_D3D11_H_HEADER_GUARD +#define BGFX_VIDEO_D3D11_H_HEADER_GUARD + +struct ID3D11Device; +struct ID3D11DeviceContext; + +namespace bgfx { namespace d3d11 +{ + struct RendererContextD3D11; + struct TextureD3D11; + struct VideoDecoderD3D11; + + struct VideoBindingD3D11 + { + ID3D11Device* device; + ID3D11DeviceContext* deviceCtx; + }; + + void initVideoDecoder(const VideoBindingD3D11& _binding); + + VideoDecoderD3D11* videoDecoderCreate(const VideoDecoderInit& _init, RendererContextD3D11* _renderer, uint16_t _width, uint16_t _height); + void videoDecoderDestroy(VideoDecoderD3D11* _decoder); + bool videoDecoderDecode(VideoDecoderD3D11* _decoder, const VideoDecoderFrame& _frame, TextureD3D11& _dst); + +} } // namespace bgfx::d3d11 + +#endif // BGFX_VIDEO_D3D11_H_HEADER_GUARD diff --git a/src/video_d3d12.cpp b/src/video_d3d12.cpp new file mode 100644 index 000000000..53a615a0c --- /dev/null +++ b/src/video_d3d12.cpp @@ -0,0 +1,1840 @@ +/* + * Copyright 2011-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE + */ + +#include "bgfx_p.h" + +#if BGFX_CONFIG_RENDERER_DIRECT3D12 +# include "renderer_d3d12.h" +# include "video_d3d12.h" + +# if BGFX_CONFIG_VIDEO_DIRECT3D12 +# include "video.h" +# include + +namespace bgfx { namespace d3d12 +{ + struct RendererContextD3D12; + + void initVideoDecoder(const VideoBindingD3D12& _binding); + ID3D12PipelineState* videoGetPipelineState(RendererContextD3D12* _renderer, ProgramHandle _handle); + ScratchBufferD3D12& videoGetScratchBuffer(RendererContextD3D12* _renderer); + D3D12_GPU_DESCRIPTOR_HANDLE videoGetSamplerHandle(RendererContextD3D12* _renderer, const uint32_t* _samplerFlags); + ID3D12GraphicsCommandList* videoGetCommandList(RendererContextD3D12* _renderer); + void videoReleaseResource(RendererContextD3D12* _renderer, ID3D12Resource* _resource); + + static VideoBindingD3D12 s_videoD3D12; + + static const GUID IID_ID3D12CommandAllocator = { 0x6102dee4, 0xaf59, 0x4b09, { 0xb9, 0x99, 0xb4, 0x4d, 0x73, 0xf0, 0x9b, 0x24 } }; + static const GUID IID_ID3D12CommandQueue = { 0x0ec870a6, 0x5d7e, 0x4c22, { 0x8c, 0xfc, 0x5b, 0xaa, 0xe0, 0x76, 0x16, 0xed } }; + static const GUID IID_ID3D12Fence = { 0x0a753dcf, 0xc4d8, 0x4b91, { 0xad, 0xf6, 0xbe, 0x5a, 0x60, 0xd9, 0x5a, 0x76 } }; + static const GUID IID_ID3D12Resource = { 0x696442be, 0xa72e, 0x4059, { 0xbc, 0x79, 0x5b, 0x5c, 0x98, 0x04, 0x0f, 0xad } }; + static const GUID IID_ID3D12VideoDevice = { 0x1f052807, 0x0b46, 0x4acc, { 0x8a, 0x89, 0x36, 0x4f, 0x79, 0x37, 0x18, 0xa4 } }; + static const GUID IID_ID3D12VideoDecoder = { 0xc59b6bdc, 0x7720, 0x4074, { 0xa1, 0x36, 0x17, 0xa1, 0x56, 0x03, 0x74, 0x70 } }; + static const GUID IID_ID3D12VideoDecoderHeap = { 0x0946b7c9, 0xebf6, 0x4047, { 0xbb, 0x73, 0x86, 0x83, 0xe2, 0x7d, 0xbb, 0x1f } }; + static const GUID IID_ID3D12VideoDecodeCommandList = { 0x3b60536e, 0xad29, 0x4e64, { 0xa2, 0x69, 0xf8, 0x53, 0x83, 0x7e, 0x5e, 0x53 } }; + static const GUID IID_ID3D12GraphicsCommandList = { 0x5b160d0f, 0xac1b, 0x4185, { 0x8b, 0xa8, 0xb3, 0xae, 0x42, 0xa5, 0xa4, 0x55 } }; + static const GUID D3D12_VIDEO_DECODE_PROFILE_H264 = { 0x1b81be68, 0xa0c7, 0x11d3, { 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5 } }; + static const GUID D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN = { 0x5b11d51b, 0x2f4c, 0x4452, { 0xbc, 0xc3, 0x09, 0xf2, 0xa1, 0x16, 0x0c, 0xc0 } }; + static const GUID D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN10 = { 0x107af0e0, 0xef1a, 0x4d19, { 0xab, 0xa8, 0x67, 0xa1, 0x63, 0x07, 0x3d, 0x13 } }; + static const GUID D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN12 = { 0x1a72925f, 0x0c2c, 0x4f15, { 0x96, 0xfb, 0xb1, 0x7d, 0x14, 0x73, 0x60, 0x3f } }; + static const GUID D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN10_422 = { 0x0bac4fe5, 0x1532, 0x4429, { 0xa8, 0x54, 0xf8, 0x4d, 0xe0, 0x49, 0x53, 0xdb } }; + static const GUID D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN12_422 = { 0x55bcac81, 0xf311, 0x4093, { 0xa7, 0xd0, 0x1c, 0xbc, 0x0b, 0x84, 0x9b, 0xee } }; + static const GUID D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN_444 = { 0x4008018f, 0xf537, 0x4b36, { 0x98, 0xcf, 0x61, 0xaf, 0x8a, 0x2c, 0x1a, 0x33 } }; + static const GUID D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN10_444 = { 0x0dabeffa, 0x4458, 0x4602, { 0xbc, 0x03, 0x07, 0x95, 0x65, 0x9d, 0x61, 0x7c } }; + static const GUID D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN12_444 = { 0x9798634d, 0xfe9d, 0x48e5, { 0xb4, 0xda, 0xdb, 0xec, 0x45, 0xb3, 0xdf, 0x01 } }; + static const GUID D3D12_VIDEO_DECODE_PROFILE_AV1_PROFILE0 = { 0xb8be4ccb, 0xcf53, 0x46ba, { 0x8d, 0x59, 0xd6, 0xb8, 0xa6, 0xda, 0x5d, 0x2a } }; + static const GUID D3D12_VIDEO_DECODE_PROFILE_AV1_PROFILE1 = { 0x6936ff0f, 0x45b1, 0x4163, { 0x9c, 0xc1, 0x64, 0x6e, 0xf6, 0x94, 0x61, 0x08 } }; + static const GUID D3D12_VIDEO_DECODE_PROFILE_AV1_PROFILE2 = { 0x0c5f2aa1, 0xe541, 0x4089, { 0xbb, 0x7b, 0x98, 0x11, 0x0a, 0x19, 0xd7, 0xc8 } }; + static const GUID D3D12_VIDEO_DECODE_PROFILE_AV1_12BIT_PROFILE2 = { 0x17127009, 0xa00f, 0x4ce1, { 0x99, 0x4e, 0xbf, 0x40, 0x81, 0xf6, 0xf3, 0xf0 } }; + static const GUID D3D12_VIDEO_DECODE_PROFILE_AV1_12BIT_PROFILE2_420 = { 0x2d80bed6, 0x9cac, 0x4835, { 0x9e, 0x91, 0x32, 0x7b, 0xbc, 0x4f, 0x9e, 0xe8 } }; + + struct VideoDecoderD3D12 + { + static constexpr uint32_t kNumDpbSlots = 17; + static constexpr uint32_t kBitstreamRingFrames = 8; + static constexpr uint32_t kCopyRingSize = 16; + + struct ReorderedPicture + { + ID3D12Resource* texture; + int32_t displayOrder; + int64_t ptsUs; + }; + + struct DpbState + { + D3D12_RESOURCE_STATES state; + int32_t poc; + int32_t frameNum; + }; + + VideoDecoderD3D12() + : m_renderer(NULL) + , m_videoDevice(NULL) + , m_decoder(NULL) + , m_decoderHeap(NULL) + , m_videoQueue(NULL) + , m_videoFence(NULL) + , m_videoFenceValue(0) + , m_videoControl(BX_COUNTOF(m_videoCmdAllocator) ) + , m_copyControl(BX_COUNTOF(m_copyCmdAllocator) ) + , m_bitstreamBuffer(NULL) + , m_bitstreamMapped(NULL) + , m_bitstreamCapacity(0) + , m_bitstreamCursor(0) + , m_dummyCbv(NULL) + , m_dpb(NULL) + , m_dpbOutput(NULL) + , m_referenceOnly(false) + , m_codedWidth(0) + , m_codedHeight(0) + , m_dstWidth(0) + , m_dstHeight(0) + , m_numDpbSlots(kNumDpbSlots) + , m_nextSlot(0) + , m_currentSlot(0) + , m_numActiveRefs(0) + , m_displayOrderNext(0) + , m_prevPocLsb(0) + , m_prevPocMsb(0) + , m_displayedSlot(-1) + , m_prevDisplayedSlot(-1) + , m_statusReportFeedback(0) + , m_initFlags(0) + , m_cachedAuBytes(0) + { + bx::memSet(m_dpbState, 0, sizeof(m_dpbState) ); + bx::memSet(m_referenceUsage, 0, sizeof(m_referenceUsage) ); + bx::memSet(&m_spsActive, 0, sizeof(m_spsActive) ); + + for (uint32_t ii = 0; ii < kBitstreamRingFrames; ++ii) + { + m_videoCmdAllocator[ii] = NULL; + m_videoCmdAllocatorFence[ii] = 0; + } + + m_videoCmd = NULL; + + for (uint32_t ii = 0; ii < kCopyRingSize; ++ii) + { + m_copyCmdAllocator[ii] = NULL; + m_copyFenceRing[ii] = 0; + } + + m_copyCmdList = NULL; + m_copyFence = NULL; + m_copyFenceValue = 0; + + bx::memSet(m_slotLastCopyFence, 0, sizeof(m_slotLastCopyFence) ); + + m_lastDpbOutputCopyFence = 0; + + for (uint32_t ii = 0; ii < kVideoMaxReorderInFlight; ++ii) + { + m_reorderPool[ii].texture = NULL; + m_reorderPool[ii].displayOrder = -1; + } + } + + bool create(const VideoDecoderInit& _init, RendererContextD3D12* _renderer, uint16_t _width, uint16_t _height) + { + m_renderer = _renderer; + m_initFlags = _init.flags; + m_cachedAuBytes = (0 != _init.cachedAuBytes) ? _init.cachedAuBytes : (4u << 20); + m_auQueue.configure(m_cachedAuBytes, 0 != (m_initFlags & BGFX_VIDEO_DECODER_INIT_RETAIN) ); + + if (VideoCodec::H264 != _init.codec) + { + BX_TRACE("VideoDecoderD3D12: codec %d not yet supported on D3D12.", _init.codec); + return false; + } + + if (NULL == _init.parameterSets + || 0 == _init.parameterSetsSize) + { + BX_TRACE("VideoDecoderD3D12: empty parameter sets."); + return false; + } + + h264::PPS ppsActive = {}; + if (!bgfx::parseParameterSets(m_spsArray, BX_COUNTOF(m_spsArray), m_ppsArray, BX_COUNTOF(m_ppsArray), m_spsActive, ppsActive, _init.parameterSets, _init.parameterSetsSize) ) + { + BX_TRACE("VideoDecoderD3D12: failed to parse SPS/PPS from parameter sets."); + return false; + } + + if (0 != m_spsActive.bit_depth_luma_minus8 + || 0 != m_spsActive.bit_depth_chroma_minus8 + || 1 != m_spsActive.chroma_format_idc) + { + BX_TRACE("VideoDecoderD3D12: only 8-bit 4:2:0 (NV12) is supported."); + return false; + } + + HRESULT hr = s_videoD3D12.device->QueryInterface(IID_ID3D12VideoDevice, (void**)&m_videoDevice); + if (FAILED(hr) + || NULL == m_videoDevice) + { + BX_TRACE("VideoDecoderD3D12: ID3D12VideoDevice not available."); + return false; + } + + m_dstWidth = _width; + m_dstHeight = _height; + + m_codedWidth = (m_spsActive.pic_width_in_mbs_minus1 + 1) * 16; + m_codedHeight = (m_spsActive.pic_height_in_map_units_minus1 + 1) * 16 * (m_spsActive.frame_mbs_only_flag ? 1 : 2); + + m_numDpbSlots = bx::clamp(uint32_t(m_spsActive.num_ref_frames) + 1, 2, kNumDpbSlots); + const DXGI_FORMAT decodeFormat = DXGI_FORMAT_NV12; + + D3D12_VIDEO_DECODER_DESC decoderDesc + { + .NodeMask = 0, + .Configuration = + { + .DecodeProfile = D3D12_VIDEO_DECODE_PROFILE_H264, + .BitstreamEncryption = {}, + .InterlaceType = D3D12_VIDEO_FRAME_CODED_INTERLACE_TYPE_NONE, + }, + }; + + D3D12_FEATURE_DATA_VIDEO_DECODE_SUPPORT decodeSupport + { + .NodeIndex = 0, + .Configuration = decoderDesc.Configuration, + .Width = m_codedWidth, + .Height = m_codedHeight, + .DecodeFormat = decodeFormat, + .FrameRate = { 0, 1 }, + .BitRate = 0, + .SupportFlags = D3D12_VIDEO_DECODE_SUPPORT_FLAG_NONE, + .ConfigurationFlags = D3D12_VIDEO_DECODE_CONFIGURATION_FLAG_NONE, + .DecodeTier = D3D12_VIDEO_DECODE_TIER_NOT_SUPPORTED, + }; + + hr = m_videoDevice->CheckFeatureSupport(D3D12_FEATURE_VIDEO_DECODE_SUPPORT, &decodeSupport, sizeof(decodeSupport) ); + + if (FAILED(hr) + || 0 == (decodeSupport.SupportFlags & D3D12_VIDEO_DECODE_SUPPORT_FLAG_SUPPORTED) + || decodeSupport.DecodeTier < D3D12_VIDEO_DECODE_TIER_1) + { + BX_TRACE("VideoDecoderD3D12: H.264 NV12 not supported by hardware (tier=%d, hr=0x%08x).", decodeSupport.DecodeTier, hr); + DX_RELEASE_I(m_videoDevice); + return false; + } + + m_referenceOnly = 0 != (decodeSupport.ConfigurationFlags & D3D12_VIDEO_DECODE_CONFIGURATION_FLAG_REFERENCE_ONLY_ALLOCATIONS_REQUIRED); + + const uint16_t vendorId = s_videoD3D12.vendorId; + if (BGFX_PCI_ID_INTEL == vendorId) + { + if (!m_referenceOnly) + { + BX_TRACE("VideoDecoderD3D12: Intel detected (vendorId=0x%04x): forcing reference-only DPB.", vendorId); + } + m_referenceOnly = true; + } + + uint32_t heapWidth = m_codedWidth; + uint32_t heapHeight = m_codedHeight; + if (0 != (decodeSupport.ConfigurationFlags & D3D12_VIDEO_DECODE_CONFIGURATION_FLAG_HEIGHT_ALIGNMENT_MULTIPLE_32_REQUIRED) ) + { + heapWidth = bx::strideAlign(heapWidth, 32); + heapHeight = bx::strideAlign(heapHeight, 32); + } + + D3D12_VIDEO_DECODER_HEAP_DESC heapDesc + { + .NodeMask = 0, + .Configuration = decoderDesc.Configuration, + .DecodeWidth = heapWidth, + .DecodeHeight = heapHeight, + .Format = decodeFormat, + .FrameRate = { 0, 1 }, + .BitRate = 0, + .MaxDecodePictureBufferCount = m_numDpbSlots, + }; + hr = m_videoDevice->CreateVideoDecoderHeap(&heapDesc, IID_ID3D12VideoDecoderHeap, (void**)&m_decoderHeap); + + if (FAILED(hr) ) + { + BX_TRACE("VideoDecoderD3D12: CreateVideoDecoderHeap failed (0x%08x).", hr); + destroy(); + return false; + } + + hr = m_videoDevice->CreateVideoDecoder(&decoderDesc, IID_ID3D12VideoDecoder, (void**)&m_decoder); + + if (FAILED(hr) ) + { + BX_TRACE("VideoDecoderD3D12: CreateVideoDecoder failed (0x%08x).", hr); + destroy(); + return false; + } + + D3D12_COMMAND_QUEUE_DESC queueDesc + { + .Type = D3D12_COMMAND_LIST_TYPE_VIDEO_DECODE, + .Priority = 0, + .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE, + .NodeMask = 0, + }; + + hr = s_videoD3D12.device->CreateCommandQueue(&queueDesc, IID_ID3D12CommandQueue, (void**)&m_videoQueue); + + if (FAILED(hr) ) + { + BX_TRACE("VideoDecoderD3D12: video CreateCommandQueue failed (0x%08x).", hr); + destroy(); + return false; + } + + for (uint32_t ii = 0; ii < kBitstreamRingFrames; ++ii) + { + hr = s_videoD3D12.device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_VIDEO_DECODE, IID_ID3D12CommandAllocator, (void**)&m_videoCmdAllocator[ii]); + if (FAILED(hr) ) + { + BX_TRACE("VideoDecoderD3D12: video CreateCommandAllocator failed (0x%08x).", hr); + destroy(); + return false; + } + } + + hr = s_videoD3D12.device->CreateCommandList(0 + , D3D12_COMMAND_LIST_TYPE_VIDEO_DECODE + , m_videoCmdAllocator[0] + , NULL + , IID_ID3D12VideoDecodeCommandList + , (void**)&m_videoCmd + ); + + if (FAILED(hr) ) + { + BX_TRACE("VideoDecoderD3D12: CreateCommandList(VIDEO_DECODE) failed (0x%08x).", hr); + destroy(); + return false; + } + + m_videoCmd->Close(); + + hr = s_videoD3D12.device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_ID3D12Fence, (void**)&m_videoFence); + + if (FAILED(hr) ) + { + BX_TRACE("VideoDecoderD3D12: CreateFence failed (0x%08x).", hr); + destroy(); + return false; + } + + for (uint32_t ii = 0; ii < kCopyRingSize; ++ii) + { + hr = s_videoD3D12.device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_ID3D12CommandAllocator, (void**)&m_copyCmdAllocator[ii]); + if (FAILED(hr) ) + { + BX_TRACE("VideoDecoderD3D12: copy CreateCommandAllocator failed (0x%08x).", hr); + destroy(); + return false; + } + } + + hr = s_videoD3D12.device->CreateCommandList(0 + , D3D12_COMMAND_LIST_TYPE_DIRECT + , m_copyCmdAllocator[0] + , NULL + , IID_ID3D12GraphicsCommandList + , (void**)&m_copyCmdList + ); + + if (FAILED(hr) ) + { + BX_TRACE("VideoDecoderD3D12: CreateCommandList(DIRECT) failed (0x%08x).", hr); + destroy(); + return false; + } + + m_copyCmdList->Close(); + + hr = s_videoD3D12.device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_ID3D12Fence, (void**)&m_copyFence); + + if (FAILED(hr) ) + { + BX_TRACE("VideoDecoderD3D12: copy CreateFence failed (0x%08x).", hr); + destroy(); + return false; + } + + for (uint32_t ii = 0; ii < kCopyRingSize; ++ii) + { + m_copyFenceRing[ii] = 0; + } + + m_copyFenceValue = 0; + m_videoControl.reset(); + m_copyControl.reset(); + bx::memSet(m_slotLastCopyFence, 0, sizeof(m_slotLastCopyFence) ); + + const uint32_t bitstreamBudget = bx::max(2u*1024u*1024u, m_codedWidth * m_codedHeight); + m_bitstreamCapacity = bx::alignUp(uint64_t(bitstreamBudget) * kBitstreamRingFrames, D3D12_VIDEO_DECODE_MIN_BITSTREAM_OFFSET_ALIGNMENT); + + { + D3D12_RESOURCE_DESC desc + { + .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER, + .Alignment = 0, + .Width = m_bitstreamCapacity, + .Height = 1, + .DepthOrArraySize = 1, + .MipLevels = 1, + .Format = DXGI_FORMAT_UNKNOWN, + .SampleDesc = { .Count = 1, .Quality = 0 }, + .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR, + .Flags = D3D12_RESOURCE_FLAG_NONE, + }; + + D3D12_HEAP_PROPERTIES heap + { + .Type = D3D12_HEAP_TYPE_UPLOAD, + .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN, + .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN, + .CreationNodeMask = 0, + .VisibleNodeMask = 0, + }; + + hr = s_videoD3D12.device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, NULL, IID_ID3D12Resource, (void**)&m_bitstreamBuffer); + + if (FAILED(hr) ) + { + BX_TRACE("VideoDecoderD3D12: bitstream buffer create failed (0x%08x).", hr); + destroy(); + return false; + } + + D3D12_RANGE noRead = { 0, 0 }; + hr = m_bitstreamBuffer->Map(0, &noRead, (void**)&m_bitstreamMapped); + + if (FAILED(hr) ) + { + BX_TRACE("VideoDecoderD3D12: bitstream Map failed (0x%08x).", hr); + destroy(); + return false; + } + } + + { + D3D12_RESOURCE_DESC desc + { + .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER, + .Alignment = 0, + .Width = 256, + .Height = 1, + .DepthOrArraySize = 1, + .MipLevels = 1, + .Format = DXGI_FORMAT_UNKNOWN, + .SampleDesc = { .Count = 1, .Quality = 0 }, + .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR, + .Flags = D3D12_RESOURCE_FLAG_NONE, + }; + + D3D12_HEAP_PROPERTIES heap + { + .Type = D3D12_HEAP_TYPE_UPLOAD, + .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN, + .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN, + .CreationNodeMask = 0, + .VisibleNodeMask = 0, + }; + + hr = s_videoD3D12.device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, NULL, IID_ID3D12Resource, (void**)&m_dummyCbv); + + if (FAILED(hr) ) + { + BX_TRACE("VideoDecoderD3D12: dummy CBV create failed (0x%08x).", hr); + destroy(); + return false; + } + } + + { + D3D12_RESOURCE_DESC desc + { + .Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D, + .Alignment = 0, + .Width = m_codedWidth, + .Height = m_codedHeight, + .DepthOrArraySize = uint16_t(m_numDpbSlots), + .MipLevels = 1, + .Format = decodeFormat, + .SampleDesc = { .Count = 1, .Quality = 0 }, + .Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN, + .Flags = m_referenceOnly + ? (D3D12_RESOURCE_FLAG_VIDEO_DECODE_REFERENCE_ONLY | D3D12_RESOURCE_FLAG_DENY_SHADER_RESOURCE) + : D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS + , + }; + + D3D12_HEAP_PROPERTIES heap + { + .Type = D3D12_HEAP_TYPE_DEFAULT, + .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN, + .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN, + .CreationNodeMask = 0, + .VisibleNodeMask = 0, + }; + + hr = s_videoD3D12.device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COMMON, NULL, IID_ID3D12Resource, (void**)&m_dpb); + + if (FAILED(hr) ) + { + BX_TRACE("VideoDecoderD3D12: DPB texture create failed (0x%08x).", hr); + destroy(); + return false; + } + + if (m_referenceOnly) + { + // AMD path: separate output texture (sampleable NV12). + desc.DepthOrArraySize = 1; + desc.Flags = D3D12_RESOURCE_FLAG_NONE; + hr = s_videoD3D12.device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COMMON, NULL, IID_ID3D12Resource, (void**)&m_dpbOutput); + if (FAILED(hr) ) + { + BX_TRACE("VideoDecoderD3D12: DPB output texture create failed (0x%08x).", hr); + destroy(); + return false; + } + } + } + + for (uint32_t ii = 0; ii < m_numDpbSlots; ++ii) + { + m_dpbState[ii].state = D3D12_RESOURCE_STATE_COMMON; + } + + BX_TRACE("VideoDecoderD3D12: H.264 decoder ready, coded %dx%d, dst %dx%d, %s DPB." + , m_codedWidth, m_codedHeight + , m_dstWidth, m_dstHeight + , m_referenceOnly ? "reference-only" : "shared" + ); + + return true; + } + + void destroy() + { + if (NULL != m_videoFence + && NULL != m_videoQueue) + { + const uint64_t fence = ++m_videoFenceValue; + m_videoQueue->Signal(m_videoFence, fence); + + if (m_videoFence->GetCompletedValue() < fence) + { + HANDLE evt = CreateEventA(NULL, FALSE, FALSE, NULL); + m_videoFence->SetEventOnCompletion(fence, evt); + WaitForSingleObject(evt, INFINITE); + CloseHandle(evt); + } + } + + if (NULL != m_copyFence + && NULL != s_videoD3D12.commandQueue) + { + const uint64_t fence = ++m_copyFenceValue; + s_videoD3D12.commandQueue->Signal(m_copyFence, fence); + + if (m_copyFence->GetCompletedValue() < fence) + { + HANDLE evt = CreateEventA(NULL, FALSE, FALSE, NULL); + m_copyFence->SetEventOnCompletion(fence, evt); + WaitForSingleObject(evt, INFINITE); + CloseHandle(evt); + } + } + + DX_RELEASE(m_videoCmd, 0); + DX_RELEASE(m_copyCmdList, 0); + + for (uint32_t ii = 0; ii < kBitstreamRingFrames; ++ii) + { + if (NULL != m_videoCmdAllocator[ii]) + { + m_videoCmdAllocator[ii]->Reset(); + } + } + + for (uint32_t ii = 0; ii < kCopyRingSize; ++ii) + { + if (NULL != m_copyCmdAllocator[ii]) + { + m_copyCmdAllocator[ii]->Reset(); + } + } + + for (uint32_t ii = 0; ii < kVideoMaxReorderInFlight; ++ii) + { + if (NULL != m_reorderPool[ii].texture) + { + videoReleaseResource(m_renderer, m_reorderPool[ii].texture); + m_reorderPool[ii].texture = NULL; + } + + m_reorderPool[ii].displayOrder = -1; + } + + if (NULL != m_bitstreamBuffer + && NULL != m_bitstreamMapped) + { + D3D12_RANGE noWrite = { 0, 0 }; + m_bitstreamBuffer->Unmap(0, &noWrite); + m_bitstreamMapped = NULL; + } + + DX_RELEASE(m_bitstreamBuffer, 0); + DX_RELEASE(m_dummyCbv, 0); + DX_RELEASE(m_dpbOutput, 0); + DX_RELEASE(m_dpb, 0); + + for (uint32_t ii = 0; ii < kBitstreamRingFrames; ++ii) + { + DX_RELEASE(m_videoCmdAllocator[ii], 0); + } + + for (uint32_t ii = 0; ii < kCopyRingSize; ++ii) + { + DX_RELEASE(m_copyCmdAllocator[ii], 0); + } + + DX_RELEASE(m_copyFence, 0); + DX_RELEASE(m_videoFence, 0); + DX_RELEASE(m_videoQueue, 0); + DX_RELEASE(m_decoder, 0); + DX_RELEASE(m_decoderHeap, 0); + + DX_RELEASE_I(m_videoDevice); + } + + bool decode(const VideoDecoderFrame& _frame, TextureD3D12& _dst) + { + if (NULL == m_decoder) + { + return false; + } + + if (0 != (_frame.flags & BGFX_VIDEO_DECODE_FRAME_SET) ) + { + resetForSet(); + } + + if (NULL != _frame.bitstream && 0 != _frame.numAus) + { + const uint8_t* bs = _frame.bitstream; + for (uint32_t ii = 0; ii < _frame.numAus; ++ii) + { + const uint32_t auSize = _frame.aus[ii].size; + const bool isIdr = bgfx::peekAuIsIdr(bs, auSize); + m_auQueue.enqueue(bs, auSize, _frame.aus[ii].ptsUs, isIdr); + bs += auSize; + } + } + + while (!m_auQueue.empty() + && bgfx::hasReorderSpace(m_reorderPool, kVideoMaxReorderInFlight) ) + { + const bgfx::PendingAU& au = m_auQueue.front(); + decodeOneAU(au.data.data(), uint32_t(au.data.size() ), au.ptsUs); + m_auQueue.pop(); + } + + m_auQueue.compact(); + + if (0 == _frame.numAus + && 0 == (_frame.flags & BGFX_VIDEO_DECODE_FRAME_NO_BLIT) ) + { + int64_t adjustedClock = _frame.presentationTimeUs; + + if (0 != (_frame.flags & BGFX_VIDEO_DECODE_FRAME_LOOP) + && m_auQueue.isRetaining() + && m_auQueue.minPts() < m_auQueue.maxPts() ) + { + adjustedClock = bgfx::applyLoopWrap(adjustedClock, m_auQueue.minPts(), m_auQueue.maxPts() ); + + const bool poolCovers = bgfx::reorderPoolCovers(m_reorderPool, kVideoMaxReorderInFlight, adjustedClock, 500000); + const bool forwardCanCatchUp = true + && !m_auQueue.empty() + && m_auQueue.front().ptsUs <= adjustedClock + 500000 + ; + + if (!poolCovers + && !forwardCanCatchUp) + { + bgfx::faultInFromCache( + m_auQueue + , adjustedClock + , [this]() { resetForSet(); } + , [this](const uint8_t* _bs, uint32_t _bsSize, int64_t _pts) + { + decodeOneAU(_bs, _bsSize, _pts); + } + , [this]() + { + return bgfx::hasReorderSpace(m_reorderPool, kVideoMaxReorderInFlight); + } + ); + } + } + + pickDisplaySlotForTime(adjustedClock, _dst); + } + + return true; + } + + void resetForSet() + { + if (m_auQueue.isRetaining() ) + { + m_auQueue.rewindTo(m_auQueue.size() ); + } + else + { + m_auQueue.clear(); + } + + for (uint32_t ii = 0; ii < kVideoMaxReorderInFlight; ++ii) + { + m_reorderPool[ii].displayOrder = -1; + m_reorderPool[ii].ptsUs = 0; + } + + m_displayedSlot = -1; + m_prevDisplayedSlot = -1; + m_displayOrderNext = 0; + m_numActiveRefs = 0; + m_nextSlot = 0; + m_currentSlot = 0; + bx::memSet(m_referenceUsage, 0, sizeof(m_referenceUsage) ); + + for (uint32_t ii = 0; ii < kNumDpbSlots; ++ii) + { + m_dpbState[ii].poc = 0; + m_dpbState[ii].frameNum = 0; + } + + m_prevPocLsb = 0; + m_prevPocMsb = 0; + } + + bool decodeOneAU(const uint8_t* _bitstream, uint32_t _bitstreamSize, int64_t _ptsUs) + { + h264::NALHeader nal = {}; + h264::SliceHeader sh = {}; + bool isIdr = false; + if (!bgfx::parseSliceFromAccessUnit(m_spsArray, m_ppsArray, _bitstream, _bitstreamSize, nal, sh, isIdr) ) + { + BX_TRACE("VideoDecoderD3D12: no slice NAL found in access unit."); + return false; + } + + const h264::PPS& pps = m_ppsArray[sh.pic_parameter_set_id]; + const h264::SPS& sps = m_spsArray[pps.seq_parameter_set_id]; + + if (isIdr) + { + m_numActiveRefs = 0; + m_nextSlot = 0; + } + + const int32_t poc = bgfx::computePoc(m_prevPocMsb, m_prevPocLsb, sps, sh, nal, isIdr); + + m_currentSlot = bgfx::pickFreeDpbSlot( + m_nextSlot + , m_numDpbSlots + , m_referenceUsage + , m_numActiveRefs + , [](uint32_t){ return false; } + ); + BX_ASSERT(m_currentSlot < m_numDpbSlots + , "DPB exhausted: m_numDpbSlots=%u m_numActiveRefs=%u. Cap maxRefs to m_numDpbSlots-1." + , m_numDpbSlots + , m_numActiveRefs + ); + m_nextSlot = (m_currentSlot + 1) % m_numDpbSlots; + + const uint32_t reorderSlot = bgfx::pickReorderSlotOrEvictOldest(m_reorderPool, kVideoMaxReorderInFlight); + const int32_t displayOrder = m_displayOrderNext++; + m_reorderPool[reorderSlot].displayOrder = displayOrder; + m_reorderPool[reorderSlot].ptsUs = _ptsUs; + + m_dpbState[m_currentSlot].poc = poc; + m_dpbState[m_currentSlot].frameNum = sh.frame_num; + + while (0 == m_videoControl.reserve(1) ) + { + const uint32_t readIdx = m_videoControl.m_read; + const uint64_t waitFence = m_videoCmdAllocatorFence[readIdx]; + if (waitFence > m_videoFence->GetCompletedValue() ) + { + HANDLE evt = CreateEventA(NULL, FALSE, FALSE, NULL); + m_videoFence->SetEventOnCompletion(waitFence, evt); + WaitForSingleObject(evt, INFINITE); + CloseHandle(evt); + } + m_videoControl.consume(1); + } + + const uint32_t allocIdx = m_videoControl.m_current; + const uint32_t sliceOffsetInAu = bgfx::findFirstSliceNalOffset(_bitstream, _bitstreamSize); + const uint8_t* sliceBitstream = _bitstream + sliceOffsetInAu; + const uint32_t sliceBitstreamSize = _bitstreamSize - sliceOffsetInAu; + + const uint32_t alignedSize = bx::alignUp(sliceBitstreamSize, D3D12_VIDEO_DECODE_MIN_BITSTREAM_OFFSET_ALIGNMENT); + if (m_bitstreamCursor + alignedSize > m_bitstreamCapacity) + { + m_bitstreamCursor = 0; + } + + const uint64_t bitstreamOffset = m_bitstreamCursor; + bx::memCopy(m_bitstreamMapped + bitstreamOffset, sliceBitstream, sliceBitstreamSize); + m_bitstreamCursor += alignedSize; + + HRESULT hr = m_videoCmdAllocator[allocIdx]->Reset(); + BX_WARN(SUCCEEDED(hr), "VideoDecoderD3D12: video allocator Reset failed (0x%08x).", hr); + hr = m_videoCmd->Reset(m_videoCmdAllocator[allocIdx]); + BX_WARN(SUCCEEDED(hr), "VideoDecoderD3D12: video cmdlist Reset failed (0x%08x).", hr); + + if (m_dpbState[m_currentSlot].state != D3D12_RESOURCE_STATE_VIDEO_DECODE_WRITE) + { + D3D12_RESOURCE_BARRIER barriers[2] + { + { + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + .Transition = + { + .pResource = m_dpb, + .Subresource = m_currentSlot, + .StateBefore = m_dpbState[m_currentSlot].state, + .StateAfter = D3D12_RESOURCE_STATE_VIDEO_DECODE_WRITE, + }, + }, + { + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + .Transition = + { + .pResource = m_dpb, + .Subresource = m_numDpbSlots + m_currentSlot, + .StateBefore = m_dpbState[m_currentSlot].state, + .StateAfter = D3D12_RESOURCE_STATE_VIDEO_DECODE_WRITE, + }, + }, + }; + m_videoCmd->ResourceBarrier(BX_COUNTOF(barriers), barriers); + m_dpbState[m_currentSlot].state = D3D12_RESOURCE_STATE_VIDEO_DECODE_WRITE; + } + + if (m_referenceOnly) + { + D3D12_RESOURCE_BARRIER barrier + { + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + .Transition = + { + .pResource = m_dpbOutput, + .Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, + .StateBefore = D3D12_RESOURCE_STATE_COMMON, + .StateAfter = D3D12_RESOURCE_STATE_VIDEO_DECODE_WRITE, + }, + }; + m_videoCmd->ResourceBarrier(1, &barrier); + } + + DXVA_PicParams_H264 picParams = {}; + DXVA_Qmatrix_H264 qmatrix = {}; + DXVA_Slice_H264_Short sliceCtl = {}; + + picParams.wFrameWidthInMbsMinus1 = uint16_t(sps.pic_width_in_mbs_minus1); + picParams.wFrameHeightInMbsMinus1 = uint16_t(sps.pic_height_in_map_units_minus1); + picParams.IntraPicFlag = isIdr ? 1 : 0; + picParams.MbaffFrameFlag = 0; + picParams.field_pic_flag = 0; + picParams.chroma_format_idc = 1; + picParams.bit_depth_chroma_minus8 = uint8_t(sps.bit_depth_chroma_minus8); + picParams.bit_depth_luma_minus8 = uint8_t(sps.bit_depth_luma_minus8); + picParams.residual_colour_transform_flag = uint8_t(sps.separate_colour_plane_flag); + picParams.CurrPic.AssociatedFlag = 0; + picParams.CurrPic.Index7Bits = uint8_t(m_currentSlot); + picParams.CurrFieldOrderCnt[0] = poc; + picParams.CurrFieldOrderCnt[1] = poc; + + for (uint32_t ii = 0; ii < 16; ++ii) + { + picParams.RefFrameList[ii].bPicEntry = 0xFF; + picParams.FieldOrderCntList[ii][0] = 0; + picParams.FieldOrderCntList[ii][1] = 0; + picParams.FrameNumList[ii] = 0; + } + picParams.UsedForReferenceFlags = 0; + for (uint32_t ii = 0; ii < m_numActiveRefs && ii < 16; ++ii) + { + const uint32_t refSlot = m_referenceUsage[ii]; + picParams.RefFrameList[ii].AssociatedFlag = 0; + picParams.RefFrameList[ii].Index7Bits = uint8_t(refSlot); + picParams.FieldOrderCntList[ii][0] = m_dpbState[refSlot].poc; + picParams.FieldOrderCntList[ii][1] = m_dpbState[refSlot].poc; + picParams.FrameNumList[ii] = uint16_t(m_dpbState[refSlot].frameNum); + picParams.UsedForReferenceFlags |= 1u << (ii * 2 + 0); + picParams.UsedForReferenceFlags |= 1u << (ii * 2 + 1); + } + + picParams.weighted_pred_flag = uint8_t(pps.weighted_pred_flag); + picParams.weighted_bipred_idc = uint8_t(pps.weighted_bipred_idc); + picParams.sp_for_switch_flag = 0; + picParams.transform_8x8_mode_flag = uint8_t(pps.transform_8x8_mode_flag); + picParams.constrained_intra_pred_flag = uint8_t(pps.constrained_intra_pred_flag); + picParams.num_ref_frames = uint8_t(sps.num_ref_frames); + picParams.MbsConsecutiveFlag = 1; + picParams.frame_mbs_only_flag = uint8_t(sps.frame_mbs_only_flag); + picParams.MinLumaBipredSize8x8Flag = sps.level_idc >= 31 ? 1 : 0; + picParams.RefPicFlag = (0 != nal.idc) ? 1 : 0; + picParams.frame_num = uint16_t(sh.frame_num); + picParams.pic_init_qp_minus26 = int8_t(pps.pic_init_qp_minus26); + picParams.pic_init_qs_minus26 = int8_t(pps.pic_init_qs_minus26); + picParams.chroma_qp_index_offset = int8_t(pps.chroma_qp_index_offset); + picParams.second_chroma_qp_index_offset = int8_t(pps.second_chroma_qp_index_offset); + picParams.log2_max_frame_num_minus4 = uint8_t(sps.log2_max_frame_num_minus4); + picParams.pic_order_cnt_type = uint8_t(sps.pic_order_cnt_type); + picParams.log2_max_pic_order_cnt_lsb_minus4 = uint8_t(sps.log2_max_pic_order_cnt_lsb_minus4); + picParams.delta_pic_order_always_zero_flag = uint8_t(sps.delta_pic_order_always_zero_flag); + picParams.direct_8x8_inference_flag = uint8_t(sps.direct_8x8_inference_flag); + picParams.entropy_coding_mode_flag = uint8_t(pps.entropy_coding_mode_flag); + picParams.pic_order_present_flag = uint8_t(pps.pic_order_present_flag); + picParams.num_slice_groups_minus1 = uint8_t(pps.num_slice_groups_minus1); + picParams.slice_group_map_type = uint8_t(pps.slice_group_map_type); + picParams.deblocking_filter_control_present_flag = uint8_t(pps.deblocking_filter_control_present_flag); + picParams.redundant_pic_cnt_present_flag = uint8_t(pps.redundant_pic_cnt_present_flag); + picParams.slice_group_change_rate_minus1 = uint16_t(pps.slice_group_change_rate_minus1); + picParams.Reserved16Bits = 3; + picParams.StatusReportFeedbackNumber = ++m_statusReportFeedback; + picParams.ContinuationFlag = 1; + picParams.num_ref_idx_l0_active_minus1 = uint8_t(pps.num_ref_idx_l0_active_minus1); + picParams.num_ref_idx_l1_active_minus1 = uint8_t(pps.num_ref_idx_l1_active_minus1); + + if (sps.seq_scaling_matrix_present_flag || pps.pic_scaling_matrix_present_flag) + { + uint8_t lists4x4[6][16]; + uint8_t lists8x8[2][64]; + buildEffectiveScalingLists(sps, pps, lists4x4, lists8x8); + for (int i = 0; i < 6; ++i) + { + for (int j = 0; j < 16; ++j) + { + qmatrix.bScalingLists4x4[i][j] = lists4x4[i][j]; + } + } + for (int i = 0; i < 64; ++i) + { + qmatrix.bScalingLists8x8[0][i] = lists8x8[0][i]; + qmatrix.bScalingLists8x8[1][i] = lists8x8[1][i]; + } + } + else + { + bx::memSet(&qmatrix, 16, sizeof(qmatrix) ); + } + + sliceCtl.BSNALunitDataLocation = 0; + sliceCtl.SliceBytesInBuffer = sliceBitstreamSize; + sliceCtl.wBadSliceChopping = 0; + + ID3D12Resource* refFrames[kNumDpbSlots] = {}; + UINT refSubresources[kNumDpbSlots] = {}; + for (uint32_t ii = 0; ii < m_numDpbSlots; ++ii) + { + refFrames[ii] = m_dpb; + refSubresources[ii] = UINT(ii); + } + + D3D12_VIDEO_DECODE_INPUT_STREAM_ARGUMENTS input + { + .NumFrameArguments = 3, + .FrameArguments = + { + { .Type = D3D12_VIDEO_DECODE_ARGUMENT_TYPE_PICTURE_PARAMETERS, .Size = sizeof(picParams), .pData = &picParams }, + { .Type = D3D12_VIDEO_DECODE_ARGUMENT_TYPE_INVERSE_QUANTIZATION_MATRIX,.Size = sizeof(qmatrix), .pData = &qmatrix }, + { .Type = D3D12_VIDEO_DECODE_ARGUMENT_TYPE_SLICE_CONTROL, .Size = sizeof(sliceCtl), .pData = &sliceCtl }, + }, + .ReferenceFrames = + { + .NumTexture2Ds = kNumDpbSlots, + .ppTexture2Ds = refFrames, + .pSubresources = refSubresources, + .ppHeaps = NULL, + }, + .CompressedBitstream = + { + .pBuffer = m_bitstreamBuffer, + .Offset = bitstreamOffset, + .Size = sliceBitstreamSize, + }, + .pHeap = m_decoderHeap, + }; + + D3D12_VIDEO_DECODE_OUTPUT_STREAM_ARGUMENTS output = m_referenceOnly + ? D3D12_VIDEO_DECODE_OUTPUT_STREAM_ARGUMENTS + { + .pOutputTexture2D = m_dpbOutput, + .OutputSubresource = 0, + .ConversionArguments = + { + .Enable = TRUE, + .pReferenceTexture2D = m_dpb, + .ReferenceSubresource = m_currentSlot, + .OutputColorSpace = DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709, + .DecodeColorSpace = DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709, + }, + } + : D3D12_VIDEO_DECODE_OUTPUT_STREAM_ARGUMENTS + { + .pOutputTexture2D = m_dpb, + .OutputSubresource = m_currentSlot, + .ConversionArguments = {}, + } + ; + + m_videoCmd->DecodeFrame(m_decoder, &output, &input); + + if (m_referenceOnly) + { + D3D12_RESOURCE_BARRIER barrier + { + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + .Transition = + { + .pResource = m_dpbOutput, + .Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, + .StateBefore = D3D12_RESOURCE_STATE_VIDEO_DECODE_WRITE, + .StateAfter = D3D12_RESOURCE_STATE_COMMON, + }, + }; + m_videoCmd->ResourceBarrier(1, &barrier); + + D3D12_RESOURCE_BARRIER dpbBarriers[2] + { + { + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + .Transition = + { + .pResource = m_dpb, + .Subresource = m_currentSlot, + .StateBefore = D3D12_RESOURCE_STATE_VIDEO_DECODE_WRITE, + .StateAfter = D3D12_RESOURCE_STATE_COMMON, + }, + }, + { + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + .Transition = + { + .pResource = m_dpb, + .Subresource = m_numDpbSlots + m_currentSlot, + .StateBefore = D3D12_RESOURCE_STATE_VIDEO_DECODE_WRITE, + .StateAfter = D3D12_RESOURCE_STATE_COMMON, + }, + }, + }; + m_videoCmd->ResourceBarrier(BX_COUNTOF(dpbBarriers), dpbBarriers); + m_dpbState[m_currentSlot].state = D3D12_RESOURCE_STATE_COMMON; + } + else + { + D3D12_RESOURCE_BARRIER barriers[2] + { + { + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + .Transition = + { + .pResource = m_dpb, + .Subresource = m_currentSlot, + .StateBefore = D3D12_RESOURCE_STATE_VIDEO_DECODE_WRITE, + .StateAfter = D3D12_RESOURCE_STATE_COMMON, + }, + }, + { + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + .Transition = + { + .pResource = m_dpb, + .Subresource = m_numDpbSlots + m_currentSlot, + .StateBefore = D3D12_RESOURCE_STATE_VIDEO_DECODE_WRITE, + .StateAfter = D3D12_RESOURCE_STATE_COMMON, + }, + }, + }; + m_videoCmd->ResourceBarrier(BX_COUNTOF(barriers), barriers); + m_dpbState[m_currentSlot].state = D3D12_RESOURCE_STATE_COMMON; + } + + hr = m_videoCmd->Close(); + BX_WARN(SUCCEEDED(hr), "VideoDecoderD3D12: video Close failed (0x%08x).", hr); + + ID3D12CommandList* lists[] = { m_videoCmd }; + + if (m_slotLastCopyFence[m_currentSlot] > 0) + { + m_videoQueue->Wait(m_copyFence, m_slotLastCopyFence[m_currentSlot]); + } + + if (m_referenceOnly && m_lastDpbOutputCopyFence > 0) + { + m_videoQueue->Wait(m_copyFence, m_lastDpbOutputCopyFence); + } + + m_videoQueue->ExecuteCommandLists(BX_COUNTOF(lists), lists); + + const uint64_t signalValue = ++m_videoFenceValue; + m_videoQueue->Signal(m_videoFence, signalValue); + m_videoCmdAllocatorFence[allocIdx] = signalValue; + m_videoControl.commit(1); + + if (0 != nal.idc) + { + if (0 != sh.drpm.adaptive_ref_pic_marking_mode_flag + && m_numActiveRefs > 0) + { + uint16_t refFrameNums[16]; + const uint32_t n = bx::min(m_numActiveRefs, 16); + + for (uint32_t ii = 0; ii < n; ++ii) + { + refFrameNums[ii] = uint16_t(m_dpbState[m_referenceUsage[ii]].frameNum); + } + + const int32_t maxFrameNum = 1 << (sps.log2_max_frame_num_minus4 + 4); + + m_numActiveRefs = applyMmcoUnmarkShortTerm( + m_referenceUsage + , refFrameNums + , n + , sh.frame_num + , maxFrameNum + , sh + ); + } + + const uint32_t maxRefs = bx::min(uint32_t(sps.num_ref_frames), m_numDpbSlots - 1); + + m_numActiveRefs = bgfx::appendShortTermRef( + m_referenceUsage + , m_numActiveRefs + , m_currentSlot + , maxRefs + ); + } + + while (0 == m_copyControl.reserve(1) ) + { + const uint32_t readIdx = m_copyControl.m_read; + const uint64_t waitFence = m_copyFenceRing[readIdx]; + + if (m_copyFence->GetCompletedValue() < waitFence) + { + HANDLE evt = CreateEventA(NULL, FALSE, FALSE, NULL); + m_copyFence->SetEventOnCompletion(waitFence, evt); + WaitForSingleObject(evt, INFINITE); + CloseHandle(evt); + } + + m_copyControl.consume(1); + } + const uint32_t copyRingIdx = m_copyControl.m_current; + + HRESULT cres = m_copyCmdAllocator[copyRingIdx]->Reset(); + BX_WARN(SUCCEEDED(cres), "VideoDecoderD3D12: copy allocator Reset failed (0x%08x).", cres); + + cres = m_copyCmdList->Reset(m_copyCmdAllocator[copyRingIdx], NULL); + BX_WARN(SUCCEEDED(cres), "VideoDecoderD3D12: copy cmdlist Reset failed (0x%08x).", cres); + + ID3D12GraphicsCommandList* commandList = m_copyCmdList; + + ID3D12Resource* nv12Source = m_referenceOnly + ? m_dpbOutput + : m_dpb; + const uint32_t nv12LumaSubres = m_referenceOnly ? 0u : m_currentSlot; + const uint32_t nv12ChromaSubres = m_referenceOnly ? 1u : (m_numDpbSlots + m_currentSlot); + + ID3D12Resource* reorderTex = acquireReorderTexture(uint32_t(reorderSlot) ); + + if (NULL == reorderTex) + { + m_copyCmdList->Close(); + return false; + } + + { + D3D12_RESOURCE_BARRIER barriers[2] + { + { + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + .Transition = + { + .pResource = reorderTex, + .Subresource = 0, + .StateBefore = D3D12_RESOURCE_STATE_COMMON, + .StateAfter = D3D12_RESOURCE_STATE_COPY_DEST, + }, + }, + { + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + .Transition = + { + .pResource = reorderTex, + .Subresource = 1, + .StateBefore = D3D12_RESOURCE_STATE_COMMON, + .StateAfter = D3D12_RESOURCE_STATE_COPY_DEST, + }, + }, + }; + + commandList->ResourceBarrier(BX_COUNTOF(barriers), barriers); + } + + { + D3D12_RESOURCE_BARRIER barriers[2] + { + { + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + .Transition = + { + .pResource = nv12Source, + .Subresource = nv12LumaSubres, + .StateBefore = D3D12_RESOURCE_STATE_COMMON, + .StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE, + }, + }, + { + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + .Transition = + { + .pResource = nv12Source, + .Subresource = nv12ChromaSubres, + .StateBefore = D3D12_RESOURCE_STATE_COMMON, + .StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE, + }, + }, + }; + + commandList->ResourceBarrier(BX_COUNTOF(barriers), barriers); + } + + { + const uint32_t reorderW = (m_dstWidth + 1) & ~1u; + const uint32_t reorderH = (m_dstHeight + 1) & ~1u; + + const D3D12_BOX lumaBox = { 0, 0, 0, reorderW, reorderH, 1 }; + const D3D12_BOX chromaBox = { 0, 0, 0, reorderW / 2, reorderH / 2, 1 }; + + { + D3D12_TEXTURE_COPY_LOCATION dst + { + .pResource = reorderTex, + .Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX, + .SubresourceIndex = 0, + }; + D3D12_TEXTURE_COPY_LOCATION src + { + .pResource = nv12Source, + .Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX, + .SubresourceIndex = nv12LumaSubres, + }; + commandList->CopyTextureRegion(&dst, 0, 0, 0, &src, &lumaBox); + } + + { + D3D12_TEXTURE_COPY_LOCATION dst + { + .pResource = reorderTex, + .Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX, + .SubresourceIndex = 1, + }; + D3D12_TEXTURE_COPY_LOCATION src + { + .pResource = nv12Source, + .Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX, + .SubresourceIndex = nv12ChromaSubres, + }; + commandList->CopyTextureRegion(&dst, 0, 0, 0, &src, &chromaBox); + } + } + + { + D3D12_RESOURCE_BARRIER barriers[2] + { + { + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + .Transition = + { + .pResource = nv12Source, + .Subresource = nv12LumaSubres, + .StateBefore = D3D12_RESOURCE_STATE_COPY_SOURCE, + .StateAfter = D3D12_RESOURCE_STATE_COMMON, + }, + }, + { + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + .Transition = + { + .pResource = nv12Source, + .Subresource = nv12ChromaSubres, + .StateBefore = D3D12_RESOURCE_STATE_COPY_SOURCE, + .StateAfter = D3D12_RESOURCE_STATE_COMMON, + }, + }, + }; + commandList->ResourceBarrier(BX_COUNTOF(barriers), barriers); + } + + { + D3D12_RESOURCE_BARRIER barriers[2] + { + { + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + .Transition = + { + .pResource = reorderTex, + .Subresource = 0, + .StateBefore = D3D12_RESOURCE_STATE_COPY_DEST, + .StateAfter = D3D12_RESOURCE_STATE_COMMON, + }, + }, + { + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + .Transition = + { + .pResource = reorderTex, + .Subresource = 1, + .StateBefore = D3D12_RESOURCE_STATE_COPY_DEST, + .StateAfter = D3D12_RESOURCE_STATE_COMMON, + }, + }, + }; + commandList->ResourceBarrier(BX_COUNTOF(barriers), barriers); + } + + cres = m_copyCmdList->Close(); + BX_WARN(SUCCEEDED(cres), "VideoDecoderD3D12: copy Close failed (0x%08x).", cres); + + ID3D12CommandQueue* gfxQueue = s_videoD3D12.commandQueue; + gfxQueue->Wait(m_videoFence, signalValue); + + ID3D12CommandList* copyLists[] = { m_copyCmdList }; + gfxQueue->ExecuteCommandLists(BX_COUNTOF(copyLists), copyLists); + + const uint64_t copySignal = ++m_copyFenceValue; + gfxQueue->Signal(m_copyFence, copySignal); + + m_copyFenceRing[copyRingIdx] = copySignal; + m_slotLastCopyFence[m_currentSlot] = copySignal; + m_lastDpbOutputCopyFence = copySignal; + m_copyControl.commit(1); + + return true; + } + + void pickDisplaySlotForTime(int64_t _presentationTimeUs, TextureD3D12& _dst) + { + bgfx::pickDisplaySlotForTime( + _presentationTimeUs + , m_reorderPool + , kVideoMaxReorderInFlight + , m_displayedSlot + , m_prevDisplayedSlot + , m_auQueue.empty() + , [this, &_dst](uint32_t _slot) + { + ID3D12GraphicsCommandList* gfx = videoGetCommandList(m_renderer); + dispatchYuvToRgb(gfx, m_reorderPool[_slot].texture, _dst); + } + ); + } + + RendererContextD3D12* m_renderer; + + ID3D12Resource* acquireReorderTexture(uint32_t _slot) + { + BX_ASSERT(_slot < kVideoMaxReorderInFlight, "VideoDecoderD3D12: reorder slot %u out of range.", _slot); + if (NULL != m_reorderPool[_slot].texture) + { + return m_reorderPool[_slot].texture; + } + + ID3D12Device* device = s_videoD3D12.device; + + D3D12_RESOURCE_DESC desc + { + .Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D, + .Alignment = 0, + .Width = (m_dstWidth + 1) & ~1u, + .Height = (m_dstHeight + 1) & ~1u, + .DepthOrArraySize = 1, + .MipLevels = 1, + .Format = DXGI_FORMAT_NV12, + .SampleDesc = { .Count = 1, .Quality = 0 }, + .Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN, + .Flags = D3D12_RESOURCE_FLAG_NONE, + }; + + D3D12_HEAP_PROPERTIES heap + { + .Type = D3D12_HEAP_TYPE_DEFAULT, + .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN, + .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN, + .CreationNodeMask = 0, + .VisibleNodeMask = 0, + }; + + HRESULT hr = device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COMMON, NULL, IID_ID3D12Resource, (void**)&m_reorderPool[_slot].texture); + if (FAILED(hr) ) + { + BX_TRACE("VideoDecoderD3D12: reorder texture create failed (0x%08x).", hr); + return NULL; + } + + return m_reorderPool[_slot].texture; + } + + void dispatchYuvToRgb(ID3D12GraphicsCommandList* _commandList, ID3D12Resource* _nv12, TextureD3D12& _dst) + { + BX_ASSERT(NULL != g_videoDecode && isValid(g_videoDecode->m_program), "Video decode program not initialized."); + + ID3D12PipelineState* pso = videoGetPipelineState(m_renderer, g_videoDecode->m_program); + + if (NULL == pso) + { + return; + } + + { + D3D12_RESOURCE_BARRIER barriers[2] + { + { + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + .Transition = + { + .pResource = _nv12, + .Subresource = 0, + .StateBefore = D3D12_RESOURCE_STATE_COMMON, + .StateAfter = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, + }, + }, + { + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + .Transition = + { + .pResource = _nv12, + .Subresource = 1, + .StateBefore = D3D12_RESOURCE_STATE_COMMON, + .StateAfter = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, + }, + }, + }; + _commandList->ResourceBarrier(BX_COUNTOF(barriers), barriers); + } + + _dst.setState(_commandList, D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + + ScratchBufferD3D12& scratch = videoGetScratchBuffer(m_renderer); + + _commandList->SetComputeRootSignature(s_videoD3D12.computeRootSignature); + ID3D12DescriptorHeap* heaps[] = + { + s_videoD3D12.samplerHeap, + scratch.getHeap(), + }; + _commandList->SetDescriptorHeaps(BX_COUNTOF(heaps), heaps); + _commandList->SetPipelineState(pso); + + uint32_t samplerFlags[BGFX_MAX_COMPUTE_BINDINGS] = {}; + samplerFlags[1] = BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP | BGFX_SAMPLER_W_CLAMP; + samplerFlags[2] = BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP | BGFX_SAMPLER_W_CLAMP; + _commandList->SetComputeRootDescriptorTable(ComputeRp::Sampler, videoGetSamplerHandle(m_renderer, samplerFlags) ); + + D3D12_GPU_VIRTUAL_ADDRESS cbvAddress = m_dummyCbv->GetGPUVirtualAddress(); + _commandList->SetComputeRootConstantBufferView(ComputeRp::CBV, cbvAddress); + + D3D12_GPU_DESCRIPTOR_HANDLE srvHandle[BGFX_MAX_COMPUTE_BINDINGS] = {}; + { + scratch.allocEmpty(srvHandle[0]); + + D3D12_SHADER_RESOURCE_VIEW_DESC srvDescY + { + .Format = DXGI_FORMAT_R8_UNORM, + .ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D, + .Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING, + .Texture2D = + { + .MostDetailedMip = 0, + .MipLevels = 1, + .PlaneSlice = 0, + .ResourceMinLODClamp = 0.0f, + }, + }; + scratch.allocSrv(srvHandle[1], _nv12, srvDescY); + + D3D12_SHADER_RESOURCE_VIEW_DESC srvDescCbCr + { + .Format = DXGI_FORMAT_R8G8_UNORM, + .ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D, + .Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING, + .Texture2D = + { + .MostDetailedMip = 0, + .MipLevels = 1, + .PlaneSlice = 1, + .ResourceMinLODClamp = 0.0f, + }, + }; + scratch.allocSrv(srvHandle[2], _nv12, srvDescCbCr); + + for (uint32_t ii = 3; ii < BGFX_MAX_COMPUTE_BINDINGS; ++ii) + { + scratch.allocEmpty(srvHandle[ii]); + } + } + _commandList->SetComputeRootDescriptorTable(ComputeRp::SRV, srvHandle[0]); + + D3D12_GPU_DESCRIPTOR_HANDLE uavHandle[BGFX_MAX_COMPUTE_BINDINGS] = {}; + { + scratch.allocUav(uavHandle[0], _dst, 0); + + for (uint32_t ii = 1; ii < BGFX_MAX_COMPUTE_BINDINGS; ++ii) + { + scratch.allocEmpty(uavHandle[ii]); + } + } + _commandList->SetComputeRootDescriptorTable(ComputeRp::UAV, uavHandle[0]); + + _commandList->Dispatch( + bx::max( (m_dstWidth + 7) / 8, 1) + , bx::max( (m_dstHeight + 7) / 8, 1) + , 1 + ); + + { + D3D12_RESOURCE_BARRIER barriers[2] + { + { + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + .Transition = + { + .pResource = _nv12, + .Subresource = 0, + .StateBefore = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, + .StateAfter = D3D12_RESOURCE_STATE_COMMON, + }, + }, + { + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + .Transition = + { + .pResource = _nv12, + .Subresource = 1, + .StateBefore = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, + .StateAfter = D3D12_RESOURCE_STATE_COMMON, + }, + }, + }; + _commandList->ResourceBarrier(BX_COUNTOF(barriers), barriers); + } + } + + ID3D12VideoDevice* m_videoDevice; + ID3D12VideoDecoder* m_decoder; + ID3D12VideoDecoderHeap* m_decoderHeap; + ID3D12CommandQueue* m_videoQueue; + ID3D12CommandAllocator* m_videoCmdAllocator[kBitstreamRingFrames]; + uint64_t m_videoCmdAllocatorFence[kBitstreamRingFrames]; + ID3D12VideoDecodeCommandList* m_videoCmd; + ID3D12Fence* m_videoFence; + uint64_t m_videoFenceValue; + bx::RingBufferControl m_videoControl; + + ID3D12CommandAllocator* m_copyCmdAllocator[kCopyRingSize]; + ID3D12GraphicsCommandList* m_copyCmdList; + ID3D12Fence* m_copyFence; + uint64_t m_copyFenceValue; + uint64_t m_copyFenceRing[kCopyRingSize]; + uint64_t m_slotLastCopyFence[kNumDpbSlots]; + + uint64_t m_lastDpbOutputCopyFence; + bx::RingBufferControl m_copyControl; + + ID3D12Resource* m_bitstreamBuffer; + uint8_t* m_bitstreamMapped; + uint64_t m_bitstreamCapacity; + uint64_t m_bitstreamCursor; + ID3D12Resource* m_dummyCbv; + + ID3D12Resource* m_dpb; + ID3D12Resource* m_dpbOutput; + bool m_referenceOnly; + DpbState m_dpbState[kNumDpbSlots]; + uint8_t m_referenceUsage[kNumDpbSlots]; + + uint32_t m_codedWidth; + uint32_t m_codedHeight; + uint32_t m_dstWidth; + uint32_t m_dstHeight; + uint32_t m_numDpbSlots; + uint32_t m_nextSlot; + uint32_t m_currentSlot; + uint32_t m_numActiveRefs; + + int32_t m_displayOrderNext; + int32_t m_prevPocLsb; + int32_t m_prevPocMsb; + + ReorderedPicture m_reorderPool[kVideoMaxReorderInFlight]; + int32_t m_displayedSlot; + int32_t m_prevDisplayedSlot; + + uint32_t m_statusReportFeedback; + + bgfx::AuQueue m_auQueue; + + uint32_t m_initFlags; + uint32_t m_cachedAuBytes; + + h264::SPS m_spsActive; + h264::SPS m_spsArray[kVideoMaxH264SpsCount]; + h264::PPS m_ppsArray[kVideoMaxH264PpsCount]; + }; + + VideoDecoderD3D12* videoDecoderCreate(const VideoDecoderInit& _init, RendererContextD3D12* _renderer, uint16_t _width, uint16_t _height) + { + VideoDecoderD3D12* decoder = BX_NEW(g_allocator, VideoDecoderD3D12); + if (!decoder->create(_init, _renderer, _width, _height) ) + { + decoder->destroy(); + bx::deleteObject(g_allocator, decoder); + return NULL; + } + return decoder; + } + + void videoDecoderDestroy(VideoDecoderD3D12* _decoder) + { + if (NULL != _decoder) + { + _decoder->destroy(); + bx::deleteObject(g_allocator, _decoder); + } + } + + bool videoDecoderDecode(VideoDecoderD3D12* _decoder, const VideoDecoderFrame& _frame, TextureD3D12& _dst) + { + return _decoder->decode(_frame, _dst); + } + + void initVideoDecoder(const VideoBindingD3D12& _binding) + { + s_videoD3D12 = _binding; + + ID3D12VideoDevice* videoDevice = NULL; + HRESULT hr = _binding.device->QueryInterface(IID_ID3D12VideoDevice, (void**)&videoDevice); + + if (FAILED(hr) || NULL == videoDevice) + { + return; + } + + D3D12_FEATURE_DATA_VIDEO_DECODE_PROFILE_COUNT profileCount = {}; + hr = videoDevice->CheckFeatureSupport( + D3D12_FEATURE_VIDEO_DECODE_PROFILE_COUNT + , &profileCount + , sizeof(profileCount) + ); + + if (FAILED(hr) || 0 == profileCount.ProfileCount) + { + DX_RELEASE(videoDevice, 0); + return; + } + + GUID* profiles = (GUID*)BX_STACK_ALLOC(profileCount.ProfileCount * sizeof(GUID) ); + + D3D12_FEATURE_DATA_VIDEO_DECODE_PROFILES profileList + { + .NodeIndex = 0, + .ProfileCount = profileCount.ProfileCount, + .pProfiles = profiles, + }; + + hr = videoDevice->CheckFeatureSupport( + D3D12_FEATURE_VIDEO_DECODE_PROFILES + , &profileList + , sizeof(profileList) + ); + + if (FAILED(hr) ) + { + DX_RELEASE(videoDevice, 0); + return; + } + + struct ProfileEntry + { + const GUID* guid; + VideoCodec::Enum codec; + uint32_t caps; + }; + + static const ProfileEntry s_profiles[] = + { + { &D3D12_VIDEO_DECODE_PROFILE_H264, VideoCodec::H264, BGFX_CAPS_VIDEO_CODEC_BIT_8 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420 }, + { &D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN, VideoCodec::H265, BGFX_CAPS_VIDEO_CODEC_BIT_8 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420 }, + { &D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN10, VideoCodec::H265, BGFX_CAPS_VIDEO_CODEC_BIT_10 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420 }, + { &D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN12, VideoCodec::H265, BGFX_CAPS_VIDEO_CODEC_BIT_12 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420 }, + { &D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN10_422, VideoCodec::H265, BGFX_CAPS_VIDEO_CODEC_BIT_10 | BGFX_CAPS_VIDEO_CODEC_CHROMA_422 }, + { &D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN12_422, VideoCodec::H265, BGFX_CAPS_VIDEO_CODEC_BIT_12 | BGFX_CAPS_VIDEO_CODEC_CHROMA_422 }, + { &D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN_444, VideoCodec::H265, BGFX_CAPS_VIDEO_CODEC_BIT_8 | BGFX_CAPS_VIDEO_CODEC_CHROMA_444 }, + { &D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN10_444, VideoCodec::H265, BGFX_CAPS_VIDEO_CODEC_BIT_10 | BGFX_CAPS_VIDEO_CODEC_CHROMA_444 }, + { &D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN12_444, VideoCodec::H265, BGFX_CAPS_VIDEO_CODEC_BIT_12 | BGFX_CAPS_VIDEO_CODEC_CHROMA_444 }, + { &D3D12_VIDEO_DECODE_PROFILE_AV1_PROFILE0, VideoCodec::AV1, BGFX_CAPS_VIDEO_CODEC_BIT_8 + | BGFX_CAPS_VIDEO_CODEC_BIT_10 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420 }, + { &D3D12_VIDEO_DECODE_PROFILE_AV1_PROFILE1, VideoCodec::AV1, BGFX_CAPS_VIDEO_CODEC_BIT_8 + | BGFX_CAPS_VIDEO_CODEC_BIT_10 | BGFX_CAPS_VIDEO_CODEC_CHROMA_444 }, + { &D3D12_VIDEO_DECODE_PROFILE_AV1_PROFILE2, VideoCodec::AV1, BGFX_CAPS_VIDEO_CODEC_BIT_10 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420 + | BGFX_CAPS_VIDEO_CODEC_CHROMA_422 }, + { &D3D12_VIDEO_DECODE_PROFILE_AV1_12BIT_PROFILE2, VideoCodec::AV1, BGFX_CAPS_VIDEO_CODEC_BIT_12 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420 + | BGFX_CAPS_VIDEO_CODEC_CHROMA_422 }, + { &D3D12_VIDEO_DECODE_PROFILE_AV1_12BIT_PROFILE2_420, VideoCodec::AV1, BGFX_CAPS_VIDEO_CODEC_BIT_12 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420 }, + + }; + + bool anySupported = false; + + for (UINT ii = 0; ii < profileList.ProfileCount; ++ii) + { + for (uint32_t jj = 0; jj < BX_COUNTOF(s_profiles); ++jj) + { + if (0 != bx::memCmp(&profiles[ii], s_profiles[jj].guid, sizeof(GUID) ) ) + { + continue; + } + + D3D12_FEATURE_DATA_VIDEO_DECODE_SUPPORT support + { + .NodeIndex = 0, + .Configuration = + { + .DecodeProfile = profiles[ii], + .BitstreamEncryption = D3D12_BITSTREAM_ENCRYPTION_TYPE_NONE, + .InterlaceType = D3D12_VIDEO_FRAME_CODED_INTERLACE_TYPE_NONE, + }, + .Width = 1920, + .Height = 1080, + .DecodeFormat = (s_profiles[jj].caps & BGFX_CAPS_VIDEO_CODEC_BIT_10) + ? DXGI_FORMAT_P010 : DXGI_FORMAT_NV12, + .FrameRate = { .Numerator = 30, .Denominator = 1 }, + .BitRate = 0, + .SupportFlags = D3D12_VIDEO_DECODE_SUPPORT_FLAG_NONE, + .ConfigurationFlags = D3D12_VIDEO_DECODE_CONFIGURATION_FLAG_NONE, + .DecodeTier = D3D12_VIDEO_DECODE_TIER_NOT_SUPPORTED, + }; + + hr = videoDevice->CheckFeatureSupport( + D3D12_FEATURE_VIDEO_DECODE_SUPPORT + , &support + , sizeof(support) + ); + + if (SUCCEEDED(hr) + && 0 != (support.SupportFlags & D3D12_VIDEO_DECODE_SUPPORT_FLAG_SUPPORTED) ) + { + g_caps.codecs[s_profiles[jj].codec] |= s_profiles[jj].caps; + anySupported = true; + } + + break; + } + } + + if (anySupported) + { + g_caps.supported |= BGFX_CAPS_VIDEO_DECODE; + + const TextureFormat::Enum dstFormats[] = + { + TextureFormat::BGRA8, + TextureFormat::RGBA8, + TextureFormat::RGB10A2, + TextureFormat::RGBA16F, + }; + + for (uint32_t ii = 0; ii < BX_COUNTOF(dstFormats); ++ii) + { + const TextureFormat::Enum fmt = dstFormats[ii]; + if (0 != (g_caps.formats[fmt] & BGFX_CAPS_FORMAT_TEXTURE_2D) ) + { + g_caps.formats[fmt] |= BGFX_CAPS_FORMAT_TEXTURE_VIDEO_DECODE_DST; + } + } + } + + DX_RELEASE_I(videoDevice); + } + +} } // namespace bgfx::d3d12 + +# else + +namespace bgfx { namespace d3d12 +{ + void initVideoDecoder(const VideoBindingD3D12& _binding) + { + BX_UNUSED(_binding); + } + + VideoDecoderD3D12* videoDecoderCreate(const VideoDecoderInit& _init, RendererContextD3D12* _renderer, uint16_t _width, uint16_t _height) + { + BX_UNUSED(_init, _renderer, _width, _height); + return NULL; + } + + void videoDecoderDestroy(VideoDecoderD3D12* _decoder) + { + BX_UNUSED(_decoder); + } + + bool videoDecoderDecode(VideoDecoderD3D12* _decoder, const VideoDecoderFrame& _frame, TextureD3D12& _dst) + { + BX_UNUSED(_decoder, _frame, _dst); + return false; + } + +} } // namespace bgfx::d3d12 + +# endif // BGFX_CONFIG_VIDEO_DIRECT3D12 + +#endif // BGFX_CONFIG_RENDERER_DIRECT3D12 diff --git a/src/video_d3d12.h b/src/video_d3d12.h new file mode 100644 index 000000000..341355eae --- /dev/null +++ b/src/video_d3d12.h @@ -0,0 +1,37 @@ +/* + * Copyright 2011-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE + */ + +#ifndef BGFX_VIDEO_D3D12_H_HEADER_GUARD +#define BGFX_VIDEO_D3D12_H_HEADER_GUARD + +struct ID3D12Device; +struct ID3D12RootSignature; +struct ID3D12DescriptorHeap; +struct ID3D12CommandQueue; + +namespace bgfx { namespace d3d12 +{ + struct RendererContextD3D12; + struct TextureD3D12; + struct VideoDecoderD3D12; + + struct VideoBindingD3D12 + { + ID3D12Device* device; + ID3D12RootSignature* computeRootSignature; + ID3D12DescriptorHeap* samplerHeap; + ID3D12CommandQueue* commandQueue; + uint16_t vendorId; + }; + + void initVideoDecoder(const VideoBindingD3D12& _binding); + + VideoDecoderD3D12* videoDecoderCreate(const VideoDecoderInit& _init, RendererContextD3D12* _renderer, uint16_t _width, uint16_t _height); + void videoDecoderDestroy(VideoDecoderD3D12* _decoder); + bool videoDecoderDecode(VideoDecoderD3D12* _decoder, const VideoDecoderFrame& _frame, TextureD3D12& _dst); + +} } // namespace bgfx::d3d12 + +#endif // BGFX_VIDEO_D3D12_H_HEADER_GUARD diff --git a/src/video_mtl.cpp b/src/video_mtl.cpp new file mode 100644 index 000000000..11788726a --- /dev/null +++ b/src/video_mtl.cpp @@ -0,0 +1,873 @@ +/* + * Copyright 2011-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE + */ + +#include "bgfx_p.h" + +#if BGFX_CONFIG_RENDERER_METAL +# include "renderer_mtl.h" +# include "video_mtl.h" + +# if BGFX_CONFIG_VIDEO_METAL +# include "video.h" +# include +# include +# include + +namespace bgfx { namespace mtl +{ + struct RendererContextMtl; + + void initVideoDecoder(); + PipelineStateMtl* videoGetComputePipelineState(RendererContextMtl* _renderer, ProgramHandle _handle); + void videoEndEncoding(RendererContextMtl* _renderer); + MTL::CommandBuffer* videoEnsureCommandBuffer(RendererContextMtl* _renderer); + MTL::SamplerState* videoGetSamplerState(RendererContextMtl* _renderer, uint64_t _samplerFlags); + + static void freeAvccBlockBxAlloc(void* _refcon, void* _memoryBlock, size_t _sizeInBytes) + { + BX_UNUSED(_refcon, _sizeInBytes); + + if (NULL != _memoryBlock) + { + bx::free(g_allocator, _memoryBlock); + } + } + + template + static uint32_t visitAnnexBNalUnits(const uint8_t* _data, uint32_t _size, Fn _visit) + { + uint32_t count = 0; + uint32_t pos = 0; + uint32_t nalStart = UINT32_MAX; + + while (pos + 3 < _size) + { + const bool sc4 = true + && 0 == _data[pos ] + && 0 == _data[pos+1] + && 0 == _data[pos+2] + && 1 == _data[pos+3] + ; + const bool sc3 = true + && 0 == _data[pos] + && 0 == _data[pos+1] + && 1 == _data[pos+2] + ; + + if (sc3 + || sc4) + { + const uint32_t scLen = sc4 ? 4 : 3; + + if (UINT32_MAX != nalStart) + { + _visit(_data + nalStart, pos - nalStart); + ++count; + } + + pos += scLen; + nalStart = pos; + + continue; + } + + ++pos; + } + + if (UINT32_MAX != nalStart && nalStart < _size) + { + _visit(_data + nalStart, _size - nalStart); + ++count; + } + + return count; + } + + static uint32_t annexBToAvcc(uint8_t* _out, const uint8_t* _data, uint32_t _size) + { + uint32_t outPos = 0; + + visitAnnexBNalUnits(_data, _size, [&](const uint8_t* _nal, uint32_t _nalSize) + { + const uint32_t lengthBE = bx::endianSwap(_nalSize); + bx::memCopy(_out + outPos, &lengthBE, 4); + outPos += 4; + bx::memCopy(_out + outPos, _nal, _nalSize); + outPos += _nalSize; + }); + + return outPos; + } + + struct VideoDecoderMtl + { + VideoDecoderMtl() + : m_renderer(NULL) + , m_device(NULL) + , m_formatDesc(NULL) + , m_session(NULL) + , m_textureCache(NULL) + , m_queueHead(0) + , m_queueTail(0) + , m_dstWidth(0) + , m_dstHeight(0) + , m_initFlags(0) + , m_cachedAuBytes(0) + , m_dtsCounter(0) + , m_lastDisplayedPts(INT64_MIN) + { + bx::memSet(m_queue, 0, sizeof(m_queue) ); + } + + bool create(const VideoDecoderInit& _init, RendererContextMtl* _renderer, MTL::Device* _device, uint16_t _width, uint16_t _height) + { + m_renderer = _renderer; + m_device = _device; + m_dstWidth = _width; + m_dstHeight = _height; + m_initFlags = _init.flags; + m_cachedAuBytes = (0 != _init.cachedAuBytes) ? _init.cachedAuBytes : (4u << 20); + m_queueHead = 0; + m_queueTail = 0; + m_dtsCounter = 0; + m_lastDisplayedPts = INT64_MIN; + + bx::memSet(m_queue, 0, sizeof(m_queue) ); + + const uint8_t* psPtrs[16]; + size_t psSize[16]; + uint32_t psCount = 0; + const bool isHevc = (VideoCodec::H265 == _init.codec); + + visitAnnexBNalUnits(_init.parameterSets, _init.parameterSetsSize, [&](const uint8_t* _nal, uint32_t _nalSize) + { + if (0 == _nalSize + || psCount >= BX_COUNTOF(psPtrs) ) + { + return; + } + + bool keep = false; + + if (isHevc) + { + if (_nalSize < 2) return; + const uint8_t nalType = (_nal[0] >> 1) & 0x3F; + keep = (32 == nalType) + || (33 == nalType) + || (34 == nalType) + ; + } + else + { + const uint8_t nalType = _nal[0] & 0x1F; + keep = (7 == nalType) + || (8 == nalType) + ; + } + + if (keep) + { + psPtrs[psCount] = _nal; + psSize[psCount] = _nalSize; + ++psCount; + } + }); + + if (0 == psCount) + { + BX_TRACE("VideoDecoderMtl: no parameter set NAL units found (codec=%d).", int(_init.codec) ); + return false; + } + + OSStatus status = noErr; + + if (isHevc) + { + if (__builtin_available(macOS 10.13, iOS 11.0, tvOS 11.0, *) ) + { + status = CMVideoFormatDescriptionCreateFromHEVCParameterSets( + kCFAllocatorDefault + , psCount + , psPtrs + , psSize + , 4 + , NULL + , &m_formatDesc + ); + } + else + { + BX_TRACE("VideoDecoderMtl: HEVC requires macOS 10.13+."); + return false; + } + } + else + { + status = CMVideoFormatDescriptionCreateFromH264ParameterSets( + kCFAllocatorDefault + , psCount + , psPtrs + , psSize + , 4 + , &m_formatDesc + ); + } + + if (noErr != status + || NULL == m_formatDesc) + { + BX_TRACE("VideoDecoderMtl: CMVideoFormatDescriptionCreateFrom%sParameterSets failed: %d" + , isHevc ? "HEVC" : "H264" + , (int)status + ); + + return false; + } + + if (!createSession() ) + { + CFRelease(m_formatDesc); + m_formatDesc = NULL; + + return false; + } + + CVReturn cvr = CVMetalTextureCacheCreate( + kCFAllocatorDefault + , NULL + , (id)_device + , NULL + , &m_textureCache + ); + + BX_ASSERT(kCVReturnSuccess == cvr, "CVMetalTextureCacheCreate failed: %d", (int)cvr); + BX_UNUSED(cvr); + + return true; + } + + bool createSession() + { + const int32_t pixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange; + + CFNumberRef pixelFormatNum = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pixelFormat); + const void* keys[] = { kCVPixelBufferPixelFormatTypeKey, kCVPixelBufferMetalCompatibilityKey, kCVPixelBufferIOSurfacePropertiesKey }; + const void* values[] = { pixelFormatNum, kCFBooleanTrue, CFDictionaryCreate(kCFAllocatorDefault, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks) }; + CFDictionaryRef destAttrs = CFDictionaryCreate( + kCFAllocatorDefault + , keys + , values + , 3 + , &kCFTypeDictionaryKeyCallBacks + , &kCFTypeDictionaryValueCallBacks + ); + CFRelease(pixelFormatNum); + CFRelease(values[2]); + + VTDecompressionOutputCallbackRecord callback; + callback.decompressionOutputCallback = &VideoDecoderMtl::decodeOutputCallback; + callback.decompressionOutputRefCon = this; + + OSStatus status = VTDecompressionSessionCreate( + kCFAllocatorDefault + , m_formatDesc + , NULL + , destAttrs + , &callback + , &m_session + ); + + CFRelease(destAttrs); + + if (noErr != status || NULL == m_session) + { + BX_TRACE("VideoDecoderMtl: VTDecompressionSessionCreate failed: %d", (int)status); + return false; + } + + VTSessionSetProperty(m_session, kVTDecompressionPropertyKey_RealTime, kCFBooleanFalse); + + return true; + } + + void resetForSet() + { + if (NULL != m_session) + { + VTDecompressionSessionWaitForAsynchronousFrames(m_session); + VTDecompressionSessionInvalidate(m_session); + CFRelease(m_session); + m_session = NULL; + } + + if (NULL != m_textureCache) + { + CVMetalTextureCacheFlush(m_textureCache, 0); + } + + { + bx::MutexScope lock(m_queueMutex); + + while (m_queueHead != m_queueTail) + { + CFRelease(m_queue[m_queueHead % kQueueCapacity].pb); + ++m_queueHead; + } + + m_queueHead = 0; + m_queueTail = 0; + } + + m_dtsCounter = 0; + m_lastDisplayedPts = INT64_MIN; + createSession(); + } + void destroy() + { + if (NULL != m_session) + { + VTDecompressionSessionWaitForAsynchronousFrames(m_session); + VTDecompressionSessionInvalidate(m_session); + CFRelease(m_session); + m_session = NULL; + } + + if (NULL != m_textureCache) + { + CVMetalTextureCacheFlush(m_textureCache, 0); + CFRelease(m_textureCache); + m_textureCache = NULL; + } + + { + bx::MutexScope lock(m_queueMutex); + + while (m_queueHead != m_queueTail) + { + CFRelease(m_queue[m_queueHead % kQueueCapacity].pb); + ++m_queueHead; + } + } + + if (NULL != m_formatDesc) + { + CFRelease(m_formatDesc); + m_formatDesc = NULL; + } + } + + bool decode(const VideoDecoderFrame& _frame, MTL::Texture* _dst) + { + if (NULL == m_session) + { + return false; + } + + if (0 != (_frame.flags & BGFX_VIDEO_DECODE_FRAME_SET) ) + { + resetForSet(); + + if (NULL == m_session) + { + return false; + } + } + + const uint8_t* auBitstream = _frame.bitstream; + + for (uint32_t auIdx = 0; auIdx < _frame.numAus; ++auIdx) + { + const uint32_t auSize = _frame.aus[auIdx].size; + const int64_t auPts = _frame.aus[auIdx].ptsUs; + + const uint32_t nalCount = visitAnnexBNalUnits(auBitstream, auSize, [](const uint8_t*, uint32_t){}); + const uint32_t avccBufSize = auSize + nalCount; + uint8_t* avccBytes = (uint8_t*)bx::alloc(g_allocator, avccBufSize); + const uint32_t avccSize = annexBToAvcc(avccBytes, auBitstream, auSize); + + BX_ASSERT(avccSize <= avccBufSize + , "annexBToAvcc wrote %u bytes into a %u-byte buffer (auSize=%u nalCount=%u)" + , avccSize, avccBufSize, auSize, nalCount + ); + + auBitstream += auSize; + + CMBlockBufferCustomBlockSource customSource = {}; + customSource.version = kCMBlockBufferCustomBlockSourceVersion; + customSource.FreeBlock = freeAvccBlockBxAlloc; + + CMBlockBufferRef blockBuffer = NULL; + OSStatus status = CMBlockBufferCreateWithMemoryBlock( + kCFAllocatorDefault + , avccBytes + , avccSize + , kCFAllocatorNull + , &customSource + , 0 + , avccSize + , 0 + , &blockBuffer + ); + + if (noErr != status) + { + bx::free(g_allocator, avccBytes); + + return false; + } + + const size_t sampleSize = avccSize; + CMSampleBufferRef sampleBuffer = NULL; + + const int64_t synthesizedDtsUs = int64_t(++m_dtsCounter * 1000ull); + + CMSampleTimingInfo timing; + timing.duration = kCMTimeInvalid; + timing.presentationTimeStamp = CMTimeMake(auPts, 1000000); + timing.decodeTimeStamp = CMTimeMake(synthesizedDtsUs, 1000000); + + status = CMSampleBufferCreate( + kCFAllocatorDefault + , blockBuffer + , true + , NULL + , NULL + , m_formatDesc + , 1 + , 1, &timing + , 1, &sampleSize + , &sampleBuffer + ); + + CFRelease(blockBuffer); + + if (noErr != status) + { + return false; + } + + VTDecodeFrameFlags flags = kVTDecodeFrame_EnableAsynchronousDecompression + | kVTDecodeFrame_EnableTemporalProcessing + ; + VTDecodeInfoFlags infoFlags = 0; + status = VTDecompressionSessionDecodeFrame(m_session, sampleBuffer, flags, NULL, &infoFlags); + + CFRelease(sampleBuffer); + + if (noErr != status) + { + BX_TRACE("VideoDecoderMtl: DecodeFrame submit failed: %d", (int)status); + + return false; + } + } + + const int64_t presentUs = _frame.presentationTimeUs; + + if (0 == presentUs && 0 != _frame.numAus) + { + return true; + } + + CVPixelBufferRef pic = NULL; + int64_t picPts = INT64_MIN; + { + bx::MutexScope lock(m_queueMutex); + + uint32_t bestIdx = UINT32_MAX; + int64_t bestPts = INT64_MIN; + + for (uint32_t ii = m_queueHead; ii != m_queueTail; ++ii) + { + const int64_t pts = m_queue[ii % kQueueCapacity].ptsUs; + if (pts <= presentUs && pts > bestPts) + { + bestPts = pts; + bestIdx = ii; + } + } + + if (UINT32_MAX == bestIdx + && m_queueHead != m_queueTail + && INT64_MIN == m_lastDisplayedPts) + { + bestIdx = m_queueHead; + bestPts = m_queue[bestIdx % kQueueCapacity].ptsUs; + + for (uint32_t ii = m_queueHead + 1; ii != m_queueTail; ++ii) + { + const int64_t pts = m_queue[ii % kQueueCapacity].ptsUs; + + if (pts < bestPts) + { + bestPts = pts; + bestIdx = ii; + } + } + } + + if (UINT32_MAX != bestIdx) + { + pic = m_queue[bestIdx % kQueueCapacity].pb; + picPts = bestPts; + CFRetain(pic); + + uint32_t writeIdx = m_queueHead; + + for (uint32_t ii = m_queueHead; ii != m_queueTail; ++ii) + { + QueueEntry& e = m_queue[ii % kQueueCapacity]; + + if (e.ptsUs <= picPts) + { + CFRelease(e.pb); + } + else + { + m_queue[writeIdx % kQueueCapacity] = e; + ++writeIdx; + } + } + + m_queueTail = writeIdx; + m_lastDisplayedPts = picPts; + } + } + + if (NULL == pic) + { + return true; + } + + BX_UNUSED(picPts); + + const size_t yWidth = CVPixelBufferGetWidthOfPlane (pic, 0); + const size_t yHeight = CVPixelBufferGetHeightOfPlane(pic, 0); + const size_t cWidth = CVPixelBufferGetWidthOfPlane (pic, 1); + const size_t cHeight = CVPixelBufferGetHeightOfPlane(pic, 1); + + CVMetalTextureRef yMtlTex = NULL; + CVMetalTextureRef cMtlTex = NULL; + + CVReturn cvr = CVMetalTextureCacheCreateTextureFromImage( + kCFAllocatorDefault + , m_textureCache + , pic + , NULL + , MTLPixelFormatR8Unorm + , yWidth, yHeight, 0 + , &yMtlTex + ); + + if (kCVReturnSuccess != cvr + || NULL == yMtlTex) + { + BX_TRACE("VideoDecoderMtl: Y plane texture create failed: %d", (int)cvr); + CFRelease(pic); + return false; + } + + cvr = CVMetalTextureCacheCreateTextureFromImage( + kCFAllocatorDefault + , m_textureCache + , pic + , NULL + , MTLPixelFormatRG8Unorm + , cWidth, cHeight, 1 + , &cMtlTex + ); + + if (kCVReturnSuccess != cvr + || NULL == cMtlTex) + { + BX_TRACE("VideoDecoderMtl: CbCr plane texture create failed: %d", (int)cvr); + CFRelease(yMtlTex); + CFRelease(pic); + return false; + } + + MTL::Texture* yTex = (MTL::Texture*)CVMetalTextureGetTexture(yMtlTex); + MTL::Texture* cTex = (MTL::Texture*)CVMetalTextureGetTexture(cMtlTex); + + BX_ASSERT(NULL != g_videoDecode, "g_videoDecode not initialized."); + PipelineStateMtl* pso = videoGetComputePipelineState(m_renderer, g_videoDecode->m_program); + + if (NULL == pso) + { + CFRelease(yMtlTex); + CFRelease(cMtlTex); + CFRelease(pic); + return false; + } + + videoEndEncoding(m_renderer); + + MTL::CommandBuffer* cmdBuf = videoEnsureCommandBuffer(m_renderer); + + MTL::ComputeCommandEncoder* cce = cmdBuf->computeCommandEncoder(); + cce->setComputePipelineState(pso->m_cps); + + MTL::SamplerState* linearClamp = videoGetSamplerState(m_renderer, + BGFX_SAMPLER_U_CLAMP + | BGFX_SAMPLER_V_CLAMP + | BGFX_SAMPLER_W_CLAMP + ); + + cce->setTexture(_dst, 0); + cce->setTexture(yTex, 1); + cce->setSamplerState(linearClamp, 1); + cce->setTexture(cTex, 2); + cce->setSamplerState(linearClamp, 2); + + const MTL::Size threadsPerGroup = MTL::Size::Make(8, 8, 1); + const MTL::Size groupCount = MTL::Size::Make( + (m_dstWidth + 7) / 8 + , (m_dstHeight + 7) / 8 + , 1 + ); + cce->dispatchThreadgroups(groupCount, threadsPerGroup); + cce->endEncoding(); + + CVPixelBufferRef retainedPic = pic; + CVMetalTextureRef retainedY = yMtlTex; + CVMetalTextureRef retainedC = cMtlTex; + + MTL::HandlerFunction releaseRefs = [retainedPic, retainedY, retainedC](MTL::CommandBuffer*) + { + CFRelease(retainedY); + CFRelease(retainedC); + CFRelease(retainedPic); + }; + + cmdBuf->addCompletedHandler(releaseRefs); + + return true; + } + + static void decodeOutputCallback( + void* _refCon + , void* /*_sourceFrameRefCon*/ + , OSStatus _status + , VTDecodeInfoFlags /*_infoFlags*/ + , CVImageBufferRef _imageBuffer + , CMTime _pts + , CMTime /*_duration*/ + ) + { + VideoDecoderMtl* self = (VideoDecoderMtl*)_refCon; + + if (noErr != _status + || NULL == _imageBuffer) + { + return; + } + + const int64_t ptsUs = CMTIME_IS_VALID(_pts) && _pts.timescale > 0 + ? int64_t(double(_pts.value) * 1000000.0 / double(_pts.timescale) ) + : INT64_MIN; + + bx::MutexScope lock(self->m_queueMutex); + + if (self->m_queueTail - self->m_queueHead >= kQueueCapacity) + { + const int64_t newPts = ptsUs; + uint32_t worstIdx = self->m_queueHead; + int64_t worstPts = self->m_queue[worstIdx % kQueueCapacity].ptsUs; + + for (uint32_t ii = self->m_queueHead + 1; ii != self->m_queueTail; ++ii) + { + const int64_t pts = self->m_queue[ii % kQueueCapacity].ptsUs; + + if (pts > worstPts) + { + worstPts = pts; + worstIdx = ii; + } + } + + if (newPts >= worstPts) + { + return; + } + + CFRelease(self->m_queue[worstIdx % kQueueCapacity].pb); + uint32_t writeIdx = self->m_queueHead; + + for (uint32_t ii = self->m_queueHead; ii != self->m_queueTail; ++ii) + { + if (ii == worstIdx) + { + continue; + } + + self->m_queue[writeIdx % kQueueCapacity] = self->m_queue[ii % kQueueCapacity]; + ++writeIdx; + } + + self->m_queueTail = writeIdx; + } + + CFRetain(_imageBuffer); + QueueEntry& e = self->m_queue[self->m_queueTail % kQueueCapacity]; + e.pb = (CVPixelBufferRef)_imageBuffer; + e.ptsUs = ptsUs; + ++self->m_queueTail; + } + + static constexpr uint32_t kQueueCapacity = 192; + static constexpr uint32_t kReorderDepth = 4; + + struct QueueEntry + { + CVPixelBufferRef pb; + int64_t ptsUs; + }; + + RendererContextMtl* m_renderer; + MTL::Device* m_device; + + CMVideoFormatDescriptionRef m_formatDesc; + VTDecompressionSessionRef m_session; + CVMetalTextureCacheRef m_textureCache; + bx::Mutex m_queueMutex; + QueueEntry m_queue[kQueueCapacity]; + uint32_t m_queueHead; + uint32_t m_queueTail; + uint16_t m_dstWidth; + uint16_t m_dstHeight; + uint32_t m_initFlags; + uint32_t m_cachedAuBytes; + uint64_t m_dtsCounter; + int64_t m_lastDisplayedPts; + }; + + VideoDecoderMtl* videoDecoderCreate(const VideoDecoderInit& _init, RendererContextMtl* _renderer, MTL::Device* _device, uint16_t _width, uint16_t _height) + { + VideoDecoderMtl* decoder = BX_NEW(g_allocator, VideoDecoderMtl); + + if (!decoder->create(_init, _renderer, _device, _width, _height) ) + { + decoder->destroy(); + bx::deleteObject(g_allocator, decoder); + + return NULL; + } + + return decoder; + } + + void videoDecoderDestroy(VideoDecoderMtl* _decoder) + { + if (NULL != _decoder) + { + _decoder->destroy(); + bx::deleteObject(g_allocator, _decoder); + } + } + + bool videoDecoderDecode(VideoDecoderMtl* _decoder, const VideoDecoderFrame& _frame, MTL::Texture* _dst) + { + return _decoder->decode(_frame, _dst); + } + + struct CodecProbe + { + VideoCodec::Enum codec; + CMVideoCodecType cmType; + uint32_t bitDepths; + uint32_t chroma; + }; + + static const CodecProbe s_probes[] = + { + { VideoCodec::H264, kCMVideoCodecType_H264, BGFX_CAPS_VIDEO_CODEC_BIT_8, BGFX_CAPS_VIDEO_CODEC_CHROMA_420 }, + { VideoCodec::H265, kCMVideoCodecType_HEVC, BGFX_CAPS_VIDEO_CODEC_BIT_8 | BGFX_CAPS_VIDEO_CODEC_BIT_10, BGFX_CAPS_VIDEO_CODEC_CHROMA_420 }, +#if defined(__MAC_14_0) || defined(__IPHONE_17_0) + { VideoCodec::AV1, kCMVideoCodecType_AV1, BGFX_CAPS_VIDEO_CODEC_BIT_8 | BGFX_CAPS_VIDEO_CODEC_BIT_10, BGFX_CAPS_VIDEO_CODEC_CHROMA_420 }, +#endif // defined(__MAC_14_0) || defined(__IPHONE_17_0) + }; + + void initVideoDecoder() + { + bool anySupported = false; + + for (uint32_t ii = 0; ii < BX_COUNTOF(s_probes); ++ii) + { + const CodecProbe& probe = s_probes[ii]; + bool supported = false; + + if (__builtin_available(macOS 10.13, iOS 11.0, tvOS 11.0, *) ) + { + supported = VTIsHardwareDecodeSupported(probe.cmType); + } + + if (supported) + { + g_caps.codecs[probe.codec] = probe.bitDepths | probe.chroma; + anySupported = true; + + BX_TRACE("[MTL] Video decode supported: codec = %d caps = 0x%08x" + , probe.codec + , g_caps.codecs[probe.codec] + ); + } + } + + if (anySupported) + { + g_caps.supported |= BGFX_CAPS_VIDEO_DECODE; + + const TextureFormat::Enum dstFormats[] = + { + TextureFormat::BGRA8, + TextureFormat::RGBA8, + TextureFormat::RGB10A2, + TextureFormat::RGBA16F, + }; + + for (uint32_t ii = 0; ii < BX_COUNTOF(dstFormats); ++ii) + { + const TextureFormat::Enum fmt = dstFormats[ii]; + if (0 != (g_caps.formats[fmt] & BGFX_CAPS_FORMAT_TEXTURE_2D) ) + { + g_caps.formats[fmt] |= BGFX_CAPS_FORMAT_TEXTURE_VIDEO_DECODE_DST; + } + } + } + } + +} } // namespace bgfx::mtl + +#else + +namespace bgfx { namespace mtl +{ + void initVideoDecoder() + { + } + + VideoDecoderMtl* videoDecoderCreate(const VideoDecoderInit& _init, RendererContextMtl* _renderer, MTL::Device* _device, uint16_t _width, uint16_t _height) + { + BX_UNUSED(_init, _renderer, _device, _width, _height); + return NULL; + } + + void videoDecoderDestroy(VideoDecoderMtl* _decoder) + { + BX_UNUSED(_decoder); + } + + bool videoDecoderDecode(VideoDecoderMtl* _decoder, const VideoDecoderFrame& _frame, MTL::Texture* _dst) + { + BX_UNUSED(_decoder, _frame, _dst); + return false; + } + +} } // namespace bgfx::mtl + +# endif // BGFX_CONFIG_VIDEO_METAL + +#endif // BGFX_CONFIG_RENDERER_METAL diff --git a/src/video_mtl.h b/src/video_mtl.h new file mode 100644 index 000000000..6d0836c38 --- /dev/null +++ b/src/video_mtl.h @@ -0,0 +1,24 @@ +/* + * Copyright 2011-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE + */ + +#ifndef BGFX_VIDEO_MTL_H_HEADER_GUARD +#define BGFX_VIDEO_MTL_H_HEADER_GUARD + +namespace MTL { class Device; class Texture; } + +namespace bgfx { namespace mtl +{ + struct RendererContextMtl; + struct VideoDecoderMtl; + + void initVideoDecoder(); + + VideoDecoderMtl* videoDecoderCreate(const VideoDecoderInit& _init, RendererContextMtl* _renderer, MTL::Device* _device, uint16_t _width, uint16_t _height); + void videoDecoderDestroy(VideoDecoderMtl* _decoder); + bool videoDecoderDecode(VideoDecoderMtl* _decoder, const VideoDecoderFrame& _frame, MTL::Texture* _dst); + +} } // namespace bgfx::mtl + +#endif // BGFX_VIDEO_MTL_H_HEADER_GUARD diff --git a/src/video_vk.cpp b/src/video_vk.cpp new file mode 100644 index 000000000..c7bd67d25 --- /dev/null +++ b/src/video_vk.cpp @@ -0,0 +1,2830 @@ +/* + * Copyright 2011-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE + */ + +#include "bgfx_p.h" + +#if BGFX_CONFIG_RENDERER_VULKAN +# include "renderer_vk.h" +# include "video_vk.h" + +# if BGFX_CONFIG_VIDEO_VULKAN +# include "video_vk.h" +# include "video.h" + +namespace bgfx { namespace vk +{ + struct RendererContextVK; + + bool videoIsExtensionSupported(RendererContextVK* _renderer, const char* _name); + void initVideoDecoder(RendererContextVK* _renderer, const VideoBindingVK& _binding); + int32_t videoSelectMemoryType(RendererContextVK* _renderer, uint32_t _typeBits, uint32_t _flags); + VkPipeline videoGetPipeline(RendererContextVK* _renderer, ProgramHandle _handle); + const ProgramVK& videoGetProgram(RendererContextVK* _renderer, ProgramHandle _handle); + VkCommandBuffer videoGetCommandBuffer(RendererContextVK* _renderer); + VkSampler videoGetSampler(RendererContextVK* _renderer, uint64_t _samplerFlags, VkFormat _format); + void videoRelease(RendererContextVK* _renderer, VkImageView& _obj); + + static VideoBindingVK s_videoVK; + + static StdVideoH264LevelIdc translateH264Level(int32_t _levelIdc) + { + switch (_levelIdc) + { + case 10: return STD_VIDEO_H264_LEVEL_IDC_1_0; + case 11: return STD_VIDEO_H264_LEVEL_IDC_1_1; + case 12: return STD_VIDEO_H264_LEVEL_IDC_1_2; + case 13: return STD_VIDEO_H264_LEVEL_IDC_1_3; + case 20: return STD_VIDEO_H264_LEVEL_IDC_2_0; + case 21: return STD_VIDEO_H264_LEVEL_IDC_2_1; + case 22: return STD_VIDEO_H264_LEVEL_IDC_2_2; + case 30: return STD_VIDEO_H264_LEVEL_IDC_3_0; + case 31: return STD_VIDEO_H264_LEVEL_IDC_3_1; + case 32: return STD_VIDEO_H264_LEVEL_IDC_3_2; + case 40: return STD_VIDEO_H264_LEVEL_IDC_4_0; + case 41: return STD_VIDEO_H264_LEVEL_IDC_4_1; + case 42: return STD_VIDEO_H264_LEVEL_IDC_4_2; + case 50: return STD_VIDEO_H264_LEVEL_IDC_5_0; + case 51: return STD_VIDEO_H264_LEVEL_IDC_5_1; + case 52: return STD_VIDEO_H264_LEVEL_IDC_5_2; + case 60: return STD_VIDEO_H264_LEVEL_IDC_6_0; + case 61: return STD_VIDEO_H264_LEVEL_IDC_6_1; + case 62: return STD_VIDEO_H264_LEVEL_IDC_6_2; + default: break; + } + + return STD_VIDEO_H264_LEVEL_IDC_4_0; + } + + struct VideoDecoderVK + { + static constexpr uint32_t kNumDpbSlots = 32; + static constexpr uint32_t kMaxSessionMemory = 8; + static constexpr uint32_t kCopyRingSize = 16; + static constexpr uint32_t kYuvDescriptorRingSize = 4; + + struct ReorderedPicture + { + ReorderedPicture() + : image(VK_NULL_HANDLE) + , memory(VK_NULL_HANDLE) + , yView(VK_NULL_HANDLE) + , cbcrView(VK_NULL_HANDLE) + , layout(VK_IMAGE_LAYOUT_UNDEFINED) + , ptsUs(0) + , displayOrder(-1) + { + } + + VkImage image; + VkDeviceMemory memory; + VkImageView yView; + VkImageView cbcrView; + VkImageLayout layout; + int64_t ptsUs; + int32_t displayOrder; + }; + + VideoDecoderVK() + : m_created(false) + , m_dstWidth(0) + , m_dstHeight(0) + , m_codedWidth(0) + , m_codedHeight(0) + , m_numDpbSlots(kNumDpbSlots) + , m_nextSlot(0) + , m_currentSlot(0) + , m_numActiveRefs(0) + , m_prevPocLsb(0) + , m_prevPocMsb(0) + , m_displayOrderNext(0) + , m_displayedSlot(-1) + , m_prevDisplayedSlot(-1) + , m_initFlags(0) + , m_cachedAuBytes(0) + , m_renderer(NULL) + , m_videoSession(VK_NULL_HANDLE) + , m_sessionParams(VK_NULL_HANDLE) + , m_numSessionMemory(0) + , m_maxActiveRefs(0) + , m_dpbCoincide(false) + , m_decodeOutputImage(VK_NULL_HANDLE) + , m_decodeOutputImageMemory(VK_NULL_HANDLE) + , m_decodeOutputImageView(VK_NULL_HANDLE) + , m_decodeOutputLayout(VK_IMAGE_LAYOUT_UNDEFINED) + , m_bitstreamBuffer(VK_NULL_HANDLE) + , m_bitstreamBufferMemory(VK_NULL_HANDLE) + , m_bitstreamBufferSize(0) + , m_bitstreamMapped(NULL) + , m_bitstreamAlignment(1) + , m_videoCommandPool(VK_NULL_HANDLE) + , m_videoCommandBuffer(VK_NULL_HANDLE) + , m_videoFence(VK_NULL_HANDLE) + , m_copyCommandPool(VK_NULL_HANDLE) + , m_copyControl(kCopyRingSize) + , m_lastCopyRingIdx(0) + , m_haveCopyInFlight(false) + , m_yuvDescriptorPool(VK_NULL_HANDLE) + , m_yuvDescriptorIndex(0) + , m_firstDecode(true) + { + bx::memSet(m_referenceUsage, 0, sizeof(m_referenceUsage) ); + bx::memSet(&m_spsActive, 0, sizeof(m_spsActive) ); + bx::memSet(m_spsArray, 0, sizeof(m_spsArray) ); + bx::memSet(m_ppsArray, 0, sizeof(m_ppsArray) ); + bx::memSet(m_spsValid, 0, sizeof(m_spsValid) ); + bx::memSet(m_ppsValid, 0, sizeof(m_ppsValid) ); + bx::memSet(m_sessionMemory, 0, sizeof(m_sessionMemory) ); + bx::memSet(&m_h264Profile, 0, sizeof(m_h264Profile) ); + bx::memSet(&m_videoProfile, 0, sizeof(m_videoProfile) ); + bx::memSet(m_pocStatus, 0, sizeof(m_pocStatus) ); + bx::memSet(m_frameNumStatus, 0, sizeof(m_frameNumStatus) ); + bx::memSet(&m_codedExtent, 0, sizeof(m_codedExtent) ); + bx::memSet(&m_stdHeaderVersion, 0, sizeof(m_stdHeaderVersion) ); + bx::memSet(m_yuvDescriptorSet, 0, sizeof(m_yuvDescriptorSet) ); + bx::memSet(m_copyCommandBufferRing, 0, sizeof(m_copyCommandBufferRing) ); + bx::memSet(m_copyFenceRing, 0, sizeof(m_copyFenceRing) ); + bx::memSet(m_videoSemaphoreRing, 0, sizeof(m_videoSemaphoreRing) ); + + m_dpbImage = VK_NULL_HANDLE; + m_dpbImageMemory = VK_NULL_HANDLE; + bx::memSet(m_dpbImageView, 0, sizeof(m_dpbImageView) ); + + for (uint32_t ii = 0; ii < kNumDpbSlots; ++ii) + { + m_dpbLayouts[ii] = VK_IMAGE_LAYOUT_UNDEFINED; + } + + for (uint32_t ii = 0; ii < kVideoMaxReorderInFlight; ++ii) + { + m_reorderPool[ii].displayOrder = -1; + m_reorderPool[ii].ptsUs = 0; + } + } + + bool create(const VideoDecoderInit& _init, RendererContextVK* _renderer, uint16_t _width, uint16_t _height) + { + m_renderer = _renderer; + m_initFlags = _init.flags; + m_cachedAuBytes = (0 != _init.cachedAuBytes) ? _init.cachedAuBytes : (4u << 20); + + m_auQueue.configure(m_cachedAuBytes, 0 != (m_initFlags & BGFX_VIDEO_DECODER_INIT_RETAIN) ); + + if (VideoCodec::H264 != _init.codec) + { + BX_TRACE("VideoDecoderVK: codec %d not yet supported.", _init.codec); + return false; + } + + if (NULL == _init.parameterSets || 0 == _init.parameterSetsSize) + { + BX_TRACE("VideoDecoderVK: empty H.264 parameter sets."); + return false; + } + + if (NULL == vkCmdBeginVideoCodingKHR + || NULL == vkCmdDecodeVideoKHR + || NULL == vkCreateVideoSessionKHR + || UINT32_MAX == s_videoVK.videoDecodeQueueFamily + ) + { + BX_TRACE("VideoDecoderVK: VK_KHR_video decode entry points/queue are unavailable."); + return false; + } + + h264::PPS ppsActive = {}; + if (!bgfx::parseParameterSets( + m_spsArray + , BX_COUNTOF(m_spsArray) + , m_ppsArray + , BX_COUNTOF(m_ppsArray) + , m_spsActive + , ppsActive + , _init.parameterSets + , _init.parameterSetsSize + , m_spsValid + , m_ppsValid + ) ) + { + BX_TRACE("VideoDecoderVK: failed to parse SPS/PPS."); + return false; + } + + if (0 != m_spsActive.bit_depth_luma_minus8 + || 0 != m_spsActive.bit_depth_chroma_minus8 + || 1 != m_spsActive.chroma_format_idc) + { + BX_TRACE("VideoDecoderVK: only 8-bit 4:2:0/NV12 H.264 is supported."); + return false; + } + + m_dstWidth = _width; + m_dstHeight = _height; + m_codedWidth = uint32_t(m_spsActive.pic_width_in_mbs_minus1 + 1) * 16; + m_codedHeight = uint32_t(m_spsActive.pic_height_in_map_units_minus1 + 1) * 16 * (m_spsActive.frame_mbs_only_flag ? 1 : 2); + m_numDpbSlots = bx::min(bx::max(uint32_t(m_spsActive.num_ref_frames) + 1, 2), kNumDpbSlots); + + VkDevice device = s_videoVK.device; + const VkAllocationCallbacks* allocCb = s_videoVK.allocCb; + + m_h264Profile = + { + .sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_H264_PROFILE_INFO_KHR, + .pNext = NULL, + .stdProfileIdc = StdVideoH264ProfileIdc(m_spsActive.profile_idc), + .pictureLayout = VK_VIDEO_DECODE_H264_PICTURE_LAYOUT_PROGRESSIVE_KHR, + }; + + m_videoProfile = + { + .sType = VK_STRUCTURE_TYPE_VIDEO_PROFILE_INFO_KHR, + .pNext = &m_h264Profile, + .videoCodecOperation = VK_VIDEO_CODEC_OPERATION_DECODE_H264_BIT_KHR, + .chromaSubsampling = VK_VIDEO_CHROMA_SUBSAMPLING_420_BIT_KHR, + .lumaBitDepth = VK_VIDEO_COMPONENT_BIT_DEPTH_8_BIT_KHR, + .chromaBitDepth = VK_VIDEO_COMPONENT_BIT_DEPTH_8_BIT_KHR, + }; + + VkVideoDecodeH264CapabilitiesKHR h264Caps + { + .sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_H264_CAPABILITIES_KHR, + .pNext = NULL, + .maxLevelIdc = STD_VIDEO_H264_LEVEL_IDC_INVALID, + .fieldOffsetGranularity = {}, + }; + + VkVideoDecodeCapabilitiesKHR decodeCaps + { + .sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_CAPABILITIES_KHR, + .pNext = &h264Caps, + .flags = 0, + }; + + VkVideoCapabilitiesKHR videoCaps + { + .sType = VK_STRUCTURE_TYPE_VIDEO_CAPABILITIES_KHR, + .pNext = &decodeCaps, + .flags = 0, + .minBitstreamBufferOffsetAlignment = 0, + .minBitstreamBufferSizeAlignment = 0, + .pictureAccessGranularity = {}, + .minCodedExtent = {}, + .maxCodedExtent = {}, + .maxDpbSlots = 0, + .maxActiveReferencePictures = 0, + .stdHeaderVersion = {}, + }; + VkResult result = vkGetPhysicalDeviceVideoCapabilitiesKHR(s_videoVK.physicalDevice, &m_videoProfile, &videoCaps); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkGetPhysicalDeviceVideoCapabilitiesKHR failed (%d).", result); + return false; + } + + m_maxActiveRefs = videoCaps.maxActiveReferencePictures; + + if (BGFX_PCI_ID_INTEL != s_videoVK.vendorId) + { + m_numDpbSlots = bx::min(m_numDpbSlots, videoCaps.maxDpbSlots); + } + + m_stdHeaderVersion = videoCaps.stdHeaderVersion; + + BX_TRACE( + "VideoDecoderVK: num_ref_frames=%u m_numDpbSlots=%u videoCaps.maxDpbSlots=%u maxActiveRefs=%u" + , uint32_t(m_spsActive.num_ref_frames) + , m_numDpbSlots + , videoCaps.maxDpbSlots + , m_maxActiveRefs + ); + + const VkExtent2D granularity = videoCaps.pictureAccessGranularity; + const uint32_t alignedW = (m_codedWidth + granularity.width - 1) / granularity.width * granularity.width; + const uint32_t alignedH = (m_codedHeight + granularity.height - 1) / granularity.height * granularity.height; + + VkVideoSessionCreateInfoKHR sessionCi + { + .sType = VK_STRUCTURE_TYPE_VIDEO_SESSION_CREATE_INFO_KHR, + .pNext = NULL, + .queueFamilyIndex = s_videoVK.videoDecodeQueueFamily, + .flags = 0, + .pVideoProfile = &m_videoProfile, + .pictureFormat = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, + .maxCodedExtent = { .width = alignedW, .height = alignedH }, + .referencePictureFormat = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, + .maxDpbSlots = m_numDpbSlots, + .maxActiveReferencePictures = bx::min(uint32_t(m_spsActive.num_ref_frames) * 2, m_maxActiveRefs), + .pStdHeaderVersion = &videoCaps.stdHeaderVersion, + }; + result = vkCreateVideoSessionKHR(device, &sessionCi, allocCb, &m_videoSession); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkCreateVideoSessionKHR failed (%d).", result); + return false; + } + + uint32_t reqCount = 0; + vkGetVideoSessionMemoryRequirementsKHR(device, m_videoSession, &reqCount, NULL); + + if (reqCount > kMaxSessionMemory) + { + BX_TRACE("VideoDecoderVK: too many session memory requirements (%u > %u).", reqCount, kMaxSessionMemory); + destroy(); + return false; + } + + VkVideoSessionMemoryRequirementsKHR reqs[kMaxSessionMemory] = {}; + + for (uint32_t ii = 0; ii < reqCount; ++ii) + { + reqs[ii].sType = VK_STRUCTURE_TYPE_VIDEO_SESSION_MEMORY_REQUIREMENTS_KHR; + } + + vkGetVideoSessionMemoryRequirementsKHR(device, m_videoSession, &reqCount, reqs); + + VkBindVideoSessionMemoryInfoKHR binds[kMaxSessionMemory] = {}; + + for (uint32_t ii = 0; ii < reqCount; ++ii) + { + VkMemoryAllocateInfo ai + { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .pNext = NULL, + .allocationSize = reqs[ii].memoryRequirements.size, + .memoryTypeIndex = uint32_t(videoSelectMemoryType(m_renderer, reqs[ii].memoryRequirements.memoryTypeBits, 0) ), + }; + + result = vkAllocateMemory(device, &ai, allocCb, &m_sessionMemory[ii]); + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkAllocateMemory[%u] failed (%d).", ii, result); + destroy(); + return false; + } + + m_numSessionMemory = ii + 1; + + binds[ii] = + { + .sType = VK_STRUCTURE_TYPE_BIND_VIDEO_SESSION_MEMORY_INFO_KHR, + .pNext = NULL, + .memoryBindIndex = reqs[ii].memoryBindIndex, + .memory = m_sessionMemory[ii], + .memoryOffset = 0, + .memorySize = ai.allocationSize, + }; + } + + result = vkBindVideoSessionMemoryKHR(device, m_videoSession, reqCount, binds); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkBindVideoSessionMemoryKHR failed (%d).", result); + destroy(); + return false; + } + + if (!createSessionParameters() ) + { + destroy(); + return false; + } + + m_dpbCoincide = (decodeCaps.flags & VK_VIDEO_DECODE_CAPABILITY_DPB_AND_OUTPUT_COINCIDE_BIT_KHR) != 0; + m_codedExtent.width = alignedW; + m_codedExtent.height = alignedH; + m_bitstreamAlignment = uint32_t(bx::max(videoCaps.minBitstreamBufferOffsetAlignment, videoCaps.minBitstreamBufferSizeAlignment) ); + + if (0 == m_bitstreamAlignment) + { + m_bitstreamAlignment = 1; + } + + VkVideoProfileListInfoKHR profileList + { + .sType = VK_STRUCTURE_TYPE_VIDEO_PROFILE_LIST_INFO_KHR, + .pNext = NULL, + .profileCount = 1, + .pProfiles = &m_videoProfile, + }; + + const uint32_t videoSharedQueueFamilies[] = + { + s_videoVK.globalQueueFamily, + s_videoVK.videoDecodeQueueFamily, + }; + + const bool isMultiQueue = s_videoVK.globalQueueFamily != s_videoVK.videoDecodeQueueFamily; + const VkSharingMode videoSharingMode = isMultiQueue ? VK_SHARING_MODE_CONCURRENT : VK_SHARING_MODE_EXCLUSIVE; + const uint32_t videoSharedQueueCount = isMultiQueue ? uint32_t(BX_COUNTOF(videoSharedQueueFamilies) ) : 0u; + const uint32_t* videoSharedQueuePtr = isMultiQueue ? videoSharedQueueFamilies : NULL; + + { + const VkImageUsageFlags dpbUsage = m_dpbCoincide + ? VkImageUsageFlags(VK_IMAGE_USAGE_VIDEO_DECODE_DPB_BIT_KHR | VK_IMAGE_USAGE_VIDEO_DECODE_DST_BIT_KHR | VK_IMAGE_USAGE_TRANSFER_SRC_BIT) + : VkImageUsageFlags(VK_IMAGE_USAGE_VIDEO_DECODE_DPB_BIT_KHR) + ; + + VkImageCreateInfo ci + { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = &profileList, + .flags = 0, + .imageType = VK_IMAGE_TYPE_2D, + .format = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, + .extent = { alignedW, alignedH, 1 }, + .mipLevels = 1, + .arrayLayers = m_numDpbSlots, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = dpbUsage, + .sharingMode = m_dpbCoincide ? videoSharingMode : VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = m_dpbCoincide ? videoSharedQueueCount : 0u, + .pQueueFamilyIndices = m_dpbCoincide ? videoSharedQueuePtr : NULL, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + }; + result = vkCreateImage(device, &ci, allocCb, &m_dpbImage); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkCreateImage(DPB) failed (%d).", result); + destroy(); + return false; + } + + VkMemoryRequirements req = {}; + vkGetImageMemoryRequirements(device, m_dpbImage, &req); + + VkMemoryAllocateInfo ai + { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .pNext = NULL, + .allocationSize = req.size, + .memoryTypeIndex = uint32_t(videoSelectMemoryType(m_renderer, req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) ), + }; + result = vkAllocateMemory(device, &ai, allocCb, &m_dpbImageMemory); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkAllocateMemory(DPB) failed (%d).", result); + destroy(); + return false; + } + + vkBindImageMemory(device, m_dpbImage, m_dpbImageMemory, 0); + + for (uint32_t slot = 0; slot < m_numDpbSlots; ++slot) + { + VkImageViewUsageCreateInfo viewUsage + { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO, + .pNext = NULL, + .usage = m_dpbCoincide + ? VkImageUsageFlags(VK_IMAGE_USAGE_VIDEO_DECODE_DPB_BIT_KHR | VK_IMAGE_USAGE_VIDEO_DECODE_DST_BIT_KHR) + : VkImageUsageFlags(VK_IMAGE_USAGE_VIDEO_DECODE_DPB_BIT_KHR), + }; + + VkImageViewCreateInfo vci + { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = &viewUsage, + .flags = 0, + .image = m_dpbImage, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, + .components = {}, + .subresourceRange = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = slot, + .layerCount = 1, + }, + }; + result = vkCreateImageView(device, &vci, allocCb, &m_dpbImageView[slot]); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkCreateImageView(DPB[%u]) failed (%d).", slot, result); + destroy(); + return false; + } + } + } + + if (!m_dpbCoincide) + { + VkImageCreateInfo ci + { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = &profileList, + .flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT, + .imageType = VK_IMAGE_TYPE_2D, + .format = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, + .extent = { alignedW, alignedH, 1 }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = 0 + | VK_IMAGE_USAGE_VIDEO_DECODE_DST_BIT_KHR + | VK_IMAGE_USAGE_TRANSFER_SRC_BIT + , + .sharingMode = videoSharingMode, + .queueFamilyIndexCount = videoSharedQueueCount, + .pQueueFamilyIndices = videoSharedQueuePtr, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + }; + result = vkCreateImage(device, &ci, allocCb, &m_decodeOutputImage); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkCreateImage(decode-output) failed (%d).", result); + destroy(); + return false; + } + + VkMemoryRequirements req = {}; + vkGetImageMemoryRequirements(device, m_decodeOutputImage, &req); + + VkMemoryAllocateInfo ai + { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .pNext = NULL, + .allocationSize = req.size, + .memoryTypeIndex = uint32_t(videoSelectMemoryType(m_renderer, req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) ), + }; + result = vkAllocateMemory(device, &ai, allocCb, &m_decodeOutputImageMemory); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkAllocateMemory(decode-output) failed (%d).", result); + destroy(); + return false; + } + vkBindImageMemory(device, m_decodeOutputImage, m_decodeOutputImageMemory, 0); + + VkImageViewUsageCreateInfo viewUsage + { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO, + .pNext = NULL, + .usage = VK_IMAGE_USAGE_VIDEO_DECODE_DST_BIT_KHR, + }; + + VkImageViewCreateInfo vci + { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = &viewUsage, + .flags = 0, + .image = m_decodeOutputImage, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = ci.format, + .components = {}, + .subresourceRange = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + result = vkCreateImageView(device, &vci, allocCb, &m_decodeOutputImageView); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkCreateImageView(decode-output) failed (%d).", result); + destroy(); + return false; + } + } + + { + const VkDeviceSize pixelFootprint = VkDeviceSize(m_codedWidth) * VkDeviceSize(m_codedHeight) * 3 / 2; + m_bitstreamBufferSize = bx::max(4 * 1024 * 1024, pixelFootprint); + m_bitstreamBufferSize = bx::alignUp(m_bitstreamBufferSize, bx::max(m_bitstreamAlignment, 1) ); + + VkBufferCreateInfo bi + { + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = &profileList, + .flags = 0, + .size = m_bitstreamBufferSize, + .usage = VK_BUFFER_USAGE_VIDEO_DECODE_SRC_BIT_KHR, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = NULL, + }; + result = vkCreateBuffer(device, &bi, allocCb, &m_bitstreamBuffer); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkCreateBuffer(bitstream) failed (%d).", result); + destroy(); + return false; + } + + VkMemoryRequirements req = {}; + vkGetBufferMemoryRequirements(device, m_bitstreamBuffer, &req); + + const int32_t typeIdx = videoSelectMemoryType( + m_renderer + , req.memoryTypeBits + , VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT + ); + + if (-1 == typeIdx) + { + BX_TRACE("VideoDecoderVK: no host-visible memory type for bitstream buffer."); + destroy(); + return false; + } + + VkMemoryAllocateInfo ai + { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .pNext = NULL, + .allocationSize = req.size, + .memoryTypeIndex = uint32_t(typeIdx), + }; + result = vkAllocateMemory(device, &ai, allocCb, &m_bitstreamBufferMemory); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkAllocateMemory(bitstream) failed (%d).", result); + destroy(); + return false; + } + + vkBindBufferMemory(device, m_bitstreamBuffer, m_bitstreamBufferMemory, 0); + result = vkMapMemory( + device + , m_bitstreamBufferMemory + , 0 + , m_bitstreamBufferSize + , 0 + , &m_bitstreamMapped + ); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkMapMemory(bitstream) failed (%d).", result); + destroy(); + return false; + } + } + + { + VkCommandPoolCreateInfo pci + { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .pNext = NULL, + .flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT, + .queueFamilyIndex = s_videoVK.videoDecodeQueueFamily, + }; + result = vkCreateCommandPool(device, &pci, allocCb, &m_videoCommandPool); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkCreateCommandPool(video) failed (%d).", result); + destroy(); + return false; + } + + VkCommandBufferAllocateInfo cbi + { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .pNext = NULL, + .commandPool = m_videoCommandPool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1, + }; + result = vkAllocateCommandBuffers(device, &cbi, &m_videoCommandBuffer); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkAllocateCommandBuffers(video) failed (%d).", result); + destroy(); + return false; + } + + VkFenceCreateInfo fci + { + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, + .pNext = NULL, + .flags = VK_FENCE_CREATE_SIGNALED_BIT, + }; + result = vkCreateFence(device, &fci, allocCb, &m_videoFence); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkCreateFence(video) failed (%d).", result); + destroy(); + return false; + } + } + + { + VkCommandPoolCreateInfo pci + { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .pNext = NULL, + .flags = 0 + | VK_COMMAND_POOL_CREATE_TRANSIENT_BIT + | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT + , + .queueFamilyIndex = s_videoVK.globalQueueFamily, + }; + result = vkCreateCommandPool(device, &pci, allocCb, &m_copyCommandPool); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkCreateCommandPool(copy) failed (%d).", result); + destroy(); + return false; + } + + VkCommandBufferAllocateInfo cbi + { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .pNext = NULL, + .commandPool = m_copyCommandPool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = kCopyRingSize, + }; + result = vkAllocateCommandBuffers(device, &cbi, m_copyCommandBufferRing); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkAllocateCommandBuffers(copy ring) failed (%d).", result); + destroy(); + return false; + } + + VkFenceCreateInfo cfci + { + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, + .pNext = NULL, + .flags = VK_FENCE_CREATE_SIGNALED_BIT, + }; + + for (uint32_t ii = 0; ii < kCopyRingSize; ++ii) + { + result = vkCreateFence(device, &cfci, allocCb, &m_copyFenceRing[ii]); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkCreateFence(copy ring) failed (%d).", result); + destroy(); + return false; + } + } + + VkSemaphoreCreateInfo sci + { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .pNext = NULL, + .flags = 0, + }; + + for (uint32_t ii = 0; ii < kCopyRingSize; ++ii) + { + result = vkCreateSemaphore(device, &sci, allocCb, &m_videoSemaphoreRing[ii]); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkCreateSemaphore(video ring) failed (%d).", result); + destroy(); + return false; + } + } + + m_copyControl.reset(); + } + + m_firstDecode = true; + m_created = true; + + BX_TRACE( + "VideoDecoderVK: H.264 session ready, coded %ux%u, dst %ux%u, %u DPB slots, coincide=%d." + , m_codedWidth + , m_codedHeight + , m_dstWidth + , m_dstHeight + , m_numDpbSlots + , m_dpbCoincide ? 1 : 0 + ); + + return true; + } + + void destroy() + { + VkDevice device = s_videoVK.device; + const VkAllocationCallbacks* allocCb = s_videoVK.allocCb; + + vkDeviceWaitIdle(device); + + if (VK_NULL_HANDLE != m_videoFence) + { + vkWaitForFences(device, 1, &m_videoFence, VK_TRUE, UINT64_MAX); + vkDestroyFence(device, m_videoFence, allocCb); + m_videoFence = VK_NULL_HANDLE; + } + + if (VK_NULL_HANDLE != m_videoCommandPool) + { + vkDestroyCommandPool(device, m_videoCommandPool, allocCb); + m_videoCommandPool = VK_NULL_HANDLE; + m_videoCommandBuffer = VK_NULL_HANDLE; + } + + for (uint32_t ii = 0; ii < kCopyRingSize; ++ii) + { + if (VK_NULL_HANDLE != m_copyFenceRing[ii]) + { + vkWaitForFences(device, 1, &m_copyFenceRing[ii], VK_TRUE, UINT64_MAX); + vkDestroyFence(device, m_copyFenceRing[ii], allocCb); + m_copyFenceRing[ii] = VK_NULL_HANDLE; + } + } + + for (uint32_t ii = 0; ii < kCopyRingSize; ++ii) + { + if (VK_NULL_HANDLE != m_videoSemaphoreRing[ii]) + { + vkDestroySemaphore(device, m_videoSemaphoreRing[ii], allocCb); + m_videoSemaphoreRing[ii] = VK_NULL_HANDLE; + } + } + + if (VK_NULL_HANDLE != m_copyCommandPool) + { + vkDestroyCommandPool(device, m_copyCommandPool, allocCb); + m_copyCommandPool = VK_NULL_HANDLE; + bx::memSet(m_copyCommandBufferRing, 0, sizeof(m_copyCommandBufferRing) ); + } + + if (VK_NULL_HANDLE != m_yuvDescriptorPool) + { + vkDestroyDescriptorPool(device, m_yuvDescriptorPool, allocCb); + m_yuvDescriptorPool = VK_NULL_HANDLE; + bx::memSet(m_yuvDescriptorSet, 0, sizeof(m_yuvDescriptorSet) ); + m_yuvDescriptorIndex = 0; + } + + if (VK_NULL_HANDLE != m_bitstreamBufferMemory) + { + if (NULL != m_bitstreamMapped) + { + vkUnmapMemory(device, m_bitstreamBufferMemory); + m_bitstreamMapped = NULL; + } + } + + if (VK_NULL_HANDLE != m_bitstreamBuffer) + { + vkDestroyBuffer(device, m_bitstreamBuffer, allocCb); + m_bitstreamBuffer = VK_NULL_HANDLE; + } + + if (VK_NULL_HANDLE != m_bitstreamBufferMemory) + { + vkFreeMemory(device, m_bitstreamBufferMemory, allocCb); + m_bitstreamBufferMemory = VK_NULL_HANDLE; + } + + if (VK_NULL_HANDLE != m_decodeOutputImageView) + { + vkDestroyImageView(device, m_decodeOutputImageView, allocCb); + m_decodeOutputImageView = VK_NULL_HANDLE; + } + + if (VK_NULL_HANDLE != m_decodeOutputImage) + { + vkDestroyImage(device, m_decodeOutputImage, allocCb); + m_decodeOutputImage = VK_NULL_HANDLE; + } + + if (VK_NULL_HANDLE != m_decodeOutputImageMemory) + { + vkFreeMemory(device, m_decodeOutputImageMemory, allocCb); + m_decodeOutputImageMemory = VK_NULL_HANDLE; + } + + for (uint32_t ii = 0; ii < kVideoMaxReorderInFlight; ++ii) + { + ReorderedPicture& pic = m_reorderPool[ii]; + + if (VK_NULL_HANDLE != pic.yView) + { + vkDestroyImageView(device, pic.yView, allocCb); + pic.yView = VK_NULL_HANDLE; + } + + if (VK_NULL_HANDLE != pic.cbcrView) + { + vkDestroyImageView(device, pic.cbcrView, allocCb); + pic.cbcrView = VK_NULL_HANDLE; + } + + if (VK_NULL_HANDLE != pic.image) + { + vkDestroyImage(device, pic.image, allocCb); + pic.image = VK_NULL_HANDLE; + } + + if (VK_NULL_HANDLE != pic.memory) + { + vkFreeMemory(device, pic.memory, allocCb); + pic.memory = VK_NULL_HANDLE; + } + + pic.layout = VK_IMAGE_LAYOUT_UNDEFINED; + } + + for (uint32_t ii = 0; ii < kNumDpbSlots; ++ii) + { + if (VK_NULL_HANDLE != m_dpbImageView[ii]) + { + vkDestroyImageView(device, m_dpbImageView[ii], allocCb); + m_dpbImageView[ii] = VK_NULL_HANDLE; + } + } + + if (VK_NULL_HANDLE != m_dpbImage) + { + vkDestroyImage(device, m_dpbImage, allocCb); + m_dpbImage = VK_NULL_HANDLE; + } + + if (VK_NULL_HANDLE != m_dpbImageMemory) + { + vkFreeMemory(device, m_dpbImageMemory, allocCb); + m_dpbImageMemory = VK_NULL_HANDLE; + } + + if (VK_NULL_HANDLE != m_sessionParams) + { + vkDestroyVideoSessionParametersKHR(device, m_sessionParams, allocCb); + m_sessionParams = VK_NULL_HANDLE; + } + + if (VK_NULL_HANDLE != m_videoSession) + { + vkDestroyVideoSessionKHR(device, m_videoSession, allocCb); + m_videoSession = VK_NULL_HANDLE; + } + + for (uint32_t ii = 0; ii < m_numSessionMemory; ++ii) + { + if (VK_NULL_HANDLE != m_sessionMemory[ii]) + { + vkFreeMemory(device, m_sessionMemory[ii], allocCb); + m_sessionMemory[ii] = VK_NULL_HANDLE; + } + } + + m_numSessionMemory = 0; + + for (uint32_t ii = 0; ii < kNumDpbSlots; ++ii) + { + m_dpbLayouts[ii] = VK_IMAGE_LAYOUT_UNDEFINED; + m_pocStatus[ii] = 0; + m_frameNumStatus[ii] = 0; + } + + m_decodeOutputLayout = VK_IMAGE_LAYOUT_UNDEFINED; + m_firstDecode = true; + m_haveCopyInFlight = false; + m_lastCopyRingIdx = 0; + + m_created = false; + resetReorder(); + } + + bool decode(const VideoDecoderFrame& _frame, TextureVK& _dst) + { + if (!m_created) + { + return false; + } + + if (0 != (_frame.flags & BGFX_VIDEO_DECODE_FRAME_SET) ) + { + resetForSet(); + } + + if (NULL != _frame.bitstream && 0 != _frame.numAus) + { + const uint8_t* bs = _frame.bitstream; + for (uint32_t ii = 0; ii < _frame.numAus; ++ii) + { + const uint32_t auSize = _frame.aus[ii].size; + const bool isIdr = bgfx::peekAuIsIdr(bs, auSize); + m_auQueue.enqueue(bs, auSize, _frame.aus[ii].ptsUs, isIdr); + bs += auSize; + } + } + + while (!m_auQueue.empty() + && bgfx::hasReorderSpace(m_reorderPool, kVideoMaxReorderInFlight) ) + { + const bgfx::PendingAU& au = m_auQueue.front(); + decodeOneAU(au.data.data(), uint32_t(au.data.size() ), au.ptsUs); + m_auQueue.pop(); + } + + m_auQueue.compact(); + + if (0 == _frame.numAus + && 0 == (_frame.flags & BGFX_VIDEO_DECODE_FRAME_NO_BLIT) ) + { + int64_t adjustedClock = _frame.presentationTimeUs; + + if (0 != (_frame.flags & BGFX_VIDEO_DECODE_FRAME_LOOP) + && m_auQueue.isRetaining() + && m_auQueue.minPts() < m_auQueue.maxPts() + ) + { + adjustedClock = bgfx::applyLoopWrap(adjustedClock, m_auQueue.minPts(), m_auQueue.maxPts() ); + + const bool poolCovers = bgfx::reorderPoolCovers(m_reorderPool, kVideoMaxReorderInFlight, adjustedClock, 500000); + const bool forwardCanCatchUp = true + && !m_auQueue.empty() + && m_auQueue.front().ptsUs <= adjustedClock + 500000 + ; + + if (!poolCovers + && !forwardCanCatchUp) + { + bgfx::faultInFromCache( + m_auQueue + , adjustedClock + , [this]() { resetForSet(); } + , [this](const uint8_t* _bs, uint32_t _bsSize, int64_t _pts) + { + decodeOneAU(_bs, _bsSize, _pts); + } + , [this]() + { + return bgfx::hasReorderSpace(m_reorderPool, kVideoMaxReorderInFlight); + } + ); + } + } + + pickDisplaySlotForTime(adjustedClock, _dst); + } + + return true; + } + + void resetForSet() + { + if (m_auQueue.isRetaining() ) + { + m_auQueue.rewindTo(m_auQueue.size() ); + } + else + { + m_auQueue.clear(); + } + + for (uint32_t ii = 0; ii < kVideoMaxReorderInFlight; ++ii) + { + m_reorderPool[ii].displayOrder = -1; + m_reorderPool[ii].ptsUs = 0; + } + + m_displayedSlot = -1; + m_prevDisplayedSlot = -1; + m_displayOrderNext = 0; + + m_numActiveRefs = 0; + m_nextSlot = 0; + m_currentSlot = 0; + + bx::memSet(m_referenceUsage, 0, sizeof(m_referenceUsage) ); + bx::memSet(m_pocStatus, 0, sizeof(m_pocStatus) ); + bx::memSet(m_frameNumStatus, 0, sizeof(m_frameNumStatus) ); + + m_prevPocLsb = 0; + m_prevPocMsb = 0; + m_firstDecode = true; + + for (uint32_t ii = 0; ii < kNumDpbSlots; ++ii) + { + m_dpbLayouts[ii] = VK_IMAGE_LAYOUT_UNDEFINED; + } + m_decodeOutputLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + if (BGFX_PCI_ID_INTEL == s_videoVK.vendorId) + { + recreateVideoSession(); + } + } + + void recreateVideoSession() + { + VkDevice device = s_videoVK.device; + const VkAllocationCallbacks* allocCb = s_videoVK.allocCb; + + vkQueueWaitIdle(s_videoVK.videoDecodeQueue); + vkQueueWaitIdle(s_videoVK.globalQueue); + + if (VK_NULL_HANDLE != m_sessionParams) + { + vkDestroyVideoSessionParametersKHR(device, m_sessionParams, allocCb); + m_sessionParams = VK_NULL_HANDLE; + } + + if (VK_NULL_HANDLE != m_videoSession) + { + vkDestroyVideoSessionKHR(device, m_videoSession, allocCb); + m_videoSession = VK_NULL_HANDLE; + } + + for (uint32_t ii = 0; ii < m_numSessionMemory; ++ii) + { + if (VK_NULL_HANDLE != m_sessionMemory[ii]) + { + vkFreeMemory(device, m_sessionMemory[ii], allocCb); + m_sessionMemory[ii] = VK_NULL_HANDLE; + } + } + m_numSessionMemory = 0; + + VkVideoSessionCreateInfoKHR sessionCi + { + .sType = VK_STRUCTURE_TYPE_VIDEO_SESSION_CREATE_INFO_KHR, + .pNext = NULL, + .queueFamilyIndex = s_videoVK.videoDecodeQueueFamily, + .flags = 0, + .pVideoProfile = &m_videoProfile, + .pictureFormat = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, + .maxCodedExtent = m_codedExtent, + .referencePictureFormat = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, + .maxDpbSlots = m_numDpbSlots, + .maxActiveReferencePictures = bx::min(uint32_t(m_spsActive.num_ref_frames) * 2, m_maxActiveRefs), + .pStdHeaderVersion = &m_stdHeaderVersion, + }; + + VkResult result = vkCreateVideoSessionKHR(device, &sessionCi, allocCb, &m_videoSession); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: recreateVideoSession vkCreateVideoSessionKHR failed (%d).", result); + return; + } + + uint32_t reqCount = 0; + vkGetVideoSessionMemoryRequirementsKHR(device, m_videoSession, &reqCount, NULL); + + VkVideoSessionMemoryRequirementsKHR reqs[kMaxSessionMemory] = {}; + + for (uint32_t ii = 0; ii < reqCount; ++ii) + { + reqs[ii].sType = VK_STRUCTURE_TYPE_VIDEO_SESSION_MEMORY_REQUIREMENTS_KHR; + } + + vkGetVideoSessionMemoryRequirementsKHR(device, m_videoSession, &reqCount, reqs); + + VkBindVideoSessionMemoryInfoKHR binds[kMaxSessionMemory] = {}; + + for (uint32_t ii = 0; ii < reqCount; ++ii) + { + VkMemoryAllocateInfo ai + { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .pNext = NULL, + .allocationSize = reqs[ii].memoryRequirements.size, + .memoryTypeIndex = uint32_t(videoSelectMemoryType(m_renderer, reqs[ii].memoryRequirements.memoryTypeBits, 0) ), + }; + vkAllocateMemory(device, &ai, allocCb, &m_sessionMemory[ii]); + m_numSessionMemory = ii + 1; + + binds[ii] = + { + .sType = VK_STRUCTURE_TYPE_BIND_VIDEO_SESSION_MEMORY_INFO_KHR, + .pNext = NULL, + .memoryBindIndex = reqs[ii].memoryBindIndex, + .memory = m_sessionMemory[ii], + .memoryOffset = 0, + .memorySize = ai.allocationSize, + }; + } + + if (reqCount > 0) + { + vkBindVideoSessionMemoryKHR(device, m_videoSession, reqCount, binds); + } + + createSessionParameters(); + } + + void pickDisplaySlotForTime(int64_t _presentationTimeUs, TextureVK& _dst) + { + bgfx::pickDisplaySlotForTime( + _presentationTimeUs + , m_reorderPool + , kVideoMaxReorderInFlight + , m_displayedSlot + , m_prevDisplayedSlot + , m_auQueue.empty() + , [this, &_dst](uint32_t _slot) + { + dispatchYuvToRgb(_slot, _dst); + } + ); + } + + bool decodeOneAU(const uint8_t* _bitstream, uint32_t _bitstreamSize, int64_t _ptsUs) + { + if (NULL == _bitstream + || 0 == _bitstreamSize) + { + return false; + } + + h264::NALHeader nal = {}; + h264::SliceHeader sh = {}; + bool isIdr = false; + if (!bgfx::parseSliceFromAccessUnit(m_spsArray, m_ppsArray, _bitstream, _bitstreamSize, nal, sh, isIdr) ) + { + return false; + } + + const h264::PPS& pps = m_ppsArray[sh.pic_parameter_set_id]; + const h264::SPS& sps = m_spsArray[pps.seq_parameter_set_id]; + const int32_t poc = bgfx::computePoc(m_prevPocMsb, m_prevPocLsb, sps, sh, nal, isIdr); + + if (isIdr) + { + m_numActiveRefs = 0; + + m_nextSlot = 0; + + if (BGFX_PCI_ID_INTEL == s_videoVK.vendorId) + { + recreateVideoSession(); + m_firstDecode = true; + } + } + + m_currentSlot = bgfx::pickFreeDpbSlot( + m_nextSlot + , m_numDpbSlots + , m_referenceUsage + , m_numActiveRefs + , [](uint32_t){ return false; } + ); + BX_ASSERT(m_currentSlot < m_numDpbSlots + , "DPB exhausted: m_numDpbSlots=%u m_numActiveRefs=%u. Cap maxRefs to m_numDpbSlots-1." + , m_numDpbSlots + , m_numActiveRefs + ); + m_nextSlot = (m_currentSlot + 1) % m_numDpbSlots; + + const uint32_t slot = bgfx::pickReorderSlotOrEvictOldest(m_reorderPool, kVideoMaxReorderInFlight); + m_reorderPool[slot].displayOrder = m_displayOrderNext++; + m_reorderPool[slot].ptsUs = _ptsUs; + + const bool isIntra = false + || isIdr + || sh.slice_type == 2 + || sh.slice_type == 4 + || sh.slice_type == 7 + || sh.slice_type == 9 + ; + const bool isReference = (0 != nal.idc); + + m_pocStatus[m_currentSlot] = poc; + m_frameNumStatus[m_currentSlot] = int32_t(sh.frame_num); + + const uint32_t sliceOffsetInAu = bgfx::findFirstSliceNalOffset(_bitstream, _bitstreamSize); + const uint8_t* sliceBitstream = _bitstream + sliceOffsetInAu; + const uint32_t sliceBitstreamSize = _bitstreamSize - sliceOffsetInAu; + + const VkDeviceSize alignedSize = bx::alignUp(sliceBitstreamSize, m_bitstreamAlignment); + if (alignedSize > m_bitstreamBufferSize) + { + BX_TRACE("VideoDecoderVK: bitstream %u exceeds buffer %u; skipping frame.", + uint32_t(alignedSize), uint32_t(m_bitstreamBufferSize) ); + return false; + } + + VkDevice device = s_videoVK.device; + + vkWaitForFences(device, 1, &m_videoFence, VK_TRUE, UINT64_MAX); + vkResetFences(device, 1, &m_videoFence); + vkResetCommandPool(device, m_videoCommandPool, 0); + + if (m_haveCopyInFlight) + { + vkWaitForFences(device, 1, &m_copyFenceRing[m_lastCopyRingIdx], VK_TRUE, UINT64_MAX); + m_haveCopyInFlight = false; + } + + bx::memCopy(m_bitstreamMapped, sliceBitstream, sliceBitstreamSize); + + if (alignedSize > sliceBitstreamSize) + { + bx::memSet( + (uint8_t*)m_bitstreamMapped + sliceBitstreamSize + , 0 + , size_t(alignedSize - sliceBitstreamSize) + ); + } + + { + VkMappedMemoryRange range + { + .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, + .pNext = NULL, + .memory = m_bitstreamBufferMemory, + .offset = 0, + .size = VK_WHOLE_SIZE, + }; + + vkFlushMappedMemoryRanges(device, 1, &range); + } + + while (0 == m_copyControl.reserve(1) ) + { + const uint32_t readIdx = m_copyControl.m_read; + const VkResult wres = vkWaitForFences( + device + , 1 + , &m_copyFenceRing[readIdx] + , VK_TRUE + , UINT64_MAX + ); + + if (VK_SUCCESS != wres) + { + BX_TRACE("VideoDecoderVK: vkWaitForFences(copy ring) failed (%d).", wres); + return false; + } + + m_copyControl.consume(1); + } + + const uint32_t ringIdx = m_copyControl.m_current; + VkCommandBuffer gfx = m_copyCommandBufferRing[ringIdx]; + VkFence copyFence = m_copyFenceRing[ringIdx]; + vkResetFences(device, 1, ©Fence); + + VkCommandBufferBeginInfo cbBegin + { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .pNext = NULL, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + .pInheritanceInfo = NULL, + }; + vkBeginCommandBuffer(m_videoCommandBuffer, &cbBegin); + + if (m_firstDecode) + { + VkImageMemoryBarrier barrier = + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = NULL, + .srcAccessMask = 0, + .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_VIDEO_DECODE_DPB_KHR, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = m_dpbImage, + .subresourceRange = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = m_numDpbSlots, + }, + }; + + vkCmdPipelineBarrier( + m_videoCommandBuffer + , VK_PIPELINE_STAGE_ALL_COMMANDS_BIT + , VK_PIPELINE_STAGE_ALL_COMMANDS_BIT + , 0 + , 0 + , NULL + , 0 + , NULL + , 1 + , &barrier + ); + + for (uint32_t ii = 0; ii < kNumDpbSlots; ++ii) + { + m_dpbLayouts[ii] = VK_IMAGE_LAYOUT_VIDEO_DECODE_DPB_KHR; + } + } + + if (m_dpbCoincide) + { + VkImageMemoryBarrier dpbBarriers[kNumDpbSlots]; + uint32_t numDpbBarriers = 0; + + const auto pushDpbBarrier = [&](uint32_t _slot) + { + if (VK_IMAGE_LAYOUT_VIDEO_DECODE_DPB_KHR == m_dpbLayouts[_slot]) + { + return; + } + dpbBarriers[numDpbBarriers] = + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = NULL, + .srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, + .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, + .oldLayout = m_dpbLayouts[_slot], + .newLayout = VK_IMAGE_LAYOUT_VIDEO_DECODE_DPB_KHR, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = m_dpbImage, + .subresourceRange = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = _slot, + .layerCount = 1, + }, + }; + m_dpbLayouts[_slot] = VK_IMAGE_LAYOUT_VIDEO_DECODE_DPB_KHR; + ++numDpbBarriers; + }; + + pushDpbBarrier(m_currentSlot); + for (uint32_t ii = 0; ii < m_numActiveRefs; ++ii) + { + pushDpbBarrier(m_referenceUsage[ii]); + } + + if (0 != numDpbBarriers) + { + vkCmdPipelineBarrier( + m_videoCommandBuffer + , VK_PIPELINE_STAGE_ALL_COMMANDS_BIT + , VK_PIPELINE_STAGE_ALL_COMMANDS_BIT + , 0 + , 0 + , NULL + , 0 + , NULL + , numDpbBarriers + , dpbBarriers + ); + } + } + + if (!m_dpbCoincide) + { + VkImageMemoryBarrier barrier + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = NULL, + .srcAccessMask = VK_ACCESS_MEMORY_READ_BIT, + .dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_VIDEO_DECODE_DST_KHR, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = m_decodeOutputImage, + .subresourceRange = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + + vkCmdPipelineBarrier( + m_videoCommandBuffer + , VK_PIPELINE_STAGE_ALL_COMMANDS_BIT + , VK_PIPELINE_STAGE_ALL_COMMANDS_BIT + , 0 + , 0 + , NULL + , 0 + , NULL + , 1 + , &barrier + ); + + m_decodeOutputLayout = VK_IMAGE_LAYOUT_VIDEO_DECODE_DST_KHR; + } + + StdVideoDecodeH264PictureInfo stdPic + { + .flags = + { + .field_pic_flag = uint32_t(sh.field_pic_flag), + .is_intra = isIntra ? 1u : 0u, + .IdrPicFlag = isIdr ? 1u : 0u, + .bottom_field_flag = uint32_t(sh.bottom_field_flag), + .is_reference = isReference ? 1u : 0u, + .complementary_field_pair = 0, + }, + + .seq_parameter_set_id = uint8_t(pps.seq_parameter_set_id), + .pic_parameter_set_id = uint8_t(sh.pic_parameter_set_id), + .reserved1 = 0, + .reserved2 = 0, + .frame_num = uint16_t(sh.frame_num), + .idr_pic_id = uint16_t(sh.idr_pic_id), + .PicOrderCnt = { poc, poc }, + }; + + VkVideoReferenceSlotInfoKHR slotInfos[kNumDpbSlots] = {}; + VkVideoPictureResourceInfoKHR slotPics[kNumDpbSlots] = {}; + VkVideoDecodeH264DpbSlotInfoKHR slotDpbH264[kNumDpbSlots] = {}; + StdVideoDecodeH264ReferenceInfo slotRef[kNumDpbSlots] = {}; + + for (uint32_t ii = 0; ii < m_numDpbSlots; ++ii) + { + slotPics[ii] = + { + .sType = VK_STRUCTURE_TYPE_VIDEO_PICTURE_RESOURCE_INFO_KHR, + .pNext = NULL, + .codedOffset = {}, + .codedExtent = m_codedExtent, + .baseArrayLayer = 0, + .imageViewBinding = m_dpbImageView[ii], + }; + + slotRef[ii].FrameNum = uint16_t(m_frameNumStatus[ii]); + slotRef[ii].PicOrderCnt[0] = m_pocStatus[ii]; + slotRef[ii].PicOrderCnt[1] = m_pocStatus[ii]; + + slotDpbH264[ii] = + { + .sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_H264_DPB_SLOT_INFO_KHR, + .pNext = NULL, + .pStdReferenceInfo = &slotRef[ii], + }; + + slotInfos[ii] = + { + .sType = VK_STRUCTURE_TYPE_VIDEO_REFERENCE_SLOT_INFO_KHR, + .pNext = &slotDpbH264[ii], + .slotIndex = int32_t(ii), + .pPictureResource = &slotPics[ii], + }; + } + + VkVideoReferenceSlotInfoKHR beginSlots[kNumDpbSlots] = {}; + uint32_t numActiveRefs = 0; + + for (uint32_t ii = 0; ii < m_numActiveRefs; ++ii) + { + const uint32_t refSlot = m_referenceUsage[ii]; + BX_ASSERT(refSlot != m_currentSlot, "Current slot must not be in reference list yet."); + beginSlots[numActiveRefs++] = slotInfos[refSlot]; + } + + beginSlots[numActiveRefs] = slotInfos[m_currentSlot]; + beginSlots[numActiveRefs].slotIndex = -1; + + const uint32_t totalBeginSlots = numActiveRefs + 1; + + VkVideoBeginCodingInfoKHR beginInfo + { + .sType = VK_STRUCTURE_TYPE_VIDEO_BEGIN_CODING_INFO_KHR, + .pNext = NULL, + .flags = 0, + .videoSession = m_videoSession, + .videoSessionParameters = m_sessionParams, + .referenceSlotCount = totalBeginSlots, + .pReferenceSlots = beginSlots, + }; + vkCmdBeginVideoCodingKHR(m_videoCommandBuffer, &beginInfo); + + const bool needsControlReset = false + || m_firstDecode + || (isIdr && BGFX_PCI_ID_INTEL == s_videoVK.vendorId) + ; + + if (needsControlReset) + { + VkVideoCodingControlInfoKHR control + { + .sType = VK_STRUCTURE_TYPE_VIDEO_CODING_CONTROL_INFO_KHR, + .pNext = NULL, + .flags = VK_VIDEO_CODING_CONTROL_RESET_BIT_KHR, + }; + vkCmdControlVideoCodingKHR(m_videoCommandBuffer, &control); + } + + uint32_t sliceOffsets[bgfx::kMaxSlicesPerPicture]; + const uint32_t sliceCount = bgfx::enumerateSliceNalOffsets( + sliceBitstream + , sliceBitstreamSize + , sliceOffsets + , BX_COUNTOF(sliceOffsets) + ); + + if (0 == sliceCount + || sliceCount > BX_COUNTOF(sliceOffsets) ) + { + BX_TRACE( + "VideoDecoderVK: invalid slice enumeration count=%u (max=%u)." + , sliceCount + , uint32_t(BX_COUNTOF(sliceOffsets) ) + ); + + return false; + } + + VkVideoDecodeH264PictureInfoKHR pictureInfoH264 + { + .sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_H264_PICTURE_INFO_KHR, + .pNext = NULL, + .pStdPictureInfo = &stdPic, + .sliceCount = sliceCount, + .pSliceOffsets = sliceOffsets, + }; + + VkVideoDecodeInfoKHR decodeInfo + { + .sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_INFO_KHR, + .pNext = &pictureInfoH264, + .flags = 0, + .srcBuffer = m_bitstreamBuffer, + .srcBufferOffset = 0, + .srcBufferRange = alignedSize, + .dstPictureResource = m_dpbCoincide + ? slotPics[m_currentSlot] + : VkVideoPictureResourceInfoKHR + { + .sType = VK_STRUCTURE_TYPE_VIDEO_PICTURE_RESOURCE_INFO_KHR, + .pNext = NULL, + .codedOffset = {}, + .codedExtent = m_codedExtent, + .baseArrayLayer = 0, + .imageViewBinding = m_decodeOutputImageView, + }, + .pSetupReferenceSlot = &slotInfos[m_currentSlot], + .referenceSlotCount = numActiveRefs, + .pReferenceSlots = 0 != numActiveRefs ? beginSlots : NULL, + }; + vkCmdDecodeVideoKHR(m_videoCommandBuffer, &decodeInfo); + + VkVideoEndCodingInfoKHR endInfo + { + .sType = VK_STRUCTURE_TYPE_VIDEO_END_CODING_INFO_KHR, + .pNext = NULL, + .flags = 0, + }; + vkCmdEndVideoCodingKHR(m_videoCommandBuffer, &endInfo); + + { + VkImageMemoryBarrier post + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = NULL, + .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, + .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT, + .oldLayout = m_dpbCoincide + ? VK_IMAGE_LAYOUT_VIDEO_DECODE_DPB_KHR + : VK_IMAGE_LAYOUT_VIDEO_DECODE_DST_KHR + , + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = m_dpbCoincide + ? m_dpbImage + : m_decodeOutputImage + , + .subresourceRange = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = m_dpbCoincide ? m_currentSlot : 0, + .layerCount = 1, + }, + }; + vkCmdPipelineBarrier(m_videoCommandBuffer + , VK_PIPELINE_STAGE_ALL_COMMANDS_BIT + , VK_PIPELINE_STAGE_ALL_COMMANDS_BIT + , 0, 0, NULL, 0, NULL, 1, &post); + } + + vkEndCommandBuffer(m_videoCommandBuffer); + + VkSubmitInfo submit + { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .pNext = NULL, + .waitSemaphoreCount = 0, + .pWaitSemaphores = NULL, + .pWaitDstStageMask = NULL, + .commandBufferCount = 1, + .pCommandBuffers = &m_videoCommandBuffer, + .signalSemaphoreCount = 1, + .pSignalSemaphores = &m_videoSemaphoreRing[ringIdx], + }; + + const VkResult sres = vkQueueSubmit(s_videoVK.videoDecodeQueue, 1, &submit, m_videoFence); + + if (VK_SUCCESS != sres) + { + BX_TRACE("VideoDecoderVK: vkQueueSubmit(video) failed (%d).", sres); + return false; + } + + vkWaitForFences(device, 1, &m_videoFence, VK_TRUE, UINT64_MAX); + + if (m_dpbCoincide) + { + m_dpbLayouts[m_currentSlot] = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + } + else + { + m_decodeOutputLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + } + + if (0 != nal.idc) + { + if (0 != sh.drpm.adaptive_ref_pic_marking_mode_flag + && m_numActiveRefs > 0) + { + uint16_t refFrameNums[16]; + const uint32_t n = bx::min(m_numActiveRefs, 16); + for (uint32_t ii = 0; ii < n; ++ii) + { + refFrameNums[ii] = uint16_t(m_frameNumStatus[m_referenceUsage[ii]]); + } + const int32_t maxFrameNum = 1 << (m_spsActive.log2_max_frame_num_minus4 + 4); + m_numActiveRefs = applyMmcoUnmarkShortTerm( + m_referenceUsage + , refFrameNums + , n + , sh.frame_num + , maxFrameNum + , sh + ); + } + + const uint32_t maxRefs = bx::min(uint32_t(m_spsActive.num_ref_frames), m_numDpbSlots - 1); + m_numActiveRefs = bgfx::appendShortTermRef( + m_referenceUsage + , m_numActiveRefs + , m_currentSlot + , maxRefs + ); + } + + m_firstDecode = false; + + if (!createReorderImage(uint32_t(slot) ) ) + { + return false; + } + + VkCommandBufferBeginInfo copyBegin + { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .pNext = NULL, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + .pInheritanceInfo = NULL, + }; + + { + VkResult bres = vkBeginCommandBuffer(gfx, ©Begin); + if (VK_SUCCESS != bres) { BX_TRACE("VideoDecoderVK: vkBeginCommandBuffer(copy) failed (%d).", bres); return false; } + } + + ReorderedPicture& reorder = m_reorderPool[slot]; + + { + VkImageMemoryBarrier preCopy + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = NULL, + .srcAccessMask = VK_ACCESS_SHADER_READ_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = reorder.image, + .subresourceRange = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + + vkCmdPipelineBarrier(gfx + , VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT + , VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT + , VK_DEPENDENCY_BY_REGION_BIT + , 0, NULL, 0, NULL, 1, &preCopy); + } + + reorder.layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + + VkImage srcImage = m_dpbCoincide ? m_dpbImage : m_decodeOutputImage; + uint32_t srcLayer = m_dpbCoincide ? m_currentSlot : 0; + + const uint32_t reorderW = (m_dstWidth + 1) & ~1u; + const uint32_t reorderH = (m_dstHeight + 1) & ~1u; + + VkImageCopy cpy + { + .srcSubresource = { .aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT, .mipLevel = 0, .baseArrayLayer = srcLayer, .layerCount = 1 }, + .srcOffset = {}, + .dstSubresource = { .aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT, .mipLevel = 0, .baseArrayLayer = 0, .layerCount = 1 }, + .dstOffset = {}, + .extent = { reorderW, reorderH, 1 }, + }; + vkCmdCopyImage(gfx, srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, reorder.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &cpy); + + cpy.extent.width = reorderW / 2; + cpy.extent.height = reorderH / 2; + cpy.srcSubresource.aspectMask = VK_IMAGE_ASPECT_PLANE_1_BIT; + cpy.dstSubresource.aspectMask = VK_IMAGE_ASPECT_PLANE_1_BIT; + vkCmdCopyImage(gfx, srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, reorder.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &cpy); + + { + VkImageMemoryBarrier postCopy + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = NULL, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = reorder.image, + .subresourceRange = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + vkCmdPipelineBarrier( + gfx + , VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT + , VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT + , VK_DEPENDENCY_BY_REGION_BIT + , 0 + , NULL + , 0 + , NULL + , 1 + , &postCopy + ); + } + + reorder.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + vkEndCommandBuffer(gfx); + + const VkPipelineStageFlags waitStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + + VkSubmitInfo copySubmit + { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .pNext = NULL, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &m_videoSemaphoreRing[ringIdx], + .pWaitDstStageMask = &waitStage, + .commandBufferCount = 1, + .pCommandBuffers = &gfx, + .signalSemaphoreCount = 0, + .pSignalSemaphores = NULL, + }; + const VkResult cres = vkQueueSubmit(s_videoVK.globalQueue, 1, ©Submit, copyFence); + + if (VK_SUCCESS != cres) + { + BX_TRACE("VideoDecoderVK: vkQueueSubmit(copy) failed (%d).", cres); + return false; + } + m_copyControl.commit(1); + + m_lastCopyRingIdx = ringIdx; + m_haveCopyInFlight = true; + + return true; + } + + bool createReorderImage(uint32_t _slot) + { + BX_ASSERT(_slot < kVideoMaxReorderInFlight, "Invalid reorder slot %u.", _slot); + ReorderedPicture& pic = m_reorderPool[_slot]; + if (VK_NULL_HANDLE != pic.image) + { + return true; + } + + VkDevice device = s_videoVK.device; + const VkAllocationCallbacks* allocCb = s_videoVK.allocCb; + + VkImageCreateInfo ci + { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = NULL, + .flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT, + .imageType = VK_IMAGE_TYPE_2D, + .format = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, + .extent = { (m_dstWidth + 1) & ~1u, (m_dstHeight + 1) & ~1u, 1 }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = 0 + | VK_IMAGE_USAGE_TRANSFER_DST_BIT + | VK_IMAGE_USAGE_SAMPLED_BIT + , + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = NULL, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + }; + + VkResult result = vkCreateImage(device, &ci, allocCb, &pic.image); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkCreateImage(reorder) failed (%d).", result); + return false; + } + + VkMemoryRequirements req = {}; + vkGetImageMemoryRequirements(device, pic.image, &req); + + VkMemoryAllocateInfo ai + { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .pNext = NULL, + .allocationSize = req.size, + .memoryTypeIndex = uint32_t(videoSelectMemoryType(m_renderer, req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) ), + }; + result = vkAllocateMemory(device, &ai, allocCb, &pic.memory); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkAllocateMemory(reorder) failed (%d).", result); + return false; + } + + result = vkBindImageMemory(device, pic.image, pic.memory, 0); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkBindImageMemory(reorder) failed (%d).", result); + return false; + } + + VkImageViewCreateInfo vci + { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .image = pic.image, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = VK_FORMAT_R8_UNORM, + .components = {}, + .subresourceRange = + { + .aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + result = vkCreateImageView(device, &vci, allocCb, &pic.yView); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkCreateImageView(reorder Y) failed (%d).", result); + return false; + } + + vci.format = VK_FORMAT_R8G8_UNORM; + vci.subresourceRange.aspectMask = VK_IMAGE_ASPECT_PLANE_1_BIT; + result = vkCreateImageView(device, &vci, allocCb, &pic.cbcrView); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkCreateImageView(reorder CbCr) failed (%d).", result); + return false; + } + + pic.layout = VK_IMAGE_LAYOUT_UNDEFINED; + return true; + } + + bool dispatchYuvToRgb(uint32_t _displaySlot, TextureVK& _dst) + { + BX_ASSERT(NULL != g_videoDecode && isValid(g_videoDecode->m_program), "Video decode program not initialized."); + + VkPipeline pso = videoGetPipeline(m_renderer, g_videoDecode->m_program); + if (VK_NULL_HANDLE == pso) + { + return false; + } + + const ProgramVK& prog = videoGetProgram(m_renderer, g_videoDecode->m_program); + VkDevice device = s_videoVK.device; + VkCommandBuffer commandBuffer = videoGetCommandBuffer(m_renderer); + + if (VK_NULL_HANDLE == m_yuvDescriptorPool) + { + const VkAllocationCallbacks* allocCb = s_videoVK.allocCb; + + VkDescriptorPoolSize poolSizes[3] + { + { .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, .descriptorCount = kYuvDescriptorRingSize }, + { .type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, .descriptorCount = kYuvDescriptorRingSize * 2 }, + { .type = VK_DESCRIPTOR_TYPE_SAMPLER, .descriptorCount = kYuvDescriptorRingSize * 2 }, + }; + + VkDescriptorPoolCreateInfo dpci + { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .maxSets = kYuvDescriptorRingSize, + .poolSizeCount = BX_COUNTOF(poolSizes), + .pPoolSizes = poolSizes, + }; + + VkResult result = vkCreateDescriptorPool(device, &dpci, allocCb, &m_yuvDescriptorPool); + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkCreateDescriptorPool(YUV->RGB) failed (%d).", result); + return false; + } + + ::VkDescriptorSetLayout layouts[kYuvDescriptorRingSize]; + for (uint32_t ii = 0; ii < kYuvDescriptorRingSize; ++ii) + { + layouts[ii] = prog.m_descriptorSetLayout; + } + + VkDescriptorSetAllocateInfo dsai + { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + .pNext = NULL, + .descriptorPool = m_yuvDescriptorPool, + .descriptorSetCount = kYuvDescriptorRingSize, + .pSetLayouts = layouts, + }; + result = vkAllocateDescriptorSets(device, &dsai, m_yuvDescriptorSet); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkAllocateDescriptorSets(YUV->RGB ring) failed (%d).", result); + return false; + } + } + + VkDescriptorSet descriptorSet = m_yuvDescriptorSet[m_yuvDescriptorIndex]; + m_yuvDescriptorIndex = (m_yuvDescriptorIndex + 1) % kYuvDescriptorRingSize; + + _dst.setState(commandBuffer, VK_IMAGE_LAYOUT_GENERAL); + + VkImageView dstView = VK_NULL_HANDLE; + { + VkImageViewCreateInfo viewInfo + { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .image = _dst.m_textureImage, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = VK_FORMAT_R8G8B8A8_UNORM, + .components = + { + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + + const VkResult result = vkCreateImageView(device, &viewInfo, s_videoVK.allocCb, &dstView); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkCreateImageView(YUV->RGB dst rgba8) failed (%d).", result); + return false; + } + } + + VkDescriptorImageInfo imageInfo[BGFX_CONFIG_MAX_TEXTURE_SAMPLERS * 2] = {}; + VkWriteDescriptorSet writes[BGFX_CONFIG_MAX_TEXTURE_SAMPLERS * 2] = {}; + uint32_t imageCount = 0; + uint32_t writeCount = 0; + + const uint32_t samplerFlags = BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP | BGFX_SAMPLER_W_CLAMP; + VkSampler ySampler = videoGetSampler(m_renderer, samplerFlags, VK_FORMAT_R8_UNORM); + VkSampler cbcrSampler = videoGetSampler(m_renderer, samplerFlags, VK_FORMAT_R8G8_UNORM); + + for (uint32_t stage = 0; stage < BGFX_CONFIG_MAX_TEXTURE_SAMPLERS; ++stage) + { + const BindInfo& bindInfo = prog.m_bindInfo[stage]; + if (!isValid(bindInfo.uniformHandle) ) + { + continue; + } + + if (BindType::Image == bindInfo.type) + { + imageInfo[imageCount].imageLayout = VK_IMAGE_LAYOUT_GENERAL; + imageInfo[imageCount].imageView = dstView; + imageInfo[imageCount].sampler = VK_NULL_HANDLE; + + writes[writeCount].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + writes[writeCount].dstSet = descriptorSet; + writes[writeCount].dstBinding = bindInfo.binding; + writes[writeCount].descriptorCount = 1; + writes[writeCount].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; + writes[writeCount].pImageInfo = &imageInfo[imageCount]; + ++writeCount; + ++imageCount; + } + else if (BindType::Sampler == bindInfo.type) + { + const bool yPlane = 1 == stage; + imageInfo[imageCount].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imageInfo[imageCount].imageView = yPlane ? m_reorderPool[_displaySlot].yView : m_reorderPool[_displaySlot].cbcrView; + imageInfo[imageCount].sampler = yPlane ? ySampler : cbcrSampler; + + writes[writeCount].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + writes[writeCount].dstSet = descriptorSet; + writes[writeCount].dstBinding = bindInfo.binding; + writes[writeCount].descriptorCount = 1; + writes[writeCount].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + writes[writeCount].pImageInfo = &imageInfo[imageCount]; + ++writeCount; + + writes[writeCount].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + writes[writeCount].dstSet = descriptorSet; + writes[writeCount].dstBinding = bindInfo.samplerBinding; + writes[writeCount].descriptorCount = 1; + writes[writeCount].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER; + writes[writeCount].pImageInfo = &imageInfo[imageCount]; + ++writeCount; + ++imageCount; + } + } + + vkUpdateDescriptorSets(device, writeCount, writes, 0, NULL); + + { + VkImageMemoryBarrier reorderImb + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = NULL, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = m_reorderPool[_displaySlot].image, + .subresourceRange = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + vkCmdPipelineBarrier(commandBuffer + , VK_PIPELINE_STAGE_TRANSFER_BIT + , VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT + , 0 + , 0, NULL + , 0, NULL + , 1, &reorderImb + ); + } + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pso); + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, prog.m_pipelineLayout, 0, 1, &descriptorSet, 0, NULL); + vkCmdDispatch(commandBuffer, bx::max((m_dstWidth + 7) / 8, 1), bx::max((m_dstHeight + 7) / 8, 1), 1); + + { + VkImageMemoryBarrier imb + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = NULL, + .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = _dst.m_textureImage, + .subresourceRange = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + vkCmdPipelineBarrier(commandBuffer + , VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT + , 0 + | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT + | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT + | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT + , 0 + , 0, NULL + , 0, NULL + , 1, &imb + ); + } + + videoRelease(m_renderer, dstView); + + return true; + } + + bool createSessionParameters() + { + VkDevice device = s_videoVK.device; + const VkAllocationCallbacks* allocCb = s_videoVK.allocCb; + + uint32_t numSps = 0; + uint32_t numPps = 0; + StdVideoH264SequenceParameterSet vkSpsArr[BX_COUNTOF(m_spsArray)] = {}; + StdVideoH264PictureParameterSet vkPpsArr[BX_COUNTOF(m_ppsArray)] = {}; + StdVideoH264ScalingLists vkPpsScalingArr[BX_COUNTOF(m_ppsArray)] = {}; + + for (uint32_t ii = 0; ii < BX_COUNTOF(m_spsArray); ++ii) + { + if (!m_spsValid[ii]) + { + continue; + } + + const h264::SPS& sps = m_spsArray[ii]; + StdVideoH264SequenceParameterSet& vk = vkSpsArr[numSps++]; + + vk.flags.constraint_set0_flag = sps.constraint_set0_flag; + vk.flags.constraint_set1_flag = sps.constraint_set1_flag; + vk.flags.constraint_set2_flag = sps.constraint_set2_flag; + vk.flags.constraint_set3_flag = sps.constraint_set3_flag; + vk.flags.constraint_set4_flag = sps.constraint_set4_flag; + vk.flags.constraint_set5_flag = sps.constraint_set5_flag; + vk.flags.direct_8x8_inference_flag = sps.direct_8x8_inference_flag; + vk.flags.mb_adaptive_frame_field_flag = sps.mb_adaptive_frame_field_flag; + vk.flags.frame_mbs_only_flag = sps.frame_mbs_only_flag; + vk.flags.delta_pic_order_always_zero_flag = sps.delta_pic_order_always_zero_flag; + vk.flags.separate_colour_plane_flag = sps.separate_colour_plane_flag; + vk.flags.gaps_in_frame_num_value_allowed_flag = sps.gaps_in_frame_num_value_allowed_flag; + vk.flags.qpprime_y_zero_transform_bypass_flag = sps.qpprime_y_zero_transform_bypass_flag; + vk.flags.frame_cropping_flag = sps.frame_cropping_flag; + vk.flags.seq_scaling_matrix_present_flag = sps.seq_scaling_matrix_present_flag; + vk.flags.vui_parameters_present_flag = 0; // skip VUI; we don't need it for decode + vk.profile_idc = StdVideoH264ProfileIdc(sps.profile_idc); + vk.level_idc = translateH264Level(sps.level_idc); + vk.chroma_format_idc = STD_VIDEO_H264_CHROMA_FORMAT_IDC_420; + vk.seq_parameter_set_id = uint8_t(sps.seq_parameter_set_id); + vk.bit_depth_luma_minus8 = uint8_t(sps.bit_depth_luma_minus8); + vk.bit_depth_chroma_minus8 = uint8_t(sps.bit_depth_chroma_minus8); + vk.log2_max_frame_num_minus4 = uint8_t(sps.log2_max_frame_num_minus4); + vk.pic_order_cnt_type = StdVideoH264PocType(sps.pic_order_cnt_type); + vk.offset_for_non_ref_pic = sps.offset_for_non_ref_pic; + vk.offset_for_top_to_bottom_field = sps.offset_for_top_to_bottom_field; + vk.log2_max_pic_order_cnt_lsb_minus4 = uint8_t(sps.log2_max_pic_order_cnt_lsb_minus4); + vk.num_ref_frames_in_pic_order_cnt_cycle = uint8_t(sps.num_ref_frames_in_pic_order_cnt_cycle); + vk.max_num_ref_frames = uint8_t(sps.num_ref_frames); + vk.pic_width_in_mbs_minus1 = uint32_t(sps.pic_width_in_mbs_minus1); + vk.pic_height_in_map_units_minus1 = uint32_t(sps.pic_height_in_map_units_minus1); + vk.frame_crop_left_offset = uint32_t(sps.frame_crop_left_offset); + vk.frame_crop_right_offset = uint32_t(sps.frame_crop_right_offset); + vk.frame_crop_top_offset = uint32_t(sps.frame_crop_top_offset); + vk.frame_crop_bottom_offset = uint32_t(sps.frame_crop_bottom_offset); + } + + for (uint32_t ii = 0; ii < BX_COUNTOF(m_ppsArray); ++ii) + { + if (!m_ppsValid[ii]) + { + continue; + } + const h264::PPS& pps = m_ppsArray[ii]; + StdVideoH264PictureParameterSet& vk = vkPpsArr[numPps]; + StdVideoH264ScalingLists& vkScaling = vkPpsScalingArr[numPps]; + ++numPps; + + vk.flags.transform_8x8_mode_flag = pps.transform_8x8_mode_flag; + vk.flags.redundant_pic_cnt_present_flag = pps.redundant_pic_cnt_present_flag; + vk.flags.constrained_intra_pred_flag = pps.constrained_intra_pred_flag; + vk.flags.deblocking_filter_control_present_flag = pps.deblocking_filter_control_present_flag; + vk.flags.weighted_pred_flag = pps.weighted_pred_flag; + vk.flags.bottom_field_pic_order_in_frame_present_flag = pps.pic_order_present_flag; + vk.flags.entropy_coding_mode_flag = pps.entropy_coding_mode_flag; + vk.flags.pic_scaling_matrix_present_flag = pps.pic_scaling_matrix_present_flag; + vk.seq_parameter_set_id = uint8_t(pps.seq_parameter_set_id); + vk.pic_parameter_set_id = uint8_t(pps.pic_parameter_set_id); + vk.num_ref_idx_l0_default_active_minus1 = uint8_t(pps.num_ref_idx_l0_active_minus1); + vk.num_ref_idx_l1_default_active_minus1 = uint8_t(pps.num_ref_idx_l1_active_minus1); + vk.weighted_bipred_idc = StdVideoH264WeightedBipredIdc(pps.weighted_bipred_idc); + vk.pic_init_qp_minus26 = int8_t(pps.pic_init_qp_minus26); + vk.pic_init_qs_minus26 = int8_t(pps.pic_init_qs_minus26); + vk.chroma_qp_index_offset = int8_t(pps.chroma_qp_index_offset); + vk.second_chroma_qp_index_offset = int8_t(pps.second_chroma_qp_index_offset); + + vkScaling = {}; + + for (uint32_t jj = 0; jj < BX_COUNTOF(pps.pic_scaling_list_present_flag); ++jj) + { + vkScaling.scaling_list_present_mask = uint16_t(vkScaling.scaling_list_present_mask | (pps.pic_scaling_list_present_flag[jj] << jj) ); + } + + for (uint32_t jj = 0; jj < BX_COUNTOF(pps.UseDefaultScalingMatrix4x4Flag); ++jj) + { + vkScaling.use_default_scaling_matrix_mask = uint16_t(vkScaling.use_default_scaling_matrix_mask | (pps.UseDefaultScalingMatrix4x4Flag[jj] << jj) ); + } + + for (uint32_t jj = 0; jj < BX_COUNTOF(pps.ScalingList4x4); ++jj) + { + for (uint32_t kk = 0; kk < BX_COUNTOF(pps.ScalingList4x4[jj]); ++kk) + { + vkScaling.ScalingList4x4[jj][kk] = uint8_t(pps.ScalingList4x4[jj][kk]); + } + } + + for (uint32_t jj = 0; jj < BX_COUNTOF(pps.ScalingList8x8); ++jj) + { + for (uint32_t kk = 0; kk < BX_COUNTOF(pps.ScalingList8x8[jj]); ++kk) + { + vkScaling.ScalingList8x8[jj][kk] = uint8_t(pps.ScalingList8x8[jj][kk]); + } + } + + vk.pScalingLists = &vkScaling; + } + + if (0 == numSps + || 0 == numPps) + { + BX_TRACE("VideoDecoderVK: no SPS/PPS to populate session parameters."); + return false; + } + + VkVideoDecodeH264SessionParametersAddInfoKHR addInfo + { + .sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_H264_SESSION_PARAMETERS_ADD_INFO_KHR, + .pNext = NULL, + .stdSPSCount = numSps, + .pStdSPSs = vkSpsArr, + .stdPPSCount = numPps, + .pStdPPSs = vkPpsArr, + }; + + VkVideoDecodeH264SessionParametersCreateInfoKHR h264Ci + { + .sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_H264_SESSION_PARAMETERS_CREATE_INFO_KHR, + .pNext = NULL, + .maxStdSPSCount = numSps, + .maxStdPPSCount = numPps, + .pParametersAddInfo = &addInfo, + }; + + VkVideoSessionParametersCreateInfoKHR ci + { + .sType = VK_STRUCTURE_TYPE_VIDEO_SESSION_PARAMETERS_CREATE_INFO_KHR, + .pNext = &h264Ci, + .flags = 0, + .videoSessionParametersTemplate = VK_NULL_HANDLE, + .videoSession = m_videoSession, + }; + + VkResult result = vkCreateVideoSessionParametersKHR(device, &ci, allocCb, &m_sessionParams); + + if (VK_SUCCESS != result) + { + BX_TRACE("VideoDecoderVK: vkCreateVideoSessionParametersKHR failed (%d).", result); + return false; + } + + return true; + } + + void resetReorder() + { + for (uint32_t ii = 0; ii < kVideoMaxReorderInFlight; ++ii) + { + if (int32_t(ii) != m_displayedSlot + && int32_t(ii) != m_prevDisplayedSlot) + { + m_reorderPool[ii].displayOrder = -1; + } + } + + m_displayOrderNext = 0; + m_numActiveRefs = 0; + m_nextSlot = 0; + } + + bool m_created; + uint32_t m_dstWidth; + uint32_t m_dstHeight; + uint32_t m_codedWidth; + uint32_t m_codedHeight; + uint32_t m_numDpbSlots; + uint32_t m_nextSlot; + uint32_t m_currentSlot; + uint32_t m_numActiveRefs; + uint8_t m_referenceUsage[kNumDpbSlots]; + int32_t m_prevPocLsb; + int32_t m_prevPocMsb; + int32_t m_displayOrderNext; + int32_t m_displayedSlot; + int32_t m_prevDisplayedSlot; + ReorderedPicture m_reorderPool[kVideoMaxReorderInFlight]; + h264::SPS m_spsActive; + h264::SPS m_spsArray[kVideoMaxH264SpsCount]; + h264::PPS m_ppsArray[kVideoMaxH264PpsCount]; + bool m_spsValid[kVideoMaxH264SpsCount]; + bool m_ppsValid[kVideoMaxH264PpsCount]; + + bgfx::AuQueue m_auQueue; + + uint32_t m_initFlags; + uint32_t m_cachedAuBytes; + + RendererContextVK* m_renderer; + + VkVideoSessionKHR m_videoSession; + VkVideoSessionParametersKHR m_sessionParams; + + VkDeviceMemory m_sessionMemory[kMaxSessionMemory]; + + uint32_t m_numSessionMemory; + uint32_t m_maxActiveRefs; + + VkVideoDecodeH264ProfileInfoKHR m_h264Profile; + VkVideoProfileInfoKHR m_videoProfile; + + VkImage m_dpbImage; + VkDeviceMemory m_dpbImageMemory; + VkImageView m_dpbImageView[kNumDpbSlots]; + VkImageLayout m_dpbLayouts[kNumDpbSlots]; + bool m_dpbCoincide; + + VkImage m_decodeOutputImage; + VkDeviceMemory m_decodeOutputImageMemory; + VkImageView m_decodeOutputImageView; + VkImageLayout m_decodeOutputLayout; + + VkBuffer m_bitstreamBuffer; + VkDeviceMemory m_bitstreamBufferMemory; + VkDeviceSize m_bitstreamBufferSize; + void* m_bitstreamMapped; + uint32_t m_bitstreamAlignment; + + VkCommandPool m_videoCommandPool; + VkCommandBuffer m_videoCommandBuffer; + VkFence m_videoFence; + VkCommandPool m_copyCommandPool; + + VkCommandBuffer m_copyCommandBufferRing[kCopyRingSize]; + + VkFence m_copyFenceRing[kCopyRingSize]; + + VkSemaphore m_videoSemaphoreRing[kCopyRingSize]; + bx::RingBufferControl m_copyControl; + + uint32_t m_lastCopyRingIdx; + bool m_haveCopyInFlight; + + VkDescriptorPool m_yuvDescriptorPool; + ::VkDescriptorSet m_yuvDescriptorSet[kYuvDescriptorRingSize]; + uint32_t m_yuvDescriptorIndex; + + int32_t m_pocStatus[kNumDpbSlots]; + int32_t m_frameNumStatus[kNumDpbSlots]; + VkExtent2D m_codedExtent; + VkExtensionProperties m_stdHeaderVersion; + bool m_firstDecode; + }; + + VideoDecoderVK* videoDecoderCreate(const VideoDecoderInit& _init, RendererContextVK* _renderer, uint16_t _width, uint16_t _height) + { + VideoDecoderVK* decoder = BX_NEW(g_allocator, VideoDecoderVK); + if (!decoder->create(_init, _renderer, _width, _height) ) + { + decoder->destroy(); + bx::deleteObject(g_allocator, decoder); + return NULL; + } + return decoder; + } + + void videoDecoderDestroy(VideoDecoderVK* _decoder) + { + if (NULL != _decoder) + { + _decoder->destroy(); + bx::deleteObject(g_allocator, _decoder); + } + } + + bool videoDecoderDecode(VideoDecoderVK* _decoder, const VideoDecoderFrame& _frame, TextureVK& _dst) + { + return _decoder->decode(_frame, _dst); + } + + struct ProfileEntry + { + VideoCodec::Enum codec; + const char* extensionName; + VkVideoCodecOperationFlagBitsKHR op; + int32_t h264Profile; + int32_t h265Profile; + int32_t av1Profile; + VkVideoChromaSubsamplingFlagBitsKHR chroma; + VkVideoComponentBitDepthFlagBitsKHR depth; + uint32_t caps; + }; + + static const ProfileEntry s_probes[] = + { + { + VideoCodec::H264 + , "VK_KHR_video_decode_h264" + , VK_VIDEO_CODEC_OPERATION_DECODE_H264_BIT_KHR + , STD_VIDEO_H264_PROFILE_IDC_HIGH + , 0 + , 0 + , VK_VIDEO_CHROMA_SUBSAMPLING_420_BIT_KHR + , VK_VIDEO_COMPONENT_BIT_DEPTH_8_BIT_KHR + , BGFX_CAPS_VIDEO_CODEC_BIT_8 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420 + }, + { + VideoCodec::H265 + , "VK_KHR_video_decode_h265" + , VK_VIDEO_CODEC_OPERATION_DECODE_H265_BIT_KHR + , 0 + , STD_VIDEO_H265_PROFILE_IDC_MAIN + , 0 + , VK_VIDEO_CHROMA_SUBSAMPLING_420_BIT_KHR + , VK_VIDEO_COMPONENT_BIT_DEPTH_8_BIT_KHR + , BGFX_CAPS_VIDEO_CODEC_BIT_8 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420 + }, + { + VideoCodec::H265 + , "VK_KHR_video_decode_h265" + , VK_VIDEO_CODEC_OPERATION_DECODE_H265_BIT_KHR + , 0 + , STD_VIDEO_H265_PROFILE_IDC_MAIN_10 + , 0 + , VK_VIDEO_CHROMA_SUBSAMPLING_420_BIT_KHR + , VK_VIDEO_COMPONENT_BIT_DEPTH_10_BIT_KHR + , BGFX_CAPS_VIDEO_CODEC_BIT_10 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420 + }, + { + VideoCodec::AV1 + , "VK_KHR_video_decode_av1" + , VK_VIDEO_CODEC_OPERATION_DECODE_AV1_BIT_KHR + , 0 + , 0 + , STD_VIDEO_AV1_PROFILE_MAIN + , VK_VIDEO_CHROMA_SUBSAMPLING_420_BIT_KHR + , VK_VIDEO_COMPONENT_BIT_DEPTH_8_BIT_KHR + , BGFX_CAPS_VIDEO_CODEC_BIT_8 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420 + }, + { + VideoCodec::AV1 + , "VK_KHR_video_decode_av1" + , VK_VIDEO_CODEC_OPERATION_DECODE_AV1_BIT_KHR + , 0 + , 0 + , STD_VIDEO_AV1_PROFILE_MAIN + , VK_VIDEO_CHROMA_SUBSAMPLING_420_BIT_KHR + , VK_VIDEO_COMPONENT_BIT_DEPTH_10_BIT_KHR + , BGFX_CAPS_VIDEO_CODEC_BIT_10 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420 + }, + }; + + void initVideoDecoder(RendererContextVK* _renderer, const VideoBindingVK& _binding) + { + s_videoVK = _binding; + + if (!videoIsExtensionSupported(_renderer, "VK_KHR_video_queue") + || NULL == vkGetPhysicalDeviceVideoCapabilitiesKHR) + { + return; + } + + bool anySupported = false; + for (uint32_t ii = 0; ii < BX_COUNTOF(s_probes); ++ii) + { + const ProfileEntry& probe = s_probes[ii]; + if (!videoIsExtensionSupported(_renderer, probe.extensionName)) + { + continue; + } + + VkVideoDecodeH264ProfileInfoKHR h264Info = {}; + VkVideoDecodeH265ProfileInfoKHR h265Info = {}; + VkVideoDecodeAV1ProfileInfoKHR av1Info = {}; + const void* codecProfileChain = NULL; + + if (probe.codec == VideoCodec::H264) + { + h264Info = + { + .sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_H264_PROFILE_INFO_KHR, + .pNext = NULL, + .stdProfileIdc = StdVideoH264ProfileIdc(probe.h264Profile), + .pictureLayout = VK_VIDEO_DECODE_H264_PICTURE_LAYOUT_PROGRESSIVE_KHR, + }; + codecProfileChain = &h264Info; + } + else if (probe.codec == VideoCodec::H265) + { + h265Info = + { + .sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_H265_PROFILE_INFO_KHR, + .pNext = NULL, + .stdProfileIdc = StdVideoH265ProfileIdc(probe.h265Profile), + }; + codecProfileChain = &h265Info; + } + else if (probe.codec == VideoCodec::AV1) + { + av1Info = + { + .sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_AV1_PROFILE_INFO_KHR, + .pNext = NULL, + .stdProfile = StdVideoAV1Profile(probe.av1Profile), + .filmGrainSupport = VK_FALSE, + }; + codecProfileChain = &av1Info; + } + + VkVideoProfileInfoKHR profileInfo + { + .sType = VK_STRUCTURE_TYPE_VIDEO_PROFILE_INFO_KHR, + .pNext = codecProfileChain, + .videoCodecOperation = probe.op, + .chromaSubsampling = VkVideoChromaSubsamplingFlagsKHR(probe.chroma), + .lumaBitDepth = VkVideoComponentBitDepthFlagsKHR(probe.depth), + .chromaBitDepth = VkVideoComponentBitDepthFlagsKHR(probe.depth), + }; + + VkVideoDecodeH264CapabilitiesKHR h264Caps + { + .sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_H264_CAPABILITIES_KHR, + .pNext = NULL, + .maxLevelIdc = STD_VIDEO_H264_LEVEL_IDC_INVALID, + .fieldOffsetGranularity = {}, + }; + + VkVideoDecodeH265CapabilitiesKHR h265Caps + { + .sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_H265_CAPABILITIES_KHR, + .pNext = NULL, + .maxLevelIdc = STD_VIDEO_H265_LEVEL_IDC_INVALID, + }; + + VkVideoDecodeAV1CapabilitiesKHR av1Caps + { + .sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_AV1_CAPABILITIES_KHR, + .pNext = NULL, + .maxLevel = STD_VIDEO_AV1_LEVEL_INVALID, + }; + + VkVideoDecodeCapabilitiesKHR decodeCaps + { + .sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_CAPABILITIES_KHR, + .pNext = probe.codec == VideoCodec::H264 ? (void*)&h264Caps + : probe.codec == VideoCodec::H265 ? (void*)&h265Caps + : probe.codec == VideoCodec::AV1 ? (void*)&av1Caps + : NULL, + .flags = 0, + }; + + VkVideoCapabilitiesKHR videoCaps + { + .sType = VK_STRUCTURE_TYPE_VIDEO_CAPABILITIES_KHR, + .pNext = &decodeCaps, + .flags = 0, + .minBitstreamBufferOffsetAlignment = 0, + .minBitstreamBufferSizeAlignment = 0, + .pictureAccessGranularity = {}, + .minCodedExtent = {}, + .maxCodedExtent = {}, + .maxDpbSlots = 0, + .maxActiveReferencePictures = 0, + .stdHeaderVersion = {}, + }; + + VkResult result = vkGetPhysicalDeviceVideoCapabilitiesKHR( + _binding.physicalDevice + , &profileInfo + , &videoCaps + ); + + if (VK_SUCCESS == result) + { + g_caps.codecs[probe.codec] |= probe.caps; + anySupported = true; + + BX_TRACE("[VK] Video decode supported: codec=%d caps=0x%08x" + , probe.codec + , g_caps.codecs[probe.codec] + ); + } + } + + if (anySupported) + { + g_caps.supported |= BGFX_CAPS_VIDEO_DECODE; + + const TextureFormat::Enum dstFormats[] = + { + TextureFormat::BGRA8, + TextureFormat::RGBA8, + TextureFormat::RGB10A2, + TextureFormat::RGBA16F, + }; + + for (uint32_t ii = 0; ii < BX_COUNTOF(dstFormats); ++ii) + { + const TextureFormat::Enum fmt = dstFormats[ii]; + if (0 != (g_caps.formats[fmt] & BGFX_CAPS_FORMAT_TEXTURE_2D) ) + { + g_caps.formats[fmt] |= BGFX_CAPS_FORMAT_TEXTURE_VIDEO_DECODE_DST; + } + } + } + } + +} } // namespace bgfx::vk + +# else + +namespace bgfx { namespace vk +{ + void initVideoDecoder(RendererContextVK* _renderer, const VideoBindingVK& _binding) + { + BX_UNUSED(_renderer, _binding); + } + + VideoDecoderVK* videoDecoderCreate(const VideoDecoderInit& _init, RendererContextVK* _renderer, uint16_t _width, uint16_t _height) + { + BX_UNUSED(_init, _renderer, _width, _height); + return NULL; + } + + void videoDecoderDestroy(VideoDecoderVK* _decoder) + { + BX_UNUSED(_decoder); + } + + bool videoDecoderDecode(VideoDecoderVK* _decoder, const VideoDecoderFrame& _frame, TextureVK& _dst) + { + BX_UNUSED(_decoder, _frame, _dst); + return false; + } + +} } // namespace bgfx::vk + +#endif // BGFX_CONFIG_VIDEO_VULKAN + +#endif // BGFX_CONFIG_RENDERER_VULKAN diff --git a/src/video_vk.h b/src/video_vk.h new file mode 100644 index 000000000..33b4d9192 --- /dev/null +++ b/src/video_vk.h @@ -0,0 +1,37 @@ +/* + * Copyright 2011-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE + */ + +#ifndef BGFX_VIDEO_VK_H_HEADER_GUARD +#define BGFX_VIDEO_VK_H_HEADER_GUARD + +#include + +namespace bgfx { namespace vk +{ + struct RendererContextVK; + struct TextureVK; + struct VideoDecoderVK; + + struct VideoBindingVK + { + VkDevice device; + const VkAllocationCallbacks* allocCb; + VkPhysicalDevice physicalDevice; + uint32_t videoDecodeQueueFamily; + uint32_t globalQueueFamily; + VkQueue videoDecodeQueue; + VkQueue globalQueue; + uint16_t vendorId; + }; + + void initVideoDecoder(RendererContextVK* _renderer, const VideoBindingVK& _binding); + + VideoDecoderVK* videoDecoderCreate(const VideoDecoderInit& _init, RendererContextVK* _renderer, uint16_t _width, uint16_t _height); + void videoDecoderDestroy(VideoDecoderVK* _decoder); + bool videoDecoderDecode(VideoDecoderVK* _decoder, const VideoDecoderFrame& _frame, TextureVK& _dst); + +} } // namespace bgfx::vk + +#endif // BGFX_VIDEO_VK_H_HEADER_GUARD diff --git a/tools/texturev/texturev.cpp b/tools/texturev/texturev.cpp index 16e0f21ec..22e4006cc 100644 --- a/tools/texturev/texturev.cpp +++ b/tools/texturev/texturev.cpp @@ -5,6 +5,8 @@ #include "common.h" +#include "video_player.h" + #include #include @@ -109,6 +111,7 @@ struct Binding View, Help, About, + Video, Count }; @@ -138,15 +141,7 @@ struct Output }; }; -static const InputBinding s_bindingApp[] = -{ - { entry::Key::KeyQ, entry::Modifier::None, 1, NULL, "exit" }, - { entry::Key::KeyF, entry::Modifier::None, 1, NULL, "graphics fullscreen" }, - - INPUT_BINDING_END -}; - -const char* s_resetCmd = +static const char* s_resetCmd = "view zoom 1.0\n" "view rotate x 0\n" "view rotate y 0\n" @@ -156,57 +151,68 @@ const char* s_resetCmd = "view ev\n" ; -static const InputBinding s_bindingView[] = +static const InputBinding s_bindingApp[] = { - { entry::Key::Esc, entry::Modifier::None, 1, NULL, "exit" }, - - { entry::Key::Comma, entry::Modifier::None, 1, NULL, "view mip prev" }, - { entry::Key::Period, entry::Modifier::None, 1, NULL, "view mip next" }, - { entry::Key::Comma, entry::Modifier::LeftShift, 1, NULL, "view mip" }, - { entry::Key::Comma, entry::Modifier::RightShift, 1, NULL, "view mip" }, - - { entry::Key::Slash, entry::Modifier::None, 1, NULL, "view filter" }, - - { entry::Key::Key1, entry::Modifier::None, 1, NULL, "view zoom 1.0\n" - "view fit\n" }, - - { entry::Key::Key0, entry::Modifier::None, 1, NULL, s_resetCmd }, - { entry::Key::Plus, entry::Modifier::None, 1, NULL, "view zoom +0.1" }, - { entry::Key::Minus, entry::Modifier::None, 1, NULL, "view zoom -0.1" }, - - { entry::Key::KeyZ, entry::Modifier::None, 1, NULL, "view rotate z -90" }, - { entry::Key::KeyZ, entry::Modifier::LeftShift, 1, NULL, "view rotate z +90" }, - - { entry::Key::KeyX, entry::Modifier::None, 1, NULL, "view rotate x +180" }, - { entry::Key::KeyY, entry::Modifier::None, 1, NULL, "view rotate y +180" }, + { entry::Key::KeyQ, entry::Modifier::None, 1, NULL, "exit" }, + { entry::Key::KeyF, entry::Modifier::None, 1, NULL, "graphics fullscreen" }, { entry::Key::Up, entry::Modifier::None, 1, NULL, "view pan\n" - "view file-up" }, + "view file-up" }, { entry::Key::Down, entry::Modifier::None, 1, NULL, "view pan\n" - "view file-down" }, + "view file-down" }, { entry::Key::PageUp, entry::Modifier::None, 1, NULL, "view pan\n" - "view file-pgup" }, + "view file-pgup" }, { entry::Key::PageDown, entry::Modifier::None, 1, NULL, "view pan\n" - "view file-pgdown" }, + "view file-pgdown" }, - { entry::Key::Left, entry::Modifier::None, 1, NULL, "view layer prev" }, - { entry::Key::Right, entry::Modifier::None, 1, NULL, "view layer next" }, + { entry::Key::Return, entry::Modifier::None, 1, NULL, "view files" }, + { entry::Key::KeyH, entry::Modifier::None, 1, NULL, "view help" }, + { entry::Key::KeyI, entry::Modifier::None, 1, NULL, "view info" }, - { entry::Key::KeyR, entry::Modifier::None, 1, NULL, "view rgb r" }, - { entry::Key::KeyG, entry::Modifier::None, 1, NULL, "view rgb g" }, - { entry::Key::KeyB, entry::Modifier::None, 1, NULL, "view rgb b" }, - { entry::Key::KeyA, entry::Modifier::None, 1, NULL, "view rgb a" }, + { entry::Key::Key1, entry::Modifier::None, 1, NULL, "view zoom 1.0\n" + "view fit\n" }, + { entry::Key::Key0, entry::Modifier::None, 1, NULL, s_resetCmd }, + { entry::Key::Plus, entry::Modifier::None, 1, NULL, "view zoom +0.1" }, + { entry::Key::Minus, entry::Modifier::None, 1, NULL, "view zoom -0.1" }, - { entry::Key::KeyI, entry::Modifier::None, 1, NULL, "view info" }, + { entry::Key::KeyZ, entry::Modifier::None, 1, NULL, "view rotate z -90" }, + { entry::Key::KeyZ, entry::Modifier::LeftShift, 1, NULL, "view rotate z +90" }, - { entry::Key::KeyH, entry::Modifier::None, 1, NULL, "view help" }, + { entry::Key::KeyX, entry::Modifier::None, 1, NULL, "view rotate x +180" }, + { entry::Key::KeyY, entry::Modifier::None, 1, NULL, "view rotate y +180" }, - { entry::Key::Return, entry::Modifier::None, 1, NULL, "view files" }, + { entry::Key::KeyR, entry::Modifier::None, 1, NULL, "view rgb r" }, + { entry::Key::KeyG, entry::Modifier::None, 1, NULL, "view rgb g" }, + { entry::Key::KeyB, entry::Modifier::None, 1, NULL, "view rgb b" }, + { entry::Key::KeyA, entry::Modifier::None, 1, NULL, "view rgb a" }, - { entry::Key::KeyS, entry::Modifier::None, 1, NULL, "view sdf" }, + { entry::Key::KeyI, entry::Modifier::None, 1, NULL, "view info" }, + + { entry::Key::KeyH, entry::Modifier::None, 1, NULL, "view help" }, + + { entry::Key::Return, entry::Modifier::None, 1, NULL, "view files" }, + + INPUT_BINDING_END +}; + +static const InputBinding s_bindingView[] = +{ + { entry::Key::Esc, entry::Modifier::None, 1, NULL, "exit" }, + + { entry::Key::Comma, entry::Modifier::None, 1, NULL, "view mip prev" }, + { entry::Key::Period, entry::Modifier::None, 1, NULL, "view mip next" }, + { entry::Key::Comma, entry::Modifier::LeftShift, 1, NULL, "view mip" }, + { entry::Key::Comma, entry::Modifier::RightShift, 1, NULL, "view mip" }, + + { entry::Key::Slash, entry::Modifier::None, 1, NULL, "view filter" }, + + { entry::Key::Left, entry::Modifier::None, 1, NULL, "view layer prev" }, + { entry::Key::Right, entry::Modifier::None, 1, NULL, "view layer next" }, + + { entry::Key::KeyS, entry::Modifier::None, 1, NULL, "view sdf" }, { entry::Key::Space, entry::Modifier::None, 1, NULL, "view geo\n" - "view pan\n" }, + "view pan\n" }, INPUT_BINDING_END }; @@ -224,12 +230,31 @@ static const InputBinding s_bindingAbout[] = INPUT_BINDING_END }; +// Bindings active while a video clip is loaded. Replaces the standard View +// bindings on file change so per-image keys (rgb channel toggle, geometry, +// layer / mip navigation) don't accidentally fire on the video texture. +// Per-file navigation, exit, zoom and help are kept. +static const InputBinding s_bindingVideo[] = +{ + { entry::Key::Esc, entry::Modifier::None, 1, NULL, "exit" }, + + { entry::Key::Left, entry::Modifier::None, 1, NULL, "video skip -8" }, + { entry::Key::Right, entry::Modifier::None, 1, NULL, "video skip +8" }, + { entry::Key::Space, entry::Modifier::None, 1, NULL, "video pause" }, + { entry::Key::LeftBracket, entry::Modifier::None, 1, NULL, "video rate down" }, + { entry::Key::RightBracket, entry::Modifier::None, 1, NULL, "video rate up" }, + { entry::Key::KeyL, entry::Modifier::None, 1, NULL, "video marker" }, + + INPUT_BINDING_END +}; + static const char* s_bindingName[] = { "App", "View", "Help", "About", + "Video", }; static_assert(Binding::Count == BX_COUNTOF(s_bindingName) ); @@ -239,6 +264,7 @@ static const InputBinding* s_binding[] = s_bindingView, s_bindingHelp, s_bindingAbout, + s_bindingVideo, }; static_assert(Binding::Count == BX_COUNTOF(s_binding) ); @@ -909,6 +935,52 @@ int cmdView(CmdContext* /*_context*/, void* _userData, int _argc, char const* co return view->cmd(_argc, _argv); } +int cmdVideo(CmdContext* /*_context*/, void* _userData, int _argc, char const* const* _argv) +{ + VideoPlayer* player = static_cast(_userData); + if (NULL == player || !player->isOpen() || _argc < 2) + { + return 0; + } + + const bx::StringView verb(_argv[1]); + + if (0 == bx::strCmp(verb, "skip", 4) ) + { + float seconds = 0.0f; + if (_argc >= 3) + { + bx::fromString(&seconds, _argv[2]); + } + player->seekRelative(int64_t(seconds * 1.0e6f) ); + } + else if (0 == bx::strCmp(verb, "pause", 5) ) + { + player->togglePause(); + } + else if (0 == bx::strCmp(verb, "rate", 4) ) + { + if (_argc >= 3 && 0 == bx::strCmp(_argv[2], "up", 2) ) + { + player->rateUp(); + } + else if (_argc >= 3 && 0 == bx::strCmp(_argv[2], "down", 4) ) + { + player->rateDown(); + } + } + else if (0 == bx::strCmp(verb, "marker", 6) ) + { + player->cycleAbMarker(); + } + else if (0 == bx::strCmp(verb, "restart", 7) ) + { + player->restart(); + } + + return 0; +} + struct PosUvwColorVertex { float m_x; @@ -1322,6 +1394,21 @@ void help(const char* _error = NULL) " -v, --version Version information only.\n" " --associate Associate file extensions with texturev.\n" "\n" + " --gl Force OpenGL renderer.\n" + " --vk Force Vulkan renderer.\n" + " --noop Force no-op renderer.\n" + " --d3d11 Force Direct3D 11 renderer.\n" + " --d3d12 Force Direct3D 12 renderer.\n" + " --mtl Force Metal renderer (macOS only).\n" + "\n" + " --amd Prefer AMD GPU.\n" + " --apple Prefer Apple GPU.\n" + " --arm Prefer ARM GPU.\n" + " --intel Prefer Intel GPU.\n" + " --nvidia Prefer NVIDIA GPU.\n" + " --microsoft Prefer Microsoft GPU.\n" + " --sw Prefer software rasterizer.\n" + "\n" "For additional information, see https://github.com/bkaradzic/bgfx\n" ); } @@ -1356,22 +1443,32 @@ int _main_(int _argc, char** _argv) uint32_t debug = BGFX_DEBUG_TEXT; + Args args(_argc, _argv); + inputAddBindings(s_bindingName[Binding::App], s_binding[Binding::App]); inputAddBindings(s_bindingName[Binding::View], s_binding[Binding::View]); View view; cmdAdd("view", cmdView, &view); + VideoPlayer videoPlayer; + cmdAdd("video", cmdVideo, &videoPlayer); + entry::setWindowFlags(entry::kDefaultWindowHandle, ENTRY_WINDOW_FLAG_ASPECT_RATIO, false); entry::setWindowSize(entry::kDefaultWindowHandle, view.m_width, view.m_height); bgfx::Init init; - init.type = view.m_rendererType; + init.type = bgfx::RendererType::Count != args.m_type + ? args.m_type + : view.m_rendererType + ; + init.vendorId = args.m_pciId; init.platformData.nwh = entry::getNativeWindowHandle(entry::kDefaultWindowHandle); init.platformData.ndt = entry::getNativeDisplayHandle(); init.resolution.width = view.m_width; init.resolution.height = view.m_height; init.resolution.reset = BGFX_RESET_VSYNC; + init.videoDecode = true; bgfx::init(init); @@ -1390,6 +1487,12 @@ int _main_(int _argc, char** _argv) const bgfx::Caps* caps = bgfx::getCaps(); bgfx::RendererType::Enum type = caps->rendererType; + if (0 != (caps->supported & BGFX_CAPS_VIDEO_DECODE) ) + { + s_supportedExt[s_numSupportedExt++] = "mp4"; + bx::quickSort(s_supportedExt, s_numSupportedExt, sizeof(s_supportedExt[0]), compareExt); + } + bgfx::UniformHandle s_texColor = bgfx::createUniform("s_texColor", bgfx::UniformType::Sampler); bgfx::UniformHandle u_mtx = bgfx::createUniform("u_mtx", bgfx::UniformType::Mat4); bgfx::UniformHandle u_params0 = bgfx::createUniform("u_params0", bgfx::UniformType::Vec4); @@ -1495,12 +1598,26 @@ int _main_(int _argc, char** _argv) ; }; - const char* filePath = _argc < 2 ? "" : _argv[1]; + // Accept the file path as the first non-flag positional argument so + // renderer / vendor switches can appear in any order on the command + // line (e.g. `texturev clip.mp4 --vk --intel` or + // `texturev --vk --intel clip.mp4` both work). + const char* filePath = ""; + for (int32_t ii = 1; ii < _argc; ++ii) + { + if (_argv[ii][0] != '-') + { + filePath = _argv[ii]; + break; + } + } view.updateFileList(filePath); int exitcode = bx::kExitSuccess; bgfx::TextureHandle texture = BGFX_INVALID_HANDLE; + bool videoOwnsTexture = false; + int playbackBinding = Binding::View; { uint32_t fileIndex = UINT32_MAX; @@ -1736,16 +1853,84 @@ int _main_(int _argc, char** _argv) name = " 2D Array"; } - ImGui::Text("%d x %d%s, mips: %d, layers %d, %s" + const char* codecName = ""; + + if (videoPlayer.isOpen() ) + { + static const char* s_videoCodecName[] = + { + ", H.264", // VideoCodec::H264 + ", H.265", // VideoCodec::H265 + ", AV1", // VideoCodec::AV1 + }; + static_assert(BX_COUNTOF(s_videoCodecName) == bgfx::VideoCodec::Count); + const bgfx::VideoCodec::Enum codec = videoPlayer.codec(); + codecName = s_videoCodecName[codec]; + } + + ImGui::Text("%d x %d%s, mips: %d, layers %d, %s%s" , view.m_textureInfo.width , view.m_textureInfo.height , name , view.m_textureInfo.numMips , view.m_textureInfo.numLayers , bimg::getName(bimg::TextureFormat::Enum(view.m_textureInfo.format) ) + , codecName ); } + if (videoPlayer.isOpen() ) + { + ImGui::Separator(); + + const bool paused = videoPlayer.isPaused(); + if (ImGui::Button(paused ? ICON_FA_PLAY : ICON_FA_PAUSE) ) + { + videoPlayer.togglePause(); + } + + const int64_t curUs = videoPlayer.playbackTimeUs(); + const int64_t durUs = int64_t(videoPlayer.durationUs() ); + const float curS = float(double(curUs) * 1e-6); + const float durS = bx::max(0.001f, float(double(durUs) * 1e-6) ); + + float sliderS = bx::clamp(curS, 0.0f, durS); + ImGui::PushItemWidth(240.0f); + + ImGui::PushItemFlag(ImGuiItemFlags_NoNav, true); + if (ImGui::SliderFloat("##videoProgress", &sliderS, 0.0f, durS, "%.2fs", ImGuiSliderFlags_AlwaysClamp) ) + { + videoPlayer.seekTo(uint64_t(double(sliderS) * 1e6) ); + } + + ImGui::PopItemFlag(); + ImGui::PopItemWidth(); + + ImGui::Text("/ %5.2fs " ICON_FA_FORWARD " %.2fx", durS, videoPlayer.playbackRate() ); + + const int64_t aUs = videoPlayer.aMarkerUs(); + const int64_t bUs = videoPlayer.bMarkerUs(); + if (aUs >= 0 && bUs >= 0) + { + ImGui::Separator(); + ImGui::TextColored( + ImVec4(1.0f, 1.0f, 0.0f, 1.0f) + , ICON_FA_REPEAT " A=%.2fs B=%.2fs" + , double(aUs)*1e-6 + , double(bUs)*1e-6 + ); + } + else if (aUs >= 0) + { + ImGui::Separator(); + ImGui::TextColored( + ImVec4(1.0f, 1.0f, 0.0f, 1.0f) + , "A=%.2fs" + , double(aUs)*1e-6 + ); + } + } + ImGui::EndMainMenuBar(); } @@ -1802,13 +1987,13 @@ int _main_(int _argc, char** _argv) if (!help) { ImGui::OpenPopup("Help"); - inputRemoveBindings(s_bindingName[Binding::View]); + inputRemoveBindings(s_bindingName[playbackBinding]); inputAddBindings(s_bindingName[Binding::Help], s_binding[Binding::Help]); } else { inputRemoveBindings(s_bindingName[Binding::Help]); - inputAddBindings(s_bindingName[Binding::View], s_binding[Binding::View]); + inputAddBindings(s_bindingName[playbackBinding], s_binding[playbackBinding]); } help = view.m_help; @@ -1819,13 +2004,13 @@ int _main_(int _argc, char** _argv) if (!about) { ImGui::OpenPopup("About"); - inputRemoveBindings(s_bindingName[Binding::View]); + inputRemoveBindings(s_bindingName[playbackBinding]); inputAddBindings(s_bindingName[Binding::About], s_binding[Binding::About]); } else { inputRemoveBindings(s_bindingName[Binding::About]); - inputAddBindings(s_bindingName[Binding::View], s_binding[Binding::View]); + inputAddBindings(s_bindingName[playbackBinding], s_binding[playbackBinding]); } about = view.m_about; @@ -2048,79 +2233,114 @@ int _main_(int _argc, char** _argv) if (view.m_fileIndex != fileIndex && 0 != view.m_fileList.size() ) { - if (bgfx::isValid(texture) ) - { - bgfx::destroy(texture); - } - fileIndex = view.m_fileIndex; bx::FilePath fp = view.m_path; fp.join(view.m_fileList[view.m_fileIndex].c_str() ); - bimg::Orientation::Enum orientation; - bx::Error loadErr; - texture = loadTexture(fp.getCPtr() - , 0 - | BGFX_SAMPLER_U_CLAMP - | BGFX_SAMPLER_V_CLAMP - | BGFX_SAMPLER_W_CLAMP - , 0 - , &view.m_textureInfo - , &orientation - , &loadErr - ); + bx::StringView fileName(view.m_fileList[view.m_fileIndex].c_str() ); - bimg::TextureFormat::Enum format = bimg::TextureFormat::Enum(view.m_textureInfo.format); - - if (format < bimg::TextureFormat::Count) + bx::StringView ext; + if (fileName.getLength() >= 4) { - view.m_inLinear = bimg::isFloat(format); - - switch (orientation) - { - default: - case bimg::Orientation::R0: cmdExec("view orientation\nview orientation z 0"); break; - case bimg::Orientation::R90: cmdExec("view orientation\nview orientation z -90"); break; - case bimg::Orientation::R180: cmdExec("view orientation\nview orientation z -180"); break; - case bimg::Orientation::R270: cmdExec("view orientation\nview orientation z -270"); break; - case bimg::Orientation::HFlip: cmdExec("view orientation\nview orientation x -180"); break; - case bimg::Orientation::HFlipR90: cmdExec("view orientation\nview orientation z -90\nview orientation x -180"); break; - case bimg::Orientation::HFlipR270: cmdExec("view orientation\nview orientation z -270\nview orientation x -180"); break; - case bimg::Orientation::VFlip: cmdExec("view orientation\nview orientation y -180"); break; - } + ext.set(fileName.getTerm() - 4, fileName.getTerm() ); } - std::string title; - if (isValid(texture) ) - { - const char* name = ""; - if (view.m_textureInfo.cubeMap) - { - name = " CubeMap"; - } - else if (1 < view.m_textureInfo.depth) - { - name = " 3D"; - view.m_textureInfo.numLayers = view.m_textureInfo.depth; - } - else if (1 < view.m_textureInfo.numLayers) - { - name = " 2D Array"; - } + const bool nextIsVideo = false + || 0 == bx::strCmpI(ext, ".mp4") + || 0 == bx::strCmpI(ext, ".m4v") + ; - bx::stringPrintf(title, "%s (%d x %d%s, mips: %d, layers %d, %s)" - , fp.getCPtr() - , view.m_textureInfo.width - , view.m_textureInfo.height - , name - , view.m_textureInfo.numMips - , view.m_textureInfo.numLayers - , bimg::getName(bimg::TextureFormat::Enum(view.m_textureInfo.format) ) - ); + if (videoOwnsTexture) + { + videoPlayer.close(); + videoOwnsTexture = false; + texture = BGFX_INVALID_HANDLE; + } + else if (bgfx::isValid(texture) ) + { + bgfx::destroy(texture); + texture = BGFX_INVALID_HANDLE; + } + + const int desiredBinding = nextIsVideo ? Binding::Video : Binding::View; + const bool modalOpen = view.m_help || view.m_about; + if (desiredBinding != playbackBinding && !modalOpen) + { + inputRemoveBindings(s_bindingName[playbackBinding]); + inputAddBindings(s_bindingName[desiredBinding], s_binding[desiredBinding]); + } + playbackBinding = desiredBinding; + + std::string title; + + if (nextIsVideo) + { + if (videoPlayer.open(fp.getCPtr() ) ) + { + texture = videoPlayer.texture(); + videoOwnsTexture = true; + + view.m_textureInfo.format = bgfx::TextureFormat::BGRA8; + view.m_textureInfo.storageSize = 0; + view.m_textureInfo.width = uint16_t(videoPlayer.width() ); + view.m_textureInfo.height = uint16_t(videoPlayer.height() ); + view.m_textureInfo.depth = 1; + view.m_textureInfo.numLayers = 1; + view.m_textureInfo.numMips = 1; + view.m_textureInfo.bitsPerPixel = 32; + view.m_textureInfo.cubeMap = false; + view.m_inLinear = false; + + cmdExec("view orientation\nview orientation z 0"); + + bx::stringPrintf(title, "%s (%d x %d, video, %5.2fs)" + , fp.getCPtr() + , view.m_textureInfo.width + , view.m_textureInfo.height + , double(videoPlayer.durationUs() ) * 1e-6 + ); + } + else + { + bx::stringPrintf(title, "Failed to load %s!", filePath); + } } else { + bimg::Orientation::Enum orientation; + bx::Error loadErr; + texture = loadTexture(fp.getCPtr() + , 0 + | BGFX_SAMPLER_U_CLAMP + | BGFX_SAMPLER_V_CLAMP + | BGFX_SAMPLER_W_CLAMP + , 0 + , &view.m_textureInfo + , &orientation + , &loadErr + ); + + bimg::TextureFormat::Enum format = bimg::TextureFormat::Enum(view.m_textureInfo.format); + + if (format < bimg::TextureFormat::Count) + { + view.m_inLinear = bimg::isFloat(format); + + switch (orientation) + { + default: + case bimg::Orientation::R0: cmdExec("view orientation\nview orientation z 0"); break; + case bimg::Orientation::R90: cmdExec("view orientation\nview orientation z -90"); break; + case bimg::Orientation::R180: cmdExec("view orientation\nview orientation z -180"); break; + case bimg::Orientation::R270: cmdExec("view orientation\nview orientation z -270"); break; + case bimg::Orientation::HFlip: cmdExec("view orientation\nview orientation x -180"); break; + case bimg::Orientation::HFlipR90: cmdExec("view orientation\nview orientation z -90\nview orientation x -180"); break; + case bimg::Orientation::HFlipR270: cmdExec("view orientation\nview orientation z -270\nview orientation x -180"); break; + case bimg::Orientation::VFlip: cmdExec("view orientation\nview orientation y -180"); break; + } + } + if (!loadErr.isOk() ) { const bx::StringView& msg = loadErr.getMessage(); @@ -2129,7 +2349,30 @@ int _main_(int _argc, char** _argv) } else { - bx::stringPrintf(title, "Failed to load %s!", filePath); + const char* name = ""; + if (view.m_textureInfo.cubeMap) + { + name = " CubeMap"; + } + else if (1 < view.m_textureInfo.depth) + { + name = " 3D"; + view.m_textureInfo.numLayers = view.m_textureInfo.depth; + } + else if (1 < view.m_textureInfo.numLayers) + { + name = " 2D Array"; + } + + bx::stringPrintf(title, "%s (%d x %d%s, mips: %d, layers %d, %s)" + , fp.getCPtr() + , view.m_textureInfo.width + , view.m_textureInfo.height + , name + , view.m_textureInfo.numMips + , view.m_textureInfo.numLayers + , bimg::getName(bimg::TextureFormat::Enum(view.m_textureInfo.format) ) + ); } } @@ -2321,18 +2564,29 @@ int _main_(int _argc, char** _argv) bgfx::discard(); } + if (videoPlayer.isOpen() ) + { + videoPlayer.tick(); + } + bgfx::frame(); // Slow down when nothing is animating... if (!dragging - && !anyActive() ) + && !anyActive() + && !videoPlayer.isOpen() ) { bx::sleep(100); } } } - if (bgfx::isValid(texture) ) + if (videoOwnsTexture) + { + videoPlayer.close(); + texture = BGFX_INVALID_HANDLE; + } + else if (bgfx::isValid(texture) ) { bgfx::destroy(texture); } diff --git a/tools/texturev/video_player.cpp b/tools/texturev/video_player.cpp new file mode 100644 index 000000000..e5d7d4e35 --- /dev/null +++ b/tools/texturev/video_player.cpp @@ -0,0 +1,1139 @@ +/* + * Copyright 2011-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE + */ + +#include "video_player.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include +namespace stl = tinystl; + +BX_PRAGMA_DIAGNOSTIC_PUSH(); +BX_PRAGMA_DIAGNOSTIC_IGNORED_MSVC(4127) // warning C4127: conditional expression is constant +BX_PRAGMA_DIAGNOSTIC_IGNORED_MSVC(4244) // warning C4244: '=': conversion, possible loss of data +BX_PRAGMA_DIAGNOSTIC_IGNORED_CLANG_GCC("-Wshadow") +#include "l-smash/lsmash.h" +BX_PRAGMA_DIAGNOSTIC_POP(); + +#include + +struct VideoPlayer::Impl +{ + bgfx::VideoCodec::Enum codec = bgfx::VideoCodec::Count; + uint8_t chroma = 1; + uint8_t bitDepth = 8; + uint8_t profile = 0; + uint8_t level = 0; + uint16_t width = 0; + uint16_t height = 0; + uint8_t maxDpbSlots = 0; + uint8_t maxActiveReferences = 0; + stl::vector parameterSets; + + lsmash_root_t* root = NULL; + lsmash_file_parameters_t fp = {}; + bool fpOpened = false; + uint32_t trackId = 0; + uint32_t lengthSize = 4; + + stl::vector annexBScratch; + + struct Frame + { + uint64_t ptsUs; + bool isKeyframe; + }; + + stl::vector frames; + + ~Impl() + { + if (NULL != root) + { + lsmash_destroy_root(root); + root = NULL; + } + + if (fpOpened) + { + lsmash_close_file(&fp); + fpOpened = false; + } + } +}; + +namespace +{ + const uint32_t kMaxSubmitPerCall = 1; + const int64_t kLookaheadUs = 2 * 1000000; + const int64_t kSkipStepUs = 8 * 1000000; + const float kRateStepFactor = 1.25892541f; + const float kRateMin = 0.1f; + const float kRateMax = 100.0f; + + void avccToAnnexB(stl::vector& _out, const uint8_t* _in, uint32_t _size, uint32_t _lengthSize) + { + uint32_t pos = 0; + + while (pos + _lengthSize <= _size) + { + uint32_t naluLen = 0; + + for (uint32_t ii = 0; ii < _lengthSize; ++ii) + { + naluLen = (naluLen << 8) | _in[pos + ii]; + } + + pos += _lengthSize; + + if (pos + naluLen > _size) + { + break; + } + + _out.push_back(0); + _out.push_back(0); + _out.push_back(1); + _out.insert(_out.end(), _in + pos, _in + pos + naluLen); + + pos += naluLen; + } + } + + uint32_t appendNalAsAnnexB(stl::vector& _out, const uint8_t* _nal, uint32_t _nalLen) + { + if (NULL == _nal + || 0 == _nalLen) + { + return 0; + } + + _out.push_back(0); + _out.push_back(0); + _out.push_back(1); + _out.insert(_out.end(), _nal, _nal + _nalLen); + + return 1; + } + + void stripEmulationPrevention(stl::vector& _dst, const uint8_t* _src, uint32_t _size) + { + _dst.clear(); + _dst.reserve(_size); + + uint32_t zeros = 0; + + for (uint32_t ii = 0; ii < _size; ++ii) + { + if (zeros >= 2 + && 0x03 == _src[ii]) + { + zeros = 0; + continue; + } + + zeros = (0 == _src[ii]) ? zeros + 1 : 0; + _dst.push_back(_src[ii]); + } + } + + bool parseAvcC(VideoPlayer::Impl& _video, const uint8_t* _data, uint32_t _size, uint32_t& _lengthSize) + { + const uint32_t kBoxHeaderSize = 8; + + if (NULL == _data + || _size < kBoxHeaderSize + 7) + { + return false; + } + + uint32_t pos = kBoxHeaderSize; + ++pos; + + const uint8_t profileIdc = _data[pos++]; + ++pos; + + const uint8_t levelIdc = _data[pos++]; + _lengthSize = uint32_t(_data[pos++] & 0x03) + 1; + const uint32_t numSps = uint32_t(_data[pos++] & 0x1f); + + _video.profile = profileIdc; + _video.level = levelIdc; + _video.chroma = 0; + _video.bitDepth = 8; + + bool spsParsed = false; + stl::vector rbsp; + + for (uint32_t ii = 0; ii < numSps; ++ii) + { + if (pos + 2 > _size) + { + return false; + } + + const uint32_t nalLen = (uint32_t(_data[pos]) << 8) | uint32_t(_data[pos + 1]); + pos += 2; + + if (pos + nalLen > _size) + { + return false; + } + + appendNalAsAnnexB(_video.parameterSets, _data + pos, nalLen); + + if (!spsParsed + && nalLen >= 1) + { + stripEmulationPrevention(rbsp, _data + pos, nalLen); + + h264::Bitstream bs; + bs.init(rbsp.data(), rbsp.size() ); + + h264::NALHeader nal; + + if (h264::read_nal_header(&nal, &bs) + && h264::NAL_UNIT_TYPE_SPS == nal.type) + { + h264::SPS sps = {}; + h264::read_sps(&sps, &bs); + + _video.chroma = + 3 == sps.chroma_format_idc ? 4 + : 2 == sps.chroma_format_idc ? 2 + : 0 + ; + + _video.bitDepth = uint8_t(sps.bit_depth_luma_minus8 + 8); + spsParsed = true; + } + } + + pos += nalLen; + } + + if (pos + 1 > _size) + { + return false; + } + + const uint32_t numPps = _data[pos++]; + + for (uint32_t ii = 0; ii < numPps; ++ii) + { + if (pos + 2 > _size) + { + return false; + } + + const uint32_t nalLen = (uint32_t(_data[pos]) << 8) | uint32_t(_data[pos + 1]); + pos += 2; + + if (pos + nalLen > _size) + { + return false; + } + + appendNalAsAnnexB(_video.parameterSets, _data + pos, nalLen); + pos += nalLen; + } + + return true; + } + + bool parseHvcC(VideoPlayer::Impl& _video, const uint8_t* _data, uint32_t _size, uint32_t& _lengthSize) + { + const uint32_t kBoxHeaderSize = 8; + + if (NULL == _data + || _size < kBoxHeaderSize + 23) + { + return false; + } + + uint32_t pos = kBoxHeaderSize; + ++pos; + + const uint8_t profileByte = _data[pos++]; + const uint8_t profileIdc = profileByte & 0x1f; + pos += 10; + + const uint8_t levelIdc = _data[pos++]; + pos += 3; + + const uint8_t chromaFormat = _data[pos++] & 0x03; + const uint8_t bitDepthLuma = uint8_t( (_data[pos++] & 0x07) + 8); + pos += 3; + + const uint8_t lengthSizeByte = _data[pos++]; + _lengthSize = uint32_t(lengthSizeByte & 0x03) + 1; + + if (pos + 1 > _size) + { + return false; + } + + const uint32_t numArrays = _data[pos++]; + + _video.profile = profileIdc; + _video.level = levelIdc; + _video.chroma = + 3 == chromaFormat ? 4 + : 2 == chromaFormat ? 2 + : 0 + ; + _video.bitDepth = bitDepthLuma; + + stl::vector vps; + stl::vector sps; + stl::vector pps; + + for (uint32_t aa = 0; aa < numArrays; ++aa) + { + if (pos + 3 > _size) + { + return false; + } + + const uint8_t arrayHdr = _data[pos++]; + const uint8_t nalUnitType = arrayHdr & 0x3f; + const uint32_t numNalus = (uint32_t(_data[pos]) << 8) | uint32_t(_data[pos + 1]); + pos += 2; + + stl::vector* dst = NULL; + + switch (nalUnitType) + { + case 32: dst = &vps; break; + case 33: dst = &sps; break; + case 34: dst = &pps; break; + default: break; + } + + for (uint32_t nn = 0; nn < numNalus; ++nn) + { + if (pos + 2 > _size) + { + return false; + } + + const uint32_t nalLen = (uint32_t(_data[pos]) << 8) | uint32_t(_data[pos + 1]); + pos += 2; + + if (pos + nalLen > _size) + { + return false; + } + + if (NULL != dst) + { + appendNalAsAnnexB(*dst, _data + pos, nalLen); + } + + pos += nalLen; + } + } + + _video.parameterSets.insert(_video.parameterSets.end(), vps.begin(), vps.end() ); + _video.parameterSets.insert(_video.parameterSets.end(), sps.begin(), sps.end() ); + _video.parameterSets.insert(_video.parameterSets.end(), pps.begin(), pps.end() ); + + return true; + } + + bool loadVideoFile(VideoPlayer::Impl& _video, const char* _path) + { + lsmash_root_t* root = lsmash_create_root(); + + if (NULL == root) + { + return false; + } + + lsmash_file_parameters_t fp = {}; + + if (lsmash_open_file(_path, 1, &fp) < 0) + { + lsmash_destroy_root(root); + return false; + } + + lsmash_file_t* fh = lsmash_set_file(root, &fp); + + if (NULL == fh) + { + lsmash_close_file(&fp); + lsmash_destroy_root(root); + + return false; + } + + if (lsmash_read_file(fh, &fp) < 0) + { + lsmash_close_file(&fp); + lsmash_destroy_root(root); + + return false; + } + + lsmash_movie_parameters_t mp; + lsmash_initialize_movie_parameters(&mp); + + if (lsmash_get_movie_parameters(root, &mp) < 0) + { + lsmash_close_file(&fp); + lsmash_destroy_root(root); + + return false; + } + + uint32_t trackId = 0; + lsmash_media_parameters_t md; + + for (uint32_t ii = 0; ii < mp.number_of_tracks; ++ii) + { + const uint32_t tid = lsmash_get_track_ID(root, ii + 1); + if (0 == tid) + { + continue; + } + + lsmash_initialize_media_parameters(&md); + + if (lsmash_get_media_parameters(root, tid, &md) < 0) + { + continue; + } + + if (md.handler_type == ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK) + { + trackId = tid; + break; + } + } + + if (0 == trackId) + { + lsmash_close_file(&fp); + lsmash_destroy_root(root); + return false; + } + + lsmash_summary_t* sum = lsmash_get_summary(root, trackId, 1); + + if (NULL == sum) + { + lsmash_close_file(&fp); + lsmash_destroy_root(root); + + return false; + } + + if (lsmash_check_codec_type_identical(sum->sample_type, ISOM_CODEC_TYPE_AVC1_VIDEO) + || lsmash_check_codec_type_identical(sum->sample_type, ISOM_CODEC_TYPE_AVC3_VIDEO) + ) + { + _video.codec = bgfx::VideoCodec::H264; + } + else if (lsmash_check_codec_type_identical(sum->sample_type, ISOM_CODEC_TYPE_HVC1_VIDEO) ) + { + _video.codec = bgfx::VideoCodec::H265; + } + else + { + lsmash_cleanup_summary(sum); + lsmash_close_file(&fp); + lsmash_destroy_root(root); + + return false; + } + + const lsmash_video_summary_t* vsum = (const lsmash_video_summary_t*)sum; + _video.width = uint16_t(vsum->width); + _video.height = uint16_t(vsum->height); + + _video.parameterSets.clear(); + uint32_t lengthSize = 4; + + if (_video.codec == bgfx::VideoCodec::H264) + { + for (uint32_t ii = 0; ; ++ii) + { + lsmash_codec_specific_t* cs = lsmash_get_codec_specific_data(sum, ii + 1); + + if (NULL == cs) + { + break; + } + + if (cs->type != LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_H264) + { + continue; + } + + lsmash_codec_specific_t* csRaw = (cs->format == LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED) + ? cs + : lsmash_convert_codec_specific_format(cs, LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED) + ; + + if (NULL == csRaw) + { + continue; + } + + parseAvcC(_video, csRaw->data.unstructured, csRaw->size, lengthSize); + + if (csRaw != cs) + { + lsmash_destroy_codec_specific_data(csRaw); + } + + break; + } + } + + if (_video.codec == bgfx::VideoCodec::H265) + { + for (uint32_t ii = 0; ; ++ii) + { + lsmash_codec_specific_t* cs = lsmash_get_codec_specific_data(sum, ii + 1); + + if (NULL == cs) + { + break; + } + + if (cs->type != LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_HEVC) + { + continue; + } + + lsmash_codec_specific_t* csRaw = (cs->format == LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED) + ? cs + : lsmash_convert_codec_specific_format(cs, LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED) + ; + + if (NULL == csRaw) + { + continue; + } + + parseHvcC(_video, csRaw->data.unstructured, csRaw->size, lengthSize); + + if (csRaw != cs) + { + lsmash_destroy_codec_specific_data(csRaw); + } + + break; + } + } + + _video.maxDpbSlots = 16; + _video.maxActiveReferences = 4; + + if (lsmash_construct_timeline(root, trackId) < 0) + { + lsmash_cleanup_summary(sum); + lsmash_close_file(&fp); + lsmash_destroy_root(root); + + return false; + } + + const uint32_t timescale = bx::max(md.timescale, 1); + const uint32_t numSamples = lsmash_get_sample_count_in_media_timeline(root, trackId); + + _video.frames.reserve(numSamples); + for (uint32_t ii = 0; ii < numSamples; ++ii) + { + lsmash_sample_t s = {}; + + if (0 != lsmash_get_sample_info_from_media_timeline(root, trackId, ii + 1, &s) ) + { + break; + } + + VideoPlayer::Impl::Frame frame = {}; + frame.ptsUs = s.cts * 1000000ull / timescale; + frame.isKeyframe = 0 != (s.prop.ra_flags & ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC); + + _video.frames.push_back(frame); + } + + lsmash_cleanup_summary(sum); + + if (_video.frames.empty() ) + { + lsmash_close_file(&fp); + lsmash_destroy_root(root); + + return false; + } + + _video.root = root; + _video.fp = fp; + _video.fpOpened = true; + _video.trackId = trackId; + _video.lengthSize = lengthSize; + + return true; + } + +} // namespace + +VideoPlayer::VideoPlayer() + : m_impl(NULL) + , m_isOpen(false) + , m_hasError(false) + , m_paused(false) + , m_pendingSet(false) + , m_width(0) + , m_height(0) + , m_durationUs(0) + , m_playbackTimeUs(0) + , m_lastTickHpc(0) + , m_nextAuToSubmit(0) + , m_lastSetIdr(UINT32_MAX) + , m_playbackRate(1.0f) + , m_aMarkerUs(-1) + , m_bMarkerUs(-1) + , m_videoTexture(BGFX_INVALID_HANDLE) +{ +} + +VideoPlayer::~VideoPlayer() +{ + close(); +} + +bool VideoPlayer::isVideoExt(const bx::StringView& _ext) +{ + return 0 == bx::strCmpI(_ext, "mp4") + || 0 == bx::strCmpI(_ext, "m4v") + ; +} + +bool VideoPlayer::open(const char* _filePath) +{ + close(); + + m_impl = BX_NEW(entry::getAllocator(), Impl); + + if (NULL == m_impl) + { + m_hasError = true; + return false; + } + + if (!loadVideoFile(*m_impl, _filePath) ) + { + bx::deleteObject(entry::getAllocator(), m_impl); + m_impl = NULL; + m_hasError = true; + + return false; + } + + m_width = m_impl->width; + m_height = m_impl->height; + + const bgfx::Caps* caps = bgfx::getCaps(); + const uint32_t codecCaps = caps->codecs[m_impl->codec]; + + const bool codecAvailable = true + && 0 != (caps->supported & BGFX_CAPS_VIDEO_DECODE) + && 0 != (codecCaps & BGFX_CAPS_VIDEO_CODEC_BIT_8) + && 0 != (codecCaps & BGFX_CAPS_VIDEO_CODEC_CHROMA_420) + ; + const bool targetSupported = 0 != (caps->formats[bgfx::TextureFormat::BGRA8] & BGFX_CAPS_FORMAT_TEXTURE_VIDEO_DECODE_DST); + const bool codecValid = bgfx::isVideoCodecValid( + m_impl->codec + , m_impl->chroma + , m_impl->bitDepth + , m_impl->width + , m_impl->height + , m_impl->maxDpbSlots + , m_impl->maxActiveReferences + ); + + if (!codecAvailable + || !targetSupported + || !codecValid) + { + bx::deleteObject(entry::getAllocator(), m_impl); + m_impl = NULL; + m_hasError = true; + return false; + } + + const uint32_t psSize = uint32_t(m_impl->parameterSets.size() ); + const uint32_t totalSize = uint32_t(sizeof(bgfx::VideoDecoderInit) ) + psSize; + + const bgfx::Memory* mem = bgfx::alloc(totalSize); + + bgfx::VideoDecoderInit* decInit = (bgfx::VideoDecoderInit*)mem->data; + decInit->magic = BX_MAKEFOURCC('V', 'D', 'I', '0'); + decInit->codec = m_impl->codec; + decInit->parameterSets = mem->data + sizeof(bgfx::VideoDecoderInit); + decInit->parameterSetsSize = psSize; + decInit->flags = 0; + decInit->cachedAuBytes = 0; + + bx::memCopy( + mem->data + sizeof(bgfx::VideoDecoderInit) + , m_impl->parameterSets.data() + , psSize + ); + + m_videoTexture = bgfx::createTexture2D( + m_width + , m_height + , false + , 1 + , bgfx::TextureFormat::BGRA8 + , BGFX_SAMPLER_U_CLAMP + | BGFX_SAMPLER_V_CLAMP + , mem + ); + + if (!bgfx::isValid(m_videoTexture) ) + { + bx::deleteObject(entry::getAllocator(), m_impl); + m_impl = NULL; + m_hasError = true; + + return false; + } + + m_durationUs = m_impl->frames.back().ptsUs; + m_playbackTimeUs = 0; + m_lastTickHpc = bx::getHPCounter(); + m_nextAuToSubmit = 0; + m_lastSetIdr = UINT32_MAX; + m_playbackRate = 1.0f; + m_paused = false; + m_pendingSet = false; + m_aMarkerUs = -1; + m_bMarkerUs = -1; + m_isOpen = true; + m_hasError = false; + + submitAccessUnitsUpTo(kLookaheadUs); + + return true; +} + +void VideoPlayer::close() +{ + if (bgfx::isValid(m_videoTexture) ) + { + bgfx::destroy(m_videoTexture); + m_videoTexture = BGFX_INVALID_HANDLE; + } + + if (NULL != m_impl) + { + bx::deleteObject(entry::getAllocator(), m_impl); + m_impl = NULL; + } + + m_isOpen = false; + m_hasError = false; + m_paused = false; + m_pendingSet = false; + m_width = 0; + m_height = 0; + m_durationUs = 0; + m_playbackTimeUs = 0; + m_nextAuToSubmit = 0; + m_lastSetIdr = UINT32_MAX; + m_playbackRate = 1.0f; + m_aMarkerUs = -1; + m_bMarkerUs = -1; +} + +bgfx::VideoCodec::Enum VideoPlayer::codec() const +{ + return NULL != m_impl ? m_impl->codec : bgfx::VideoCodec::Count; +} + +uint32_t VideoPlayer::findIdrAtOrBefore(uint64_t _ptsUs) const +{ + if (NULL == m_impl + || m_impl->frames.empty() ) + { + return 0; + } + + uint32_t lastIdr = 0; + + for (uint32_t ii = 0; ii < uint32_t(m_impl->frames.size() ); ++ii) + { + if (m_impl->frames[ii].ptsUs > _ptsUs) + { + break; + } + + if (m_impl->frames[ii].isKeyframe) + { + lastIdr = ii; + } + } + + return lastIdr; +} + +uint32_t VideoPlayer::findIdrAtOrAfter(uint64_t _ptsUs) const +{ + if (NULL == m_impl + || m_impl->frames.empty() ) + { + return 0; + } + + const uint32_t numFrames = uint32_t(m_impl->frames.size() ); + + for (uint32_t ii = 0; ii < numFrames; ++ii) + { + if (m_impl->frames[ii].ptsUs >= _ptsUs + && m_impl->frames[ii].isKeyframe) + { + return ii; + } + } + + return numFrames; +} + +uint32_t VideoPlayer::findFrameAtOrBefore(uint64_t _ptsUs) const +{ + if (NULL == m_impl + || m_impl->frames.empty() ) + { + return 0; + } + + const uint32_t numFrames = uint32_t(m_impl->frames.size() ); + uint32_t result = 0; + + for (uint32_t ii = 0; ii < numFrames; ++ii) + { + if (m_impl->frames[ii].ptsUs > _ptsUs) + { + break; + } + + result = ii; + } + + return result; +} + +void VideoPlayer::submitDecodeFrame(uint32_t _index, bool _setPosition) +{ + const VideoPlayer::Impl::Frame& f = m_impl->frames[_index]; + + lsmash_sample_t* s = lsmash_get_sample_from_media_timeline( + m_impl->root + , m_impl->trackId + , _index + 1 + ); + + if (NULL == s) + { + return; + } + + struct Payload + { + bgfx::VideoDecoderFrame frame; + bgfx::VideoDecoderAu au; + }; + + stl::vector& scratch = m_impl->annexBScratch; + scratch.clear(); + avccToAnnexB(scratch, s->data, s->length, m_impl->lengthSize); + const uint32_t bitstreamSize = uint32_t(scratch.size() ); + const uint32_t totalSize = uint32_t(sizeof(Payload) ) + bitstreamSize; + + const bgfx::Memory* mem = bgfx::alloc(totalSize); + Payload* dstPayload = (Payload*)mem->data; + uint8_t* dstBitstream = (uint8_t*)(dstPayload + 1); + + bx::memCopy(dstBitstream, scratch.data(), bitstreamSize); + + dstPayload->au.size = bitstreamSize; + dstPayload->au.ptsUs = int64_t(f.ptsUs); + dstPayload->frame.magic = BX_MAKEFOURCC('V', 'D', 'F', '0'); + dstPayload->frame.bitstream = dstBitstream; + dstPayload->frame.aus = &dstPayload->au; + dstPayload->frame.numAus = 1; + dstPayload->frame.presentationTimeUs = 0; + dstPayload->frame.flags = _setPosition + ? BGFX_VIDEO_DECODE_FRAME_SET + : BGFX_VIDEO_DECODE_FRAME_NONE + ; + + bgfx::updateTexture2D( + m_videoTexture + , 0 + , 0 + , 0, 0 + , m_width + , m_height + , mem + ); + + lsmash_delete_sample(s); +} + +void VideoPlayer::submitAccessUnitsUpTo(int64_t _untilPtsUs) +{ + if (NULL == m_impl) + { + return; + } + + uint32_t submitted = 0; + + while (m_nextAuToSubmit < uint32_t(m_impl->frames.size() ) + && int64_t(m_impl->frames[m_nextAuToSubmit].ptsUs) <= _untilPtsUs + && submitted < kMaxSubmitPerCall) + { + submitDecodeFrame(m_nextAuToSubmit, m_pendingSet); + m_pendingSet = false; + ++m_nextAuToSubmit; + ++submitted; + } +} + +void VideoPlayer::submitPresentationTick(int64_t _presentationTimeUs) +{ + bgfx::VideoDecoderFrame desc = {}; + desc.magic = BX_MAKEFOURCC('V', 'D', 'F', '0'); + desc.bitstream = NULL; + desc.aus = NULL; + desc.numAus = 0; + desc.presentationTimeUs = _presentationTimeUs; + desc.flags = BGFX_VIDEO_DECODE_FRAME_NONE; + + bgfx::updateTexture2D( + m_videoTexture + , 0 + , 0 + , 0, 0 + , m_width + , m_height + , bgfx::copy(&desc, sizeof(desc) ) + ); +} + +void VideoPlayer::tick() +{ + if (!m_isOpen) + { + return; + } + + const int64_t nowHpc = bx::getHPCounter(); + const int64_t deltaHpc = nowHpc - m_lastTickHpc; + m_lastTickHpc = nowHpc; + + if (!m_paused) + { + m_lastSetIdr = UINT32_MAX; + + const uint64_t deltaUs = uint64_t(double(deltaHpc) * 1000000.0 * double(m_playbackRate) / double(bx::getHPFrequency() ) ); + m_playbackTimeUs += deltaUs; + + if (m_aMarkerUs >= 0 + && m_bMarkerUs > m_aMarkerUs + && int64_t(m_playbackTimeUs) >= m_bMarkerUs) + { + seekTo(uint64_t(m_aMarkerUs) ); + } + else if (m_playbackTimeUs > m_durationUs) + { + seekTo(m_aMarkerUs >= 0 ? uint64_t(m_aMarkerUs) : 0); + } + } + + submitPresentationTick(int64_t(m_playbackTimeUs) ); + submitAccessUnitsUpTo(int64_t(m_playbackTimeUs) + kLookaheadUs); +} + +void VideoPlayer::seekTo(uint64_t _targetUs) +{ + if (!m_isOpen + || NULL == m_impl) + { + return; + } + + const uint64_t target = bx::min(_targetUs, m_durationUs); + + const uint32_t idr = findIdrAtOrBefore(target); + const uint32_t targetFrame = findFrameAtOrBefore(target); + const bool needReprime = + targetFrame < m_nextAuToSubmit + || idr > m_nextAuToSubmit + ; + + if (needReprime + && idr != m_lastSetIdr) + { + m_nextAuToSubmit = idr; + m_pendingSet = true; + m_lastSetIdr = idr; + } + + m_playbackTimeUs = m_paused + ? target + : m_impl->frames[idr].ptsUs + ; + m_lastTickHpc = bx::getHPCounter(); +} + +void VideoPlayer::seekRelative(int64_t _deltaUs) +{ + if (!m_isOpen + || NULL == m_impl) + { + return; + } + + m_lastSetIdr = UINT32_MAX; + + int64_t target = int64_t(m_playbackTimeUs) + _deltaUs; + + int64_t lo = 0; + int64_t hi = int64_t(m_durationUs); + + if (m_aMarkerUs >= 0 + && m_bMarkerUs > m_aMarkerUs) + { + lo = m_aMarkerUs; + hi = m_bMarkerUs; + } + + if (_deltaUs > 0 + && target > hi) + { + seekTo(uint64_t(lo) ); + return; + } + + target = bx::clamp(target, lo, hi); + + if (_deltaUs > 0) + { + const uint32_t idrBefore = findIdrAtOrBefore(uint64_t(target) ); + const uint64_t idrBeforePts = m_impl->frames[idrBefore].ptsUs; + + if (idrBeforePts <= m_playbackTimeUs) + { + const uint32_t numFrames = uint32_t(m_impl->frames.size() ); + const uint32_t idrAfter = findIdrAtOrAfter(m_playbackTimeUs + 1); + + if (idrAfter < numFrames) + { + m_nextAuToSubmit = idrAfter; + m_pendingSet = true; + m_lastSetIdr = idrAfter; + m_playbackTimeUs = m_impl->frames[idrAfter].ptsUs; + m_lastTickHpc = bx::getHPCounter(); + return; + } + + return; + } + } + + seekTo(uint64_t(target) ); +} + +void VideoPlayer::restart() +{ + if (!m_isOpen) + { + return; + } + + m_lastSetIdr = UINT32_MAX; + seekTo(m_aMarkerUs >= 0 ? uint64_t(m_aMarkerUs) : 0); +} + +void VideoPlayer::togglePause() +{ + if (!m_isOpen) + { + return; + } + + m_paused = !m_paused; + + m_lastTickHpc = bx::getHPCounter(); +} + +void VideoPlayer::rateUp() +{ + if (!m_isOpen) + { + return; + } + + m_playbackRate = bx::min(m_playbackRate * kRateStepFactor, kRateMax); +} + +void VideoPlayer::rateDown() +{ + if (!m_isOpen) + { + return; + } + + m_playbackRate = bx::max(m_playbackRate / kRateStepFactor, kRateMin); +} + +void VideoPlayer::cycleAbMarker() +{ + if (!m_isOpen) + { + return; + } + + if (m_aMarkerUs < 0) + { + m_aMarkerUs = int64_t(m_playbackTimeUs); + m_bMarkerUs = -1; + } + else if (m_bMarkerUs < 0) + { + int64_t b = int64_t(m_playbackTimeUs); + + if (b < m_aMarkerUs) + { + m_bMarkerUs = m_aMarkerUs; + m_aMarkerUs = b; + } + else + { + m_bMarkerUs = b; + } + } + else + { + m_aMarkerUs = -1; + m_bMarkerUs = -1; + } +} diff --git a/tools/texturev/video_player.h b/tools/texturev/video_player.h new file mode 100644 index 000000000..be094f399 --- /dev/null +++ b/tools/texturev/video_player.h @@ -0,0 +1,92 @@ +/* + * Copyright 2011-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE + */ + +#ifndef TEXTUREV_VIDEO_PLAYER_H_HEADER_GUARD +#define TEXTUREV_VIDEO_PLAYER_H_HEADER_GUARD + +#include +#include + +class VideoPlayer +{ +public: + VideoPlayer(); + ~VideoPlayer(); + + static bool isVideoExt(const bx::StringView& _ext); + + bool open(const char* _filePath); + void close(); + + bool isOpen() const { return m_isOpen; } + bool hasError() const { return m_hasError; } + uint16_t width() const { return m_width; } + uint16_t height() const { return m_height; } + bgfx::TextureHandle texture() const { return m_videoTexture; } + + void tick(); + + bool isActive() const { return m_isOpen && !m_paused; } + + void seekRelative(int64_t _deltaUs); + void seekTo(uint64_t _targetUs); + + void restart(); + + void togglePause(); + + void rateUp(); + void rateDown(); + + void cycleAbMarker(); + + uint64_t durationUs() const { return m_durationUs; } + uint64_t playbackTimeUs() const { return m_playbackTimeUs; } + float playbackRate() const { return m_playbackRate; } + bool isPaused() const { return m_paused; } + + int64_t aMarkerUs() const { return m_aMarkerUs; } + int64_t bMarkerUs() const { return m_bMarkerUs; } + bool hasAMarker() const { return m_aMarkerUs >= 0; } + bool hasBMarker() const { return m_bMarkerUs >= 0; } + + bgfx::VideoCodec::Enum codec() const; + + struct Impl; + +private: + void submitAccessUnitsUpTo(int64_t _untilPtsUs); + void submitPresentationTick(int64_t _presentationTimeUs); + void submitDecodeFrame(uint32_t _index, bool _setPosition); + + uint32_t findIdrAtOrBefore(uint64_t _ptsUs) const; + uint32_t findIdrAtOrAfter(uint64_t _ptsUs) const; + + uint32_t findFrameAtOrBefore(uint64_t _ptsUs) const; + + Impl* m_impl; + bool m_isOpen; + bool m_hasError; + bool m_paused; + bool m_pendingSet; + + uint16_t m_width; + uint16_t m_height; + + uint64_t m_durationUs; + uint64_t m_playbackTimeUs; + int64_t m_lastTickHpc; + uint32_t m_nextAuToSubmit; + + uint32_t m_lastSetIdr; + float m_playbackRate; + + int64_t m_aMarkerUs; + int64_t m_bMarkerUs; + + bgfx::TextureHandle m_videoTexture; +}; + +#endif // TEXTUREV_VIDEO_PLAYER_H_HEADER_GUARD