blob: 5eaef49bfc528cd803bb0de19431782a8e19911c [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/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 cros {
int OnGotCameraInfo(void* context,
const cros_cam_info_t* info,
int is_removed) {
// TODO(b/151047930): Support hot-plugging when external camera is supported.
CHECK_EQ(is_removed, 0u);
auto 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;
};
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;
}
auto* client = reinterpret_cast<CrosCameraConnectorClient*>(context);
client->SetCamInfo(info);
return 0;
}
int OnCaptureResultAvailable(void* context,
const cros_cam_capture_result_t* result) {
static uint32_t frame_count = 0;
CHECK_EQ(result->status, 0);
const cros_cam_frame_t* frame = result->frame;
CHECK_NE(frame, nullptr);
LOGF(INFO) << "Frame Available";
auto* client = reinterpret_cast<CrosCameraConnectorClient*>(context);
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()),
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;
}
CHECK(!camera_device_list_.empty());
request_device_iter_ = camera_device_list_.begin();
request_format_iter_ = format_info_map_[*request_device_iter_].begin();
while (request_device_iter_ != camera_device_list_.end() &&
request_format_iter_ ==
format_info_map_[*request_device_iter_].end()) {
request_device_iter_++;
request_format_iter_ = format_info_map_[*request_device_iter_].begin();
}
CHECK(request_device_iter_ != camera_device_list_.end());
capture_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&CrosCameraConnectorClient::StartCaptureOnThread,
base::Unretained(this)));
return EX_OK;
}
void CrosCameraConnectorClient::OnShutdown(int* exit_code) {
capture_thread_.Stop();
cros_cam_exit();
}
void CrosCameraConnectorClient::SetCamInfo(const cros_cam_info_t* info) {
camera_device_list_.push_back(info->id);
auto& format_info = format_info_map_[info->id];
format_info = {info->format_info, info->format_info + info->format_count};
}
void CrosCameraConnectorClient::RestartCapture() {
capture_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&CrosCameraConnectorClient::RestartCaptureOnThread,
base::Unretained(this)));
}
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),
request_format_iter_->height * frame->planes[0].stride);
file.WriteAtCurrentPos(
reinterpret_cast<const char*>(frame->planes[1].data),
(request_format_iter_->height + 1) / 2 * frame->planes[1].stride);
LOGF(INFO) << "Saved YUV (NV12): " << output_path;
}
frame_iter++;
}
void CrosCameraConnectorClient::StartCaptureOnThread() {
CHECK(capture_thread_.task_runner()->BelongsToCurrentThread());
LOGF(INFO) << "Startin capture: device = " << (*request_device_iter_)
<< ", fourcc = " << request_format_iter_->fourcc
<< ", width = " << request_format_iter_->width
<< ", height = " << request_format_iter_->height
<< ", fps = " << request_format_iter_->fps;
const cros_cam_capture_request_t request = {
.id = *request_device_iter_,
.format = &(*request_format_iter_),
};
cros_cam_start_capture(&request, &OnCaptureResultAvailable, this);
}
void CrosCameraConnectorClient::StopCaptureOnThread() {
CHECK(capture_thread_.task_runner()->BelongsToCurrentThread());
cros_cam_stop_capture(*request_device_iter_);
}
void CrosCameraConnectorClient::RestartCaptureOnThread() {
CHECK(capture_thread_.task_runner()->BelongsToCurrentThread());
++num_restarts_;
LOGF(INFO) << "Restarting capture #" << num_restarts_;
StopCaptureOnThread();
// TODO(b/151047930): Test the start/stop capture sequence with gtest.
request_format_iter_++;
if (request_format_iter_ == format_info_map_[*request_device_iter_].end()) {
request_device_iter_++;
if (request_device_iter_ == camera_device_list_.end()) {
LOGF(INFO) << "Finished all captures";
client_runner_->PostTask(FROM_HERE,
base::BindOnce(&CrosCameraConnectorClient::Quit,
base::Unretained(this)));
return;
}
request_format_iter_ = format_info_map_[*request_device_iter_].begin();
}
StartCaptureOnThread();
}
} // namespace cros
int main() {
cros::CrosCameraConnectorClient connector_client;
return connector_client.Run();
}