Coverage Report

Created: 2026-02-14 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libheif/libheif/plugins/decoder_openh264.cc
Line
Count
Source
1
/*
2
 * openh264 codec.
3
 * Copyright (c) 2024 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_openh264.h"
24
#include <cstring>
25
#include <cassert>
26
#include <vector>
27
#include <cstdio>
28
#include <deque>
29
30
#include <wels/codec_api.h>
31
#include <string>
32
#include <utility>
33
34
// TODO: the openh264 decoder seems to fail with some images.
35
//       I have not figured out yet which images are affected.
36
//       Maybe it has something to do with the image size. I got the error with large images.
37
38
struct openh264_decoder
39
{
40
  struct Packet
41
  {
42
    std::vector<uint8_t> data;
43
    uintptr_t pts;
44
  };
45
46
  std::deque<Packet> input_data;
47
  bool m_eof_reached = false;
48
49
  std::string error_message;
50
51
52
  // --- decoder
53
54
  ISVCDecoder* decoder = nullptr;
55
56
  ~openh264_decoder()
57
0
  {
58
0
    if (decoder) {
59
      // Step 6:uninitialize the decoder and memory free
60
61
0
      decoder->Uninitialize(); // TODO: do we have to Uninitialize when an error is returned?
62
63
64
0
      WelsDestroyDecoder(decoder);
65
0
    }
66
0
  }
67
};
68
69
static const char kSuccess[] = "Success";
70
71
// Reduced priority because OpenH264 cannot pass through user-data.
72
// We need this feature for decoding sequences with SAI.
73
// Prefer to use the FFMPEG plugin.
74
static const int OpenH264_PLUGIN_PRIORITY = 70;
75
76
#define MAX_PLUGIN_NAME_LENGTH 80
77
78
static char plugin_name[MAX_PLUGIN_NAME_LENGTH];
79
80
static heif_error kError_EOF = {heif_error_Decoder_plugin_error, heif_suberror_End_of_data, "Insufficient input data"};
81
82
83
static const char* openh264_plugin_name()
84
0
{
85
0
  OpenH264Version version = WelsGetCodecVersion();
86
87
0
  sprintf(plugin_name, "OpenH264 %d.%d.%d", version.uMajor, version.uMinor, version.uRevision);
88
89
0
  return plugin_name;
90
0
}
91
92
93
static void openh264_init_plugin()
94
14.6k
{
95
14.6k
}
96
97
98
static void openh264_deinit_plugin()
99
14.6k
{
100
14.6k
}
101
102
103
static int openh264_does_support_format(heif_compression_format format)
104
10.1k
{
105
10.1k
  if (format == heif_compression_AVC) {
106
0
    return OpenH264_PLUGIN_PRIORITY;
107
0
  }
108
10.1k
  else {
109
10.1k
    return 0;
110
10.1k
  }
111
10.1k
}
112
113
static int openh264_does_support_format2(const heif_decoder_plugin_compressed_format_description* format)
114
0
{
115
0
  return openh264_does_support_format(format->format);
116
0
}
117
118
119
heif_error openh264_new_decoder2(void** dec, const heif_decoder_plugin_options* options)
120
0
{
121
0
  auto* decoder = new openh264_decoder();
122
0
  *dec = decoder;
123
124
  // Step 2:decoder creation
125
0
  WelsCreateDecoder(&decoder->decoder);
126
0
  if (!decoder->decoder) {
127
0
    return {
128
0
      heif_error_Decoder_plugin_error,
129
0
      heif_suberror_Unspecified,
130
0
      "Cannot create OpenH264 decoder"
131
0
    };
132
0
  }
133
134
  // Step 3:declare required parameter, used to differentiate Decoding only and Parsing only
135
0
  SDecodingParam sDecParam{};
136
0
  sDecParam.sVideoProperty.eVideoBsType = VIDEO_BITSTREAM_AVC;
137
138
  //for Parsing only, the assignment is mandatory
139
  // sDecParam.bParseOnly = true;
140
141
  // Step 4:initialize the parameter and decoder context, allocate memory
142
0
  decoder->decoder->Initialize(&sDecParam);
143
144
0
  return {heif_error_Ok, heif_suberror_Unspecified, kSuccess};
145
0
}
146
147
148
heif_error openh264_new_decoder(void** dec)
149
0
{
150
0
  heif_decoder_plugin_options options;
151
0
  options.format = heif_compression_AVC;
152
0
  options.num_threads = 0;
153
0
  options.strict_decoding = false;
154
155
0
  return openh264_new_decoder2(dec, &options);
156
0
}
157
158
void openh264_free_decoder(void* decoder_raw)
159
0
{
160
0
  auto* decoder = (openh264_decoder*) decoder_raw;
161
162
0
  if (!decoder) {
163
0
    return;
164
0
  }
165
166
0
  delete decoder;
167
0
}
168
169
170
void openh264_set_strict_decoding(void* decoder_raw, int flag)
171
0
{
172
  //  auto* decoder = (openh264_decoder*) decoder_raw;
173
0
}
174
175
176
heif_error openh264_push_data2(void* decoder_raw, const void* frame_data, size_t frame_size, uintptr_t user_data)
177
0
{
178
0
  auto* decoder = (openh264_decoder*) decoder_raw;
179
180
0
  const auto* input_data = (const uint8_t*) frame_data;
181
182
0
  assert(frame_size > 4);
183
184
0
  openh264_decoder::Packet pkt;
185
0
  pkt.data.insert(pkt.data.end(), input_data, input_data + frame_size);
186
0
  pkt.pts = user_data;
187
0
  decoder->input_data.push_back(std::move(pkt));
188
189
0
  return {heif_error_Ok, heif_suberror_Unspecified, kSuccess};
190
0
}
191
192
heif_error openh264_push_data(void* decoder_raw, const void* frame_data, size_t frame_size)
193
0
{
194
0
  return openh264_push_data2(decoder_raw, frame_data, frame_size, 0);
195
0
}
196
197
198
heif_error openh264_decode_next_image2(void* decoder_raw, heif_image** out_img,
199
                                       uintptr_t* out_user_data,
200
                                       const heif_security_limits* limits)
201
0
{
202
0
  auto* decoder = (openh264_decoder*) decoder_raw;
203
0
  ISVCDecoder* pSvcDecoder = decoder->decoder;
204
205
  // --- Not enough data, but no EOF yet.
206
207
0
  if (decoder->input_data.empty() && !decoder->m_eof_reached) {
208
0
    *out_img = nullptr;
209
0
    return heif_error_ok;
210
0
  }
211
212
213
  //output: [0~2] for Y,U,V buffer for Decoding only
214
0
  unsigned char* pData[3] = {nullptr, nullptr, nullptr};
215
216
  // in-out: for Decoding only: declare and initialize the output buffer info, this should never co-exist with Parsing only
217
218
0
  SBufferInfo sDstBufInfo;
219
0
  memset(&sDstBufInfo, 0, sizeof(SBufferInfo));
220
221
0
  int iRet;
222
223
0
  if (!decoder->input_data.empty()) {
224
0
    sDstBufInfo.uiInBsTimeStamp = decoder->input_data.front().pts;
225
226
0
    const std::vector<uint8_t>& indata = decoder->input_data.front().data;
227
0
    std::vector<uint8_t> scdata;
228
229
0
    size_t idx = 0;
230
0
    while (idx < indata.size()) {
231
0
      if (indata.size() - 4 < idx) {
232
0
        return kError_EOF;
233
0
      }
234
235
0
      uint32_t size = ((indata[idx] << 24) | (indata[idx + 1] << 16) | (indata[idx + 2] << 8) | indata[idx + 3]);
236
0
      idx += 4;
237
238
0
      if (indata.size() < size || indata.size() - size < idx) {
239
0
        return kError_EOF;
240
0
      }
241
242
0
      scdata.push_back(0);
243
0
      scdata.push_back(0);
244
0
      scdata.push_back(1);
245
246
      // check for need of start code emulation prevention
247
248
0
      bool do_start_code_emulation_check = true;
249
250
0
      while (do_start_code_emulation_check && size >= 3) {
251
0
        bool found_start_code_emulation = false;
252
253
0
        for (size_t i = 0; i < size - 3; i++) {
254
0
          if (indata[idx + 0] == 0 &&
255
0
              indata[idx + 1] == 0 &&
256
0
              (indata[idx + 2] >= 0 && indata[idx + 2] <= 3)) {
257
0
            scdata.push_back(0);
258
0
            scdata.push_back(0);
259
0
            scdata.push_back(3);
260
261
0
            scdata.insert(scdata.end(), &indata[idx + 2], indata.data() + idx + i + 2);
262
0
            idx += i + 2;
263
0
            size -= (uint32_t) (i + 2);
264
0
            found_start_code_emulation = true;
265
0
            break;
266
0
          }
267
0
        }
268
269
0
        do_start_code_emulation_check = found_start_code_emulation;
270
0
      }
271
272
0
      assert(size > 0);
273
      // Note: we cannot write &indata[idx + size] since that would use the operator[] on an element beyond the vector range.
274
0
      scdata.insert(scdata.end(), &indata[idx], indata.data() + idx + size);
275
276
0
      idx += size;
277
0
    }
278
279
0
    if (idx != indata.size()) {
280
0
      decoder->input_data.pop_front();
281
0
      return kError_EOF;
282
0
    }
283
284
0
    decoder->input_data.pop_front();
285
286
    // input: encoded bitstream start position; should include start code prefix
287
0
    unsigned char* pBuf = scdata.data();
288
289
    // input: encoded bit stream length; should include the size of start code prefix
290
0
    int iSize = static_cast<int>(scdata.size());
291
292
293
    // Step 5:do actual decoding process in slice level; this can be done in a loop until data ends
294
295
296
    //for Decoding only
297
0
    iRet = pSvcDecoder->DecodeFrameNoDelay(pBuf, iSize, pData, &sDstBufInfo);
298
0
  }
299
0
  else {
300
0
    iRet = pSvcDecoder->FlushFrame(pData, &sDstBufInfo);
301
0
  }
302
303
0
  if (iRet != 0) {
304
0
    return {
305
0
      heif_error_Decoder_plugin_error,
306
0
      heif_suberror_Unspecified,
307
0
      "OpenH264 decoder error"
308
0
    };
309
0
  }
310
311
0
  if (sDstBufInfo.iBufferStatus != 1) {
312
0
    *out_img = nullptr;
313
0
    return heif_error_ok;
314
0
  }
315
316
0
  if (out_user_data) {
317
0
    *out_user_data = sDstBufInfo.uiOutYuvTimeStamp;
318
0
  }
319
320
  /*
321
  // TODO: I receive an iBufferStatus==0, but the output image is still decoded
322
  if (sDstBufInfo.iBufferStatus == 0) {
323
    return {heif_error_Decoder_plugin_error,
324
            heif_suberror_Unspecified,
325
            "OpenH264 decoder did not output any image"};
326
  }
327
  */
328
329
0
  uint32_t width = sDstBufInfo.UsrData.sSystemBuffer.iWidth;
330
0
  uint32_t height = sDstBufInfo.UsrData.sSystemBuffer.iHeight;
331
332
0
  heif_image* heif_img;
333
0
  heif_error err{};
334
335
0
  uint32_t cwidth, cheight;
336
337
0
  if (sDstBufInfo.UsrData.sSystemBuffer.iFormat == videoFormatI420) {
338
0
    cwidth = (width + 1) / 2;
339
0
    cheight = (height + 1) / 2;
340
341
0
    err = heif_image_create(width, height,
342
0
                            heif_colorspace_YCbCr,
343
0
                            heif_chroma_420,
344
0
                            &heif_img);
345
0
    if (err.code != heif_error_Ok) {
346
0
      assert(heif_img == nullptr);
347
0
      return err;
348
0
    }
349
350
0
    *out_img = heif_img;
351
352
0
    err = heif_image_add_plane_safe(heif_img, heif_channel_Y, width, height, 8, limits);
353
0
    if (err.code != heif_error_Ok) {
354
      // copy error message to decoder object because heif_image will be released
355
0
      decoder->error_message = err.message;
356
0
      err.message = decoder->error_message.c_str();
357
358
0
      heif_image_release(heif_img);
359
0
      return err;
360
0
    }
361
362
0
    err = heif_image_add_plane_safe(heif_img, heif_channel_Cb, cwidth, cheight, 8, limits);
363
0
    if (err.code != heif_error_Ok) {
364
      // copy error message to decoder object because heif_image will be released
365
0
      decoder->error_message = err.message;
366
0
      err.message = decoder->error_message.c_str();
367
368
0
      heif_image_release(heif_img);
369
0
      return err;
370
0
    }
371
372
0
    err = heif_image_add_plane_safe(heif_img, heif_channel_Cr, cwidth, cheight, 8, limits);
373
0
    if (err.code != heif_error_Ok) {
374
      // copy error message to decoder object because heif_image will be released
375
0
      decoder->error_message = err.message;
376
0
      err.message = decoder->error_message.c_str();
377
378
0
      heif_image_release(heif_img);
379
0
      return err;
380
0
    }
381
382
0
    size_t y_stride;
383
0
    size_t cb_stride;
384
0
    size_t cr_stride;
385
0
    uint8_t* py = heif_image_get_plane2(heif_img, heif_channel_Y, &y_stride);
386
0
    uint8_t* pcb = heif_image_get_plane2(heif_img, heif_channel_Cb, &cb_stride);
387
0
    uint8_t* pcr = heif_image_get_plane2(heif_img, heif_channel_Cr, &cr_stride);
388
389
0
    int ystride = sDstBufInfo.UsrData.sSystemBuffer.iStride[0];
390
0
    int cstride = sDstBufInfo.UsrData.sSystemBuffer.iStride[1];
391
392
0
    for (uint32_t y = 0; y < height; y++) {
393
0
      memcpy(py + y * y_stride, sDstBufInfo.pDst[0] + y * ystride, width);
394
0
    }
395
396
0
    for (uint32_t y = 0; y < (height + 1) / 2; y++) {
397
0
      memcpy(pcb + y * cb_stride, sDstBufInfo.pDst[1] + y * cstride, (width + 1) / 2);
398
0
      memcpy(pcr + y * cr_stride, sDstBufInfo.pDst[2] + y * cstride, (width + 1) / 2);
399
0
    }
400
0
  }
401
0
  else {
402
0
    return {
403
0
      heif_error_Decoder_plugin_error,
404
0
      heif_suberror_Unspecified,
405
0
      "Unsupported image pixel format"
406
0
    };
407
0
  }
408
409
  // decoder->data.clear();
410
411
0
  return heif_error_ok;
412
0
}
413
414
415
heif_error openh264_decode_next_image(void* decoder_raw, heif_image** out_img,
416
                                      const heif_security_limits* limits)
417
0
{
418
0
  return openh264_decode_next_image2(decoder_raw, out_img, nullptr, limits);
419
0
}
420
421
heif_error openh264_decode_image(void* decoder_raw, heif_image** out_img)
422
0
{
423
0
  auto* limits = heif_get_global_security_limits();
424
0
  return openh264_decode_next_image(decoder_raw, out_img, limits);
425
0
}
426
427
428
heif_error openh264_flush_data(void* decoder_raw)
429
0
{
430
0
  auto* decoder = (struct openh264_decoder*) decoder_raw;
431
432
0
  decoder->m_eof_reached = true;
433
434
0
  return heif_error_ok;
435
0
}
436
437
438
static const heif_decoder_plugin decoder_openh264{
439
  5,
440
  openh264_plugin_name,
441
  openh264_init_plugin,
442
  openh264_deinit_plugin,
443
  openh264_does_support_format,
444
  openh264_new_decoder,
445
  openh264_free_decoder,
446
  openh264_push_data,
447
  openh264_decode_image,
448
  openh264_set_strict_decoding,
449
  "openh264",
450
  openh264_decode_next_image,
451
  /* minimum_required_libheif_version */ LIBHEIF_MAKE_VERSION(1,21,0),
452
  openh264_does_support_format2,
453
  openh264_new_decoder2,
454
  openh264_push_data2,
455
  openh264_flush_data,
456
  openh264_decode_next_image2
457
};
458
459
460
const heif_decoder_plugin* get_decoder_plugin_openh264()
461
14.6k
{
462
14.6k
  return &decoder_openh264;
463
14.6k
}
464
465
466
#if PLUGIN_OpenH264_DECODER
467
heif_plugin_info plugin_info {
468
  1,
469
  heif_plugin_type_decoder,
470
  &decoder_openh264
471
};
472
#endif