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

ossia / score / 33555463967

01 Sep 2026 08:27PM UTC coverage: 23.997% (+0.3%) from 23.689%
33555463967

push

github

jcelerier
avnd: clear one-shot controls on the CPU-analysis GFX renderer too

The renderer for texture-input / value-output nodes (GfxNode with no texture,
buffer or geometry output) runs the processor like CpuFilterNode does and was
missed by the previous commit; its impulses stayed set forever.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SV6BNsGfa18QxBcaaWoe7m

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

1073 existing lines in 18 files now uncovered.

54239 of 226023 relevant lines covered (24.0%)

62188.78 hits per line

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

54.21
/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 <cstdlib>
18
#include <functional>
19
#include <iostream>
20
#include <string_view>
21
#include <thread>
22

23
#if defined(__linux__)
24
#include <unistd.h>
25
#endif
26

27
#if SCORE_HAS_LIBAV
28

29
extern "C" {
30
#include <libavcodec/avcodec.h>
31
#include <libavformat/avformat.h>
32
#include <libavutil/pixdesc.h>
33
#include <libswscale/swscale.h>
34
#include <libavcodec/packet.h>
35
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(61, 3, 100)
36
#if __has_include(<libavutil/mastering_display_metadata.h>)
37
#include <libavutil/mastering_display_metadata.h>
38
#endif
39
#endif
40
}
41

42
#if __APPLE__ && __has_include(<libavcodec/videotoolbox.h>)
43
#include "VideoDecoder.vtb.cpp"
44
#endif
45

46
namespace Video
47
{
48
#if LIBAVUTIL_VERSION_MAJOR >= 57
49
static auto get_format_for_codeccontext(AVCodecContext* ctx, const AVPixelFormat* p)
×
50
{
51
  //qDebug() << "device: " << av_pix_fmt_desc_get(ctx->pix_fmt)->name;
52

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

UNCOV
72
  return ctx->pix_fmt;
×
UNCOV
73
}
×
74
#endif
75

76
void LibAVDecoder::init_scaler(VideoInterface& self) noexcept
1✔
77
{
78
  if(!Video::formatNeedsDecoding(self.pixel_format))
1✔
79
    return;
1✔
80

UNCOV
81
  m_rescale.open(self);
×
82

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

UNCOV
91
  self.pixel_format = AV_PIX_FMT_RGBA;
×
UNCOV
92
  self.color_space = AVCOL_SPC_RGB;
×
93
}
1✔
94

95
int LibAVDecoder::init_codec_context(
5✔
96
    const AVCodec* codec, AVBufferRef* hw_dev_ctx, const AVStream* stream,
97
    std::function<void(AVCodecContext&)> setup)
98
{
99
  m_codecContext = avcodec_alloc_context3(codec);
5✔
100

101
  avcodec_parameters_to_context(m_codecContext, stream->codecpar);
5✔
102

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

132
  SCORE_ASSERT(setup);
5✔
133
  setup(*m_codecContext);
5✔
134

135
  int err = avcodec_open2(m_codecContext, codec, nullptr);
5✔
136
  if(err < 0)
5✔
137
  {
UNCOV
138
    qDebug() << "avcodec_open2: " << av_to_string(err);
×
UNCOV
139
    avcodec_free_context(&m_codecContext);
×
UNCOV
140
  }
×
141
  return err;
5✔
UNCOV
142
}
×
143

144
bool LibAVDecoder::open_codec_context(
1✔
145
    VideoInterface& self, const AVStream* stream,
146
    std::function<void(AVCodecContext&)> setup)
147
{
148
  if(auto [hw_dev_ctx, hw_codec] = open_hwdec(*m_codec); hw_codec)
1✔
149
  {
150
    int err = init_codec_context(hw_codec, hw_dev_ctx, stream, setup);
×
UNCOV
151
    if(err == 0)
×
152
    {
UNCOV
153
      init_scaler(self);
×
UNCOV
154
      return true;
×
155
    }
UNCOV
156
  }
×
157

158
  // Maybe opening an HW accel failed, we retry in software mode
159
  int err = init_codec_context(m_codec, nullptr, stream, setup);
1✔
160
  if(err == 0)
1✔
161
  {
162
    init_scaler(self);
1✔
163
    return true;
1✔
164
  }
UNCOV
165
  return false;
×
166
}
1✔
167

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

187
  if(hwAccel == AV_PIX_FMT_NONE
×
UNCOV
188
     || !codecSupportsHWPixelFormat(detected_codec.id, hwAccel))
×
189
  {
190
    auto autoFmt = selectHardwareAcceleration(
×
UNCOV
191
        m_conf.graphicsApi, detected_codec.id, m_conf.gpuVendorId);
×
192
    if(autoFmt != AV_PIX_FMT_NONE)
×
193
      hwAccel = autoFmt;
×
194
    else
UNCOV
195
      return {};
×
196
  }
×
197

198
  const auto device = ffmpegHardwareDecodingFormats(hwAccel).device;
×
UNCOV
199
  if(device == AV_HWDEVICE_TYPE_NONE)
×
200
    return {};
×
201

202
  auto mapped = Video::hwCodecName(detected_codec.name, device);
×
203
  if(mapped.empty())
×
204
    return {};
×
205

206
  auto codec = mapped == detected_codec.name
×
UNCOV
207
                   ? &detected_codec // VideoToolbox case
×
UNCOV
208
                   : avcodec_find_decoder_by_name(mapped.c_str());
×
UNCOV
209
  if(!codec)
×
210
    return {};
×
211

UNCOV
212
  if(hwAccel == AV_PIX_FMT_DRM_PRIME)
×
213
  {
214
    // V4L2M2M: just want to map h264 to h264_v4l2m2m,
215
    // this isn't a true "hwdevice" accel
216
    return {nullptr, codec};
×
217
  }
218

219
  AVBufferRef* hw_device_ctx{};
×
UNCOV
220
  int ret = Video::createHardwareDevice(&hw_device_ctx, device);
×
221
  if(ret != 0)
×
UNCOV
222
    return {};
×
223

UNCOV
224
  if(hwAccel == AV_PIX_FMT_QSV)
×
UNCOV
225
    return {hw_device_ctx, codec};
×
226
  else
UNCOV
227
    return {hw_device_ctx, &detected_codec};
×
228
#else
229
  return {};
230
#endif
231
}
1✔
232

233
ReadFrame LibAVDecoder::enqueue_frame(const AVPacket* pkt) noexcept
160✔
234
{
235
  auto receive = [this]() -> ReadFrame
478✔
236
  {
237
    auto frame = m_frames.newFrame();
318✔
238
    auto read
239
        = receiveVideoFrame(m_codecContext, frame.get(), this->m_conf.ignorePTS);
318✔
240
    if(read.error == AVERROR_EOF)
318✔
241
      m_finished = true;
6✔
242

243
    if(!read.frame)
318✔
244
    {
245
      m_frames.enqueue_decoding_error(frame.release());
161✔
246
      return read;
161✔
247
    }
248

249
    if(m_rescale)
157✔
250
    {
UNCOV
251
      m_rescale.rescale(m_frames, frame, read);
×
UNCOV
252
    }
×
253
    else if(read.frame == frame.get())
157✔
254
    {
255
      frame.release();
157✔
256
    }
157✔
257

258
    return read;
157✔
259
  };
318✔
260

261
  ReadFrame last{nullptr, AVERROR(EAGAIN)};
160✔
262
  auto keepInOrder = [this, &last](ReadFrame read) {
317✔
263
    if(last.frame)
157✔
264
      m_frames.enqueue(last.frame);
18✔
265
    last = read;
157✔
266
  };
157✔
267

268
  // A decoder may refuse a new packet until every pending frame has been
269
  // received. Drain those frames and retry the exact same packet.
270
  for(;;)
160✔
271
  {
272
    const int ret = avcodec_send_packet(m_codecContext, pkt);
164✔
273
    if(ret == 0)
164✔
274
      break;
159✔
275
    if(ret != AVERROR(EAGAIN))
5✔
276
    {
277
      if(ret == AVERROR_EOF)
1✔
278
        m_finished = true;
1✔
279
      else
UNCOV
280
        qDebug() << "avcodec_send_packet: " << av_to_string(ret) << ret;
×
281
      return last.frame ? last : ReadFrame{nullptr, ret};
1✔
282
    }
283

284
    auto read = receive();
4✔
285
    if(read.frame)
4✔
286
    {
287
      keepInOrder(read);
2✔
288
      continue;
2✔
289
    }
290

291
    // error == 0 with no frame: a frame was decoded but discarded (negative
292
    // pts). The codec still made room, so the packet must be retried, not
293
    // dropped.
294
    if(read.error == 0)
2✔
295
      continue;
2✔
296

297
    // EAGAIN from send_packet guarantees receive_frame yields a frame; if it
298
    // does not, no progress is possible: bail out instead of spinning.
UNCOV
299
    return last.frame ? last : read;
×
300
  }
301

302
  // One packet can make more than one frame available. Receive all of them
303
  // before reading the next packet so inter-frame reference chains stay intact.
304
  for(;;)
159✔
305
  {
306
    auto read = receive();
314✔
307
    if(read.frame)
314✔
308
    {
309
      keepInOrder(read);
155✔
310
      continue;
155✔
311
    }
312
    // A decoded-but-discarded frame (negative pts) is not the end of the
313
    // available frames: keep draining.
314
    if(read.error == 0)
159✔
UNCOV
315
      continue;
×
316
    if(read.error != AVERROR(EAGAIN) && read.error != AVERROR_EOF)
159✔
UNCOV
317
      return last.frame ? last : read;
×
318
    break;
159✔
319
  }
320

321
  if(last.frame)
159✔
322
    last.error = 0;
139✔
323
  return last;
159✔
324
}
160✔
325

326
#if 0
327
static void listHardwareDecodeTextureFormats(AVFrame* frame)
328
{
329
#if LIBAVUTIL_VERSION_MAJOR >= 57
330
  AVPixelFormat* arr = {};
331
  av_hwframe_transfer_get_formats(
332
      frame->hw_frames_ctx,
333
      AVHWFrameTransferDirection::AV_HWFRAME_TRANSFER_DIRECTION_FROM, &arr, 0);
334
  for(auto p = arr; *p != AV_PIX_FMT_NONE; ++p)
335
  {
336
    auto desc = av_pix_fmt_desc_get(*p);
337
    if(desc)
338
      qDebug() << "supported format : " << desc->name;
339
  }
340
  av_free(arr);
341
#endif
342
}
343
#endif
344

345
// Mainly used for HAP which we do not want to decode through ffmpeg
UNCOV
346
void LibAVDecoder::load_packet_in_frame(const AVPacket& packet, AVFrame& frame)
×
347
{
348
  auto cp = m_avstream->codecpar;
×
349
  // TODO this is a hack, we store the FOURCC in the format...
350

351
  memcpy(&frame.format, &cp->codec_tag, 4);
×
352

353
  frame.buf[0] = av_buffer_ref(packet.buf);
×
354
  frame.width = cp->width;
×
355
  frame.height = cp->height;
×
UNCOV
356
  frame.format = (cp->codec_tag);
×
UNCOV
357
  frame.best_effort_timestamp = packet.pts;
×
UNCOV
358
  frame.data[0] = packet.data;
×
359
  frame.linesize[0] = packet.size;
×
UNCOV
360
  frame.pts = packet.pts;
×
361
  frame.pkt_dts = packet.dts;
×
362
#if(LIBAVUTIL_VERSION_MAJOR < 58)
363
  frame.pkt_duration = packet.duration;
364
#else
UNCOV
365
  frame.duration = packet.duration;
×
366
#endif
UNCOV
367
}
×
368

369
ReadFrame receiveVideoFrame(
318✔
370
    AVCodecContext* codecContext, AVFrame* frame, bool ignorePts)
371
{
372
  if(codecContext && frame)
318✔
373
  {
374
    int ret = avcodec_receive_frame(codecContext, frame);
318✔
375

376
    if(ret < 0)
318✔
377
    {
378
      return {nullptr, ret};
159✔
379
    }
380
    else
381
    {
382
      if(ignorePts || frame->pts >= 0)
159✔
383
      {
384
#if LIBAVUTIL_VERSION_MAJOR >= 57
385
        // Transfer HW frame to CPU
386
        if(formatIsHardwareDecoded(AVPixelFormat(frame->format)))
157✔
387
        {
388
          AVFrame* sw_frame = av_frame_alloc();
×
389
          sw_frame->format = AV_PIX_FMT_NONE;
×
390

391
          int hw_ret = av_hwframe_transfer_data(sw_frame, frame, 0);
×
392
          if(hw_ret >= 0)
×
393
          {
394
            sw_frame->pts = frame->pts;
×
395
            av_frame_unref(frame);
×
UNCOV
396
            av_frame_move_ref(frame, sw_frame);
×
UNCOV
397
          }
×
UNCOV
398
          av_frame_free(&sw_frame);
×
UNCOV
399
          if(hw_ret < 0)
×
UNCOV
400
            return {nullptr, hw_ret};
×
UNCOV
401
        }
×
402
#endif
403
        return {frame, ret};
157✔
404
      }
405
      else
406
      {
407
        return {nullptr, ret};
2✔
408
      }
409
    }
410
  }
411

UNCOV
412
  return {nullptr, AVERROR_UNKNOWN};
×
413
}
318✔
414

415
VideoInterface::~VideoInterface() { }
9✔
416

417
VideoDecoder::VideoDecoder(DecoderConfiguration conf) noexcept
14✔
418
{
7✔
419
  m_conf = std::move(conf);
7✔
420
}
421

422
VideoDecoder::~VideoDecoder() noexcept
7✔
UNCOV
423
{
×
424
  close_file();
7✔
425
}
7✔
426

427
bool VideoDecoder::open(const std::string& inputFile) noexcept
7✔
428
{
429
  close_file();
7✔
430

431
  m_inputFile = inputFile;
7✔
432
  this->filePath = inputFile;
7✔
433

434
  if(avformat_open_input(&m_formatContext, inputFile.c_str(), nullptr, nullptr) != 0)
7✔
435
  {
436
    close_file();
6✔
437
    return false;
6✔
438
  }
439

440
  if(avformat_find_stream_info(m_formatContext, nullptr) < 0)
1✔
441
  {
442
    close_file();
×
443
    return false;
×
444
  }
445

446
  if(!open_stream())
1✔
447
  {
UNCOV
448
    close_file();
×
UNCOV
449
    return false;
×
450
  }
451

452
  // Elementary streams and truncated files have no known duration:
453
  // formatContext->duration stays AV_NOPTS_VALUE (INT64_MIN), and scaling
454
  // that to flicks is a signed overflow. Report 0, like "unknown".
455
  if(m_formatContext->duration == AV_NOPTS_VALUE || m_formatContext->duration < 0)
1✔
456
  {
UNCOV
457
    m_duration = 0;
×
458
  }
×
459
  else
460
  {
461
    int64_t secs = m_formatContext->duration / AV_TIME_BASE;
1✔
462
    int64_t us = m_formatContext->duration % AV_TIME_BASE;
1✔
463

464
    m_duration = secs * ossia::flicks_per_second<int64_t>;
1✔
465
    m_duration += us * ossia::flicks_per_millisecond<int64_t> / 1000;
1✔
466
  }
467

468
  return true;
1✔
469
}
7✔
470

471
bool VideoDecoder::load(const std::string& inputFile) noexcept
1✔
472
{
473
  if(!open(inputFile))
1✔
UNCOV
474
    return false;
×
475

476
  m_running.store(true, std::memory_order_release);
1✔
477
  // TODO use a thread pool
478
  m_thread = std::thread{[this] {
2✔
479
    ossia::set_thread_name("ossia video");
1✔
480
    this->buffer_thread();
1✔
481
  }};
1✔
482

483
  return true;
1✔
484
}
1✔
485

UNCOV
486
int64_t VideoDecoder::duration() const noexcept
×
487
{
UNCOV
488
  return m_duration;
×
489
}
490

UNCOV
491
void VideoDecoder::seek(int64_t flicks)
×
492
{
UNCOV
493
  m_seekTo = flicks;
×
UNCOV
494
  m_condVar.notify_one();
×
UNCOV
495
}
×
496

497
AVFrame* VideoDecoder::dequeue_frame() noexcept
310✔
498
{
499
  auto f = m_frames.discard_and_dequeue_one();
310✔
500
  if(f)
310✔
501
  {
502
    m_last_dequeued_dts = f->pkt_dts;
50✔
503
  }
50✔
504
  m_condVar.notify_one();
310✔
505
  return f;
310✔
506
}
507

508
void VideoDecoder::release_frame(AVFrame* frame) noexcept
50✔
509
{
510
  m_frames.release(frame);
50✔
511
}
50✔
512

513
void VideoDecoder::buffer_thread() noexcept
1✔
514
{
515
  while(m_running.load(std::memory_order_acquire))
43✔
516
  {
517
    if(int64_t seek = m_seekTo.exchange(-1); seek >= 0)
43✔
518
    {
UNCOV
519
      seek_impl(seek);
×
UNCOV
520
    }
×
521
    else
522
    {
523
      std::unique_lock lck{m_condMut};
43✔
524
      m_condVar.wait(lck, [&] {
183✔
525
        return (m_frames.size() < frames_to_buffer / 2 && !m_finished)
280✔
526
               || !m_running.load(std::memory_order_acquire) || (m_seekTo != -1);
140✔
527
      });
528
      if(!m_running.load(std::memory_order_acquire))
43✔
529
        return;
1✔
530

531
      if(int64_t seek = m_seekTo.exchange(-1); seek >= 0)
42✔
532
      {
UNCOV
533
        seek_impl(seek);
×
UNCOV
534
      }
×
535

536
      if(m_frames.size() < (frames_to_buffer / 2) && !m_finished)
42✔
537
      {
538
        if(auto f = read_frame_impl())
42✔
539
        {
540
          m_frames.enqueue(f);
41✔
541
        }
41✔
542
        std::this_thread::sleep_for(std::chrono::milliseconds(4));
42✔
543
      }
42✔
544
    }
43✔
545
  }
546
}
1✔
547

548
void VideoDecoder::close_file() noexcept
20✔
549
{
550
  // Stop the running status
551
  m_running.store(false, std::memory_order_release);
20✔
552
  m_condVar.notify_one();
20✔
553

554
  if(m_thread.joinable())
20✔
555
    m_thread.join();
1✔
556

557
  // Remove frames that were in flight
558
  m_frames.drain();
20✔
559

560
  // Clear the stream
561
  close_video();
20✔
562

563
  // Clear the fmt context
564
  if(m_formatContext)
20✔
565
  {
566
    avio_flush(m_formatContext->pb);
1✔
567
    avformat_flush(m_formatContext);
1✔
568
    // avformat_close_input() already frees the context and sets it to nullptr;
569
    // do NOT also call avformat_free_context() on it (double free).
570
    avformat_close_input(&m_formatContext);
1✔
571
    m_formatContext = nullptr;
1✔
572
  }
1✔
573
}
20✔
574

575
ReadFrame LibAVDecoder::read_one_frame_raw(AVPacket& packet)
×
576
{
UNCOV
577
  int res{};
×
578

UNCOV
579
  while((res = av_read_frame(m_formatContext, &packet)) >= 0)
×
580
  {
UNCOV
581
    if(packet.stream_index == m_avstream->index)
×
582
    {
UNCOV
583
      auto frame = m_frames.newFrame();
×
UNCOV
584
      if(frame->buf[0])
×
UNCOV
585
        av_buffer_unref(&frame->buf[0]);
×
586
      // Mainly for HAP: we feed the raw undecoded codec data directly to the GPU, see HAPDecoder
587
      load_packet_in_frame(packet, *frame);
×
588

589
      av_packet_unref(&packet);
×
590
      return {frame.release(), 0};
×
591
    }
×
592
    else
593
    {
UNCOV
594
      av_packet_unref(&packet);
×
595
    }
596
  }
597

598
  // A file that ends in garbage (truncation, an interrupted transcode) makes
599
  // av_read_frame return AVERROR_INVALIDDATA & co forever instead of EOF.
600
  // Either way no more packets will ever come: finish, like ffplay does —
601
  // otherwise buffer_thread retries this error at full tilt for the rest of
602
  // the process's life.
UNCOV
603
  if(res < 0 && res != AVERROR(EAGAIN))
×
604
  {
UNCOV
605
    m_finished = true;
×
UNCOV
606
  }
×
UNCOV
607
  av_packet_unref(&packet);
×
UNCOV
608
  return {nullptr, res};
×
UNCOV
609
}
×
610

611
ReadFrame LibAVDecoder::read_one_frame_avcodec(AVPacket& packet)
42✔
612
{
613
  ReadFrame ret_frame;
42✔
614
  int res{};
42✔
615

616
  int z = 0;
42✔
617
do_read_frame:
618
  av_packet_unref(&packet);
51✔
619
  while((res = av_read_frame(m_formatContext, &packet)) >= 0)
51✔
620
  {
621
    if(packet.stream_index == m_avstream->index)
50✔
622
    {
623
      SCORE_ASSERT(m_codecContext);
50✔
624

625
      //av_packet_rescale_ts(
626
      //     &packet, this->m_avstream->time_base, this->m_codecContext->pkt_timebase);
627

628
      ret_frame = enqueue_frame(&packet);
50✔
629
      if(ret_frame.error == AVERROR(EAGAIN))
50✔
630
      {
631
        if(z++ < 100)
9✔
632
          goto do_read_frame;
9✔
UNCOV
633
      }
×
634
      av_packet_unref(&packet);
41✔
635
      return ret_frame;
41✔
636
    }
637
    else
638
    {
UNCOV
639
      av_packet_unref(&packet);
×
640
    }
641
  }
642

643
  // AVERROR_EOF, or a persistent demux error: a truncated or corrupt tail
644
  // makes av_read_frame return AVERROR_INVALIDDATA & co forever, never EOF.
645
  // Either way no more packets will ever come, so both are the end of the
646
  // stream, like ffplay treats them — otherwise buffer_thread retries the
647
  // error at full tilt forever and the frames still buffered inside the
648
  // decoder are never shown.
649
  if(res < 0 && res != AVERROR(EAGAIN))
1✔
650
  {
651
    // Flush codec to get remaining frames from the reorder buffer (B-frames)
652
    if(m_codecContext)
1✔
653
    {
654
      // enqueue_frame routes every drained frame through the rescaler, like
655
      // every other decode path: these are the last frames of the clip, and a
656
      // decoder whose pixel_format was relabelled RGBA by init_scaler must
657
      // not suddenly emit its native format for them.
658
      auto flushed = enqueue_frame(nullptr);
1✔
659
      if(flushed.frame)
1✔
660
        m_frames.enqueue(flushed.frame);
1✔
661
    }
1✔
662
    m_finished = true;
1✔
663
  }
1✔
664
  av_packet_unref(&packet);
1✔
665
  return {nullptr, res};
1✔
666
}
42✔
667

668
ReadFrame LibAVDecoder::read_one_frame(AVPacket& packet)
42✔
669
{
670
  if(m_conf.useAVCodec)
42✔
671
    return read_one_frame_avcodec(packet);
42✔
672
  else
673
    return read_one_frame_raw(packet);
×
674
}
42✔
675
/*
676
// https://stackoverflow.com/a/44468529/1495627
677
static
678
int seek_to_frame(AVFormatContext* format, AVStream* stream, int frameIndex)
679
{
680
  using namespace std;
681
  // Seek is done on packet dts
682
  int64_t target_dts_usecs = std::round(frameIndex * (double)stream->r_frame_rate.den / stream->r_frame_rate.num * AV_TIME_BASE);
683
  // Remove first dts: when non zero seek should be more accurate
684
  auto first_dts_usecs = std::round(stream->first_dts * (double)stream->time_base.num / stream->time_base.den * AV_TIME_BASE);
685
  target_dts_usecs += first_dts_usecs;
686
  return av_seek_frame(format, -1, target_dts_usecs, AVSEEK_FLAG_BACKWARD);
687
}
688
*/
689

UNCOV
690
static int64_t to_av_time_base(AVRational tb, int64_t dts)
×
691
{
692
  constexpr auto av_tb = AVRational{1, AV_TIME_BASE};
×
UNCOV
693
  return av_rescale_q(dts, tb, av_tb);
×
694
}
695

UNCOV
696
bool VideoDecoder::seek_impl(int64_t flicks) noexcept
×
697
{
UNCOV
698
  if(m_avstream->index >= int(m_formatContext->nb_streams))
×
UNCOV
699
    return false;
×
700

701
  // Seeking with stream == -1 means that it is done AV_TIME_BASE
UNCOV
702
  constexpr auto av_tb = AVRational{1, AV_TIME_BASE};
×
703
  constexpr auto av_dts_per_flicks
×
704
      = (av_tb.den / (av_tb.num * ossia::flicks_per_second<double>));
705

UNCOV
706
  const int64_t dts = flicks * av_dts_per_flicks;
×
707

708
  const auto codec_tb
UNCOV
709
      = m_codecContext ? m_codecContext->pkt_timebase : m_avstream->time_base;
×
710

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

714
  // qDebug() << "Codec pkt_timebase: " << m_codecContext->pkt_timebase.num
715
  //          << m_codecContext->pkt_timebase.den;
716
  // qDebug() << "Codec timebase: " << m_codecContext->time_base.num
717
  //          << m_codecContext->time_base.den;
718
  // qDebug() << "Stream timebase: " << stream->time_base.num << stream->time_base.den;
719
  // qDebug() << "AV timebase: " << av_tb.num << av_tb.den;
UNCOV
720
  const auto last_av_dts = to_av_time_base(codec_tb, m_last_dequeued_dts);
×
UNCOV
721
  const int64_t min_dts_delta = (0.2 * av_tb.den) / av_tb.num;
×
722
  // qDebug() << AV_TIME_BASE << min_dts_delta << dts << last_av_dts << dts - last_av_dts
723
  //          << (std::abs(dts - last_av_dts) <= min_dts_delta);
UNCOV
724
  if(last_av_dts > INT64_MIN && std::abs(dts - last_av_dts) <= min_dts_delta)
×
725
  {
726
    // Let's always ensure that we seek to zero when asked no matter what
727
    if(dts != 0)
×
728
    {
UNCOV
729
      return false;
×
730
    }
731
  }
×
732

733
  // TODO - maybe we should also store the "last dequeued dts" from the
734
  // decoder side - this way no need to seek if we are in the interval
735
  // const bool seek_forward = dts >= this->m_last_dequeued_dts;
736
  // #if LIBAVFORMAT_VERSION_MAJOR >= 59
737
  //   const int64_t start = 0;
738
  // #else
739
  //   const int64_t start = m_avstream->first_dts;
740
  // #endif
741

UNCOV
742
  if(!ossia::seek_to_flick(m_formatContext, m_codecContext, m_avstream, flicks))
×
743
  {
744
    qDebug() << "Failed to seek for time ";
×
745
    return false;
×
746
  }
747

UNCOV
748
  ReadFrame r;
×
749
  do
×
750
  {
751
    // First flush the buffer or smth
UNCOV
752
    do
×
753
    {
UNCOV
754
      if(r.frame)
×
755
      {
UNCOV
756
        SCORE_LIBAV_FRAME_DEALLOC_CHECK(r.frame);
×
UNCOV
757
        av_frame_free(&r.frame);
×
UNCOV
758
      }
×
759

UNCOV
760
      auto pkt = av_packet_alloc();
×
UNCOV
761
      r = read_one_frame(*pkt);
×
UNCOV
762
      av_packet_unref(pkt);
×
UNCOV
763
      av_packet_free(&pkt);
×
UNCOV
764
    } while(r.error == AVERROR(EAGAIN));
×
765

UNCOV
766
    if(r.error == AVERROR_EOF || !r.frame)
×
767
    {
UNCOV
768
      break;
×
769
    }
770

771
    /*
772
    // Rescale the packet's dts into AV_TIME_BASE
773
    auto max_dts = r.frame->pkt_dts + r.frame->duration;
774
    auto max_av_dts = to_av_time_base(codec_tb, max_dts);
775
    //av_rescale_q(max_dts, stream->time_base, tb);
776
    // we're starting to see correct frames, try to get close to the dts we want.
777
    while(max_av_dts < dts)
778
    {
779
      r = read_one_frame(AVFramePointer{r.frame}, pkt);
780
      if(r.error == AVERROR_EOF || !r.frame)
781
        break;
782
    }
783
    */
UNCOV
784
  } while(0);
×
785

UNCOV
786
  if(r.frame)
×
787
  {
UNCOV
788
    m_frames.set_discard_frame(r.frame);
×
UNCOV
789
    m_frames.enqueue(r.frame);
×
UNCOV
790
  }
×
791
  else
792
  {
UNCOV
793
    SCORE_LIBAV_FRAME_DEALLOC_CHECK(r.frame);
×
UNCOV
794
    av_frame_free(&r.frame);
×
795
  }
796

UNCOV
797
  m_finished = false;
×
798

UNCOV
799
  return true;
×
UNCOV
800
}
×
801

802
AVFrame* VideoDecoder::read_frame_impl() noexcept
42✔
803
{
804
  ReadFrame res;
42✔
805

806
  if(m_avstream)
42✔
807
  {
808
    auto packet = av_packet_alloc();
42✔
809

810
    do
42✔
811
    {
812
      av_packet_unref(packet);
42✔
813
      res = read_one_frame(*packet);
42✔
814

815
      if(res.error == AVERROR_EOF)
42✔
816
      {
817
        m_finished = true;
1✔
818
        av_packet_unref(packet);
1✔
819
        av_packet_free(&packet);
1✔
820
        return res.frame;
1✔
821
      }
822
    } while(res.error == AVERROR(EAGAIN));
41✔
823

824
    av_packet_unref(packet);
41✔
825
    av_packet_free(&packet);
41✔
826
  }
41✔
827
  return res.frame;
41✔
828
}
42✔
829

830
bool VideoDecoder::open_stream() noexcept
1✔
831
{
832
  bool res = false;
1✔
833

834
  if(!m_formatContext)
1✔
UNCOV
835
    return res;
×
836

837
  int stream = -1;
1✔
838

839
  for(unsigned int i = 0; i < m_formatContext->nb_streams; i++)
2✔
840
  {
841
    if(m_formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
1✔
842
    {
843
      if(stream == -1)
1✔
844
      {
845
        stream = i;
1✔
846
        continue;
1✔
847
      }
848
    }
×
849
    m_formatContext->streams[i]->discard = AVDISCARD_ALL;
×
UNCOV
850
  }
×
851

852
  if(stream != -1)
1✔
853
  {
854
    m_avstream = m_formatContext->streams[stream];
1✔
855
    const AVRational tb = m_avstream->time_base;
1✔
856
    dts_per_flicks = (tb.den / (tb.num * ossia::flicks_per_second<double>));
1✔
857
    flicks_per_dts = (tb.num * ossia::flicks_per_second<double>) / tb.den;
1✔
858

859
    auto codecPar = m_avstream->codecpar;
1✔
860
    if((m_codec = avcodec_find_decoder(codecPar->codec_id)))
1✔
861
    {
862
      if(codecPar->width <= 0 || codecPar->height <= 0)
1✔
863
      {
UNCOV
864
        qDebug() << "VideoDecoder: invalid video: width or height is 0";
×
UNCOV
865
        res = false;
×
UNCOV
866
      }
×
867
      else
868
      {
869
        color_range = codecPar->color_range;
1✔
870
        color_primaries = codecPar->color_primaries;
1✔
871
        color_trc = codecPar->color_trc;
1✔
872
        color_space = codecPar->color_space;
1✔
873
        chroma_location = codecPar->chroma_location;
1✔
874

875
        // Detect wide-gamut / HDR evidence from primaries and transfer function.
876
        // This is used to infer color_space when it is unspecified.
877
        const bool has_bt2020_evidence =
1✔
878
            color_primaries == AVCOL_PRI_BT2020
1✔
879
            || color_trc == AVCOL_TRC_SMPTE2084      // PQ (HDR10 / BT.2100)
1✔
880
            || color_trc == AVCOL_TRC_ARIB_STD_B67;   // HLG (BT.2100)
1✔
881

882
        // Display P3 content may use BT.709 matrix coefficients
883
        // but with wider primaries. Don't force it to BT.2020.
884
        const bool has_p3_evidence =
1✔
885
            color_primaries == AVCOL_PRI_SMPTE432     // Display P3 (D65)
1✔
886
            || color_primaries == AVCOL_PRI_SMPTE431; // DCI-P3
1✔
887

888
        if(color_space == AVCOL_SPC_UNSPECIFIED)
1✔
889
        {
890
          if(has_bt2020_evidence)
1✔
UNCOV
891
            color_space = AVCOL_SPC_BT2020_NCL;
×
892
          else if(has_p3_evidence)
1✔
893
            // P3 content typically uses BT.709 matrix coefficients.
894
            // colorMatrix() will detect the P3 primaries and route
895
            // through the Display P3 pipeline.
UNCOV
896
            color_space = AVCOL_SPC_BT709;
×
897
          else if(codecPar->height < 625)
1✔
898
            color_space = AVCOL_SPC_SMPTE170M;
1✔
UNCOV
899
          else if(codecPar->height < 720)
×
UNCOV
900
            color_space = AVCOL_SPC_BT470BG;
×
901
          else
UNCOV
902
            color_space = AVCOL_SPC_BT709;
×
903
        }
1✔
904
        if(color_range == AVCOL_RANGE_UNSPECIFIED)
1✔
905
          color_range = AVCOL_RANGE_MPEG;
1✔
906

907
        // HDR handling
908
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(61, 3, 100)
909
        {
910
          const auto data = codecPar->coded_side_data;
911
          const auto n = codecPar->nb_coded_side_data;
912
          // Light data
913
          if(auto sd = av_packet_side_data_get(data, n, AV_PKT_DATA_CONTENT_LIGHT_LEVEL))
914
            if(sd->data)
915
              this->content_light = *reinterpret_cast<const AVContentLightMetadata*>(sd->data);
916

917
          // Mastering side data
918
          if (auto sd = av_packet_side_data_get(data, n, AV_PKT_DATA_MASTERING_DISPLAY_METADATA))
919
            if(sd->data)
920
              this->mastering_display = *(AVMasteringDisplayMetadata *)sd->data;
921
        }
922
#endif
923

924
        codec_id = codecPar->codec_id;
1✔
925

926
        // Check if this is a GPU-direct codec (HAP or DXV DXT1/DXT5)
927
        bool use_gpu_direct = false;
1✔
928
        if(m_avstream->codecpar->codec_id == AV_CODEC_ID_HAP)
1✔
929
        {
930
          // HAP: store the FOURCC in the format for GPU decoder matching
931
          memcpy(&pixel_format, &m_avstream->codecpar->codec_tag, 4);
×
932
          use_gpu_direct = true;
×
UNCOV
933
        }
×
934
        else if(m_avstream->codecpar->codec_id == AV_CODEC_ID_DXV)
1✔
935
        {
936
          // DXV: peek first packet to determine sub-format (DXT1/DXT5)
937
          // Store synthetic fourcc in pixel_format for GPU decoder matching
938
          auto packet = av_packet_alloc();
×
939
          if(av_read_frame(m_formatContext, packet) >= 0 && packet->size >= 4)
×
940
          {
UNCOV
941
            uint32_t tag = packet->data[0] | (packet->data[1] << 8)
×
942
                           | (packet->data[2] << 16)
×
943
                           | ((uint32_t)packet->data[3] << 24);
×
944
            switch(tag)
×
945
            {
946
              case 0x44585431: // MKBETAG('D','X','T','1')
947
                memcpy(&pixel_format, "Dxv1", 4);
×
948
                use_gpu_direct = true;
×
UNCOV
949
                break;
×
950
              case 0x44585435: // MKBETAG('D','X','T','5')
951
                memcpy(&pixel_format, "Dxv5", 4);
×
952
                use_gpu_direct = true;
×
953
                break;
×
954
              case 0x59434736: // MKBETAG('Y','C','G','6')
955
                memcpy(&pixel_format, "DxvY", 4);
×
956
                use_gpu_direct = true;
×
957
                break;
×
958
              case 0x59473130: // MKBETAG('Y','G','1','0')
959
                memcpy(&pixel_format, "DxvA", 4);
×
UNCOV
960
                use_gpu_direct = true;
×
UNCOV
961
                break;
×
962
              default: {
963
                // Old format: check type flags in high byte
964
                uint8_t old_type = tag >> 24;
×
UNCOV
965
                if(old_type & 0x40)
×
966
                {
967
                  memcpy(&pixel_format, "Dxv5", 4);
×
UNCOV
968
                  use_gpu_direct = true;
×
UNCOV
969
                }
×
UNCOV
970
                else if(old_type & 0x20)
×
971
                {
972
                  memcpy(&pixel_format, "Dxv1", 4);
×
973
                  use_gpu_direct = true;
×
UNCOV
974
                }
×
975
                // Unknown old format falls through to avcodec
976
                break;
×
977
              }
978
            }
979
            av_packet_unref(packet);
×
UNCOV
980
          }
×
UNCOV
981
          av_packet_free(&packet);
×
982
          // Seek back to beginning regardless
UNCOV
983
          av_seek_frame(m_formatContext, m_avstream->index, 0, AVSEEK_FLAG_BACKWARD);
×
UNCOV
984
        }
×
985

986
        if(use_gpu_direct)
1✔
987
        {
UNCOV
988
          width = codecPar->width;
×
UNCOV
989
          height = codecPar->height;
×
UNCOV
990
          fps = av_q2d(m_avstream->avg_frame_rate);
×
991

UNCOV
992
          m_conf.useAVCodec = false;
×
UNCOV
993
          m_codecContext = nullptr;
×
UNCOV
994
          m_codec = nullptr;
×
UNCOV
995
          res = true;
×
UNCOV
996
        }
×
997
        else
998
        {
999
          pixel_format = (AVPixelFormat)codecPar->format;
1✔
1000
          width = codecPar->width;
1✔
1001
          height = codecPar->height;
1✔
1002
          fps = av_q2d(m_avstream->avg_frame_rate);
1✔
1003

1004
          res = open_codec_context(*this, m_avstream, [this](AVCodecContext& ctx) {
2✔
1005
            ctx.framerate
1✔
1006
                = av_guess_frame_rate(m_formatContext, (AVStream*)m_avstream, NULL);
2✔
1007
            m_codecContext->pkt_timebase = m_avstream->time_base;
1✔
1008
            // m_codecContext->codec_id = m_codec->id;
1009
          });
1✔
1010

1011
          if(m_codecContext)
1✔
1012
          {
1013
            auto tb = m_codecContext->pkt_timebase;
1✔
1014
            dts_per_flicks = (tb.den / (tb.num * ossia::flicks_per_second<double>));
1✔
1015
            flicks_per_dts = (tb.num * ossia::flicks_per_second<double>) / tb.den;
1✔
1016
          }
1✔
1017
        }
1018
      }
1019
    }
1✔
1020
  }
1✔
1021

1022
  if(!res)
1✔
1023
  {
UNCOV
1024
    close_video();
×
UNCOV
1025
  }
×
1026
  return res;
1✔
1027
}
1✔
1028

1029
void VideoDecoder::close_video() noexcept
20✔
1030
{
1031
  if(m_codecContext)
20✔
1032
  {
1033
    avcodec_flush_buffers(m_codecContext);
1✔
1034
#if defined(__APPLE__)
1035
#if FF_API_VT_HWACCEL_CONTEXT
1036
    if(m_codecContext->hwaccel_context)
1037
      av_videotoolbox_default_free(m_codecContext);
1038
#endif
1039
#endif
1040
    avcodec_free_context(&m_codecContext);
1✔
1041

1042
    m_codecContext = nullptr;
1✔
1043
    m_codec = nullptr;
1✔
1044
  }
1✔
1045

1046
  m_rescale.close();
20✔
1047

1048
  m_avstream = nullptr;
20✔
1049
}
20✔
1050
}
1051
#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