lcameraDev: Add session mgr lib for libcamera device binding

This commit is contained in:
2026-06-13 12:02:04 -04:00
parent cc7f4fcd9b
commit 46f767f232
21 changed files with 1363 additions and 64 deletions
+66 -61
View File
@@ -34,8 +34,10 @@ lcameraBuff (stimBuffApi) SMO-facing producers, channel fan-out, intrins
Channel splitting, colourspace conversion, threshold masks, and stencils belong
in a separate shared raster library (`rasterStimulus`, future) and in
`lcameraBuff`; `lcameraDev` stops at “here is a stable, acquired libcamera
`Camera` you can configure and stream from.”
`lcameraBuff`; `lcameraDev` stops at selector resolution, `CameraManager`
lifecycle, and a refcounted, **acquired** `libcamera::Camera` handle per
resolved device. Stream negotiation, pixel-format selection, frame buffers,
and capture timing belong in `lcameraBuff` (and supporting libraries), not here.
## Why libcamera (for now)
@@ -120,7 +122,8 @@ Possible future params:
* `fps-hz=30`
* `width=640|height=480`
* `pixfmt=NV12` — negotiation hint passed down to `lcameraDev`
* `pixfmt=NV12` — negotiation hint for `lcameraBuff` stream setup (not
`lcameraDev`)
## Device selector format
@@ -180,7 +183,7 @@ tokens).
Resolution algorithm (summary):
1. Start `CameraManager` (once per process, inside `lcameraDev`).
1. Start `CameraManager` in `lcameraDev_main` (once per process).
2. Enumerate cameras; build an identity record per camera (`id`, `model`,
`location`, `systemDevices`).
3. Parse `deviceSelector` into criterion clauses; apply **all** clauses (AND).
@@ -197,9 +200,9 @@ each cameras `id()`, `model`, and `location` so operators can copy a stable
## Shared session and refcounting
Unlike X11 windows, a camera has no “sub-objects” inside its feed — the selector
always designates the whole camera stream. Multiple DAP lines for H, S, and V
are **views** over one capture session, all under the same `dev-identifier`
(e.g. `cam0`).
always designates the whole physical camera. Multiple DAP lines for H, S, and V
share one **device session** (acquired `libcamera::Camera`), all under the same
`dev-identifier` (e.g. `cam0`).
```text
dev-identifier cam0 + deviceSelector (resolved) ──> LcameraDeviceSession (refcounted)
@@ -213,11 +216,10 @@ dev-identifier cam0 + deviceSelector (resolved) ──> LcameraDeviceSession (re
Rules:
* First `getOrCreate` for a resolved camera ID: enumerate, `acquire()` the
`libcamera::Camera`, negotiate and start the shared stream.
`libcamera::Camera`, create the session entry.
* Subsequent `getOrCreate` for the same ID: increment refcount; return the same
session handle.
* Last `release`: `release()` the libcamera camera, stop streaming, tear down
buffers.
* Last `release`: `release()` the libcamera camera and erase the session entry.
* Different `deviceSelector` strings that resolve to the same libcamera ID share
one session (even if the selector text differs, e.g. one line uses
`lcamera-id:...` and another uses a compound selector that resolves to the
@@ -226,84 +228,89 @@ Rules:
the same physical device from SMOs perspective; channel differences come from
the qualeIface name on each DAP line.
## dlopen API (planned)
Streaming, frame delivery, and colourspace work are **out of scope** for
`lcameraDev`; `lcameraBuff` uses the sessions acquired camera handle to set up
capture on attach.
## dlopen API
Exported from `liblcameraDev.so` using `extern "C"` symbols (mirroring
`livoxProto1`). `lcameraBuff` loads the library with `dlopen` + `dlsym` and
calls through function pointers.
calls through function pointers. Hot-path operations are **`*CReq` coroutine
invokers** (`sscl::co::ViralNonPostingInvoker`); `main` / `exit` remain
synchronous.
Header: `commonLibs/lcameraDev/lcameraDev.h`.
### Lifecycle
```c
/* Start CameraManager; idempotent per process. */
typedef void lcameraDev_mainFn(void);
typedef void lcameraDev_mainFn(
const std::shared_ptr<sscl::ComponentThread>& componentThread);
/* Stop manager, release all devices; called on SMO shutdown. */
typedef void lcameraDev_exitFn(void);
```
`lcameraDev_main` records the Body `ComponentThread`, starts libcamera's
`CameraManager` (idempotent), and must succeed before any `*CReq` runs.
### Device acquisition
```c
```cpp
struct LcameraDevGetOrCreateResult
{
bool success;
/* Opaque session handle; valid until matching release call. */
void* deviceSession;
/* Resolved libcamera camera ID (stable session key). */
const char* resolvedCameraId;
std::shared_ptr<CameraSession> deviceSession;
CameraIdentityRecord resolvedIdentity;
};
typedef LcameraDevGetOrCreateResult lcameraDev_getOrCreateDeviceFn(
const char* deviceSelector);
typedef sscl::co::ViralNonPostingInvoker<LcameraDevGetOrCreateResult>
lcameraDev_getOrCreateDeviceCReqFn(const std::string& deviceSelector);
typedef void lcameraDev_releaseDeviceFn(void* deviceSession);
typedef sscl::co::ViralNonPostingInvoker<void>
lcameraDev_releaseDeviceCReqFn(
const std::shared_ptr<CameraSession>& deviceSession);
```
`deviceSession` is opaque to callers. `lcameraBuff` passes it back when
detaching; it must not call libcamera APIs directly.
Failures throw `std::exception`. `lcameraBuff` holds the returned
`shared_ptr<CameraSession>` and passes it back to `releaseDeviceCReq`. The
session wraps the acquired `libcamera::Camera`; higher layers configure and
stream from that handle — `lcameraDev` does not expose frame or stream APIs.
### Enumeration (discovery)
```c
```cpp
struct LcameraDevCameraInfo
{
const char* id;
const char* model; /* empty string if unavailable */
const char* location; /* "front", "back", "external", or "" */
std::string id;
std::string model;
std::string location;
};
struct LcameraDevEnumerateResult
{
size_t count;
LcameraDevCameraInfo* cameras; /* caller frees via lcameraDev_freeEnumerateResult */
};
typedef LcameraDevEnumerateResult lcameraDev_enumerateCamerasFn(void);
typedef void lcameraDev_freeEnumerateResultFn(LcameraDevEnumerateResult* result);
typedef sscl::co::ViralNonPostingInvoker<std::vector<LcameraDevCameraInfo>>
lcameraDev_enumerateCamerasCReqFn(void);
```
### Frame access (boundary with lcameraBuff)
### Manual verification tools
Exact frame-delivery API is TBD and will likely use a callback or a “dequeue
latest frame buffer” method on the session handle. `lcameraDev` owns libcamera
`FrameBuffer` allocation and `Request` requeue; `lcameraBuff` copies or maps the
plane it needs for the active qualeIface channel.
When built with `-DENABLE_LIB_lcameraDev=ON`:
Principle: **one negotiated stream format per session** (prefer native YUV such
as NV12/YUYV). `lcameraBuff` + `rasterStimulus` derive H/S/V greyscale planes
from that single stream.
* `lcameraDev_list_cameras` — runs `enumerateCamerasCReq` on a minimal probe
`ComponentThread`.
* `lcameraDev_probe <deviceSelector>``getOrCreateDeviceCReq`, then
`releaseDeviceCReq` (selector and session attach/detach only).
## Module layout (planned)
## Module layout
```text
commonLibs/lcameraDev/
CMakeLists.txt
lcameraDev.h Public C API + C++ internal headers
lcameraDev.cpp dlopen exports, CameraManager singleton
cameraSession.cpp Refcounted session, stream negotiation
selectorResolve.cpp deviceSelector parsing and matching
cameraEnumerate.cpp Discovery / identity records
lcameraDev.h / lcameraDev.cpp Public C API and dlopen exports
cameraManagerState.h / .cpp CameraManager singleton, session map
cameraSession.h / .cpp Refcounted acquired-camera session
cameraIdentity.h / .cpp Discovery identity records
selectorParse.h / .cpp Compound selector parsing
selectorResolve.h / .cpp AND-match resolution
tools/ lcameraDev_list_cameras, lcameraDev_probe
```
Build links against `libcamera` (pkg-config). Does **not** link Salmanoff
@@ -313,8 +320,8 @@ Build links against `libcamera` (pkg-config). Does **not** link Salmanoff
| Component | Responsibility |
|---|---|
| `lcameraDev` | libcamera lifecycle, selector resolution, shared capture session |
| `lcameraBuff` | `StimBuffApiDesc`, `StimulusProducer`, per-channel ring buffers, intrins |
| `lcameraDev` | libcamera lifecycle, selector resolution, refcounted acquired camera session |
| `lcameraBuff` | Stream setup, frames, `StimBuffApiDesc`, channel fan-out, intrins |
| `rasterStimulus` (future) | YUV↔HSV, plane extraction, threshold masks, stencil geometry |
| `xcbWindow` / `waylandWindow` | Separate capture path; reuse `rasterStimulus` only |
@@ -324,12 +331,10 @@ If libcamera IDs prove insufficient in practice, selector policies can gain
## Open questions
1. **Stream format defaults** — prefer first supported YUV format, or allow
`lcameraBuff(pixfmt=...)` override?
2. **Hot-unplug** — on camera removal, fail all attached `lcameraBuff` producers
1. **Hot-unplug** — on camera removal, fail all attached `lcameraBuff` producers
and drop the session, or attempt re-enumeration by stored `lcamera-id:`?
3. **Thread model** — libcamera callbacks vs polling in the Body thread
production daemon; align with `StimulusProducer::productionCDaemon` timing
(`CONFIG_STIMBUFF_FRAME_PERIOD_MS`).
4. **IPA packaging** — document per-platform `apt install` requirements in the
2. **IPA packaging** — document per-platform `apt install` requirements in the
main README when `lcameraBuff` lands (RPi needs `libcamera-ipa`).
Stream format, frame timing, and libcamera callback threading are owned by
`lcameraBuff`, not `lcameraDev`.