fix: detection correcte des cartes son CoreAudio avec nombre de canaux reel

- Parse coreaudio_device_input/output depuis system_profiler (nombre canaux)
- Ajoute sampleRate reel par device
- Ajoute metadata: manufacturer, transport, isDefault
- Filtre devices sans input ni output
- Corrige l'API pour exposer les 11 devices au lieu de 2
This commit is contained in:
2026-05-26 15:18:41 +02:00
parent 61b3bedcae
commit 2acd652df0
6 changed files with 67 additions and 25 deletions
+2
View File
@@ -301,6 +301,8 @@ export class AudioBridge extends EventEmitter {
token: this.options.liveKitToken,
roomName: this.options.roomName,
participantName: 'AudioBridge',
sampleRate: this.options.sampleRate,
channels: this.options.channels,
audioBitrate: this.opusEncoder.options.bitrate
});
+19 -1
View File
@@ -61,9 +61,27 @@ class AudioBridgeManager extends EventEmitter {
// Import dynamique du AudioBridge
const { AudioBridge } = await import('./AudioBridge.js');
// Préparer la config avec conversion explicite des valeurs numériques
const audioConfig = { ...config.audio };
// Conversion explicite des paramètres numériques (depuis YAML ils peuvent être strings)
if (audioConfig.sampleRate) audioConfig.sampleRate = parseInt(audioConfig.sampleRate, 10);
if (audioConfig.channels) audioConfig.channels = parseInt(audioConfig.channels, 10);
// frameSize en millisecondes → conversion en nombre d'échantillons
// Ex: 20ms à 48kHz = 960 échantillons
if (audioConfig.frameSize) {
const frameSizeMs = parseInt(audioConfig.frameSize, 10);
const sampleRate = audioConfig.sampleRate || 48000;
audioConfig.frameSize = Math.floor((frameSizeMs * sampleRate) / 1000);
}
if (audioConfig.defaultBitrate) audioConfig.defaultBitrate = parseInt(audioConfig.defaultBitrate, 10);
if (audioConfig.customOpusBitrate) audioConfig.customOpusBitrate = parseInt(audioConfig.customOpusBitrate, 10);
// Créer l'instance avec la config
this.bridge = new AudioBridge({
...config.audio,
...audioConfig,
// Options LiveKit
liveKitUrl: config.server?.livekit?.url || 'ws://localhost:7880',
liveKitToken,
+26 -11
View File
@@ -10,7 +10,7 @@
* - Reconnexion automatique
*/
import { Room, RoomEvent, AudioSource, AudioFrame, LocalAudioTrack } from '@livekit/rtc-node';
import { Room, RoomEvent, AudioSource, AudioFrame, LocalAudioTrack, TrackSource } from '@livekit/rtc-node';
import { EventEmitter } from 'events';
export class LiveKitClient extends EventEmitter {
@@ -86,20 +86,35 @@ export class LiveKitClient extends EventEmitter {
*/
async _createAudioSource() {
try {
// Création de l'AudioSource
this.audioSource = new AudioSource(
this.options.sampleRate,
this.options.channels
);
// Debug: afficher les valeurs avant conversion
const sampleRate = parseInt(this.options.sampleRate, 10);
const channels = parseInt(this.options.channels, 10);
console.log('🔍 DEBUG AudioSource:', {
sampleRateOriginal: this.options.sampleRate,
sampleRateType: typeof this.options.sampleRate,
sampleRateConverted: sampleRate,
sampleRateConvertedType: typeof sampleRate,
channelsOriginal: this.options.channels,
channelsType: typeof this.options.channels,
channelsConverted: channels,
channelsConvertedType: typeof channels
});
// Création de l'AudioSource (conversion en int32 explicite)
this.audioSource = new AudioSource(sampleRate, channels);
console.log('✓ AudioSource créée:', this.audioSource);
// Création du LocalAudioTrack depuis l'AudioSource
const localTrack = LocalAudioTrack.createAudioTrack('bridge-audio', this.audioSource);
console.log('✓ LocalAudioTrack créé:', localTrack);
// Publication du track
const options = {
source: 'microphone' // Simule un microphone pour les clients
source: TrackSource.SOURCE_MICROPHONE // Simule un microphone pour les clients
};
console.log('🔍 DEBUG publishTrack options:', options);
this.localAudioTrack = await this.room.localParticipant.publishTrack(
localTrack,
options
@@ -223,13 +238,13 @@ export class LiveKitClient extends EventEmitter {
}
try {
// Création d'un AudioFrame
const samplesPerChannel = pcmData.length / 2 / this.options.channels;
// Création d'un AudioFrame (conversion en int32 explicite)
const samplesPerChannel = Math.floor(pcmData.length / 2 / this.options.channels);
const frame = new AudioFrame(
pcmData,
this.options.sampleRate,
this.options.channels,
parseInt(this.options.sampleRate, 10),
parseInt(this.options.channels, 10),
samplesPerChannel
);
+18 -11
View File
@@ -57,22 +57,29 @@ export class CoreAudioBackend extends EventEmitter {
item._items.forEach(device => {
const name = device._name || 'Unknown Device';
// Déterminer type (input/output)
const isInput = name.toLowerCase().includes('input') ||
name.toLowerCase().includes('microphone') ||
name.toLowerCase().includes('mic');
// Les clés coreaudio_device_input/output contiennent le nombre de canaux
const inputChannels = parseInt(device.coreaudio_device_input) || 0;
const outputChannels = parseInt(device.coreaudio_device_output) || 0;
const sampleRate = parseInt(device.coreaudio_device_srate) || 48000;
const isOutput = name.toLowerCase().includes('output') ||
name.toLowerCase().includes('speaker') ||
name.toLowerCase().includes('headphone');
// Ignorer les devices sans input ni output
if (inputChannels === 0 && outputChannels === 0) {
return;
}
devices.push({
id: id++,
name: name,
maxInputChannels: isInput ? 2 : 0,
maxOutputChannels: isOutput ? 2 : 0,
defaultSampleRate: 48000,
hostAPIName: 'Core Audio'
maxInputChannels: inputChannels,
maxOutputChannels: outputChannels,
defaultSampleRate: sampleRate,
hostAPIName: 'Core Audio',
manufacturer: device.coreaudio_device_manufacturer || 'Unknown',
transport: device.coreaudio_device_transport || 'unknown',
isDefault: {
input: device.coreaudio_default_audio_input_device === 'spaudio_yes',
output: device.coreaudio_default_audio_output_device === 'spaudio_yes'
}
});
});
}