libcamera: virtual: Add VirtualPipelineHandler

Add VirtualPipelineHandler for more unit tests and verfiy libcamera
infrastructure works on devices without using hardware cameras.

Signed-off-by: Harvey Yang <chenghaoyang@chromium.org>
Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
This commit is contained in:
Harvey Yang
2024-10-22 07:43:39 +00:00
committed by Kieran Bingham
parent 670bbf3dc2
commit 3a884ebfe4
5 changed files with 390 additions and 1 deletions
+337
View File
@@ -0,0 +1,337 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2024, Google Inc.
*
* Pipeline handler for virtual cameras
*/
#include "virtual.h"
#include <algorithm>
#include <array>
#include <chrono>
#include <errno.h>
#include <map>
#include <memory>
#include <ostream>
#include <set>
#include <stdint.h>
#include <string>
#include <time.h>
#include <utility>
#include <vector>
#include <libcamera/base/flags.h>
#include <libcamera/base/log.h>
#include <libcamera/control_ids.h>
#include <libcamera/controls.h>
#include <libcamera/formats.h>
#include <libcamera/pixel_format.h>
#include <libcamera/property_ids.h>
#include <libcamera/request.h>
#include "libcamera/internal/camera.h"
#include "libcamera/internal/dma_buf_allocator.h"
#include "libcamera/internal/formats.h"
#include "libcamera/internal/pipeline_handler.h"
namespace libcamera {
LOG_DEFINE_CATEGORY(Virtual)
namespace {
uint64_t currentTimestamp()
{
const auto now = std::chrono::steady_clock::now();
auto nsecs = std::chrono::duration_cast<std::chrono::nanoseconds>(
now.time_since_epoch());
return nsecs.count();
}
} /* namespace */
class VirtualCameraConfiguration : public CameraConfiguration
{
public:
static constexpr unsigned int kBufferCount = 4;
VirtualCameraConfiguration(VirtualCameraData *data);
Status validate() override;
private:
const VirtualCameraData *data_;
};
class PipelineHandlerVirtual : public PipelineHandler
{
public:
PipelineHandlerVirtual(CameraManager *manager);
~PipelineHandlerVirtual();
std::unique_ptr<CameraConfiguration> generateConfiguration(Camera *camera,
Span<const StreamRole> roles) override;
int configure(Camera *camera, CameraConfiguration *config) override;
int exportFrameBuffers(Camera *camera, Stream *stream,
std::vector<std::unique_ptr<FrameBuffer>> *buffers) override;
int start(Camera *camera, const ControlList *controls) override;
void stopDevice(Camera *camera) override;
int queueRequestDevice(Camera *camera, Request *request) override;
bool match(DeviceEnumerator *enumerator) override;
private:
static bool created_;
VirtualCameraData *cameraData(Camera *camera)
{
return static_cast<VirtualCameraData *>(camera->_d());
}
DmaBufAllocator dmaBufAllocator_;
bool resetCreated_ = false;
};
VirtualCameraData::VirtualCameraData(PipelineHandler *pipe,
const std::vector<Resolution> &supportedResolutions)
: Camera::Private(pipe), supportedResolutions_(supportedResolutions)
{
for (const auto &resolution : supportedResolutions_) {
if (minResolutionSize_.isNull() || minResolutionSize_ > resolution.size)
minResolutionSize_ = resolution.size;
maxResolutionSize_ = std::max(maxResolutionSize_, resolution.size);
}
/* \todo Support multiple streams and pass multi_stream_test */
streamConfigs_.resize(kMaxStream);
}
VirtualCameraConfiguration::VirtualCameraConfiguration(VirtualCameraData *data)
: CameraConfiguration(), data_(data)
{
}
CameraConfiguration::Status VirtualCameraConfiguration::validate()
{
Status status = Valid;
if (config_.empty()) {
LOG(Virtual, Error) << "Empty config";
return Invalid;
}
/* Only one stream is supported */
if (config_.size() > VirtualCameraData::kMaxStream) {
config_.resize(VirtualCameraData::kMaxStream);
status = Adjusted;
}
for (StreamConfiguration &cfg : config_) {
bool adjusted = false;
bool found = false;
for (const auto &resolution : data_->supportedResolutions_) {
if (resolution.size.width == cfg.size.width &&
resolution.size.height == cfg.size.height) {
found = true;
break;
}
}
if (!found) {
/*
* \todo It's a pipeline's decision to choose a
* resolution when the exact one is not supported.
* Defining the default logic in PipelineHandler to
* find the closest resolution would be nice.
*/
cfg.size = data_->maxResolutionSize_;
status = Adjusted;
adjusted = true;
}
if (cfg.pixelFormat != formats::NV12) {
cfg.pixelFormat = formats::NV12;
status = Adjusted;
adjusted = true;
}
if (adjusted)
LOG(Virtual, Info)
<< "Stream configuration adjusted to " << cfg.toString();
const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat);
cfg.stride = info.stride(cfg.size.width, 0, 1);
cfg.frameSize = info.frameSize(cfg.size, 1);
cfg.bufferCount = VirtualCameraConfiguration::kBufferCount;
}
return status;
}
/* static */
bool PipelineHandlerVirtual::created_ = false;
PipelineHandlerVirtual::PipelineHandlerVirtual(CameraManager *manager)
: PipelineHandler(manager),
dmaBufAllocator_(DmaBufAllocator::DmaBufAllocatorFlag::CmaHeap |
DmaBufAllocator::DmaBufAllocatorFlag::SystemHeap |
DmaBufAllocator::DmaBufAllocatorFlag::UDmaBuf)
{
}
PipelineHandlerVirtual::~PipelineHandlerVirtual()
{
if (resetCreated_)
created_ = false;
}
std::unique_ptr<CameraConfiguration>
PipelineHandlerVirtual::generateConfiguration(Camera *camera,
Span<const StreamRole> roles)
{
VirtualCameraData *data = cameraData(camera);
auto config = std::make_unique<VirtualCameraConfiguration>(data);
if (roles.empty())
return config;
for (const StreamRole role : roles) {
switch (role) {
case StreamRole::StillCapture:
case StreamRole::VideoRecording:
case StreamRole::Viewfinder:
break;
case StreamRole::Raw:
default:
LOG(Virtual, Error)
<< "Requested stream role not supported: " << role;
config.reset();
return config;
}
std::map<PixelFormat, std::vector<SizeRange>> streamFormats;
PixelFormat pixelFormat = formats::NV12;
streamFormats[pixelFormat] = { { data->minResolutionSize_,
data->maxResolutionSize_ } };
StreamFormats formats(streamFormats);
StreamConfiguration cfg(formats);
cfg.pixelFormat = pixelFormat;
cfg.size = data->maxResolutionSize_;
cfg.bufferCount = VirtualCameraConfiguration::kBufferCount;
config->addConfiguration(cfg);
}
ASSERT(config->validate() != CameraConfiguration::Invalid);
return config;
}
int PipelineHandlerVirtual::configure(Camera *camera,
CameraConfiguration *config)
{
VirtualCameraData *data = cameraData(camera);
for (auto [i, c] : utils::enumerate(*config))
c.setStream(&data->streamConfigs_[i].stream);
return 0;
}
int PipelineHandlerVirtual::exportFrameBuffers([[maybe_unused]] Camera *camera,
Stream *stream,
std::vector<std::unique_ptr<FrameBuffer>> *buffers)
{
if (!dmaBufAllocator_.isValid())
return -ENOBUFS;
const StreamConfiguration &config = stream->configuration();
auto info = PixelFormatInfo::info(config.pixelFormat);
std::vector<unsigned int> planeSizes;
for (size_t i = 0; i < info.planes.size(); ++i)
planeSizes.push_back(info.planeSize(config.size, i));
return dmaBufAllocator_.exportBuffers(config.bufferCount, planeSizes, buffers);
}
int PipelineHandlerVirtual::start([[maybe_unused]] Camera *camera,
[[maybe_unused]] const ControlList *controls)
{
return 0;
}
void PipelineHandlerVirtual::stopDevice([[maybe_unused]] Camera *camera)
{
}
int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera,
Request *request)
{
for (auto it : request->buffers())
completeBuffer(request, it.second);
request->metadata().set(controls::SensorTimestamp, currentTimestamp());
completeRequest(request);
return 0;
}
bool PipelineHandlerVirtual::match([[maybe_unused]] DeviceEnumerator *enumerator)
{
if (created_)
return false;
created_ = true;
/* \todo Add virtual cameras according to a config file. */
std::vector<VirtualCameraData::Resolution> supportedResolutions;
supportedResolutions.resize(2);
supportedResolutions[0] = { .size = Size(1920, 1080), .frameRates = { 30 } };
supportedResolutions[1] = { .size = Size(1280, 720), .frameRates = { 30 } };
std::unique_ptr<VirtualCameraData> data =
std::make_unique<VirtualCameraData>(this, supportedResolutions);
data->properties_.set(properties::Location, properties::CameraLocationFront);
data->properties_.set(properties::Model, "Virtual Video Device");
data->properties_.set(properties::PixelArrayActiveAreas, { Rectangle(Size(1920, 1080)) });
/* \todo Set FrameDurationLimits based on config. */
ControlInfoMap::Map controls;
int64_t min_frame_duration = 33333, max_frame_duration = 33333;
controls[&controls::FrameDurationLimits] = ControlInfo(min_frame_duration, max_frame_duration);
std::vector<ControlValue> supportedFaceDetectModes{
static_cast<int32_t>(controls::draft::FaceDetectModeOff),
};
controls[&controls::draft::FaceDetectMode] = ControlInfo(supportedFaceDetectModes);
data->controlInfo_ = ControlInfoMap(std::move(controls), controls::controls);
/* Create and register the camera. */
std::set<Stream *> streams;
for (auto &streamConfig : data->streamConfigs_)
streams.insert(&streamConfig.stream);
const std::string id = "Virtual0";
std::shared_ptr<Camera> camera = Camera::create(std::move(data), id, streams);
registerCamera(std::move(camera));
resetCreated_ = true;
return true;
}
REGISTER_PIPELINE_HANDLER(PipelineHandlerVirtual, "virtual")
} /* namespace libcamera */