Coverage Report

Created: 2026-09-06 07:31

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