blob: ade22dbc3e184f0778f80928d6ef1e9521e90a94 [file] [log] [blame]
// Copyright 2020 The Fuchsia 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 "src/camera/bin/device/stream_impl.h"
#include <lib/async/cpp/task.h>
#include <lib/fit/function.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/trace/event.h>
#include <lib/zx/time.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include "src/camera/bin/device/messages.h"
#include "src/camera/bin/device/util.h"
#include "src/lib/fsl/handles/object_info.h"
namespace camera {
namespace {
static fuchsia::math::Size ConvertToSize(fuchsia::sysmem::ImageFormat_2 format) {
ZX_DEBUG_ASSERT(format.coded_width < std::numeric_limits<int32_t>::max());
ZX_DEBUG_ASSERT(format.coded_height < std::numeric_limits<int32_t>::max());
return {.width = static_cast<int32_t>(format.coded_width),
.height = static_cast<int32_t>(format.coded_height)};
}
} // namespace
StreamImpl::StreamImpl(async_dispatcher_t* dispatcher, MetricsReporter::Stream& metrics,
const fuchsia::camera3::StreamProperties2& properties,
const fuchsia::camera2::hal::StreamConfig& legacy_config,
fidl::InterfaceRequest<fuchsia::camera3::Stream> request,
StreamRequestedCallback on_stream_requested, fit::closure on_no_clients)
: dispatcher_(dispatcher),
metrics_(metrics),
properties_(properties),
legacy_config_(legacy_config),
on_stream_requested_(std::move(on_stream_requested)),
on_no_clients_(std::move(on_no_clients)) {
legacy_stream_.set_error_handler(fit::bind_member(this, &StreamImpl::OnLegacyStreamDisconnected));
legacy_stream_.events().OnFrameAvailable = fit::bind_member(this, &StreamImpl::OnFrameAvailable);
current_resolution_ = ConvertToSize(properties.image_format());
OnNewRequest(std::move(request));
}
StreamImpl::~StreamImpl() = default;
void StreamImpl::CloseAllClients(zx_status_t status) {
while (clients_.size() > 1) {
auto& [id, client] = *clients_.begin();
client->CloseConnection(status);
}
if (clients_.size() == 1) {
// After last client has been removed, on_no_clients_ will run and potentially delete 'this' so
// handle last client on it's own and don't touch 'this' after.
clients_.begin()->second->CloseConnection(status);
}
}
void StreamImpl::SetMuteState(MuteState mute_state) {
TRACE_DURATION("camera", "StreamImpl::SetMuteState");
mute_state_ = mute_state;
// On either transition, invalidate existing frames.
for (auto& [id, client] : clients_) {
client->ClearFrames();
}
}
fit::scope& StreamImpl::Scope() { return scope_; }
void StreamImpl::OnNewRequest(fidl::InterfaceRequest<fuchsia::camera3::Stream> request) {
TRACE_DURATION("camera", "StreamImpl::OnNewRequest");
auto client = std::make_unique<Client>(*this, client_id_next_, std::move(request));
client->ReceiveResolution(current_resolution_);
client->ReceiveCropRegion(nullptr);
clients_.emplace(client_id_next_++, std::move(client));
}
void StreamImpl::OnLegacyStreamDisconnected(zx_status_t status) {
FX_PLOGS(ERROR, status) << "Legacy Stream disconnected unexpectedly.";
clients_.clear();
on_no_clients_();
}
void StreamImpl::RemoveClient(uint64_t id) {
TRACE_DURATION("camera", "StreamImpl::RemoveClient");
clients_.erase(id);
if (clients_.empty()) {
on_no_clients_();
}
}
void StreamImpl::OnFrameAvailable(fuchsia::camera2::FrameAvailableInfo info) {
TRACE_DURATION("camera", "StreamImpl::OnFrameAvailable");
if (info.metadata.has_timestamp()) {
TRACE_FLOW_END("camera", "camera_stream_on_frame_available", info.metadata.timestamp());
}
metrics_.FrameReceived();
if (info.frame_status != fuchsia::camera2::FrameStatus::OK) {
FX_LOGS(WARNING) << "Driver reported a bad frame. This will not be reported to clients.";
legacy_stream_->AcknowledgeFrameError();
return;
}
if (frame_waiters_.find(info.buffer_id) != frame_waiters_.end()) {
FX_LOGS(WARNING) << "Driver sent a frame that was already in use (ID = " << info.buffer_id
<< "). This frame will not be sent to clients.";
legacy_stream_->ReleaseFrame(info.buffer_id);
return;
}
if (!info.metadata.has_timestamp()) {
FX_LOGS(WARNING)
<< "Driver sent a frame without a timestamp. This frame will not be sent to clients.";
legacy_stream_->ReleaseFrame(info.buffer_id);
return;
}
uint64_t capture_timestamp = 0;
if (info.metadata.has_capture_timestamp()) {
capture_timestamp = info.metadata.capture_timestamp();
} else {
FX_LOGS(DEBUG) << "Driver sent a frame without a capture timestamp.";
}
// Discard any spurious frames received while muted.
if (mute_state_.muted()) {
legacy_stream_->ReleaseFrame(info.buffer_id);
return;
}
// The frame is valid and camera is unmuted, so increment the frame counter.
++frame_counter_;
// Discard the frame if there are too many frames outstanding.
// TODO(fxbug.dev/64801): Recycle LRU frames.
if (frame_waiters_.size() == max_camping_buffers_) {
metrics_.FrameDropped();
legacy_stream_->ReleaseFrame(info.buffer_id);
return;
}
// Construct the frame info and create a release fence per client.
std::vector<zx::eventpair> fences;
for (auto& [id, client] : clients_) {
if (!client->Participant()) {
continue;
}
zx::eventpair fence;
zx::eventpair release_fence;
ZX_ASSERT(zx::eventpair::create(0u, &fence, &release_fence) == ZX_OK);
fences.push_back(std::move(fence));
fuchsia::camera3::FrameInfo2 frame;
frame.set_buffer_index(info.buffer_id);
frame.set_frame_counter(frame_counter_);
frame.set_timestamp(info.metadata.timestamp());
frame.set_capture_timestamp(capture_timestamp);
frame.set_release_fence(std::move(release_fence));
client->AddFrame(std::move(frame));
}
// No participating clients exist. Release the frame immediately.
if (fences.empty()) {
legacy_stream_->ReleaseFrame(info.buffer_id);
return;
}
// Queue a waiter so that when the client end of the fence is released, the frame is released back
// to the driver.
ZX_ASSERT(frame_waiters_.size() <= max_camping_buffers_);
frame_waiters_[info.buffer_id] =
std::make_unique<FrameWaiter>(dispatcher_, std::move(fences), [this, index = info.buffer_id] {
legacy_stream_->ReleaseFrame(index);
frame_waiters_.erase(index);
});
}
void StreamImpl::SetBufferCollection(
uint64_t id, fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> token_handle) {
TRACE_DURATION("camera", "StreamImpl::SetBufferCollection");
auto it = clients_.find(id);
if (it == clients_.end()) {
FX_LOGS(ERROR) << "Client " << id << " not found.";
token_handle.BindSync()->Close();
ZX_DEBUG_ASSERT(false);
return;
}
auto& client = it->second;
// If null, just unregister the client and return.
if (!token_handle) {
client->Participant() = false;
return;
}
client->SetInitialToken(std::move(token_handle));
// Sync token before marking as a Participant to avoid introducing non-responsive tokens to
// the participant list.
client->InitialToken()->Sync([this, &client]() {
client->Participant() = true;
// Duplicate and send each client a token.
std::map<uint64_t, fuchsia::sysmem::BufferCollectionTokenHandle> client_tokens;
for (auto& client_i : clients_) {
if (client_i.second->Participant()) {
client->InitialToken()->Duplicate(ZX_RIGHT_SAME_RIGHTS,
client_tokens[client_i.first].NewRequest());
}
}
client->InitialToken()->Sync(
[this, &client, client_tokens = std::move(client_tokens)]() mutable {
for (auto& [id, token] : client_tokens) {
auto it = clients_.find(id);
if (it == clients_.end()) {
token.BindSync()->Close();
} else {
it->second->ReceiveBufferCollection(std::move(token));
}
}
// Send the last token to the device for constraints application.
frame_waiters_.clear();
on_stream_requested_(
client->TakeInitialToken(), legacy_stream_.NewRequest(),
[this](uint32_t max_camping_buffers) { max_camping_buffers_ = max_camping_buffers; },
legacy_stream_format_index_);
RestoreLegacyStreamState();
legacy_stream_->Start();
});
});
}
void StreamImpl::SetResolution(uint64_t id, fuchsia::math::Size coded_size) {
TRACE_DURATION("camera", "StreamImpl::SetResolution");
auto it = clients_.find(id);
if (it == clients_.end()) {
FX_LOGS(ERROR) << "Client " << id << " not found.";
ZX_DEBUG_ASSERT(false);
return;
}
auto& client = it->second;
// Begin with the full resolution.
auto best_size = ConvertToSize(properties_.image_format());
if (coded_size.width > best_size.width || coded_size.height > best_size.height) {
client->CloseConnection(ZX_ERR_INVALID_ARGS);
return;
}
// Examine all supported resolutions, preferring those that cover the requested resolution but
// have fewer pixels, breaking ties by picking the one with a smaller width.
uint32_t best_index = 0;
for (uint32_t i = 0; i < legacy_config_.image_formats.size(); ++i) {
auto size = ConvertToSize(legacy_config_.image_formats[i]);
bool contains_request = size.width >= coded_size.width && size.height >= coded_size.height;
bool smaller_size = size.width * size.height < best_size.width * best_size.height;
bool equal_size = size.width * size.height == best_size.width * best_size.height;
bool smaller_width = size.width < best_size.width;
if (contains_request && (smaller_size || (equal_size && smaller_width))) {
best_size = size;
best_index = i;
}
}
// Save the selected image format, and set it on the stream if bound.
legacy_stream_format_index_ = best_index;
if (legacy_stream_) {
legacy_stream_->SetImageFormat(legacy_stream_format_index_, [this](zx_status_t status) {
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Unexpected response from driver.";
while (!clients_.empty()) {
auto it = clients_.begin();
it->second->CloseConnection(ZX_ERR_INTERNAL);
clients_.erase(it);
}
on_no_clients_();
return;
}
});
}
current_resolution_ = best_size;
// Inform clients of the resolution change.
for (auto& [id, client] : clients_) {
client->ReceiveResolution(best_size);
}
}
void StreamImpl::SetCropRegion(uint64_t id, std::unique_ptr<fuchsia::math::RectF> region) {
TRACE_DURATION("camera", "StreamImpl::SetCropRegion");
if (legacy_stream_) {
float x_min = 0.0f;
float y_min = 0.0f;
float x_max = 1.0f;
float y_max = 1.0f;
if (region) {
x_min = region->x;
y_min = region->y;
x_max = x_min + region->width;
y_max = y_min + region->height;
}
legacy_stream_->SetRegionOfInterest(x_min, y_min, x_max, y_max, [](zx_status_t status) {
// TODO(fxbug.dev/50908): Make this an error once RegionOfInterest support is known at
// init time. FX_PLOGS(WARNING, status) << "Stream does not support crop region.";
});
}
current_crop_region_ = std::move(region);
// Inform clients of the resolution change.
for (auto& [id, client] : clients_) {
std::unique_ptr<fuchsia::math::RectF> region;
if (current_crop_region_) {
region = std::make_unique<fuchsia::math::RectF>(*current_crop_region_);
}
client->ReceiveCropRegion(std::move(region));
}
}
void StreamImpl::RestoreLegacyStreamState() {
TRACE_DURATION("camera", "StreamImpl::RestoreLegacyStreamState");
// Note that image format does not need restoration as it is passed to the driver during creation.
if (current_crop_region_) {
legacy_stream_->SetRegionOfInterest(current_crop_region_->x, current_crop_region_->y,
current_crop_region_->x + current_crop_region_->width,
current_crop_region_->y + current_crop_region_->height,
[](zx_status_t) {});
}
}
} // namespace camera