| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * This file is part of FFmpeg. | ||
| 3 | * | ||
| 4 | * FFmpeg is free software; you can redistribute it and/or | ||
| 5 | * modify it under the terms of the GNU Lesser General Public | ||
| 6 | * License as published by the Free Software Foundation; either | ||
| 7 | * version 2.1 of the License, or (at your option) any later version. | ||
| 8 | * | ||
| 9 | * FFmpeg is distributed in the hope that it will be useful, | ||
| 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
| 12 | * Lesser General Public License for more details. | ||
| 13 | * | ||
| 14 | * You should have received a copy of the GNU Lesser General Public | ||
| 15 | * License along with FFmpeg; if not, write to the Free Software | ||
| 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
| 17 | */ | ||
| 18 | |||
| 19 | /** | ||
| 20 | * @file | ||
| 21 | * Filter to compute an HDR gain map from a base and alternate rendition. | ||
| 22 | */ | ||
| 23 | |||
| 24 | #include <float.h> | ||
| 25 | #include <math.h> | ||
| 26 | |||
| 27 | #include "libavutil/csp.h" | ||
| 28 | #include "libavutil/gain_map.h" | ||
| 29 | #include "libavutil/internal.h" | ||
| 30 | #include "libavutil/mastering_display_metadata.h" | ||
| 31 | #include "libavutil/mem.h" | ||
| 32 | #include "libavutil/opt.h" | ||
| 33 | #include "libavutil/pixdesc.h" | ||
| 34 | |||
| 35 | #include "avfilter.h" | ||
| 36 | #include "colorspace.h" | ||
| 37 | #include "filters.h" | ||
| 38 | #include "formats.h" | ||
| 39 | #include "framesync.h" | ||
| 40 | #include "video.h" | ||
| 41 | |||
| 42 | #define SDR_DIFFUSE_WHITE 203.0 | ||
| 43 | |||
| 44 | enum GainMapColorspace { | ||
| 45 | GAINMAP_CSP_BASE, ///< apply the map in the base rendition's space | ||
| 46 | GAINMAP_CSP_ALT, ///< apply it in the alternate rendition's space | ||
| 47 | GAINMAP_CSP_NB, | ||
| 48 | }; | ||
| 49 | |||
| 50 | enum GainMapMode { | ||
| 51 | GAINMAP_RGB, ///< one channel per component | ||
| 52 | GAINMAP_LUMA, ///< single channel, from the luminance | ||
| 53 | GAINMAP_MAXRGB, ///< single channel, from max(R,G,B) | ||
| 54 | GAINMAP_MODE_NB, | ||
| 55 | }; | ||
| 56 | |||
| 57 | enum GainMapMeasure { | ||
| 58 | MEASURE_MIN = 1 << 0, | ||
| 59 | MEASURE_MAX = 1 << 1, | ||
| 60 | MEASURE_GAMMA = 1 << 2, | ||
| 61 | MEASURE_RANGE = MEASURE_MIN | MEASURE_MAX, | ||
| 62 | MEASURE_ALL = MEASURE_RANGE | MEASURE_GAMMA, | ||
| 63 | }; | ||
| 64 | |||
| 65 | typedef struct GainMapEOTF { | ||
| 66 | enum AVColorTransferCharacteristic trc; | ||
| 67 | av_csp_eotf_function eotf; /* or NULL */ | ||
| 68 | double Lw; | ||
| 69 | |||
| 70 | #define GAINMAP_LUT_SIZE 1025 | ||
| 71 | float lut[GAINMAP_LUT_SIZE + 1]; /* extra padding entry */ | ||
| 72 | } GainMapEOTF; | ||
| 73 | |||
| 74 | typedef struct GainMapContext { | ||
| 75 | const AVClass *class; | ||
| 76 | FFFrameSync fs; | ||
| 77 | |||
| 78 | /* Filter options */ | ||
| 79 | int mode; | ||
| 80 | int colorspace; | ||
| 81 | AVRational gain_min; | ||
| 82 | AVRational gain_max; | ||
| 83 | AVRational gamma; | ||
| 84 | AVRational base_offset; | ||
| 85 | AVRational alt_offset; | ||
| 86 | AVRational base_nits; | ||
| 87 | AVRational alt_nits; | ||
| 88 | |||
| 89 | int warned_noop; | ||
| 90 | int nb_channels; | ||
| 91 | int nb_threads; /* for slice threading */ | ||
| 92 | int convert; /* colorspace conversion needed */ | ||
| 93 | int sign; /* sign(alt_peak - base_peak) */ | ||
| 94 | |||
| 95 | /* Colorspace parameters */ | ||
| 96 | GainMapEOTF base_eotf; | ||
| 97 | GainMapEOTF alt_eotf; | ||
| 98 | float rgb2y[3]; | ||
| 99 | float rgb2rgb[3][3]; | ||
| 100 | |||
| 101 | /* Quantization parameters, either static or recomputed dynamically */ | ||
| 102 | float quant_scale[3]; | ||
| 103 | float quant_offset[3]; | ||
| 104 | float quant_gamma[3]; | ||
| 105 | |||
| 106 | /* Measured frame statistics */ | ||
| 107 | float (*slice_min)[3]; /* for MEASURE_MIN */ | ||
| 108 | float (*slice_max)[3]; /* for MEASURE_MAX */ | ||
| 109 | double (*slice_sum)[3]; /* for MEASURE_GAMMA */ | ||
| 110 | enum GainMapMeasure measure; | ||
| 111 | |||
| 112 | /* Generated gain map parameters (recomputed per frame) */ | ||
| 113 | AVGainMapParams params; | ||
| 114 | } GainMapContext; | ||
| 115 | |||
| 116 | typedef struct ThreadData { | ||
| 117 | AVFrame *out; | ||
| 118 | const AVFrame *base, *alt; | ||
| 119 | } ThreadData; | ||
| 120 | |||
| 121 | #define OFFSET(x) offsetof(GainMapContext, x) | ||
| 122 | #define FLAGS AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM | ||
| 123 | |||
| 124 | static const AVOption gainmap_options[] = { | ||
| 125 | { "mode", "quantity the gain is computed over", OFFSET(mode), AV_OPT_TYPE_INT, { .i64 = GAINMAP_RGB }, 0, GAINMAP_MODE_NB - 1, FLAGS, .unit = "mode" }, | ||
| 126 | { "rgb", "one gain channel per component", 0, AV_OPT_TYPE_CONST, { .i64 = GAINMAP_RGB }, 0, 0, FLAGS, .unit = "mode" }, | ||
| 127 | { "luma", "single gain channel, from luminance", 0, AV_OPT_TYPE_CONST, { .i64 = GAINMAP_LUMA }, 0, 0, FLAGS, .unit = "mode" }, | ||
| 128 | { "maxrgb", "single gain channel, from max(R,G,B)", 0, AV_OPT_TYPE_CONST, { .i64 = GAINMAP_MAXRGB }, 0, 0, FLAGS, .unit = "mode" }, | ||
| 129 | { "colorspace", "rendition whose colour space the map is applied in", OFFSET(colorspace), AV_OPT_TYPE_INT, { .i64 = GAINMAP_CSP_BASE }, 0, GAINMAP_CSP_NB - 1, FLAGS, .unit = "colorspace" }, | ||
| 130 | { "base", "the base rendition's colour space", 0, AV_OPT_TYPE_CONST, { .i64 = GAINMAP_CSP_BASE }, 0, 0, FLAGS, .unit = "colorspace" }, | ||
| 131 | { "alternate", "the alternate rendition's colour space", 0, AV_OPT_TYPE_CONST, { .i64 = GAINMAP_CSP_ALT }, 0, 0, FLAGS, .unit = "colorspace" }, | ||
| 132 | { "min", "override lower bound on the encoded gain, in log2 space", OFFSET(gain_min), AV_OPT_TYPE_RATIONAL, { .dbl = NAN }, -32.0, 32.0, FLAGS }, | ||
| 133 | { "max", "override upper bound on the encoded gain, in log2 space", OFFSET(gain_max), AV_OPT_TYPE_RATIONAL, { .dbl = NAN }, -32.0, 32.0, FLAGS }, | ||
| 134 | { "gamma", "override encoding gamma of the stored map (default: measured)", OFFSET(gamma), AV_OPT_TYPE_RATIONAL, { .dbl = NAN }, 0.0001, 100.0, FLAGS }, | ||
| 135 | { "base_offset", "constant added to the base rendition", OFFSET(base_offset), AV_OPT_TYPE_RATIONAL, { .dbl = 1.0 / 64.0 }, 1e-6, 1.0, FLAGS }, | ||
| 136 | { "alt_offset", "constant added to the alternate rendition", OFFSET(alt_offset), AV_OPT_TYPE_RATIONAL, { .dbl = 1.0 / 64.0 }, 1e-6, 1.0, FLAGS }, | ||
| 137 | { "base_nits", "override input luminance of the base rendition", OFFSET(base_nits), AV_OPT_TYPE_RATIONAL, { .dbl = NAN }, 1.0, 10000.0, FLAGS }, | ||
| 138 | { "alt_nits", "override input luminance of the alternate rendition", OFFSET(alt_nits), AV_OPT_TYPE_RATIONAL, { .dbl = NAN }, 1.0, 10000.0, FLAGS }, | ||
| 139 | { NULL } | ||
| 140 | }; | ||
| 141 | |||
| 142 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 16 times.
|
84 | FRAMESYNC_DEFINE_CLASS(gainmap, GainMapContext, fs); |
| 143 | |||
| 144 | /* Quantize to a fixed point representation with the correct rounding mode */ | ||
| 145 | 28 | static AVRational quantq(double x, enum AVRounding rnd) | |
| 146 | { | ||
| 147 | 28 | const int scale = 1 << 22; | |
| 148 | |||
| 149 |
3/4✓ Branch 0 taken 6 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
|
28 | switch (rnd) { |
| 150 | 6 | case AV_ROUND_DOWN: x = floor(x * scale); break; | |
| 151 | 6 | case AV_ROUND_UP: x = ceil(x * scale); break; | |
| 152 | 16 | case AV_ROUND_NEAR_INF: x = round(x * scale); break; | |
| 153 | ✗ | default: av_unreachable("not used / implemented"); | |
| 154 | } | ||
| 155 | |||
| 156 | AVRational q; | ||
| 157 | 28 | av_reduce(&q.num, &q.den, x, scale, INT_MAX); | |
| 158 | 28 | return q; | |
| 159 | } | ||
| 160 | |||
| 161 | 12 | static void setup_range(AVFilterContext *ctx, int ch, AVRational min, AVRational max) | |
| 162 | { | ||
| 163 | 12 | GainMapContext *s = ctx->priv; | |
| 164 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
|
12 | if (av_cmp_q(max, min) <= 0) { |
| 165 | /* Nothing to quantize; flat gain map */ | ||
| 166 | ✗ | s->quant_scale[ch] = s->quant_offset[ch] = 0.0f; | |
| 167 | ✗ | max = min; /* sanity */ | |
| 168 | } else { | ||
| 169 | 12 | const float minf = av_q2d(min), maxf = av_q2d(max); | |
| 170 | 12 | s->quant_scale[ch] = 1.0f / (maxf - minf); | |
| 171 | 12 | s->quant_offset[ch] = -minf * s->quant_scale[ch]; | |
| 172 | } | ||
| 173 | |||
| 174 | 12 | s->params.channels[ch].gain_map_min = min; | |
| 175 | 12 | s->params.channels[ch].gain_map_max = max; | |
| 176 | 12 | av_log(ctx, AV_LOG_TRACE, "channel %d: measured min=%g max=%g\n", | |
| 177 | ch, av_q2d(min), av_q2d(max)); | ||
| 178 | 12 | } | |
| 179 | |||
| 180 | 12 | static void setup_gamma(AVFilterContext *ctx, int ch, AVRational gamma) | |
| 181 | { | ||
| 182 | 12 | GainMapContext *s = ctx->priv; | |
| 183 | 12 | s->quant_gamma[ch] = av_q2d(gamma); | |
| 184 | 12 | s->params.channels[ch].gamma = gamma; | |
| 185 | 12 | av_log(ctx, AV_LOG_TRACE, "channel %d: measured gamma=%g\n", | |
| 186 | 12 | ch, s->quant_gamma[ch]); | |
| 187 | 12 | } | |
| 188 | |||
| 189 | 10 | static av_cold int init(AVFilterContext *ctx) | |
| 190 | { | ||
| 191 | 10 | GainMapContext *s = ctx->priv; | |
| 192 | |||
| 193 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
|
10 | if (av_cmp_q(s->gain_min, s->gain_max) >= 0) { |
| 194 | ✗ | av_log(ctx, AV_LOG_ERROR, "min (%g) must be below max (%g)\n", | |
| 195 | av_q2d(s->gain_min), av_q2d(s->gain_max)); | ||
| 196 | ✗ | return AVERROR(EINVAL); | |
| 197 | } | ||
| 198 | |||
| 199 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 6 times.
|
10 | s->nb_channels = s->mode == GAINMAP_RGB ? 3 : 1; |
| 200 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
|
10 | if (!s->gain_min.den) |
| 201 | 8 | s->measure |= MEASURE_MIN; | |
| 202 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
|
10 | if (!s->gain_max.den) |
| 203 | 8 | s->measure |= MEASURE_MAX; | |
| 204 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
|
10 | if (!s->gamma.den) |
| 205 | 8 | s->measure |= MEASURE_GAMMA; | |
| 206 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
|
10 | if (s->measure) { |
| 207 | 8 | av_log(ctx, AV_LOG_VERBOSE, "Using two passes to measure gain map " | |
| 208 | "parameters from the input frame.\n"); | ||
| 209 | } | ||
| 210 | |||
| 211 | 10 | s->params = (AVGainMapParams) { | |
| 212 | .version = 0, | ||
| 213 | 10 | .nb_channels = s->nb_channels, | |
| 214 | 10 | .use_base_color_space = s->colorspace == GAINMAP_CSP_BASE, | |
| 215 | /* payload metadata recomputed per frame */ | ||
| 216 | }; | ||
| 217 | |||
| 218 |
2/2✓ Branch 0 taken 18 times.
✓ Branch 1 taken 10 times.
|
28 | for (int c = 0; c < s->nb_channels; c++) { |
| 219 | 18 | struct AVGainMapChannel *ch = &s->params.channels[c]; | |
| 220 | 18 | ch->base_offset = s->base_offset; | |
| 221 | 18 | ch->alternate_offset = s->alt_offset; | |
| 222 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 12 times.
|
18 | if (!(s->measure & MEASURE_RANGE)) |
| 223 | 6 | setup_range(ctx, c, s->gain_min, s->gain_max); | |
| 224 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 12 times.
|
18 | if (!(s->measure & MEASURE_GAMMA)) |
| 225 | 6 | setup_gamma(ctx, c, s->gamma); | |
| 226 | } | ||
| 227 | |||
| 228 | 10 | return 0; | |
| 229 | } | ||
| 230 | |||
| 231 | 5 | static int query_formats(const AVFilterContext *ctx, | |
| 232 | AVFilterFormatsConfig **cfg_in, | ||
| 233 | AVFilterFormatsConfig **cfg_out) | ||
| 234 | { | ||
| 235 | 5 | const GainMapContext *s = ctx->priv; | |
| 236 | enum AVPixelFormat in_fmt, out_fmt; | ||
| 237 | int ret; | ||
| 238 | |||
| 239 | 5 | in_fmt = AV_PIX_FMT_GBRPF32; | |
| 240 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
|
5 | out_fmt = s->nb_channels == 1 ? AV_PIX_FMT_GRAYF32 : AV_PIX_FMT_GBRPF32; |
| 241 | |||
| 242 | 5 | ret = ff_formats_ref(ff_make_formats_list_singleton(out_fmt), &cfg_out[0]->formats); | |
| 243 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | if (ret < 0) |
| 244 | ✗ | return ret; | |
| 245 | |||
| 246 | 5 | return ff_set_common_formats2(ctx, cfg_in, cfg_out, | |
| 247 | ff_make_formats_list_singleton(in_fmt)); | ||
| 248 | } | ||
| 249 | |||
| 250 | 8 | static double frame_luminance(const AVFrame *frame) | |
| 251 | { | ||
| 252 | const AVFrameSideData *sd; | ||
| 253 | 8 | sd = av_frame_get_side_data(frame, AV_FRAME_DATA_MASTERING_DISPLAY_METADATA); | |
| 254 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (sd) { |
| 255 | const AVMasteringDisplayMetadata *mdm; | ||
| 256 | ✗ | mdm = (const AVMasteringDisplayMetadata *) sd->data; | |
| 257 | ✗ | if (mdm->has_luminance && mdm->max_luminance.num > 0) | |
| 258 | ✗ | return av_q2d(mdm->max_luminance); | |
| 259 | } | ||
| 260 | |||
| 261 |
2/3✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
|
8 | switch (frame->color_trc) { |
| 262 | 4 | case AVCOL_TRC_SMPTE2084: return 10000.0; | |
| 263 | ✗ | case AVCOL_TRC_ARIB_STD_B67: return 1000.0; | |
| 264 | 4 | default: return SDR_DIFFUSE_WHITE; | |
| 265 | } | ||
| 266 | } | ||
| 267 | |||
| 268 | 10 | static void update_eotf(GainMapEOTF *tf, enum AVColorTransferCharacteristic trc, | |
| 269 | av_csp_eotf_function eotf, double Lw) | ||
| 270 | { | ||
| 271 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
10 | if (trc == tf->trc && Lw == tf->Lw) |
| 272 | ✗ | return; /* no change */ | |
| 273 | 10 | tf->trc = trc; | |
| 274 | 10 | tf->Lw = Lw; | |
| 275 | |||
| 276 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | switch (trc) { |
| 277 | /* EOTFs that are safe to collapse into a 1D LUT */ | ||
| 278 | 10 | case AVCOL_TRC_BT709: | |
| 279 | case AVCOL_TRC_GAMMA22: | ||
| 280 | case AVCOL_TRC_GAMMA28: | ||
| 281 | case AVCOL_TRC_SMPTE170M: | ||
| 282 | case AVCOL_TRC_SMPTE240M: | ||
| 283 | case AVCOL_TRC_LINEAR: | ||
| 284 | case AVCOL_TRC_IEC61966_2_4: | ||
| 285 | case AVCOL_TRC_BT1361_ECG: | ||
| 286 | case AVCOL_TRC_IEC61966_2_1: | ||
| 287 | case AVCOL_TRC_BT2020_10: | ||
| 288 | case AVCOL_TRC_BT2020_12: | ||
| 289 | case AVCOL_TRC_SMPTE2084: { | ||
| 290 |
2/2✓ Branch 0 taken 10250 times.
✓ Branch 1 taken 10 times.
|
10260 | for (int i = 0; i < GAINMAP_LUT_SIZE; i++) { |
| 291 | 10250 | double x[3] = { i / (GAINMAP_LUT_SIZE - 1.0) }; | |
| 292 | 10250 | eotf(Lw, 0.0, x); | |
| 293 | 10250 | tf->lut[i] = x[0] / SDR_DIFFUSE_WHITE; /* normalize */ | |
| 294 | } | ||
| 295 | |||
| 296 | 10 | tf->lut[GAINMAP_LUT_SIZE] = tf->lut[GAINMAP_LUT_SIZE - 1]; | |
| 297 | 10 | tf->eotf = NULL; | |
| 298 | 10 | break; | |
| 299 | } | ||
| 300 | /* EOTFs that use the av_csp_eotf_function fallback */ | ||
| 301 | ✗ | case AVCOL_TRC_ARIB_STD_B67: /* nontrivial OOTF */ | |
| 302 | case AVCOL_TRC_SMPTE428: /* different normalization per channel */ | ||
| 303 | default: | ||
| 304 | ✗ | tf->eotf = eotf; | |
| 305 | ✗ | break; | |
| 306 | } | ||
| 307 | } | ||
| 308 | |||
| 309 | ✗ | static const char *unknown_if_null(const char *s) | |
| 310 | { | ||
| 311 | ✗ | return s ? s : "unknown"; | |
| 312 | } | ||
| 313 | |||
| 314 | /* Run per frame, since trc/primaries are not (yet) link-level properties */ | ||
| 315 | 5 | static int setup_colorspace(AVFilterContext *ctx, const AVFrame *base, const AVFrame *alt) | |
| 316 | { | ||
| 317 | 5 | GainMapContext *const s = ctx->priv; | |
| 318 | 5 | av_csp_eotf_function base_eotf = av_csp_itu_eotf(base->color_trc); | |
| 319 | 5 | av_csp_eotf_function alt_eotf = av_csp_itu_eotf(alt->color_trc); | |
| 320 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
|
5 | if (!base_eotf || !alt_eotf) { |
| 321 | ✗ | av_log(ctx, AV_LOG_ERROR, "Unknown transfer: base=%s, alternate=%s\n", | |
| 322 | ✗ | unknown_if_null(av_color_transfer_name(base->color_trc)), | |
| 323 | ✗ | unknown_if_null(av_color_transfer_name(alt->color_trc))); | |
| 324 | ✗ | return AVERROR(EINVAL); | |
| 325 | } | ||
| 326 | |||
| 327 | const AVColorPrimariesDesc *base_desc, *alt_desc; | ||
| 328 | 5 | base_desc = av_csp_primaries_desc_from_id(base->color_primaries); | |
| 329 | 5 | alt_desc = av_csp_primaries_desc_from_id(alt->color_primaries); | |
| 330 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
|
5 | if (!base_desc || !alt_desc) { |
| 331 | ✗ | av_log(ctx, AV_LOG_ERROR, "Unknown primaries: base=%s, alternate=%s\n", | |
| 332 | ✗ | unknown_if_null(av_color_primaries_name(base->color_primaries)), | |
| 333 | ✗ | unknown_if_null(av_color_primaries_name(alt->color_primaries))); | |
| 334 | ✗ | return AVERROR(EINVAL); | |
| 335 | } | ||
| 336 | |||
| 337 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
|
5 | double base_lw = s->base_nits.den ? av_q2d(s->base_nits) : frame_luminance(base); |
| 338 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
|
5 | double alt_lw = s->alt_nits.den ? av_q2d(s->alt_nits) : frame_luminance(alt); |
| 339 | 5 | double base_headroom = fmax(log2(base_lw / SDR_DIFFUSE_WHITE), 0.0); | |
| 340 | 5 | double alt_headroom = fmax(log2(alt_lw / SDR_DIFFUSE_WHITE), 0.0); | |
| 341 | 5 | s->params.base_hdr_headroom = quantq(base_headroom, AV_ROUND_NEAR_INF); | |
| 342 | 5 | s->params.alternate_hdr_headroom = quantq(alt_headroom, AV_ROUND_NEAR_INF); | |
| 343 | 5 | s->sign = FFDIFFSIGN(alt_headroom, base_headroom); | |
| 344 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | if (!s->sign) { |
| 345 | /* No-op, set up empty gain map */ | ||
| 346 | ✗ | for (int i = 0; i < s->nb_channels; i++) { | |
| 347 | ✗ | setup_range(ctx, i, (AVRational) { 0, 1 }, (AVRational) { 0, 1 }); | |
| 348 | ✗ | setup_gamma(ctx, i, (AVRational) { 1, 1 }); | |
| 349 | } | ||
| 350 | |||
| 351 | ✗ | av_log_once(ctx, AV_LOG_WARNING, AV_LOG_VERBOSE, &s->warned_noop, | |
| 352 | "Base and alternate renditions have the same peak " | ||
| 353 | "luminance (%g nits), gain map will be empty\n", base_lw); | ||
| 354 | ✗ | return 0; | |
| 355 | } else { | ||
| 356 | 5 | av_log(ctx, AV_LOG_DEBUG, "Base peak: %g nits, alternate peak: %g nits\n", | |
| 357 | base_lw, alt_lw); | ||
| 358 | } | ||
| 359 | |||
| 360 | 5 | update_eotf(&s->base_eotf, base->color_trc, base_eotf, base_lw); | |
| 361 | 5 | update_eotf(&s->alt_eotf, alt->color_trc, alt_eotf, alt_lw); | |
| 362 | |||
| 363 | /* Assume conversion from alt to base */ | ||
| 364 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | if (s->colorspace == GAINMAP_CSP_ALT) |
| 365 | ✗ | FFSWAP(const AVColorPrimariesDesc *, base_desc, alt_desc); | |
| 366 | |||
| 367 | double rgb2xyz[3][3]; | ||
| 368 | 5 | ff_fill_rgb2xyz_table(&base_desc->prim, &base_desc->wp, rgb2xyz); | |
| 369 |
2/2✓ Branch 0 taken 15 times.
✓ Branch 1 taken 5 times.
|
20 | for (int i = 0; i < 3; i++) |
| 370 | 15 | s->rgb2y[i] = rgb2xyz[1][i] / av_q2d(base_desc->wp.y); | |
| 371 | |||
| 372 | 5 | s->convert = base->color_primaries != alt->color_primaries; | |
| 373 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | if (s->convert) { |
| 374 | /* Note: Ignores whitepoint differences (chromatic adaptation) */ | ||
| 375 | double xyz2rgb[3][3], rgb2rgb[3][3]; | ||
| 376 | 5 | ff_fill_rgb2xyz_table(&base_desc->prim, &base_desc->wp, rgb2xyz); | |
| 377 | 5 | ff_matrix_invert_3x3(rgb2xyz, xyz2rgb); | |
| 378 | 5 | ff_fill_rgb2xyz_table(&alt_desc->prim, &alt_desc->wp, rgb2xyz); | |
| 379 | 5 | ff_matrix_mul_3x3(rgb2rgb, rgb2xyz, xyz2rgb); | |
| 380 |
2/2✓ Branch 0 taken 15 times.
✓ Branch 1 taken 5 times.
|
20 | for (int i = 0; i < 3; i++) |
| 381 |
2/2✓ Branch 0 taken 45 times.
✓ Branch 1 taken 15 times.
|
60 | for (int j = 0; j < 3; j++) |
| 382 | 45 | s->rgb2rgb[i][j] = (float) rgb2rgb[i][j]; | |
| 383 | } else { | ||
| 384 | ✗ | memset(s->rgb2rgb, 0, sizeof(s->rgb2rgb)); | |
| 385 | ✗ | for (int i = 0; i < 3; i++) | |
| 386 | ✗ | s->rgb2rgb[i][i] = 1.0f; | |
| 387 | } | ||
| 388 | |||
| 389 | 5 | return 0; | |
| 390 | } | ||
| 391 | |||
| 392 | 27648 | static av_always_inline float quantize(float gain, float scale, float offset, float gamma) | |
| 393 | { | ||
| 394 | 27648 | const float norm = av_clipf(scale * gain + offset, 0.0f, 1.0f); | |
| 395 |
2/2✓ Branch 0 taken 18432 times.
✓ Branch 1 taken 9216 times.
|
27648 | return gamma == 1.0f ? norm : powf(norm, gamma); |
| 396 | } | ||
| 397 | |||
| 398 | static av_always_inline void | ||
| 399 | 30720 | get_pixel(const GainMapContext *s, float dst[3], | |
| 400 | const float *restrict const src[3], int x, int alt) | ||
| 401 | { | ||
| 402 |
2/2✓ Branch 0 taken 15360 times.
✓ Branch 1 taken 15360 times.
|
30720 | const GainMapEOTF *const tf = alt ? &s->alt_eotf : &s->base_eotf; |
| 403 | float rgb[3]; | ||
| 404 | |||
| 405 | /* Linearize to normalized RGB */ | ||
| 406 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 30720 times.
|
30720 | if (tf->eotf) { |
| 407 | ✗ | double rgbd[3] = { src[0][x], src[1][x], src[2][x] }; | |
| 408 | ✗ | tf->eotf(tf->Lw, 0.0, rgbd); | |
| 409 | ✗ | for (int i = 0; i < 3; i++) | |
| 410 | ✗ | rgb[i] = rgbd[i] / SDR_DIFFUSE_WHITE; | |
| 411 | } else { | ||
| 412 |
2/2✓ Branch 0 taken 92160 times.
✓ Branch 1 taken 30720 times.
|
122880 | for (int i = 0; i < 3; i++) { |
| 413 | 92160 | const float fx = av_clipf(src[i][x], 0.0f, 1.0f) * (GAINMAP_LUT_SIZE - 1.0); | |
| 414 | 92160 | const int ix = (int) fx; | |
| 415 | 92160 | const float lo = tf->lut[ix]; | |
| 416 | 92160 | const float hi = tf->lut[ix + 1]; | |
| 417 | 92160 | rgb[i] = lo + (fx - ix) * (hi - lo); | |
| 418 | } | ||
| 419 | } | ||
| 420 | |||
| 421 | /* Convert to correct colorspace if needed */ | ||
| 422 |
3/4✓ Branch 0 taken 30720 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15360 times.
✓ Branch 3 taken 15360 times.
|
46080 | if (s->convert && (s->colorspace == GAINMAP_CSP_ALT) != alt) { |
| 423 | 15360 | const float r = rgb[0], g = rgb[1], b = rgb[2]; | |
| 424 |
2/2✓ Branch 0 taken 46080 times.
✓ Branch 1 taken 15360 times.
|
61440 | for (int i = 0; i < 3; i++) { |
| 425 | 46080 | dst[i] = fmaxf(r * s->rgb2rgb[i][0] + | |
| 426 | 46080 | g * s->rgb2rgb[i][1] + | |
| 427 | 46080 | b * s->rgb2rgb[i][2], 0.0f); | |
| 428 | } | ||
| 429 | } else { | ||
| 430 |
2/2✓ Branch 0 taken 46080 times.
✓ Branch 1 taken 15360 times.
|
61440 | for (int i = 0; i < 3; i++) |
| 431 | 46080 | dst[i] = fmaxf(rgb[i], 0.0f); | |
| 432 | } | ||
| 433 | 30720 | } | |
| 434 | |||
| 435 | /* GBRP plane order */ | ||
| 436 | static const int gbr_order[3] = { 2, 0, 1 }; | ||
| 437 | |||
| 438 | static av_always_inline int | ||
| 439 | 45 | slice_internal(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs, int quant) | |
| 440 | { | ||
| 441 | 45 | GainMapContext *s = ctx->priv; | |
| 442 | 45 | const ThreadData *td = arg; | |
| 443 | 45 | const AVFrame *base = td->base, *alt = td->alt, *out = td->out; | |
| 444 | |||
| 445 | 45 | const float *restrict scale = s->quant_scale; | |
| 446 | 45 | const float *restrict offset = s->quant_offset; | |
| 447 | 45 | const float *restrict gamma = s->quant_gamma; | |
| 448 | |||
| 449 | /* Normalize both to the base colorspace */ | ||
| 450 | 45 | const float base_off = av_q2d(s->base_offset); | |
| 451 | 45 | const float alt_off = av_q2d(s->alt_offset); | |
| 452 | 45 | const float sign = s->sign; | |
| 453 | |||
| 454 | 45 | const int y_start = ff_slice_pos(out->height, jobnr, nb_jobs); | |
| 455 | 45 | const int y_end = ff_slice_pos(out->height, jobnr + 1, nb_jobs); | |
| 456 | 45 | const int width = out->width; | |
| 457 | 45 | const int mode = s->mode; | |
| 458 | 45 | const int nb_ch = s->nb_channels; | |
| 459 | |||
| 460 | 45 | float min[3] = { FLT_MAX, FLT_MAX, FLT_MAX }; | |
| 461 | 45 | float max[3] = { -FLT_MAX, -FLT_MAX, -FLT_MAX }; | |
| 462 | 45 | double sum[3] = { 0.0, 0.0, 0.0 }; | |
| 463 | |||
| 464 |
2/2✓ Branch 0 taken 240 times.
✓ Branch 1 taken 45 times.
|
285 | for (int y = y_start; y < y_end; y++) { |
| 465 | const float *restrict b_row[3], *restrict a_row[3]; | ||
| 466 |
2/2✓ Branch 0 taken 720 times.
✓ Branch 1 taken 240 times.
|
960 | for (int i = 0; i < 3; i++) { |
| 467 | 720 | const int p = gbr_order[i]; | |
| 468 | 720 | b_row[i] = (const float *) (base->data[p] + y * base->linesize[p]); | |
| 469 | 720 | a_row[i] = (const float *) ( alt->data[p] + y * alt->linesize[p]); | |
| 470 | } | ||
| 471 | |||
| 472 | float *restrict out_row[3]; | ||
| 473 |
2/2✓ Branch 0 taken 432 times.
✓ Branch 1 taken 240 times.
|
672 | for (int i = 0; i < nb_ch; i++) { |
| 474 |
2/2✓ Branch 0 taken 288 times.
✓ Branch 1 taken 144 times.
|
432 | const int p = nb_ch == 1 ? 0 : gbr_order[i]; |
| 475 | 432 | out_row[i] = (float *) (out->data[p] + y * out->linesize[p]); | |
| 476 | } | ||
| 477 | |||
| 478 |
2/2✓ Branch 0 taken 15360 times.
✓ Branch 1 taken 240 times.
|
15600 | for (int x = 0; x < width; x++) { |
| 479 | float b[3], a[3], gain[3]; | ||
| 480 | 15360 | get_pixel(s, b, b_row, x, 0); | |
| 481 | 15360 | get_pixel(s, a, a_row, x, 1); | |
| 482 | |||
| 483 | #define GAIN(a, b) (sign * log2f(((a) + alt_off) / ((b) + base_off))) | ||
| 484 |
3/4✓ Branch 0 taken 6144 times.
✓ Branch 1 taken 3072 times.
✓ Branch 2 taken 6144 times.
✗ Branch 3 not taken.
|
15360 | switch (mode) { |
| 485 | 6144 | case GAINMAP_MAXRGB: { | |
| 486 | 6144 | const float max_a = fmaxf(fmaxf(a[0], a[1]), a[2]); | |
| 487 | 6144 | const float max_b = fmaxf(fmaxf(b[0], b[1]), b[2]); | |
| 488 | 6144 | gain[0] = GAIN(max_a, max_b); | |
| 489 | 6144 | break; | |
| 490 | } | ||
| 491 | 3072 | case GAINMAP_LUMA: { | |
| 492 | 3072 | const float y_a = s->rgb2y[0] * a[0] + s->rgb2y[1] * a[1] + s->rgb2y[2] * a[2]; | |
| 493 | 3072 | const float y_b = s->rgb2y[0] * b[0] + s->rgb2y[1] * b[1] + s->rgb2y[2] * b[2]; | |
| 494 | 3072 | gain[0] = GAIN(y_a, y_b); | |
| 495 | 3072 | break; | |
| 496 | } | ||
| 497 | 6144 | case GAINMAP_RGB: | |
| 498 | 6144 | gain[0] = GAIN(a[0], b[0]); | |
| 499 | 6144 | gain[1] = GAIN(a[1], b[1]); | |
| 500 | 6144 | gain[2] = GAIN(a[2], b[2]); | |
| 501 | 6144 | break; | |
| 502 | } | ||
| 503 | #undef GAIN | ||
| 504 | |||
| 505 |
2/2✓ Branch 0 taken 3072 times.
✓ Branch 1 taken 12288 times.
|
15360 | if (quant) { |
| 506 |
2/2✓ Branch 0 taken 9216 times.
✓ Branch 1 taken 3072 times.
|
12288 | for (int i = 0; i < nb_ch; i++) |
| 507 | 9216 | out_row[i][x] = quantize(gain[i], scale[i], offset[i], gamma[i]); | |
| 508 | } else { | ||
| 509 |
2/2✓ Branch 0 taken 18432 times.
✓ Branch 1 taken 12288 times.
|
30720 | for (int i = 0; i < nb_ch; i++) { |
| 510 | 18432 | out_row[i][x] = gain[i]; | |
| 511 | 18432 | min[i] = fminf(min[i], gain[i]); | |
| 512 | 18432 | max[i] = fmaxf(max[i], gain[i]); | |
| 513 | 18432 | sum[i] += gain[i]; | |
| 514 | } | ||
| 515 | } | ||
| 516 | |||
| 517 | } | ||
| 518 | } | ||
| 519 | |||
| 520 |
4/4✓ Branch 0 taken 144 times.
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 108 times.
✓ Branch 3 taken 36 times.
|
153 | for (int i = 0; !quant && i < 3; i++) { |
| 521 | 108 | s->slice_min[jobnr][i] = min[i]; | |
| 522 | 108 | s->slice_max[jobnr][i] = max[i]; | |
| 523 | 108 | s->slice_sum[jobnr][i] = sum[i]; | |
| 524 | } | ||
| 525 | |||
| 526 | 45 | return 0; | |
| 527 | } | ||
| 528 | |||
| 529 | 36 | static int slice_gain(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) | |
| 530 | { | ||
| 531 | 36 | return slice_internal(ctx, arg, jobnr, nb_jobs, 0); | |
| 532 | } | ||
| 533 | |||
| 534 | 9 | static int slice_gain_quant(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) | |
| 535 | { | ||
| 536 | 9 | return slice_internal(ctx, arg, jobnr, nb_jobs, 1); | |
| 537 | } | ||
| 538 | |||
| 539 | 36 | static int slice_quant(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) | |
| 540 | { | ||
| 541 | 36 | const ThreadData *td = arg; | |
| 542 | 36 | const GainMapContext *s = ctx->priv; | |
| 543 | 36 | const AVFrame *out = td->out; | |
| 544 | |||
| 545 | 36 | const float *restrict scale = s->quant_scale; | |
| 546 | 36 | const float *restrict offset = s->quant_offset; | |
| 547 | 36 | const float *restrict gamma = s->quant_gamma; | |
| 548 | |||
| 549 | 36 | const int y_start = ff_slice_pos(out->height, jobnr, nb_jobs); | |
| 550 | 36 | const int y_end = ff_slice_pos(out->height, jobnr + 1, nb_jobs); | |
| 551 | 36 | const int width = out->width; | |
| 552 | 36 | const int nb_ch = s->nb_channels; | |
| 553 | |||
| 554 |
2/2✓ Branch 0 taken 192 times.
✓ Branch 1 taken 36 times.
|
228 | for (int y = y_start; y < y_end; y++) { |
| 555 | float *restrict out_row[3]; | ||
| 556 |
2/2✓ Branch 0 taken 288 times.
✓ Branch 1 taken 192 times.
|
480 | for (int i = 0; i < nb_ch; i++) { |
| 557 |
2/2✓ Branch 0 taken 144 times.
✓ Branch 1 taken 144 times.
|
288 | const int p = nb_ch == 1 ? 0 : gbr_order[i]; |
| 558 | 288 | out_row[i] = (float *) (out->data[p] + y * out->linesize[p]); | |
| 559 | } | ||
| 560 | |||
| 561 |
2/2✓ Branch 0 taken 12288 times.
✓ Branch 1 taken 192 times.
|
12480 | for (int x = 0; x < width; x++) { |
| 562 |
2/2✓ Branch 0 taken 18432 times.
✓ Branch 1 taken 12288 times.
|
30720 | for (int i = 0; i < nb_ch; i++) |
| 563 | 18432 | out_row[i][x] = quantize(out_row[i][x], scale[i], offset[i], gamma[i]); | |
| 564 | } | ||
| 565 | } | ||
| 566 | |||
| 567 | 36 | return 0; | |
| 568 | } | ||
| 569 | |||
| 570 | 4 | static void choose_quant_params(AVFilterContext *ctx, const AVFrame *out, int nb_jobs) | |
| 571 | { | ||
| 572 | 4 | GainMapContext *s = ctx->priv; | |
| 573 | 4 | float frame_min[3] = { FLT_MAX, FLT_MAX, FLT_MAX }; | |
| 574 | 4 | float frame_max[3] = { -FLT_MAX, -FLT_MAX, -FLT_MAX }; | |
| 575 | 4 | double frame_sum[3] = { 0.0, 0.0, 0.0 }; | |
| 576 | |||
| 577 |
2/2✓ Branch 0 taken 36 times.
✓ Branch 1 taken 4 times.
|
40 | for (int j = 0; j < nb_jobs; j++) { |
| 578 |
2/2✓ Branch 0 taken 54 times.
✓ Branch 1 taken 36 times.
|
90 | for (int i = 0; i < s->nb_channels; i++) { |
| 579 | 54 | frame_min[i] = fminf(frame_min[i], s->slice_min[j][i]); | |
| 580 | 54 | frame_max[i] = fmaxf(frame_max[i], s->slice_max[j][i]); | |
| 581 | 54 | frame_sum[i] += s->slice_sum[j][i]; | |
| 582 | } | ||
| 583 | } | ||
| 584 | |||
| 585 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 4 times.
|
10 | for (int i = 0; i < s->nb_channels; i++) { |
| 586 | 6 | AVRational min = s->gain_min, max = s->gain_max; | |
| 587 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | if (s->measure & MEASURE_MIN) |
| 588 | 6 | min = quantq(frame_min[i], AV_ROUND_DOWN); | |
| 589 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | if (s->measure & MEASURE_MAX) |
| 590 | 6 | max = quantq(frame_max[i], AV_ROUND_UP); | |
| 591 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | if (s->measure & MEASURE_RANGE) |
| 592 | 6 | setup_range(ctx, i, min, max); | |
| 593 | |||
| 594 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (!(s->measure & MEASURE_GAMMA)) |
| 595 | ✗ | continue; | |
| 596 | |||
| 597 | 6 | const int64_t nb_pixels = (int64_t) out->width * out->height; | |
| 598 | 6 | const double mean = frame_sum[i] / nb_pixels; | |
| 599 | 6 | const double mean_quant = s->quant_scale[i] * mean + s->quant_offset[i]; | |
| 600 | |||
| 601 | /** | ||
| 602 | * Choose gamma such that mean_quant ^ gamma = 0.5; clamp to a sane | ||
| 603 | * value range of [1/8, 8] to prevent degenerate encodings. A gamma | ||
| 604 | * of >8 would collapse half the encoding space into a single 8-bit | ||
| 605 | * value, at which point we're losing more then gaining, and a value | ||
| 606 | * of <1/8 limits the amount by which a single high outlier pixel | ||
| 607 | * can determine the overall encoding gamma. | ||
| 608 | * | ||
| 609 | * We also clamp the mean to [0.01, 0.99] to prevent numerical explosion | ||
| 610 | * in the case that the mean is very close to 0 or 1. This is a looser | ||
| 611 | * bound than the final gamma clamp, so the exact values chosen do not | ||
| 612 | * matter as much. | ||
| 613 | */ | ||
| 614 | 6 | double gamma = log(0.5) / log(av_clipd(mean_quant, 0.01, 0.99)); | |
| 615 | 6 | setup_gamma(ctx, i, quantq(av_clipd(gamma, 1/8.0, 8.0), AV_ROUND_NEAR_INF)); | |
| 616 | } | ||
| 617 | 4 | } | |
| 618 | |||
| 619 | 5 | static int process_frame(FFFrameSync *fs) | |
| 620 | { | ||
| 621 | 5 | AVFilterContext *ctx = fs->parent; | |
| 622 | 5 | GainMapContext *s = ctx->priv; | |
| 623 | 5 | AVFilterLink *outlink = ctx->outputs[0]; | |
| 624 | AVFrame *base, *alt, *out; | ||
| 625 | |||
| 626 | 5 | int ret = ff_framesync_dualinput_get(fs, &base, &alt); | |
| 627 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | if (ret < 0) |
| 628 | ✗ | return ret; | |
| 629 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
|
5 | if (!base || !alt) |
| 630 | ✗ | return AVERROR_BUG; | |
| 631 | |||
| 632 | 5 | ret = setup_colorspace(ctx, base, alt); | |
| 633 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | if (ret < 0) |
| 634 | ✗ | return ret; | |
| 635 | |||
| 636 | 5 | out = ff_get_video_buffer(outlink, outlink->w, outlink->h); | |
| 637 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | if (!out) |
| 638 | ✗ | return AVERROR(ENOMEM); | |
| 639 | 5 | av_frame_copy_props(out, base); | |
| 640 | 5 | av_frame_side_data_remove_by_props(&out->side_data, &out->nb_side_data, | |
| 641 | AV_SIDE_DATA_PROP_COLOR_DEPENDENT); | ||
| 642 | |||
| 643 | 5 | out->color_trc = AVCOL_TRC_UNSPECIFIED; | |
| 644 | 5 | out->color_primaries = AVCOL_PRI_UNSPECIFIED; | |
| 645 | 5 | out->colorspace = AVCOL_SPC_UNSPECIFIED; | |
| 646 | 5 | out->color_range = AVCOL_RANGE_JPEG; | |
| 647 | |||
| 648 | 5 | ThreadData td = { | |
| 649 | .out = out, | ||
| 650 | .base = base, | ||
| 651 | .alt = alt, | ||
| 652 | }; | ||
| 653 | |||
| 654 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | if (!s->sign) { /* no-op */ |
| 655 | ✗ | for (int i = 0; i < s->nb_channels; i++) | |
| 656 | ✗ | memset(out->data[i], 0, out->height * out->linesize[i]); | |
| 657 | ✗ | goto skip; | |
| 658 | } | ||
| 659 | |||
| 660 | 5 | const int nb_jobs = FFMIN(outlink->h, s->nb_threads); | |
| 661 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
|
5 | ret = ff_filter_execute(ctx, s->measure ? slice_gain : slice_gain_quant, |
| 662 | &td, NULL, nb_jobs); | ||
| 663 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | if (ret < 0) |
| 664 | ✗ | goto fail; | |
| 665 | |||
| 666 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
|
5 | if (s->measure) { |
| 667 | 4 | choose_quant_params(ctx, out, nb_jobs); | |
| 668 | 4 | ret = ff_filter_execute(ctx, slice_quant, &td, NULL, nb_jobs); | |
| 669 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if (ret < 0) |
| 670 | ✗ | goto fail; | |
| 671 | } | ||
| 672 | |||
| 673 | 5 | skip:; | |
| 674 | AVGainMapParams *params; | ||
| 675 | 5 | params = av_gain_map_params_create_side_data(&out->side_data, &out->nb_side_data); | |
| 676 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | if (!params) { |
| 677 | ✗ | ret = AVERROR(ENOMEM); | |
| 678 | ✗ | goto fail; | |
| 679 | } | ||
| 680 | |||
| 681 | 5 | *params = s->params; | |
| 682 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
|
5 | if (av_gain_map_params_validate(params) < 0) |
| 683 | ✗ | return AVERROR_BUG; /* should never happen */ | |
| 684 | |||
| 685 | 5 | return ff_filter_frame(outlink, out); | |
| 686 | |||
| 687 | ✗ | fail: | |
| 688 | ✗ | av_frame_free(&out); | |
| 689 | ✗ | return ret; | |
| 690 | } | ||
| 691 | |||
| 692 | 5 | static int config_output(AVFilterLink *outlink) | |
| 693 | { | ||
| 694 | 5 | AVFilterContext *ctx = outlink->src; | |
| 695 | 5 | GainMapContext *s = ctx->priv; | |
| 696 | 5 | AVFilterLink *base = ctx->inputs[0]; | |
| 697 | 5 | AVFilterLink *alt = ctx->inputs[1]; | |
| 698 | int ret; | ||
| 699 | |||
| 700 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
|
5 | if (base->w != alt->w || base->h != alt->h) { |
| 701 | ✗ | av_log(ctx, AV_LOG_ERROR, | |
| 702 | "Input dimensions must match (%dx%d != %dx%d)\n", | ||
| 703 | base->w, base->h, alt->w, alt->h); | ||
| 704 | ✗ | return AVERROR(EINVAL); | |
| 705 | } | ||
| 706 | |||
| 707 | 5 | outlink->w = base->w; | |
| 708 | 5 | outlink->h = base->h; | |
| 709 | 5 | outlink->colorspace = AVCOL_SPC_UNSPECIFIED; | |
| 710 | 5 | outlink->color_range = AVCOL_RANGE_JPEG; | |
| 711 | |||
| 712 | 5 | s->nb_threads = ff_filter_get_nb_threads(ctx); | |
| 713 | 5 | s->slice_min = av_calloc(s->nb_threads, sizeof(*s->slice_min)); | |
| 714 | 5 | s->slice_max = av_calloc(s->nb_threads, sizeof(*s->slice_max)); | |
| 715 | 5 | s->slice_sum = av_calloc(s->nb_threads, sizeof(*s->slice_sum)); | |
| 716 |
3/6✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 5 times.
|
5 | if (!s->slice_min || !s->slice_max || !s->slice_sum) |
| 717 | ✗ | return AVERROR(ENOMEM); | |
| 718 | |||
| 719 | 5 | ret = ff_framesync_init_dualinput(&s->fs, ctx); | |
| 720 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | if (ret < 0) |
| 721 | ✗ | return ret; | |
| 722 | |||
| 723 | 5 | s->fs.on_event = process_frame; | |
| 724 | 5 | return ff_framesync_configure(&s->fs); | |
| 725 | } | ||
| 726 | |||
| 727 | 18 | static int activate(AVFilterContext *ctx) | |
| 728 | { | ||
| 729 | 18 | GainMapContext *s = ctx->priv; | |
| 730 | 18 | return ff_framesync_activate(&s->fs); | |
| 731 | } | ||
| 732 | |||
| 733 | 10 | static av_cold void uninit(AVFilterContext *ctx) | |
| 734 | { | ||
| 735 | 10 | GainMapContext *s = ctx->priv; | |
| 736 | 10 | ff_framesync_uninit(&s->fs); | |
| 737 | 10 | av_freep(&s->slice_min); | |
| 738 | 10 | av_freep(&s->slice_max); | |
| 739 | 10 | av_freep(&s->slice_sum); | |
| 740 | 10 | } | |
| 741 | |||
| 742 | static const AVFilterPad gainmap_inputs[] = { | ||
| 743 | { | ||
| 744 | .name = "base", | ||
| 745 | .type = AVMEDIA_TYPE_VIDEO, | ||
| 746 | }, | ||
| 747 | { | ||
| 748 | .name = "alternate", | ||
| 749 | .type = AVMEDIA_TYPE_VIDEO, | ||
| 750 | }, | ||
| 751 | }; | ||
| 752 | |||
| 753 | static const AVFilterPad gainmap_outputs[] = { | ||
| 754 | { | ||
| 755 | .name = "default", | ||
| 756 | .type = AVMEDIA_TYPE_VIDEO, | ||
| 757 | .config_props = config_output, | ||
| 758 | }, | ||
| 759 | }; | ||
| 760 | |||
| 761 | const FFFilter ff_vf_gainmap = { | ||
| 762 | .p.name = "gainmap", | ||
| 763 | .p.description = NULL_IF_CONFIG_SMALL("Generate a gain map from a base/alternate pair."), | ||
| 764 | .p.priv_class = &gainmap_class, | ||
| 765 | .p.flags = AVFILTER_FLAG_SLICE_THREADS, | ||
| 766 | .preinit = gainmap_framesync_preinit, | ||
| 767 | .priv_size = sizeof(GainMapContext), | ||
| 768 | .init = init, | ||
| 769 | .uninit = uninit, | ||
| 770 | .activate = activate, | ||
| 771 | FILTER_INPUTS(gainmap_inputs), | ||
| 772 | FILTER_OUTPUTS(gainmap_outputs), | ||
| 773 | FILTER_QUERY_FUNC2(query_formats), | ||
| 774 | }; | ||
| 775 |