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) // Camera service (rpicam-apps wrapper)
// //
// libcamera lets only ONE process own the camera, so everything here goes through a single // 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); }, 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() { #streamArgs() {
const c = this.#cfg; const c = this.#cfg;
const s = this.#settings; const plan = this.#cameraPlan(this.#settings, c.fps);
const shutter = s.shutterUs > 0 ? Math.min(s.shutterUs, c.streamMaxShutterUs) : 0; const { settings, shutterUs, fps } = plan;
const fps = shutter > 0 ? Math.min(c.fps, 1_000_000 / shutter) : c.fps;
return { return {
fps, fps,
@@ -415,8 +424,8 @@ export class CameraService extends EventEmitter {
'--height', String(c.height), '--height', String(c.height),
'--framerate', fps.toFixed(3), '--framerate', fps.toFixed(3),
'--codec', 'mjpeg', '--codec', 'mjpeg',
'-q', String(s.quality), '-q', String(settings.quality),
...this.#controlArgs(s, { shutterUs: shutter }), ...this.#controlArgs(settings, { shutterUs }),
'-o', '-', '-o', '-',
], ],
}; };
@@ -424,7 +433,7 @@ export class CameraService extends EventEmitter {
#startStream() { #startStream() {
const { fps, args } = this.#streamArgs(); 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 proc = spawn(this.#cfg.vidBin, args, { stdio: ['ignore', 'pipe', 'pipe'] });
const stream = { proc, fps, expected: false, startedAt: Date.now(), frames: 0, exited: null }; 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 } = {}) { #stillArgs(file, settings, { raw = false } = {}) {
const c = this.#cfg; 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 [ return [
'-n', '-n',
@@ -681,7 +691,9 @@ export class CameraService extends EventEmitter {
const w = width || c.videoWidth; const w = width || c.videoWidth;
const h = height || c.videoHeight; 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 base = `video_${stamp()}`;
const rawFile = path.join(this.#dirs.videos, `${base}.h264`); const rawFile = path.join(this.#dirs.videos, `${base}.h264`);
@@ -696,7 +708,7 @@ export class CameraService extends EventEmitter {
'--framerate', String(f), '--framerate', String(f),
'--codec', 'h264', '--codec', 'h264',
'--inline', '--inline',
...this.#controlArgs(settings), ...this.#controlArgs(plan.settings, { shutterUs: plan.shutterUs }),
'-o', rawFile, '-o', rawFile,
]; ];
@@ -750,7 +762,11 @@ export class CameraService extends EventEmitter {
proc.once('close', (code, signal) => finish(code, signal)); 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(); return this.#jobSummary();
} }
@@ -1077,4 +1093,4 @@ export class CameraService extends EventEmitter {
dir: `/media/timelapse/${job.id}`, dir: `/media/timelapse/${job.id}`,
}; };
} }
} }