Add lcameraBuff Stage 2 plugin with YUV attach and unit tests.

Introduce params parsing, pixel/format decisions, capture layout, shared
YuvStimProducer per camera, and channel stimulus buffers with attach flow.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-14 11:03:19 -04:00
parent 809861be2b
commit e7b7a311f7
18 changed files with 1698 additions and 0 deletions
+1
View File
@@ -1,2 +1,3 @@
add_subdirectory(xcbWindow)
add_subdirectory(livoxGen1)
add_subdirectory(lcameraBuff)
+54
View File
@@ -0,0 +1,54 @@
cmake_dependent_option(ENABLE_STIMBUFFAPI_lcameraBuff
"Enable libcamera YUV stim buff API" ON
"ENABLE_LIB_lcameraDev" OFF)
if(ENABLE_STIMBUFFAPI_lcameraBuff)
set(CONFIG_STIMBUFFAPI_LCAMERABUFF_ENABLED 1)
pkg_check_modules(LIBCAMERA REQUIRED libcamera)
add_library(lcameraBuff SHARED
lcameraBuff.cpp
lcameraBuffParams.cpp
pixelAndColorFormatDecisions.cpp
yuvCaptureLayout.cpp
yuvChannelStimulusBuffer.cpp
yuvStimProducer.cpp
)
set_target_properties(lcameraBuff PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
)
target_include_directories(lcameraBuff PUBLIC
${Boost_INCLUDE_DIRS}
${CMAKE_SOURCE_DIR}/include
${CMAKE_SOURCE_DIR}/smocore/include
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/commonLibs
${CMAKE_SOURCE_DIR}/commonLibs/lcameraDev
${LIBCAMERA_INCLUDE_DIRS}
)
target_link_libraries(lcameraBuff PUBLIC
Boost::system
Boost::log
attachmentSupport
spinscale
)
add_custom_command(TARGET lcameraBuff POST_BUILD
COMMAND ${CMAKE_COMMAND} -DVERIFY_FILE="$<TARGET_FILE:lcameraBuff>"
-P ${CMAKE_SOURCE_DIR}/cmake/VerifyBoostDynamic.cmake
COMMENT "Verifying Boost dynamic dependencies for lcameraBuff"
)
install(TARGETS lcameraBuff
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} NAMELINK_SKIP
)
if(ENABLE_TESTS)
add_subdirectory(tests)
endif()
endif()
+354
View File
@@ -0,0 +1,354 @@
#include <boostAsioLinkageFix.h>
#include <lcameraBuffInternal.h>
#include <lcameraBuffParams.h>
#include <yuvStimProducer.h>
#include <algorithm>
#include <cassert>
#include <dlfcn.h>
#include <iostream>
#include <stdexcept>
namespace smo::stim_buff::lcamera_buff {
const SmoCallbacks *lcameraBuffSmoHooksPtr = nullptr;
SmoThreadingModelDesc lcameraBuffThreadingModelDesc;
LcameraDevDllState lcameraDevDll;
std::vector<std::shared_ptr<YuvStimProducer>> attachedStimulusProducers;
void LcameraDevDllState::DlCloser::operator()(void *handle)
{
if (handle) {
dlclose(handle);
}
}
LcameraDevDllState::LcameraDevDllState()
: dlopenHandle(nullptr, DlCloser{}),
lcameraDev_main(nullptr),
lcameraDev_exit(nullptr),
lcameraDev_getOrCreateDeviceCReq(nullptr),
lcameraDev_releaseDeviceCReq(nullptr),
lcameraDev_configureSessionModeCReq(nullptr)
{}
std::shared_ptr<YuvStimProducer> findStimProducerByCameraId(
const std::string& resolvedCameraId)
{
for (const std::shared_ptr<YuvStimProducer>& producer :
attachedStimulusProducers)
{
assert(producer != nullptr);
if (producer->resolvedCameraId == resolvedCameraId) {
return producer;
}
}
return nullptr;
}
std::shared_ptr<YuvStimProducer> findStimProducerWithAttachedBuffer(
const std::shared_ptr<device::DeviceAttachmentSpec>& desc)
{
for (const std::shared_ptr<YuvStimProducer>& producer :
attachedStimulusProducers)
{
assert(producer != nullptr);
if (producer->getAttachedStimulusBufferByAttachIdentity(
desc->deviceIdentifier, desc->qualeIfaceApi))
{
return producer;
}
}
return nullptr;
}
bool validateAttachRequest(
const std::shared_ptr<device::DeviceAttachmentSpec>& spec)
{
if (!spec)
{
std::cerr << __func__ << ": null DeviceAttachmentSpec\n";
return false;
}
if (spec->stimBuffApi != "lcameraBuff")
{
std::cerr << __func__ << ": stimBuffApi must be lcameraBuff, got '"
<< spec->stimBuffApi << "'\n";
return false;
}
if (spec->provider != "lcameraDev")
{
std::cerr << __func__ << ": provider must be lcameraDev(), got '"
<< spec->provider << "'\n";
return false;
}
if (!YuvStimProducer::supportsQualeIfaceApi(spec->qualeIfaceApi))
{
std::cerr << __func__ << ": unsupported qualeIfaceApi '"
<< spec->qualeIfaceApi << "'\n";
return false;
}
return true;
}
namespace {
void loadLcameraDevSymbols()
{
lcameraDevDll.lcameraDev_main = reinterpret_cast<lcameraDev_mainFn *>(
dlsym(lcameraDevDll.dlopenHandle.get(), "lcameraDev_main"));
lcameraDevDll.lcameraDev_exit = reinterpret_cast<lcameraDev_exitFn *>(
dlsym(lcameraDevDll.dlopenHandle.get(), "lcameraDev_exit"));
lcameraDevDll.lcameraDev_getOrCreateDeviceCReq = reinterpret_cast<
lcameraDev_getOrCreateDeviceCReqFn *>(
dlsym(
lcameraDevDll.dlopenHandle.get(),
"lcameraDev_getOrCreateDeviceCReq"));
lcameraDevDll.lcameraDev_releaseDeviceCReq = reinterpret_cast<
lcameraDev_releaseDeviceCReqFn *>(
dlsym(
lcameraDevDll.dlopenHandle.get(),
"lcameraDev_releaseDeviceCReq"));
lcameraDevDll.lcameraDev_configureSessionModeCReq = reinterpret_cast<
lcameraDev_configureSessionModeCReqFn *>(
dlsym(
lcameraDevDll.dlopenHandle.get(),
"lcameraDev_configureSessionModeCReq"));
if (!lcameraDevDll.lcameraDev_main
|| !lcameraDevDll.lcameraDev_exit
|| !lcameraDevDll.lcameraDev_getOrCreateDeviceCReq
|| !lcameraDevDll.lcameraDev_releaseDeviceCReq
|| !lcameraDevDll.lcameraDev_configureSessionModeCReq)
{
throw std::runtime_error(
std::string(__func__)
+ ": failed to resolve lcameraDev library symbols");
}
}
sscl::co::ViralNonPostingInvoker<StimBuffDeviceOpResult>
attachChannelBufferToProducer(
const std::shared_ptr<device::DeviceAttachmentSpec>& desc,
const std::shared_ptr<YuvStimProducer>& producer)
{
producer->getOrCreateAttachedStimulusBuffer(desc);
co_return StimBuffDeviceOpResult{true, desc};
}
sscl::co::ViralNonPostingInvoker<StimBuffDeviceOpResult>
attachToExistingProducer(
const std::shared_ptr<device::DeviceAttachmentSpec>& desc,
const std::shared_ptr<YuvStimProducer>& producer)
{
const LcameraBuffParsedParams parsedParams = parseLcameraBuffParams(*desc);
const lcamera_dev::LcameraDevCameraModeRequest modeRequest =
toCameraModeRequest(parsedParams);
co_await (*lcameraDevDll.lcameraDev_configureSessionModeCReq)(
producer->deviceSession,
modeRequest);
co_return co_await attachChannelBufferToProducer(desc, producer);
}
sscl::co::ViralNonPostingInvoker<StimBuffDeviceOpResult>
attachByCreatingProducer(
const std::shared_ptr<device::DeviceAttachmentSpec>& desc,
const std::shared_ptr<sscl::ComponentThread>& componentThread,
const lcamera_dev::LcameraDevGetOrCreateResult& createResult)
{
const LcameraBuffParsedParams parsedParams = parseLcameraBuffParams(*desc);
const lcamera_dev::LcameraDevCameraModeRequest modeRequest =
toCameraModeRequest(parsedParams);
const lcamera_dev::LcameraDevConfiguredCameraMode configuredMode =
co_await (*lcameraDevDll.lcameraDev_configureSessionModeCReq)(
createResult.deviceSession,
modeRequest);
auto producer = std::make_shared<YuvStimProducer>(
desc,
componentThread->getIoContext(),
createResult.deviceSession,
createResult.resolvedIdentity,
parsedParams,
configuredMode);
attachedStimulusProducers.push_back(producer);
co_return co_await attachChannelBufferToProducer(desc, producer);
}
} // namespace
sscl::co::ViralNonPostingInvoker<int> lcameraBuff_initializeCInd()
{
if (!lcameraBuffSmoHooksPtr)
{
throw std::runtime_error(
std::string(__func__) + ": SMO hooks not initialized");
}
const std::optional<std::string> libPath =
lcameraBuffSmoHooksPtr->searchForLibInSmoSearchPaths(
"liblcameraDev.so");
lcameraDevDll.dlopenHandle.reset(
dlopen(
libPath.value_or("liblcameraDev.so").c_str(),
RTLD_LAZY));
if (!lcameraDevDll.dlopenHandle)
{
throw std::runtime_error(
std::string(__func__) + ": failed to load lcameraDev library: "
+ (dlerror() ? dlerror() : "unknown error"));
}
loadLcameraDevSymbols();
(*lcameraDevDll.lcameraDev_main)(
lcameraBuffThreadingModelDesc.componentThread);
co_return 0;
}
sscl::co::ViralNonPostingInvoker<int> lcameraBuff_finalizeCInd()
{
for (const std::shared_ptr<YuvStimProducer>& producer :
attachedStimulusProducers)
{
assert(producer != nullptr);
producer->deviceSession.reset();
}
attachedStimulusProducers.clear();
if (lcameraDevDll.lcameraDev_exit) {
(*lcameraDevDll.lcameraDev_exit)();
}
lcameraDevDll.dlopenHandle.reset(nullptr);
lcameraDevDll = LcameraDevDllState();
co_return 0;
}
sscl::co::DynamicViralPostingInvoker<StimBuffDeviceOpResult>
lcameraBuff_attachDeviceCReq(
sscl::co::ExplicitPostTarget,
const std::shared_ptr<device::DeviceAttachmentSpec>& desc,
const std::shared_ptr<sscl::ComponentThread>& componentThread)
{
if (!validateAttachRequest(desc)) {
co_return StimBuffDeviceOpResult{false, desc};
}
const lcamera_dev::LcameraDevGetOrCreateResult createResult =
co_await (*lcameraDevDll.lcameraDev_getOrCreateDeviceCReq)(
desc->deviceSelector);
const std::string resolvedCameraId = createResult.resolvedIdentity.id;
auto existingProducer = findStimProducerByCameraId(resolvedCameraId);
if (existingProducer) {
co_return co_await attachToExistingProducer(desc, existingProducer);
}
co_return co_await attachByCreatingProducer(
desc, componentThread, createResult);
}
sscl::co::DynamicViralPostingInvoker<StimBuffDeviceOpResult>
lcameraBuff_detachDeviceCReq(
[[maybe_unused]] sscl::co::ExplicitPostTarget postTarget,
const std::shared_ptr<device::DeviceAttachmentSpec>& desc)
{
(void)postTarget;
if (!validateAttachRequest(desc)) {
co_return StimBuffDeviceOpResult{false, desc};
}
auto producer = findStimProducerWithAttachedBuffer(desc);
if (!producer) {
co_return StimBuffDeviceOpResult{true, desc};
}
auto stimBuffer = producer->getAttachedStimulusBufferByAttachIdentity(
desc->deviceIdentifier, desc->qualeIfaceApi);
if (!stimBuffer) {
co_return StimBuffDeviceOpResult{true, desc};
}
producer->destroyAttachedStimulusBuffer(stimBuffer);
const std::shared_ptr<lcamera_dev::CameraSession> deviceSession =
producer->deviceSession;
co_await (*lcameraDevDll.lcameraDev_releaseDeviceCReq)(deviceSession);
if (!producer->attachedStimulusBuffers.empty()) {
co_return StimBuffDeviceOpResult{true, desc};
}
attachedStimulusProducers.erase(
std::remove_if(
attachedStimulusProducers.begin(),
attachedStimulusProducers.end(),
[&producer](const std::shared_ptr<YuvStimProducer>& candidate)
{
return candidate == producer;
}),
attachedStimulusProducers.end());
producer->deviceSession.reset();
co_return StimBuffDeviceOpResult{true, desc};
}
} // namespace smo::stim_buff::lcamera_buff
extern "C" smo::stim_buff::SMO_GET_STIM_BUFF_API_DESC_FN_TYPEDEF
SMO_GET_STIM_BUFF_API_DESC_FN_NAME;
namespace {
static const smo::stim_buff::StimBuffApiDesc lcameraBuffApiDesc = {
.name = "lcameraBuff",
.exportedQualeIfaceApis =
{
{.name = "colour-yuv-y"},
{.name = "colour-yuv-u"},
{.name = "colour-yuv-v"},
},
.sal_mgmt_libOps =
{
.initializeCInd =
smo::stim_buff::lcamera_buff::lcameraBuff_initializeCInd,
.finalizeCInd =
smo::stim_buff::lcamera_buff::lcameraBuff_finalizeCInd,
.attachDeviceCReq =
smo::stim_buff::lcamera_buff::lcameraBuff_attachDeviceCReq,
.detachDeviceCReq =
smo::stim_buff::lcamera_buff::lcameraBuff_detachDeviceCReq,
},
};
} // namespace
const smo::stim_buff::StimBuffApiDesc& SMO_GET_STIM_BUFF_API_DESC_FN_NAME(
const smo::stim_buff::SmoCallbacks& callbacks,
const smo::stim_buff::SmoThreadingModelDesc& threadingModel)
{
smo::stim_buff::lcamera_buff::lcameraBuffSmoHooksPtr = &callbacks;
smo::stim_buff::lcamera_buff::lcameraBuffThreadingModelDesc = threadingModel;
return lcameraBuffApiDesc;
}
@@ -0,0 +1,59 @@
#ifndef LCAMERA_BUFF_INTERNAL_H
#define LCAMERA_BUFF_INTERNAL_H
#include <lcameraDev.h>
#include <memory>
#include <user/deviceAttachmentSpec.h>
#include <user/senseApiDesc.h>
#include <vector>
#include <spinscale/co/invokers.h>
namespace smo::stim_buff::lcamera_buff {
class YuvStimProducer;
struct LcameraDevDllState
{
struct DlCloser
{
void operator()(void *handle);
};
LcameraDevDllState();
std::unique_ptr<void, DlCloser> dlopenHandle;
lcameraDev_mainFn *lcameraDev_main;
lcameraDev_exitFn *lcameraDev_exit;
lcameraDev_getOrCreateDeviceCReqFn *lcameraDev_getOrCreateDeviceCReq;
lcameraDev_releaseDeviceCReqFn *lcameraDev_releaseDeviceCReq;
lcameraDev_configureSessionModeCReqFn *lcameraDev_configureSessionModeCReq;
};
extern const SmoCallbacks *lcameraBuffSmoHooksPtr;
extern SmoThreadingModelDesc lcameraBuffThreadingModelDesc;
extern LcameraDevDllState lcameraDevDll;
extern std::vector<std::shared_ptr<YuvStimProducer>> attachedStimulusProducers;
std::shared_ptr<YuvStimProducer> findStimProducerByCameraId(
const std::string& resolvedCameraId);
bool validateAttachRequest(
const std::shared_ptr<device::DeviceAttachmentSpec>& spec);
sscl::co::ViralNonPostingInvoker<int> lcameraBuff_initializeCInd();
sscl::co::ViralNonPostingInvoker<int> lcameraBuff_finalizeCInd();
sscl::co::DynamicViralPostingInvoker<StimBuffDeviceOpResult>
lcameraBuff_attachDeviceCReq(
sscl::co::ExplicitPostTarget postTarget,
const std::shared_ptr<device::DeviceAttachmentSpec>& desc,
const std::shared_ptr<sscl::ComponentThread>& componentThread);
sscl::co::DynamicViralPostingInvoker<StimBuffDeviceOpResult>
lcameraBuff_detachDeviceCReq(
sscl::co::ExplicitPostTarget postTarget,
const std::shared_ptr<device::DeviceAttachmentSpec>& desc);
} // namespace smo::stim_buff::lcamera_buff
#endif // LCAMERA_BUFF_INTERNAL_H
@@ -0,0 +1,290 @@
#include <lcameraBuffParams.h>
#include <sstream>
#include <stdexcept>
#include <unordered_map>
#include <vector>
namespace smo {
namespace stim_buff {
namespace lcamera_buff {
namespace {
constexpr const char *mutuallyExclusiveResolutionMessage =
"lcameraBuff: explicit dimension width/height params cannot be combined "
"with vertical-resolution shorthand params";
constexpr const char *missingResolutionMessage =
"lcameraBuff: specify either explicit dimension width and height, or a "
"vertical-resolution shorthand (v-res, vres, etc.)";
constexpr const char *incompleteExplicitResolutionMessage =
"lcameraBuff: both dimension width and height are required when using "
"explicit dimension params";
const std::vector<std::string> widthParamSynonyms = {
"frame-width",
"frame-w",
"dimension-width",
"dimension-w",
"dim-width",
"dim-w",
};
const std::vector<std::string> heightParamSynonyms = {
"frame-height",
"frame-h",
"dimension-height",
"dimension-h",
"dim-height",
"dim-h",
};
const std::vector<std::string> verticalResolutionParamSynonyms = {
"vertical-resolution",
"v-resolution",
"v-res",
"vres",
};
const std::vector<std::string> colourSpaceParamSynonyms = {
"colorspace",
"color-space",
"colourspace",
"colour-space",
};
const std::vector<std::string> optPlanarParamSynonyms = {
"full-planar-is-optional",
"opt-planar",
};
std::string normalizeVerticalResolutionToken(const std::string& token)
{
std::string normalizedToken =
device::DeviceAttachmentSpec::normalizeParamToken(token);
if (normalizedToken.empty())
{
throw std::runtime_error(
"lcameraBuff: vertical-resolution value is empty");
}
if (normalizedToken.back() == 'p')
{
normalizedToken.pop_back();
}
if (normalizedToken.empty())
{
throw std::runtime_error(
"lcameraBuff: vertical-resolution value is empty after "
"normalization");
}
return normalizedToken;
}
struct VerticalResolutionPreset
{
unsigned width;
unsigned height;
};
const std::unordered_map<std::string, VerticalResolutionPreset>&
verticalResolutionPresetTable()
{
static const std::unordered_map<std::string, VerticalResolutionPreset>
presets = {
{"360", {640, 360}},
{"480", {640, 480}},
{"720", {1280, 720}},
{"1080", {1920, 1080}},
};
return presets;
}
VerticalResolutionPreset resolveVerticalResolutionPreset(
const std::string& token)
{
const std::string normalizedToken =
normalizeVerticalResolutionToken(token);
const std::unordered_map<std::string, VerticalResolutionPreset>& presets =
verticalResolutionPresetTable();
const auto presetIt = presets.find(normalizedToken);
if (presetIt == presets.end())
{
std::ostringstream supportedStream;
bool first = true;
for (const auto& entry : presets)
{
if (!first) {
supportedStream << ", ";
}
supportedStream << entry.first << "p";
first = false;
}
throw std::runtime_error(
"lcameraBuff: unsupported vertical-resolution '"
+ token + "'; supported values: "
+ supportedStream.str());
}
return presetIt->second;
}
lcamera_dev::LcameraDevColourSpace parseColourSpaceValue(
const std::string& value)
{
const std::string lowered =
device::DeviceAttachmentSpec::normalizeParamToken(value);
if (lowered == "yuv") {
return lcamera_dev::LcameraDevColourSpace::Yuv;
}
throw std::runtime_error(
"lcameraBuff: unsupported colour-space '" + value
+ "'; supported values: yuv");
}
void applyExplicitDimensions(
const std::vector<std::pair<std::string, std::string>>& params,
LcameraBuffParsedParams& parsedParams)
{
const std::optional<std::pair<std::string, std::string>> widthParam =
device::DeviceAttachmentSpec::findOptionalParamWithSynonyms(
params, widthParamSynonyms);
const std::optional<std::pair<std::string, std::string>> heightParam =
device::DeviceAttachmentSpec::findOptionalParamWithSynonyms(
params, heightParamSynonyms);
if (!widthParam && !heightParam) { return; }
const std::optional<std::pair<std::string, std::string>>
verticalResolutionParam =
device::DeviceAttachmentSpec::findOptionalParamWithSynonyms(
params, verticalResolutionParamSynonyms);
if (verticalResolutionParam) {
throw std::runtime_error(mutuallyExclusiveResolutionMessage);
}
if (!widthParam || !heightParam) {
throw std::runtime_error(incompleteExplicitResolutionMessage);
}
parsedParams.width = static_cast<unsigned>(
device::DeviceAttachmentSpec::parseParamValueAsPositiveInt(
widthParam->first, widthParam->second));
parsedParams.height = static_cast<unsigned>(
device::DeviceAttachmentSpec::parseParamValueAsPositiveInt(
heightParam->first, heightParam->second));
}
void applyVerticalResolutionShorthand(
const std::vector<std::pair<std::string, std::string>>& params,
LcameraBuffParsedParams& parsedParams)
{
const std::optional<std::string> verticalResolutionValue =
device::DeviceAttachmentSpec::parseOptionalParamValueWithSynonyms(
params,
verticalResolutionParamSynonyms,
"lcameraBuff: vertical-resolution param requires a value "
"(e.g. v-res=480p)");
if (!verticalResolutionValue) { return; }
const VerticalResolutionPreset preset =
resolveVerticalResolutionPreset(*verticalResolutionValue);
parsedParams.width = preset.width;
parsedParams.height = preset.height;
}
void applyColourSpaceParam(
const std::vector<std::pair<std::string, std::string>>& params,
LcameraBuffParsedParams& parsedParams)
{
const std::optional<std::string> colourSpaceValue =
device::DeviceAttachmentSpec::parseOptionalParamValueWithSynonyms(
params,
colourSpaceParamSynonyms,
"lcameraBuff: colour-space param requires a value "
"(e.g. colour-space=yuv)");
if (!colourSpaceValue) { return; }
parsedParams.colourSpace = parseColourSpaceValue(*colourSpaceValue);
}
void finalizeCheckResolutionValues(const LcameraBuffParsedParams& parsedParams)
{
if (parsedParams.width == 0 || parsedParams.height == 0)
{
throw std::runtime_error(missingResolutionMessage);
}
}
} // namespace
LcameraBuffParsedParams parseLcameraBuffParams(
const device::DeviceAttachmentSpec& spec)
{
const auto& params = spec.stimBuffApiParams;
device::DeviceAttachmentSpec::rejectUnknownParams(
params,
"lcameraBuff: unknown stimbuff-api param '",
widthParamSynonyms,
heightParamSynonyms,
verticalResolutionParamSynonyms,
colourSpaceParamSynonyms,
optPlanarParamSynonyms);
LcameraBuffParsedParams parsedParams;
applyExplicitDimensions(params, parsedParams);
applyVerticalResolutionShorthand(params, parsedParams);
applyColourSpaceParam(params, parsedParams);
/* Presence flag: opt-planar or opt-planar= with no value means true. */
parsedParams.fullPlanarIsOptional =
device::DeviceAttachmentSpec::parseOptionalParamAsBoolWithSynonyms(
params,
optPlanarParamSynonyms,
/*defaultValue=*/false,
/*emptyMeansTrue=*/true);
finalizeCheckResolutionValues(parsedParams);
return parsedParams;
}
lcamera_dev::LcameraDevCameraModeRequest toCameraModeRequest(
const LcameraBuffParsedParams& parsedParams)
{
lcamera_dev::LcameraDevCameraModeRequest request;
request.width = parsedParams.width;
request.height = parsedParams.height;
request.colourSpace = parsedParams.colourSpace;
request.fullPlanarIsOptional = parsedParams.fullPlanarIsOptional;
return request;
}
bool lcameraBuffParamsEqual(
const LcameraBuffParsedParams& left,
const LcameraBuffParsedParams& right)
{
return left.width == right.width
&& left.height == right.height
&& left.colourSpace == right.colourSpace
&& left.fullPlanarIsOptional == right.fullPlanarIsOptional;
}
} // namespace lcamera_buff
} // namespace stim_buff
} // namespace smo
@@ -0,0 +1,34 @@
#ifndef LCAMERA_BUFF_PARAMS_H
#define LCAMERA_BUFF_PARAMS_H
#include <cameraModeRequest.h>
#include <user/deviceAttachmentSpec.h>
namespace smo {
namespace stim_buff {
namespace lcamera_buff {
struct LcameraBuffParsedParams
{
unsigned width = 0;
unsigned height = 0;
lcamera_dev::LcameraDevColourSpace colourSpace =
lcamera_dev::LcameraDevColourSpace::Yuv;
bool fullPlanarIsOptional = false;
};
LcameraBuffParsedParams parseLcameraBuffParams(
const device::DeviceAttachmentSpec& spec);
lcamera_dev::LcameraDevCameraModeRequest toCameraModeRequest(
const LcameraBuffParsedParams& parsedParams);
bool lcameraBuffParamsEqual(
const LcameraBuffParsedParams& left,
const LcameraBuffParsedParams& right);
} // namespace lcamera_buff
} // namespace stim_buff
} // namespace smo
#endif // LCAMERA_BUFF_PARAMS_H
@@ -0,0 +1,141 @@
#include <pixelAndColorFormatDecisions.h>
#include <algorithm>
#include <cctype>
#include <stdexcept>
#include <string>
#include <unordered_set>
namespace smo {
namespace stim_buff {
namespace lcamera_buff {
namespace {
std::string normalizeFourccToken(std::string token)
{
for (char& character : token)
{
character = static_cast<char>(
std::toupper(static_cast<unsigned char>(character)));
}
return token;
}
bool fourccInSet(
const std::string& fourcc,
const std::unordered_set<std::string>& candidates)
{
return candidates.find(fourcc) != candidates.end();
}
const std::unordered_set<std::string>& yuv420FourccSet()
{
static const std::unordered_set<std::string> fourccs = {
"YU12", "YV12", "YUV420", "YVU420", "NV12", "NV21",
};
return fourccs;
}
const std::unordered_set<std::string>& yuv422FourccSet()
{
static const std::unordered_set<std::string> fourccs = {
"YUYV", "YVYU", "UYVY", "VYUY", "YUV422", "YVU422",
"NV16", "NV61", "YU16", "YV16",
};
return fourccs;
}
const std::unordered_set<std::string>& yuv444FourccSet()
{
static const std::unordered_set<std::string> fourccs = {
"YUV444", "YVU444",
};
return fourccs;
}
unsigned chromaPlaneWidth(unsigned frameWidth, YuvChromaSubsampling subsampling)
{
switch (subsampling)
{
case YuvChromaSubsampling::Yuv420:
case YuvChromaSubsampling::Yuv422:
return (frameWidth + 1u) / 2u;
case YuvChromaSubsampling::Yuv444:
return frameWidth;
}
throw std::logic_error(
"lcameraBuff: unhandled YuvChromaSubsampling in chromaPlaneWidth");
}
unsigned chromaPlaneHeight(unsigned frameHeight, YuvChromaSubsampling subsampling)
{
switch (subsampling)
{
case YuvChromaSubsampling::Yuv420:
return (frameHeight + 1u) / 2u;
case YuvChromaSubsampling::Yuv422:
case YuvChromaSubsampling::Yuv444:
return frameHeight;
}
throw std::logic_error(
"lcameraBuff: unhandled YuvChromaSubsampling in chromaPlaneHeight");
}
} // namespace
YuvChromaSubsampling classifyYuvChromaSubsampling(
const lcamera_dev::LcameraDevConfiguredCameraMode& configuredMode)
{
const std::string fourcc =
normalizeFourccToken(configuredMode.pixelFormatName);
if (fourccInSet(fourcc, yuv420FourccSet())) {
return YuvChromaSubsampling::Yuv420;
}
if (fourccInSet(fourcc, yuv422FourccSet())) {
return YuvChromaSubsampling::Yuv422;
}
if (fourccInSet(fourcc, yuv444FourccSet())) {
return YuvChromaSubsampling::Yuv444;
}
throw std::runtime_error(
"lcameraBuff: unsupported YUV pixel format for chroma geometry: "
+ configuredMode.pixelFormatName);
}
size_t computeDeinterleavedChannelByteSize(
YuvChannelKind channel,
unsigned width, unsigned height,
YuvChromaSubsampling subsampling)
{
switch (channel)
{
case YuvChannelKind::Y:
return static_cast<size_t>(width) * static_cast<size_t>(height);
case YuvChannelKind::U:
case YuvChannelKind::V:
{
const unsigned chromaWidth = chromaPlaneWidth(width, subsampling);
const unsigned chromaHeight =
chromaPlaneHeight(height, subsampling);
return static_cast<size_t>(chromaWidth)
* static_cast<size_t>(chromaHeight);
}
}
throw std::logic_error(
"lcameraBuff: unhandled YuvChannelKind in computeDeinterleavedChannelByteSize");
}
} // namespace lcamera_buff
} // namespace stim_buff
} // namespace smo
@@ -0,0 +1,59 @@
#ifndef LCAMERA_BUFF_PIXEL_AND_COLOR_FORMAT_DECISIONS_H
#define LCAMERA_BUFF_PIXEL_AND_COLOR_FORMAT_DECISIONS_H
#include <cameraModeRequest.h>
#include <cstddef>
/**
* Y/U/V channel sizing for lcameraBuff.
*
* lcameraBuff exports three quale ifaces (colour-yuv-y/u/v), each backed by a
* separate deinterleaved channel buffer. libcamera, however, negotiates a single
* configured pixel format (YUYV, NV12, YUV420, etc.) that may be packed or
* multi-plane. This module bridges that gap at attach time: it classifies the
* configured fourcc into a chroma subsampling family (420 / 422 / 444) and
* computes how many bytes each deinterleaved Y, U, or V plane needs for the
* negotiated width and height.
*
* What it provides to the stimbuff:
* - YuvChromaSubsampling on YuvStimProducer (derived from configured mode)
* - channelByteSize for each YuvChannelStimulusBuffer / StagingBuffer mmap
*
* Capture layout (packed vs planar, direct vs deinterleaved backing) is handled
* separately by yuvCaptureLayout.h; this module is only plane geometry and
* byte counts. Future capture/deinterleave stages must write into buffers sized
* by these functions.
*/
namespace smo {
namespace stim_buff {
namespace lcamera_buff {
enum class YuvChannelKind
{
Y,
U,
V,
};
enum class YuvChromaSubsampling
{
Yuv420,
Yuv422,
Yuv444,
};
YuvChromaSubsampling classifyYuvChromaSubsampling(
const lcamera_dev::LcameraDevConfiguredCameraMode& configuredMode);
size_t computeDeinterleavedChannelByteSize(
YuvChannelKind channel,
unsigned width,
unsigned height,
YuvChromaSubsampling subsampling);
} // namespace lcamera_buff
} // namespace stim_buff
} // namespace smo
#endif // LCAMERA_BUFF_PIXEL_AND_COLOR_FORMAT_DECISIONS_H
@@ -0,0 +1,27 @@
add_executable(lcameraBuff_unit_tests
lcameraBuffParams_tests.cpp
pixelAndColorFormatDecisions_tests.cpp
yuvLayoutClassification_tests.cpp
)
target_include_directories(lcameraBuff_unit_tests PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/stimBuffApis/lcameraBuff
${CMAKE_SOURCE_DIR}/commonLibs/lcameraDev
${CMAKE_SOURCE_DIR}/libspinscale/tests
${CMAKE_SOURCE_DIR}/tests/fixtures
${CMAKE_SOURCE_DIR}/include
${CMAKE_BINARY_DIR}/include
)
target_link_libraries(lcameraBuff_unit_tests
gtest_main
lcameraBuff
spinscale_test_support
${Boost_LIBRARIES}
${OPENCL_LIBRARIES}
)
add_dependencies(lcameraBuff_unit_tests gtest_main spinscale_test_support)
add_test(NAME lcameraBuff_unit_tests COMMAND lcameraBuff_unit_tests)
@@ -0,0 +1,131 @@
#include <lcameraBuffParams.h>
#include <gtest/gtest.h>
#include <support/exceptionAssertions.h>
#include <user/deviceAttachmentSpec.h>
namespace smo {
namespace stim_buff {
namespace lcamera_buff {
namespace {
device::DeviceAttachmentSpec makeSpecWithParams(
std::vector<std::pair<std::string, std::string>> params)
{
device::DeviceAttachmentSpec spec;
spec.stimBuffApi = "lcameraBuff";
spec.provider = "lcameraDev";
spec.stimBuffApiParams = std::move(params);
return spec;
}
TEST(LcameraBuffParamsTest, DimWidthHeightSynonymsParseExplicitResolution)
{
device::DeviceAttachmentSpec spec = makeSpecWithParams({
{"dim-w", "640"},
{"dimension-h", "480"},
{"colour-space", "yuv"},
});
const LcameraBuffParsedParams parsed = parseLcameraBuffParams(spec);
EXPECT_EQ(parsed.width, 640u);
EXPECT_EQ(parsed.height, 480u);
EXPECT_EQ(
parsed.colourSpace,
lcamera_dev::LcameraDevColourSpace::Yuv);
EXPECT_FALSE(parsed.fullPlanarIsOptional);
}
TEST(LcameraBuffParamsTest, VerticalResolutionShorthand480p)
{
device::DeviceAttachmentSpec spec = makeSpecWithParams({
{"v-res", "480p"},
{"colour-space", "yuv"},
{"opt-planar", ""},
});
const LcameraBuffParsedParams parsed = parseLcameraBuffParams(spec);
EXPECT_EQ(parsed.width, 640u);
EXPECT_EQ(parsed.height, 480u);
EXPECT_TRUE(parsed.fullPlanarIsOptional);
}
TEST(LcameraBuffParamsTest, OptPlanarSynonymEmptyValueMeansTrue)
{
device::DeviceAttachmentSpec spec = makeSpecWithParams({
{"v-res", "480p"},
{"full-planar-is-optional", ""},
});
EXPECT_TRUE(parseLcameraBuffParams(spec).fullPlanarIsOptional);
}
TEST(LcameraBuffParamsTest, OptPlanarExplicitFalseDisables)
{
device::DeviceAttachmentSpec spec = makeSpecWithParams({
{"dim-w", "640"},
{"dim-h", "480"},
{"opt-planar", "false"},
});
EXPECT_FALSE(parseLcameraBuffParams(spec).fullPlanarIsOptional);
}
TEST(LcameraBuffParamsTest, OptPlanarAbsentDefaultsFalse)
{
device::DeviceAttachmentSpec spec = makeSpecWithParams({
{"v-res", "480p"},
});
EXPECT_FALSE(parseLcameraBuffParams(spec).fullPlanarIsOptional);
}
TEST(LcameraBuffParamsTest, MixedResolutionGroupsThrow)
{
device::DeviceAttachmentSpec spec = makeSpecWithParams({
{"v-res", "480p"},
{"dim-w", "640"},
});
try {
parseLcameraBuffParams(spec);
FAIL() << "Expected runtime_error";
}
catch (const std::runtime_error& exception)
{
sscl::tests::expectExceptionMessageContains(
exception,
"cannot be combined");
}
}
TEST(LcameraBuffParamsTest, UnsupportedColourSpaceThrows)
{
device::DeviceAttachmentSpec spec = makeSpecWithParams({
{"v-res", "480"},
{"colour-space", "rgb"},
});
EXPECT_THROW(parseLcameraBuffParams(spec), std::runtime_error);
}
TEST(LcameraBuffParamsTest, ToCameraModeRequestCopiesFields)
{
LcameraBuffParsedParams parsed;
parsed.width = 1280;
parsed.height = 720;
parsed.fullPlanarIsOptional = true;
const lcamera_dev::LcameraDevCameraModeRequest request =
toCameraModeRequest(parsed);
EXPECT_EQ(request.width, 1280u);
EXPECT_EQ(request.height, 720u);
EXPECT_TRUE(request.fullPlanarIsOptional);
}
} // namespace
} // namespace lcamera_buff
} // namespace stim_buff
} // namespace smo
@@ -0,0 +1,82 @@
#include <pixelAndColorFormatDecisions.h>
#include <gtest/gtest.h>
namespace smo {
namespace stim_buff {
namespace lcamera_buff {
namespace {
lcamera_dev::LcameraDevConfiguredCameraMode makeConfiguredMode(
const char *pixelFormatName)
{
lcamera_dev::LcameraDevConfiguredCameraMode mode;
mode.width = 640;
mode.height = 480;
mode.pixelFormatName = pixelFormatName;
return mode;
}
TEST(PixelAndColorFormatDecisionsTest, YuyvClassifiesAsYuv422)
{
const lcamera_dev::LcameraDevConfiguredCameraMode mode =
makeConfiguredMode("YUYV");
EXPECT_EQ(
classifyYuvChromaSubsampling(mode),
YuvChromaSubsampling::Yuv422);
}
TEST(PixelAndColorFormatDecisionsTest, Yuv420ChannelSizes)
{
EXPECT_EQ(
computeDeinterleavedChannelByteSize(
YuvChannelKind::Y,
640,
480,
YuvChromaSubsampling::Yuv420),
640u * 480u);
EXPECT_EQ(
computeDeinterleavedChannelByteSize(
YuvChannelKind::U,
640,
480,
YuvChromaSubsampling::Yuv420),
320u * 240u);
EXPECT_EQ(
computeDeinterleavedChannelByteSize(
YuvChannelKind::V,
640,
480,
YuvChromaSubsampling::Yuv420),
320u * 240u);
}
TEST(PixelAndColorFormatDecisionsTest, Yuv422ChannelSizesForDellLaptopYuyv)
{
EXPECT_EQ(
computeDeinterleavedChannelByteSize(
YuvChannelKind::Y,
640,
480,
YuvChromaSubsampling::Yuv422),
640u * 480u);
EXPECT_EQ(
computeDeinterleavedChannelByteSize(
YuvChannelKind::U,
640,
480,
YuvChromaSubsampling::Yuv422),
320u * 480u);
EXPECT_EQ(
computeDeinterleavedChannelByteSize(
YuvChannelKind::V,
640,
480,
YuvChromaSubsampling::Yuv422),
320u * 480u);
}
} // namespace
} // namespace lcamera_buff
} // namespace stim_buff
} // namespace smo
@@ -0,0 +1,44 @@
#include <yuvCaptureLayout.h>
#include <gtest/gtest.h>
namespace smo {
namespace stim_buff {
namespace lcamera_buff {
namespace {
TEST(YuvCaptureLayoutTest, PackedYuyvUsesMmapDeinterleavedBacking)
{
lcamera_dev::LcameraDevConfiguredCameraMode mode;
mode.pixelFormatName = "YUYV";
mode.isFullyPlanar = false;
mode.planeCount = 1;
EXPECT_EQ(
classifyYuvCaptureLayoutPath(mode),
YuvCaptureLayoutPath::PackedDeinterleave);
EXPECT_EQ(
getChannelBackingPlanForLayoutPath(
YuvCaptureLayoutPath::PackedDeinterleave),
YuvChannelBackingPlan::MmapDeinterleaved);
}
TEST(YuvCaptureLayoutTest, FullPlanarUsesDirectBacking)
{
lcamera_dev::LcameraDevConfiguredCameraMode mode;
mode.pixelFormatName = "YU12";
mode.isFullyPlanar = true;
mode.planeCount = 3;
EXPECT_EQ(
classifyYuvCaptureLayoutPath(mode),
YuvCaptureLayoutPath::FullPlanarDirect);
EXPECT_EQ(
getChannelBackingPlanForLayoutPath(
YuvCaptureLayoutPath::FullPlanarDirect),
YuvChannelBackingPlan::LCameraDirect);
}
} // namespace
} // namespace lcamera_buff
} // namespace stim_buff
} // namespace smo
@@ -0,0 +1,49 @@
#include <yuvCaptureLayout.h>
#include <stdexcept>
namespace smo {
namespace stim_buff {
namespace lcamera_buff {
YuvCaptureLayoutPath classifyYuvCaptureLayoutPath(
const lcamera_dev::LcameraDevConfiguredCameraMode& configuredMode)
{
if (configuredMode.isFullyPlanar && configuredMode.planeCount == 3u) {
return YuvCaptureLayoutPath::FullPlanarDirect;
}
if (configuredMode.planeCount == 2u) {
return YuvCaptureLayoutPath::SemiPlanarDeinterleave;
}
if (configuredMode.planeCount == 1u) {
return YuvCaptureLayoutPath::PackedDeinterleave;
}
throw std::runtime_error(
"lcameraBuff: unsupported configured YUV layout: pixelFormat="
+ configuredMode.pixelFormatName
+ " planeCount=" + std::to_string(configuredMode.planeCount));
}
YuvChannelBackingPlan getChannelBackingPlanForLayoutPath(
YuvCaptureLayoutPath layoutPath)
{
switch (layoutPath)
{
case YuvCaptureLayoutPath::FullPlanarDirect:
return YuvChannelBackingPlan::LCameraDirect;
case YuvCaptureLayoutPath::SemiPlanarDeinterleave:
case YuvCaptureLayoutPath::PackedDeinterleave:
return YuvChannelBackingPlan::MmapDeinterleaved;
}
throw std::logic_error(
"lcameraBuff: unhandled YuvCaptureLayoutPath in "
"getChannelBackingPlanForLayoutPath");
}
} // namespace lcamera_buff
} // namespace stim_buff
} // namespace smo
@@ -0,0 +1,33 @@
#ifndef LCAMERA_BUFF_YUV_CAPTURE_LAYOUT_H
#define LCAMERA_BUFF_YUV_CAPTURE_LAYOUT_H
#include <cameraModeRequest.h>
namespace smo {
namespace stim_buff {
namespace lcamera_buff {
enum class YuvCaptureLayoutPath
{
FullPlanarDirect,
SemiPlanarDeinterleave,
PackedDeinterleave,
};
enum class YuvChannelBackingPlan
{
LCameraDirect,
MmapDeinterleaved,
};
YuvCaptureLayoutPath classifyYuvCaptureLayoutPath(
const lcamera_dev::LcameraDevConfiguredCameraMode& configuredMode);
YuvChannelBackingPlan getChannelBackingPlanForLayoutPath(
YuvCaptureLayoutPath layoutPath);
} // namespace lcamera_buff
} // namespace stim_buff
} // namespace smo
#endif // LCAMERA_BUFF_YUV_CAPTURE_LAYOUT_H
@@ -0,0 +1,70 @@
#include <yuvChannelStimulusBuffer.h>
#include <stdexcept>
namespace smo {
namespace stim_buff {
namespace lcamera_buff {
namespace {
constexpr const char *qualeIfaceColourYuvY = "colour-yuv-y";
constexpr const char *qualeIfaceColourYuvU = "colour-yuv-u";
constexpr const char *qualeIfaceColourYuvV = "colour-yuv-v";
} // namespace
YuvChannelKind YuvChannelStimulusBuffer::yuvChannelKindFromQualeIfaceApi(
const std::string& qualeIfaceApi)
{
if (qualeIfaceApi == qualeIfaceColourYuvY) {
return YuvChannelKind::Y;
}
if (qualeIfaceApi == qualeIfaceColourYuvU) {
return YuvChannelKind::U;
}
if (qualeIfaceApi == qualeIfaceColourYuvV) {
return YuvChannelKind::V;
}
throw std::runtime_error(
"lcameraBuff: unsupported qualeIfaceApi '" + qualeIfaceApi
+ "'; expected colour-yuv-y, colour-yuv-u, or colour-yuv-v");
}
StagingBuffer::IOEngineConstraints yuvChannelStagingInputConstraints(
size_t channelByteSize)
{
const size_t pageSize = static_cast<size_t>(sysconf(_SC_PAGE_SIZE));
return StagingBuffer::IOEngineConstraints(
1,
channelByteSize,
pageSize,
pageSize);
}
YuvChannelStimulusBuffer::YuvChannelStimulusBuffer(
StimulusProducer& parent,
const std::shared_ptr<device::DeviceAttachmentSpec>& deviceAttachmentSpec,
int histbuffMs,
YuvChannelKind _channelKind,
size_t _channelByteSize,
const SmoCallbacks& callbacks,
cl_mem_flags flags)
: StimulusBuffer(
parent,
deviceAttachmentSpec,
histbuffMs,
yuvChannelStagingInputConstraints(_channelByteSize),
yuvChannelStagingInputConstraints(_channelByteSize),
callbacks,
flags),
channelKind(_channelKind),
channelByteSize(_channelByteSize)
{}
} // namespace lcamera_buff
} // namespace stim_buff
} // namespace smo
@@ -0,0 +1,51 @@
#ifndef LCAMERA_BUFF_YUV_CHANNEL_STIMULUS_BUFFER_H
#define LCAMERA_BUFF_YUV_CHANNEL_STIMULUS_BUFFER_H
#include <config.h>
#include <user/deviceAttachmentSpec.h>
#include <user/stagingBuffer.h>
#include <user/stimulusBuffer.h>
#include <pixelAndColorFormatDecisions.h>
#define CL_TARGET_OPENCL_VERSION 120
#include <CL/cl.h>
#include <unistd.h>
namespace smo {
namespace stim_buff {
namespace lcamera_buff {
class YuvStimProducer;
StagingBuffer::IOEngineConstraints yuvChannelStagingInputConstraints(
size_t channelByteSize);
/**
* Channel stimbuff placeholder for Stage 2 setup-only attach. Uses mmap-backed
* StagingBuffer sized for deinterleaved Y/U/V component storage.
*/
class YuvChannelStimulusBuffer
: public StimulusBuffer
{
public:
static YuvChannelKind yuvChannelKindFromQualeIfaceApi(
const std::string& qualeIfaceApi);
YuvChannelStimulusBuffer(
StimulusProducer& parent,
const std::shared_ptr<device::DeviceAttachmentSpec>& deviceAttachmentSpec,
int histbuffMs,
YuvChannelKind channelKind,
size_t channelByteSize,
const SmoCallbacks& callbacks,
cl_mem_flags flags);
public:
YuvChannelKind channelKind;
size_t channelByteSize;
};
} // namespace lcamera_buff
} // namespace stim_buff
} // namespace smo
#endif // LCAMERA_BUFF_YUV_CHANNEL_STIMULUS_BUFFER_H
@@ -0,0 +1,154 @@
#include <yuvStimProducer.h>
#include <yuvChannelStimulusBuffer.h>
#include <lcameraBuffInternal.h>
#include <config.h>
#include <stdexcept>
#define CL_TARGET_OPENCL_VERSION 120
#include <CL/cl.h>
#include <spinscale/syncCancelerForAsyncWork.h>
namespace smo {
namespace stim_buff {
namespace lcamera_buff {
YuvStimProducer::YuvStimProducer(
const std::shared_ptr<device::DeviceAttachmentSpec>& deviceAttachmentSpec,
boost::asio::io_context& ioContext,
const std::shared_ptr<lcamera_dev::CameraSession>& _deviceSession,
const lcamera_dev::CameraIdentityRecord& resolvedIdentity,
const LcameraBuffParsedParams& _parsedParams,
const lcamera_dev::LcameraDevConfiguredCameraMode& _configuredMode)
: StimulusProducer(deviceAttachmentSpec, ioContext),
deviceSession(_deviceSession),
resolvedCameraId(resolvedIdentity.id),
parsedParams(_parsedParams),
configuredMode(_configuredMode),
layoutPath(classifyYuvCaptureLayoutPath(_configuredMode)),
chromaSubsampling(classifyYuvChromaSubsampling(_configuredMode)),
channelBackingPlan(getChannelBackingPlanForLayoutPath(layoutPath))
{}
bool YuvStimProducer::supportsQualeIfaceApi(const std::string& qualeIfaceApi)
{
return qualeIfaceApi == "colour-yuv-y"
|| qualeIfaceApi == "colour-yuv-u"
|| qualeIfaceApi == "colour-yuv-v";
}
bool YuvStimProducer::exportsQualeIfaceApi(
const std::string& qualeIfaceApi) const
{
return supportsQualeIfaceApi(qualeIfaceApi);
}
std::shared_ptr<StimulusBuffer> YuvStimProducer::getOrCreateAttachedStimulusBuffer(
const std::shared_ptr<device::DeviceAttachmentSpec>& attachSpec)
{
if (!lcameraBuffSmoHooksPtr)
{
throw std::runtime_error(
"lcameraBuff: SMO hooks not initialized");
}
if (!supportsQualeIfaceApi(attachSpec->qualeIfaceApi))
{
throw std::runtime_error(
"lcameraBuff: unsupported qualeIfaceApi '"
+ attachSpec->qualeIfaceApi + "'");
}
const LcameraBuffParsedParams attachParsedParams =
parseLcameraBuffParams(*attachSpec);
if (!lcameraBuffParamsEqual(attachParsedParams, parsedParams))
{
throw std::runtime_error(
"lcameraBuff: conflicting stimbuff-api params across Y/U/V "
"attachments for camera '" + resolvedCameraId + "'");
}
auto existingBuffer = getAttachedStimulusBuffer(attachSpec);
if (existingBuffer) {
return existingBuffer;
}
/* One YuvStimProducer == one libcamera session. A second DAP line may use
* a different deviceSelector yet resolve to this same session; it must not
* attach the same qualeIface API twice.
*/
if (hasBufferWithQualeIfaceApi(attachSpec->qualeIfaceApi))
{
throw std::runtime_error(
"lcameraBuff: qualeIface '" + attachSpec->qualeIfaceApi
+ "' is already attached for camera session '"
+ resolvedCameraId + "'; each producer allows one buffer per "
"qualeIface API");
}
const YuvChannelKind channelKind =
YuvChannelStimulusBuffer::yuvChannelKindFromQualeIfaceApi(
attachSpec->qualeIfaceApi);
const size_t channelByteSize = computeDeinterleavedChannelByteSize(
channelKind,
configuredMode.width,
configuredMode.height,
chromaSubsampling);
auto channelBuffer = std::make_shared<YuvChannelStimulusBuffer>(
*this,
attachSpec,
CONFIG_STIMBUFF_FRAME_PERIOD_MS,
channelKind,
channelByteSize,
*lcameraBuffSmoHooksPtr,
CL_MEM_READ_WRITE);
if (!addAttachedStimulusBufferIfNotExists(channelBuffer))
{
if (hasBufferWithQualeIfaceApi(attachSpec->qualeIfaceApi))
{
throw std::runtime_error(
"lcameraBuff: qualeIface '"
+ attachSpec->qualeIfaceApi
+ "' is already attached for camera session '"
+ resolvedCameraId + "'");
}
throw std::runtime_error(
"lcameraBuff: duplicate stimbuff attachment for "
+ attachSpec->stringify());
}
return channelBuffer;
}
void YuvStimProducer::destroyAttachedStimulusBuffer(
const std::shared_ptr<StimulusBuffer>& buffer)
{
StimulusProducer::destroyAttachedStimulusBuffer(buffer);
}
void YuvStimProducer::start()
{
// Stage 2: setup-only attach; capture daemon deferred to Stage 8.
}
void YuvStimProducer::stop()
{
// Stage 2: setup-only attach; capture daemon deferred to Stage 8.
}
sscl::co::ViralNonPostingInvoker<void>
YuvStimProducer::stimFrameProductionTimesliceCInd(
sscl::SyncCancelerForAsyncWork& canceler)
{
(void)canceler;
throw std::logic_error(
"lcameraBuff: capture daemon not implemented in Stage 2");
co_return;
}
} // namespace lcamera_buff
} // namespace stim_buff
} // namespace smo
@@ -0,0 +1,65 @@
#ifndef LCAMERA_BUFF_YUV_STIM_PRODUCER_H
#define LCAMERA_BUFF_YUV_STIM_PRODUCER_H
#include <cameraModeRequest.h>
#include <lcameraDev.h>
#include <lcameraBuffParams.h>
#include <user/stimulusProducer.h>
#include <yuvCaptureLayout.h>
#include <pixelAndColorFormatDecisions.h>
#include <memory>
#include <string>
namespace smo {
namespace stim_buff {
namespace lcamera_buff {
class YuvChannelStimulusBuffer;
class YuvStimProducer
: public StimulusProducer
{
public:
YuvStimProducer(
const std::shared_ptr<device::DeviceAttachmentSpec>& deviceAttachmentSpec,
boost::asio::io_context& ioContext,
const std::shared_ptr<lcamera_dev::CameraSession>& deviceSession,
const lcamera_dev::CameraIdentityRecord& resolvedIdentity,
const LcameraBuffParsedParams& parsedParams,
const lcamera_dev::LcameraDevConfiguredCameraMode& configuredMode);
static bool supportsQualeIfaceApi(const std::string& qualeIfaceApi);
bool exportsQualeIfaceApi(
const std::string& qualeIfaceApi) const override;
std::shared_ptr<StimulusBuffer> getOrCreateAttachedStimulusBuffer(
const std::shared_ptr<device::DeviceAttachmentSpec>&
deviceAttachmentSpec) override;
void destroyAttachedStimulusBuffer(
const std::shared_ptr<StimulusBuffer>& buffer) override;
void start() override;
void stop() override;
protected:
sscl::co::ViralNonPostingInvoker<void>
stimFrameProductionTimesliceCInd(
sscl::SyncCancelerForAsyncWork& canceler) override;
public:
std::shared_ptr<lcamera_dev::CameraSession> deviceSession;
std::string resolvedCameraId;
LcameraBuffParsedParams parsedParams;
lcamera_dev::LcameraDevConfiguredCameraMode configuredMode;
YuvCaptureLayoutPath layoutPath;
YuvChromaSubsampling chromaSubsampling;
YuvChannelBackingPlan channelBackingPlan;
};
} // namespace lcamera_buff
} // namespace stim_buff
} // namespace smo
#endif // LCAMERA_BUFF_YUV_STIM_PRODUCER_H