diff -Naur ffmpeg-7.1.2.old/configure ffmpeg-7.1.2/configure --- ffmpeg-7.1.2.old/configure 2025-10-27 10:07:02.407490033 +0100 +++ ffmpeg-7.1.2/configure 2025-10-27 10:07:02.458434476 +0100 @@ -2466,6 +2466,7 @@ kCMVideoCodecType_HEVC kCMVideoCodecType_HEVCWithAlpha kCMVideoCodecType_VP9 + kCMVideoCodecType_AV1 kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange kCVPixelFormatType_422YpCbCr8BiPlanarVideoRange kCVPixelFormatType_422YpCbCr10BiPlanarVideoRange @@ -3172,6 +3173,8 @@ av1_vaapi_hwaccel_select="av1_decoder" av1_vdpau_hwaccel_deps="vdpau VdpPictureInfoAV1" av1_vdpau_hwaccel_select="av1_decoder" +av1_videotoolbox_hwaccel_deps="videotoolbox" +av1_videotoolbox_hwaccel_select="av1_decoder" av1_vulkan_hwaccel_deps="vulkan" av1_vulkan_hwaccel_select="av1_decoder" h263_vaapi_hwaccel_deps="vaapi" @@ -3342,6 +3345,7 @@ av1_mediacodec_decoder_deps="mediacodec" av1_mediacodec_encoder_deps="mediacodec" av1_mediacodec_encoder_select="extract_extradata_bsf" +av1_mf_encoder_deps="mediafoundation" av1_nvenc_encoder_deps="nvenc NV_ENC_PIC_PARAMS_AV1" av1_nvenc_encoder_select="atsc_a53" av1_qsv_decoder_select="qsvdec" @@ -6724,6 +6728,7 @@ check_func_headers CoreMedia/CMFormatDescription.h kCMVideoCodecType_HEVC "-framework CoreMedia" check_func_headers CoreMedia/CMFormatDescription.h kCMVideoCodecType_HEVCWithAlpha "-framework CoreMedia" check_func_headers CoreMedia/CMFormatDescription.h kCMVideoCodecType_VP9 "-framework CoreMedia" + check_func_headers CoreMedia/CMFormatDescription.h kCMVideoCodecType_AV1 "-framework CoreMedia" check_func_headers CoreVideo/CVPixelBuffer.h kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange "-framework CoreVideo" check_func_headers CoreVideo/CVPixelBuffer.h kCVPixelFormatType_422YpCbCr8BiPlanarVideoRange "-framework CoreVideo" check_func_headers CoreVideo/CVPixelBuffer.h kCVPixelFormatType_422YpCbCr10BiPlanarVideoRange "-framework CoreVideo" diff -Naur ffmpeg-7.1.2.old/libavcodec/ac3dec.c ffmpeg-7.1.2/libavcodec/ac3dec.c --- ffmpeg-7.1.2.old/libavcodec/ac3dec.c 2025-10-27 10:07:00.254471036 +0100 +++ ffmpeg-7.1.2/libavcodec/ac3dec.c 2025-10-27 10:07:02.463169316 +0100 @@ -253,72 +253,6 @@ } /** - * Parse the 'sync info' and 'bit stream info' from the AC-3 bitstream. - * GetBitContext within AC3DecodeContext must point to - * the start of the synchronized AC-3 bitstream. - */ -static int ac3_parse_header(AC3DecodeContext *s) -{ - GetBitContext *gbc = &s->gbc; - int i; - - /* read the rest of the bsi. read twice for dual mono mode. */ - i = !s->channel_mode; - do { - s->dialog_normalization[(!s->channel_mode)-i] = -get_bits(gbc, 5); - if (s->dialog_normalization[(!s->channel_mode)-i] == 0) { - s->dialog_normalization[(!s->channel_mode)-i] = -31; - } - if (s->target_level != 0) { - s->level_gain[(!s->channel_mode)-i] = powf(2.0f, - (float)(s->target_level - - s->dialog_normalization[(!s->channel_mode)-i])/6.0f); - } - if (s->compression_exists[(!s->channel_mode)-i] = get_bits1(gbc)) { - s->heavy_dynamic_range[(!s->channel_mode)-i] = - AC3_HEAVY_RANGE(get_bits(gbc, 8)); - } - if (get_bits1(gbc)) - skip_bits(gbc, 8); //skip language code - if (get_bits1(gbc)) - skip_bits(gbc, 7); //skip audio production information - } while (i--); - - skip_bits(gbc, 2); //skip copyright bit and original bitstream bit - - /* skip the timecodes or parse the Alternate Bit Stream Syntax */ - if (s->bitstream_id != 6) { - if (get_bits1(gbc)) - skip_bits(gbc, 14); //skip timecode1 - if (get_bits1(gbc)) - skip_bits(gbc, 14); //skip timecode2 - } else { - if (get_bits1(gbc)) { - s->preferred_downmix = get_bits(gbc, 2); - s->center_mix_level_ltrt = get_bits(gbc, 3); - s->surround_mix_level_ltrt = av_clip(get_bits(gbc, 3), 3, 7); - s->center_mix_level = get_bits(gbc, 3); - s->surround_mix_level = av_clip(get_bits(gbc, 3), 3, 7); - } - if (get_bits1(gbc)) { - s->dolby_surround_ex_mode = get_bits(gbc, 2); - s->dolby_headphone_mode = get_bits(gbc, 2); - skip_bits(gbc, 10); // skip adconvtyp (1), xbsi2 (8), encinfo (1) - } - } - - /* skip additional bitstream info */ - if (get_bits1(gbc)) { - i = get_bits(gbc, 6); - do { - skip_bits(gbc, 8); - } while (i--); - } - - return 0; -} - -/** * Common function to parse AC-3 or E-AC-3 frame header */ static int parse_frame_header(AC3DecodeContext *s) @@ -375,10 +309,25 @@ s->dba_syntax = 1; s->skip_syntax = 1; memset(s->channel_uses_aht, 0, sizeof(s->channel_uses_aht)); - return ac3_parse_header(s); + /* volume control params */ + for (int i = 0; i < (s->channel_mode ? 1 : 2); i++) { + s->dialog_normalization[i] = hdr.dialog_normalization[i]; + if (s->dialog_normalization[i] == 0) { + s->dialog_normalization[i] = -31; + } + if (s->target_level != 0) { + s->level_gain[i] = powf(2.0f, + (float)(s->target_level - s->dialog_normalization[i])/6.0f); + } + s->compression_exists[i] = hdr.compression_exists[i]; + if (s->compression_exists[i]) { + s->heavy_dynamic_range[i] = AC3_HEAVY_RANGE(hdr.heavy_dynamic_range[i]); + } + } + return 0; } else if (CONFIG_EAC3_DECODER) { s->eac3 = 1; - return ff_eac3_parse_header(s); + return ff_eac3_parse_header(s, &hdr); } else { av_log(s->avctx, AV_LOG_ERROR, "E-AC-3 support not compiled in\n"); return AVERROR(ENOSYS); @@ -1562,6 +1511,9 @@ av_log(avctx, AV_LOG_ERROR, "invalid frame type\n"); } break; + case AC3_PARSE_ERROR_CHANNEL_MAP: + av_log(avctx, AV_LOG_ERROR, "invalid channel map\n"); + return AVERROR_INVALIDDATA; case AC3_PARSE_ERROR_CRC: break; default: // Normal AVERROR do not try to recover. diff -Naur ffmpeg-7.1.2.old/libavcodec/ac3dec.h ffmpeg-7.1.2/libavcodec/ac3dec.h --- ffmpeg-7.1.2.old/libavcodec/ac3dec.h 2025-10-27 10:07:00.587473974 +0100 +++ ffmpeg-7.1.2/libavcodec/ac3dec.h 2025-10-27 10:07:02.463287298 +0100 @@ -255,11 +255,12 @@ AVChannelLayout downmix_layout; } AC3DecodeContext; +struct AC3HeaderInfo; /** * Parse the E-AC-3 frame header. * This parses both the bit stream info and audio frame header. */ -static int ff_eac3_parse_header(AC3DecodeContext *s); +static int ff_eac3_parse_header(AC3DecodeContext *s, const struct AC3HeaderInfo *hdr); /** * Decode mantissas in a single channel for the entire frame. diff -Naur ffmpeg-7.1.2.old/libavcodec/ac3defs.h ffmpeg-7.1.2/libavcodec/ac3defs.h --- ffmpeg-7.1.2.old/libavcodec/ac3defs.h 2025-10-27 10:07:00.586473965 +0100 +++ ffmpeg-7.1.2/libavcodec/ac3defs.h 2025-10-27 10:07:02.463340118 +0100 @@ -34,6 +34,8 @@ #define AC3_CRITICAL_BANDS 50 #define AC3_MAX_CPL_BANDS 18 +#define EAC3_SR_CODE_REDUCED 3 + /* exponent encoding strategy */ #define EXP_REUSE 0 #define EXP_NEW 1 diff -Naur ffmpeg-7.1.2.old/libavcodec/ac3_parser.c ffmpeg-7.1.2/libavcodec/ac3_parser.c --- ffmpeg-7.1.2.old/libavcodec/ac3_parser.c 2025-10-27 10:07:00.290471354 +0100 +++ ffmpeg-7.1.2/libavcodec/ac3_parser.c 2025-10-27 10:07:02.462962336 +0100 @@ -73,6 +73,217 @@ return i; } +/** + * Parse the 'sync info' and 'bit stream info' from the AC-3 bitstream. + * GetBitContext within AC3DecodeContext must point to + * the start of the synchronized AC-3 bitstream. + */ +static int ac3_parse_header(GetBitContext *gbc, AC3HeaderInfo *hdr) +{ + /* read the rest of the bsi. read twice for dual mono mode. */ + for (int i = 0; i < (hdr->channel_mode ? 1 : 2); i++) { + hdr->dialog_normalization[i] = -get_bits(gbc, 5); + hdr->compression_exists[i] = get_bits1(gbc); + if (hdr->compression_exists[i]) + hdr->heavy_dynamic_range[i] = get_bits(gbc, 8); + if (get_bits1(gbc)) + skip_bits(gbc, 8); //skip language code + if (get_bits1(gbc)) + skip_bits(gbc, 7); //skip audio production information + } + + skip_bits(gbc, 2); //skip copyright bit and original bitstream bit + + /* skip the timecodes or parse the Alternate Bit Stream Syntax */ + if (hdr->bitstream_id != 6) { + if (get_bits1(gbc)) + skip_bits(gbc, 14); //skip timecode1 + if (get_bits1(gbc)) + skip_bits(gbc, 14); //skip timecode2 + } else { + if (get_bits1(gbc)) { + hdr->preferred_downmix = get_bits(gbc, 2); + hdr->center_mix_level_ltrt = get_bits(gbc, 3); + hdr->surround_mix_level_ltrt = av_clip(get_bits(gbc, 3), 3, 7); + hdr->center_mix_level = get_bits(gbc, 3); + hdr->surround_mix_level = av_clip(get_bits(gbc, 3), 3, 7); + } + if (get_bits1(gbc)) { + hdr->dolby_surround_ex_mode = get_bits(gbc, 2); + hdr->dolby_headphone_mode = get_bits(gbc, 2); + skip_bits(gbc, 10); // skip adconvtyp (1), xbsi2 (8), encinfo (1) + } + } + + /* skip additional bitstream info */ + if (get_bits1(gbc)) { + int i = get_bits(gbc, 6); + do { + skip_bits(gbc, 8); + } while (i--); + } + + return 0; +} + +static int eac3_parse_header(GetBitContext *gbc, AC3HeaderInfo *hdr) +{ + if (hdr->frame_type == EAC3_FRAME_TYPE_RESERVED) + return AC3_PARSE_ERROR_FRAME_TYPE; + if (hdr->substreamid) + return AC3_PARSE_ERROR_FRAME_TYPE; + + skip_bits(gbc, 5); // skip bitstream id + + /* volume control params */ + for (int i = 0; i < (hdr->channel_mode ? 1 : 2); i++) { + hdr->dialog_normalization[i] = -get_bits(gbc, 5); + hdr->compression_exists[i] = get_bits1(gbc); + if (hdr->compression_exists[i]) + hdr->heavy_dynamic_range[i] = get_bits(gbc, 8); + } + + /* dependent stream channel map */ + if (hdr->frame_type == EAC3_FRAME_TYPE_DEPENDENT) { + hdr->channel_map_present = get_bits1(gbc); + if (hdr->channel_map_present) { + int64_t channel_layout = 0; + int channel_map = get_bits(gbc, 16); + + for (int i = 0; i < 16; i++) + if (channel_map & (1 << (EAC3_MAX_CHANNELS - i - 1))) + channel_layout |= ff_eac3_custom_channel_map_locations[i][1]; + + if (av_popcount64(channel_layout) > EAC3_MAX_CHANNELS) { + return AC3_PARSE_ERROR_CHANNEL_MAP; + } + hdr->channel_map = channel_map; + } + } + + /* mixing metadata */ + if (get_bits1(gbc)) { + /* center and surround mix levels */ + if (hdr->channel_mode > AC3_CHMODE_STEREO) { + hdr->preferred_downmix = get_bits(gbc, 2); + if (hdr->channel_mode & 1) { + /* if three front channels exist */ + hdr->center_mix_level_ltrt = get_bits(gbc, 3); + hdr->center_mix_level = get_bits(gbc, 3); + } + if (hdr->channel_mode & 4) { + /* if a surround channel exists */ + hdr->surround_mix_level_ltrt = av_clip(get_bits(gbc, 3), 3, 7); + hdr->surround_mix_level = av_clip(get_bits(gbc, 3), 3, 7); + } + } + + /* lfe mix level */ + if (hdr->lfe_on && (hdr->lfe_mix_level_exists = get_bits1(gbc))) { + hdr->lfe_mix_level = get_bits(gbc, 5); + } + + /* info for mixing with other streams and substreams */ + if (hdr->frame_type == EAC3_FRAME_TYPE_INDEPENDENT) { + for (int i = 0; i < (hdr->channel_mode ? 1 : 2); i++) { + // TODO: apply program scale factor + if (get_bits1(gbc)) { + skip_bits(gbc, 6); // skip program scale factor + } + } + if (get_bits1(gbc)) { + skip_bits(gbc, 6); // skip external program scale factor + } + /* skip mixing parameter data */ + switch(get_bits(gbc, 2)) { + case 1: skip_bits(gbc, 5); break; + case 2: skip_bits(gbc, 12); break; + case 3: { + int mix_data_size = (get_bits(gbc, 5) + 2) << 3; + skip_bits_long(gbc, mix_data_size); + break; + } + } + /* skip pan information for mono or dual mono source */ + if (hdr->channel_mode < AC3_CHMODE_STEREO) { + for (int i = 0; i < (hdr->channel_mode ? 1 : 2); i++) { + if (get_bits1(gbc)) { + /* note: this is not in the ATSC A/52B specification + reference: ETSI TS 102 366 V1.1.1 + section: E.1.3.1.25 */ + skip_bits(gbc, 8); // skip pan mean direction index + skip_bits(gbc, 6); // skip reserved paninfo bits + } + } + } + /* skip mixing configuration information */ + if (get_bits1(gbc)) { + for (int i = 0; i < hdr->num_blocks; i++) { + if (hdr->num_blocks == 1 || get_bits1(gbc)) { + skip_bits(gbc, 5); + } + } + } + } + } + + /* informational metadata */ + if (get_bits1(gbc)) { + hdr->bitstream_mode = get_bits(gbc, 3); + skip_bits(gbc, 2); // skip copyright bit and original bitstream bit + if (hdr->channel_mode == AC3_CHMODE_STEREO) { + hdr->dolby_surround_mode = get_bits(gbc, 2); + hdr->dolby_headphone_mode = get_bits(gbc, 2); + } + if (hdr->channel_mode >= AC3_CHMODE_2F2R) { + hdr->dolby_surround_ex_mode = get_bits(gbc, 2); + } + for (int i = 0; i < (hdr->channel_mode ? 1 : 2); i++) { + if (get_bits1(gbc)) { + skip_bits(gbc, 8); // skip mix level, room type, and A/D converter type + } + } + if (hdr->sr_code != EAC3_SR_CODE_REDUCED) { + skip_bits1(gbc); // skip source sample rate code + } + } + + /* converter synchronization flag + If frames are less than six blocks, this bit should be turned on + once every 6 blocks to indicate the start of a frame set. + reference: RFC 4598, Section 2.1.3 Frame Sets */ + if (hdr->frame_type == EAC3_FRAME_TYPE_INDEPENDENT && hdr->num_blocks != 6) { + skip_bits1(gbc); // skip converter synchronization flag + } + + /* original frame size code if this stream was converted from AC-3 */ + if (hdr->frame_type == EAC3_FRAME_TYPE_AC3_CONVERT && + (hdr->num_blocks == 6 || get_bits1(gbc))) { + skip_bits(gbc, 6); // skip frame size code + } + + /* additional bitstream info */ + if (get_bits1(gbc)) { + int addbsil = get_bits(gbc, 6); + for (int i = 0; i < addbsil + 1; i++) { + if (i == 0) { + /* In this 8 bit chunk, the LSB is equal to flag_ec3_extension_type_a + which can be used to detect Atmos presence */ + skip_bits(gbc, 7); + hdr->eac3_extension_type_a = get_bits1(gbc); + if (hdr->eac3_extension_type_a) { + hdr->complexity_index_type_a = get_bits(gbc, 8); + i++; + } + } else { + skip_bits(gbc, 8); // skip additional bit stream info + } + } + } + + return 0; +} + int ff_ac3_parse_header(GetBitContext *gbc, AC3HeaderInfo *hdr) { int frame_size_code; @@ -133,6 +344,10 @@ hdr->frame_size = ff_ac3_frame_size_tab[frame_size_code][hdr->sr_code] * 2; hdr->frame_type = EAC3_FRAME_TYPE_AC3_CONVERT; //EAC3_FRAME_TYPE_INDEPENDENT; hdr->substreamid = 0; + + int ret = ac3_parse_header(gbc, hdr); + if (ret < 0) + return ret; } else { /* Enhanced AC-3 */ hdr->crc1 = 0; @@ -165,6 +380,10 @@ hdr->bit_rate = 8LL * hdr->frame_size * hdr->sample_rate / (hdr->num_blocks * 256); hdr->channels = ff_ac3_channels_tab[hdr->channel_mode] + hdr->lfe_on; + + int ret = eac3_parse_header(gbc, hdr); + if (ret < 0) + return ret; } hdr->channel_layout = ff_ac3_channel_layout_tab[hdr->channel_mode]; if (hdr->lfe_on) @@ -202,9 +421,13 @@ { GetBitContext gb; AC3HeaderInfo hdr; + uint8_t tmp[32 + AV_INPUT_BUFFER_PADDING_SIZE]; int err; - err = init_get_bits8(&gb, buf, size); + size = FFMIN(32, size); + memcpy(tmp, buf, size); + memset(tmp + size, 0, AV_INPUT_BUFFER_PADDING_SIZE); + err = init_get_bits8(&gb, tmp, size); if (err < 0) return AVERROR_INVALIDDATA; err = ff_ac3_parse_header(&gb, &hdr); diff -Naur ffmpeg-7.1.2.old/libavcodec/ac3_parser_internal.h ffmpeg-7.1.2/libavcodec/ac3_parser_internal.h --- ffmpeg-7.1.2.old/libavcodec/ac3_parser_internal.h 2025-10-27 10:07:00.519473374 +0100 +++ ffmpeg-7.1.2/libavcodec/ac3_parser_internal.h 2025-10-27 10:07:02.463059088 +0100 @@ -46,6 +46,7 @@ int substreamid; ///< substream identification int center_mix_level; ///< Center mix level index int surround_mix_level; ///< Surround mix level index + uint8_t channel_map_present; uint16_t channel_map; int num_blocks; ///< number of audio blocks int dolby_surround_mode; @@ -62,6 +63,23 @@ uint64_t channel_layout; int8_t ac3_bit_rate_code; /** @} */ + + /** @name enhanced eac3 extension coded elements + * @{ + */ + int8_t dialog_normalization[2]; + uint8_t compression_exists[2]; + uint8_t heavy_dynamic_range[2]; + uint8_t center_mix_level_ltrt; ///< Center mix level index + uint8_t surround_mix_level_ltrt; ///< Surround mix level index + uint8_t dolby_headphone_mode; + uint8_t dolby_surround_ex_mode; + uint8_t lfe_mix_level_exists; + uint8_t lfe_mix_level; + uint8_t preferred_downmix; + uint8_t eac3_extension_type_a; + uint8_t complexity_index_type_a; + /** @} */ } AC3HeaderInfo; typedef enum { @@ -71,6 +89,7 @@ AC3_PARSE_ERROR_FRAME_SIZE = -0x4030c0a, AC3_PARSE_ERROR_FRAME_TYPE = -0x5030c0a, AC3_PARSE_ERROR_CRC = -0x6030c0a, + AC3_PARSE_ERROR_CHANNEL_MAP = -0x7030c0a, } AC3ParseError; /** diff -Naur ffmpeg-7.1.2.old/libavcodec/allcodecs.c ffmpeg-7.1.2/libavcodec/allcodecs.c --- ffmpeg-7.1.2.old/libavcodec/allcodecs.c 2025-10-27 10:07:01.864539025 +0100 +++ ffmpeg-7.1.2/libavcodec/allcodecs.c 2025-10-27 10:07:02.458796899 +0100 @@ -839,6 +839,7 @@ extern const FFCodec ff_av1_qsv_decoder; extern const FFCodec ff_av1_qsv_encoder; extern const FFCodec ff_av1_amf_encoder; +extern const FFCodec ff_av1_mf_encoder; extern const FFCodec ff_av1_vaapi_encoder; extern const FFCodec ff_libopenh264_encoder; extern const FFCodec ff_libopenh264_decoder; diff -Naur ffmpeg-7.1.2.old/libavcodec/amfenc_av1.c ffmpeg-7.1.2/libavcodec/amfenc_av1.c --- ffmpeg-7.1.2.old/libavcodec/amfenc_av1.c 2025-10-27 10:07:00.536473524 +0100 +++ ffmpeg-7.1.2/libavcodec/amfenc_av1.c 2025-10-27 10:07:02.440481173 +0100 @@ -116,6 +116,7 @@ { "none", "no adaptive quantization", 0, AV_OPT_TYPE_CONST, {.i64 = AMF_VIDEO_ENCODER_AV1_AQ_MODE_NONE }, 0, 0, VE, .unit = "adaptive_quantisation_mode" }, { "caq", "context adaptive quantization", 0, AV_OPT_TYPE_CONST, {.i64 = AMF_VIDEO_ENCODER_AV1_AQ_MODE_CAQ }, 0, 0, VE, .unit = "adaptive_quantisation_mode" }, + { "forced_idr", "Force I frames to be IDR frames", OFFSET(forced_idr), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, VE }, { "align", "alignment mode", OFFSET(align), AV_OPT_TYPE_INT, {.i64 = AMF_VIDEO_ENCODER_AV1_ALIGNMENT_MODE_NO_RESTRICTIONS }, AMF_VIDEO_ENCODER_AV1_ALIGNMENT_MODE_64X16_ONLY, AMF_VIDEO_ENCODER_AV1_ALIGNMENT_MODE_NO_RESTRICTIONS, VE, .unit = "align" }, { "64x16", "", 0, AV_OPT_TYPE_CONST, {.i64 = AMF_VIDEO_ENCODER_AV1_ALIGNMENT_MODE_64X16_ONLY }, 0, 0, VE, .unit = "align" }, @@ -186,6 +187,8 @@ AMFRate framerate; AMFSize framesize = AMFConstructSize(avctx->width, avctx->height); amf_int64 color_depth; + amf_int64 color_primaries; + amf_int64 transfer_characteristic; amf_int64 color_profile; enum AVPixelFormat pix_fmt; @@ -238,7 +241,11 @@ } /// Color profile + color_primaries = ff_amf_get_color_primaries(avctx); + transfer_characteristic = ff_amf_get_transfer_characteristic(avctx); color_profile = ff_amf_get_color_profile(avctx); + AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_AV1_OUTPUT_COLOR_PRIMARIES, color_primaries); + AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_AV1_OUTPUT_TRANSFER_CHARACTERISTIC, transfer_characteristic); AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_AV1_OUTPUT_COLOR_PROFILE, color_profile); /// Color Depth @@ -250,16 +257,6 @@ } AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_AV1_COLOR_BIT_DEPTH, color_depth); - AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_AV1_OUTPUT_COLOR_PROFILE, color_profile); - if (color_depth == AMF_COLOR_BIT_DEPTH_8) { - /// Color Transfer Characteristics (AMF matches ISO/IEC) - AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_AV1_OUTPUT_TRANSFER_CHARACTERISTIC, AMF_COLOR_TRANSFER_CHARACTERISTIC_BT709); - /// Color Primaries (AMF matches ISO/IEC) - AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_AV1_OUTPUT_COLOR_PRIMARIES, AMF_COLOR_PRIMARIES_BT709); - } else { - AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_AV1_OUTPUT_TRANSFER_CHARACTERISTIC, AMF_COLOR_TRANSFER_CHARACTERISTIC_SMPTE2084); - AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_AV1_OUTPUT_COLOR_PRIMARIES, AMF_COLOR_PRIMARIES_BT2020); - } profile_level = avctx->level; if (profile_level == AV_LEVEL_UNKNOWN) { diff -Naur ffmpeg-7.1.2.old/libavcodec/amfenc.c ffmpeg-7.1.2/libavcodec/amfenc.c --- ffmpeg-7.1.2.old/libavcodec/amfenc.c 2025-10-27 10:07:00.387472209 +0100 +++ ffmpeg-7.1.2/libavcodec/amfenc.c 2025-10-27 10:07:02.445244447 +0100 @@ -415,10 +415,6 @@ else pix_fmt = avctx->pix_fmt; - if (pix_fmt == AV_PIX_FMT_P010) { - AMF_RETURN_IF_FALSE(ctx, ctx->version >= AMF_MAKE_FULL_VERSION(1, 4, 32, 0), AVERROR_UNKNOWN, "10-bit encoder is not supported by AMD GPU drivers versions lower than 23.30.\n"); - } - ctx->format = amf_av_to_amf_format(pix_fmt); AMF_RETURN_IF_FALSE(ctx, ctx->format != AMF_SURFACE_UNKNOWN, AVERROR(EINVAL), "Format %s is not supported\n", av_get_pix_fmt_name(pix_fmt)); @@ -766,11 +762,50 @@ switch (avctx->codec->id) { case AV_CODEC_ID_H264: AMF_ASSIGN_PROPERTY_INT64(res, surface, AMF_VIDEO_ENCODER_INSERT_AUD, !!ctx->aud); + switch (frame->pict_type) { + case AV_PICTURE_TYPE_I: + if (ctx->forced_idr) { + AMF_ASSIGN_PROPERTY_INT64(res, surface, AMF_VIDEO_ENCODER_INSERT_SPS, 1); + AMF_ASSIGN_PROPERTY_INT64(res, surface, AMF_VIDEO_ENCODER_INSERT_PPS, 1); + AMF_ASSIGN_PROPERTY_INT64(res, surface, AMF_VIDEO_ENCODER_FORCE_PICTURE_TYPE, AMF_VIDEO_ENCODER_PICTURE_TYPE_IDR); + } else { + AMF_ASSIGN_PROPERTY_INT64(res, surface, AMF_VIDEO_ENCODER_FORCE_PICTURE_TYPE, AMF_VIDEO_ENCODER_PICTURE_TYPE_I); + } + break; + case AV_PICTURE_TYPE_P: + AMF_ASSIGN_PROPERTY_INT64(res, surface, AMF_VIDEO_ENCODER_FORCE_PICTURE_TYPE, AMF_VIDEO_ENCODER_PICTURE_TYPE_P); + break; + case AV_PICTURE_TYPE_B: + AMF_ASSIGN_PROPERTY_INT64(res, surface, AMF_VIDEO_ENCODER_FORCE_PICTURE_TYPE, AMF_VIDEO_ENCODER_PICTURE_TYPE_B); + break; + } break; case AV_CODEC_ID_HEVC: AMF_ASSIGN_PROPERTY_INT64(res, surface, AMF_VIDEO_ENCODER_HEVC_INSERT_AUD, !!ctx->aud); + switch (frame->pict_type) { + case AV_PICTURE_TYPE_I: + if (ctx->forced_idr) { + AMF_ASSIGN_PROPERTY_INT64(res, surface, AMF_VIDEO_ENCODER_HEVC_INSERT_HEADER, 1); + AMF_ASSIGN_PROPERTY_INT64(res, surface, AMF_VIDEO_ENCODER_HEVC_FORCE_PICTURE_TYPE, AMF_VIDEO_ENCODER_HEVC_PICTURE_TYPE_IDR); + } else { + AMF_ASSIGN_PROPERTY_INT64(res, surface, AMF_VIDEO_ENCODER_HEVC_FORCE_PICTURE_TYPE, AMF_VIDEO_ENCODER_HEVC_PICTURE_TYPE_I); + } + break; + case AV_PICTURE_TYPE_P: + AMF_ASSIGN_PROPERTY_INT64(res, surface, AMF_VIDEO_ENCODER_HEVC_FORCE_PICTURE_TYPE, AMF_VIDEO_ENCODER_HEVC_PICTURE_TYPE_P); + break; + } + break; + case AV_CODEC_ID_AV1: + if (frame->pict_type == AV_PICTURE_TYPE_I) { + if (ctx->forced_idr) { + AMF_ASSIGN_PROPERTY_INT64(res, surface, AMF_VIDEO_ENCODER_AV1_FORCE_INSERT_SEQUENCE_HEADER, 1); + AMF_ASSIGN_PROPERTY_INT64(res, surface, AMF_VIDEO_ENCODER_AV1_FORCE_FRAME_TYPE, AMF_VIDEO_ENCODER_AV1_FORCE_FRAME_TYPE_KEY); + } else { + AMF_ASSIGN_PROPERTY_INT64(res, surface, AMF_VIDEO_ENCODER_AV1_FORCE_FRAME_TYPE, AMF_VIDEO_ENCODER_AV1_FORCE_FRAME_TYPE_INTRA_ONLY); + } + } break; - //case AV_CODEC_ID_AV1 not supported default: break; } @@ -878,6 +913,115 @@ return ret; } +int ff_amf_get_color_primaries(AVCodecContext *avctx) +{ + amf_int64 color_primaries = AMF_COLOR_PRIMARIES_UNDEFINED; + switch (avctx->color_primaries) { + case AVCOL_PRI_BT709: + color_primaries = AMF_COLOR_PRIMARIES_BT709; + break; + case AVCOL_PRI_UNSPECIFIED: + color_primaries = AMF_COLOR_PRIMARIES_UNSPECIFIED; + break; + case AVCOL_PRI_RESERVED: + color_primaries = AMF_COLOR_PRIMARIES_RESERVED; + break; + case AVCOL_PRI_BT470M: + color_primaries = AMF_COLOR_PRIMARIES_BT470M; + break; + case AVCOL_PRI_BT470BG: + color_primaries = AMF_COLOR_PRIMARIES_BT470BG; + break; + case AVCOL_PRI_SMPTE170M: + color_primaries = AMF_COLOR_PRIMARIES_SMPTE170M; + break; + case AVCOL_PRI_SMPTE240M: + color_primaries = AMF_COLOR_PRIMARIES_SMPTE240M; + break; + case AVCOL_PRI_FILM: + color_primaries = AMF_COLOR_PRIMARIES_FILM; + break; + case AVCOL_PRI_BT2020: + color_primaries = AMF_COLOR_PRIMARIES_BT2020; + break; + case AVCOL_PRI_SMPTE428: + color_primaries = AMF_COLOR_PRIMARIES_SMPTE428; + break; + case AVCOL_PRI_SMPTE431: + color_primaries = AMF_COLOR_PRIMARIES_SMPTE431; + break; + case AVCOL_PRI_SMPTE432: + color_primaries = AMF_COLOR_PRIMARIES_SMPTE432; + break; + case AVCOL_PRI_EBU3213: + color_primaries = AMF_COLOR_PRIMARIES_JEDEC_P22; + break; + } + return color_primaries; +} + +int ff_amf_get_transfer_characteristic(AVCodecContext *avctx) +{ + amf_int64 transfer_characteristic = AMF_COLOR_TRANSFER_CHARACTERISTIC_UNDEFINED; + switch (avctx->color_trc) { + case AVCOL_TRC_BT709: + transfer_characteristic = AMF_COLOR_TRANSFER_CHARACTERISTIC_BT709; + break; + case AVCOL_TRC_UNSPECIFIED: + transfer_characteristic = AMF_COLOR_TRANSFER_CHARACTERISTIC_UNSPECIFIED; + break; + case AVCOL_TRC_RESERVED: + transfer_characteristic = AMF_COLOR_TRANSFER_CHARACTERISTIC_RESERVED; + break; + case AVCOL_TRC_GAMMA22: + transfer_characteristic = AMF_COLOR_TRANSFER_CHARACTERISTIC_GAMMA22; + break; + case AVCOL_TRC_GAMMA28: + transfer_characteristic = AMF_COLOR_TRANSFER_CHARACTERISTIC_GAMMA28; + break; + case AVCOL_TRC_SMPTE170M: + transfer_characteristic = AMF_COLOR_TRANSFER_CHARACTERISTIC_SMPTE170M; + break; + case AVCOL_TRC_SMPTE240M: + transfer_characteristic = AMF_COLOR_TRANSFER_CHARACTERISTIC_SMPTE240M; + break; + case AVCOL_TRC_LINEAR: + transfer_characteristic = AMF_COLOR_TRANSFER_CHARACTERISTIC_LINEAR; + break; + case AVCOL_TRC_LOG: + transfer_characteristic = AMF_COLOR_TRANSFER_CHARACTERISTIC_LOG; + break; + case AVCOL_TRC_LOG_SQRT: + transfer_characteristic = AMF_COLOR_TRANSFER_CHARACTERISTIC_LOG_SQRT; + break; + case AVCOL_TRC_IEC61966_2_4: + transfer_characteristic = AMF_COLOR_TRANSFER_CHARACTERISTIC_IEC61966_2_4; + break; + case AVCOL_TRC_BT1361_ECG: + transfer_characteristic = AMF_COLOR_TRANSFER_CHARACTERISTIC_BT1361_ECG; + break; + case AVCOL_TRC_IEC61966_2_1: + transfer_characteristic = AMF_COLOR_TRANSFER_CHARACTERISTIC_IEC61966_2_1; + break; + case AVCOL_TRC_BT2020_10: + transfer_characteristic = AMF_COLOR_TRANSFER_CHARACTERISTIC_BT2020_10; + break; + case AVCOL_TRC_BT2020_12: + transfer_characteristic = AMF_COLOR_TRANSFER_CHARACTERISTIC_BT2020_12; + break; + case AVCOL_TRC_SMPTE2084: + transfer_characteristic = AMF_COLOR_TRANSFER_CHARACTERISTIC_SMPTE2084; + break; + case AVCOL_TRC_SMPTE428: + transfer_characteristic = AMF_COLOR_TRANSFER_CHARACTERISTIC_SMPTE428; + break; + case AVCOL_TRC_ARIB_STD_B67: + transfer_characteristic = AMF_COLOR_TRANSFER_CHARACTERISTIC_ARIB_STD_B67; + break; + } + return transfer_characteristic; +} + int ff_amf_get_color_profile(AVCodecContext *avctx) { amf_int64 color_profile = AMF_VIDEO_CONVERTER_COLOR_PROFILE_UNKNOWN; diff -Naur ffmpeg-7.1.2.old/libavcodec/amfenc.h ffmpeg-7.1.2/libavcodec/amfenc.h --- ffmpeg-7.1.2.old/libavcodec/amfenc.h 2025-10-27 10:07:00.322471636 +0100 +++ ffmpeg-7.1.2/libavcodec/amfenc.h 2025-10-27 10:07:02.440421671 +0100 @@ -114,6 +114,7 @@ int max_b_frames; int qvbr_quality_level; int hw_high_motion_quality_boost; + int forced_idr; // HEVC - specific options @@ -173,6 +174,8 @@ */ extern const enum AVPixelFormat ff_amf_pix_fmts[]; +int ff_amf_get_color_primaries(AVCodecContext *avctx); +int ff_amf_get_transfer_characteristic(AVCodecContext *avctx); int ff_amf_get_color_profile(AVCodecContext *avctx); /** diff -Naur ffmpeg-7.1.2.old/libavcodec/amfenc_h264.c ffmpeg-7.1.2/libavcodec/amfenc_h264.c --- ffmpeg-7.1.2.old/libavcodec/amfenc_h264.c 2025-10-27 10:07:00.604474124 +0100 +++ ffmpeg-7.1.2/libavcodec/amfenc_h264.c 2025-10-27 10:07:02.440571423 +0100 @@ -133,6 +133,7 @@ { "me_half_pel", "Enable ME Half Pixel", OFFSET(me_half_pel), AV_OPT_TYPE_BOOL, { .i64 = -1 }, -1, 1, VE }, { "me_quarter_pel", "Enable ME Quarter Pixel", OFFSET(me_quarter_pel),AV_OPT_TYPE_BOOL, { .i64 = -1 }, -1, 1, VE }, + { "forced_idr", "Force I frames to be IDR frames", OFFSET(forced_idr) , AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, VE }, { "aud", "Inserts AU Delimiter NAL unit", OFFSET(aud) , AV_OPT_TYPE_BOOL, { .i64 = -1 }, -1, 1, VE }, @@ -201,6 +202,8 @@ AMFRate framerate; AMFSize framesize = AMFConstructSize(avctx->width, avctx->height); int deblocking_filter = (avctx->flags & AV_CODEC_FLAG_LOOP_FILTER) ? 1 : 0; + amf_int64 color_primaries; + amf_int64 transfer_characteristic; amf_int64 color_profile; enum AVPixelFormat pix_fmt; @@ -273,7 +276,11 @@ AMF_ASSIGN_PROPERTY_RATIO(res, ctx->encoder, AMF_VIDEO_ENCODER_ASPECT_RATIO, ratio); } + color_primaries = ff_amf_get_color_primaries(avctx); + transfer_characteristic = ff_amf_get_transfer_characteristic(avctx); color_profile = ff_amf_get_color_profile(avctx); + AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_OUTPUT_COLOR_PRIMARIES, color_primaries); + AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_OUTPUT_TRANSFER_CHARACTERISTIC, transfer_characteristic); AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_OUTPUT_COLOR_PROFILE, color_profile); /// Color Range (Support for older Drivers) @@ -287,10 +294,6 @@ AMF_RETURN_IF_FALSE(ctx, pix_fmt != AV_PIX_FMT_P010, AVERROR_INVALIDDATA, "10-bit input video is not supported by AMF H264 encoder\n"); AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_COLOR_BIT_DEPTH, AMF_COLOR_BIT_DEPTH_8); - /// Color Transfer Characteristics (AMF matches ISO/IEC) - AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_OUTPUT_TRANSFER_CHARACTERISTIC, (amf_int64)avctx->color_trc); - /// Color Primaries (AMF matches ISO/IEC) - AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_OUTPUT_COLOR_PRIMARIES, (amf_int64)avctx->color_primaries); // autodetect rate control method if (ctx->rate_control_mode == AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_UNKNOWN) { diff -Naur ffmpeg-7.1.2.old/libavcodec/amfenc_hevc.c ffmpeg-7.1.2/libavcodec/amfenc_hevc.c --- ffmpeg-7.1.2.old/libavcodec/amfenc_hevc.c 2025-10-27 10:07:00.324471654 +0100 +++ ffmpeg-7.1.2/libavcodec/amfenc_hevc.c 2025-10-27 10:07:02.440657756 +0100 @@ -100,6 +100,7 @@ { "me_half_pel", "Enable ME Half Pixel", OFFSET(me_half_pel), AV_OPT_TYPE_BOOL,{ .i64 = -1 }, -1, 1, VE }, { "me_quarter_pel", "Enable ME Quarter Pixel ", OFFSET(me_quarter_pel),AV_OPT_TYPE_BOOL,{ .i64 = -1 }, -1, 1, VE }, + { "forced_idr", "Force I frames to be IDR frames", OFFSET(forced_idr) ,AV_OPT_TYPE_BOOL,{ .i64 = 0 }, 0, 1, VE }, { "aud", "Inserts AU Delimiter NAL unit", OFFSET(aud) ,AV_OPT_TYPE_BOOL,{ .i64 = -1 }, -1, 1, VE }, @@ -167,6 +168,8 @@ AMFSize framesize = AMFConstructSize(avctx->width, avctx->height); int deblocking_filter = (avctx->flags & AV_CODEC_FLAG_LOOP_FILTER) ? 1 : 0; amf_int64 color_depth; + amf_int64 color_primaries; + amf_int64 transfer_characteristic; amf_int64 color_profile; enum AVPixelFormat pix_fmt; @@ -241,7 +244,11 @@ AMF_ASSIGN_PROPERTY_RATIO(res, ctx->encoder, AMF_VIDEO_ENCODER_HEVC_ASPECT_RATIO, ratio); } + color_primaries = ff_amf_get_color_primaries(avctx); + transfer_characteristic = ff_amf_get_transfer_characteristic(avctx); color_profile = ff_amf_get_color_profile(avctx); + AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_HEVC_OUTPUT_COLOR_PRIMARIES, color_primaries); + AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_HEVC_OUTPUT_TRANSFER_CHARACTERISTIC, transfer_characteristic); AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_HEVC_OUTPUT_COLOR_PROFILE, color_profile); /// Color Range (Support for older Drivers) AMF_ASSIGN_PROPERTY_BOOL(res, ctx->encoder, AMF_VIDEO_ENCODER_HEVC_NOMINAL_RANGE, !!(avctx->color_range == AVCOL_RANGE_JPEG)); @@ -253,15 +260,6 @@ color_depth = AMF_COLOR_BIT_DEPTH_10; } AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_HEVC_COLOR_BIT_DEPTH, color_depth); - if (color_depth == AMF_COLOR_BIT_DEPTH_8) { - /// Color Transfer Characteristics (AMF matches ISO/IEC) - AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_HEVC_OUTPUT_TRANSFER_CHARACTERISTIC, AMF_COLOR_TRANSFER_CHARACTERISTIC_BT709); - /// Color Primaries (AMF matches ISO/IEC) - AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_HEVC_OUTPUT_COLOR_PRIMARIES, AMF_COLOR_PRIMARIES_BT709); - } else { - AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_HEVC_OUTPUT_TRANSFER_CHARACTERISTIC, AMF_COLOR_TRANSFER_CHARACTERISTIC_SMPTE2084); - AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_HEVC_OUTPUT_COLOR_PRIMARIES, AMF_COLOR_PRIMARIES_BT2020); - } // Picture control properties AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_HEVC_NUM_GOPS_PER_IDR, ctx->gops_per_idr); diff -Naur ffmpeg-7.1.2.old/libavcodec/av1dec.c ffmpeg-7.1.2/libavcodec/av1dec.c --- ffmpeg-7.1.2.old/libavcodec/av1dec.c 2025-10-27 10:07:00.478473013 +0100 +++ ffmpeg-7.1.2/libavcodec/av1dec.c 2025-10-27 10:07:02.454638605 +0100 @@ -541,6 +541,7 @@ CONFIG_AV1_NVDEC_HWACCEL + \ CONFIG_AV1_VAAPI_HWACCEL + \ CONFIG_AV1_VDPAU_HWACCEL + \ + CONFIG_AV1_VIDEOTOOLBOX_HWACCEL + \ CONFIG_AV1_VULKAN_HWACCEL) enum AVPixelFormat pix_fmts[HWACCEL_MAX + 2], *fmtp = pix_fmts; @@ -568,6 +569,9 @@ #if CONFIG_AV1_VDPAU_HWACCEL *fmtp++ = AV_PIX_FMT_VDPAU; #endif +#if CONFIG_AV1_VIDEOTOOLBOX_HWACCEL + *fmtp++ = AV_PIX_FMT_VIDEOTOOLBOX; +#endif #if CONFIG_AV1_VULKAN_HWACCEL *fmtp++ = AV_PIX_FMT_VULKAN; #endif @@ -592,6 +596,9 @@ #if CONFIG_AV1_VDPAU_HWACCEL *fmtp++ = AV_PIX_FMT_VDPAU; #endif +#if CONFIG_AV1_VIDEOTOOLBOX_HWACCEL + *fmtp++ = AV_PIX_FMT_VIDEOTOOLBOX; +#endif #if CONFIG_AV1_VULKAN_HWACCEL *fmtp++ = AV_PIX_FMT_VULKAN; #endif @@ -1002,6 +1009,8 @@ break; } case ITU_T_T35_PROVIDER_CODE_DOLBY: { + AVBufferRef *rpu_buf; + AVFrameSideData *rpu; int provider_oriented_code = bytestream2_get_be32(&gb); if (itut_t35->itu_t_t35_country_code != ITU_T_T35_COUNTRY_CODE_US || provider_oriented_code != 0x800) @@ -1014,6 +1023,18 @@ break; // ignore } + rpu_buf = av_buffer_alloc(itut_t35->payload_size); + if (rpu_buf) { + memcpy(rpu_buf->data, itut_t35->payload, itut_t35->payload_size); + rpu = av_frame_new_side_data_from_buf(frame, AV_FRAME_DATA_DOVI_RPU_BUFFER_T35, rpu_buf); + if (!rpu) { + av_buffer_unref(&rpu_buf); + return AVERROR(ENOMEM); + } + } else { + return AVERROR(ENOMEM); + } + ret = ff_dovi_attach_side_data(&s->dovi, frame); if (ret < 0) return ret; @@ -1439,6 +1460,10 @@ if (raw_tile_group && (s->tile_num == raw_tile_group->tg_end + 1)) { int show_frame = s->raw_frame_header->show_frame; + // Set nb_unit to point at the next OBU, to indicate which + // OBUs have been processed for this current frame. (If this + // frame gets output, we set nb_unit to this value later too.) + s->nb_unit = i + 1; if (avctx->hwaccel && s->cur_frame.f) { ret = FF_HW_SIMPLE_CALL(avctx, end_frame); if (ret < 0) { @@ -1449,6 +1474,8 @@ update_reference_list(avctx); + // Set start_unit to indicate the first OBU of the next frame. + s->start_unit = s->nb_unit; raw_tile_group = NULL; s->raw_frame_header = NULL; @@ -1478,7 +1505,7 @@ s->raw_frame_header = NULL; av_packet_unref(s->pkt); ff_cbs_fragment_reset(&s->current_obu); - s->nb_unit = 0; + s->nb_unit = s->start_unit = 0; } if (!ret && !frame->buf[0]) ret = AVERROR(EAGAIN); @@ -1505,7 +1532,7 @@ return ret; } - s->nb_unit = 0; + s->nb_unit = s->start_unit = 0; av_log(avctx, AV_LOG_DEBUG, "Total OBUs on this packet: %d.\n", s->current_obu.nb_units); } @@ -1526,7 +1553,7 @@ av1_frame_unref(&s->cur_frame); s->operating_point_idc = 0; - s->nb_unit = 0; + s->nb_unit = s->start_unit = 0; s->raw_frame_header = NULL; s->raw_seq = NULL; s->cll = NULL; @@ -1594,6 +1621,9 @@ #if CONFIG_AV1_VDPAU_HWACCEL HWACCEL_VDPAU(av1), #endif +#if CONFIG_AV1_VIDEOTOOLBOX_HWACCEL + HWACCEL_VIDEOTOOLBOX(av1), +#endif #if CONFIG_AV1_VULKAN_HWACCEL HWACCEL_VULKAN(av1), #endif diff -Naur ffmpeg-7.1.2.old/libavcodec/av1dec.h ffmpeg-7.1.2/libavcodec/av1dec.h --- ffmpeg-7.1.2.old/libavcodec/av1dec.h 2025-10-27 10:07:00.402472342 +0100 +++ ffmpeg-7.1.2/libavcodec/av1dec.h 2025-10-27 10:07:02.454762738 +0100 @@ -114,7 +114,8 @@ AV1Frame ref[AV1_NUM_REF_FRAMES]; AV1Frame cur_frame; - int nb_unit; + int nb_unit; ///< The index of the next OBU to be processed. + int start_unit; ///< The index of the first OBU of the current frame. // AVOptions int operating_point; diff -Naur ffmpeg-7.1.2.old/libavcodec/bsf/hevc_mp4toannexb.c ffmpeg-7.1.2/libavcodec/bsf/hevc_mp4toannexb.c --- ffmpeg-7.1.2.old/libavcodec/bsf/hevc_mp4toannexb.c 2025-10-27 10:07:00.449472757 +0100 +++ ffmpeg-7.1.2/libavcodec/bsf/hevc_mp4toannexb.c 2025-10-27 10:07:02.435022530 +0100 @@ -125,6 +125,7 @@ AVPacket *in; GetByteContext gb; + int has_sps = 0, has_pps = 0; int got_irap = 0; int i, ret = 0; @@ -158,11 +159,14 @@ } nalu_type = (bytestream2_peek_byte(&gb) >> 1) & 0x3f; + has_sps = (has_sps || nalu_type == HEVC_NAL_SPS); + has_pps = (has_pps || nalu_type == HEVC_NAL_PPS); /* prepend extradata to IRAP frames */ is_irap = nalu_type >= HEVC_NAL_BLA_W_LP && nalu_type <= HEVC_NAL_RSV_IRAP_VCL23; - add_extradata = is_irap && !got_irap; + /* ignore the extradata if IRAP frame has sps and pps */ + add_extradata = is_irap && !got_irap && !(has_sps && has_pps); extra_size = add_extradata * ctx->par_out->extradata_size; got_irap |= is_irap; diff -Naur ffmpeg-7.1.2.old/libavcodec/ccaption_dec.c ffmpeg-7.1.2/libavcodec/ccaption_dec.c --- ffmpeg-7.1.2.old/libavcodec/ccaption_dec.c 2025-10-27 10:07:00.593474027 +0100 +++ ffmpeg-7.1.2/libavcodec/ccaption_dec.c 2025-10-27 10:07:02.429490227 +0100 @@ -889,12 +889,13 @@ if (ctx->buffer[bidx].str[0] || ctx->real_time) { ff_dlog(ctx, "cdp writing data (%s)\n", ctx->buffer[bidx].str); - start_time = ctx->buffer_time[0]; - sub->pts = start_time; - end_time = ctx->buffer_time[1]; - if (!ctx->real_time) + if (!ctx->real_time) { + start_time = ctx->buffer_time[0]; + sub->pts = start_time; + end_time = ctx->buffer_time[1]; sub->end_display_time = av_rescale_q(end_time - start_time, AV_TIME_BASE_Q, ms_tb); + } else sub->end_display_time = -1; ret = ff_ass_add_rect2(sub, ctx->buffer[bidx].str, ctx->readorder++, 0, NULL, NULL, &nb_rect_allocated); diff -Naur ffmpeg-7.1.2.old/libavcodec/dvdsubdec.c ffmpeg-7.1.2/libavcodec/dvdsubdec.c --- ffmpeg-7.1.2.old/libavcodec/dvdsubdec.c 2025-10-27 10:07:00.303471468 +0100 +++ ffmpeg-7.1.2/libavcodec/dvdsubdec.c 2025-10-27 10:07:02.428490218 +0100 @@ -45,6 +45,8 @@ int buf_size; int forced_subs_only; uint8_t used_color[256]; + int64_t pts; + int output_empty_rects; } DVDSubContext; static void yuv_a_to_rgba(const uint8_t *ycbcr, const uint8_t *alpha, uint32_t *rgba, int num_values) @@ -230,7 +232,10 @@ uint32_t size; int64_t offset1, offset2; - if (buf_size < 10) + if (buf_size < 2) + return AVERROR(EAGAIN); + + if (buf_size == 2 && AV_RB16(buf) == 0) return -1; if (AV_RB16(buf) == 0) { /* HD subpicture with 4-byte offsets */ @@ -243,15 +248,22 @@ cmd_pos = 2; } + if (big_offsets && buf_size < 6) + return AVERROR(EAGAIN); + size = READ_OFFSET(buf + (big_offsets ? 2 : 0)); - cmd_pos = READ_OFFSET(buf + cmd_pos); - if (cmd_pos < 0 || cmd_pos > buf_size - 2 - offset_size) { - if (cmd_pos > size) { - av_log(ctx, AV_LOG_ERROR, "Discarding invalid packet\n"); - return 0; - } + if (size == 0) + return -1; + + if (buf_size < size) return AVERROR(EAGAIN); + + cmd_pos = READ_OFFSET(buf + cmd_pos); + + if (cmd_pos < 0 || cmd_pos > size) { + av_log(ctx, AV_LOG_ERROR, "Discarding invalid packet\n"); + return AVERROR_INVALIDDATA; } while (cmd_pos > 0 && cmd_pos < buf_size - 2 - offset_size) { @@ -524,10 +536,13 @@ int appended = 0; int is_menu; + if (ctx->pts == AV_NOPTS_VALUE && sub->pts != AV_NOPTS_VALUE) + ctx->pts = sub->pts; if (ctx->buf_size) { int ret = append_to_cached_buf(avctx, buf, buf_size); if (ret < 0) { *data_size = 0; + ctx->pts = AV_NOPTS_VALUE; return ret; } buf = ctx->buf; @@ -538,7 +553,12 @@ is_menu = decode_dvd_subtitles(ctx, sub, buf, buf_size); if (is_menu == AVERROR(EAGAIN)) { *data_size = 0; - return appended ? 0 : append_to_cached_buf(avctx, buf, buf_size); + int ret = appended ? 0 : append_to_cached_buf(avctx, buf, buf_size); + if (ret < 0) { + ctx->pts = AV_NOPTS_VALUE; + return ret; + } + return buf_size; } if (is_menu < 0) { @@ -547,9 +567,10 @@ reset_rects(sub); *data_size = 0; + ctx->pts = AV_NOPTS_VALUE; return buf_size; } - if (!is_menu && find_smallest_bounding_rectangle(ctx, sub) == 0) + if (!is_menu && !ctx->output_empty_rects && find_smallest_bounding_rectangle(ctx, sub) == 0) goto no_subtitle; if (ctx->forced_subs_only && !(sub->rects[0]->flags & AV_SUBTITLE_FLAG_FORCED)) @@ -557,6 +578,8 @@ ctx->buf_size = 0; *data_size = 1; + sub->pts = ctx->pts; + ctx->pts = AV_NOPTS_VALUE; return buf_size; } @@ -682,6 +705,7 @@ av_log(avctx, AV_LOG_DEBUG, " 0x%06"PRIx32, ctx->palette[i]); av_log(avctx, AV_LOG_DEBUG, "\n"); } + ctx->pts = AV_NOPTS_VALUE; return 1; } @@ -698,6 +722,7 @@ { "palette", "set the global palette", OFFSET(palette_str), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, SD }, { "ifo_palette", "obtain the global palette from .IFO file", OFFSET(ifo_str), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, SD }, { "forced_subs_only", "Only show forced subtitles", OFFSET(forced_subs_only), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, SD}, + { "output_empty_rects", "Output subtitles with empty or fully transparent rects", OFFSET(output_empty_rects), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, SD}, { NULL } }; static const AVClass dvdsub_class = { diff -Naur ffmpeg-7.1.2.old/libavcodec/eac3dec.c ffmpeg-7.1.2/libavcodec/eac3dec.c --- ffmpeg-7.1.2.old/libavcodec/eac3dec.c 2025-10-27 10:07:00.490473118 +0100 +++ ffmpeg-7.1.2/libavcodec/eac3dec.c 2025-10-27 10:07:02.463392296 +0100 @@ -53,8 +53,6 @@ EAC3_GAQ_124 } EAC3GaqMode; -#define EAC3_SR_CODE_REDUCED 3 - static void ff_eac3_apply_spectral_extension(AC3DecodeContext *s) { int bin, bnd, ch, i; @@ -287,7 +285,7 @@ } } -static int ff_eac3_parse_header(AC3DecodeContext *s) +static int ff_eac3_parse_header(AC3DecodeContext *s, const AC3HeaderInfo *hdr) { int i, blk, ch; int ac3_exponent_strategy, parse_aht_info, parse_spx_atten_data; @@ -323,11 +321,10 @@ avpriv_request_sample(s->avctx, "Reduced sampling rate"); return AVERROR_PATCHWELCOME; } - skip_bits(gbc, 5); // skip bitstream id /* volume control params */ for (i = 0; i < (s->channel_mode ? 1 : 2); i++) { - s->dialog_normalization[i] = -get_bits(gbc, 5); + s->dialog_normalization[i] = hdr->dialog_normalization[i]; if (s->dialog_normalization[i] == 0) { s->dialog_normalization[i] = -31; } @@ -335,147 +332,30 @@ s->level_gain[i] = powf(2.0f, (float)(s->target_level - s->dialog_normalization[i])/6.0f); } - s->compression_exists[i] = get_bits1(gbc); - if (s->compression_exists[i]) { - s->heavy_dynamic_range[i] = AC3_HEAVY_RANGE(get_bits(gbc, 8)); + if (hdr->compression_exists[i]) { + s->heavy_dynamic_range[i] = AC3_HEAVY_RANGE(hdr->heavy_dynamic_range[i]); } } - /* dependent stream channel map */ - if (s->frame_type == EAC3_FRAME_TYPE_DEPENDENT) { - if (get_bits1(gbc)) { - int64_t channel_layout = 0; - int channel_map = get_bits(gbc, 16); - av_log(s->avctx, AV_LOG_DEBUG, "channel_map: %0X\n", channel_map); - - for (i = 0; i < 16; i++) - if (channel_map & (1 << (EAC3_MAX_CHANNELS - i - 1))) - channel_layout |= ff_eac3_custom_channel_map_locations[i][1]; - - if (av_popcount64(channel_layout) > EAC3_MAX_CHANNELS) { - return AVERROR_INVALIDDATA; - } - s->channel_map = channel_map; - } - } + s->channel_map = hdr->channel_map; /* mixing metadata */ - if (get_bits1(gbc)) { - /* center and surround mix levels */ - if (s->channel_mode > AC3_CHMODE_STEREO) { - s->preferred_downmix = get_bits(gbc, 2); - if (s->channel_mode & 1) { - /* if three front channels exist */ - s->center_mix_level_ltrt = get_bits(gbc, 3); - s->center_mix_level = get_bits(gbc, 3); - } - if (s->channel_mode & 4) { - /* if a surround channel exists */ - s->surround_mix_level_ltrt = av_clip(get_bits(gbc, 3), 3, 7); - s->surround_mix_level = av_clip(get_bits(gbc, 3), 3, 7); - } - } - - /* lfe mix level */ - if (s->lfe_on && (s->lfe_mix_level_exists = get_bits1(gbc))) { - s->lfe_mix_level = get_bits(gbc, 5); - } - - /* info for mixing with other streams and substreams */ - if (s->frame_type == EAC3_FRAME_TYPE_INDEPENDENT) { - for (i = 0; i < (s->channel_mode ? 1 : 2); i++) { - // TODO: apply program scale factor - if (get_bits1(gbc)) { - skip_bits(gbc, 6); // skip program scale factor - } - } - if (get_bits1(gbc)) { - skip_bits(gbc, 6); // skip external program scale factor - } - /* skip mixing parameter data */ - switch(get_bits(gbc, 2)) { - case 1: skip_bits(gbc, 5); break; - case 2: skip_bits(gbc, 12); break; - case 3: { - int mix_data_size = (get_bits(gbc, 5) + 2) << 3; - skip_bits_long(gbc, mix_data_size); - break; - } - } - /* skip pan information for mono or dual mono source */ - if (s->channel_mode < AC3_CHMODE_STEREO) { - for (i = 0; i < (s->channel_mode ? 1 : 2); i++) { - if (get_bits1(gbc)) { - /* note: this is not in the ATSC A/52B specification - reference: ETSI TS 102 366 V1.1.1 - section: E.1.3.1.25 */ - skip_bits(gbc, 8); // skip pan mean direction index - skip_bits(gbc, 6); // skip reserved paninfo bits - } - } - } - /* skip mixing configuration information */ - if (get_bits1(gbc)) { - for (blk = 0; blk < s->num_blocks; blk++) { - if (s->num_blocks == 1 || get_bits1(gbc)) { - skip_bits(gbc, 5); - } - } - } - } - } + s->preferred_downmix = hdr->preferred_downmix; + s->center_mix_level_ltrt = hdr->center_mix_level_ltrt; + s->center_mix_level = hdr->center_mix_level; + s->surround_mix_level_ltrt = hdr->surround_mix_level_ltrt; + s->surround_mix_level = hdr->surround_mix_level; + s->lfe_mix_level_exists = hdr->lfe_mix_level_exists; + s->lfe_mix_level = hdr->lfe_mix_level; + s->dolby_surround_mode = hdr->dolby_surround_mode; + s->dolby_headphone_mode = hdr->dolby_headphone_mode; + s->dolby_surround_ex_mode = hdr->dolby_surround_ex_mode; /* informational metadata */ - if (get_bits1(gbc)) { - s->bitstream_mode = get_bits(gbc, 3); - skip_bits(gbc, 2); // skip copyright bit and original bitstream bit - if (s->channel_mode == AC3_CHMODE_STEREO) { - s->dolby_surround_mode = get_bits(gbc, 2); - s->dolby_headphone_mode = get_bits(gbc, 2); - } - if (s->channel_mode >= AC3_CHMODE_2F2R) { - s->dolby_surround_ex_mode = get_bits(gbc, 2); - } - for (i = 0; i < (s->channel_mode ? 1 : 2); i++) { - if (get_bits1(gbc)) { - skip_bits(gbc, 8); // skip mix level, room type, and A/D converter type - } - } - if (s->bit_alloc_params.sr_code != EAC3_SR_CODE_REDUCED) { - skip_bits1(gbc); // skip source sample rate code - } - } - - /* converter synchronization flag - If frames are less than six blocks, this bit should be turned on - once every 6 blocks to indicate the start of a frame set. - reference: RFC 4598, Section 2.1.3 Frame Sets */ - if (s->frame_type == EAC3_FRAME_TYPE_INDEPENDENT && s->num_blocks != 6) { - skip_bits1(gbc); // skip converter synchronization flag - } - - /* original frame size code if this stream was converted from AC-3 */ - if (s->frame_type == EAC3_FRAME_TYPE_AC3_CONVERT && - (s->num_blocks == 6 || get_bits1(gbc))) { - skip_bits(gbc, 6); // skip frame size code - } + s->bitstream_mode = hdr->bitstream_mode; /* additional bitstream info */ - if (get_bits1(gbc)) { - int addbsil = get_bits(gbc, 6); - for (i = 0; i < addbsil + 1; i++) { - if (i == 0) { - /* In this 8 bit chunk, the LSB is equal to flag_ec3_extension_type_a - which can be used to detect Atmos presence */ - skip_bits(gbc, 7); - if (get_bits1(gbc)) { - s->eac3_extension_type_a = 1; - } - } else { - skip_bits(gbc, 8); // skip additional bit stream info - } - } - } + s->eac3_extension_type_a = hdr->eac3_extension_type_a; /* audio frame syntax flags, strategy data, and per-frame data */ diff -Naur ffmpeg-7.1.2.old/libavcodec/hwaccels.h ffmpeg-7.1.2/libavcodec/hwaccels.h --- ffmpeg-7.1.2.old/libavcodec/hwaccels.h 2025-10-27 10:07:00.251471009 +0100 +++ ffmpeg-7.1.2/libavcodec/hwaccels.h 2025-10-27 10:07:02.454807062 +0100 @@ -26,6 +26,7 @@ extern const struct FFHWAccel ff_av1_nvdec_hwaccel; extern const struct FFHWAccel ff_av1_vaapi_hwaccel; extern const struct FFHWAccel ff_av1_vdpau_hwaccel; +extern const struct FFHWAccel ff_av1_videotoolbox_hwaccel; extern const struct FFHWAccel ff_av1_vulkan_hwaccel; extern const struct FFHWAccel ff_h263_vaapi_hwaccel; extern const struct FFHWAccel ff_h263_videotoolbox_hwaccel; diff -Naur ffmpeg-7.1.2.old/libavcodec/libdav1d.c ffmpeg-7.1.2/libavcodec/libdav1d.c --- ffmpeg-7.1.2.old/libavcodec/libdav1d.c 2025-10-27 10:07:00.474472977 +0100 +++ ffmpeg-7.1.2/libavcodec/libdav1d.c 2025-10-27 10:07:02.436286040 +0100 @@ -563,6 +563,8 @@ break; } case ITU_T_T35_PROVIDER_CODE_DOLBY: { + AVBufferRef *rpu_buf; + AVFrameSideData *rpu; int provider_oriented_code = bytestream2_get_be32(&gb); if (itut_t35->country_code != ITU_T_T35_COUNTRY_CODE_US || provider_oriented_code != 0x800) @@ -575,6 +577,18 @@ break; // ignore } + rpu_buf = av_buffer_alloc(itut_t35->payload_size); + if (rpu_buf) { + memcpy(rpu_buf->data, itut_t35->payload, itut_t35->payload_size); + rpu = av_frame_new_side_data_from_buf(frame, AV_FRAME_DATA_DOVI_RPU_BUFFER_T35, rpu_buf); + if (!rpu) { + av_buffer_unref(&rpu_buf); + goto fail; + } + } else { + goto fail; + } + res = ff_dovi_attach_side_data(&dav1d->dovi, frame); if (res < 0) goto fail; diff -Naur ffmpeg-7.1.2.old/libavcodec/Makefile ffmpeg-7.1.2/libavcodec/Makefile --- ffmpeg-7.1.2.old/libavcodec/Makefile 2025-10-27 10:07:01.865180885 +0100 +++ ffmpeg-7.1.2/libavcodec/Makefile 2025-10-27 10:07:02.454516424 +0100 @@ -1008,6 +1008,7 @@ OBJS-$(CONFIG_AV1_NVDEC_HWACCEL) += nvdec_av1.o OBJS-$(CONFIG_AV1_VAAPI_HWACCEL) += vaapi_av1.o OBJS-$(CONFIG_AV1_VDPAU_HWACCEL) += vdpau_av1.o +OBJS-$(CONFIG_AV1_VIDEOTOOLBOX_HWACCEL) += videotoolbox_av1.o OBJS-$(CONFIG_AV1_VULKAN_HWACCEL) += vulkan_decode.o vulkan_av1.o OBJS-$(CONFIG_H263_VAAPI_HWACCEL) += vaapi_mpeg4.o OBJS-$(CONFIG_H263_VIDEOTOOLBOX_HWACCEL) += videotoolbox.o diff -Naur ffmpeg-7.1.2.old/libavcodec/mfenc.c ffmpeg-7.1.2/libavcodec/mfenc.c --- ffmpeg-7.1.2.old/libavcodec/mfenc.c 2025-10-27 10:07:00.524473418 +0100 +++ ffmpeg-7.1.2/libavcodec/mfenc.c 2025-10-27 10:07:02.459027162 +0100 @@ -1315,3 +1315,4 @@ MF_ENCODER(VIDEO, h264, H264, venc_opts, VFMTS, VCAPS, defaults); MF_ENCODER(VIDEO, hevc, HEVC, venc_opts, VFMTS, VCAPS, defaults); +MF_ENCODER(VIDEO, av1, AV1, venc_opts, VFMTS, VCAPS, defaults); diff -Naur ffmpeg-7.1.2.old/libavcodec/mf_utils.c ffmpeg-7.1.2/libavcodec/mf_utils.c --- ffmpeg-7.1.2.old/libavcodec/mf_utils.c 2025-10-27 10:07:00.467472915 +0100 +++ ffmpeg-7.1.2/libavcodec/mf_utils.c 2025-10-27 10:07:02.458887429 +0100 @@ -240,6 +240,7 @@ GUID_ENTRY(MFMediaType_Video), GUID_ENTRY(MFAudioFormat_PCM), GUID_ENTRY(MFAudioFormat_Float), + GUID_ENTRY(ff_MFVideoFormat_AV1), GUID_ENTRY(MFVideoFormat_H264), GUID_ENTRY(MFVideoFormat_H264_ES), GUID_ENTRY(ff_MFVideoFormat_HEVC), @@ -507,6 +508,7 @@ const CLSID *ff_codec_to_mf_subtype(enum AVCodecID codec) { switch (codec) { + case AV_CODEC_ID_AV1: return &ff_MFVideoFormat_AV1; case AV_CODEC_ID_H264: return &MFVideoFormat_H264; case AV_CODEC_ID_HEVC: return &ff_MFVideoFormat_HEVC; case AV_CODEC_ID_AC3: return &MFAudioFormat_Dolby_AC3; diff -Naur ffmpeg-7.1.2.old/libavcodec/mf_utils.h ffmpeg-7.1.2/libavcodec/mf_utils.h --- ffmpeg-7.1.2.old/libavcodec/mf_utils.h 2025-10-27 10:07:00.429472580 +0100 +++ ffmpeg-7.1.2/libavcodec/mf_utils.h 2025-10-27 10:07:02.458955668 +0100 @@ -113,6 +113,7 @@ DEFINE_MEDIATYPE_GUID(ff_MFVideoFormat_HEVC, 0x43564548); // FCC('HEVC') DEFINE_MEDIATYPE_GUID(ff_MFVideoFormat_HEVC_ES, 0x53564548); // FCC('HEVS') +DEFINE_MEDIATYPE_GUID(ff_MFVideoFormat_AV1, 0x31305641); // FCC('AV01') // This enum is missing from mingw-w64's codecapi.h by v7.0.0. diff -Naur ffmpeg-7.1.2.old/libavcodec/pgssubdec.c ffmpeg-7.1.2/libavcodec/pgssubdec.c --- ffmpeg-7.1.2.old/libavcodec/pgssubdec.c 2025-10-27 10:07:00.318471601 +0100 +++ ffmpeg-7.1.2/libavcodec/pgssubdec.c 2025-10-27 10:07:02.446191090 +0100 @@ -35,9 +35,11 @@ #include "libavutil/opt.h" #define RGBA(r,g,b,a) (((unsigned)(a) << 24) | ((r) << 16) | ((g) << 8) | (b)) -#define MAX_EPOCH_PALETTES 8 // Max 8 allowed per PGS epoch -#define MAX_EPOCH_OBJECTS 64 // Max 64 allowed per PGS epoch -#define MAX_OBJECT_REFS 2 // Max objects per display set +#define MAX_EPOCH_PALETTES 8 // Max 8 allowed per PGS epoch +#define MAX_EPOCH_OBJECTS 64 // Max 64 allowed per PGS epoch +#define MAX_OBJECT_REFS 2 // Max objects per display set +#define MAX_OBJECT_WH 4096 // Max object width/height + enum SegmentType { PALETTE_SEGMENT = 0x14, @@ -48,57 +50,80 @@ }; typedef struct PGSSubObjectRef { - int id; - int window_id; - uint8_t composition_flag; - int x; - int y; - int crop_x; - int crop_y; - int crop_w; - int crop_h; + uint16_t id; + uint8_t window_id; + uint8_t composition_flag; + uint16_t x; + uint16_t y; + uint16_t crop_x; + uint16_t crop_y; + uint16_t crop_w; + uint16_t crop_h; } PGSSubObjectRef; typedef struct PGSSubPresentation { - int id_number; - int palette_id; - int object_count; + uint8_t palette_flag; + uint8_t palette_id; + uint8_t object_count; PGSSubObjectRef objects[MAX_OBJECT_REFS]; - int64_t pts; + int64_t pts; } PGSSubPresentation; typedef struct PGSSubObject { - int id; - int w; - int h; - uint8_t *rle; - unsigned int rle_buffer_size, rle_data_len; - unsigned int rle_remaining_len; + uint16_t id; + uint16_t w; + uint16_t h; + uint8_t *rle; + uint8_t *bitmap; + uint32_t rle_buffer_size; + uint32_t rle_data_len; + uint32_t rle_remaining_len; + uint32_t bitmap_buffer_size; + uint32_t bitmap_size; } PGSSubObject; typedef struct PGSSubObjects { - int count; + uint8_t count; PGSSubObject object[MAX_EPOCH_OBJECTS]; } PGSSubObjects; typedef struct PGSSubPalette { - int id; - uint32_t clut[256]; + uint8_t id; + uint32_t clut[AVPALETTE_COUNT]; } PGSSubPalette; typedef struct PGSSubPalettes { - int count; + uint8_t count; PGSSubPalette palette[MAX_EPOCH_PALETTES]; } PGSSubPalettes; +typedef struct PGSGraphicPlane { + uint8_t count; + uint8_t writable; + AVSubtitleRect visible_rect[MAX_OBJECT_REFS]; +} PGSGraphicPlane; + typedef struct PGSSubContext { AVClass *class; PGSSubPresentation presentation; PGSSubPalettes palettes; PGSSubObjects objects; + PGSGraphicPlane plane; int forced_subs_only; } PGSSubContext; +static void clear_graphic_plane(PGSSubContext *ctx) +{ + int i; + + for (i = 0; i < ctx->plane.count; i++) { + av_freep(&ctx->plane.visible_rect[i].data[0]); + memset(&ctx->plane.visible_rect[i], 0, sizeof(ctx->plane.visible_rect[i])); + } + ctx->plane.writable = 0; + ctx->plane.count = 0; +} + static void flush_cache(AVCodecContext *avctx) { PGSSubContext *ctx = avctx->priv_data; @@ -106,8 +131,11 @@ for (i = 0; i < ctx->objects.count; i++) { av_freep(&ctx->objects.object[i].rle); - ctx->objects.object[i].rle_buffer_size = 0; + ctx->objects.object[i].rle_buffer_size = 0; ctx->objects.object[i].rle_remaining_len = 0; + av_freep(&ctx->objects.object[i].bitmap); + ctx->objects.object[i].bitmap_buffer_size = 0; + ctx->objects.object[i].bitmap_size = 0; } ctx->objects.count = 0; ctx->palettes.count = 0; @@ -144,6 +172,7 @@ static av_cold int close_decoder(AVCodecContext *avctx) { + clear_graphic_plane((PGSSubContext *)avctx->priv_data); flush_cache(avctx); return 0; @@ -159,48 +188,51 @@ * @param buf pointer to the RLE data to process * @param buf_size size of the RLE data to process */ -static int decode_rle(AVCodecContext *avctx, AVSubtitleRect *rect, - const uint8_t *buf, unsigned int buf_size) +static int decode_object_rle(AVCodecContext *avctx, PGSSubObject *object) { - const uint8_t *rle_bitmap_end; + const uint8_t *rle_buf; + const uint8_t *rle_end; int pixel_count, line_count; + rle_buf = object->rle; + rle_end = object->rle + object->rle_data_len; - rle_bitmap_end = buf + buf_size; + object->bitmap_size = object->w * object->h; + av_fast_padded_malloc(&object->bitmap, &object->bitmap_buffer_size, + object->bitmap_size); - rect->data[0] = av_malloc_array(rect->w, rect->h); - - if (!rect->data[0]) + if (!object->bitmap) return AVERROR(ENOMEM); pixel_count = 0; line_count = 0; - while (buf < rle_bitmap_end && line_count < rect->h) { + while (rle_buf < rle_end && line_count < object->h) { uint8_t flags, color; int run; - color = bytestream_get_byte(&buf); + color = bytestream_get_byte(&rle_buf); run = 1; if (color == 0x00) { - flags = bytestream_get_byte(&buf); + flags = bytestream_get_byte(&rle_buf); run = flags & 0x3f; if (flags & 0x40) - run = (run << 8) + bytestream_get_byte(&buf); - color = flags & 0x80 ? bytestream_get_byte(&buf) : 0; + run = (run << 8) + bytestream_get_byte(&rle_buf); + color = flags & 0x80 ? bytestream_get_byte(&rle_buf) : 0; } - if (run > 0 && pixel_count + run <= rect->w * rect->h) { - memset(rect->data[0] + pixel_count, color, run); + if (run > 0 && pixel_count + run <= object->w * object->h) { + memset(object->bitmap + pixel_count, color, run); pixel_count += run; } else if (!run) { /* * New Line. Check if correct pixels decoded, if not display warning * and adjust bitmap pointer to correct new line position. */ - if (pixel_count % rect->w > 0) { - av_log(avctx, AV_LOG_ERROR, "Decoded %d pixels, when line should be %d pixels\n", - pixel_count % rect->w, rect->w); + if (pixel_count % object->w > 0) { + av_log(avctx, AV_LOG_ERROR, + "Decoded %d pixels, when object line should be %d pixels\n", + pixel_count % object->w, object->w); if (avctx->err_recognition & AV_EF_EXPLODE) { return AVERROR_INVALIDDATA; } @@ -209,13 +241,11 @@ } } - if (pixel_count < rect->w * rect->h) { - av_log(avctx, AV_LOG_ERROR, "Insufficient RLE data for subtitle\n"); + if (pixel_count < object->w * object->h) { + av_log(avctx, AV_LOG_ERROR, "Insufficient RLE data for object\n"); return AVERROR_INVALIDDATA; } - - ff_dlog(avctx, "Pixel Count = %d, Area = %d\n", pixel_count, rect->w * rect->h); - + ff_dlog(avctx, "Pixel Count = %d, Area = %d\n", pixel_count, object->w * object->h); return 0; } @@ -237,7 +267,7 @@ uint8_t sequence_desc; unsigned int rle_bitmap_len, width, height; - int id; + int id, ret; if (buf_size <= 4) return AVERROR_INVALIDDATA; @@ -260,57 +290,71 @@ /* Read the Sequence Description to determine if start of RLE data or appended to previous RLE */ sequence_desc = bytestream_get_byte(&buf); - if (!(sequence_desc & 0x80)) { - /* Additional RLE data */ - if (buf_size > object->rle_remaining_len) + /* First in sequence object definition segment */ + if (sequence_desc & 0x80) { + if (buf_size <= 7) return AVERROR_INVALIDDATA; + buf_size -= 7; - memcpy(object->rle + object->rle_data_len, buf, buf_size); - object->rle_data_len += buf_size; - object->rle_remaining_len -= buf_size; - - return 0; - } - - if (buf_size <= 7) - return AVERROR_INVALIDDATA; - buf_size -= 7; + /* Decode rle bitmap length, stored size includes width/height data */ + rle_bitmap_len = bytestream_get_be24(&buf) - 2*2; - /* Decode rle bitmap length, stored size includes width/height data */ - rle_bitmap_len = bytestream_get_be24(&buf) - 2*2; + if (buf_size > rle_bitmap_len) { + av_log(avctx, AV_LOG_ERROR, + "Buffer dimension %d larger than the expected RLE data %d\n", + buf_size, rle_bitmap_len); + return AVERROR_INVALIDDATA; + } - if (buf_size > rle_bitmap_len) { - av_log(avctx, AV_LOG_ERROR, - "Buffer dimension %d larger than the expected RLE data %d\n", - buf_size, rle_bitmap_len); - return AVERROR_INVALIDDATA; - } + /* Get bitmap dimensions from data */ + width = bytestream_get_be16(&buf); + height = bytestream_get_be16(&buf); + + /* Make sure the bitmap is not too large */ + if (MAX_OBJECT_WH < width || MAX_OBJECT_WH < height || !width || !height) { + av_log(avctx, AV_LOG_ERROR, "Bitmap dimensions (%dx%d) invalid.\n", width, height); + return AVERROR_INVALIDDATA; + } - /* Get bitmap dimensions from data */ - width = bytestream_get_be16(&buf); - height = bytestream_get_be16(&buf); - - /* Make sure the bitmap is not too large */ - if (avctx->width < width || avctx->height < height || !width || !height) { - av_log(avctx, AV_LOG_ERROR, "Bitmap dimensions (%dx%d) invalid.\n", width, height); - return AVERROR_INVALIDDATA; - } + object->rle_data_len = 0; + object->w = width; + object->h = height; + /* Dimensions against video are checked at decode after cropping. */ + av_fast_padded_malloc(&object->rle, &object->rle_buffer_size, rle_bitmap_len); - object->w = width; - object->h = height; + if (!object->rle) { + object->rle_remaining_len = 0; + return AVERROR(ENOMEM); + } - av_fast_padded_malloc(&object->rle, &object->rle_buffer_size, rle_bitmap_len); + memcpy(object->rle, buf, buf_size); + object->rle_remaining_len = rle_bitmap_len; + } else { + /* Additional RLE data */ + if (buf_size > object->rle_remaining_len) + return AVERROR_INVALIDDATA; - if (!object->rle) { - object->rle_data_len = 0; - object->rle_remaining_len = 0; - return AVERROR(ENOMEM); + memcpy(object->rle + object->rle_data_len, buf, buf_size); } + object->rle_data_len += buf_size; + object->rle_remaining_len -= buf_size; - memcpy(object->rle, buf, buf_size); - object->rle_data_len = buf_size; - object->rle_remaining_len = rle_bitmap_len - buf_size; - + /* Last in sequence object definition (can be both first and last) */ + if (sequence_desc & 0x40) { + /* Attempt decoding if data is valid */ + if (0 == object->rle_remaining_len) { + ret = decode_object_rle(avctx, object); + if (ret < 0 && (avctx->err_recognition & AV_EF_EXPLODE || ret == AVERROR(ENOMEM))) { + return ret; + } + } else { + av_log(avctx, AV_LOG_ERROR, + "RLE data length %u is %u bytes shorter than expected\n", + object->rle_data_len, object->rle_remaining_len); + if (avctx->err_recognition & AV_EF_EXPLODE) + return AVERROR_INVALIDDATA; + } + } return 0; } @@ -318,7 +362,7 @@ * Parse the palette segment packet. * * The palette segment contains details of the palette, - * a maximum of 256 colors can be defined. + * a maximum of 256 colors (AVPALETTE_COUNT) can be defined. * * @param avctx contains the current codec context * @param buf pointer to the packet to process @@ -391,13 +435,17 @@ int64_t pts) { PGSSubContext *ctx = avctx->priv_data; - int i, state, ret; + int ret; + uint8_t i, state; const uint8_t *buf_end = buf + buf_size; // Video descriptor int w = bytestream_get_be16(&buf); int h = bytestream_get_be16(&buf); + // On a new display set, reset writability of the graphic plane + ctx->plane.writable = 0; + ctx->presentation.pts = pts; ff_dlog(avctx, "Video Dimensions %dx%d\n", @@ -406,88 +454,121 @@ if (ret < 0) return ret; - /* Skip 1 bytes of unknown, frame rate */ - buf++; + /* Skip 3 bytes: framerate (1), presentation id number (2) */ + buf+=3; - // Composition descriptor - ctx->presentation.id_number = bytestream_get_be16(&buf); /* - * state is a 2 bit field that defines pgs epoch boundaries + * State is a 2 bit field that defines pgs epoch boundaries * 00 - Normal, previously defined objects and palettes are still valid * 01 - Acquisition point, previous objects and palettes can be released * 10 - Epoch start, previous objects and palettes can be released * 11 - Epoch continue, previous objects and palettes can be released * - * reserved 6 bits discarded + * Reserved 6 bits discarded */ state = bytestream_get_byte(&buf) >> 6; if (state != 0) { + /* Epoch start always wipes the graphic plane. Epoch continue does only if + * playback is not seamless, which should not happen with a proper stream. + */ + if (0b10 == state) + clear_graphic_plane((PGSSubContext *)avctx->priv_data); flush_cache(avctx); } + /* Reserved 7 bits discarded. */ + ctx->presentation.palette_flag = bytestream_get_byte(&buf) & 0x80; + ctx->presentation.palette_id = bytestream_get_byte(&buf); + /* - * skip palette_update_flag (0x80), + * On palette update, don't parse the compositions references, + * just evaluate the existing graphic plane with the new palette. */ - buf += 1; - ctx->presentation.palette_id = bytestream_get_byte(&buf); - ctx->presentation.object_count = bytestream_get_byte(&buf); - if (ctx->presentation.object_count > MAX_OBJECT_REFS) { - av_log(avctx, AV_LOG_ERROR, - "Invalid number of presentation objects %d\n", - ctx->presentation.object_count); - ctx->presentation.object_count = 2; - if (avctx->err_recognition & AV_EF_EXPLODE) { - return AVERROR_INVALIDDATA; + if (!ctx->presentation.palette_flag) { + ctx->presentation.object_count = bytestream_get_byte(&buf); + if (ctx->presentation.object_count > MAX_OBJECT_REFS) { + av_log(avctx, AV_LOG_ERROR, + "Invalid number of presentation objects %d\n", + ctx->presentation.object_count); + ctx->presentation.object_count = 2; + if (avctx->err_recognition & AV_EF_EXPLODE) { + return AVERROR_INVALIDDATA; + } } - } + for (i = 0; i < ctx->presentation.object_count; i++) { + PGSSubObjectRef *const object = &ctx->presentation.objects[i]; - for (i = 0; i < ctx->presentation.object_count; i++) - { - PGSSubObjectRef *const object = &ctx->presentation.objects[i]; - - if (buf_end - buf < 8) { - av_log(avctx, AV_LOG_ERROR, "Insufficent space for object\n"); - ctx->presentation.object_count = i; - return AVERROR_INVALIDDATA; - } - - object->id = bytestream_get_be16(&buf); - object->window_id = bytestream_get_byte(&buf); - object->composition_flag = bytestream_get_byte(&buf); - - object->x = bytestream_get_be16(&buf); - object->y = bytestream_get_be16(&buf); - - // If cropping - if (object->composition_flag & 0x80) { - object->crop_x = bytestream_get_be16(&buf); - object->crop_y = bytestream_get_be16(&buf); - object->crop_w = bytestream_get_be16(&buf); - object->crop_h = bytestream_get_be16(&buf); - } - - ff_dlog(avctx, "Subtitle Placement x=%d, y=%d\n", - object->x, object->y); - - if (object->x > avctx->width || object->y > avctx->height) { - av_log(avctx, AV_LOG_ERROR, "Subtitle out of video bounds. x = %d, y = %d, video width = %d, video height = %d.\n", - object->x, object->y, - avctx->width, avctx->height); - object->y = object->x = 0; - if (avctx->err_recognition & AV_EF_EXPLODE) { + if (buf_end - buf < 8) { + av_log(avctx, AV_LOG_ERROR, "Insufficent space for object\n"); + ctx->presentation.object_count = i; return AVERROR_INVALIDDATA; } + + object->id = bytestream_get_be16(&buf); + object->window_id = bytestream_get_byte(&buf); + object->composition_flag = bytestream_get_byte(&buf); + + object->x = bytestream_get_be16(&buf); + object->y = bytestream_get_be16(&buf); + + // If cropping + if (object->composition_flag & 0x80) { + object->crop_x = bytestream_get_be16(&buf); + object->crop_y = bytestream_get_be16(&buf); + object->crop_w = bytestream_get_be16(&buf); + object->crop_h = bytestream_get_be16(&buf); + } + + /* Placement is checked at decode after cropping. */ + ff_dlog(avctx, "Subtitle Placement x=%d, y=%d\n", + object->x, object->y); } } + return 0; +} + +/** + * Parse the window segment packet. + * + * The window segment instructs the decoder to redraw the graphic plane + * with the composition references provided in the presentation segment + * + * @param avctx contains the current codec context + */ +static int parse_window_segment(AVCodecContext *avctx, const uint8_t *buf, + int buf_size) +{ + PGSSubContext *ctx = (PGSSubContext *)avctx->priv_data; + + // 1 byte: number of windows defined + if (bytestream_get_byte(&buf) > MAX_OBJECT_REFS) { + av_log(avctx, AV_LOG_ERROR, "Too many windows defined.\n"); + return AVERROR_INVALIDDATA; + } + /* TODO: mask objects with windows when transfering to the graphic plane + * Window Segment Structure + * { + * 1 byte : window id, + * 2 bytes: X position of window, + * 2 bytes: Y position of window, + * 2 bytes: Width of window, + * 2 bytes: Height of window. + * } + */ + // Flush the graphic plane, it will be redrawn. + clear_graphic_plane(ctx); + ctx->plane.writable = 1; + ctx->plane.count = ctx->presentation.object_count; return 0; } /** * Parse the display segment packet. * - * The display segment controls the updating of the display. + * The display segment closes the display set. The inferred data is used + * to decide if the display should be updated. * * @param avctx contains the current codec context * @param data pointer to the data pertaining the subtitle to display @@ -500,26 +581,33 @@ PGSSubContext *ctx = avctx->priv_data; int64_t pts; PGSSubPalette *palette; - int i, ret; + int i; pts = ctx->presentation.pts != AV_NOPTS_VALUE ? ctx->presentation.pts : sub->pts; memset(sub, 0, sizeof(*sub)); sub->pts = pts; ctx->presentation.pts = AV_NOPTS_VALUE; - sub->start_display_time = 0; // There is no explicit end time for PGS subtitles. The end time // is defined by the start of the next sub which may contain no // objects (i.e. clears the previous sub) sub->end_display_time = UINT32_MAX; - sub->format = 0; - // Blank if last object_count was 0. - if (!ctx->presentation.object_count) + // Object count is zero only on an epoch start with no WDS + // or the last DS with a WDS had no presentation object. + if (!ctx->plane.count) { return 1; - sub->rects = av_calloc(ctx->presentation.object_count, sizeof(*sub->rects)); - if (!sub->rects) { - return AVERROR(ENOMEM); } + + if (!ctx->presentation.palette_flag && !ctx->plane.writable) { + // This display set does not perform a display update + // E.g. it only defines new objects or palettes for future usage. + return 0; + } + + sub->rects = av_calloc(ctx->plane.count, sizeof(*sub->rects)); + if (!sub->rects) + return AVERROR(ENOMEM); + palette = find_palette(ctx->presentation.palette_id, &ctx->palettes); if (!palette) { // Missing palette. Should only happen with damaged streams. @@ -528,57 +616,128 @@ avsubtitle_free(sub); return AVERROR_INVALIDDATA; } - for (i = 0; i < ctx->presentation.object_count; i++) { - AVSubtitleRect *const rect = av_mallocz(sizeof(*rect)); - PGSSubObject *object; - if (!rect) - return AVERROR(ENOMEM); - sub->rects[sub->num_rects++] = rect; - rect->type = SUBTITLE_BITMAP; + for (i = 0; i < ctx->plane.count; i++) { + const PGSSubObjectRef *sub_object = &ctx->presentation.objects[i]; + AVSubtitleRect *const gp_rect = &ctx->plane.visible_rect[i]; + AVSubtitleRect *rect; + gp_rect->type = SUBTITLE_BITMAP; + + // Compose the graphic plane if a window segment has been provided + if (ctx->plane.writable) { + PGSSubObject *object; + + // Process bitmap + object = find_object(sub_object->id, &ctx->objects); + if (!object) { + // Missing object. Should only happen with damaged streams. + av_log(avctx, AV_LOG_ERROR, "Invalid object id %d\n", sub_object->id); + if (avctx->err_recognition & AV_EF_EXPLODE) + return AVERROR_INVALIDDATA; + // Leaves rect empty with 0 width and height. + continue; + } + if (sub_object->composition_flag & 0x40) + gp_rect->flags |= AV_SUBTITLE_FLAG_FORCED; - /* Process bitmap */ - object = find_object(ctx->presentation.objects[i].id, &ctx->objects); - if (!object) { - // Missing object. Should only happen with damaged streams. - av_log(avctx, AV_LOG_ERROR, "Invalid object id %d\n", - ctx->presentation.objects[i].id); - if (avctx->err_recognition & AV_EF_EXPLODE) - return AVERROR_INVALIDDATA; - // Leaves rect empty with 0 width and height. - continue; - } - if (ctx->presentation.objects[i].composition_flag & 0x40) - rect->flags |= AV_SUBTITLE_FLAG_FORCED; + gp_rect->x = sub_object->x; + gp_rect->y = sub_object->y; - rect->x = ctx->presentation.objects[i].x; - rect->y = ctx->presentation.objects[i].y; + if (object->rle) { + int out_of_picture = 0; + gp_rect->w = object->w; + gp_rect->h = object->h; + + gp_rect->linesize[0] = object->w; + + // Check for cropping. + if (sub_object->composition_flag & 0x80) { + int out_of_object = 0; + + if (object->w < sub_object->crop_x + sub_object->crop_w) + out_of_object = 1; + if (object->h < sub_object->crop_y + sub_object->crop_h) + out_of_object = 1; + + if (out_of_object) { + av_log(avctx, AV_LOG_ERROR, + "Subtitle cropping values are out of object. " + "obj_w = %d, obj_h = %d, crop_x = %d, crop_y = %d, " + "crop_w = %d, crop_h = %d.\n", + object->w, + object->h, + sub_object->crop_x, + sub_object->crop_y, + sub_object->crop_w, + sub_object->crop_h); + if (avctx->err_recognition & AV_EF_EXPLODE) + return AVERROR_INVALIDDATA; + } else { + // Replace subtitle dimensions with cropping ones. + gp_rect->w = sub_object->crop_w; + gp_rect->h = sub_object->crop_h; + gp_rect->linesize[0] = sub_object->crop_w; + } + } - if (object->rle) { - rect->w = object->w; - rect->h = object->h; + /* Make sure the subtitle is not out of picture. */ + if (avctx->width < gp_rect->x + gp_rect->w || !gp_rect->w) + out_of_picture = 1; + if (avctx->height < gp_rect->y + gp_rect->h || !gp_rect->h) + out_of_picture = 1; + if (out_of_picture) { + av_log(avctx, AV_LOG_ERROR, + "Subtitle out of video bounds. " + "x = %d, y = %d, width = %d, height = %d.\n", + gp_rect->x, gp_rect->y, gp_rect->w, gp_rect->h); + if (avctx->err_recognition & AV_EF_EXPLODE) + return AVERROR_INVALIDDATA; + gp_rect->w = 0; + gp_rect->h = 0; + continue; + } - rect->linesize[0] = object->w; + if (!object->bitmap_size || object->rle_remaining_len) { + gp_rect->w = 0; + gp_rect->h = 0; + continue; + } - if (object->rle_remaining_len) { - av_log(avctx, AV_LOG_ERROR, "RLE data length %u is %u bytes shorter than expected\n", - object->rle_data_len, object->rle_remaining_len); - if (avctx->err_recognition & AV_EF_EXPLODE) - return AVERROR_INVALIDDATA; - } - ret = decode_rle(avctx, rect, object->rle, object->rle_data_len); - if (ret < 0) { - if ((avctx->err_recognition & AV_EF_EXPLODE) || - ret == AVERROR(ENOMEM)) { - return ret; + gp_rect->data[0] = av_malloc_array(gp_rect->w, gp_rect->h); + if (!gp_rect->data[0]) + return AVERROR(ENOMEM); + + if (sub_object->composition_flag & 0x80) { + /* Copy cropped bitmap. */ + int y; + + for (y = 0; y < sub_object->crop_h; y++) { + memcpy(&gp_rect->data[0][y * sub_object->crop_w], + &object->bitmap[(sub_object->crop_y + y) * + object->w + sub_object->crop_x], + sub_object->crop_w); + } + } + else { + /* copy full object */ + memcpy(gp_rect->data[0], object->bitmap, object->bitmap_size); } - rect->w = 0; - rect->h = 0; - continue; } } - /* Allocate memory for colors */ - rect->nb_colors = 256; + // Export graphic plane content with latest palette + rect = av_memdup(gp_rect, sizeof(*gp_rect)); + if (!rect) + return AVERROR(ENOMEM); + + sub->rects[sub->num_rects++] = rect; + if (gp_rect->data[0]) { + rect->data[0] = av_memdup(gp_rect->data[0], rect->w*rect->h); + if (!rect->data[0]) + return AVERROR(ENOMEM); + } + + // Allocate memory for colors + rect->nb_colors = AVPALETTE_COUNT; rect->data[1] = av_mallocz(AVPALETTE_SIZE); if (!rect->data[1]) return AVERROR(ENOMEM); @@ -641,14 +800,7 @@ ret = parse_presentation_segment(avctx, buf, segment_length, sub->pts); break; case WINDOW_SEGMENT: - /* - * Window Segment Structure (No new information provided): - * 2 bytes: Unknown, - * 2 bytes: X position of subtitle, - * 2 bytes: Y position of subtitle, - * 2 bytes: Width of subtitle, - * 2 bytes: Height of subtitle. - */ + ret = parse_window_segment(avctx, buf, segment_length); break; case DISPLAY_SEGMENT: if (*got_sub_ptr) { diff -Naur ffmpeg-7.1.2.old/libavcodec/qsvenc_av1.c ffmpeg-7.1.2/libavcodec/qsvenc_av1.c --- ffmpeg-7.1.2.old/libavcodec/qsvenc_av1.c 2025-10-27 10:07:00.531473480 +0100 +++ ffmpeg-7.1.2/libavcodec/qsvenc_av1.c 2025-10-27 10:07:02.443863055 +0100 @@ -189,6 +189,10 @@ { "tile_cols", "Number of columns for tiled encoding", OFFSET(qsv.tile_cols), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, UINT16_MAX, VE }, { "tile_rows", "Number of rows for tiled encoding", OFFSET(qsv.tile_rows), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, UINT16_MAX, VE }, { "look_ahead_depth", "Depth of look ahead in number frames, available when extbrc option is enabled", OFFSET(qsv.look_ahead_depth), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 100, VE }, +#if QSV_HAVE_EXT_AV1_SCC + { "palette_mode", "Enable palette mode of Screen Content Tool for encoding", OFFSET(qsv.palette_mode), AV_OPT_TYPE_BOOL, { .i64 = 0}, 0, 1, VE}, + { "intrabc", "Enable intra block copy of Screen Content Tool for encoding", OFFSET(qsv.intrabc), AV_OPT_TYPE_BOOL, { .i64 = 0}, 0, 1, VE}, +#endif { NULL }, }; diff -Naur ffmpeg-7.1.2.old/libavcodec/qsvenc.c ffmpeg-7.1.2/libavcodec/qsvenc.c --- ffmpeg-7.1.2.old/libavcodec/qsvenc.c 2025-10-27 10:07:00.592474018 +0100 +++ ffmpeg-7.1.2/libavcodec/qsvenc.c 2025-10-27 10:07:02.443621469 +0100 @@ -494,6 +494,9 @@ mfxExtAV1BitstreamParam *av1_bs_param = (mfxExtAV1BitstreamParam *)coding_opts[1]; mfxExtCodingOption2 *co2 = (mfxExtCodingOption2*)coding_opts[2]; mfxExtCodingOption3 *co3 = (mfxExtCodingOption3*)coding_opts[3]; +#if QSV_HAVE_EXT_AV1_SCC + mfxExtAV1ScreenContentTools *scc = (mfxExtAV1ScreenContentTools*)coding_opts[4]; +#endif av_log(avctx, AV_LOG_VERBOSE, "profile: %s; level: %"PRIu16"\n", print_profile(avctx->codec_id, info->CodecProfile), info->CodecLevel); @@ -566,6 +569,13 @@ print_threestate(av1_bs_param->WriteIVFHeaders)); av_log(avctx, AV_LOG_VERBOSE, "LowDelayBRC: %s\n", print_threestate(co3->LowDelayBRC)); av_log(avctx, AV_LOG_VERBOSE, "MaxFrameSize: %d;\n", co2->MaxFrameSize); +#if QSV_HAVE_EXT_AV1_SCC + if (scc) { + av_log(avctx, AV_LOG_VERBOSE, + "Palette: %s; IntraBlockCopy: %s\n", + print_threestate(scc->Palette), print_threestate(scc->IntraBlockCopy)); + } +#endif } #endif @@ -1282,6 +1292,28 @@ } #endif +#if QSV_HAVE_EXT_AV1_SCC + if (q->palette_mode || q->intrabc) { + if (QSV_RUNTIME_VERSION_ATLEAST(q->ver, 2, 13)) { + if (q->param.mfx.CodecId != MFX_CODEC_AV1) { + av_log(avctx, AV_LOG_ERROR, "Not supported encoder for Screen Content Tool Encode. " + "Supported: av1_qsv \n"); + return AVERROR_UNKNOWN; + } + + q->extsccparam.Header.BufferId = MFX_EXTBUFF_AV1_SCREEN_CONTENT_TOOLS; + q->extsccparam.Header.BufferSz = sizeof(q->extsccparam); + q->extsccparam.Palette = q->palette_mode ? MFX_CODINGOPTION_ON : MFX_CODINGOPTION_OFF; + q->extsccparam.IntraBlockCopy = q->intrabc ? MFX_CODINGOPTION_ON : MFX_CODINGOPTION_OFF; + q->extparam_internal[q->nb_extparam_internal++] = (mfxExtBuffer *)&q->extsccparam; + } else { + av_log(avctx, AV_LOG_ERROR, + "This version of runtime doesn't support Screen Content Tool Encode\n"); + return AVERROR_UNKNOWN; + } + } +#endif + if (!check_enc_param(avctx,q)) { av_log(avctx, AV_LOG_ERROR, "some encoding parameters are not supported by the QSV " @@ -1389,11 +1421,21 @@ .Header.BufferSz = sizeof(co3), }; +#if QSV_HAVE_EXT_AV1_SCC + mfxExtAV1ScreenContentTools scc_buf = { + .Header.BufferId = MFX_EXTBUFF_AV1_SCREEN_CONTENT_TOOLS, + .Header.BufferSz = sizeof(scc_buf), + }; +#endif + mfxExtBuffer *ext_buffers[] = { (mfxExtBuffer*)&av1_extend_tile_buf, (mfxExtBuffer*)&av1_bs_param, (mfxExtBuffer*)&co2, (mfxExtBuffer*)&co3, +#if QSV_HAVE_EXT_AV1_SCC + (mfxExtBuffer*)&scc_buf, +#endif }; if (!QSV_RUNTIME_VERSION_ATLEAST(q->ver, 2, 5)) { @@ -1842,6 +1884,13 @@ return ret; } + // Update AVCodecContext with actual encoding parameters + mfxInfoMFX *info = &q->param.mfx; + avctx->has_b_frames = 0; + if (info->GopRefDist > 1) { + avctx->has_b_frames = info->GopRefDist - 1; + } + q->avctx = avctx; return 0; diff -Naur ffmpeg-7.1.2.old/libavcodec/qsvenc.h ffmpeg-7.1.2/libavcodec/qsvenc.h --- ffmpeg-7.1.2.old/libavcodec/qsvenc.h 2025-10-27 10:07:00.282471283 +0100 +++ ffmpeg-7.1.2/libavcodec/qsvenc.h 2025-10-27 10:07:02.443799916 +0100 @@ -38,6 +38,7 @@ #define QSV_HAVE_EXT_VP9_TILES QSV_VERSION_ATLEAST(1, 29) #define QSV_HAVE_EXT_AV1_PARAM QSV_VERSION_ATLEAST(2, 5) +#define QSV_HAVE_EXT_AV1_SCC QSV_VERSION_ATLEAST(2, 13) #if defined(_WIN32) || defined(__CYGWIN__) #define QSV_HAVE_AVBR 1 @@ -188,10 +189,13 @@ mfxFrameSurface1 **opaque_surfaces; AVBufferRef *opaque_alloc_buf; #endif +#if QSV_HAVE_EXT_AV1_SCC + mfxExtAV1ScreenContentTools extsccparam; +#endif mfxExtVideoSignalInfo extvsi; - mfxExtBuffer *extparam_internal[5 + (QSV_HAVE_MF * 2) + (QSV_HAVE_EXT_AV1_PARAM * 2) + QSV_HAVE_HE]; + mfxExtBuffer *extparam_internal[5 + (QSV_HAVE_MF * 2) + (QSV_HAVE_EXT_AV1_PARAM * 2) + QSV_HAVE_HE + QSV_HAVE_EXT_AV1_SCC]; int nb_extparam_internal; mfxExtBuffer **extparam_str; @@ -319,6 +323,8 @@ int dual_gfx; AVDictionary *qsv_params; + int palette_mode; + int intrabc; } QSVEncContext; int ff_qsv_enc_init(AVCodecContext *avctx, QSVEncContext *q); diff -Naur ffmpeg-7.1.2.old/libavcodec/videotoolbox_av1.c ffmpeg-7.1.2/libavcodec/videotoolbox_av1.c --- ffmpeg-7.1.2.old/libavcodec/videotoolbox_av1.c 1970-01-01 01:00:00.000000000 +0100 +++ ffmpeg-7.1.2/libavcodec/videotoolbox_av1.c 2025-10-27 10:07:02.455024552 +0100 @@ -0,0 +1,105 @@ +/* + * Videotoolbox hardware acceleration for AV1 + * Copyright (c) 2023 Jan Ekström + * Copyright (c) 2024 Ruslan Chernenko + * Copyright (c) 2024 Martin Storsjö + * + * 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 "libavutil/mem.h" + +#include "av1dec.h" +#include "hwaccel_internal.h" +#include "internal.h" +#include "vt_internal.h" + +CFDataRef ff_videotoolbox_av1c_extradata_create(AVCodecContext *avctx) +{ + AV1DecContext *s = avctx->priv_data; + uint8_t *buf; + CFDataRef data; + if (!s->raw_seq) + return NULL; + + buf = av_malloc(s->seq_data_ref->size + 4); + if (!buf) + return NULL; + buf[0] = 0x81; // version and marker (constant) + buf[1] = s->raw_seq->seq_profile << 5 | s->raw_seq->seq_level_idx[0]; + buf[2] = s->raw_seq->seq_tier[0] << 7 | + s->raw_seq->color_config.high_bitdepth << 6 | + s->raw_seq->color_config.twelve_bit << 5 | + s->raw_seq->color_config.mono_chrome << 4 | + s->raw_seq->color_config.subsampling_x << 3 | + s->raw_seq->color_config.subsampling_y << 2 | + s->raw_seq->color_config.chroma_sample_position; + + if (s->raw_seq->initial_display_delay_present_flag) + buf[3] = 0 << 5 | + s->raw_seq->initial_display_delay_present_flag << 4 | + s->raw_seq->initial_display_delay_minus_1[0]; + else + buf[3] = 0x00; + memcpy(buf + 4, s->seq_data_ref->data, s->seq_data_ref->size); + data = CFDataCreate(kCFAllocatorDefault, buf, s->seq_data_ref->size + 4); + av_free(buf); + return data; +}; + + +static int videotoolbox_av1_start_frame(AVCodecContext *avctx, + const uint8_t *buffer, + uint32_t size) +{ + return 0; +} + +static int videotoolbox_av1_decode_slice(AVCodecContext *avctx, + const uint8_t *buffer, + uint32_t size) +{ + return 0; +} + +static int videotoolbox_av1_end_frame(AVCodecContext *avctx) +{ + const AV1DecContext *s = avctx->priv_data; + VTContext *vtctx = avctx->internal->hwaccel_priv_data; + AVFrame *frame = s->cur_frame.f; + + vtctx->bitstream_size = 0; + for (int i = s->start_unit; i < s->nb_unit; i++) + ff_videotoolbox_buffer_append(vtctx, s->current_obu.units[i].data, + s->current_obu.units[i].data_size); + return ff_videotoolbox_common_end_frame(avctx, frame); +} + +const FFHWAccel ff_av1_videotoolbox_hwaccel = { + .p.name = "av1_videotoolbox", + .p.type = AVMEDIA_TYPE_VIDEO, + .p.id = AV_CODEC_ID_AV1, + .p.pix_fmt = AV_PIX_FMT_VIDEOTOOLBOX, + .alloc_frame = ff_videotoolbox_alloc_frame, + .start_frame = videotoolbox_av1_start_frame, + .decode_slice = videotoolbox_av1_decode_slice, + .end_frame = videotoolbox_av1_end_frame, + .frame_params = ff_videotoolbox_frame_params, + .init = ff_videotoolbox_common_init, + .uninit = ff_videotoolbox_uninit, + .priv_data_size = sizeof(VTContext), +}; diff -Naur ffmpeg-7.1.2.old/libavcodec/videotoolbox.c ffmpeg-7.1.2/libavcodec/videotoolbox.c --- ffmpeg-7.1.2.old/libavcodec/videotoolbox.c 2025-10-27 10:07:00.379472139 +0100 +++ ffmpeg-7.1.2/libavcodec/videotoolbox.c 2025-10-27 10:07:02.456490466 +0100 @@ -56,6 +56,10 @@ enum { kCMVideoCodecType_VP9 = 'vp09' }; #endif +#if !HAVE_KCMVIDEOCODECTYPE_AV1 +enum { kCMVideoCodecType_AV1 = 'av01' }; +#endif + #define VIDEOTOOLBOX_ESDS_EXTRADATA_PADDING 12 typedef struct VTHWFrame { @@ -92,6 +96,26 @@ return 0; } +int ff_videotoolbox_buffer_append(VTContext *vtctx, + const uint8_t *buffer, + uint32_t size) +{ + void *tmp; + + tmp = av_fast_realloc(vtctx->bitstream, + &vtctx->allocated_size, + vtctx->bitstream_size + size); + + if (!tmp) + return AVERROR(ENOMEM); + + vtctx->bitstream = tmp; + memcpy(vtctx->bitstream + vtctx->bitstream_size, buffer, size); + vtctx->bitstream_size += size; + + return 0; +} + static int videotoolbox_postproc_frame(void *avctx, AVFrame *frame) { int ret; @@ -108,9 +132,6 @@ frame->crop_top = 0; frame->crop_bottom = 0; - if ((ret = av_vt_pixbuf_set_attachments(avctx, ref->pixbuf, frame)) < 0) - return ret; - frame->data[3] = (uint8_t*)ref->pixbuf; if (ref->hw_frames_ctx) { @@ -790,7 +811,7 @@ #if TARGET_OS_IPHONE CFDictionarySetValue(buffer_attributes, kCVPixelBufferOpenGLESCompatibilityKey, kCFBooleanTrue); #else - CFDictionarySetValue(buffer_attributes, kCVPixelBufferIOSurfaceOpenGLTextureCompatibilityKey, kCFBooleanTrue); + CFDictionarySetValue(buffer_attributes, kCVPixelBufferMetalCompatibilityKey, kCFBooleanTrue); #endif CFRelease(io_surface_properties); @@ -847,6 +868,13 @@ CFDictionarySetValue(avc_info, CFSTR("vpcC"), data); break; #endif +#if CONFIG_AV1_VIDEOTOOLBOX_HWACCEL + case kCMVideoCodecType_AV1 : + data = ff_videotoolbox_av1c_extradata_create(avctx); + if (data) + CFDictionarySetValue(avc_info, CFSTR("av1C"), data); + break; +#endif default: break; } @@ -912,10 +940,30 @@ case AV_CODEC_ID_VP9 : videotoolbox->cm_codec_type = kCMVideoCodecType_VP9; break; + case AV_CODEC_ID_AV1 : + videotoolbox->cm_codec_type = kCMVideoCodecType_AV1; + break; default : break; } +#if ARCH_X86_64 + if (avctx->codec_id == AV_CODEC_ID_H264 && + avctx->sw_pix_fmt == AV_PIX_FMT_YUV420P10) + { + // 10-bit H.264 is not supported on x86_64 + return AVERROR(ENOSYS); + } +#endif + + if (avctx->codec_id == AV_CODEC_ID_H264 && + (avctx->level == 61 || avctx->level == 62)) + { + // H.264 Level 6.1 and 6.2 can't be + // decoded properly + return AVERROR(ENOSYS); + } + #if defined(MAC_OS_X_VERSION_10_9) && !TARGET_OS_IPHONE && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9) && AV_HAS_BUILTIN(__builtin_available) if (avctx->codec_id == AV_CODEC_ID_PRORES) { if (__builtin_available(macOS 10.9, *)) { diff -Naur ffmpeg-7.1.2.old/libavcodec/vt_internal.h ffmpeg-7.1.2/libavcodec/vt_internal.h --- ffmpeg-7.1.2.old/libavcodec/vt_internal.h 2025-10-27 10:07:00.531473480 +0100 +++ ffmpeg-7.1.2/libavcodec/vt_internal.h 2025-10-27 10:07:02.455086969 +0100 @@ -56,6 +56,9 @@ int ff_videotoolbox_buffer_copy(VTContext *vtctx, const uint8_t *buffer, uint32_t size); +int ff_videotoolbox_buffer_append(VTContext *vtctx, + const uint8_t *buffer, + uint32_t size); int ff_videotoolbox_uninit(AVCodecContext *avctx); int ff_videotoolbox_h264_start_frame(AVCodecContext *avctx, const uint8_t *buffer, @@ -64,6 +67,7 @@ const uint8_t *buffer, uint32_t size); int ff_videotoolbox_common_end_frame(AVCodecContext *avctx, AVFrame *frame); +CFDataRef ff_videotoolbox_av1c_extradata_create(AVCodecContext *avctx); CFDataRef ff_videotoolbox_avcc_extradata_create(AVCodecContext *avctx); CFDataRef ff_videotoolbox_hvcc_extradata_create(AVCodecContext *avctx); CFDataRef ff_videotoolbox_vpcc_extradata_create(AVCodecContext *avctx); diff -Naur ffmpeg-7.1.2.old/libavfilter/vf_vpp_qsv.c ffmpeg-7.1.2/libavfilter/vf_vpp_qsv.c --- ffmpeg-7.1.2.old/libavfilter/vf_vpp_qsv.c 2025-10-27 10:07:00.764475536 +0100 +++ ffmpeg-7.1.2/libavfilter/vf_vpp_qsv.c 2025-10-27 10:07:02.461658339 +0100 @@ -494,9 +494,9 @@ outvsi_conf.Header.BufferId = MFX_EXTBUFF_VIDEO_SIGNAL_INFO_OUT; outvsi_conf.Header.BufferSz = sizeof(mfxExtVideoSignalInfo); outvsi_conf.VideoFullRange = (out->color_range == AVCOL_RANGE_JPEG); - outvsi_conf.ColourPrimaries = (out->color_primaries == AVCOL_PRI_UNSPECIFIED) ? AVCOL_PRI_BT709 : out->color_primaries; - outvsi_conf.TransferCharacteristics = (out->color_trc == AVCOL_TRC_UNSPECIFIED) ? AVCOL_TRC_BT709 : out->color_trc; - outvsi_conf.MatrixCoefficients = (out->colorspace == AVCOL_SPC_UNSPECIFIED) ? AVCOL_SPC_BT709 : out->colorspace; + outvsi_conf.ColourPrimaries = (out->color_primaries == AVCOL_PRI_UNSPECIFIED) ? invsi_conf.ColourPrimaries : out->color_primaries; + outvsi_conf.TransferCharacteristics = (out->color_trc == AVCOL_TRC_UNSPECIFIED) ? invsi_conf.TransferCharacteristics : out->color_trc; + outvsi_conf.MatrixCoefficients = (out->colorspace == AVCOL_SPC_UNSPECIFIED) ? invsi_conf.MatrixCoefficients : out->colorspace; outvsi_conf.ColourDescriptionPresent = 1; if (memcmp(&vpp->invsi_conf, &invsi_conf, sizeof(mfxExtVideoSignalInfo)) || diff -Naur ffmpeg-7.1.2.old/libavformat/isom.h ffmpeg-7.1.2/libavformat/isom.h --- ffmpeg-7.1.2.old/libavformat/isom.h 2025-10-27 10:07:00.909476815 +0100 +++ ffmpeg-7.1.2/libavformat/isom.h 2025-10-27 10:07:02.422783282 +0100 @@ -272,6 +272,9 @@ MOVEncryptionIndex *encryption_index; } cenc; + int has_fallback; // Audio fallback track + int fallback; + struct IAMFDemuxContext *iamf; } MOVStreamContext; diff -Naur ffmpeg-7.1.2.old/libavformat/matroskaenc.c ffmpeg-7.1.2/libavformat/matroskaenc.c --- ffmpeg-7.1.2.old/libavformat/matroskaenc.c 2025-10-27 10:07:00.955477221 +0100 +++ ffmpeg-7.1.2/libavformat/matroskaenc.c 2025-10-27 10:07:02.430490236 +0100 @@ -2933,6 +2933,16 @@ case AV_CODEC_ID_AAC: if (side_data_size && mkv->track.bc) { int output_sample_rate = 0; + if (par->extradata && par->extradata_size) { + if (par->extradata_size != side_data_size || + memcmp(par->extradata, side_data, side_data_size)) { + av_log(s, AV_LOG_ERROR, "Error, AAC extradata changed mid-stream.\n"); + return AVERROR(EINVAL); + } else { + // Already written + break; + } + } ret = get_aac_sample_rates(s, mkv, side_data, side_data_size, &track->sample_rate, &output_sample_rate); if (ret < 0) diff -Naur ffmpeg-7.1.2.old/libavformat/mov.c ffmpeg-7.1.2/libavformat/mov.c --- ffmpeg-7.1.2.old/libavformat/mov.c 2025-10-27 10:07:00.889476639 +0100 +++ ffmpeg-7.1.2/libavformat/mov.c 2025-10-27 10:07:02.447584145 +0100 @@ -56,6 +56,7 @@ #include "libavcodec/mpegaudiodecheader.h" #include "libavcodec/mlp_parse.h" #include "avformat.h" +#include "avlanguage.h" #include "internal.h" #include "avio_internal.h" #include "demux.h" @@ -131,6 +132,33 @@ return 0; } +static int mov_metadata_int16_no_padding(MOVContext *c, AVIOContext *pb, + unsigned len, const char *key) +{ + c->fc->event_flags |= AVFMT_EVENT_FLAG_METADATA_UPDATED; + av_dict_set_int(&c->fc->metadata, key, avio_rb16(pb), 0); + + return 0; +} + +static int mov_metadata_int32_no_padding(MOVContext *c, AVIOContext *pb, + unsigned len, const char *key) +{ + c->fc->event_flags |= AVFMT_EVENT_FLAG_METADATA_UPDATED; + av_dict_set_int(&c->fc->metadata, key, avio_rb32(pb), 0); + + return 0; +} + +static int mov_metadata_int64_no_padding(MOVContext *c, AVIOContext *pb, + unsigned len, const char *key) +{ + c->fc->event_flags |= AVFMT_EVENT_FLAG_METADATA_UPDATED; + av_dict_set_int(&c->fc->metadata, key, avio_rb64(pb), 0); + + return 0; +} + static int mov_metadata_gnre(MOVContext *c, AVIOContext *pb, unsigned len, const char *key) { @@ -342,6 +370,73 @@ return 0; } +static int mov_read_3gp_udta_tag(MOVContext *c, AVIOContext *pb, MOVAtom atom) +{ + const char *key; + AVDictionary *metadata; + uint16_t langcode = 0; + char key2[32], language[4] = {0}; + uint32_t str_size, version; + char *str; + if (atom.size < 6) + return AVERROR_INVALIDDATA; + switch (atom.type) { + case MKTAG( 'a','l','b','m'): key = "album"; break; + case MKTAG( 'a','u','t','h'): key = "author"; break; + case MKTAG( 'c','p','r','t'): key = "copyright"; break; + case MKTAG( 'd','s','c','p'): key = "comment"; break; + case MKTAG( 'g','n','r','e'): key = "genre"; break; + case MKTAG( 'p','e','r','f'): key = "artist"; break; + case MKTAG( 't','i','t','l'): key = "title"; break; + case MKTAG( 'y','r','r','c'): key = "date"; break; + default: return 0; + } + version = avio_rb32(pb); // version + flags + if (version != 0) + av_log(c->fc, AV_LOG_WARNING, "udta %s unknown version number: %u\n", str, version); + if (MKTAG( 'y','r','r','c') == atom.type) { + int year; + year = avio_rb16(pb); + str = av_asprintf("%d", year); + if (!str) + return AVERROR(ENOMEM); + } else { + int ret; + const char *tmp; + langcode = avio_rb16(pb); + ff_mov_lang_to_iso639(langcode, language); + tmp = ff_convert_lang_to(language, AV_LANG_ISO639_2_BIBL); + if (!tmp) + av_log(c->fc, AV_LOG_WARNING, "udta %s unknown language code: %u\n", str, langcode); + str_size = atom.size - 6; + if (str_size <= 0 || str_size >= INT_MAX/2) + return AVERROR_INVALIDDATA; + str = av_mallocz(str_size + 1); + if (!str) + return AVERROR(ENOMEM); + ret = ffio_read_size(pb, str, str_size); + if (ret < 0) { + av_free(str); + return ret; + } + str[str_size] = 0; + } + if (c->trak_index < 0) { + metadata = c->fc->metadata; + c->fc->event_flags |= AVFMT_EVENT_FLAG_METADATA_UPDATED; + } + else { + metadata = c->fc->streams[c->trak_index]->metadata; + } + av_dict_set(&metadata, key, str, 0); + if (*language && strcmp(language, "und")) { + snprintf(key2, sizeof(key2), "%s-%s", key, language); + av_dict_set(&metadata, key2, str, 0); + } + av_freep(&str); + return 0; +} + static int mov_read_udta_string(MOVContext *c, AVIOContext *pb, MOVAtom atom) { char tmp_key[AV_FOURCC_MAX_STRING_SIZE] = {0}; @@ -354,6 +449,7 @@ int (*parse)(MOVContext*, AVIOContext*, unsigned, const char*) = NULL; int raw = 0; int num = 0; + AVDictionary *metadata; switch (atom.type) { case MKTAG( '@','P','R','M'): key = "premiere_version"; raw = 1; break; @@ -364,35 +460,71 @@ case MKTAG( 'a','k','I','D'): key = "account_type"; parse = mov_metadata_int8_no_padding; break; case MKTAG( 'a','p','I','D'): key = "account_id"; break; + case MKTAG( 'a','t','I','D'): key = "artist_id"; + parse = mov_metadata_int32_no_padding; break; case MKTAG( 'c','a','t','g'): key = "category"; break; + case MKTAG( 'c','m','I','D'): key = "composer_id"; + parse = mov_metadata_int32_no_padding; break; + case MKTAG( 'c','n','I','D'): key = "content_id"; + parse = mov_metadata_int32_no_padding; break; case MKTAG( 'c','p','i','l'): key = "compilation"; parse = mov_metadata_int8_no_padding; break; - case MKTAG( 'c','p','r','t'): key = "copyright"; break; + case MKTAG( 'c','p','r','t'): + key = "copyright"; + if (!c->itunes_metadata) { + int64_t pos = avio_tell(pb); + int ret = mov_read_3gp_udta_tag(c, pb, atom); + if (ret != AVERROR_INVALIDDATA) + return ret; + avio_seek(pb, pos, SEEK_SET); + } + break; case MKTAG( 'd','e','s','c'): key = "description"; break; case MKTAG( 'd','i','s','k'): key = "disc"; parse = mov_metadata_track_or_disc_number; break; case MKTAG( 'e','g','i','d'): key = "episode_uid"; parse = mov_metadata_int8_no_padding; break; case MKTAG( 'F','I','R','M'): key = "firmware"; raw = 1; break; - case MKTAG( 'g','n','r','e'): key = "genre"; - parse = mov_metadata_gnre; break; + case MKTAG( 'g','e','I','D'): key = "genre_id"; + parse = mov_metadata_int32_no_padding; break; + case MKTAG( 'g','n','r','e'): + key = "genre"; + parse = mov_metadata_gnre; + if (!c->itunes_metadata) { + int64_t pos = avio_tell(pb); + int ret = mov_read_3gp_udta_tag(c, pb, atom); + if (ret != AVERROR_INVALIDDATA) + return ret; + avio_seek(pb, pos, SEEK_SET); + } + break; case MKTAG( 'h','d','v','d'): key = "hd_video"; parse = mov_metadata_int8_no_padding; break; case MKTAG( 'H','M','M','T'): return mov_metadata_hmmt(c, pb, atom.size); + case MKTAG( 'i','t','n','u'): key = "itunes_u"; + parse = mov_metadata_int8_no_padding; break; case MKTAG( 'k','e','y','w'): key = "keywords"; break; case MKTAG( 'l','d','e','s'): key = "synopsis"; break; case MKTAG( 'l','o','c','i'): return mov_metadata_loci(c, pb, atom.size); case MKTAG( 'm','a','n','u'): key = "make"; break; case MKTAG( 'm','o','d','l'): key = "model"; break; + case MKTAG( 'n','a','m','e'): key = "title"; raw = 1; break; case MKTAG( 'p','c','s','t'): key = "podcast"; parse = mov_metadata_int8_no_padding; break; case MKTAG( 'p','g','a','p'): key = "gapless_playback"; parse = mov_metadata_int8_no_padding; break; + case MKTAG( 'p','l','I','D'): key = "playlist_id"; + parse = mov_metadata_int64_no_padding; break; case MKTAG( 'p','u','r','d'): key = "purchase_date"; break; case MKTAG( 'r','t','n','g'): key = "rating"; parse = mov_metadata_int8_no_padding; break; + case MKTAG( 's','f','I','D'): key = "itunes_country"; + parse = mov_metadata_int32_no_padding; break; + case MKTAG( 's','d','e','s'): key = "series_description"; break; + case MKTAG( 's','h','w','m'): key = "show_work_and_movement"; + parse = mov_metadata_int8_no_padding; break; case MKTAG( 's','o','a','a'): key = "sort_album_artist"; break; case MKTAG( 's','o','a','l'): key = "sort_album"; break; case MKTAG( 's','o','a','r'): key = "sort_artist"; break; @@ -401,6 +533,8 @@ case MKTAG( 's','o','s','n'): key = "sort_show"; break; case MKTAG( 's','t','i','k'): key = "media_type"; parse = mov_metadata_int8_no_padding; break; + case MKTAG( 't','m','p','o'): key = "tmpo"; + parse = mov_metadata_int16_no_padding; break; case MKTAG( 't','r','k','n'): key = "track"; parse = mov_metadata_track_or_disc_number; break; case MKTAG( 't','v','e','n'): key = "episode_id"; break; @@ -410,17 +544,23 @@ case MKTAG( 't','v','s','h'): key = "show"; break; case MKTAG( 't','v','s','n'): key = "season_number"; parse = mov_metadata_int8_bypass_padding; break; + case MKTAG( 'x','i','d',' '): key = "xid"; break; case MKTAG(0xa9,'A','R','T'): key = "artist"; break; case MKTAG(0xa9,'P','R','D'): key = "producer"; break; case MKTAG(0xa9,'a','l','b'): key = "album"; break; - case MKTAG(0xa9,'a','u','t'): key = "artist"; break; + case MKTAG(0xa9,'a','r','d'): key = "art_director"; break; + case MKTAG(0xa9,'a','r','g'): key = "arranger"; break; + case MKTAG(0xa9,'a','u','t'): key = "author"; break; + case MKTAG(0xa9,'c','a','k'): key = "acknowledgement"; break; case MKTAG(0xa9,'c','h','p'): key = "chapter"; break; case MKTAG(0xa9,'c','m','t'): key = "comment"; break; case MKTAG(0xa9,'c','o','m'): key = "composer"; break; + case MKTAG(0xa9,'c','o','n'): key = "conductor"; break; case MKTAG(0xa9,'c','p','y'): key = "copyright"; break; case MKTAG(0xa9,'d','a','y'): key = "date"; break; case MKTAG(0xa9,'d','i','r'): key = "director"; break; case MKTAG(0xa9,'d','i','s'): key = "disclaimer"; break; + case MKTAG(0xa9,'d','e','s'): key = "song_description"; break; case MKTAG(0xa9,'e','d','1'): key = "edit_date"; break; case MKTAG(0xa9,'e','n','c'): key = "encoder"; break; case MKTAG(0xa9,'f','m','t'): key = "original_format"; break; @@ -428,23 +568,45 @@ case MKTAG(0xa9,'g','r','p'): key = "grouping"; break; case MKTAG(0xa9,'h','s','t'): key = "host_computer"; break; case MKTAG(0xa9,'i','n','f'): key = "comment"; break; + case MKTAG(0xa9,'l','n','t'): key = "linear_notes"; break; case MKTAG(0xa9,'l','y','r'): key = "lyrics"; break; case MKTAG(0xa9,'m','a','k'): key = "make"; break; case MKTAG(0xa9,'m','o','d'): key = "model"; break; + case MKTAG(0xa9,'m','v','n'): key = "movement_name"; break; + case MKTAG(0xa9,'m','v','i'): key = "movement_number"; + parse = mov_metadata_int16_no_padding; break; + case MKTAG(0xa9,'m','v','c'): key = "movement_count"; + parse = mov_metadata_int16_no_padding; break; case MKTAG(0xa9,'n','a','m'): key = "title"; break; case MKTAG(0xa9,'o','p','e'): key = "original_artist"; break; + case MKTAG(0xa9,'p','h','g'): key = "phonogram_rights"; break; case MKTAG(0xa9,'p','r','d'): key = "producer"; break; case MKTAG(0xa9,'p','r','f'): key = "performers"; break; + case MKTAG(0xa9,'p','u','b'): key = "publisher"; break; case MKTAG(0xa9,'r','e','q'): key = "playback_requirements"; break; + case MKTAG(0xa9,'s','n','e'): key = "sound_engineer"; break; + case MKTAG(0xa9,'s','o','l'): key = "soloist"; break; case MKTAG(0xa9,'s','r','c'): key = "original_source"; break; case MKTAG(0xa9,'s','t','3'): key = "subtitle"; break; case MKTAG(0xa9,'s','w','r'): key = "encoder"; break; + case MKTAG(0xa9,'t','h','x'): key = "thanks"; break; case MKTAG(0xa9,'t','o','o'): key = "encoder"; break; case MKTAG(0xa9,'t','r','k'): key = "track"; break; case MKTAG(0xa9,'u','r','l'): key = "URL"; break; + case MKTAG(0xa9,'w','r','k'): key = "work_name"; break; case MKTAG(0xa9,'w','r','n'): key = "warning"; break; case MKTAG(0xa9,'w','r','t'): key = "composer"; break; + case MKTAG(0xa9,'x','p','d'): key = "executive_producer"; break; case MKTAG(0xa9,'x','y','z'): key = "location"; break; + case MKTAG( 'a','l','b','m'): + case MKTAG( 'a','u','t','h'): + case MKTAG( 'd','s','c','p'): + case MKTAG( 'p','e','r','f'): + case MKTAG( 't','i','t','l'): + case MKTAG( 'y','r','r','c'): + if (!c->itunes_metadata) { + return mov_read_3gp_udta_tag(c, pb, atom); + } } retry: if (c->itunes_metadata && atom.size > 8) { @@ -571,17 +733,23 @@ } str[str_size] = 0; } - c->fc->event_flags |= AVFMT_EVENT_FLAG_METADATA_UPDATED; - av_dict_set(&c->fc->metadata, key, str, 0); + if (c->trak_index < 0) { + metadata = c->fc->metadata; + c->fc->event_flags |= AVFMT_EVENT_FLAG_METADATA_UPDATED; + if (!strcmp(key, "encoder")) { + int major, minor, micro; + if (sscanf(str, "HandBrake %d.%d.%d", &major, &minor, µ) == 3) { + c->handbrake_version = 1000000*major + 1000*minor + micro; + } + } + } + else { + metadata = c->fc->streams[c->trak_index]->metadata; + } + av_dict_set(&metadata, key, str, 0); if (*language && strcmp(language, "und")) { snprintf(key2, sizeof(key2), "%s-%s", key, language); - av_dict_set(&c->fc->metadata, key2, str, 0); - } - if (!strcmp(key, "encoder")) { - int major, minor, micro; - if (sscanf(str, "HandBrake %d.%d.%d", &major, &minor, µ) == 3) { - c->handbrake_version = 1000000*major + 1000*minor + micro; - } + av_dict_set(&metadata, key2, str, 0); } } @@ -9088,6 +9256,23 @@ return ret; } +static int mov_read_fall(MOVContext *c, AVIOContext *pb, MOVAtom atom) +{ + AVStream *st; + MOVStreamContext *sc; + + if (c->fc->nb_streams < 1) + return 0; + st = c->fc->streams[c->fc->nb_streams-1]; + sc = st->priv_data; + + sc->fallback = avio_rb32(pb); + sc->has_fallback = 1; + + return 0; +} + + static const MOVParseTableEntry mov_default_parse_table[] = { { MKTAG('A','C','L','R'), mov_read_aclr }, { MKTAG('A','P','R','G'), mov_read_avid }, @@ -9190,6 +9375,7 @@ { MKTAG('v','p','c','C'), mov_read_vpcc }, { MKTAG('m','d','c','v'), mov_read_mdcv }, { MKTAG('c','l','l','i'), mov_read_clli }, +{ MKTAG('f','a','l','l'), mov_read_fall }, { MKTAG('d','v','c','C'), mov_read_dvcc_dvvc }, { MKTAG('d','v','v','C'), mov_read_dvcc_dvvc }, { MKTAG('d','v','w','C'), mov_read_dvcc_dvvc }, @@ -10374,6 +10560,23 @@ err = ff_replaygain_export(st, s->metadata); if (err < 0) return err; + if (sc->has_fallback) { + for (j = 0; j < s->nb_streams; j++) { + if (s->streams[j]->id == sc->fallback) { + AVPacketSideData *sd; + int *fallback; + sd = av_packet_side_data_new(&st->codecpar->coded_side_data, + &st->codecpar->nb_coded_side_data, + AV_PKT_DATA_FALLBACK_TRACK, + sizeof(int), 0); + if (!sd) + return AVERROR(ENOMEM); + fallback = (int*)sd->data; + *fallback = j; + break; + } + } + } break; case AVMEDIA_TYPE_VIDEO: if (sc->display_matrix) { diff -Naur ffmpeg-7.1.2.old/libavformat/movenc.c ffmpeg-7.1.2/libavformat/movenc.c --- ffmpeg-7.1.2.old/libavformat/movenc.c 2025-10-27 10:07:00.933477027 +0100 +++ ffmpeg-7.1.2/libavformat/movenc.c 2025-10-27 10:07:02.463726465 +0100 @@ -28,6 +28,7 @@ #include "movenc.h" #include "avformat.h" +#include "avlanguage.h" #include "avio_internal.h" #include "dovi_isom.h" #include "riff.h" @@ -393,6 +394,8 @@ uint16_t chan_loc; /* if there is no dependent substream, then one bit reserved instead */ } substream[1]; /* TODO: support 8 independent substreams */ + /* indicates the decoding complexity, 8 bits */ + uint8_t complexity_index_type_a; }; static int mov_write_ac3_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *track) @@ -474,6 +477,8 @@ info->data_rate = FFMAX(info->data_rate, hdr->bit_rate / 1000); info->ac3_bit_rate_code = FFMAX(info->ac3_bit_rate_code, hdr->ac3_bit_rate_code); + info->complexity_index_type_a = hdr->complexity_index_type_a; + num_blocks = hdr->num_blocks; if (!info->ec3_done) { @@ -532,8 +537,6 @@ int parent = hdr->substreamid; while (cumul_size != pkt->size) { - GetBitContext gbc; - int i; ret = avpriv_ac3_parse_header(&hdr, pkt->data + cumul_size, pkt->size - cumul_size); if (ret < 0) goto end; @@ -544,20 +547,9 @@ info->substream[parent].num_dep_sub++; ret /= 8; - /* header is parsed up to lfeon, but custom channel map may be needed */ - init_get_bits8(&gbc, pkt->data + cumul_size + ret, pkt->size - cumul_size - ret); - /* skip bsid */ - skip_bits(&gbc, 5); - /* skip volume control params */ - for (i = 0; i < (hdr->channel_mode ? 1 : 2); i++) { - skip_bits(&gbc, 5); // skip dialog normalization - if (get_bits1(&gbc)) { - skip_bits(&gbc, 8); // skip compression gain word - } - } /* get the dependent stream channel map, if exists */ - if (get_bits1(&gbc)) - info->substream[parent].chan_loc |= (get_bits(&gbc, 16) >> 5) & 0x1f; + if (hdr->channel_map_present) + info->substream[parent].chan_loc |= (hdr->channel_map >> 5) & 0x1f; else info->substream[parent].chan_loc |= hdr->channel_mode; cumul_size += hdr->frame_size; @@ -614,7 +606,7 @@ } info = track->eac3_priv; - size = 2 + ((32 * (info->num_ind_sub + 1) + 7) >> 3); + size = 2 + (4 * (info->num_ind_sub + 1)) + (2 * !!info->complexity_index_type_a); buf = av_malloc(size); if (!buf) { return AVERROR(ENOMEM); @@ -639,6 +631,11 @@ put_bits(&pbc, 9, info->substream[i].chan_loc); } } + if (info->complexity_index_type_a) { + put_bits(&pbc, 7, 0); /* reserved */ + put_bits(&pbc, 1, 1); // flag_eac3_extension_type_a + put_bits(&pbc, 8, info->complexity_index_type_a); + } flush_put_bits(&pbc); size = put_bytes_output(&pbc); @@ -4055,6 +4052,41 @@ return len + 24; } +static uint16_t language_code(const char *str) +{ + return (((str[0] - 0x60) & 0x1F) << 10) + + (((str[1] - 0x60) & 0x1F) << 5) + + (( str[2] - 0x60) & 0x1F); +} +static int mov_write_3gp_udta_tag(AVIOContext *pb, AVDictionary *metadata, + const char *tag, const char *str) +{ + int64_t pos = avio_tell(pb); + AVDictionaryEntry *t = av_dict_get(metadata, str, NULL, 0); + if (!t || !utf8len(t->value)) + return 0; + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, tag); /* type */ + avio_wb32(pb, 0); /* version + flags */ + if (!strcmp(tag, "yrrc")) + avio_wb16(pb, atoi(t->value)); + else { + int lang = 0, len; + len = strlen(t->key); + if (t->key[len - 4] == '-') { + lang = ff_mov_iso639_to_lang(&t->key[len - 3], 1); + } + if (!lang) + lang = ff_mov_iso639_to_lang("und", 1); + avio_wb16(pb, lang); /* language */ + avio_write(pb, t->value, strlen(t->value) + 1); /* UTF8 string value */ + if (!strcmp(tag, "albm") && + (t = av_dict_get(metadata, "track", NULL, 0))) + avio_w8(pb, atoi(t->value)); + } + return update_size(pb, pos); +} + static int mov_write_track_metadata(AVIOContext *pb, AVStream *st, const char *tag, const char *str) { @@ -4129,8 +4161,23 @@ if (ret < 0) return ret; - if (mov->mode & (MODE_MP4|MODE_MOV)) + if (mov->mode & (MODE_MP4|MODE_MOV)) { + AVDictionaryEntry *t = NULL; + int und = 0; + mov_write_track_metadata(pb_buf, st, "name", "title"); + while ((t = av_dict_get(st->metadata, "title-", t, AV_DICT_IGNORE_SUFFIX))) { + int len = strlen(t->key); + if (len == 10 && + ff_convert_lang_to(&t->key[len - 3], AV_LANG_ISO639_2_BIBL)) { + mov_write_3gp_udta_tag(pb_buf, st->metadata, "titl", t->key); + if (!strcmp("und", &t->key[len - 3])) + und = 1; + } + } + if (!und) + mov_write_3gp_udta_tag(pb_buf, st->metadata, "titl", "title"); + } if (mov->mode & MODE_MP4) { if ((ret = mov_write_track_kinds(pb_buf, st)) < 0) @@ -4505,20 +4552,66 @@ return size; } +/* iTunes iTMF metadata */ +static int mov_write_itmf_tag(AVIOContext *pb, AVFormatContext *s, + const char *name, const char *tag) +{ + AVDictionaryEntry *t = av_dict_get(s->metadata, tag, NULL, 0); + uint64_t size = 0; + if (t) { + size_t name_len = strlen(name); + size_t value_len = strlen(t->value); + size = 8 + 28 + 12 + name_len + 16 + value_len; + + if (size > INT_MAX) { + av_log(s, AV_LOG_WARNING, + "Unable to write %s, invalid value len %ld\n", + name, value_len); + return 0; + } + + avio_wb32(pb, size); // size + ffio_wfourcc(pb, "----"); // type + + avio_wb32(pb, 28); // size + ffio_wfourcc(pb, "mean"); // type + avio_wb32(pb, 0); // version + flags + avio_write(pb, "com.apple.iTunes", 16); + + avio_wb32(pb, 12 + name_len); // size + ffio_wfourcc(pb, "name"); // type + avio_wb32(pb, 0); // version + flags + avio_write(pb, name, name_len); + + avio_wb32(pb, 16 + value_len); // size + ffio_wfourcc(pb, "data"); // type + avio_wb16(pb, 0); // typeReserved + avio_w8(pb, 0); // typeSetIdentifier + avio_w8(pb, 1); // typeCode = UTF-8 + avio_wb32(pb, 0); // locale + avio_write(pb, t->value, value_len); + } + return size; +} + static int mov_write_int8_metadata(AVFormatContext *s, AVIOContext *pb, const char *name, const char *tag, int len) { AVDictionaryEntry *t = NULL; - uint8_t num; + uint64_t num; int size = 24 + len; - if (len != 1 && len != 4) + if (len != 1 && len != 4 && + len != 2 && len != 8) return -1; if (!(t = av_dict_get(s->metadata, tag, NULL, 0))) return 0; - num = atoi(t->value); + if (len <= 4) + num = atoi(t->value); + else + num = atol(t->value); avio_wb32(pb, size); ffio_wfourcc(pb, name); @@ -4526,7 +4619,9 @@ ffio_wfourcc(pb, "data"); avio_wb32(pb, 0x15); avio_wb32(pb, 0); - if (len==4) avio_wb32(pb, num); + if (len==8) avio_wb64(pb, num); + else if (len==4) avio_wb32(pb, num); + else if (len==2) avio_wb16(pb, num); else avio_w8 (pb, num); return size; @@ -4582,6 +4677,8 @@ mov_write_string_metadata(s, pb, "\251lyr", "lyrics" , 1); mov_write_string_metadata(s, pb, "desc", "description",1); mov_write_string_metadata(s, pb, "ldes", "synopsis" , 1); + mov_write_string_metadata(s, pb, "sdes", "series_description", 1); + mov_write_string_metadata(s, pb, "rtng", "rating", 1); mov_write_string_metadata(s, pb, "tvsh", "show" , 1); mov_write_string_metadata(s, pb, "tven", "episode_id",1); mov_write_string_metadata(s, pb, "tvnn", "network" , 1); @@ -4592,10 +4689,67 @@ mov_write_int8_metadata (s, pb, "hdvd", "hd_video", 1); mov_write_int8_metadata (s, pb, "pgap", "gapless_playback",1); mov_write_int8_metadata (s, pb, "cpil", "compilation", 1); + + mov_write_string_metadata(s, pb, "\251st3", "subtitle" , 1); + mov_write_string_metadata(s, pb, "\251des", "song_description", 1); + mov_write_string_metadata(s, pb, "\251dir", "director" , 1); + mov_write_string_metadata(s, pb, "\251ard", "art_director" , 1); + mov_write_string_metadata(s, pb, "\251arg", "arranger" , 1); + mov_write_string_metadata(s, pb, "\251aut", "author" , 1); + mov_write_string_metadata(s, pb, "\251cak", "acknowledgement" , 1); + mov_write_string_metadata(s, pb, "\251con", "conductor" , 1); + + mov_write_string_metadata(s, pb, "\251wrk", "work_name" , 1); + mov_write_string_metadata(s, pb, "\251mvn", "movement_name" , 1); + mov_write_int8_metadata (s, pb, "\251mvi", "movement_number", 2); + mov_write_int8_metadata (s, pb, "\251mvc", "movement_count" , 2); + mov_write_int8_metadata (s, pb, "shwm", "show_work_and_movement", 1); + + mov_write_string_metadata(s, pb, "\251lnt", "linear_notes" , 1); + mov_write_string_metadata(s, pb, "\251mak", "make" , 1); // Record company + mov_write_string_metadata(s, pb, "\251ope", "original_artist" , 1); + mov_write_string_metadata(s, pb, "\251phg", "phonogram_rights" , 1); + mov_write_string_metadata(s, pb, "\251prd", "producer" , 1); + mov_write_string_metadata(s, pb, "\251prf", "performers" , 1); + mov_write_string_metadata(s, pb, "\251pub", "publisher" , 1); + mov_write_string_metadata(s, pb, "\251sne", "sound_engineer" , 1); + mov_write_string_metadata(s, pb, "\251sol", "soloist" , 1); + mov_write_string_metadata(s, pb, "\251src", "original_source" , 1); // Credits + mov_write_string_metadata(s, pb, "\251thx", "thanks" , 1); + mov_write_string_metadata(s, pb, "\251url", "URL" , 1); // Online extras + mov_write_string_metadata(s, pb, "\251xpd", "executive_producer", 1); + + mov_write_string_metadata(s, pb, "sonm", "sort_name" , 1); + mov_write_string_metadata(s, pb, "soar", "sort_artist" , 1); + mov_write_string_metadata(s, pb, "soaa", "sort_album_artist", 1); + mov_write_string_metadata(s, pb, "soal", "sort_album" , 1); + mov_write_string_metadata(s, pb, "soco", "sort_composer" , 1); + mov_write_string_metadata(s, pb, "sosn", "sort_show" , 1); + + mov_write_string_metadata(s, pb, "\251enc", "encoder" , 1); // Encoded by + mov_write_string_metadata(s, pb, "purd", "purchase_date" , 1); + + mov_write_int8_metadata (s, pb, "itnu", "itunes_u" , 1); + mov_write_int8_metadata (s, pb, "pcst", "podcast" , 1); + mov_write_string_metadata(s, pb, "catg", "category" , 1); + + mov_write_string_metadata(s, pb, "apID", "account_id" , 1); + mov_write_int8_metadata (s, pb, "akID", "account_type" , 1); + mov_write_int8_metadata (s, pb, "sfID", "itunes_country", 4); + mov_write_int8_metadata (s, pb, "cnID", "content_id" , 4); + mov_write_int8_metadata (s, pb, "atID", "artist_id" , 4); + mov_write_int8_metadata (s, pb, "plID", "playlist_id" , 8); + mov_write_int8_metadata (s, pb, "geID", "genre_id" , 4); + mov_write_int8_metadata (s, pb, "cmID", "composer_id" , 4); + mov_write_string_metadata(s, pb, "xid ", "xid" , 1); + mov_write_covr(pb, s); mov_write_trkn_tag(pb, mov, s, 0); // track number mov_write_trkn_tag(pb, mov, s, 1); // disc number mov_write_tmpo_tag(pb, s); + mov_write_itmf_tag(pb, s, "iTunEXTC", "iTunEXTC"); + mov_write_itmf_tag(pb, s, "iTunMOVI", "iTunMOVI"); + return update_size(pb, pos); } @@ -4727,35 +4881,6 @@ return 0; } -static uint16_t language_code(const char *str) -{ - return (((str[0] - 0x60) & 0x1F) << 10) + - (((str[1] - 0x60) & 0x1F) << 5) + - (( str[2] - 0x60) & 0x1F); -} - -static int mov_write_3gp_udta_tag(AVIOContext *pb, AVFormatContext *s, - const char *tag, const char *str) -{ - int64_t pos = avio_tell(pb); - AVDictionaryEntry *t = av_dict_get(s->metadata, str, NULL, 0); - if (!t || !utf8len(t->value)) - return 0; - avio_wb32(pb, 0); /* size */ - ffio_wfourcc(pb, tag); /* type */ - avio_wb32(pb, 0); /* version + flags */ - if (!strcmp(tag, "yrrc")) - avio_wb16(pb, atoi(t->value)); - else { - avio_wb16(pb, language_code("eng")); /* language */ - avio_write(pb, t->value, strlen(t->value) + 1); /* UTF8 string value */ - if (!strcmp(tag, "albm") && - (t = av_dict_get(s->metadata, "track", NULL, 0))) - avio_w8(pb, atoi(t->value)); - } - return update_size(pb, pos); -} - static int mov_write_chpl_tag(AVIOContext *pb, AVFormatContext *s) { int64_t pos = avio_tell(pb); @@ -4794,14 +4919,14 @@ return ret; if (mov->mode & MODE_3GP) { - mov_write_3gp_udta_tag(pb_buf, s, "perf", "artist"); - mov_write_3gp_udta_tag(pb_buf, s, "titl", "title"); - mov_write_3gp_udta_tag(pb_buf, s, "auth", "author"); - mov_write_3gp_udta_tag(pb_buf, s, "gnre", "genre"); - mov_write_3gp_udta_tag(pb_buf, s, "dscp", "comment"); - mov_write_3gp_udta_tag(pb_buf, s, "albm", "album"); - mov_write_3gp_udta_tag(pb_buf, s, "cprt", "copyright"); - mov_write_3gp_udta_tag(pb_buf, s, "yrrc", "date"); + mov_write_3gp_udta_tag(pb_buf, s->metadata, "perf", "artist"); + mov_write_3gp_udta_tag(pb_buf, s->metadata, "titl", "title"); + mov_write_3gp_udta_tag(pb_buf, s->metadata, "auth", "author"); + mov_write_3gp_udta_tag(pb_buf, s->metadata, "gnre", "genre"); + mov_write_3gp_udta_tag(pb_buf, s->metadata, "dscp", "comment"); + mov_write_3gp_udta_tag(pb_buf, s->metadata, "albm", "album"); + mov_write_3gp_udta_tag(pb_buf, s->metadata, "cprt", "copyright"); + mov_write_3gp_udta_tag(pb_buf, s->metadata, "yrrc", "date"); mov_write_loci_tag(s, pb_buf); } else if (mov->mode == MODE_MOV && !(mov->flags & FF_MOV_FLAG_USE_MDTA)) { // the title field breaks gtkpod with mp4 and my suspicion is that stuff is not valid in mp4 mov_write_string_metadata(s, pb_buf, "\251ART", "artist", 0); diff -Naur ffmpeg-7.1.2.old/libavutil/frame.c ffmpeg-7.1.2/libavutil/frame.c --- ffmpeg-7.1.2.old/libavutil/frame.c 2025-10-27 10:07:00.804475889 +0100 +++ ffmpeg-7.1.2/libavutil/frame.c 2025-10-27 10:07:02.436386459 +0100 @@ -45,6 +45,7 @@ [AV_FRAME_DATA_FILM_GRAIN_PARAMS] = { "Film grain parameters" }, [AV_FRAME_DATA_DETECTION_BBOXES] = { "Bounding boxes for object detection and classification" }, [AV_FRAME_DATA_DOVI_RPU_BUFFER] = { "Dolby Vision RPU Data" }, + [AV_FRAME_DATA_DOVI_RPU_BUFFER_T35] = { "Dolby Vision RPU ITU T35 Data" }, [AV_FRAME_DATA_DOVI_METADATA] = { "Dolby Vision Metadata" }, [AV_FRAME_DATA_LCEVC] = { "LCEVC NAL data" }, [AV_FRAME_DATA_VIEW_ID] = { "View ID" }, diff -Naur ffmpeg-7.1.2.old/libavutil/frame.h ffmpeg-7.1.2/libavutil/frame.h --- ffmpeg-7.1.2.old/libavutil/frame.h 2025-10-27 10:07:00.786475730 +0100 +++ ffmpeg-7.1.2/libavutil/frame.h 2025-10-27 10:07:02.436480126 +0100 @@ -201,6 +201,12 @@ AV_FRAME_DATA_DOVI_RPU_BUFFER, /** + * Dolby Vision RPU ITU T35 raw data, suitable for passing to SVT-AV1 + * or other libraries. Array of uint8_t. + */ + AV_FRAME_DATA_DOVI_RPU_BUFFER_T35, + + /** * Parsed Dolby Vision metadata, suitable for passing to a software * implementation. The payload is the AVDOVIMetadata struct defined in * libavutil/dovi_meta.h. diff -Naur ffmpeg-7.1.2.old/libavutil/hwcontext_videotoolbox.c ffmpeg-7.1.2/libavutil/hwcontext_videotoolbox.c --- ffmpeg-7.1.2.old/libavutil/hwcontext_videotoolbox.c 2025-10-27 10:07:00.813475968 +0100 +++ ffmpeg-7.1.2/libavutil/hwcontext_videotoolbox.c 2025-10-27 10:07:02.457067390 +0100 @@ -183,7 +183,8 @@ VTFramesContext *fctx = ctx->hwctx; AVVTFramesContext *hw_ctx = &fctx->p; CVReturn err; - CFNumberRef w, h, pixfmt; + CFNumberRef w, h, extend_w_num, extend_h_num, pixfmt; + int extend_w, extend_h; uint32_t cv_pixfmt; CFMutableDictionaryRef attributes, iosurface_properties; @@ -216,6 +217,15 @@ CFRelease(w); CFRelease(h); + extend_w = FFALIGN(ctx->width, 16) - ctx->width; + extend_h = FFALIGN(ctx->height, 16) - ctx->height; + extend_w_num = CFNumberCreate(NULL, kCFNumberSInt32Type, &extend_w); + extend_h_num = CFNumberCreate(NULL, kCFNumberSInt32Type, &extend_h); + CFDictionarySetValue(attributes, kCVPixelBufferExtendedPixelsRightKey, extend_w_num); + CFDictionarySetValue(attributes, kCVPixelBufferExtendedPixelsBottomKey, extend_h_num); + CFRelease(extend_w_num); + CFRelease(extend_h_num); + err = CVPixelBufferPoolCreate( NULL, NULL, diff -Naur ffmpeg-7.1.2.old/libswscale/swscale_unscaled.c ffmpeg-7.1.2/libswscale/swscale_unscaled.c --- ffmpeg-7.1.2.old/libswscale/swscale_unscaled.c 2025-10-27 10:07:00.644474477 +0100 +++ ffmpeg-7.1.2/libswscale/swscale_unscaled.c 2025-10-27 10:07:02.433490262 +0100 @@ -341,7 +341,7 @@ const uint8_t *tsrc0 = src[0]; for (x = c->srcW; x > 0; x--) { t = *tsrc0++; - output_pixel(tdstY++, t | (t << 8)); + output_pixel(tdstY++, (t << 8)); } src[0] += srcStride[0]; dstY += dstStride[0] / 2; @@ -352,9 +352,9 @@ const uint8_t *tsrc2 = src[2]; for (x = c->srcW / 2; x > 0; x--) { t = *tsrc1++; - output_pixel(tdstUV++, t | (t << 8)); + output_pixel(tdstUV++, (t << 8)); t = *tsrc2++; - output_pixel(tdstUV++, t | (t << 8)); + output_pixel(tdstUV++, (t << 8)); } src[1] += srcStride[1]; src[2] += srcStride[2];