| // 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 <garnet/lib/media/camera/simple_camera_lib/fake-control-impl.h> |
| #include <garnet/lib/media/camera/simple_camera_lib/video_display.h> |
| |
| #include <fcntl.h> |
| #include <lib/fxl/files/unique_fd.h> |
| #include <lib/fxl/log_level.h> |
| #include <lib/fxl/logging.h> |
| #include <lib/fxl/strings/string_printf.h> |
| |
| #define ROUNDUP(a, b) (((a) + ((b)-1)) & ~((b)-1)) |
| |
| namespace simple_camera { |
| |
| const bool use_fake_camera = false; |
| |
| // 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_LOG(ERROR) << "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) { |
| // auto image_info = fuchsia::images::ImageInfo::New(); |
| 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_LOG(ERROR) << "Failed to duplicate vmo (status: " << status << ")."; |
| 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; |
| } |
| |
| zx_status_t VideoDisplay::OpenCamera(int dev_id) { |
| std::string dev_path = fxl::StringPrintf("/dev/class/camera/%03u", dev_id); |
| fxl::UniqueFD dev_node(::open(dev_path.c_str(), O_RDONLY)); |
| if (!dev_node.is_valid()) { |
| FXL_LOG(ERROR) << "Client::Open failed to open device node at \"" |
| << dev_path << "\". (" << strerror(errno) << " : " << errno |
| << ")"; |
| return ZX_ERR_IO; |
| } |
| |
| zx::channel channel; |
| ssize_t res = |
| ioctl_camera_get_channel(dev_node.get(), channel.reset_and_get_address()); |
| if (res < 0) { |
| FXL_LOG(ERROR) << "Failed to obtain channel (res " << res << ")"; |
| return static_cast<zx_status_t>(res); |
| } |
| |
| camera_client_->control_.Bind(std::move(channel)); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t VideoDisplay::OpenFakeCamera() { |
| // CameraStream FIDL interface |
| static fbl::unique_ptr<simple_camera::FakeControlImpl> |
| fake_camera_control_server_ = nullptr; |
| // Loop used to run the FIDL server |
| static fbl::unique_ptr<async::Loop> fidl_dispatch_loop_; |
| |
| FXL_LOG(INFO) << "Opening Fake Camera"; |
| |
| if (fake_camera_control_server_ != nullptr) { |
| FXL_LOG(ERROR) << "Camera Control already running"; |
| // TODO(CAM-XXX): support multiple concurrent clients. |
| return ZX_ERR_ACCESS_DENIED; |
| } |
| |
| if (fidl_dispatch_loop_ == nullptr) { |
| fidl_dispatch_loop_ = |
| fbl::make_unique<async::Loop>(&kAsyncLoopConfigNoAttachToThread); |
| fidl_dispatch_loop_->StartThread(); |
| } |
| |
| fidl::InterfaceHandle<fuchsia::camera::Control> control_handle; |
| fidl::InterfaceRequest<fuchsia::camera::Control> control_interface = |
| control_handle.NewRequest(); |
| |
| if (control_interface.is_valid()) { |
| FXL_LOG(INFO) << "Starting Fake Camera Server"; |
| fake_camera_control_server_ = |
| fbl::make_unique<simple_camera::FakeControlImpl>( |
| std::move(control_interface), fidl_dispatch_loop_->dispatcher(), |
| [] { |
| FXL_LOG(INFO) << "Deleting Fake Camera Server"; |
| fake_camera_control_server_.reset(); |
| }); |
| |
| FXL_LOG(INFO) << "Binding camera_control_ to control_handle"; |
| camera_client_->control_.Bind(control_handle.TakeChannel()); |
| |
| return ZX_OK; |
| } else { |
| return ZX_ERR_NO_RESOURCES; |
| } |
| } |
| |
| // TODO(CAM-9): Clean up this function after major changes land. |
| zx_status_t VideoDisplay::ConnectToCamera( |
| 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 = |
| [video_display = this](fuchsia::camera::FrameAvailableEvent frame) { |
| video_display->IncomingBufferFilled(frame); |
| }; |
| |
| camera_client_->stream_.set_error_handler([this](zx_status_t status) { |
| DisconnectFromCamera(); |
| on_shut_down_callback_(); |
| }); |
| |
| // Open a connection to the Camera |
| zx_status_t status = |
| use_fake_camera ? OpenFakeCamera() : OpenCamera(camera_id); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Couldn't open camera client (status " << status << ")"; |
| DisconnectFromCamera(); |
| return status; |
| } |
| |
| // Figure out a format |
| std::vector<fuchsia::camera::VideoFormat> formats; |
| { |
| zx_status_t driver_status; |
| uint32_t total_format_count; |
| uint32_t format_index = 0; |
| do { |
| std::vector<fuchsia::camera::VideoFormat> formats_ptr; |
| status = camera_client_->control_->GetFormats( |
| format_index, &formats_ptr, &total_format_count, &driver_status); |
| if (status != ZX_OK || driver_status != ZX_OK) { |
| FXL_LOG(ERROR) << "Couldn't get camera formats (status " << status |
| << " : " << driver_status << ")"; |
| 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_LOG(ERROR) << "Couldn't allocate buffers. status: " << status; |
| DisconnectFromCamera(); |
| return status; |
| } |
| |
| status = SetupBuffers(buffer_collection); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Couldn't set up buffers. status: " << status; |
| 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_LOG(ERROR) << "Couldn't create driver token. status: " << status; |
| DisconnectFromCamera(); |
| return status; |
| } |
| status = camera_client_->control_->CreateStream( |
| std::move(buffer_collection), chosen_format.rate, |
| camera_client_->stream_.NewRequest(), std::move(driver_token)); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Couldn't set camera format. status: " << status; |
| DisconnectFromCamera(); |
| return status; |
| } |
| } |
| |
| // Start streaming |
| camera_client_->stream_->Start(); |
| |
| FXL_LOG(INFO) << "Camera Client Initialization Successful!"; |
| |
| return ZX_OK; |
| } |
| |
| void VideoDisplay::DisconnectFromCamera() { |
| image_pipe_.Unbind(); |
| camera_client_.reset(); |
| } |
| |
| } // namespace simple_camera |