blob: 65187fa00f11f38f8678a5c69463e014fb4299c3 [file] [log] [blame]
// Copyright 2018 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/simple_camera/simple_camera_lib/video_display.h"
#include <fcntl.h>
#include <src/lib/files/unique_fd.h>
#include <src/lib/fxl/log_level.h>
#include <src/lib/fxl/logging.h>
#include <src/lib/fxl/strings/string_printf.h>
#define ROUNDUP(a, b) (((a) + ((b)-1)) & ~((b)-1))
namespace simple_camera {
// When a buffer is released, signal that it is available to the writer
// In this case, that means directly write to the buffer then re-present it
void VideoDisplay::BufferReleased(uint32_t buffer_id) {
camera_client_->stream_->ReleaseFrame(buffer_id);
}
// When an incoming buffer is filled, VideoDisplay releases the acquire fence
zx_status_t VideoDisplay::IncomingBufferFilled(
const fuchsia::camera::FrameAvailableEvent& frame) {
if (frame.frame_status != fuchsia::camera::FrameStatus::OK) {
FXL_LOG(ERROR) << "Error set on incoming frame. Error: "
<< static_cast<int>(frame.frame_status);
return ZX_OK; // no reason to stop the channel...
}
uint32_t buffer_id = frame.buffer_id;
uint64_t capture_time_ns = frame.metadata.timestamp;
uint64_t pres_time = frame_scheduler_.GetPresentationTimeNs(capture_time_ns);
zx::event acquire_fence, release_fence;
// TODO(garratt): these are supposed to be fire and forget:
frame_buffers_[buffer_id]->DuplicateAcquireFence(&acquire_fence);
frame_buffers_[buffer_id]->DuplicateReleaseFence(&release_fence);
std::vector<zx::event> acquire_fences;
acquire_fences.push_back(std::move(acquire_fence));
std::vector<zx::event> release_fences;
release_fences.push_back(std::move(release_fence));
FXL_VLOG(4) << "presenting Buffer " << buffer_id << " at " << pres_time;
image_pipe_->PresentImage(
buffer_id + 1, pres_time, std::move(acquire_fences),
std::move(release_fences),
[this, pres_time](const fuchsia::images::PresentationInfo& info) {
this->frame_scheduler_.OnFramePresented(
info.presentation_time, info.presentation_interval, pres_time);
});
frame_buffers_[buffer_id]->Signal();
return ZX_OK;
}
// This is a stand-in for some actual gralloc type service which would allocate
// the right type of memory for the application and return it as a vmo.
zx_status_t Gralloc(fuchsia::camera::VideoFormat format, uint32_t num_buffers,
fuchsia::sysmem::BufferCollectionInfo* buffer_collection) {
// In the future, some special alignment might happen here, or special
// memory allocated...
// Simple GetBufferSize. Only valid for simple formats:
size_t buffer_size = ROUNDUP(
format.format.height * format.format.planes[0].bytes_per_row, PAGE_SIZE);
buffer_collection->buffer_count = num_buffers;
buffer_collection->vmo_size = buffer_size;
buffer_collection->format.set_image(std::move(format.format));
zx_status_t status;
for (uint32_t i = 0; i < num_buffers; ++i) {
status = zx::vmo::create(buffer_size, 0, &buffer_collection->vmos[i]);
if (status != ZX_OK) {
FXL_PLOG(ERROR, status) << "Failed to allocate Buffer Collection";
return status;
}
}
return ZX_OK;
}
// This function is a stand-in for the fact that our formats are not
// standardized accross the platform. This is an issue, we are tracking
// it as (MTWN-98).
bool ConvertFormat(fuchsia::sysmem::PixelFormat driver_format,
fuchsia::images::PixelFormat* out_fmt) {
switch (driver_format.type) {
case fuchsia::sysmem::PixelFormatType::BGRA32:
*out_fmt = fuchsia::images::PixelFormat::BGRA_8;
return true;
case fuchsia::sysmem::PixelFormatType::YUY2:
*out_fmt = fuchsia::images::PixelFormat::YUY2;
return true;
case fuchsia::sysmem::PixelFormatType::NV12:
*out_fmt = fuchsia::images::PixelFormat::NV12;
return true;
default:
return false;
}
}
zx_status_t VideoDisplay::SetupBuffers(
const fuchsia::sysmem::BufferCollectionInfo& buffer_collection) {
fuchsia::images::ImageInfo image_info;
image_info.stride = buffer_collection.format.image().planes[0].bytes_per_row;
image_info.tiling = fuchsia::images::Tiling::LINEAR;
image_info.width = buffer_collection.format.image().width;
image_info.height = buffer_collection.format.image().height;
// To make things look like a webcam application, mirror left-right.
image_info.transform = fuchsia::images::Transform::FLIP_HORIZONTAL;
if (!ConvertFormat(buffer_collection.format.image().pixel_format,
&image_info.pixel_format)) {
FXL_CHECK(false) << "Unsupported format";
}
for (size_t id = 0; id < buffer_collection.buffer_count; ++id) {
zx::vmo vmo_dup;
zx_status_t status =
buffer_collection.vmos[id].duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo_dup);
if (status != ZX_OK) {
FXL_PLOG(ERROR, status) << "Failed to duplicate vmo";
return status;
}
image_pipe_->AddImage(id + 1, image_info, std::move(vmo_dup), 0,
buffer_collection.vmo_size,
fuchsia::images::MemoryType::HOST_MEMORY);
// Now create the fence for the buffer:
std::unique_ptr<BufferFence> fence = BufferFence::Create(id);
if (fence == nullptr) {
return ZX_ERR_INTERNAL;
}
// Set release fence callback so we know when a frame is made available
fence->SetReleaseFenceHandler(
// TODO(garratt): This does not handle return value
[this](BufferFence* fence) { this->BufferReleased(fence->index()); });
fence->Reset();
frame_buffers_.push_back(std::move(fence));
}
return ZX_OK;
}
// TODO(CAM-9): Clean up this function after major changes land.
zx_status_t VideoDisplay::ConnectToCamera(
component::StartupContext* context, uint32_t camera_id,
fidl::InterfaceHandle<fuchsia::images::ImagePipe> image_pipe,
OnShutdownCallback callback) {
if (!callback) {
return ZX_ERR_INVALID_ARGS;
}
on_shut_down_callback_ = std::move(callback);
image_pipe_ = image_pipe.Bind();
image_pipe_.set_error_handler([this](zx_status_t status) {
// Deal with image_pipe_ issues
on_shut_down_callback_();
});
// Create the FIDL interface and bind events
camera_client_ = std::make_unique<CameraClient>();
camera_client_->stream_.events().OnFrameAvailable =
[this](fuchsia::camera::FrameAvailableEvent frame) {
IncomingBufferFilled(frame);
};
camera_client_->stream_.set_error_handler([this](zx_status_t status) {
DisconnectFromCamera();
on_shut_down_callback_();
});
// Open a connection to the Camera Manager
context->ConnectToEnvironmentService(camera_client_->manager_.NewRequest());
zx_status_t status = ZX_OK;
// Figure out a format
std::vector<fuchsia::camera::VideoFormat> formats;
{
uint32_t total_format_count;
uint32_t format_index = 0;
do {
std::vector<fuchsia::camera::VideoFormat> formats_ptr;
status = camera_client_->manager_->GetFormats(
camera_id, format_index, &formats_ptr, &total_format_count);
if (status != ZX_OK) {
FXL_PLOG(ERROR, status) << "Couldn't get camera formats";
DisconnectFromCamera();
return status;
}
for (auto&& f : formats_ptr) {
formats.push_back(f);
}
format_index += formats_ptr.size();
} while (formats.size() < total_format_count);
FXL_LOG(INFO) << "Available formats: " << formats.size();
for (int i = 0; i < (int)formats.size(); i++) {
FXL_LOG(INFO) << "format[" << i
<< "] - width: " << formats[i].format.width
<< ", height: " << formats[i].format.height << ", stride: "
<< formats[i].format.planes[0].bytes_per_row;
}
}
uint32_t idx = 0;
fuchsia::images::PixelFormat fmt;
while (!ConvertFormat(formats[idx].format.pixel_format, &fmt)) {
if (++idx == formats.size()) {
return ZX_ERR_NOT_SUPPORTED;
}
}
auto chosen_format = formats[idx];
// Allocate VMO buffer storage
{
fuchsia::sysmem::BufferCollectionInfo buffer_collection;
status = Gralloc(chosen_format, kNumberOfBuffers, &buffer_collection);
if (status != ZX_OK) {
FXL_PLOG(ERROR, status) << "Couldn't allocate buffers";
DisconnectFromCamera();
return status;
}
status = SetupBuffers(buffer_collection);
if (status != ZX_OK) {
FXL_PLOG(ERROR, status) << "Couldn't set up buffers";
DisconnectFromCamera();
return status;
}
// Create stream token. The stream token is not very meaningful when
// you have a direct connection to the driver, but this use case should
// be disappearing soon anyway. For now, we just hold on to the token.
zx::eventpair driver_token;
status = zx::eventpair::create(0, &stream_token_, &driver_token);
if (status != ZX_OK) {
FXL_PLOG(ERROR, status) << "Couldn't create driver token";
DisconnectFromCamera();
return status;
}
fuchsia::camera::VideoStream request = {.camera_id = camera_id,
.format = chosen_format};
status = camera_client_->manager_->CreateStream(
request, std::move(buffer_collection),
camera_client_->stream_.NewRequest(), std::move(driver_token));
if (status != ZX_OK) {
FXL_PLOG(ERROR, status) << "Couldn't set camera format";
DisconnectFromCamera();
return status;
}
}
// Start streaming
camera_client_->stream_->Start();
return ZX_OK;
}
void VideoDisplay::DisconnectFromCamera() {
image_pipe_.Unbind();
camera_client_.reset();
}
} // namespace simple_camera