blob: ceb618e8ddb009bd4c12f92adbb246f38b215ac7 [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.
#ifndef SRC_CAMERA_BIN_USB_DEVICE_STREAM_IMPL_H_
#define SRC_CAMERA_BIN_USB_DEVICE_STREAM_IMPL_H_
#include <fuchsia/camera/cpp/fidl.h>
#include <fuchsia/camera3/cpp/fidl.h>
#include <lib/async/cpp/wait.h>
#include <lib/fidl/cpp/binding.h>
#include <lib/fpromise/promise.h>
#include <lib/fpromise/scope.h>
#include <lib/zx/result.h>
#include <zircon/status.h>
#include <memory>
#include <queue>
#include <set>
#include <vector>
#include "src/camera/bin/usb_device/frame_waiter.h"
#include "src/camera/bin/usb_device/util_fidl.h"
#include "src/camera/bin/usb_device/uvc_hack.h"
#include "src/camera/lib/actor/actor_base.h"
#include "src/camera/lib/hanging_get_helper/hanging_get_helper.h"
namespace camera {
// Represents a specific stream in a camera device's configuration. Serves multiple clients of the
// camera3.Stream protocol.
class StreamImpl : public actor::ActorBase {
public:
template <typename RetT_, typename ErrT_ = void>
using promise = fpromise::promise<RetT_, ErrT_>;
// Called by the stream when it needs to connect to its associated legacy stream.
using StreamRequestedCallback =
fit::function<promise<void>(fuchsia::sysmem::BufferCollectionInfo, fuchsia::camera::FrameRate,
fidl::InterfaceRequest<fuchsia::camera::Stream>, zx::eventpair)>;
// Called by the stream when it needs to call BindSharedCollection.
using AllocatorBindSharedCollectionCallback =
fit::function<promise<void>(fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken>,
fidl::InterfaceRequest<fuchsia::sysmem::BufferCollection>)>;
// Called by the stream to register a BufferCollection deallocation devent.
using RegisterDeallocationEvent = fit::function<promise<void>(zx::eventpair deallocation_event)>;
// Called by the stream when it has no more clients.
using NoClientsCallback = fit::function<promise<void>()>;
StreamImpl(async_dispatcher_t* dispatcher, const fuchsia::camera3::StreamProperties2& properties,
fidl::InterfaceRequest<fuchsia::camera3::Stream> request,
StreamRequestedCallback on_stream_requested,
AllocatorBindSharedCollectionCallback allocator_bind_shared_collection,
RegisterDeallocationEvent register_deallocation_event, NoClientsCallback on_no_clients,
std::optional<std::string> description = std::nullopt);
~StreamImpl() = default;
// Ask UVC driver to stop streaming.
promise<void> StopStreaming();
// Close all client connections with given status as epitaph.
promise<void> CloseAllClients(zx_status_t status);
private:
// Schedules a cleanup.
void OnError(zx_status_t status, const std::string& message);
// Cleanup promise. Unmaps and closes VMOs and closes client BufferCollection. Disconnects all
// clients and calls the on_no_clients_ callback. If status != ZX_OK, logs at error level.
promise<void> Cleanup(zx_status_t status, const std::string& message);
// Called when a client calls Rebind.
void OnNewRequest(fidl::InterfaceRequest<fuchsia::camera3::Stream> request);
// Remove the client with the given id.
promise<void> RemoveClient(uint64_t id);
// Called when the legacy stream's OnFrameAvailable event fires.
void OnFrameAvailable(fuchsia::camera::FrameAvailableEvent info);
// Calls Sync on the given token and then produces that token when the sync is complete.
promise<fuchsia::sysmem::BufferCollectionTokenPtr> TokenSync(
fuchsia::sysmem::BufferCollectionTokenPtr token);
// Calls Sync on the given BufferCollection and the promise completes when the sync is complete.
promise<void> BufferCollectionSync(fuchsia::sysmem::BufferCollectionPtr& collection);
// Calls WaitForBuffersAllocated on client_buffer_collection_ and then returns either a status in
// the case of an error or the BufferCollectionInfo_2 if it was successful.
promise<fuchsia::sysmem::BufferCollectionInfo_2, zx_status_t>
WaitForClientBufferCollectionAllocated();
// Given an InterfaceHandle<BufferCollectionToken> with a valid underlying channel, the client
// will be set as a participant and the returned promise will yield the bound token for further
// use. Otherwise, the client will not be a participant and the promise will yield nullopt.
promise<std::optional<fuchsia::sysmem::BufferCollectionTokenPtr>> SetClientParticipation(
uint64_t id, fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> token_handle);
// Renegotiate buffers or opt out of buffer renegotiation for the client with the given id.
promise<fuchsia::sysmem::BufferCollectionInfo, zx_status_t> InitBufferCollections(
fuchsia::sysmem::BufferCollectionTokenPtr token);
// The following functions are async daisy-chained to execute the steps to initialize the
// client-facing buffer collection:
//
// 1. InitializeSharedCollection - Bind shared collection and set attributes
// 2. SetClientBufferCollectionConstraints - Set constraints
// 3. WaitForClientBufferCollectionAllocated - Wait for the allocation to finish
// 4. RegisterDeallocationEvent - so it is known when the client BufferCollection is deallocated
// 5. InitializeClientBuffers - Initialize individual buffers
promise<void> InitializeClientSharedCollection(fuchsia::sysmem::BufferCollectionTokenPtr token);
promise<void> SetClientBufferCollectionConstraints();
promise<void> RegisterClientBufferCollectionDeallocationEvent();
promise<void> InitializeClientBuffers(
fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info);
// Allocate the driver-facing buffer collection.
promise<fuchsia::sysmem::BufferCollectionInfo, zx_status_t> AllocateDriverBufferCollection(
fuchsia::camera::VideoFormat video_format);
// Connect to UVC camera stream and start streaming.
promise<void> ConnectAndStartStream(fuchsia::sysmem::BufferCollectionInfo buffer_collection_info);
// Setup our end of the UVC camera stream and connect to the UVC camera stream.
promise<void> ConnectToStream(fuchsia::sysmem::BufferCollectionInfo buffer_collection_info,
fuchsia::camera::FrameRate frame_rate);
// Ask UVC driver to start streaming.
promise<void> StartStreaming();
// Our local stub to call BindSharedCollection at DeviceImpl.
void AllocatorBindSharedCollection(
fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> token_handle,
fidl::InterfaceRequest<fuchsia::sysmem::BufferCollection> request);
// Process incoming UVC camera frame in preparation for sending to client.
void ProcessFrameForSend(uint32_t buffer_id);
// Tell UVC driver that a driver-facing buffer can be returned to the free pool.
void ReleaseClientFrame(uint32_t buffer_id);
// Represents a single client connection to the StreamImpl class.
class Client : public fuchsia::camera3::Stream {
public:
Client(StreamImpl& stream, uint64_t id,
fidl::InterfaceRequest<fuchsia::camera3::Stream> request);
~Client() override;
// Add a frame to the queue of available frames.
void AddFrame(fuchsia::camera3::FrameInfo2 frame);
// Send a frame to the client if one is available and has been requested.
void MaybeSendFrame();
// Closes |binding_| with the provided |status| epitaph, and removes the client instance from
// the parent |clients_| map.
void CloseConnection(zx_status_t status);
// Add the given token to the client's token queue.
void ReceiveBufferCollection(fuchsia::sysmem::BufferCollectionTokenHandle token);
// Update the client's resolution.
void ReceiveResolution(fuchsia::math::Size coded_size);
// Update the client's crop region.
void ReceiveCropRegion(std::unique_ptr<fuchsia::math::RectF> region);
// Returns a mutable reference to this client's state as a participant in buffer renegotiation.
bool& Participant();
// Clears the client's queue of unsent frames.
void ClearFrames();
private:
// Called when the client endpoint of |binding_| is closed.
void OnClientDisconnected(zx_status_t status);
// |fuchsia::camera3::Stream|
void GetProperties(GetPropertiesCallback callback) override;
void GetProperties2(GetProperties2Callback callback) override;
void SetCropRegion(std::unique_ptr<fuchsia::math::RectF> region) override;
void WatchCropRegion(WatchCropRegionCallback callback) override;
void SetResolution(fuchsia::math::Size coded_size) override;
void WatchResolution(WatchResolutionCallback callback) override;
void SetBufferCollection(
fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> token) override;
void WatchBufferCollection(WatchBufferCollectionCallback callback) override;
void WatchOrientation(WatchOrientationCallback callback) override;
void GetNextFrame(GetNextFrameCallback callback) override;
void GetNextFrame2(GetNextFrame2Callback callback) override;
void Rebind(fidl::InterfaceRequest<Stream> request) override;
StreamImpl& stream_;
std::string log_prefix_;
// Tracking for whether a message has already been logged for the named stage of client
// progress. This is used to ensure that only one such message is logged per transition per
// client, as they are high-frequency events that would otherwise spam syslog.
struct {
// The client has called camera3::Stream::GetNextFrame.
bool requested = false;
// The parent stream has added a frame to this client's available frame queue by calling
// AddFrame.
bool available = false;
// A frame has been sent to the client by invoking the callback provided in a previous call to
// GetNextFrame.
bool sent = false;
} frame_logging_state_;
uint64_t id_;
fidl::Binding<fuchsia::camera3::Stream> binding_;
HangingGetHelper<fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken>> buffers_;
HangingGetHelper<fuchsia::math::Size,
fit::function<bool(fuchsia::math::Size, fuchsia::math::Size)>>
resolution_;
HangingGetHelper<std::unique_ptr<fuchsia::math::RectF>> crop_region_;
GetNextFrame2Callback frame_callback_;
bool participant_ = false;
std::queue<fuchsia::camera3::FrameInfo2> frames_;
WatchOrientationCallback orientation_callback_;
};
async_dispatcher_t* dispatcher_;
fuchsia::sysmem::AllocatorPtr allocator_;
const fuchsia::camera3::StreamProperties2& properties_;
std::map<uint64_t, std::unique_ptr<Client>> clients_;
uint64_t client_id_next_ = 1;
StreamRequestedCallback on_stream_requested_;
AllocatorBindSharedCollectionCallback allocator_bind_shared_collection_;
RegisterDeallocationEvent register_deallocation_event_;
NoClientsCallback on_no_clients_;
uint32_t max_camping_buffers_ = kUvcHackClientMaxBufferCountForCamping;
// USB video stream
fuchsia::camera::StreamPtr stream_;
// USB video stream token used to signal connection drop
zx::eventpair stream_token_;
// Client-facing buffer collection
fuchsia::sysmem::BufferCollectionInfo_2 client_buffer_collection_info_;
fuchsia::sysmem::BufferCollectionPtr client_buffer_collection_;
// Driver-facing buffer collection
fuchsia::sysmem::BufferCollectionInfo driver_buffer_collection_info_;
// Maps client-facing buffer ID to the buffer's virtual address.
std::map<uint32_t, uintptr_t> client_buffer_id_to_virt_addr_;
// Maps driver-facing buffer ID to the buffer's virtual address.
std::map<uint32_t, uintptr_t> driver_buffer_id_to_virt_addr_;
// Buffer ID's of client-facing buffers that are not in use.
std::queue<uint32_t> client_buffer_free_queue_;
// Is the USB video driver streaming?
bool streaming_ = false;
uint64_t frame_counter_ = 0;
std::map<uint32_t, std::unique_ptr<FrameWaiter>> frame_waiters_;
fuchsia::math::Size current_resolution_;
std::unique_ptr<fuchsia::math::RectF> current_crop_region_;
std::string description_;
// This should always be the last thing in the object. Otherwise scheduled tasks within this scope
// which reference members of this object may be allowed to run after destruction of this object
// has started. Keeping this at the end ensures that the scope is destroyed first, cancelling any
// scheduled tasks before the rest of the members are destroyed.
fpromise::scope scope_;
friend class Client;
};
} // namespace camera
#endif // SRC_CAMERA_BIN_USB_DEVICE_STREAM_IMPL_H_