From c9ec10dfd97463b09e99bfea558098630d482ffb Mon Sep 17 00:00:00 2001 From: Benoit Date: Thu, 28 May 2026 16:13:35 +0200 Subject: [PATCH 1/7] fix: shutdown propre sans erreurs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problèmes corrigés pendant l'arrêt serveur : 1. RangeError offset bounds : vérification availableSpace avant .set() 2. audioBackend null : vérification avant queueAudio() 3. LiveKit track not found : try/catch sur unpublishTrack Shutdown maintenant sans erreurs fatales. --- client/dev-dist/sw.js | 2 +- client/dev-dist/sw.js.map | 2 +- server/bridge/AudioBridge.js | 15 +++++++++++++-- server/bridge/LiveKitClient.js | 9 ++++++++- server/config/config.yaml | 10 ++++------ 5 files changed, 27 insertions(+), 11 deletions(-) diff --git a/client/dev-dist/sw.js b/client/dev-dist/sw.js index 328e5b3..ee9a6c4 100644 --- a/client/dev-dist/sw.js +++ b/client/dev-dist/sw.js @@ -81,7 +81,7 @@ define(['./workbox-290dd570'], (function (workbox) { 'use strict'; "revision": "3ca0b8505b4bec776b69afdba2768812" }, { "url": "index.html", - "revision": "0.881sreuemg" + "revision": "0.0p0vks93ec8" }], {}); workbox.cleanupOutdatedCaches(); workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), { diff --git a/client/dev-dist/sw.js.map b/client/dev-dist/sw.js.map index 8081cf2..3f08f5e 100644 --- a/client/dev-dist/sw.js.map +++ b/client/dev-dist/sw.js.map @@ -1 +1 @@ -{"version":3,"file":"sw.js","sources":["../../../../../../private/var/folders/lc/87d3wzj544l4skb096m6003m0000gn/T/f8fa4b4e219ad6182d9f85cf284aed1f/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-routing/registerRoute.mjs';\nimport {ExpirationPlugin as workbox_expiration_ExpirationPlugin} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-expiration/ExpirationPlugin.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-core/clientsClaim.mjs';\nimport {precacheAndRoute as workbox_precaching_precacheAndRoute} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-precaching/precacheAndRoute.mjs';\nimport {cleanupOutdatedCaches as workbox_precaching_cleanupOutdatedCaches} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-precaching/cleanupOutdatedCaches.mjs';\nimport {NavigationRoute as workbox_routing_NavigationRoute} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-routing/NavigationRoute.mjs';\nimport {createHandlerBoundToURL as workbox_precaching_createHandlerBoundToURL} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-precaching/createHandlerBoundToURL.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\n\n\nself.skipWaiting();\nworkbox_core_clientsClaim();\n/**\n * The precacheAndRoute() method efficiently caches and responds to\n * requests for URLs in the manifest.\n * See https://goo.gl/S9QRab\n */\nworkbox_precaching_precacheAndRoute([\n {\n \"url\": \"registerSW.js\",\n \"revision\": \"3ca0b8505b4bec776b69afdba2768812\"\n },\n {\n \"url\": \"index.html\",\n \"revision\": \"0.881sreuemg\"\n }\n], {});\nworkbox_precaching_cleanupOutdatedCaches();workbox_routing_registerRoute(new workbox_routing_NavigationRoute(workbox_precaching_createHandlerBoundToURL(\"index.html\"), {\n allowlist: [/^\\/$/], }));\nworkbox_routing_registerRoute(/^https:\\/\\/.*\\.livekit\\.cloud\\/.*/i, new workbox_strategies_NetworkFirst({ \"cacheName\":\"livekit-cache\", plugins: [new workbox_expiration_ExpirationPlugin({ maxEntries: 10, maxAgeSeconds: 86400 })] }), 'GET');\n\n\n"],"names":["self","skipWaiting","workbox_core_clientsClaim","workbox_precaching_precacheAndRoute","workbox_precaching_cleanupOutdatedCaches","workbox_routing_registerRoute","workbox_routing_NavigationRoute","workbox_precaching_createHandlerBoundToURL","allowlist","workbox_strategies_NetworkFirst","plugins","workbox_expiration_ExpirationPlugin","maxEntries","maxAgeSeconds"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"sw.js","sources":["../../../../../../private/var/folders/lc/87d3wzj544l4skb096m6003m0000gn/T/5e890726dc94fea00c4cff61d0cf3d6d/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-routing/registerRoute.mjs';\nimport {ExpirationPlugin as workbox_expiration_ExpirationPlugin} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-expiration/ExpirationPlugin.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-core/clientsClaim.mjs';\nimport {precacheAndRoute as workbox_precaching_precacheAndRoute} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-precaching/precacheAndRoute.mjs';\nimport {cleanupOutdatedCaches as workbox_precaching_cleanupOutdatedCaches} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-precaching/cleanupOutdatedCaches.mjs';\nimport {NavigationRoute as workbox_routing_NavigationRoute} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-routing/NavigationRoute.mjs';\nimport {createHandlerBoundToURL as workbox_precaching_createHandlerBoundToURL} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-precaching/createHandlerBoundToURL.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\n\n\nself.skipWaiting();\nworkbox_core_clientsClaim();\n/**\n * The precacheAndRoute() method efficiently caches and responds to\n * requests for URLs in the manifest.\n * See https://goo.gl/S9QRab\n */\nworkbox_precaching_precacheAndRoute([\n {\n \"url\": \"registerSW.js\",\n \"revision\": \"3ca0b8505b4bec776b69afdba2768812\"\n },\n {\n \"url\": \"index.html\",\n \"revision\": \"0.0p0vks93ec8\"\n }\n], {});\nworkbox_precaching_cleanupOutdatedCaches();workbox_routing_registerRoute(new workbox_routing_NavigationRoute(workbox_precaching_createHandlerBoundToURL(\"index.html\"), {\n allowlist: [/^\\/$/], }));\nworkbox_routing_registerRoute(/^https:\\/\\/.*\\.livekit\\.cloud\\/.*/i, new workbox_strategies_NetworkFirst({ \"cacheName\":\"livekit-cache\", plugins: [new workbox_expiration_ExpirationPlugin({ maxEntries: 10, maxAgeSeconds: 86400 })] }), 'GET');\n\n\n"],"names":["self","skipWaiting","workbox_core_clientsClaim","workbox_precaching_precacheAndRoute","workbox_precaching_cleanupOutdatedCaches","workbox_routing_registerRoute","workbox_routing_NavigationRoute","workbox_precaching_createHandlerBoundToURL","allowlist","workbox_strategies_NetworkFirst","plugins","workbox_expiration_ExpirationPlugin","maxEntries","maxAgeSeconds"],"mappings":"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"} \ No newline at end of file diff --git a/server/bridge/AudioBridge.js b/server/bridge/AudioBridge.js index e3f66f4..fe71172 100644 --- a/server/bridge/AudioBridge.js +++ b/server/bridge/AudioBridge.js @@ -491,12 +491,23 @@ export class AudioBridge extends EventEmitter { const accumulator = this.liveKitFrameAccumulators.get(groupName); + // Vérifier que le buffer ne débordera pas + const availableSpace = 960 - accumulator.offset; + const samplesToCopy = Math.min(samplesReceived, availableSpace); + // Copier les samples dans l'accumulateur - accumulator.buffer.set(float32Data, accumulator.offset); - accumulator.offset += samplesReceived; + if (samplesToCopy > 0) { + accumulator.buffer.set(float32Data.subarray(0, samplesToCopy), accumulator.offset); + accumulator.offset += samplesToCopy; + } // Si on a accumulé assez de samples (960), router vers les outputs if (accumulator.offset >= 960) { + // Vérifier que le backend est toujours actif (évite crash pendant shutdown) + if (!this.audioBackend) { + return; + } + // Stocker le buffer complet pour le routing this.groupBuffersFromLiveKit.set(groupName, accumulator.buffer); diff --git a/server/bridge/LiveKitClient.js b/server/bridge/LiveKitClient.js index 272ca23..a3948a7 100644 --- a/server/bridge/LiveKitClient.js +++ b/server/bridge/LiveKitClient.js @@ -349,7 +349,14 @@ export class LiveKitClient extends EventEmitter { if (this.room) { // Unpublish track if (this.localAudioTrack) { - await this.room.localParticipant.unpublishTrack(this.localAudioTrack.sid); + try { + await this.room.localParticipant.unpublishTrack(this.localAudioTrack.sid); + } catch (error) { + // Ignorer l'erreur si le track n'existe plus (shutdown rapide) + if (!error.message?.includes('track not found')) { + console.warn('⚠️ Erreur unpublish track:', error.message); + } + } this.localAudioTrack = null; } diff --git a/server/config/config.yaml b/server/config/config.yaml index 662a0a0..6f8231d 100644 --- a/server/config/config.yaml +++ b/server/config/config.yaml @@ -4,10 +4,8 @@ audio: defaultBitrate: 96 jitterBufferMs: 40 device: - # Laissez null pour auto-détection du device par défaut - # Ou spécifiez le nom exact via l'interface /admin - inputDeviceId: alsa_input.pci-0000_00_01.0.analog-stereo - outputDeviceId: alsa_output.pci-0000_00_01.0.analog-stereo + inputDeviceId: Microphone MacBook Pro + outputDeviceId: Haut-parleurs MacBook Pro sampleRate: 48000 routing: inputToGroup: @@ -52,8 +50,8 @@ server: host: 0.0.0.0 port: 3000 livekit: - url: AUTO # AUTO = détection automatique IP réseau | ou ws://IP:7880 pour manuel + url: AUTO logging: - level: debug # Changez à 'debug' pour voir plus de détails + level: debug logLatency: false logAudioStats: false -- 2.52.0 From f302b3f26610915b164d61feae55f95b66e6388f Mon Sep 17 00:00:00 2001 From: Benoit Date: Mon, 1 Jun 2026 23:24:38 +0200 Subject: [PATCH 2/7] fix: persistance des groupes avec ConfigManager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Conversion des routes groupes (GET/POST/PUT/DELETE) pour utiliser configManager - Suppression de loadConfig() et saveConfig() locales (redondantes) - Fix: les modifications de groupes sont maintenant persistées correctement - Les groupes créés/modifiés/supprimés survivent au redémarrage du serveur --- server/api/admin.js | 68 ++++++++------------------------------------- 1 file changed, 12 insertions(+), 56 deletions(-) diff --git a/server/api/admin.js b/server/api/admin.js index 3ca1263..8f65f3e 100644 --- a/server/api/admin.js +++ b/server/api/admin.js @@ -4,9 +4,6 @@ */ import { Router } from 'express'; -import { readFileSync, writeFileSync } from 'fs'; -import { join } from 'path'; -import YAML from 'yaml'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; import { CoreAudioBackend } from '../bridge/backends/CoreAudioBackend.js'; @@ -41,47 +38,6 @@ const stats = { logs: [] }; -// Configuration file path -const configPath = join(__dirname, '..', 'config', 'config.yaml'); - -/** - * Charge la configuration depuis le fichier YAML - * et génère les IDs à partir des noms - */ -function loadConfig() { - const configFile = readFileSync(configPath, 'utf8'); - const config = YAML.parse(configFile); - - // Générer les IDs pour les groupes - config.groups = config.groups.map(group => { - const groupId = slugify(group.name); - return { - ...group, - id: groupId - }; - }); - - return config; -} - -/** - * Sauvegarde la configuration dans le fichier YAML - * Ne sauvegarde PAS les IDs (ils sont générés dynamiquement) - */ -function saveConfig(config) { - // Nettoyer les IDs avant de sauvegarder - const cleanConfig = { - ...config, - groups: config.groups.map(group => { - const { id, ...groupWithoutId } = group; - return groupWithoutId; - }) - }; - - const yamlContent = YAML.stringify(cleanConfig); - writeFileSync(configPath, yamlContent, 'utf8'); -} - /** * Ajoute un log au système */ @@ -166,7 +122,7 @@ export function addAudioStats(data) { */ router.get('/groups', (req, res) => { try { - const config = loadConfig(); + const config = configManager.get(); res.json({ groups: config.groups }); @@ -192,7 +148,7 @@ router.post('/groups', (req, res) => { }); } - const config = loadConfig(); + const config = configManager.get(); // Générer l'ID à partir du nom const id = slugify(name); @@ -211,7 +167,7 @@ router.post('/groups', (req, res) => { }; config.groups.push(newGroup); - saveConfig(config); + configManager.save(config); addLog('info', `Group created: ${name}`, { id }); @@ -237,7 +193,7 @@ router.put('/groups/:id', (req, res) => { const { id } = req.params; const { name, audioBitrate } = req.body; - const config = loadConfig(); + const config = configManager.get(); // Chercher le groupe par son nom (qui correspond à l'ID slugifié) const groupIndex = config.groups.findIndex(g => slugify(g.name) === id); @@ -252,12 +208,12 @@ router.put('/groups/:id', (req, res) => { if (name !== undefined) config.groups[groupIndex].name = name; if (audioBitrate !== undefined) config.groups[groupIndex].audioBitrate = audioBitrate; - saveConfig(config); + configManager.save(config); addLog('info', `Group updated: ${config.groups[groupIndex].name}`, { id }); - // Recharger pour obtenir les IDs générés - const updatedConfig = loadConfig(); + // Récupérer la config à jour avec les IDs générés + const updatedConfig = configManager.get(); const updatedGroupIndex = updatedConfig.groups.findIndex(g => slugify(g.name) === id || slugify(g.name) === slugify(name)); const updatedGroup = updatedGroupIndex !== -1 ? updatedConfig.groups[updatedGroupIndex] : null; @@ -281,7 +237,7 @@ router.delete('/groups/:id', (req, res) => { try { const { id } = req.params; - const config = loadConfig(); + const config = configManager.get(); const groupIndex = config.groups.findIndex(g => slugify(g.name) === id); if (groupIndex === -1) { @@ -292,7 +248,7 @@ router.delete('/groups/:id', (req, res) => { const groupName = config.groups[groupIndex].name; config.groups.splice(groupIndex, 1); - saveConfig(config); + configManager.save(config); addLog('info', `Group deleted: ${groupName}`, { id }); @@ -412,7 +368,7 @@ router.get('/logs', (req, res) => { */ router.get('/config', (req, res) => { try { - const config = loadConfig(); + const config = configManager.get(); res.json(config); } catch (error) { console.error('Erreur GET /admin/config:', error); @@ -429,13 +385,13 @@ router.put('/config/audio', (req, res) => { try { const { sampleRate, defaultBitrate, jitterBufferMs } = req.body; - const config = loadConfig(); + const config = configManager.get(); if (sampleRate !== undefined) config.audio.sampleRate = sampleRate; if (defaultBitrate !== undefined) config.audio.defaultBitrate = defaultBitrate; if (jitterBufferMs !== undefined) config.audio.jitterBufferMs = jitterBufferMs; - saveConfig(config); + configManager.save(config); addLog('info', 'Audio config updated', { sampleRate, defaultBitrate, jitterBufferMs }); -- 2.52.0 From 36e1799ec5361c294706fd0c5b0688c347e4b039 Mon Sep 17 00:00:00 2001 From: Benoit Date: Mon, 1 Jun 2026 23:51:01 +0200 Subject: [PATCH 3/7] fix: chargement des groupes dans l'onglet Audio pour matrice de routing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ajout fetch groupes dans loadAudioDevices() - Fix: matrice de routing maintenant éditable (groupes chargés) - Fix: WebSocket audio-levels connecté (nécessite VITE_WS_AUDIO_LEVELS_URL dans .env) --- client/src/Admin.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/client/src/Admin.jsx b/client/src/Admin.jsx index 80b3f99..82f275b 100644 --- a/client/src/Admin.jsx +++ b/client/src/Admin.jsx @@ -108,17 +108,20 @@ function Admin() { }; const loadAudioDevices = async () => { - const [devicesRes, currentDeviceRes, channelNamesRes] = await Promise.all([ + const [devicesRes, currentDeviceRes, channelNamesRes, groupsRes] = await Promise.all([ fetch(`${API_URL}/admin/audio/devices`), fetch(`${API_URL}/admin/audio/device`), - fetch(`${API_URL}/admin/audio/channels/names`) + fetch(`${API_URL}/admin/audio/channels/names`), + fetch(`${API_URL}/admin/groups`) ]); const devicesData = await devicesRes.json(); const currentData = await currentDeviceRes.json(); const channelNamesData = await channelNamesRes.json(); + const groupsData = await groupsRes.json(); setAudioDevices(devicesData.devices || []); + setGroups(groupsData.groups || []); const device = currentData.device || { inputChannels: 8, outputChannels: 8 }; setCurrentDevice(device); -- 2.52.0 From a803250f9fcd7b244e115ea5ba14b77423d1451b Mon Sep 17 00:00:00 2001 From: Benoit Date: Tue, 2 Jun 2026 00:33:26 +0200 Subject: [PATCH 4/7] fix: routing audio macOS avec support multi-canaux et LiveKit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Corrections majeures pour le support audio sous macOS : - CoreAudioBackend : syntaxe sox correcte avec `-t coreaudio "Device Name"` - AudioBridge : dé-entrelacement stéréo → canaux séparés (ligne 410-424) - AudioBridge : entrelacement canaux → stéréo pour sortie (ligne 490-522) - AudioBridge : duplication mono → stéréo pour LiveKit (ligne 438-449) - config.yaml : ajout `channels: 2` pour capture stéréo - config.yaml : ajout groupes "Production" et "Technique" Résultat : - Capture stéréo fonctionnelle depuis Loopback Audio 4 - Routing : 2 inputs → 3 groupes → LiveKit + 2 outputs - Format audio correct pour LiveKit (mono dupliqué en stéréo) - Pas d'erreur "Taille frame incorrecte" Problème restant : sox playback se ferme après 0.4s (EPIPE) --- server/bridge/AudioBridge.js | 96 +++++++++++++++++++--- server/bridge/backends/CoreAudioBackend.js | 67 ++++++++------- server/config/config.yaml | 21 +++-- 3 files changed, 133 insertions(+), 51 deletions(-) diff --git a/server/bridge/AudioBridge.js b/server/bridge/AudioBridge.js index fe71172..7321291 100644 --- a/server/bridge/AudioBridge.js +++ b/server/bridge/AudioBridge.js @@ -399,10 +399,30 @@ export class AudioBridge extends EventEmitter { // Convertir PCM Buffer → Float32Array (pour GroupAudioRouter) const float32Data = this._bufferToFloat32(pcmData); - // Pour l'instant, on assume que l'audio vient du canal 0 - // TODO: Supporter multi-canaux depuis la carte son - const channelId = this.options.inputDeviceChannel || 0; - this.inputChannelBuffers.set(channelId, float32Data); + // Séparer les canaux si audio multi-canaux (entrelacé) + const numChannels = this.options.channels || 1; + + if (numChannels === 1) { + // Mono : un seul canal + const channelId = this.options.inputDeviceChannel || 0; + this.inputChannelBuffers.set(channelId, float32Data); + } else { + // Multi-canaux : dé-entrelacer les samples + // Format entrelacé : [L0, R0, L1, R1, ...] → [L0, L1, ...] et [R0, R1, ...] + const samplesPerChannel = float32Data.length / numChannels; + + for (let ch = 0; ch < numChannels; ch++) { + const channelBuffer = new Float32Array(samplesPerChannel); + + for (let i = 0; i < samplesPerChannel; i++) { + channelBuffer[i] = float32Data[i * numChannels + ch]; + } + + // Mapper canal hardware → canal logique (peut être configuré) + const logicalChannelId = this.options.channelMapping?.[ch] ?? ch; + this.inputChannelBuffers.set(logicalChannelId, channelBuffer); + } + } // ÉTAPE 1 : Inputs physiques → Groupes (via GroupAudioRouter) const groupBuffers = this.groupAudioRouter.processInputsToGroups( @@ -415,10 +435,23 @@ export class AudioBridge extends EventEmitter { // ÉTAPE 2 : Pour chaque groupe, envoyer vers le LiveKitClient correspondant groupBuffers.forEach((groupBuffer, groupName) => { - // Convertir Float32Array → PCM Buffer - const pcmBuffer = this._float32ToBuffer(groupBuffer); + // Les groupes sont MONO (Float32Array de N samples) + // Mais LiveKit attend du STÉRÉO (2 canaux) + // → Dupliquer le canal mono pour créer du faux stéréo - // Encoder en Opus + const samplesPerChannel = groupBuffer.length; + const stereoBuffer = new Float32Array(samplesPerChannel * 2); + + // Entrelacer : [M0, M1, M2, ...] → [M0, M0, M1, M1, M2, M2, ...] + for (let i = 0; i < samplesPerChannel; i++) { + stereoBuffer[i * 2] = groupBuffer[i]; // Canal gauche + stereoBuffer[i * 2 + 1] = groupBuffer[i]; // Canal droit (dupliqué) + } + + // Convertir Float32Array stéréo → PCM Buffer + const pcmBuffer = this._float32ToBuffer(stereoBuffer); + + // Encoder en Opus (maintenant en stéréo) const opusData = this.opusEncoder.encode(pcmBuffer); if (opusData) { @@ -432,7 +465,7 @@ export class AudioBridge extends EventEmitter { if (client && client.isConnected) { client.sendAudioData(pcmBuffer); if (this.stats.framesCapture % 100 === 0) { - console.log(`[AudioBridge] → LiveKit groupe "${groupName}": ${pcmBuffer.length} bytes`); + console.log(`[AudioBridge] → LiveKit groupe "${groupName}": ${pcmBuffer.length} bytes (mono→stéréo)`); } } else { if (this.stats.framesCapture % 100 === 0) { @@ -453,16 +486,53 @@ export class AudioBridge extends EventEmitter { } // ÉTAPE 4 : Envoyer chaque output à la carte son - outputBuffers.forEach((outputBuffer, channelId) => { - const pcmBuffer = this._float32ToBuffer(outputBuffer); + const numOutputChannels = this.options.channels || 1; - // Envoyer à la carte son + if (numOutputChannels === 1) { + // Mono : un seul output + if (outputBuffers.size > 0) { + const [firstChannelId, outputBuffer] = outputBuffers.entries().next().value; + const pcmBuffer = this._float32ToBuffer(outputBuffer); + this.audioBackend.queueAudio(pcmBuffer); + + if (this.stats.framesCapture % 100 === 0) { + console.log(`[AudioBridge] → Output mono (canal ${firstChannelId}): ${pcmBuffer.length} bytes`); + } + } + } else { + // Multi-canaux : entrelacer les samples + // Récupérer les buffers dans l'ordre des canaux hardware + const channelBuffers = []; + const samplesPerChannel = this.options.frameSize; + + for (let ch = 0; ch < numOutputChannels; ch++) { + const logicalChannelId = this.options.channelMapping?.[ch] ?? ch; + const buffer = outputBuffers.get(logicalChannelId); + + if (buffer && buffer.length === samplesPerChannel) { + channelBuffers.push(buffer); + } else { + // Canal absent ou taille incorrecte : silence + channelBuffers.push(new Float32Array(samplesPerChannel)); + } + } + + // Entrelacer : [L0, L1, ...] et [R0, R1, ...] → [L0, R0, L1, R1, ...] + const interleavedBuffer = new Float32Array(samplesPerChannel * numOutputChannels); + + for (let i = 0; i < samplesPerChannel; i++) { + for (let ch = 0; ch < numOutputChannels; ch++) { + interleavedBuffer[i * numOutputChannels + ch] = channelBuffers[ch][i]; + } + } + + const pcmBuffer = this._float32ToBuffer(interleavedBuffer); this.audioBackend.queueAudio(pcmBuffer); if (this.stats.framesCapture % 100 === 0) { - console.log(`[AudioBridge] → Output ${channelId}: ${pcmBuffer.length} bytes`); + console.log(`[AudioBridge] → Output multi-canaux (${numOutputChannels}ch): ${pcmBuffer.length} bytes`); } - }); + } this.stats.framesCapture++; this.stats.framesPlayback++; diff --git a/server/bridge/backends/CoreAudioBackend.js b/server/bridge/backends/CoreAudioBackend.js index 5983895..50d7cad 100644 --- a/server/bridge/backends/CoreAudioBackend.js +++ b/server/bridge/backends/CoreAudioBackend.js @@ -184,30 +184,33 @@ export class CoreAudioBackend extends EventEmitter { } try { - // Commande sox pour capturer audio - // rec : enregistrer depuis input par défaut - // -t raw : format raw PCM - // -b 16 : 16-bit - // -e signed-integer : signed PCM - // -c 1 : mono (ou nombre de canaux) - // -r 48000 : sample rate - // - : sortie vers stdout - const args = [ - '-t', 'coreaudio', // Driver CoreAudio - 'default', // Device par défaut (ou spécifier nom) + // Commande sox pour capturer audio sur macOS + // Sur macOS, sox utilise CoreAudio par défaut via 'rec' (alias de sox -d) + // Format: sox -d [options] output + // -d = default input device OU -t coreaudio "Device Name" + + const args = []; + + // Spécifier le device d'entrée + if (this.options.inputDeviceName) { + // Utiliser le device spécifié par son nom + args.push('-t', 'coreaudio', this.options.inputDeviceName); + } else { + // Device par défaut + args.push('-d'); + } + + // Format de sortie (stdout) + args.push( '-t', 'raw', '-b', '16', '-e', 'signed-integer', - `-c`, String(this.options.channels), - `-r`, String(this.options.sampleRate), + '-c', String(this.options.channels), + '-r', String(this.options.sampleRate), '-' // Stdout - ]; - - // Si device spécifié - if (this.options.inputDeviceName) { - args[2] = this.options.inputDeviceName; // Index 2 = device name - } + ); + console.log(`🎤 Démarrage capture sox: ${args.join(' ')}`); this.captureProcess = spawn('sox', args); this.captureProcess.stdout.on('data', (audioData) => { @@ -265,27 +268,31 @@ export class CoreAudioBackend extends EventEmitter { } try { - // Commande sox pour lecture audio - // play : lire vers output par défaut - // -t raw : format raw PCM depuis stdin - // --buffer : taille du buffer interne sox (en bytes) + // Commande sox pour lecture audio sur macOS + // Format: sox [options] input output + // Input = stdin (-) + // Output = -d (default) OU -t coreaudio "Device Name" + const args = [ '--buffer', '8192', // Buffer interne sox '-t', 'raw', '-b', '16', '-e', 'signed-integer', - `-c`, String(this.options.channels), - `-r`, String(this.options.sampleRate), - '-', // Stdin - '-t', 'coreaudio', - 'default' // Device par défaut + '-c', String(this.options.channels), + '-r', String(this.options.sampleRate), + '-' // Input = stdin ]; - // Si device spécifié + // Spécifier le device de sortie if (this.options.outputDeviceName) { - args[args.length - 1] = this.options.outputDeviceName; + // Utiliser le device spécifié par son nom + args.push('-t', 'coreaudio', this.options.outputDeviceName); + } else { + // Device par défaut + args.push('-d'); } + console.log(`🔊 Démarrage playback sox: ${args.join(' ')}`); this.playbackProcess = spawn('sox', args, { stdio: ['pipe', 'ignore', 'pipe'] // stdin=pipe, stdout=ignore, stderr=pipe }); diff --git a/server/config/config.yaml b/server/config/config.yaml index 6f8231d..10b456f 100644 --- a/server/config/config.yaml +++ b/server/config/config.yaml @@ -1,17 +1,20 @@ audio: sampleRate: 48000 + channels: 2 frameSize: 20 defaultBitrate: 96 jitterBufferMs: 40 device: - inputDeviceId: Microphone MacBook Pro + inputDeviceId: Loopback Audio 4 outputDeviceId: Haut-parleurs MacBook Pro sampleRate: 48000 routing: inputToGroup: "0": - production - "1": [] + - default + "1": + - default "2": [] "4": - technique @@ -23,29 +26,31 @@ audio: production: - "0" - "1" + default: + - "0" gains: {} channelNames: inputs: - "0": iphone + "0": Mac "1": Talkback FOH "2": Retour Console "3": Liaison Scène "4": Monitor Mix "5": Spare 1 outputs: - "0": Sortie Principale - "1": Retour Scène + "0": L + "1": R "2": Talkback Console groups: + - name: Default + audioBitrate: 96 + channels: [] - name: Production audioBitrate: 96 channels: [] - name: Technique audioBitrate: 96 channels: [] - - name: Sonorisation - audioBitrate: 128 - channels: [] server: host: 0.0.0.0 port: 3000 -- 2.52.0 From 9aff58c5288a95e7df3192937dbaa9d2b2d54115 Mon Sep 17 00:00:00 2001 From: Benoit Date: Tue, 2 Jun 2026 00:45:41 +0200 Subject: [PATCH 5/7] =?UTF-8?q?fix:=20d=C3=A9formation=20audio=20par=20sat?= =?UTF-8?q?uration=20du=20mixage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problème: - Son complètement déformé (clipping massif) - CoreAudio capture en 32-bit mais traité comme 16-bit - Mixage additif sans normalisation Solution: 1. Sox convertit 32→16 bit automatiquement 2. GroupAudioRouter divise gain par nombre de sources Exemple: 2 inputs → groupe default = gain × 0.5 chacun Résultat: Aucun clipping détecté, audio propre --- client/dev-dist/sw.js | 2 +- client/dev-dist/sw.js.map | 2 +- server/bridge/GroupAudioRouter.js | 22 ++++++++++++++++++++-- server/bridge/backends/CoreAudioBackend.js | 10 ++++------ 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/client/dev-dist/sw.js b/client/dev-dist/sw.js index ee9a6c4..04ba11b 100644 --- a/client/dev-dist/sw.js +++ b/client/dev-dist/sw.js @@ -81,7 +81,7 @@ define(['./workbox-290dd570'], (function (workbox) { 'use strict'; "revision": "3ca0b8505b4bec776b69afdba2768812" }, { "url": "index.html", - "revision": "0.0p0vks93ec8" + "revision": "0.rol69f8gtdg" }], {}); workbox.cleanupOutdatedCaches(); workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), { diff --git a/client/dev-dist/sw.js.map b/client/dev-dist/sw.js.map index 3f08f5e..e54a28b 100644 --- a/client/dev-dist/sw.js.map +++ b/client/dev-dist/sw.js.map @@ -1 +1 @@ -{"version":3,"file":"sw.js","sources":["../../../../../../private/var/folders/lc/87d3wzj544l4skb096m6003m0000gn/T/5e890726dc94fea00c4cff61d0cf3d6d/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-routing/registerRoute.mjs';\nimport {ExpirationPlugin as workbox_expiration_ExpirationPlugin} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-expiration/ExpirationPlugin.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-core/clientsClaim.mjs';\nimport {precacheAndRoute as workbox_precaching_precacheAndRoute} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-precaching/precacheAndRoute.mjs';\nimport {cleanupOutdatedCaches as workbox_precaching_cleanupOutdatedCaches} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-precaching/cleanupOutdatedCaches.mjs';\nimport {NavigationRoute as workbox_routing_NavigationRoute} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-routing/NavigationRoute.mjs';\nimport {createHandlerBoundToURL as workbox_precaching_createHandlerBoundToURL} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-precaching/createHandlerBoundToURL.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\n\n\nself.skipWaiting();\nworkbox_core_clientsClaim();\n/**\n * The precacheAndRoute() method efficiently caches and responds to\n * requests for URLs in the manifest.\n * See https://goo.gl/S9QRab\n */\nworkbox_precaching_precacheAndRoute([\n {\n \"url\": \"registerSW.js\",\n \"revision\": \"3ca0b8505b4bec776b69afdba2768812\"\n },\n {\n \"url\": \"index.html\",\n \"revision\": \"0.0p0vks93ec8\"\n }\n], {});\nworkbox_precaching_cleanupOutdatedCaches();workbox_routing_registerRoute(new workbox_routing_NavigationRoute(workbox_precaching_createHandlerBoundToURL(\"index.html\"), {\n allowlist: [/^\\/$/], }));\nworkbox_routing_registerRoute(/^https:\\/\\/.*\\.livekit\\.cloud\\/.*/i, new workbox_strategies_NetworkFirst({ \"cacheName\":\"livekit-cache\", plugins: [new workbox_expiration_ExpirationPlugin({ maxEntries: 10, maxAgeSeconds: 86400 })] }), 'GET');\n\n\n"],"names":["self","skipWaiting","workbox_core_clientsClaim","workbox_precaching_precacheAndRoute","workbox_precaching_cleanupOutdatedCaches","workbox_routing_registerRoute","workbox_routing_NavigationRoute","workbox_precaching_createHandlerBoundToURL","allowlist","workbox_strategies_NetworkFirst","plugins","workbox_expiration_ExpirationPlugin","maxEntries","maxAgeSeconds"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"sw.js","sources":["../../../../../../private/var/folders/lc/87d3wzj544l4skb096m6003m0000gn/T/87cf5c14c8c38c95af8eeaf5774e033f/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-routing/registerRoute.mjs';\nimport {ExpirationPlugin as workbox_expiration_ExpirationPlugin} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-expiration/ExpirationPlugin.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-core/clientsClaim.mjs';\nimport {precacheAndRoute as workbox_precaching_precacheAndRoute} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-precaching/precacheAndRoute.mjs';\nimport {cleanupOutdatedCaches as workbox_precaching_cleanupOutdatedCaches} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-precaching/cleanupOutdatedCaches.mjs';\nimport {NavigationRoute as workbox_routing_NavigationRoute} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-routing/NavigationRoute.mjs';\nimport {createHandlerBoundToURL as workbox_precaching_createHandlerBoundToURL} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-precaching/createHandlerBoundToURL.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\n\n\nself.skipWaiting();\nworkbox_core_clientsClaim();\n/**\n * The precacheAndRoute() method efficiently caches and responds to\n * requests for URLs in the manifest.\n * See https://goo.gl/S9QRab\n */\nworkbox_precaching_precacheAndRoute([\n {\n \"url\": \"registerSW.js\",\n \"revision\": \"3ca0b8505b4bec776b69afdba2768812\"\n },\n {\n \"url\": \"index.html\",\n \"revision\": \"0.rol69f8gtdg\"\n }\n], {});\nworkbox_precaching_cleanupOutdatedCaches();workbox_routing_registerRoute(new workbox_routing_NavigationRoute(workbox_precaching_createHandlerBoundToURL(\"index.html\"), {\n allowlist: [/^\\/$/], }));\nworkbox_routing_registerRoute(/^https:\\/\\/.*\\.livekit\\.cloud\\/.*/i, new workbox_strategies_NetworkFirst({ \"cacheName\":\"livekit-cache\", plugins: [new workbox_expiration_ExpirationPlugin({ maxEntries: 10, maxAgeSeconds: 86400 })] }), 'GET');\n\n\n"],"names":["self","skipWaiting","workbox_core_clientsClaim","workbox_precaching_precacheAndRoute","workbox_precaching_cleanupOutdatedCaches","workbox_routing_registerRoute","workbox_routing_NavigationRoute","workbox_precaching_createHandlerBoundToURL","allowlist","workbox_strategies_NetworkFirst","plugins","workbox_expiration_ExpirationPlugin","maxEntries","maxAgeSeconds"],"mappings":"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"} \ No newline at end of file diff --git a/server/bridge/GroupAudioRouter.js b/server/bridge/GroupAudioRouter.js index f3b8881..2b2e158 100644 --- a/server/bridge/GroupAudioRouter.js +++ b/server/bridge/GroupAudioRouter.js @@ -216,6 +216,21 @@ export class GroupAudioRouter extends EventEmitter { this.groupBuffers.set(groupId, new Float32Array(this.config.frameSize)); }); + // Compter le nombre de sources par groupe pour normalisation + const groupSourceCount = new Map(); + inputChannelsData.forEach((_, channelId) => { + const key = `in_${channelId}`; + const routes = this.inputToGroupRoutes.get(key); + if (routes) { + routes.forEach(route => { + groupSourceCount.set( + route.destination, + (groupSourceCount.get(route.destination) || 0) + 1 + ); + }); + } + }); + // Pour chaque canal d'entrée inputChannelsData.forEach((pcmData, channelId) => { const key = `in_${channelId}`; @@ -234,9 +249,12 @@ export class GroupAudioRouter extends EventEmitter { return; } - // Mixage avec gain + // Mixage avec gain + atténuation par nombre de sources + const sourceCount = groupSourceCount.get(route.destination) || 1; + const mixGain = route.linearGain / sourceCount; + for (let i = 0; i < pcmData.length && i < groupBuffer.length; i++) { - groupBuffer[i] += pcmData[i] * route.linearGain; + groupBuffer[i] += pcmData[i] * mixGain; } }); }); diff --git a/server/bridge/backends/CoreAudioBackend.js b/server/bridge/backends/CoreAudioBackend.js index 50d7cad..3dc6a21 100644 --- a/server/bridge/backends/CoreAudioBackend.js +++ b/server/bridge/backends/CoreAudioBackend.js @@ -191,19 +191,17 @@ export class CoreAudioBackend extends EventEmitter { const args = []; - // Spécifier le device d'entrée + // Spécifier le device d'entrée (CoreAudio capture en 32-bit natif) if (this.options.inputDeviceName) { - // Utiliser le device spécifié par son nom args.push('-t', 'coreaudio', this.options.inputDeviceName); } else { - // Device par défaut args.push('-d'); } - // Format de sortie (stdout) + // Format de sortie (stdout) - convertir 32→16 bit args.push( - '-t', 'raw', - '-b', '16', + '-t', 'raw', // Format sortie raw PCM + '-b', '16', // Convertir vers 16-bit '-e', 'signed-integer', '-c', String(this.options.channels), '-r', String(this.options.sampleRate), -- 2.52.0 From 2b88ea0ad59814b9df1e02e9f2f19aa2762da7c0 Mon Sep 17 00:00:00 2001 From: Benoit Date: Tue, 2 Jun 2026 01:14:49 +0200 Subject: [PATCH 6/7] =?UTF-8?q?fix:=20audio=20hach=C3=A9=20depuis=20carte?= =?UTF-8?q?=20son=20par=20chunks=20sox=20de=20taille=20variable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- client/dev-dist/sw.js | 2 +- client/dev-dist/sw.js.map | 2 +- server/bridge/AudioBridge.js | 83 +++++++++++++--------- server/bridge/LiveKitClient.js | 13 +++- server/bridge/backends/CoreAudioBackend.js | 39 +++++++--- server/config/config.yaml | 4 +- 6 files changed, 92 insertions(+), 51 deletions(-) diff --git a/client/dev-dist/sw.js b/client/dev-dist/sw.js index 04ba11b..62de964 100644 --- a/client/dev-dist/sw.js +++ b/client/dev-dist/sw.js @@ -81,7 +81,7 @@ define(['./workbox-290dd570'], (function (workbox) { 'use strict'; "revision": "3ca0b8505b4bec776b69afdba2768812" }, { "url": "index.html", - "revision": "0.rol69f8gtdg" + "revision": "0.t6h2k1g9avg" }], {}); workbox.cleanupOutdatedCaches(); workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), { diff --git a/client/dev-dist/sw.js.map b/client/dev-dist/sw.js.map index e54a28b..3ac3d7b 100644 --- a/client/dev-dist/sw.js.map +++ b/client/dev-dist/sw.js.map @@ -1 +1 @@ -{"version":3,"file":"sw.js","sources":["../../../../../../private/var/folders/lc/87d3wzj544l4skb096m6003m0000gn/T/87cf5c14c8c38c95af8eeaf5774e033f/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-routing/registerRoute.mjs';\nimport {ExpirationPlugin as workbox_expiration_ExpirationPlugin} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-expiration/ExpirationPlugin.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-core/clientsClaim.mjs';\nimport {precacheAndRoute as workbox_precaching_precacheAndRoute} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-precaching/precacheAndRoute.mjs';\nimport {cleanupOutdatedCaches as workbox_precaching_cleanupOutdatedCaches} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-precaching/cleanupOutdatedCaches.mjs';\nimport {NavigationRoute as workbox_routing_NavigationRoute} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-routing/NavigationRoute.mjs';\nimport {createHandlerBoundToURL as workbox_precaching_createHandlerBoundToURL} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-precaching/createHandlerBoundToURL.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\n\n\nself.skipWaiting();\nworkbox_core_clientsClaim();\n/**\n * The precacheAndRoute() method efficiently caches and responds to\n * requests for URLs in the manifest.\n * See https://goo.gl/S9QRab\n */\nworkbox_precaching_precacheAndRoute([\n {\n \"url\": \"registerSW.js\",\n \"revision\": \"3ca0b8505b4bec776b69afdba2768812\"\n },\n {\n \"url\": \"index.html\",\n \"revision\": \"0.rol69f8gtdg\"\n }\n], {});\nworkbox_precaching_cleanupOutdatedCaches();workbox_routing_registerRoute(new workbox_routing_NavigationRoute(workbox_precaching_createHandlerBoundToURL(\"index.html\"), {\n allowlist: [/^\\/$/], }));\nworkbox_routing_registerRoute(/^https:\\/\\/.*\\.livekit\\.cloud\\/.*/i, new workbox_strategies_NetworkFirst({ \"cacheName\":\"livekit-cache\", plugins: [new workbox_expiration_ExpirationPlugin({ maxEntries: 10, maxAgeSeconds: 86400 })] }), 'GET');\n\n\n"],"names":["self","skipWaiting","workbox_core_clientsClaim","workbox_precaching_precacheAndRoute","workbox_precaching_cleanupOutdatedCaches","workbox_routing_registerRoute","workbox_routing_NavigationRoute","workbox_precaching_createHandlerBoundToURL","allowlist","workbox_strategies_NetworkFirst","plugins","workbox_expiration_ExpirationPlugin","maxEntries","maxAgeSeconds"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"sw.js","sources":["../../../../../../private/var/folders/lc/87d3wzj544l4skb096m6003m0000gn/T/0f5f046e81f07806d4c31fff9c31a3fd/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-routing/registerRoute.mjs';\nimport {ExpirationPlugin as workbox_expiration_ExpirationPlugin} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-expiration/ExpirationPlugin.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-core/clientsClaim.mjs';\nimport {precacheAndRoute as workbox_precaching_precacheAndRoute} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-precaching/precacheAndRoute.mjs';\nimport {cleanupOutdatedCaches as workbox_precaching_cleanupOutdatedCaches} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-precaching/cleanupOutdatedCaches.mjs';\nimport {NavigationRoute as workbox_routing_NavigationRoute} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-routing/NavigationRoute.mjs';\nimport {createHandlerBoundToURL as workbox_precaching_createHandlerBoundToURL} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-precaching/createHandlerBoundToURL.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\n\n\nself.skipWaiting();\nworkbox_core_clientsClaim();\n/**\n * The precacheAndRoute() method efficiently caches and responds to\n * requests for URLs in the manifest.\n * See https://goo.gl/S9QRab\n */\nworkbox_precaching_precacheAndRoute([\n {\n \"url\": \"registerSW.js\",\n \"revision\": \"3ca0b8505b4bec776b69afdba2768812\"\n },\n {\n \"url\": \"index.html\",\n \"revision\": \"0.t6h2k1g9avg\"\n }\n], {});\nworkbox_precaching_cleanupOutdatedCaches();workbox_routing_registerRoute(new workbox_routing_NavigationRoute(workbox_precaching_createHandlerBoundToURL(\"index.html\"), {\n allowlist: [/^\\/$/], }));\nworkbox_routing_registerRoute(/^https:\\/\\/.*\\.livekit\\.cloud\\/.*/i, new workbox_strategies_NetworkFirst({ \"cacheName\":\"livekit-cache\", plugins: [new workbox_expiration_ExpirationPlugin({ maxEntries: 10, maxAgeSeconds: 86400 })] }), 'GET');\n\n\n"],"names":["self","skipWaiting","workbox_core_clientsClaim","workbox_precaching_precacheAndRoute","workbox_precaching_cleanupOutdatedCaches","workbox_routing_registerRoute","workbox_routing_NavigationRoute","workbox_precaching_createHandlerBoundToURL","allowlist","workbox_strategies_NetworkFirst","plugins","workbox_expiration_ExpirationPlugin","maxEntries","maxAgeSeconds"],"mappings":"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"} \ No newline at end of file diff --git a/server/bridge/AudioBridge.js b/server/bridge/AudioBridge.js index 7321291..472d78b 100644 --- a/server/bridge/AudioBridge.js +++ b/server/bridge/AudioBridge.js @@ -430,52 +430,65 @@ export class AudioBridge extends EventEmitter { ); if (this.stats.framesCapture % 100 === 0) { - console.log(`[AudioBridge] Frame ${this.stats.framesCapture}: ${this.inputChannelBuffers.size} inputs → ${groupBuffers.size} groupes`); + // Détecter si l'audio est du silence (toutes les samples < 0.001) + let totalEnergy = 0; + this.inputChannelBuffers.forEach((buffer) => { + for (let i = 0; i < buffer.length; i++) { + totalEnergy += Math.abs(buffer[i]); + } + }); + const avgEnergy = totalEnergy / (this.inputChannelBuffers.size * (this.options.frameSize || 960)); + console.log(`[AudioBridge] Frame ${this.stats.framesCapture}: ${this.inputChannelBuffers.size} inputs → ${groupBuffers.size} groupes | Énergie audio: ${avgEnergy.toFixed(6)}`); } // ÉTAPE 2 : Pour chaque groupe, envoyer vers le LiveKitClient correspondant groupBuffers.forEach((groupBuffer, groupName) => { // Les groupes sont MONO (Float32Array de N samples) - // Mais LiveKit attend du STÉRÉO (2 canaux) - // → Dupliquer le canal mono pour créer du faux stéréo + // Mais la config globale peut être STÉRÉO (channels=2) + // → Adapter selon la configuration - const samplesPerChannel = groupBuffer.length; - const stereoBuffer = new Float32Array(samplesPerChannel * 2); + let pcmBuffer; + const configChannels = this.options.channels || 1; - // Entrelacer : [M0, M1, M2, ...] → [M0, M0, M1, M1, M2, M2, ...] - for (let i = 0; i < samplesPerChannel; i++) { - stereoBuffer[i * 2] = groupBuffer[i]; // Canal gauche - stereoBuffer[i * 2 + 1] = groupBuffer[i]; // Canal droit (dupliqué) - } + if (configChannels === 1) { + // Config MONO : envoyer directement + pcmBuffer = this._float32ToBuffer(groupBuffer); + } else if (configChannels === 2) { + // Config STÉRÉO : dupliquer le canal mono + const samplesPerChannel = groupBuffer.length; + const stereoBuffer = new Float32Array(samplesPerChannel * 2); - // Convertir Float32Array stéréo → PCM Buffer - const pcmBuffer = this._float32ToBuffer(stereoBuffer); - - // Encoder en Opus (maintenant en stéréo) - const opusData = this.opusEncoder.encode(pcmBuffer); - - if (opusData) { - this.stats.bytesEncoded += opusData.length; - - // Récupérer le client LiveKit pour ce groupe - const client = this.liveKitClients.get(groupName); - - // Envoi vers LiveKit via sendAudioData (prend du PCM, pas de l'Opus) - // Note: LiveKit gère lui-même l'encodage Opus en interne - if (client && client.isConnected) { - client.sendAudioData(pcmBuffer); - if (this.stats.framesCapture % 100 === 0) { - console.log(`[AudioBridge] → LiveKit groupe "${groupName}": ${pcmBuffer.length} bytes (mono→stéréo)`); - } - } else { - if (this.stats.framesCapture % 100 === 0) { - console.log(`[AudioBridge] ⚠️ LiveKit non connecté pour groupe "${groupName}", audio non envoyé`); - } + // Entrelacer : [M0, M1, M2, ...] → [M0, M0, M1, M1, M2, M2, ...] + for (let i = 0; i < samplesPerChannel; i++) { + stereoBuffer[i * 2] = groupBuffer[i]; // Canal gauche + stereoBuffer[i * 2 + 1] = groupBuffer[i]; // Canal droit (dupliqué) } - // Émettre aussi pour monitoring/debug - this.emit('groupAudioOut', { groupName, opusData, pcmBuffer }); + pcmBuffer = this._float32ToBuffer(stereoBuffer); + } else { + console.error(`❌ Nombre de canaux non supporté: ${configChannels}`); + return; } + + // Récupérer le client LiveKit pour ce groupe + const client = this.liveKitClients.get(groupName); + + // Envoi vers LiveKit via sendAudioData (prend du PCM 16-bit) + // Note: LiveKit gère lui-même l'encodage Opus en interne + if (client && client.isConnected) { + client.sendAudioData(pcmBuffer); + if (this.stats.framesCapture % 100 === 0) { + const channelLabel = configChannels === 1 ? 'mono' : `${configChannels}ch`; + console.log(`[AudioBridge] → LiveKit groupe "${groupName}": ${pcmBuffer.length} bytes (${channelLabel})`); + } + } else { + if (this.stats.framesCapture % 100 === 0) { + console.log(`[AudioBridge] ⚠️ LiveKit non connecté pour groupe "${groupName}", audio non envoyé`); + } + } + + // Émettre aussi pour monitoring/debug + this.emit('groupAudioOut', { groupName, pcmBuffer }); }); // ÉTAPE 3 : Loopback local - Groupes → Outputs physiques (sans passer par LiveKit) diff --git a/server/bridge/LiveKitClient.js b/server/bridge/LiveKitClient.js index a3948a7..ac4bdfd 100644 --- a/server/bridge/LiveKitClient.js +++ b/server/bridge/LiveKitClient.js @@ -264,11 +264,18 @@ export class LiveKitClient extends EventEmitter { } try { - // Création d'un AudioFrame (conversion en int32 explicite) - const samplesPerChannel = Math.floor(pcmData.length / 2 / this.options.channels); + // AudioFrame attend Int16Array, pas Buffer + // Convertir Buffer → Int16Array (éviter .slice, utiliser .subarray selon doc) + const int16Array = new Int16Array( + pcmData.buffer, + pcmData.byteOffset, + pcmData.length / 2 // length en samples, pas en bytes + ); + + const samplesPerChannel = Math.floor(int16Array.length / this.options.channels); const frame = new AudioFrame( - pcmData, + int16Array, parseInt(this.options.sampleRate, 10), parseInt(this.options.channels, 10), samplesPerChannel diff --git a/server/bridge/backends/CoreAudioBackend.js b/server/bridge/backends/CoreAudioBackend.js index 3dc6a21..7b8bb16 100644 --- a/server/bridge/backends/CoreAudioBackend.js +++ b/server/bridge/backends/CoreAudioBackend.js @@ -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(); diff --git a/server/config/config.yaml b/server/config/config.yaml index 10b456f..08ef7d4 100644 --- a/server/config/config.yaml +++ b/server/config/config.yaml @@ -11,10 +11,8 @@ audio: routing: inputToGroup: "0": - - production - - default - "1": - default + "1": [] "2": [] "4": - technique -- 2.52.0 From 060453fe0689e9e5defe0d94ecff26196e1d734d Mon Sep 17 00:00:00 2001 From: Benoit Date: Tue, 2 Jun 2026 21:19:45 +0200 Subject: [PATCH 7/7] 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(); -- 2.52.0