chromium/media/gpu/mac/video_toolbox_decompression_session.cc

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/gpu/mac/video_toolbox_decompression_session.h"

#include "base/logging.h"
#include "media/base/media_log.h"

namespace media {

namespace {

void OnOutputThunk(void* decompression_output_refcon,
                   void* source_frame_refcon,
                   OSStatus status,
                   VTDecodeInfoFlags info_flags,
                   CVImageBufferRef image_buffer,
                   CMTime presentation_time_stamp,
                   CMTime presentation_duration) {
  VideoToolboxDecompressionSessionImpl* vtdsi =
      static_cast<VideoToolboxDecompressionSessionImpl*>(
          decompression_output_refcon);
  vtdsi->OnOutputOnAnyThread(reinterpret_cast<uintptr_t>(source_frame_refcon),
                             status, info_flags,
                             base::apple::ScopedCFTypeRef<CVImageBufferRef>(
                                 image_buffer, base::scoped_policy::RETAIN));
}

}  // namespace

VideoToolboxDecompressionSessionImpl::VideoToolboxDecompressionSessionImpl(
    scoped_refptr<base::SequencedTaskRunner> task_runner,
    std::unique_ptr<MediaLog> media_log,
    OutputCB output_cb)
    : task_runner_(std::move(task_runner)),
      media_log_(std::move(media_log)),
      output_cb_(std::move(output_cb)) {
  DVLOG(1) << __func__;
  weak_this_ = weak_this_factory_.GetWeakPtr();
}

VideoToolboxDecompressionSessionImpl::~VideoToolboxDecompressionSessionImpl() {
  DVLOG(1) << __func__;
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  Invalidate();
}

bool VideoToolboxDecompressionSessionImpl::Create(
    CMFormatDescriptionRef format,
    CFDictionaryRef decoder_config,
    CFDictionaryRef image_config) {
  DVLOG(2) << __func__;
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  DCHECK(!IsValid());

  VTDecompressionOutputCallbackRecord callback = {&OnOutputThunk,
                                                  static_cast<void*>(this)};

  OSStatus status = VTDecompressionSessionCreate(
      /*allocator=*/kCFAllocatorDefault,
      /*videoFormatDescription=*/format,
      /*videoDecoderSpecification=*/decoder_config,
      /*destinationImageBufferAttributes=*/image_config,
      /*outputCallback=*/&callback, session_.InitializeInto());
  if (status != noErr) {
    OSSTATUS_MEDIA_LOG(ERROR, status, media_log_.get())
        << "VTDecompressionSessionCreate()";
    DCHECK(!session_);
    return false;
  }

  DCHECK(session_);
  return true;
}

void VideoToolboxDecompressionSessionImpl::Invalidate() {
  DVLOG(2) << __func__;
  DCHECK(task_runner_->RunsTasksInCurrentSequence());

  if (!session_) {
    return;
  }

  VTDecompressionSessionInvalidate(session_.get());
  session_.reset();

  // Drop in-flight OnOutput() tasks. Reassignment of |weak_this_| is safe
  // because OnOutputOnAnyThread() will not be called again until we create a
  // new session.
  weak_this_factory_.InvalidateWeakPtrs();
  weak_this_ = weak_this_factory_.GetWeakPtr();
}

bool VideoToolboxDecompressionSessionImpl::IsValid() {
  DVLOG(4) << __func__;
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  return session_.get();
}

bool VideoToolboxDecompressionSessionImpl::CanAcceptFormat(
    CMFormatDescriptionRef format) {
  DVLOG(4) << __func__;
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  CHECK(session_);
  return VTDecompressionSessionCanAcceptFormatDescription(session_.get(),
                                                          format);
}

bool VideoToolboxDecompressionSessionImpl::DecodeFrame(CMSampleBufferRef sample,
                                                       uintptr_t context) {
  DVLOG(3) << __func__;
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  CHECK(session_);

  VTDecodeFrameFlags decode_flags =
      kVTDecodeFrame_EnableAsynchronousDecompression;

  OSStatus status = VTDecompressionSessionDecodeFrame(
      session_.get(), sample, decode_flags, reinterpret_cast<void*>(context),
      nullptr);
  if (status != noErr) {
    OSSTATUS_MEDIA_LOG(ERROR, status, media_log_.get())
        << "VTDecompressionSessionDecodeFrame()";
    return false;
  }

  return true;
}

void VideoToolboxDecompressionSessionImpl::OnOutputOnAnyThread(
    uintptr_t context,
    OSStatus status,
    VTDecodeInfoFlags flags,
    base::apple::ScopedCFTypeRef<CVImageBufferRef> image) {
  DVLOG(4) << __func__;
  task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&VideoToolboxDecompressionSessionImpl::OnOutput,
                     weak_this_, context, status, flags, std::move(image)));
}

void VideoToolboxDecompressionSessionImpl::OnOutput(
    uintptr_t context,
    OSStatus status,
    VTDecodeInfoFlags flags,
    base::apple::ScopedCFTypeRef<CVImageBufferRef> image) {
  DVLOG(3) << __func__;
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  CHECK(session_);
  output_cb_.Run(context, status, flags, std::move(image));
}

}  // namespace media