libheif/plugins/decoder_libde265.cc (290 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 "libheif/heif_colorconversion.h"
//#include "libheif/heif_api_structs.h"
#include "decoder_libde265.h"
#include <assert.h>
#include <memory>
#include <cstring>
#include <libde265/de265.h>
struct libde265_decoder
{
de265_decoder_context* ctx;
bool strict_decoding = false;
};
static const char kEmptyString[] = "";
static const char kSuccess[] = "Success";
static const int LIBDE265_PLUGIN_PRIORITY = 100;
#define MAX_PLUGIN_NAME_LENGTH 80
static char plugin_name[MAX_PLUGIN_NAME_LENGTH];
static const char* libde265_plugin_name()
{
strcpy(plugin_name, "libde265 HEVC decoder");
const char* libde265_version = de265_get_version();
if (strlen(libde265_version) + 10 < MAX_PLUGIN_NAME_LENGTH) {
strcat(plugin_name, ", version ");
strcat(plugin_name, libde265_version);
}
return plugin_name;
}
static void libde265_init_plugin()
{
de265_init();
}
static void libde265_deinit_plugin()
{
de265_free();
}
static int libde265_does_support_format(enum heif_compression_format format)
{
if (format == heif_compression_HEVC) {
return LIBDE265_PLUGIN_PRIORITY;
}
else {
return 0;
}
}
static struct heif_error convert_libde265_image_to_heif_image(struct libde265_decoder* decoder,
const struct de265_image* de265img,
struct heif_image** image)
{
bool is_mono = (de265_get_chroma_format(de265img) == de265_chroma_mono);
heif_error err;
err = heif_image_create(de265_get_image_width(de265img, 0),
de265_get_image_height(de265img, 0),
is_mono ? heif_colorspace_monochrome : heif_colorspace_YCbCr,
(heif_chroma) de265_get_chroma_format(de265img),
image);
if (err.code) {
return err;
}
// --- transfer data from de265_image to HeifPixelImage
heif_channel channel2plane[3] = {
heif_channel_Y,
heif_channel_Cb,
heif_channel_Cr
};
int bpp = de265_get_bits_per_pixel(de265img, 0);
int num_planes = (is_mono ? 1 : 3);
for (int c = 0; c < num_planes; c++) {
if (de265_get_bits_per_pixel(de265img, c) != bpp) {
heif_image_release(*image);
err = {heif_error_Unsupported_feature,
heif_suberror_Unsupported_color_conversion,
"Channels with different number of bits per pixel are not supported"};
return err;
}
int stride;
const uint8_t* data = de265_get_image_plane(de265img, c, &stride);
int w = de265_get_image_width(de265img, c);
int h = de265_get_image_height(de265img, c);
if (w <= 0 || h <= 0) {
heif_image_release(*image);
err = {heif_error_Decoder_plugin_error,
heif_suberror_Invalid_image_size,
kEmptyString};
return err;
}
err = heif_image_add_plane(*image, channel2plane[c], w,h, bpp);
if (err.code) {
heif_image_release(*image);
return err;
}
int dst_stride;
uint8_t* dst_mem = heif_image_get_plane(*image, channel2plane[c], &dst_stride);
int bytes_per_pixel = (bpp + 7) / 8;
for (int y = 0; y < h; y++) {
memcpy(dst_mem + y * dst_stride, data + y * stride, w * bytes_per_pixel);
}
}
return {heif_error_Ok, heif_suberror_Unspecified, kSuccess};
}
static struct heif_error libde265_new_decoder(void** dec, int nthreads)
{
struct libde265_decoder* decoder = new libde265_decoder();
struct heif_error err = {heif_error_Ok, heif_suberror_Unspecified, kSuccess};
decoder->ctx = de265_new_decoder();
#if defined(__EMSCRIPTEN__)
// Speed up decoding from JavaScript.
de265_set_parameter_bool(decoder->ctx, DE265_DECODER_PARAM_DISABLE_DEBLOCKING, 1);
de265_set_parameter_bool(decoder->ctx, DE265_DECODER_PARAM_DISABLE_SAO, 1);
#else
// Worker threads are not supported when running on Emscripten.
de265_start_worker_threads(decoder->ctx, nthreads);
#endif
*dec = decoder;
return err;
}
static void libde265_free_decoder(void* decoder_raw)
{
struct libde265_decoder* decoder = (struct libde265_decoder*) decoder_raw;
de265_error err = de265_free_decoder(decoder->ctx);
(void) err;
delete decoder;
}
void libde265_set_strict_decoding(void* decoder_raw, int flag)
{
struct libde265_decoder* decoder = (libde265_decoder*) decoder_raw;
decoder->strict_decoding = flag;
}
#if LIBDE265_NUMERIC_VERSION >= 0x02000000
static struct heif_error libde265_v2_push_data(void* decoder_raw, const void* data, size_t size)
{
struct libde265_decoder* decoder = (struct libde265_decoder*)decoder_raw;
const uint8_t* cdata = (const uint8_t*)data;
size_t ptr=0;
while (ptr < size) {
if (4 > size - ptr) {
struct heif_error err = { heif_error_Decoder_plugin_error,
heif_suberror_End_of_data,
kEmptyString };
return err;
}
// TODO: the size of the NAL unit length variable is defined in the hvcC header.
// We should not assume that it is always 4 bytes.
uint32_t nal_size = (uint32_t)((cdata[ptr]<<24) | (cdata[ptr+1]<<16) | (cdata[ptr+2]<<8) | (cdata[ptr+3]));
ptr+=4;
if (nal_size > size - ptr) {
//sstr << "NAL size (" << size32 << ") exceeds available data in file ("
//<< data_bytes_left_to_read << ")";
struct heif_error err = { heif_error_Decoder_plugin_error,
heif_suberror_End_of_data,
kEmptyString };
return err;
}
de265_push_NAL(decoder->ctx, cdata+ptr, nal_size, 0, nullptr);
ptr += nal_size;
}
struct heif_error err = { heif_error_Ok, heif_suberror_Unspecified, kSuccess };
return err;
}
static struct heif_error libde265_v2_decode_image(void* decoder_raw,
struct heif_image** out_img)
{
struct libde265_decoder* decoder = (struct libde265_decoder*)decoder_raw;
de265_push_end_of_stream(decoder->ctx);
int action = de265_get_action(decoder->ctx, 1);
// TODO: read NCLX from h265 bitstream
// TODO(farindk): Set "err" if no image was decoded.
if (action==de265_action_get_image) {
const de265_image* img = de265_get_next_picture(decoder->ctx);
if (img) {
struct heif_error err = convert_libde265_image_to_heif_image(decoder, img,
out_img);
de265_release_picture(img);
return err;
}
}
struct heif_error err = { heif_error_Decoder_plugin_error, heif_suberror_Unspecified, kEmptyString };
return err;
}
#else
static struct heif_error libde265_v1_push_data(void* decoder_raw, const void* data, size_t size)
{
struct libde265_decoder* decoder = (struct libde265_decoder*) decoder_raw;
const uint8_t* cdata = (const uint8_t*) data;
size_t ptr = 0;
while (ptr < size) {
if (4 > size - ptr) {
struct heif_error err = {heif_error_Decoder_plugin_error,
heif_suberror_End_of_data,
kEmptyString};
return err;
}
uint32_t nal_size = static_cast<uint32_t>((cdata[ptr] << 24) | (cdata[ptr + 1] << 16) | (cdata[ptr + 2] << 8) | (cdata[ptr + 3]));
ptr += 4;
if (nal_size > size - ptr) {
struct heif_error err = {heif_error_Decoder_plugin_error,
heif_suberror_End_of_data,
kEmptyString};
return err;
}
de265_push_NAL(decoder->ctx, cdata + ptr, nal_size, 0, nullptr);
ptr += nal_size;
}
// TODO(farindk): Set "err" if data could not be pushed
//de265_push_data(decoder->ctx, data, size, 0, nullptr);
struct heif_error err = {heif_error_Ok, heif_suberror_Unspecified, kSuccess};
return err;
}
static struct heif_error libde265_v1_decode_image(void* decoder_raw,
struct heif_image** out_img)
{
struct libde265_decoder* decoder = (struct libde265_decoder*) decoder_raw;
struct heif_error err = {heif_error_Ok, heif_suberror_Unspecified, kSuccess};
de265_flush_data(decoder->ctx);
// TODO(farindk): Set "err" if no image was decoded.
int more;
de265_error decode_err;
*out_img = nullptr;
do {
more = 0;
decode_err = de265_decode(decoder->ctx, &more);
if (decode_err != DE265_OK) {
// printf("Error decoding: %s (%d)\n", de265_get_error_text(decode_err), decode_err);
break;
}
// TODO: read NCLX from h265 bitstream
const struct de265_image* image = de265_get_next_picture(decoder->ctx);
if (image) {
// TODO(farindk): Should we return the first image instead?
if (*out_img) {
heif_image_release(*out_img);
}
err = convert_libde265_image_to_heif_image(decoder, image, out_img);
if (err.code != heif_error_Ok) {
return err;
}
struct heif_color_profile_nclx* nclx = heif_nclx_color_profile_alloc();
#if LIBDE265_NUMERIC_VERSION >= 0x01000700
HEIF_WARN_OR_FAIL(decoder->strict_decoding, *out_img, heif_nclx_color_profile_set_color_primaries(nclx, static_cast<uint16_t>(de265_get_image_colour_primaries(image))),
{
heif_nclx_color_profile_free(nclx);
heif_image_release(*out_img);
*out_img = nullptr;
});
HEIF_WARN_OR_FAIL(decoder->strict_decoding, *out_img, heif_nclx_color_profile_set_transfer_characteristics(nclx, static_cast<uint16_t>(de265_get_image_transfer_characteristics(image))),
{
heif_nclx_color_profile_free(nclx);
heif_image_release(*out_img);
*out_img = nullptr;
});
HEIF_WARN_OR_FAIL(decoder->strict_decoding, *out_img, heif_nclx_color_profile_set_matrix_coefficients(nclx, static_cast<uint16_t>(de265_get_image_matrix_coefficients(image))),
{
heif_nclx_color_profile_free(nclx);
heif_image_release(*out_img);
*out_img = nullptr;
});
nclx->full_range_flag = (bool) de265_get_image_full_range_flag(image);
#endif
heif_image_set_nclx_color_profile(*out_img, nclx);
heif_nclx_color_profile_free(nclx);
de265_release_next_picture(decoder->ctx);
}
} while (more);
return err;
}
#endif
#if LIBDE265_NUMERIC_VERSION >= 0x02000000
static const struct heif_decoder_plugin decoder_libde265
{
1,
libde265_plugin_name,
libde265_init_plugin,
libde265_deinit_plugin,
libde265_does_support_format,
libde265_new_decoder,
libde265_free_decoder,
libde265_v2_push_data,
libde265_v2_decode_image
};
#else
static const struct heif_decoder_plugin decoder_libde265
{
3,
libde265_plugin_name,
libde265_init_plugin,
libde265_deinit_plugin,
libde265_does_support_format,
libde265_new_decoder,
libde265_free_decoder,
libde265_v1_push_data,
libde265_v1_decode_image,
libde265_set_strict_decoding,
"libde265"
};
#endif
const struct heif_decoder_plugin* get_decoder_plugin_libde265()
{
return &decoder_libde265;
}
#if PLUGIN_LIBDE265
heif_plugin_info plugin_info {
1,
heif_plugin_type_decoder,
&decoder_libde265
};
#endif