Coverage Report

Created: 2026-05-16 07:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libheif/libheif/plugins/decoder_libde265.cc
Line
Count
Source
1
/*
2
 * HEIF codec.
3
 * Copyright (c) 2017 Dirk Farin <dirk.farin@gmail.com>
4
 *
5
 * This file is part of libheif.
6
 *
7
 * libheif is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU Lesser General Public License as
9
 * published by the Free Software Foundation, either version 3 of
10
 * the License, or (at your option) any later version.
11
 *
12
 * libheif is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General Public License
18
 * along with libheif.  If not, see <http://www.gnu.org/licenses/>.
19
 */
20
21
#include "libheif/heif.h"
22
#include "libheif/heif_plugin.h"
23
#include "decoder_libde265.h"
24
#include <cassert>
25
#include <memory>
26
#include <cstring>
27
#include <string>
28
29
#include <libde265/de265.h>
30
31
32
33
struct libde265_decoder
34
{
35
  de265_decoder_context* ctx;
36
  bool strict_decoding = false;
37
  std::string error_message;
38
};
39
40
static const char kEmptyString[] = "";
41
static const char kSuccess[] = "Success";
42
43
static const int LIBDE265_PLUGIN_PRIORITY = 100;
44
45
0
#define MAX_PLUGIN_NAME_LENGTH 80
46
47
static char plugin_name[MAX_PLUGIN_NAME_LENGTH];
48
static constexpr char version_prefix[] = ", version ";
49
50
static const char* libde265_plugin_name()
51
0
{
52
0
  strcpy(plugin_name, "libde265 HEVC decoder");
53
54
0
  const char* libde265_version = de265_get_version();
55
56
0
  if (strlen(plugin_name) + strlen(libde265_version) + (sizeof(version_prefix) - 1) < MAX_PLUGIN_NAME_LENGTH) {
57
0
    strcat(plugin_name, version_prefix);
58
0
    strcat(plugin_name, libde265_version);
59
0
  }
60
61
0
  return plugin_name;
62
0
}
63
64
65
static void libde265_init_plugin()
66
294
{
67
294
  de265_init();
68
294
}
69
70
71
static void libde265_deinit_plugin()
72
0
{
73
0
  de265_free();
74
0
}
75
76
77
static int libde265_does_support_format(heif_compression_format format)
78
9.04k
{
79
9.04k
  if (format == heif_compression_HEVC) {
80
8.89k
    return LIBDE265_PLUGIN_PRIORITY;
81
8.89k
  }
82
154
  else {
83
154
    return 0;
84
154
  }
85
9.04k
}
86
87
88
[[maybe_unused]]
89
static int libde265_does_support_format2(const heif_decoder_plugin_compressed_format_description* format)
90
0
{
91
0
  return libde265_does_support_format(format->format);
92
0
}
93
94
95
96
static heif_error convert_libde265_image_to_heif_image(libde265_decoder* decoder,
97
                                                       const de265_image* de265img,
98
                                                       heif_image** image,
99
                                                       const heif_security_limits* limits)
100
2.94k
{
101
2.94k
  bool is_mono = (de265_get_chroma_format(de265img) == de265_chroma_mono);
102
103
2.94k
  heif_error err;
104
2.94k
  err = heif_image_create(de265_get_image_width(de265img, 0),
105
2.94k
                          de265_get_image_height(de265img, 0),
106
2.94k
                          is_mono ? heif_colorspace_monochrome : heif_colorspace_YCbCr,
107
2.94k
                          (heif_chroma) de265_get_chroma_format(de265img),
108
2.94k
                          image);
109
2.94k
  if (err.code) {
110
0
    return err;
111
0
  }
112
113
  // --- transfer data from de265_image to HeifPixelImage
114
115
2.94k
  heif_channel channel2plane[3] = {
116
2.94k
      heif_channel_Y,
117
2.94k
      heif_channel_Cb,
118
2.94k
      heif_channel_Cr
119
2.94k
  };
120
121
122
2.94k
  int bpp = de265_get_bits_per_pixel(de265img, 0);
123
124
2.94k
  int num_planes = (is_mono ? 1 : 3);
125
126
4.28k
  for (int c = 0; c < num_planes; c++) {
127
3.92k
    if (de265_get_bits_per_pixel(de265img, c) != bpp) {
128
261
      heif_image_release(*image);
129
261
      err = {heif_error_Unsupported_feature,
130
261
             heif_suberror_Unsupported_color_conversion,
131
261
             "Channels with different number of bits per pixel are not supported"};
132
261
      return err;
133
261
    }
134
135
3.66k
    int stride;
136
3.66k
    const uint8_t* data = de265_get_image_plane(de265img, c, &stride);
137
138
3.66k
    int w = de265_get_image_width(de265img, c);
139
3.66k
    int h = de265_get_image_height(de265img, c);
140
3.66k
    if (w <= 0 || h <= 0) {
141
0
      heif_image_release(*image);
142
0
      err = {heif_error_Decoder_plugin_error,
143
0
             heif_suberror_Invalid_image_size,
144
0
             kEmptyString};
145
0
      return err;
146
0
    }
147
148
3.66k
    err = heif_image_add_plane_safe(*image, channel2plane[c], w,h, bpp, limits);
149
3.66k
    if (err.code) {
150
      // copy error message to decoder object because heif_image will be released
151
2.32k
      decoder->error_message = err.message;
152
2.32k
      err.message = decoder->error_message.c_str();
153
154
2.32k
      heif_image_release(*image);
155
2.32k
      return err;
156
2.32k
    }
157
158
1.33k
    size_t dst_stride;
159
1.33k
    uint8_t* dst_mem = heif_image_get_plane2(*image, channel2plane[c], &dst_stride);
160
161
1.33k
    int bytes_per_pixel = (bpp + 7) / 8;
162
163
117k
    for (int y = 0; y < h; y++) {
164
116k
      memcpy(dst_mem + y * dst_stride, data + static_cast<size_t>(y) * stride, static_cast<size_t>(w) * bytes_per_pixel);
165
116k
    }
166
1.33k
  }
167
168
169
358
  return {heif_error_Ok, heif_suberror_Unspecified, kSuccess};
170
2.94k
}
171
172
173
174
// Create a new decoder context for decoding an image
175
heif_error libde265_new_decoder2(void** dec, const heif_decoder_plugin_options* options)
176
7.70k
{
177
7.70k
  libde265_decoder* decoder = new libde265_decoder();
178
7.70k
  heif_error err = {heif_error_Ok, heif_suberror_Unspecified, kSuccess};
179
180
7.70k
  decoder->ctx = de265_new_decoder();
181
#if defined(__EMSCRIPTEN__)
182
  // Speed up decoding from JavaScript.
183
  de265_set_parameter_bool(decoder->ctx, DE265_DECODER_PARAM_DISABLE_DEBLOCKING, 1);
184
  de265_set_parameter_bool(decoder->ctx, DE265_DECODER_PARAM_DISABLE_SAO, 1);
185
#else
186
7.70k
  int nThreads = (options->num_threads ? options->num_threads : 1);
187
188
  // Worker threads are not supported when running on Emscripten.
189
7.70k
  de265_start_worker_threads(decoder->ctx, nThreads);
190
7.70k
#endif
191
192
7.70k
  decoder->strict_decoding = options->strict_decoding;
193
194
7.70k
  *dec = decoder;
195
7.70k
  return err;
196
7.70k
}
197
198
199
static heif_error libde265_new_decoder(void** dec)
200
0
{
201
0
  heif_decoder_plugin_options options{};
202
0
  options.format = heif_compression_HEVC;
203
0
  options.num_threads = 0;
204
0
  options.strict_decoding = false;
205
206
0
  return libde265_new_decoder2(dec, &options);
207
0
}
208
209
static void libde265_free_decoder(void* decoder_raw)
210
7.70k
{
211
7.70k
  libde265_decoder* decoder = (libde265_decoder*) decoder_raw;
212
213
7.70k
  de265_error err = de265_free_decoder(decoder->ctx);
214
7.70k
  (void) err;
215
216
7.70k
  delete decoder;
217
7.70k
}
218
219
220
void libde265_set_strict_decoding(void* decoder_raw, int flag)
221
0
{
222
0
  libde265_decoder* decoder = (libde265_decoder*) decoder_raw;
223
224
0
  decoder->strict_decoding = flag;
225
0
}
226
227
228
#if LIBDE265_NUMERIC_VERSION >= 0x02000000
229
230
static heif_error libde265_v2_push_data(void* decoder_raw, const void* data, size_t size)
231
{
232
  libde265_decoder* decoder = (libde265_decoder*)decoder_raw;
233
234
  const uint8_t* cdata = (const uint8_t*)data;
235
236
  size_t ptr=0;
237
  while (ptr < size) {
238
    if (4 > size - ptr) {
239
      return { heif_error_Decoder_plugin_error,
240
               heif_suberror_End_of_data,
241
               kEmptyString };
242
    }
243
244
    // TODO: the size of the NAL unit length variable is defined in the hvcC header.
245
    // We should not assume that it is always 4 bytes.
246
    uint32_t nal_size = (uint32_t)((cdata[ptr]<<24) | (cdata[ptr+1]<<16) | (cdata[ptr+2]<<8) | (cdata[ptr+3]));
247
    ptr+=4;
248
249
    if (nal_size > size - ptr) {
250
      //sstr << "NAL size (" << size32 << ") exceeds available data in file ("
251
      //<< data_bytes_left_to_read << ")";
252
253
      return { heif_error_Decoder_plugin_error,
254
               heif_suberror_End_of_data,
255
               kEmptyString };
256
    }
257
258
    de265_push_NAL(decoder->ctx, cdata+ptr, nal_size, 0, nullptr);
259
    ptr += nal_size;
260
  }
261
262
263
  return { heif_error_Ok, heif_suberror_Unspecified, kSuccess };
264
}
265
266
267
static heif_error libde265_v2_decode_next_image(void* decoder_raw,
268
                                                heif_image** out_img,
269
                                                const heif_security_limits* limits)
270
{
271
  libde265_decoder* decoder = (libde265_decoder*)decoder_raw;
272
273
  de265_push_end_of_stream(decoder->ctx);
274
275
  int action = de265_get_action(decoder->ctx, 1);
276
277
  // TODO: read NCLX from h265 bitstream
278
279
  // TODO(farindk): Set "err" if no image was decoded.
280
  if (action==de265_action_get_image) {
281
    const de265_image* img = de265_get_next_picture(decoder->ctx);
282
    if (img) {
283
      heif_error err = convert_libde265_image_to_heif_image(decoder, img,
284
                                                                   out_img, limits);
285
      de265_release_picture(img);
286
287
      return err;
288
    }
289
  }
290
291
  return { heif_error_Decoder_plugin_error, heif_suberror_Unspecified, kEmptyString };
292
}
293
294
static heif_error libde265_v2_decode_image(void* decoder_raw,
295
                                           heif_image** out_img)
296
{
297
  auto* limits = heif_get_global_security_limits();
298
  return libde265_v2_decode_next_image(decoder_raw, out_img, limits);
299
}
300
#else
301
302
static heif_error libde265_v1_push_data2(void* decoder_raw, const void* data, size_t size, uintptr_t user_data)
303
7.22k
{
304
7.22k
  libde265_decoder* decoder = (libde265_decoder*) decoder_raw;
305
306
7.22k
  const uint8_t* cdata = (const uint8_t*) data;
307
308
7.22k
  size_t ptr = 0;
309
41.1k
  while (ptr < size) {
310
33.9k
    if (4 > size - ptr) {
311
4
      return {
312
4
        heif_error_Decoder_plugin_error,
313
4
        heif_suberror_End_of_data,
314
4
        kEmptyString
315
4
      };
316
4
    }
317
318
33.9k
    uint32_t nal_size = static_cast<uint32_t>((cdata[ptr] << 24) | (cdata[ptr + 1] << 16) | (cdata[ptr + 2] << 8) | (cdata[ptr + 3]));
319
33.9k
    ptr += 4;
320
321
33.9k
    if (nal_size > size - ptr) {
322
47
      return {
323
47
        heif_error_Decoder_plugin_error,
324
47
        heif_suberror_End_of_data,
325
47
        kEmptyString
326
47
      };
327
47
    }
328
329
#if 0
330
    FILE* fh = fopen("data.h265", "a");
331
    fputc(0, fh);
332
    fputc(0, fh);
333
    fputc(1, fh);
334
    fwrite(cdata + ptr, nal_size, 1, fh);
335
    fclose(fh);
336
337
    printf("put nal with size %d %x\n", nal_size, *(cdata+ptr));
338
#endif
339
340
33.9k
    de265_push_NAL(decoder->ctx, cdata + ptr, nal_size, 0, (void*)user_data);
341
33.9k
    ptr += nal_size;
342
33.9k
  }
343
344
  // TODO(farindk): Set "err" if data could not be pushed
345
  //de265_push_data(decoder->ctx, data, size, 0, nullptr);
346
347
7.17k
  return {heif_error_Ok, heif_suberror_Unspecified, kSuccess};
348
7.22k
}
349
350
static heif_error libde265_v1_push_data(void* decoder_raw, const void* data, size_t size)
351
0
{
352
0
  return libde265_v1_push_data2(decoder_raw, data, size, 0);
353
0
}
354
355
static heif_error libde265_flush_data(void* decoder_raw)
356
7.17k
{
357
7.17k
  libde265_decoder* decoder = (libde265_decoder*) decoder_raw;
358
359
7.17k
  de265_flush_data(decoder->ctx);
360
361
7.17k
  return heif_error_ok;
362
7.17k
}
363
364
365
366
static heif_error libde265_v1_decode_next_image2(void* decoder_raw,
367
                                                 heif_image** out_img,
368
                                                 uintptr_t* out_user_data,
369
                                                 const heif_security_limits* limits)
370
216k
{
371
216k
  libde265_decoder* decoder = (libde265_decoder*) decoder_raw;
372
216k
  heif_error err = {heif_error_Ok, heif_suberror_Unspecified, kSuccess};
373
374
  // TODO(251119) : de265_flush_data(decoder->ctx);
375
376
  // TODO(farindk): Set "err" if no image was decoded.
377
216k
  int more;
378
216k
  de265_error decode_err;
379
216k
  *out_img = nullptr;
380
231k
  do {
381
231k
    more = 0;
382
231k
    decode_err = de265_decode(decoder->ctx, &more);
383
231k
    if (decode_err != DE265_OK) {
384
      // printf("Error decoding: %s (%d)\n", de265_get_error_text(decode_err), decode_err);
385
9.39k
      break;
386
9.39k
    }
387
388
    // TODO: read NCLX from h265 bitstream
389
390
222k
    const de265_image* image = de265_get_next_picture(decoder->ctx);
391
222k
    if (image) {
392
      // TODO(farindk): Should we return the first image instead?
393
2.94k
      if (*out_img) {
394
0
        heif_image_release(*out_img);
395
0
      }
396
397
2.94k
      if (out_user_data) {
398
0
        *out_user_data = (uintptr_t)de265_get_image_user_data(image);
399
0
      }
400
401
2.94k
      err = convert_libde265_image_to_heif_image(decoder, image, out_img, limits);
402
2.94k
      if (err.code != heif_error_Ok) {
403
2.58k
        return err;
404
2.58k
      }
405
406
358
      heif_color_profile_nclx* nclx = heif_nclx_color_profile_alloc();
407
358
#if LIBDE265_NUMERIC_VERSION >= 0x01000700
408
358
      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))),
409
358
                        {
410
358
                          heif_nclx_color_profile_free(nclx);
411
358
                          heif_image_release(*out_img);
412
358
                          *out_img = nullptr;
413
358
                        });
414
358
      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))),
415
358
                        {
416
358
                          heif_nclx_color_profile_free(nclx);
417
358
                          heif_image_release(*out_img);
418
358
                          *out_img = nullptr;
419
358
                        });
420
358
      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))),
421
358
                        {
422
358
                          heif_nclx_color_profile_free(nclx);
423
358
                          heif_image_release(*out_img);
424
358
                          *out_img = nullptr;
425
358
                        });
426
358
      nclx->full_range_flag = (bool) de265_get_image_full_range_flag(image);
427
358
#endif
428
358
      heif_image_set_nclx_color_profile(*out_img, nclx);
429
358
      heif_nclx_color_profile_free(nclx);
430
431
358
      de265_release_next_picture(decoder->ctx);
432
358
      return heif_error_ok;
433
358
    }
434
222k
  } while (more);
435
436
213k
  return err;
437
216k
}
438
439
440
static heif_error libde265_v1_decode_next_image(void* decoder_raw,
441
                                                heif_image** out_img,
442
                                                const heif_security_limits* limits)
443
0
{
444
0
  return libde265_v1_decode_next_image2(decoder_raw, out_img, nullptr, limits);
445
0
}
446
447
static heif_error libde265_v1_decode_image(void* decoder_raw,
448
                                           heif_image** out_img)
449
0
{
450
0
  auto* limits = heif_get_global_security_limits();
451
0
  return libde265_v1_decode_next_image(decoder_raw, out_img, limits);
452
0
}
453
454
#endif
455
456
457
#if LIBDE265_NUMERIC_VERSION >= 0x02000000
458
459
static const heif_decoder_plugin decoder_libde265
460
{
461
  1,
462
  libde265_plugin_name,
463
  libde265_init_plugin,
464
  libde265_deinit_plugin,
465
  libde265_does_support_format,
466
  libde265_new_decoder,
467
  libde265_free_decoder,
468
  libde265_v2_push_data,
469
  libde265_v2_decode_image,
470
  libde265_set_strict_decoding,
471
  "libde265",
472
  libde265_v2_decode_next_image
473
};
474
475
#else
476
477
static const heif_decoder_plugin decoder_libde265
478
    {
479
        5,
480
        libde265_plugin_name,
481
        libde265_init_plugin,
482
        libde265_deinit_plugin,
483
        libde265_does_support_format,
484
        libde265_new_decoder,
485
        libde265_free_decoder,
486
        libde265_v1_push_data,
487
        libde265_v1_decode_image,
488
        libde265_set_strict_decoding,
489
        "libde265",
490
        libde265_v1_decode_next_image,
491
        /* minimum_required_libheif_version */ LIBHEIF_MAKE_VERSION(1,21,0),
492
        libde265_does_support_format2,
493
        libde265_new_decoder2,
494
        libde265_v1_push_data2,
495
        libde265_flush_data,
496
        libde265_v1_decode_next_image2
497
    };
498
499
#endif
500
501
const heif_decoder_plugin* get_decoder_plugin_libde265()
502
294
{
503
294
  return &decoder_libde265;
504
294
}
505
506
507
508
#if PLUGIN_LIBDE265
509
heif_plugin_info plugin_info {
510
  1,
511
  heif_plugin_type_decoder,
512
  &decoder_libde265
513
};
514
#endif