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:
@@ -1,2 +1,3 @@
|
|||||||
add_subdirectory(xcbWindow)
|
add_subdirectory(xcbWindow)
|
||||||
add_subdirectory(livoxGen1)
|
add_subdirectory(livoxGen1)
|
||||||
|
add_subdirectory(lcameraBuff)
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user