| // 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-loop/default.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/fit/function.h> |
| #include <lib/syslog/cpp/logger.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/syslog/cpp/logger.h" |
| |
| StreamImpl::StreamImpl(const fuchsia::camera3::StreamProperties& properties, |
| const fuchsia::camera2::hal::StreamConfig& legacy_config, |
| fidl::InterfaceRequest<fuchsia::camera3::Stream> request, |
| StreamRequestedCallback on_stream_requested, fit::closure on_no_clients) |
| : loop_(&kAsyncLoopConfigNoAttachToCurrentThread), |
| 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); |
| auto client = std::make_unique<Client>(*this, client_id_next_, std::move(request)); |
| clients_.emplace(client_id_next_++, std::move(client)); |
| ZX_ASSERT(loop_.StartThread("Camera Stream Thread") == ZX_OK); |
| } |
| |
| StreamImpl::~StreamImpl() { |
| Unbind(legacy_stream_); |
| loop_.Quit(); |
| loop_.JoinThreads(); |
| } |
| |
| void StreamImpl::OnLegacyStreamDisconnected(zx_status_t status) { |
| FX_PLOGS(ERROR, status) << "Legacy Stream disconnected unexpectedly."; |
| clients_.clear(); |
| on_no_clients_(); |
| } |
| |
| void StreamImpl::PostRemoveClient(uint64_t id) { |
| async::PostTask(loop_.dispatcher(), [=]() { |
| clients_.erase(id); |
| if (clients_.empty()) { |
| on_no_clients_(); |
| } |
| }); |
| } |
| |
| void StreamImpl::PostAddFrameSink(uint64_t id) { |
| async::PostTask(loop_.dispatcher(), [=]() { |
| frame_sinks_.push(id); |
| SendFrames(); |
| }); |
| } |
| |
| void StreamImpl::OnFrameAvailable(fuchsia::camera2::FrameAvailableInfo info) { |
| 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 (!info.metadata.has_timestamp()) { |
| FX_LOGS(WARNING) |
| << "Driver sent a frame without a timestamp. This frame will not be sent to clients."; |
| return; |
| } |
| |
| // Construct the frame info and create the release fence. |
| fuchsia::camera3::FrameInfo frame; |
| frame.buffer_index = info.buffer_id; |
| frame.frame_counter = ++frame_counter_; |
| frame.timestamp = info.metadata.timestamp(); |
| zx::eventpair fence; |
| ZX_ASSERT(zx::eventpair::create(0u, &fence, &frame.release_fence) == ZX_OK); |
| frames_.push(std::move(frame)); |
| |
| // Release frames in excess of the camping limit. |
| while (frames_.size() > max_camping_buffers_) { |
| frames_.pop(); |
| } |
| |
| // Queue a waiter so that when the client end of the fence is released, the frame is released back |
| // to the driver. |
| auto waiter = |
| std::make_unique<async::Wait>(fence.get(), ZX_EVENTPAIR_PEER_CLOSED, 0, |
| [this, fence = std::move(fence), index = frame.buffer_index]( |
| async_dispatcher_t* dispatcher, async::WaitBase* wait, |
| zx_status_t status, const zx_packet_signal_t* signal) { |
| legacy_stream_->ReleaseFrame(index); |
| frame_waiters_.erase(index); |
| }); |
| ZX_ASSERT(waiter->Begin(loop_.dispatcher()) == ZX_OK); |
| frame_waiters_[frame.buffer_index] = std::move(waiter); |
| |
| // Send the frame to any pending recipients. |
| SendFrames(); |
| } |
| |
| void StreamImpl::PostSetBufferCollection( |
| uint64_t id, fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> token) { |
| async::PostTask(loop_.dispatcher(), [this, id, token_handle = std::move(token)]() mutable { |
| 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->Participant() = true; |
| |
| // Bind and duplicate the token for each participating client. |
| fuchsia::sysmem::BufferCollectionTokenPtr token; |
| ZX_ASSERT(token.Bind(std::move(token_handle), loop_.dispatcher()) == ZX_OK); |
| for (auto& client : clients_) { |
| if (client.second->Participant()) { |
| fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> client_token; |
| token->Duplicate(ZX_RIGHT_SAME_RIGHTS, client_token.NewRequest()); |
| client.second->PostReceiveBufferCollection(std::move(client_token)); |
| } |
| } |
| |
| // Synchronize duplications then pass the final token to the device for constraints application. |
| token->Sync([this, token = std::move(token)]() mutable { |
| ZX_ASSERT(on_stream_requested_); |
| frame_waiters_.clear(); |
| on_stream_requested_( |
| std::move(token), legacy_stream_.NewRequest(loop_.dispatcher()), |
| [this](uint32_t max_camping_buffers) { max_camping_buffers_ = max_camping_buffers; }, |
| legacy_stream_format_index_); |
| legacy_stream_->Start(); |
| }); |
| }); |
| } |
| |
| void StreamImpl::SendFrames() { |
| if (frame_sinks_.size() > 1 && !frame_sink_warning_sent_) { |
| FX_LOGS(INFO) << Messages::kMultipleFrameClients; |
| frame_sink_warning_sent_ = true; |
| } |
| |
| while (!frames_.empty() && !frame_sinks_.empty()) { |
| auto it = clients_.find(frame_sinks_.front()); |
| frame_sinks_.pop(); |
| if (it != clients_.end()) { |
| it->second->PostSendFrame(std::move(frames_.front())); |
| frames_.pop(); |
| } |
| } |
| } |
| |
| 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)}; |
| } |
| |
| void StreamImpl::PostSetResolution(uint32_t id, fuchsia::math::Size coded_size) { |
| zx_status_t status = async::PostTask(loop_.dispatcher(), [this, id, coded_size] { |
| 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->PostCloseConnection(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->PostCloseConnection(ZX_ERR_INTERNAL); |
| clients_.erase(it); |
| } |
| on_no_clients_(); |
| return; |
| } |
| }); |
| } |
| |
| // Inform clients of the resolution change. |
| for (auto& [id, client] : clients_) { |
| client->PostReceiveResolution(best_size); |
| } |
| }); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| } |