HaishinKit.swift/Sources/Media/AudioIOComponent.swift

246 lines
7.6 KiB
Swift

import AVFoundation
#if canImport(SwiftPMSupport)
import SwiftPMSupport
#endif
final class AudioIOComponent: IOComponent, DisplayLinkedQueueClockReference {
lazy var encoder = AudioConverter()
let lockQueue = DispatchQueue(label: "com.haishinkit.HaishinKit.AudioIOComponent.lock")
var audioEngine: AVAudioEngine?
var duration: TimeInterval {
guard let nodeTime = playerNode.lastRenderTime, let playerTime = playerNode.playerTime(forNodeTime: nodeTime) else {
return 0.0
}
return TimeInterval(playerTime.sampleTime) / playerTime.sampleRate
}
var currentBuffers: Atomic<Int> = .init(0)
var soundTransform: SoundTransform = .init() {
didSet {
soundTransform.apply(playerNode)
}
}
private var _playerNode: AVAudioPlayerNode?
private var playerNode: AVAudioPlayerNode! {
get {
if _playerNode == nil {
_playerNode = AVAudioPlayerNode()
audioEngine?.attach(_playerNode!)
}
return _playerNode
}
set {
if let playerNode = _playerNode {
audioEngine?.detach(playerNode)
}
_playerNode = newValue
}
}
private var audioFormat: AVAudioFormat? {
didSet {
guard let audioFormat = audioFormat, let audioEngine = audioEngine else {
return
}
nstry({
audioEngine.connect(self.playerNode, to: audioEngine.mainMixerNode, format: audioFormat)
}, { exeption in
logger.warn(exeption)
})
do {
try audioEngine.start()
if !playerNode.isPlaying {
playerNode.play()
}
} catch {
logger.warn(error)
}
}
}
#if os(iOS) || os(macOS)
var input: AVCaptureDeviceInput? {
didSet {
guard let mixer: AVMixer = mixer, oldValue != input else {
return
}
if let oldValue: AVCaptureDeviceInput = oldValue {
mixer.session.removeInput(oldValue)
}
if let input: AVCaptureDeviceInput = input, mixer.session.canAddInput(input) {
mixer.session.addInput(input)
}
}
}
private var _output: AVCaptureAudioDataOutput?
var output: AVCaptureAudioDataOutput! {
get {
if _output == nil {
_output = AVCaptureAudioDataOutput()
}
return _output
}
set {
if _output == newValue {
return
}
if let output: AVCaptureAudioDataOutput = _output {
output.setSampleBufferDelegate(nil, queue: nil)
mixer?.session.removeOutput(output)
}
_output = newValue
}
}
#endif
override init(mixer: AVMixer) {
super.init(mixer: mixer)
encoder.lockQueue = lockQueue
}
func appendSampleBuffer(_ sampleBuffer: CMSampleBuffer) {
mixer?.recorder.appendSampleBuffer(sampleBuffer, mediaType: .audio)
encoder.encodeSampleBuffer(sampleBuffer)
}
#if os(iOS) || os(macOS)
func attachAudio(_ audio: AVCaptureDevice?, automaticallyConfiguresApplicationAudioSession: Bool) throws {
guard let mixer: AVMixer = mixer else {
return
}
mixer.session.beginConfiguration()
defer {
mixer.session.commitConfiguration()
}
output = nil
encoder.invalidate()
guard let audio: AVCaptureDevice = audio else {
input = nil
return
}
input = try AVCaptureDeviceInput(device: audio)
#if os(iOS)
mixer.session.automaticallyConfiguresApplicationAudioSession = automaticallyConfiguresApplicationAudioSession
#endif
mixer.session.addOutput(output)
output.setSampleBufferDelegate(self, queue: lockQueue)
}
func dispose() {
input = nil
output = nil
playerNode = nil
audioFormat = nil
}
#else
func dispose() {
playerNode = nil
audioFormat = nil
}
#endif
func registerEffect(_ effect: AudioEffect) -> Bool {
encoder.effects.insert(effect).inserted
}
func unregisterEffect(_ effect: AudioEffect) -> Bool {
encoder.effects.remove(effect) != nil
}
func startDecoding(_ audioEngine: AVAudioEngine?) {
self.audioEngine = audioEngine
encoder.delegate = self
encoder.startRunning()
}
func stopDecoding() {
playerNode.reset()
audioEngine = nil
encoder.delegate = nil
encoder.stopRunning()
currentBuffers.mutate { $0 = 0 }
}
}
extension AudioIOComponent: AVCaptureAudioDataOutputSampleBufferDelegate {
// MARK: AVCaptureAudioDataOutputSampleBufferDelegate
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
appendSampleBuffer(sampleBuffer)
}
}
extension AudioIOComponent: AudioConverterDelegate {
// MARK: AudioConverterDelegate
func didSetFormatDescription(audio formatDescription: CMFormatDescription?) {
guard let formatDescription = formatDescription else {
mixer?.videoIO.queue.clockReference = nil
return
}
#if os(iOS)
if #available(iOS 9.0, *) {
audioFormat = AVAudioFormat(cmAudioFormatDescription: formatDescription)
} else {
guard let asbd = formatDescription.streamBasicDescription?.pointee else {
return
}
audioFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: asbd.mSampleRate, channels: asbd.mChannelsPerFrame, interleaved: false)
}
#else
audioFormat = AVAudioFormat(cmAudioFormatDescription: formatDescription)
#endif
mixer?.videoIO.queue.clockReference = self
}
func sampleOutput(audio data: UnsafeMutableAudioBufferListPointer, presentationTimeStamp: CMTime) {
guard !data.isEmpty, data[0].mDataByteSize != 0 else { return }
guard
let audioFormat = audioFormat,
let buffer = AVAudioPCMBuffer(pcmFormat: audioFormat, frameCapacity: data[0].mDataByteSize / 4) else {
return
}
if let queue = mixer?.videoIO.queue, queue.isPaused {
queue.isPaused = false
}
buffer.frameLength = buffer.frameCapacity
let bufferList = UnsafeMutableAudioBufferListPointer(buffer.mutableAudioBufferList)
for i in 0..<bufferList.count {
guard let mData = data[i].mData else { continue }
memcpy(bufferList[i].mData, mData, Int(data[i].mDataByteSize))
bufferList[i].mDataByteSize = data[i].mDataByteSize
bufferList[i].mNumberChannels = 1
}
mixer?.delegate?.didOutputAudio(buffer, presentationTimeStamp: presentationTimeStamp)
currentBuffers.mutate { $0 += 1 }
nstry({
self.playerNode.scheduleBuffer(buffer, completionHandler: self.didAVAudioNodeCompletion)
if !self.playerNode.isPlaying {
self.playerNode.play()
}
}, { exeption in
logger.warn(exeption)
})
}
private func didAVAudioNodeCompletion() {
currentBuffers.mutate { value in
value -= 1
if value == 0 {
self.playerNode.pause()
self.mixer?.didBufferEmpty(self)
}
}
}
}