| // 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 |