chromium/media/gpu/windows/mf_video_processor_accelerator.cc

// Copyright 2024 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/windows/mf_video_processor_accelerator.h"

#include <mfapi.h>

#include "media/base/win/mf_helpers.h"

namespace media {

MediaFoundationVideoProcessorAccelerator::
    MediaFoundationVideoProcessorAccelerator(
        const gpu::GpuPreferences& gpu_preferences,
        const gpu::GpuDriverBugWorkarounds& gpu_workarounds)
    : workarounds_(gpu_workarounds) {}

MediaFoundationVideoProcessorAccelerator::
    ~MediaFoundationVideoProcessorAccelerator() {
  DVLOG(3) << __func__;
}

bool MediaFoundationVideoProcessorAccelerator::Initialize(
    const Config& config,
    scoped_refptr<DXGIDeviceManager> dxgi_device_manager,
    std::unique_ptr<MediaLog> media_log) {
  dxgi_device_manager_ = std::move(dxgi_device_manager);
  media_log_ = std::move(media_log);

  return InitializeVideoProcessor(config);
}

bool MediaFoundationVideoProcessorAccelerator::InitializeVideoProcessor(
    const Config& config) {
  HRESULT hr =
      CoCreateInstance(CLSID_VideoProcessorMFT, nullptr, CLSCTX_INPROC_SERVER,
                       IID_PPV_ARGS(&video_processor_));
  RETURN_ON_HR_FAILURE(hr, "Couldn't activate video processor", false);

  if (dxgi_device_manager_) {
    auto mf_dxgi_device_manager =
        dxgi_device_manager_->GetMFDXGIDeviceManager();
    hr = video_processor_->ProcessMessage(
        MFT_MESSAGE_SET_D3D_MANAGER,
        reinterpret_cast<ULONG_PTR>(mf_dxgi_device_manager.Get()));
    RETURN_ON_HR_FAILURE(hr, "Couldn't set D3D manager", false);
  }

  // Allow encoder binding of output samples
  ComMFAttributes output_attributes;
  hr = video_processor_->GetOutputStreamAttributes(0, &output_attributes);
  RETURN_ON_HR_FAILURE(hr, "Couldn't get output attributes", false);
  hr = output_attributes->SetUINT32(MF_SA_D3D11_BINDFLAGS,
                                    D3D11_BIND_VIDEO_ENCODER);
  RETURN_ON_HR_FAILURE(hr, L"Couldn't set encoder bind flags", false);

  // Disable frame rate conversion; this allows the MFT to be 1-in
  // 1-out, avoiding latency and extra complexity.  If frame rate
  // conversion is implemented for this component, the sample
  // processing model will need to change to accommodate as the
  // processor will hold multiple input samples before it
  // generates an output sample.
  ComMFAttributes mft_attributes;
  hr = video_processor_->GetAttributes(&mft_attributes);
  RETURN_ON_HR_FAILURE(hr, "Couldn't get MFT attributes", false);
  hr = mft_attributes->SetUINT32(MF_XVP_DISABLE_FRC, 1);
  RETURN_ON_HR_FAILURE(hr, "Couldn't disable FRC", false);

  hr = MFCreateMediaType(&input_media_type_);
  RETURN_ON_HR_FAILURE(hr, "Couldn't create input media type", false);

  hr = input_media_type_->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
  RETURN_ON_HR_FAILURE(hr, "Couldn't set major type", false);
  hr = input_media_type_->SetGUID(
      MF_MT_SUBTYPE, VideoPixelFormatToMFSubtype(config.input_format));
  RETURN_ON_HR_FAILURE(hr, "Couldn't set subtype", false);
  hr = MFSetAttributeSize(input_media_type_.Get(), MF_MT_FRAME_SIZE,
                          config.input_visible_size.width(),
                          config.input_visible_size.height());
  RETURN_ON_HR_FAILURE(hr, "Couldn't set frame size", false);
  hr = input_media_type_->SetUINT32(MF_MT_COMPRESSED, 0);
  RETURN_ON_HR_FAILURE(hr, "Couldn't set compressed flag", false);
  hr = input_media_type_->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, 1);
  RETURN_ON_HR_FAILURE(hr, "Couldn't set independent samples flag", false);
  hr = input_media_type_->SetUINT32(
      MF_MT_SAMPLE_SIZE, VideoFrame::AllocationSize(config.input_format,
                                                    config.input_visible_size));
  RETURN_ON_HR_FAILURE(hr, "Couldn't set sample size", false);
  hr = MFSetAttributeRatio(input_media_type_.Get(), MF_MT_PIXEL_ASPECT_RATIO, 1,
                           1);
  RETURN_ON_HR_FAILURE(hr, "Couldn't set pixel aspect ratio", false);
  hr = input_media_type_->SetUINT32(
      MF_MT_VIDEO_PRIMARIES,
      VideoPrimariesToMFVideoPrimaries(config.input_color_space.primaries));
  RETURN_ON_HR_FAILURE(hr, "Couldn't set video primaries", false);
  if (config.input_format == PIXEL_FORMAT_XRGB ||
      config.input_format == PIXEL_FORMAT_ARGB) {
    // The video processor will calculate stride, but for RGB formats
    // it uses a bottom-up stride.  Set the stride explicitly for RGB.
    hr = input_media_type_->SetUINT32(MF_MT_DEFAULT_STRIDE,
                                      config.input_visible_size.width() * 4);
    RETURN_ON_HR_FAILURE(hr, "Couldn't set default stride", false);
  }

  // This component currently is not used for frame rate conversion.  If needed,
  // the actual frame rate should be set here.
  hr = MFSetAttributeRatio(input_media_type_.Get(), MF_MT_FRAME_RATE, 30, 1);
  RETURN_ON_HR_FAILURE(hr, "Couldn't set frame rate", false);

  // This component currently is not used for deinterlacing.  If needed,
  // the actual interlace mode should be set here.
  hr = input_media_type_->SetUINT32(MF_MT_INTERLACE_MODE,
                                    MFVideoInterlace_Progressive);
  RETURN_ON_HR_FAILURE(hr, "Couldn't set interlace mode", false);

  ComMFMediaType output_media_type;
  hr = MFCreateMediaType(&output_media_type);
  RETURN_ON_HR_FAILURE(hr, "Couldn't create output media type", false);

  hr = output_media_type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
  RETURN_ON_HR_FAILURE(hr, L"Couldn't set major type", false);
  hr = output_media_type->SetGUID(
      MF_MT_SUBTYPE, VideoPixelFormatToMFSubtype(config.output_format));
  RETURN_ON_HR_FAILURE(hr, L"Couldn't set subtype", false);
  hr = MFSetAttributeSize(output_media_type.Get(), MF_MT_FRAME_SIZE,
                          config.output_visible_size.width(),
                          config.output_visible_size.height());
  RETURN_ON_HR_FAILURE(hr, L"Couldn't set frame size", false);
  hr = output_media_type->SetUINT32(MF_MT_COMPRESSED, 0);
  RETURN_ON_HR_FAILURE(hr, L"Couldn't set compressed flag", false);
  hr = output_media_type->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, 1);
  RETURN_ON_HR_FAILURE(hr, L"Couldn't set independent samples flag", false);
  hr = MFSetAttributeRatio(output_media_type.Get(), MF_MT_PIXEL_ASPECT_RATIO, 1,
                           1);
  RETURN_ON_HR_FAILURE(hr, L"Couldn't set pixel aspect ratio", false);
  hr = output_media_type->SetUINT32(
      MF_MT_VIDEO_PRIMARIES,
      VideoPrimariesToMFVideoPrimaries(config.output_color_space.primaries));
  RETURN_ON_HR_FAILURE(hr, L"Couldn't set video primaries", false);
  hr = output_media_type->SetUINT32(MF_MT_INTERLACE_MODE,
                                    MFVideoInterlace_Progressive);
  RETURN_ON_HR_FAILURE(hr, L"Couldn't set interlace mode", false);

  // If doing frame rate conversion, the actual frame rate should be
  // set here.
  hr = MFSetAttributeRatio(output_media_type.Get(), MF_MT_FRAME_RATE, 30, 1);
  RETURN_ON_HR_FAILURE(hr, "Couldn't set frame rate", false);

  hr = video_processor_->SetInputType(0, input_media_type_.Get(), 0);
  RETURN_ON_HR_FAILURE(hr, "Couldn't set input media type", false);
  hr = video_processor_->SetOutputType(0, output_media_type.Get(), 0);
  RETURN_ON_HR_FAILURE(hr, "Couldn't set output media type", false);

  hr = video_processor_->ProcessMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0);
  RETURN_ON_HR_FAILURE(hr, "Couldn't notify begin streaming", false);
  hr = video_processor_->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0);
  RETURN_ON_HR_FAILURE(hr, "Coudln't notify start of stream", false);

  return true;
}

HRESULT MediaFoundationVideoProcessorAccelerator::Convert(
    scoped_refptr<VideoFrame> frame,
    IMFSample** sample_out) {
  DCHECK(video_processor_ != nullptr);

  ComMFSample sample;
  HRESULT hr = GenerateSampleFromVideoFrame(
      frame.get(), dxgi_device_manager_.get(), true, nullptr, 0, &sample);
  RETURN_ON_HR_FAILURE(hr, L"Couldn't generate MF sample from VideoFrame", hr);

  hr = AdjustInputSizeIfNeeded(sample.Get());
  RETURN_ON_HR_FAILURE(hr, L"Couldn't adjust input size for new frame", hr);

  // The video processor will internally acquire the keyed mutex for the
  // underlying texture when it is needed.  No need to synchronize here.
  return Convert(sample.Get(), sample_out);
}

HRESULT MediaFoundationVideoProcessorAccelerator::Convert(
    IMFSample* sample,
    IMFSample** sample_out) {
  // This approach of feeding an input to the MFT and expecting an output
  // only works if frame rate conversion is off (MF_XVP_DISABLE_FRC).  If this
  // component needs to do frame rate conversion, this logic will need
  // to be reworked to allow for multiple input samples before an output
  // is generated.
  DCHECK(video_processor_ != nullptr);
  HRESULT hr = video_processor_->ProcessInput(0, sample, 0);
  RETURN_ON_HR_FAILURE(hr, L"Failed ProcessInput for video processing", hr);

  MFT_OUTPUT_STREAM_INFO stream_info;
  hr = video_processor_->GetOutputStreamInfo(0, &stream_info);
  RETURN_ON_HR_FAILURE(hr, L"Couldn't get output stream info from XVP", hr);

  MFT_OUTPUT_DATA_BUFFER data_buffer;
  data_buffer.dwStreamID = 0;
  data_buffer.pSample = nullptr;
  data_buffer.dwStatus = 0;
  data_buffer.pEvents = nullptr;

  ComMFSample sample_for_output;
  if (!(stream_info.dwFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES) &&
      !(stream_info.dwFlags & MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES)) {
    // In D3D mode, the XVP should always provide samples.  This block
    // handles software processing mode.
    ComMFMediaBuffer media_buffer;

    hr = MFCreateMemoryBuffer(stream_info.cbSize, &media_buffer);
    RETURN_ON_HR_FAILURE(hr, L"Couldn't create output memory buffer", hr);
    hr = MFCreateSample(&sample_for_output);
    data_buffer.pSample = sample_for_output.Get();
    RETURN_ON_HR_FAILURE(hr, L"Couldn't create output sample", hr);
    hr = data_buffer.pSample->AddBuffer(media_buffer.Get());
    RETURN_ON_HR_FAILURE(hr, L"Couldn't add output buffer", hr);
  }

  DWORD status;
  hr = video_processor_->ProcessOutput(0, 1, &data_buffer, &status);
  RETURN_ON_HR_FAILURE(hr, L"Failed ProcessOutput for video processing", hr);

  if (data_buffer.pEvents) {
    data_buffer.pEvents->Release();
  }

  *sample_out = data_buffer.pSample;
  if (sample_for_output) {
    sample_for_output.Detach();
  }

  return S_OK;
}

HRESULT MediaFoundationVideoProcessorAccelerator::AdjustInputSizeIfNeeded(
    IMFSample* sample) {
  ComMFMediaBuffer buffer;
  HRESULT hr = sample->GetBufferByIndex(0, &buffer);
  RETURN_ON_HR_FAILURE(hr, "Couldn't get sample buffer", hr);
  Microsoft::WRL::ComPtr<IMFDXGIBuffer> dxgi_buffer;
  hr = buffer.As(&dxgi_buffer);
  if (SUCCEEDED(hr)) {
    ComD3D11Texture2D texture;
    hr = dxgi_buffer->GetResource(IID_PPV_ARGS(&texture));
    RETURN_ON_HR_FAILURE(hr, "Couldn't get texture from DXGI buffer", hr);
    D3D11_TEXTURE2D_DESC input_desc = {};
    texture->GetDesc(&input_desc);

    UINT32 mt_width = input_desc.Width;
    UINT32 mt_height = input_desc.Height;
    (void)MFGetAttributeSize(input_media_type_.Get(), MF_MT_FRAME_SIZE,
                             &mt_width, &mt_height);
    if (input_desc.Width != mt_width || input_desc.Height != mt_height) {
      ComMFMediaType new_input_media_type;
      hr = MFCreateMediaType(&new_input_media_type);
      RETURN_ON_HR_FAILURE(hr, "Couldn't create new input media type", hr);
      hr = input_media_type_->CopyAllItems(new_input_media_type.Get());
      RETURN_ON_HR_FAILURE(hr, "Couldn't clone input media type", hr);
      hr = MFSetAttributeSize(new_input_media_type.Get(), MF_MT_FRAME_SIZE,
                              input_desc.Width, input_desc.Height);
      RETURN_ON_HR_FAILURE(hr, "Couldn't set new frame size", hr);
      hr = video_processor_->SetInputType(0, new_input_media_type.Get(), 0);
      RETURN_ON_HR_FAILURE(hr, "Couldn't set new input type on video processor",
                           hr);
      input_media_type_ = new_input_media_type;
    }
  }

  return S_OK;
}

}  // namespace media