blob: 8d4d98e014742475ecc969000e9de8e4a203fdc9 [file] [log] [blame]
/* Copyright 2019 The Chromium OS 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 <cctype>
#include <libyuv.h>
#include <linux/videodev2.h>
#include <memory>
#include <mojo/core/embedder/embedder.h>
#include <mojo/public/cpp/system/platform_handle.h>
#include <utility>
#include "cros-camera/common.h"
#include "cros-camera/ipc_util.h"
#include "hal/ip/camera_device.h"
#include "hal/ip/camera_hal.h"
#include "hal/ip/metadata_handler.h"
namespace cros {
static int initialize(const camera3_device_t* dev,
const camera3_callback_ops_t* callback_ops) {
CameraDevice* device = reinterpret_cast<CameraDevice*>(dev->priv);
if (!device) {
LOGF(ERROR) << "Camera device is NULL";
return -ENODEV;
}
return device->Initialize(callback_ops);
}
static int configure_streams(const camera3_device_t* dev,
camera3_stream_configuration_t* stream_list) {
CameraDevice* device = reinterpret_cast<CameraDevice*>(dev->priv);
if (!device) {
LOGF(ERROR) << "Camera device is NULL";
return -ENODEV;
}
return device->ConfigureStreams(stream_list);
}
static const camera_metadata_t* construct_default_request_settings(
const camera3_device_t* dev, int type) {
CameraDevice* device = reinterpret_cast<CameraDevice*>(dev->priv);
if (!device) {
LOGF(ERROR) << "Camera device is NULL";
return nullptr;
}
return device->ConstructDefaultRequestSettings(type);
}
static int process_capture_request(const camera3_device_t* dev,
camera3_capture_request_t* request) {
CameraDevice* device = reinterpret_cast<CameraDevice*>(dev->priv);
if (!device) {
LOGF(ERROR) << "Camera device is NULL";
return -ENODEV;
}
return device->ProcessCaptureRequest(request);
}
static void dump(const camera3_device_t* dev, int fd) {}
static int flush(const camera3_device_t* dev) {
CameraDevice* device = reinterpret_cast<CameraDevice*>(dev->priv);
if (!device) {
LOGF(ERROR) << "Camera device is NULL";
return -ENODEV;
}
return device->Flush();
}
} // namespace cros
static camera3_device_ops_t g_camera_device_ops = {
.initialize = cros::initialize,
.configure_streams = cros::configure_streams,
.register_stream_buffers = nullptr,
.construct_default_request_settings =
cros::construct_default_request_settings,
.process_capture_request = cros::process_capture_request,
.get_metadata_vendor_tag_ops = nullptr,
.dump = cros::dump,
.flush = cros::flush,
.reserved = {},
};
namespace cros {
static int camera_device_close(struct hw_device_t* hw_device) {
camera3_device_t* dev = reinterpret_cast<camera3_device_t*>(hw_device);
CameraDevice* device = static_cast<CameraDevice*>(dev->priv);
if (!device) {
LOGF(ERROR) << "Camera device is NULL";
return -EIO;
}
device->Close();
return 0;
}
CameraDevice::CameraDevice(int id)
: open_(false),
id_(id),
camera3_device_(),
callback_ops_(nullptr),
format_(0),
width_(0),
height_(0),
binding_(this),
buffer_manager_(nullptr),
jpeg_(false),
jpeg_thread_("JPEG Processing") {
memset(&camera3_device_, 0, sizeof(camera3_device_));
camera3_device_.common.tag = HARDWARE_DEVICE_TAG;
camera3_device_.common.version = CAMERA_DEVICE_API_VERSION_3_3;
camera3_device_.common.close = cros::camera_device_close;
camera3_device_.common.module = nullptr;
camera3_device_.ops = &g_camera_device_ops;
camera3_device_.priv = this;
buffer_manager_ = CameraBufferManager::GetInstance();
}
int CameraDevice::Init(mojom::IpCameraDevicePtr ip_device,
const std::string& ip,
const std::string& name,
mojom::PixelFormat format,
int32_t width,
int32_t height,
double fps) {
ipc_task_runner_ = mojo::core::GetIOTaskRunner();
DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence());
ip_device_ = std::move(ip_device);
width_ = width;
height_ = height;
switch (format) {
case mojom::PixelFormat::JPEG:
jpeg_ = true;
FALLTHROUGH;
case mojom::PixelFormat::YUV_420:
format_ = HAL_PIXEL_FORMAT_YCbCr_420_888;
break;
default:
LOGF(ERROR) << "Unrecognized pixel format: " << format;
return -EINVAL;
}
static_metadata_ = MetadataHandler::CreateStaticMetadata(
ip, name, format_, width_, height_, fps);
if (jpeg_) {
if (!jpeg_thread_.StartWithOptions(
base::Thread::Options(base::MessagePumpType::IO, 0))) {
LOGF(ERROR) << "Failed to start jpeg processing thread";
return -ENODEV;
}
jpeg_thread_.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&CameraDevice::StartJpegProcessor,
base::Unretained(this)));
}
mojom::IpCameraFrameListenerPtr listener;
binding_.Bind(mojo::MakeRequest(&listener));
binding_.set_connection_error_handler(
base::Bind(&CameraDevice::OnConnectionError, base::Unretained(this)));
ip_device_.set_connection_error_handler(
base::Bind(&CameraDevice::OnConnectionError, base::Unretained(this)));
ip_device_->RegisterFrameListener(std::move(listener));
return 0;
}
void CameraDevice::Open(const hw_module_t* module, hw_device_t** hw_device) {
camera3_device_.common.module = const_cast<hw_module_t*>(module);
*hw_device = &camera3_device_.common;
open_ = true;
}
CameraDevice::~CameraDevice() {
DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence());
if (jpeg_thread_.IsRunning()) {
jpeg_thread_.Stop();
}
jda_.reset();
ip_device_.reset();
binding_.Close();
}
bool CameraDevice::IsOpen() {
return open_;
}
android::CameraMetadata* CameraDevice::GetStaticMetadata() {
return &static_metadata_;
}
int CameraDevice::Initialize(const camera3_callback_ops_t* callback_ops) {
callback_ops_ = callback_ops;
request_queue_.SetCallbacks(callback_ops_);
return 0;
}
void CameraDevice::Close() {
open_ = false;
request_queue_.Flush();
// If called from the HAL it won't be on the IPC thread, and we should tell
// the IP camera to stop streaming. If called from the IPC thread, it's
// because the connection was lost or the device was reported as disconnected,
// so no need to tell it to stop streaming (the pointer probably isn't valid
// anyway).
if (!ipc_task_runner_->RunsTasksInCurrentSequence()) {
auto return_val = Future<void>::Create(nullptr);
ipc_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&CameraDevice::StopStreamingOnIpcThread,
base::Unretained(this), return_val));
return_val->Wait(-1);
}
}
void CameraDevice::StopStreamingOnIpcThread(
scoped_refptr<Future<void>> return_val) {
DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence());
ip_device_->StopStreaming();
return_val->Set();
}
bool CameraDevice::ValidateStream(camera3_stream_t* stream) {
if (!stream) {
LOGFID(ERROR, id_) << "NULL stream";
return false;
}
if (stream->stream_type != CAMERA3_STREAM_OUTPUT) {
LOGFID(ERROR, id_) << "Unsupported stream type: " << stream->stream_type;
return false;
}
if (stream->width != width_) {
LOGFID(ERROR, id_) << "Unsupported stream width: " << stream->width;
return false;
}
if (stream->height != height_) {
LOGFID(ERROR, id_) << "Unsupported stream height: " << stream->height;
return false;
}
if (stream->format != format_) {
LOGFID(ERROR, id_) << "Unsupported stream format: " << stream->format;
return false;
}
if (stream->rotation != CAMERA3_STREAM_ROTATION_0) {
LOGFID(ERROR, id_) << "Unsupported stream rotation: " << stream->rotation;
return false;
}
return true;
}
int CameraDevice::ConfigureStreams(
camera3_stream_configuration_t* stream_list) {
DCHECK(!ipc_task_runner_->RunsTasksInCurrentSequence());
if (callback_ops_ == nullptr) {
LOGFID(ERROR, id_) << "Device is not initialized";
return -EINVAL;
}
if (stream_list == nullptr) {
LOGFID(ERROR, id_) << "Null stream list array";
return -EINVAL;
}
if (stream_list->num_streams != 1) {
LOGFID(ERROR, id_) << "Unsupported number of streams: "
<< stream_list->num_streams;
return -EINVAL;
}
if (stream_list->operation_mode != CAMERA3_STREAM_CONFIGURATION_NORMAL_MODE) {
LOGFID(ERROR, id_) << "Unsupported operation mode: "
<< stream_list->operation_mode;
return -EINVAL;
}
if (!ValidateStream(stream_list->streams[0])) {
return -EINVAL;
}
// TODO(pceballos): revisit these two values, the number of buffers may need
// to be adjusted by each different device
stream_list->streams[0]->usage |= GRALLOC_USAGE_SW_WRITE_OFTEN;
stream_list->streams[0]->max_buffers = 4;
auto return_val = Future<void>::Create(nullptr);
ipc_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&CameraDevice::StartStreamingOnIpcThread,
base::Unretained(this), return_val));
return_val->Wait(-1);
return 0;
}
void CameraDevice::StartStreamingOnIpcThread(
scoped_refptr<Future<void>> return_val) {
DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence());
ip_device_->StartStreaming();
return_val->Set();
}
const camera_metadata_t* CameraDevice::ConstructDefaultRequestSettings(
int type) {
if (type != CAMERA3_TEMPLATE_PREVIEW) {
LOGFID(ERROR, id_) << "Unsupported request template:" << type;
return nullptr;
}
return MetadataHandler::GetDefaultRequestSettings();
}
int CameraDevice::ProcessCaptureRequest(camera3_capture_request_t* request) {
if (!request) {
LOGFID(ERROR, id_) << "Received a NULL request";
return -EINVAL;
}
if (request->input_buffer) {
LOGFID(ERROR, id_) << "Input buffers are not supported";
return -EINVAL;
}
if (request->num_output_buffers != 1) {
LOGFID(ERROR, id_) << "Invalid number of output buffers: "
<< request->num_output_buffers;
return -EINVAL;
}
const camera3_stream_buffer_t* buffer = request->output_buffers;
if (!ValidateStream(buffer->stream)) {
return -EINVAL;
}
if (!open_) {
LOGFID(ERROR, id_) << "Device is not open";
return -ENODEV;
}
request_queue_.Push(request);
return 0;
}
int CameraDevice::Flush() {
request_queue_.Flush();
return 0;
}
void CameraDevice::CopyFromMappingToOutputBuffer(
base::ReadOnlySharedMemoryMapping* mapping, buffer_handle_t* buffer) {
buffer_manager_->Register(*buffer);
struct android_ycbcr ycbcr;
if (buffer_manager_->GetV4L2PixelFormat(*buffer) != V4L2_PIX_FMT_NV12) {
LOGF(FATAL)
<< "Output buffer is wrong pixel format, only NV12 is supported";
}
buffer_manager_->LockYCbCr(*buffer, 0, 0, 0, width_, height_, &ycbcr);
// Convert from I420 to NV12 while copying the buffer since the buffer manager
// allocates an NV12 buffer
const uint8_t* in_y = reinterpret_cast<const uint8_t*>(mapping->memory());
const uint8_t* in_u =
reinterpret_cast<const uint8_t*>(mapping->memory()) + width_ * height_;
const uint8_t* in_v = reinterpret_cast<const uint8_t*>(mapping->memory()) +
width_ * height_ * 5 / 4;
uint8_t* out_y = reinterpret_cast<uint8_t*>(ycbcr.y);
uint8_t* out_uv = reinterpret_cast<uint8_t*>(ycbcr.cb);
int res = libyuv::I420ToNV12(in_y, width_, in_u, width_ / 4, in_v, width_ / 4,
out_y, ycbcr.ystride, out_uv, ycbcr.cstride,
width_, height_);
if (res != 0) {
LOGF(ERROR) << "Conversion from I420 to NV12 returned error: " << res;
}
buffer_manager_->Unlock(*buffer);
buffer_manager_->Deregister(*buffer);
}
void CameraDevice::ReturnBufferOnIpcThread(int32_t id) {
ip_device_->ReturnBuffer(id);
}
void CameraDevice::DecodeJpeg(base::ReadOnlySharedMemoryRegion shm,
int32_t id,
uint32_t size) {
std::unique_ptr<CaptureRequest> request = request_queue_.Pop();
if (!request) {
ipc_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&CameraDevice::ReturnBufferOnIpcThread,
base::Unretained(this), id));
return;
}
buffer_handle_t* buffer = request->GetOutputBuffer()->buffer;
base::subtle::ScopedFDPair fd =
base::ReadOnlySharedMemoryRegion::TakeHandleForSerialization(
std::move(shm))
.PassPlatformHandle();
JpegDecodeAccelerator::Error err =
jda_->DecodeSync(fd.get().fd, size, 0, *buffer);
if (err == JpegDecodeAccelerator::Error::TRY_START_AGAIN) {
LOGFID(WARNING, id_) << "Restarting JPEG processor";
if (!jda_->Start()) {
LOGFID(ERROR, id_) << "Failed to restart JPEG processor";
} else {
err = jda_->DecodeSync(fd.get().fd, size, 0, *buffer);
}
}
if (err != JpegDecodeAccelerator::Error::NO_ERRORS) {
LOGFID(ERROR, id_) << "Jpeg decoder returned error";
request_queue_.NotifyError(std::move(request));
ipc_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&CameraDevice::ReturnBufferOnIpcThread,
base::Unretained(this), id));
return;
}
ipc_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&CameraDevice::ReturnBufferOnIpcThread,
base::Unretained(this), id));
// TODO(pceballos): Currently the JPEG decoder doesn't sync output buffer
// memory. Force it to sync by locking then unlocking it.
buffer_manager_->Register(*buffer);
struct android_ycbcr ycbcr;
buffer_manager_->LockYCbCr(*buffer, 0, 0, 0, width_, height_, &ycbcr);
buffer_manager_->Unlock(*buffer);
buffer_manager_->Deregister(*buffer);
request_queue_.NotifyCapture(std::move(request));
}
void CameraDevice::OnFrameCaptured(mojo::ScopedSharedBufferHandle shm_handle,
int32_t id,
uint32_t size) {
if (request_queue_.IsEmpty()) {
ip_device_->ReturnBuffer(id);
return;
}
base::ReadOnlySharedMemoryRegion shm =
mojo::UnwrapReadOnlySharedMemoryRegion(std::move(shm_handle));
if (!shm.IsValid()) {
LOGFID(ERROR, id_) << "Error receiving shared memory region";
ip_device_->ReturnBuffer(id);
return;
}
if (jpeg_) {
jpeg_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&CameraDevice::DecodeJpeg, base::Unretained(this),
std::move(shm), id, size));
return;
}
base::ReadOnlySharedMemoryMapping mapping = shm.Map();
if (!mapping.IsValid()) {
LOGFID(ERROR, id_) << "Error mapping shm, unable to handle captured frame";
ip_device_->ReturnBuffer(id);
return;
}
std::unique_ptr<CaptureRequest> request = request_queue_.Pop();
if (!request) {
ip_device_->ReturnBuffer(id);
return;
}
CopyFromMappingToOutputBuffer(&mapping, request->GetOutputBuffer()->buffer);
ip_device_->ReturnBuffer(id);
request_queue_.NotifyCapture(std::move(request));
}
void CameraDevice::OnConnectionError() {
LOGF(ERROR) << "Lost connection to IP Camera";
ip_device_.reset();
binding_.Close();
}
void CameraDevice::StartJpegProcessor() {
jda_ = JpegDecodeAccelerator::CreateInstance(
CameraHal::GetInstance().GetMojoManagerToken());
if (!jda_->Start()) {
LOGF(ERROR) << "Error starting JPEG processor";
}
}
} // namespace cros