mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
fix(voice): stop stereo microphones publishing as mono (#2846)
This commit is contained in:
@@ -12,7 +12,6 @@ export const publishDefaults: TrackPublishDefaults = {
|
|||||||
audioPreset: AudioPresets.music,
|
audioPreset: AudioPresets.music,
|
||||||
dtx: false,
|
dtx: false,
|
||||||
red: true,
|
red: true,
|
||||||
forceStereo: false,
|
|
||||||
simulcast: true,
|
simulcast: true,
|
||||||
screenShareEncoding: ScreenSharePresets.original.encoding,
|
screenShareEncoding: ScreenSharePresets.original.encoding,
|
||||||
stopMicTrackOnMute: false,
|
stopMicTrackOnMute: false,
|
||||||
|
|||||||
@@ -622,7 +622,7 @@ export class VoiceEngineV2AppMediaExecutionAdapter extends Store {
|
|||||||
const guild = channel?.guildId ? Guilds.getGuild(channel.guildId) : null;
|
const guild = channel?.guildId ? Guilds.getGuild(channel.guildId) : null;
|
||||||
const channelBitrate = resolveVoiceChannelBitrate(channel?.bitrate, guild?.features);
|
const channelBitrate = resolveVoiceChannelBitrate(channel?.bitrate, guild?.features);
|
||||||
const profile = this.resolveActiveMicrophoneProfile();
|
const profile = this.resolveActiveMicrophoneProfile();
|
||||||
return buildMicrophonePublishOptions(channelBitrate, profile.mode, profile.stereoCapture);
|
return buildMicrophonePublishOptions(channelBitrate, profile.stereoCapture);
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshMicrophonePublishSettings(room: Room | null, channelId: string | null): Promise<void> {
|
async refreshMicrophonePublishSettings(room: Room | null, channelId: string | null): Promise<void> {
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import {VoiceTrackSource} from '@app/features/voice/engine/VoiceTrackSource';
|
import {VoiceTrackSource} from '@app/features/voice/engine/VoiceTrackSource';
|
||||||
import type {VoiceProcessingMode} from '@app/features/voice/utils/VoiceProcessingProfile';
|
|
||||||
import type {TrackPublishOptions} from 'livekit-client';
|
import type {TrackPublishOptions} from 'livekit-client';
|
||||||
|
|
||||||
export const OPUS_MAX_AUDIO_BITRATE_BPS = 510000;
|
export const OPUS_MAX_AUDIO_BITRATE_BPS = 510000;
|
||||||
export const VOICE_CHANNEL_MIN_AUDIO_BITRATE_BPS = 8000;
|
export const VOICE_CHANNEL_MIN_AUDIO_BITRATE_BPS = 8000;
|
||||||
export const STEREO_VOICE_MIN_AUDIO_BITRATE_BPS = 128000;
|
export const STEREO_VOICE_MIN_AUDIO_BITRATE_BPS = 64000;
|
||||||
|
|
||||||
export function normaliseAudioBitrateBps(value: number | null | undefined): number | undefined {
|
export function normaliseAudioBitrateBps(value: number | null | undefined): number | undefined {
|
||||||
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) return undefined;
|
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) return undefined;
|
||||||
@@ -17,27 +16,18 @@ export function normaliseAudioBitrateBps(value: number | null | undefined): numb
|
|||||||
|
|
||||||
export function buildMicrophonePublishOptions(
|
export function buildMicrophonePublishOptions(
|
||||||
channelBitrate: number | null | undefined,
|
channelBitrate: number | null | undefined,
|
||||||
processingMode: VoiceProcessingMode,
|
|
||||||
stereoCapture = false,
|
stereoCapture = false,
|
||||||
): TrackPublishOptions | undefined {
|
): TrackPublishOptions | undefined {
|
||||||
const maxBitrate = normaliseAudioBitrateBps(channelBitrate);
|
const maxBitrate = normaliseAudioBitrateBps(channelBitrate);
|
||||||
if (!maxBitrate) return undefined;
|
if (!maxBitrate) return undefined;
|
||||||
const studioMode = processingMode === 'studio';
|
|
||||||
const bitrateAllowsStereo = maxBitrate >= STEREO_VOICE_MIN_AUDIO_BITRATE_BPS;
|
|
||||||
return {
|
return {
|
||||||
audioPreset: {
|
audioPreset: {
|
||||||
maxBitrate,
|
maxBitrate,
|
||||||
priority: 'high',
|
priority: 'high',
|
||||||
},
|
},
|
||||||
|
dtx: false,
|
||||||
red: true,
|
red: true,
|
||||||
...(studioMode
|
forceStereo: stereoCapture && maxBitrate >= STEREO_VOICE_MIN_AUDIO_BITRATE_BPS,
|
||||||
? {
|
|
||||||
dtx: false,
|
|
||||||
forceStereo: bitrateAllowsStereo,
|
|
||||||
}
|
|
||||||
: stereoCapture && !bitrateAllowsStereo
|
|
||||||
? {forceStereo: false}
|
|
||||||
: {}),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ import {
|
|||||||
} from '@app/features/voice/utils/noise_suppression/NoiseSuppressionBackends';
|
} from '@app/features/voice/utils/noise_suppression/NoiseSuppressionBackends';
|
||||||
import {readEffectiveNoiseSuppression} from '@app/features/voice/utils/noise_suppression/NoiseSuppressionRuntime';
|
import {readEffectiveNoiseSuppression} from '@app/features/voice/utils/noise_suppression/NoiseSuppressionRuntime';
|
||||||
import {readNoiseSuppressionRuntimeCapabilities} from '@app/features/voice/utils/noise_suppression/NoiseSuppressionSelection';
|
import {readNoiseSuppressionRuntimeCapabilities} from '@app/features/voice/utils/noise_suppression/NoiseSuppressionSelection';
|
||||||
import {legacyNoiseSuppressionBackend} from '@app/features/voice/utils/VoiceProcessingProfile';
|
import {
|
||||||
|
legacyNoiseSuppressionBackend,
|
||||||
|
resolveVoiceProcessingFromState,
|
||||||
|
} from '@app/features/voice/utils/VoiceProcessingProfile';
|
||||||
|
|
||||||
export const NOISE_SUPPRESSION_UI_SAMPLE_RATE = 48000;
|
export const NOISE_SUPPRESSION_UI_SAMPLE_RATE = 48000;
|
||||||
|
|
||||||
@@ -67,8 +70,8 @@ export function isStereoMicrophoneChoiceAvailable(): boolean {
|
|||||||
const assignment = VoiceNoiseSuppressionRollout.assignment;
|
const assignment = VoiceNoiseSuppressionRollout.assignment;
|
||||||
if (!assignment.enabled || !assignment.stereo_enabled) return false;
|
if (!assignment.enabled || !assignment.stereo_enabled) return false;
|
||||||
const effective = readEffectiveNoiseSuppression(NOISE_SUPPRESSION_UI_SAMPLE_RATE);
|
const effective = readEffectiveNoiseSuppression(NOISE_SUPPRESSION_UI_SAMPLE_RATE);
|
||||||
if (!effective.rolloutApplied || effective.backend == null) return false;
|
const backend = effective.backend ?? resolveVoiceProcessingFromState(VoiceSettings).noiseSuppressionBackend;
|
||||||
return getNoiseSuppressionBackendDescriptor(effective.backend).preservesInputChannels;
|
return getNoiseSuppressionBackendDescriptor(backend).preservesInputChannels;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isStereoMicrophoneEnabled(): boolean {
|
export function isStereoMicrophoneEnabled(): boolean {
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ export function resolveEffectiveNoiseSuppression(
|
|||||||
): EffectiveNoiseSuppression {
|
): EffectiveNoiseSuppression {
|
||||||
const preference = isVoiceNoiseSuppressionBackend(userPreference) ? userPreference : null;
|
const preference = isVoiceNoiseSuppressionBackend(userPreference) ? userPreference : null;
|
||||||
const resolution = resolveVoiceNoiseSuppressionForCall(assignment, guildId, preference);
|
const resolution = resolveVoiceNoiseSuppressionForCall(assignment, guildId, preference);
|
||||||
if (resolution == null) return INERT_EFFECTIVE_NOISE_SUPPRESSION;
|
const stereoAllowed = assignment.enabled && assignment.stereo_enabled && stereoPreference !== false;
|
||||||
|
if (resolution == null) return {...INERT_EFFECTIVE_NOISE_SUPPRESSION, stereoEnabled: stereoAllowed};
|
||||||
const backend = selectUsableNoiseSuppressionBackend(resolution.backend, capabilities);
|
const backend = selectUsableNoiseSuppressionBackend(resolution.backend, capabilities);
|
||||||
return {
|
return {
|
||||||
rolloutApplied: true,
|
rolloutApplied: true,
|
||||||
@@ -53,9 +54,7 @@ export function resolveEffectiveNoiseSuppression(
|
|||||||
source: resolution.source,
|
source: resolution.source,
|
||||||
suppressionStrength: resolution.suppressionStrength,
|
suppressionStrength: resolution.suppressionStrength,
|
||||||
stereoEnabled:
|
stereoEnabled:
|
||||||
resolution.stereoEnabled &&
|
stereoAllowed && resolution.stereoEnabled && getNoiseSuppressionBackendDescriptor(backend).preservesInputChannels,
|
||||||
stereoPreference !== false &&
|
|
||||||
getNoiseSuppressionBackendDescriptor(backend).preservesInputChannels,
|
|
||||||
configVersion: resolution.configVersion,
|
configVersion: resolution.configVersion,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -78,12 +77,20 @@ export function getNoiseSuppressionScopeGuildId(): string | null {
|
|||||||
return activeScopeGuildId;
|
return activeScopeGuildId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveStereoCapture(
|
||||||
|
effective: EffectiveNoiseSuppression,
|
||||||
|
backend: VoiceNoiseSuppressionBackend,
|
||||||
|
): boolean {
|
||||||
|
return effective.stereoEnabled && getNoiseSuppressionBackendDescriptor(backend).preservesInputChannels;
|
||||||
|
}
|
||||||
|
|
||||||
export function applyNoiseSuppressionOverride(
|
export function applyNoiseSuppressionOverride(
|
||||||
profile: ResolvedVoiceProcessing,
|
profile: ResolvedVoiceProcessing,
|
||||||
effective: EffectiveNoiseSuppression,
|
effective: EffectiveNoiseSuppression,
|
||||||
): ResolvedVoiceProcessing {
|
): ResolvedVoiceProcessing {
|
||||||
if (!effective.rolloutApplied || effective.backend == null) return profile;
|
if (!effective.rolloutApplied || effective.backend == null || profile.mode === 'studio') {
|
||||||
if (profile.mode === 'studio') return profile;
|
return {...profile, stereoCapture: resolveStereoCapture(effective, profile.noiseSuppressionBackend)};
|
||||||
|
}
|
||||||
const backend = effective.backend;
|
const backend = effective.backend;
|
||||||
const descriptor = getNoiseSuppressionBackendDescriptor(backend);
|
const descriptor = getNoiseSuppressionBackendDescriptor(backend);
|
||||||
return {
|
return {
|
||||||
@@ -92,6 +99,6 @@ export function applyNoiseSuppressionOverride(
|
|||||||
deepFilter: backend === 'deep_filter',
|
deepFilter: backend === 'deep_filter',
|
||||||
deepFilterNoiseReductionLevel: backend === 'deep_filter' ? effective.suppressionStrength : 0,
|
deepFilterNoiseReductionLevel: backend === 'deep_filter' ? effective.suppressionStrength : 0,
|
||||||
noiseSuppressionBackend: backend,
|
noiseSuppressionBackend: backend,
|
||||||
stereoCapture: effective.stereoEnabled,
|
stereoCapture: resolveStereoCapture(effective, backend),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user