Coverage Report

Created: 2026-08-31 06:21

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