Update services/camera.mjs

This commit is contained in:
2026-10-22 02:02:09 -04:00
parent e71c877214
commit 0c965dcb30
+28 -12
View File
@@ -1,4 +1,3 @@
// Camera service (rpicam-apps wrapper)
//
// libcamera lets only ONE process own the camera, so everything here goes through a single
@@ -401,11 +400,21 @@ export class CameraService extends EventEmitter {
}, this.#cfg.streamIdleStopMs);
}
// Resolve exposure and frame timing once so preview and recording don't silently
// choose different shutter speeds or incompatible frame rates.
#cameraPlan(settings, requestedFps) {
const shutterUs = settings.shutterUs > 0 ? settings.shutterUs : 0;
const fps = shutterUs > 0
? Math.min(Number(requestedFps), 1_000_000 / shutterUs)
: Number(requestedFps);
return { settings, shutterUs, fps: Math.max(0.001, fps) };
}
#streamArgs() {
const c = this.#cfg;
const s = this.#settings;
const shutter = s.shutterUs > 0 ? Math.min(s.shutterUs, c.streamMaxShutterUs) : 0;
const fps = shutter > 0 ? Math.min(c.fps, 1_000_000 / shutter) : c.fps;
const plan = this.#cameraPlan(this.#settings, c.fps);
const { settings, shutterUs, fps } = plan;
return {
fps,
@@ -415,8 +424,8 @@ export class CameraService extends EventEmitter {
'--height', String(c.height),
'--framerate', fps.toFixed(3),
'--codec', 'mjpeg',
'-q', String(s.quality),
...this.#controlArgs(s, { shutterUs: shutter }),
'-q', String(settings.quality),
...this.#controlArgs(settings, { shutterUs }),
'-o', '-',
],
};
@@ -424,7 +433,7 @@ export class CameraService extends EventEmitter {
#startStream() {
const { fps, args } = this.#streamArgs();
this.#log.info(`starting live view ${this.#cfg.width}x${this.#cfg.height} @ ${fps.toFixed(1)} fps`);
this.#log.info(`starting live view ${this.#cfg.width}x${this.#cfg.height} @ ${fps.toFixed(3)} fps`);
const proc = spawn(this.#cfg.vidBin, args, { stdio: ['ignore', 'pipe', 'pipe'] });
const stream = { proc, fps, expected: false, startedAt: Date.now(), frames: 0, exited: null };
@@ -584,7 +593,8 @@ export class CameraService extends EventEmitter {
#stillArgs(file, settings, { raw = false } = {}) {
const c = this.#cfg;
const manual = settings.shutterUs > 0 && settings.gain > 0;
const { shutterUs } = this.#cameraPlan(settings, 1);
const manual = shutterUs > 0 && settings.gain > 0;
return [
'-n',
@@ -681,7 +691,9 @@ export class CameraService extends EventEmitter {
const w = width || c.videoWidth;
const h = height || c.videoHeight;
const f = fps || c.videoFps;
const requestedFps = Number(fps || c.videoFps);
const plan = this.#cameraPlan(settings, requestedFps);
const f = plan.fps;
const base = `video_${stamp()}`;
const rawFile = path.join(this.#dirs.videos, `${base}.h264`);
@@ -696,7 +708,7 @@ export class CameraService extends EventEmitter {
'--framerate', String(f),
'--codec', 'h264',
'--inline',
...this.#controlArgs(settings),
...this.#controlArgs(plan.settings, { shutterUs: plan.shutterUs }),
'-o', rawFile,
];
@@ -750,7 +762,11 @@ export class CameraService extends EventEmitter {
proc.once('close', (code, signal) => finish(code, signal));
});
this.#log.info(`video recording started (${w}x${h} @ ${f} fps${durationSec ? `, ${durationSec}s` : ''})`);
this.#log.info(
`video recording started (${w}x${h} @ ${f.toFixed(3)} fps` +
`${f < requestedFps ? `, limited from ${requestedFps} fps by ${plan.shutterUs}µs shutter` : ''}` +
`${durationSec ? `, ${durationSec}s` : ''})`,
);
return this.#jobSummary();
}
@@ -1077,4 +1093,4 @@ export class CameraService extends EventEmitter {
dir: `/media/timelapse/${job.id}`,
};
}
}
}