blob: 78acbf573e1ed817f78c2d32c7a2f67d0bcb14ae [file] [log] [blame]
/*
* Copyright 2020 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 <linux/videodev2.h>
#include <sysexits.h>
#include <cstring>
#include <string>
#include <base/bind.h>
#include <base/files/file.h>
#include <base/posix/safe_strerror.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <base/threading/sequenced_task_runner_handle.h>
#include "cros-camera/common.h"
#include "tools/connector_client/cros_camera_connector_client.h"
namespace {
std::string GetDrmFormatName(uint32_t fourcc) {
std::string result = "0000";
for (size_t i = 0; i < 4; i++, fourcc >>= 8) {
const char c = static_cast<char>(fourcc & 0xFF);
if (c <= 0x1f || c >= 0x7f) {
return base::StringPrintf("0x%x", fourcc);
}
result[i] = c;
}
return result;
}
} // namespace
namespace cros {
int OnGotCameraInfo(void* context,
const cros_cam_info_t* info,
int is_removed) {
auto* client = reinterpret_cast<CrosCameraConnectorClient*>(context);
if (is_removed) {
client->RemoveCamera(info->id);
return 0;
}
LOGF(INFO) << "Gotten camera info of " << info->id
<< " (name = " << info->name
<< ", format_count = " << info->format_count << ")";
for (int i = 0; i < info->format_count; i++) {
LOGF(INFO) << "format = " << GetDrmFormatName(info->format_info[i].fourcc)
<< ", width = " << info->format_info[i].width
<< ", height = " << info->format_info[i].height
<< ", fps = " << info->format_info[i].fps;
}
client->SetCamInfo(info);
return 0;
}
int OnCaptureResultAvailable(void* context,
const cros_cam_capture_result_t* result) {
static uint32_t frame_count = 0;
auto* client = reinterpret_cast<CrosCameraConnectorClient*>(context);
if (result->status != 0) {
LOGF(ERROR) << "Received an error notification: "
<< base::safe_strerror(-result->status);
if (result->status == -ENODEV) {
LOGF(ERROR)
<< "Device encountered a serious error. Starting capture again";
client->StartCapture();
}
return 0;
}
const cros_cam_frame_t* frame = result->frame;
CHECK_NE(frame, nullptr);
LOGF(INFO) << "Frame Available";
client->ProcessFrame(frame);
frame_count++;
if (frame_count == 10) {
frame_count = 0;
LOGF(INFO) << "Restarting capture";
client->RestartCapture();
}
return 0;
}
CrosCameraConnectorClient::CrosCameraConnectorClient()
: client_runner_(base::SequencedTaskRunnerHandle::Get()),
init_done_(false),
current_id_(-1),
capture_thread_("CamConnClient"),
num_restarts_(0) {}
int CrosCameraConnectorClient::OnInit() {
int res = brillo::Daemon::OnInit();
if (res != EX_OK) {
return res;
}
if (!capture_thread_.Start()) {
LOGF(FATAL) << "Failed to start capture thread";
}
const cros_cam_init_option_t option = {
.api_version = 0,
};
res = cros_cam_init(&option);
if (res != 0) {
return EX_UNAVAILABLE;
}
res = cros_cam_get_cam_info(&OnGotCameraInfo, this);
if (res != 0) {
return EX_UNAVAILABLE;
}
init_done_ = true;
CHECK(!camera_device_list_.empty());
for (auto id : camera_device_list_) {
auto& infos = format_info_map_[id];
auto& pending_capture = pending_captures_map_[id];
for (const auto& info : infos) {
pending_capture.push(info);
}
}
StartCapture();
return EX_OK;
}
void CrosCameraConnectorClient::OnShutdown(int* exit_code) {
capture_thread_.Stop();
cros_cam_exit();
}
void CrosCameraConnectorClient::SetCamInfo(const cros_cam_info_t* info) {
{
base::AutoLock camera_info_lock(camera_info_lock_);
base::AutoLock capture_lock(capture_lock_);
auto& pending_captures = pending_captures_map_[info->id];
pending_captures = {};
for (int i = 0; i < info->format_count; i++) {
pending_captures.push(info->format_info[i]);
}
if (std::find(camera_device_list_.begin(), camera_device_list_.end(),
info->id) == camera_device_list_.end()) {
LOGF(INFO) << "Camera added: " << info->id;
camera_device_list_.push_back(info->id);
}
}
if (init_done_) {
LOGF(INFO) << "Restarting capture";
RestartCapture();
}
}
void CrosCameraConnectorClient::RemoveCamera(int32_t id) {
base::AutoLock camera_info_lock(camera_info_lock_);
base::AutoLock capture_lock(capture_lock_);
auto it =
std::find(camera_device_list_.begin(), camera_device_list_.end(), id);
CHECK(it != camera_device_list_.end());
LOGF(INFO) << "Camera removed: " << (*it);
camera_device_list_.erase(it);
pending_captures_map_[id] = {};
}
void CrosCameraConnectorClient::ProcessFrame(const cros_cam_frame_t* frame) {
static const char kJpegFilePattern[] = "/tmp/connector_#.jpg";
static const char kNv12FilePattern[] = "/tmp/connector_#.yuv";
static int frame_iter = 0;
if (frame->format.fourcc == V4L2_PIX_FMT_MJPEG) {
std::string output_path(kJpegFilePattern);
base::ReplaceSubstringsAfterOffset(&output_path, /*start_offset=*/0, "#",
base::StringPrintf("%06u", frame_iter));
base::File file(base::FilePath(output_path),
base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
file.WriteAtCurrentPos(reinterpret_cast<char*>(frame->planes[0].data),
frame->planes[0].size);
LOGF(INFO) << "Saved JPEG: " << output_path
<< " (size = " << frame->planes[0].size << ")";
} else if (frame->format.fourcc == V4L2_PIX_FMT_NV12) {
std::string output_path(kNv12FilePattern);
base::ReplaceSubstringsAfterOffset(&output_path, /*start_offset=*/0, "#",
base::StringPrintf("%06u", frame_iter));
base::File file(base::FilePath(output_path),
base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
file.WriteAtCurrentPos(
reinterpret_cast<const char*>(frame->planes[0].data),
current_format_info_.height * frame->planes[0].stride);
file.WriteAtCurrentPos(
reinterpret_cast<const char*>(frame->planes[1].data),
(current_format_info_.height + 1) / 2 * frame->planes[1].stride);
LOGF(INFO) << "Saved YUV (NV12): " << output_path;
}
frame_iter++;
}
void CrosCameraConnectorClient::StartCapture() {
capture_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&CrosCameraConnectorClient::StartCaptureOnThread,
base::Unretained(this)));
}
void CrosCameraConnectorClient::RestartCapture() {
capture_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&CrosCameraConnectorClient::RestartCaptureOnThread,
base::Unretained(this)));
}
void CrosCameraConnectorClient::StartCaptureOnThread() {
CHECK(capture_thread_.task_runner()->BelongsToCurrentThread());
{
base::AutoLock capture_lock(capture_lock_);
// TODO(b/151047930): Test the start/stop capture sequence with gtest.
if (pending_captures_map_.empty()) {
return;
}
auto it = pending_captures_map_.begin();
while (it != pending_captures_map_.end() && it->second.empty()) {
pending_captures_map_.erase(it);
it = pending_captures_map_.begin();
}
if (it == pending_captures_map_.end()) {
return;
}
current_id_ = it->first;
current_format_info_ = it->second.front();
it->second.pop();
LOGF(INFO) << "Starting capture: device = " << current_id_
<< ", fourcc = " << GetDrmFormatName(current_format_info_.fourcc)
<< ", width = " << current_format_info_.width
<< ", height = " << current_format_info_.height
<< ", fps = " << current_format_info_.fps;
}
const cros_cam_capture_request_t request = {
.id = current_id_,
.format = &current_format_info_,
};
cros_cam_start_capture(&request, &OnCaptureResultAvailable, this);
}
void CrosCameraConnectorClient::StopCaptureOnThread() {
CHECK(capture_thread_.task_runner()->BelongsToCurrentThread());
base::AutoLock capture_lock(capture_lock_);
cros_cam_stop_capture(current_id_);
current_id_ = -1;
}
void CrosCameraConnectorClient::RestartCaptureOnThread() {
CHECK(capture_thread_.task_runner()->BelongsToCurrentThread());
++num_restarts_;
LOGF(INFO) << "Restarting capture #" << num_restarts_;
StopCaptureOnThread();
StartCaptureOnThread();
}
} // namespace cros
int main() {
cros::CrosCameraConnectorClient connector_client;
return connector_client.Run();
}