Coverage Report

Created: 2025-06-22 08:04

/src/aom/av1/encoder/superres_scale.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2020, Alliance for Open Media. All rights reserved.
3
 *
4
 * This source code is subject to the terms of the BSD 2 Clause License and
5
 * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
6
 * was not distributed with this source code in the LICENSE file, you can
7
 * obtain it at www.aomedia.org/license/software. If the Alliance for Open
8
 * Media Patent License 1.0 was not distributed with this source code in the
9
 * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
10
 */
11
12
#include "av1/encoder/encoder_alloc.h"
13
#include "av1/encoder/superres_scale.h"
14
#include "av1/encoder/random.h"
15
16
// Compute the horizontal frequency components' energy in a frame
17
// by calculuating the 16x4 Horizontal DCT. This is to be used to
18
// decide the superresolution parameters.
19
0
static void analyze_hor_freq(const AV1_COMP *cpi, double *energy) {
20
0
  uint64_t freq_energy[16] = { 0 };
21
0
  const YV12_BUFFER_CONFIG *buf = cpi->source;
22
0
  const int bd = cpi->td.mb.e_mbd.bd;
23
0
  const int width = buf->y_crop_width;
24
0
  const int height = buf->y_crop_height;
25
0
  DECLARE_ALIGNED(16, int32_t, coeff[16 * 4]);
26
0
  int n = 0;
27
0
  memset(freq_energy, 0, sizeof(freq_energy));
28
0
  if (buf->flags & YV12_FLAG_HIGHBITDEPTH) {
29
0
    const int16_t *src16 = (const int16_t *)CONVERT_TO_SHORTPTR(buf->y_buffer);
30
0
    for (int i = 0; i < height - 4; i += 4) {
31
0
      for (int j = 0; j < width - 16; j += 16) {
32
0
        av1_fwd_txfm2d_16x4(src16 + i * buf->y_stride + j, coeff, buf->y_stride,
33
0
                            H_DCT, bd);
34
0
        for (int k = 1; k < 16; ++k) {
35
0
          const uint64_t this_energy =
36
0
              ((int64_t)coeff[k] * coeff[k]) +
37
0
              ((int64_t)coeff[k + 16] * coeff[k + 16]) +
38
0
              ((int64_t)coeff[k + 32] * coeff[k + 32]) +
39
0
              ((int64_t)coeff[k + 48] * coeff[k + 48]);
40
0
          freq_energy[k] += ROUND_POWER_OF_TWO(this_energy, 2 + 2 * (bd - 8));
41
0
        }
42
0
        n++;
43
0
      }
44
0
    }
45
0
  } else {
46
0
    assert(bd == 8);
47
0
    DECLARE_ALIGNED(16, int16_t, src16[16 * 4]);
48
0
    for (int i = 0; i < height - 4; i += 4) {
49
0
      for (int j = 0; j < width - 16; j += 16) {
50
0
        for (int ii = 0; ii < 4; ++ii)
51
0
          for (int jj = 0; jj < 16; ++jj)
52
0
            src16[ii * 16 + jj] =
53
0
                buf->y_buffer[(i + ii) * buf->y_stride + (j + jj)];
54
0
        av1_fwd_txfm2d_16x4(src16, coeff, 16, H_DCT, bd);
55
0
        for (int k = 1; k < 16; ++k) {
56
0
          const uint64_t this_energy =
57
0
              ((int64_t)coeff[k] * coeff[k]) +
58
0
              ((int64_t)coeff[k + 16] * coeff[k + 16]) +
59
0
              ((int64_t)coeff[k + 32] * coeff[k + 32]) +
60
0
              ((int64_t)coeff[k + 48] * coeff[k + 48]);
61
0
          freq_energy[k] += ROUND_POWER_OF_TWO(this_energy, 2);
62
0
        }
63
0
        n++;
64
0
      }
65
0
    }
66
0
  }
67
0
  if (n) {
68
0
    for (int k = 1; k < 16; ++k) energy[k] = (double)freq_energy[k] / n;
69
    // Convert to cumulative energy
70
0
    for (int k = 14; k > 0; --k) energy[k] += energy[k + 1];
71
0
  } else {
72
0
    for (int k = 1; k < 16; ++k) energy[k] = 1e+20;
73
0
  }
74
0
}
75
76
0
static uint8_t calculate_next_resize_scale(const AV1_COMP *cpi) {
77
  // Choose an arbitrary random number
78
0
  static unsigned int seed = 56789;
79
0
  const ResizeCfg *resize_cfg = &cpi->oxcf.resize_cfg;
80
0
  if (is_stat_generation_stage(cpi)) return SCALE_NUMERATOR;
81
0
  uint8_t new_denom = SCALE_NUMERATOR;
82
83
0
  if (cpi->common.seq_params->reduced_still_picture_hdr) return SCALE_NUMERATOR;
84
0
  switch (resize_cfg->resize_mode) {
85
0
    case RESIZE_NONE: new_denom = SCALE_NUMERATOR; break;
86
0
    case RESIZE_FIXED:
87
0
      if (cpi->common.current_frame.frame_type == KEY_FRAME)
88
0
        new_denom = resize_cfg->resize_kf_scale_denominator;
89
0
      else
90
0
        new_denom = resize_cfg->resize_scale_denominator;
91
0
      break;
92
0
    case RESIZE_RANDOM: new_denom = lcg_rand16(&seed) % 9 + 8; break;
93
0
    default: assert(0);
94
0
  }
95
0
  return new_denom;
96
0
}
97
98
0
int av1_superres_in_recode_allowed(const AV1_COMP *const cpi) {
99
0
  const AV1EncoderConfig *const oxcf = &cpi->oxcf;
100
  // Empirically found to not be beneficial for image coding.
101
0
  return oxcf->superres_cfg.superres_mode == AOM_SUPERRES_AUTO &&
102
0
         cpi->sf.hl_sf.superres_auto_search_type != SUPERRES_AUTO_SOLO &&
103
0
         cpi->rc.frames_to_key > 1;
104
0
}
105
106
0
#define SUPERRES_ENERGY_BY_Q2_THRESH_KEYFRAME_SOLO 0.012
107
0
#define SUPERRES_ENERGY_BY_Q2_THRESH_KEYFRAME 0.008
108
0
#define SUPERRES_ENERGY_BY_Q2_THRESH_ARFFRAME 0.008
109
0
#define SUPERRES_ENERGY_BY_AC_THRESH 0.2
110
111
static double get_energy_by_q2_thresh(const GF_GROUP *gf_group,
112
                                      const RATE_CONTROL *rc,
113
0
                                      int gf_frame_index) {
114
  // TODO(now): Return keyframe thresh * factor based on frame type / pyramid
115
  // level.
116
0
  if (gf_group->update_type[gf_frame_index] == ARF_UPDATE) {
117
0
    return SUPERRES_ENERGY_BY_Q2_THRESH_ARFFRAME;
118
0
  } else if (gf_group->update_type[gf_frame_index] == KF_UPDATE) {
119
0
    if (rc->frames_to_key <= 1)
120
0
      return SUPERRES_ENERGY_BY_Q2_THRESH_KEYFRAME_SOLO;
121
0
    else
122
0
      return SUPERRES_ENERGY_BY_Q2_THRESH_KEYFRAME;
123
0
  } else {
124
0
    assert(0);
125
0
  }
126
0
  return 0;
127
0
}
128
129
static uint8_t get_superres_denom_from_qindex_energy(int qindex, double *energy,
130
                                                     double threshq,
131
0
                                                     double threshp) {
132
0
  const double q = av1_convert_qindex_to_q(qindex, AOM_BITS_8);
133
0
  const double tq = threshq * q * q;
134
0
  const double tp = threshp * energy[1];
135
0
  const double thresh = AOMMIN(tq, tp);
136
0
  int k;
137
0
  for (k = SCALE_NUMERATOR * 2; k > SCALE_NUMERATOR; --k) {
138
0
    if (energy[k - 1] > thresh) break;
139
0
  }
140
0
  return 3 * SCALE_NUMERATOR - k;
141
0
}
142
143
static uint8_t get_superres_denom_for_qindex(const AV1_COMP *cpi, int qindex,
144
0
                                             int sr_kf, int sr_arf) {
145
  // Use superres for Key-frames and Alt-ref frames only.
146
0
  const GF_GROUP *gf_group = &cpi->ppi->gf_group;
147
0
  if (gf_group->update_type[cpi->gf_frame_index] != KF_UPDATE &&
148
0
      gf_group->update_type[cpi->gf_frame_index] != ARF_UPDATE) {
149
0
    return SCALE_NUMERATOR;
150
0
  }
151
0
  if (gf_group->update_type[cpi->gf_frame_index] == KF_UPDATE && !sr_kf) {
152
0
    return SCALE_NUMERATOR;
153
0
  }
154
0
  if (gf_group->update_type[cpi->gf_frame_index] == ARF_UPDATE && !sr_arf) {
155
0
    return SCALE_NUMERATOR;
156
0
  }
157
158
0
  double energy[16];
159
0
  analyze_hor_freq(cpi, energy);
160
161
0
  const double energy_by_q2_thresh =
162
0
      get_energy_by_q2_thresh(gf_group, &cpi->rc, cpi->gf_frame_index);
163
0
  int denom = get_superres_denom_from_qindex_energy(
164
0
      qindex, energy, energy_by_q2_thresh, SUPERRES_ENERGY_BY_AC_THRESH);
165
  /*
166
  printf("\nenergy = [");
167
  for (int k = 1; k < 16; ++k) printf("%f, ", energy[k]);
168
  printf("]\n");
169
  printf("boost = %d\n",
170
         (gf_group->update_type[cpi->gf_frame_index] == KF_UPDATE)
171
             ? cpi->ppi->p_rc.kf_boost
172
             : cpi->rc.gfu_boost);
173
  printf("denom = %d\n", denom);
174
  */
175
0
  if (av1_superres_in_recode_allowed(cpi)) {
176
0
    assert(cpi->superres_mode != AOM_SUPERRES_NONE);
177
    // Force superres to be tried in the recode loop, as full-res is also going
178
    // to be tried anyway.
179
0
    denom = AOMMAX(denom, SCALE_NUMERATOR + 1);
180
0
  }
181
0
  return denom;
182
0
}
183
184
0
static uint8_t calculate_next_superres_scale(AV1_COMP *cpi) {
185
  // Choose an arbitrary random number
186
0
  static unsigned int seed = 34567;
187
0
  const AV1EncoderConfig *oxcf = &cpi->oxcf;
188
0
  const SuperResCfg *const superres_cfg = &oxcf->superres_cfg;
189
0
  const FrameDimensionCfg *const frm_dim_cfg = &oxcf->frm_dim_cfg;
190
0
  const RateControlCfg *const rc_cfg = &oxcf->rc_cfg;
191
192
0
  if (is_stat_generation_stage(cpi)) return SCALE_NUMERATOR;
193
0
  uint8_t new_denom = SCALE_NUMERATOR;
194
195
  // Make sure that superres mode of the frame is consistent with the
196
  // sequence-level flag.
197
0
  assert(IMPLIES(superres_cfg->superres_mode != AOM_SUPERRES_NONE,
198
0
                 cpi->common.seq_params->enable_superres));
199
0
  assert(IMPLIES(!cpi->common.seq_params->enable_superres,
200
0
                 superres_cfg->superres_mode == AOM_SUPERRES_NONE));
201
  // Make sure that superres mode for current encoding is consistent with user
202
  // provided superres mode.
203
0
  assert(IMPLIES(superres_cfg->superres_mode != AOM_SUPERRES_AUTO,
204
0
                 cpi->superres_mode == superres_cfg->superres_mode));
205
206
  // Note: we must look at the current superres_mode to be tried in 'cpi' here,
207
  // not the user given mode in 'oxcf'.
208
0
  switch (cpi->superres_mode) {
209
0
    case AOM_SUPERRES_NONE: new_denom = SCALE_NUMERATOR; break;
210
0
    case AOM_SUPERRES_FIXED:
211
0
      if (cpi->common.current_frame.frame_type == KEY_FRAME)
212
0
        new_denom = superres_cfg->superres_kf_scale_denominator;
213
0
      else
214
0
        new_denom = superres_cfg->superres_scale_denominator;
215
0
      break;
216
0
    case AOM_SUPERRES_RANDOM: new_denom = lcg_rand16(&seed) % 9 + 8; break;
217
0
    case AOM_SUPERRES_QTHRESH: {
218
      // Do not use superres when screen content tools are used.
219
0
      if (cpi->common.features.allow_screen_content_tools) break;
220
0
      if (rc_cfg->mode == AOM_VBR || rc_cfg->mode == AOM_CQ)
221
0
        av1_set_target_rate(cpi, frm_dim_cfg->width, frm_dim_cfg->height);
222
223
      // Now decide the use of superres based on 'q'.
224
0
      int bottom_index, top_index;
225
0
      const int q = av1_rc_pick_q_and_bounds(
226
0
          cpi, frm_dim_cfg->width, frm_dim_cfg->height, cpi->gf_frame_index,
227
0
          &bottom_index, &top_index);
228
229
0
      const int qthresh = (frame_is_intra_only(&cpi->common))
230
0
                              ? superres_cfg->superres_kf_qthresh
231
0
                              : superres_cfg->superres_qthresh;
232
0
      if (q <= qthresh) {
233
0
        new_denom = SCALE_NUMERATOR;
234
0
      } else {
235
0
        new_denom = get_superres_denom_for_qindex(cpi, q, 1, 1);
236
0
      }
237
0
      break;
238
0
    }
239
0
    case AOM_SUPERRES_AUTO: {
240
0
      if (cpi->common.features.allow_screen_content_tools) break;
241
0
      if (rc_cfg->mode == AOM_VBR || rc_cfg->mode == AOM_CQ)
242
0
        av1_set_target_rate(cpi, frm_dim_cfg->width, frm_dim_cfg->height);
243
244
      // Now decide the use of superres based on 'q'.
245
0
      int bottom_index, top_index;
246
0
      const int q = av1_rc_pick_q_and_bounds(
247
0
          cpi, frm_dim_cfg->width, frm_dim_cfg->height, cpi->gf_frame_index,
248
0
          &bottom_index, &top_index);
249
250
0
      const SUPERRES_AUTO_SEARCH_TYPE sr_search_type =
251
0
          cpi->sf.hl_sf.superres_auto_search_type;
252
0
      const int qthresh = (sr_search_type == SUPERRES_AUTO_SOLO) ? 128 : 0;
253
0
      if (q <= qthresh) {
254
0
        new_denom = SCALE_NUMERATOR;  // Don't use superres.
255
0
      } else {
256
0
        if (sr_search_type == SUPERRES_AUTO_ALL) {
257
0
          if (cpi->common.current_frame.frame_type == KEY_FRAME)
258
0
            new_denom = superres_cfg->superres_kf_scale_denominator;
259
0
          else
260
0
            new_denom = superres_cfg->superres_scale_denominator;
261
0
        } else {
262
0
          new_denom = get_superres_denom_for_qindex(cpi, q, 1, 1);
263
0
        }
264
0
      }
265
0
      break;
266
0
    }
267
0
    default: assert(0);
268
0
  }
269
0
  return new_denom;
270
0
}
271
272
0
static int dimension_is_ok(int orig_dim, int resized_dim, int denom) {
273
0
  return (resized_dim * SCALE_NUMERATOR >= orig_dim * denom / 2);
274
0
}
275
276
0
static int dimensions_are_ok(int owidth, int oheight, size_params_type *rsz) {
277
  // Only need to check the width, as scaling is horizontal only.
278
0
  (void)oheight;
279
0
  return dimension_is_ok(owidth, rsz->resize_width, rsz->superres_denom);
280
0
}
281
282
static int validate_size_scales(RESIZE_MODE resize_mode,
283
                                aom_superres_mode superres_mode, int owidth,
284
0
                                int oheight, size_params_type *rsz) {
285
0
  if (dimensions_are_ok(owidth, oheight, rsz)) {  // Nothing to do.
286
0
    return 1;
287
0
  }
288
289
  // Calculate current resize scale.
290
0
  int resize_denom =
291
0
      AOMMAX(DIVIDE_AND_ROUND(owidth * SCALE_NUMERATOR, rsz->resize_width),
292
0
             DIVIDE_AND_ROUND(oheight * SCALE_NUMERATOR, rsz->resize_height));
293
294
0
  if (resize_mode != RESIZE_RANDOM && superres_mode == AOM_SUPERRES_RANDOM) {
295
    // Alter superres scale as needed to enforce conformity.
296
0
    rsz->superres_denom =
297
0
        (2 * SCALE_NUMERATOR * SCALE_NUMERATOR) / resize_denom;
298
0
    if (!dimensions_are_ok(owidth, oheight, rsz)) {
299
0
      if (rsz->superres_denom > SCALE_NUMERATOR) --rsz->superres_denom;
300
0
    }
301
0
  } else if (resize_mode == RESIZE_RANDOM &&
302
0
             superres_mode != AOM_SUPERRES_RANDOM) {
303
    // Alter resize scale as needed to enforce conformity.
304
0
    resize_denom =
305
0
        (2 * SCALE_NUMERATOR * SCALE_NUMERATOR) / rsz->superres_denom;
306
0
    rsz->resize_width = owidth;
307
0
    rsz->resize_height = oheight;
308
0
    av1_calculate_scaled_size(&rsz->resize_width, &rsz->resize_height,
309
0
                              resize_denom);
310
0
    if (!dimensions_are_ok(owidth, oheight, rsz)) {
311
0
      if (resize_denom > SCALE_NUMERATOR) {
312
0
        --resize_denom;
313
0
        rsz->resize_width = owidth;
314
0
        rsz->resize_height = oheight;
315
0
        av1_calculate_scaled_size(&rsz->resize_width, &rsz->resize_height,
316
0
                                  resize_denom);
317
0
      }
318
0
    }
319
0
  } else if (resize_mode == RESIZE_RANDOM &&
320
0
             superres_mode == AOM_SUPERRES_RANDOM) {
321
    // Alter both resize and superres scales as needed to enforce conformity.
322
0
    do {
323
0
      if (resize_denom > rsz->superres_denom)
324
0
        --resize_denom;
325
0
      else
326
0
        --rsz->superres_denom;
327
0
      rsz->resize_width = owidth;
328
0
      rsz->resize_height = oheight;
329
0
      av1_calculate_scaled_size(&rsz->resize_width, &rsz->resize_height,
330
0
                                resize_denom);
331
0
    } while (!dimensions_are_ok(owidth, oheight, rsz) &&
332
0
             (resize_denom > SCALE_NUMERATOR ||
333
0
              rsz->superres_denom > SCALE_NUMERATOR));
334
0
  } else {  // We are allowed to alter neither resize scale nor superres
335
            // scale.
336
0
    return 0;
337
0
  }
338
0
  return dimensions_are_ok(owidth, oheight, rsz);
339
0
}
340
341
// Calculates resize and superres params for next frame
342
0
static size_params_type calculate_next_size_params(AV1_COMP *cpi) {
343
0
  const AV1EncoderConfig *oxcf = &cpi->oxcf;
344
0
  ResizePendingParams *resize_pending_params = &cpi->resize_pending_params;
345
0
  const FrameDimensionCfg *const frm_dim_cfg = &oxcf->frm_dim_cfg;
346
0
  size_params_type rsz = { frm_dim_cfg->width, frm_dim_cfg->height,
347
0
                           SCALE_NUMERATOR };
348
0
  int resize_denom = SCALE_NUMERATOR;
349
0
  if (has_no_stats_stage(cpi) && cpi->ppi->use_svc &&
350
0
      (cpi->common.width != cpi->oxcf.frm_dim_cfg.width ||
351
0
       cpi->common.height != cpi->oxcf.frm_dim_cfg.height)) {
352
0
    rsz.resize_width = cpi->common.width;
353
0
    rsz.resize_height = cpi->common.height;
354
0
    return rsz;
355
0
  }
356
0
  if (is_stat_generation_stage(cpi)) return rsz;
357
0
  if (resize_pending_params->width && resize_pending_params->height) {
358
0
    rsz.resize_width = resize_pending_params->width;
359
0
    rsz.resize_height = resize_pending_params->height;
360
0
    resize_pending_params->width = resize_pending_params->height = 0;
361
0
    if (oxcf->superres_cfg.superres_mode == AOM_SUPERRES_NONE) return rsz;
362
0
  } else {
363
0
    resize_denom = calculate_next_resize_scale(cpi);
364
0
    rsz.resize_width = frm_dim_cfg->width;
365
0
    rsz.resize_height = frm_dim_cfg->height;
366
0
    av1_calculate_scaled_size(&rsz.resize_width, &rsz.resize_height,
367
0
                              resize_denom);
368
0
  }
369
0
  rsz.superres_denom = calculate_next_superres_scale(cpi);
370
0
  if (!validate_size_scales(oxcf->resize_cfg.resize_mode, cpi->superres_mode,
371
0
                            frm_dim_cfg->width, frm_dim_cfg->height, &rsz))
372
0
    assert(0 && "Invalid scale parameters");
373
0
  return rsz;
374
0
}
375
376
static void setup_frame_size_from_params(AV1_COMP *cpi,
377
0
                                         const size_params_type *rsz) {
378
0
  int encode_width = rsz->resize_width;
379
0
  int encode_height = rsz->resize_height;
380
381
0
  AV1_COMMON *cm = &cpi->common;
382
0
  cm->superres_upscaled_width = encode_width;
383
0
  cm->superres_upscaled_height = encode_height;
384
0
  cm->superres_scale_denominator = rsz->superres_denom;
385
0
  av1_calculate_scaled_superres_size(&encode_width, &encode_height,
386
0
                                     rsz->superres_denom);
387
0
  av1_set_frame_size(cpi, encode_width, encode_height);
388
0
}
389
390
0
void av1_setup_frame_size(AV1_COMP *cpi) {
391
0
  AV1_COMMON *cm = &cpi->common;
392
  // Reset superres params from previous frame.
393
0
  cm->superres_scale_denominator = SCALE_NUMERATOR;
394
0
  const size_params_type rsz = calculate_next_size_params(cpi);
395
0
  setup_frame_size_from_params(cpi, &rsz);
396
397
0
  assert(av1_is_min_tile_width_satisfied(cm));
398
0
}
399
400
0
void av1_superres_post_encode(AV1_COMP *cpi) {
401
0
  AV1_COMMON *cm = &cpi->common;
402
403
0
  assert(cpi->oxcf.superres_cfg.enable_superres);
404
0
  assert(!is_lossless_requested(&cpi->oxcf.rc_cfg));
405
0
  assert(!cm->features.all_lossless);
406
407
0
  av1_superres_upscale(cm, NULL, cpi->alloc_pyramid);
408
409
  // If regular resizing is occurring the source will need to be downscaled to
410
  // match the upscaled superres resolution. Otherwise the original source is
411
  // used.
412
0
  if (!av1_resize_scaled(cm)) {
413
0
    cpi->source = cpi->unscaled_source;
414
0
    if (cpi->last_source != NULL) cpi->last_source = cpi->unscaled_last_source;
415
0
  } else {
416
0
    assert(cpi->unscaled_source->y_crop_width != cm->superres_upscaled_width);
417
0
    assert(cpi->unscaled_source->y_crop_height != cm->superres_upscaled_height);
418
    // Do downscale. cm->(width|height) has been updated by
419
    // av1_superres_upscale
420
0
    cpi->source = realloc_and_scale_source(cpi, cm->superres_upscaled_width,
421
0
                                           cm->superres_upscaled_height);
422
0
  }
423
0
}