libheif/plugins/encoder_aom.cc (870 lines of code) (raw):
/*
* HEIF codec.
* Copyright (c) 2017 Dirk Farin <dirk.farin@gmail.com>
*
* This file is part of libheif.
*
* libheif is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* libheif is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libheif. If not, see <http://www.gnu.org/licenses/>.
*/
#include "libheif/heif.h"
#include "libheif/heif_plugin.h"
#include "common_utils.h"
#include <algorithm>
#include <cstring>
#include <cassert>
#include <vector>
#include <string>
#include <thread>
#include <memory>
#include "encoder_aom.h"
#include <aom/aom_encoder.h>
#include <aom/aomcx.h>
#include <mutex>
// Detect whether the aom_codec_set_option() function is available.
// See https://aomedia.googlesource.com/aom/+/c1d42fe6615c96fc929257ed53c41fa094f38836%5E%21/aom/aom_codec.h.
#if AOM_CODEC_ABI_VERSION >= (6 + AOM_IMAGE_ABI_VERSION)
#define HAVE_AOM_CODEC_SET_OPTION 1
#endif
#if defined(HAVE_AOM_CODEC_SET_OPTION)
struct custom_option
{
std::string name;
std::string value;
};
#endif
struct encoder_struct_aom
{
~encoder_struct_aom()
{
for (auto* error : aom_errors) {
delete[] error;
}
}
// --- parameters
bool realtime_mode;
int cpu_used; // = parameter 'speed'. I guess this is a better name than 'cpu_used'.
int quality;
int alpha_quality;
int min_q;
int max_q;
int alpha_min_q;
int alpha_max_q;
int threads;
bool lossless;
bool lossless_alpha;
#if defined(HAVE_AOM_CODEC_SET_OPTION)
std::vector<custom_option> custom_options;
void add_custom_option(const custom_option&);
void add_custom_option(std::string name, std::string value);
#endif
aom_tune_metric tune;
heif_chroma chroma = heif_chroma_420;
// --- input
bool alpha_quality_set = false;
bool alpha_min_q_set = false;
bool alpha_max_q_set = false;
// --- output
std::vector<uint8_t> compressedData;
bool data_read = false;
// --- error message copies
std::mutex aom_errors_mutex;
std::vector<const char*> aom_errors;
const char* set_aom_error(const char* aom_error_detail);
};
#if defined(HAVE_AOM_CODEC_SET_OPTION)
void encoder_struct_aom::add_custom_option(const custom_option& p)
{
// if there is already a parameter of that name, remove it from list
for (auto iter = custom_options.begin(); iter != custom_options.end(); ++iter) {
if (iter->name == p.name) {
custom_options.erase(iter);
break;
}
}
// and add the new parameter at the end of the list
custom_options.push_back(p);
}
void encoder_struct_aom::add_custom_option(std::string name, std::string value)
{
custom_option p;
p.name = name;
p.value = value;
add_custom_option(p);
}
#endif
static const char* kError_undefined_error = "Undefined AOM error";
static const char* kError_codec_enc_config_default = "Error creating the default encoder config";
const char* encoder_struct_aom::set_aom_error(const char* aom_error)
{
if (aom_error) {
// We have to make a copy because the error returned from aom_codec_error_detail() is only valid
// while the codec structure exists.
char* err_copy = new char[strlen(aom_error) + 1];
strcpy(err_copy, aom_error);
std::lock_guard<std::mutex> lock(aom_errors_mutex);
aom_errors.push_back(err_copy);
return err_copy;
}
else {
return kError_undefined_error;
}
}
static const char* kParam_min_q = "min-q";
static const char* kParam_max_q = "max-q";
static const char* kParam_alpha_quality = "alpha-quality";
static const char* kParam_alpha_min_q = "alpha-min-q";
static const char* kParam_alpha_max_q = "alpha-max-q";
static const char* kParam_lossless_alpha = "lossless-alpha";
static const char* kParam_threads = "threads";
static const char* kParam_realtime = "realtime";
static const char* kParam_speed = "speed";
static const char* kParam_chroma = "chroma";
static const char* const kParam_chroma_valid_values[] = {
"420", "422", "444", nullptr
};
static const char* kParam_tune = "tune";
static const char* const kParam_tune_valid_values[] = {
"psnr", "ssim", nullptr
};
static const int AOM_PLUGIN_PRIORITY = 60;
#define MAX_PLUGIN_NAME_LENGTH 80
static char plugin_name[MAX_PLUGIN_NAME_LENGTH];
static void aom_set_default_parameters(void* encoder);
static const char* aom_plugin_name()
{
const char* encoder_name = aom_codec_iface_name(aom_codec_av1_cx());
if (strlen(encoder_name) < MAX_PLUGIN_NAME_LENGTH) {
strcpy(plugin_name, encoder_name);
}
else {
strcpy(plugin_name, "AOMedia AV1 encoder");
}
return plugin_name;
}
#define MAX_NPARAMETERS 14
static struct heif_encoder_parameter aom_encoder_params[MAX_NPARAMETERS];
static const struct heif_encoder_parameter* aom_encoder_parameter_ptrs[MAX_NPARAMETERS + 1];
static void aom_init_parameters()
{
struct heif_encoder_parameter* p = aom_encoder_params;
const struct heif_encoder_parameter** d = aom_encoder_parameter_ptrs;
int i = 0;
assert(i < MAX_NPARAMETERS);
p->version = 2;
p->name = kParam_realtime;
p->type = heif_encoder_parameter_type_boolean;
p->boolean.default_value = false;
p->has_default = true;
d[i++] = p++;
assert(i < MAX_NPARAMETERS);
p->version = 2;
p->name = kParam_speed;
p->type = heif_encoder_parameter_type_integer;
p->integer.default_value = 6;
p->has_default = true;
p->integer.have_minimum_maximum = true;
p->integer.minimum = 0;
if (aom_codec_version_major() >= 3) {
p->integer.maximum = 9;
}
else {
p->integer.maximum = 8;
}
p->integer.valid_values = NULL;
p->integer.num_valid_values = 0;
d[i++] = p++;
assert(i < MAX_NPARAMETERS);
p->version = 2;
p->name = kParam_threads;
p->type = heif_encoder_parameter_type_integer;
p->has_default = true;
p->integer.have_minimum_maximum = true;
p->integer.minimum = 1;
p->integer.maximum = 64;
int threads = static_cast<int>(std::thread::hardware_concurrency());
if (threads == 0) {
// Could not autodetect, use previous default value.
threads = 4;
}
threads = std::min(threads, p->integer.maximum);
p->integer.default_value = threads;
p->integer.valid_values = NULL;
p->integer.num_valid_values = 0;
d[i++] = p++;
assert(i < MAX_NPARAMETERS);
p->version = 2;
p->name = heif_encoder_parameter_name_quality;
p->type = heif_encoder_parameter_type_integer;
p->integer.default_value = 50;
p->has_default = true;
p->integer.have_minimum_maximum = true;
p->integer.minimum = 0;
p->integer.maximum = 100;
p->integer.valid_values = NULL;
p->integer.num_valid_values = 0;
d[i++] = p++;
assert(i < MAX_NPARAMETERS);
p->version = 2;
p->name = heif_encoder_parameter_name_lossless;
p->type = heif_encoder_parameter_type_boolean;
p->boolean.default_value = false;
p->has_default = true;
d[i++] = p++;
assert(i < MAX_NPARAMETERS);
p->version = 2;
p->name = kParam_chroma;
p->type = heif_encoder_parameter_type_string;
p->string.default_value = "420";
p->has_default = true;
p->string.valid_values = kParam_chroma_valid_values;
d[i++] = p++;
assert(i < MAX_NPARAMETERS);
p->version = 2;
p->name = kParam_tune;
p->type = heif_encoder_parameter_type_string;
p->string.default_value = "ssim";
p->has_default = true;
p->string.valid_values = kParam_tune_valid_values;
d[i++] = p++;
assert(i < MAX_NPARAMETERS);
p->version = 2;
p->name = kParam_min_q;
p->type = heif_encoder_parameter_type_integer;
p->integer.default_value = 0;
p->has_default = true;
p->integer.have_minimum_maximum = true;
p->integer.minimum = 0;
p->integer.maximum = 63;
p->integer.valid_values = NULL;
p->integer.num_valid_values = 0;
d[i++] = p++;
assert(i < MAX_NPARAMETERS);
p->version = 2;
p->name = kParam_max_q;
p->type = heif_encoder_parameter_type_integer;
p->integer.default_value = 63;
p->has_default = true;
p->integer.have_minimum_maximum = true;
p->integer.minimum = 0;
p->integer.maximum = 63;
p->integer.valid_values = NULL;
p->integer.num_valid_values = 0;
d[i++] = p++;
assert(i < MAX_NPARAMETERS);
p->version = 2;
p->name = kParam_alpha_quality;
p->type = heif_encoder_parameter_type_integer;
p->has_default = false;
p->integer.have_minimum_maximum = true;
p->integer.minimum = 0;
p->integer.maximum = 100;
p->integer.valid_values = NULL;
p->integer.num_valid_values = 0;
d[i++] = p++;
assert(i < MAX_NPARAMETERS);
p->version = 2;
p->name = kParam_alpha_min_q;
p->type = heif_encoder_parameter_type_integer;
p->has_default = false;
p->integer.have_minimum_maximum = true;
p->integer.minimum = 0;
p->integer.maximum = 63;
p->integer.valid_values = NULL;
p->integer.num_valid_values = 0;
d[i++] = p++;
assert(i < MAX_NPARAMETERS);
p->version = 2;
p->name = kParam_alpha_max_q;
p->type = heif_encoder_parameter_type_integer;
p->has_default = false;
p->integer.have_minimum_maximum = true;
p->integer.minimum = 0;
p->integer.maximum = 63;
p->integer.valid_values = NULL;
p->integer.num_valid_values = 0;
d[i++] = p++;
assert(i < MAX_NPARAMETERS);
p->version = 2;
p->name = kParam_lossless_alpha;
p->type = heif_encoder_parameter_type_boolean;
p->boolean.default_value = false;
p->has_default = true;
d[i++] = p++;
assert(i < MAX_NPARAMETERS + 1);
d[i++] = nullptr;
}
const struct heif_encoder_parameter** aom_list_parameters(void* encoder)
{
return aom_encoder_parameter_ptrs;
}
static void aom_init_plugin()
{
aom_init_parameters();
}
static void aom_cleanup_plugin()
{
}
struct heif_error aom_new_encoder(void** enc)
{
struct encoder_struct_aom* encoder = new encoder_struct_aom();
struct heif_error err = heif_error_ok;
*enc = encoder;
// set default parameters
aom_set_default_parameters(encoder);
return err;
}
void aom_free_encoder(void* encoder_raw)
{
struct encoder_struct_aom* encoder = (struct encoder_struct_aom*) encoder_raw;
delete encoder;
}
struct heif_error aom_set_parameter_quality(void* encoder_raw, int quality)
{
struct encoder_struct_aom* encoder = (struct encoder_struct_aom*) encoder_raw;
if (quality < 0 || quality > 100) {
return heif_error_invalid_parameter_value;
}
encoder->quality = quality;
return heif_error_ok;
}
struct heif_error aom_get_parameter_quality(void* encoder_raw, int* quality)
{
struct encoder_struct_aom* encoder = (struct encoder_struct_aom*) encoder_raw;
*quality = encoder->quality;
return heif_error_ok;
}
struct heif_error aom_set_parameter_lossless(void* encoder_raw, int enable)
{
struct encoder_struct_aom* encoder = (struct encoder_struct_aom*) encoder_raw;
if (enable) {
encoder->min_q = 0;
encoder->max_q = 0;
encoder->alpha_min_q = 0;
encoder->alpha_min_q_set = true;
encoder->alpha_max_q = 0;
encoder->alpha_max_q_set = true;
}
encoder->lossless = enable;
return heif_error_ok;
}
struct heif_error aom_get_parameter_lossless(void* encoder_raw, int* enable)
{
struct encoder_struct_aom* encoder = (struct encoder_struct_aom*) encoder_raw;
*enable = encoder->lossless;
return heif_error_ok;
}
struct heif_error aom_set_parameter_logging_level(void* encoder_raw, int logging)
{
#if 0
struct encoder_struct_x265* encoder = (struct encoder_struct_x265*)encoder_raw;
if (logging<0 || logging>4) {
return heif_error_invalid_parameter_value;
}
encoder->logLevel = logging;
#endif
return heif_error_ok;
}
struct heif_error aom_get_parameter_logging_level(void* encoder_raw, int* loglevel)
{
#if 0
struct encoder_struct_x265* encoder = (struct encoder_struct_x265*)encoder_raw;
*loglevel = encoder->logLevel;
#else
*loglevel = 0;
#endif
return heif_error_ok;
}
#define set_value(paramname, paramvar) if (strcmp(name, paramname)==0) { encoder->paramvar = value; return heif_error_ok; }
#define get_value(paramname, paramvar) if (strcmp(name, paramname)==0) { *value = encoder->paramvar; return heif_error_ok; }
struct heif_error aom_set_parameter_integer(void* encoder_raw, const char* name, int value)
{
struct encoder_struct_aom* encoder = (struct encoder_struct_aom*) encoder_raw;
if (strcmp(name, heif_encoder_parameter_name_quality) == 0) {
return aom_set_parameter_quality(encoder, value);
}
else if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) {
return aom_set_parameter_lossless(encoder, value);
}
else if (strcmp(name, kParam_alpha_quality) == 0) {
if (value < 0 || value > 100) {
return heif_error_invalid_parameter_value;
}
encoder->alpha_quality = value;
encoder->alpha_quality_set = true;
return heif_error_ok;
}
else if (strcmp(name, kParam_alpha_min_q) == 0) {
encoder->alpha_min_q = value;
encoder->alpha_min_q_set = true;
return heif_error_ok;
}
else if (strcmp(name, kParam_alpha_max_q) == 0) {
encoder->alpha_max_q = value;
encoder->alpha_max_q_set = true;
return heif_error_ok;
}
set_value(kParam_min_q, min_q);
set_value(kParam_max_q, max_q);
set_value(kParam_threads, threads);
set_value(kParam_speed, cpu_used);
return heif_error_unsupported_parameter;
}
struct heif_error aom_get_parameter_integer(void* encoder_raw, const char* name, int* value)
{
struct encoder_struct_aom* encoder = (struct encoder_struct_aom*) encoder_raw;
if (strcmp(name, heif_encoder_parameter_name_quality) == 0) {
return aom_get_parameter_quality(encoder, value);
}
else if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) {
return aom_get_parameter_lossless(encoder, value);
}
else if (strcmp(name, kParam_alpha_quality) == 0) {
*value = encoder->alpha_quality_set ? encoder->alpha_quality : encoder->quality;
return heif_error_ok;
}
else if (strcmp(name, kParam_alpha_max_q) == 0) {
*value = encoder->alpha_max_q_set ? encoder->alpha_max_q : encoder->max_q;
return heif_error_ok;
}
else if (strcmp(name, kParam_alpha_min_q) == 0) {
*value = encoder->alpha_min_q_set ? encoder->alpha_min_q : encoder->min_q;
return heif_error_ok;
}
get_value(kParam_min_q, min_q);
get_value(kParam_max_q, max_q);
get_value(kParam_threads, threads);
get_value(kParam_speed, cpu_used);
return heif_error_unsupported_parameter;
}
struct heif_error aom_set_parameter_boolean(void* encoder_raw, const char* name, int value)
{
struct encoder_struct_aom* encoder = (struct encoder_struct_aom*) encoder_raw;
if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) {
return aom_set_parameter_lossless(encoder, value);
}
else if (strcmp(name, kParam_lossless_alpha) == 0) {
encoder->lossless_alpha = value;
if (value) {
encoder->alpha_max_q = 0;
encoder->alpha_max_q_set = true;
encoder->alpha_min_q = 0;
encoder->alpha_min_q_set = true;
}
return heif_error_ok;
}
set_value(kParam_realtime, realtime_mode);
return heif_error_unsupported_parameter;
}
struct heif_error aom_get_parameter_boolean(void* encoder_raw, const char* name, int* value)
{
struct encoder_struct_aom* encoder = (struct encoder_struct_aom*) encoder_raw;
if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) {
return aom_get_parameter_lossless(encoder, value);
}
get_value(kParam_realtime, realtime_mode);
get_value(kParam_lossless_alpha, lossless_alpha);
return heif_error_unsupported_parameter;
}
struct heif_error aom_set_parameter_string(void* encoder_raw, const char* name, const char* value)
{
struct encoder_struct_aom* encoder = (struct encoder_struct_aom*) encoder_raw;
if (strcmp(name, kParam_chroma) == 0) {
if (strcmp(value, "420") == 0) {
encoder->chroma = heif_chroma_420;
return heif_error_ok;
}
else if (strcmp(value, "422") == 0) {
encoder->chroma = heif_chroma_422;
return heif_error_ok;
}
else if (strcmp(value, "444") == 0) {
encoder->chroma = heif_chroma_444;
return heif_error_ok;
}
else {
return heif_error_invalid_parameter_value;
}
}
if (strcmp(name, kParam_tune) == 0) {
if (strcmp(value, "psnr") == 0) {
encoder->tune = AOM_TUNE_PSNR;
return heif_error_ok;
}
else if (strcmp(value, "ssim") == 0) {
encoder->tune = AOM_TUNE_SSIM;
return heif_error_ok;
}
else {
return heif_error_invalid_parameter_value;
}
}
#if defined(HAVE_AOM_CODEC_SET_OPTION)
if (strncmp(name, "aom:", 4) == 0) {
encoder->add_custom_option(std::string(name).substr(4), std::string(value));
return heif_error_ok;
}
#endif
return heif_error_unsupported_parameter;
}
static void save_strcpy(char* dst, int dst_size, const char* src)
{
strncpy(dst, src, dst_size - 1);
dst[dst_size - 1] = 0;
}
struct heif_error aom_get_parameter_string(void* encoder_raw, const char* name,
char* value, int value_size)
{
struct encoder_struct_aom* encoder = (struct encoder_struct_aom*) encoder_raw;
if (strcmp(name, kParam_chroma) == 0) {
switch (encoder->chroma) {
case heif_chroma_420:
save_strcpy(value, value_size, "420");
break;
case heif_chroma_422:
save_strcpy(value, value_size, "422");
break;
case heif_chroma_444:
save_strcpy(value, value_size, "444");
break;
default:
assert(false);
return heif_error_invalid_parameter_value;
}
return heif_error_ok;
}
else if (strcmp(name, kParam_tune) == 0) {
switch (encoder->tune) {
case AOM_TUNE_PSNR:
save_strcpy(value, value_size, "psnr");
break;
case AOM_TUNE_SSIM:
save_strcpy(value, value_size, "ssim");
break;
default:
assert(false);
return heif_error_invalid_parameter_value;
}
return heif_error_ok;
}
return heif_error_unsupported_parameter;
}
static void aom_set_default_parameters(void* encoder)
{
for (const struct heif_encoder_parameter** p = aom_encoder_parameter_ptrs; *p; p++) {
const struct heif_encoder_parameter* param = *p;
if (param->has_default) {
switch (param->type) {
case heif_encoder_parameter_type_integer:
aom_set_parameter_integer(encoder, param->name, param->integer.default_value);
break;
case heif_encoder_parameter_type_boolean:
aom_set_parameter_boolean(encoder, param->name, param->boolean.default_value);
break;
case heif_encoder_parameter_type_string:
aom_set_parameter_string(encoder, param->name, param->string.default_value);
break;
}
}
}
}
void aom_query_input_colorspace(heif_colorspace* colorspace, heif_chroma* chroma)
{
*colorspace = heif_colorspace_YCbCr;
*chroma = heif_chroma_420;
}
void aom_query_input_colorspace2(void* encoder_raw, heif_colorspace* colorspace, heif_chroma* chroma)
{
struct encoder_struct_aom* encoder = (struct encoder_struct_aom*) encoder_raw;
if (*colorspace == heif_colorspace_monochrome) {
// keep the monochrome colorspace
}
else {
*colorspace = heif_colorspace_YCbCr;
*chroma = encoder->chroma;
}
}
struct heif_error aom_encode_image(void* encoder_raw, const struct heif_image* image,
heif_image_input_class input_class)
{
struct encoder_struct_aom* encoder = (struct encoder_struct_aom*) encoder_raw;
struct heif_error err;
const int source_width = heif_image_get_width(image, heif_channel_Y);
const int source_height = heif_image_get_height(image, heif_channel_Y);
const heif_chroma chroma = heif_image_get_chroma_format(image);
int bpp_y = heif_image_get_bits_per_pixel_range(image, heif_channel_Y);
// --- check for AOM 3.6.0 bug
bool is_aom_3_6_0 = (aom_codec_version() == 0x030600);
if (is_aom_3_6_0) {
// This bound might be too tight, as I still could encode images with 8193 x 4353 correctly. Even 8200x4400, but 8200x4800 fails.
// Let's still keep it as most images will be smaller anyway.
if (!(source_width <= 8192 * 2 && source_height <= 4352 * 2 && source_width * source_height <= 8192 * 4352)) {
err = {heif_error_Encoding_error,
heif_suberror_Encoder_encoding,
"AOM v3.6.0 has a bug when encoding large images. Please upgrade to at least AOM v3.6.1."};
return err;
}
}
// --- copy libheif image to aom image
aom_image_t input_image;
aom_img_fmt_t img_format = AOM_IMG_FMT_NONE;
int chroma_height = 0;
int chroma_sample_position = AOM_CSP_UNKNOWN;
switch (chroma) {
case heif_chroma_420:
case heif_chroma_monochrome:
img_format = AOM_IMG_FMT_I420;
chroma_height = (source_height+1)/2;
chroma_sample_position = AOM_CSP_UNKNOWN; // TODO: change this to CSP_CENTER in the future (https://github.com/AOMediaCodec/av1-avif/issues/88)
break;
case heif_chroma_422:
img_format = AOM_IMG_FMT_I422;
chroma_height = (source_height+1)/2;
chroma_sample_position = AOM_CSP_COLOCATED;
break;
case heif_chroma_444:
img_format = AOM_IMG_FMT_I444;
chroma_height = source_height;
chroma_sample_position = AOM_CSP_COLOCATED;
break;
default:
img_format = AOM_IMG_FMT_NONE;
chroma_sample_position = AOM_CSP_UNKNOWN;
assert(false);
break;
}
if (bpp_y > 8) {
img_format = (aom_img_fmt_t) (img_format | AOM_IMG_FMT_HIGHBITDEPTH);
}
if (!aom_img_alloc(&input_image, img_format,
source_width, source_height, 1)) {
err = {heif_error_Memory_allocation_error,
heif_suberror_Unspecified,
"Failed to allocate image"};
return err;
}
for (int plane = 0; plane < 3; plane++) {
unsigned char* buf = input_image.planes[plane];
const int stride = input_image.stride[plane];
if (chroma == heif_chroma_monochrome && plane != 0) {
if (bpp_y == 8) {
memset(buf, 128, chroma_height * stride);
}
else {
uint16_t* buf16 = (uint16_t*) buf;
uint16_t half_range = (uint16_t) (1 << (bpp_y - 1));
for (int i = 0; i < chroma_height * stride / 2; i++) {
buf16[i] = half_range;
}
}
continue;
}
/*
const int w = aom_img_plane_width(img, plane) *
((img->fmt & AOM_IMG_FMT_HIGHBITDEPTH) ? 2 : 1);
const int h = aom_img_plane_height(img, plane);
*/
int in_stride = 0;
const uint8_t* in_p = heif_image_get_plane_readonly(image, (heif_channel) plane, &in_stride);
int w = source_width;
int h = source_height;
if (plane != 0) {
if (chroma != heif_chroma_444) { w = (w + 1) / 2; }
if (chroma == heif_chroma_420) { h = (h + 1) / 2; }
assert(w == heif_image_get_width(image, (heif_channel) plane));
assert(h == heif_image_get_height(image, (heif_channel) plane));
}
if (bpp_y > 8) {
w *= 2;
}
for (int y = 0; y < h; y++) {
memcpy(buf, &in_p[y * in_stride], w);
buf += stride;
}
}
// --- configure codec
aom_codec_iface_t* iface;
aom_codec_ctx_t codec;
iface = aom_codec_av1_cx();
//encoder->encoder = get_aom_encoder_by_name("av1");
if (!iface) {
err = {heif_error_Unsupported_feature,
heif_suberror_Unsupported_codec,
"Unsupported codec: AOMedia Project AV1 Encoder"};
return err;
}
#if defined(AOM_USAGE_ALL_INTRA)
// aom 3.1.0
unsigned int aomUsage = AOM_USAGE_ALL_INTRA;
#else
// aom 2.0
unsigned int aomUsage = AOM_USAGE_GOOD_QUALITY;
#endif
if (encoder->realtime_mode) {
aomUsage = AOM_USAGE_REALTIME;
}
aom_codec_enc_cfg_t cfg;
aom_codec_err_t res = aom_codec_enc_config_default(iface, &cfg, aomUsage);
if (res) {
err = {heif_error_Encoder_plugin_error,
heif_suberror_Encoder_initialization,
kError_codec_enc_config_default};
return err;
}
int seq_profile = compute_avif_profile(heif_image_get_bits_per_pixel_range(image, heif_channel_Y),
heif_image_get_chroma_format(image));
cfg.g_w = source_width;
cfg.g_h = source_height;
// Set the max number of frames to encode to 1. This makes the libaom encoder
// set still_picture and reduced_still_picture_header to 1 in the AV1 sequence
// header OBU.
cfg.g_limit = 1;
// Use the default settings of the new AOM_USAGE_ALL_INTRA (added in
// https://crbug.com/aomedia/2959).
//
// Set g_lag_in_frames to 0 to reduce the number of frame buffers (from 20
// to 2) in libaom's lookahead structure. This reduces memory consumption when
// encoding a single image.
cfg.g_lag_in_frames = 0;
// Disable automatic placement of key frames by the encoder.
cfg.kf_mode = AOM_KF_DISABLED;
// Tell libaom that all frames will be key frames.
cfg.kf_max_dist = 0;
cfg.g_profile = seq_profile;
cfg.g_bit_depth = (aom_bit_depth_t) bpp_y;
cfg.g_input_bit_depth = bpp_y;
cfg.rc_end_usage = AOM_Q;
int min_q = encoder->min_q;
int max_q = encoder->max_q;
if (input_class == heif_image_input_class_alpha && encoder->alpha_min_q_set && encoder->alpha_max_q_set) {
min_q = encoder->alpha_min_q;
max_q = encoder->alpha_max_q;
}
int quality = encoder->quality;
if (input_class == heif_image_input_class_alpha && encoder->alpha_quality_set) {
quality = encoder->alpha_quality;
}
int cq_level = ((100 - quality) * 63 + 50) / 100;
// Work around the bug in libaom v2.0.2 or older fixed by
// https://aomedia-review.googlesource.com/c/aom/+/113064. If using a libaom
// release with the bug, set cfg.rc_min_quantizer to cq_level to prevent
// libaom from incorrectly using a quantizer index lower than cq_level.
bool aom_2_0_2_or_older = aom_codec_version() <= 0x020002;
cfg.rc_min_quantizer = aom_2_0_2_or_older ? cq_level : min_q;
cfg.rc_max_quantizer = max_q;
cfg.g_error_resilient = 0;
cfg.g_threads = encoder->threads;
if (chroma == heif_chroma_monochrome) {
cfg.monochrome = 1;
}
// --- initialize codec
aom_codec_flags_t encoder_flags = 0;
if (bpp_y > 8) {
encoder_flags = (aom_codec_flags_t) (encoder_flags | AOM_CODEC_USE_HIGHBITDEPTH);
}
if (aom_codec_enc_init(&codec, iface, &cfg, encoder_flags)) {
// AOM makes sure that the error text returned by aom_codec_error_detail() is always a static
// text that is valid even through the codec allocation failed (#788).
err = {heif_error_Encoder_plugin_error,
heif_suberror_Encoder_initialization,
encoder->set_aom_error(aom_codec_error_detail(&codec))};
return err;
}
aom_codec_control(&codec, AOME_SET_CPUUSED, encoder->cpu_used);
aom_codec_control(&codec, AOME_SET_CQ_LEVEL, cq_level);
if (encoder->threads > 1) {
#if defined(AOM_CTRL_AV1E_SET_ROW_MT)
// aom 2.0
aom_codec_control(&codec, AV1E_SET_ROW_MT, 1);
#endif
}
// TODO: set AV1E_SET_TILE_ROWS and AV1E_SET_TILE_COLUMNS.
struct heif_color_profile_nclx* nclx = nullptr;
err = heif_image_get_nclx_color_profile(image, &nclx);
if (err.code != heif_error_Ok) {
nclx = nullptr;
}
// make sure NCLX profile is deleted at end of function
auto nclx_deleter = std::unique_ptr<heif_color_profile_nclx, void (*)(heif_color_profile_nclx*)>(nclx, heif_nclx_color_profile_free);
// In aom, color_range defaults to limited range (0). Set it to full range (1).
aom_codec_control(&codec, AV1E_SET_COLOR_RANGE, nclx ? nclx->full_range_flag : 1);
aom_codec_control(&codec, AV1E_SET_CHROMA_SAMPLE_POSITION, chroma_sample_position);
if (nclx &&
(input_class == heif_image_input_class_normal ||
input_class == heif_image_input_class_thumbnail)) {
aom_codec_control(&codec, AV1E_SET_COLOR_PRIMARIES, nclx->color_primaries);
aom_codec_control(&codec, AV1E_SET_MATRIX_COEFFICIENTS, nclx->matrix_coefficients);
aom_codec_control(&codec, AV1E_SET_TRANSFER_CHARACTERISTICS, nclx->transfer_characteristics);
}
aom_codec_control(&codec, AOME_SET_TUNING, encoder->tune);
if (encoder->lossless || (input_class == heif_image_input_class_alpha && encoder->lossless_alpha)) {
aom_codec_control(&codec, AV1E_SET_LOSSLESS, 1);
}
#if defined(AOM_CTRL_AV1E_SET_SKIP_POSTPROC_FILTERING)
if (cfg.g_usage == AOM_USAGE_ALL_INTRA) {
// Enable AV1E_SET_SKIP_POSTPROC_FILTERING for still-picture encoding,
// which is disabled by default.
aom_codec_control(&codec, AV1E_SET_SKIP_POSTPROC_FILTERING, 1);
}
#endif
#if defined(HAVE_AOM_CODEC_SET_OPTION)
// Apply the custom AOM encoder options.
// These should always be applied last as they can override the values that were set above.
for (const auto& p : encoder->custom_options) {
aom_codec_set_option(&codec, p.name.c_str(), p.value.c_str());
}
#endif
// --- encode frame
res = aom_codec_encode(&codec, &input_image,
0, // only encoding a single frame
1,
0); // no flags
// Note: we are freeing the input image directly after use.
// This covers the usual success case and also all error cases that occur below.
aom_img_free(&input_image);
if (res != AOM_CODEC_OK) {
err = {
heif_error_Encoder_plugin_error,
heif_suberror_Encoder_encoding,
encoder->set_aom_error(aom_codec_error_detail(&codec))
};
aom_codec_destroy(&codec);
return err;
}
encoder->compressedData.clear();
const aom_codec_cx_pkt_t* pkt = NULL;
aom_codec_iter_t iter = NULL; // for extracting the compressed packets
while ((pkt = aom_codec_get_cx_data(&codec, &iter)) != NULL) {
if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) {
//std::cerr.write((char*)pkt->data.frame.buf, pkt->data.frame.sz);
//printf("packet of size: %d\n",(int)pkt->data.frame.sz);
// TODO: split the received data into separate OBUs
// This allows libheif to easily extract the sequence header for the av1C header
size_t n = pkt->data.frame.sz;
size_t oldSize = encoder->compressedData.size();
encoder->compressedData.resize(oldSize + n);
memcpy(encoder->compressedData.data() + oldSize,
pkt->data.frame.buf,
n);
encoder->data_read = false;
}
}
int flags = 0;
res = aom_codec_encode(&codec, NULL, -1, 0, flags);
if (res != AOM_CODEC_OK) {
err = {heif_error_Encoder_plugin_error,
heif_suberror_Encoder_encoding,
encoder->set_aom_error(aom_codec_error_detail(&codec))};
aom_codec_destroy(&codec);
return err;
}
iter = NULL;
while ((pkt = aom_codec_get_cx_data(&codec, &iter)) != NULL) {
if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) {
//std::cerr.write((char*)pkt->data.frame.buf, pkt->data.frame.sz);
//printf("packet of size: %d\n",(int)pkt->data.frame.sz);
// TODO: split the received data into separate OBUs
// This allows libheif to easily extract the sequence header for the av1C header
size_t n = pkt->data.frame.sz;
size_t oldSize = encoder->compressedData.size();
encoder->compressedData.resize(oldSize + n);
memcpy(encoder->compressedData.data() + oldSize,
pkt->data.frame.buf,
n);
encoder->data_read = false;
}
}
// --- clean up
if (aom_codec_destroy(&codec)) {
// Note: do not call aom_codec_error_detail(), because it is not set in aom_codec_destroy(). (see #788)
err = {heif_error_Encoder_plugin_error,
heif_suberror_Encoder_cleanup,
kError_undefined_error};
return err;
}
return heif_error_ok;
}
struct heif_error aom_get_compressed_data(void* encoder_raw, uint8_t** data, int* size,
enum heif_encoded_data_type* type)
{
struct encoder_struct_aom* encoder = (struct encoder_struct_aom*) encoder_raw;
if (encoder->data_read) {
*size = 0;
*data = nullptr;
}
else {
*size = (int) encoder->compressedData.size();
*data = encoder->compressedData.data();
encoder->data_read = true;
}
return heif_error_ok;
}
static const struct heif_encoder_plugin encoder_plugin_aom
{
/* plugin_api_version */ 3,
/* compression_format */ heif_compression_AV1,
/* id_name */ "aom",
/* priority */ AOM_PLUGIN_PRIORITY,
/* supports_lossy_compression */ true,
/* supports_lossless_compression */ true,
/* get_plugin_name */ aom_plugin_name,
/* init_plugin */ aom_init_plugin,
/* cleanup_plugin */ aom_cleanup_plugin,
/* new_encoder */ aom_new_encoder,
/* free_encoder */ aom_free_encoder,
/* set_parameter_quality */ aom_set_parameter_quality,
/* get_parameter_quality */ aom_get_parameter_quality,
/* set_parameter_lossless */ aom_set_parameter_lossless,
/* get_parameter_lossless */ aom_get_parameter_lossless,
/* set_parameter_logging_level */ aom_set_parameter_logging_level,
/* get_parameter_logging_level */ aom_get_parameter_logging_level,
/* list_parameters */ aom_list_parameters,
/* set_parameter_integer */ aom_set_parameter_integer,
/* get_parameter_integer */ aom_get_parameter_integer,
/* set_parameter_boolean */ aom_set_parameter_boolean,
/* get_parameter_boolean */ aom_get_parameter_boolean,
/* set_parameter_string */ aom_set_parameter_string,
/* get_parameter_string */ aom_get_parameter_string,
/* query_input_colorspace */ aom_query_input_colorspace,
/* encode_image */ aom_encode_image,
/* get_compressed_data */ aom_get_compressed_data,
/* query_input_colorspace (v2) */ aom_query_input_colorspace2,
/* query_encoded_size (v3) */ nullptr
};
const struct heif_encoder_plugin* get_encoder_plugin_aom()
{
return &encoder_plugin_aom;
}
#if PLUGIN_AOM_ENCODER
heif_plugin_info plugin_info {
1,
heif_plugin_type_encoder,
&encoder_plugin_aom
};
#endif