chromium/media/gpu/android/video_frame_factory_impl_unittest.cc

// Copyright 2017 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/android/video_frame_factory_impl.h"

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "gpu/command_buffer/service/mock_texture_owner.h"
#include "gpu/command_buffer/service/ref_counted_lock_for_test.h"
#include "gpu/command_buffer/service/shared_context_state.h"
#include "gpu/config/gpu_finch_features.h"
#include "gpu/config/gpu_preferences.h"
#include "media/base/android/test_destruction_observable.h"
#include "media/base/limits.h"
#include "media/gpu/android/codec_buffer_wait_coordinator.h"
#include "media/gpu/android/maybe_render_early_manager.h"
#include "media/gpu/android/mock_codec_image.h"
#include "media/gpu/android/mock_shared_image_video_provider.h"
#include "media/gpu/android/shared_image_video_provider.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using base::test::RunOnceCallback;
using testing::_;
using testing::Return;
using testing::SaveArg;

namespace gpu {
class CommandBufferStub;
}  // namespace gpu

namespace media {

class MockMaybeRenderEarlyManager : public MaybeRenderEarlyManager {
 public:
  MOCK_METHOD1(SetSurfaceBundle, void(scoped_refptr<CodecSurfaceBundle>));
  MOCK_METHOD1(AddCodecImage, void(scoped_refptr<CodecImageHolder>));
  MOCK_METHOD0(MaybeRenderEarly, void());
};

class MockFrameInfoHelper : public FrameInfoHelper,
                            public DestructionObservable {
 public:
  void GetFrameInfo(std::unique_ptr<CodecOutputBufferRenderer> buffer_renderer,
                    FrameInfoReadyCB cb) override {
    FrameInfo info;
    info.coded_size = buffer_renderer->size();
    info.visible_rect = gfx::Rect(info.coded_size);

    std::move(cb).Run(std::move(buffer_renderer), info);
  }

  MOCK_CONST_METHOD0(IsStalled, bool());
};

class VideoFrameFactoryImplTest : public testing::Test {
 public:
  VideoFrameFactoryImplTest()
      : task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()) {
    auto image_provider = std::make_unique<MockSharedImageVideoProvider>();
    image_provider_raw_ = image_provider.get();

    auto mre_manager = std::make_unique<MockMaybeRenderEarlyManager>();
    mre_manager_raw_ = mre_manager.get();

    auto info_helper = std::make_unique<MockFrameInfoHelper>();
    info_helper_raw_ = info_helper.get();

    impl_ = std::make_unique<VideoFrameFactoryImpl>(
        task_runner_, gpu_preferences_, std::move(image_provider),
        std::move(mre_manager), std::move(info_helper),
        features::NeedThreadSafeAndroidMedia()
            ? base::MakeRefCounted<gpu::RefCountedLockForTest>()
            : nullptr);
    auto texture_owner = base::MakeRefCounted<NiceMock<gpu::MockTextureOwner>>(
        0, nullptr, nullptr, true);
    auto codec_buffer_wait_coordinator =
        base::MakeRefCounted<CodecBufferWaitCoordinator>(
            std::move(texture_owner),
            features::NeedThreadSafeAndroidMedia()
                ? base::MakeRefCounted<gpu::RefCountedLockForTest>()
                : nullptr);

    // Provide a non-null |codec_buffer_wait_coordinator| to |impl_|.
    impl_->SetCodecBufferWaitCorrdinatorForTesting(
        std::move(codec_buffer_wait_coordinator));
  }

  ~VideoFrameFactoryImplTest() override = default;

  struct {
    gfx::Size coded_size{100, 100};
    gfx::Rect visible_rect{coded_size};
    gfx::Size natural_size{coded_size};
    gfx::ColorSpace color_space{gfx::ColorSpace::CreateSRGBLinear()};
  } video_frame_params_;

  void RequestVideoFrame() {
    auto output_buffer = CodecOutputBuffer::CreateForTesting(
        0, video_frame_params_.coded_size, video_frame_params_.color_space,
        std::nullopt);
    ASSERT_TRUE(VideoFrame::IsValidConfig(
        PIXEL_FORMAT_ARGB, VideoFrame::STORAGE_OPAQUE,
        video_frame_params_.coded_size, video_frame_params_.visible_rect,
        video_frame_params_.natural_size));

    // Save a copy in case the test wants it.
    output_buffer_raw_ = output_buffer.get();

    // We should get a call to the output callback, but no calls to the
    // provider.
    // TODO(liberato): Verify that it's sending the proper TextureOwner.
    // However, we haven't actually given it a TextureOwner yet.
    output_buffer_raw_ = output_buffer.get();
    EXPECT_CALL(*image_provider_raw_, MockRequestImage());

    impl_->CreateVideoFrame(std::move(output_buffer), base::TimeDelta(),
                            video_frame_params_.natural_size,
                            base::NullCallback(), output_cb_.Get());
    base::RunLoop().RunUntilIdle();

    // TODO(liberato): Verify that it requested a shared image.
  }

  // |release_cb_called_flag| will be set when the record's |release_cb| runs.
  SharedImageVideoProvider::ImageRecord MakeImageRecord(
      bool* release_cb_called_flag = nullptr) {
    SharedImageVideoProvider::ImageRecord record;
    record.shared_image = gpu::Mailbox::Generate();
    if (release_cb_called_flag)
      *release_cb_called_flag = false;
    record.release_cb = base::BindOnce(
        [](bool* flag, const gpu::SyncToken&) {
          if (flag)
            *flag = true;
        },
        base::Unretained(release_cb_called_flag));
    auto codec_image =
        base::MakeRefCounted<MockCodecImage>(gfx::Size(100, 100));
    record.codec_image_holder = base::MakeRefCounted<CodecImageHolder>(
        task_runner_, codec_image,
        features::NeedThreadSafeAndroidMedia()
            ? base::MakeRefCounted<gpu::RefCountedLockForTest>()
            : nullptr);
    return record;
  }

  base::test::TaskEnvironment task_environment_;
  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;

  std::unique_ptr<VideoFrameFactoryImpl> impl_;

  raw_ptr<MockMaybeRenderEarlyManager> mre_manager_raw_ = nullptr;
  raw_ptr<MockSharedImageVideoProvider> image_provider_raw_ = nullptr;
  raw_ptr<MockFrameInfoHelper> info_helper_raw_ = nullptr;

  // Most recently created CodecOutputBuffer.
  raw_ptr<CodecOutputBuffer> output_buffer_raw_ = nullptr;

  // Sent to |impl_| by RequestVideoFrame..
  base::MockCallback<VideoFrameFactory::OnceOutputCB> output_cb_;

  std::unique_ptr<DestructionObserver> ycbcr_destruction_observer_;

  gpu::GpuPreferences gpu_preferences_;
};

TEST_F(VideoFrameFactoryImplTest, ImageProviderInitFailure) {
  // If the image provider fails to init, then our init cb should be called with
  // no TextureOwner.
  EXPECT_CALL(*image_provider_raw_, Initialize_(_))
      .Times(1)
      .WillOnce(RunOnceCallback<0>(nullptr));
  base::MockCallback<VideoFrameFactory::InitCB> init_cb;
  EXPECT_CALL(init_cb, Run(scoped_refptr<gpu::TextureOwner>(nullptr)));
  impl_->Initialize(VideoFrameFactory::OverlayMode::kDontRequestPromotionHints,
                    init_cb.Get());
  base::RunLoop().RunUntilIdle();

  // TODO(liberato): for testing, we could just skip calling the gpu init cb,
  // since |impl_| doesn't know or care if it's called.  that way, we don't need
  // to mock out making the callback work.  would be nice, though.
}

TEST_F(VideoFrameFactoryImplTest,
       SetSurfaceBundleForwardsToMaybeRenderEarlyManager) {
  // Sending a non-null CodecSurfaceBundle should forward it to |mre_manager|.
  // Also provide a non-null TextureOwner to it.
  scoped_refptr<CodecSurfaceBundle> surface_bundle =
      base::MakeRefCounted<CodecSurfaceBundle>(
          base::MakeRefCounted<NiceMock<gpu::MockTextureOwner>>(0, nullptr,
                                                                nullptr, true),
          features::NeedThreadSafeAndroidMedia()
              ? base::MakeRefCounted<gpu::RefCountedLockForTest>()
              : nullptr);
  EXPECT_CALL(*mre_manager_raw_, SetSurfaceBundle(surface_bundle));
  impl_->SetSurfaceBundle(surface_bundle);
  base::RunLoop().RunUntilIdle();
}

TEST_F(VideoFrameFactoryImplTest, CreateVideoFrameFailsIfUnsupportedFormat) {
  // Sending an unsupported format should cause an early failure, without a
  // thread hop.
  gfx::Size coded_size(limits::kMaxDimension + 1, limits::kMaxDimension + 1);
  gfx::Rect visible_rect(coded_size);
  gfx::Size natural_size(0, 0);
  auto output_buffer = CodecOutputBuffer::CreateForTesting(
      0, coded_size, gfx::ColorSpace(), std::nullopt);
  ASSERT_FALSE(VideoFrame::IsValidConfig(PIXEL_FORMAT_ARGB,
                                         VideoFrame::STORAGE_OPAQUE, coded_size,
                                         visible_rect, natural_size));

  // We should get a call to the output callback, but no calls to the provider.
  base::MockCallback<VideoFrameFactory::OnceOutputCB> output_cb;
  EXPECT_CALL(output_cb, Run(scoped_refptr<VideoFrame>(nullptr)));
  EXPECT_CALL(*image_provider_raw_, MockRequestImage()).Times(0);

  impl_->CreateVideoFrame(std::move(output_buffer), base::TimeDelta(),
                          natural_size, base::NullCallback(), output_cb.Get());
  base::RunLoop().RunUntilIdle();
}

TEST_F(VideoFrameFactoryImplTest, CreateVideoFrameSucceeds) {
  // Creating a video frame calls through to the image provider, and forwards a
  // VideoFrame to the output cb.
  //
  // TODO(liberato): Consider testing the metadata values.
  RequestVideoFrame();

  // Call the ImageReadyCB.
  scoped_refptr<VideoFrame> frame;
  EXPECT_CALL(output_cb_, Run(_)).WillOnce(SaveArg<0>(&frame));
  bool release_cb_called_flag = false;
  auto record = MakeImageRecord(&release_cb_called_flag);
  scoped_refptr<CodecImage> codec_image(
      record.codec_image_holder->codec_image_raw());
  image_provider_raw_->ProvideOneRequestedImage(&record);
  base::RunLoop().RunUntilIdle();
  EXPECT_NE(frame, nullptr);

  // Make sure that it set the output buffer properly.
  EXPECT_EQ(codec_image->get_codec_output_buffer_for_testing(),
            output_buffer_raw_);

  EXPECT_EQ(frame->coded_size(), video_frame_params_.coded_size);
  EXPECT_EQ(frame->natural_size(), video_frame_params_.natural_size);
  EXPECT_EQ(frame->visible_rect(), video_frame_params_.visible_rect);
  EXPECT_EQ(frame->ColorSpace(), video_frame_params_.color_space);

  // Destroy the VideoFrame, and verify that our release cb is called.
  EXPECT_FALSE(release_cb_called_flag);
  frame = nullptr;
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(release_cb_called_flag);
}

TEST_F(VideoFrameFactoryImplTest,
       DestroyingFactoryDuringVideoFrameCreationDoesntCrash) {
  // We should be able to destroy |impl_| while a VideoFrame is pending, and
  // nothing bad should happen.
  RequestVideoFrame();
  impl_ = nullptr;
  base::RunLoop().RunUntilIdle();
}

TEST_F(VideoFrameFactoryImplTest, IsStalled) {
  EXPECT_CALL(*info_helper_raw_, IsStalled()).WillOnce(Return(false));
  EXPECT_FALSE(impl_->IsStalled());
  EXPECT_CALL(*info_helper_raw_, IsStalled()).WillOnce(Return(true));
  EXPECT_TRUE(impl_->IsStalled());
}

}  // namespace media