blob: fab1f2502a8b1a8b840b384b0f50deef0f45c074 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/mirroring/browser/single_client_video_capture_host.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/test/scoped_task_environment.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::InvokeWithoutArgs;
using media::VideoFrameReceiver;
namespace mirroring {
namespace {
class MockVideoCaptureDevice final
: public content::LaunchedVideoCaptureDevice {
public:
MockVideoCaptureDevice() {}
~MockVideoCaptureDevice() override {}
void GetPhotoState(
VideoCaptureDevice::GetPhotoStateCallback callback) const override {}
void SetPhotoOptions(
media::mojom::PhotoSettingsPtr settings,
VideoCaptureDevice::SetPhotoOptionsCallback callback) override {}
void TakePhoto(VideoCaptureDevice::TakePhotoCallback callback) override {}
void SetDesktopCaptureWindowIdAsync(gfx::NativeViewId window_id,
base::OnceClosure done_cb) override {}
MOCK_METHOD0(MaybeSuspendDevice, void());
MOCK_METHOD0(ResumeDevice, void());
MOCK_METHOD0(RequestRefreshFrame, void());
MOCK_METHOD2(OnUtilizationReport, void(int, double));
private:
DISALLOW_COPY_AND_ASSIGN(MockVideoCaptureDevice);
};
class FakeDeviceLauncher final : public content::VideoCaptureDeviceLauncher {
public:
using DeviceLaunchedCallback =
base::OnceCallback<void(base::WeakPtr<VideoFrameReceiver>,
MockVideoCaptureDevice*)>;
explicit FakeDeviceLauncher(DeviceLaunchedCallback launched_cb)
: after_launch_cb_(std::move(launched_cb)), weak_factory_(this) {}
~FakeDeviceLauncher() override {}
// content::VideoCaptureDeviceLauncher implementation.
void LaunchDeviceAsync(const std::string& device_id,
content::MediaStreamType stream_type,
const VideoCaptureParams& params,
base::WeakPtr<VideoFrameReceiver> receiver,
base::OnceClosure connection_lost_cb,
Callbacks* callbacks,
base::OnceClosure done_cb) override {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&FakeDeviceLauncher::OnDeviceLaunched,
weak_factory_.GetWeakPtr(), receiver,
callbacks, std::move(done_cb)));
}
void AbortLaunch() override {}
private:
void OnDeviceLaunched(base::WeakPtr<VideoFrameReceiver> receiver,
VideoCaptureDeviceLauncher::Callbacks* callbacks,
base::OnceClosure done_cb) {
auto launched_device = std::make_unique<MockVideoCaptureDevice>();
EXPECT_FALSE(after_launch_cb_.is_null());
std::move(after_launch_cb_).Run(receiver, launched_device.get());
callbacks->OnDeviceLaunched(std::move(launched_device));
std::move(done_cb).Run();
}
DeviceLaunchedCallback after_launch_cb_;
base::WeakPtrFactory<FakeDeviceLauncher> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(FakeDeviceLauncher);
};
class StubReadWritePermission final
: public VideoCaptureDevice::Client::Buffer::ScopedAccessPermission {
public:
StubReadWritePermission() {}
~StubReadWritePermission() override {}
private:
DISALLOW_COPY_AND_ASSIGN(StubReadWritePermission);
};
class MockVideoCaptureObserver final
: public media::mojom::VideoCaptureObserver {
public:
explicit MockVideoCaptureObserver(media::mojom::VideoCaptureHostPtr host)
: host_(std::move(host)), binding_(this) {}
MOCK_METHOD1(OnBufferCreatedCall, void(int buffer_id));
void OnNewBuffer(int32_t buffer_id,
media::mojom::VideoBufferHandlePtr buffer_handle) override {
EXPECT_EQ(buffers_.find(buffer_id), buffers_.end());
EXPECT_EQ(frame_infos_.find(buffer_id), frame_infos_.end());
buffers_[buffer_id] = std::move(buffer_handle);
OnBufferCreatedCall(buffer_id);
}
MOCK_METHOD1(OnBufferReadyCall, void(int buffer_id));
void OnBufferReady(int32_t buffer_id,
media::mojom::VideoFrameInfoPtr info) override {
EXPECT_TRUE(buffers_.find(buffer_id) != buffers_.end());
EXPECT_EQ(frame_infos_.find(buffer_id), frame_infos_.end());
frame_infos_[buffer_id] = std::move(info);
OnBufferReadyCall(buffer_id);
}
MOCK_METHOD1(OnBufferDestroyedCall, void(int buffer_id));
void OnBufferDestroyed(int32_t buffer_id) override {
// The consumer should have finished consuming the buffer before it is being
// destroyed.
EXPECT_TRUE(frame_infos_.find(buffer_id) == frame_infos_.end());
const auto iter = buffers_.find(buffer_id);
EXPECT_TRUE(iter != buffers_.end());
buffers_.erase(iter);
OnBufferDestroyedCall(buffer_id);
}
MOCK_METHOD1(OnStateChanged, void(media::mojom::VideoCaptureState state));
void Start() {
media::mojom::VideoCaptureObserverPtr observer;
binding_.Bind(mojo::MakeRequest(&observer));
host_->Start(0, 0, VideoCaptureParams(), std::move(observer));
}
void FinishConsumingBuffer(int32_t buffer_id, double utilization) {
EXPECT_TRUE(buffers_.find(buffer_id) != buffers_.end());
const auto iter = frame_infos_.find(buffer_id);
EXPECT_TRUE(iter != frame_infos_.end());
frame_infos_.erase(iter);
host_->ReleaseBuffer(0, buffer_id, utilization);
}
void Stop() { host_->Stop(0); }
private:
media::mojom::VideoCaptureHostPtr host_;
mojo::Binding<media::mojom::VideoCaptureObserver> binding_;
base::flat_map<int, media::mojom::VideoBufferHandlePtr> buffers_;
base::flat_map<int, media::mojom::VideoFrameInfoPtr> frame_infos_;
DISALLOW_COPY_AND_ASSIGN(MockVideoCaptureObserver);
};
media::mojom::VideoFrameInfoPtr GetVideoFrameInfo() {
return media::mojom::VideoFrameInfo::New(
base::TimeDelta(), base::Value(base::Value::Type::DICTIONARY),
media::PIXEL_FORMAT_I420, gfx::Size(320, 180), gfx::Rect(320, 180));
}
} // namespace
class SingleClientVideoCaptureHostTest : public ::testing::Test {
public:
SingleClientVideoCaptureHostTest() : weak_factory_(this) {
auto host_impl = std::make_unique<SingleClientVideoCaptureHost>(
std::string(), content::MediaStreamType::MEDIA_GUM_TAB_VIDEO_CAPTURE,
base::BindRepeating(
&SingleClientVideoCaptureHostTest::CreateDeviceLauncher,
base::Unretained(this)));
media::mojom::VideoCaptureHostPtr host;
mojo::MakeStrongBinding(std::move(host_impl), mojo::MakeRequest(&host));
consumer_ = std::make_unique<MockVideoCaptureObserver>(std::move(host));
base::RunLoop run_loop;
EXPECT_CALL(*this, OnDeviceLaunchedCall())
.WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
consumer_->Start();
run_loop.Run();
// The video capture device is launched.
EXPECT_TRUE(!!launched_device_);
EXPECT_TRUE(!!frame_receiver_);
}
~SingleClientVideoCaptureHostTest() override {
base::RunLoop run_loop;
EXPECT_CALL(*consumer_,
OnStateChanged(media::mojom::VideoCaptureState::ENDED))
.WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
consumer_->Stop();
run_loop.Run();
}
protected:
void CreateBuffer(int buffer_id, int expected_buffer_context_id) {
base::RunLoop run_loop;
EXPECT_CALL(*consumer_, OnBufferCreatedCall(expected_buffer_context_id))
.WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
media::mojom::VideoBufferHandlePtr stub_buffer_handle =
media::mojom::VideoBufferHandle::New();
stub_buffer_handle->set_shared_buffer_handle(
mojo::SharedBufferHandle::Create(10));
frame_receiver_->OnNewBuffer(buffer_id, std::move(stub_buffer_handle));
run_loop.Run();
}
void FrameReadyInBuffer(int buffer_id,
int buffer_context_id,
int feedback_id) {
base::RunLoop run_loop;
EXPECT_CALL(*consumer_, OnBufferReadyCall(buffer_context_id))
.WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
frame_receiver_->OnFrameReadyInBuffer(
buffer_id, feedback_id, std::make_unique<StubReadWritePermission>(),
GetVideoFrameInfo());
run_loop.Run();
}
void FinishConsumingBuffer(int buffer_context_id,
int feedback_id,
double utilization) {
base::RunLoop run_loop;
EXPECT_CALL(*launched_device_,
OnUtilizationReport(feedback_id, utilization))
.WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
consumer_->FinishConsumingBuffer(buffer_context_id, utilization);
run_loop.Run();
}
void RetireBuffer(int buffer_id, int buffer_context_id) {
base::RunLoop run_loop;
EXPECT_CALL(*consumer_, OnBufferDestroyedCall(buffer_context_id))
.WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
frame_receiver_->OnBufferRetired(buffer_id);
run_loop.Run();
}
base::test::ScopedTaskEnvironment scoped_task_environment_;
std::unique_ptr<MockVideoCaptureObserver> consumer_;
base::WeakPtr<VideoFrameReceiver> frame_receiver_;
MockVideoCaptureDevice* launched_device_ = nullptr;
private:
std::unique_ptr<content::VideoCaptureDeviceLauncher> CreateDeviceLauncher() {
return std::make_unique<FakeDeviceLauncher>(
base::BindOnce(&SingleClientVideoCaptureHostTest::OnDeviceLaunched,
weak_factory_.GetWeakPtr()));
}
MOCK_METHOD0(OnDeviceLaunchedCall, void());
void OnDeviceLaunched(base::WeakPtr<VideoFrameReceiver> receiver,
MockVideoCaptureDevice* launched_device) {
frame_receiver_ = std::move(receiver);
launched_device_ = launched_device;
OnDeviceLaunchedCall();
}
base::WeakPtrFactory<SingleClientVideoCaptureHostTest> weak_factory_;
};
TEST_F(SingleClientVideoCaptureHostTest, Basic) {
CreateBuffer(1, 0);
FrameReadyInBuffer(1, 0, 5);
FinishConsumingBuffer(0, 5, 1.0);
RetireBuffer(1, 0);
}
TEST_F(SingleClientVideoCaptureHostTest, ReuseBufferId) {
CreateBuffer(0, 0);
FrameReadyInBuffer(0, 0, 3);
// Retire buffer 0. The consumer is not expected to receive OnBufferDestroyed
// since the buffer is not returned yet.
{
EXPECT_CALL(*consumer_, OnBufferDestroyedCall(0)).Times(0);
frame_receiver_->OnBufferRetired(0);
scoped_task_environment_.RunUntilIdle();
}
// Re-use buffer 0.
CreateBuffer(0, 1);
FrameReadyInBuffer(0, 1, 7);
// Finish consuming frame in the retired buffer 0.
FinishConsumingBuffer(0, 3, 1.0);
// The retired buffer is expected to be destroyed since the consumer finished
// consuming the frame in that buffer.
base::RunLoop run_loop;
EXPECT_CALL(*consumer_, OnBufferDestroyedCall(0))
.WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
run_loop.Run();
FinishConsumingBuffer(1, 7, 0.5);
RetireBuffer(0, 1);
}
TEST_F(SingleClientVideoCaptureHostTest, StopCapturingWhileBuffersInUse) {
for (int i = 0; i < 10; ++i) {
CreateBuffer(i, i);
FrameReadyInBuffer(i, i, i);
}
}
} // namespace mirroring