blob: 4c2ff5eb8fbb8a41b97a18a23b28f21abb6daa8a [file] [log] [blame]
// 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 "stream_server.h"
#include <array>
#include <memory>
#include <ddktl/protocol/isp.h>
#include <fbl/auto_lock.h>
#include <src/lib/fxl/logging.h>
#include "../modules/dma-mgr.h"
namespace {
static const auto kDmaPixelFormat = camera::DmaFormat::PixelType::NV12_YUV;
static const auto kPixelFormat = fuchsia::sysmem::PixelFormatType::NV12;
static const auto kColorSpace = fuchsia::sysmem::ColorSpaceType::SRGB;
static const uint32_t kWidth = 1920;
static const uint32_t kHeight = 1080;
static const uint32_t kBufferCount = 8;
static const uint32_t kFramesToHold = kBufferCount - 2;
} // namespace
namespace camera {
zx_status_t StreamServer::Create(zx::bti* bti, std::unique_ptr<StreamServer>* server_out,
fuchsia_sysmem_BufferCollectionInfo* buffers_out) {
*buffers_out = fuchsia_sysmem_BufferCollectionInfo{};
auto server = std::make_unique<StreamServer>();
// Start a loop to handle client messages.
zx_status_t status = server->loop_.StartThread("isp-stream-server-loop");
if (status != ZX_OK) {
FXL_PLOG(ERROR, status) << "Failed to start loop";
return status;
}
// Construct the buffers to pass to the ISP.
auto format = DmaFormat(kWidth, kHeight, kDmaPixelFormat, false);
fuchsia::sysmem::BufferCollectionInfo buffers{};
buffers.format.image.width = kWidth;
buffers.format.image.height = kHeight;
buffers.format.image.layers = 2;
buffers.format.image.pixel_format.type = kPixelFormat;
buffers.format.image.color_space.type = kColorSpace;
buffers.format.image.planes[0].bytes_per_row =
std::abs(static_cast<int32_t>(format.GetLineOffset())); // May be 'negative'.
buffers.format.image.planes[1].bytes_per_row =
buffers.format.image.planes[0].bytes_per_row / 2;
buffers.buffer_count = kBufferCount;
buffers.vmo_size = format.GetImageSize();
for (uint32_t i = 0; i < buffers.buffer_count; ++i) {
status = zx::vmo::create_contiguous(*bti, buffers.vmo_size, 0,
&buffers.vmos[i]);
if (status != ZX_OK) {
FXL_PLOG(ERROR, status) << "Failed to create vmo";
return status;
}
// Initialize chroma channels to 128 (grayscale).
uintptr_t chroma = 0;
size_t map_size = format.GetImageSize() - format.GetBank0Offset();
status = zx::vmar::root_self()->map(0, buffers.vmos[i], format.GetBank0Offset(), map_size,
ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, &chroma);
if (status != ZX_OK) {
FXL_PLOG(ERROR, status) << "Error mapping vmo";
return status;
}
memset(reinterpret_cast<void*>(chroma), 128, map_size);
status = zx::vmar::root_self()->unmap(chroma, map_size);
if (status != ZX_OK) {
FXL_PLOG(ERROR, status) << "Error unmapping vmo";
return status;
}
}
server->buffers_ = std::move(buffers);
status = server->GetBuffers(buffers_out);
if (status != ZX_OK) {
FXL_PLOG(ERROR, status) << "Error getting writable buffers";
return status;
}
*server_out = std::move(server);
return ZX_OK;
}
zx_status_t StreamServer::AddClient(zx::channel channel,
fuchsia_sysmem_BufferCollectionInfo* buffers_out) {
std::unique_ptr<camera::StreamImpl> stream;
zx_status_t status = camera::StreamImpl::Create(std::move(channel), loop_.dispatcher(), &stream);
if (status != ZX_OK) {
FXL_PLOG(ERROR, status) << "Error creating StreamImpl";
return status;
}
status = GetBuffers(buffers_out);
if (status != ZX_OK) {
FXL_PLOG(ERROR, status) << "Error getting read-only buffers";
return status;
}
FXL_LOG(INFO) << "Client " << next_stream_id_ << " connected.";
streams_[next_stream_id_++] = std::move(stream);
return ZX_OK;
}
zx_status_t StreamServer::GetBuffers(fuchsia_sysmem_BufferCollectionInfo* buffers_out) {
buffers_out->format.image.width = buffers_.format.image.width;
buffers_out->format.image.height = buffers_.format.image.height;
buffers_out->format.image.layers = buffers_.format.image.layers;
buffers_out->format.image.pixel_format.type =
static_cast<uint32_t>(buffers_.format.image.pixel_format.type);
buffers_out->format.image.color_space.type =
static_cast<uint32_t>(buffers_.format.image.color_space.type);
memcpy(buffers_out->format.image.planes, buffers_.format.image.planes.data(),
sizeof(buffers_out->format.image.planes));
buffers_out->buffer_count = buffers_.buffer_count;
buffers_out->vmo_size = buffers_.vmo_size;
std::vector<zx::vmo> vmos(buffers_.buffer_count);
for (uint32_t i = 0; i < buffers_.buffer_count; ++i) {
zx::vmo vmo;
zx_status_t status = buffers_.vmos[i].duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo);
if (status != ZX_OK) {
FXL_PLOG(ERROR, status) << "Failed to duplicate VMO";
return status;
}
vmos[i] = std::move(vmo);
}
for (uint32_t i = 0; i < vmos.size(); ++i) {
buffers_out->vmos[i] = vmos[i].release();
}
return ZX_OK;
}
void StreamServer::FrameAvailable(uint32_t id, std::list<uint32_t>* out_frames_to_be_released) {
// Clean up any disconnected clients.
std::unordered_set<uint32_t> disconnected_client_ids;
for (const auto& stream : streams_) {
if (!stream.second->IsBound()) {
disconnected_client_ids.insert(stream.first);
}
}
for (auto id : disconnected_client_ids) {
FXL_LOG(INFO) << "Client " << id << " disconnected.";
streams_.erase(id);
}
// Release any unused frames back to the ISP.
std::array<uint32_t, kBufferCount> buffer_refs{};
for (const auto& stream : streams_) {
const auto& buffer_ids = stream.second->GetOutstandingBuffers();
if (buffer_ids.size() >= kFramesToHold) {
FXL_LOG(WARNING) << "Client " << stream.first
<< " is holding too many buffer references and stalling other clients.";
}
for (const auto buffer_id : buffer_ids) {
++buffer_refs[buffer_id];
}
}
for (uint32_t i = 0; i < kBufferCount; ++i) {
auto it = read_locked_buffers_.find(i);
if (buffer_refs[i] == 0 && it != read_locked_buffers_.end()) {
out_frames_to_be_released->push_back(i);
read_locked_buffers_.erase(it);
}
}
// If clients are collectively holding too many frames, immediately return the buffer to the ISP.
if (read_locked_buffers_.size() >= kFramesToHold) {
FXL_LOG(WARNING)
<< "Clients are collectively holding too many buffers. Frames will be dropped.";
out_frames_to_be_released->push_back(id);
return;
}
// Otherwise, hand it off to the clients.
read_locked_buffers_.insert(id);
for (const auto& stream : streams_) {
stream.second->FrameAvailable(id);
}
}
} // namespace camera