aboutsummaryrefslogtreecommitdiffstats
path: root/libavcodec
diff options
context:
space:
mode:
authorNiklas Haas <git@haasn.dev>2024-04-12 13:11:10 +0200
committerNiklas Haas <git@haasn.dev>2024-04-22 12:17:05 +0200
commitf131086a7092d902286c5f58fbb04be9dfb44fe0 (patch)
tree9ae8d72b69192046ea4038618137525aaa2d0f61 /libavcodec
parent783734d979d3d357a3cdcf62ee3a32c570a7bf7c (diff)
downloadffmpeg-f131086a7092d902286c5f58fbb04be9dfb44fe0.tar.gz
avcodec/dovi_rpu: split into dovi_rpu.c and dovi_rpudec.c
To allow compiling the decoding objects without the encoding objects and vice versa. Common helpers that users of both APIs need are put into the shared dovi_rpu.c.
Diffstat (limited to 'libavcodec')
-rw-r--r--libavcodec/Makefile2
-rw-r--r--libavcodec/dovi_rpu.c624
-rw-r--r--libavcodec/dovi_rpu.h14
-rw-r--r--libavcodec/dovi_rpudec.c635
4 files changed, 651 insertions, 624 deletions
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 9216a6fe6d..34deb749c8 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -86,7 +86,7 @@ OBJS-$(CONFIG_CBS_MPEG2) += cbs_mpeg2.o
OBJS-$(CONFIG_CBS_VP8) += cbs_vp8.o vp8data.o
OBJS-$(CONFIG_CBS_VP9) += cbs_vp9.o
OBJS-$(CONFIG_DEFLATE_WRAPPER) += zlib_wrapper.o
-OBJS-$(CONFIG_DOVI_RPUDEC) += dovi_rpu.o
+OBJS-$(CONFIG_DOVI_RPUDEC) += dovi_rpu.o dovi_rpudec.o
OBJS-$(CONFIG_ERROR_RESILIENCE) += error_resilience.o
OBJS-$(CONFIG_EVCPARSE) += evc_parse.o evc_ps.o
OBJS-$(CONFIG_EXIF) += exif.o tiff_common.o
diff --git a/libavcodec/dovi_rpu.c b/libavcodec/dovi_rpu.c
index 77fef8c496..b26c19dd5e 100644
--- a/libavcodec/dovi_rpu.c
+++ b/libavcodec/dovi_rpu.c
@@ -2,7 +2,7 @@
* Dolby Vision RPU decoder
*
* Copyright (C) 2021 Jan Ekström
- * Copyright (C) 2021 Niklas Haas
+ * Copyright (C) 2021-2024 Niklas Haas
*
* This file is part of FFmpeg.
*
@@ -21,29 +21,11 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include "libavutil/buffer.h"
#include "libavutil/mem.h"
-#include "libavutil/crc.h"
-#include "avcodec.h"
#include "dovi_rpu.h"
-#include "golomb.h"
-#include "get_bits.h"
#include "refstruct.h"
-enum {
- RPU_COEFF_FIXED = 0,
- RPU_COEFF_FLOAT = 1,
-};
-
-/**
- * Private contents of vdr.
- */
-typedef struct DOVIVdr {
- AVDOVIDataMapping mapping;
- AVDOVIColorMetadata color;
-} DOVIVdr;
-
void ff_dovi_ctx_unref(DOVIContext *s)
{
for (int i = 0; i < FF_ARRAY_ELEMS(s->vdr); i++)
@@ -83,44 +65,6 @@ void ff_dovi_ctx_replace(DOVIContext *s, const DOVIContext *s0)
ff_refstruct_replace(&s->ext_blocks, s0->ext_blocks);
}
-int ff_dovi_attach_side_data(DOVIContext *s, AVFrame *frame)
-{
- AVFrameSideData *sd;
- AVBufferRef *buf;
- AVDOVIMetadata *dovi;
- size_t dovi_size, ext_sz;
-
- if (!s->mapping || !s->color)
- return 0; /* incomplete dovi metadata */
-
- dovi = av_dovi_metadata_alloc(&dovi_size);
- if (!dovi)
- return AVERROR(ENOMEM);
-
- buf = av_buffer_create((uint8_t *) dovi, dovi_size, NULL, NULL, 0);
- if (!buf) {
- av_free(dovi);
- return AVERROR(ENOMEM);
- }
-
- sd = av_frame_new_side_data_from_buf(frame, AV_FRAME_DATA_DOVI_METADATA, buf);
- if (!sd) {
- av_buffer_unref(&buf);
- return AVERROR(ENOMEM);
- }
-
- /* Copy only the parts of these structs known to us at compiler-time. */
-#define COPY(t, a, b, last) memcpy(a, b, offsetof(t, last) + sizeof((b)->last))
- COPY(AVDOVIRpuDataHeader, av_dovi_get_header(dovi), &s->header, disable_residual_flag);
- COPY(AVDOVIDataMapping, av_dovi_get_mapping(dovi), s->mapping, nlq_pivots);
- COPY(AVDOVIColorMetadata, av_dovi_get_color(dovi), s->color, source_diagonal);
- ext_sz = FFMIN(sizeof(AVDOVIDmData), dovi->ext_block_size);
- for (int i = 0; i < s->num_ext_blocks; i++)
- memcpy(av_dovi_get_ext(dovi, i), &s->ext_blocks[i], ext_sz);
- dovi->num_ext_blocks = s->num_ext_blocks;
- return 0;
-}
-
int ff_dovi_guess_profile_hevc(const AVDOVIRpuDataHeader *hdr)
{
switch (hdr->vdr_rpu_profile) {
@@ -142,569 +86,3 @@ int ff_dovi_guess_profile_hevc(const AVDOVIRpuDataHeader *hdr)
return 0; /* unknown */
}
-
-static inline uint64_t get_ue_coef(GetBitContext *gb, const AVDOVIRpuDataHeader *hdr)
-{
- uint64_t ipart;
- union { uint32_t u32; float f32; } fpart;
-
- switch (hdr->coef_data_type) {
- case RPU_COEFF_FIXED:
- ipart = get_ue_golomb_long(gb);
- fpart.u32 = get_bits_long(gb, hdr->coef_log2_denom);
- return (ipart << hdr->coef_log2_denom) | fpart.u32;
-
- case RPU_COEFF_FLOAT:
- fpart.u32 = get_bits_long(gb, 32);
- return fpart.f32 * (1LL << hdr->coef_log2_denom);
- }
-
- return 0; /* unreachable */
-}
-
-static inline int64_t get_se_coef(GetBitContext *gb, const AVDOVIRpuDataHeader *hdr)
-{
- int64_t ipart;
- union { uint32_t u32; float f32; } fpart;
-
- switch (hdr->coef_data_type) {
- case RPU_COEFF_FIXED:
- ipart = get_se_golomb_long(gb);
- fpart.u32 = get_bits_long(gb, hdr->coef_log2_denom);
- return ipart * (1LL << hdr->coef_log2_denom) | fpart.u32;
-
- case RPU_COEFF_FLOAT:
- fpart.u32 = get_bits_long(gb, 32);
- return fpart.f32 * (1LL << hdr->coef_log2_denom);
- }
-
- return 0; /* unreachable */
-}
-
-static inline unsigned get_variable_bits(GetBitContext *gb, int n)
-{
- unsigned int value = get_bits(gb, n);
- int read_more = get_bits1(gb);
- while (read_more) {
- value = (value + 1) << n;
- value |= get_bits(gb, n);
- read_more = get_bits1(gb);
- }
- return value;
-}
-
-#define VALIDATE(VAR, MIN, MAX) \
- do { \
- if (VAR < MIN || VAR > MAX) { \
- av_log(s->logctx, AV_LOG_ERROR, "RPU validation failed: " \
- #MIN" <= "#VAR" = %d <= "#MAX"\n", (int) VAR); \
- goto fail; \
- } \
- } while (0)
-
-static void parse_ext_v1(DOVIContext *s, GetBitContext *gb, AVDOVIDmData *dm)
-{
- switch (dm->level) {
- case 1:
- dm->l1.min_pq = get_bits(gb, 12);
- dm->l1.max_pq = get_bits(gb, 12);
- dm->l1.avg_pq = get_bits(gb, 12);
- break;
- case 2:
- dm->l2.target_max_pq = get_bits(gb, 12);
- dm->l2.trim_slope = get_bits(gb, 12);
- dm->l2.trim_offset = get_bits(gb, 12);
- dm->l2.trim_power = get_bits(gb, 12);
- dm->l2.trim_chroma_weight = get_bits(gb, 12);
- dm->l2.trim_saturation_gain = get_bits(gb, 12);
- dm->l2.ms_weight = get_bits(gb, 13) - 8192;
- break;
- case 4:
- dm->l4.anchor_pq = get_bits(gb, 12);
- dm->l4.anchor_power = get_bits(gb, 12);
- break;
- case 5:
- dm->l5.left_offset = get_bits(gb, 13);
- dm->l5.right_offset = get_bits(gb, 13);
- dm->l5.top_offset = get_bits(gb, 13);
- dm->l5.bottom_offset = get_bits(gb, 13);
- break;
- case 6:
- dm->l6.max_luminance = get_bits(gb, 16);
- dm->l6.min_luminance = get_bits(gb, 16);
- dm->l6.max_cll = get_bits(gb, 16);
- dm->l6.max_fall = get_bits(gb, 16);
- break;
- case 255:
- dm->l255.dm_run_mode = get_bits(gb, 8);
- dm->l255.dm_run_version = get_bits(gb, 8);
- for (int i = 0; i < 4; i++)
- dm->l255.dm_debug[i] = get_bits(gb, 8);
- break;
- default:
- av_log(s->logctx, AV_LOG_WARNING,
- "Unknown Dolby Vision DM v1 level: %u\n", dm->level);
- }
-}
-
-static AVCIExy get_cie_xy(GetBitContext *gb)
-{
- AVCIExy xy;
- const int denom = 32767;
- xy.x = av_make_q(get_sbits(gb, 16), denom);
- xy.y = av_make_q(get_sbits(gb, 16), denom);
- return xy;
-}
-
-static void parse_ext_v2(DOVIContext *s, GetBitContext *gb, AVDOVIDmData *dm,
- int ext_block_length)
-{
- switch (dm->level) {
- case 3:
- dm->l3.min_pq_offset = get_bits(gb, 12);
- dm->l3.max_pq_offset = get_bits(gb, 12);
- dm->l3.avg_pq_offset = get_bits(gb, 12);
- break;
- case 8:
- dm->l8.target_display_index = get_bits(gb, 8);
- dm->l8.trim_slope = get_bits(gb, 12);
- dm->l8.trim_offset = get_bits(gb, 12);
- dm->l8.trim_power = get_bits(gb, 12);
- dm->l8.trim_chroma_weight = get_bits(gb, 12);
- dm->l8.trim_saturation_gain = get_bits(gb, 12);
- dm->l8.ms_weight = get_bits(gb, 12) - 8192;
- if (ext_block_length < 12)
- break;
- dm->l8.target_mid_contrast = get_bits(gb, 12);
- if (ext_block_length < 13)
- break;
- dm->l8.clip_trim = get_bits(gb, 12);
- if (ext_block_length < 19)
- break;
- for (int i = 0; i < 6; i++)
- dm->l8.saturation_vector_field[i] = get_bits(gb, 8);
- if (ext_block_length < 25)
- break;
- for (int i = 0; i < 6; i++)
- dm->l8.hue_vector_field[i] = get_bits(gb, 8);
- break;
- case 9:
- dm->l9.source_primary_index = get_bits(gb, 8);
- if (ext_block_length < 17)
- break;
- dm->l9.source_display_primaries.prim.r = get_cie_xy(gb);
- dm->l9.source_display_primaries.prim.g = get_cie_xy(gb);
- dm->l9.source_display_primaries.prim.b = get_cie_xy(gb);
- dm->l9.source_display_primaries.wp = get_cie_xy(gb);
- break;
- case 10:
- dm->l10.target_display_index = get_bits(gb, 8);
- dm->l10.target_max_pq = get_bits(gb, 12);
- dm->l10.target_min_pq = get_bits(gb, 12);
- dm->l10.target_primary_index = get_bits(gb, 8);
- if (ext_block_length < 21)
- break;
- dm->l10.target_display_primaries.prim.r = get_cie_xy(gb);
- dm->l10.target_display_primaries.prim.g = get_cie_xy(gb);
- dm->l10.target_display_primaries.prim.b = get_cie_xy(gb);
- dm->l10.target_display_primaries.wp = get_cie_xy(gb);
- break;
- case 11:
- dm->l11.content_type = get_bits(gb, 8);
- dm->l11.whitepoint = get_bits(gb, 4);
- dm->l11.reference_mode_flag = get_bits1(gb);
- skip_bits(gb, 3); /* reserved */
- dm->l11.sharpness = get_bits(gb, 2);
- dm->l11.noise_reduction = get_bits(gb, 2);
- dm->l11.mpeg_noise_reduction = get_bits(gb, 2);
- dm->l11.frame_rate_conversion = get_bits(gb, 2);
- dm->l11.brightness = get_bits(gb, 2);
- dm->l11.color = get_bits(gb, 2);
- break;
- case 254:
- dm->l254.dm_mode = get_bits(gb, 8);
- dm->l254.dm_version_index = get_bits(gb, 8);
- break;
- default:
- av_log(s->logctx, AV_LOG_WARNING,
- "Unknown Dolby Vision DM v2 level: %u\n", dm->level);
- }
-}
-
-static int parse_ext_blocks(DOVIContext *s, GetBitContext *gb, int ver)
-{
- int num_ext_blocks, ext_block_length, start_pos, parsed_bits;
-
- num_ext_blocks = get_ue_golomb_31(gb);
- align_get_bits(gb);
- if (s->num_ext_blocks + num_ext_blocks > AV_DOVI_MAX_EXT_BLOCKS)
- return AVERROR_INVALIDDATA;
-
- if (!s->ext_blocks) {
- s->ext_blocks = ff_refstruct_allocz(sizeof(AVDOVIDmData) * AV_DOVI_MAX_EXT_BLOCKS);
- if (!s->ext_blocks)
- return AVERROR(ENOMEM);
- }
-
- while (num_ext_blocks--) {
- AVDOVIDmData *dm = &s->ext_blocks[s->num_ext_blocks++];
- ext_block_length = get_ue_golomb_31(gb);
- dm->level = get_bits(gb, 8);
- start_pos = get_bits_count(gb);
-
- switch (ver) {
- case 1: parse_ext_v1(s, gb, dm); break;
- case 2: parse_ext_v2(s, gb, dm, ext_block_length); break;
- }
-
- parsed_bits = get_bits_count(gb) - start_pos;
- if (parsed_bits > ext_block_length * 8)
- return AVERROR_INVALIDDATA;
- skip_bits(gb, ext_block_length * 8 - parsed_bits);
- }
-
- return 0;
-}
-
-int ff_dovi_rpu_parse(DOVIContext *s, const uint8_t *rpu, size_t rpu_size,
- int err_recognition)
-{
- AVDOVIRpuDataHeader *hdr = &s->header;
- GetBitContext *gb = &(GetBitContext){0};
- DOVIVdr *vdr;
- int ret;
-
- uint8_t rpu_type;
- uint8_t vdr_seq_info_present;
- uint8_t vdr_dm_metadata_present;
- uint8_t use_prev_vdr_rpu;
- uint8_t use_nlq;
- uint8_t profile;
-
- if (rpu_size < 5)
- goto fail;
-
- /* Container */
- if (s->cfg.dv_profile == 10 /* dav1.10 */) {
- /* DV inside AV1 re-uses an EMDF container skeleton, but with fixed
- * values - so we can effectively treat this as a magic byte sequence.
- *
- * The exact fields are, as follows:
- * emdf_version : f(2) = 0
- * key_id : f(3) = 6
- * emdf_payload_id : f(5) = 31
- * emdf_payload_id_ext : var(5) = 225
- * smploffste : f(1) = 0
- * duratione : f(1) = 0
- * groupide : f(1) = 0
- * codecdatae : f(1) = 0
- * discard_unknown_payload : f(1) = 1
- */
- const unsigned header_magic = 0x01be6841u;
- unsigned emdf_header, emdf_payload_size, emdf_protection;
- if ((ret = init_get_bits8(gb, rpu, rpu_size)) < 0)
- return ret;
- emdf_header = get_bits_long(gb, 27);
- VALIDATE(emdf_header, header_magic, header_magic);
- emdf_payload_size = get_variable_bits(gb, 8);
- VALIDATE(emdf_payload_size, 6, 512);
- if (emdf_payload_size * 8 > get_bits_left(gb))
- return AVERROR_INVALIDDATA;
-
- /* The payload is not byte-aligned (off by *one* bit, curse Dolby),
- * so copy into a fresh buffer to preserve byte alignment of the
- * RPU struct */
- av_fast_padded_malloc(&s->rpu_buf, &s->rpu_buf_sz, emdf_payload_size);
- if (!s->rpu_buf)
- return AVERROR(ENOMEM);
- for (int i = 0; i < emdf_payload_size; i++)
- s->rpu_buf[i] = get_bits(gb, 8);
- rpu = s->rpu_buf;
- rpu_size = emdf_payload_size;
-
- /* Validate EMDF footer */
- emdf_protection = get_bits(gb, 5 + 12);
- VALIDATE(emdf_protection, 0x400, 0x400);
- } else {
- /* NAL RBSP with prefix and trailing zeroes */
- VALIDATE(rpu[0], 25, 25); /* NAL prefix */
- rpu++;
- rpu_size--;
- /* Strip trailing padding bytes */
- while (rpu_size && rpu[rpu_size - 1] == 0)
- rpu_size--;
- }
-
- if (!rpu_size || rpu[rpu_size - 1] != 0x80)
- goto fail;
-
- if (err_recognition & AV_EF_CRCCHECK) {
- uint32_t crc = av_bswap32(av_crc(av_crc_get_table(AV_CRC_32_IEEE),
- -1, rpu, rpu_size - 1)); /* exclude 0x80 */
- if (crc) {
- av_log(s->logctx, AV_LOG_ERROR, "RPU CRC mismatch: %X\n", crc);
- if (err_recognition & AV_EF_EXPLODE)
- goto fail;
- }
- }
-
- if ((ret = init_get_bits8(gb, rpu, rpu_size)) < 0)
- return ret;
-
- /* RPU header */
- rpu_type = get_bits(gb, 6);
- if (rpu_type != 2) {
- av_log(s->logctx, AV_LOG_WARNING, "Unrecognized RPU type "
- "%"PRIu8", ignoring\n", rpu_type);
- return 0;
- }
-
- hdr->rpu_type = rpu_type;
- hdr->rpu_format = get_bits(gb, 11);
-
- /* Values specific to RPU type 2 */
- hdr->vdr_rpu_profile = get_bits(gb, 4);
- hdr->vdr_rpu_level = get_bits(gb, 4);
-
- vdr_seq_info_present = get_bits1(gb);
- if (vdr_seq_info_present) {
- hdr->chroma_resampling_explicit_filter_flag = get_bits1(gb);
- hdr->coef_data_type = get_bits(gb, 2);
- VALIDATE(hdr->coef_data_type, RPU_COEFF_FIXED, RPU_COEFF_FLOAT);
- switch (hdr->coef_data_type) {
- case RPU_COEFF_FIXED:
- hdr->coef_log2_denom = get_ue_golomb(gb);
- VALIDATE(hdr->coef_log2_denom, 13, 32);
- break;
- case RPU_COEFF_FLOAT:
- hdr->coef_log2_denom = 32; /* arbitrary, choose maximum precision */
- break;
- }
-
- hdr->vdr_rpu_normalized_idc = get_bits(gb, 2);
- hdr->bl_video_full_range_flag = get_bits1(gb);
-
- if ((hdr->rpu_format & 0x700) == 0) {
- int bl_bit_depth_minus8 = get_ue_golomb_31(gb);
- int el_bit_depth_minus8 = get_ue_golomb_31(gb);
- int vdr_bit_depth_minus8 = get_ue_golomb_31(gb);
- VALIDATE(bl_bit_depth_minus8, 0, 8);
- VALIDATE(el_bit_depth_minus8, 0, 8);
- VALIDATE(vdr_bit_depth_minus8, 0, 8);
- hdr->bl_bit_depth = bl_bit_depth_minus8 + 8;
- hdr->el_bit_depth = el_bit_depth_minus8 + 8;
- hdr->vdr_bit_depth = vdr_bit_depth_minus8 + 8;
- hdr->spatial_resampling_filter_flag = get_bits1(gb);
- skip_bits(gb, 3); /* reserved_zero_3bits */
- hdr->el_spatial_resampling_filter_flag = get_bits1(gb);
- hdr->disable_residual_flag = get_bits1(gb);
- }
- } else {
- /* lack of documentation/samples */
- avpriv_request_sample(s->logctx, "Missing RPU VDR sequence info\n");
- ff_dovi_ctx_unref(s);
- return AVERROR_PATCHWELCOME;
- }
-
- vdr_dm_metadata_present = get_bits1(gb);
- use_prev_vdr_rpu = get_bits1(gb);
- use_nlq = (hdr->rpu_format & 0x700) == 0 && !hdr->disable_residual_flag;
-
- profile = s->cfg.dv_profile ? s->cfg.dv_profile : ff_dovi_guess_profile_hevc(hdr);
- if (profile == 5 && use_nlq) {
- av_log(s->logctx, AV_LOG_ERROR, "Profile 5 RPUs should not use NLQ\n");
- goto fail;
- }
-
- if (use_prev_vdr_rpu) {
- int prev_vdr_rpu_id = get_ue_golomb_31(gb);
- VALIDATE(prev_vdr_rpu_id, 0, DOVI_MAX_DM_ID);
- if (!s->vdr[prev_vdr_rpu_id]) {
- av_log(s->logctx, AV_LOG_ERROR, "Unknown previous RPU ID: %u\n",
- prev_vdr_rpu_id);
- goto fail;
- }
- vdr = s->vdr[prev_vdr_rpu_id];
- s->mapping = &vdr->mapping;
- } else {
- int vdr_rpu_id = get_ue_golomb_31(gb);
- VALIDATE(vdr_rpu_id, 0, DOVI_MAX_DM_ID);
- if (!s->vdr[vdr_rpu_id]) {
- s->vdr[vdr_rpu_id] = ff_refstruct_allocz(sizeof(DOVIVdr));
- if (!s->vdr[vdr_rpu_id])
- return AVERROR(ENOMEM);
- }
-
- vdr = s->vdr[vdr_rpu_id];
- s->mapping = &vdr->mapping;
-
- vdr->mapping.vdr_rpu_id = vdr_rpu_id;
- vdr->mapping.mapping_color_space = get_ue_golomb_31(gb);
- vdr->mapping.mapping_chroma_format_idc = get_ue_golomb_31(gb);
-
- for (int c = 0; c < 3; c++) {
- AVDOVIReshapingCurve *curve = &vdr->mapping.curves[c];
- int num_pivots_minus_2 = get_ue_golomb_31(gb);
- int pivot = 0;
-
- VALIDATE(num_pivots_minus_2, 0, AV_DOVI_MAX_PIECES - 1);
- curve->num_pivots = num_pivots_minus_2 + 2;
- for (int i = 0; i < curve->num_pivots; i++) {
- pivot += get_bits(gb, hdr->bl_bit_depth);
- curve->pivots[i] = av_clip_uint16(pivot);
- }
- }
-
- if (use_nlq) {
- int nlq_pivot = 0;
- vdr->mapping.nlq_method_idc = get_bits(gb, 3);
-
- for (int i = 0; i < 2; i++) {
- nlq_pivot += get_bits(gb, hdr->bl_bit_depth);
- vdr->mapping.nlq_pivots[i] = av_clip_uint16(nlq_pivot);
- }
-
- /**
- * The patent mentions another legal value, NLQ_MU_LAW, but it's
- * not documented anywhere how to parse or apply that type of NLQ.
- */
- VALIDATE(vdr->mapping.nlq_method_idc, 0, AV_DOVI_NLQ_LINEAR_DZ);
- } else {
- vdr->mapping.nlq_method_idc = AV_DOVI_NLQ_NONE;
- }
-
- vdr->mapping.num_x_partitions = get_ue_golomb_long(gb) + 1;
- vdr->mapping.num_y_partitions = get_ue_golomb_long(gb) + 1;
- /* End of rpu_data_header(), start of vdr_rpu_data_payload() */
-
- for (int c = 0; c < 3; c++) {
- AVDOVIReshapingCurve *curve = &vdr->mapping.curves[c];
- for (int i = 0; i < curve->num_pivots - 1; i++) {
- int mapping_idc = get_ue_golomb_31(gb);
- VALIDATE(mapping_idc, 0, 1);
- curve->mapping_idc[i] = mapping_idc;
- switch (mapping_idc) {
- case AV_DOVI_MAPPING_POLYNOMIAL: {
- int poly_order_minus1 = get_ue_golomb_31(gb);
- VALIDATE(poly_order_minus1, 0, 1);
- curve->poly_order[i] = poly_order_minus1 + 1;
- if (poly_order_minus1 == 0) {
- int linear_interp_flag = get_bits1(gb);
- if (linear_interp_flag) {
- /* lack of documentation/samples */
- avpriv_request_sample(s->logctx, "Dolby Vision "
- "linear interpolation");
- ff_dovi_ctx_unref(s);
- return AVERROR_PATCHWELCOME;
- }
- }
- for (int k = 0; k <= curve->poly_order[i]; k++)
- curve->poly_coef[i][k] = get_se_coef(gb, hdr);
- break;
- }
- case AV_DOVI_MAPPING_MMR: {
- int mmr_order_minus1 = get_bits(gb, 2);
- VALIDATE(mmr_order_minus1, 0, 2);
- curve->mmr_order[i] = mmr_order_minus1 + 1;
- curve->mmr_constant[i] = get_se_coef(gb, hdr);
- for (int j = 0; j < curve->mmr_order[i]; j++) {
- for (int k = 0; k < 7; k++)
- curve->mmr_coef[i][j][k] = get_se_coef(gb, hdr);
- }
- break;
- }
- }
- }
- }
-
- if (use_nlq) {
- for (int c = 0; c < 3; c++) {
- AVDOVINLQParams *nlq = &vdr->mapping.nlq[c];
- nlq->nlq_offset = get_bits(gb, hdr->el_bit_depth);
- nlq->vdr_in_max = get_ue_coef(gb, hdr);
- switch (vdr->mapping.nlq_method_idc) {
- case AV_DOVI_NLQ_LINEAR_DZ:
- nlq->linear_deadzone_slope = get_ue_coef(gb, hdr);
- nlq->linear_deadzone_threshold = get_ue_coef(gb, hdr);
- break;
- }
- }
- }
- }
-
- if (vdr_dm_metadata_present) {
- AVDOVIColorMetadata *color;
- int affected_dm_id = get_ue_golomb_31(gb);
- int current_dm_id = get_ue_golomb_31(gb);
- VALIDATE(affected_dm_id, 0, DOVI_MAX_DM_ID);
- VALIDATE(current_dm_id, 0, DOVI_MAX_DM_ID);
- if (!s->vdr[affected_dm_id]) {
- s->vdr[affected_dm_id] = ff_refstruct_allocz(sizeof(DOVIVdr));
- if (!s->vdr[affected_dm_id])
- return AVERROR(ENOMEM);
- }
-
- if (!s->vdr[current_dm_id]) {
- av_log(s->logctx, AV_LOG_ERROR, "Unknown previous RPU DM ID: %u\n",
- current_dm_id);
- goto fail;
- }
-
- /* Update current pointer based on current_dm_id */
- vdr = s->vdr[current_dm_id];
- s->color = &vdr->color;
-
- /* Update values of affected_dm_id */
- vdr = s->vdr[affected_dm_id];
- color = &vdr->color;
- color->dm_metadata_id = affected_dm_id;
- color->scene_refresh_flag = get_ue_golomb_31(gb);
- for (int i = 0; i < 9; i++)
- color->ycc_to_rgb_matrix[i] = av_make_q(get_sbits(gb, 16), 1 << 13);
- for (int i = 0; i < 3; i++) {
- int denom = profile == 4 ? (1 << 30) : (1 << 28);
- unsigned offset = get_bits_long(gb, 32);
- if (offset > INT_MAX) {
- /* Ensure the result fits inside AVRational */
- offset >>= 1;
- denom >>= 1;
- }
- color->ycc_to_rgb_offset[i] = av_make_q(offset, denom);
- }
- for (int i = 0; i < 9; i++)
- color->rgb_to_lms_matrix[i] = av_make_q(get_sbits(gb, 16), 1 << 14);
-
- color->signal_eotf = get_bits(gb, 16);
- color->signal_eotf_param0 = get_bits(gb, 16);
- color->signal_eotf_param1 = get_bits(gb, 16);
- color->signal_eotf_param2 = get_bits_long(gb, 32);
- color->signal_bit_depth = get_bits(gb, 5);
- VALIDATE(color->signal_bit_depth, 8, 16);
- color->signal_color_space = get_bits(gb, 2);
- color->signal_chroma_format = get_bits(gb, 2);
- color->signal_full_range_flag = get_bits(gb, 2);
- color->source_min_pq = get_bits(gb, 12);
- color->source_max_pq = get_bits(gb, 12);
- color->source_diagonal = get_bits(gb, 10);
- }
-
- /* Parse extension blocks */
- s->num_ext_blocks = 0;
- if ((ret = parse_ext_blocks(s, gb, 1)) < 0) {
- ff_dovi_ctx_unref(s);
- return ret;
- }
-
- if (get_bits_left(gb) > 48 /* padding + CRC32 + terminator */) {
- if ((ret = parse_ext_blocks(s, gb, 2)) < 0) {
- ff_dovi_ctx_unref(s);
- return ret;
- }
- }
-
- return 0;
-
-fail:
- ff_dovi_ctx_unref(s); /* don't leak potentially invalid state */
- return AVERROR_INVALIDDATA;
-}
diff --git a/libavcodec/dovi_rpu.h b/libavcodec/dovi_rpu.h
index a866bbfebe..0c12a3fee0 100644
--- a/libavcodec/dovi_rpu.h
+++ b/libavcodec/dovi_rpu.h
@@ -100,6 +100,20 @@ int ff_dovi_rpu_parse(DOVIContext *s, const uint8_t *rpu, size_t rpu_size,
*/
int ff_dovi_attach_side_data(DOVIContext *s, AVFrame *frame);
+/***************************************************
+ * The following section is for internal use only. *
+ ***************************************************/
+
+typedef struct DOVIVdr {
+ AVDOVIDataMapping mapping;
+ AVDOVIColorMetadata color;
+} DOVIVdr;
+
+enum {
+ RPU_COEFF_FIXED = 0,
+ RPU_COEFF_FLOAT = 1,
+};
+
/**
* Internal helper function to guess the correct DV profile for HEVC.
*
diff --git a/libavcodec/dovi_rpudec.c b/libavcodec/dovi_rpudec.c
new file mode 100644
index 0000000000..7c7eda9d09
--- /dev/null
+++ b/libavcodec/dovi_rpudec.c
@@ -0,0 +1,635 @@
+/*
+ * Dolby Vision RPU decoder
+ *
+ * Copyright (C) 2021 Jan Ekström
+ * Copyright (C) 2021-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/mem.h"
+#include "libavutil/crc.h"
+
+#include "avcodec.h"
+#include "dovi_rpu.h"
+#include "golomb.h"
+#include "get_bits.h"
+#include "refstruct.h"
+
+int ff_dovi_attach_side_data(DOVIContext *s, AVFrame *frame)
+{
+ AVFrameSideData *sd;
+ AVBufferRef *buf;
+ AVDOVIMetadata *dovi;
+ size_t dovi_size, ext_sz;
+
+ if (!s->mapping || !s->color)
+ return 0; /* incomplete dovi metadata */
+
+ dovi = av_dovi_metadata_alloc(&dovi_size);
+ if (!dovi)
+ return AVERROR(ENOMEM);
+
+ buf = av_buffer_create((uint8_t *) dovi, dovi_size, NULL, NULL, 0);
+ if (!buf) {
+ av_free(dovi);
+ return AVERROR(ENOMEM);
+ }
+
+ sd = av_frame_new_side_data_from_buf(frame, AV_FRAME_DATA_DOVI_METADATA, buf);
+ if (!sd) {
+ av_buffer_unref(&buf);
+ return AVERROR(ENOMEM);
+ }
+
+ /* Copy only the parts of these structs known to us at compiler-time. */
+#define COPY(t, a, b, last) memcpy(a, b, offsetof(t, last) + sizeof((b)->last))
+ COPY(AVDOVIRpuDataHeader, av_dovi_get_header(dovi), &s->header, disable_residual_flag);
+ COPY(AVDOVIDataMapping, av_dovi_get_mapping(dovi), s->mapping, nlq_pivots);
+ COPY(AVDOVIColorMetadata, av_dovi_get_color(dovi), s->color, source_diagonal);
+ ext_sz = FFMIN(sizeof(AVDOVIDmData), dovi->ext_block_size);
+ for (int i = 0; i < s->num_ext_blocks; i++)
+ memcpy(av_dovi_get_ext(dovi, i), &s->ext_blocks[i], ext_sz);
+ dovi->num_ext_blocks = s->num_ext_blocks;
+ return 0;
+}
+
+static inline uint64_t get_ue_coef(GetBitContext *gb, const AVDOVIRpuDataHeader *hdr)
+{
+ uint64_t ipart;
+ union { uint32_t u32; float f32; } fpart;
+
+ switch (hdr->coef_data_type) {
+ case RPU_COEFF_FIXED:
+ ipart = get_ue_golomb_long(gb);
+ fpart.u32 = get_bits_long(gb, hdr->coef_log2_denom);
+ return (ipart << hdr->coef_log2_denom) | fpart.u32;
+
+ case RPU_COEFF_FLOAT:
+ fpart.u32 = get_bits_long(gb, 32);
+ return fpart.f32 * (1LL << hdr->coef_log2_denom);
+ }
+
+ return 0; /* unreachable */
+}
+
+static inline int64_t get_se_coef(GetBitContext *gb, const AVDOVIRpuDataHeader *hdr)
+{
+ int64_t ipart;
+ union { uint32_t u32; float f32; } fpart;
+
+ switch (hdr->coef_data_type) {
+ case RPU_COEFF_FIXED:
+ ipart = get_se_golomb_long(gb);
+ fpart.u32 = get_bits_long(gb, hdr->coef_log2_denom);
+ return ipart * (1LL << hdr->coef_log2_denom) | fpart.u32;
+
+ case RPU_COEFF_FLOAT:
+ fpart.u32 = get_bits_long(gb, 32);
+ return fpart.f32 * (1LL << hdr->coef_log2_denom);
+ }
+
+ return 0; /* unreachable */
+}
+
+static inline unsigned get_variable_bits(GetBitContext *gb, int n)
+{
+ unsigned int value = get_bits(gb, n);
+ int read_more = get_bits1(gb);
+ while (read_more) {
+ value = (value + 1) << n;
+ value |= get_bits(gb, n);
+ read_more = get_bits1(gb);
+ }
+ return value;
+}
+
+#define VALIDATE(VAR, MIN, MAX) \
+ do { \
+ if (VAR < MIN || VAR > MAX) { \
+ av_log(s->logctx, AV_LOG_ERROR, "RPU validation failed: " \
+ #MIN" <= "#VAR" = %d <= "#MAX"\n", (int) VAR); \
+ goto fail; \
+ } \
+ } while (0)
+
+static void parse_ext_v1(DOVIContext *s, GetBitContext *gb, AVDOVIDmData *dm)
+{
+ switch (dm->level) {
+ case 1:
+ dm->l1.min_pq = get_bits(gb, 12);
+ dm->l1.max_pq = get_bits(gb, 12);
+ dm->l1.avg_pq = get_bits(gb, 12);
+ break;
+ case 2:
+ dm->l2.target_max_pq = get_bits(gb, 12);
+ dm->l2.trim_slope = get_bits(gb, 12);
+ dm->l2.trim_offset = get_bits(gb, 12);
+ dm->l2.trim_power = get_bits(gb, 12);
+ dm->l2.trim_chroma_weight = get_bits(gb, 12);
+ dm->l2.trim_saturation_gain = get_bits(gb, 12);
+ dm->l2.ms_weight = get_bits(gb, 13) - 8192;
+ break;
+ case 4:
+ dm->l4.anchor_pq = get_bits(gb, 12);
+ dm->l4.anchor_power = get_bits(gb, 12);
+ break;
+ case 5:
+ dm->l5.left_offset = get_bits(gb, 13);
+ dm->l5.right_offset = get_bits(gb, 13);
+ dm->l5.top_offset = get_bits(gb, 13);
+ dm->l5.bottom_offset = get_bits(gb, 13);
+ break;
+ case 6:
+ dm->l6.max_luminance = get_bits(gb, 16);
+ dm->l6.min_luminance = get_bits(gb, 16);
+ dm->l6.max_cll = get_bits(gb, 16);
+ dm->l6.max_fall = get_bits(gb, 16);
+ break;
+ case 255:
+ dm->l255.dm_run_mode = get_bits(gb, 8);
+ dm->l255.dm_run_version = get_bits(gb, 8);
+ for (int i = 0; i < 4; i++)
+ dm->l255.dm_debug[i] = get_bits(gb, 8);
+ break;
+ default:
+ av_log(s->logctx, AV_LOG_WARNING,
+ "Unknown Dolby Vision DM v1 level: %u\n", dm->level);
+ }
+}
+
+static AVCIExy get_cie_xy(GetBitContext *gb)
+{
+ AVCIExy xy;
+ const int denom = 32767;
+ xy.x = av_make_q(get_sbits(gb, 16), denom);
+ xy.y = av_make_q(get_sbits(gb, 16), denom);
+ return xy;
+}
+
+static void parse_ext_v2(DOVIContext *s, GetBitContext *gb, AVDOVIDmData *dm,
+ int ext_block_length)
+{
+ switch (dm->level) {
+ case 3:
+ dm->l3.min_pq_offset = get_bits(gb, 12);
+ dm->l3.max_pq_offset = get_bits(gb, 12);
+ dm->l3.avg_pq_offset = get_bits(gb, 12);
+ break;
+ case 8:
+ dm->l8.target_display_index = get_bits(gb, 8);
+ dm->l8.trim_slope = get_bits(gb, 12);
+ dm->l8.trim_offset = get_bits(gb, 12);
+ dm->l8.trim_power = get_bits(gb, 12);
+ dm->l8.trim_chroma_weight = get_bits(gb, 12);
+ dm->l8.trim_saturation_gain = get_bits(gb, 12);
+ dm->l8.ms_weight = get_bits(gb, 12) - 8192;
+ if (ext_block_length < 12)
+ break;
+ dm->l8.target_mid_contrast = get_bits(gb, 12);
+ if (ext_block_length < 13)
+ break;
+ dm->l8.clip_trim = get_bits(gb, 12);
+ if (ext_block_length < 19)
+ break;
+ for (int i = 0; i < 6; i++)
+ dm->l8.saturation_vector_field[i] = get_bits(gb, 8);
+ if (ext_block_length < 25)
+ break;
+ for (int i = 0; i < 6; i++)
+ dm->l8.hue_vector_field[i] = get_bits(gb, 8);
+ break;
+ case 9:
+ dm->l9.source_primary_index = get_bits(gb, 8);
+ if (ext_block_length < 17)
+ break;
+ dm->l9.source_display_primaries.prim.r = get_cie_xy(gb);
+ dm->l9.source_display_primaries.prim.g = get_cie_xy(gb);
+ dm->l9.source_display_primaries.prim.b = get_cie_xy(gb);
+ dm->l9.source_display_primaries.wp = get_cie_xy(gb);
+ break;
+ case 10:
+ dm->l10.target_display_index = get_bits(gb, 8);
+ dm->l10.target_max_pq = get_bits(gb, 12);
+ dm->l10.target_min_pq = get_bits(gb, 12);
+ dm->l10.target_primary_index = get_bits(gb, 8);
+ if (ext_block_length < 21)
+ break;
+ dm->l10.target_display_primaries.prim.r = get_cie_xy(gb);
+ dm->l10.target_display_primaries.prim.g = get_cie_xy(gb);
+ dm->l10.target_display_primaries.prim.b = get_cie_xy(gb);
+ dm->l10.target_display_primaries.wp = get_cie_xy(gb);
+ break;
+ case 11:
+ dm->l11.content_type = get_bits(gb, 8);
+ dm->l11.whitepoint = get_bits(gb, 4);
+ dm->l11.reference_mode_flag = get_bits1(gb);
+ skip_bits(gb, 3); /* reserved */
+ dm->l11.sharpness = get_bits(gb, 2);
+ dm->l11.noise_reduction = get_bits(gb, 2);
+ dm->l11.mpeg_noise_reduction = get_bits(gb, 2);
+ dm->l11.frame_rate_conversion = get_bits(gb, 2);
+ dm->l11.brightness = get_bits(gb, 2);
+ dm->l11.color = get_bits(gb, 2);
+ break;
+ case 254:
+ dm->l254.dm_mode = get_bits(gb, 8);
+ dm->l254.dm_version_index = get_bits(gb, 8);
+ break;
+ default:
+ av_log(s->logctx, AV_LOG_WARNING,
+ "Unknown Dolby Vision DM v2 level: %u\n", dm->level);
+ }
+}
+
+static int parse_ext_blocks(DOVIContext *s, GetBitContext *gb, int ver)
+{
+ int num_ext_blocks, ext_block_length, start_pos, parsed_bits;
+
+ num_ext_blocks = get_ue_golomb_31(gb);
+ align_get_bits(gb);
+ if (s->num_ext_blocks + num_ext_blocks > AV_DOVI_MAX_EXT_BLOCKS)
+ return AVERROR_INVALIDDATA;
+
+ if (!s->ext_blocks) {
+ s->ext_blocks = ff_refstruct_allocz(sizeof(AVDOVIDmData) * AV_DOVI_MAX_EXT_BLOCKS);
+ if (!s->ext_blocks)
+ return AVERROR(ENOMEM);
+ }
+
+ while (num_ext_blocks--) {
+ AVDOVIDmData *dm = &s->ext_blocks[s->num_ext_blocks++];
+ ext_block_length = get_ue_golomb_31(gb);
+ dm->level = get_bits(gb, 8);
+ start_pos = get_bits_count(gb);
+
+ switch (ver) {
+ case 1: parse_ext_v1(s, gb, dm); break;
+ case 2: parse_ext_v2(s, gb, dm, ext_block_length); break;
+ }
+
+ parsed_bits = get_bits_count(gb) - start_pos;
+ if (parsed_bits > ext_block_length * 8)
+ return AVERROR_INVALIDDATA;
+ skip_bits(gb, ext_block_length * 8 - parsed_bits);
+ }
+
+ return 0;
+}
+
+int ff_dovi_rpu_parse(DOVIContext *s, const uint8_t *rpu, size_t rpu_size,
+ int err_recognition)
+{
+ AVDOVIRpuDataHeader *hdr = &s->header;
+ GetBitContext *gb = &(GetBitContext){0};
+ DOVIVdr *vdr;
+ int ret;
+
+ uint8_t rpu_type;
+ uint8_t vdr_seq_info_present;
+ uint8_t vdr_dm_metadata_present;
+ uint8_t use_prev_vdr_rpu;
+ uint8_t use_nlq;
+ uint8_t profile;
+
+ if (rpu_size < 5)
+ goto fail;
+
+ /* Container */
+ if (s->cfg.dv_profile == 10 /* dav1.10 */) {
+ /* DV inside AV1 re-uses an EMDF container skeleton, but with fixed
+ * values - so we can effectively treat this as a magic byte sequence.
+ *
+ * The exact fields are, as follows:
+ * emdf_version : f(2) = 0
+ * key_id : f(3) = 6
+ * emdf_payload_id : f(5) = 31
+ * emdf_payload_id_ext : var(5) = 225
+ * smploffste : f(1) = 0
+ * duratione : f(1) = 0
+ * groupide : f(1) = 0
+ * codecdatae : f(1) = 0
+ * discard_unknown_payload : f(1) = 1
+ */
+ const unsigned header_magic = 0x01be6841u;
+ unsigned emdf_header, emdf_payload_size, emdf_protection;
+ if ((ret = init_get_bits8(gb, rpu, rpu_size)) < 0)
+ return ret;
+ emdf_header = get_bits_long(gb, 27);
+ VALIDATE(emdf_header, header_magic, header_magic);
+ emdf_payload_size = get_variable_bits(gb, 8);
+ VALIDATE(emdf_payload_size, 6, 512);
+ if (emdf_payload_size * 8 > get_bits_left(gb))
+ return AVERROR_INVALIDDATA;
+
+ /* The payload is not byte-aligned (off by *one* bit, curse Dolby),
+ * so copy into a fresh buffer to preserve byte alignment of the
+ * RPU struct */
+ av_fast_padded_malloc(&s->rpu_buf, &s->rpu_buf_sz, emdf_payload_size);
+ if (!s->rpu_buf)
+ return AVERROR(ENOMEM);
+ for (int i = 0; i < emdf_payload_size; i++)
+ s->rpu_buf[i] = get_bits(gb, 8);
+ rpu = s->rpu_buf;
+ rpu_size = emdf_payload_size;
+
+ /* Validate EMDF footer */
+ emdf_protection = get_bits(gb, 5 + 12);
+ VALIDATE(emdf_protection, 0x400, 0x400);
+ } else {
+ /* NAL RBSP with prefix and trailing zeroes */
+ VALIDATE(rpu[0], 25, 25); /* NAL prefix */
+ rpu++;
+ rpu_size--;
+ /* Strip trailing padding bytes */
+ while (rpu_size && rpu[rpu_size - 1] == 0)
+ rpu_size--;
+ }
+
+ if (!rpu_size || rpu[rpu_size - 1] != 0x80)
+ goto fail;
+
+ if (err_recognition & AV_EF_CRCCHECK) {
+ uint32_t crc = av_bswap32(av_crc(av_crc_get_table(AV_CRC_32_IEEE),
+ -1, rpu, rpu_size - 1)); /* exclude 0x80 */
+ if (crc) {
+ av_log(s->logctx, AV_LOG_ERROR, "RPU CRC mismatch: %X\n", crc);
+ if (err_recognition & AV_EF_EXPLODE)
+ goto fail;
+ }
+ }
+
+ if ((ret = init_get_bits8(gb, rpu, rpu_size)) < 0)
+ return ret;
+
+ /* RPU header */
+ rpu_type = get_bits(gb, 6);
+ if (rpu_type != 2) {
+ av_log(s->logctx, AV_LOG_WARNING, "Unrecognized RPU type "
+ "%"PRIu8", ignoring\n", rpu_type);
+ return 0;
+ }
+
+ hdr->rpu_type = rpu_type;
+ hdr->rpu_format = get_bits(gb, 11);
+
+ /* Values specific to RPU type 2 */
+ hdr->vdr_rpu_profile = get_bits(gb, 4);
+ hdr->vdr_rpu_level = get_bits(gb, 4);
+
+ vdr_seq_info_present = get_bits1(gb);
+ if (vdr_seq_info_present) {
+ hdr->chroma_resampling_explicit_filter_flag = get_bits1(gb);
+ hdr->coef_data_type = get_bits(gb, 2);
+ VALIDATE(hdr->coef_data_type, RPU_COEFF_FIXED, RPU_COEFF_FLOAT);
+ switch (hdr->coef_data_type) {
+ case RPU_COEFF_FIXED:
+ hdr->coef_log2_denom = get_ue_golomb(gb);
+ VALIDATE(hdr->coef_log2_denom, 13, 32);
+ break;
+ case RPU_COEFF_FLOAT:
+ hdr->coef_log2_denom = 32; /* arbitrary, choose maximum precision */
+ break;
+ }
+
+ hdr->vdr_rpu_normalized_idc = get_bits(gb, 2);
+ hdr->bl_video_full_range_flag = get_bits1(gb);
+
+ if ((hdr->rpu_format & 0x700) == 0) {
+ int bl_bit_depth_minus8 = get_ue_golomb_31(gb);
+ int el_bit_depth_minus8 = get_ue_golomb_31(gb);
+ int vdr_bit_depth_minus8 = get_ue_golomb_31(gb);
+ VALIDATE(bl_bit_depth_minus8, 0, 8);
+ VALIDATE(el_bit_depth_minus8, 0, 8);
+ VALIDATE(vdr_bit_depth_minus8, 0, 8);
+ hdr->bl_bit_depth = bl_bit_depth_minus8 + 8;
+ hdr->el_bit_depth = el_bit_depth_minus8 + 8;
+ hdr->vdr_bit_depth = vdr_bit_depth_minus8 + 8;
+ hdr->spatial_resampling_filter_flag = get_bits1(gb);
+ skip_bits(gb, 3); /* reserved_zero_3bits */
+ hdr->el_spatial_resampling_filter_flag = get_bits1(gb);
+ hdr->disable_residual_flag = get_bits1(gb);
+ }
+ } else {
+ /* lack of documentation/samples */
+ avpriv_request_sample(s->logctx, "Missing RPU VDR sequence info\n");
+ ff_dovi_ctx_unref(s);
+ return AVERROR_PATCHWELCOME;
+ }
+
+ vdr_dm_metadata_present = get_bits1(gb);
+ use_prev_vdr_rpu = get_bits1(gb);
+ use_nlq = (hdr->rpu_format & 0x700) == 0 && !hdr->disable_residual_flag;
+
+ profile = s->cfg.dv_profile ? s->cfg.dv_profile : ff_dovi_guess_profile_hevc(hdr);
+ if (profile == 5 && use_nlq) {
+ av_log(s->logctx, AV_LOG_ERROR, "Profile 5 RPUs should not use NLQ\n");
+ goto fail;
+ }
+
+ if (use_prev_vdr_rpu) {
+ int prev_vdr_rpu_id = get_ue_golomb_31(gb);
+ VALIDATE(prev_vdr_rpu_id, 0, DOVI_MAX_DM_ID);
+ if (!s->vdr[prev_vdr_rpu_id]) {
+ av_log(s->logctx, AV_LOG_ERROR, "Unknown previous RPU ID: %u\n",
+ prev_vdr_rpu_id);
+ goto fail;
+ }
+ vdr = s->vdr[prev_vdr_rpu_id];
+ s->mapping = &vdr->mapping;
+ } else {
+ int vdr_rpu_id = get_ue_golomb_31(gb);
+ VALIDATE(vdr_rpu_id, 0, DOVI_MAX_DM_ID);
+ if (!s->vdr[vdr_rpu_id]) {
+ s->vdr[vdr_rpu_id] = ff_refstruct_allocz(sizeof(DOVIVdr));
+ if (!s->vdr[vdr_rpu_id])
+ return AVERROR(ENOMEM);
+ }
+
+ vdr = s->vdr[vdr_rpu_id];
+ s->mapping = &vdr->mapping;
+
+ vdr->mapping.vdr_rpu_id = vdr_rpu_id;
+ vdr->mapping.mapping_color_space = get_ue_golomb_31(gb);
+ vdr->mapping.mapping_chroma_format_idc = get_ue_golomb_31(gb);
+
+ for (int c = 0; c < 3; c++) {
+ AVDOVIReshapingCurve *curve = &vdr->mapping.curves[c];
+ int num_pivots_minus_2 = get_ue_golomb_31(gb);
+ int pivot = 0;
+
+ VALIDATE(num_pivots_minus_2, 0, AV_DOVI_MAX_PIECES - 1);
+ curve->num_pivots = num_pivots_minus_2 + 2;
+ for (int i = 0; i < curve->num_pivots; i++) {
+ pivot += get_bits(gb, hdr->bl_bit_depth);
+ curve->pivots[i] = av_clip_uint16(pivot);
+ }
+ }
+
+ if (use_nlq) {
+ int nlq_pivot = 0;
+ vdr->mapping.nlq_method_idc = get_bits(gb, 3);
+
+ for (int i = 0; i < 2; i++) {
+ nlq_pivot += get_bits(gb, hdr->bl_bit_depth);
+ vdr->mapping.nlq_pivots[i] = av_clip_uint16(nlq_pivot);
+ }
+
+ /**
+ * The patent mentions another legal value, NLQ_MU_LAW, but it's
+ * not documented anywhere how to parse or apply that type of NLQ.
+ */
+ VALIDATE(vdr->mapping.nlq_method_idc, 0, AV_DOVI_NLQ_LINEAR_DZ);
+ } else {
+ vdr->mapping.nlq_method_idc = AV_DOVI_NLQ_NONE;
+ }
+
+ vdr->mapping.num_x_partitions = get_ue_golomb_long(gb) + 1;
+ vdr->mapping.num_y_partitions = get_ue_golomb_long(gb) + 1;
+ /* End of rpu_data_header(), start of vdr_rpu_data_payload() */
+
+ for (int c = 0; c < 3; c++) {
+ AVDOVIReshapingCurve *curve = &vdr->mapping.curves[c];
+ for (int i = 0; i < curve->num_pivots - 1; i++) {
+ int mapping_idc = get_ue_golomb_31(gb);
+ VALIDATE(mapping_idc, 0, 1);
+ curve->mapping_idc[i] = mapping_idc;
+ switch (mapping_idc) {
+ case AV_DOVI_MAPPING_POLYNOMIAL: {
+ int poly_order_minus1 = get_ue_golomb_31(gb);
+ VALIDATE(poly_order_minus1, 0, 1);
+ curve->poly_order[i] = poly_order_minus1 + 1;
+ if (poly_order_minus1 == 0) {
+ int linear_interp_flag = get_bits1(gb);
+ if (linear_interp_flag) {
+ /* lack of documentation/samples */
+ avpriv_request_sample(s->logctx, "Dolby Vision "
+ "linear interpolation");
+ ff_dovi_ctx_unref(s);
+ return AVERROR_PATCHWELCOME;
+ }
+ }
+ for (int k = 0; k <= curve->poly_order[i]; k++)
+ curve->poly_coef[i][k] = get_se_coef(gb, hdr);
+ break;
+ }
+ case AV_DOVI_MAPPING_MMR: {
+ int mmr_order_minus1 = get_bits(gb, 2);
+ VALIDATE(mmr_order_minus1, 0, 2);
+ curve->mmr_order[i] = mmr_order_minus1 + 1;
+ curve->mmr_constant[i] = get_se_coef(gb, hdr);
+ for (int j = 0; j < curve->mmr_order[i]; j++) {
+ for (int k = 0; k < 7; k++)
+ curve->mmr_coef[i][j][k] = get_se_coef(gb, hdr);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ if (use_nlq) {
+ for (int c = 0; c < 3; c++) {
+ AVDOVINLQParams *nlq = &vdr->mapping.nlq[c];
+ nlq->nlq_offset = get_bits(gb, hdr->el_bit_depth);
+ nlq->vdr_in_max = get_ue_coef(gb, hdr);
+ switch (vdr->mapping.nlq_method_idc) {
+ case AV_DOVI_NLQ_LINEAR_DZ:
+ nlq->linear_deadzone_slope = get_ue_coef(gb, hdr);
+ nlq->linear_deadzone_threshold = get_ue_coef(gb, hdr);
+ break;
+ }
+ }
+ }
+ }
+
+ if (vdr_dm_metadata_present) {
+ AVDOVIColorMetadata *color;
+ int affected_dm_id = get_ue_golomb_31(gb);
+ int current_dm_id = get_ue_golomb_31(gb);
+ VALIDATE(affected_dm_id, 0, DOVI_MAX_DM_ID);
+ VALIDATE(current_dm_id, 0, DOVI_MAX_DM_ID);
+ if (!s->vdr[affected_dm_id]) {
+ s->vdr[affected_dm_id] = ff_refstruct_allocz(sizeof(DOVIVdr));
+ if (!s->vdr[affected_dm_id])
+ return AVERROR(ENOMEM);
+ }
+
+ if (!s->vdr[current_dm_id]) {
+ av_log(s->logctx, AV_LOG_ERROR, "Unknown previous RPU DM ID: %u\n",
+ current_dm_id);
+ goto fail;
+ }
+
+ /* Update current pointer based on current_dm_id */
+ vdr = s->vdr[current_dm_id];
+ s->color = &vdr->color;
+
+ /* Update values of affected_dm_id */
+ vdr = s->vdr[affected_dm_id];
+ color = &vdr->color;
+ color->dm_metadata_id = affected_dm_id;
+ color->scene_refresh_flag = get_ue_golomb_31(gb);
+ for (int i = 0; i < 9; i++)
+ color->ycc_to_rgb_matrix[i] = av_make_q(get_sbits(gb, 16), 1 << 13);
+ for (int i = 0; i < 3; i++) {
+ int denom = profile == 4 ? (1 << 30) : (1 << 28);
+ unsigned offset = get_bits_long(gb, 32);
+ if (offset > INT_MAX) {
+ /* Ensure the result fits inside AVRational */
+ offset >>= 1;
+ denom >>= 1;
+ }
+ color->ycc_to_rgb_offset[i] = av_make_q(offset, denom);
+ }
+ for (int i = 0; i < 9; i++)
+ color->rgb_to_lms_matrix[i] = av_make_q(get_sbits(gb, 16), 1 << 14);
+
+ color->signal_eotf = get_bits(gb, 16);
+ color->signal_eotf_param0 = get_bits(gb, 16);
+ color->signal_eotf_param1 = get_bits(gb, 16);
+ color->signal_eotf_param2 = get_bits_long(gb, 32);
+ color->signal_bit_depth = get_bits(gb, 5);
+ VALIDATE(color->signal_bit_depth, 8, 16);
+ color->signal_color_space = get_bits(gb, 2);
+ color->signal_chroma_format = get_bits(gb, 2);
+ color->signal_full_range_flag = get_bits(gb, 2);
+ color->source_min_pq = get_bits(gb, 12);
+ color->source_max_pq = get_bits(gb, 12);
+ color->source_diagonal = get_bits(gb, 10);
+ }
+
+ /* Parse extension blocks */
+ s->num_ext_blocks = 0;
+ if ((ret = parse_ext_blocks(s, gb, 1)) < 0) {
+ ff_dovi_ctx_unref(s);
+ return ret;
+ }
+
+ if (get_bits_left(gb) > 48 /* padding + CRC32 + terminator */) {
+ if ((ret = parse_ext_blocks(s, gb, 2)) < 0) {
+ ff_dovi_ctx_unref(s);
+ return ret;
+ }
+ }
+
+ return 0;
+
+fail:
+ ff_dovi_ctx_unref(s); /* don't leak potentially invalid state */
+ return AVERROR_INVALIDDATA;
+}