libheif/plugins/encoder_x265.cc (753 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_x265.h"
#include <memory>
#include <sstream>
#include <string>
#include <cstring>
#include <cstdio>
#include <cassert>
#include <vector>
extern "C" {
#include <x265.h>
}
static const char* kError_unsupported_bit_depth = "Bit depth not supported by x265";
static const char* kError_unsupported_image_size = "Images smaller than 16 pixels are not supported";
enum parameter_type
{
UndefinedType, Int, Bool, String
};
struct parameter
{
parameter_type type = UndefinedType;
std::string name;
int value_int = 0; // also used for boolean
std::string value_string;
};
struct encoder_struct_x265
{
x265_encoder* encoder = nullptr;
x265_nal* nals = nullptr;
uint32_t num_nals = 0;
uint32_t nal_output_counter = 0;
int bit_depth = 0;
heif_chroma chroma;
// --- parameters
std::vector<parameter> parameters;
void add_param(const parameter&);
void add_param(const std::string& name, int value);
void add_param(const std::string& name, bool value);
void add_param(const std::string& name, const std::string& value);
parameter get_param(const std::string& name) const;
std::string preset;
std::string tune;
int logLevel = X265_LOG_NONE;
};
void encoder_struct_x265::add_param(const parameter& p)
{
// if there is already a parameter of that name, remove it from list
for (size_t i = 0; i < parameters.size(); i++) {
if (parameters[i].name == p.name) {
for (size_t k = i + 1; k < parameters.size(); k++) {
parameters[k - 1] = parameters[k];
}
parameters.pop_back();
break;
}
}
// and add the new parameter at the end of the list
parameters.push_back(p);
}
void encoder_struct_x265::add_param(const std::string& name, int value)
{
parameter p;
p.type = Int;
p.name = name;
p.value_int = value;
add_param(p);
}
void encoder_struct_x265::add_param(const std::string& name, bool value)
{
parameter p;
p.type = Bool;
p.name = name;
p.value_int = value;
add_param(p);
}
void encoder_struct_x265::add_param(const std::string& name, const std::string& value)
{
parameter p;
p.type = String;
p.name = name;
p.value_string = value;
add_param(p);
}
parameter encoder_struct_x265::get_param(const std::string& name) const
{
for (size_t i = 0; i < parameters.size(); i++) {
if (parameters[i].name == name) {
return parameters[i];
}
}
return parameter();
}
static const char* kParam_preset = "preset";
static const char* kParam_tune = "tune";
static const char* kParam_TU_intra_depth = "tu-intra-depth";
static const char* kParam_complexity = "complexity";
static const char* const kParam_preset_valid_values[] = {
"ultrafast", "superfast", "veryfast", "faster", "fast", "medium",
"slow", "slower", "veryslow", "placebo", nullptr
};
static const char* const kParam_tune_valid_values[] = {
"psnr", "ssim", "grain", "fastdecode", nullptr
// note: zerolatency is missing, because we do not need it for single images
};
static const char* kParam_chroma = "chroma";
static const char* const kParam_chroma_valid_values[] = {
"420", "422", "444", nullptr
};
static const int X265_PLUGIN_PRIORITY = 100;
#define MAX_PLUGIN_NAME_LENGTH 80
static char plugin_name[MAX_PLUGIN_NAME_LENGTH];
static void x265_set_default_parameters(void* encoder);
static const char* x265_plugin_name()
{
strcpy(plugin_name, "x265 HEVC encoder");
const x265_api* api = x265_api_get(0);
const char* x265_version = ((api != nullptr && api->version_str != nullptr) ? api->version_str : "null");
if (strlen(x265_version) + strlen(plugin_name) + 4 < MAX_PLUGIN_NAME_LENGTH) {
strcat(plugin_name, " (");
strcat(plugin_name, x265_version);
strcat(plugin_name, ")");
}
return plugin_name;
}
#define MAX_NPARAMETERS 10
static struct heif_encoder_parameter x265_encoder_params[MAX_NPARAMETERS];
static const struct heif_encoder_parameter* x265_encoder_parameter_ptrs[MAX_NPARAMETERS + 1];
static void x265_init_parameters()
{
struct heif_encoder_parameter* p = x265_encoder_params;
const struct heif_encoder_parameter** d = x265_encoder_parameter_ptrs;
int i = 0;
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_preset;
p->type = heif_encoder_parameter_type_string;
p->string.default_value = "slow"; // increases computation time
p->has_default = true;
p->string.valid_values = kParam_preset_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_TU_intra_depth;
p->type = heif_encoder_parameter_type_integer;
p->integer.default_value = 2; // increases computation time
p->has_default = true;
p->integer.have_minimum_maximum = true;
p->integer.minimum = 1;
p->integer.maximum = 4;
p->integer.valid_values = NULL;
p->integer.num_valid_values = 0;
d[i++] = p++;
assert(i < MAX_NPARAMETERS);
p->version = 2;
p->name = kParam_complexity;
p->type = heif_encoder_parameter_type_integer;
p->integer.default_value = 50;
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_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++;
d[i++] = nullptr;
}
const struct heif_encoder_parameter** x265_list_parameters(void* encoder)
{
return x265_encoder_parameter_ptrs;
}
static void x265_init_plugin()
{
x265_init_parameters();
}
static void x265_cleanup_plugin()
{
x265_cleanup();
}
static struct heif_error x265_new_encoder(void** enc)
{
struct encoder_struct_x265* encoder = new encoder_struct_x265();
struct heif_error err = heif_error_ok;
// encoder has to be allocated in x265_encode_image, because it needs to know the image size
encoder->encoder = nullptr;
encoder->nals = nullptr;
encoder->num_nals = 0;
encoder->nal_output_counter = 0;
encoder->bit_depth = 8;
*enc = encoder;
// set default parameters
x265_set_default_parameters(encoder);
return err;
}
static void x265_free_encoder(void* encoder_raw)
{
struct encoder_struct_x265* encoder = (struct encoder_struct_x265*) encoder_raw;
if (encoder->encoder) {
const x265_api* api = x265_api_get(encoder->bit_depth);
api->encoder_close(encoder->encoder);
}
delete encoder;
}
static struct heif_error x265_set_parameter_quality(void* encoder_raw, int quality)
{
struct encoder_struct_x265* encoder = (struct encoder_struct_x265*) encoder_raw;
if (quality < 0 || quality > 100) {
return heif_error_invalid_parameter_value;
}
encoder->add_param(heif_encoder_parameter_name_quality, quality);
return heif_error_ok;
}
static struct heif_error x265_get_parameter_quality(void* encoder_raw, int* quality)
{
struct encoder_struct_x265* encoder = (struct encoder_struct_x265*) encoder_raw;
parameter p = encoder->get_param(heif_encoder_parameter_name_quality);
*quality = p.value_int;
return heif_error_ok;
}
static struct heif_error x265_set_parameter_lossless(void* encoder_raw, int enable)
{
struct encoder_struct_x265* encoder = (struct encoder_struct_x265*) encoder_raw;
encoder->add_param(heif_encoder_parameter_name_lossless, (bool) enable);
return heif_error_ok;
}
static struct heif_error x265_get_parameter_lossless(void* encoder_raw, int* enable)
{
struct encoder_struct_x265* encoder = (struct encoder_struct_x265*) encoder_raw;
parameter p = encoder->get_param(heif_encoder_parameter_name_lossless);
*enable = p.value_int;
return heif_error_ok;
}
static struct heif_error x265_set_parameter_logging_level(void* encoder_raw, int logging)
{
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;
return heif_error_ok;
}
static struct heif_error x265_get_parameter_logging_level(void* encoder_raw, int* loglevel)
{
struct encoder_struct_x265* encoder = (struct encoder_struct_x265*) encoder_raw;
*loglevel = encoder->logLevel;
return heif_error_ok;
}
static struct heif_error x265_set_parameter_integer(void* encoder_raw, const char* name, int value)
{
struct encoder_struct_x265* encoder = (struct encoder_struct_x265*) encoder_raw;
if (strcmp(name, heif_encoder_parameter_name_quality) == 0) {
return x265_set_parameter_quality(encoder, value);
}
else if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) {
return x265_set_parameter_lossless(encoder, value);
}
else if (strcmp(name, kParam_TU_intra_depth) == 0) {
if (value < 1 || value > 4) {
return heif_error_invalid_parameter_value;
}
encoder->add_param(name, value);
return heif_error_ok;
}
else if (strcmp(name, kParam_complexity) == 0) {
if (value < 0 || value > 100) {
return heif_error_invalid_parameter_value;
}
encoder->add_param(name, value);
return heif_error_ok;
}
return heif_error_unsupported_parameter;
}
static struct heif_error x265_get_parameter_integer(void* encoder_raw, const char* name, int* value)
{
struct encoder_struct_x265* encoder = (struct encoder_struct_x265*) encoder_raw;
if (strcmp(name, heif_encoder_parameter_name_quality) == 0) {
return x265_get_parameter_quality(encoder, value);
}
else if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) {
return x265_get_parameter_lossless(encoder, value);
}
else if (strcmp(name, kParam_TU_intra_depth) == 0) {
*value = encoder->get_param(name).value_int;
return heif_error_ok;
}
else if (strcmp(name, kParam_complexity) == 0) {
*value = encoder->get_param(name).value_int;
return heif_error_ok;
}
return heif_error_unsupported_parameter;
}
static struct heif_error x265_set_parameter_boolean(void* encoder, const char* name, int value)
{
if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) {
return x265_set_parameter_lossless(encoder, value);
}
return heif_error_unsupported_parameter;
}
// Unused, will use "x265_get_parameter_integer" instead.
/*
static struct heif_error x265_get_parameter_boolean(void* encoder, const char* name, int* value)
{
if (strcmp(name, heif_encoder_parameter_name_lossless)==0) {
return x265_get_parameter_lossless(encoder,value);
}
return heif_error_unsupported_parameter;
}
*/
static bool string_list_contains(const char* const* values_list, const char* value)
{
for (int i = 0; values_list[i]; i++) {
if (strcmp(values_list[i], value) == 0) {
return true;
}
}
return false;
}
static struct heif_error x265_set_parameter_string(void* encoder_raw, const char* name, const char* value)
{
struct encoder_struct_x265* encoder = (struct encoder_struct_x265*) encoder_raw;
if (strcmp(name, kParam_preset) == 0) {
if (!string_list_contains(kParam_preset_valid_values, value)) {
return heif_error_invalid_parameter_value;
}
encoder->preset = value;
return heif_error_ok;
}
else if (strcmp(name, kParam_tune) == 0) {
if (!string_list_contains(kParam_tune_valid_values, value)) {
return heif_error_invalid_parameter_value;
}
encoder->tune = value;
return heif_error_ok;
}
else if (strncmp(name, "x265:", 5) == 0) {
encoder->add_param(name, std::string(value));
return heif_error_ok;
}
else 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;
}
static struct heif_error x265_get_parameter_string(void* encoder_raw, const char* name,
char* value, int value_size)
{
struct encoder_struct_x265* encoder = (struct encoder_struct_x265*) encoder_raw;
if (strcmp(name, kParam_preset) == 0) {
save_strcpy(value, value_size, encoder->preset.c_str());
return heif_error_ok;
}
else if (strcmp(name, kParam_tune) == 0) {
save_strcpy(value, value_size, encoder->tune.c_str());
return heif_error_ok;
}
else 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 x265_set_default_parameters(void* encoder)
{
for (const struct heif_encoder_parameter** p = x265_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:
x265_set_parameter_integer(encoder, param->name, param->integer.default_value);
break;
case heif_encoder_parameter_type_boolean:
x265_set_parameter_boolean(encoder, param->name, param->boolean.default_value);
break;
case heif_encoder_parameter_type_string:
x265_set_parameter_string(encoder, param->name, param->string.default_value);
break;
}
}
}
}
static void x265_query_input_colorspace(heif_colorspace* colorspace, heif_chroma* chroma)
{
if (*colorspace == heif_colorspace_monochrome) {
*colorspace = heif_colorspace_monochrome;
*chroma = heif_chroma_monochrome;
}
else {
*colorspace = heif_colorspace_YCbCr;
*chroma = heif_chroma_420;
}
}
static void x265_query_input_colorspace2(void* encoder_raw, heif_colorspace* colorspace, heif_chroma* chroma)
{
auto* encoder = (struct encoder_struct_x265*) encoder_raw;
if (*colorspace == heif_colorspace_monochrome) {
*colorspace = heif_colorspace_monochrome;
*chroma = heif_chroma_monochrome;
}
else {
*colorspace = heif_colorspace_YCbCr;
*chroma = encoder->chroma;
}
}
static int rounded_size(int s)
{
s = (s + 1) & ~1;
if (s < 64) {
s = 64;
}
return s;
}
static struct heif_error x265_encode_image(void* encoder_raw, const struct heif_image* image,
heif_image_input_class input_class)
{
struct encoder_struct_x265* encoder = (struct encoder_struct_x265*) encoder_raw;
// close previous encoder if there is still one hanging around
if (encoder->encoder) {
const x265_api* api = x265_api_get(encoder->bit_depth);
api->encoder_close(encoder->encoder);
encoder->encoder = nullptr;
}
int bit_depth = heif_image_get_bits_per_pixel_range(image, heif_channel_Y);
bool isGreyscale = (heif_image_get_colorspace(image) == heif_colorspace_monochrome);
heif_chroma chroma = heif_image_get_chroma_format(image);
const x265_api* api = x265_api_get(bit_depth);
if (api == nullptr) {
struct heif_error err = {
heif_error_Encoder_plugin_error,
heif_suberror_Unsupported_bit_depth,
kError_unsupported_bit_depth
};
return err;
}
x265_param* param = api->param_alloc();
api->param_default_preset(param, encoder->preset.c_str(), encoder->tune.c_str());
if (bit_depth == 8) api->param_apply_profile(param, "mainstillpicture");
else if (bit_depth == 10) api->param_apply_profile(param, "main10-intra");
else if (bit_depth == 12) api->param_apply_profile(param, "main12-intra");
else {
api->param_free(param);
return heif_error_unsupported_parameter;
}
param->fpsNum = 1;
param->fpsDenom = 1;
// x265 cannot encode images smaller than one CTU size
// https://bitbucket.org/multicoreware/x265/issues/475/x265-does-not-allow-image-sizes-smaller
// -> use smaller CTU sizes for very small images
const char* ctu = NULL;
int ctuSize = 64;
#if 1
while (ctuSize > 16 &&
(heif_image_get_width(image, heif_channel_Y) < ctuSize ||
heif_image_get_height(image, heif_channel_Y) < ctuSize)) {
ctuSize /= 2;
}
if (ctuSize < 16) {
api->param_free(param);
struct heif_error err = {
heif_error_Encoder_plugin_error,
heif_suberror_Invalid_parameter_value,
kError_unsupported_image_size
};
return err;
}
#else
// TODO: There seems to be a bug in x265 where increasing the CTU size between
// multiple encoding jobs causes a segmentation fault. E.g. encoding multiple
// times with a CTU of 16 works, the next encoding with a CTU of 32 crashes.
// Use hardcoded value of 64 and reject images that are too small.
if (heif_image_get_width(image, heif_channel_Y) < ctuSize ||
heif_image_get_height(image, heif_channel_Y) < ctuSize) {
api->param_free(param);
struct heif_error err = {
heif_error_Encoder_plugin_error,
heif_suberror_Invalid_parameter_value,
kError_unsupported_image_size
};
return err;
}
#endif
// ctuSize should be a power of 2 in [16;64]
switch (ctuSize) {
case 64:
ctu = "64";
break;
case 32:
ctu = "32";
break;
case 16:
ctu = "16";
break;
default:
struct heif_error err = {
heif_error_Encoder_plugin_error,
heif_suberror_Invalid_parameter_value,
kError_unsupported_image_size
};
return err;
}
// BPG uses CQP. It does not seem to be better though.
// param->rc.rateControlMode = X265_RC_CQP;
// param->rc.qp = (100 - encoder->quality)/2;
param->totalFrames = 1;
if (isGreyscale) {
param->internalCsp = X265_CSP_I400;
}
else if (chroma == heif_chroma_420) {
param->internalCsp = X265_CSP_I420;
}
else if (chroma == heif_chroma_422) {
param->internalCsp = X265_CSP_I422;
}
else if (chroma == heif_chroma_444) {
param->internalCsp = X265_CSP_I444;
}
if (chroma != heif_chroma_monochrome) {
int w = heif_image_get_width(image, heif_channel_Y);
int h = heif_image_get_height(image, heif_channel_Y);
if (chroma != heif_chroma_444) { w = (w + 1) / 2; }
if (chroma == heif_chroma_420) { h = (h + 1) / 2; }
assert(heif_image_get_width(image, heif_channel_Cb)==w);
assert(heif_image_get_width(image, heif_channel_Cr)==w);
assert(heif_image_get_height(image, heif_channel_Cb)==h);
assert(heif_image_get_height(image, heif_channel_Cr)==h);
(void) w;
(void) h;
}
api->param_parse(param, "info", "0");
api->param_parse(param, "limit-modes", "0");
api->param_parse(param, "limit-refs", "0");
api->param_parse(param, "ctu", ctu);
api->param_parse(param, "rskip", "0");
api->param_parse(param, "rect", "1");
api->param_parse(param, "amp", "1");
api->param_parse(param, "aq-mode", "1");
api->param_parse(param, "psy-rd", "1.0");
api->param_parse(param, "psy-rdoq", "1.0");
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);
if (nclx) {
api->param_parse(param, "range", nclx->full_range_flag ? "full" : "limited");
}
else {
api->param_parse(param, "range", "full");
}
if (nclx &&
(input_class == heif_image_input_class_normal ||
input_class == heif_image_input_class_thumbnail)) {
{
std::stringstream sstr;
sstr << nclx->color_primaries;
api->param_parse(param, "colorprim", sstr.str().c_str());
}
{
std::stringstream sstr;
sstr << nclx->transfer_characteristics;
api->param_parse(param, "transfer", sstr.str().c_str());
}
{
std::stringstream sstr;
sstr << nclx->matrix_coefficients;
api->param_parse(param, "colormatrix", sstr.str().c_str());
}
}
for (const auto& p : encoder->parameters) {
if (p.name == heif_encoder_parameter_name_quality) {
// quality=0 -> crf=50
// quality=50 -> crf=25
// quality=100 -> crf=0
param->rc.rfConstant = (100 - p.value_int) / 2.0;
}
else if (p.name == heif_encoder_parameter_name_lossless) {
param->bLossless = p.value_int;
}
else if (p.name == kParam_TU_intra_depth) {
auto valString = std::to_string(p.value_int);
api->param_parse(param, "tu-intra-depth", valString.c_str());
}
else if (p.name == kParam_complexity) {
const int complexity = p.value_int;
if (complexity >= 60) {
api->param_parse(param, "rd-refine", "1"); // increases computation time
api->param_parse(param, "rd", "6");
}
if (complexity >= 70) {
api->param_parse(param, "cu-lossless", "1"); // increases computation time
}
if (complexity >= 90) {
api->param_parse(param, "wpp", "0"); // setting to 0 significantly increases computation time
}
}
else if (strncmp(p.name.c_str(), "x265:", 5) == 0) {
std::string x265p = p.name.substr(5);
api->param_parse(param, x265p.c_str(), p.value_string.c_str());
}
}
param->logLevel = encoder->logLevel;
param->sourceWidth = heif_image_get_width(image, heif_channel_Y);
param->sourceHeight = heif_image_get_height(image, heif_channel_Y);
param->internalBitDepth = bit_depth;
param->sourceWidth = rounded_size(param->sourceWidth);
param->sourceHeight = rounded_size(param->sourceHeight);
// Note: it is ok to cast away the const, as the image content is not changed.
// However, we have to guarantee that there are no plane pointers or stride values kept over calling the svt_encode_image() function.
err = heif_image_extend_padding_to_size(const_cast<struct heif_image*>(image),
param->sourceWidth,
param->sourceHeight);
if (err.code) {
return err;
}
x265_picture* pic = api->picture_alloc();
api->picture_init(param, pic);
if (isGreyscale) {
pic->planes[0] = (void*) heif_image_get_plane_readonly(image, heif_channel_Y, &pic->stride[0]);
}
else {
pic->planes[0] = (void*) heif_image_get_plane_readonly(image, heif_channel_Y, &pic->stride[0]);
pic->planes[1] = (void*) heif_image_get_plane_readonly(image, heif_channel_Cb, &pic->stride[1]);
pic->planes[2] = (void*) heif_image_get_plane_readonly(image, heif_channel_Cr, &pic->stride[2]);
}
pic->bitDepth = bit_depth;
encoder->bit_depth = bit_depth;
encoder->encoder = api->encoder_open(param);
api->encoder_encode(encoder->encoder,
&encoder->nals,
&encoder->num_nals,
pic,
NULL);
api->picture_free(pic);
api->param_free(param);
encoder->nal_output_counter = 0;
return heif_error_ok;
}
static struct heif_error x265_get_compressed_data(void* encoder_raw, uint8_t** data, int* size,
enum heif_encoded_data_type* type)
{
struct encoder_struct_x265* encoder = (struct encoder_struct_x265*) encoder_raw;
if (encoder->encoder == nullptr) {
*data = nullptr;
*size = 0;
return heif_error_ok;
}
const x265_api* api = x265_api_get(encoder->bit_depth);
for (;;) {
while (encoder->nal_output_counter < encoder->num_nals) {
*data = encoder->nals[encoder->nal_output_counter].payload;
*size = encoder->nals[encoder->nal_output_counter].sizeBytes;
encoder->nal_output_counter++;
// --- skip start code ---
// skip '0' bytes
while (**data == 0 && *size > 0) {
(*data)++;
(*size)--;
}
// skip '1' byte
(*data)++;
(*size)--;
// --- skip NALs with irrelevant data ---
if (*size >= 3 && (*data)[0] == 0x4e && (*data)[2] == 5) {
// skip "unregistered user data SEI"
}
else {
// output NAL
return heif_error_ok;
}
}
encoder->nal_output_counter = 0;
int result = api->encoder_encode(encoder->encoder,
&encoder->nals,
&encoder->num_nals,
NULL,
NULL);
if (result <= 0) {
*data = nullptr;
*size = 0;
return heif_error_ok;
}
}
}
static const struct heif_encoder_plugin encoder_plugin_x265
{
/* plugin_api_version */ 2,
/* compression_format */ heif_compression_HEVC,
/* id_name */ "x265",
/* priority */ X265_PLUGIN_PRIORITY,
/* supports_lossy_compression */ true,
/* supports_lossless_compression */ true,
/* get_plugin_name */ x265_plugin_name,
/* init_plugin */ x265_init_plugin,
/* cleanup_plugin */ x265_cleanup_plugin,
/* new_encoder */ x265_new_encoder,
/* free_encoder */ x265_free_encoder,
/* set_parameter_quality */ x265_set_parameter_quality,
/* get_parameter_quality */ x265_get_parameter_quality,
/* set_parameter_lossless */ x265_set_parameter_lossless,
/* get_parameter_lossless */ x265_get_parameter_lossless,
/* set_parameter_logging_level */ x265_set_parameter_logging_level,
/* get_parameter_logging_level */ x265_get_parameter_logging_level,
/* list_parameters */ x265_list_parameters,
/* set_parameter_integer */ x265_set_parameter_integer,
/* get_parameter_integer */ x265_get_parameter_integer,
/* set_parameter_boolean */ x265_set_parameter_integer, // boolean also maps to integer function
/* get_parameter_boolean */ x265_get_parameter_integer, // boolean also maps to integer function
/* set_parameter_string */ x265_set_parameter_string,
/* get_parameter_string */ x265_get_parameter_string,
/* query_input_colorspace */ x265_query_input_colorspace,
/* encode_image */ x265_encode_image,
/* get_compressed_data */ x265_get_compressed_data,
/* query_input_colorspace (v2) */ x265_query_input_colorspace2
};
const struct heif_encoder_plugin* get_encoder_plugin_x265()
{
return &encoder_plugin_x265;
}
#if PLUGIN_X265
heif_plugin_info plugin_info {
1,
heif_plugin_type_encoder,
&encoder_plugin_x265
};
#endif