@@ -7,6 +7,7 @@
#include "config_parser.h"
+#include <filesystem>
#include <string.h>
#include <utility>
@@ -24,6 +25,38 @@ namespace libcamera {
LOG_DECLARE_CATEGORY(Virtual)
+namespace {
+
+std::optional<std::vector<std::filesystem::path>> collectFiles(const std::string &path)
+{
+ std::vector<std::filesystem::path> files;
+
+ switch (std::filesystem::symlink_status(path).type()) {
+ case std::filesystem::file_type::regular:
+ files.push_back(path);
+ break;
+
+ case std::filesystem::file_type::directory:
+ for (const auto &dentry : std::filesystem::directory_iterator{ path })
+ if (dentry.is_regular_file())
+ files.push_back(dentry.path());
+
+ std::sort(files.begin(), files.end(), [](const auto &a, const auto &b) {
+ return ::strverscmp(a.c_str(), b.c_str()) < 0;
+ });
+
+ if (files.empty())
+ return std::nullopt;
+ break;
+
+ default:
+ return std::nullopt;
+ }
+ return files;
+}
+
+} /* namespace */
+
std::vector<std::unique_ptr<VirtualCameraData>>
ConfigParser::parseConfigFile(File &file, PipelineHandler *pipe)
{
@@ -156,13 +189,20 @@ int ConfigParser::parseFrameGenerator(const ValueNode &cameraConfigData, Virtual
{
const std::string testPatternKey = "test_pattern";
const std::string framesKey = "frames";
- if (cameraConfigData.contains(testPatternKey)) {
- if (cameraConfigData.contains(framesKey)) {
- LOG(Virtual, Error) << "A camera should use either "
- << testPatternKey << " or " << framesKey;
- return -EINVAL;
- }
+ const std::string rawFramesKey = "raw_frames";
+
+ /* Ensure only one frame source is specified */
+ int sourcesSpecified = cameraConfigData.contains(testPatternKey) +
+ cameraConfigData.contains(framesKey) +
+ cameraConfigData.contains(rawFramesKey);
+ if (sourcesSpecified > 1) {
+ LOG(Virtual, Error) << "A camera should use only one of "
+ << testPatternKey << ", " << framesKey
+ << ", or " << rawFramesKey;
+ return -EINVAL;
+ }
+ if (cameraConfigData.contains(testPatternKey)) {
auto testPattern = cameraConfigData[testPatternKey].get<std::string>("");
if (testPattern == "bars") {
@@ -178,6 +218,75 @@ int ConfigParser::parseFrameGenerator(const ValueNode &cameraConfigData, Virtual
return 0;
}
+ if (cameraConfigData.contains(rawFramesKey)) {
+ const ValueNode &rawFrames = cameraConfigData[rawFramesKey];
+
+ if (!rawFrames.isDictionary()) {
+ LOG(Virtual, Error) << "'raw_frames' is not a dictionary.";
+ return -EINVAL;
+ }
+
+ auto path = rawFrames["path"].get<std::string>();
+ if (!path) {
+ LOG(Virtual, Error) << "raw_frames: path must be specified.";
+ return -EINVAL;
+ }
+
+ auto type = std::filesystem::symlink_status(*path).type();
+ if (type != std::filesystem::file_type::regular &&
+ type != std::filesystem::file_type::directory) {
+ LOG(Virtual, Error) << "raw_frames path: " << *path << " is not supported";
+ return -EINVAL;
+ }
+
+ auto files = collectFiles(*path);
+
+ if (!files) {
+ LOG(Virtual, Error) << "raw_frames directory has no files: " << *path;
+ return -EINVAL;
+ }
+
+ /* Parse bayer_order */
+ auto bayerOrder = rawFrames["bayer_order"].get<std::string>();
+ if (!bayerOrder) {
+ LOG(Virtual, Error) << "raw_frames: bayer_order must be specified.";
+ return -EINVAL;
+ }
+
+ static const std::map<std::string, uint32_t> bayerOrderMap = {
+ { "RGGB", properties::draft::ColorFilterArrangementEnum::RGGB },
+ { "BGGR", properties::draft::ColorFilterArrangementEnum::BGGR },
+ { "GRBG", properties::draft::ColorFilterArrangementEnum::GRBG },
+ { "GBRG", properties::draft::ColorFilterArrangementEnum::GBRG },
+ };
+
+ auto it = bayerOrderMap.find(*bayerOrder);
+ if (it == bayerOrderMap.end()) {
+ LOG(Virtual, Error) << "raw_frames: unsupported bayer_order: "
+ << *bayerOrder
+ << ", must be one of RGGB, BGGR, GRBG, GBRG";
+ return -EINVAL;
+ }
+
+ /* Parse bit_depth */
+ auto bitDepth = rawFrames["bit_depth"].get<unsigned int>();
+ if (!bitDepth) {
+ LOG(Virtual, Error) << "raw_frames: bit_depth must be specified.";
+ return -EINVAL;
+ }
+
+ static const std::set<unsigned int> supportedBitDepths = { 8, 10, 12, 14, 16 };
+ if (supportedBitDepths.find(*bitDepth) == supportedBitDepths.end()) {
+ LOG(Virtual, Error) << "raw_frames: bit_depth unsupported: " << *bitDepth
+ << ", must be one of 8, 10, 12, 14, 16";
+ return -EINVAL;
+ }
+
+ data->config_.frame = RawFrames{ std::move(*files), it->second, *bitDepth };
+
+ return 0;
+ }
+
const ValueNode &frames = cameraConfigData[framesKey];
/* When there is no frames provided in the config file, use color bar test pattern */
@@ -198,35 +307,20 @@ int ConfigParser::parseFrameGenerator(const ValueNode &cameraConfigData, Virtual
return -EINVAL;
}
- std::vector<std::filesystem::path> files;
-
- switch (std::filesystem::symlink_status(*path).type()) {
- case std::filesystem::file_type::regular:
- files.push_back(*path);
- break;
-
- case std::filesystem::file_type::directory:
- for (const auto &dentry : std::filesystem::directory_iterator{ *path }) {
- if (dentry.is_regular_file())
- files.push_back(dentry.path());
- }
-
- std::sort(files.begin(), files.end(), [](const auto &a, const auto &b) {
- return ::strverscmp(a.c_str(), b.c_str()) < 0;
- });
-
- if (files.empty()) {
- LOG(Virtual, Error) << "Directory has no files: " << *path;
- return -EINVAL;
- }
- break;
-
- default:
+ auto type = std::filesystem::symlink_status(*path).type();
+ if (type != std::filesystem::file_type::regular &&
+ type != std::filesystem::file_type::directory) {
LOG(Virtual, Error) << "Frame: " << *path << " is not supported";
return -EINVAL;
}
- data->config_.frame = ImageFrames{ std::move(files) };
+ auto files = collectFiles(*path);
+ if (!files) {
+ LOG(Virtual, Error) << "Directory has no files: " << *path;
+ return -EINVAL;
+ }
+
+ data->config_.frame = ImageFrames{ std::move(*files) };
return 0;
}
@@ -19,6 +19,7 @@
#include <string>
#include <time.h>
#include <utility>
+#include <variant>
#include <vector>
#include <libcamera/base/flags.h>
@@ -31,13 +32,13 @@
#include <libcamera/pixel_format.h>
#include <libcamera/property_ids.h>
+#include "libcamera/internal/bayer_format.h"
#include "libcamera/internal/camera.h"
#include "libcamera/internal/dma_buf_allocator.h"
#include "libcamera/internal/formats.h"
#include "libcamera/internal/framebuffer.h"
#include "libcamera/internal/pipeline_handler.h"
#include "libcamera/internal/request.h"
-#include "libcamera/internal/value_node.h"
#include "pipeline/virtual/config_parser.h"
@@ -202,21 +203,28 @@ CameraConfiguration::Status VirtualCameraConfiguration::validate()
adjusted = true;
}
- if (cfg.pixelFormat != formats::NV12) {
- cfg.pixelFormat = formats::NV12;
- status = Adjusted;
- adjusted = true;
- }
+ const PixelFormatInfo &fmtInfo = PixelFormatInfo::info(cfg.pixelFormat);
+ const bool rawStream = fmtInfo.colourEncoding == PixelFormatInfo::ColourEncodingRAW;
- if (cfg.colorSpace != ColorSpace::Rec709) {
- cfg.colorSpace = ColorSpace::Rec709;
- status = Adjusted;
- adjusted = true;
- }
+ if (!rawStream) {
+ if (cfg.pixelFormat != formats::NV12) {
+ cfg.pixelFormat = formats::NV12;
+ status = Adjusted;
+ adjusted = true;
+ }
- if (validateColorSpaces() == Adjusted) {
- status = Adjusted;
- adjusted = true;
+ if (cfg.colorSpace != ColorSpace::Rec709) {
+ cfg.colorSpace = ColorSpace::Rec709;
+ status = Adjusted;
+ adjusted = true;
+ }
+
+ if (validateColorSpaces() == Adjusted) {
+ status = Adjusted;
+ adjusted = true;
+ }
+ } else {
+ cfg.colorSpace = ColorSpace::Raw;
}
if (adjusted)
@@ -267,7 +275,54 @@ PipelineHandlerVirtual::generateConfiguration(Camera *camera,
case StreamRole::Viewfinder:
break;
- case StreamRole::Raw:
+ case StreamRole::Raw: {
+ const auto *rawFrames = std::get_if<RawFrames>(&data->config_.frame);
+ if (!rawFrames) {
+ LOG(Virtual, Error)
+ << "StreamRole::Raw requested but camera is not configured with raw_frames";
+ return {};
+ }
+
+ static const std::map<uint32_t, BayerFormat::Order> cfaToOrder = {
+ { properties::draft::ColorFilterArrangementEnum::RGGB, BayerFormat::RGGB },
+ { properties::draft::ColorFilterArrangementEnum::BGGR, BayerFormat::BGGR },
+ { properties::draft::ColorFilterArrangementEnum::GRBG, BayerFormat::GRBG },
+ { properties::draft::ColorFilterArrangementEnum::GBRG, BayerFormat::GBRG },
+ };
+
+ auto it = cfaToOrder.find(rawFrames->cfaPattern);
+ if (it == cfaToOrder.end()) {
+ LOG(Virtual, Error) << "Unsupported CFA pattern";
+ return {};
+ }
+
+ PixelFormat rawFormat =
+ BayerFormat(it->second, rawFrames->bitDepth,
+ BayerFormat::Packing::None)
+ .toPixelFormat();
+
+ if (!rawFormat.isValid()) {
+ LOG(Virtual, Error) << "Could not find PixelFormat for Bayer format";
+ return {};
+ }
+
+ /*
+ * Use the Bayer format matching the raw frame
+ * configuration.
+ */
+ std::map<PixelFormat, std::vector<SizeRange>> rawStreamFormats;
+ rawStreamFormats[rawFormat] = { { data->config_.minResolutionSize,
+ data->config_.maxResolutionSize } };
+ StreamFormats rawFormats(rawStreamFormats);
+ StreamConfiguration rawCfg(rawFormats);
+ rawCfg.pixelFormat = rawFormat;
+ rawCfg.size = data->config_.maxResolutionSize;
+ rawCfg.bufferCount = VirtualCameraConfiguration::kBufferCount;
+ rawCfg.colorSpace = ColorSpace::Raw;
+ config->addConfiguration(rawCfg);
+ continue;
+ }
+
default:
LOG(Virtual, Error)
<< "Requested stream role not supported: " << role;
@@ -401,6 +456,13 @@ bool PipelineHandlerVirtual::match([[maybe_unused]] DeviceEnumerator *enumerator
std::set<Stream *> streams;
for (auto &streamConfig : data->streamConfigs_)
streams.insert(&streamConfig.stream);
+
+ if (const auto *rawFrames = std::get_if<RawFrames>(&data->config_.frame)) {
+ data->properties_.set(properties::draft::ColorFilterArrangement, static_cast<int32_t>(rawFrames->cfaPattern));
+ data->properties_.set(properties::PixelArraySize, data->config_.maxResolutionSize);
+ data->properties_.set(properties::UnitCellSize, Size(1000, 1000));
+ }
+
std::string id = data->config_.id;
std::shared_ptr<Camera> camera = Camera::create(std::move(data), id, streams);
@@ -434,7 +496,12 @@ bool PipelineHandlerVirtual::initFrameGenerator(Camera *camera)
[&](ImageFrames &imageFrames) {
for (auto &streamConfig : data->streamConfigs_)
streamConfig.frameGenerator = ImageFrameGenerator::create(imageFrames);
- } },
+ },
+ [&](RawFrames &rawFrames) {
+ for (auto &streamConfig : data->streamConfigs_)
+ streamConfig.frameGenerator = RawFrameGenerator::create(rawFrames);
+ },
+ },
frame);
for (auto &streamConfig : data->streamConfigs_)
@@ -23,11 +23,12 @@
#include "frame_generator.h"
#include "image_frame_generator.h"
+#include "raw_frame_generator.h"
#include "test_pattern_generator.h"
namespace libcamera {
-using VirtualFrame = std::variant<TestPattern, ImageFrames>;
+using VirtualFrame = std::variant<TestPattern, ImageFrames, RawFrames>;
class VirtualCameraData : public Camera::Private,
public Thread,
Extend parseFrameGenerator() to handle the new raw_frames YAML key alongside the existing test_pattern and frames keys. File collection logic (e.g. either single file or directory with natural sorting) follows the existing frames path handling. This raw_frames block accepts: - path (single file or directory) - bayer_order (RGGB, BGGR, GRBG, GBRG) - bit_depth (8, 10, 12, 14, 16), in accordance to libcamera/formats.yaml Extend generateConfiguration() to return a Bayer StreamConfiguration when the StreamRole::Raw is requested on a camera configured with raw_frames. Extend validate() to allow Bayer formats, also set the correct stride and frameSize for single plane Bayer buffers. Signed-off-by: Max Bretschneider <maxbretschneider@protonmail.com> --- .../pipeline/virtual/config_parser.cpp | 156 ++++++++++++++---- src/libcamera/pipeline/virtual/virtual.cpp | 99 +++++++++-- src/libcamera/pipeline/virtual/virtual.h | 3 +- 3 files changed, 210 insertions(+), 48 deletions(-)