|  | // Copyright 2019 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/drivers/controller/input_node.h" | 
|  |  | 
|  | #include <fuchsia/hardware/isp/c/banjo.h> | 
|  | #include <lib/syslog/cpp/macros.h> | 
|  | #include <zircon/errors.h> | 
|  | #include <zircon/types.h> | 
|  |  | 
|  | #include <ddk/trace/event.h> | 
|  |  | 
|  | #include "src/camera/drivers/controller/graph_utils.h" | 
|  | #include "src/camera/lib/format_conversion/buffer_collection_helper.h" | 
|  | #include "src/camera/lib/format_conversion/format_conversion.h" | 
|  | #include "src/devices/lib/sysmem/sysmem.h" | 
|  |  | 
|  | namespace camera { | 
|  |  | 
|  | constexpr auto kTag = "camera_controller_input_node"; | 
|  |  | 
|  | fit::result<std::unique_ptr<InputNode>, zx_status_t> InputNode::CreateInputNode( | 
|  | StreamCreationData* info, const ControllerMemoryAllocator& memory_allocator, | 
|  | async_dispatcher_t* dispatcher, const ddk::IspProtocolClient& isp) { | 
|  | uint8_t isp_stream_type; | 
|  | if (info->node.input_stream_type == fuchsia::camera2::CameraStreamType::FULL_RESOLUTION) { | 
|  | isp_stream_type = STREAM_TYPE_FULL_RESOLUTION; | 
|  | } else if (info->node.input_stream_type == | 
|  | fuchsia::camera2::CameraStreamType::DOWNSCALED_RESOLUTION) { | 
|  | isp_stream_type = STREAM_TYPE_DOWNSCALED; | 
|  | } else { | 
|  | return fit::error(ZX_ERR_INVALID_ARGS); | 
|  | } | 
|  |  | 
|  | auto result = camera::GetBuffers(memory_allocator, info->node, info, kTag); | 
|  | if (result.is_error()) { | 
|  | FX_PLOGST(ERROR, kTag, result.error()) << "Failed to get buffers"; | 
|  | return fit::error(result.error()); | 
|  | } | 
|  | auto buffers = std::move(result.value()); | 
|  |  | 
|  | // Use a BufferCollectionHelper to manage the conversion | 
|  | // between buffer collection representations. | 
|  | BufferCollectionHelper buffer_collection_helper(buffers); | 
|  |  | 
|  | auto image_format = ConvertHlcppImageFormat2toCType(info->node.image_formats[0]); | 
|  |  | 
|  | // Create Input Node | 
|  | auto processing_node = | 
|  | std::make_unique<camera::InputNode>(info, std::move(buffers), dispatcher, isp); | 
|  | if (!processing_node) { | 
|  | FX_LOGST(ERROR, kTag) << "Failed to create Input node"; | 
|  | return fit::error(ZX_ERR_NO_MEMORY); | 
|  | } | 
|  |  | 
|  | // Create stream with ISP | 
|  | auto isp_stream_protocol = std::make_unique<camera::IspStreamProtocol>(); | 
|  | if (!isp_stream_protocol) { | 
|  | FX_LOGST(ERROR, kTag) << "Failed to create ISP stream protocol"; | 
|  | return fit::error(ZX_ERR_INTERNAL); | 
|  | } | 
|  |  | 
|  | buffer_collection_info_2 temp_buffer_collection; | 
|  | image_format_2_t temp_image_format; | 
|  | sysmem::buffer_collection_info_2_banjo_from_fidl(*buffer_collection_helper.GetC(), | 
|  | temp_buffer_collection); | 
|  | sysmem::image_format_2_banjo_from_fidl(image_format, temp_image_format); | 
|  | auto status = isp.CreateOutputStream( | 
|  | &temp_buffer_collection, &temp_image_format, | 
|  | reinterpret_cast<const frame_rate_t*>(&info->node.output_frame_rate), isp_stream_type, | 
|  | processing_node->isp_frame_callback(), isp_stream_protocol->protocol()); | 
|  | if (status != ZX_OK) { | 
|  | FX_PLOGST(ERROR, kTag, status) << "Failed to create output stream on ISP"; | 
|  | return fit::error(status); | 
|  | } | 
|  |  | 
|  | // Update the input node with the ISP stream protocol | 
|  | processing_node->set_isp_stream_protocol(std::move(isp_stream_protocol)); | 
|  | return fit::ok(std::move(processing_node)); | 
|  | } | 
|  |  | 
|  | void InputNode::OnReadyToProcess(const frame_available_info_t* info) { | 
|  | // Use the timestamp of the input as the capture time. | 
|  | auto modified = *info; | 
|  | modified.metadata.capture_timestamp = info->metadata.timestamp; | 
|  | OnFrameAvailable(&modified); | 
|  | } | 
|  |  | 
|  | void InputNode::OnFrameAvailable(const frame_available_info_t* info) { | 
|  | ZX_ASSERT(thread_checker_.is_thread_valid()); | 
|  | TRACE_DURATION("camera", "InputNode::OnFrameAvailable", "buffer_index", info->buffer_id); | 
|  | if (shutdown_requested_ || info->frame_status != FRAME_STATUS_OK) { | 
|  | TRACE_INSTANT("camera", "bad_status", TRACE_SCOPE_THREAD, "frame_status", | 
|  | static_cast<uint32_t>(info->frame_status)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | UpdateFrameCounterForAllChildren(); | 
|  |  | 
|  | if (NeedToDropFrame()) { | 
|  | TRACE_INSTANT("camera", "drop_frame", TRACE_SCOPE_THREAD); | 
|  | isp_stream_protocol_->ReleaseFrame(info->buffer_id); | 
|  | return; | 
|  | } | 
|  | ProcessNode::OnFrameAvailable(info); | 
|  | } | 
|  |  | 
|  | void InputNode::OnReleaseFrame(uint32_t buffer_index) { | 
|  | TRACE_DURATION("camera", "InputNode::OnReleaseFrame", "buffer_index", buffer_index); | 
|  | fbl::AutoLock al(&in_use_buffer_lock_); | 
|  | ZX_ASSERT(buffer_index < in_use_buffer_count_.size()); | 
|  | in_use_buffer_count_[buffer_index]--; | 
|  | if (in_use_buffer_count_[buffer_index] != 0) { | 
|  | return; | 
|  | } | 
|  | if (!shutdown_requested_) { | 
|  | isp_stream_protocol_->ReleaseFrame(buffer_index); | 
|  | } | 
|  | } | 
|  |  | 
|  | void InputNode::OnStartStreaming() { | 
|  | enabled_ = true; | 
|  | isp_stream_protocol_->Start(); | 
|  | } | 
|  |  | 
|  | void InputNode::OnStopStreaming() { | 
|  | if (AllChildNodesDisabled()) { | 
|  | enabled_ = false; | 
|  | isp_stream_protocol_->Stop(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void InputNode::OnShutdown(fit::function<void(void)> shutdown_callback) { | 
|  | shutdown_callback_ = std::move(shutdown_callback); | 
|  |  | 
|  | // After a shutdown request has been made, | 
|  | // no other calls should be made to the ISP driver. | 
|  | shutdown_requested_ = true; | 
|  |  | 
|  | isp_stream_shutdown_callback_t isp_stream_shutdown_cb = { | 
|  | .shutdown_complete = | 
|  | [](void* ctx, zx_status_t /*status*/) { | 
|  | auto* input_node = static_cast<decltype(this)>(ctx); | 
|  | input_node->node_callback_received_ = true; | 
|  | input_node->OnCallbackReceived(); | 
|  | }, | 
|  | .ctx = this, | 
|  | }; | 
|  |  | 
|  | zx_status_t status = isp_stream_protocol_->Shutdown(&isp_stream_shutdown_cb); | 
|  | if (status != ZX_OK) { | 
|  | FX_PLOGST(ERROR, kTag, status) << "Failure during stream shutdown"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | auto child_shutdown_completion_callback = [this]() { | 
|  | child_node_callback_received_ = true; | 
|  | OnCallbackReceived(); | 
|  | }; | 
|  |  | 
|  | ZX_ASSERT_MSG(configured_streams().size() == 1, | 
|  | "Cannot shutdown a stream which supports multiple streams"); | 
|  |  | 
|  | // Forward the shutdown request to child node. | 
|  | if (child_nodes().empty()) { | 
|  | // If an incomplete graph is shut down, invoke the completion directly. | 
|  | child_shutdown_completion_callback(); | 
|  | } else { | 
|  | // Otherwise, propagate the shutdown command and completion to the next child. | 
|  | child_nodes().at(0)->OnShutdown(child_shutdown_completion_callback); | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace camera |