blob: d58e7c6f30d7309e116b7e73eacd4450c0c87833 [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/camera-gym/stream_cycler.h"
#include <lib/async-loop/default.h>
#include <lib/async/cpp/task.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/trace/event.h>
#include <zircon/types.h>
#include <sstream>
#include "src/camera/bin/camera-gym/moving_window.h"
#include "src/lib/fsl/handles/object_info.h"
namespace camera {
constexpr zx::duration kDemoTime = zx::msec(CONFIGURATION_CYCLE_TIME_MS);
// Ratio controls how often ROI is moved.
// (1 = every frame, 2 = every other frame, etc)
constexpr uint32_t kRegionOfInterestFramesPerMoveRatio = 1;
// Sets the error handler on the provided interface to log an error and abort the process.
template <class T>
static void SetAbortOnError(fidl::InterfacePtr<T>& p, std::string message) {
p.set_error_handler([message](zx_status_t status) {
// FATAL severity causes abort to be called.
FX_PLOGS(FATAL, status) << message;
});
}
StreamCycler::StreamCycler(async_dispatcher_t* dispatcher, bool manual_mode)
: dispatcher_(dispatcher), manual_mode_(manual_mode) {
SetAbortOnError(watcher_, "fuchsia.camera3.DeviceWatcher disconnected.");
SetAbortOnError(allocator_, "fuchsia.sysmem.Allocator disconnected.");
SetAbortOnError(device_, "fuchsia.camera3.Device disconnected.");
}
StreamCycler::~StreamCycler() = default;
fit::result<std::unique_ptr<StreamCycler>, zx_status_t> StreamCycler::Create(
fuchsia::camera3::DeviceWatcherHandle watcher, fuchsia::sysmem::AllocatorHandle allocator,
async_dispatcher_t* dispatcher, bool manual_mode) {
auto cycler = std::unique_ptr<StreamCycler>(new StreamCycler(dispatcher, manual_mode));
zx_status_t status = cycler->watcher_.Bind(std::move(watcher), dispatcher);
if (status != ZX_OK) {
FX_PLOGS(ERROR, status);
return fit::error(status);
}
status = cycler->allocator_.Bind(std::move(allocator), dispatcher);
if (status != ZX_OK) {
FX_PLOGS(ERROR, status);
return fit::error(status);
}
cycler->watcher_->WatchDevices(
fit::bind_member(cycler.get(), &StreamCycler::WatchDevicesCallback));
return fit::ok(std::move(cycler));
}
void StreamCycler::SetHandlers(StreamCycler::AddCollectionHandler on_add_collection,
StreamCycler::RemoveCollectionHandler on_remove_collection,
StreamCycler::ShowBufferHandler on_show_buffer,
StreamCycler::MuteStateHandler on_mute_changed) {
add_collection_handler_ = std::move(on_add_collection);
remove_collection_handler_ = std::move(on_remove_collection);
show_buffer_handler_ = std::move(on_show_buffer);
mute_state_handler_ = std::move(on_mute_changed);
}
void StreamCycler::WatchDevicesCallback(std::vector<fuchsia::camera3::WatchDevicesEvent> events) {
for (auto& event : events) {
if (event.is_added()) {
// Connect to device.
// TODO(fxbug.dev/48506) Properly detect expected device id.
watcher_->ConnectToDevice(event.added(), device_.NewRequest(dispatcher_));
// Watch for mute changes.
device_->WatchMuteState(fit::bind_member(this, &StreamCycler::WatchMuteStateHandler));
// Fetch camera configurations
device_->GetConfigurations(
[this](std::vector<fuchsia::camera3::Configuration> configurations) {
configurations_ = std::move(configurations);
// Once we have the known camera configurations, default to the first configuration
// index. This is automatically chosen in the driver, so we do not need to ask for it.
// The callback for WatchCurrentConfiguration() will connect to all streams.
device_->WatchCurrentConfiguration(
fit::bind_member(this, &StreamCycler::WatchCurrentConfigurationCallback));
});
}
}
// Hanging get.
watcher_->WatchDevices(fit::bind_member(this, &StreamCycler::WatchDevicesCallback));
}
void StreamCycler::WatchMuteStateHandler(bool software_muted, bool hardware_muted) {
mute_state_handler_(software_muted | hardware_muted);
device_->WatchMuteState(fit::bind_member(this, &StreamCycler::WatchMuteStateHandler));
}
void StreamCycler::ForceNextStreamConfiguration() {
uint32_t config_index = NextConfigIndex();
ZX_ASSERT(configurations_.size() > config_index);
ZX_ASSERT(!configurations_[config_index].streams.empty());
device_->SetCurrentConfiguration(config_index);
}
void StreamCycler::WatchCurrentConfigurationCallback(uint32_t config_index) {
// Remember the current device config_index.
current_config_index_ = config_index;
// Start connecting to all streams.
ConnectToAllStreams();
// After a specified demo period, set the next stream configuration, which will end up cutting off
// all existing streams.
async::PostDelayedTask(
dispatcher_, [this]() { ForceNextStreamConfiguration(); }, kDemoTime);
// Be ready for configuration changes.
device_->WatchCurrentConfiguration(
fit::bind_member(this, &StreamCycler::WatchCurrentConfigurationCallback));
}
void StreamCycler::ConnectToAllStreams() {
// Connect all streams in descending order to put more stress on the controller.
ConnectToStream(current_config_index_, configurations_[current_config_index_].streams.size() - 1);
}
void StreamCycler::ConnectToStream(uint32_t config_index, uint32_t stream_index) {
ZX_ASSERT(configurations_.size() > config_index);
ZX_ASSERT(configurations_[config_index].streams.size() > stream_index);
auto image_format = configurations_[config_index].streams[stream_index].image_format;
// Connect to specific stream
StreamInfo new_stream_info;
stream_infos_.emplace(stream_index, std::move(new_stream_info));
auto& stream = stream_infos_[stream_index].stream;
auto stream_request = stream.NewRequest(dispatcher_);
if (config_index == 1 || config_index == 2) {
stream_infos_[stream_index].source_highlight = 0;
}
// Allocate buffer collection
fuchsia::sysmem::BufferCollectionTokenPtr token_orig;
allocator_->AllocateSharedCollection(token_orig.NewRequest());
token_orig->Sync([this, image_format, config_index, stream_index, &stream,
token_orig = std::move(token_orig)]() mutable {
stream->SetBufferCollection(std::move(token_orig));
stream->WatchBufferCollection([this, image_format, config_index, stream_index, &stream](
fuchsia::sysmem::BufferCollectionTokenHandle token_back) {
ZX_ASSERT(image_format.coded_width > 0); // image_format must be reasonable.
ZX_ASSERT(image_format.coded_height > 0);
if (add_collection_handler_) {
auto& stream_info = stream_infos_[stream_index];
std::ostringstream oss;
oss << "c" << config_index << "s" << stream_index << ".data";
stream_info.add_collection_handler_returned_value =
add_collection_handler_(std::move(token_back), image_format, oss.str());
} else {
token_back.BindSync()->Close();
}
if (stream_index > 0) {
ConnectToStream(config_index, stream_index - 1);
}
// Kick start the stream
stream->GetNextFrame([this, stream_index](fuchsia::camera3::FrameInfo frame_info) {
OnNextFrame(stream_index, std::move(frame_info));
});
});
});
device_->ConnectToStream(stream_index, std::move(stream_request));
stream.set_error_handler(
[this, stream_index](zx_status_t status) { DisconnectStream(stream_index); });
}
void StreamCycler::OnNextFrame(uint32_t stream_index, fuchsia::camera3::FrameInfo frame_info) {
TRACE_DURATION("camera", "StreamCycler::OnNextFrame");
TRACE_FLOW_END("camera", "camera3::Stream::GetNextFrame",
fsl::GetKoid(frame_info.release_fence.get()));
auto& stream_info = stream_infos_[stream_index];
if (show_buffer_handler_ && stream_info.add_collection_handler_returned_value) {
show_buffer_handler_(stream_info.add_collection_handler_returned_value.value(),
frame_info.buffer_index, std::move(frame_info.release_fence),
stream_info.highlight);
} else {
frame_info.release_fence.reset();
}
auto& stream = stream_infos_[stream_index].stream;
// Set the region of interest if appropriate.
ZX_ASSERT(configurations_.size() > current_config_index_);
auto& current_configuration = configurations_[current_config_index_];
ZX_ASSERT(current_configuration.streams.size() > stream_index);
auto& current_stream = current_configuration.streams[stream_index];
if (current_stream.supports_crop_region) {
const uint32_t limit = kRegionOfInterestFramesPerMoveRatio;
static uint32_t count = 0;
++count;
if (count >= limit) {
count = 0;
auto region = moving_window_.NextWindow();
stream->SetCropRegion(std::make_unique<fuchsia::math::RectF>(region));
if (stream_infos_[stream_index].source_highlight) {
stream_infos_[stream_infos_[stream_index].source_highlight.value()].highlight = region;
}
}
}
stream->GetNextFrame([this, stream_index](fuchsia::camera3::FrameInfo frame_info) {
OnNextFrame(stream_index, std::move(frame_info));
});
}
void StreamCycler::DisconnectStream(uint32_t stream_index) {
if (remove_collection_handler_) {
auto& stream_info = stream_infos_[stream_index];
if (stream_info.add_collection_handler_returned_value) {
remove_collection_handler_(stream_info.add_collection_handler_returned_value.value());
}
}
stream_infos_.erase(stream_index);
}
uint32_t StreamCycler::NextConfigIndex() {
ZX_ASSERT(configurations_.size() > 0);
ZX_ASSERT(current_config_index_ < configurations_.size());
return (current_config_index_ + 1) % configurations_.size();
}
} // namespace camera