fix: audio haché depuis carte son par chunks sox de taille variable
- Ajout buffer d'accumulation dans CoreAudioBackend pour garantir frames fixes - sox envoie des chunks de taille variable → accumuler jusqu'à frameSize complet - Émission de frames exactement 960 samples × 2ch × 2 bytes = 3840 bytes - Adaptation automatique mono/stéréo selon config channels - Audio fluide sans hachage/robotique vers clients LiveKit
This commit is contained in:
@@ -32,6 +32,11 @@ export class CoreAudioBackend extends EventEmitter {
|
||||
this.playbackProcess = null;
|
||||
this.isCapturing = false;
|
||||
this.isPlaying = false;
|
||||
this.shuttingDown = false;
|
||||
|
||||
// Buffer d'accumulation pour la capture (sox envoie des chunks de taille variable)
|
||||
this.captureAccumulator = Buffer.alloc(0);
|
||||
this.targetCaptureBytes = this.options.framesPerBuffer * 2 * this.options.channels; // 2 bytes per sample
|
||||
|
||||
// Buffer circulaire pour la lecture
|
||||
this.playbackBuffer = [];
|
||||
@@ -212,8 +217,17 @@ export class CoreAudioBackend extends EventEmitter {
|
||||
this.captureProcess = spawn('sox', args);
|
||||
|
||||
this.captureProcess.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.captureProcess.stderr.on('data', (data) => {
|
||||
@@ -249,6 +263,7 @@ export class CoreAudioBackend extends EventEmitter {
|
||||
this.captureProcess.kill('SIGTERM');
|
||||
this.captureProcess = null;
|
||||
this.isCapturing = false;
|
||||
this.captureAccumulator = Buffer.alloc(0); // Reset accumulator
|
||||
console.log('✓ Capture audio arrêtée');
|
||||
}
|
||||
}
|
||||
@@ -272,7 +287,7 @@ export class CoreAudioBackend extends EventEmitter {
|
||||
// Output = -d (default) OU -t coreaudio "Device Name"
|
||||
|
||||
const args = [
|
||||
'--buffer', '8192', // Buffer interne sox
|
||||
'--buffer', '65536', // Buffer 64k (évite EOF prématuré)
|
||||
'-t', 'raw',
|
||||
'-b', '16',
|
||||
'-e', 'signed-integer',
|
||||
@@ -318,13 +333,20 @@ export class CoreAudioBackend extends EventEmitter {
|
||||
});
|
||||
|
||||
this.playbackProcess.on('close', (code) => {
|
||||
console.log(`⚠️ Sox playback fermé (code ${code}) après ${((Date.now() - this.playbackStartTime) / 1000).toFixed(1)}s`);
|
||||
const uptime = ((Date.now() - this.playbackStartTime) / 1000).toFixed(1);
|
||||
console.log(`⚠️ Sox playback fermé (code ${code}) après ${uptime}s`);
|
||||
this.isPlaying = false;
|
||||
|
||||
// Tenter de redémarrer si c'était inattendu
|
||||
if (code !== 0) {
|
||||
console.log('🔄 Tentative de redémarrage du playback...');
|
||||
setTimeout(() => this.startPlayback(), 1000);
|
||||
// Redémarrer automatiquement (sox se ferme quand le buffer stdin se vide)
|
||||
if (!this.shuttingDown) {
|
||||
console.log('🔄 Redémarrage automatique du playback...');
|
||||
setTimeout(() => {
|
||||
if (!this.shuttingDown) {
|
||||
this.startPlayback().catch(err => {
|
||||
console.error('Erreur redémarrage playback:', err);
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -443,6 +465,7 @@ export class CoreAudioBackend extends EventEmitter {
|
||||
* Arrête tous les streams
|
||||
*/
|
||||
destroy() {
|
||||
this.shuttingDown = true;
|
||||
this.stopCapture();
|
||||
this.stopPlayback();
|
||||
this.removeAllListeners();
|
||||
|
||||
Reference in New Issue
Block a user