blob: 3c1311bf5d06870454cb53d2feadbe498bacd019 [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/fuchsia/camera/fake_fuchsia_camera.h"
#include <fuchsia/sysmem/cpp/fidl.h>
#include <lib/sys/cpp/component_context.h>
#include "base/fuchsia/fuchsia_logging.h"
#include "base/fuchsia/process_context.h"
#include "base/memory/platform_shared_memory_region.h"
#include "base/memory/writable_shared_memory_region.h"
#include "base/process/process_handle.h"
#include "base/task/current_thread.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
namespace {
constexpr uint8_t kYPlaneSalt = 1;
constexpr uint8_t kUPlaneSalt = 2;
constexpr uint8_t kVPlaneSalt = 3;
uint8_t GetTestFrameValue(gfx::Size size, int x, int y, uint8_t salt) {
return static_cast<uint8_t>(y + x * size.height() + salt);
}
// Fills one plane of a test frame. |data| points at the location of the pixel
// (0, 0). |orientation| specifies frame orientation transformation that will be
// applied on the receiving end, so this function applies _reverse_ of the
// |orientation| transformation.
void FillPlane(uint8_t* data,
gfx::Size size,
int x_step,
int y_step,
fuchsia::camera3::Orientation orientation,
uint8_t salt) {
// First flip X axis for flipped orientation.
if (orientation == fuchsia::camera3::Orientation::UP_FLIPPED ||
orientation == fuchsia::camera3::Orientation::DOWN_FLIPPED ||
orientation == fuchsia::camera3::Orientation::RIGHT_FLIPPED ||
orientation == fuchsia::camera3::Orientation::LEFT_FLIPPED) {
// Move the origin to the top right corner and flip the X axis.
data += (size.width() - 1) * x_step;
x_step = -x_step;
}
switch (orientation) {
case fuchsia::camera3::Orientation::UP:
case fuchsia::camera3::Orientation::UP_FLIPPED:
break;
case fuchsia::camera3::Orientation::DOWN:
case fuchsia::camera3::Orientation::DOWN_FLIPPED:
// Move |data| to point to the bottom right corner and reverse direction
// of both axes.
data += (size.width() - 1) * x_step + (size.height() - 1) * y_step;
x_step = -x_step;
y_step = -y_step;
break;
case fuchsia::camera3::Orientation::LEFT:
case fuchsia::camera3::Orientation::LEFT_FLIPPED:
// Rotate 90 degrees clockwise by moving |data| to point to the right top
// corner, swapping the axes and reversing direction of the Y axis.
data += (size.width() - 1) * x_step;
size = gfx::Size(size.height(), size.width());
std::swap(x_step, y_step);
y_step = -y_step;
break;
case fuchsia::camera3::Orientation::RIGHT:
case fuchsia::camera3::Orientation::RIGHT_FLIPPED:
// Rotate 90 degrees counter-clockwise by moving |data| to point to the
// bottom left corner, swapping the axes and reversing direction of the X
// axis.
data += (size.height() - 1) * y_step;
size = gfx::Size(size.height(), size.width());
std::swap(x_step, y_step);
x_step = -x_step;
break;
}
for (int y = 0; y < size.height(); ++y) {
for (int x = 0; x < size.width(); ++x) {
data[x * x_step + y * y_step] = GetTestFrameValue(size, x, y, salt);
}
}
}
void ValidatePlane(const uint8_t* data,
gfx::Size size,
size_t x_step,
size_t y_step,
uint8_t salt) {
for (int y = 0; y < size.height(); ++y) {
for (int x = 0; x < size.width(); ++x) {
SCOPED_TRACE(testing::Message() << "x=" << x << " y=" << y);
EXPECT_EQ(data[x * x_step + y * y_step],
GetTestFrameValue(size, x, y, salt));
}
}
}
} // namespace
// static
const gfx::Size FakeCameraStream::kMaxFrameSize = gfx::Size(100, 60);
// static
const gfx::Size FakeCameraStream::kDefaultFrameSize = gfx::Size(60, 40);
// static
void FakeCameraStream::ValidateFrameData(const uint8_t* data,
gfx::Size size,
uint8_t salt) {
const uint8_t* y_plane = data;
{
SCOPED_TRACE("Y plane");
ValidatePlane(y_plane, size, 1, size.width(), salt + kYPlaneSalt);
}
gfx::Size uv_size(size.width() / 2, size.height() / 2);
const uint8_t* u_plane = y_plane + size.width() * size.height();
{
SCOPED_TRACE("U plane");
ValidatePlane(u_plane, uv_size, 1, uv_size.width(), salt + kUPlaneSalt);
}
const uint8_t* v_plane = u_plane + uv_size.width() * uv_size.height();
{
SCOPED_TRACE("V plane");
ValidatePlane(v_plane, uv_size, 1, uv_size.width(), salt + kVPlaneSalt);
}
}
struct FakeCameraStream::Buffer {
explicit Buffer(base::WritableSharedMemoryMapping mapping)
: mapping(std::move(mapping)),
release_fence_watch_controller(FROM_HERE) {}
base::WritableSharedMemoryMapping mapping;
// Frame is used by the client when the |release_fence| is not null.
zx::eventpair release_fence;
base::MessagePumpForIO::ZxHandleWatchController
release_fence_watch_controller;
};
FakeCameraStream::FakeCameraStream()
: binding_(this),
sysmem_allocator_(base::ComponentContextForProcess()
->svc()
->Connect<fuchsia::sysmem::Allocator>()) {
sysmem_allocator_->SetDebugClientInfo("ChromiumFakeCameraStream",
base::GetCurrentProcId());
}
FakeCameraStream::~FakeCameraStream() = default;
void FakeCameraStream::Bind(
fidl::InterfaceRequest<fuchsia::camera3::Stream> request) {
binding_.Bind(std::move(request));
}
void FakeCameraStream::SetFirstBufferCollectionFailMode(
SysmemFailMode fail_mode) {
first_buffer_collection_fail_mode_ = fail_mode;
}
void FakeCameraStream::SetFakeResolution(gfx::Size resolution) {
resolution_ = resolution;
resolution_update_ =
fuchsia::math::Size{resolution_.width(), resolution_.height()};
SendResolution();
}
void FakeCameraStream::SetFakeOrientation(
fuchsia::camera3::Orientation orientation) {
orientation_ = orientation;
orientation_update_ = orientation;
SendOrientation();
}
bool FakeCameraStream::WaitBuffersAllocated() {
EXPECT_FALSE(wait_buffers_allocated_run_loop_);
if (!buffers_.empty())
return true;
wait_buffers_allocated_run_loop_.emplace();
wait_buffers_allocated_run_loop_->Run();
wait_buffers_allocated_run_loop_.reset();
return !buffers_.empty();
}
bool FakeCameraStream::WaitFreeBuffer() {
EXPECT_FALSE(wait_free_buffer_run_loop_);
if (num_used_buffers_ < buffers_.size())
return true;
wait_free_buffer_run_loop_.emplace();
wait_free_buffer_run_loop_->Run();
wait_free_buffer_run_loop_.reset();
return num_used_buffers_ < buffers_.size();
}
void FakeCameraStream::ProduceFrame(base::TimeTicks timestamp, uint8_t salt) {
ASSERT_LT(num_used_buffers_, buffers_.size());
ASSERT_FALSE(next_frame_);
size_t index = buffers_.size();
for (size_t i = 0; i < buffers_.size(); ++i) {
if (!buffers_[i]->release_fence) {
index = i;
break;
}
}
EXPECT_LT(index, buffers_.size());
auto* buffer = buffers_[index].get();
gfx::Size coded_size((resolution_.width() + 1) & ~1,
(resolution_.height() + 1) & ~1);
// Fill Y plane.
uint8_t* y_plane = reinterpret_cast<uint8_t*>(buffer->mapping.memory());
size_t stride = kMaxFrameSize.width();
FillPlane(y_plane, coded_size, /*x_step=*/1, /*y_step=*/stride, orientation_,
salt + kYPlaneSalt);
// Fill UV plane.
gfx::Size uv_size(coded_size.width() / 2, coded_size.height() / 2);
uint8_t* uv_plane = y_plane + kMaxFrameSize.width() * kMaxFrameSize.height();
FillPlane(uv_plane, uv_size, /*x_step=*/2, /*y_step=*/stride, orientation_,
salt + kUPlaneSalt);
FillPlane(uv_plane + 1, uv_size, /*x_step=*/2, /*y_step=*/stride,
orientation_, salt + kVPlaneSalt);
// Create FrameInfo.
fuchsia::camera3::FrameInfo frame;
frame.frame_counter = frame_counter_++;
frame.buffer_index = 0;
frame.timestamp = timestamp.ToZxTime();
EXPECT_EQ(
zx::eventpair::create(0u, &frame.release_fence, &buffer->release_fence),
ZX_OK);
// Watch release fence to get notified when the frame is released.
base::CurrentIOThread::Get()->WatchZxHandle(
buffer->release_fence.get(), /*persistent=*/false,
ZX_EVENTPAIR_PEER_CLOSED, &buffer->release_fence_watch_controller, this);
num_used_buffers_++;
next_frame_ = std::move(frame);
SendNextFrame();
}
void FakeCameraStream::WatchResolution(WatchResolutionCallback callback) {
EXPECT_FALSE(watch_resolution_callback_);
watch_resolution_callback_ = std::move(callback);
SendResolution();
}
void FakeCameraStream::WatchOrientation(WatchOrientationCallback callback) {
EXPECT_FALSE(watch_orientation_callback_);
watch_orientation_callback_ = std::move(callback);
SendOrientation();
}
void FakeCameraStream::SetBufferCollection(
fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken>
token_handle) {
EXPECT_TRUE(token_handle);
// Drop old buffers.
buffers_.clear();
if (buffer_collection_) {
buffer_collection_->Close();
buffer_collection_.Unbind();
}
new_buffer_collection_token_.Bind(std::move(token_handle));
new_buffer_collection_token_.set_error_handler(
fit::bind_member(this, &FakeCameraStream::OnBufferCollectionError));
// Duplicate the token to access from the stream.
fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken>
token_for_client;
new_buffer_collection_token_->Duplicate(ZX_RIGHT_SAME_RIGHTS,
token_for_client.NewRequest());
fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> failed_token;
if (first_buffer_collection_fail_mode_ == SysmemFailMode::kFailSync) {
// Create an additional token that's dropped in OnBufferCollectionSyncDone()
// before buffers are allocated. This will cause sysmem to fail the
// collection, so the future attempt to Sync() the collection from the
// production code will fail as well.
new_buffer_collection_token_->Duplicate(
/*rights_attenuation_mask=*/0, failed_token.NewRequest());
}
new_buffer_collection_token_->Sync(
[this, token_for_client = std::move(token_for_client),
failed_token = std::move(failed_token)]() mutable {
OnBufferCollectionSyncDone(std::move(token_for_client),
std::move(failed_token));
});
}
void FakeCameraStream::WatchBufferCollection(
WatchBufferCollectionCallback callback) {
EXPECT_FALSE(watch_buffer_collection_callback_);
watch_buffer_collection_callback_ = std::move(callback);
SendBufferCollection();
}
void FakeCameraStream::GetNextFrame(GetNextFrameCallback callback) {
EXPECT_FALSE(get_next_frame_callback_);
get_next_frame_callback_ = std::move(callback);
SendNextFrame();
}
void FakeCameraStream::NotImplemented_(const std::string& name) {
ADD_FAILURE() << "NotImplemented_: " << name;
}
void FakeCameraStream::OnBufferCollectionSyncDone(
fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken>
token_for_client,
fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken>
failed_token) {
// Return the token back to the client.
new_buffer_collection_token_for_client_ = std::move(token_for_client);
SendBufferCollection();
// Initialize the new collection using |local_token|.
sysmem_allocator_->BindSharedCollection(
std::move(new_buffer_collection_token_), buffer_collection_.NewRequest());
buffer_collection_.set_error_handler(
fit::bind_member(this, &FakeCameraStream::OnBufferCollectionError));
fuchsia::sysmem::BufferCollectionConstraints constraints;
constraints.usage.cpu =
fuchsia::sysmem::cpuUsageRead | fuchsia::sysmem::cpuUsageWrite;
// The client is expected to request buffers it may need. We don't need to
// reserve any for the server side.
constraints.min_buffer_count_for_camping = 0;
// Initialize image format.
constraints.image_format_constraints_count = 1;
constraints.image_format_constraints[0].pixel_format.type =
fuchsia::sysmem::PixelFormatType::NV12;
constraints.image_format_constraints[0].color_spaces_count = 1;
constraints.image_format_constraints[0].color_space[0].type =
fuchsia::sysmem::ColorSpaceType::REC601_NTSC;
constraints.image_format_constraints[0].required_max_coded_width =
kMaxFrameSize.width();
constraints.image_format_constraints[0].required_max_coded_height =
kMaxFrameSize.height();
if (first_buffer_collection_fail_mode_ == SysmemFailMode::kFailAllocation) {
// Set color space to SRGB to trigger sysmem collection failure (SRGB is not
// compatible with NV12 pixel type).
constraints.image_format_constraints[0].color_space[0].type =
fuchsia::sysmem::ColorSpaceType::SRGB;
}
buffer_collection_->SetConstraints(/*has_constraints=*/true,
std::move(constraints));
buffer_collection_->WaitForBuffersAllocated(
fit::bind_member(this, &FakeCameraStream::OnBufferCollectionAllocated));
}
void FakeCameraStream::OnBufferCollectionError(zx_status_t status) {
if (first_buffer_collection_fail_mode_ != SysmemFailMode::kNone) {
first_buffer_collection_fail_mode_ = SysmemFailMode::kNone;
// Create a new buffer collection to retry buffer allocation.
fuchsia::sysmem::BufferCollectionTokenPtr token;
sysmem_allocator_->AllocateSharedCollection(token.NewRequest());
SetBufferCollection(std::move(token));
return;
}
ADD_FAILURE() << "BufferCollection failed.";
if (wait_buffers_allocated_run_loop_)
wait_buffers_allocated_run_loop_->Quit();
if (wait_free_buffer_run_loop_)
wait_free_buffer_run_loop_->Quit();
}
void FakeCameraStream::OnBufferCollectionAllocated(
zx_status_t status,
fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info) {
if (status != ZX_OK) {
OnBufferCollectionError(status);
return;
}
EXPECT_TRUE(buffers_.empty());
EXPECT_TRUE(buffer_collection_info.settings.has_image_format_constraints);
EXPECT_EQ(buffer_collection_info.settings.image_format_constraints
.pixel_format.type,
fuchsia::sysmem::PixelFormatType::NV12);
size_t buffer_size =
buffer_collection_info.settings.buffer_settings.size_bytes;
for (size_t i = 0; i < buffer_collection_info.buffer_count; ++i) {
auto& buffer = buffer_collection_info.buffers[i];
EXPECT_EQ(buffer.vmo_usable_start, 0U);
auto region = base::WritableSharedMemoryRegion::Deserialize(
base::subtle::PlatformSharedMemoryRegion::Take(
std::move(buffer.vmo),
base::subtle::PlatformSharedMemoryRegion::Mode::kWritable,
buffer_size, base::UnguessableToken::Create()));
auto mapping = region.Map();
EXPECT_TRUE(mapping.IsValid());
buffers_.push_back(std::make_unique<Buffer>(std::move(mapping)));
}
if (wait_buffers_allocated_run_loop_)
wait_buffers_allocated_run_loop_->Quit();
}
void FakeCameraStream::SendResolution() {
if (!watch_resolution_callback_ || !resolution_update_)
return;
watch_resolution_callback_(resolution_update_.value());
watch_resolution_callback_ = {};
resolution_update_.reset();
}
void FakeCameraStream::SendOrientation() {
if (!watch_orientation_callback_ || !orientation_update_)
return;
watch_orientation_callback_(orientation_update_.value());
watch_orientation_callback_ = {};
orientation_update_.reset();
}
void FakeCameraStream::SendBufferCollection() {
if (!watch_buffer_collection_callback_ ||
!new_buffer_collection_token_for_client_) {
return;
}
watch_buffer_collection_callback_(
std::move(new_buffer_collection_token_for_client_.value()));
watch_buffer_collection_callback_ = {};
new_buffer_collection_token_for_client_.reset();
}
void FakeCameraStream::SendNextFrame() {
if (!get_next_frame_callback_ || !next_frame_)
return;
get_next_frame_callback_(std::move(next_frame_.value()));
get_next_frame_callback_ = {};
next_frame_.reset();
}
void FakeCameraStream::OnZxHandleSignalled(zx_handle_t handle,
zx_signals_t signals) {
EXPECT_EQ(signals, ZX_EVENTPAIR_PEER_CLOSED);
// Find the buffer that corresponds to the |handle|.
size_t index = buffers_.size();
for (size_t i = 0; i < buffers_.size(); ++i) {
if (buffers_[i]->release_fence.get() == handle) {
index = i;
break;
}
}
ASSERT_LT(index, buffers_.size());
buffers_[index]->release_fence = {};
buffers_[index]->release_fence_watch_controller.StopWatchingZxHandle();
num_used_buffers_--;
if (wait_free_buffer_run_loop_)
wait_free_buffer_run_loop_->Quit();
}
FakeCameraDevice::FakeCameraDevice() = default;
FakeCameraDevice::~FakeCameraDevice() = default;
void FakeCameraDevice::Bind(
fidl::InterfaceRequest<fuchsia::camera3::Device> request) {
bindings_.AddBinding(this, std::move(request));
}
void FakeCameraDevice::SetGetIdentifierHandler(
base::RepeatingCallback<void(GetIdentifierCallback)>
get_identifier_handler) {
get_identifier_handler_ = std::move(get_identifier_handler);
}
void FakeCameraDevice::GetIdentifier(GetIdentifierCallback callback) {
if (get_identifier_handler_) {
get_identifier_handler_.Run(std::move(callback));
return;
}
callback("Fake Camera");
}
void FakeCameraDevice::GetConfigurations(GetConfigurationsCallback callback) {
std::vector<fuchsia::camera3::Configuration> configurations(1);
configurations[0].streams.resize(1);
configurations[0].streams[0].frame_rate.numerator = 30;
configurations[0].streams[0].frame_rate.denominator = 1;
configurations[0].streams[0].image_format.pixel_format.type =
fuchsia::sysmem::PixelFormatType::NV12;
configurations[0].streams[0].image_format.coded_width = 640;
configurations[0].streams[0].image_format.coded_height = 480;
configurations[0].streams[0].image_format.bytes_per_row = 640;
callback(std::move(configurations));
}
void FakeCameraDevice::ConnectToStream(
uint32_t index,
fidl::InterfaceRequest<fuchsia::camera3::Stream> request) {
EXPECT_EQ(index, 0U);
stream_.Bind(std::move(request));
}
void FakeCameraDevice::NotImplemented_(const std::string& name) {
ADD_FAILURE() << "NotImplemented_: " << name;
}
FakeCameraDeviceWatcher::FakeCameraDeviceWatcher(
sys::OutgoingDirectory* outgoing_directory) {
zx_status_t status =
outgoing_directory->AddPublicService<fuchsia::camera3::DeviceWatcher>(
[this](
fidl::InterfaceRequest<fuchsia::camera3::DeviceWatcher> request) {
auto client = std::make_unique<Client>(this);
// Queue events for all existing devices.
for (auto& device : devices_) {
fuchsia::camera3::WatchDevicesEvent event;
event.set_added(device.first);
client->QueueEvent(std::move(event));
}
bindings_.AddBinding(std::move(client), std::move(request));
});
ZX_CHECK(status == ZX_OK, status) << "AddPublicService failed";
devices_.insert(
std::make_pair(next_device_id_++, std::make_unique<FakeCameraDevice>()));
}
FakeCameraDeviceWatcher::~FakeCameraDeviceWatcher() = default;
void FakeCameraDeviceWatcher::DisconnectClients() {
bindings_.CloseAll();
}
std::unique_ptr<FakeCameraDevice> FakeCameraDeviceWatcher::RemoveDevice(
uint64_t device_id) {
auto device_it = devices_.find(device_id);
DCHECK(device_it != devices_.end());
// Queue an event for each client to inform about the device removal.
for (auto& binding : bindings_.bindings()) {
fuchsia::camera3::WatchDevicesEvent event;
event.set_removed(device_id);
binding->impl()->QueueEvent(std::move(event));
}
std::unique_ptr<FakeCameraDevice> device = std::move(device_it->second);
devices_.erase(device_it);
return device;
}
FakeCameraDeviceWatcher::Client::Client(FakeCameraDeviceWatcher* device_watcher)
: device_watcher_(device_watcher) {}
FakeCameraDeviceWatcher::Client::~Client() {}
void FakeCameraDeviceWatcher::Client::QueueEvent(
fuchsia::camera3::WatchDevicesEvent event) {
event_queue_.push_back(std::move(event));
if (watch_devices_callback_) {
watch_devices_callback_(std::move(event_queue_));
event_queue_.clear();
watch_devices_callback_ = {};
}
}
void FakeCameraDeviceWatcher::Client::WatchDevices(
WatchDevicesCallback callback) {
DCHECK(!watch_devices_callback_);
if (initial_list_sent_ && event_queue_.empty()) {
watch_devices_callback_ = std::move(callback);
return;
}
callback(std::move(event_queue_));
event_queue_.clear();
initial_list_sent_ = true;
}
void FakeCameraDeviceWatcher::Client::ConnectToDevice(
uint64_t id,
fidl::InterfaceRequest<fuchsia::camera3::Device> request) {
auto it = device_watcher_->devices().find(id);
if (it == device_watcher_->devices().end())
return;
it->second->Bind(std::move(request));
}
void FakeCameraDeviceWatcher::Client::NotImplemented_(const std::string& name) {
ADD_FAILURE() << "NotImplemented_: " << name;
}
} // namespace media