Coverage Report

Created: 2026-09-01 06:57

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 <limits>
28
#include <string>
29
30
#include <libde265/de265.h>
31
32
33
34
struct libde265_decoder
35
{
36
  de265_decoder_context* ctx;
37
  bool strict_decoding = false;
38
  std::string error_message;
39
};
40
41
static const char kEmptyString[] = "";
42
static const char kSuccess[] = "Success";
43
44
static const int LIBDE265_PLUGIN_PRIORITY = 100;
45
46
0
#define MAX_PLUGIN_NAME_LENGTH 80
47
48
static char plugin_name[MAX_PLUGIN_NAME_LENGTH];
49
static constexpr char version_prefix[] = ", version ";
50
51
static const char* libde265_plugin_name()
52
0
{
53
0
  strcpy(plugin_name, "libde265 HEVC decoder");
54
55
0
  const char* libde265_version = de265_get_version();
56
57
0
  if (strlen(plugin_name) + strlen(libde265_version) + (sizeof(version_prefix) - 1) < MAX_PLUGIN_NAME_LENGTH) {
58
0
    strcat(plugin_name, version_prefix);
59
0
    strcat(plugin_name, libde265_version);
60
0
  }
61
62
0
  return plugin_name;
63
0
}
64
65
66
static void libde265_init_plugin()
67
16
{
68
16
  de265_init();
69
16
}
70
71
72
static void libde265_deinit_plugin()
73
2
{
74
2
  de265_free();
75
2
}
76
77
78
static int libde265_does_support_format(heif_compression_format format)
79
90.2k
{
80
90.2k
  if (format == heif_compression_HEVC) {
81
34.3k
    return LIBDE265_PLUGIN_PRIORITY;
82
34.3k
  }
83
55.9k
  else {
84
55.9k
    return 0;
85
55.9k
  }
86
90.2k
}
87
88
89
[[maybe_unused]]
90
static int libde265_does_support_format2(const heif_decoder_plugin_compressed_format_description* format)
91
0
{
92
0
  return libde265_does_support_format(format->format);
93
0
}
94
95
96
97
static heif_error convert_libde265_image_to_heif_image(libde265_decoder* decoder,
98
                                                       const de265_image* de265img,
99
                                                       heif_image** image,
100
                                                       const heif_security_limits* limits)
101
15.5k
{
102
15.5k
  bool is_mono = (de265_get_chroma_format(de265img) == de265_chroma_mono);
103
104
15.5k
  heif_error err;
105
15.5k
  err = heif_image_create(de265_get_image_width(de265img, 0),
106
15.5k
                          de265_get_image_height(de265img, 0),
107
15.5k
                          is_mono ? heif_colorspace_monochrome : heif_colorspace_YCbCr,
108
15.5k
                          (heif_chroma) de265_get_chroma_format(de265img),
109
15.5k
                          image);
110
15.5k
  if (err.code) {
111
0
    return err;
112
0
  }
113
114
  // --- transfer data from de265_image to HeifPixelImage
115
116
15.5k
  heif_channel channel2plane[3] = {
117
15.5k
      heif_channel_Y,
118
15.5k
      heif_channel_Cb,
119
15.5k
      heif_channel_Cr
120
15.5k
  };
121
122
123
15.5k
  int bpp = de265_get_bits_per_pixel(de265img, 0);
124
125
15.5k
  int num_planes = (is_mono ? 1 : 3);
126
127
47.2k
  for (int c = 0; c < num_planes; c++) {
128
39.1k
    if (de265_get_bits_per_pixel(de265img, c) != bpp) {
129
7.44k
      heif_image_release(*image);
130
7.44k
      err = {heif_error_Unsupported_feature,
131
7.44k
             heif_suberror_Unsupported_color_conversion,
132
7.44k
             "Channels with different number of bits per pixel are not supported"};
133
7.44k
      return err;
134
7.44k
    }
135
136
31.6k
    int stride;
137
31.6k
    const uint8_t* data = de265_get_image_plane(de265img, c, &stride);
138
139
31.6k
    int w = de265_get_image_width(de265img, c);
140
31.6k
    int h = de265_get_image_height(de265img, c);
141
31.6k
    if (w <= 0 || h <= 0) {
142
0
      heif_image_release(*image);
143
0
      err = {heif_error_Decoder_plugin_error,
144
0
             heif_suberror_Invalid_image_size,
145
0
             kEmptyString};
146
0
      return err;
147
0
    }
148
149
31.6k
    err = heif_image_add_plane_safe(*image, channel2plane[c], w,h, bpp, limits);
150
31.6k
    if (err.code) {
151
      // copy error message to decoder object because heif_image will be released
152
0
      decoder->error_message = err.message;
153
0
      err.message = decoder->error_message.c_str();
154
155
0
      heif_image_release(*image);
156
0
      return err;
157
0
    }
158
159
31.6k
    size_t dst_stride;
160
31.6k
    uint8_t* dst_mem = heif_image_get_plane2(*image, channel2plane[c], &dst_stride);
161
162
31.6k
    int bytes_per_pixel = (bpp + 7) / 8;
163
164
3.84M
    for (int y = 0; y < h; y++) {
165
3.81M
      memcpy(dst_mem + y * dst_stride, data + static_cast<size_t>(y) * stride, static_cast<size_t>(w) * bytes_per_pixel);
166
3.81M
    }
167
31.6k
  }
168
169
170
8.15k
  return {heif_error_Ok, heif_suberror_Unspecified, kSuccess};
171
15.5k
}
172
173
174
175
// Create a new decoder context for decoding an image
176
heif_error libde265_new_decoder2(void** dec, const heif_decoder_plugin_options* options)
177
68.0k
{
178
68.0k
  libde265_decoder* decoder = new libde265_decoder();
179
68.0k
  heif_error err = {heif_error_Ok, heif_suberror_Unspecified, kSuccess};
180
181
68.0k
  decoder->ctx = de265_new_decoder();
182
183
68.0k
#if LIBDE265_NUMERIC_VERSION >= 0x01010000
184
  // Pass libheif's max_image_size_pixels through to libde265 so the decoder
185
  // rejects bitstreams whose SPS declares an image far larger than what the
186
  // surrounding HEIF file claims. libheif tightens this limit per-decode to
187
  // ispe + one coding-unit margin (see tighten_image_size_limit_for_ispe),
188
  // so the padding margin is already baked into the value we see here.
189
68.0k
  {
190
68.0k
    const heif_security_limits* limits = options->limits ? options->limits : heif_get_global_security_limits();
191
68.0k
    de265_security_limits* de265_limits = de265_get_security_limits(decoder->ctx);
192
68.0k
    if (limits->max_image_size_pixels > std::numeric_limits<uint32_t>::max()) {
193
0
      de265_limits->max_image_size_pixels = 0;
194
0
    }
195
68.0k
    else {
196
68.0k
      de265_limits->max_image_size_pixels = static_cast<uint32_t>(limits->max_image_size_pixels);
197
68.0k
    }
198
68.0k
  }
199
68.0k
#endif
200
201
#if defined(__EMSCRIPTEN__)
202
  // Speed up decoding from JavaScript.
203
  de265_set_parameter_bool(decoder->ctx, DE265_DECODER_PARAM_DISABLE_DEBLOCKING, 1);
204
  de265_set_parameter_bool(decoder->ctx, DE265_DECODER_PARAM_DISABLE_SAO, 1);
205
#else
206
68.0k
  int nThreads = (options->num_threads ? options->num_threads : 1);
207
208
  // Worker threads are not supported when running on Emscripten.
209
68.0k
  de265_start_worker_threads(decoder->ctx, nThreads);
210
68.0k
#endif
211
212
68.0k
  decoder->strict_decoding = options->strict_decoding;
213
214
68.0k
  *dec = decoder;
215
68.0k
  return err;
216
68.0k
}
217
218
219
static heif_error libde265_new_decoder(void** dec)
220
0
{
221
0
  heif_decoder_plugin_options options{};
222
0
  options.format = heif_compression_HEVC;
223
0
  options.num_threads = 0;
224
0
  options.strict_decoding = false;
225
226
0
  return libde265_new_decoder2(dec, &options);
227
0
}
228
229
static void libde265_free_decoder(void* decoder_raw)
230
68.0k
{
231
68.0k
  libde265_decoder* decoder = (libde265_decoder*) decoder_raw;
232
233
68.0k
  de265_error err = de265_free_decoder(decoder->ctx);
234
68.0k
  (void) err;
235
236
68.0k
  delete decoder;
237
68.0k
}
238
239
240
void libde265_set_strict_decoding(void* decoder_raw, int flag)
241
0
{
242
0
  libde265_decoder* decoder = (libde265_decoder*) decoder_raw;
243
244
0
  decoder->strict_decoding = flag;
245
0
}
246
247
248
#if LIBDE265_NUMERIC_VERSION >= 0x02000000
249
250
static heif_error libde265_v2_push_data(void* decoder_raw, const void* data, size_t size)
251
{
252
  libde265_decoder* decoder = (libde265_decoder*)decoder_raw;
253
254
  const uint8_t* cdata = (const uint8_t*)data;
255
256
  size_t ptr=0;
257
  while (ptr < size) {
258
    if (4 > size - ptr) {
259
      return { heif_error_Decoder_plugin_error,
260
               heif_suberror_End_of_data,
261
               kEmptyString };
262
    }
263
264
    // TODO: the size of the NAL unit length variable is defined in the hvcC header.
265
    // We should not assume that it is always 4 bytes.
266
    uint32_t nal_size = (uint32_t)((cdata[ptr]<<24) | (cdata[ptr+1]<<16) | (cdata[ptr+2]<<8) | (cdata[ptr+3]));
267
    ptr+=4;
268
269
    if (nal_size > size - ptr) {
270
      //sstr << "NAL size (" << size32 << ") exceeds available data in file ("
271
      //<< data_bytes_left_to_read << ")";
272
273
      return { heif_error_Decoder_plugin_error,
274
               heif_suberror_End_of_data,
275
               kEmptyString };
276
    }
277
278
    de265_push_NAL(decoder->ctx, cdata+ptr, nal_size, 0, nullptr);
279
    ptr += nal_size;
280
  }
281
282
283
  return { heif_error_Ok, heif_suberror_Unspecified, kSuccess };
284
}
285
286
287
static heif_error libde265_v2_decode_next_image(void* decoder_raw,
288
                                                heif_image** out_img,
289
                                                const heif_security_limits* limits)
290
{
291
  libde265_decoder* decoder = (libde265_decoder*)decoder_raw;
292
293
  de265_push_end_of_stream(decoder->ctx);
294
295
  int action = de265_get_action(decoder->ctx, 1);
296
297
  // TODO: read NCLX from h265 bitstream
298
299
  // TODO(farindk): Set "err" if no image was decoded.
300
  if (action==de265_action_get_image) {
301
    const de265_image* img = de265_get_next_picture(decoder->ctx);
302
    if (img) {
303
      heif_error err = convert_libde265_image_to_heif_image(decoder, img,
304
                                                                   out_img, limits);
305
      de265_release_picture(img);
306
307
      return err;
308
    }
309
  }
310
311
  return { heif_error_Decoder_plugin_error, heif_suberror_Unspecified, kEmptyString };
312
}
313
314
static heif_error libde265_v2_decode_image(void* decoder_raw,
315
                                           heif_image** out_img)
316
{
317
  auto* limits = heif_get_global_security_limits();
318
  return libde265_v2_decode_next_image(decoder_raw, out_img, limits);
319
}
320
#else
321
322
static heif_error libde265_v1_push_data2(void* decoder_raw, const void* data, size_t size, uintptr_t user_data)
323
39.1k
{
324
39.1k
  libde265_decoder* decoder = (libde265_decoder*) decoder_raw;
325
326
39.1k
  const uint8_t* cdata = (const uint8_t*) data;
327
328
39.1k
  size_t ptr = 0;
329
529k
  while (ptr < size) {
330
500k
    if (4 > size - ptr) {
331
1.76k
      return {
332
1.76k
        heif_error_Decoder_plugin_error,
333
1.76k
        heif_suberror_End_of_data,
334
1.76k
        kEmptyString
335
1.76k
      };
336
1.76k
    }
337
338
498k
    uint32_t nal_size = static_cast<uint32_t>((cdata[ptr] << 24) | (cdata[ptr + 1] << 16) | (cdata[ptr + 2] << 8) | (cdata[ptr + 3]));
339
498k
    ptr += 4;
340
341
498k
    if (nal_size > size - ptr) {
342
8.76k
      return {
343
8.76k
        heif_error_Decoder_plugin_error,
344
8.76k
        heif_suberror_End_of_data,
345
8.76k
        kEmptyString
346
8.76k
      };
347
8.76k
    }
348
349
#if 0
350
    FILE* fh = fopen("data.h265", "a");
351
    fputc(0, fh);
352
    fputc(0, fh);
353
    fputc(1, fh);
354
    fwrite(cdata + ptr, nal_size, 1, fh);
355
    fclose(fh);
356
357
    printf("put nal with size %d %x\n", nal_size, *(cdata+ptr));
358
#endif
359
360
    // The codec hands this opaque pointer back unchanged; it carries an integer, not an address.
361
489k
    de265_push_NAL(decoder->ctx, cdata + ptr, nal_size, 0, (void*)user_data); // NOLINT(performance-no-int-to-ptr)
362
489k
    ptr += nal_size;
363
489k
  }
364
365
  // TODO(farindk): Set "err" if data could not be pushed
366
  //de265_push_data(decoder->ctx, data, size, 0, nullptr);
367
368
28.5k
  return {heif_error_Ok, heif_suberror_Unspecified, kSuccess};
369
39.1k
}
370
371
static heif_error libde265_v1_push_data(void* decoder_raw, const void* data, size_t size)
372
0
{
373
0
  return libde265_v1_push_data2(decoder_raw, data, size, 0);
374
0
}
375
376
static heif_error libde265_flush_data(void* decoder_raw)
377
28.5k
{
378
28.5k
  libde265_decoder* decoder = (libde265_decoder*) decoder_raw;
379
380
28.5k
  de265_flush_data(decoder->ctx);
381
382
28.5k
  return heif_error_ok;
383
28.5k
}
384
385
386
387
static heif_error libde265_v1_decode_next_image2(void* decoder_raw,
388
                                                 heif_image** out_img,
389
                                                 uintptr_t* out_user_data,
390
                                                 const heif_security_limits* limits)
391
674k
{
392
674k
  libde265_decoder* decoder = (libde265_decoder*) decoder_raw;
393
674k
  heif_error err = {heif_error_Ok, heif_suberror_Unspecified, kSuccess};
394
395
  // TODO(251119) : de265_flush_data(decoder->ctx);
396
397
  // TODO(farindk): Set "err" if no image was decoded.
398
674k
  int more;
399
674k
  de265_error decode_err;
400
674k
  *out_img = nullptr;
401
736k
  do {
402
736k
    more = 0;
403
736k
    decode_err = de265_decode(decoder->ctx, &more);
404
736k
    if (decode_err != DE265_OK) {
405
      // printf("Error decoding: %s (%d)\n", de265_get_error_text(decode_err), decode_err);
406
31.3k
      break;
407
31.3k
    }
408
409
    // TODO: read NCLX from h265 bitstream
410
411
705k
    const de265_image* image = de265_get_next_picture(decoder->ctx);
412
705k
    if (image) {
413
      // TODO(farindk): Should we return the first image instead?
414
15.5k
      if (*out_img) {
415
0
        heif_image_release(*out_img);
416
0
      }
417
418
15.5k
      if (out_user_data) {
419
0
        *out_user_data = (uintptr_t)de265_get_image_user_data(image);
420
0
      }
421
422
15.5k
      err = convert_libde265_image_to_heif_image(decoder, image, out_img, limits);
423
15.5k
      if (err.code != heif_error_Ok) {
424
7.44k
        return err;
425
7.44k
      }
426
427
8.15k
      heif_color_profile_nclx* nclx = heif_nclx_color_profile_alloc();
428
8.15k
#if LIBDE265_NUMERIC_VERSION >= 0x01000700
429
8.15k
      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))),
430
8.15k
                        {
431
8.15k
                          heif_nclx_color_profile_free(nclx);
432
8.15k
                          heif_image_release(*out_img);
433
8.15k
                          *out_img = nullptr;
434
8.15k
                        });
435
8.15k
      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))),
436
8.15k
                        {
437
8.15k
                          heif_nclx_color_profile_free(nclx);
438
8.15k
                          heif_image_release(*out_img);
439
8.15k
                          *out_img = nullptr;
440
8.15k
                        });
441
8.15k
      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))),
442
8.15k
                        {
443
8.15k
                          heif_nclx_color_profile_free(nclx);
444
8.15k
                          heif_image_release(*out_img);
445
8.15k
                          *out_img = nullptr;
446
8.15k
                        });
447
8.15k
      nclx->full_range_flag = (bool) de265_get_image_full_range_flag(image);
448
8.15k
#endif
449
8.15k
      heif_image_set_nclx_color_profile(*out_img, nclx);
450
8.15k
      heif_nclx_color_profile_free(nclx);
451
452
8.15k
      de265_release_next_picture(decoder->ctx);
453
8.15k
      return heif_error_ok;
454
8.15k
    }
455
705k
  } while (more);
456
457
658k
  return err;
458
674k
}
459
460
461
static heif_error libde265_v1_decode_next_image(void* decoder_raw,
462
                                                heif_image** out_img,
463
                                                const heif_security_limits* limits)
464
0
{
465
0
  return libde265_v1_decode_next_image2(decoder_raw, out_img, nullptr, limits);
466
0
}
467
468
static heif_error libde265_v1_decode_image(void* decoder_raw,
469
                                           heif_image** out_img)
470
0
{
471
0
  auto* limits = heif_get_global_security_limits();
472
0
  return libde265_v1_decode_next_image(decoder_raw, out_img, limits);
473
0
}
474
475
#endif
476
477
478
#if LIBDE265_NUMERIC_VERSION >= 0x02000000
479
480
static const heif_decoder_plugin decoder_libde265
481
{
482
  1,
483
  libde265_plugin_name,
484
  libde265_init_plugin,
485
  libde265_deinit_plugin,
486
  libde265_does_support_format,
487
  libde265_new_decoder,
488
  libde265_free_decoder,
489
  libde265_v2_push_data,
490
  libde265_v2_decode_image,
491
  libde265_set_strict_decoding,
492
  "libde265",
493
  libde265_v2_decode_next_image
494
};
495
496
#else
497
498
static const heif_decoder_plugin decoder_libde265
499
    {
500
        5,
501
        libde265_plugin_name,
502
        libde265_init_plugin,
503
        libde265_deinit_plugin,
504
        libde265_does_support_format,
505
        libde265_new_decoder,
506
        libde265_free_decoder,
507
        libde265_v1_push_data,
508
        libde265_v1_decode_image,
509
        libde265_set_strict_decoding,
510
        "libde265",
511
        libde265_v1_decode_next_image,
512
        /* minimum_required_libheif_version */ LIBHEIF_MAKE_VERSION(1,21,0),
513
        libde265_does_support_format2,
514
        libde265_new_decoder2,
515
        libde265_v1_push_data2,
516
        libde265_flush_data,
517
        libde265_v1_decode_next_image2
518
    };
519
520
#endif
521
522
const heif_decoder_plugin* get_decoder_plugin_libde265()
523
16
{
524
16
  return &decoder_libde265;
525
16
}
526
527
528
529
#if PLUGIN_LIBDE265
530
heif_plugin_info plugin_info {
531
  1,
532
  heif_plugin_type_decoder,
533
  &decoder_libde265
534
};
535
#endif