blob: 63484483d9729dfb034ea622f548417d1b50d7f0 [file] [log] [blame]
// Copyright 2020 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 "media/capture/video/fuchsia/video_capture_device_fuchsia.h"
#include "base/fuchsia/test_component_context_for_process.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "media/capture/video/fuchsia/video_capture_device_factory_fuchsia.h"
#include "media/fuchsia/camera/fake_fuchsia_camera.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
namespace media {
namespace {
struct ReceivedFrame {
VideoCaptureDevice::Client::Buffer buffer;
VideoCaptureFormat format;
gfx::ColorSpace color_space;
base::TimeTicks reference_time;
base::TimeDelta timestamp;
gfx::Rect visible_rect;
};
void ValidateReceivedFrame(const ReceivedFrame& frame,
gfx::Size expected_size,
uint8_t salt) {
gfx::Size coded_size((expected_size.width() + 1) & ~1,
(expected_size.height() + 1) & ~1);
ASSERT_EQ(frame.format.frame_size, coded_size);
EXPECT_EQ(frame.format.pixel_format, PIXEL_FORMAT_I420);
EXPECT_GT(frame.format.frame_rate, 0.0);
EXPECT_EQ(frame.visible_rect, gfx::Rect(expected_size));
EXPECT_EQ(frame.color_space, gfx::ColorSpace());
auto handle = frame.buffer.handle_provider->GetHandleForInProcessAccess();
FakeCameraStream::ValidateFrameData(handle->data(), coded_size, salt);
}
// VideoCaptureBufferHandle implementation that references memory allocated on
// the heap.
class HeapBufferHandle : public VideoCaptureBufferHandle {
public:
HeapBufferHandle(size_t size, uint8_t* data) : size_(size), data_(data) {}
size_t mapped_size() const final { return size_; }
uint8_t* data() const final { return data_; }
const uint8_t* const_data() const final { return data_; }
private:
const size_t size_;
uint8_t* const data_;
};
// VideoCaptureDevice::Client::Buffer::HandleProvider implementation that
// allocates memory on the heap.
class HeapBufferHandleProvider final
: public VideoCaptureDevice::Client::Buffer::HandleProvider {
public:
HeapBufferHandleProvider(size_t size) : data_(size) {}
~HeapBufferHandleProvider() override = default;
base::UnsafeSharedMemoryRegion DuplicateAsUnsafeRegion() override {
NOTREACHED();
return {};
}
mojo::ScopedSharedBufferHandle DuplicateAsMojoBuffer() override {
NOTREACHED();
return {};
}
std::unique_ptr<VideoCaptureBufferHandle> GetHandleForInProcessAccess()
override {
return std::make_unique<HeapBufferHandle>(data_.size(), data_.data());
}
gfx::GpuMemoryBufferHandle GetGpuMemoryBufferHandle() override {
return gfx::GpuMemoryBufferHandle();
}
private:
std::vector<uint8_t> data_;
};
class TestVideoCaptureClient final : public VideoCaptureDevice::Client {
public:
~TestVideoCaptureClient() override = default;
void WaitFrame() {
EXPECT_FALSE(wait_frame_run_loop_);
wait_frame_run_loop_.emplace();
wait_frame_run_loop_->Run();
wait_frame_run_loop_.reset();
}
const std::vector<ReceivedFrame>& received_frames() {
return received_frames_;
}
private:
// VideoCaptureDevice::Client implementation.
void OnStarted() final {
EXPECT_FALSE(started_);
started_ = true;
}
ReserveResult ReserveOutputBuffer(const gfx::Size& dimensions,
VideoPixelFormat format,
int frame_feedback_id,
Buffer* buffer) override {
EXPECT_TRUE(started_);
EXPECT_EQ(format, PIXEL_FORMAT_I420);
EXPECT_EQ(dimensions.width() % 2, 0);
EXPECT_EQ(dimensions.height() % 2, 0);
size_t size = dimensions.width() * dimensions.height() * 3 / 2;
buffer->handle_provider = std::make_unique<HeapBufferHandleProvider>(size);
return VideoCaptureDevice::Client::ReserveResult::kSucceeded;
}
void OnIncomingCapturedBufferExt(
Buffer buffer,
const VideoCaptureFormat& format,
const gfx::ColorSpace& color_space,
base::TimeTicks reference_time,
base::TimeDelta timestamp,
gfx::Rect visible_rect,
const VideoFrameMetadata& additional_metadata) override {
EXPECT_TRUE(started_);
received_frames_.push_back(ReceivedFrame{std::move(buffer), format,
color_space, reference_time,
timestamp, visible_rect});
if (wait_frame_run_loop_)
wait_frame_run_loop_->Quit();
}
void OnIncomingCapturedData(const uint8_t* data,
int length,
const VideoCaptureFormat& frame_format,
const gfx::ColorSpace& color_space,
int clockwise_rotation,
bool flip_y,
base::TimeTicks reference_time,
base::TimeDelta timestamp,
int frame_feedback_id) override {
NOTREACHED();
}
void OnIncomingCapturedGfxBuffer(gfx::GpuMemoryBuffer* buffer,
const VideoCaptureFormat& frame_format,
int clockwise_rotation,
base::TimeTicks reference_time,
base::TimeDelta timestamp,
int frame_feedback_id) override {
NOTREACHED();
}
void OnIncomingCapturedExternalBuffer(
CapturedExternalVideoBuffer buffer,
std::vector<CapturedExternalVideoBuffer> scaled_buffers,
base::TimeTicks reference_time,
base::TimeDelta timestamp) override {
NOTREACHED();
}
void OnIncomingCapturedBuffer(Buffer buffer,
const VideoCaptureFormat& format,
base::TimeTicks reference_time,
base::TimeDelta timestamp) override {
NOTREACHED();
}
void OnError(VideoCaptureError error,
const base::Location& from_here,
const std::string& reason) override {
NOTREACHED();
}
void OnFrameDropped(VideoCaptureFrameDropReason reason) override {
NOTREACHED();
}
void OnLog(const std::string& message) override { NOTREACHED(); }
double GetBufferPoolUtilization() const override {
NOTREACHED();
return 0;
}
bool started_ = false;
std::vector<ReceivedFrame> received_frames_;
absl::optional<base::RunLoop> wait_frame_run_loop_;
};
} // namespace
class VideoCaptureDeviceFuchsiaTest : public testing::Test {
public:
VideoCaptureDeviceFuchsiaTest() {
test_context_.AddService("fuchsia.sysmem.Allocator");
}
~VideoCaptureDeviceFuchsiaTest() override {
if (device_)
device_->StopAndDeAllocate();
}
std::vector<VideoCaptureDeviceInfo> GetDevicesInfo() {
std::vector<VideoCaptureDeviceInfo> devices_info;
base::RunLoop run_loop;
device_factory_.GetDevicesInfo(base::BindLambdaForTesting(
[&devices_info, &run_loop](std::vector<VideoCaptureDeviceInfo> result) {
devices_info = std::move(result);
run_loop.Quit();
}));
run_loop.Run();
return devices_info;
}
void CreateDevice() {
auto devices_info = GetDevicesInfo();
ASSERT_EQ(devices_info.size(), 1U);
device_ = device_factory_.CreateDevice(devices_info[0].descriptor);
}
FakeCameraStream* GetDefaultCameraStream() {
DCHECK(!fake_device_watcher_.devices().empty());
return fake_device_watcher_.devices().begin()->second->stream();
}
void StartCapturer() {
if (!device_)
CreateDevice();
VideoCaptureParams params;
params.requested_format.frame_size = FakeCameraStream::kDefaultFrameSize;
params.requested_format.frame_rate = 30.0;
params.requested_format.pixel_format = PIXEL_FORMAT_I420;
auto client = std::make_unique<TestVideoCaptureClient>();
client_ = client.get();
device_->AllocateAndStart(params, std::move(client));
EXPECT_TRUE(GetDefaultCameraStream()->WaitBuffersAllocated());
}
void ProduceAndCaptureFrame() {
const uint8_t kFrameSalt = 1;
auto frame_timestamp = base::TimeTicks::Now();
GetDefaultCameraStream()->ProduceFrame(frame_timestamp, kFrameSalt);
client_->WaitFrame();
ASSERT_EQ(client_->received_frames().size(), 1U);
EXPECT_EQ(client_->received_frames()[0].reference_time, frame_timestamp);
ValidateReceivedFrame(client_->received_frames()[0],
FakeCameraStream::kDefaultFrameSize, kFrameSalt);
}
protected:
base::test::SingleThreadTaskEnvironment task_environment_{
base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
base::TestComponentContextForProcess test_context_;
FakeCameraDeviceWatcher fake_device_watcher_{
test_context_.additional_services()};
VideoCaptureDeviceFactoryFuchsia device_factory_;
std::unique_ptr<VideoCaptureDevice> device_;
TestVideoCaptureClient* client_ = nullptr;
};
TEST_F(VideoCaptureDeviceFuchsiaTest, Initialize) {
StartCapturer();
}
TEST_F(VideoCaptureDeviceFuchsiaTest, SendFrame) {
StartCapturer();
ProduceAndCaptureFrame();
}
// Verifies that VideoCaptureDevice can recover from failed Sync() on the sysmem
// buffer collection.
TEST_F(VideoCaptureDeviceFuchsiaTest, FailBufferCollectionSync) {
GetDefaultCameraStream()->SetFirstBufferCollectionFailMode(
FakeCameraStream::SysmemFailMode::kFailSync);
StartCapturer();
ProduceAndCaptureFrame();
}
// Verifies that VideoCaptureDevice can recover from sysmem buffer allocation
// failures..
TEST_F(VideoCaptureDeviceFuchsiaTest, FailBufferCollectionAllocation) {
GetDefaultCameraStream()->SetFirstBufferCollectionFailMode(
FakeCameraStream::SysmemFailMode::kFailAllocation);
StartCapturer();
ProduceAndCaptureFrame();
}
TEST_F(VideoCaptureDeviceFuchsiaTest, MultipleFrames) {
StartCapturer();
FakeCameraStream* stream = GetDefaultCameraStream();
EXPECT_TRUE(stream->WaitBuffersAllocated());
auto start_timestamp = base::TimeTicks::Now();
for (size_t i = 0; i < 10; ++i) {
ASSERT_TRUE(stream->WaitFreeBuffer());
auto frame_timestamp = start_timestamp + base::Milliseconds(i * 16);
stream->ProduceFrame(frame_timestamp, i);
client_->WaitFrame();
ASSERT_EQ(client_->received_frames().size(), i + 1);
EXPECT_EQ(client_->received_frames()[i].reference_time, frame_timestamp);
ValidateReceivedFrame(client_->received_frames()[i],
FakeCameraStream::kDefaultFrameSize, i);
}
}
TEST_F(VideoCaptureDeviceFuchsiaTest, FrameRotation) {
const gfx::Size kResolution(4, 2);
FakeCameraStream* stream = GetDefaultCameraStream();
stream->SetFakeResolution(kResolution);
StartCapturer();
EXPECT_TRUE(stream->WaitBuffersAllocated());
for (int i = static_cast<int>(fuchsia::camera3::Orientation::UP);
i <= static_cast<int>(fuchsia::camera3::Orientation::RIGHT_FLIPPED);
++i) {
SCOPED_TRACE(testing::Message() << "Orientation " << i);
auto orientation = static_cast<fuchsia::camera3::Orientation>(i);
ASSERT_TRUE(stream->WaitFreeBuffer());
stream->SetFakeOrientation(orientation);
stream->ProduceFrame(base::TimeTicks::Now(), i);
client_->WaitFrame();
gfx::Size expected_size = kResolution;
if (orientation == fuchsia::camera3::Orientation::LEFT ||
orientation == fuchsia::camera3::Orientation::LEFT_FLIPPED ||
orientation == fuchsia::camera3::Orientation::RIGHT ||
orientation == fuchsia::camera3::Orientation::RIGHT_FLIPPED) {
expected_size = gfx::Size(expected_size.height(), expected_size.width());
}
ValidateReceivedFrame(client_->received_frames().back(), expected_size, i);
}
}
TEST_F(VideoCaptureDeviceFuchsiaTest, FrameDimensionsNotDivisibleBy2) {
FakeCameraStream* stream = GetDefaultCameraStream();
const gfx::Size kOddResolution(21, 7);
stream->SetFakeResolution(kOddResolution);
StartCapturer();
stream->ProduceFrame(base::TimeTicks::Now(), 1);
client_->WaitFrame();
ASSERT_EQ(client_->received_frames().size(), 1U);
ValidateReceivedFrame(client_->received_frames()[0], kOddResolution, 1);
}
TEST_F(VideoCaptureDeviceFuchsiaTest, MidStreamResolutionChange) {
StartCapturer();
// Capture the first frame at the default resolution.
FakeCameraStream* stream = GetDefaultCameraStream();
stream->ProduceFrame(base::TimeTicks::Now(), 1);
client_->WaitFrame();
ASSERT_TRUE(stream->WaitFreeBuffer());
// Update resolution and produce another frames.
const gfx::Size kUpdatedResolution(3, 14);
stream->SetFakeResolution(kUpdatedResolution);
stream->ProduceFrame(base::TimeTicks::Now(), 1);
client_->WaitFrame();
// Verify that we get captured frames with correct resolution.
ASSERT_EQ(client_->received_frames().size(), 2U);
ValidateReceivedFrame(client_->received_frames()[0],
FakeCameraStream::kDefaultFrameSize, 1);
ValidateReceivedFrame(client_->received_frames()[1], kUpdatedResolution, 1);
}
TEST_F(VideoCaptureDeviceFuchsiaTest,
CreateDeviceAfterDeviceWatcherDisconnect) {
auto devices_info = GetDevicesInfo();
ASSERT_EQ(devices_info.size(), 1U);
// Disconnect DeviceWatcher and run the run loop so |device_factory_| can
// handle the disconnect.
fake_device_watcher_.DisconnectClients();
base::RunLoop().RunUntilIdle();
// The factory is expected to reconnect DeviceWatcher.
device_ = device_factory_.CreateDevice(devices_info[0].descriptor);
StartCapturer();
GetDefaultCameraStream()->ProduceFrame(base::TimeTicks::Now(), 1);
client_->WaitFrame();
ASSERT_EQ(client_->received_frames().size(), 1U);
}
} // namespace media