diff --git a/client/dev-dist/sw.js b/client/dev-dist/sw.js index 328e5b3..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.881sreuemg" + "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 8081cf2..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/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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsBAA,CAAAA,CAAAA,CAAAA,CAAI,CAACC,WAAW,CAAA,CAAE;AAClBC,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAyB,CAAA,CAAE;AAC3B,CAAA,CAAA,CAAA,CAAA;AACA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;AACA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;AACA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;AACA,CAAA,CAAA,CAAA,CAAA;AACAC,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAmC,CAAC,CAClC;EACE,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAK,EAAE,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAe;AACtB,CAAA,CAAA,CAAA,CAAA,UAAU,CAAA,CAAE,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;AACd,CAAA,CAAA,CAAC,CAAA,CACD;EACE,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAK,EAAE,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAY;AACnB,CAAA,CAAA,CAAA,CAAA,UAAU,CAAA,CAAE,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;EACd,CAAC,CACF,CAAA,CAAE,CAAA,CAAE,CAAC;AACNC,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAwC,CAAA,CAAE;AAACC,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAA6B,CAAC,IAAIC,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAA+B,CAACC,+BAA0C,CAAC,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAY,CAAC,CAAA,CAAE;IACrKC,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAS,CAAA,CAAE,CAAC,CAAA,CAAA,CAAA,CAAA,CAAA,CAAM;AAAI,CAAA,CAAA,CAAC,CAAC,CAAC;AAC3BH,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAA6B,CAAC,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAoC,CAAA,CAAE,CAAA,CAAA,CAAA,CAAII,oBAA+B,CAAC;EAAE,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAW,EAAC,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAe;AAAEC,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAO,CAAA,CAAE,CAAC,CAAA,CAAA,CAAA,CAAIC,wBAAmC,CAAC;EAAEC,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAU,EAAE,CAAA,CAAE;AAAEC,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,aAAa,CAAA,CAAE,CAAA,CAAA,CAAA,CAAA;AAAM,CAAA,CAAA,CAAA,CAAA,CAAC,CAAC;EAAE,CAAC,CAAC,CAAA,CAAE,CAAA,CAAA,CAAA,CAAA,CAAK,CAAC;;"} \ 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/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); 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 }); diff --git a/server/bridge/AudioBridge.js b/server/bridge/AudioBridge.js index e3f66f4..472d78b 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( @@ -410,39 +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) => { - // Convertir Float32Array → PCM Buffer - const pcmBuffer = this._float32ToBuffer(groupBuffer); + // Les groupes sont MONO (Float32Array de N samples) + // Mais la config globale peut être STÉRÉO (channels=2) + // → Adapter selon la configuration - // Encoder en Opus - const opusData = this.opusEncoder.encode(pcmBuffer); + let pcmBuffer; + const configChannels = this.options.channels || 1; - if (opusData) { - this.stats.bytesEncoded += opusData.length; + 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); - // 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`); - } - } 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) @@ -453,16 +499,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++; @@ -491,12 +574,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/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/LiveKitClient.js b/server/bridge/LiveKitClient.js index 272ca23..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 @@ -349,7 +356,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/bridge/backends/CoreAudioBackend.js b/server/bridge/backends/CoreAudioBackend.js index 5983895..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 = []; @@ -184,35 +189,45 @@ 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) - '-t', 'raw', - '-b', '16', - '-e', 'signed-integer', - `-c`, String(this.options.channels), - `-r`, String(this.options.sampleRate), - '-' // Stdout - ]; + // 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" - // Si device spécifié + const args = []; + + // Spécifier le device d'entrée (CoreAudio capture en 32-bit natif) if (this.options.inputDeviceName) { - args[2] = this.options.inputDeviceName; // Index 2 = device name + args.push('-t', 'coreaudio', this.options.inputDeviceName); + } else { + args.push('-d'); } + // Format de sortie (stdout) - convertir 32→16 bit + args.push( + '-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), + '-' // Stdout + ); + + console.log(`🎤 Démarrage capture sox: ${args.join(' ')}`); 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) => { @@ -248,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'); } } @@ -265,27 +281,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 + '--buffer', '65536', // Buffer 64k (évite EOF prématuré) '-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 }); @@ -313,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); } }); @@ -438,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/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(); diff --git a/server/config/config.yaml b/server/config/config.yaml index 662a0a0..08ef7d4 100644 --- a/server/config/config.yaml +++ b/server/config/config.yaml @@ -1,18 +1,17 @@ audio: sampleRate: 48000 + channels: 2 frameSize: 20 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: Loopback Audio 4 + outputDeviceId: Haut-parleurs MacBook Pro sampleRate: 48000 routing: inputToGroup: "0": - - production + - default "1": [] "2": [] "4": @@ -25,35 +24,37 @@ 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 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