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:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user