- Changes in mov_write_video_tag function to handle APV elementary stream - Provided structure APVDecoderConfigurationRecord that specifies the decoder configuration information for APV video content
Signed-off-by: Dawid Kozinski <d.kozin...@samsung.com> --- libavformat/Makefile | 2 +- libavformat/apv.c | 827 ++++++++++++++++++++++++++++++++++++++++ libavformat/apv.h | 94 +++++ libavformat/isom_tags.c | 2 + libavformat/movenc.c | 47 +++ 5 files changed, 971 insertions(+), 1 deletion(-) create mode 100644 libavformat/apv.c create mode 100644 libavformat/apv.h diff --git a/libavformat/Makefile b/libavformat/Makefile index 5a4ffc1568..004ab9fb6a 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -379,7 +379,7 @@ OBJS-$(CONFIG_MOV_DEMUXER) += mov.o mov_chan.o mov_esds.o \ OBJS-$(CONFIG_MOV_MUXER) += movenc.o \ movenchint.o mov_chan.o rtp.o \ movenccenc.o movenc_ttml.o rawutils.o \ - dovi_isom.o evc.o cbs.o cbs_av1.o + dovi_isom.o evc.o cbs.o cbs_av1.o apv.o OBJS-$(CONFIG_MP2_MUXER) += rawenc.o OBJS-$(CONFIG_MP3_DEMUXER) += mp3dec.o replaygain.o OBJS-$(CONFIG_MP3_MUXER) += mp3enc.o rawenc.o id3v2enc.o diff --git a/libavformat/apv.c b/libavformat/apv.c new file mode 100644 index 0000000000..9c71b39022 --- /dev/null +++ b/libavformat/apv.c @@ -0,0 +1,827 @@ +/* + * APV helper functions for muxers + * Copyright (c) 2025 Dawid Kozinski <d.kozin...@samsung.com> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + #include <stdbool.h> + +#include "libavutil/intreadwrite.h" +#include "libavutil/mem.h" + +#include "libavcodec/get_bits.h" +#include "libavcodec/golomb.h" +#include "libavcodec/apv.h" +#include "avformat.h" +#include "avio.h" +#include "apv.h" +#include "avio_internal.h" + + +#define TILE_COLS_MAX 20 +#define TILE_ROWS_MAX 20 +#define NUM_TILES_MAX TILE_COLS_MAX*TILE_ROWS_MAX + +#define NUM_COMP_MAX 4 + +#define MB_WIDTH 16 +#define MB_HEIGHT 16 + +/***************************************************************************** + * PBU types + *****************************************************************************/ +#define APV_PBU_TYPE_RESERVED (0) +#define APV_PBU_TYPE_PRIMARY_FRAME (1) +#define APV_PBU_TYPE_NON_PRIMARY_FRAME (2) +#define APV_PBU_TYPE_PREVIEW_FRAME (25) +#define APV_PBU_TYPE_DEPTH_FRAME (26) +#define APV_PBU_TYPE_ALPHA_FRAME (27) +#define APV_PBU_TYPE_AU_INFO (65) +#define APV_PBU_TYPE_METADATA (66) +#define APV_PBU_TYPE_FILLER (67) +#define APV_PBU_TYPE_UNKNOWN (-1) +#define APV_PBU_NUMS (10) + +#define APV_FRAME_TYPE_PRIMARY_FRAME (0) +#define APV_FRAME_TYPE_NON_PRIMARY_FRAME (1) +#define APV_FRAME_TYPE_PREVIEW_FRAME (2) +#define APV_FRAME_TYPE_DEPTH_FRAME (3) +#define APV_FRAME_TYPE_ALPHA_FRAME (4) +#define APV_FRAME_TYPE_NON_FRAME (-1) +#define APV_PBU_FRAME_TYPE_NUM (5) +#define CONFIGURATIONS_MAX (APV_PBU_FRAME_TYPE_NUM) + +typedef struct APVDecoderFrameInfo { + uint8_t reserved_zero_6bits; // 6 bits + uint8_t color_description_present_flag; // 1 bit + + // The variable indicates whether the capture_time_distance value in the APV bitstream's frame header should be ignored during playback. + // If capture_time_distance_ignored is set to true, the capture_time_distance information will not be utilized, + // and timing information for playback should be calculated using an alternative method. + // If set to false, the capture_time_distance value will be used as is from the frame header. + // It is recommended to set this variable to true, allowing the use of MP4 timestamps for playback and recording, + // which enables the conventional compression and playback methods based on the timestamp table defined by the ISO-based file format. + uint8_t capture_time_distance_ignored; // 1-bit + + uint8_t profile_idc; // 8 bits + uint8_t level_idc; // 8 bits + uint8_t band_idc; // 8 bits + uint32_t frame_width; // 32 bits + uint32_t frame_height; // 32 bits + uint8_t chroma_format_idc; // 4 bits + uint8_t bit_depth_minus8; // 4 bits + uint8_t capture_time_distance; // 8 bits + + // if (color_description_present_flag) + uint8_t color_primaries; // 8 bits + uint8_t transfer_characteristics; // 8 bits + uint8_t matrix_coefficients; // 8 bits + uint8_t full_range_flag; // 1 bit + uint8_t reserved_zero_7bits; // 7 bits + +} APVDecoderFrameInfo; + +typedef struct APVDecoderConfigurationEntry { + uint8_t pbu_type; // 8 bits + uint8_t number_of_frame_info; // 8 bits + + APVDecoderFrameInfo** frame_info; // An array of size number_of_frame_info storing elements of type APVDecoderFrameInfo* + +} APVDecoderConfigurationEntry; + +// ISOBMFF binding for APV +// @see https://github.com/openapv/openapv/blob/main/readme/apv_isobmff.md +typedef struct APVDecoderConfigurationRecord { + uint8_t configurationVersion; // 8 bits + uint8_t number_of_configuration_entry; // 8 bits + + APVDecoderConfigurationEntry configuration_entry[CONFIGURATIONS_MAX]; // table of size number_of_configuration_entry + +} APVDecoderConfigurationRecord ; + +// 5.3.6. Frame information +// @see https://www.ietf.org/archive/id/draft-lim-apv-04.html#name-frame-information +typedef struct frame_info_t { + uint8_t profile_idc; // 8 bits + uint8_t level_idc; // 8 bits + uint8_t band_idc; // 3 bits + uint8_t reserved_zero_5bits; // 5 bits + + uint32_t frame_width; // 24 bits + uint32_t frame_height; // 24 bits + + uint8_t chroma_format_idc; // 4 bits + uint8_t bit_depth_minus8; // 4 bits + + uint8_t capture_time_distance; // 8 bits + uint8_t reserved_zero_8bit; // 8 bits +} frame_info_t; + +// 5.3.7. Quantization matrix +// @see https://www.ietf.org/archive/id/draft-lim-apv-04.html#name-quantization-matrix +typedef struct quantization_matrix_t { + uint8_t q_matrix[3][8][8]; // @todo use NumCmp instead of 3 +} quantization_matrix_t; + +// 5.3.8. Tile info +// @see https://www.ietf.org/archive/id/draft-lim-apv-04.html#name-tile-info +typedef struct tile_info_t { + uint32_t tile_width_in_mbs; // u(20) + uint32_t tile_height_in_mbs; // u(20) + uint8_t tile_size_present_in_fh_flag; // u(1) + uint32_t tile_size_in_fh[TILE_ROWS_MAX*TILE_COLS_MAX]; // u(32) -> size of table: NumTiles + + // private + uint32_t ColStarts[TILE_COLS_MAX]; + uint32_t RowStarts[TILE_ROWS_MAX]; + + uint32_t TileCols; // The value of TileCols MUST be less than or equal to TILE_COLS_MAX. + uint32_t TileRows; // The value of TileRows MUST be less than or equal to TILE_ROWS_MAX. + uint32_t NumTiles; + +} tile_info_t; + + +// 5.3.17. Byte alignment +// @see https://www.ietf.org/archive/id/draft-lim-apv-04.html#name-byte-alignment +typedef struct byte_alignment_t { + uint8_t alignment_bit_equal_to_zero; /* equal to 0*/ // f(1) +} byte_alignment_t; + +// 5.3.5. Frame header +// @see https://www.ietf.org/archive/id/draft-lim-apv-04.html#name-frame-header +typedef struct frame_header_t { + frame_info_t frame_info; + uint8_t reserved_zero_8bits; // 8 bits + uint8_t color_description_present_flag; // 1 bits + + uint8_t color_primaries; // 8 bits + uint8_t transfer_characteristics; // 8 bits + uint8_t matrix_coefficients; // 8 bits + uint8_t full_range_flag; // 1 bit + + uint8_t use_q_matrix; // 1 bit + + quantization_matrix_t quantization_matrix; + tile_info_t tile_info; + + uint8_t reserved_zero_8bits_2; // 8 bits + byte_alignment_t byte_alignment_t; +} frame_header_t; + +// 5.3.11. Filler +// @see https://www.ietf.org/archive/id/draft-lim-apv-04.html#name-filler +typedef struct filler_t { + uint8_t *ff_byte; /* is a byte equal to 0xFF */ // f(8) +} filler_t; + +// 5.3.13. Tile header +// @see https://www.ietf.org/archive/id/draft-lim-apv-04.html#name-tile-header +typedef struct tile_header_t { + uint16_t tile_header_size; // u(16) + uint16_t tile_index; // u(16) + uint32_t tile_data_size[NUM_COMP_MAX]; // u(32) table of size NumComps MAX = 4 + uint8_t tile_qp[NUM_COMP_MAX]; // u(8) table of size NumComps + uint8_t reserved_zero_8bits; // u(8) + byte_alignment_t byte_alignment; + +} tile_header_t; + +// 5.3.16. AC coefficient coding +// @see https://www.ietf.org/archive/id/draft-lim-apv-04.html#name-ac-coefficient-coding +typedef struct { + uint8_t coeff_zero_run; + uint8_t abs_ac_coeff_minus1; + uint8_t sign_ac_coeff; +} ac_coeff_coding_t; + +// 5.3.15. Macroblock layer +// @see https://www.ietf.org/archive/id/draft-lim-apv-04.html#name-macroblock-layer +typedef struct macroblock_layer_t { + uint8_t abs_dc_coeff_diff; // h(v) + uint8_t sign_dc_coeff_diff; // u(1) + ac_coeff_coding_t ac_coeff_coding; +} macroblock_layer_t; + +// 5.3.14. Tile data +// @see https://www.ietf.org/archive/id/draft-lim-apv-04.html#name-tile-data +typedef struct tile_data_t { + macroblock_layer_t macroblock_layer; + byte_alignment_t byte_alignment; +} tile_data_t; + + +// 5.3.11. Filler +// @see https://www.ietf.org/archive/id/draft-lim-apv-04.html#name-filler +typedef struct tile_t { + tile_header_t tile_header; + uint8_t tile_data; + uint8_t ff_byte; /* is a byte equal to 0xFF */ // f(8) +} tile_t; + +// 5.3.4 Frame +// @see https://www.ietf.org/archive/id/draft-lim-apv-04.html#name-frame +typedef struct frame_t { + frame_header_t frame_header; + uint32_t tile_size[NUM_TILES_MAX]; // table of NumTiles size + tile_t tile[NUM_TILES_MAX]; // table of NumTiles size + filler_t filler; +} frame_t; + +// 5.3.3. Primitive bitstream unit header +// @see https://www.ietf.org/archive/id/draft-lim-apv-04.html#name-primitive-bitstream-unit-he +typedef struct pbu_header_t { + uint8_t pbu_type; // 8 bits + uint16_t group_id; // 16 bits + uint8_t reserved_zero_8bits; // 8 bits +} pbu_header_t; + +// 5.3.9. Access unit information +// @see https://www.ietf.org/archive/id/draft-lim-apv-04.html#name-access-unit-information +typedef struct au_info_t { + uint16_t num_frames; // 16 bits + + uint8_t pbu_type; // 8 bits + uint16_t group_id; // 16 bits + uint8_t reserved_zero_8bits; // 8 bits + frame_info_t frame_info; + + uint8_t reserved_zero_8bits_2; // 8 bits + byte_alignment_t byte_alignment; + +} au_info_t; + +typedef struct { + uint64_t high; + uint64_t low; +} uint128_t; + +// 5.3.10. Metadata +// @see https://www.ietf.org/archive/id/draft-lim-apv-04.html#name-metadata +typedef struct metadata_t { + uint16_t metadata_size; // 32 bits + + /* is a byte equal to 0xFF */ + uint8_t *ff_byte; // f(8) table of size payloadSize + uint8_t metadata_payload_type; // u(8) + + /* is a byte equal to 0xFF */ + uint8_t ff_byte_2; // f(8) + uint8_t metadata_payload_size; // u(8) + + // metadata ITU-T T.35 + uint8_t itu_t_t35_country_code; // u(8) + uint8_t itu_t_t35_country_code_extension; // u(8) + uint8_t itu_t_t35_payload; // u(8) + + // metadata mdcv (Mastering display color volume metadata) + uint16_t primary_chromaticity_x[3]; // u(16) + uint16_t primary_chromaticity_y[3]; // u(16) + uint16_t white_point_chromaticity_x; // u(16) + uint16_t white_point_chromaticity_y; // u(16) + uint32_t max_mastering_luminance; // u(32) + uint32_t min_mastering_luminance; // u(32) + + // metadata ccl (Content light level information metadata) + uint16_t max_cll; // u(16) + uint16_t max_fall; // u(16) + + // User defined metadata + uint128_t uuid; + uint8_t *user_defined_data_payload; // table of size payloadSize - 16 + + // Undefined metadata + uint8_t *undefined_metadata_payload_byte; // table of size payloadSize + + filler_t filler; + + uint8_t alignment_bit_equal_to_zero; +} metadata_t; + +// 5.3.2. Primitive bitstream unit +// @see https://www.ietf.org/archive/id/draft-lim-apv-04.html#name-primitive-bitstream-unit +typedef struct pbu_t { + pbu_header_t pbu_header; + frame_t frame; + au_info_t au_info; + metadata_t metadata; + filler_t filler; +} pbu_t; + +static int apv_read_pbu_header(GetBitContext *gb, pbu_header_t *pbu_header) +{ + pbu_header->pbu_type = get_bits(gb, 8); + pbu_header->group_id = get_bits(gb, 16); + pbu_header->reserved_zero_8bits = get_bits(gb, 8); + + return 0; +} + +// @see https://www.ietf.org/archive/id/draft-lim-apv-04.html#name-frame-information +static int apv_read_frame_info(GetBitContext *gb, frame_info_t *frame_info) +{ + uint8_t reserved_zero_5bits; + uint8_t reserved_zero_8bits; + + frame_info->profile_idc = get_bits(gb, 8); + frame_info->level_idc = get_bits(gb, 8); + frame_info->band_idc = get_bits(gb, 3); + + reserved_zero_5bits = get_bits(gb, 5); + + frame_info->frame_width = get_bits(gb,24); + frame_info->frame_height = get_bits(gb,24); + + frame_info->chroma_format_idc = get_bits(gb, 4); + frame_info->bit_depth_minus8 = get_bits(gb, 4); + + frame_info->capture_time_distance = get_bits(gb, 8); + reserved_zero_8bits = get_bits(gb, 8); + + if (reserved_zero_5bits != 0x00) + return -1; + + if (reserved_zero_8bits != 0x00) + return -1; + + return 0; +} + +static int apv_read_frame_header(GetBitContext *gb, frame_header_t *frame_header) +{ + uint8_t reserved_zero_8bits[2]; + + apv_read_frame_info(gb, &frame_header->frame_info); + + reserved_zero_8bits[0] = get_bits(gb, 8); + if(reserved_zero_8bits[0]!=0) { + return -1; + } + + frame_header->color_description_present_flag = get_bits(gb, 1); + if(frame_header->color_description_present_flag) { + frame_header->color_primaries = get_bits(gb, 8); + frame_header->transfer_characteristics = get_bits(gb, 8); + frame_header->matrix_coefficients = get_bits(gb, 8); + frame_header->full_range_flag = get_bits(gb, 1); + } + + // The further parsing of AU is not needed + + return 0; +} + +static inline uint32_t apv_read_pbu_size(const uint8_t *bits, int bits_size) +{ + uint32_t pbu_size = 0; + + if (bits_size < APV_PBU_SIZE_PREFIX_LENGTH) { + av_log(NULL, AV_LOG_ERROR, "Can't read PBU (primitive bitstream unit) size\n"); + return 0; + } + + pbu_size = AV_RB32(bits); + + return pbu_size; +} + +static int apv_read_au_info(GetBitContext *gb, au_info_t *au_info) +{ + int ret = 0; + au_info->num_frames = get_bits(gb, 16); + + for(int idx=0; idx<au_info->num_frames; idx++) { + au_info->pbu_type = get_bits(gb, 8); + au_info->group_id = get_bits(gb, 16); + au_info->reserved_zero_8bits = get_bits(gb, 8); + + ret = apv_read_frame_info(gb, &au_info->frame_info); + if(ret!=0) return ret; + } + au_info->reserved_zero_8bits_2 = get_bits(gb, 8); + if(au_info->reserved_zero_8bits_2!=0) return -1; + + // The further parsing of AU Info is not needed + + return ret; +} + +static int apv_read_frame(GetBitContext *gb, frame_t *frame) +{ + apv_read_frame_header(gb, &frame->frame_header); + + // The further parsing of frame is not needed + + return 0; +} + +// primitive bytestream unit +static int apv_read_pbu(const uint8_t *bs, int bs_size, pbu_t *pbu) +{ + GetBitContext gb; + + int ret = init_get_bits8(&gb, bs, bs_size); + if (ret < 0) + return ret; + + ret = apv_read_pbu_header(&gb, &pbu->pbu_header); + if (ret < 0) + return ret; + if(( 1 <= pbu->pbu_header.pbu_type && pbu->pbu_header.pbu_type <= 2) || + ( 25 <= pbu->pbu_header.pbu_type && pbu->pbu_header.pbu_type <= 27) ) { + ret = apv_read_frame(&gb, &pbu->frame); + } else if(pbu->pbu_header.pbu_type ==65) { + ret = apv_read_au_info(&gb, &pbu->au_info); + } else { + ret = -1; + } + + return ret; +} + +static int apv_number_of_pbu_entry(const uint8_t *data, uint32_t au_size) { + uint32_t currReadSize = 0; + int ret = 0; + + do { + uint32_t pbu_size = apv_read_pbu_size(data + currReadSize, APV_PBU_SIZE_PREFIX_LENGTH); + if (pbu_size == 0) return -1; + + currReadSize += APV_PBU_SIZE_PREFIX_LENGTH; + currReadSize += pbu_size; + ret++; + } while(au_size > currReadSize); + + return ret; +} + +static void apvc_init(APVDecoderConfigurationRecord * apvc) +{ + memset(apvc, 0, sizeof(APVDecoderConfigurationRecord )); + apvc->configurationVersion = 1; +} + +static void apvc_close(APVDecoderConfigurationRecord *apvc) +{ + for(int i=0;i<apvc->number_of_configuration_entry;i++) { + for(int j=0;j<apvc->configuration_entry[i].number_of_frame_info;j++) { + free(apvc->configuration_entry[i].frame_info[j]); + } + free(apvc->configuration_entry[i].frame_info); + apvc->configuration_entry[i].number_of_frame_info = 0; + } + apvc->number_of_configuration_entry = 0; +} + +static int apvc_write(AVIOContext *pb, APVDecoderConfigurationRecord * apvc) +{ + av_log(NULL, AV_LOG_TRACE, "configurationVersion: %"PRIu8"\n", + apvc->configurationVersion); + + av_log(NULL, AV_LOG_TRACE, "number_of_configuration_entry: %"PRIu8"\n", + apvc->number_of_configuration_entry); + + for(int i=0; i<apvc->number_of_configuration_entry;i++) { + av_log(NULL, AV_LOG_TRACE, "pbu_type: %"PRIu8"\n", + apvc->configuration_entry[i].pbu_type); + + av_log(NULL, AV_LOG_TRACE, "number_of_frame_info: %"PRIu8"\n", + apvc->configuration_entry[i].number_of_frame_info); + + for(int j=0; j < apvc->configuration_entry[i].number_of_frame_info; j++) { + av_log(NULL, AV_LOG_TRACE, "color_description_present_flag: %"PRIu8"\n", + apvc->configuration_entry[i].frame_info[j]->color_description_present_flag); + + av_log(NULL, AV_LOG_TRACE, "capture_time_distance_ignored: %"PRIu8"\n", + apvc->configuration_entry[i].frame_info[j]->capture_time_distance_ignored); + + av_log(NULL, AV_LOG_TRACE, "profile_idc: %"PRIu8"\n", + apvc->configuration_entry[i].frame_info[j]->profile_idc); + + av_log(NULL, AV_LOG_TRACE, "level_idc: %"PRIu8"\n", + apvc->configuration_entry[i].frame_info[j]->level_idc); + + av_log(NULL, AV_LOG_TRACE, "band_idc: %"PRIu8"\n", + apvc->configuration_entry[i].frame_info[j]->band_idc); + + av_log(NULL, AV_LOG_TRACE, "frame_width: %"PRIu32"\n", + apvc->configuration_entry[i].frame_info[j]->frame_width); + + av_log(NULL, AV_LOG_TRACE, "frame_height: %"PRIu32"\n", + apvc->configuration_entry[i].frame_info[j]->frame_height); + + av_log(NULL, AV_LOG_TRACE, "chroma_format_idc: %"PRIu8"\n", + apvc->configuration_entry[i].frame_info[j]->chroma_format_idc); + + av_log(NULL, AV_LOG_TRACE, "bit_depth_minus8: %"PRIu8"\n", + apvc->configuration_entry[i].frame_info[j]->bit_depth_minus8); + + av_log(NULL, AV_LOG_TRACE, "capture_time_distance: %"PRIu8"\n", + apvc->configuration_entry[i].frame_info[j]->capture_time_distance); + + if(apvc->configuration_entry[i].frame_info[j]->color_description_present_flag) { + + av_log(NULL, AV_LOG_TRACE, "color_primaries: %"PRIu8"\n", + apvc->configuration_entry[i].frame_info[j]->color_primaries); + + av_log(NULL, AV_LOG_TRACE, "transfer_characteristics: %"PRIu8"\n", + apvc->configuration_entry[i].frame_info[j]->transfer_characteristics); + + av_log(NULL, AV_LOG_TRACE, "matrix_coefficients: %"PRIu8"\n", + apvc->configuration_entry[i].frame_info[j]->matrix_coefficients); + + av_log(NULL, AV_LOG_TRACE, "full_range_flag: %"PRIu8"\n", + apvc->configuration_entry[i].frame_info[j]->full_range_flag); + } + + } + } + + /* unsigned int(8) configurationVersion = 1; */ + avio_w8(pb, apvc->configurationVersion); + + avio_w8(pb, apvc->number_of_configuration_entry); + + for(int i=0; i<apvc->number_of_configuration_entry;i++) { + avio_w8(pb, apvc->configuration_entry[i].pbu_type); + avio_w8(pb, apvc->configuration_entry[i].number_of_frame_info); + + for(int j=0; j < apvc->configuration_entry[i].number_of_frame_info; j++) { + + /* unsigned int(6) reserved_zero_6bits + * unsigned int(1) color_description_present_flag + * unsigned int(1) capture_time_distance_ignored + */ + avio_w8(pb, apvc->configuration_entry[i].frame_info[j]->reserved_zero_6bits << 2 | + apvc->configuration_entry[i].frame_info[j]->color_description_present_flag << 1 | + apvc->configuration_entry[i].frame_info[j]->capture_time_distance_ignored); + + /* unsigned int(8) profile_idc */ + avio_w8(pb, apvc->configuration_entry[i].frame_info[j]->profile_idc); + + /* unsigned int(8) level_idc */ + avio_w8(pb, apvc->configuration_entry[i].frame_info[j]->level_idc); + + /* unsigned int(8) band_idc */ + avio_w8(pb, apvc->configuration_entry[i].frame_info[j]->band_idc); + + /* unsigned int(32) frame_width_minus1 */ + avio_wb32(pb, apvc->configuration_entry[i].frame_info[j]->frame_width); + + /* unsigned int(32) frame_height_minus1 */ + avio_wb32(pb, apvc->configuration_entry[i].frame_info[j]->frame_height); + + /* unsigned int(4) chroma_format_idc */ + /* unsigned int(4) bit_depth_minus8 */ + avio_w8(pb, apvc->configuration_entry[i].frame_info[j]->chroma_format_idc << 4 | apvc->configuration_entry[i].frame_info[j]->bit_depth_minus8); + + /* unsigned int(8) capture_time_distance */ + avio_w8(pb, apvc->configuration_entry[i].frame_info[j]->capture_time_distance); + + if(apvc->configuration_entry[i].frame_info[j]->color_description_present_flag) { + /* unsigned int(8) color_primaries */ + avio_w8(pb, apvc->configuration_entry[i].frame_info[j]->color_primaries); + + /* unsigned int(8) transfer_characteristics */ + avio_w8(pb, apvc->configuration_entry[i].frame_info[j]->transfer_characteristics); + + /* unsigned int(8) matrix_coefficients */ + avio_w8(pb, apvc->configuration_entry[i].frame_info[j]->matrix_coefficients); + + /* unsigned int(1) full_range_flag */ + avio_w8(pb, apvc->configuration_entry[i].frame_info[j]->full_range_flag << 7 | + apvc->configuration_entry[i].frame_info[j]->reserved_zero_7bits); + } + } + } + + return 0; +} + +int ff_isom_write_apvc(AVIOContext *pb, const uint8_t *data, + int size, int ps_array_completeness) +{ + APVDecoderConfigurationRecord *apvc = (APVDecoderConfigurationRecord *)data; + int ret = 0; + + if (size < 8) { + /* We can't write a valid apvC from the provided data */ + return AVERROR_INVALIDDATA; + } + + if(size!=sizeof(APVDecoderConfigurationRecord)) return -1; + ret = apvc_write(pb, apvc); + + apvc_close(apvc); + return ret; +} + +static int apv_add_frameinfo(APVDecoderConfigurationEntry *configuration_entry, APVDecoderFrameInfo *frame_info) { + APVDecoderFrameInfo **temp = NULL; + if(configuration_entry->number_of_frame_info == 0) { + temp = (APVDecoderFrameInfo **)malloc(sizeof(APVDecoderFrameInfo*)); + if (temp == NULL) { + return AVERROR_INVALIDDATA; + } + } else { + temp = (APVDecoderFrameInfo **)realloc(configuration_entry->frame_info, (configuration_entry->number_of_frame_info + 1) * sizeof(APVDecoderFrameInfo*)); + if (temp == NULL) { + return AVERROR_INVALIDDATA; + } + } + + temp[configuration_entry->number_of_frame_info] = (APVDecoderFrameInfo*)malloc(sizeof(APVDecoderFrameInfo)); + memcpy(temp[configuration_entry->number_of_frame_info], frame_info, sizeof(APVDecoderFrameInfo)); + + configuration_entry->frame_info = temp; + + configuration_entry->number_of_frame_info++; + + return 0; +} + +static bool apv_cmp_frameinfo(const APVDecoderFrameInfo *a, const APVDecoderFrameInfo *b) { + if (a->reserved_zero_6bits != b->reserved_zero_6bits) return false; + if (a->color_description_present_flag != b->color_description_present_flag) return false; + if (a->capture_time_distance_ignored != b->capture_time_distance_ignored) return false; + if (a->profile_idc != b->profile_idc) return false; + if (a->level_idc != b->level_idc) return false; + if (a->band_idc != b->band_idc) return false; + if (a->frame_width != b->frame_width) return false; + if (a->frame_height != b->frame_height) return false; + if (a->chroma_format_idc != b->chroma_format_idc) return false; + if (a->bit_depth_minus8 != b->bit_depth_minus8) return false; + if (a->capture_time_distance != b->capture_time_distance) return false; + if (a->color_primaries != b->color_primaries) return false; + if (a->transfer_characteristics != b->transfer_characteristics) return false; + if (a->matrix_coefficients != b->matrix_coefficients) return false; + if (a->full_range_flag != b->full_range_flag) return false; + if (a->reserved_zero_7bits != b->reserved_zero_7bits) return false; + + return true; +} + +static int apv_set_frameinfo(APVDecoderFrameInfo* frame_info, const pbu_t *pbu) { + frame_info->reserved_zero_6bits = 0; + + frame_info->color_description_present_flag = pbu->frame.frame_header.color_description_present_flag; + frame_info->capture_time_distance_ignored = 1; + + frame_info->profile_idc = pbu->frame.frame_header.frame_info.profile_idc; + frame_info->level_idc = pbu->frame.frame_header.frame_info.level_idc; + frame_info->band_idc = pbu->frame.frame_header.frame_info.band_idc; + + frame_info->frame_width = pbu->frame.frame_header.frame_info.frame_width; + frame_info->frame_height = pbu->frame.frame_header.frame_info.frame_height; + + frame_info->chroma_format_idc = pbu->frame.frame_header.frame_info.chroma_format_idc; + frame_info->bit_depth_minus8 = pbu->frame.frame_header.frame_info.bit_depth_minus8; + + frame_info->capture_time_distance = pbu->frame.frame_header.frame_info.capture_time_distance; + + + if(frame_info->color_description_present_flag) { + frame_info->color_primaries = pbu->frame.frame_header.color_primaries; + frame_info->transfer_characteristics = pbu->frame.frame_header.transfer_characteristics; + frame_info->matrix_coefficients = pbu->frame.frame_header.matrix_coefficients; + + frame_info->full_range_flag = pbu->frame.frame_header.full_range_flag; + frame_info->reserved_zero_7bits = 0; + } + + return 0; +} + +int ff_isom_create_apv_dconf_record(uint8_t **data, int *size) { + *size = sizeof(APVDecoderConfigurationRecord); + *data = (uint8_t*)av_malloc(sizeof(APVDecoderConfigurationRecord)); + if(*data==NULL) { + *size = 0; + return AVERROR_INVALIDDATA; + } + apvc_init((APVDecoderConfigurationRecord*)*data); + return 0; +} + +void ff_isom_free_apv_dconf_record(uint8_t **data) { + if (data != NULL && *data != NULL) { + APVDecoderConfigurationRecord* apvc = (APVDecoderConfigurationRecord*)*data; + apvc_close(apvc); + + free(*data); + *data = NULL; + } +} + +int ff_isom_fill_apv_dconf_record(const uint8_t *apvdcr, const uint8_t *data, int size) { + + uint32_t au_size = 0; + uint32_t number_of_pbu_entry = 0; + + uint32_t frame_type = -1; + APVDecoderFrameInfo frame_info; + + int bytes_to_read = size; + int ret = 0; + + APVDecoderConfigurationRecord* apvc = (APVDecoderConfigurationRecord*)apvdcr; + if (size < 8) { + /* We can't write a valid apvC from the provided data */ + return AVERROR_INVALIDDATA; + } + + if (bytes_to_read > APV_AU_SIZE_PREFIX_LENGTH) { + au_size = apv_read_au_size(data, APV_AU_SIZE_PREFIX_LENGTH); + if (au_size == 0) { + ret = AVERROR_INVALIDDATA; + goto end; + } + + data += APV_AU_SIZE_PREFIX_LENGTH; + bytes_to_read -= APV_AU_SIZE_PREFIX_LENGTH; + + if (bytes_to_read < au_size) { + ret = AVERROR_INVALIDDATA; + goto end; + } + + data += APV_SIGNATURE_LENGTH; + bytes_to_read -= APV_SIGNATURE_LENGTH; + + // pbu (primitive bitstream units number) + // + number_of_pbu_entry = apv_number_of_pbu_entry(data, bytes_to_read); + if (number_of_pbu_entry <= 0) { + ret = AVERROR_INVALIDDATA; + goto end; + } + + for(int i=0;i<number_of_pbu_entry;i++) { + + pbu_t pbu; + uint32_t pbu_size = apv_read_pbu_size(data, APV_PBU_SIZE_PREFIX_LENGTH); + + data += APV_AU_SIZE_PREFIX_LENGTH; + + if(!apv_read_pbu(data, pbu_size, &pbu)) { + + switch (pbu.pbu_header.pbu_type) + { + case APV_PBU_TYPE_PRIMARY_FRAME: + frame_type = APV_FRAME_TYPE_PRIMARY_FRAME; + break; + case APV_PBU_TYPE_NON_PRIMARY_FRAME: + frame_type = APV_FRAME_TYPE_NON_PRIMARY_FRAME; + break; + case APV_PBU_TYPE_PREVIEW_FRAME: + frame_type = APV_FRAME_TYPE_PREVIEW_FRAME; + break; + case APV_PBU_TYPE_DEPTH_FRAME: + frame_type = APV_FRAME_TYPE_DEPTH_FRAME; + break; + case APV_PBU_TYPE_ALPHA_FRAME: + frame_type = APV_FRAME_TYPE_ALPHA_FRAME; + break; + default: + frame_type = APV_FRAME_TYPE_NON_FRAME; + break; + }; + + if(frame_type == APV_FRAME_TYPE_NON_FRAME) continue; + + apv_set_frameinfo(&frame_info, &pbu); + + if(apvc->configuration_entry[frame_type].number_of_frame_info == 0) { + apv_add_frameinfo(&apvc->configuration_entry[frame_type], &frame_info); + apvc->number_of_configuration_entry++; + } else { + for(i=0; i<apvc->configuration_entry[frame_type].number_of_frame_info;i++) { + if(!apv_cmp_frameinfo(apvc->configuration_entry[frame_type].frame_info[i], &frame_info)) { + apv_add_frameinfo(&apvc->configuration_entry[i], &frame_info); + break; + } + } + } + } + data += pbu_size; + } + } + +end: + return ret; +} diff --git a/libavformat/apv.h b/libavformat/apv.h new file mode 100644 index 0000000000..16252a66b5 --- /dev/null +++ b/libavformat/apv.h @@ -0,0 +1,94 @@ +/* + * APV helper functions for muxers + * Copyright (c) 2025 Dawid Kozinski <d.kozin...@samsung.com> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVFORMAT_APV_H +#define AVFORMAT_APV_H + +#include <stdint.h> + +#include "libavutil/intreadwrite.h" +#include "libavutil/rational.h" +#include "libavcodec/apv.h" +#include "avio.h" + +static inline uint32_t apv_read_frame_data_size(const uint8_t *bits, int bits_size) +{ + if (bits_size >= APV_FRAME_DATA_SIZE_PREFIX_LENGTH) + return AV_RB32(bits); + + return 0; +} + +static inline uint32_t apv_read_au_size(const uint8_t *bits, int bits_size) +{ + if (bits_size >= APV_AU_SIZE_PREFIX_LENGTH) + return AV_RB32(bits); + + return 0; +} + +/** + * Writes APV sample metadata to the provided AVIOContext. + * + * @param pb pointer to the AVIOContext where the apv sample metadata shall be written + * @param buf input data buffer + * @param size size in bytes of the input data buffer + * @param ps_array_completeness + * + * @return 0 in case of success, a negative error code in case of failure + */ +int ff_isom_write_apvc(AVIOContext *pb, const uint8_t *data, + int size, int ps_array_completeness); + +/** + * @brief Creates and allocates memory for an APV decoder configuration record. + * + * This function allocates memory for an APVDecoderConfigurationRecord and + * initializes it. The size of the record is returned through the `size` parameter. + * + * @param data Pointer to a pointer where the allocated data will be stored. + * @param size Pointer to an integer where the size of the allocated record will be stored. + * @return 0 on success, or AVERROR_INVALIDDATA if memory allocation fails. + */ +int ff_isom_create_apv_dconf_record(uint8_t **data, int *size); + +/** + * @brief Frees the memory allocated for the APV decoder configuration record. + * + * @param data data to be freed + */ +void ff_isom_free_apv_dconf_record(uint8_t **data); + +/** + * @brief Fills an APV decoder configuration record with data. + * + * This function populates the APVDecoderConfigurationRecord pointed to by + * `apvdcr` with the data from `data`, which has a specified size. The data + * represents an access unit. + * + * @param apvdcr Pointer to the APVDecoderConfigurationRecord to be filled. + * @param data Pointer to the data to fill the record with. + * @param size Size of the data to be copied into the record. + * @return 0 on success, or a negative value on error. + */ +int ff_isom_fill_apv_dconf_record(const uint8_t *apvc, const uint8_t *data, int size); + +#endif // AVFORMAT_APV_H diff --git a/libavformat/isom_tags.c b/libavformat/isom_tags.c index f05762beec..6a806e09da 100644 --- a/libavformat/isom_tags.c +++ b/libavformat/isom_tags.c @@ -156,6 +156,8 @@ const AVCodecTag ff_codec_movvideo_tags[] = { { AV_CODEC_ID_H264, MKTAG('d', 'v', 'a', 'v') }, /* AVC-based Dolby Vision derived from avc3 */ { AV_CODEC_ID_EVC, MKTAG('e', 'v', 'c', '1') }, /* EVC/MPEG-5 */ + { AV_CODEC_ID_APV, MKTAG('a', 'p', 'v', '1') }, /* APV */ + { AV_CODEC_ID_VP8, MKTAG('v', 'p', '0', '8') }, /* VP8 */ { AV_CODEC_ID_VP9, MKTAG('v', 'p', '0', '9') }, /* VP9 */ diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 4bc8bd1b2a..91d7dc23a8 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -37,6 +37,7 @@ #include "av1.h" #include "avc.h" #include "evc.h" +#include "apv.h" #include "libavcodec/ac3_parser_internal.h" #include "libavcodec/dnxhddata.h" #include "libavcodec/flac.h" @@ -1647,6 +1648,24 @@ static int mov_write_vvcc_tag(AVIOContext *pb, MOVTrack *track) return update_size(pb, pos); } +static int mov_write_apvc_tag(AVIOContext *pb, MOVTrack *track) +{ + int64_t pos = avio_tell(pb); + + avio_wb32(pb, 0); + ffio_wfourcc(pb, "apvC"); + + avio_w8 (pb, 0); /* version */ + avio_wb24(pb, 0); /* flags */ + + if (track->tag == MKTAG('a','p','v','1')) + ff_isom_write_apvc(pb, track->vos_data, track->vos_len, 1); + else + ff_isom_write_apvc(pb, track->vos_data, track->vos_len, 0); + + return update_size(pb, pos); +} + /* also used by all avid codecs (dv, imx, meridien) and their variants */ static int mov_write_avid_tag(AVIOContext *pb, MOVTrack *track) { @@ -1906,6 +1925,17 @@ static int mov_get_evc_codec_tag(AVFormatContext *s, MOVTrack *track) return tag; } +static int mov_get_apv_codec_tag(AVFormatContext *s, MOVTrack *track) +{ + int tag = track->par->codec_tag; + + if (!tag) + tag = MKTAG('a', 'p', 'v', '1'); + + return tag; +} + + static const struct { enum AVPixelFormat pix_fmt; uint32_t tag; @@ -1992,6 +2022,8 @@ static unsigned int mov_get_codec_tag(AVFormatContext *s, MOVTrack *track) tag = mov_get_h264_codec_tag(s, track); else if (track->par->codec_id == AV_CODEC_ID_EVC) tag = mov_get_evc_codec_tag(s, track); + else if (track->par->codec_id == AV_CODEC_ID_APV) + tag = mov_get_apv_codec_tag(s, track); else if (track->par->codec_id == AV_CODEC_ID_DNXHD) tag = mov_get_dnxhd_codec_tag(s, track); else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { @@ -2757,6 +2789,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex } else if (track->par->codec_id ==AV_CODEC_ID_EVC) { mov_write_evcc_tag(pb, track); + } else if (track->par->codec_id ==AV_CODEC_ID_APV) { + mov_write_apvc_tag(pb, track); } else if (track->par->codec_id == AV_CODEC_ID_VP9) { mov_write_vpcc_tag(mov->fc, pb, track); } else if (track->par->codec_id == AV_CODEC_ID_AV1) { @@ -6713,6 +6747,18 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) memset(trk->vos_data + size, 0, AV_INPUT_BUFFER_PADDING_SIZE); } + if (par->codec_id == AV_CODEC_ID_APV && !trk->vos_len) { + ret = ff_isom_create_apv_dconf_record(&trk->vos_data, &trk->vos_len); + if (!trk->vos_data) { + ret = AVERROR(ENOMEM); + goto err; + } + } + + if (par->codec_id == AV_CODEC_ID_APV && trk->vos_len) { + ret = ff_isom_fill_apv_dconf_record(trk->vos_data, pkt->data, size); + } + if (par->codec_id == AV_CODEC_ID_AAC && pkt->size > 2 && (AV_RB16(pkt->data) & 0xfff0) == 0xfff0) { if (!trk->st->nb_frames) { @@ -8651,6 +8697,7 @@ static const AVCodecTag codec_mp4_tags[] = { { AV_CODEC_ID_VVC, MKTAG('v', 'v', 'c', '1') }, { AV_CODEC_ID_VVC, MKTAG('v', 'v', 'i', '1') }, { AV_CODEC_ID_EVC, MKTAG('e', 'v', 'c', '1') }, + { AV_CODEC_ID_APV, MKTAG('a', 'p', 'v', '1') }, { AV_CODEC_ID_MPEG2VIDEO, MKTAG('m', 'p', '4', 'v') }, { AV_CODEC_ID_MPEG1VIDEO, MKTAG('m', 'p', '4', 'v') }, { AV_CODEC_ID_MJPEG, MKTAG('m', 'p', '4', 'v') }, -- 2.34.1 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".