Coverage Report

Created: 2026-09-14 07:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libheif/libheif/plugins/encoder_aom.cc
Line
Count
Source
1
/*
2
 * HEIF codec.
3
 * Copyright (c) 2017 Dirk Farin <dirk.farin@gmail.com>
4
 *
5
 * This file is part of libheif.
6
 *
7
 * libheif is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU Lesser General Public License as
9
 * published by the Free Software Foundation, either version 3 of
10
 * the License, or (at your option) any later version.
11
 *
12
 * libheif is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General Public License
18
 * along with libheif.  If not, see <http://www.gnu.org/licenses/>.
19
 */
20
21
#include "libheif/heif.h"
22
#include "libheif/heif_plugin.h"
23
#include "common_utils.h"
24
#include <algorithm>
25
#include <cstring>
26
#include <cassert>
27
#include <sstream>
28
#include <vector>
29
#include <string>
30
#include <thread>
31
#include <memory>
32
#include <utility>
33
#include "encoder_aom.h"
34
#include "encoder_input_check.h"
35
36
#include <deque>
37
#include <aom/aom_encoder.h>
38
#include <aom/aomcx.h>
39
#include <mutex>
40
41
// Detect whether the aom_codec_set_option() function is available.
42
// See https://aomedia.googlesource.com/aom/+/c1d42fe6615c96fc929257ed53c41fa094f38836%5E%21/aom/aom_codec.h.
43
#if AOM_CODEC_ABI_VERSION >= (6 + AOM_IMAGE_ABI_VERSION)
44
#define HAVE_AOM_CODEC_SET_OPTION 1
45
#endif
46
47
#if defined(HAVE_AOM_CODEC_SET_OPTION)
48
struct custom_option
49
{
50
    std::string name;
51
    std::string value;
52
};
53
#endif
54
55
struct encoder_struct_aom
56
{
57
  ~encoder_struct_aom()
58
0
  {
59
0
    for (auto* error : aom_errors) {
60
0
      delete[] error;
61
0
    }
62
63
    // automatically destroy aom_codec_ctx_t when we leave the function
64
//    auto codec_ctx_deleter = std::unique_ptr<aom_codec_ctx_t, aom_codec_err_t (*)(aom_codec_ctx_t*)>(&codec, aom_codec_destroy);
65
66
0
    aom_codec_destroy(&codec);
67
0
  }
68
69
  aom_codec_ctx_t codec;
70
71
  // --- parameters
72
73
  bool realtime_mode;
74
  int cpu_used;  // = parameter 'speed'. I guess this is a better name than 'cpu_used'.
75
76
  int quality;
77
  int alpha_quality;
78
  int min_q;
79
  int max_q;
80
  int alpha_min_q;
81
  int alpha_max_q;
82
  int threads;
83
  bool lossless;
84
  bool lossless_alpha;
85
  bool auto_tiles;
86
  bool enable_intra_block_copy;
87
88
#if defined(HAVE_AOM_CODEC_SET_OPTION)
89
  std::vector<custom_option> custom_options;
90
91
  void add_custom_option(const custom_option&);
92
93
  void add_custom_option(std::string name, std::string value);
94
#endif
95
96
  aom_tune_metric tune;
97
  bool tune_auto = true;
98
99
  heif_chroma chroma = heif_chroma_420;
100
101
  // bit depth the codec was initialized with, to check the later frames of a sequence against
102
  int bit_depth = 8;
103
104
  // --- input
105
106
  bool alpha_quality_set = false;
107
  bool alpha_min_q_set = false;
108
  bool alpha_max_q_set = false;
109
110
  // --- output
111
112
  struct Packet
113
  {
114
    std::vector<uint8_t> compressedData;
115
    uintptr_t frameNr = 0;
116
    bool is_keyframe = false;
117
  };
118
119
  std::deque<Packet> output_packets;
120
  std::vector<uint8_t> active_output_data;
121
122
  //bool data_read = false;
123
124
  // --- error message copies
125
126
  std::mutex aom_errors_mutex;
127
  std::vector<const char*> aom_errors;
128
129
  const char* set_aom_error(const char* aom_error_detail);
130
};
131
132
#if defined(HAVE_AOM_CODEC_SET_OPTION)
133
134
void encoder_struct_aom::add_custom_option(const custom_option& p)
135
0
{
136
  // if there is already a parameter of that name, remove it from list
137
138
0
  for (auto iter = custom_options.begin(); iter != custom_options.end(); ++iter) {
139
0
    if (iter->name == p.name) {
140
0
      custom_options.erase(iter);
141
0
      break;
142
0
    }
143
0
  }
144
145
  // and add the new parameter at the end of the list
146
147
0
  custom_options.push_back(p);
148
0
}
149
150
void encoder_struct_aom::add_custom_option(std::string name, std::string value)
151
0
{
152
0
  custom_option p;
153
0
  p.name = std::move(name);
154
0
  p.value = std::move(value);
155
0
  add_custom_option(p);
156
0
}
157
158
#endif
159
160
static const char* kError_undefined_error = "Undefined AOM error";
161
static const char* kError_codec_enc_config_default = "Error creating the default encoder config";
162
163
const char* encoder_struct_aom::set_aom_error(const char* aom_error)
164
0
{
165
0
  if (aom_error) {
166
    // We have to make a copy because the error returned from aom_codec_error_detail() is only valid
167
    // while the codec structure exists.
168
169
0
    char* err_copy = new char[strlen(aom_error) + 1];
170
0
    strcpy(err_copy, aom_error);
171
172
0
    std::lock_guard<std::mutex> lock(aom_errors_mutex);
173
0
    aom_errors.push_back(err_copy);
174
175
0
    return err_copy;
176
0
  }
177
0
  else {
178
0
    return kError_undefined_error;
179
0
  }
180
0
}
181
182
static const char* kParam_min_q = "min-q";
183
static const char* kParam_max_q = "max-q";
184
static const char* kParam_alpha_quality = "alpha-quality";
185
static const char* kParam_alpha_min_q = "alpha-min-q";
186
static const char* kParam_alpha_max_q = "alpha-max-q";
187
static const char* kParam_lossless_alpha = "lossless-alpha";
188
static const char* kParam_auto_tiles = "auto-tiles";
189
static const char* kParam_enable_intra_block_copy = "enable-intrabc";
190
static const char* kParam_threads = "threads";
191
static const char* kParam_realtime = "realtime";
192
static const char* kParam_speed = "speed";
193
194
static const char* kParam_chroma = "chroma";
195
static const char* const kParam_chroma_valid_values[] = {
196
    "420", "422", "444", nullptr
197
};
198
199
static const char* kParam_tune = "tune";
200
static const char* const kParam_tune_valid_values[] = {
201
    "auto", "psnr", "ssim", "iq", nullptr
202
};
203
204
#if defined(AOM_HAVE_TUNE_IQ)
205
// libaom's handle_tuning() enables chroma delta-q for AOM_TUNE_IQ, and validate_config()
206
// then rejects that combination with lossless coding. Tracked upstream as
207
// https://aomedia.g-issues.chromium.org/issues/383595066 (still present in libaom v3.15.0).
208
// Once libaom lifts the restriction, this workaround can be dropped for those versions.
209
static heif_error heif_error_lossless_with_tune_iq = {
210
  heif_error_Usage_error,
211
  heif_suberror_Invalid_parameter_value,
212
  "AOM 'tune=iq' cannot be combined with lossless encoding because libaom enables "
213
  "chroma delta-q for this tune. Use 'tune=ssim' or 'tune=psnr' for lossless images."
214
};
215
216
// This table has been copied from libavif/src/codec_aom.c
217
218
// Quality (q) to quantizer (qp) formula for tune=iq (Image Quality), expressed as a look-up table for more clarity.
219
// Copied from libavif (src/codec_aom.c). The formula is a piecewise linear function empirically selected
220
// to correct for the non-linear bitrate increase of tune=iq relative to tune=ssim with the same qp.
221
//
222
// | Quality | Quantizer                          | Step size |
223
// |---------|------------------------------------|-----------|
224
// |  0 -  6 | 63 - floor(quality / 3)            |         3 |
225
// |  7 - 28 | 61 - round((quality - 7) / 2)      |         2 |
226
// | 29 - 53 | 50 - round((quality - 29) * 3 / 5) |      1.66 |
227
// | 54 - 99 | 35 - round((quality - 54) * 3 / 4) |      1.33 |
228
// |     100 | 0 (lossless)                       |         1 |
229
//
230
// The x axis of the table represents the ones digit, while the y axis represents the tens digit
231
// of the q value [0-100], which is then mapped to a qp value [0-63].
232
// clang-format off
233
static const int tuneIqQualityToQuantizer[101] = {
234
// 1s digit: *0  *1  *2  *3  *4  *5  *6  *7  *8  *9     10s digit:
235
             63, 63, 63, 62, 62, 62, 61, 61, 60, 60, // 0*
236
             59, 59, 58, 58, 57, 57, 56, 56, 55, 55, // 1*
237
             54, 54, 53, 53, 52, 52, 51, 51, 50, 50, // 2*
238
             49, 49, 48, 48, 47, 46, 46, 45, 45, 44, // 3*
239
             43, 43, 42, 42, 41, 40, 40, 39, 39, 38, // 4*
240
             37, 37, 36, 36, 35, 34, 33, 33, 32, 31, // 5*
241
             30, 30, 29, 28, 27, 27, 26, 25, 24, 24, // 6*
242
             23, 22, 21, 21, 20, 19, 18, 18, 17, 16, // 7*
243
             15, 15, 14, 13, 12, 12, 11, 10,  9,  9, // 8*
244
              8,  7,  6,  6,  5,  4,  3,  3,  2,  1, // 9*
245
              0  // quality 100
246
};
247
// clang-format on
248
#endif
249
250
static const int AOM_PLUGIN_PRIORITY = 60;
251
252
0
#define MAX_PLUGIN_NAME_LENGTH 80
253
254
static char plugin_name[MAX_PLUGIN_NAME_LENGTH];
255
256
257
static void aom_set_default_parameters(void* encoder);
258
259
260
static const char* aom_plugin_name()
261
0
{
262
0
  const char* encoder_name = aom_codec_iface_name(aom_codec_av1_cx());
263
0
  if (strlen(encoder_name) < MAX_PLUGIN_NAME_LENGTH) {
264
0
    strcpy(plugin_name, encoder_name);
265
0
  }
266
0
  else {
267
0
    strcpy(plugin_name, "AOMedia AV1 encoder");
268
0
  }
269
270
0
  return plugin_name;
271
0
}
272
273
274
#define MAX_NPARAMETERS 16
275
276
static heif_encoder_parameter aom_encoder_params[MAX_NPARAMETERS];
277
static const heif_encoder_parameter* aom_encoder_parameter_ptrs[MAX_NPARAMETERS + 1];
278
279
static void aom_init_parameters()
280
20.6k
{
281
20.6k
  heif_encoder_parameter* p = aom_encoder_params;
282
20.6k
  const heif_encoder_parameter** d = aom_encoder_parameter_ptrs;
283
20.6k
  int i = 0;
284
285
20.6k
  assert(i < MAX_NPARAMETERS);
286
20.6k
  p->version = 2;
287
20.6k
  p->name = kParam_realtime;
288
20.6k
  p->type = heif_encoder_parameter_type_boolean;
289
20.6k
  p->boolean.default_value = false;
290
20.6k
  p->has_default = true;
291
20.6k
  d[i++] = p++;
292
293
20.6k
  assert(i < MAX_NPARAMETERS);
294
20.6k
  p->version = 2;
295
20.6k
  p->name = kParam_speed;
296
20.6k
  p->type = heif_encoder_parameter_type_integer;
297
20.6k
  p->integer.default_value = 6;
298
20.6k
  p->has_default = true;
299
20.6k
  p->integer.have_minimum_maximum = true;
300
20.6k
  p->integer.minimum = 0;
301
20.6k
  if (aom_codec_version_major() >= 3) {
302
20.6k
    p->integer.maximum = 9;
303
20.6k
  }
304
0
  else {
305
0
    p->integer.maximum = 8;
306
0
  }
307
20.6k
  p->integer.valid_values = NULL;
308
20.6k
  p->integer.num_valid_values = 0;
309
20.6k
  d[i++] = p++;
310
311
20.6k
  assert(i < MAX_NPARAMETERS);
312
20.6k
  p->version = 2;
313
20.6k
  p->name = kParam_threads;
314
20.6k
  p->type = heif_encoder_parameter_type_integer;
315
20.6k
  p->has_default = true;
316
20.6k
  p->integer.have_minimum_maximum = true;
317
20.6k
  p->integer.minimum = 1;
318
20.6k
  p->integer.maximum = 64;
319
20.6k
  int threads = static_cast<int>(std::thread::hardware_concurrency());
320
20.6k
  if (threads == 0) {
321
    // Could not autodetect, use previous default value.
322
0
    threads = 4;
323
0
  }
324
20.6k
  threads = std::min(threads, p->integer.maximum);
325
20.6k
  p->integer.default_value = threads;
326
20.6k
  p->integer.valid_values = NULL;
327
20.6k
  p->integer.num_valid_values = 0;
328
20.6k
  d[i++] = p++;
329
330
20.6k
  assert(i < MAX_NPARAMETERS);
331
20.6k
  p->version = 2;
332
20.6k
  p->name = heif_encoder_parameter_name_quality;
333
20.6k
  p->type = heif_encoder_parameter_type_integer;
334
20.6k
  p->integer.default_value = 50;
335
20.6k
  p->has_default = true;
336
20.6k
  p->integer.have_minimum_maximum = true;
337
20.6k
  p->integer.minimum = 0;
338
20.6k
  p->integer.maximum = 100;
339
20.6k
  p->integer.valid_values = NULL;
340
20.6k
  p->integer.num_valid_values = 0;
341
20.6k
  d[i++] = p++;
342
343
20.6k
  assert(i < MAX_NPARAMETERS);
344
20.6k
  p->version = 2;
345
20.6k
  p->name = heif_encoder_parameter_name_lossless;
346
20.6k
  p->type = heif_encoder_parameter_type_boolean;
347
20.6k
  p->boolean.default_value = false;
348
20.6k
  p->has_default = true;
349
20.6k
  d[i++] = p++;
350
351
20.6k
  assert(i < MAX_NPARAMETERS);
352
20.6k
  p->version = 2;
353
20.6k
  p->name = kParam_chroma;
354
20.6k
  p->type = heif_encoder_parameter_type_string;
355
20.6k
  p->string.default_value = "420";
356
20.6k
  p->has_default = true;
357
20.6k
  p->string.valid_values = kParam_chroma_valid_values;
358
20.6k
  d[i++] = p++;
359
360
20.6k
  assert(i < MAX_NPARAMETERS);
361
20.6k
  p->version = 2;
362
20.6k
  p->name = kParam_tune;
363
20.6k
  p->type = heif_encoder_parameter_type_string;
364
20.6k
  p->string.default_value = "auto";
365
20.6k
  p->has_default = true;
366
20.6k
  p->string.valid_values = kParam_tune_valid_values;
367
20.6k
  d[i++] = p++;
368
369
20.6k
  assert(i < MAX_NPARAMETERS);
370
20.6k
  p->version = 2;
371
20.6k
  p->name = kParam_min_q;
372
20.6k
  p->type = heif_encoder_parameter_type_integer;
373
20.6k
  p->integer.default_value = 0;
374
20.6k
  p->has_default = true;
375
20.6k
  p->integer.have_minimum_maximum = true;
376
20.6k
  p->integer.minimum = 0;
377
20.6k
  p->integer.maximum = 63;
378
20.6k
  p->integer.valid_values = NULL;
379
20.6k
  p->integer.num_valid_values = 0;
380
20.6k
  d[i++] = p++;
381
382
20.6k
  assert(i < MAX_NPARAMETERS);
383
20.6k
  p->version = 2;
384
20.6k
  p->name = kParam_max_q;
385
20.6k
  p->type = heif_encoder_parameter_type_integer;
386
20.6k
  p->integer.default_value = 63;
387
20.6k
  p->has_default = true;
388
20.6k
  p->integer.have_minimum_maximum = true;
389
20.6k
  p->integer.minimum = 0;
390
20.6k
  p->integer.maximum = 63;
391
20.6k
  p->integer.valid_values = NULL;
392
20.6k
  p->integer.num_valid_values = 0;
393
20.6k
  d[i++] = p++;
394
395
20.6k
  assert(i < MAX_NPARAMETERS);
396
20.6k
  p->version = 2;
397
20.6k
  p->name = kParam_alpha_quality;
398
20.6k
  p->type = heif_encoder_parameter_type_integer;
399
20.6k
  p->has_default = false;
400
20.6k
  p->integer.have_minimum_maximum = true;
401
20.6k
  p->integer.minimum = 0;
402
20.6k
  p->integer.maximum = 100;
403
20.6k
  p->integer.valid_values = NULL;
404
20.6k
  p->integer.num_valid_values = 0;
405
20.6k
  d[i++] = p++;
406
407
20.6k
  assert(i < MAX_NPARAMETERS);
408
20.6k
  p->version = 2;
409
20.6k
  p->name = kParam_alpha_min_q;
410
20.6k
  p->type = heif_encoder_parameter_type_integer;
411
20.6k
  p->has_default = false;
412
20.6k
  p->integer.have_minimum_maximum = true;
413
20.6k
  p->integer.minimum = 0;
414
20.6k
  p->integer.maximum = 63;
415
20.6k
  p->integer.valid_values = NULL;
416
20.6k
  p->integer.num_valid_values = 0;
417
20.6k
  d[i++] = p++;
418
419
20.6k
  assert(i < MAX_NPARAMETERS);
420
20.6k
  p->version = 2;
421
20.6k
  p->name = kParam_alpha_max_q;
422
20.6k
  p->type = heif_encoder_parameter_type_integer;
423
20.6k
  p->has_default = false;
424
20.6k
  p->integer.have_minimum_maximum = true;
425
20.6k
  p->integer.minimum = 0;
426
20.6k
  p->integer.maximum = 63;
427
20.6k
  p->integer.valid_values = NULL;
428
20.6k
  p->integer.num_valid_values = 0;
429
20.6k
  d[i++] = p++;
430
431
20.6k
  assert(i < MAX_NPARAMETERS);
432
20.6k
  p->version = 2;
433
20.6k
  p->name = kParam_lossless_alpha;
434
20.6k
  p->type = heif_encoder_parameter_type_boolean;
435
20.6k
  p->boolean.default_value = false;
436
20.6k
  p->has_default = true;
437
20.6k
  d[i++] = p++;
438
439
20.6k
  assert(i < MAX_NPARAMETERS);
440
20.6k
  p->version = 2;
441
20.6k
  p->name = kParam_auto_tiles;
442
20.6k
  p->type = heif_encoder_parameter_type_boolean;
443
20.6k
  p->boolean.default_value = false;
444
20.6k
  p->has_default = true;
445
20.6k
  d[i++] = p++;
446
447
20.6k
  assert(i < MAX_NPARAMETERS);
448
20.6k
  p->version = 2;
449
20.6k
  p->name = kParam_enable_intra_block_copy;
450
20.6k
  p->type = heif_encoder_parameter_type_boolean;
451
20.6k
  p->boolean.default_value = true;
452
20.6k
  p->has_default = true;
453
20.6k
  d[i++] = p++;
454
455
20.6k
  assert(i < MAX_NPARAMETERS + 1);
456
20.6k
  d[i++] = nullptr;
457
20.6k
}
458
459
460
const heif_encoder_parameter** aom_list_parameters(void* encoder)
461
0
{
462
0
  return aom_encoder_parameter_ptrs;
463
0
}
464
465
static void aom_init_plugin()
466
20.6k
{
467
20.6k
  aom_init_parameters();
468
20.6k
}
469
470
471
static void aom_cleanup_plugin()
472
20.6k
{
473
20.6k
}
474
475
heif_error aom_new_encoder(void** enc)
476
0
{
477
0
  encoder_struct_aom* encoder = new encoder_struct_aom();
478
0
  heif_error err = heif_error_ok;
479
480
0
  *enc = encoder;
481
482
  // set default parameters
483
484
0
  aom_set_default_parameters(encoder);
485
486
0
  return err;
487
0
}
488
489
void aom_free_encoder(void* encoder_raw)
490
0
{
491
0
  struct encoder_struct_aom* encoder = (encoder_struct_aom*) encoder_raw;
492
493
0
  delete encoder;
494
0
}
495
496
497
heif_error aom_set_parameter_quality(void* encoder_raw, int quality)
498
0
{
499
0
  encoder_struct_aom* encoder = (encoder_struct_aom*) encoder_raw;
500
501
0
  if (quality < 0 || quality > 100) {
502
0
    return heif_error_invalid_parameter_value;
503
0
  }
504
505
0
  encoder->quality = quality;
506
507
0
  return heif_error_ok;
508
0
}
509
510
heif_error aom_get_parameter_quality(void* encoder_raw, int* quality)
511
0
{
512
0
  encoder_struct_aom* encoder = (encoder_struct_aom*) encoder_raw;
513
514
0
  *quality = encoder->quality;
515
516
0
  return heif_error_ok;
517
0
}
518
519
heif_error aom_set_parameter_lossless(void* encoder_raw, int enable)
520
0
{
521
0
  encoder_struct_aom* encoder = (encoder_struct_aom*) encoder_raw;
522
523
0
  if (enable) {
524
0
    encoder->min_q = 0;
525
0
    encoder->max_q = 0;
526
0
    encoder->alpha_min_q = 0;
527
0
    encoder->alpha_min_q_set = true;
528
0
    encoder->alpha_max_q = 0;
529
0
    encoder->alpha_max_q_set = true;
530
0
  }
531
532
0
  encoder->lossless = enable;
533
534
0
  return heif_error_ok;
535
0
}
536
537
heif_error aom_get_parameter_lossless(void* encoder_raw, int* enable)
538
0
{
539
0
  encoder_struct_aom* encoder = (encoder_struct_aom*) encoder_raw;
540
541
0
  *enable = encoder->lossless;
542
543
0
  return heif_error_ok;
544
0
}
545
546
struct heif_error aom_set_parameter_logging_level(void* encoder_raw, int logging)
547
0
{
548
#if 0
549
  struct encoder_struct_x265* encoder = (struct encoder_struct_x265*)encoder_raw;
550
551
  if (logging<0 || logging>4) {
552
    return heif_error_invalid_parameter_value;
553
  }
554
555
  encoder->logLevel = logging;
556
#endif
557
558
0
  return heif_error_ok;
559
0
}
560
561
struct heif_error aom_get_parameter_logging_level(void* encoder_raw, int* loglevel)
562
0
{
563
#if 0
564
  struct encoder_struct_x265* encoder = (struct encoder_struct_x265*)encoder_raw;
565
566
  *loglevel = encoder->logLevel;
567
#else
568
0
  *loglevel = 0;
569
0
#endif
570
571
0
  return heif_error_ok;
572
0
}
573
574
0
#define set_value(paramname, paramvar) if (strcmp(name, paramname)==0) { encoder->paramvar = value; return heif_error_ok; }
575
0
#define get_value(paramname, paramvar) if (strcmp(name, paramname)==0) { *value = encoder->paramvar; return heif_error_ok; }
576
577
578
heif_error aom_set_parameter_integer(void* encoder_raw, const char* name, int value)
579
0
{
580
0
  encoder_struct_aom* encoder = (encoder_struct_aom*) encoder_raw;
581
582
0
  if (strcmp(name, heif_encoder_parameter_name_quality) == 0) {
583
0
    return aom_set_parameter_quality(encoder, value);
584
0
  }
585
0
  else if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) {
586
0
    return aom_set_parameter_lossless(encoder, value);
587
0
  }
588
0
  else if (strcmp(name, kParam_alpha_quality) == 0) {
589
0
      if (value < 0 || value > 100) {
590
0
          return heif_error_invalid_parameter_value;
591
0
      }
592
593
0
      encoder->alpha_quality = value;
594
0
      encoder->alpha_quality_set = true;
595
0
      return heif_error_ok;
596
0
  }
597
0
  else if (strcmp(name, kParam_alpha_min_q) == 0) {
598
0
      encoder->alpha_min_q = value;
599
0
      encoder->alpha_min_q_set = true;
600
0
      return heif_error_ok;
601
0
  }
602
0
  else if (strcmp(name, kParam_alpha_max_q) == 0) {
603
0
      encoder->alpha_max_q = value;
604
0
      encoder->alpha_max_q_set = true;
605
0
      return heif_error_ok;
606
0
  }
607
608
0
  set_value(kParam_min_q, min_q);
609
0
  set_value(kParam_max_q, max_q);
610
0
  set_value(kParam_threads, threads);
611
0
  set_value(kParam_speed, cpu_used);
612
613
0
  return heif_error_unsupported_parameter;
614
0
}
615
616
heif_error aom_get_parameter_integer(void* encoder_raw, const char* name, int* value)
617
0
{
618
0
  encoder_struct_aom* encoder = (encoder_struct_aom*) encoder_raw;
619
620
0
  if (strcmp(name, heif_encoder_parameter_name_quality) == 0) {
621
0
    return aom_get_parameter_quality(encoder, value);
622
0
  }
623
0
  else if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) {
624
0
    return aom_get_parameter_lossless(encoder, value);
625
0
  }
626
0
  else if (strcmp(name, kParam_alpha_quality) == 0) {
627
0
      *value = encoder->alpha_quality_set ? encoder->alpha_quality : encoder->quality;
628
0
      return heif_error_ok;
629
0
  }
630
0
  else if (strcmp(name, kParam_alpha_max_q) == 0) {
631
0
      *value = encoder->alpha_max_q_set ? encoder->alpha_max_q : encoder->max_q;
632
0
      return heif_error_ok;
633
0
  }
634
0
  else if (strcmp(name, kParam_alpha_min_q) == 0) {
635
0
      *value = encoder->alpha_min_q_set ? encoder->alpha_min_q : encoder->min_q;
636
0
      return heif_error_ok;
637
0
  }
638
639
0
  get_value(kParam_min_q, min_q);
640
0
  get_value(kParam_max_q, max_q);
641
0
  get_value(kParam_threads, threads);
642
0
  get_value(kParam_speed, cpu_used);
643
644
0
  return heif_error_unsupported_parameter;
645
0
}
646
647
648
heif_error aom_set_parameter_boolean(void* encoder_raw, const char* name, int value)
649
0
{
650
0
  encoder_struct_aom* encoder = (encoder_struct_aom*) encoder_raw;
651
652
0
  if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) {
653
0
    return aom_set_parameter_lossless(encoder, value);
654
0
  }
655
0
  else if (strcmp(name, kParam_lossless_alpha) == 0) {
656
0
      encoder->lossless_alpha = value;
657
0
      if (value) {
658
0
          encoder->alpha_max_q = 0;
659
0
          encoder->alpha_max_q_set = true;
660
0
          encoder->alpha_min_q = 0;
661
0
          encoder->alpha_min_q_set = true;
662
0
      }
663
0
      return heif_error_ok;
664
0
  } else if (strcmp(name, kParam_auto_tiles) == 0) {
665
0
      encoder->auto_tiles = value;
666
0
      return heif_error_ok;
667
0
  } else if (strcmp(name, kParam_enable_intra_block_copy) == 0) {
668
0
      encoder->enable_intra_block_copy = value;
669
0
      return heif_error_ok;
670
0
  }
671
672
0
  set_value(kParam_realtime, realtime_mode);
673
674
0
  return heif_error_unsupported_parameter;
675
0
}
676
677
heif_error aom_get_parameter_boolean(void* encoder_raw, const char* name, int* value)
678
0
{
679
0
  encoder_struct_aom* encoder = (encoder_struct_aom*) encoder_raw;
680
681
0
  if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) {
682
0
    return aom_get_parameter_lossless(encoder, value);
683
0
  }
684
685
0
  get_value(kParam_realtime, realtime_mode);
686
0
  get_value(kParam_lossless_alpha, lossless_alpha);
687
0
  get_value(kParam_auto_tiles, auto_tiles);
688
0
  get_value(kParam_enable_intra_block_copy, enable_intra_block_copy);
689
690
0
  return heif_error_unsupported_parameter;
691
0
}
692
693
694
heif_error aom_set_parameter_string(void* encoder_raw, const char* name, const char* value)
695
0
{
696
0
  encoder_struct_aom* encoder = (encoder_struct_aom*) encoder_raw;
697
698
0
  if (strcmp(name, kParam_chroma) == 0) {
699
0
    if (strcmp(value, "420") == 0) {
700
0
      encoder->chroma = heif_chroma_420;
701
0
      return heif_error_ok;
702
0
    }
703
0
    else if (strcmp(value, "422") == 0) {
704
0
      encoder->chroma = heif_chroma_422;
705
0
      return heif_error_ok;
706
0
    }
707
0
    else if (strcmp(value, "444") == 0) {
708
0
      encoder->chroma = heif_chroma_444;
709
0
      return heif_error_ok;
710
0
    }
711
0
    else {
712
0
      return heif_error_invalid_parameter_value;
713
0
    }
714
0
  }
715
716
0
  if (strcmp(name, kParam_tune) == 0) {
717
0
    if (strcmp(value, "auto") == 0) {
718
0
      encoder->tune_auto = true;
719
0
      return heif_error_ok;
720
0
    }
721
0
    else if (strcmp(value, "psnr") == 0) {
722
0
      encoder->tune = AOM_TUNE_PSNR;
723
0
      encoder->tune_auto = false;
724
0
      return heif_error_ok;
725
0
    }
726
0
    else if (strcmp(value, "ssim") == 0) {
727
0
      encoder->tune = AOM_TUNE_SSIM;
728
0
      encoder->tune_auto = false;
729
0
      return heif_error_ok;
730
0
    }
731
0
#if defined(AOM_HAVE_TUNE_IQ)
732
0
    else if (strcmp(value, "iq") == 0) {
733
0
      encoder->tune = AOM_TUNE_IQ;
734
0
      encoder->tune_auto = false;
735
0
      return heif_error_ok;
736
0
    }
737
0
#endif
738
0
    else {
739
0
      return heif_error_invalid_parameter_value;
740
0
    }
741
0
  }
742
743
0
#if defined(HAVE_AOM_CODEC_SET_OPTION)
744
0
  if (strncmp(name, "aom:", 4) == 0) {
745
0
    encoder->add_custom_option(std::string(name).substr(4), std::string(value));
746
0
    return heif_error_ok;
747
0
  }
748
0
#endif
749
750
0
  return heif_error_unsupported_parameter;
751
0
}
752
753
754
static void save_strcpy(char* dst, int dst_size, const char* src)
755
0
{
756
0
  strncpy(dst, src, dst_size - 1);
757
0
  dst[dst_size - 1] = 0;
758
0
}
759
760
761
heif_error aom_get_parameter_string(void* encoder_raw, const char* name,
762
                                    char* value, int value_size)
763
0
{
764
0
  encoder_struct_aom* encoder = (encoder_struct_aom*) encoder_raw;
765
766
0
  if (strcmp(name, kParam_chroma) == 0) {
767
0
    switch (encoder->chroma) {
768
0
      case heif_chroma_420:
769
0
        save_strcpy(value, value_size, "420");
770
0
        break;
771
0
      case heif_chroma_422:
772
0
        save_strcpy(value, value_size, "422");
773
0
        break;
774
0
      case heif_chroma_444:
775
0
        save_strcpy(value, value_size, "444");
776
0
        break;
777
0
      default:
778
0
        assert(false);
779
0
        return heif_error_invalid_parameter_value;
780
0
    }
781
0
    return heif_error_ok;
782
0
  }
783
0
  else if (strcmp(name, kParam_tune) == 0) {
784
0
    if (encoder->tune_auto) {
785
0
      save_strcpy(value, value_size, "auto");
786
0
      return heif_error_ok;
787
0
    }
788
0
    switch (encoder->tune) {
789
0
      case AOM_TUNE_PSNR:
790
0
        save_strcpy(value, value_size, "psnr");
791
0
        break;
792
0
      case AOM_TUNE_SSIM:
793
0
        save_strcpy(value, value_size, "ssim");
794
0
        break;
795
0
#if defined(AOM_HAVE_TUNE_IQ)
796
0
      case AOM_TUNE_IQ:
797
0
        save_strcpy(value, value_size, "iq");
798
0
        break;
799
0
#endif
800
0
      default:
801
0
        assert(false);
802
0
        return heif_error_invalid_parameter_value;
803
0
    }
804
0
    return heif_error_ok;
805
0
  }
806
807
0
  return heif_error_unsupported_parameter;
808
0
}
809
810
811
static void aom_set_default_parameters(void* encoder)
812
0
{
813
0
  for (const heif_encoder_parameter** p = aom_encoder_parameter_ptrs; *p; p++) {
814
0
    const heif_encoder_parameter* param = *p;
815
816
0
    if (param->has_default) {
817
0
      switch (param->type) {
818
0
        case heif_encoder_parameter_type_integer:
819
0
          aom_set_parameter_integer(encoder, param->name, param->integer.default_value);
820
0
          break;
821
0
        case heif_encoder_parameter_type_boolean:
822
0
          aom_set_parameter_boolean(encoder, param->name, param->boolean.default_value);
823
0
          break;
824
0
        case heif_encoder_parameter_type_string:
825
0
          aom_set_parameter_string(encoder, param->name, param->string.default_value);
826
0
          break;
827
0
      }
828
0
    }
829
0
  }
830
0
}
831
832
833
void aom_query_input_colorspace(heif_colorspace* colorspace, heif_chroma* chroma)
834
0
{
835
0
  *colorspace = heif_colorspace_YCbCr;
836
0
  *chroma = heif_chroma_420;
837
0
}
838
839
840
void aom_query_input_colorspace2(void* encoder_raw, heif_colorspace* colorspace, heif_chroma* chroma)
841
0
{
842
0
  encoder_struct_aom* encoder = (encoder_struct_aom*) encoder_raw;
843
844
0
  if (*colorspace == heif_colorspace_monochrome) {
845
    // keep the monochrome colorspace
846
0
  }
847
0
  else {
848
0
    *colorspace = heif_colorspace_YCbCr;
849
0
    *chroma = encoder->chroma;
850
0
  }
851
0
}
852
853
// returns 'true' when an error was detected
854
// Note: some older AOM versions take a non-const pointer to aom_codec_error(). Thus, we also have to use a non-const pointer here.
855
static bool check_aom_error(aom_codec_err_t aom_error, /*const*/ aom_codec_ctx_t* codec, encoder_struct_aom* encoder, struct heif_error* heif_error)
856
0
{
857
0
  if (aom_error == AOM_CODEC_OK) {
858
0
    return false;
859
0
  }
860
861
0
  std::stringstream sstr;
862
0
  sstr << "AOM encoder error: " << aom_codec_error(codec) << " - " << aom_codec_error_detail(codec);
863
864
0
  heif_error->code = heif_error_Encoder_plugin_error;
865
0
  heif_error->subcode = heif_suberror_Unsupported_parameter;
866
0
  heif_error->message = encoder->set_aom_error(sstr.str().c_str());
867
868
0
  return true;
869
0
}
870
871
0
#define CHECK_ERROR \
872
0
if (check_aom_error(aom_error, &codec, encoder, &err)) { \
873
0
  return err; \
874
0
}
875
876
struct chroma_info
877
{
878
  aom_img_fmt_t img_format = AOM_IMG_FMT_NONE;
879
  int chroma_height = 0;
880
  int chroma_sample_position = AOM_CSP_UNKNOWN;
881
};
882
883
884
chroma_info get_chroma_info(heif_chroma chroma,
885
                            int bpp_y, int source_height)
886
0
{
887
0
  chroma_info info;
888
889
0
  switch (chroma) {
890
0
    case heif_chroma_420:
891
0
    case heif_chroma_monochrome:
892
0
      info.img_format = AOM_IMG_FMT_I420;
893
0
      info.chroma_height = (source_height+1)/2;
894
0
      info.chroma_sample_position = AOM_CSP_UNKNOWN; // TODO: change this to CSP_CENTER in the future (https://github.com/AOMediaCodec/av1-avif/issues/88)
895
0
      break;
896
0
    case heif_chroma_422:
897
0
      info.img_format = AOM_IMG_FMT_I422;
898
0
      info.chroma_height = source_height; // 4:2:2 is subsampled horizontally only
899
0
      info.chroma_sample_position = AOM_CSP_COLOCATED;
900
0
      break;
901
0
    case heif_chroma_444:
902
0
      info.img_format = AOM_IMG_FMT_I444;
903
0
      info.chroma_height = source_height;
904
0
      info.chroma_sample_position = AOM_CSP_COLOCATED;
905
0
      break;
906
0
    default:
907
0
      info.img_format = AOM_IMG_FMT_NONE;
908
0
      info.chroma_sample_position = AOM_CSP_UNKNOWN;
909
0
      assert(false);
910
0
      break;
911
0
  }
912
913
0
  if (bpp_y > 8) {
914
    // aom_img_fmt_t is a set of flags, so the combined value is intentionally not an enumerator.
915
0
    info.img_format = (aom_img_fmt_t) (info.img_format | AOM_IMG_FMT_HIGHBITDEPTH); // NOLINT(clang-analyzer-optin.core.EnumCastOutOfRange)
916
0
  }
917
918
0
  return info;
919
0
}
920
921
922
923
static heif_error aom_start_sequence_encoding_intern(void* encoder_raw, const heif_image* image,
924
                                                     enum heif_image_input_class input_class,
925
                                                     uint32_t framerate_num, uint32_t framerate_denom,
926
                                                     const heif_sequence_encoding_options* options,
927
                                                     bool image_sequence)
928
0
{
929
0
  encoder_struct_aom* encoder = (encoder_struct_aom*) encoder_raw;
930
931
  // destroy the codec in case it was already initialized
932
  // (e.g. when the encoder is reused for alpha encoding after being used for YUV encoding)
933
0
  aom_codec_destroy(&encoder->codec);
934
935
0
  heif_error err;
936
937
0
  const int source_width = heif_image_get_width(image, heif_channel_Y);
938
0
  const int source_height = heif_image_get_height(image, heif_channel_Y);
939
940
0
  const heif_chroma chroma = heif_image_get_chroma_format(image);
941
942
0
  int bpp_y = heif_image_get_bits_per_pixel_range(image, heif_channel_Y);
943
944
945
  // --- check for AOM 3.6.0 bug
946
947
0
  bool is_aom_3_6_0 = (aom_codec_version() == 0x030600);
948
949
0
  if (is_aom_3_6_0) {
950
    // This bound might be too tight, as I still could encode images with 8193 x 4353 correctly. Even 8200x4400, but 8200x4800 fails.
951
    // Let's still keep it as most images will be smaller anyway.
952
0
    if (!(source_width <= 8192 * 2 && source_height <= 4352 * 2 && source_width * source_height <= 8192 * 4352)) {
953
0
      err = {heif_error_Encoding_error,
954
0
             heif_suberror_Encoder_encoding,
955
0
             "AOM v3.6.0 has a bug when encoding large images. Please upgrade to at least AOM v3.6.1."};
956
0
      return err;
957
0
    }
958
0
  }
959
960
961
  // --- copy libheif image to aom image
962
963
0
  chroma_info chroma_info = get_chroma_info(chroma, bpp_y, source_height);
964
965
966
  // --- configure codec
967
968
0
  aom_codec_iface_t* iface;
969
0
  aom_codec_ctx_t& codec = encoder->codec;
970
971
0
  iface = aom_codec_av1_cx();
972
  //encoder->encoder = get_aom_encoder_by_name("av1");
973
0
  if (!iface) {
974
0
    return {
975
0
      heif_error_Unsupported_feature,
976
0
      heif_suberror_Unsupported_codec,
977
0
      "Unsupported codec: AOMedia Project AV1 Encoder"
978
0
    };
979
0
  }
980
981
982
0
#if defined(AOM_USAGE_ALL_INTRA)
983
  // aom 3.1.0
984
0
  unsigned int aomUsage = AOM_USAGE_ALL_INTRA;
985
#else
986
  // aom 2.0
987
  unsigned int aomUsage = AOM_USAGE_GOOD_QUALITY;
988
#endif
989
990
0
  if (image_sequence &&
991
0
      options->gop_structure != heif_sequence_gop_structure_intra_only &&
992
0
      options->keyframe_distance_max != 1) {
993
0
    aomUsage = AOM_USAGE_GOOD_QUALITY;
994
0
  }
995
996
0
  if (encoder->realtime_mode) {
997
0
    aomUsage = AOM_USAGE_REALTIME;
998
0
  }
999
1000
0
  aom_codec_enc_cfg_t cfg;
1001
0
  aom_codec_err_t res = aom_codec_enc_config_default(iface, &cfg, aomUsage);
1002
0
  if (res) {
1003
0
    err = {heif_error_Encoder_plugin_error,
1004
0
           heif_suberror_Encoder_initialization,
1005
0
           kError_codec_enc_config_default};
1006
0
    return err;
1007
0
  }
1008
1009
0
  int seq_profile = compute_avif_profile(heif_image_get_bits_per_pixel_range(image, heif_channel_Y),
1010
0
                                         heif_image_get_chroma_format(image));
1011
1012
0
  cfg.g_w = source_width;
1013
0
  cfg.g_h = source_height;
1014
  // Set the max number of frames to encode to 1. This makes the libaom encoder
1015
  // set still_picture and reduced_still_picture_header to 1 in the AV1 sequence
1016
  // header OBU.
1017
0
  if (!image_sequence) {
1018
0
    cfg.g_limit = 1;
1019
0
  }
1020
1021
  // Use the default settings of the new AOM_USAGE_ALL_INTRA (added in
1022
  // https://crbug.com/aomedia/2959).
1023
  //
1024
  // Set g_lag_in_frames to 0 to reduce the number of frame buffers (from 20
1025
  // to 2) in libaom's lookahead structure. This reduces memory consumption when
1026
  // encoding a single image.
1027
0
  cfg.g_lag_in_frames = 0;
1028
  // Disable automatic placement of key frames by the encoder.
1029
0
  cfg.kf_mode = AOM_KF_DISABLED;
1030
1031
0
  if (!image_sequence) {
1032
    // Tell libaom that all frames will be key frames.
1033
0
    cfg.kf_max_dist = 0;
1034
0
  }
1035
0
  else if (options->gop_structure == heif_sequence_gop_structure_intra_only) {
1036
0
    cfg.kf_max_dist = 0;
1037
0
  }
1038
0
  else {
1039
0
    if (options->keyframe_distance_min) {
1040
0
      cfg.kf_min_dist = options->keyframe_distance_min;
1041
0
    }
1042
1043
0
    if (options->keyframe_distance_max) {
1044
0
      cfg.kf_max_dist = options->keyframe_distance_max;
1045
0
    }
1046
0
  }
1047
1048
0
  cfg.g_profile = seq_profile;
1049
0
  cfg.g_bit_depth = (aom_bit_depth_t) bpp_y;
1050
0
  cfg.g_input_bit_depth = bpp_y;
1051
1052
0
  cfg.rc_end_usage = AOM_Q;
1053
1054
0
  int min_q = encoder->min_q;
1055
0
  int max_q = encoder->max_q;
1056
1057
0
  if (input_class == heif_image_input_class_alpha && encoder->alpha_min_q_set && encoder->alpha_max_q_set) {
1058
0
    min_q = encoder->alpha_min_q;
1059
0
    max_q = encoder->alpha_max_q;
1060
0
  }
1061
1062
0
  int quality = encoder->quality;
1063
1064
0
  if (input_class == heif_image_input_class_alpha && encoder->alpha_quality_set) {
1065
0
    quality = encoder->alpha_quality;
1066
0
  }
1067
1068
  // Fetch NCLX and determine the effective tune metric early, since the
1069
  // quality-to-quantizer mapping for AOM_TUNE_IQ uses a different (non-linear) table.
1070
1071
0
  heif_color_profile_nclx* nclx = nullptr;
1072
0
  err = heif_image_get_nclx_color_profile(image, &nclx);
1073
0
  if (err.code != heif_error_Ok) {
1074
0
    assert(nclx == nullptr);
1075
0
  }
1076
1077
  // make sure NCLX profile is deleted at end of function
1078
0
  auto nclx_deleter = std::unique_ptr<heif_color_profile_nclx, void (*)(heif_color_profile_nclx*)>(nclx, heif_nclx_color_profile_free);
1079
1080
  // A slide-show-style image sequence is treated like a still image (favor
1081
  // perceptual quality / AOM_TUNE_IQ when supported); only true video content
1082
  // keeps SSIM-tuned encoding.
1083
0
  bool tune_as_video = (image_sequence &&
1084
0
                        options &&
1085
0
                        options->version >= 3 &&
1086
0
                        options->content_kind == heif_sequence_content_kind_video);
1087
1088
0
  bool is_lossless = (encoder->lossless ||
1089
0
                      (input_class == heif_image_input_class_alpha && encoder->lossless_alpha));
1090
1091
0
  aom_tune_metric effective_tune = encoder->tune;
1092
0
  if (encoder->tune_auto) {
1093
0
    if (tune_as_video) {
1094
0
      effective_tune = AOM_TUNE_SSIM;
1095
0
    }
1096
0
    else if (input_class == heif_image_input_class_alpha) {
1097
      // AOM_TUNE_SSIM causes ringing on alpha; PSNR avoids that.
1098
0
      effective_tune = AOM_TUNE_PSNR;
1099
0
    }
1100
0
    else {
1101
0
      effective_tune = AOM_TUNE_SSIM;
1102
1103
0
#if defined(AOM_HAVE_TUNE_IQ)
1104
      // AOM_TUNE_IQ is tuned for the YCbCr family of color spaces (and other YUV-like
1105
      // spaces such as YCgCo, ICtCp, including monochrome). It does NOT generalize to
1106
      // GBR samples (matrix_coefficients = IDENTITY), so we keep SSIM for that case.
1107
      // AOM_TUNE_IQ stabilized in libaom v3.13.0 (all-intra only); v3.14.0 added
1108
      // support for the good-quality and realtime inter-frame modes.
1109
1110
0
      static const int aom_version_3_13_0 = (3 << 16) | (13 << 8);
1111
0
      static const int aom_version_3_14_0 = (3 << 16) | (14 << 8);
1112
1113
0
      bool is_identity_matrix = nclx && (nclx->matrix_coefficients == heif_matrix_coefficients_RGB_GBR);
1114
0
      int aom_version = aom_codec_version();
1115
0
      bool iq_supports_inter = (aom_version >= aom_version_3_14_0);
1116
1117
      // libaom turns on chroma delta-q for AOM_TUNE_IQ and then refuses to combine
1118
      // that with lossless coding, so keep AOM_TUNE_SSIM for lossless images.
1119
      // See https://aomedia.g-issues.chromium.org/issues/383595066
1120
0
      if (!is_identity_matrix && !is_lossless &&
1121
0
          (cfg.g_usage == AOM_USAGE_ALL_INTRA || iq_supports_inter) &&
1122
0
          aom_version >= aom_version_3_13_0) {
1123
0
        effective_tune = AOM_TUNE_IQ;
1124
0
      }
1125
0
#endif
1126
0
    }
1127
0
  }
1128
1129
0
#if defined(AOM_HAVE_TUNE_IQ)
1130
0
  if (is_lossless && effective_tune == AOM_TUNE_IQ) {
1131
0
    return heif_error_lossless_with_tune_iq;
1132
0
  }
1133
0
#endif
1134
1135
0
  int cq_level;
1136
0
#if defined(AOM_HAVE_TUNE_IQ)
1137
0
  if (effective_tune == AOM_TUNE_IQ) {
1138
0
    cq_level = tuneIqQualityToQuantizer[quality];
1139
0
  }
1140
0
  else
1141
0
#endif
1142
0
  {
1143
0
    cq_level = ((100 - quality) * 63 + 50) / 100;
1144
0
  }
1145
1146
  // Work around the bug in libaom v2.0.2 or older fixed by
1147
  // https://aomedia-review.googlesource.com/c/aom/+/113064. If using a libaom
1148
  // release with the bug, set cfg.rc_min_quantizer to cq_level to prevent
1149
  // libaom from incorrectly using a quantizer index lower than cq_level.
1150
0
  bool aom_2_0_2_or_older = aom_codec_version() <= 0x020002;
1151
1152
0
  cfg.rc_min_quantizer = aom_2_0_2_or_older ? cq_level : min_q;
1153
0
  cfg.rc_max_quantizer = max_q;
1154
0
  cfg.g_error_resilient = 0;
1155
0
  cfg.g_threads = encoder->threads;
1156
1157
0
  if (chroma == heif_chroma_monochrome) {
1158
0
    cfg.monochrome = 1;
1159
0
  }
1160
1161
0
  cfg.g_timebase.num = static_cast<int>(framerate_num);
1162
0
  cfg.g_timebase.den = static_cast<int>(framerate_denom);
1163
1164
  // --- initialize codec
1165
1166
0
  aom_codec_flags_t encoder_flags = 0;
1167
0
  if (bpp_y > 8) {
1168
0
    encoder_flags = (aom_codec_flags_t) (encoder_flags | AOM_CODEC_USE_HIGHBITDEPTH);
1169
0
  }
1170
1171
  // allocate aom_codec_ctx_t
1172
0
  if (aom_codec_enc_init(&codec, iface, &cfg, encoder_flags)) {
1173
    // AOM makes sure that the error text returned by aom_codec_error_detail() is always a static
1174
    // text that is valid even though the codec allocation failed (#788).
1175
0
    err = {heif_error_Encoder_plugin_error,
1176
0
           heif_suberror_Encoder_initialization,
1177
0
           encoder->set_aom_error(aom_codec_error_detail(&codec))};
1178
0
    return err;
1179
0
  }
1180
1181
0
  encoder->bit_depth = bpp_y;
1182
1183
0
  aom_codec_err_t aom_error;
1184
1185
0
  aom_error = aom_codec_control(&codec, AOME_SET_CPUUSED, encoder->cpu_used); CHECK_ERROR;
1186
1187
0
  aom_error = aom_codec_control(&codec, AOME_SET_CQ_LEVEL, cq_level); CHECK_ERROR;
1188
1189
0
  if (encoder->threads > 1) {
1190
0
#if defined(AOM_CTRL_AV1E_SET_ROW_MT)
1191
    // aom 2.0
1192
0
    aom_error = aom_codec_control(&codec, AV1E_SET_ROW_MT, 1); CHECK_ERROR;
1193
0
#endif
1194
0
  }
1195
1196
0
#if defined(AOM_CTRL_AV1E_SET_AUTO_TILES)
1197
  // aom 3.10.0
1198
0
  aom_error = aom_codec_control(&codec, AV1E_SET_AUTO_TILES, encoder->auto_tiles); CHECK_ERROR;
1199
0
#endif
1200
1201
  // TODO: set AV1E_SET_TILE_ROWS and AV1E_SET_TILE_COLUMNS.
1202
1203
1204
  // In aom, color_range defaults to limited range (0). Set it to full range (1).
1205
0
  aom_error = aom_codec_control(&codec, AV1E_SET_COLOR_RANGE, nclx ? nclx->full_range_flag : 1); CHECK_ERROR;
1206
0
  aom_error = aom_codec_control(&codec, AV1E_SET_CHROMA_SAMPLE_POSITION, chroma_info.chroma_sample_position); CHECK_ERROR;
1207
1208
0
  if (nclx &&
1209
0
      (input_class == heif_image_input_class_normal ||
1210
0
       input_class == heif_image_input_class_thumbnail)) {
1211
0
    aom_error = aom_codec_control(&codec, AV1E_SET_COLOR_PRIMARIES, nclx->color_primaries); CHECK_ERROR
1212
0
    aom_error = aom_codec_control(&codec, AV1E_SET_MATRIX_COEFFICIENTS, nclx->matrix_coefficients); CHECK_ERROR;
1213
0
    aom_error = aom_codec_control(&codec, AV1E_SET_TRANSFER_CHARACTERISTICS, nclx->transfer_characteristics); CHECK_ERROR;
1214
0
       }
1215
1216
0
  aom_error = aom_codec_control(&codec, AOME_SET_TUNING, effective_tune); CHECK_ERROR;
1217
1218
0
  if (is_lossless) {
1219
0
    aom_error = aom_codec_control(&codec, AV1E_SET_LOSSLESS, 1); CHECK_ERROR;
1220
0
  }
1221
1222
0
#if defined(AOM_CTRL_AV1E_SET_SKIP_POSTPROC_FILTERING)
1223
0
  if (cfg.g_usage == AOM_USAGE_ALL_INTRA) {
1224
    // Enable AV1E_SET_SKIP_POSTPROC_FILTERING for still-picture encoding,
1225
    // which is disabled by default.
1226
0
    aom_error = aom_codec_control(&codec, AV1E_SET_SKIP_POSTPROC_FILTERING, 1); CHECK_ERROR;
1227
0
  }
1228
0
#endif
1229
1230
0
  aom_error = aom_codec_control(&codec, AV1E_SET_ENABLE_INTRABC, encoder->enable_intra_block_copy); CHECK_ERROR;
1231
1232
0
#if defined(HAVE_AOM_CODEC_SET_OPTION)
1233
  // Apply the custom AOM encoder options.
1234
  // These should always be applied last as they can override the values that were set above.
1235
0
  for (const auto& p : encoder->custom_options) {
1236
0
    if (aom_codec_set_option(&codec, p.name.c_str(), p.value.c_str()) != AOM_CODEC_OK) {
1237
0
      std::stringstream sstr;
1238
0
      sstr << "Cannot set AOM encoder option (name: " << p.name << ", value: " << p.value << "): "
1239
0
           << aom_codec_error(&codec) << " - " << aom_codec_error_detail(&codec);
1240
1241
0
      err = {
1242
0
        heif_error_Encoder_plugin_error,
1243
0
        heif_suberror_Unsupported_parameter,
1244
0
        encoder->set_aom_error(sstr.str().c_str())
1245
0
      };
1246
0
      return err;
1247
0
    }
1248
0
  }
1249
0
#endif
1250
1251
0
  return {};
1252
0
}
1253
1254
1255
1256
static heif_error aom_start_sequence_encoding(void* encoder_raw, const heif_image* image,
1257
                                       enum heif_image_input_class input_class,
1258
                                       uint32_t framerate_num, uint32_t framerate_denom,
1259
                                       const heif_sequence_encoding_options* options)
1260
0
{
1261
0
  return aom_start_sequence_encoding_intern(encoder_raw, image, input_class, framerate_num, framerate_denom, options,
1262
0
    true);
1263
0
}
1264
1265
1266
static heif_error aom_encode_sequence_frame(void* encoder_raw, const heif_image* image,
1267
                                            uintptr_t frame_nr)
1268
0
{
1269
  // AV1 signals one bit depth for all planes, so an image whose color
1270
  // channels disagree cannot be encoded.
1271
0
  heif_error input_error = check_encoder_input_image(image, /*supports_monochrome=*/true,
1272
0
                                                    {8, 10, 12});
1273
0
  if (input_error.code != heif_error_Ok) {
1274
0
    return input_error;
1275
0
  }
1276
1277
0
  encoder_struct_aom* encoder = (encoder_struct_aom*) encoder_raw;
1278
0
  aom_codec_ctx_t& codec = encoder->codec;
1279
1280
  // AOM_CODEC_USE_HIGHBITDEPTH was decided when the codec was initialized from the
1281
  // first frame of the sequence. libaom refuses a frame that disagrees with it, but
1282
  // with an error that says nothing about the cause.
1283
0
  input_error = check_sequence_frame_bit_depth(image, encoder->bit_depth);
1284
0
  if (input_error.code != heif_error_Ok) {
1285
0
    return input_error;
1286
0
  }
1287
1288
0
  heif_error err;
1289
1290
0
  const int source_width = heif_image_get_width(image, heif_channel_Y);
1291
0
  const int source_height = heif_image_get_height(image, heif_channel_Y);
1292
1293
0
  const heif_chroma chroma = heif_image_get_chroma_format(image);
1294
1295
0
  int bpp_y = heif_image_get_bits_per_pixel_range(image, heif_channel_Y);
1296
1297
0
  chroma_info chroma_info = get_chroma_info(chroma, bpp_y, source_height);
1298
1299
0
  std::unique_ptr<aom_image_t, void (*)(aom_image_t*)> input_image(aom_img_alloc(nullptr,
1300
0
                                                                                 chroma_info.img_format,
1301
0
                                                                                 source_width,
1302
0
                                                                                 source_height,
1303
0
                                                                                 1),
1304
0
                                                                   aom_img_free);
1305
0
  if (!input_image) {
1306
0
    err = {heif_error_Memory_allocation_error,
1307
0
           heif_suberror_Unspecified,
1308
0
           "Failed to allocate image"};
1309
0
    return err;
1310
0
  }
1311
1312
1313
0
  for (int plane = 0; plane < 3; plane++) {
1314
0
    unsigned char* buf = input_image->planes[plane];
1315
0
    const int stride = input_image->stride[plane];
1316
1317
0
    if (chroma == heif_chroma_monochrome && plane != 0) {
1318
0
      if (bpp_y == 8) {
1319
0
        memset(buf, 128, chroma_info.chroma_height * stride);
1320
0
      }
1321
0
      else {
1322
0
        uint16_t* buf16 = (uint16_t*) buf;
1323
0
        uint16_t half_range = (uint16_t) (1 << (bpp_y - 1));
1324
0
        for (int i = 0; i < chroma_info.chroma_height * stride / 2; i++) {
1325
0
          buf16[i] = half_range;
1326
0
        }
1327
0
      }
1328
1329
0
      continue;
1330
0
    }
1331
1332
    /*
1333
    const int w = aom_img_plane_width(img, plane) *
1334
                  ((img->fmt & AOM_IMG_FMT_HIGHBITDEPTH) ? 2 : 1);
1335
    const int h = aom_img_plane_height(img, plane);
1336
    */
1337
1338
0
    size_t in_stride = 0;
1339
0
    const uint8_t* in_p = heif_image_get_plane_readonly2(image, (heif_channel) plane, &in_stride);
1340
1341
0
    int w = source_width;
1342
0
    int h = source_height;
1343
1344
0
    if (plane != 0) {
1345
0
      if (chroma != heif_chroma_444) { w = (w + 1) / 2; }
1346
0
      if (chroma == heif_chroma_420) { h = (h + 1) / 2; }
1347
1348
0
      assert(w == heif_image_get_width(image, (heif_channel) plane));
1349
0
      assert(h == heif_image_get_height(image, (heif_channel) plane));
1350
0
    }
1351
1352
0
    if (bpp_y > 8) {
1353
0
      w *= 2;
1354
0
    }
1355
1356
0
    for (int y = 0; y < h; y++) {
1357
0
      memcpy(buf, &in_p[y * in_stride], w);
1358
0
      buf += stride;
1359
0
    }
1360
0
  }
1361
1362
  //input_image->user_priv = (void*)frame_nr;
1363
1364
  // --- encode frame
1365
1366
0
  aom_codec_err_t res = aom_codec_encode(&codec, input_image.get(),
1367
0
                                         frame_nr, // PTS (only encoding a single frame) TODO
1368
0
                                         1,
1369
0
                                         0); // no flags
1370
1371
0
  if (res != AOM_CODEC_OK) {
1372
0
    err = {
1373
0
        heif_error_Encoder_plugin_error,
1374
0
        heif_suberror_Encoder_encoding,
1375
0
        encoder->set_aom_error(aom_codec_error_detail(&codec))
1376
0
    };
1377
0
    return err;
1378
0
  }
1379
1380
  // TODO: do we need this ? encoder->compressedData.clear();
1381
1382
0
  const aom_codec_cx_pkt_t* pkt = NULL;
1383
0
  aom_codec_iter_t iter = NULL; // for extracting the compressed packets
1384
1385
0
  while ((pkt = aom_codec_get_cx_data(&codec, &iter)) != NULL) {
1386
1387
0
    if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) {
1388
      //std::cerr.write((char*)pkt->data.frame.buf, pkt->data.frame.sz);
1389
1390
      //printf("packet of size: %d\n",(int)pkt->data.frame.sz);
1391
1392
1393
      // TODO: split the received data into separate OBUs
1394
      // This allows libheif to easily extract the sequence header for the av1C header
1395
1396
0
      size_t n = pkt->data.frame.sz;
1397
1398
0
      encoder_struct_aom::Packet output_packet;
1399
0
      output_packet.frameNr = pkt->data.frame.pts;
1400
0
      output_packet.is_keyframe = (pkt->data.frame.flags & AOM_FRAME_IS_INTRAONLY);
1401
1402
0
      encoder->output_packets.emplace_back(output_packet);
1403
0
      encoder->output_packets.back().compressedData.resize(n);
1404
1405
0
      memcpy(encoder->output_packets.back().compressedData.data(),
1406
0
             pkt->data.frame.buf,
1407
0
             n);
1408
0
    }
1409
0
  }
1410
1411
1412
0
  return heif_error_ok;
1413
0
}
1414
1415
1416
static heif_error aom_end_sequence_encoding(void *encoder_raw)
1417
0
{
1418
0
  encoder_struct_aom* encoder = (encoder_struct_aom*) encoder_raw;
1419
0
  aom_codec_ctx_t& codec = encoder->codec;
1420
1421
0
  heif_error err;
1422
1423
0
  int flags = 0;
1424
0
  aom_codec_err_t res = aom_codec_encode(&codec, NULL, -1, 0, flags);
1425
0
  if (res != AOM_CODEC_OK) {
1426
0
    err = {heif_error_Encoder_plugin_error,
1427
0
           heif_suberror_Encoder_encoding,
1428
0
           encoder->set_aom_error(aom_codec_error_detail(&codec))};
1429
0
    return err;
1430
0
  }
1431
1432
1433
0
  aom_codec_iter_t iter = NULL; // for extracting the compressed packets
1434
1435
0
  const aom_codec_cx_pkt_t* pkt = NULL;
1436
0
  while ((pkt = aom_codec_get_cx_data(&codec, &iter)) != NULL) {
1437
1438
0
    if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) {
1439
      //std::cerr.write((char*)pkt->data.frame.buf, pkt->data.frame.sz);
1440
1441
      //printf("packet of size: %d\n",(int)pkt->data.frame.sz);
1442
1443
1444
      // TODO: split the received data into separate OBUs
1445
      // This allows libheif to easily extract the sequence header for the av1C header
1446
1447
0
      size_t n = pkt->data.frame.sz;
1448
1449
0
      encoder_struct_aom::Packet output_packet;
1450
0
      output_packet.frameNr = pkt->data.frame.pts;
1451
1452
0
      encoder->output_packets.emplace_back(output_packet);
1453
0
      encoder->output_packets.back().compressedData.resize(n);
1454
1455
0
      memcpy(encoder->output_packets.back().compressedData.data(),
1456
0
             pkt->data.frame.buf,
1457
0
             n);
1458
0
    }
1459
0
  }
1460
1461
0
  return {};
1462
0
}
1463
1464
1465
static heif_error aom_encode_image(void* encoder_raw, const heif_image* image,
1466
                                   heif_image_input_class input_class)
1467
0
{
1468
0
  heif_error err;
1469
0
  err = aom_start_sequence_encoding_intern(encoder_raw, image, input_class, 1,25, nullptr, false);
1470
0
  if (err.code) {
1471
0
    return err;
1472
0
  }
1473
1474
0
  err = aom_encode_sequence_frame(encoder_raw, image, 0);
1475
0
  if (err.code) {
1476
0
    return err;
1477
0
  }
1478
1479
0
  return aom_end_sequence_encoding(encoder_raw);
1480
0
}
1481
1482
1483
heif_error aom_get_compressed_data2(void* encoder_raw, uint8_t** data, int* size,
1484
                                    uintptr_t* out_framenr, int* out_is_keyframe,
1485
                                    int* more_frame_packets)
1486
0
{
1487
0
  encoder_struct_aom* encoder = (encoder_struct_aom*) encoder_raw;
1488
1489
0
  encoder->active_output_data.clear();
1490
1491
0
  if (encoder->output_packets.empty()) {
1492
0
    *size = 0;
1493
0
    *data = nullptr;
1494
0
  }
1495
0
  else {
1496
0
    encoder->active_output_data = std::move(encoder->output_packets.front().compressedData);
1497
0
    if (out_framenr) {
1498
0
      *out_framenr = encoder->output_packets.front().frameNr;
1499
0
    }
1500
1501
0
    if (out_is_keyframe) {
1502
0
      *out_is_keyframe = encoder->output_packets.front().is_keyframe;
1503
0
    }
1504
1505
0
    encoder->output_packets.pop_front();
1506
1507
0
    *size = (int) encoder->active_output_data.size();
1508
0
    *data = encoder->active_output_data.data();
1509
0
  }
1510
1511
0
  return heif_error_ok;
1512
0
}
1513
1514
1515
static heif_error aom_get_compressed_data(void* encoder_raw, uint8_t** data, int* size,
1516
                                          heif_encoded_data_type* type)
1517
0
{
1518
0
  return aom_get_compressed_data2(encoder_raw, data, size, nullptr, nullptr, nullptr);
1519
0
}
1520
1521
1522
static const heif_encoder_plugin encoder_plugin_aom
1523
    {
1524
        /* plugin_api_version */ 4,
1525
        /* compression_format */ heif_compression_AV1,
1526
        /* id_name */ "aom",
1527
        /* priority */ AOM_PLUGIN_PRIORITY,
1528
        /* supports_lossy_compression */ true,
1529
        /* supports_lossless_compression */ true,
1530
        /* get_plugin_name */ aom_plugin_name,
1531
        /* init_plugin */ aom_init_plugin,
1532
        /* cleanup_plugin */ aom_cleanup_plugin,
1533
        /* new_encoder */ aom_new_encoder,
1534
        /* free_encoder */ aom_free_encoder,
1535
        /* set_parameter_quality */ aom_set_parameter_quality,
1536
        /* get_parameter_quality */ aom_get_parameter_quality,
1537
        /* set_parameter_lossless */ aom_set_parameter_lossless,
1538
        /* get_parameter_lossless */ aom_get_parameter_lossless,
1539
        /* set_parameter_logging_level */ aom_set_parameter_logging_level,
1540
        /* get_parameter_logging_level */ aom_get_parameter_logging_level,
1541
        /* list_parameters */ aom_list_parameters,
1542
        /* set_parameter_integer */ aom_set_parameter_integer,
1543
        /* get_parameter_integer */ aom_get_parameter_integer,
1544
        /* set_parameter_boolean */ aom_set_parameter_boolean,
1545
        /* get_parameter_boolean */ aom_get_parameter_boolean,
1546
        /* set_parameter_string */ aom_set_parameter_string,
1547
        /* get_parameter_string */ aom_get_parameter_string,
1548
        /* query_input_colorspace */ aom_query_input_colorspace,
1549
        /* encode_image */ aom_encode_image,
1550
        /* get_compressed_data */ aom_get_compressed_data,
1551
        /* query_input_colorspace (v2) */ aom_query_input_colorspace2,
1552
        /* query_encoded_size (v3) */ nullptr,
1553
        /* minimum_required_libheif_version */ LIBHEIF_MAKE_VERSION(1,21,0),
1554
        /* start_sequence_encoding (v4) */ aom_start_sequence_encoding,
1555
        /* encode_sequence_frame (v4) */ aom_encode_sequence_frame,
1556
        /* end_sequence_encoding (v4) */ aom_end_sequence_encoding,
1557
        /* get_compressed_data2 (v4) */ aom_get_compressed_data2,
1558
        /* does_indicate_keyframes (v4) */ 1
1559
    };
1560
1561
const heif_encoder_plugin* get_encoder_plugin_aom()
1562
20.6k
{
1563
20.6k
  return &encoder_plugin_aom;
1564
20.6k
}
1565
1566
1567
#if PLUGIN_AOM_ENCODER
1568
heif_plugin_info plugin_info {
1569
  1,
1570
  heif_plugin_type_encoder,
1571
  &encoder_plugin_aom
1572
};
1573
#endif