fix: accumulation frames LiveKit 240→960 samples avant routing

Problème : LiveKit envoie des frames de 240 samples (5ms @ 48kHz)
mais GroupRouter attend 960 samples (20ms). Cela causait :
- Bruit audio (720 samples de silence ajoutés à chaque frame)
- Latence de plusieurs secondes (buffers qui s'accumulent)

Solution :
- Ajout liveKitFrameAccumulators Map pour chaque groupe
- Accumulation de 4 frames LiveKit (240×4 = 960) avant routing
- Nettoyage logs verbeux dans AudioBridge et GroupAudioRouter

Résultat attendu :
- Audio clair (pas de silence injecté)
- Latence réduite (~20ms au lieu de plusieurs secondes)
This commit is contained in:
2026-05-28 15:21:26 +02:00
parent cfeb275d18
commit 1941e9c8a1
3 changed files with 36930 additions and 37991 deletions
+28 -15
View File
@@ -65,6 +65,9 @@ export class AudioBridge extends EventEmitter {
this.inputChannelBuffers = new Map(); // Map<channelId, Float32Array> this.inputChannelBuffers = new Map(); // Map<channelId, Float32Array>
this.groupBuffersFromLiveKit = new Map(); // Map<groupName, Float32Array> this.groupBuffersFromLiveKit = new Map(); // Map<groupName, Float32Array>
// Frame accumulators pour LiveKit (240 samples → 960 samples)
this.liveKitFrameAccumulators = new Map(); // Map<groupName, { buffer: Float32Array, offset: number }>
// Pool de buffers pré-alloués pour éviter allocations répétées // Pool de buffers pré-alloués pour éviter allocations répétées
this.bufferPool = { this.bufferPool = {
float32: [], // Pool de Float32Array réutilisables float32: [], // Pool de Float32Array réutilisables
@@ -369,12 +372,8 @@ export class AudioBridge extends EventEmitter {
// Réception audio depuis les clients LiveKit de ce groupe // Réception audio depuis les clients LiveKit de ce groupe
client.on('audioData', ({ participantName, pcmData, sampleRate, channels }) => { client.on('audioData', ({ participantName, pcmData, sampleRate, channels }) => {
console.log(`[AudioBridge FLUX 2] Audio reçu de ${participantName} (groupe "${groupName}"): ${pcmData.length} bytes`);
// Router vers le bon groupe // Router vers le bon groupe
this.emit('groupAudioIn', { groupName: groupId, pcmBuffer: pcmData }); this.emit('groupAudioIn', { groupName: groupId, pcmBuffer: pcmData });
console.log(`[AudioBridge FLUX 2] Événement groupAudioIn émis pour groupe "${groupId}"`);
}); });
// Connexion // Connexion
@@ -478,32 +477,46 @@ export class AudioBridge extends EventEmitter {
// Écouter l'audio entrant de LiveKit (sera connecté par LiveKitServerBridge) // Écouter l'audio entrant de LiveKit (sera connecté par LiveKitServerBridge)
this.on('groupAudioIn', ({ groupName, pcmBuffer }) => { this.on('groupAudioIn', ({ groupName, pcmBuffer }) => {
try { try {
console.log(`[AudioBridge FLUX 2] Handler groupAudioIn: groupe="${groupName}", buffer=${pcmBuffer.length} bytes`); // Convertir PCM Buffer → Float32Array
// Stocker le buffer du groupe pour le routing
const float32Data = this._bufferToFloat32(pcmBuffer); const float32Data = this._bufferToFloat32(pcmBuffer);
this.groupBuffersFromLiveKit.set(groupName, float32Data); const samplesReceived = float32Data.length;
console.log(`[AudioBridge FLUX 2] Buffer Float32 créé: ${float32Data.length} samples`); // Initialiser l'accumulateur pour ce groupe si nécessaire
if (!this.liveKitFrameAccumulators.has(groupName)) {
this.liveKitFrameAccumulators.set(groupName, {
buffer: new Float32Array(960), // Frame size attendu par GroupRouter
offset: 0
});
}
const accumulator = this.liveKitFrameAccumulators.get(groupName);
// Copier les samples dans l'accumulateur
accumulator.buffer.set(float32Data, accumulator.offset);
accumulator.offset += samplesReceived;
// Si on a accumulé assez de samples (960), router vers les outputs
if (accumulator.offset >= 960) {
// Stocker le buffer complet pour le routing
this.groupBuffersFromLiveKit.set(groupName, accumulator.buffer);
// ÉTAPE 3 : Groupes → Outputs physiques (via GroupAudioRouter) // ÉTAPE 3 : Groupes → Outputs physiques (via GroupAudioRouter)
const outputBuffers = this.groupAudioRouter.processGroupsToOutputs( const outputBuffers = this.groupAudioRouter.processGroupsToOutputs(
this.groupBuffersFromLiveKit this.groupBuffersFromLiveKit
); );
console.log(`[AudioBridge FLUX 2] GroupRouter processGroupsToOutputs: ${this.groupBuffersFromLiveKit.size} groupes → ${outputBuffers.size} outputs`);
// ÉTAPE 4 : Envoyer chaque output à la carte son // ÉTAPE 4 : Envoyer chaque output à la carte son
outputBuffers.forEach((outputBuffer, channelId) => { outputBuffers.forEach((outputBuffer, channelId) => {
const pcmBuffer = this._float32ToBuffer(outputBuffer); const pcmBuffer = this._float32ToBuffer(outputBuffer);
console.log(`[AudioBridge FLUX 2] → Output ${channelId}: ${pcmBuffer.length} bytes vers carte son`);
// Envoyer à la carte son
this.audioBackend.queueAudio(pcmBuffer); this.audioBackend.queueAudio(pcmBuffer);
}); });
// Réinitialiser l'accumulateur
accumulator.offset = 0;
accumulator.buffer.fill(0);
this.stats.framesPlayback++; this.stats.framesPlayback++;
}
} catch (error) { } catch (error) {
console.error('Erreur routing lecture:', error); console.error('Erreur routing lecture:', error);
this.stats.errors.playback++; this.stats.errors.playback++;
-6
View File
@@ -270,17 +270,11 @@ export class GroupAudioRouter extends EventEmitter {
// Réinitialise les buffers de sortie // Réinitialise les buffers de sortie
this.outputBuffers.clear(); this.outputBuffers.clear();
logger.debug(`[GroupRouter] processGroupsToOutputs: ${groupBuffersData.size} groupes en entrée`);
logger.debug(`[GroupRouter] Routes disponibles: ${JSON.stringify([...this.groupToOutputRoutes.keys()])}`);
// Pour chaque groupe // Pour chaque groupe
groupBuffersData.forEach((pcmData, groupName) => { groupBuffersData.forEach((pcmData, groupName) => {
const routes = this.groupToOutputRoutes.get(groupName); const routes = this.groupToOutputRoutes.get(groupName);
logger.debug(`[GroupRouter] Groupe "${groupName}": ${routes ? routes.length : 0} routes trouvées`);
if (!routes || routes.length === 0) { if (!routes || routes.length === 0) {
logger.warn(`[GroupRouter] Aucune route de sortie configurée pour groupe "${groupName}" - audio ignoré`);
return; return;
} }
+36895 -37963
View File
File diff suppressed because one or more lines are too long