feat: stabilize voice activity and audio routing

This commit is contained in:
Edison Jwa
2026-05-25 01:19:09 +09:00
parent eb9014cd81
commit 5515ff6643
34 changed files with 3054 additions and 1751 deletions
+68 -16
View File
@@ -270,23 +270,19 @@ Future<BridgeAudioProcessingStats> audioProcessingStats() =>
Future<BridgeAudioDeviceList> listAudioDevices() =>
RustLib.instance.api.crateApiListAudioDevices();
/// Set the preferred input device by name. Takes effect on next
/// Set the preferred input device by id. Takes effect on next
/// `start_audio`.
Future<void> setInputDevice({String? name}) =>
RustLib.instance.api.crateApiSetInputDevice(name: name);
Future<void> setInputDevice({String? id}) =>
RustLib.instance.api.crateApiSetInputDevice(id: id);
/// Set the preferred output device by name.
Future<void> setOutputDevice({String? name}) =>
RustLib.instance.api.crateApiSetOutputDevice(name: name);
/// Set the preferred output device by id.
Future<void> setOutputDevice({String? id}) =>
RustLib.instance.api.crateApiSetOutputDevice(id: id);
/// Configure the VAD model path.
Future<void> setVadModelPath({required String path}) =>
RustLib.instance.api.crateApiSetVadModelPath(path: path);
/// Configure the TEN VAD ONNX model path.
Future<void> setTenVadModelPath({required String path}) =>
RustLib.instance.api.crateApiSetTenVadModelPath(path: path);
/// Enable or disable audio debug WAV dumping.
Future<void> enableAudioDebugWavDump({required bool enabled}) =>
RustLib.instance.api.crateApiEnableAudioDebugWavDump(enabled: enabled);
@@ -321,24 +317,47 @@ enum BridgeAudioBackend {
/// Audio device info from the platform.
class BridgeAudioDevice {
/// Stable platform-reported device identifier.
final String id;
/// Human-readable device name.
final String name;
/// Additional device details useful for disambiguation.
final String details;
/// True if the OS reports this as the default device.
final bool isDefault;
const BridgeAudioDevice({required this.name, required this.isDefault});
/// True if Chanora currently has this device pinned.
final bool isSelected;
const BridgeAudioDevice({
required this.id,
required this.name,
required this.details,
required this.isDefault,
required this.isSelected,
});
@override
int get hashCode => name.hashCode ^ isDefault.hashCode;
int get hashCode =>
id.hashCode ^
name.hashCode ^
details.hashCode ^
isDefault.hashCode ^
isSelected.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is BridgeAudioDevice &&
runtimeType == other.runtimeType &&
id == other.id &&
name == other.name &&
isDefault == other.isDefault;
details == other.details &&
isDefault == other.isDefault &&
isSelected == other.isSelected;
}
/// List of available audio devices.
@@ -512,6 +531,21 @@ class BridgeAudioProcessingStats {
/// Clipped samples.
final BigInt clippedSamples;
/// Number of effectively silent processed capture frames.
final BigInt zeroFrames;
/// Number of processed capture frames.
final BigInt captureFrames;
/// Input callbacks carrying 10 ms of audio.
final BigInt callbacks10Ms;
/// Input callbacks carrying 20 ms of audio.
final BigInt callbacks20Ms;
/// Input callbacks carrying other sizes.
final BigInt callbacksOther;
/// Sonora enabled.
final bool sonoraEnabled;
@@ -536,6 +570,11 @@ class BridgeAudioProcessingStats {
required this.outputUnderruns,
required this.callbackXruns,
required this.clippedSamples,
required this.zeroFrames,
required this.captureFrames,
required this.callbacks10Ms,
required this.callbacks20Ms,
required this.callbacksOther,
required this.sonoraEnabled,
required this.platformVoiceProcessingEnabled,
});
@@ -559,6 +598,11 @@ class BridgeAudioProcessingStats {
outputUnderruns.hashCode ^
callbackXruns.hashCode ^
clippedSamples.hashCode ^
zeroFrames.hashCode ^
captureFrames.hashCode ^
callbacks10Ms.hashCode ^
callbacks20Ms.hashCode ^
callbacksOther.hashCode ^
sonoraEnabled.hashCode ^
platformVoiceProcessingEnabled.hashCode;
@@ -584,6 +628,11 @@ class BridgeAudioProcessingStats {
outputUnderruns == other.outputUnderruns &&
callbackXruns == other.callbackXruns &&
clippedSamples == other.clippedSamples &&
zeroFrames == other.zeroFrames &&
captureFrames == other.captureFrames &&
callbacks10Ms == other.callbacks10Ms &&
callbacks20Ms == other.callbacks20Ms &&
callbacksOther == other.callbacksOther &&
sonoraEnabled == other.sonoraEnabled &&
platformVoiceProcessingEnabled ==
other.platformVoiceProcessingEnabled;
@@ -976,6 +1025,12 @@ sealed class BridgeEvent with _$BridgeEvent {
required BridgeMessageTarget target,
}) = BridgeEvent_ChatMessage;
/// Human-readable server activity surfaced from protocol bookkeeping events.
const factory BridgeEvent.serverActivity({
/// TeamSpeak-style activity line.
required String message,
}) = BridgeEvent_ServerActivity;
/// Audio route changed (speaker/earpiece/BT/wired).
const factory BridgeEvent.audioRouteChanged({
/// The new audio route.
@@ -1169,9 +1224,6 @@ enum BridgeVadBackend {
/// Silero ONNX VAD.
sileroOnnx,
/// TEN VAD.
tenVad,
/// WebRTC fallback VAD.
webrtcVad,