|
| 1 | +// Copyright 2026 LiveKit, Inc. |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | + |
| 15 | +import '../track/options.dart'; |
| 16 | + |
| 17 | +/// The implementation in effect for an audio processing component. |
| 18 | +enum AudioProcessingImplementation { |
| 19 | + unknown('unknown'), |
| 20 | + disabled('disabled'), |
| 21 | + software('software'), |
| 22 | + platform('platform'), |
| 23 | + softwareAndPlatform('softwareAndPlatform'); |
| 24 | + |
| 25 | + const AudioProcessingImplementation(this.value); |
| 26 | + |
| 27 | + final String value; |
| 28 | + |
| 29 | + static AudioProcessingImplementation fromValue(String? value) => AudioProcessingImplementation.values.firstWhere( |
| 30 | + (e) => e.value == value, |
| 31 | + orElse: () => AudioProcessingImplementation.unknown, |
| 32 | + ); |
| 33 | +} |
| 34 | + |
| 35 | +AudioProcessingMode _modeFromValue(String? value) { |
| 36 | + for (final mode in AudioProcessingMode.values) { |
| 37 | + if (mode.constraintValue == value) return mode; |
| 38 | + } |
| 39 | + return AudioProcessingMode.automatic; |
| 40 | +} |
| 41 | + |
| 42 | +/// The caller's request for one audio processing component: enabled flag plus |
| 43 | +/// implementation mode. |
| 44 | +class AudioProcessingComponentRequest { |
| 45 | + const AudioProcessingComponentRequest({ |
| 46 | + required this.enabled, |
| 47 | + required this.mode, |
| 48 | + }); |
| 49 | + |
| 50 | + factory AudioProcessingComponentRequest.fromMap(Map<dynamic, dynamic> map) => AudioProcessingComponentRequest( |
| 51 | + enabled: (map['enabled'] as bool?) ?? false, |
| 52 | + mode: _modeFromValue(map['mode'] as String?), |
| 53 | + ); |
| 54 | + |
| 55 | + final bool enabled; |
| 56 | + final AudioProcessingMode mode; |
| 57 | +} |
| 58 | + |
| 59 | +/// Diagnostic state of one audio processing component (echo cancellation, |
| 60 | +/// noise suppression, auto gain control or high-pass filter), observed at |
| 61 | +/// three stages of one pipeline: requested (caller intent) -> resolved (the |
| 62 | +/// engine's per-path decision) -> active (live truth), with [effective] as |
| 63 | +/// the merged verdict. |
| 64 | +class AudioProcessingComponentState { |
| 65 | + const AudioProcessingComponentState({ |
| 66 | + this.requested, |
| 67 | + required this.isSoftwareResolved, |
| 68 | + required this.isSoftwareActive, |
| 69 | + required this.isPlatformAvailable, |
| 70 | + required this.isPlatformResolved, |
| 71 | + required this.isPlatformActive, |
| 72 | + required this.effective, |
| 73 | + }); |
| 74 | + |
| 75 | + factory AudioProcessingComponentState.fromMap(Map<dynamic, dynamic> map) => AudioProcessingComponentState( |
| 76 | + requested: map['requested'] is Map |
| 77 | + ? AudioProcessingComponentRequest.fromMap(Map<dynamic, dynamic>.from(map['requested'] as Map)) |
| 78 | + : null, |
| 79 | + isSoftwareResolved: (map['isSoftwareResolved'] as bool?) ?? false, |
| 80 | + isSoftwareActive: (map['isSoftwareActive'] as bool?) ?? false, |
| 81 | + isPlatformAvailable: (map['isPlatformAvailable'] as bool?) ?? false, |
| 82 | + isPlatformResolved: (map['isPlatformResolved'] as bool?) ?? false, |
| 83 | + isPlatformActive: (map['isPlatformActive'] as bool?) ?? false, |
| 84 | + effective: AudioProcessingImplementation.fromValue(map['effective'] as String?), |
| 85 | + ); |
| 86 | + |
| 87 | + /// What the caller most recently requested for this component. Null when no |
| 88 | + /// audio processing options have ever been applied — "nobody asked". |
| 89 | + final AudioProcessingComponentRequest? requested; |
| 90 | + |
| 91 | + /// Whether the resolver decided the WebRTC software (APM) implementation |
| 92 | + /// should run, after weighing the requested mode against platform |
| 93 | + /// availability, coupling, and policy. |
| 94 | + final bool isSoftwareResolved; |
| 95 | + |
| 96 | + /// Whether APM's live configuration currently has this component enabled. |
| 97 | + final bool isSoftwareActive; |
| 98 | + |
| 99 | + /// Whether this device/OS offers a built-in implementation at all. |
| 100 | + final bool isPlatformAvailable; |
| 101 | + |
| 102 | + /// Whether the engine asked the OS to run the platform implementation. The |
| 103 | + /// OS owns the outcome: it can decline, defer, or couple components. |
| 104 | + final bool isPlatformResolved; |
| 105 | + |
| 106 | + /// Whether the device reports the platform implementation actually running. |
| 107 | + final bool isPlatformActive; |
| 108 | + |
| 109 | + /// The verdict: which implementation is in effect right now. |
| 110 | + final AudioProcessingImplementation effective; |
| 111 | +} |
| 112 | + |
| 113 | +/// Diagnostic snapshot of the resolved audio processing state for the shared |
| 114 | +/// audio processing module. |
| 115 | +/// |
| 116 | +/// The module is owned by the native peer connection factory and shared |
| 117 | +/// engine-wide, so this reflects what is actually applied (per-component |
| 118 | +/// [AudioProcessingComponentState.effective]) versus what was requested — for |
| 119 | +/// the whole engine, not a single track. |
| 120 | +class AudioProcessingState { |
| 121 | + const AudioProcessingState({ |
| 122 | + required this.hasAudioProcessingModule, |
| 123 | + required this.echoCancellation, |
| 124 | + required this.noiseSuppression, |
| 125 | + required this.autoGainControl, |
| 126 | + required this.highPassFilter, |
| 127 | + }); |
| 128 | + |
| 129 | + factory AudioProcessingState.fromMap(Map<dynamic, dynamic> map) => AudioProcessingState( |
| 130 | + hasAudioProcessingModule: (map['hasAudioProcessingModule'] as bool?) ?? false, |
| 131 | + echoCancellation: |
| 132 | + AudioProcessingComponentState.fromMap(Map<dynamic, dynamic>.from(map['echoCancellation'] as Map)), |
| 133 | + noiseSuppression: |
| 134 | + AudioProcessingComponentState.fromMap(Map<dynamic, dynamic>.from(map['noiseSuppression'] as Map)), |
| 135 | + autoGainControl: |
| 136 | + AudioProcessingComponentState.fromMap(Map<dynamic, dynamic>.from(map['autoGainControl'] as Map)), |
| 137 | + highPassFilter: AudioProcessingComponentState.fromMap(Map<dynamic, dynamic>.from(map['highPassFilter'] as Map)), |
| 138 | + ); |
| 139 | + |
| 140 | + final bool hasAudioProcessingModule; |
| 141 | + final AudioProcessingComponentState echoCancellation; |
| 142 | + final AudioProcessingComponentState noiseSuppression; |
| 143 | + final AudioProcessingComponentState autoGainControl; |
| 144 | + final AudioProcessingComponentState highPassFilter; |
| 145 | +} |
0 commit comments