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

ossia / score / 37492781442

06 Oct 2026 04:04PM UTC coverage: 48.588% (+1.1%) from 47.45%
37492781442

push

github

jcelerier
execution: a port's address is rebound on the ossia port it has now

The rebinds on a port's exposed name and scriptable flag are queued, and a
queued call still runs after the port is bound again: one posted while a
shader's undo restored its ports ran after the second program change had
replaced the ossia inlets, and the audio thread unregistered a freed inlet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TcLJ4VuB2nzXbazQb9nyC1

14 of 14 new or added lines in 1 file covered. (100.0%)

4596 existing lines in 94 files now uncovered.

157183 of 323499 relevant lines covered (48.59%)

149381.01 hits per line

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

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

3
#include <Media/Libav.hpp>
4
#include <Video/FrameAccess.hpp>
5
#include <Video/GpuFormats.hpp>
6
#include <Video/PlaybackTime.hpp>
7

8
#include <score/tools/Debug.hpp>
9

10
#include <ossia/detail/flicks.hpp>
11
#include <ossia/detail/libav.hpp>
12
#include <ossia/detail/thread.hpp>
13

14
#include <QApplication>
15
#include <QDebug>
16
#include <QElapsedTimer>
17
#include <QTimer>
18

19
#include <algorithm>
20
#include <cstdlib>
21
#include <functional>
22
#include <iostream>
23
#include <string_view>
24
#include <thread>
25

26
#if defined(__linux__)
27
#include <unistd.h>
28
#endif
29

30
#if SCORE_HAS_LIBAV
31

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

45
#if __APPLE__ && __has_include(<libavcodec/videotoolbox.h>)
46
#include "VideoDecoder.vtb.cpp"
47
#endif
48

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

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

UNCOV
75
  return ctx->pix_fmt;
×
UNCOV
76
}
×
77
#endif
78

79
void LibAVDecoder::init_scaler(VideoInterface& self) noexcept
4 ✔
80
{
81
  if(!Video::formatNeedsDecoding(self.pixel_format))
4 ✔
82
    return;
4 ✔
83

UNCOV
84
  m_rescale.open(self);
×
85

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

UNCOV
94
  self.pixel_format = AV_PIX_FMT_RGBA;
×
UNCOV
95
  self.color_space = AVCOL_SPC_RGB;
×
96
}
4 ✔
97

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

104
  avcodec_parameters_to_context(m_codecContext, stream->codecpar);
8 ✔
105

106
  // m_codecContext->flags |= AV_CODEC_FLAG_LOW_DELAY;
107
  // m_codecContext->flags2 |= AV_CODEC_FLAG2_FAST;
108
  const auto threading = chooseDecoderThreading(
8 ✔
109
      *codec, stream->codecpar, m_conf.useCase, m_conf.threads, hw_dev_ctx != nullptr);
8 ✔
110
#if LIBAVUTIL_VERSION_MAJOR >= 57
111
  if(hw_dev_ctx)
8 ✔
112
  {
113
    m_codecContext->hw_device_ctx = hw_dev_ctx;
×
UNCOV
114
    m_codecContext->opaque = (void*)this;
×
UNCOV
115
    m_codecContext->get_format = get_format_for_codeccontext;
×
UNCOV
116
  }
×
117
#endif
118
  applyDecoderThreading(*m_codecContext, threading);
8 ✔
119

120
  SCORE_ASSERT(setup);
8 ✔
121
  setup(*m_codecContext);
8 ✔
122

123
  int err = avcodec_open2(m_codecContext, codec, nullptr);
8 ✔
124
  if(err < 0)
8 ✔
125
  {
UNCOV
126
    qDebug() << "avcodec_open2: " << av_to_string(err);
×
UNCOV
127
    avcodec_free_context(&m_codecContext);
×
UNCOV
128
  }
×
129
  else
130
  {
131
    qDebug().noquote() << "Video decoder:"
16 ✔
132
                       << describeDecoderThreading(*m_codecContext, threading).c_str();
8 ✔
133
  }
134
  return err;
8 ✔
UNCOV
135
}
×
136

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

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

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

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

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

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

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

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

212
  AVBufferRef* hw_device_ctx{};
×
UNCOV
213
  int ret = Video::createHardwareDevice(&hw_device_ctx, device);
×
UNCOV
214
  if(ret != 0)
×
UNCOV
215
    return {};
×
216

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

226
ReadFrame LibAVDecoder::enqueue_frame(const AVPacket* pkt) noexcept
191 ✔
227
{
228
  auto receive = [this]() -> ReadFrame
570 ✔
229
  {
230
    auto frame = m_frames.newFrame();
379 ✔
231
    auto read
232
        = receiveVideoFrame(
379 ✔
233
            m_codecContext, frame.get(), this->m_conf.ignorePTS, &m_missingTimestamps);
379 ✔
234
    if(read.error == AVERROR_EOF)
379 ✔
235
      m_finished = true;
7 ✔
236

237
    if(!read.frame)
379 ✔
238
    {
239
      m_frames.enqueue_decoding_error(frame.release());
192 ✔
240
      return read;
192 ✔
241
    }
242

243
    if(m_rescale)
187 ✔
244
    {
UNCOV
245
      m_rescale.rescale(m_frames, frame, read);
×
UNCOV
246
    }
×
247
    else if(read.frame == frame.get())
187 ✔
248
    {
249
      frame.release();
187 ✔
250
    }
187 ✔
251

252
    return read;
187 ✔
253
  };
379 ✔
254

255
  ReadFrame last{nullptr, AVERROR(EAGAIN)};
191 ✔
256
  auto keepInOrder = [this, &last](ReadFrame read) {
378 ✔
257
    if(last.frame)
187 ✔
258
      m_frames.enqueue(last.frame);
22 ✔
259
    last = read;
187 ✔
260
  };
187 ✔
261

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

278
    auto read = receive();
4 ✔
279
    if(read.frame)
4 ✔
280
    {
281
      keepInOrder(read);
2 ✔
282
      continue;
2 ✔
283
    }
284

285
    // error == 0 with no frame: a frame was decoded but discarded (negative
286
    // pts). The codec still made room, so the packet must be retried, not
287
    // dropped.
288
    if(read.error == 0)
2 ✔
289
      continue;
2 ✔
290

291
    // EAGAIN from send_packet guarantees receive_frame yields a frame; if it
292
    // does not, no progress is possible: bail out instead of spinning.
UNCOV
293
    return last.frame ? last : read;
×
294
  }
295

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

315
  if(last.frame)
190 ✔
316
    last.error = 0;
165 ✔
317
  return last;
190 ✔
318
}
191 ✔
319

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

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

UNCOV
345
  memcpy(&frame.format, &cp->codec_tag, 4);
×
346

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

363
ReadFrame receiveVideoFrame(
379 ✔
364
    AVCodecContext* codecContext, AVFrame* frame, bool ignorePts,
365
    MissingTimestamps* missing)
366
{
367
  if(codecContext && frame)
379 ✔
368
  {
369
    int ret = avcodec_receive_frame(codecContext, frame);
379 ✔
370

371
    if(ret < 0)
379 ✔
372
    {
373
      return {nullptr, ret};
190 ✔
374
    }
375
    else
376
    {
377
      if(missing)
189 ✔
378
      {
379
        if(frame->pts == AV_NOPTS_VALUE)
189 ✔
380
          frame->pts = frame->best_effort_timestamp;
30 ✔
381
        // Without timestamps the demuxer's durations are unreliable too (raw
382
        // H.264 gives 1 tick after a byte seek): number by the frame rate.
383
        int64_t duration = missing->step;
189 ✔
384
        if(frame->pts == AV_NOPTS_VALUE)
189 ✔
385
        {
386
          frame->pts = missing->next;
30 ✔
387
#if(LIBAVUTIL_VERSION_MAJOR < 58)
388
          frame->pkt_duration = duration;
389
#else
390
          frame->duration = duration;
30 ✔
391
#endif
392
        }
30 ✔
393
        else
394
#if(LIBAVUTIL_VERSION_MAJOR < 58)
395
          duration = frame->pkt_duration > 0 ? frame->pkt_duration : missing->step;
396
#else
397
          duration = frame->duration > 0 ? frame->duration : missing->step;
159 ✔
398
#endif
399
        missing->next = frame->pts + duration;
189 ✔
400
      }
189 ✔
401

402
      if(ignorePts || frame->pts >= 0)
189 ✔
403
      {
404
#if LIBAVUTIL_VERSION_MAJOR >= 57
405
        // Transfer HW frame to CPU
406
        if(formatIsHardwareDecoded(AVPixelFormat(frame->format)))
187 ✔
407
        {
UNCOV
408
          AVFrame* sw_frame = av_frame_alloc();
×
UNCOV
409
          sw_frame->format = AV_PIX_FMT_NONE;
×
410

UNCOV
411
          int hw_ret = av_hwframe_transfer_data(sw_frame, frame, 0);
×
412
          if(hw_ret >= 0)
×
413
          {
UNCOV
414
            sw_frame->pts = frame->pts;
×
UNCOV
415
            av_frame_unref(frame);
×
UNCOV
416
            av_frame_move_ref(frame, sw_frame);
×
UNCOV
417
          }
×
UNCOV
418
          av_frame_free(&sw_frame);
×
UNCOV
419
          if(hw_ret < 0)
×
UNCOV
420
            return {nullptr, hw_ret};
×
UNCOV
421
        }
×
422
#endif
423
        return {frame, ret};
187 ✔
424
      }
425
      else
426
      {
427
        return {nullptr, ret};
2 ✔
428
      }
429
    }
430
  }
431

UNCOV
432
  return {nullptr, AVERROR_UNKNOWN};
×
433
}
379 ✔
434

435
VideoInterface::~VideoInterface() { }
265 ✔
436

437
VideoDecoder::VideoDecoder(DecoderConfiguration conf) noexcept
40 ✔
438
{
20 ✔
439
  m_conf = std::move(conf);
20 ✔
440
}
441

442
VideoDecoder::~VideoDecoder() noexcept
20 ✔
443
{
×
444
  close_file();
20 ✔
445
}
20 ✔
446

447
bool VideoDecoder::open(const std::string& inputFile) noexcept
20 ✔
448
{
449
  close_file();
20 ✔
450

451
  m_inputFile = inputFile;
20 ✔
452
  this->filePath = inputFile;
20 ✔
453

454
  if(avformat_open_input(&m_formatContext, inputFile.c_str(), nullptr, nullptr) != 0)
20 ✔
455
  {
456
    close_file();
16 ✔
457
    return false;
16 ✔
458
  }
459

460
  if(avformat_find_stream_info(m_formatContext, nullptr) < 0)
4 ✔
461
  {
UNCOV
462
    close_file();
×
UNCOV
463
    return false;
×
464
  }
465

466
  if(!open_stream())
4 ✔
467
  {
UNCOV
468
    close_file();
×
UNCOV
469
    return false;
×
470
  }
471

472
  // Elementary streams and truncated files have no known duration:
473
  // formatContext->duration stays AV_NOPTS_VALUE (INT64_MIN), and scaling
474
  // that to flicks is a signed overflow. Report 0, like "unknown".
475
  if(m_formatContext->duration == AV_NOPTS_VALUE || m_formatContext->duration < 0)
4 ✔
476
  {
477
    m_duration = 0;
1 ✔
478
  }
1 ✔
479
  else
480
  {
481
    int64_t secs = m_formatContext->duration / AV_TIME_BASE;
3 ✔
482
    int64_t us = m_formatContext->duration % AV_TIME_BASE;
3 ✔
483

484
    m_duration = secs * ossia::flicks_per_second<int64_t>;
3 ✔
485
    m_duration += us * ossia::flicks_per_millisecond<int64_t> / 1000;
3 ✔
486
  }
487

488
  return true;
4 ✔
489
}
20 ✔
490

491
bool VideoDecoder::load(const std::string& inputFile) noexcept
5 ✔
492
{
493
  if(!open(inputFile))
5 ✔
494
    return false;
2 ✔
495

496
  m_running.store(true, std::memory_order_release);
3 ✔
497
  // TODO use a thread pool
498
  m_thread = std::thread{[this] {
6 ✔
499
    ossia::set_thread_name("ossia video");
3 ✔
500
    this->buffer_thread();
3 ✔
501
  }};
3 ✔
502

503
  return true;
3 ✔
504
}
5 ✔
505

UNCOV
506
int64_t VideoDecoder::duration() const noexcept
×
507
{
UNCOV
508
  return m_duration;
×
509
}
510

UNCOV
511
void VideoDecoder::seek(int64_t flicks)
×
512
{
UNCOV
513
  m_seekTo = flicks;
×
UNCOV
514
  m_condVar.notify_one();
×
UNCOV
515
}
×
516

517
AVFrame* VideoDecoder::dequeue_frame() noexcept
539 ✔
518
{
519
  auto f = m_frames.discard_and_dequeue_one();
539 ✔
520
  if(f)
539 ✔
521
  {
522
    m_last_dequeued_pts = f->pts;
80 ✔
523
  }
80 ✔
524
  m_condVar.notify_one();
539 ✔
525
  return f;
539 ✔
526
}
527

528
void VideoDecoder::release_frame(AVFrame* frame) noexcept
80 ✔
529
{
530
  m_frames.release(frame);
80 ✔
531
}
80 ✔
532

533
void VideoDecoder::buffer_thread() noexcept
3 ✔
534
{
535
  while(m_running.load(std::memory_order_acquire))
70 ✔
536
  {
537
    if(int64_t seek = m_seekTo.exchange(-1); seek >= 0)
69 ✔
538
    {
UNCOV
539
      seek_impl(seek);
×
UNCOV
540
    }
×
541
    else
542
    {
543
      std::unique_lock lck{m_condMut};
69 ✔
544
      m_condVar.wait(lck, [&] {
334 ✔
545
        return (m_frames.size() < frames_to_buffer / 2 && !m_finished)
530 ✔
546
               || !m_running.load(std::memory_order_acquire) || (m_seekTo != -1);
265 ✔
547
      });
548
      if(!m_running.load(std::memory_order_acquire))
69 ✔
549
        return;
2 ✔
550

551
      if(int64_t seek = m_seekTo.exchange(-1); seek >= 0)
67 ✔
552
      {
UNCOV
553
        seek_impl(seek);
×
UNCOV
554
      }
×
555

556
      if(m_frames.size() < (frames_to_buffer / 2) && !m_finished)
67 ✔
557
      {
558
        if(auto f = read_frame_impl())
67 ✔
559
        {
560
          m_frames.enqueue(f);
65 ✔
561
        }
65 ✔
562
        std::this_thread::sleep_for(std::chrono::milliseconds(4));
67 ✔
563
      }
67 ✔
564
    }
69 ✔
565
  }
566
}
3 ✔
567

568
void VideoDecoder::close_file() noexcept
56 ✔
569
{
570
  // Stop the running status
571
  m_running.store(false, std::memory_order_release);
56 ✔
572
  m_condVar.notify_one();
56 ✔
573

574
  if(m_thread.joinable())
56 ✔
575
    m_thread.join();
3 ✔
576

577
  // Remove frames that were in flight
578
  m_frames.drain();
56 ✔
579

580
  // Clear the stream
581
  close_video();
56 ✔
582

583
  // Clear the fmt context
584
  if(m_formatContext)
56 ✔
585
  {
586
    // avformat_close_input() already frees the context and sets it to nullptr;
587
    // do NOT also call avformat_free_context() on it (double free).
588
    avformat_close_input(&m_formatContext);
4 ✔
589
    m_formatContext = nullptr;
4 ✔
590
  }
4 ✔
591
}
56 ✔
592

UNCOV
593
ReadFrame LibAVDecoder::read_one_frame_raw(AVPacket& packet)
×
594
{
UNCOV
595
  int res{};
×
596

UNCOV
597
  while((res = av_read_frame(m_formatContext, &packet)) >= 0)
×
598
  {
UNCOV
599
    if(packet.stream_index == m_avstream->index)
×
600
    {
UNCOV
601
      auto frame = m_frames.newFrame();
×
UNCOV
602
      if(frame->buf[0])
×
603
        av_buffer_unref(&frame->buf[0]);
×
604
      // Mainly for HAP: we feed the raw undecoded codec data directly to the GPU, see HAPDecoder
605
      load_packet_in_frame(packet, *frame);
×
606

607
      av_packet_unref(&packet);
×
608
      return {frame.release(), 0};
×
609
    }
×
610
    else
611
    {
UNCOV
612
      av_packet_unref(&packet);
×
613
    }
614
  }
615

616
  // A file that ends in garbage (truncation, an interrupted transcode) makes
617
  // av_read_frame return AVERROR_INVALIDDATA & co forever instead of EOF.
618
  // Either way no more packets will ever come: finish, like ffplay does —
619
  // otherwise buffer_thread retries this error at full tilt for the rest of
620
  // the process's life.
UNCOV
621
  if(res < 0 && res != AVERROR(EAGAIN))
×
622
  {
UNCOV
623
    m_finished = true;
×
UNCOV
624
  }
×
UNCOV
625
  av_packet_unref(&packet);
×
UNCOV
626
  return {nullptr, res};
×
UNCOV
627
}
×
628

629
ReadFrame LibAVDecoder::read_one_frame_avcodec(AVPacket& packet)
67 ✔
630
{
631
  ReadFrame ret_frame;
67 ✔
632
  int res{};
67 ✔
633

634
  int z = 0;
67 ✔
635
do_read_frame:
636
  av_packet_unref(&packet);
82 ✔
637
  while((res = av_read_frame(m_formatContext, &packet)) >= 0)
82 ✔
638
  {
639
    if(packet.stream_index == m_avstream->index)
80 ✔
640
    {
641
      SCORE_ASSERT(m_codecContext);
80 ✔
642
      if(packet.pts != AV_NOPTS_VALUE && packet.dts != AV_NOPTS_VALUE)
80 ✔
643
        m_ptsLead = std::max(m_ptsLead, packet.pts - packet.dts);
50 ✔
644

645
      //av_packet_rescale_ts(
646
      //     &packet, this->m_avstream->time_base, this->m_codecContext->pkt_timebase);
647

648
      ret_frame = enqueue_frame(&packet);
80 ✔
649
      if(ret_frame.error == AVERROR(EAGAIN))
80 ✔
650
      {
651
        if(z++ < 100)
15 ✔
652
          goto do_read_frame;
15 ✔
UNCOV
653
      }
×
654
      av_packet_unref(&packet);
65 ✔
655
      return ret_frame;
65 ✔
656
    }
657
    else
658
    {
UNCOV
659
      av_packet_unref(&packet);
×
660
    }
661
  }
662

663
  // AVERROR_EOF, or a persistent demux error: a truncated or corrupt tail
664
  // makes av_read_frame return AVERROR_INVALIDDATA & co forever, never EOF.
665
  // Either way no more packets will ever come, so both are the end of the
666
  // stream, like ffplay treats them — otherwise buffer_thread retries the
667
  // error at full tilt forever and the frames still buffered inside the
668
  // decoder are never shown.
669
  if(res < 0 && res != AVERROR(EAGAIN))
2 ✔
670
  {
671
    // Flush codec to get remaining frames from the reorder buffer (B-frames)
672
    if(m_codecContext)
2 ✔
673
    {
674
      // enqueue_frame routes every drained frame through the rescaler, like
675
      // every other decode path: these are the last frames of the clip, and a
676
      // decoder whose pixel_format was relabelled RGBA by init_scaler must
677
      // not suddenly emit its native format for them.
678
      auto flushed = enqueue_frame(nullptr);
2 ✔
679
      if(flushed.frame)
2 ✔
680
        m_frames.enqueue(flushed.frame);
2 ✔
681
    }
2 ✔
682
    m_finished = true;
2 ✔
683
  }
2 ✔
684
  av_packet_unref(&packet);
2 ✔
685
  return {nullptr, res};
2 ✔
686
}
67 ✔
687

688
ReadFrame LibAVDecoder::read_one_frame(AVPacket& packet)
67 ✔
689
{
690
  if(m_conf.useAVCodec)
67 ✔
691
    return read_one_frame_avcodec(packet);
67 ✔
692
  else
693
    return read_one_frame_raw(packet);
×
694
}
67 ✔
695
/*
696
// https://stackoverflow.com/a/44468529/1495627
697
static
698
int seek_to_frame(AVFormatContext* format, AVStream* stream, int frameIndex)
699
{
700
  using namespace std;
701
  // Seek is done on packet dts
702
  int64_t target_dts_usecs = std::round(frameIndex * (double)stream->r_frame_rate.den / stream->r_frame_rate.num * AV_TIME_BASE);
703
  // Remove first dts: when non zero seek should be more accurate
704
  auto first_dts_usecs = std::round(stream->first_dts * (double)stream->time_base.num / stream->time_base.den * AV_TIME_BASE);
705
  target_dts_usecs += first_dts_usecs;
706
  return av_seek_frame(format, -1, target_dts_usecs, AVSEEK_FLAG_BACKWARD);
707
}
708
*/
709

UNCOV
710
bool VideoDecoder::seek_impl(int64_t flicks) noexcept
×
711
{
UNCOV
712
  if(m_avstream->index >= int(m_formatContext->nb_streams))
×
UNCOV
713
    return false;
×
714

715
  // `flicks` is playback time; the stream's timestamps start at start_pts.
UNCOV
716
  const AVRational stream_tb = m_avstream->time_base;
×
UNCOV
717
  const int64_t target = flicksToPts(flicks, stream_tb, start_pts);
×
UNCOV
718
  const int64_t absolute_flicks
×
UNCOV
719
      = flicks + av_rescale_q(start_pts, stream_tb, flicks_time_base);
×
720

721
  constexpr auto av_tb = AVRational{1, AV_TIME_BASE};
×
722

723
  // No seek for a target less than 0.2 s ahead of the last frame taken: the
724
  // frames already queued reach it. Behind it they never would.
UNCOV
725
  const int64_t last = m_last_dequeued_pts;
×
UNCOV
726
  if(last != AV_NOPTS_VALUE && flicks != 0 && target >= last
×
727
     && av_rescale_q(target - last, stream_tb, av_tb) <= (av_tb.den / 5) / av_tb.num)
×
UNCOV
728
    return false;
×
729

730
  // A stream without timestamps cannot be sought by time: it is restarted by
731
  // bytes, numbered from its beginning, and the frames before the target are
732
  // skipped below.
UNCOV
733
  const bool no_timestamps = m_formatContext->iformat->flags & AVFMT_NOTIMESTAMPS;
×
UNCOV
734
  auto seek_to = [&](int64_t abs_flicks) {
×
UNCOV
735
    if(no_timestamps
×
UNCOV
736
       || !ossia::seek_to_flick(m_formatContext, m_codecContext, m_avstream, abs_flicks))
×
737
    {
UNCOV
738
      if(av_seek_frame(m_formatContext, m_avstream->index, 0, AVSEEK_FLAG_BYTE) < 0)
×
UNCOV
739
        return false;
×
UNCOV
740
      if(m_codecContext)
×
UNCOV
741
        avcodec_flush_buffers(m_codecContext);
×
742
      m_missingTimestamps.next = start_pts;
×
UNCOV
743
    }
×
744
    return true;
×
745
  };
×
746

UNCOV
747
  auto read_first = [&] {
×
748
    ReadFrame r;
×
749
    do
×
750
    {
UNCOV
751
      if(r.frame)
×
752
      {
UNCOV
753
        SCORE_LIBAV_FRAME_DEALLOC_CHECK(r.frame);
×
754
        av_frame_free(&r.frame);
×
UNCOV
755
      }
×
756

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

765
  // Every frame enqueued from here on is after the seek; the reader drops
766
  // the ones before, held back or still queued.
UNCOV
767
  const int generation = m_frames.start_generation();
×
768
  const int64_t lead_flicks = av_rescale_q(m_ptsLead, stream_tb, flicks_time_base);
×
UNCOV
769
  if(!seek_to(absolute_flicks - lead_flicks))
×
770
  {
UNCOV
771
    qDebug() << "Failed to seek for time ";
×
UNCOV
772
    seek_generation.store(generation, std::memory_order_release);
×
UNCOV
773
    return false;
×
774
  }
775

776
  // A demuxer seeking on dts (MPEG-TS, MPEG-PS) can land on a frame shown
777
  // after the target while m_ptsLead is not yet known: back off until the
778
  // first frame is not later. A video starting after its container
779
  // legitimately shows its first frame after an earlier target. Frames queued
780
  // by a retry are discarded by the consumer up to the frame marked below.
UNCOV
781
  const int64_t landing = m_avstream->start_time != AV_NOPTS_VALUE
×
UNCOV
782
                              ? std::max(target, m_avstream->start_time)
×
UNCOV
783
                              : target;
×
784
  ReadFrame r = read_first();
×
UNCOV
785
  int64_t back = 0;
×
786
  for(int attempt = 0; attempt < 6 && r.frame && r.frame->pts != AV_NOPTS_VALUE
×
UNCOV
787
                       && r.frame->pts > landing && absolute_flicks - back > 0;
×
UNCOV
788
      attempt++)
×
789
  {
UNCOV
790
    SCORE_LIBAV_FRAME_DEALLOC_CHECK(r.frame);
×
UNCOV
791
    av_frame_free(&r.frame);
×
UNCOV
792
    back = back ? back * 2
×
UNCOV
793
                : 2 * av_rescale_q(m_missingTimestamps.step, stream_tb, flicks_time_base);
×
UNCOV
794
    if(!seek_to(std::max<int64_t>(0, absolute_flicks - lead_flicks - back)))
×
UNCOV
795
      break;
×
796
    r = read_first();
×
797
  }
×
798
  while(no_timestamps && r.frame && r.frame->pts + m_missingTimestamps.step <= target)
×
799
  {
UNCOV
800
    SCORE_LIBAV_FRAME_DEALLOC_CHECK(r.frame);
×
801
    av_frame_free(&r.frame);
×
802
    r = read_first();
×
803
  }
804

805
  if(r.frame)
×
806
  {
807
    // Enqueue BEFORE publishing the discard marker. Otherwise the GFX thread's
808
    // discard_and_dequeue* can observe the marker for a frame not yet in
809
    // `available`, drain the queue and return r.frame as current while the
810
    // decoder still owns it and is about to enqueue it → double ownership /
811
    // UAF of the pixels a live zero-copy GPU upload references. With this
812
    // order the marker is only ever visible once its frame is already in the
813
    // queue, and the consumer falls through to a normal dequeue when the
814
    // marker frame isn't found.
UNCOV
815
    m_frames.enqueue(r.frame);
×
UNCOV
816
    m_frames.set_discard_frame(r.frame);
×
UNCOV
817
  }
×
818
  else
819
  {
UNCOV
820
    SCORE_LIBAV_FRAME_DEALLOC_CHECK(r.frame);
×
UNCOV
821
    av_frame_free(&r.frame);
×
822
  }
823

UNCOV
824
  m_finished = false;
×
UNCOV
825
  seek_generation.store(generation, std::memory_order_release);
×
826

UNCOV
827
  return true;
×
UNCOV
828
}
×
829

830
AVFrame* VideoDecoder::read_frame_impl() noexcept
67 ✔
831
{
832
  ReadFrame res;
67 ✔
833

834
  if(m_avstream)
67 ✔
835
  {
836
    auto packet = av_packet_alloc();
67 ✔
837

838
    do
67 ✔
839
    {
840
      av_packet_unref(packet);
67 ✔
841
      res = read_one_frame(*packet);
67 ✔
842

843
      if(res.error == AVERROR_EOF)
67 ✔
844
      {
845
        m_finished = true;
2 ✔
846
        av_packet_unref(packet);
2 ✔
847
        av_packet_free(&packet);
2 ✔
848
        return res.frame;
2 ✔
849
      }
850
    } while(res.error == AVERROR(EAGAIN));
65 ✔
851

852
    av_packet_unref(packet);
65 ✔
853
    av_packet_free(&packet);
65 ✔
854
  }
65 ✔
855
  return res.frame;
65 ✔
856
}
67 ✔
857

858
bool VideoDecoder::open_stream() noexcept
4 ✔
859
{
860
  bool res = false;
4 ✔
861

862
  if(!m_formatContext)
4 ✔
UNCOV
863
    return res;
×
864

865
  int stream = -1;
4 ✔
866

867
  for(unsigned int i = 0; i < m_formatContext->nb_streams; i++)
8 ✔
868
  {
869
    if(m_formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
4 ✔
870
    {
871
      if(stream == -1)
4 ✔
872
      {
873
        stream = i;
4 ✔
874
        continue;
4 ✔
875
      }
UNCOV
876
    }
×
UNCOV
877
    m_formatContext->streams[i]->discard = AVDISCARD_ALL;
×
UNCOV
878
  }
×
879

880
  if(stream != -1)
4 ✔
881
  {
882
    m_avstream = m_formatContext->streams[stream];
4 ✔
883
    const AVRational tb = m_avstream->time_base;
4 ✔
884
    {
885
      const AVRational rate = m_avstream->avg_frame_rate.num > 0
4 ✔
886
                                  ? m_avstream->avg_frame_rate
4 ✔
UNCOV
887
                                  : m_avstream->r_frame_rate;
×
888
      start_pts = playbackStartPts(*m_formatContext, *m_avstream);
4 ✔
889
      time_base = tb;
4 ✔
890
      m_missingTimestamps = {};
4 ✔
891
      m_missingTimestamps.next = start_pts;
892
      if(rate.num > 0 && rate.den > 0)
4 ✔
893
        m_missingTimestamps.step
8 ✔
894
            = std::max<int64_t>(1, av_rescale_q(1, av_inv_q(rate), tb));
4 ✔
895
    }
896
    dts_per_flicks = (tb.den / (tb.num * ossia::flicks_per_second<double>));
4 ✔
897
    flicks_per_dts = (tb.num * ossia::flicks_per_second<double>) / tb.den;
4 ✔
898

899
    auto codecPar = m_avstream->codecpar;
4 ✔
900
    if((m_codec = avcodec_find_decoder(codecPar->codec_id)))
4 ✔
901
    {
902
      if(codecPar->width <= 0 || codecPar->height <= 0)
4 ✔
903
      {
904
        qDebug() << "VideoDecoder: invalid video: width or height is 0";
×
UNCOV
905
        res = false;
×
UNCOV
906
      }
×
907
      else
908
      {
909
        color_range = codecPar->color_range;
4 ✔
910
        color_primaries = codecPar->color_primaries;
4 ✔
911
        color_trc = codecPar->color_trc;
4 ✔
912
        color_space = codecPar->color_space;
4 ✔
913
        chroma_location = codecPar->chroma_location;
4 ✔
914

915
        // Detect wide-gamut / HDR evidence from primaries and transfer function.
916
        // This is used to infer color_space when it is unspecified.
917
        const bool has_bt2020_evidence =
4 ✔
918
            color_primaries == AVCOL_PRI_BT2020
4 ✔
919
            || color_trc == AVCOL_TRC_SMPTE2084      // PQ (HDR10 / BT.2100)
4 ✔
920
            || color_trc == AVCOL_TRC_ARIB_STD_B67;   // HLG (BT.2100)
4 ✔
921

922
        // Display P3 content may use BT.709 matrix coefficients
923
        // but with wider primaries. Don't force it to BT.2020.
924
        const bool has_p3_evidence =
4 ✔
925
            color_primaries == AVCOL_PRI_SMPTE432     // Display P3 (D65)
4 ✔
926
            || color_primaries == AVCOL_PRI_SMPTE431; // DCI-P3
4 ✔
927

928
        if(color_space == AVCOL_SPC_UNSPECIFIED)
4 ✔
929
        {
930
          if(has_bt2020_evidence)
2 ✔
UNCOV
931
            color_space = AVCOL_SPC_BT2020_NCL;
×
932
          else if(has_p3_evidence)
2 ✔
933
            // P3 content typically uses BT.709 matrix coefficients.
934
            // colorMatrix() will detect the P3 primaries and route
935
            // through the Display P3 pipeline.
UNCOV
936
            color_space = AVCOL_SPC_BT709;
×
937
          else if(codecPar->height < 625)
2 ✔
938
            color_space = AVCOL_SPC_SMPTE170M;
2 ✔
939
          else if(codecPar->height < 720)
×
940
            color_space = AVCOL_SPC_BT470BG;
×
941
          else
UNCOV
942
            color_space = AVCOL_SPC_BT709;
×
943
        }
2 ✔
944
        if(color_range == AVCOL_RANGE_UNSPECIFIED)
4 ✔
945
          color_range = AVCOL_RANGE_MPEG;
2 ✔
946

947
        // HDR handling
948
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(61, 3, 100)
949
        {
950
          const auto data = codecPar->coded_side_data;
951
          const auto n = codecPar->nb_coded_side_data;
952
          // Light data
953
          if(auto sd = av_packet_side_data_get(data, n, AV_PKT_DATA_CONTENT_LIGHT_LEVEL))
954
            if(sd->data)
955
              this->content_light = *reinterpret_cast<const AVContentLightMetadata*>(sd->data);
956

957
          // Mastering side data
958
          if (auto sd = av_packet_side_data_get(data, n, AV_PKT_DATA_MASTERING_DISPLAY_METADATA))
959
            if(sd->data)
960
              this->mastering_display = *(AVMasteringDisplayMetadata *)sd->data;
961
        }
962
#endif
963

964
        codec_id = codecPar->codec_id;
4 ✔
965

966
        // Check if this is a GPU-direct codec (HAP or DXV DXT1/DXT5)
967
        bool use_gpu_direct = false;
4 ✔
968
        if(m_avstream->codecpar->codec_id == AV_CODEC_ID_HAP)
4 ✔
969
        {
970
          // HAP: store the FOURCC in the format for GPU decoder matching
UNCOV
971
          memcpy(&pixel_format, &m_avstream->codecpar->codec_tag, 4);
×
972
          use_gpu_direct = true;
×
973
        }
×
974
        else if(m_avstream->codecpar->codec_id == AV_CODEC_ID_DXV)
4 ✔
975
        {
976
          // DXV: peek first packet to determine sub-format (DXT1/DXT5)
977
          // Store synthetic fourcc in pixel_format for GPU decoder matching
978
          auto packet = av_packet_alloc();
×
UNCOV
979
          if(av_read_frame(m_formatContext, packet) >= 0 && packet->size >= 4)
×
980
          {
981
            uint32_t tag = packet->data[0] | (packet->data[1] << 8)
×
982
                           | (packet->data[2] << 16)
×
UNCOV
983
                           | ((uint32_t)packet->data[3] << 24);
×
984
            switch(tag)
×
985
            {
986
              case 0x44585431: // MKBETAG('D','X','T','1')
987
                memcpy(&pixel_format, "Dxv1", 4);
×
988
                use_gpu_direct = true;
×
989
                break;
×
990
              case 0x44585435: // MKBETAG('D','X','T','5')
991
                memcpy(&pixel_format, "Dxv5", 4);
×
992
                use_gpu_direct = true;
×
UNCOV
993
                break;
×
994
              case 0x59434736: // MKBETAG('Y','C','G','6')
UNCOV
995
                memcpy(&pixel_format, "DxvY", 4);
×
996
                use_gpu_direct = true;
×
997
                break;
×
998
              case 0x59473130: // MKBETAG('Y','G','1','0')
UNCOV
999
                memcpy(&pixel_format, "DxvA", 4);
×
1000
                use_gpu_direct = true;
×
1001
                break;
×
1002
              default: {
1003
                // Old format: check type flags in high byte
1004
                uint8_t old_type = tag >> 24;
×
UNCOV
1005
                if(old_type & 0x40)
×
1006
                {
UNCOV
1007
                  memcpy(&pixel_format, "Dxv5", 4);
×
UNCOV
1008
                  use_gpu_direct = true;
×
UNCOV
1009
                }
×
UNCOV
1010
                else if(old_type & 0x20)
×
1011
                {
UNCOV
1012
                  memcpy(&pixel_format, "Dxv1", 4);
×
UNCOV
1013
                  use_gpu_direct = true;
×
UNCOV
1014
                }
×
1015
                // Unknown old format falls through to avcodec
UNCOV
1016
                break;
×
1017
              }
1018
            }
UNCOV
1019
            av_packet_unref(packet);
×
UNCOV
1020
          }
×
UNCOV
1021
          av_packet_free(&packet);
×
1022
          // Seek back to beginning regardless
UNCOV
1023
          av_seek_frame(m_formatContext, m_avstream->index, 0, AVSEEK_FLAG_BACKWARD);
×
UNCOV
1024
        }
×
1025

1026
        if(use_gpu_direct)
4 ✔
1027
        {
UNCOV
1028
          width = codecPar->width;
×
UNCOV
1029
          height = codecPar->height;
×
UNCOV
1030
          fps = av_q2d(m_avstream->avg_frame_rate);
×
1031

1032
          m_conf.useAVCodec = false;
×
1033
          m_codecContext = nullptr;
×
UNCOV
1034
          m_codec = nullptr;
×
UNCOV
1035
          res = true;
×
UNCOV
1036
        }
×
1037
        else
1038
        {
1039
          pixel_format = (AVPixelFormat)codecPar->format;
4 ✔
1040
          width = codecPar->width;
4 ✔
1041
          height = codecPar->height;
4 ✔
1042
          fps = av_q2d(m_avstream->avg_frame_rate);
4 ✔
1043

1044
          res = open_codec_context(*this, m_avstream, [this](AVCodecContext& ctx) {
8 ✔
1045
            ctx.framerate
4 ✔
1046
                = av_guess_frame_rate(m_formatContext, (AVStream*)m_avstream, NULL);
8 ✔
1047
            m_codecContext->pkt_timebase = m_avstream->time_base;
4 ✔
1048
            // m_codecContext->codec_id = m_codec->id;
1049
          });
4 ✔
1050

1051
          if(m_codecContext)
4 ✔
1052
          {
1053
            auto tb = m_codecContext->pkt_timebase;
4 ✔
1054
            dts_per_flicks = (tb.den / (tb.num * ossia::flicks_per_second<double>));
4 ✔
1055
            flicks_per_dts = (tb.num * ossia::flicks_per_second<double>) / tb.den;
4 ✔
1056
          }
4 ✔
1057
        }
1058
      }
1059
    }
4 ✔
1060
  }
4 ✔
1061

1062
  if(res && m_avstream)
4 ✔
1063
  {
1064
    const auto probe
1065
        = classifyFrameAccess(*m_formatContext, *m_avstream, m_inputFile.c_str());
4 ✔
1066
    frame_access = probe.access;
4 ✔
1067
    max_keyframe_gap = probe.max_gap;
4 ✔
1068
  }
4 ✔
1069
  else
1070
  {
UNCOV
1071
    frame_access = FrameAccess::Unknown;
×
UNCOV
1072
    max_keyframe_gap = -1;
×
UNCOV
1073
    close_video();
×
1074
  }
1075
  return res;
4 ✔
1076
}
4 ✔
1077

1078
void VideoDecoder::close_video() noexcept
56 ✔
1079
{
1080
  if(m_codecContext)
56 ✔
1081
  {
1082
    avcodec_flush_buffers(m_codecContext);
4 ✔
1083
#if defined(__APPLE__)
1084
#if FF_API_VT_HWACCEL_CONTEXT
1085
    if(m_codecContext->hwaccel_context)
1086
      av_videotoolbox_default_free(m_codecContext);
1087
#endif
1088
#endif
1089
    avcodec_free_context(&m_codecContext);
4 ✔
1090

1091
    m_codecContext = nullptr;
4 ✔
1092
    m_codec = nullptr;
4 ✔
1093
  }
4 ✔
1094

1095
  m_rescale.close();
56 ✔
1096

1097
  m_avstream = nullptr;
56 ✔
1098
}
56 ✔
1099
}
1100
#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