xref: /third_party/ffmpeg/libavcodec/dovi_rpu.c (revision cabdff1a)
1/*
2 * Dolby Vision RPU decoder
3 *
4 * Copyright (C) 2021 Jan Ekström
5 * Copyright (C) 2021 Niklas Haas
6 *
7 * This file is part of FFmpeg.
8 *
9 * FFmpeg is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
13 *
14 * FFmpeg is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with FFmpeg; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 */
23
24#include "libavutil/buffer.h"
25
26#include "dovi_rpu.h"
27#include "golomb.h"
28#include "get_bits.h"
29
30enum {
31    RPU_COEFF_FIXED = 0,
32    RPU_COEFF_FLOAT = 1,
33};
34
35/**
36 * Private contents of vdr_ref.
37 */
38typedef struct DOVIVdrRef {
39    AVDOVIDataMapping mapping;
40    AVDOVIColorMetadata color;
41} DOVIVdrRef;
42
43void ff_dovi_ctx_unref(DOVIContext *s)
44{
45    for (int i = 0; i < FF_ARRAY_ELEMS(s->vdr_ref); i++)
46        av_buffer_unref(&s->vdr_ref[i]);
47
48    *s = (DOVIContext) {
49        .logctx = s->logctx,
50    };
51}
52
53void ff_dovi_ctx_flush(DOVIContext *s)
54{
55    for (int i = 0; i < FF_ARRAY_ELEMS(s->vdr_ref); i++)
56        av_buffer_unref(&s->vdr_ref[i]);
57
58    *s = (DOVIContext) {
59        .logctx = s->logctx,
60        .dv_profile = s->dv_profile,
61    };
62}
63
64int ff_dovi_ctx_replace(DOVIContext *s, const DOVIContext *s0)
65{
66    int ret;
67    s->logctx = s0->logctx;
68    s->mapping = s0->mapping;
69    s->color = s0->color;
70    s->dv_profile = s0->dv_profile;
71    for (int i = 0; i < DOVI_MAX_DM_ID; i++) {
72        if ((ret = av_buffer_replace(&s->vdr_ref[i], s0->vdr_ref[i])) < 0)
73            goto fail;
74    }
75
76    return 0;
77
78fail:
79    ff_dovi_ctx_unref(s);
80    return ret;
81}
82
83void ff_dovi_update_cfg(DOVIContext *s, const AVDOVIDecoderConfigurationRecord *cfg)
84{
85    if (!cfg)
86        return;
87
88    s->dv_profile = cfg->dv_profile;
89}
90
91int ff_dovi_attach_side_data(DOVIContext *s, AVFrame *frame)
92{
93    AVFrameSideData *sd;
94    AVBufferRef *buf;
95    AVDOVIMetadata *dovi;
96    size_t dovi_size;
97
98    if (!s->mapping || !s->color)
99        return 0; /* incomplete dovi metadata */
100
101    dovi = av_dovi_metadata_alloc(&dovi_size);
102    if (!dovi)
103        return AVERROR(ENOMEM);
104
105    buf = av_buffer_create((uint8_t *) dovi, dovi_size, NULL, NULL, 0);
106    if (!buf) {
107        av_free(dovi);
108        return AVERROR(ENOMEM);
109    }
110
111    sd = av_frame_new_side_data_from_buf(frame, AV_FRAME_DATA_DOVI_METADATA, buf);
112    if (!sd) {
113        av_buffer_unref(&buf);
114        return AVERROR(ENOMEM);
115    }
116
117    /* Copy only the parts of these structs known to us at compiler-time. */
118#define COPY(t, a, b, last) memcpy(a, b, offsetof(t, last) + sizeof((b)->last))
119    COPY(AVDOVIRpuDataHeader, av_dovi_get_header(dovi), &s->header, disable_residual_flag);
120    COPY(AVDOVIDataMapping, av_dovi_get_mapping(dovi), s->mapping, nlq[2].linear_deadzone_threshold);
121    COPY(AVDOVIColorMetadata, av_dovi_get_color(dovi), s->color, source_diagonal);
122    return 0;
123}
124
125static int guess_profile(const AVDOVIRpuDataHeader *hdr)
126{
127    switch (hdr->vdr_rpu_profile) {
128    case 0:
129        if (hdr->bl_video_full_range_flag)
130            return 5;
131        break;
132    case 1:
133        if (hdr->el_spatial_resampling_filter_flag && !hdr->disable_residual_flag) {
134            if (hdr->vdr_bit_depth == 12) {
135                return 7;
136            } else {
137                return 4;
138            }
139        } else {
140            return 8;
141        }
142    }
143
144    return 0; /* unknown */
145}
146
147static inline uint64_t get_ue_coef(GetBitContext *gb, const AVDOVIRpuDataHeader *hdr)
148{
149    uint64_t ipart;
150    union { uint32_t u32; float f32; } fpart;
151
152    switch (hdr->coef_data_type) {
153    case RPU_COEFF_FIXED:
154        ipart = get_ue_golomb_long(gb);
155        fpart.u32 = get_bits_long(gb, hdr->coef_log2_denom);
156        return (ipart << hdr->coef_log2_denom) + fpart.u32;
157
158    case RPU_COEFF_FLOAT:
159        fpart.u32 = get_bits_long(gb, 32);
160        return fpart.f32 * (1LL << hdr->coef_log2_denom);
161    }
162
163    return 0; /* unreachable */
164}
165
166static inline int64_t get_se_coef(GetBitContext *gb, const AVDOVIRpuDataHeader *hdr)
167{
168    int64_t ipart;
169    union { uint32_t u32; float f32; } fpart;
170
171    switch (hdr->coef_data_type) {
172    case RPU_COEFF_FIXED:
173        ipart = get_se_golomb_long(gb);
174        fpart.u32 = get_bits_long(gb, hdr->coef_log2_denom);
175        return ipart * (1LL << hdr->coef_log2_denom) + fpart.u32;
176
177    case RPU_COEFF_FLOAT:
178        fpart.u32 = get_bits_long(gb, 32);
179        return fpart.f32 * (1LL << hdr->coef_log2_denom);
180    }
181
182    return 0; /* unreachable */
183}
184
185#define VALIDATE(VAR, MIN, MAX)                                                 \
186    do {                                                                        \
187        if (VAR < MIN || VAR > MAX) {                                           \
188            av_log(s->logctx, AV_LOG_ERROR, "RPU validation failed: "           \
189                   #MIN" <= "#VAR" = %d <= "#MAX"\n", (int) VAR);               \
190            goto fail;                                                          \
191        }                                                                       \
192    } while (0)
193
194int ff_dovi_rpu_parse(DOVIContext *s, const uint8_t *rpu, size_t rpu_size)
195{
196    AVDOVIRpuDataHeader *hdr = &s->header;
197    GetBitContext *gb = &(GetBitContext){0};
198    DOVIVdrRef *vdr;
199    int ret;
200
201    uint8_t nal_prefix;
202    uint8_t rpu_type;
203    uint8_t vdr_seq_info_present;
204    uint8_t vdr_dm_metadata_present;
205    uint8_t use_prev_vdr_rpu;
206    uint8_t use_nlq;
207    uint8_t profile;
208    if ((ret = init_get_bits8(gb, rpu, rpu_size)) < 0)
209        return ret;
210
211    /* RPU header, common values */
212    nal_prefix = get_bits(gb, 8);
213    VALIDATE(nal_prefix, 25, 25);
214    rpu_type = get_bits(gb, 6);
215    if (rpu_type != 2) {
216        av_log(s->logctx, AV_LOG_WARNING, "Unrecognized RPU type "
217               "%"PRIu8", ignoring\n", rpu_type);
218        return 0;
219    }
220
221    hdr->rpu_type = rpu_type;
222    hdr->rpu_format = get_bits(gb, 11);
223
224    /* Values specific to RPU type 2 */
225    hdr->vdr_rpu_profile = get_bits(gb, 4);
226    hdr->vdr_rpu_level = get_bits(gb, 4);
227
228    vdr_seq_info_present = get_bits1(gb);
229    if (vdr_seq_info_present) {
230        hdr->chroma_resampling_explicit_filter_flag = get_bits1(gb);
231        hdr->coef_data_type = get_bits(gb, 2);
232        VALIDATE(hdr->coef_data_type, RPU_COEFF_FIXED, RPU_COEFF_FLOAT);
233        switch (hdr->coef_data_type) {
234        case RPU_COEFF_FIXED:
235            hdr->coef_log2_denom = get_ue_golomb(gb);
236            VALIDATE(hdr->coef_log2_denom, 13, 32);
237            break;
238        case RPU_COEFF_FLOAT:
239            hdr->coef_log2_denom = 32; /* arbitrary, choose maximum precision */
240            break;
241        }
242
243        hdr->vdr_rpu_normalized_idc = get_bits(gb, 2);
244        hdr->bl_video_full_range_flag = get_bits1(gb);
245
246        if ((hdr->rpu_format & 0x700) == 0) {
247            int bl_bit_depth_minus8 = get_ue_golomb_31(gb);
248            int el_bit_depth_minus8 = get_ue_golomb_31(gb);
249            int vdr_bit_depth_minus8 = get_ue_golomb_31(gb);
250            VALIDATE(bl_bit_depth_minus8, 0, 8);
251            VALIDATE(el_bit_depth_minus8, 0, 8);
252            VALIDATE(vdr_bit_depth_minus8, 0, 8);
253            hdr->bl_bit_depth = bl_bit_depth_minus8 + 8;
254            hdr->el_bit_depth = el_bit_depth_minus8 + 8;
255            hdr->vdr_bit_depth = vdr_bit_depth_minus8 + 8;
256            hdr->spatial_resampling_filter_flag = get_bits1(gb);
257            skip_bits(gb, 3); /* reserved_zero_3bits */
258            hdr->el_spatial_resampling_filter_flag = get_bits1(gb);
259            hdr->disable_residual_flag = get_bits1(gb);
260        }
261    }
262
263    if (!hdr->bl_bit_depth) {
264        av_log(s->logctx, AV_LOG_ERROR, "Missing RPU VDR sequence info?\n");
265        goto fail;
266    }
267
268    vdr_dm_metadata_present = get_bits1(gb);
269    use_prev_vdr_rpu = get_bits1(gb);
270    use_nlq = (hdr->rpu_format & 0x700) == 0 && !hdr->disable_residual_flag;
271
272    profile = s->dv_profile ? s->dv_profile : guess_profile(hdr);
273    if (profile == 5 && use_nlq) {
274        av_log(s->logctx, AV_LOG_ERROR, "Profile 5 RPUs should not use NLQ\n");
275        goto fail;
276    }
277
278    if (use_prev_vdr_rpu) {
279        int prev_vdr_rpu_id = get_ue_golomb_31(gb);
280        VALIDATE(prev_vdr_rpu_id, 0, DOVI_MAX_DM_ID);
281        if (!s->vdr_ref[prev_vdr_rpu_id]) {
282            av_log(s->logctx, AV_LOG_ERROR, "Unknown previous RPU ID: %u\n",
283                   prev_vdr_rpu_id);
284            goto fail;
285        }
286        vdr = (DOVIVdrRef *) s->vdr_ref[prev_vdr_rpu_id]->data;
287        s->mapping = &vdr->mapping;
288    } else {
289        int vdr_rpu_id = get_ue_golomb_31(gb);
290        VALIDATE(vdr_rpu_id, 0, DOVI_MAX_DM_ID);
291        if (!s->vdr_ref[vdr_rpu_id]) {
292            s->vdr_ref[vdr_rpu_id] = av_buffer_allocz(sizeof(DOVIVdrRef));
293            if (!s->vdr_ref[vdr_rpu_id])
294                return AVERROR(ENOMEM);
295        }
296
297        vdr = (DOVIVdrRef *) s->vdr_ref[vdr_rpu_id]->data;
298        s->mapping = &vdr->mapping;
299
300        vdr->mapping.vdr_rpu_id = vdr_rpu_id;
301        vdr->mapping.mapping_color_space = get_ue_golomb_31(gb);
302        vdr->mapping.mapping_chroma_format_idc = get_ue_golomb_31(gb);
303
304        for (int c = 0; c < 3; c++) {
305            AVDOVIReshapingCurve *curve = &vdr->mapping.curves[c];
306            int num_pivots_minus_2 = get_ue_golomb_31(gb);
307            int pivot = 0;
308
309            VALIDATE(num_pivots_minus_2, 0, AV_DOVI_MAX_PIECES - 1);
310            curve->num_pivots = num_pivots_minus_2 + 2;
311            for (int i = 0; i < curve->num_pivots; i++) {
312                pivot += get_bits(gb, hdr->bl_bit_depth);
313                curve->pivots[i] = av_clip_uint16(pivot);
314            }
315        }
316
317        if (use_nlq) {
318            vdr->mapping.nlq_method_idc = get_bits(gb, 3);
319            /**
320             * The patent mentions another legal value, NLQ_MU_LAW, but it's
321             * not documented anywhere how to parse or apply that type of NLQ.
322             */
323            VALIDATE(vdr->mapping.nlq_method_idc, 0, AV_DOVI_NLQ_LINEAR_DZ);
324        } else {
325            vdr->mapping.nlq_method_idc = AV_DOVI_NLQ_NONE;
326        }
327
328        vdr->mapping.num_x_partitions = get_ue_golomb_long(gb) + 1;
329        vdr->mapping.num_y_partitions = get_ue_golomb_long(gb) + 1;
330        /* End of rpu_data_header(), start of vdr_rpu_data_payload() */
331
332        for (int c = 0; c < 3; c++) {
333            AVDOVIReshapingCurve *curve = &vdr->mapping.curves[c];
334            for (int i = 0; i < curve->num_pivots - 1; i++) {
335                int mapping_idc = get_ue_golomb_31(gb);
336                VALIDATE(mapping_idc, 0, 1);
337                curve->mapping_idc[i] = mapping_idc;
338                switch (mapping_idc) {
339                case AV_DOVI_MAPPING_POLYNOMIAL: {
340                    int poly_order_minus1 = get_ue_golomb_31(gb);
341                    VALIDATE(poly_order_minus1, 0, 1);
342                    curve->poly_order[i] = poly_order_minus1 + 1;
343                    if (poly_order_minus1 == 0) {
344                        int linear_interp_flag = get_bits1(gb);
345                        if (linear_interp_flag) {
346                            /* lack of documentation/samples */
347                            avpriv_request_sample(s->logctx, "Dolby Vision "
348                                                  "linear interpolation");
349                            ff_dovi_ctx_unref(s);
350                            return AVERROR_PATCHWELCOME;
351                        }
352                    }
353                    for (int k = 0; k <= curve->poly_order[i]; k++)
354                        curve->poly_coef[i][k] = get_se_coef(gb, hdr);
355                    break;
356                }
357                case AV_DOVI_MAPPING_MMR: {
358                    int mmr_order_minus1 = get_bits(gb, 2);
359                    VALIDATE(mmr_order_minus1, 0, 2);
360                    curve->mmr_order[i] = mmr_order_minus1 + 1;
361                    curve->mmr_constant[i] = get_se_coef(gb, hdr);
362                    for (int j = 0; j < curve->mmr_order[i]; j++) {
363                        for (int k = 0; k < 7; k++)
364                            curve->mmr_coef[i][j][k] = get_se_coef(gb, hdr);
365                    }
366                    break;
367                }
368                }
369            }
370        }
371
372        if (use_nlq) {
373            for (int c = 0; c < 3; c++) {
374                AVDOVINLQParams *nlq = &vdr->mapping.nlq[c];
375                nlq->nlq_offset = get_bits(gb, hdr->el_bit_depth);
376                nlq->vdr_in_max = get_ue_coef(gb, hdr);
377                switch (vdr->mapping.nlq_method_idc) {
378                case AV_DOVI_NLQ_LINEAR_DZ:
379                    nlq->linear_deadzone_slope = get_ue_coef(gb, hdr);
380                    nlq->linear_deadzone_threshold = get_ue_coef(gb, hdr);
381                    break;
382                }
383            }
384        }
385    }
386
387    if (vdr_dm_metadata_present) {
388        AVDOVIColorMetadata *color;
389        int affected_dm_id = get_ue_golomb_31(gb);
390        int current_dm_id = get_ue_golomb_31(gb);
391        VALIDATE(affected_dm_id, 0, DOVI_MAX_DM_ID);
392        VALIDATE(current_dm_id, 0, DOVI_MAX_DM_ID);
393        if (!s->vdr_ref[affected_dm_id]) {
394            s->vdr_ref[affected_dm_id] = av_buffer_allocz(sizeof(DOVIVdrRef));
395            if (!s->vdr_ref[affected_dm_id])
396                return AVERROR(ENOMEM);
397        }
398
399        if (!s->vdr_ref[current_dm_id]) {
400            av_log(s->logctx, AV_LOG_ERROR, "Unknown previous RPU DM ID: %u\n",
401                   current_dm_id);
402            goto fail;
403        }
404
405        /* Update current pointer based on current_dm_id */
406        vdr = (DOVIVdrRef *) s->vdr_ref[current_dm_id]->data;
407        s->color = &vdr->color;
408
409        /* Update values of affected_dm_id */
410        vdr = (DOVIVdrRef *) s->vdr_ref[affected_dm_id]->data;
411        color = &vdr->color;
412        color->dm_metadata_id = affected_dm_id;
413        color->scene_refresh_flag = get_ue_golomb_31(gb);
414        for (int i = 0; i < 9; i++)
415            color->ycc_to_rgb_matrix[i] = av_make_q(get_sbits(gb, 16), 1 << 13);
416        for (int i = 0; i < 3; i++) {
417            int denom = profile == 4 ? (1 << 30) : (1 << 28);
418            unsigned offset = get_bits_long(gb, 32);
419            if (offset > INT_MAX) {
420                /* Ensure the result fits inside AVRational */
421                offset >>= 1;
422                denom >>= 1;
423            }
424            color->ycc_to_rgb_offset[i] = av_make_q(offset, denom);
425        }
426        for (int i = 0; i < 9; i++)
427            color->rgb_to_lms_matrix[i] = av_make_q(get_sbits(gb, 16), 1 << 14);
428
429        color->signal_eotf = get_bits(gb, 16);
430        color->signal_eotf_param0 = get_bits(gb, 16);
431        color->signal_eotf_param1 = get_bits(gb, 16);
432        color->signal_eotf_param2 = get_bits_long(gb, 32);
433        color->signal_bit_depth = get_bits(gb, 5);
434        VALIDATE(color->signal_bit_depth, 8, 16);
435        color->signal_color_space = get_bits(gb, 2);
436        color->signal_chroma_format = get_bits(gb, 2);
437        color->signal_full_range_flag = get_bits(gb, 2);
438        color->source_min_pq = get_bits(gb, 12);
439        color->source_max_pq = get_bits(gb, 12);
440        color->source_diagonal = get_bits(gb, 10);
441    }
442
443    /* FIXME: verify CRC32, requires implementation of AV_CRC_32_MPEG_2 */
444    return 0;
445
446fail:
447    ff_dovi_ctx_unref(s); /* don't leak potentially invalid state */
448    return AVERROR(EINVAL);
449}
450