Skip to content

Commit 8eb7189

Browse files
authored
Custom audio capturing (#766)
Make `AudioManager.shared.mixer.capture(appAudio:)` public and support multiple input formats.
1 parent 0de1a8c commit 8eb7189

2 files changed

Lines changed: 30 additions & 12 deletions

File tree

.changes/audio-capture-api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
patch type="changed" "Expose audio capture API"

Sources/LiveKit/Audio/MixerEngineObserver.swift

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,11 @@ public final class MixerEngineObserver: AudioEngineObserver, Loggable {
7474

7575
// Internal states
7676
var isInputConnected: Bool = false
77-
var appAudioConverter: AudioConverter?
77+
78+
// Cached converters
79+
var converters: [AVAudioFormat: AudioConverter] = [:]
80+
81+
// Reference to engine format
7882
var engineFormat: AVAudioFormat?
7983
}
8084

@@ -147,6 +151,10 @@ public final class MixerEngineObserver: AudioEngineObserver, Loggable {
147151
engine.connect(micMixerNode, to: mainMixerNode, format: format)
148152

149153
_state.mutate {
154+
if let previousEngineFormat = $0.engineFormat, previousEngineFormat != format {
155+
// Clear cached converters when engine format changes
156+
$0.converters.removeAll()
157+
}
150158
$0.engineFormat = format
151159
$0.isInputConnected = true
152160
}
@@ -169,22 +177,31 @@ public final class MixerEngineObserver: AudioEngineObserver, Loggable {
169177
}
170178

171179
extension MixerEngineObserver {
180+
// Create or use cached AudioConverter.
181+
func converter(for format: AVAudioFormat) -> AudioConverter? {
182+
_state.mutate {
183+
guard let engineFormat = $0.engineFormat else { return nil }
184+
185+
if let converter = $0.converters[format] {
186+
return converter
187+
}
188+
189+
let newConverter = AudioConverter(from: format, to: engineFormat)
190+
$0.converters[format] = newConverter
191+
return newConverter
192+
}
193+
}
194+
172195
// Capture appAudio and apply conversion automatically suitable for internal audio engine.
173-
func capture(appAudio inputBuffer: AVAudioPCMBuffer) {
174-
let (isConnected, appNode, oldConverter, engineFormat) = _state.read {
175-
($0.isInputConnected, $0.appNode, $0.appAudioConverter, $0.engineFormat)
196+
public func capture(appAudio inputBuffer: AVAudioPCMBuffer) {
197+
let (isConnected, appNode) = _state.read {
198+
($0.isInputConnected, $0.appNode)
176199
}
177200

178-
guard isConnected, let engineFormat, let engine = appNode.engine, engine.isRunning else { return }
201+
guard isConnected, let engine = appNode.engine, engine.isRunning else { return }
179202

180203
// Create or update the converter if needed
181-
let converter = (oldConverter?.inputFormat == inputBuffer.format)
182-
? oldConverter
183-
: {
184-
let newConverter = AudioConverter(from: inputBuffer.format, to: engineFormat)!
185-
self._state.mutate { $0.appAudioConverter = newConverter }
186-
return newConverter
187-
}()
204+
let converter = converter(for: inputBuffer.format)
188205

189206
guard let converter else { return }
190207

0 commit comments

Comments
 (0)