Skip to content

Commit

Permalink
Ports
Browse files Browse the repository at this point in the history
  • Loading branch information
hiroshihorie committed Feb 2, 2025
1 parent 59171f6 commit 18056e7
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 20 deletions.
36 changes: 36 additions & 0 deletions Sources/LiveKit/Audio/AudioPortManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2025 LiveKit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import AVFAudio

public class AudioPortManager {
struct State {
var ports: [String: AVAudioPlayerNode] = [:]
}

let _state = StateSync(State())

func node(for portId: String) -> AVAudioPlayerNode {
_state.mutate {
if let r = $0.ports[portId] {
return r
}
let r = AVAudioPlayerNode()
$0.ports[portId] = r
return r
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,55 +14,90 @@
* limitations under the License.
*/

import AVFoundation
@preconcurrency import AVFoundation

#if swift(>=5.9)
internal import LiveKitWebRTC
#else
@_implementationOnly import LiveKitWebRTC
#endif

public final class AudioEngineAudioInputObserver: AudioEngineObserver, Loggable {
public let playerNode = AVAudioPlayerNode()
public let playerMixerNode = AVAudioMixerNode()
public let micMixerNode = AVAudioMixerNode()
enum AudioPort: Sendable {
case defaultInput
case custom(AVAudioPlayerNode)
}

public final class DefaultAudioInputObserver: AudioEngineObserver, Loggable {
// <AVAudioFormat 0x600003055180: 2 ch, 48000 Hz, Float32, deinterleaved>
let playerNodeFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32,
sampleRate: 48000,
channels: 2,
interleaved: false)

var next: (any AudioEngineObserver)?
struct State {
var next: (any AudioEngineObserver)?
public let playerNode: AVAudioPlayerNode
public let playerMixerNode = AVAudioMixerNode()
public let micMixerNode = AVAudioMixerNode()
}

let _state: StateSync<State>

public init() {}
public var next: (any AudioEngineObserver)? {
get { _state.next }
set { _state.mutate { $0.next = newValue } }
}

public init(playerNode: AVAudioPlayerNode) {
_state = StateSync(State(playerNode: playerNode))
}

public func setNext(_ handler: any AudioEngineObserver) {
next = handler
}

public func engineDidCreate(_ engine: AVAudioEngine) {
let (playerNode, playerMixerNode, micMixerNode) = _state.read {
($0.playerNode, $0.playerMixerNode, $0.micMixerNode)
}

engine.attach(playerNode)
engine.attach(playerMixerNode)
engine.attach(micMixerNode)

micMixerNode.outputVolume = 0.0

// Invoke next
next?.engineDidCreate(engine)
}

public func engineWillRelease(_ engine: AVAudioEngine) {
// Invoke next
next?.engineWillRelease(engine)

let (playerNode, playerMixerNode, micMixerNode) = _state.read {
($0.playerNode, $0.playerMixerNode, $0.micMixerNode)
}

engine.detach(playerNode)
engine.detach(playerMixerNode)
engine.detach(micMixerNode)
}

public func engineWillConnectInput(_ engine: AVAudioEngine, src: AVAudioNode, dst: AVAudioNode, format: AVAudioFormat) -> Bool {
public func engineWillConnectInput(_ engine: AVAudioEngine, src: AVAudioNode?, dst: AVAudioNode, format: AVAudioFormat) -> Bool {
let (playerNode, playerMixerNode, micMixerNode) = _state.read {
($0.playerNode, $0.playerMixerNode, $0.micMixerNode)
}

// inputPlayer -> playerMixer -> mainMixer
engine.connect(playerNode, to: playerMixerNode, format: playerNodeFormat)
engine.connect(playerMixerNode, to: dst, format: format)

// mic -> micMixer -> mainMixer
engine.connect(src, to: micMixerNode, format: format)
engine.connect(micMixerNode, to: dst, format: format)
if let src {
// mic -> micMixer -> mainMixer
engine.connect(src, to: micMixerNode, format: format)
engine.connect(micMixerNode, to: dst, format: format)
}

return true
}
Expand Down
8 changes: 6 additions & 2 deletions Sources/LiveKit/Track/AudioManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,8 @@ public class AudioManager: Loggable {
_state.mutate { $0.engineObservers = engineObservers }
}

public let ports = AudioPortManager()

// MARK: - For testing

var isPlayoutInitialized: Bool {
Expand Down Expand Up @@ -305,9 +307,11 @@ public class AudioManager: Loggable {

init() {
#if os(iOS) || os(visionOS) || os(tvOS)
let engineObservers: [any AudioEngineObserver] = [DefaultAudioSessionObserver()]
let playerNode = ports.node(for: "default")
let engineObservers: [any AudioEngineObserver] = [DefaultAudioSessionObserver(), DefaultAudioInputObserver(playerNode: playerNode)]
#else
let engineObservers: [any AudioEngineObserver] = []
let playerNode = ports.node(for: "default")
let engineObservers: [any AudioEngineObserver] = [DefaultAudioInputObserver(playerNode: playerNode)]
#endif
_state = StateSync(State(engineObservers: engineObservers))
_admDelegateAdapter.audioManager = self
Expand Down
14 changes: 7 additions & 7 deletions Sources/LiveKit/Track/Capturers/MacOSScreenCapturer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -218,13 +218,13 @@ extension MacOSScreenCapturer: SCStreamOutput {
guard sampleBuffer.isValid else { return }

if case .audio = outputType {
if let pcm = sampleBuffer.toAVAudioPCMBuffer(),
let node = AudioManager.shared.screenShareAppAudioPlayerNode,
let engine = node.engine, engine.isRunning
{
node.scheduleBuffer(pcm)
if !node.isPlaying {
node.play()
if let pcm = sampleBuffer.toAVAudioPCMBuffer() {
let node = AudioManager.shared.ports.node(for: "default")
if let engine = node.engine, engine.isRunning {
node.scheduleBuffer(pcm)
if !node.isPlaying {
node.play()
}
}
}
} else if case .screen = outputType {
Expand Down

0 comments on commit 18056e7

Please sign in to comment.