| // 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 <fuchsia/camera3/cpp/fidl.h> |
| #include <fuchsia/sysmem/cpp/fidl.h> |
| #include <fuchsia/ui/policy/cpp/fidl.h> |
| #include <fuchsia/ui/scenic/cpp/fidl.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/fpromise/bridge.h> |
| #include <lib/fpromise/single_threaded_executor.h> |
| #include <lib/fzl/vmo-mapper.h> |
| #include <lib/sys/cpp/component_context.h> |
| #include <lib/syslog/cpp/log_settings.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/trace-provider/provider.h> |
| |
| #include <set> |
| |
| #include "src/camera/bin/camera-gym/buffer_collage.h" |
| #include "src/camera/bin/camera-gym/controller_receiver.h" |
| #include "src/camera/bin/camera-gym/frame_capture.h" |
| #include "src/camera/bin/camera-gym/lifecycle_impl.h" |
| #include "src/camera/bin/camera-gym/stream_cycler.h" |
| #include "src/lib/fxl/command_line.h" |
| |
| using Command = fuchsia::camera::gym::Command; |
| |
| constexpr zx::duration kDefaultAutoCycleInterval = zx::msec(CONFIGURATION_CYCLE_INTERVAL_MS); |
| |
| int main(int argc, char* argv[]) { |
| auto command_line = fxl::CommandLineFromArgcArgv(argc, argv); |
| |
| // Default must match existing behavior as started from UI. |
| bool manual_mode = command_line.HasOption("manual"); |
| std::optional<zx::duration> auto_cycle_interval = kDefaultAutoCycleInterval; |
| std::string value_string; |
| if (command_line.GetOptionValue("auto-cycle-interval-ms", &value_string)) { |
| auto auto_cycle_interval_ms_value = std::stoul(value_string, nullptr, 0); |
| |
| // Value of 0 means "infinite". (Meaning no config cycling at all.) |
| if (auto_cycle_interval_ms_value == 0) { |
| auto_cycle_interval.reset(); |
| } else { |
| auto_cycle_interval = zx::msec(auto_cycle_interval_ms_value); |
| } |
| } |
| |
| syslog::SetLogSettings({.min_log_level = CAMERA_MIN_LOG_LEVEL}, {"camera-gym"}); |
| |
| async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); |
| trace::TraceProviderWithFdio trace_provider(loop.dispatcher()); |
| auto context = sys::ComponentContext::CreateAndServeOutgoingDirectory(); |
| |
| // Connect to required services for the collage. |
| fuchsia::sysmem::AllocatorHandle allocator; |
| zx_status_t status = context->svc()->Connect(allocator.NewRequest()); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to request Allocator service."; |
| return EXIT_FAILURE; |
| } |
| fuchsia::ui::scenic::ScenicHandle scenic; |
| status = context->svc()->Connect(scenic.NewRequest()); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to request Scenic service."; |
| return EXIT_FAILURE; |
| } |
| fuchsia::ui::policy::PresenterHandle presenter; |
| context->svc()->Connect(presenter.NewRequest()); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to request Presenter service."; |
| return EXIT_FAILURE; |
| } |
| |
| // Create the collage. |
| auto collage_result = camera::BufferCollage::Create( |
| std::move(scenic), std::move(allocator), std::move(presenter), [&loop] { loop.Quit(); }); |
| if (collage_result.is_error()) { |
| FX_PLOGS(ERROR, collage_result.error()) << "Failed to create BufferCollage."; |
| return EXIT_FAILURE; |
| } |
| auto collage = collage_result.take_value(); |
| |
| // Support frame capture. |
| if (manual_mode) { |
| auto frame_capture = std::make_unique<camera::FrameCapture>(); |
| frame_capture->Initialize(); |
| collage->SetFrameCapture(std::move(frame_capture)); |
| } |
| |
| // Connect to required services for the cycler. |
| fuchsia::camera3::DeviceWatcherHandle watcher; |
| status = context->svc()->Connect(watcher.NewRequest()); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to request DeviceWatcher service."; |
| return EXIT_FAILURE; |
| } |
| allocator = nullptr; |
| status = context->svc()->Connect(allocator.NewRequest()); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to request Allocator service."; |
| return EXIT_FAILURE; |
| } |
| |
| // Create the async::Loop to be used privately by StreamCycler. |
| async::Loop cycler_loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| |
| // Create the cycler and attach it to the collage. |
| auto cycler_result = camera::StreamCycler::Create(std::move(watcher), std::move(allocator), |
| cycler_loop.dispatcher(), manual_mode); |
| if (cycler_result.is_error()) { |
| FX_PLOGS(ERROR, cycler_result.error()) << "Failed to create StreamCycler."; |
| return EXIT_FAILURE; |
| } |
| auto cycler = cycler_result.take_value(); |
| cycler->set_auto_cycle_interval(auto_cycle_interval); |
| |
| status = cycler_loop.StartThread("StreamCycler Thread"); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status); |
| return EXIT_FAILURE; |
| } |
| |
| bool device_muted = false; |
| std::set<uint32_t> collection_ids; |
| |
| camera::StreamCycler::AddCollectionHandler add_collection_handler = |
| [&collage, &collection_ids](fuchsia::sysmem::BufferCollectionTokenHandle token, |
| fuchsia::sysmem::ImageFormat_2 image_format, |
| std::string description) -> uint32_t { |
| auto result = fpromise::run_single_threaded( |
| collage->AddCollection(std::move(token), image_format, description)); |
| if (result.is_error()) { |
| FX_LOGS(FATAL) << "Failed to add collection to collage."; |
| return 0; |
| } |
| collection_ids.insert(result.value()); |
| return result.value(); |
| }; |
| |
| camera::StreamCycler::RemoveCollectionHandler remove_collection_handler = |
| [&collage, &collection_ids](uint32_t id) { |
| collection_ids.erase(id); |
| collage->RemoveCollection(id); |
| }; |
| |
| camera::StreamCycler::ShowBufferHandler show_buffer_handler = |
| [&collage, &device_muted](uint32_t collection_id, uint32_t buffer_index, |
| zx::eventpair release_fence, |
| std::optional<fuchsia::math::RectF> subregion) { |
| collage->PostShowBuffer(collection_id, buffer_index, std::move(release_fence), |
| std::move(subregion)); |
| if (!device_muted) { |
| // Only make the collection visible after we have shown an unmuted frame. |
| collage->PostSetCollectionVisibility(collection_id, true); |
| } |
| }; |
| |
| camera::StreamCycler::MuteStateHandler mute_handler = [&collage, &collection_ids, |
| &device_muted](bool muted) { |
| collage->PostSetMuteIconVisibility(muted); |
| if (muted) { |
| // Immediately hide all collections on mute. |
| for (auto id : collection_ids) { |
| collage->PostSetCollectionVisibility(id, false); |
| } |
| } |
| device_muted = muted; |
| }; |
| |
| cycler->SetHandlers(std::move(add_collection_handler), std::move(remove_collection_handler), |
| std::move(show_buffer_handler), std::move(mute_handler)); |
| |
| std::unique_ptr<camera::ControllerReceiver> controller_receiver; |
| if (manual_mode) { |
| controller_receiver = std::make_unique<camera::ControllerReceiver>(); |
| cycler->set_controller_dispatcher(loop.dispatcher()); |
| collage->set_controller_dispatcher(loop.dispatcher()); |
| |
| FX_LOGS(INFO) << "Running in manual mode."; |
| |
| // Bridge ControllerReceiver to StreamCycler. |
| camera::ControllerReceiver::CommandHandler command_handler = |
| [&cycler, &collage](fuchsia::camera::gym::Command command, |
| camera::ControllerReceiver::SendCommandCallback callback) { |
| switch (command.Which()) { |
| case Command::Tag::kSetDescription: |
| case Command::Tag::kCaptureFrame: |
| collage->ExecuteCommand(std::move(command), std::move(callback)); |
| break; |
| default: |
| cycler->ExecuteCommand(std::move(command), std::move(callback)); |
| break; |
| } |
| }; |
| controller_receiver->SetHandlers(std::move(command_handler)); |
| |
| context->outgoing()->AddPublicService(controller_receiver->GetHandler()); |
| } else { |
| FX_LOGS(INFO) << "Running in automatic mode."; |
| } |
| |
| // Publish the view service. |
| context->outgoing()->AddPublicService(collage->GetHandler()); |
| |
| // Publish a handler for the Lifecycle protocol that cleanly quits the component. |
| LifecycleImpl lifecycle([&loop] { loop.Quit(); }); |
| context->outgoing()->AddPublicService(lifecycle.GetHandler()); |
| |
| loop.Run(); |
| cycler_loop.Quit(); |
| cycler_loop.JoinThreads(); |
| return EXIT_SUCCESS; |
| } |