From 060453fe0689e9e5defe0d94ecff26196e1d734d Mon Sep 17 00:00:00 2001 From: Benoit Date: Tue, 2 Jun 2026 21:19:45 +0200 Subject: [PATCH] fix: application du buffer d'accumulation aux backends Linux (PipeWire/JACK) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ajout buffer d'accumulation dans PipeWireBackend (même pattern que CoreAudio) - Ajout buffer d'accumulation dans JACKBackend - pw-cat et jack_rec émettent aussi des chunks de taille variable - Garantit frames fixes (960 samples) sur tous les backends - Prévient audio haché/robotique sous Linux --- server/bridge/backends/JACKBackend.js | 21 +++++++++++++++++++-- server/bridge/backends/PipeWireBackend.js | 20 +++++++++++++++++++- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/server/bridge/backends/JACKBackend.js b/server/bridge/backends/JACKBackend.js index f223fba..790b814 100644 --- a/server/bridge/backends/JACKBackend.js +++ b/server/bridge/backends/JACKBackend.js @@ -30,6 +30,12 @@ export class JACKBackend extends EventEmitter { this.jackProcess = null; this.isCapturing = false; this.isPlaying = false; + this.shuttingDown = false; + + // Buffer d'accumulation pour la capture (JACK peut envoyer des chunks de taille variable) + this.captureAccumulator = Buffer.alloc(0); + this.targetCaptureBytes = this.options.framesPerBuffer * 2 * this.options.channels; // 2 bytes per sample + this.playbackBuffer = []; this.maxBufferSize = 10; @@ -213,8 +219,17 @@ export class JACKBackend extends EventEmitter { ]); this.jackProcess.stdout.on('data', (audioData) => { - // Émet les données audio capturées (Buffer PCM 16-bit) - this.emit('audioData', audioData); + // Accumuler les données jusqu'à avoir un frame complet + this.captureAccumulator = Buffer.concat([this.captureAccumulator, audioData]); + + // Émettre des frames de taille fixe + while (this.captureAccumulator.length >= this.targetCaptureBytes) { + const frame = this.captureAccumulator.subarray(0, this.targetCaptureBytes); + this.emit('audioData', Buffer.from(frame)); // Copier pour éviter les références + + // Garder le reste pour la prochaine frame + this.captureAccumulator = this.captureAccumulator.subarray(this.targetCaptureBytes); + } }); this.jackProcess.stderr.on('data', (data) => { @@ -248,6 +263,7 @@ export class JACKBackend extends EventEmitter { this.jackProcess.kill('SIGTERM'); this.jackProcess = null; this.isCapturing = false; + this.captureAccumulator = Buffer.alloc(0); // Reset accumulator console.log('✓ Capture JACK arrêtée'); } } @@ -359,6 +375,7 @@ export class JACKBackend extends EventEmitter { * Arrête tous les streams */ destroy() { + this.shuttingDown = true; this.stopCapture(); this.stopPlayback(); this.removeAllListeners(); diff --git a/server/bridge/backends/PipeWireBackend.js b/server/bridge/backends/PipeWireBackend.js index 8f4698c..38f2d17 100644 --- a/server/bridge/backends/PipeWireBackend.js +++ b/server/bridge/backends/PipeWireBackend.js @@ -33,6 +33,12 @@ export class PipeWireBackend extends EventEmitter { this.playbackProcess = null; this.isCapturing = false; this.isPlaying = false; + this.shuttingDown = false; + + // Buffer d'accumulation pour la capture (pw-cat envoie des chunks de taille variable) + this.captureAccumulator = Buffer.alloc(0); + this.targetCaptureBytes = this.options.framesPerBuffer * 2 * this.options.channels; // 2 bytes per sample + this.playbackBuffer = []; this.maxBufferSize = 10; } @@ -194,7 +200,17 @@ export class PipeWireBackend extends EventEmitter { this.captureProcess = spawn('pw-cat', args); this.captureProcess.stdout.on('data', (audioData) => { - this.emit('audioData', audioData); + // Accumuler les données jusqu'à avoir un frame complet + this.captureAccumulator = Buffer.concat([this.captureAccumulator, audioData]); + + // Émettre des frames de taille fixe + while (this.captureAccumulator.length >= this.targetCaptureBytes) { + const frame = this.captureAccumulator.subarray(0, this.targetCaptureBytes); + this.emit('audioData', Buffer.from(frame)); // Copier pour éviter les références + + // Garder le reste pour la prochaine frame + this.captureAccumulator = this.captureAccumulator.subarray(this.targetCaptureBytes); + } }); this.captureProcess.stderr.on('data', (data) => { @@ -231,6 +247,7 @@ export class PipeWireBackend extends EventEmitter { this.captureProcess.kill('SIGTERM'); this.captureProcess = null; this.isCapturing = false; + this.captureAccumulator = Buffer.alloc(0); // Reset accumulator console.log('✓ Capture PipeWire arrêtée'); } } @@ -360,6 +377,7 @@ export class PipeWireBackend extends EventEmitter { * Arrête tous les streams */ destroy() { + this.shuttingDown = true; this.stopCapture(); this.stopPlayback(); this.removeAllListeners();