chromium/content/browser/renderer_host/media/video_capture_buffer_pool_unittest.cc

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

// Unit test for VideoCaptureBufferPool.

#include "media/capture/video/video_capture_buffer_pool.h"

#include <stddef.h>
#include <stdint.h>
#include <string.h>

#include <memory>
#include <utility>
#include <vector>

#include "base/functional/bind.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "content/browser/renderer_host/media/video_capture_controller.h"
#include "media/base/video_frame.h"
#include "media/capture/video/video_capture_buffer_pool_impl.h"
#include "media/capture/video/video_capture_buffer_tracker_factory_impl.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

#if BUILDFLAG(IS_MAC)
#include "ui/gfx/mac/io_surface.h"
#endif

#if BUILDFLAG(IS_WIN)
#include <dxgi1_2.h>
#include <mfapi.h>
#include "media/base/win/dxgi_device_manager.h"
#endif

namespace content {

namespace {

size_t ImageAllocationSize(const media::VideoCaptureFormat& format) {}

}  // namespace

static constexpr media::VideoPixelFormat kCapturePixelFormats[] =;

static constexpr media::VideoCaptureBufferType kVideoCaptureBufferTypes[] =;

static constexpr int kTestBufferPoolSize =;

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
static constexpr gfx::Size kDefaultTextureSize = gfx::Size(1080, 720);
static constexpr int kInvalidId = -1;
static constexpr media::VideoPixelFormat kDefaultPixelFormat =
    media::VideoPixelFormat::PIXEL_FORMAT_NV12;
static const media::VideoCaptureFormat kDefaultFormat =
    media::VideoCaptureFormat(kDefaultTextureSize, 30, kDefaultPixelFormat);
static const gfx::ColorSpace kDefaultColorSpace = gfx::ColorSpace();

#endif

// Note that this test does not exercise the class VideoCaptureBufferPool
// in isolation. The "unit under test" is an instance of VideoCaptureBufferPool
// with some context that is specific to renderer_host/media, and therefore
// this test must live here and not in media/capture/video.
class VideoCaptureBufferPoolTest
    : public testing::TestWithParam<
          std::tuple<media::VideoPixelFormat, media::VideoCaptureBufferType>> {};

TEST_P(VideoCaptureBufferPoolTest, BufferPool) {}

#if BUILDFLAG(IS_WIN)
namespace {

gfx::GpuMemoryBufferHandle CreateHandle(ID3D11Device* d3d11_device) {
  EXPECT_TRUE(d3d11_device != nullptr);

  D3D11_TEXTURE2D_DESC desc = {};
  desc.Width = kDefaultTextureSize.width();
  desc.Height = kDefaultTextureSize.height();
  desc.MipLevels = 1;
  desc.ArraySize = 1;
  desc.Format = DXGI_FORMAT_NV12;
  desc.Usage = D3D11_USAGE_DEFAULT;
  desc.SampleDesc.Count = 1;
  desc.BindFlags = 0;
  desc.MiscFlags =
      D3D11_RESOURCE_MISC_SHARED | D3D11_RESOURCE_MISC_SHARED_NTHANDLE;

  ID3D11Texture2D* texture = nullptr;
  HRESULT hr = d3d11_device->CreateTexture2D(&desc, nullptr, &texture);
  EXPECT_HRESULT_SUCCEEDED(hr);

  Microsoft::WRL::ComPtr<IDXGIResource1> dxgi_resource;
  hr = texture->QueryInterface(IID_PPV_ARGS(&dxgi_resource));
  EXPECT_HRESULT_SUCCEEDED(hr);

  HANDLE texture_handle;
  hr = dxgi_resource->CreateSharedHandle(
      nullptr, DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE, nullptr,
      &texture_handle);
  EXPECT_HRESULT_SUCCEEDED(hr);

  gfx::GpuMemoryBufferHandle result;
  result.type = gfx::GpuMemoryBufferType::DXGI_SHARED_HANDLE;
  result.dxgi_handle.Set(texture_handle);
  result.dxgi_token = gfx::DXGIHandleToken();
  return result;
}

}  // namespace

TEST_P(VideoCaptureBufferPoolTest, BufferPoolExternalWin) {
  auto handle0 = CreateHandle(d3d11_device_);
  auto handle1 = CreateHandle(d3d11_device_);
  auto handle2 = CreateHandle(d3d11_device_);
  int buffer_id_to_drop;
  int buffer_id0 = kInvalidId;
  EXPECT_EQ(pool_->ReserveIdForExternalBuffer(
                media::CapturedExternalVideoBuffer(
                    std::move(handle0), kDefaultFormat, kDefaultColorSpace),
                kDefaultTextureSize, &buffer_id_to_drop, &buffer_id0),
            media::VideoCaptureDevice::Client::ReserveResult::kSucceeded);
  EXPECT_NE(buffer_id0, kInvalidId);
  EXPECT_EQ(buffer_id_to_drop, kInvalidId);
  pool_->HoldForConsumers(buffer_id0, 1);
  pool_->RelinquishProducerReservation(buffer_id0);
  // We should get a new buffer for handle1.
  int buffer_id1 = kInvalidId;
  EXPECT_EQ(pool_->ReserveIdForExternalBuffer(
                media::CapturedExternalVideoBuffer(
                    std::move(handle1), kDefaultFormat, kDefaultColorSpace),
                kDefaultTextureSize, &buffer_id_to_drop, &buffer_id1),
            media::VideoCaptureDevice::Client::ReserveResult::kSucceeded);
  EXPECT_NE(buffer_id1, kInvalidId);
  EXPECT_EQ(buffer_id_to_drop, kInvalidId);
  pool_->HoldForConsumers(buffer_id1, 1);
  pool_->RelinquishProducerReservation(buffer_id1);
  pool_->RelinquishConsumerHold(buffer_id1, 1);
  // We should reuse handle1's buffer.
  int buffer_id1_reuse = kInvalidId;

  EXPECT_EQ(pool_->ReserveIdForExternalBuffer(
                media::CapturedExternalVideoBuffer(
                    pool_->GetGpuMemoryBufferHandle(buffer_id1), kDefaultFormat,
                    kDefaultColorSpace),
                kDefaultTextureSize, &buffer_id_to_drop, &buffer_id1_reuse),
            media::VideoCaptureDevice::Client::ReserveResult::kSucceeded);
  EXPECT_EQ(buffer_id1, buffer_id1_reuse);
  EXPECT_EQ(buffer_id_to_drop, kInvalidId);
  pool_->HoldForConsumers(buffer_id1_reuse, 1);
  pool_->RelinquishProducerReservation(buffer_id1_reuse);
  // If we leave buffer_id1 held for a consumer, then we create a new buffer id
  // for it.
  int buffer_id1_new = kInvalidId;
  EXPECT_EQ(pool_->ReserveIdForExternalBuffer(
                media::CapturedExternalVideoBuffer(
                    pool_->GetGpuMemoryBufferHandle(buffer_id1), kDefaultFormat,
                    kDefaultColorSpace),
                kDefaultTextureSize, &buffer_id_to_drop, &buffer_id1_new),
            media::VideoCaptureDevice::Client::ReserveResult::kSucceeded);
  EXPECT_NE(buffer_id1, buffer_id1_new);
  EXPECT_EQ(buffer_id_to_drop, kInvalidId);
  pool_->HoldForConsumers(buffer_id1_new, 1);
  pool_->RelinquishProducerReservation(buffer_id1_new);
  pool_->RelinquishConsumerHold(buffer_id1_new, 1);
  // We have now reached kTestBufferPoolSize buffers. So our next allocation
  // will return the LRU buffer, which is buffer_id1_new.
  pool_->RelinquishConsumerHold(buffer_id1_reuse, 1);
  int buffer_id2 = kInvalidId;
  EXPECT_EQ(pool_->ReserveIdForExternalBuffer(
                media::CapturedExternalVideoBuffer(
                    std::move(handle2), kDefaultFormat, kDefaultColorSpace),
                kDefaultTextureSize, &buffer_id_to_drop, &buffer_id2),
            media::VideoCaptureDevice::Client::ReserveResult::kSucceeded);
  EXPECT_NE(buffer_id0, buffer_id2);
  EXPECT_NE(buffer_id1, buffer_id2);
  EXPECT_NE(buffer_id1_new, buffer_id2);
  EXPECT_EQ(buffer_id_to_drop, buffer_id1_new);
  EXPECT_NE(buffer_id2, kInvalidId);
  // Finally, let's reuse handle0.
  pool_->RelinquishConsumerHold(buffer_id0, 1);
  int buffer_id0_reuse = kInvalidId;
  EXPECT_EQ(pool_->ReserveIdForExternalBuffer(
                media::CapturedExternalVideoBuffer(
                    pool_->GetGpuMemoryBufferHandle(buffer_id0), kDefaultFormat,
                    kDefaultColorSpace),
                kDefaultTextureSize, &buffer_id_to_drop, &buffer_id0_reuse),
            media::VideoCaptureDevice::Client::ReserveResult::kSucceeded);
  EXPECT_EQ(buffer_id0, buffer_id0_reuse);
  EXPECT_EQ(buffer_id_to_drop, kInvalidId);
}

#endif

#if BUILDFLAG(IS_MAC)
namespace {

gfx::GpuMemoryBufferHandle CreateIOSurfaceHandle() {
  gfx::GpuMemoryBufferHandle result;
  result.type = gfx::GpuMemoryBufferType::IO_SURFACE_BUFFER;
  result.id = gfx::GpuMemoryBufferHandle::kInvalidId;
  result.io_surface =
      gfx::CreateIOSurface(kDefaultTextureSize, gfx::BufferFormat::BGRA_8888);
  return result;
}

}  // namespace

TEST_P(VideoCaptureBufferPoolTest, BufferPoolExternal) {
  auto handle0 = CreateIOSurfaceHandle();
  auto handle1 = CreateIOSurfaceHandle();
  auto handle2 = CreateIOSurfaceHandle();

  int buffer_id_to_drop;
  int buffer_id0 = kInvalidId;
  EXPECT_EQ(pool_->ReserveIdForExternalBuffer(
                media::CapturedExternalVideoBuffer(
                    std::move(handle0), kDefaultFormat, kDefaultColorSpace),
                kDefaultTextureSize, &buffer_id_to_drop, &buffer_id0),
            media::VideoCaptureDevice::Client::ReserveResult::kSucceeded);
  EXPECT_NE(buffer_id0, kInvalidId);
  EXPECT_EQ(buffer_id_to_drop, kInvalidId);
  EXPECT_FALSE(IOSurfaceIsInUse(
      pool_->GetGpuMemoryBufferHandle(buffer_id0).io_surface.get()));
  pool_->HoldForConsumers(buffer_id0, 1);
  EXPECT_TRUE(IOSurfaceIsInUse(
      pool_->GetGpuMemoryBufferHandle(buffer_id0).io_surface.get()));
  pool_->RelinquishProducerReservation(buffer_id0);
  // We should get a new buffer for handle1.
  int buffer_id1 = kInvalidId;
  EXPECT_EQ(pool_->ReserveIdForExternalBuffer(
                media::CapturedExternalVideoBuffer(
                    std::move(handle1), kDefaultFormat, kDefaultColorSpace),
                kDefaultTextureSize, &buffer_id_to_drop, &buffer_id1),
            media::VideoCaptureDevice::Client::ReserveResult::kSucceeded);
  EXPECT_NE(buffer_id1, kInvalidId);
  EXPECT_EQ(buffer_id_to_drop, kInvalidId);
  pool_->HoldForConsumers(buffer_id1, 1);
  pool_->RelinquishProducerReservation(buffer_id1);
  pool_->RelinquishConsumerHold(buffer_id1, 1);
  // We should reuse handle1's buffer.
  int buffer_id1_reuse = kInvalidId;
  EXPECT_EQ(pool_->ReserveIdForExternalBuffer(
                media::CapturedExternalVideoBuffer(
                    pool_->GetGpuMemoryBufferHandle(buffer_id1), kDefaultFormat,
                    kDefaultColorSpace),
                kDefaultTextureSize, &buffer_id_to_drop, &buffer_id1_reuse),
            media::VideoCaptureDevice::Client::ReserveResult::kSucceeded);
  EXPECT_EQ(buffer_id1, buffer_id1_reuse);
  EXPECT_EQ(buffer_id_to_drop, kInvalidId);
  pool_->HoldForConsumers(buffer_id1_reuse, 1);
  pool_->RelinquishProducerReservation(buffer_id1_reuse);
  // If we leave buffer_id1 held for a consumer, then we create a new buffer id
  // for it.
  int buffer_id1_new = kInvalidId;
  EXPECT_EQ(pool_->ReserveIdForExternalBuffer(
                media::CapturedExternalVideoBuffer(
                    pool_->GetGpuMemoryBufferHandle(buffer_id1), kDefaultFormat,
                    kDefaultColorSpace),
                kDefaultTextureSize, &buffer_id_to_drop, &buffer_id1_new),
            media::VideoCaptureDevice::Client::ReserveResult::kSucceeded);
  EXPECT_NE(buffer_id1, buffer_id1_new);
  EXPECT_EQ(buffer_id_to_drop, kInvalidId);
  pool_->HoldForConsumers(buffer_id1_new, 1);
  pool_->RelinquishProducerReservation(buffer_id1_new);
  pool_->RelinquishConsumerHold(buffer_id1_new, 1);
  // We have now reached kTestBufferPoolSize buffers. So our next allocation
  // will return the LRU buffer, which is buffer_id1_new.
  pool_->RelinquishConsumerHold(buffer_id1_reuse, 1);
  int buffer_id2 = kInvalidId;
  EXPECT_EQ(pool_->ReserveIdForExternalBuffer(
                media::CapturedExternalVideoBuffer(
                    std::move(handle2), kDefaultFormat, kDefaultColorSpace),
                kDefaultTextureSize, &buffer_id_to_drop, &buffer_id2),
            media::VideoCaptureDevice::Client::ReserveResult::kSucceeded);
  EXPECT_NE(buffer_id0, buffer_id2);
  EXPECT_NE(buffer_id1, buffer_id2);
  EXPECT_NE(buffer_id1_new, buffer_id2);
  EXPECT_NE(buffer_id2, kInvalidId);
  EXPECT_EQ(buffer_id_to_drop, buffer_id1_new);
  // Finally, let's reuse handle0.
  pool_->RelinquishConsumerHold(buffer_id0, 1);
  int buffer_id0_reuse = kInvalidId;
  EXPECT_EQ(pool_->ReserveIdForExternalBuffer(
                media::CapturedExternalVideoBuffer(
                    pool_->GetGpuMemoryBufferHandle(buffer_id0), kDefaultFormat,
                    kDefaultColorSpace),
                kDefaultTextureSize, &buffer_id_to_drop, &buffer_id0_reuse),
            media::VideoCaptureDevice::Client::ReserveResult::kSucceeded);
  EXPECT_EQ(buffer_id0, buffer_id0_reuse);
  EXPECT_EQ(buffer_id_to_drop, kInvalidId);
}
#endif

INSTANTIATE_TEST_SUITE_P();

}  // namespace content