libheif/plugins/decoder_ffmpeg.cc (437 lines of code) (raw):
/*
* HEIF codec.
* Copyright (c) 2023 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 "decoder_ffmpeg.h"
#if defined(HAVE_CONFIG_H)
#include "config.h"
#endif
#include <map>
extern "C"
{
#include <libavcodec/avcodec.h>
}
class NalUnit {
public:
NalUnit();
~NalUnit();
bool set_data(const unsigned char* in_data, int n);
int size() const { return nal_data_size; }
int unit_type() const { return nal_unit_type; }
const unsigned char* data() const { return nal_data_ptr; }
int bitExtracted(int number, int bits_count, int position_nr)
{
return (((1 << bits_count) - 1) & (number >> (position_nr - 1)));
}
private:
const unsigned char* nal_data_ptr;
int nal_unit_type;
int nal_data_size;
};
struct ffmpeg_decoder
{
#define NAL_UNIT_VPS_NUT 32
#define NAL_UNIT_SPS_NUT 33
#define NAL_UNIT_PPS_NUT 34
#define NAL_UNIT_IDR_W_RADL 19
#define NAL_UNIT_IDR_N_LP 20
std::map<int,NalUnit*> NalMap;
bool strict_decoding = false;
};
static const int FFMPEG_DECODER_PLUGIN_PRIORITY = 90;
#define MAX_PLUGIN_NAME_LENGTH 80
static char plugin_name[MAX_PLUGIN_NAME_LENGTH];
static const char* ffmpeg_plugin_name()
{
snprintf(plugin_name, MAX_PLUGIN_NAME_LENGTH, "FFMPEG HEVC decoder %s", av_version_info());
plugin_name[MAX_PLUGIN_NAME_LENGTH - 1] = 0; //null-terminated
return plugin_name;
}
static void ffmpeg_init_plugin()
{
}
static void ffmpeg_deinit_plugin()
{
}
static int ffmpeg_does_support_format(enum heif_compression_format format)
{
if (format == heif_compression_HEVC) {
return FFMPEG_DECODER_PLUGIN_PRIORITY;
}
else {
return 0;
}
}
static struct heif_error ffmpeg_new_decoder(void** dec, int nthreads)
{
struct ffmpeg_decoder* decoder = new ffmpeg_decoder();
*dec = decoder;
return heif_error_success;
}
static void ffmpeg_free_decoder(void* decoder_raw)
{
struct ffmpeg_decoder* decoder = (struct ffmpeg_decoder*) decoder_raw;
delete decoder;
}
void ffmpeg_set_strict_decoding(void* decoder_raw, int flag)
{
struct ffmpeg_decoder* decoder = (ffmpeg_decoder*) decoder_raw;
decoder->strict_decoding = flag;
}
NalUnit::NalUnit()
{
nal_data_ptr = NULL;
nal_unit_type = 0;
nal_data_size = 0;
}
NalUnit::~NalUnit()
{
}
bool NalUnit::set_data(const unsigned char* in_data, int n)
{
nal_data_ptr = in_data;
nal_unit_type = bitExtracted(nal_data_ptr[0], 6, 2);
nal_data_size = n;
return true;
}
static struct heif_error ffmpeg_v1_push_data(void* decoder_raw, const void* data, size_t size)
{
struct ffmpeg_decoder* decoder = (struct ffmpeg_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,
"insufficient data" };
return err;
}
uint32_t nal_size = (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,
"insufficient data" };
return err;
}
NalUnit* nal_unit = new NalUnit();
nal_unit->set_data(cdata + ptr, nal_size);
NalUnit* old_nal_unit = decoder->NalMap[nal_unit->unit_type()];
decoder->NalMap[nal_unit->unit_type()] = nal_unit;
delete old_nal_unit;
ptr += nal_size;
}
return heif_error_success;
}
static heif_chroma ffmpeg_get_chroma_format(enum AVPixelFormat pix_fmt) {
if (pix_fmt == AV_PIX_FMT_GRAY8)
{
return heif_chroma_monochrome;
}
else if ((pix_fmt == AV_PIX_FMT_YUV420P) || (pix_fmt == AV_PIX_FMT_YUVJ420P) ||
(pix_fmt == AV_PIX_FMT_YUV420P10LE))
{
return heif_chroma_420;
}
else if (pix_fmt == AV_PIX_FMT_YUV422P)
{
return heif_chroma_422;
}
else if (pix_fmt == AV_PIX_FMT_YUV444P)
{
return heif_chroma_444;
}
// Unsupported pix_fmt
return heif_chroma_undefined;
}
static int ffmpeg_get_chroma_width(const AVFrame* frame, heif_channel channel, heif_chroma chroma)
{
if (channel == heif_channel_Y)
{
return frame->width;
}
else if (chroma == heif_chroma_420 || chroma == heif_chroma_422)
{
return (frame->width + 1) / 2;
}
else
{
return frame->width;
}
}
static int ffmpeg_get_chroma_height(const AVFrame* frame, heif_channel channel, heif_chroma chroma)
{
if (channel == heif_channel_Y)
{
return frame->height;
}
else if (chroma == heif_chroma_420)
{
return (frame->height + 1) / 2;
}
else
{
return frame->height;
}
}
static struct heif_error hevc_decode(AVCodecContext* hevc_dec_ctx, AVFrame* hevc_frame, AVPacket* hevc_pkt, struct heif_image** image)
{
int ret;
ret = avcodec_send_packet(hevc_dec_ctx, hevc_pkt);
if (ret < 0) {
struct heif_error err = { heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "Error in avcodec_send_packet" };
return err;
}
ret = avcodec_receive_frame(hevc_dec_ctx, hevc_frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
struct heif_error err = { heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "avcodec_receive_frame returned EAGAIN or ERROR_EOF" };
return err;
}
else if (ret < 0) {
struct heif_error err = { heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "Error in avcodec_receive_frame" };
return err;
}
heif_chroma chroma = ffmpeg_get_chroma_format(hevc_dec_ctx->pix_fmt);
if (chroma != heif_chroma_undefined)
{
bool is_mono = (chroma == heif_chroma_monochrome);
heif_error err;
err = heif_image_create(hevc_frame->width,
hevc_frame->height,
is_mono ? heif_colorspace_monochrome : heif_colorspace_YCbCr,
chroma,
image);
if (err.code) {
return err;
}
heif_channel channel2plane[3] = {
heif_channel_Y,
heif_channel_Cb,
heif_channel_Cr
};
int nPlanes = is_mono ? 1 : 3;
for (int channel = 0; channel < nPlanes; channel++) {
int bpp = (hevc_dec_ctx->pix_fmt == AV_PIX_FMT_YUV420P10LE) ? 10 : 8;
int stride = hevc_frame->linesize[channel];
const uint8_t* data = hevc_frame->data[channel];
int w = ffmpeg_get_chroma_width(hevc_frame, channel2plane[channel], chroma);
int h = ffmpeg_get_chroma_height(hevc_frame, channel2plane[channel], chroma);
if (w <= 0 || h <= 0) {
heif_image_release(*image);
err = { heif_error_Decoder_plugin_error,
heif_suberror_Invalid_image_size,
"invalid image size" };
return err;
}
err = heif_image_add_plane(*image, channel2plane[channel], 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[channel], &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_success;
}
else
{
struct heif_error err = { heif_error_Unsupported_feature,
heif_suberror_Unsupported_color_conversion,
"Pixel format not implemented" };
return err;
}
}
static struct heif_error ffmpeg_v1_decode_image(void* decoder_raw,
struct heif_image** out_img)
{
struct ffmpeg_decoder* decoder = (struct ffmpeg_decoder*) decoder_raw;
int heif_idrpic_size;
int heif_vps_size;
int heif_sps_size;
int heif_pps_size;
const unsigned char* heif_vps_data;
const unsigned char* heif_sps_data;
const unsigned char* heif_pps_data;
const unsigned char* heif_idrpic_data;
if ((decoder->NalMap.count(NAL_UNIT_VPS_NUT) > 0)
&& (decoder->NalMap.count(NAL_UNIT_SPS_NUT) > 0)
&& (decoder->NalMap.count(NAL_UNIT_PPS_NUT) > 0)
)
{
heif_vps_size = decoder->NalMap[NAL_UNIT_VPS_NUT]->size();
heif_vps_data = decoder->NalMap[NAL_UNIT_VPS_NUT]->data();
heif_sps_size = decoder->NalMap[NAL_UNIT_SPS_NUT]->size();
heif_sps_data = decoder->NalMap[NAL_UNIT_SPS_NUT]->data();
heif_pps_size = decoder->NalMap[NAL_UNIT_PPS_NUT]->size();
heif_pps_data = decoder->NalMap[NAL_UNIT_PPS_NUT]->data();
}
else
{
struct heif_error err = { heif_error_Decoder_plugin_error,
heif_suberror_End_of_data,
"Unexpected end of data" };
return err;
}
if ((decoder->NalMap.count(NAL_UNIT_IDR_W_RADL) > 0) || (decoder->NalMap.count(NAL_UNIT_IDR_N_LP) > 0))
{
if (decoder->NalMap.count(NAL_UNIT_IDR_W_RADL) > 0)
{
heif_idrpic_data = decoder->NalMap[NAL_UNIT_IDR_W_RADL]->data();
heif_idrpic_size = decoder->NalMap[NAL_UNIT_IDR_W_RADL]->size();
}
else
{
heif_idrpic_data = decoder->NalMap[NAL_UNIT_IDR_N_LP]->data();
heif_idrpic_size = decoder->NalMap[NAL_UNIT_IDR_N_LP]->size();
}
}
else
{
struct heif_error err = { heif_error_Decoder_plugin_error,
heif_suberror_End_of_data,
"Unexpected end of data" };
return err;
}
const char hevc_AnnexB_StartCode[] = { 0x00, 0x00, 0x00, 0x01 };
int hevc_AnnexB_StartCode_size = 4;
size_t hevc_data_size = heif_vps_size + heif_sps_size + heif_pps_size + heif_idrpic_size + 4 * hevc_AnnexB_StartCode_size;
uint8_t* hevc_data = (uint8_t*)malloc(hevc_data_size + AV_INPUT_BUFFER_PADDING_SIZE);
//Copy hevc pps data
uint8_t* hevc_data_ptr = hevc_data;
memcpy(hevc_data_ptr, hevc_AnnexB_StartCode, hevc_AnnexB_StartCode_size);
hevc_data_ptr += hevc_AnnexB_StartCode_size;
memcpy(hevc_data_ptr, heif_vps_data, heif_vps_size);
hevc_data_ptr += heif_vps_size;
//Copy hevc sps data
memcpy(hevc_data_ptr, hevc_AnnexB_StartCode, hevc_AnnexB_StartCode_size);
hevc_data_ptr += hevc_AnnexB_StartCode_size;
memcpy(hevc_data_ptr, heif_sps_data, heif_sps_size);
hevc_data_ptr += heif_sps_size;
//Copy hevc pps data
memcpy(hevc_data_ptr, hevc_AnnexB_StartCode, hevc_AnnexB_StartCode_size);
hevc_data_ptr += hevc_AnnexB_StartCode_size;
memcpy(hevc_data_ptr, heif_pps_data, heif_pps_size);
hevc_data_ptr += heif_pps_size;
//Copy hevc idrpic data
memcpy(hevc_data_ptr, hevc_AnnexB_StartCode, hevc_AnnexB_StartCode_size);
hevc_data_ptr += hevc_AnnexB_StartCode_size;
memcpy(hevc_data_ptr, heif_idrpic_data, heif_idrpic_size);
//decoder->NalMap not needed anymore
for (auto current = decoder->NalMap.begin(); current != decoder->NalMap.end(); ++current) {
delete current->second;
}
decoder->NalMap.clear();
const AVCodec* hevc_codec = NULL;
AVCodecParserContext* hevc_parser = NULL;
AVCodecContext* hevc_codecContext = NULL;
AVPacket* hevc_pkt = NULL;
AVFrame* hevc_frame = NULL;
AVCodecParameters* hevc_codecParam = NULL;
struct heif_color_profile_nclx* nclx = NULL;
int ret = 0;
struct heif_error err = heif_error_success;
uint8_t* parse_hevc_data = NULL;
int parse_hevc_data_size = 0;
uint8_t video_full_range_flag = 0;
uint8_t color_primaries = 0;
uint8_t transfer_characteristics = 0;
uint8_t matrix_coefficients = 0;
hevc_pkt = av_packet_alloc();
if (!hevc_pkt) {
err = { heif_error_Memory_allocation_error, heif_suberror_Unspecified, "av_packet_alloc returned error" };
goto errexit;
}
// Find HEVC video decoder
hevc_codec = avcodec_find_decoder(AV_CODEC_ID_HEVC);
if (!hevc_codec) {
err = { heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "avcodec_find_decoder(AV_CODEC_ID_HEVC) returned error" };
goto errexit;
}
hevc_parser = av_parser_init(hevc_codec->id);
if (!hevc_parser) {
err = { heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "av_parser_init returned error" };
goto errexit;
}
hevc_codecContext = avcodec_alloc_context3(hevc_codec);
if (!hevc_codecContext) {
err = { heif_error_Memory_allocation_error, heif_suberror_Unspecified, "avcodec_alloc_context3 returned error" };
goto errexit;
}
/* open it */
if (avcodec_open2(hevc_codecContext, hevc_codec, NULL) < 0) {
err = { heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "avcodec_open2 returned error" };
goto errexit;
}
hevc_frame = av_frame_alloc();
if (!hevc_frame) {
err = { heif_error_Memory_allocation_error, heif_suberror_Unspecified, "av_frame_alloc returned error" };
goto errexit;
}
parse_hevc_data = hevc_data;
parse_hevc_data_size = (int)hevc_data_size;
while (parse_hevc_data_size > 0) {
hevc_parser->flags = PARSER_FLAG_COMPLETE_FRAMES;
ret = av_parser_parse2(hevc_parser, hevc_codecContext, &hevc_pkt->data, &hevc_pkt->size, parse_hevc_data, parse_hevc_data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
if (ret < 0) {
err = { heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "av_parser_parse2 returned error" };
goto errexit;
}
parse_hevc_data += ret;
parse_hevc_data_size -= ret;
if (hevc_pkt->size)
{
err = hevc_decode(hevc_codecContext, hevc_frame, hevc_pkt, out_img);
if (err.code != heif_error_Ok)
goto errexit;
}
}
hevc_codecParam = avcodec_parameters_alloc();
if (!hevc_codecParam) {
err = { heif_error_Memory_allocation_error, heif_suberror_Unspecified, "avcodec_parameters_alloc returned error" };
goto errexit;
}
if (avcodec_parameters_from_context(hevc_codecParam, hevc_codecContext) < 0)
{
err = { heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "avcodec_parameters_from_context returned error" };
goto errexit;
}
video_full_range_flag = (hevc_codecParam->color_range == AVCOL_RANGE_JPEG) ? 1 : 0;
color_primaries = hevc_codecParam->color_primaries;
transfer_characteristics = hevc_codecParam->color_trc;
matrix_coefficients = hevc_codecParam->color_space;
nclx = heif_nclx_color_profile_alloc();
heif_nclx_color_profile_set_color_primaries(nclx, static_cast<uint16_t>(color_primaries));
heif_nclx_color_profile_set_transfer_characteristics(nclx, static_cast<uint16_t>(transfer_characteristics));
heif_nclx_color_profile_set_matrix_coefficients(nclx, static_cast<uint16_t>(matrix_coefficients));
nclx->full_range_flag = (bool)video_full_range_flag;
heif_image_set_nclx_color_profile(*out_img, nclx);
errexit:
if (hevc_codecParam) avcodec_parameters_free(&hevc_codecParam);
if (hevc_data) free(hevc_data);
if (hevc_parser) av_parser_close(hevc_parser);
if (hevc_codecContext) avcodec_free_context(&hevc_codecContext);
if (hevc_frame) av_frame_free(&hevc_frame);
if (hevc_pkt) av_packet_free(&hevc_pkt);
if (nclx) heif_nclx_color_profile_free(nclx);
return err;
}
static const struct heif_decoder_plugin decoder_ffmpeg
{
3,
ffmpeg_plugin_name,
ffmpeg_init_plugin,
ffmpeg_deinit_plugin,
ffmpeg_does_support_format,
ffmpeg_new_decoder,
ffmpeg_free_decoder,
ffmpeg_v1_push_data,
ffmpeg_v1_decode_image,
ffmpeg_set_strict_decoding,
"ffmpeg"
};
const struct heif_decoder_plugin* get_decoder_plugin_ffmpeg()
{
return &decoder_ffmpeg;
}
#if PLUGIN_FFMPEG_DECODER
heif_plugin_info plugin_info{
1,
heif_plugin_type_decoder,
&decoder_ffmpeg
};
#endif