libheif/plugins/encoder_rav1e.cc (522 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 "encoder_rav1e.h"
#include <vector>
#include <memory>
#include <cstring>
#include <cassert>
#include <string>
#include <algorithm>
#include <iostream> // TODO: remove me
#include "rav1e.h"
struct encoder_struct_rav1e
{
int speed; // 0-10
int quality; // TODO: not sure yet how to map quality to min/max q
int min_q;
int threads;
int tile_rows = 1; // 1,2,4,8,16,32,64
int tile_cols = 1; // 1,2,4,8,16,32,64
heif_chroma chroma;
// --- output
std::vector<uint8_t> compressed_data;
bool data_read = false;
};
//static const char* kError_out_of_memory = "Out of memory";
static const char* kParam_min_q = "min-q";
static const char* kParam_threads = "threads";
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 int valid_tile_num_values[] = {1, 2, 4, 8, 16, 32, 64};
static struct heif_error heif_error_codec_library_error = {heif_error_Encoder_plugin_error,
heif_suberror_Unspecified,
"rav1e error"};
static const int RAV1E_PLUGIN_PRIORITY = 20;
#define MAX_PLUGIN_NAME_LENGTH 80
static char plugin_name[MAX_PLUGIN_NAME_LENGTH];
static void rav1e_set_default_parameters(void* encoder);
static const char* rav1e_plugin_name()
{
strcpy(plugin_name, "Rav1e encoder");
return plugin_name;
}
#define MAX_NPARAMETERS 10
static struct heif_encoder_parameter rav1e_encoder_params[MAX_NPARAMETERS];
static const struct heif_encoder_parameter* rav1e_encoder_parameter_ptrs[MAX_NPARAMETERS + 1];
static void rav1e_init_parameters()
{
struct heif_encoder_parameter* p = rav1e_encoder_params;
const struct heif_encoder_parameter** d = rav1e_encoder_parameter_ptrs;
int i = 0;
assert(i < MAX_NPARAMETERS);
p->version = 2;
p->name = kParam_speed;
p->type = heif_encoder_parameter_type_integer;
p->integer.default_value = 8;
p->has_default = true;
p->integer.have_minimum_maximum = true;
p->integer.minimum = 0;
p->integer.maximum = 10;
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->integer.default_value = 4;
p->has_default = true;
p->integer.have_minimum_maximum = true;
p->integer.minimum = 1;
p->integer.maximum = 16;
p->integer.valid_values = NULL;
p->integer.num_valid_values = 0;
d[i++] = p++;
assert(i < MAX_NPARAMETERS);
p->version = 2;
p->name = "tile-rows";
p->type = heif_encoder_parameter_type_integer;
p->integer.default_value = 4;
p->has_default = true;
p->integer.have_minimum_maximum = false;
p->integer.valid_values = valid_tile_num_values;
p->integer.num_valid_values = 7;
d[i++] = p++;
assert(i < MAX_NPARAMETERS);
p->version = 2;
p->name = "tile-cols";
p->type = heif_encoder_parameter_type_integer;
p->integer.default_value = 4;
p->has_default = true;
p->integer.have_minimum_maximum = false;
p->integer.valid_values = valid_tile_num_values;
p->integer.num_valid_values = 7;
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_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 = 255;
p->integer.valid_values = NULL;
p->integer.num_valid_values = 0;
d[i++] = p++;
d[i++] = nullptr;
}
const struct heif_encoder_parameter** rav1e_list_parameters(void* encoder)
{
return rav1e_encoder_parameter_ptrs;
}
static void rav1e_init_plugin()
{
rav1e_init_parameters();
}
static void rav1e_cleanup_plugin()
{
}
struct heif_error rav1e_new_encoder(void** enc)
{
auto* encoder = new encoder_struct_rav1e();
struct heif_error err = heif_error_ok;
*enc = encoder;
// set default parameters
rav1e_set_default_parameters(encoder);
return err;
}
void rav1e_free_encoder(void* encoder_raw)
{
auto* encoder = (struct encoder_struct_rav1e*) encoder_raw;
delete encoder;
}
struct heif_error rav1e_set_parameter_quality(void* encoder_raw, int quality)
{
auto* encoder = (struct encoder_struct_rav1e*) encoder_raw;
if (quality < 0 || quality > 100) {
return heif_error_invalid_parameter_value;
}
encoder->quality = quality;
return heif_error_ok;
}
struct heif_error rav1e_get_parameter_quality(void* encoder_raw, int* quality)
{
auto* encoder = (struct encoder_struct_rav1e*) encoder_raw;
*quality = encoder->quality;
return heif_error_ok;
}
struct heif_error rav1e_set_parameter_lossless(void* encoder_raw, int enable)
{
struct encoder_struct_rav1e* encoder = (struct encoder_struct_rav1e*) encoder_raw;
if (enable) {
encoder->min_q = 0;
}
return heif_error_ok;
}
struct heif_error rav1e_get_parameter_lossless(void* encoder_raw, int* enable)
{
struct encoder_struct_rav1e* encoder = (struct encoder_struct_rav1e*) encoder_raw;
*enable = (encoder->min_q == 0);
return heif_error_ok;
}
struct heif_error rav1e_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 rav1e_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 rav1e_set_parameter_integer(void* encoder_raw, const char* name, int value)
{
struct encoder_struct_rav1e* encoder = (struct encoder_struct_rav1e*) encoder_raw;
if (strcmp(name, heif_encoder_parameter_name_quality) == 0) {
return rav1e_set_parameter_quality(encoder, value);
}
else if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) {
return rav1e_set_parameter_lossless(encoder, value);
}
set_value(kParam_min_q, min_q);
set_value(kParam_threads, threads);
set_value(kParam_speed, speed);
set_value("tile-rows", tile_rows);
set_value("tile-cols", tile_cols);
return heif_error_unsupported_parameter;
}
struct heif_error rav1e_get_parameter_integer(void* encoder_raw, const char* name, int* value)
{
struct encoder_struct_rav1e* encoder = (struct encoder_struct_rav1e*) encoder_raw;
if (strcmp(name, heif_encoder_parameter_name_quality) == 0) {
return rav1e_get_parameter_quality(encoder, value);
}
else if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) {
return rav1e_get_parameter_lossless(encoder, value);
}
get_value(kParam_min_q, min_q);
get_value(kParam_threads, threads);
get_value(kParam_speed, speed);
get_value("tile-rows", tile_rows);
get_value("tile-cols", tile_cols);
return heif_error_unsupported_parameter;
}
struct heif_error rav1e_set_parameter_boolean(void* encoder_raw, const char* name, int value)
{
auto* encoder = (struct encoder_struct_rav1e*) encoder_raw;
if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) {
return rav1e_set_parameter_lossless(encoder, value);
}
//set_value(kParam_realtime, realtime_mode);
return heif_error_unsupported_parameter;
}
struct heif_error rav1e_get_parameter_boolean(void* encoder_raw, const char* name, int* value)
{
auto* encoder = (struct encoder_struct_rav1e*) encoder_raw;
if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) {
return rav1e_get_parameter_lossless(encoder, value);
}
//get_value(kParam_realtime, realtime_mode);
return heif_error_unsupported_parameter;
}
struct heif_error rav1e_set_parameter_string(void* encoder_raw, const char* name, const char* value)
{
auto* encoder = (struct encoder_struct_rav1e*) 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;
}
}
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 rav1e_get_parameter_string(void* encoder_raw, const char* name,
char* value, int value_size)
{
struct encoder_struct_rav1e* encoder = (struct encoder_struct_rav1e*) 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;
}
return heif_error_unsupported_parameter;
}
static void rav1e_set_default_parameters(void* encoder)
{
for (const struct heif_encoder_parameter** p = rav1e_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:
rav1e_set_parameter_integer(encoder, param->name, param->integer.default_value);
break;
case heif_encoder_parameter_type_boolean:
rav1e_set_parameter_boolean(encoder, param->name, param->boolean.default_value);
break;
case heif_encoder_parameter_type_string:
rav1e_set_parameter_string(encoder, param->name, param->string.default_value);
break;
}
}
}
}
void rav1e_query_input_colorspace(heif_colorspace* colorspace, heif_chroma* chroma)
{
*colorspace = heif_colorspace_YCbCr;
*chroma = heif_chroma_420;
}
void rav1e_query_input_colorspace2(void* encoder_raw, heif_colorspace* colorspace, heif_chroma* chroma)
{
auto* encoder = (struct encoder_struct_rav1e*) encoder_raw;
*colorspace = heif_colorspace_YCbCr;
*chroma = encoder->chroma;
}
struct heif_error rav1e_encode_image(void* encoder_raw, const struct heif_image* image,
heif_image_input_class input_class)
{
auto* encoder = (struct encoder_struct_rav1e*) encoder_raw;
const heif_chroma chroma = heif_image_get_chroma_format(image);
uint8_t yShift = 0;
RaChromaSampling chromaSampling;
RaChromaSamplePosition chromaPosition;
RaPixelRange rav1eRange;
if (input_class == heif_image_input_class_alpha) {
chromaSampling = RA_CHROMA_SAMPLING_CS420; // I can't seem to get RA_CHROMA_SAMPLING_CS400 to work right now, unfortunately
chromaPosition = RA_CHROMA_SAMPLE_POSITION_UNKNOWN; // TODO: set to CENTER when AV1 and rav1e supports this
yShift = 1;
}
else {
switch (chroma) {
case heif_chroma_444:
chromaSampling = RA_CHROMA_SAMPLING_CS444;
chromaPosition = RA_CHROMA_SAMPLE_POSITION_COLOCATED;
break;
case heif_chroma_422:
chromaSampling = RA_CHROMA_SAMPLING_CS422;
chromaPosition = RA_CHROMA_SAMPLE_POSITION_COLOCATED;
break;
case heif_chroma_420:
chromaSampling = RA_CHROMA_SAMPLING_CS420;
chromaPosition = RA_CHROMA_SAMPLE_POSITION_UNKNOWN; // TODO: set to CENTER when AV1 and rav1e supports this
yShift = 1;
break;
default:
return heif_error_codec_library_error;
}
}
struct heif_color_profile_nclx* nclx = nullptr;
heif_error 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);
rav1eRange = RA_PIXEL_RANGE_FULL;
if (nclx) {
rav1eRange = nclx->full_range_flag ? RA_PIXEL_RANGE_FULL : RA_PIXEL_RANGE_LIMITED;
}
int bitDepth = heif_image_get_bits_per_pixel(image, heif_channel_Y);
auto rav1eConfigRaw = rav1e_config_default();
auto rav1eConfig = std::shared_ptr<RaConfig>(rav1eConfigRaw, [](RaConfig* c) { rav1e_config_unref(c); });
if (rav1e_config_set_pixel_format(rav1eConfig.get(), (uint8_t) bitDepth, chromaSampling, chromaPosition, rav1eRange) < 0) {
return heif_error_codec_library_error;
}
if (rav1e_config_parse(rav1eConfig.get(), "still_picture", "true") == -1) {
return heif_error_codec_library_error;
}
if (rav1e_config_parse_int(rav1eConfig.get(), "width", heif_image_get_width(image, heif_channel_Y)) == -1) {
return heif_error_codec_library_error;
}
if (rav1e_config_parse_int(rav1eConfig.get(), "height", heif_image_get_height(image, heif_channel_Y)) == -1) {
return heif_error_codec_library_error;
}
if (rav1e_config_parse_int(rav1eConfig.get(), "threads", encoder->threads) == -1) {
return heif_error_codec_library_error;
}
if (nclx &&
(input_class == heif_image_input_class_normal ||
input_class == heif_image_input_class_thumbnail)) {
if (rav1e_config_set_color_description(rav1eConfig.get(),
(RaMatrixCoefficients) nclx->matrix_coefficients,
(RaColorPrimaries) nclx->color_primaries,
(RaTransferCharacteristics) nclx->transfer_characteristics) == -1) {
return heif_error_codec_library_error;
}
}
if (rav1e_config_parse_int(rav1eConfig.get(), "min_quantizer", encoder->min_q) == -1) {
return heif_error_codec_library_error;
}
int base_quantizer = ((100 - encoder->quality) * 255 + 50) / 100;
if (rav1e_config_parse_int(rav1eConfig.get(), "quantizer", base_quantizer) == -1) {
return heif_error_codec_library_error;
}
if (encoder->tile_rows != 1) {
if (rav1e_config_parse_int(rav1eConfig.get(), "tile_rows", encoder->tile_rows) == -1) {
return heif_error_codec_library_error;
}
}
if (encoder->tile_cols != 1) {
if (rav1e_config_parse_int(rav1eConfig.get(), "tile_cols", encoder->tile_cols) == -1) {
return heif_error_codec_library_error;
}
}
/*if (encoder->speed != -1)*/ {
if (rav1e_config_parse_int(rav1eConfig.get(), "speed", encoder->speed) == -1) {
return heif_error_codec_library_error;
}
}
if (nclx) {
rav1e_config_set_color_description(rav1eConfig.get(),
(RaMatrixCoefficients) nclx->matrix_coefficients,
(RaColorPrimaries) nclx->color_primaries,
(RaTransferCharacteristics) nclx->transfer_characteristics);
}
RaContext* rav1eContextRaw = rav1e_context_new(rav1eConfig.get());
if (!rav1eContextRaw) {
return heif_error_codec_library_error;
}
auto rav1eContext = std::shared_ptr<RaContext>(rav1eContextRaw, [](RaContext* ctx) { rav1e_context_unref(ctx); });
// --- copy libheif image to rav1e image
auto rav1eFrameRaw = rav1e_frame_new(rav1eContext.get());
auto rav1eFrame = std::shared_ptr<RaFrame>(rav1eFrameRaw, [](RaFrame* frm) { rav1e_frame_unref(frm); });
int byteWidth = (bitDepth > 8) ? 2 : 1;
// if (input_class == heif_image_input_class_alpha) {
//} else
{
int strideY;
const uint8_t* Y = heif_image_get_plane_readonly(image, heif_channel_Y, &strideY);
int strideCb;
const uint8_t* Cb = heif_image_get_plane_readonly(image, heif_channel_Cb, &strideCb);
int strideCr;
const uint8_t* Cr = heif_image_get_plane_readonly(image, heif_channel_Cr, &strideCr);
uint32_t height = heif_image_get_height(image, heif_channel_Y);
uint32_t uvHeight = (height + yShift) >> yShift;
rav1e_frame_fill_plane(rav1eFrame.get(), 0, Y, strideY * height, strideY, byteWidth);
rav1e_frame_fill_plane(rav1eFrame.get(), 1, Cb, strideCb * uvHeight, strideCb, byteWidth);
rav1e_frame_fill_plane(rav1eFrame.get(), 2, Cr, strideCr * uvHeight, strideCr, byteWidth);
}
RaEncoderStatus encoderStatus = rav1e_send_frame(rav1eContext.get(), rav1eFrame.get());
if (encoderStatus != 0) {
return heif_error_codec_library_error;
}
// flush encoder
encoderStatus = rav1e_send_frame(rav1eContext.get(), nullptr);
if (encoderStatus != 0) {
return heif_error_codec_library_error;
}
RaPacket* pkt = nullptr;
encoderStatus = rav1e_receive_packet(rav1eContext.get(), &pkt);
if (encoderStatus != 0) {
return heif_error_codec_library_error;
}
if (pkt && pkt->data && (pkt->len > 0)) {
encoder->compressed_data.resize(pkt->len);
memcpy(encoder->compressed_data.data(), pkt->data, pkt->len);
encoder->data_read = false;
}
if (pkt) {
rav1e_packet_unref(pkt);
}
return heif_error_ok;
}
struct heif_error rav1e_get_compressed_data(void* encoder_raw, uint8_t** data, int* size,
enum heif_encoded_data_type* type)
{
auto* encoder = (struct encoder_struct_rav1e*) encoder_raw;
if (encoder->data_read) {
*data = nullptr;
*size = 0;
}
else {
*data = encoder->compressed_data.data();
*size = (int) encoder->compressed_data.size();
encoder->data_read = true;
}
return heif_error_ok;
}
static const struct heif_encoder_plugin encoder_plugin_rav1e
{
/* plugin_api_version */ 3,
/* compression_format */ heif_compression_AV1,
/* id_name */ "rav1e",
/* priority */ RAV1E_PLUGIN_PRIORITY,
/* supports_lossy_compression */ true,
/* supports_lossless_compression */ false,
/* get_plugin_name */ rav1e_plugin_name,
/* init_plugin */ rav1e_init_plugin,
/* cleanup_plugin */ rav1e_cleanup_plugin,
/* new_encoder */ rav1e_new_encoder,
/* free_encoder */ rav1e_free_encoder,
/* set_parameter_quality */ rav1e_set_parameter_quality,
/* get_parameter_quality */ rav1e_get_parameter_quality,
/* set_parameter_lossless */ rav1e_set_parameter_lossless,
/* get_parameter_lossless */ rav1e_get_parameter_lossless,
/* set_parameter_logging_level */ rav1e_set_parameter_logging_level,
/* get_parameter_logging_level */ rav1e_get_parameter_logging_level,
/* list_parameters */ rav1e_list_parameters,
/* set_parameter_integer */ rav1e_set_parameter_integer,
/* get_parameter_integer */ rav1e_get_parameter_integer,
/* set_parameter_boolean */ rav1e_set_parameter_boolean,
/* get_parameter_boolean */ rav1e_get_parameter_boolean,
/* set_parameter_string */ rav1e_set_parameter_string,
/* get_parameter_string */ rav1e_get_parameter_string,
/* query_input_colorspace */ rav1e_query_input_colorspace,
/* encode_image */ rav1e_encode_image,
/* get_compressed_data */ rav1e_get_compressed_data,
/* query_input_colorspace (v2) */ rav1e_query_input_colorspace2,
/* query_encoded_size (v3) */ nullptr
};
const struct heif_encoder_plugin* get_encoder_plugin_rav1e()
{
return &encoder_plugin_rav1e;
}
#if PLUGIN_RAV1E
heif_plugin_info plugin_info{
1,
heif_plugin_type_encoder,
&encoder_plugin_rav1e
};
#endif