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