in libheif/plugins/encoder_x265.cc [643:916]
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;
}