• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ossia / score / 32923660642

26 Aug 2026 02:40AM UTC coverage: 23.574% (+0.4%) from 23.21%
32923660642

push

github

jcelerier
gfx: add missing guards on message processing for texture / buffer / geometry

0 of 6 new or added lines in 1 file covered. (0.0%)

4413 existing lines in 83 files now uncovered.

53181 of 225592 relevant lines covered (23.57%)

61000.63 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

5.7
/src/plugins/score-plugin-media/Video/VideoDecoder.cpp
1
#include "VideoDecoder.hpp"
2

3
#include <Media/Libav.hpp>
4
#include <Video/GpuFormats.hpp>
5

6
#include <score/tools/Debug.hpp>
7

8
#include <ossia/detail/flicks.hpp>
9
#include <ossia/detail/libav.hpp>
10
#include <ossia/detail/thread.hpp>
11

12
#include <QApplication>
13
#include <QDebug>
14
#include <QElapsedTimer>
15
#include <QTimer>
16

17
#include <functional>
18
#include <iostream>
19
#include <thread>
20

21
#if SCORE_HAS_LIBAV
22

23
extern "C" {
24
#include <libavcodec/avcodec.h>
25
#include <libavformat/avformat.h>
26
#include <libavutil/pixdesc.h>
27
#include <libswscale/swscale.h>
28
#include <libavcodec/packet.h>
29
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(61, 3, 100)
30
#if __has_include(<libavutil/mastering_display_metadata.h>)
31
#include <libavutil/mastering_display_metadata.h>
32
#endif
33
#endif
34
}
35

36
#if __APPLE__ && __has_include(<libavcodec/videotoolbox.h>)
37
#include "VideoDecoder.vtb.cpp"
38
#endif
39

40
namespace Video
41
{
42
#if LIBAVUTIL_VERSION_MAJOR >= 57
43
static auto get_format_for_codeccontext(AVCodecContext* ctx, const AVPixelFormat* p)
×
44
{
45
  //qDebug() << "device: " << av_pix_fmt_desc_get(ctx->pix_fmt)->name;
46

47
  if(auto self = (LibAVDecoder*)ctx->opaque)
×
48
  {
49
    while(*p != AV_PIX_FMT_NONE)
×
50
    {
51
      //qDebug() << av_pix_fmt_desc_get(*p)->name;
52
      // Check if the format matches the one we want from the expected HWDec
53
      if(*p == self->m_conf.hardwareAcceleration)
×
54
      {
55
        // Check if the format is indeed available
56
        auto fmt = ffmpegHardwareDecodingFormats(*p).format;
×
57
        if(fmt != AV_PIX_FMT_NONE)
×
58
        {
59
          return fmt;
×
60
        }
61
      }
×
62
      ++p;
×
63
    }
64
  }
×
65

66
  return ctx->pix_fmt;
×
67
}
×
68
#endif
69

70
void LibAVDecoder::init_scaler(VideoInterface& self) noexcept
×
71
{
72
  if(!Video::formatNeedsDecoding(self.pixel_format))
×
73
    return;
×
74

75
  m_rescale.open(self);
×
76

77
  // Only claim RGBA once the rescaler that produces it actually opened:
78
  // sws_getContext refuses some source descriptions (a stream whose
79
  // codecpar->format is AV_PIX_FMT_NONE, for one), and the renderer copies
80
  // color_space out of this metadata and applies it to frames that are still
81
  // in their native YUV.
UNCOV
82
  if(!m_rescale)
×
UNCOV
83
    return;
×
84

UNCOV
85
  self.pixel_format = AV_PIX_FMT_RGBA;
×
86
  self.color_space = AVCOL_SPC_RGB;
×
UNCOV
87
}
×
88

UNCOV
89
int LibAVDecoder::init_codec_context(
×
90
    const AVCodec* codec, AVBufferRef* hw_dev_ctx, const AVStream* stream,
91
    std::function<void(AVCodecContext&)> setup)
92
{
93
  m_codecContext = avcodec_alloc_context3(codec);
×
94

95
  avcodec_parameters_to_context(m_codecContext, stream->codecpar);
×
96

97
  // m_codecContext->flags |= AV_CODEC_FLAG_LOW_DELAY;
98
  // m_codecContext->flags2 |= AV_CODEC_FLAG2_FAST;
99
#if LIBAVUTIL_VERSION_MAJOR >= 57
UNCOV
100
  if(hw_dev_ctx)
×
101
  {
UNCOV
102
    m_codecContext->hw_device_ctx = hw_dev_ctx;
×
UNCOV
103
    m_codecContext->opaque = (void*)this;
×
UNCOV
104
    m_codecContext->get_format = get_format_for_codeccontext;
×
UNCOV
105
    m_codecContext->thread_count = 1;
×
UNCOV
106
    m_codecContext->thread_type = FF_THREAD_SLICE;
×
UNCOV
107
  }
×
108
  else
109
#endif
110
  {
111
#if defined(__EMSCRIPTEN__)
112
    // Force single-threaded video decoding on wasm. With the default (threads=0,
113
    // i.e. ffmpeg auto = CPU-count frame threads), avcodec_open2 sets up a
114
    // multithreaded decoder whose teardown (avcodec_flush_buffers /
115
    // avcodec_free_context) crashes on the emscripten pthread runtime -- even
116
    // when no frame was ever decoded.
117
    m_codecContext->thread_count = 1;
118
    m_codecContext->thread_type = 0;
119
#else
120
    m_codecContext->thread_count = m_conf.threads;
×
121
    if(m_conf.threads > 0)
×
UNCOV
122
      m_codecContext->thread_type = FF_THREAD_SLICE;
×
123
#endif
124
  }
125

126
  SCORE_ASSERT(setup);
×
127
  setup(*m_codecContext);
×
128

129
  int err = avcodec_open2(m_codecContext, codec, nullptr);
×
UNCOV
130
  if(err < 0)
×
131
  {
UNCOV
132
    qDebug() << "avcodec_open2: " << av_to_string(err);
×
133
    avcodec_free_context(&m_codecContext);
×
UNCOV
134
  }
×
135
  return err;
×
136
}
×
137

138
bool LibAVDecoder::open_codec_context(
×
139
    VideoInterface& self, const AVStream* stream,
140
    std::function<void(AVCodecContext&)> setup)
141
{
UNCOV
142
  if(auto [hw_dev_ctx, hw_codec] = open_hwdec(*m_codec); hw_codec)
×
143
  {
144
    int err = init_codec_context(hw_codec, hw_dev_ctx, stream, setup);
×
145
    if(err == 0)
×
146
    {
147
      init_scaler(self);
×
148
      return true;
×
149
    }
150
  }
×
151

152
  // Maybe opening an HW accel failed, we retry in software mode
UNCOV
153
  int err = init_codec_context(m_codec, nullptr, stream, setup);
×
UNCOV
154
  if(err == 0)
×
155
  {
UNCOV
156
    init_scaler(self);
×
UNCOV
157
    return true;
×
158
  }
UNCOV
159
  return false;
×
UNCOV
160
}
×
161

162
/*
163
 *
164
    using codec_map_type = ossia::flat_map<AVCodecID, const char*>;
165
    static const codec_map_type codecs{
166
        {AV_CODEC_ID_AV1, "av1_cuvid"},          {AV_CODEC_ID_H264, "h264_cuvid"},
167
        {AV_CODEC_ID_HEVC, "hevc_cuvid"},        {AV_CODEC_ID_MJPEG, "mjpeg_cuvid"},
168
        {AV_CODEC_ID_MPEG1VIDEO, "mpeg1_cuvid"}, {AV_CODEC_ID_MPEG2VIDEO, "mpeg2_cuvid"},
169
        {AV_CODEC_ID_MPEG4, "mpeg4_cuvid"},      {AV_CODEC_ID_VC1, "vc1_cuvid"},
170
        {AV_CODEC_ID_VP8, "vp8_cuvid"},          {AV_CODEC_ID_VP9, "vp9_cuvid"},
171
    };
172
    */
173
std::pair<AVBufferRef*, const AVCodec*>
UNCOV
174
LibAVDecoder::open_hwdec(const AVCodec& detected_codec) noexcept
×
175
{
176
#if LIBAVUTIL_VERSION_MAJOR >= 57
177
  auto hwAccel = m_conf.hardwareAcceleration;
×
178
  if(hwAccel == AV_PIX_FMT_NONE)
×
UNCOV
179
    return {};
×
180

181
  if(hwAccel == AV_PIX_FMT_NONE
×
UNCOV
182
     || !codecSupportsHWPixelFormat(detected_codec.id, hwAccel))
×
183
  {
184
    auto autoFmt = selectHardwareAcceleration(
×
185
        m_conf.graphicsApi, detected_codec.id, m_conf.gpuVendorId);
×
UNCOV
186
    if(autoFmt != AV_PIX_FMT_NONE)
×
187
      hwAccel = autoFmt;
×
188
    else
189
      return {};
×
UNCOV
190
  }
×
191

192
  const auto device = ffmpegHardwareDecodingFormats(hwAccel).device;
×
193
  if(device == AV_HWDEVICE_TYPE_NONE)
×
194
    return {};
×
195

UNCOV
196
  auto mapped = Video::hwCodecName(detected_codec.name, device);
×
197
  if(mapped.empty())
×
UNCOV
198
    return {};
×
199

UNCOV
200
  auto codec = mapped == detected_codec.name
×
201
                   ? &detected_codec // VideoToolbox case
×
UNCOV
202
                   : avcodec_find_decoder_by_name(mapped.c_str());
×
UNCOV
203
  if(!codec)
×
204
    return {};
×
205

206
  if(hwAccel == AV_PIX_FMT_DRM_PRIME)
×
207
  {
208
    // V4L2M2M: just want to map h264 to h264_v4l2m2m,
209
    // this isn't a true "hwdevice" accel
210
    return {nullptr, codec};
×
211
  }
212

UNCOV
213
  AVBufferRef* hw_device_ctx{};
×
UNCOV
214
  int ret = av_hwdevice_ctx_create(&hw_device_ctx, device, nullptr, nullptr, 0);
×
UNCOV
215
  if(ret != 0)
×
216
    return {};
×
217

218
  if(hwAccel == AV_PIX_FMT_QSV)
×
UNCOV
219
    return {hw_device_ctx, codec};
×
220
  else
UNCOV
221
    return {hw_device_ctx, &detected_codec};
×
222
#else
223
  return {};
224
#endif
UNCOV
225
}
×
226

227
ReadFrame LibAVDecoder::enqueue_frame(const AVPacket* pkt) noexcept
×
228
{
229
  auto frame = m_frames.newFrame();
×
230

231
  ReadFrame read
232
      = readVideoFrame(m_codecContext, pkt, frame.get(), this->m_conf.ignorePTS);
×
UNCOV
233
  if(read.error == AVERROR_EOF)
×
234
  {
235
    m_finished = true;
×
UNCOV
236
  }
×
237

238
  if(!read.frame)
×
239
  {
UNCOV
240
    this->m_frames.enqueue_decoding_error(frame.release());
×
241
    return read;
×
242
  }
243

244
  if(m_rescale)
×
245
  {
UNCOV
246
    m_rescale.rescale(m_frames, frame, read);
×
UNCOV
247
  }
×
248
  else
249
  {
UNCOV
250
    if(read.frame == frame.get())
×
UNCOV
251
      frame.release();
×
252
  }
UNCOV
253
  return read;
×
UNCOV
254
}
×
255

256
#if 0
257
static void listHardwareDecodeTextureFormats(AVFrame* frame)
258
{
259
#if LIBAVUTIL_VERSION_MAJOR >= 57
260
  AVPixelFormat* arr = {};
261
  av_hwframe_transfer_get_formats(
262
      frame->hw_frames_ctx,
263
      AVHWFrameTransferDirection::AV_HWFRAME_TRANSFER_DIRECTION_FROM, &arr, 0);
264
  for(auto p = arr; *p != AV_PIX_FMT_NONE; ++p)
265
  {
266
    auto desc = av_pix_fmt_desc_get(*p);
267
    if(desc)
268
      qDebug() << "supported format : " << desc->name;
269
  }
270
  av_free(arr);
271
#endif
272
}
273
#endif
274

275
// Mainly used for HAP which we do not want to decode through ffmpeg
276
void LibAVDecoder::load_packet_in_frame(const AVPacket& packet, AVFrame& frame)
×
277
{
278
  auto cp = m_avstream->codecpar;
×
279
  // TODO this is a hack, we store the FOURCC in the format...
280

281
  memcpy(&frame.format, &cp->codec_tag, 4);
×
282

UNCOV
283
  frame.buf[0] = av_buffer_ref(packet.buf);
×
UNCOV
284
  frame.width = cp->width;
×
UNCOV
285
  frame.height = cp->height;
×
286
  frame.format = (cp->codec_tag);
×
UNCOV
287
  frame.best_effort_timestamp = packet.pts;
×
288
  frame.data[0] = packet.data;
×
UNCOV
289
  frame.linesize[0] = packet.size;
×
290
  frame.pts = packet.pts;
×
UNCOV
291
  frame.pkt_dts = packet.dts;
×
292
#if(LIBAVUTIL_VERSION_MAJOR < 58)
293
  frame.pkt_duration = packet.duration;
294
#else
295
  frame.duration = packet.duration;
×
296
#endif
297
}
×
298

299
ReadFrame readVideoFrame(
×
300
    AVCodecContext* codecContext, const AVPacket* pkt, AVFrame* frame, bool ignorePts)
301
{
302
  if(codecContext && pkt && frame)
×
303
  {
UNCOV
304
    int ret = avcodec_send_packet(codecContext, pkt);
×
305
    // avcodec_send_packet: if it's EAGAIN then we *have* to read through avcodec_receive_frame
UNCOV
306
    if(ret < 0 && ret != AVERROR(EAGAIN))
×
307
    {
UNCOV
308
      if(ret != AVERROR_EOF)
×
309
        qDebug() << "avcodec_send_packet: " << av_to_string(ret) << ret;
×
310

UNCOV
311
      return {nullptr, ret};
×
312
    }
313

UNCOV
314
    ret = avcodec_receive_frame(codecContext, frame);
×
315

UNCOV
316
    if(ret < 0)
×
317
    {
UNCOV
318
      return {nullptr, ret};
×
319
    }
320
    else
321
    {
322
      if(ignorePts || frame->pts >= 0)
×
323
      {
324
#if LIBAVUTIL_VERSION_MAJOR >= 57
325
        // Transfer HW frame to CPU
326
        if(formatIsHardwareDecoded(AVPixelFormat(frame->format)))
×
327
        {
328
          AVFrame* sw_frame = av_frame_alloc();
×
329
          sw_frame->format = AV_PIX_FMT_NONE;
×
330

331
          int hw_ret = av_hwframe_transfer_data(sw_frame, frame, 0);
×
332
          if(hw_ret >= 0)
×
333
          {
334
            sw_frame->pts = frame->pts;
×
UNCOV
335
            av_frame_unref(frame);
×
UNCOV
336
            av_frame_move_ref(frame, sw_frame);
×
UNCOV
337
          }
×
338
          av_frame_free(&sw_frame);
×
UNCOV
339
          if(hw_ret < 0)
×
UNCOV
340
            return {nullptr, hw_ret};
×
UNCOV
341
        }
×
342
#endif
343
        return {frame, ret};
×
344
      }
345
      else
346
      {
UNCOV
347
        return {nullptr, ret};
×
348
      }
349
    }
350
  }
351

UNCOV
352
  return {nullptr, AVERROR_UNKNOWN};
×
UNCOV
353
}
×
354

355
VideoInterface::~VideoInterface() { }
8✔
356

357
VideoDecoder::VideoDecoder(DecoderConfiguration conf) noexcept
12✔
358
{
6✔
359
  m_conf = std::move(conf);
6✔
360
}
361

362
VideoDecoder::~VideoDecoder() noexcept
6✔
UNCOV
363
{
×
364
  close_file();
6✔
365
}
6✔
366

367
bool VideoDecoder::open(const std::string& inputFile) noexcept
6✔
368
{
369
  close_file();
6✔
370

371
  m_inputFile = inputFile;
6✔
372
  this->filePath = inputFile;
6✔
373

374
  if(avformat_open_input(&m_formatContext, inputFile.c_str(), nullptr, nullptr) != 0)
6✔
375
  {
376
    close_file();
6✔
377
    return false;
6✔
378
  }
379

380
  if(avformat_find_stream_info(m_formatContext, nullptr) < 0)
×
381
  {
UNCOV
382
    close_file();
×
383
    return false;
×
384
  }
385

386
  if(!open_stream())
×
387
  {
UNCOV
388
    close_file();
×
389
    return false;
×
390
  }
391

392
  int64_t secs = m_formatContext->duration / AV_TIME_BASE;
×
UNCOV
393
  int64_t us = m_formatContext->duration % AV_TIME_BASE;
×
394

395
  m_duration = secs * ossia::flicks_per_second<int64_t>;
×
UNCOV
396
  m_duration += us * ossia::flicks_per_millisecond<int64_t> / 1000;
×
397

UNCOV
398
  return true;
×
399
}
6✔
400

401
bool VideoDecoder::load(const std::string& inputFile) noexcept
×
402
{
UNCOV
403
  if(!open(inputFile))
×
404
    return false;
×
405

UNCOV
406
  m_running.store(true, std::memory_order_release);
×
407
  // TODO use a thread pool
UNCOV
408
  m_thread = std::thread{[this] {
×
409
    ossia::set_thread_name("ossia video");
×
UNCOV
410
    this->buffer_thread();
×
UNCOV
411
  }};
×
412

UNCOV
413
  return true;
×
414
}
×
415

416
int64_t VideoDecoder::duration() const noexcept
×
417
{
418
  return m_duration;
×
419
}
420

421
void VideoDecoder::seek(int64_t flicks)
×
422
{
423
  m_seekTo = flicks;
×
424
  m_condVar.notify_one();
×
425
}
×
426

UNCOV
427
AVFrame* VideoDecoder::dequeue_frame() noexcept
×
428
{
429
  auto f = m_frames.discard_and_dequeue_one();
×
UNCOV
430
  if(f)
×
431
  {
432
    m_last_dequeued_dts = f->pkt_dts;
×
UNCOV
433
  }
×
434
  m_condVar.notify_one();
×
UNCOV
435
  return f;
×
436
}
437

438
void VideoDecoder::release_frame(AVFrame* frame) noexcept
×
439
{
440
  m_frames.release(frame);
×
441
}
×
442

UNCOV
443
void VideoDecoder::buffer_thread() noexcept
×
444
{
445
  while(m_running.load(std::memory_order_acquire))
×
446
  {
447
    if(int64_t seek = m_seekTo.exchange(-1); seek >= 0)
×
448
    {
449
      seek_impl(seek);
×
450
    }
×
451
    else
452
    {
UNCOV
453
      std::unique_lock lck{m_condMut};
×
454
      m_condVar.wait(lck, [&] {
×
455
        return (m_frames.size() < frames_to_buffer / 2 && !m_finished)
×
UNCOV
456
               || !m_running.load(std::memory_order_acquire) || (m_seekTo != -1);
×
457
      });
UNCOV
458
      if(!m_running.load(std::memory_order_acquire))
×
459
        return;
×
460

461
      if(int64_t seek = m_seekTo.exchange(-1); seek >= 0)
×
462
      {
463
        seek_impl(seek);
×
464
      }
×
465

UNCOV
466
      if(m_frames.size() < (frames_to_buffer / 2) && !m_finished)
×
467
      {
UNCOV
468
        if(auto f = read_frame_impl())
×
469
        {
UNCOV
470
          m_frames.enqueue(f);
×
UNCOV
471
        }
×
UNCOV
472
        std::this_thread::sleep_for(std::chrono::milliseconds(4));
×
UNCOV
473
      }
×
UNCOV
474
    }
×
475
  }
476
}
×
477

478
void VideoDecoder::close_file() noexcept
18✔
479
{
480
  // Stop the running status
481
  m_running.store(false, std::memory_order_release);
18✔
482
  m_condVar.notify_one();
18✔
483

484
  if(m_thread.joinable())
18✔
UNCOV
485
    m_thread.join();
×
486

487
  // Remove frames that were in flight
488
  m_frames.drain();
18✔
489

490
  // Clear the stream
491
  close_video();
18✔
492

493
  // Clear the fmt context
494
  if(m_formatContext)
18✔
495
  {
496
    avio_flush(m_formatContext->pb);
×
UNCOV
497
    avformat_flush(m_formatContext);
×
498
    // avformat_close_input() already frees the context and sets it to nullptr;
499
    // do NOT also call avformat_free_context() on it (double free).
500
    avformat_close_input(&m_formatContext);
×
UNCOV
501
    m_formatContext = nullptr;
×
502
  }
×
503
}
18✔
504

505
ReadFrame LibAVDecoder::read_one_frame_raw(AVPacket& packet)
×
506
{
UNCOV
507
  int res{};
×
508

UNCOV
509
  while((res = av_read_frame(m_formatContext, &packet)) >= 0)
×
510
  {
511
    if(packet.stream_index == m_avstream->index)
×
512
    {
UNCOV
513
      auto frame = m_frames.newFrame();
×
UNCOV
514
      if(frame->buf[0])
×
515
        av_buffer_unref(&frame->buf[0]);
×
516
      // Mainly for HAP: we feed the raw undecoded codec data directly to the GPU, see HAPDecoder
UNCOV
517
      load_packet_in_frame(packet, *frame);
×
518

519
      av_packet_unref(&packet);
×
UNCOV
520
      return {frame.release(), 0};
×
UNCOV
521
    }
×
522
    else
523
    {
524
      av_packet_unref(&packet);
×
525
    }
526
  }
527

528
  if(res != 0 && res != AVERROR_EOF)
×
529
  {
530
    // qDebug() << "Error while reading a frame: "
531
    //          << av_to_string(res);
532
  }
×
UNCOV
533
  else if(res == AVERROR_EOF)
×
534
  {
535
    m_finished = true;
×
UNCOV
536
  }
×
537
  av_packet_unref(&packet);
×
UNCOV
538
  return {nullptr, res};
×
539
}
×
540

UNCOV
541
ReadFrame LibAVDecoder::read_one_frame_avcodec(AVPacket& packet)
×
542
{
UNCOV
543
  ReadFrame ret_frame;
×
544
  int res{};
×
545

UNCOV
546
  int z = 0;
×
547
do_read_frame:
UNCOV
548
  av_packet_unref(&packet);
×
549
  while((res = av_read_frame(m_formatContext, &packet)) >= 0)
×
550
  {
UNCOV
551
    if(packet.stream_index == m_avstream->index)
×
552
    {
553
      SCORE_ASSERT(m_codecContext);
×
554

555
      //av_packet_rescale_ts(
556
      //     &packet, this->m_avstream->time_base, this->m_codecContext->pkt_timebase);
557

UNCOV
558
      ret_frame = enqueue_frame(&packet);
×
UNCOV
559
      if(ret_frame.error == AVERROR(EAGAIN))
×
560
      {
UNCOV
561
        if(z++ < 100)
×
UNCOV
562
          goto do_read_frame;
×
UNCOV
563
      }
×
564
      av_packet_unref(&packet);
×
UNCOV
565
      return ret_frame;
×
566
    }
567
    else
568
    {
569
      av_packet_unref(&packet);
×
570
    }
571
  }
572

UNCOV
573
  if(res != 0 && res != AVERROR_EOF)
×
574
  {
575
    // qDebug() << "Error while reading a frame: "
576
    //          << av_to_string(res);
UNCOV
577
  }
×
578
  else if(res == AVERROR_EOF)
×
579
  {
580
    // Flush codec to get remaining frames from the reorder buffer (B-frames)
581
    if(m_codecContext)
×
582
    {
583
      avcodec_send_packet(m_codecContext, nullptr);
×
584
      auto frame = m_frames.newFrame();
×
585
      while(avcodec_receive_frame(m_codecContext, frame.get()) == 0)
×
586
      {
587
        // Through the rescaler, like every other decode path: these are the
588
        // last frames of the clip, and a decoder whose pixel_format was
589
        // relabelled RGBA by init_scaler must not suddenly emit its native
590
        // format for them.
591
        ReadFrame read{frame.get(), 0};
×
592
        if(m_rescale)
×
593
        {
594
          m_rescale.rescale(m_frames, frame, read);
×
595
        }
×
UNCOV
596
        else if(read.frame == frame.get())
×
597
        {
UNCOV
598
          frame.release();
×
UNCOV
599
        }
×
UNCOV
600
        if(read.frame)
×
UNCOV
601
          m_frames.enqueue(read.frame);
×
UNCOV
602
        frame = m_frames.newFrame();
×
603
      }
UNCOV
604
      m_frames.enqueue_decoding_error(frame.release());
×
UNCOV
605
    }
×
UNCOV
606
    m_finished = true;
×
UNCOV
607
  }
×
UNCOV
608
  av_packet_unref(&packet);
×
UNCOV
609
  return {nullptr, res};
×
UNCOV
610
}
×
611

UNCOV
612
ReadFrame LibAVDecoder::read_one_frame(AVPacket& packet)
×
613
{
614
  if(m_conf.useAVCodec)
×
UNCOV
615
    return read_one_frame_avcodec(packet);
×
616
  else
617
    return read_one_frame_raw(packet);
×
UNCOV
618
}
×
619
/*
620
// https://stackoverflow.com/a/44468529/1495627
621
static
622
int seek_to_frame(AVFormatContext* format, AVStream* stream, int frameIndex)
623
{
624
  using namespace std;
625
  // Seek is done on packet dts
626
  int64_t target_dts_usecs = std::round(frameIndex * (double)stream->r_frame_rate.den / stream->r_frame_rate.num * AV_TIME_BASE);
627
  // Remove first dts: when non zero seek should be more accurate
628
  auto first_dts_usecs = std::round(stream->first_dts * (double)stream->time_base.num / stream->time_base.den * AV_TIME_BASE);
629
  target_dts_usecs += first_dts_usecs;
630
  return av_seek_frame(format, -1, target_dts_usecs, AVSEEK_FLAG_BACKWARD);
631
}
632
*/
633

UNCOV
634
static int64_t to_av_time_base(AVRational tb, int64_t dts)
×
635
{
UNCOV
636
  constexpr auto av_tb = AVRational{1, AV_TIME_BASE};
×
UNCOV
637
  return av_rescale_q(dts, tb, av_tb);
×
638
}
639

UNCOV
640
bool VideoDecoder::seek_impl(int64_t flicks) noexcept
×
641
{
642
  if(m_avstream->index >= int(m_formatContext->nb_streams))
×
UNCOV
643
    return false;
×
644

645
  // Seeking with stream == -1 means that it is done AV_TIME_BASE
UNCOV
646
  constexpr auto av_tb = AVRational{1, AV_TIME_BASE};
×
UNCOV
647
  constexpr auto av_dts_per_flicks
×
648
      = (av_tb.den / (av_tb.num * ossia::flicks_per_second<double>));
649

650
  const int64_t dts = flicks * av_dts_per_flicks;
×
651

652
  const auto codec_tb
UNCOV
653
      = m_codecContext ? m_codecContext->pkt_timebase : m_avstream->time_base;
×
654

655
  // Don't seek if we're less than 0.2 second close to the request
656
  // unit of the timestamps in seconds: stream->time_base.num / stream->time_base.den
657

658
  // qDebug() << "Codec pkt_timebase: " << m_codecContext->pkt_timebase.num
659
  //          << m_codecContext->pkt_timebase.den;
660
  // qDebug() << "Codec timebase: " << m_codecContext->time_base.num
661
  //          << m_codecContext->time_base.den;
662
  // qDebug() << "Stream timebase: " << stream->time_base.num << stream->time_base.den;
663
  // qDebug() << "AV timebase: " << av_tb.num << av_tb.den;
UNCOV
664
  const auto last_av_dts = to_av_time_base(codec_tb, m_last_dequeued_dts);
×
665
  const int64_t min_dts_delta = (0.2 * av_tb.den) / av_tb.num;
×
666
  // qDebug() << AV_TIME_BASE << min_dts_delta << dts << last_av_dts << dts - last_av_dts
667
  //          << (std::abs(dts - last_av_dts) <= min_dts_delta);
UNCOV
668
  if(last_av_dts > INT64_MIN && std::abs(dts - last_av_dts) <= min_dts_delta)
×
669
  {
670
    // Let's always ensure that we seek to zero when asked no matter what
UNCOV
671
    if(dts != 0)
×
672
    {
673
      return false;
×
674
    }
675
  }
×
676

677
  // TODO - maybe we should also store the "last dequeued dts" from the
678
  // decoder side - this way no need to seek if we are in the interval
679
  // const bool seek_forward = dts >= this->m_last_dequeued_dts;
680
  // #if LIBAVFORMAT_VERSION_MAJOR >= 59
681
  //   const int64_t start = 0;
682
  // #else
683
  //   const int64_t start = m_avstream->first_dts;
684
  // #endif
685

UNCOV
686
  if(!ossia::seek_to_flick(m_formatContext, m_codecContext, m_avstream, flicks))
×
687
  {
UNCOV
688
    qDebug() << "Failed to seek for time ";
×
689
    return false;
×
690
  }
691

UNCOV
692
  ReadFrame r;
×
UNCOV
693
  do
×
694
  {
695
    // First flush the buffer or smth
UNCOV
696
    do
×
697
    {
UNCOV
698
      if(r.frame)
×
699
      {
UNCOV
700
        SCORE_LIBAV_FRAME_DEALLOC_CHECK(r.frame);
×
UNCOV
701
        av_frame_free(&r.frame);
×
UNCOV
702
      }
×
703

UNCOV
704
      auto pkt = av_packet_alloc();
×
705
      r = read_one_frame(*pkt);
×
UNCOV
706
      av_packet_unref(pkt);
×
707
      av_packet_free(&pkt);
×
UNCOV
708
    } while(r.error == AVERROR(EAGAIN));
×
709

710
    if(r.error == AVERROR_EOF || !r.frame)
×
711
    {
UNCOV
712
      break;
×
713
    }
714

715
    /*
716
    // Rescale the packet's dts into AV_TIME_BASE
717
    auto max_dts = r.frame->pkt_dts + r.frame->duration;
718
    auto max_av_dts = to_av_time_base(codec_tb, max_dts);
719
    //av_rescale_q(max_dts, stream->time_base, tb);
720
    // we're starting to see correct frames, try to get close to the dts we want.
721
    while(max_av_dts < dts)
722
    {
723
      r = read_one_frame(AVFramePointer{r.frame}, pkt);
724
      if(r.error == AVERROR_EOF || !r.frame)
725
        break;
726
    }
727
    */
UNCOV
728
  } while(0);
×
729

UNCOV
730
  if(r.frame)
×
731
  {
UNCOV
732
    m_frames.set_discard_frame(r.frame);
×
733
    m_frames.enqueue(r.frame);
×
734
  }
×
735
  else
736
  {
UNCOV
737
    SCORE_LIBAV_FRAME_DEALLOC_CHECK(r.frame);
×
738
    av_frame_free(&r.frame);
×
739
  }
740

741
  m_finished = false;
×
742

743
  return true;
×
UNCOV
744
}
×
745

746
AVFrame* VideoDecoder::read_frame_impl() noexcept
×
747
{
748
  ReadFrame res;
×
749

UNCOV
750
  if(m_avstream)
×
751
  {
UNCOV
752
    auto packet = av_packet_alloc();
×
753

UNCOV
754
    do
×
755
    {
756
      av_packet_unref(packet);
×
UNCOV
757
      res = read_one_frame(*packet);
×
758

UNCOV
759
      if(res.error == AVERROR_EOF)
×
760
      {
UNCOV
761
        m_finished = true;
×
762
        av_packet_unref(packet);
×
UNCOV
763
        av_packet_free(&packet);
×
764
        return res.frame;
×
765
      }
766
    } while(res.error == AVERROR(EAGAIN));
×
767

UNCOV
768
    av_packet_unref(packet);
×
769
    av_packet_free(&packet);
×
770
  }
×
771
  return res.frame;
×
UNCOV
772
}
×
773

UNCOV
774
bool VideoDecoder::open_stream() noexcept
×
775
{
776
  bool res = false;
×
777

778
  if(!m_formatContext)
×
UNCOV
779
    return res;
×
780

781
  int stream = -1;
×
782

783
  for(unsigned int i = 0; i < m_formatContext->nb_streams; i++)
×
784
  {
785
    if(m_formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
×
786
    {
787
      if(stream == -1)
×
788
      {
UNCOV
789
        stream = i;
×
790
        continue;
×
791
      }
792
    }
×
793
    m_formatContext->streams[i]->discard = AVDISCARD_ALL;
×
794
  }
×
795

UNCOV
796
  if(stream != -1)
×
797
  {
798
    m_avstream = m_formatContext->streams[stream];
×
799
    const AVRational tb = m_avstream->time_base;
×
800
    dts_per_flicks = (tb.den / (tb.num * ossia::flicks_per_second<double>));
×
801
    flicks_per_dts = (tb.num * ossia::flicks_per_second<double>) / tb.den;
×
802

UNCOV
803
    auto codecPar = m_avstream->codecpar;
×
UNCOV
804
    if((m_codec = avcodec_find_decoder(codecPar->codec_id)))
×
805
    {
806
      if(codecPar->width <= 0 || codecPar->height <= 0)
×
807
      {
UNCOV
808
        qDebug() << "VideoDecoder: invalid video: width or height is 0";
×
809
        res = false;
×
UNCOV
810
      }
×
811
      else
812
      {
813
        color_range = codecPar->color_range;
×
UNCOV
814
        color_primaries = codecPar->color_primaries;
×
UNCOV
815
        color_trc = codecPar->color_trc;
×
UNCOV
816
        color_space = codecPar->color_space;
×
817
        chroma_location = codecPar->chroma_location;
×
818

819
        // Detect wide-gamut / HDR evidence from primaries and transfer function.
820
        // This is used to infer color_space when it is unspecified.
821
        const bool has_bt2020_evidence =
×
UNCOV
822
            color_primaries == AVCOL_PRI_BT2020
×
823
            || color_trc == AVCOL_TRC_SMPTE2084      // PQ (HDR10 / BT.2100)
×
824
            || color_trc == AVCOL_TRC_ARIB_STD_B67;   // HLG (BT.2100)
×
825

826
        // Display P3 content may use BT.709 matrix coefficients
827
        // but with wider primaries. Don't force it to BT.2020.
UNCOV
828
        const bool has_p3_evidence =
×
UNCOV
829
            color_primaries == AVCOL_PRI_SMPTE432     // Display P3 (D65)
×
UNCOV
830
            || color_primaries == AVCOL_PRI_SMPTE431; // DCI-P3
×
831

UNCOV
832
        if(color_space == AVCOL_SPC_UNSPECIFIED)
×
833
        {
UNCOV
834
          if(has_bt2020_evidence)
×
UNCOV
835
            color_space = AVCOL_SPC_BT2020_NCL;
×
UNCOV
836
          else if(has_p3_evidence)
×
837
            // P3 content typically uses BT.709 matrix coefficients.
838
            // colorMatrix() will detect the P3 primaries and route
839
            // through the Display P3 pipeline.
UNCOV
840
            color_space = AVCOL_SPC_BT709;
×
UNCOV
841
          else if(codecPar->height < 625)
×
UNCOV
842
            color_space = AVCOL_SPC_SMPTE170M;
×
UNCOV
843
          else if(codecPar->height < 720)
×
UNCOV
844
            color_space = AVCOL_SPC_BT470BG;
×
845
          else
UNCOV
846
            color_space = AVCOL_SPC_BT709;
×
UNCOV
847
        }
×
848
        if(color_range == AVCOL_RANGE_UNSPECIFIED)
×
849
          color_range = AVCOL_RANGE_MPEG;
×
850

851
        // HDR handling
852
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(61, 3, 100)
853
        {
854
          const auto data = codecPar->coded_side_data;
855
          const auto n = codecPar->nb_coded_side_data;
856
          // Light data
857
          if(auto sd = av_packet_side_data_get(data, n, AV_PKT_DATA_CONTENT_LIGHT_LEVEL))
858
            if(sd->data)
859
              this->content_light = *reinterpret_cast<const AVContentLightMetadata*>(sd->data);
860

861
          // Mastering side data
862
          if (auto sd = av_packet_side_data_get(data, n, AV_PKT_DATA_MASTERING_DISPLAY_METADATA))
863
            if(sd->data)
864
              this->mastering_display = *(AVMasteringDisplayMetadata *)sd->data;
865
        }
866
#endif
867

868
        codec_id = codecPar->codec_id;
×
869

870
        // Check if this is a GPU-direct codec (HAP or DXV DXT1/DXT5)
UNCOV
871
        bool use_gpu_direct = false;
×
872
        if(m_avstream->codecpar->codec_id == AV_CODEC_ID_HAP)
×
873
        {
874
          // HAP: store the FOURCC in the format for GPU decoder matching
UNCOV
875
          memcpy(&pixel_format, &m_avstream->codecpar->codec_tag, 4);
×
876
          use_gpu_direct = true;
×
877
        }
×
878
        else if(m_avstream->codecpar->codec_id == AV_CODEC_ID_DXV)
×
879
        {
880
          // DXV: peek first packet to determine sub-format (DXT1/DXT5)
881
          // Store synthetic fourcc in pixel_format for GPU decoder matching
882
          auto packet = av_packet_alloc();
×
UNCOV
883
          if(av_read_frame(m_formatContext, packet) >= 0 && packet->size >= 4)
×
884
          {
885
            uint32_t tag = packet->data[0] | (packet->data[1] << 8)
×
886
                           | (packet->data[2] << 16)
×
UNCOV
887
                           | ((uint32_t)packet->data[3] << 24);
×
888
            switch(tag)
×
889
            {
890
              case 0x44585431: // MKBETAG('D','X','T','1')
891
                memcpy(&pixel_format, "Dxv1", 4);
×
UNCOV
892
                use_gpu_direct = true;
×
893
                break;
×
894
              case 0x44585435: // MKBETAG('D','X','T','5')
895
                memcpy(&pixel_format, "Dxv5", 4);
×
UNCOV
896
                use_gpu_direct = true;
×
897
                break;
×
898
              case 0x59434736: // MKBETAG('Y','C','G','6')
UNCOV
899
                memcpy(&pixel_format, "DxvY", 4);
×
900
                use_gpu_direct = true;
×
901
                break;
×
902
              case 0x59473130: // MKBETAG('Y','G','1','0')
UNCOV
903
                memcpy(&pixel_format, "DxvA", 4);
×
904
                use_gpu_direct = true;
×
905
                break;
×
906
              default: {
907
                // Old format: check type flags in high byte
UNCOV
908
                uint8_t old_type = tag >> 24;
×
909
                if(old_type & 0x40)
×
910
                {
911
                  memcpy(&pixel_format, "Dxv5", 4);
×
UNCOV
912
                  use_gpu_direct = true;
×
913
                }
×
914
                else if(old_type & 0x20)
×
915
                {
916
                  memcpy(&pixel_format, "Dxv1", 4);
×
917
                  use_gpu_direct = true;
×
UNCOV
918
                }
×
919
                // Unknown old format falls through to avcodec
920
                break;
×
921
              }
922
            }
923
            av_packet_unref(packet);
×
UNCOV
924
          }
×
925
          av_packet_free(&packet);
×
926
          // Seek back to beginning regardless
927
          av_seek_frame(m_formatContext, m_avstream->index, 0, AVSEEK_FLAG_BACKWARD);
×
928
        }
×
929

930
        if(use_gpu_direct)
×
931
        {
932
          width = codecPar->width;
×
UNCOV
933
          height = codecPar->height;
×
934
          fps = av_q2d(m_avstream->avg_frame_rate);
×
935

936
          m_conf.useAVCodec = false;
×
937
          m_codecContext = nullptr;
×
UNCOV
938
          m_codec = nullptr;
×
UNCOV
939
          res = true;
×
940
        }
×
941
        else
942
        {
943
          pixel_format = (AVPixelFormat)codecPar->format;
×
UNCOV
944
          width = codecPar->width;
×
945
          height = codecPar->height;
×
946
          fps = av_q2d(m_avstream->avg_frame_rate);
×
947

948
          res = open_codec_context(*this, m_avstream, [this](AVCodecContext& ctx) {
×
UNCOV
949
            ctx.framerate
×
UNCOV
950
                = av_guess_frame_rate(m_formatContext, (AVStream*)m_avstream, NULL);
×
UNCOV
951
            m_codecContext->pkt_timebase = m_avstream->time_base;
×
952
            // m_codecContext->codec_id = m_codec->id;
UNCOV
953
          });
×
954

UNCOV
955
          if(m_codecContext)
×
956
          {
UNCOV
957
            auto tb = m_codecContext->pkt_timebase;
×
UNCOV
958
            dts_per_flicks = (tb.den / (tb.num * ossia::flicks_per_second<double>));
×
UNCOV
959
            flicks_per_dts = (tb.num * ossia::flicks_per_second<double>) / tb.den;
×
UNCOV
960
          }
×
961
        }
962
      }
963
    }
×
964
  }
×
965

UNCOV
966
  if(!res)
×
967
  {
UNCOV
968
    close_video();
×
UNCOV
969
  }
×
UNCOV
970
  return res;
×
UNCOV
971
}
×
972

973
void VideoDecoder::close_video() noexcept
18✔
974
{
975
  if(m_codecContext)
18✔
976
  {
UNCOV
977
    avcodec_flush_buffers(m_codecContext);
×
978
#if defined(__APPLE__)
979
#if FF_API_VT_HWACCEL_CONTEXT
980
    if(m_codecContext->hwaccel_context)
981
      av_videotoolbox_default_free(m_codecContext);
982
#endif
983
#endif
UNCOV
984
    avcodec_free_context(&m_codecContext);
×
985

UNCOV
986
    m_codecContext = nullptr;
×
UNCOV
987
    m_codec = nullptr;
×
UNCOV
988
  }
×
989

990
  m_rescale.close();
18✔
991

992
  m_avstream = nullptr;
18✔
993
}
18✔
994
}
995
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc