2024-04-12 13:24:22 +02:00
|
|
|
/*
|
|
|
|
* Dolby Vision RPU encoder
|
|
|
|
*
|
|
|
|
* Copyright (C) 2024 Niklas Haas
|
|
|
|
*
|
|
|
|
* 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/avassert.h"
|
2024-03-30 19:12:35 +02:00
|
|
|
#include "libavutil/crc.h"
|
2024-04-12 13:24:22 +02:00
|
|
|
#include "libavutil/mem.h"
|
|
|
|
|
|
|
|
#include "avcodec.h"
|
|
|
|
#include "dovi_rpu.h"
|
2024-03-30 19:12:35 +02:00
|
|
|
#include "itut35.h"
|
|
|
|
#include "put_bits.h"
|
|
|
|
#include "put_golomb.h"
|
2024-10-15 22:37:56 +02:00
|
|
|
#include "libavutil/refstruct.h"
|
2024-04-12 13:24:22 +02:00
|
|
|
|
|
|
|
static struct {
|
|
|
|
uint64_t pps; // maximum pixels per second
|
|
|
|
int width; // maximum width
|
|
|
|
int main; // maximum bitrate in main tier
|
|
|
|
int high; // maximum bitrate in high tier
|
|
|
|
} dv_levels[] = {
|
|
|
|
[1] = {1280*720*24, 1280, 20, 50},
|
|
|
|
[2] = {1280*720*30, 1280, 20, 50},
|
|
|
|
[3] = {1920*1080*24, 1920, 20, 70},
|
|
|
|
[4] = {1920*1080*30, 2560, 20, 70},
|
|
|
|
[5] = {1920*1080*60, 3840, 20, 70},
|
|
|
|
[6] = {3840*2160*24, 3840, 25, 130},
|
|
|
|
[7] = {3840*2160*30, 3840, 25, 130},
|
|
|
|
[8] = {3840*2160*48, 3840, 40, 130},
|
|
|
|
[9] = {3840*2160*60, 3840, 40, 130},
|
|
|
|
[10] = {3840*2160*120, 3840, 60, 240},
|
|
|
|
[11] = {3840*2160*120, 7680, 60, 240},
|
|
|
|
[12] = {7680*4320*60, 7680, 120, 450},
|
|
|
|
[13] = {7680*4320*120u, 7680, 240, 800},
|
|
|
|
};
|
|
|
|
|
2024-07-15 13:26:13 +02:00
|
|
|
int ff_dovi_configure_ext(DOVIContext *s, AVCodecParameters *codecpar,
|
|
|
|
const AVDOVIMetadata *metadata,
|
2024-07-16 15:04:31 +02:00
|
|
|
enum AVDOVICompression compression,
|
2024-07-15 13:26:13 +02:00
|
|
|
int strict_std_compliance)
|
2024-04-12 13:24:22 +02:00
|
|
|
{
|
|
|
|
AVDOVIDecoderConfigurationRecord *cfg;
|
|
|
|
const AVDOVIRpuDataHeader *hdr = NULL;
|
2024-05-03 22:43:22 +02:00
|
|
|
int dv_profile, dv_level, bl_compat_id = -1;
|
2024-04-12 13:24:22 +02:00
|
|
|
size_t cfg_size;
|
|
|
|
uint64_t pps;
|
|
|
|
|
|
|
|
if (!s->enable)
|
|
|
|
goto skip;
|
|
|
|
|
2024-07-15 13:26:13 +02:00
|
|
|
if (metadata)
|
|
|
|
hdr = av_dovi_get_header(metadata);
|
2024-04-12 13:24:22 +02:00
|
|
|
|
|
|
|
if (s->enable == FF_DOVI_AUTOMATIC && !hdr)
|
|
|
|
goto skip;
|
|
|
|
|
2024-07-16 15:04:31 +02:00
|
|
|
if (compression == AV_DOVI_COMPRESSION_RESERVED ||
|
|
|
|
compression > AV_DOVI_COMPRESSION_EXTENDED)
|
|
|
|
return AVERROR(EINVAL);
|
|
|
|
|
2024-07-15 13:26:13 +02:00
|
|
|
switch (codecpar->codec_id) {
|
2024-04-12 13:24:22 +02:00
|
|
|
case AV_CODEC_ID_AV1: dv_profile = 10; break;
|
|
|
|
case AV_CODEC_ID_H264: dv_profile = 9; break;
|
2024-07-15 13:36:07 +02:00
|
|
|
case AV_CODEC_ID_HEVC:
|
|
|
|
if (hdr) {
|
|
|
|
dv_profile = ff_dovi_guess_profile_hevc(hdr);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* This is likely to be proprietary IPTPQc2 */
|
|
|
|
if (codecpar->color_space == AVCOL_SPC_IPT_C2 ||
|
|
|
|
(codecpar->color_space == AVCOL_SPC_UNSPECIFIED &&
|
|
|
|
codecpar->color_trc == AVCOL_TRC_UNSPECIFIED))
|
|
|
|
dv_profile = 5;
|
|
|
|
else
|
|
|
|
dv_profile = 8;
|
|
|
|
break;
|
2024-04-12 13:24:22 +02:00
|
|
|
default:
|
|
|
|
/* No other encoder should be calling this! */
|
|
|
|
av_assert0(0);
|
|
|
|
return AVERROR_BUG;
|
|
|
|
}
|
|
|
|
|
2024-07-15 13:26:13 +02:00
|
|
|
if (strict_std_compliance > FF_COMPLIANCE_UNOFFICIAL) {
|
2024-04-12 13:24:22 +02:00
|
|
|
if (dv_profile == 9) {
|
2024-07-15 13:26:13 +02:00
|
|
|
if (codecpar->format != AV_PIX_FMT_YUV420P)
|
2024-04-12 13:24:22 +02:00
|
|
|
dv_profile = 0;
|
|
|
|
} else {
|
2024-07-15 13:26:13 +02:00
|
|
|
if (codecpar->format != AV_PIX_FMT_YUV420P10)
|
2024-04-12 13:24:22 +02:00
|
|
|
dv_profile = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (dv_profile) {
|
|
|
|
case 4: /* HEVC with enhancement layer */
|
|
|
|
case 7:
|
|
|
|
if (s->enable > 0) {
|
|
|
|
av_log(s->logctx, AV_LOG_ERROR, "Coding of Dolby Vision enhancement "
|
|
|
|
"layers is currently unsupported.");
|
|
|
|
return AVERROR_PATCHWELCOME;
|
|
|
|
} else {
|
|
|
|
goto skip;
|
|
|
|
}
|
|
|
|
case 5: /* HEVC with proprietary IPTPQc2 */
|
|
|
|
bl_compat_id = 0;
|
|
|
|
break;
|
|
|
|
case 10:
|
|
|
|
/* FIXME: check for proper H.273 tags once those are added */
|
|
|
|
if (hdr && hdr->bl_video_full_range_flag) {
|
|
|
|
/* AV1 with proprietary IPTPQc2 */
|
|
|
|
bl_compat_id = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* fall through */
|
|
|
|
case 8: /* HEVC (or AV1) with BL compatibility */
|
2024-07-15 13:26:13 +02:00
|
|
|
if (codecpar->color_space == AVCOL_SPC_BT2020_NCL &&
|
|
|
|
codecpar->color_primaries == AVCOL_PRI_BT2020 &&
|
|
|
|
codecpar->color_trc == AVCOL_TRC_SMPTE2084) {
|
2024-04-12 13:24:22 +02:00
|
|
|
bl_compat_id = 1;
|
2024-07-15 13:26:13 +02:00
|
|
|
} else if (codecpar->color_space == AVCOL_SPC_BT2020_NCL &&
|
|
|
|
codecpar->color_primaries == AVCOL_PRI_BT2020 &&
|
|
|
|
codecpar->color_trc == AVCOL_TRC_ARIB_STD_B67) {
|
2024-04-12 13:24:22 +02:00
|
|
|
bl_compat_id = 4;
|
2024-07-15 13:26:13 +02:00
|
|
|
} else if (codecpar->color_space == AVCOL_SPC_BT709 &&
|
|
|
|
codecpar->color_primaries == AVCOL_PRI_BT709 &&
|
|
|
|
codecpar->color_trc == AVCOL_TRC_BT709) {
|
2024-04-12 13:24:22 +02:00
|
|
|
bl_compat_id = 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!dv_profile || bl_compat_id < 0) {
|
|
|
|
if (s->enable > 0) {
|
|
|
|
av_log(s->logctx, AV_LOG_ERROR, "Dolby Vision enabled, but could "
|
2024-05-03 22:34:00 +02:00
|
|
|
"not determine profile and compatibility mode. Double-check "
|
2024-04-12 13:24:22 +02:00
|
|
|
"colorspace and format settings for compatibility?\n");
|
|
|
|
return AVERROR(EINVAL);
|
|
|
|
}
|
|
|
|
goto skip;
|
|
|
|
}
|
|
|
|
|
2024-07-16 15:04:31 +02:00
|
|
|
if (compression != AV_DOVI_COMPRESSION_NONE) {
|
|
|
|
if (dv_profile < 8 && strict_std_compliance > FF_COMPLIANCE_UNOFFICIAL) {
|
|
|
|
av_log(s->logctx, AV_LOG_ERROR, "Dolby Vision metadata compression "
|
|
|
|
"is not permitted for profiles 7 and earlier. (dv_profile: %d, "
|
|
|
|
"compression: %d)\n", dv_profile, compression);
|
|
|
|
return AVERROR(EINVAL);
|
|
|
|
} else if (compression == AV_DOVI_COMPRESSION_EXTENDED &&
|
|
|
|
strict_std_compliance > FF_COMPLIANCE_EXPERIMENTAL) {
|
|
|
|
av_log(s->logctx, AV_LOG_ERROR, "Dolby Vision extended metadata "
|
|
|
|
"compression is experimental and not supported by "
|
|
|
|
"devices.");
|
|
|
|
return AVERROR(EINVAL);
|
|
|
|
} else if (dv_profile == 8) {
|
|
|
|
av_log(s->logctx, AV_LOG_WARNING, "Dolby Vision metadata compression "
|
|
|
|
"for profile 8 is known to be unsupported by many devices, "
|
|
|
|
"use with caution.\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-15 13:26:13 +02:00
|
|
|
pps = codecpar->width * codecpar->height;
|
|
|
|
if (codecpar->framerate.num) {
|
|
|
|
pps = pps * codecpar->framerate.num / codecpar->framerate.den;
|
2024-04-12 13:24:22 +02:00
|
|
|
} else {
|
|
|
|
pps *= 25; /* sanity fallback */
|
|
|
|
}
|
|
|
|
|
|
|
|
dv_level = 0;
|
|
|
|
for (int i = 1; i < FF_ARRAY_ELEMS(dv_levels); i++) {
|
|
|
|
if (pps > dv_levels[i].pps)
|
|
|
|
continue;
|
2024-07-15 13:26:13 +02:00
|
|
|
if (codecpar->width > dv_levels[i].width)
|
2024-04-12 13:24:22 +02:00
|
|
|
continue;
|
|
|
|
/* In theory, we should also test the bitrate when known, and
|
|
|
|
* distinguish between main and high tier. In practice, just ignore
|
|
|
|
* the bitrate constraints and hope they work out. This would ideally
|
|
|
|
* be handled by either the encoder or muxer directly. */
|
|
|
|
dv_level = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!dv_level) {
|
2024-07-15 13:26:13 +02:00
|
|
|
if (strict_std_compliance >= FF_COMPLIANCE_STRICT) {
|
2024-04-12 13:24:22 +02:00
|
|
|
av_log(s->logctx, AV_LOG_ERROR, "Coded PPS (%"PRIu64") and width (%d) "
|
2024-07-15 13:26:13 +02:00
|
|
|
"exceed Dolby Vision limitations\n", pps, codecpar->width);
|
2024-04-12 13:24:22 +02:00
|
|
|
return AVERROR(EINVAL);
|
|
|
|
} else {
|
|
|
|
av_log(s->logctx, AV_LOG_WARNING, "Coded PPS (%"PRIu64") and width (%d) "
|
|
|
|
"exceed Dolby Vision limitations. Ignoring, resulting file "
|
2024-07-15 13:26:13 +02:00
|
|
|
"may be non-conforming.\n", pps, codecpar->width);
|
2024-04-12 13:24:22 +02:00
|
|
|
dv_level = FF_ARRAY_ELEMS(dv_levels) - 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cfg = av_dovi_alloc(&cfg_size);
|
|
|
|
if (!cfg)
|
|
|
|
return AVERROR(ENOMEM);
|
|
|
|
|
2024-07-15 13:26:13 +02:00
|
|
|
if (!av_packet_side_data_add(&codecpar->coded_side_data,
|
|
|
|
&codecpar->nb_coded_side_data,
|
2024-04-12 13:24:22 +02:00
|
|
|
AV_PKT_DATA_DOVI_CONF, cfg, cfg_size, 0)) {
|
|
|
|
av_free(cfg);
|
|
|
|
return AVERROR(ENOMEM);
|
|
|
|
}
|
|
|
|
|
|
|
|
cfg->dv_version_major = 1;
|
|
|
|
cfg->dv_version_minor = 0;
|
|
|
|
cfg->dv_profile = dv_profile;
|
|
|
|
cfg->dv_level = dv_level;
|
|
|
|
cfg->rpu_present_flag = 1;
|
|
|
|
cfg->el_present_flag = 0;
|
|
|
|
cfg->bl_present_flag = 1;
|
|
|
|
cfg->dv_bl_signal_compatibility_id = bl_compat_id;
|
2024-07-16 15:04:31 +02:00
|
|
|
cfg->dv_md_compression = compression;
|
2024-04-12 13:24:22 +02:00
|
|
|
|
|
|
|
s->cfg = *cfg;
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
skip:
|
|
|
|
s->cfg = (AVDOVIDecoderConfigurationRecord) {0};
|
|
|
|
return 0;
|
|
|
|
}
|
2024-03-30 19:12:35 +02:00
|
|
|
|
2024-07-15 13:26:13 +02:00
|
|
|
int ff_dovi_configure(DOVIContext *s, AVCodecContext *avctx)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
const AVFrameSideData *sd;
|
|
|
|
const AVDOVIMetadata *metadata = NULL;
|
|
|
|
AVCodecParameters *codecpar = avcodec_parameters_alloc();
|
|
|
|
if (!codecpar)
|
|
|
|
return AVERROR(ENOMEM);
|
|
|
|
|
|
|
|
ret = avcodec_parameters_from_context(codecpar, avctx);
|
|
|
|
if (ret < 0)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
sd = av_frame_side_data_get(avctx->decoded_side_data,
|
|
|
|
avctx->nb_decoded_side_data,
|
|
|
|
AV_FRAME_DATA_DOVI_METADATA);
|
|
|
|
if (sd)
|
|
|
|
metadata = (const AVDOVIMetadata *) sd->data;
|
|
|
|
|
2024-07-16 15:04:31 +02:00
|
|
|
/* Current encoders cannot handle metadata compression during encoding */
|
|
|
|
ret = ff_dovi_configure_ext(s, codecpar, metadata, AV_DOVI_COMPRESSION_NONE,
|
|
|
|
avctx->strict_std_compliance);
|
2024-07-15 13:26:13 +02:00
|
|
|
if (ret < 0)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
ret = avcodec_parameters_to_context(avctx, codecpar);
|
|
|
|
|
|
|
|
fail:
|
|
|
|
avcodec_parameters_free(&codecpar);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2024-07-14 18:18:42 +02:00
|
|
|
/* Compares only the static DM metadata parts of AVDOVIColorMetadata (excluding
|
|
|
|
* dm_metadata_id and scene_refresh_flag) */
|
|
|
|
static int cmp_dm_level0(const AVDOVIColorMetadata *dm1,
|
|
|
|
const AVDOVIColorMetadata *dm2)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
for (int i = 0; i < FF_ARRAY_ELEMS(dm1->ycc_to_rgb_matrix); i++) {
|
|
|
|
if ((ret = av_cmp_q(dm1->ycc_to_rgb_matrix[i], dm2->ycc_to_rgb_matrix[i])))
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < FF_ARRAY_ELEMS(dm1->ycc_to_rgb_offset); i++) {
|
|
|
|
if ((ret = av_cmp_q(dm1->ycc_to_rgb_offset[i], dm2->ycc_to_rgb_offset[i])))
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < FF_ARRAY_ELEMS(dm1->rgb_to_lms_matrix); i++) {
|
|
|
|
if ((ret = av_cmp_q(dm1->rgb_to_lms_matrix[i], dm2->rgb_to_lms_matrix[i])))
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return memcmp(&dm1->signal_eotf, &dm2->signal_eotf,
|
|
|
|
sizeof(AVDOVIColorMetadata) -offsetof(AVDOVIColorMetadata, signal_eotf));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Tries to re-use the static ext blocks. May reorder `ext->dm_static` */
|
|
|
|
static int try_reuse_ext(DOVIExt *ext, const AVDOVIMetadata *metadata)
|
|
|
|
{
|
|
|
|
int i, j, idx = 0;
|
|
|
|
|
|
|
|
for (i = 0; i < metadata->num_ext_blocks; i++) {
|
|
|
|
const AVDOVIDmData *dm = av_dovi_get_ext(metadata, i);
|
|
|
|
if (!ff_dovi_rpu_extension_is_static(dm->level))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Find the first matching ext block and move it to [idx] */
|
|
|
|
for (j = idx; j < ext->num_static; j++) {
|
|
|
|
if (!memcmp(&ext->dm_static[j], dm, sizeof(*dm))) {
|
|
|
|
if (j != idx)
|
|
|
|
FFSWAP(AVDOVIDmData, ext->dm_static[j], ext->dm_static[idx]);
|
|
|
|
idx++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (j == ext->num_static) {
|
|
|
|
/* Found no matching ext block */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If idx is less than ext->num_static, then there are extra unmatched
|
|
|
|
* ext blocks inside ext->dm_static */
|
|
|
|
return idx == ext->num_static;
|
|
|
|
}
|
|
|
|
|
2024-03-30 19:12:35 +02:00
|
|
|
static inline void put_ue_coef(PutBitContext *pb, const AVDOVIRpuDataHeader *hdr,
|
|
|
|
uint64_t coef)
|
|
|
|
{
|
|
|
|
union { uint32_t u32; float f32; } fpart;
|
|
|
|
|
|
|
|
switch (hdr->coef_data_type) {
|
|
|
|
case RPU_COEFF_FIXED:
|
|
|
|
set_ue_golomb(pb, coef >> hdr->coef_log2_denom);
|
|
|
|
put_bits64(pb, hdr->coef_log2_denom,
|
|
|
|
coef & ((1LL << hdr->coef_log2_denom) - 1));
|
|
|
|
break;
|
|
|
|
case RPU_COEFF_FLOAT:
|
|
|
|
fpart.f32 = coef / (float) (1LL << hdr->coef_log2_denom);
|
|
|
|
put_bits64(pb, hdr->coef_log2_denom, fpart.u32);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void put_se_coef(PutBitContext *pb, const AVDOVIRpuDataHeader *hdr,
|
|
|
|
uint64_t coef)
|
|
|
|
{
|
|
|
|
union { uint32_t u32; float f32; } fpart;
|
|
|
|
|
|
|
|
switch (hdr->coef_data_type) {
|
|
|
|
case RPU_COEFF_FIXED:
|
|
|
|
set_se_golomb(pb, coef >> hdr->coef_log2_denom);
|
|
|
|
put_bits64(pb, hdr->coef_log2_denom,
|
|
|
|
coef & ((1LL << hdr->coef_log2_denom) - 1));
|
|
|
|
break;
|
|
|
|
case RPU_COEFF_FLOAT:
|
|
|
|
fpart.f32 = coef / (float) (1LL << hdr->coef_log2_denom);
|
|
|
|
put_bits64(pb, hdr->coef_log2_denom, fpart.u32);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int av_q2den(AVRational q, int den)
|
|
|
|
{
|
2024-04-30 21:32:04 +02:00
|
|
|
if (!q.den || q.den == den)
|
2024-03-30 19:12:35 +02:00
|
|
|
return q.num;
|
|
|
|
q = av_mul_q(q, av_make_q(den, 1));
|
|
|
|
return (q.num + (q.den >> 1)) / q.den;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void generate_ext_v1(PutBitContext *pb, const AVDOVIDmData *dm)
|
|
|
|
{
|
|
|
|
int ext_block_length, start_pos, pad_bits;
|
|
|
|
|
|
|
|
switch (dm->level) {
|
|
|
|
case 1: ext_block_length = 5; break;
|
|
|
|
case 2: ext_block_length = 11; break;
|
|
|
|
case 4: ext_block_length = 3; break;
|
|
|
|
case 5: ext_block_length = 7; break;
|
|
|
|
case 6: ext_block_length = 8; break;
|
|
|
|
case 255: ext_block_length = 6; break;
|
|
|
|
default: return;
|
|
|
|
}
|
|
|
|
|
|
|
|
set_ue_golomb(pb, ext_block_length);
|
|
|
|
put_bits(pb, 8, dm->level);
|
|
|
|
start_pos = put_bits_count(pb);
|
|
|
|
|
|
|
|
switch (dm->level) {
|
|
|
|
case 1:
|
|
|
|
put_bits(pb, 12, dm->l1.min_pq);
|
|
|
|
put_bits(pb, 12, dm->l1.max_pq);
|
|
|
|
put_bits(pb, 12, dm->l1.avg_pq);
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
put_bits(pb, 12, dm->l2.target_max_pq);
|
|
|
|
put_bits(pb, 12, dm->l2.trim_slope);
|
|
|
|
put_bits(pb, 12, dm->l2.trim_offset);
|
|
|
|
put_bits(pb, 12, dm->l2.trim_power);
|
|
|
|
put_bits(pb, 12, dm->l2.trim_chroma_weight);
|
|
|
|
put_bits(pb, 12, dm->l2.trim_saturation_gain);
|
avcodec/dovi_rpu{enc,dec}: fix ms_weight handling
The code as written was wrong. In the spec, these fields are treated
merely as plain integers in the range 0 to 4095. The only difference
between L2 and L8 is that L2.ms_weight also accepts an additional value
of -1, hence the extra sign bit. While it's likely that these are still
shifted integers in disguise, since all real-world samples seem to use
a value of 2048 here, the offset used in the code was wrong.
In addition, because the l8.ms_weight struct member is unsigned, these
wrong shifting semantics ended up overflowing the field, leading to
undefined behavior when transcoding. Fortunately, the damage was
relatively contained in practice, because it just corrupts the coding of
this field, which is ignored in practice in all implementations I have
seen.
2024-05-25 18:05:04 +02:00
|
|
|
put_sbits(pb, 13, dm->l2.ms_weight);
|
2024-03-30 19:12:35 +02:00
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
put_bits(pb, 12, dm->l4.anchor_pq);
|
|
|
|
put_bits(pb, 12, dm->l4.anchor_power);
|
|
|
|
break;
|
|
|
|
case 5:
|
|
|
|
put_bits(pb, 13, dm->l5.left_offset);
|
|
|
|
put_bits(pb, 13, dm->l5.right_offset);
|
|
|
|
put_bits(pb, 13, dm->l5.top_offset);
|
|
|
|
put_bits(pb, 13, dm->l5.bottom_offset);
|
|
|
|
break;
|
|
|
|
case 6:
|
|
|
|
put_bits(pb, 16, dm->l6.max_luminance);
|
|
|
|
put_bits(pb, 16, dm->l6.min_luminance);
|
|
|
|
put_bits(pb, 16, dm->l6.max_cll);
|
|
|
|
put_bits(pb, 16, dm->l6.max_fall);
|
|
|
|
break;
|
|
|
|
case 255:
|
|
|
|
put_bits(pb, 8, dm->l255.dm_run_mode);
|
|
|
|
put_bits(pb, 8, dm->l255.dm_run_version);
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
|
|
put_bits(pb, 8, dm->l255.dm_debug[i]);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
pad_bits = ext_block_length * 8 - (put_bits_count(pb) - start_pos);
|
|
|
|
av_assert1(pad_bits >= 0);
|
|
|
|
put_bits(pb, pad_bits, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void put_cie_xy(PutBitContext *pb, AVCIExy xy)
|
|
|
|
{
|
|
|
|
const int denom = 32767;
|
|
|
|
put_sbits(pb, 16, av_q2den(xy.x, denom));
|
|
|
|
put_sbits(pb, 16, av_q2den(xy.y, denom));
|
|
|
|
}
|
|
|
|
|
|
|
|
#define ANY6(arr) (arr[0] || arr[1] || arr[2] || arr[3] || arr[4] || arr[5])
|
|
|
|
#define ANY_XY(xy) (xy.x.num || xy.y.num)
|
|
|
|
#define ANY_CSP(csp) (ANY_XY(csp.prim.r) || ANY_XY(csp.prim.g) || \
|
|
|
|
ANY_XY(csp.prim.b) || ANY_XY(csp.wp))
|
|
|
|
|
|
|
|
static void generate_ext_v2(PutBitContext *pb, const AVDOVIDmData *dm)
|
|
|
|
{
|
|
|
|
int ext_block_length, start_pos, pad_bits;
|
|
|
|
|
|
|
|
switch (dm->level) {
|
|
|
|
case 3: ext_block_length = 5; break;
|
|
|
|
case 8:
|
|
|
|
if (ANY6(dm->l8.hue_vector_field)) {
|
|
|
|
ext_block_length = 25;
|
|
|
|
} else if (ANY6(dm->l8.saturation_vector_field)) {
|
|
|
|
ext_block_length = 19;
|
|
|
|
} else if (dm->l8.clip_trim) {
|
|
|
|
ext_block_length = 13;
|
|
|
|
} else if (dm->l8.target_mid_contrast) {
|
|
|
|
ext_block_length = 12;
|
|
|
|
} else {
|
|
|
|
ext_block_length = 10;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 9:
|
|
|
|
if (ANY_CSP(dm->l9.source_display_primaries)) {
|
|
|
|
ext_block_length = 17;
|
|
|
|
} else {
|
|
|
|
ext_block_length = 1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 10:
|
|
|
|
if (ANY_CSP(dm->l10.target_display_primaries)) {
|
|
|
|
ext_block_length = 21;
|
|
|
|
} else {
|
|
|
|
ext_block_length = 5;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 11: ext_block_length = 4; break;
|
|
|
|
case 254: ext_block_length = 2; break;
|
|
|
|
default: return;
|
|
|
|
}
|
|
|
|
|
|
|
|
set_ue_golomb(pb, ext_block_length);
|
|
|
|
put_bits(pb, 8, dm->level);
|
|
|
|
start_pos = put_bits_count(pb);
|
|
|
|
|
|
|
|
switch (dm->level) {
|
|
|
|
case 3:
|
|
|
|
put_bits(pb, 12, dm->l3.min_pq_offset);
|
|
|
|
put_bits(pb, 12, dm->l3.max_pq_offset);
|
|
|
|
put_bits(pb, 12, dm->l3.avg_pq_offset);
|
|
|
|
break;
|
|
|
|
case 8:
|
|
|
|
put_bits(pb, 8, dm->l8.target_display_index);
|
|
|
|
put_bits(pb, 12, dm->l8.trim_slope);
|
|
|
|
put_bits(pb, 12, dm->l8.trim_offset);
|
|
|
|
put_bits(pb, 12, dm->l8.trim_power);
|
|
|
|
put_bits(pb, 12, dm->l8.trim_chroma_weight);
|
|
|
|
put_bits(pb, 12, dm->l8.trim_saturation_gain);
|
avcodec/dovi_rpu{enc,dec}: fix ms_weight handling
The code as written was wrong. In the spec, these fields are treated
merely as plain integers in the range 0 to 4095. The only difference
between L2 and L8 is that L2.ms_weight also accepts an additional value
of -1, hence the extra sign bit. While it's likely that these are still
shifted integers in disguise, since all real-world samples seem to use
a value of 2048 here, the offset used in the code was wrong.
In addition, because the l8.ms_weight struct member is unsigned, these
wrong shifting semantics ended up overflowing the field, leading to
undefined behavior when transcoding. Fortunately, the damage was
relatively contained in practice, because it just corrupts the coding of
this field, which is ignored in practice in all implementations I have
seen.
2024-05-25 18:05:04 +02:00
|
|
|
put_bits(pb, 12, dm->l8.ms_weight);
|
2024-03-30 19:12:35 +02:00
|
|
|
if (ext_block_length < 12)
|
|
|
|
break;
|
|
|
|
put_bits(pb, 12, dm->l8.target_mid_contrast);
|
|
|
|
if (ext_block_length < 13)
|
|
|
|
break;
|
|
|
|
put_bits(pb, 12, dm->l8.clip_trim);
|
|
|
|
if (ext_block_length < 19)
|
|
|
|
break;
|
|
|
|
for (int i = 0; i < 6; i++)
|
|
|
|
put_bits(pb, 8, dm->l8.saturation_vector_field[i]);
|
|
|
|
if (ext_block_length < 25)
|
|
|
|
break;
|
|
|
|
for (int i = 0; i < 6; i++)
|
|
|
|
put_bits(pb, 8, dm->l8.hue_vector_field[i]);
|
|
|
|
break;
|
|
|
|
case 9:
|
|
|
|
put_bits(pb, 8, dm->l9.source_primary_index);
|
|
|
|
if (ext_block_length < 17)
|
|
|
|
break;
|
|
|
|
put_cie_xy(pb, dm->l9.source_display_primaries.prim.r);
|
|
|
|
put_cie_xy(pb, dm->l9.source_display_primaries.prim.g);
|
|
|
|
put_cie_xy(pb, dm->l9.source_display_primaries.prim.b);
|
|
|
|
put_cie_xy(pb, dm->l9.source_display_primaries.wp);
|
|
|
|
break;
|
|
|
|
case 10:
|
|
|
|
put_bits(pb, 8, dm->l10.target_display_index);
|
|
|
|
put_bits(pb, 12, dm->l10.target_max_pq);
|
|
|
|
put_bits(pb, 12, dm->l10.target_min_pq);
|
|
|
|
put_bits(pb, 8, dm->l10.target_primary_index);
|
|
|
|
if (ext_block_length < 21)
|
|
|
|
break;
|
|
|
|
put_cie_xy(pb, dm->l10.target_display_primaries.prim.r);
|
|
|
|
put_cie_xy(pb, dm->l10.target_display_primaries.prim.g);
|
|
|
|
put_cie_xy(pb, dm->l10.target_display_primaries.prim.b);
|
|
|
|
put_cie_xy(pb, dm->l10.target_display_primaries.wp);
|
|
|
|
break;
|
|
|
|
case 11:
|
|
|
|
put_bits(pb, 8, dm->l11.content_type);
|
|
|
|
put_bits(pb, 4, dm->l11.whitepoint);
|
|
|
|
put_bits(pb, 1, dm->l11.reference_mode_flag);
|
|
|
|
put_bits(pb, 3, 0); /* reserved */
|
|
|
|
put_bits(pb, 2, dm->l11.sharpness);
|
|
|
|
put_bits(pb, 2, dm->l11.noise_reduction);
|
|
|
|
put_bits(pb, 2, dm->l11.mpeg_noise_reduction);
|
|
|
|
put_bits(pb, 2, dm->l11.frame_rate_conversion);
|
|
|
|
put_bits(pb, 2, dm->l11.brightness);
|
|
|
|
put_bits(pb, 2, dm->l11.color);
|
|
|
|
break;
|
|
|
|
case 254:
|
|
|
|
put_bits(pb, 8, dm->l254.dm_mode);
|
|
|
|
put_bits(pb, 8, dm->l254.dm_version_index);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
pad_bits = ext_block_length * 8 - (put_bits_count(pb) - start_pos);
|
|
|
|
av_assert1(pad_bits >= 0);
|
|
|
|
put_bits(pb, pad_bits, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
int ff_dovi_rpu_generate(DOVIContext *s, const AVDOVIMetadata *metadata,
|
2024-06-18 17:18:14 +02:00
|
|
|
int flags, uint8_t **out_rpu, int *out_size)
|
2024-03-30 19:12:35 +02:00
|
|
|
{
|
|
|
|
PutBitContext *pb = &(PutBitContext){0};
|
|
|
|
const AVDOVIRpuDataHeader *hdr;
|
|
|
|
const AVDOVIDataMapping *mapping;
|
|
|
|
const AVDOVIColorMetadata *color;
|
2024-06-09 14:58:12 +02:00
|
|
|
int vdr_dm_metadata_present, vdr_rpu_id, use_prev_vdr_rpu, profile,
|
2024-07-14 18:18:42 +02:00
|
|
|
buffer_size, rpu_size, pad, zero_run, dm_compression;
|
2024-03-30 19:12:35 +02:00
|
|
|
int num_ext_blocks_v1, num_ext_blocks_v2;
|
2024-06-18 17:23:28 +02:00
|
|
|
int dv_md_compression = s->cfg.dv_md_compression;
|
2024-03-30 19:12:35 +02:00
|
|
|
uint32_t crc;
|
|
|
|
uint8_t *dst;
|
|
|
|
if (!metadata) {
|
|
|
|
*out_rpu = NULL;
|
|
|
|
*out_size = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
hdr = av_dovi_get_header(metadata);
|
|
|
|
mapping = av_dovi_get_mapping(metadata);
|
|
|
|
color = av_dovi_get_color(metadata);
|
|
|
|
av_assert0(s->cfg.dv_profile);
|
|
|
|
|
2024-07-14 18:18:42 +02:00
|
|
|
if (!(flags & FF_DOVI_COMPRESS_RPU))
|
|
|
|
dv_md_compression = AV_DOVI_COMPRESSION_NONE;
|
|
|
|
else if (dv_md_compression == AV_DOVI_COMPRESSION_RESERVED)
|
|
|
|
return AVERROR(EINVAL);
|
|
|
|
|
2024-03-30 19:12:35 +02:00
|
|
|
if (hdr->rpu_type != 2) {
|
|
|
|
av_log(s->logctx, AV_LOG_ERROR, "Unhandled RPU type %"PRIu8"\n",
|
|
|
|
hdr->rpu_type);
|
|
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
}
|
|
|
|
|
2024-06-18 17:23:28 +02:00
|
|
|
if (!(flags & FF_DOVI_COMPRESS_RPU))
|
|
|
|
dv_md_compression = AV_DOVI_COMPRESSION_NONE;
|
|
|
|
|
2024-07-18 15:39:45 +02:00
|
|
|
vdr_rpu_id = mapping->vdr_rpu_id;
|
2024-07-18 15:45:15 +02:00
|
|
|
use_prev_vdr_rpu = 0;
|
|
|
|
|
2024-03-30 19:12:35 +02:00
|
|
|
if (!s->vdr[vdr_rpu_id]) {
|
2024-10-15 22:37:56 +02:00
|
|
|
s->vdr[vdr_rpu_id] = av_refstruct_allocz(sizeof(AVDOVIDataMapping));
|
2024-03-30 19:12:35 +02:00
|
|
|
if (!s->vdr[vdr_rpu_id])
|
|
|
|
return AVERROR(ENOMEM);
|
|
|
|
}
|
|
|
|
|
2024-06-18 17:23:28 +02:00
|
|
|
switch (dv_md_compression) {
|
2024-07-18 15:45:15 +02:00
|
|
|
case AV_DOVI_COMPRESSION_LIMITED:
|
|
|
|
/* Limited metadata compression requires vdr_rpi_id == 0 */
|
|
|
|
if (vdr_rpu_id != 0)
|
|
|
|
break;
|
|
|
|
/* fall through */
|
|
|
|
case AV_DOVI_COMPRESSION_EXTENDED:
|
|
|
|
if (s->vdr[vdr_rpu_id])
|
|
|
|
use_prev_vdr_rpu = !memcmp(s->vdr[vdr_rpu_id], mapping, sizeof(*mapping));
|
|
|
|
break;
|
|
|
|
case AV_DOVI_COMPRESSION_RESERVED:
|
|
|
|
return AVERROR(EINVAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (s->cfg.dv_md_compression != AV_DOVI_COMPRESSION_EXTENDED) {
|
|
|
|
/* Flush VDRs to avoid leaking old state; maintaining multiple VDR
|
|
|
|
* references requires extended compression */
|
|
|
|
for (int i = 0; i <= DOVI_MAX_DM_ID; i++) {
|
|
|
|
if (i != vdr_rpu_id)
|
2024-10-15 22:37:56 +02:00
|
|
|
av_refstruct_unref(&s->vdr[i]);
|
2024-07-18 15:45:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-14 18:18:42 +02:00
|
|
|
if (metadata->num_ext_blocks && !s->ext_blocks) {
|
2024-10-15 22:37:56 +02:00
|
|
|
s->ext_blocks = av_refstruct_allocz(sizeof(*s->ext_blocks));
|
2024-07-14 18:18:42 +02:00
|
|
|
if (!s->ext_blocks)
|
|
|
|
return AVERROR(ENOMEM);
|
|
|
|
}
|
|
|
|
|
|
|
|
vdr_dm_metadata_present = memcmp(color, &ff_dovi_color_default, sizeof(*color));
|
|
|
|
if (metadata->num_ext_blocks)
|
|
|
|
vdr_dm_metadata_present = 1;
|
|
|
|
|
|
|
|
if (vdr_dm_metadata_present && !s->dm) {
|
2024-10-15 22:37:56 +02:00
|
|
|
s->dm = av_refstruct_allocz(sizeof(AVDOVIColorMetadata));
|
2024-07-14 18:18:42 +02:00
|
|
|
if (!s->dm)
|
|
|
|
return AVERROR(ENOMEM);
|
|
|
|
}
|
|
|
|
|
|
|
|
dm_compression = 0;
|
|
|
|
if (dv_md_compression != AV_DOVI_COMPRESSION_NONE) {
|
|
|
|
if (!cmp_dm_level0(s->dm, color) && try_reuse_ext(s->ext_blocks, metadata))
|
|
|
|
dm_compression = 1;
|
|
|
|
}
|
|
|
|
|
2024-03-30 19:12:35 +02:00
|
|
|
num_ext_blocks_v1 = num_ext_blocks_v2 = 0;
|
|
|
|
for (int i = 0; i < metadata->num_ext_blocks; i++) {
|
|
|
|
const AVDOVIDmData *dm = av_dovi_get_ext(metadata, i);
|
2024-07-14 18:18:42 +02:00
|
|
|
if (dm_compression && ff_dovi_rpu_extension_is_static(dm->level))
|
|
|
|
continue;
|
|
|
|
|
2024-03-30 19:12:35 +02:00
|
|
|
switch (dm->level) {
|
|
|
|
case 1:
|
|
|
|
case 2:
|
|
|
|
case 4:
|
|
|
|
case 5:
|
|
|
|
case 6:
|
|
|
|
case 255:
|
|
|
|
num_ext_blocks_v1++;
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
case 8:
|
|
|
|
case 9:
|
|
|
|
case 10:
|
|
|
|
case 11:
|
|
|
|
case 254:
|
|
|
|
num_ext_blocks_v2++;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
av_log(s->logctx, AV_LOG_ERROR, "Invalid ext block level %d\n",
|
|
|
|
dm->level);
|
|
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
buffer_size = 12 /* vdr seq info */ + 5 /* CRC32 + terminator */;
|
|
|
|
buffer_size += num_ext_blocks_v1 * 13;
|
|
|
|
buffer_size += num_ext_blocks_v2 * 28;
|
|
|
|
if (!use_prev_vdr_rpu) {
|
|
|
|
buffer_size += 160;
|
|
|
|
for (int c = 0; c < 3; c++) {
|
|
|
|
for (int i = 0; i < mapping->curves[c].num_pivots - 1; i++) {
|
|
|
|
switch (mapping->curves[c].mapping_idc[i]) {
|
|
|
|
case AV_DOVI_MAPPING_POLYNOMIAL: buffer_size += 26; break;
|
|
|
|
case AV_DOVI_MAPPING_MMR: buffer_size += 177; break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-06-09 14:58:12 +02:00
|
|
|
if (vdr_dm_metadata_present)
|
2024-03-30 19:12:35 +02:00
|
|
|
buffer_size += 67;
|
|
|
|
|
|
|
|
av_fast_padded_malloc(&s->rpu_buf, &s->rpu_buf_sz, buffer_size);
|
|
|
|
if (!s->rpu_buf)
|
|
|
|
return AVERROR(ENOMEM);
|
|
|
|
init_put_bits(pb, s->rpu_buf, s->rpu_buf_sz);
|
|
|
|
|
|
|
|
/* RPU header */
|
|
|
|
put_bits(pb, 6, hdr->rpu_type);
|
|
|
|
put_bits(pb, 11, hdr->rpu_format);
|
|
|
|
put_bits(pb, 4, hdr->vdr_rpu_profile);
|
|
|
|
put_bits(pb, 4, hdr->vdr_rpu_level);
|
|
|
|
put_bits(pb, 1, 1); /* vdr_seq_info_present */
|
|
|
|
put_bits(pb, 1, hdr->chroma_resampling_explicit_filter_flag);
|
|
|
|
put_bits(pb, 2, hdr->coef_data_type);
|
|
|
|
if (hdr->coef_data_type == RPU_COEFF_FIXED)
|
|
|
|
set_ue_golomb(pb, hdr->coef_log2_denom);
|
|
|
|
put_bits(pb, 2, hdr->vdr_rpu_normalized_idc);
|
|
|
|
put_bits(pb, 1, hdr->bl_video_full_range_flag);
|
|
|
|
if ((hdr->rpu_format & 0x700) == 0) {
|
2024-05-22 17:50:45 +02:00
|
|
|
int ext_mapping_idc = (hdr->ext_mapping_idc_5_7 << 5) | hdr->ext_mapping_idc_0_4;
|
2024-03-30 19:12:35 +02:00
|
|
|
set_ue_golomb(pb, hdr->bl_bit_depth - 8);
|
2024-05-22 17:50:45 +02:00
|
|
|
set_ue_golomb(pb, (ext_mapping_idc << 8) | (hdr->el_bit_depth - 8));
|
2024-03-30 19:12:35 +02:00
|
|
|
set_ue_golomb(pb, hdr->vdr_bit_depth - 8);
|
|
|
|
put_bits(pb, 1, hdr->spatial_resampling_filter_flag);
|
2024-07-14 18:18:42 +02:00
|
|
|
put_bits(pb, 3, dm_compression);
|
2024-03-30 19:12:35 +02:00
|
|
|
put_bits(pb, 1, hdr->el_spatial_resampling_filter_flag);
|
|
|
|
put_bits(pb, 1, hdr->disable_residual_flag);
|
|
|
|
}
|
|
|
|
s->header = *hdr;
|
|
|
|
|
2024-06-09 14:58:12 +02:00
|
|
|
put_bits(pb, 1, vdr_dm_metadata_present);
|
2024-03-30 19:12:35 +02:00
|
|
|
put_bits(pb, 1, use_prev_vdr_rpu);
|
|
|
|
set_ue_golomb(pb, vdr_rpu_id);
|
2024-06-09 16:43:29 +02:00
|
|
|
s->mapping = s->vdr[vdr_rpu_id];
|
2024-03-30 19:12:35 +02:00
|
|
|
|
2024-05-03 22:28:16 +02:00
|
|
|
profile = s->cfg.dv_profile ? s->cfg.dv_profile : ff_dovi_guess_profile_hevc(hdr);
|
|
|
|
|
2024-03-30 19:12:35 +02:00
|
|
|
if (!use_prev_vdr_rpu) {
|
|
|
|
set_ue_golomb(pb, mapping->mapping_color_space);
|
|
|
|
set_ue_golomb(pb, mapping->mapping_chroma_format_idc);
|
|
|
|
for (int c = 0; c < 3; c++) {
|
|
|
|
const AVDOVIReshapingCurve *curve = &mapping->curves[c];
|
|
|
|
int prev = 0;
|
|
|
|
set_ue_golomb(pb, curve->num_pivots - 2);
|
|
|
|
for (int i = 0; i < curve->num_pivots; i++) {
|
|
|
|
put_bits(pb, hdr->bl_bit_depth, curve->pivots[i] - prev);
|
|
|
|
prev = curve->pivots[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mapping->nlq_method_idc != AV_DOVI_NLQ_NONE) {
|
|
|
|
put_bits(pb, 3, mapping->nlq_method_idc);
|
|
|
|
put_bits(pb, hdr->bl_bit_depth, mapping->nlq_pivots[0]);
|
|
|
|
put_bits(pb, hdr->bl_bit_depth, mapping->nlq_pivots[1] - mapping->nlq_pivots[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
set_ue_golomb(pb, mapping->num_x_partitions - 1);
|
|
|
|
set_ue_golomb(pb, mapping->num_y_partitions - 1);
|
|
|
|
|
|
|
|
for (int c = 0; c < 3; c++) {
|
|
|
|
const AVDOVIReshapingCurve *curve = &mapping->curves[c];
|
|
|
|
for (int i = 0; i < curve->num_pivots - 1; i++) {
|
|
|
|
set_ue_golomb(pb, curve->mapping_idc[i]);
|
|
|
|
switch (curve->mapping_idc[i]) {
|
|
|
|
case AV_DOVI_MAPPING_POLYNOMIAL: {
|
|
|
|
set_ue_golomb(pb, curve->poly_order[i] - 1);
|
|
|
|
if (curve->poly_order[i] == 1)
|
|
|
|
put_bits(pb, 1, 0); /* linear_interp_flag */
|
|
|
|
for (int k = 0; k <= curve->poly_order[i]; k++)
|
|
|
|
put_se_coef(pb, hdr, curve->poly_coef[i][k]);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case AV_DOVI_MAPPING_MMR: {
|
|
|
|
put_bits(pb, 2, curve->mmr_order[i] - 1);
|
|
|
|
put_se_coef(pb, hdr, curve->mmr_constant[i]);
|
|
|
|
for (int j = 0; j < curve->mmr_order[i]; j++) {
|
|
|
|
for (int k = 0; k < 7; k++)
|
|
|
|
put_se_coef(pb, hdr, curve->mmr_coef[i][j][k]);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mapping->nlq_method_idc != AV_DOVI_NLQ_NONE) {
|
|
|
|
for (int c = 0; c < 3; c++) {
|
|
|
|
const AVDOVINLQParams *nlq = &mapping->nlq[c];
|
|
|
|
put_bits(pb, hdr->el_bit_depth, nlq->nlq_offset);
|
|
|
|
put_ue_coef(pb, hdr, nlq->vdr_in_max);
|
|
|
|
switch (mapping->nlq_method_idc) {
|
|
|
|
case AV_DOVI_NLQ_LINEAR_DZ:
|
|
|
|
put_ue_coef(pb, hdr, nlq->linear_deadzone_slope);
|
|
|
|
put_ue_coef(pb, hdr, nlq->linear_deadzone_threshold);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-09 16:43:29 +02:00
|
|
|
memcpy(s->vdr[vdr_rpu_id], mapping, sizeof(*mapping));
|
2024-03-30 19:12:35 +02:00
|
|
|
}
|
|
|
|
|
2024-06-09 14:58:12 +02:00
|
|
|
if (vdr_dm_metadata_present) {
|
2024-07-14 15:31:30 +02:00
|
|
|
DOVIExt *ext = s->ext_blocks;
|
2024-03-30 19:12:35 +02:00
|
|
|
const int denom = profile == 4 ? (1 << 30) : (1 << 28);
|
|
|
|
set_ue_golomb(pb, color->dm_metadata_id); /* affected_dm_id */
|
|
|
|
set_ue_golomb(pb, color->dm_metadata_id); /* current_dm_id */
|
|
|
|
set_ue_golomb(pb, color->scene_refresh_flag);
|
2024-07-14 18:18:42 +02:00
|
|
|
if (!dm_compression) {
|
|
|
|
for (int i = 0; i < 9; i++)
|
|
|
|
put_sbits(pb, 16, av_q2den(color->ycc_to_rgb_matrix[i], 1 << 13));
|
|
|
|
for (int i = 0; i < 3; i++)
|
|
|
|
put_bits32(pb, av_q2den(color->ycc_to_rgb_offset[i], denom));
|
|
|
|
for (int i = 0; i < 9; i++)
|
|
|
|
put_sbits(pb, 16, av_q2den(color->rgb_to_lms_matrix[i], 1 << 14));
|
|
|
|
put_bits(pb, 16, color->signal_eotf);
|
|
|
|
put_bits(pb, 16, color->signal_eotf_param0);
|
|
|
|
put_bits(pb, 16, color->signal_eotf_param1);
|
|
|
|
put_bits32(pb, color->signal_eotf_param2);
|
|
|
|
put_bits(pb, 5, color->signal_bit_depth);
|
|
|
|
put_bits(pb, 2, color->signal_color_space);
|
|
|
|
put_bits(pb, 2, color->signal_chroma_format);
|
|
|
|
put_bits(pb, 2, color->signal_full_range_flag);
|
|
|
|
put_bits(pb, 12, color->source_min_pq);
|
|
|
|
put_bits(pb, 12, color->source_max_pq);
|
|
|
|
put_bits(pb, 10, color->source_diagonal);
|
|
|
|
}
|
2024-03-30 19:12:35 +02:00
|
|
|
|
2024-06-09 15:04:45 +02:00
|
|
|
memcpy(s->dm, color, sizeof(*color));
|
|
|
|
s->color = s->dm;
|
2024-03-30 19:12:35 +02:00
|
|
|
|
2024-06-09 16:45:56 +02:00
|
|
|
/* Extension blocks */
|
|
|
|
set_ue_golomb(pb, num_ext_blocks_v1);
|
2024-03-30 19:12:35 +02:00
|
|
|
align_put_bits(pb);
|
2024-07-14 18:18:42 +02:00
|
|
|
for (int i = 0; i < metadata->num_ext_blocks; i++) {
|
|
|
|
const AVDOVIDmData *dm = av_dovi_get_ext(metadata, i);
|
|
|
|
if (dm_compression && ff_dovi_rpu_extension_is_static(dm->level))
|
|
|
|
continue;
|
|
|
|
generate_ext_v1(pb, dm);
|
|
|
|
}
|
2024-06-09 16:45:56 +02:00
|
|
|
|
|
|
|
if (num_ext_blocks_v2) {
|
|
|
|
set_ue_golomb(pb, num_ext_blocks_v2);
|
|
|
|
align_put_bits(pb);
|
2024-07-14 18:18:42 +02:00
|
|
|
for (int i = 0; i < metadata->num_ext_blocks; i++) {
|
|
|
|
const AVDOVIDmData *dm = av_dovi_get_ext(metadata, i);
|
|
|
|
if (dm_compression && ff_dovi_rpu_extension_is_static(dm->level))
|
|
|
|
continue;
|
2024-06-09 16:45:56 +02:00
|
|
|
generate_ext_v2(pb, av_dovi_get_ext(metadata, i));
|
2024-07-14 18:18:42 +02:00
|
|
|
}
|
2024-06-09 16:45:56 +02:00
|
|
|
}
|
2024-06-14 15:00:31 +02:00
|
|
|
|
2024-07-14 15:31:30 +02:00
|
|
|
if (ext) {
|
|
|
|
size_t ext_sz = FFMIN(sizeof(AVDOVIDmData), metadata->ext_block_size);
|
2024-07-14 18:18:42 +02:00
|
|
|
ext->num_dynamic = 0;
|
|
|
|
if (!dm_compression)
|
|
|
|
ext->num_static = 0;
|
2024-07-10 14:47:40 +02:00
|
|
|
for (int i = 0; i < metadata->num_ext_blocks; i++) {
|
|
|
|
const AVDOVIDmData *dm = av_dovi_get_ext(metadata, i);
|
2024-07-14 18:18:42 +02:00
|
|
|
if (!ff_dovi_rpu_extension_is_static(dm->level))
|
2024-07-10 14:47:40 +02:00
|
|
|
memcpy(&ext->dm_dynamic[ext->num_dynamic++], dm, ext_sz);
|
2024-07-14 18:18:42 +02:00
|
|
|
else if (!dm_compression)
|
|
|
|
memcpy(&ext->dm_static[ext->num_static++], dm, ext_sz);
|
2024-07-10 14:47:40 +02:00
|
|
|
}
|
2024-07-14 15:31:30 +02:00
|
|
|
}
|
2024-06-09 16:45:56 +02:00
|
|
|
} else {
|
|
|
|
s->color = &ff_dovi_color_default;
|
2024-10-15 22:37:56 +02:00
|
|
|
av_refstruct_unref(&s->ext_blocks);
|
2024-03-30 19:12:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
flush_put_bits(pb);
|
|
|
|
crc = av_bswap32(av_crc(av_crc_get_table(AV_CRC_32_IEEE), -1,
|
|
|
|
s->rpu_buf, put_bytes_output(pb)));
|
|
|
|
put_bits32(pb, crc);
|
|
|
|
put_bits(pb, 8, 0x80); /* terminator */
|
|
|
|
flush_put_bits(pb);
|
|
|
|
|
|
|
|
rpu_size = put_bytes_output(pb);
|
2024-06-18 17:42:21 +02:00
|
|
|
if (flags & FF_DOVI_WRAP_T35) {
|
2024-03-30 19:12:35 +02:00
|
|
|
*out_rpu = av_malloc(rpu_size + 15);
|
|
|
|
if (!*out_rpu)
|
|
|
|
return AVERROR(ENOMEM);
|
|
|
|
init_put_bits(pb, *out_rpu, rpu_size + 15);
|
|
|
|
put_bits(pb, 8, ITU_T_T35_COUNTRY_CODE_US);
|
|
|
|
put_bits(pb, 16, ITU_T_T35_PROVIDER_CODE_DOLBY);
|
|
|
|
put_bits32(pb, 0x800); /* provider_oriented_code */
|
|
|
|
put_bits(pb, 27, 0x01be6841u); /* fixed EMDF header, see above */
|
|
|
|
if (rpu_size > 0xFF) {
|
|
|
|
av_assert2(rpu_size <= 0x10000);
|
|
|
|
put_bits(pb, 8, (rpu_size >> 8) - 1);
|
|
|
|
put_bits(pb, 1, 1); /* read_more */
|
|
|
|
put_bits(pb, 8, rpu_size & 0xFF);
|
|
|
|
put_bits(pb, 1, 0);
|
|
|
|
} else {
|
|
|
|
put_bits(pb, 8, rpu_size);
|
|
|
|
put_bits(pb, 1, 0);
|
|
|
|
}
|
|
|
|
ff_copy_bits(pb, s->rpu_buf, rpu_size * 8);
|
|
|
|
put_bits(pb, 17, 0x400); /* emdf payload id + emdf_protection */
|
|
|
|
|
|
|
|
pad = pb->bit_left & 7;
|
|
|
|
put_bits(pb, pad, (1 << pad) - 1); /* pad to next byte with 1 bits */
|
|
|
|
flush_put_bits(pb);
|
|
|
|
*out_size = put_bytes_output(pb);
|
|
|
|
return 0;
|
2024-06-18 17:42:21 +02:00
|
|
|
} else if (flags & FF_DOVI_WRAP_NAL) {
|
|
|
|
*out_rpu = dst = av_malloc(4 + rpu_size * 3 / 2); /* worst case */
|
2024-03-30 19:12:35 +02:00
|
|
|
if (!*out_rpu)
|
|
|
|
return AVERROR(ENOMEM);
|
|
|
|
*dst++ = 25; /* NAL prefix */
|
|
|
|
zero_run = 0;
|
|
|
|
for (int i = 0; i < rpu_size; i++) {
|
|
|
|
if (zero_run < 2) {
|
|
|
|
if (s->rpu_buf[i] == 0) {
|
|
|
|
zero_run++;
|
|
|
|
} else {
|
|
|
|
zero_run = 0;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if ((s->rpu_buf[i] & ~3) == 0) {
|
|
|
|
/* emulation prevention */
|
|
|
|
*dst++ = 3;
|
|
|
|
}
|
|
|
|
zero_run = s->rpu_buf[i] == 0;
|
|
|
|
}
|
|
|
|
*dst++ = s->rpu_buf[i];
|
|
|
|
}
|
|
|
|
*out_size = dst - *out_rpu;
|
|
|
|
return 0;
|
2024-06-18 17:42:21 +02:00
|
|
|
} else {
|
|
|
|
/* Return intermediate buffer directly */
|
|
|
|
*out_rpu = s->rpu_buf;
|
|
|
|
*out_size = rpu_size;
|
|
|
|
s->rpu_buf = NULL;
|
|
|
|
s->rpu_buf_sz = 0;
|
|
|
|
return 0;
|
2024-03-30 19:12:35 +02:00
|
|
|
}
|
|
|
|
}
|