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

ossia / score / 30161608849

25 Jul 2026 02:25PM UTC coverage: 15.303% (-0.008%) from 15.311%
30161608849

Pull #2148

github

web-flow
Merge d2b82a62f into 13afd939a
Pull Request #2148: wasm: stream large media from Blob URLs instead of copying into RAM (Tier B)

4 of 179 new or added lines in 10 files covered. (2.23%)

7 existing lines in 5 files now uncovered.

30396 of 198627 relevant lines covered (15.3%)

983.52 hits per line

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

0.0
/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
  self.pixel_format = AV_PIX_FMT_RGBA;
×
77
  self.color_space = AVCOL_SPC_RGB;
×
78
}
×
79

80
int LibAVDecoder::init_codec_context(
×
81
    const AVCodec* codec, AVBufferRef* hw_dev_ctx, const AVStream* stream,
82
    std::function<void(AVCodecContext&)> setup)
83
{
84
  m_codecContext = avcodec_alloc_context3(codec);
×
85

86
  avcodec_parameters_to_context(m_codecContext, stream->codecpar);
×
87

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

117
  SCORE_ASSERT(setup);
×
118
  setup(*m_codecContext);
×
119

120
  int err = avcodec_open2(m_codecContext, codec, nullptr);
×
121
  if(err < 0)
×
122
  {
123
    qDebug() << "avcodec_open2: " << av_to_string(err);
×
124
    avcodec_free_context(&m_codecContext);
×
125
  }
×
126
  return err;
×
127
}
×
128

129
bool LibAVDecoder::open_codec_context(
×
130
    VideoInterface& self, const AVStream* stream,
131
    std::function<void(AVCodecContext&)> setup)
132
{
133
  if(auto [hw_dev_ctx, hw_codec] = open_hwdec(*m_codec); hw_codec)
×
134
  {
135
    int err = init_codec_context(hw_codec, hw_dev_ctx, stream, setup);
×
136
    if(err == 0)
×
137
    {
138
      init_scaler(self);
×
139
      return true;
×
140
    }
141
  }
×
142

143
  // Maybe opening an HW accel failed, we retry in software mode
144
  int err = init_codec_context(m_codec, nullptr, stream, setup);
×
145
  if(err == 0)
×
146
  {
147
    init_scaler(self);
×
148
    return true;
×
149
  }
150
  return false;
×
151
}
×
152

153
/*
154
 *
155
    using codec_map_type = ossia::flat_map<AVCodecID, const char*>;
156
    static const codec_map_type codecs{
157
        {AV_CODEC_ID_AV1, "av1_cuvid"},          {AV_CODEC_ID_H264, "h264_cuvid"},
158
        {AV_CODEC_ID_HEVC, "hevc_cuvid"},        {AV_CODEC_ID_MJPEG, "mjpeg_cuvid"},
159
        {AV_CODEC_ID_MPEG1VIDEO, "mpeg1_cuvid"}, {AV_CODEC_ID_MPEG2VIDEO, "mpeg2_cuvid"},
160
        {AV_CODEC_ID_MPEG4, "mpeg4_cuvid"},      {AV_CODEC_ID_VC1, "vc1_cuvid"},
161
        {AV_CODEC_ID_VP8, "vp8_cuvid"},          {AV_CODEC_ID_VP9, "vp9_cuvid"},
162
    };
163
    */
164
std::pair<AVBufferRef*, const AVCodec*>
165
LibAVDecoder::open_hwdec(const AVCodec& detected_codec) noexcept
×
166
{
167
#if LIBAVUTIL_VERSION_MAJOR >= 57
168
  auto hwAccel = m_conf.hardwareAcceleration;
×
169
  if(hwAccel == AV_PIX_FMT_NONE)
×
170
    return {};
×
171

172
  if(hwAccel == AV_PIX_FMT_NONE
×
173
     || !codecSupportsHWPixelFormat(detected_codec.id, hwAccel))
×
174
  {
175
    auto autoFmt = selectHardwareAcceleration(
×
176
        m_conf.graphicsApi, detected_codec.id, m_conf.gpuVendorId);
×
177
    if(autoFmt != AV_PIX_FMT_NONE)
×
178
      hwAccel = autoFmt;
×
179
    else
180
      return {};
×
181
  }
×
182

183
  const auto device = ffmpegHardwareDecodingFormats(hwAccel).device;
×
184
  if(device == AV_HWDEVICE_TYPE_NONE)
×
185
    return {};
×
186

187
  auto mapped = Video::hwCodecName(detected_codec.name, device);
×
188
  if(mapped.empty())
×
189
    return {};
×
190

191
  auto codec = mapped == detected_codec.name
×
192
                   ? &detected_codec // VideoToolbox case
×
193
                   : avcodec_find_decoder_by_name(mapped.c_str());
×
194
  if(!codec)
×
195
    return {};
×
196

197
  if(hwAccel == AV_PIX_FMT_DRM_PRIME)
×
198
  {
199
    // V4L2M2M: just want to map h264 to h264_v4l2m2m,
200
    // this isn't a true "hwdevice" accel
201
    return {nullptr, codec};
×
202
  }
203

204
  AVBufferRef* hw_device_ctx{};
×
205
  int ret = av_hwdevice_ctx_create(&hw_device_ctx, device, nullptr, nullptr, 0);
×
206
  if(ret != 0)
×
207
    return {};
×
208

209
  if(hwAccel == AV_PIX_FMT_QSV)
×
210
    return {hw_device_ctx, codec};
×
211
  else
212
    return {hw_device_ctx, &detected_codec};
×
213
#else
214
  return {};
215
#endif
216
}
×
217

218
ReadFrame LibAVDecoder::enqueue_frame(const AVPacket* pkt) noexcept
×
219
{
220
  auto frame = m_frames.newFrame();
×
221

222
  ReadFrame read
223
      = readVideoFrame(m_codecContext, pkt, frame.get(), this->m_conf.ignorePTS);
×
224
  if(read.error == AVERROR_EOF)
×
225
  {
226
    m_finished = true;
×
227
  }
×
228

229
  if(!read.frame)
×
230
  {
231
    this->m_frames.enqueue_decoding_error(frame.release());
×
232
    return read;
×
233
  }
234

235
  if(m_rescale)
×
236
  {
237
    m_rescale.rescale(m_frames, frame, read);
×
238
  }
×
239
  else
240
  {
241
    if(read.frame == frame.get())
×
242
      frame.release();
×
243
  }
244
  return read;
×
245
}
×
246

247
#if 0
248
static void listHardwareDecodeTextureFormats(AVFrame* frame)
249
{
250
#if LIBAVUTIL_VERSION_MAJOR >= 57
251
  AVPixelFormat* arr = {};
252
  av_hwframe_transfer_get_formats(
253
      frame->hw_frames_ctx,
254
      AVHWFrameTransferDirection::AV_HWFRAME_TRANSFER_DIRECTION_FROM, &arr, 0);
255
  for(auto p = arr; *p != AV_PIX_FMT_NONE; ++p)
256
  {
257
    auto desc = av_pix_fmt_desc_get(*p);
258
    if(desc)
259
      qDebug() << "supported format : " << desc->name;
260
  }
261
  av_free(arr);
262
#endif
263
}
264
#endif
265

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

272
  memcpy(&frame.format, &cp->codec_tag, 4);
×
273

274
  frame.buf[0] = av_buffer_ref(packet.buf);
×
275
  frame.width = cp->width;
×
276
  frame.height = cp->height;
×
277
  frame.format = (cp->codec_tag);
×
278
  frame.best_effort_timestamp = packet.pts;
×
279
  frame.data[0] = packet.data;
×
280
  frame.linesize[0] = packet.size;
×
281
  frame.pts = packet.pts;
×
282
  frame.pkt_dts = packet.dts;
×
283
#if(LIBAVUTIL_VERSION_MAJOR < 58)
284
  frame.pkt_duration = packet.duration;
285
#else
286
  frame.duration = packet.duration;
×
287
#endif
288
}
×
289

290
ReadFrame readVideoFrame(
×
291
    AVCodecContext* codecContext, const AVPacket* pkt, AVFrame* frame, bool ignorePts)
292
{
293
  if(codecContext && pkt && frame)
×
294
  {
295
    int ret = avcodec_send_packet(codecContext, pkt);
×
296
    // avcodec_send_packet: if it's EAGAIN then we *have* to read through avcodec_receive_frame
297
    if(ret < 0 && ret != AVERROR(EAGAIN))
×
298
    {
299
      if(ret != AVERROR_EOF)
×
300
        qDebug() << "avcodec_send_packet: " << av_to_string(ret) << ret;
×
301

302
      return {nullptr, ret};
×
303
    }
304

305
    ret = avcodec_receive_frame(codecContext, frame);
×
306

307
    if(ret < 0)
×
308
    {
309
      return {nullptr, ret};
×
310
    }
311
    else
312
    {
313
      if(ignorePts || frame->pts >= 0)
×
314
      {
315
#if LIBAVUTIL_VERSION_MAJOR >= 57
316
        // Transfer HW frame to CPU
317
        if(formatIsHardwareDecoded(AVPixelFormat(frame->format)))
×
318
        {
319
          AVFrame* sw_frame = av_frame_alloc();
×
320
          sw_frame->format = AV_PIX_FMT_NONE;
×
321

322
          int hw_ret = av_hwframe_transfer_data(sw_frame, frame, 0);
×
323
          if(hw_ret >= 0)
×
324
          {
325
            sw_frame->pts = frame->pts;
×
326
            av_frame_unref(frame);
×
327
            av_frame_move_ref(frame, sw_frame);
×
328
          }
×
329
          av_frame_free(&sw_frame);
×
330
          if(hw_ret < 0)
×
331
            return {nullptr, hw_ret};
×
332
        }
×
333
#endif
334
        return {frame, ret};
×
335
      }
336
      else
337
      {
338
        return {nullptr, ret};
×
339
      }
340
    }
341
  }
342

343
  return {nullptr, AVERROR_UNKNOWN};
×
344
}
×
345

346
VideoInterface::~VideoInterface() { }
×
347

348
VideoDecoder::VideoDecoder(DecoderConfiguration conf) noexcept
×
349
{
×
350
  m_conf = std::move(conf);
×
351
}
352

353
VideoDecoder::~VideoDecoder() noexcept
×
354
{
×
355
  close_file();
×
356
}
×
357

358
bool VideoDecoder::open(const std::string& inputFile) noexcept
×
359
{
360
  close_file();
×
361

362
  m_inputFile = inputFile;
×
363
  this->filePath = inputFile;
×
364

NEW
365
  if(Media::isStreamedMediaPath(inputFile))
×
366
  {
NEW
367
    if(!Media::open_input_custom_io(
×
NEW
368
           m_formatContext, m_io, QString::fromStdString(inputFile)))
×
369
    {
NEW
370
      close_file();
×
NEW
371
      return false;
×
372
    }
NEW
373
  }
×
NEW
374
  else if(avformat_open_input(&m_formatContext, inputFile.c_str(), nullptr, nullptr) != 0)
×
375
  {
376
    close_file();
×
377
    return false;
×
378
  }
379

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

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

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

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

398
  return true;
×
399
}
×
400

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

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

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

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

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

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
    {
453
      std::unique_lock lck{m_condMut};
×
454
      m_condVar.wait(lck, [&] {
×
455
        return (m_frames.size() < frames_to_buffer / 2 && !m_finished)
×
456
               || !m_running.load(std::memory_order_acquire) || (m_seekTo != -1);
×
457
      });
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

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

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

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

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

490
  // Clear the stream
491
  close_video();
×
492

493
  // Clear the fmt context
494
  if(m_formatContext)
×
495
  {
496
    avio_flush(m_formatContext->pb);
×
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);
×
501
    m_formatContext = nullptr;
×
502
  }
×
NEW
503
  m_io = Media::AvIoDevice{};
×
UNCOV
504
}
×
505

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

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

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

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

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

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

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

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

574
  if(res != 0 && res != AVERROR_EOF)
×
575
  {
576
    // qDebug() << "Error while reading a frame: "
577
    //          << av_to_string(res);
578
  }
×
579
  else if(res == AVERROR_EOF)
×
580
  {
581
    // Flush codec to get remaining frames from the reorder buffer (B-frames)
582
    if(m_codecContext)
×
583
    {
584
      avcodec_send_packet(m_codecContext, nullptr);
×
585
      auto frame = m_frames.newFrame();
×
586
      while(avcodec_receive_frame(m_codecContext, frame.get()) == 0)
×
587
      {
588
        m_frames.enqueue(frame.release());
×
589
        frame = m_frames.newFrame();
×
590
      }
591
      m_frames.enqueue_decoding_error(frame.release());
×
592
    }
×
593
    m_finished = true;
×
594
  }
×
595
  av_packet_unref(&packet);
×
596
  return {nullptr, res};
×
597
}
×
598

599
ReadFrame LibAVDecoder::read_one_frame(AVPacket& packet)
×
600
{
601
  if(m_conf.useAVCodec)
×
602
    return read_one_frame_avcodec(packet);
×
603
  else
604
    return read_one_frame_raw(packet);
×
605
}
×
606
/*
607
// https://stackoverflow.com/a/44468529/1495627
608
static
609
int seek_to_frame(AVFormatContext* format, AVStream* stream, int frameIndex)
610
{
611
  using namespace std;
612
  // Seek is done on packet dts
613
  int64_t target_dts_usecs = std::round(frameIndex * (double)stream->r_frame_rate.den / stream->r_frame_rate.num * AV_TIME_BASE);
614
  // Remove first dts: when non zero seek should be more accurate
615
  auto first_dts_usecs = std::round(stream->first_dts * (double)stream->time_base.num / stream->time_base.den * AV_TIME_BASE);
616
  target_dts_usecs += first_dts_usecs;
617
  return av_seek_frame(format, -1, target_dts_usecs, AVSEEK_FLAG_BACKWARD);
618
}
619
*/
620

621
static int64_t to_av_time_base(AVRational tb, int64_t dts)
×
622
{
623
  constexpr auto av_tb = AVRational{1, AV_TIME_BASE};
×
624
  return av_rescale_q(dts, tb, av_tb);
×
625
}
626

627
bool VideoDecoder::seek_impl(int64_t flicks) noexcept
×
628
{
629
  if(m_avstream->index >= int(m_formatContext->nb_streams))
×
630
    return false;
×
631

632
  // Seeking with stream == -1 means that it is done AV_TIME_BASE
633
  constexpr auto av_tb = AVRational{1, AV_TIME_BASE};
×
634
  constexpr auto av_dts_per_flicks
×
635
      = (av_tb.den / (av_tb.num * ossia::flicks_per_second<double>));
636

637
  const int64_t dts = flicks * av_dts_per_flicks;
×
638

639
  const auto codec_tb
640
      = m_codecContext ? m_codecContext->pkt_timebase : m_avstream->time_base;
×
641

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

645
  // qDebug() << "Codec pkt_timebase: " << m_codecContext->pkt_timebase.num
646
  //          << m_codecContext->pkt_timebase.den;
647
  // qDebug() << "Codec timebase: " << m_codecContext->time_base.num
648
  //          << m_codecContext->time_base.den;
649
  // qDebug() << "Stream timebase: " << stream->time_base.num << stream->time_base.den;
650
  // qDebug() << "AV timebase: " << av_tb.num << av_tb.den;
651
  const auto last_av_dts = to_av_time_base(codec_tb, m_last_dequeued_dts);
×
652
  const int64_t min_dts_delta = (0.2 * av_tb.den) / av_tb.num;
×
653
  // qDebug() << AV_TIME_BASE << min_dts_delta << dts << last_av_dts << dts - last_av_dts
654
  //          << (std::abs(dts - last_av_dts) <= min_dts_delta);
655
  if(last_av_dts > INT64_MIN && std::abs(dts - last_av_dts) <= min_dts_delta)
×
656
  {
657
    // Let's always ensure that we seek to zero when asked no matter what
658
    if(dts != 0)
×
659
    {
660
      return false;
×
661
    }
662
  }
×
663

664
  // TODO - maybe we should also store the "last dequeued dts" from the
665
  // decoder side - this way no need to seek if we are in the interval
666
  // const bool seek_forward = dts >= this->m_last_dequeued_dts;
667
  // #if LIBAVFORMAT_VERSION_MAJOR >= 59
668
  //   const int64_t start = 0;
669
  // #else
670
  //   const int64_t start = m_avstream->first_dts;
671
  // #endif
672

673
  if(!ossia::seek_to_flick(m_formatContext, m_codecContext, m_avstream, flicks))
×
674
  {
675
    qDebug() << "Failed to seek for time ";
×
676
    return false;
×
677
  }
678

679
  ReadFrame r;
×
680
  do
×
681
  {
682
    // First flush the buffer or smth
683
    do
×
684
    {
685
      if(r.frame)
×
686
      {
687
        SCORE_LIBAV_FRAME_DEALLOC_CHECK(r.frame);
×
688
        av_frame_free(&r.frame);
×
689
      }
×
690

691
      auto pkt = av_packet_alloc();
×
692
      r = read_one_frame(*pkt);
×
693
      av_packet_unref(pkt);
×
694
      av_packet_free(&pkt);
×
695
    } while(r.error == AVERROR(EAGAIN));
×
696

697
    if(r.error == AVERROR_EOF || !r.frame)
×
698
    {
699
      break;
×
700
    }
701

702
    /*
703
    // Rescale the packet's dts into AV_TIME_BASE
704
    auto max_dts = r.frame->pkt_dts + r.frame->duration;
705
    auto max_av_dts = to_av_time_base(codec_tb, max_dts);
706
    //av_rescale_q(max_dts, stream->time_base, tb);
707
    // we're starting to see correct frames, try to get close to the dts we want.
708
    while(max_av_dts < dts)
709
    {
710
      r = read_one_frame(AVFramePointer{r.frame}, pkt);
711
      if(r.error == AVERROR_EOF || !r.frame)
712
        break;
713
    }
714
    */
715
  } while(0);
×
716

717
  if(r.frame)
×
718
  {
719
    m_frames.set_discard_frame(r.frame);
×
720
    m_frames.enqueue(r.frame);
×
721
  }
×
722
  else
723
  {
724
    SCORE_LIBAV_FRAME_DEALLOC_CHECK(r.frame);
×
725
    av_frame_free(&r.frame);
×
726
  }
727

728
  m_finished = false;
×
729

730
  return true;
×
731
}
×
732

733
AVFrame* VideoDecoder::read_frame_impl() noexcept
×
734
{
735
  ReadFrame res;
×
736

737
  if(m_avstream)
×
738
  {
739
    auto packet = av_packet_alloc();
×
740

741
    do
×
742
    {
743
      av_packet_unref(packet);
×
744
      res = read_one_frame(*packet);
×
745

746
      if(res.error == AVERROR_EOF)
×
747
      {
748
        m_finished = true;
×
749
        av_packet_unref(packet);
×
750
        av_packet_free(&packet);
×
751
        return res.frame;
×
752
      }
753
    } while(res.error == AVERROR(EAGAIN));
×
754

755
    av_packet_unref(packet);
×
756
    av_packet_free(&packet);
×
757
  }
×
758
  return res.frame;
×
759
}
×
760

761
bool VideoDecoder::open_stream() noexcept
×
762
{
763
  bool res = false;
×
764

765
  if(!m_formatContext)
×
766
    return res;
×
767

768
  int stream = -1;
×
769

770
  for(unsigned int i = 0; i < m_formatContext->nb_streams; i++)
×
771
  {
772
    if(m_formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
×
773
    {
774
      if(stream == -1)
×
775
      {
776
        stream = i;
×
777
        continue;
×
778
      }
779
    }
×
780
    m_formatContext->streams[i]->discard = AVDISCARD_ALL;
×
781
  }
×
782

783
  if(stream != -1)
×
784
  {
785
    m_avstream = m_formatContext->streams[stream];
×
786
    const AVRational tb = m_avstream->time_base;
×
787
    dts_per_flicks = (tb.den / (tb.num * ossia::flicks_per_second<double>));
×
788
    flicks_per_dts = (tb.num * ossia::flicks_per_second<double>) / tb.den;
×
789

790
    auto codecPar = m_avstream->codecpar;
×
791
    if((m_codec = avcodec_find_decoder(codecPar->codec_id)))
×
792
    {
793
      if(codecPar->width <= 0 || codecPar->height <= 0)
×
794
      {
795
        qDebug() << "VideoDecoder: invalid video: width or height is 0";
×
796
        res = false;
×
797
      }
×
798
      else
799
      {
800
        color_range = codecPar->color_range;
×
801
        color_primaries = codecPar->color_primaries;
×
802
        color_trc = codecPar->color_trc;
×
803
        color_space = codecPar->color_space;
×
804
        chroma_location = codecPar->chroma_location;
×
805

806
        // Detect wide-gamut / HDR evidence from primaries and transfer function.
807
        // This is used to infer color_space when it is unspecified.
808
        const bool has_bt2020_evidence =
×
809
            color_primaries == AVCOL_PRI_BT2020
×
810
            || color_trc == AVCOL_TRC_SMPTE2084      // PQ (HDR10 / BT.2100)
×
811
            || color_trc == AVCOL_TRC_ARIB_STD_B67;   // HLG (BT.2100)
×
812

813
        // Display P3 content may use BT.709 matrix coefficients
814
        // but with wider primaries. Don't force it to BT.2020.
815
        const bool has_p3_evidence =
×
816
            color_primaries == AVCOL_PRI_SMPTE432     // Display P3 (D65)
×
817
            || color_primaries == AVCOL_PRI_SMPTE431; // DCI-P3
×
818

819
        if(color_space == AVCOL_SPC_UNSPECIFIED)
×
820
        {
821
          if(has_bt2020_evidence)
×
822
            color_space = AVCOL_SPC_BT2020_NCL;
×
823
          else if(has_p3_evidence)
×
824
            // P3 content typically uses BT.709 matrix coefficients.
825
            // colorMatrix() will detect the P3 primaries and route
826
            // through the Display P3 pipeline.
827
            color_space = AVCOL_SPC_BT709;
×
828
          else if(codecPar->height < 625)
×
829
            color_space = AVCOL_SPC_SMPTE170M;
×
830
          else if(codecPar->height < 720)
×
831
            color_space = AVCOL_SPC_BT470BG;
×
832
          else
833
            color_space = AVCOL_SPC_BT709;
×
834
        }
×
835
        if(color_range == AVCOL_RANGE_UNSPECIFIED)
×
836
          color_range = AVCOL_RANGE_MPEG;
×
837

838
        // HDR handling
839
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(61, 3, 100)
840
        {
841
          const auto data = codecPar->coded_side_data;
842
          const auto n = codecPar->nb_coded_side_data;
843
          // Light data
844
          if(auto sd = av_packet_side_data_get(data, n, AV_PKT_DATA_CONTENT_LIGHT_LEVEL))
845
            if(sd->data)
846
              this->content_light = *reinterpret_cast<const AVContentLightMetadata*>(sd->data);
847

848
          // Mastering side data
849
          if (auto sd = av_packet_side_data_get(data, n, AV_PKT_DATA_MASTERING_DISPLAY_METADATA))
850
            if(sd->data)
851
              this->mastering_display = *(AVMasteringDisplayMetadata *)sd->data;
852
        }
853
#endif
854

855
        codec_id = codecPar->codec_id;
×
856

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

917
        if(use_gpu_direct)
×
918
        {
919
          width = codecPar->width;
×
920
          height = codecPar->height;
×
921
          fps = av_q2d(m_avstream->avg_frame_rate);
×
922

923
          m_conf.useAVCodec = false;
×
924
          m_codecContext = nullptr;
×
925
          m_codec = nullptr;
×
926
          res = true;
×
927
        }
×
928
        else
929
        {
930
          pixel_format = (AVPixelFormat)codecPar->format;
×
931
          width = codecPar->width;
×
932
          height = codecPar->height;
×
933
          fps = av_q2d(m_avstream->avg_frame_rate);
×
934

935
          res = open_codec_context(*this, m_avstream, [this](AVCodecContext& ctx) {
×
936
            ctx.framerate
×
937
                = av_guess_frame_rate(m_formatContext, (AVStream*)m_avstream, NULL);
×
938
            m_codecContext->pkt_timebase = m_avstream->time_base;
×
939
            // m_codecContext->codec_id = m_codec->id;
940
          });
×
941

942
          if(m_codecContext)
×
943
          {
944
            auto tb = m_codecContext->pkt_timebase;
×
945
            dts_per_flicks = (tb.den / (tb.num * ossia::flicks_per_second<double>));
×
946
            flicks_per_dts = (tb.num * ossia::flicks_per_second<double>) / tb.den;
×
947
          }
×
948
        }
949
      }
950
    }
×
951
  }
×
952

953
  if(!res)
×
954
  {
955
    close_video();
×
956
  }
×
957
  return res;
×
958
}
×
959

960
void VideoDecoder::close_video() noexcept
×
961
{
962
  if(m_codecContext)
×
963
  {
964
    avcodec_flush_buffers(m_codecContext);
×
965
#if defined(__APPLE__)
966
#if FF_API_VT_HWACCEL_CONTEXT
967
    if(m_codecContext->hwaccel_context)
968
      av_videotoolbox_default_free(m_codecContext);
969
#endif
970
#endif
971
    avcodec_free_context(&m_codecContext);
×
972

973
    m_codecContext = nullptr;
×
974
    m_codec = nullptr;
×
975
  }
×
976

977
  m_rescale.close();
×
978

979
  m_avstream = nullptr;
×
980
}
×
981
}
982
#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