原文地址 : https://blog.lm1024.club/archives/2020121928
public class LYAudioRecord {
private let KInputBus: AudioUnitElement = 1
private let KOutputBus: AudioUnitElement = 0
private let sampleRate: Int = 44100
private let channel: Int = 1
private var audioSamples: UInt32?
private var isRecording: Bool = false
private var remoteIOUnit: AudioUnit?
public var mAudioFormat: AudioStreamBasicDescription?
public var delegate: WJAudioSourceOutputDelegate?
init() {
configRecorder()
}
private func initAudioComponent() {
var remoteIODesc = AudioComponentDescription(componentType: kAudioUnitType_Output,
componentSubType: kAudioUnitSubType_HALOutput,
componentManufacturer: kAudioUnitManufacturer_Apple,
componentFlags: 0,
componentFlagsMask: 0)
let remoteIOComponent = AudioComponentFindNext(nil, &remoteIODesc)
checkError(status: AudioComponentInstanceNew(remoteIOComponent!, &remoteIOUnit), msg: "AudioComponentInstanceNew")
checkError(status: AudioUnitInitialize(remoteIOUnit!), msg: "AudioUnitInitialize failure")
}
private func configAudioFormat() {
var one: UInt32 = 1
var status = AudioUnitSetProperty(remoteIOUnit!,
kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Input,
KInputBus,
&one,
UInt32(MemoryLayout.size(ofValue: one)))
checkError(status: status, msg: "couldn't kAudioOutputUnitProperty_EnableIO with kAudioUnitScope_Input")
// Attention! set kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, disable
var disableFlag: UInt32 = 0
status = AudioUnitSetProperty(remoteIOUnit!,
kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Output,
KOutputBus,
&disableFlag,
UInt32(MemoryLayout.size(ofValue: disableFlag)))
checkError(status: status, msg: "couldn't kAudioOutputUnitProperty_EnableIO with kAudioUnitScope_Output")
var defaultDevice: AudioDeviceID = kAudioDeviceUnknown
var propertySize = UInt32(MemoryLayout.size(ofValue: defaultDevice))
var defaultDeviceProperty = AudioObjectPropertyAddress(mSelector: kAudioHardwarePropertyDefaultInputDevice,
mScope: kAudioObjectPropertyScopeInput,
mElement: kAudioObjectPropertyElementMaster)
status = AudioObjectGetPropertyData(AudioObjectID(kAudioObjectSystemObject),
&defaultDeviceProperty,
0,
nil,
&propertySize,
&defaultDevice)
checkError(status: status, msg: "AudioObjectGetPropertyData failure")
var temp = Float64(sampleRate)
defaultDeviceProperty.mSelector = kAudioDevicePropertyNominalSampleRate
status = AudioObjectSetPropertyData(defaultDevice, &defaultDeviceProperty, 0, nil, UInt32(MemoryLayout<Float64>.size), &temp)
checkError(status: status, msg: "AudioObjectSetPropertyData failure")
status = AudioUnitSetProperty(remoteIOUnit!,
kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global,
KInputBus,
&defaultDevice,
propertySize)
checkError(status: status, msg: " kAudioOutputUnitProperty_CurrentDevice error")
let mBitsPerChannel: UInt32 = 16
let mChannelsPerFrame = UInt32(channel)
let mFramesPerPacket: UInt32 = 1
let mBytesPerFrame: UInt32 = (mBitsPerChannel / 8) * mChannelsPerFrame
let mBytesPerPacket: UInt32 = mBytesPerFrame * mFramesPerPacket
let mAudioFormat = AudioStreamBasicDescription(mSampleRate: Float64(sampleRate),
mFormatID: kAudioFormatLinearPCM,
mFormatFlags: kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked,
mBytesPerPacket: mBytesPerPacket,
mFramesPerPacket: mFramesPerPacket,
mBytesPerFrame: mBytesPerFrame,
mChannelsPerFrame: mChannelsPerFrame,
mBitsPerChannel: mBitsPerChannel,
mReserved: 0)
self.mAudioFormat = mAudioFormat
status = AudioUnitSetProperty(remoteIOUnit!,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output,
KInputBus,
&(self.mAudioFormat!),
UInt32(MemoryLayout.size(ofValue: self.mAudioFormat!)))
checkError(status: status, msg: "couldn't set kAudioUnitProperty_StreamFormat with kAudioUnitScope_Output")
var param:UInt32 = UInt32(MemoryLayout<UInt32>.size)
status = AudioUnitGetProperty(remoteIOUnit!,
kAudioDevicePropertyBufferFrameSize,
kAudioUnitScope_Global,
0,
&audioSamples,
¶m);
checkError(status: status, msg: "kAudioDevicePropertyBufferFrameSize failure")
}
private func initInputCallBack() {
var inputCallback = AURenderCallbackStruct()
inputCallback.inputProc = { inRefCon, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData in
let record = Unmanaged<WJAudioRecord>.fromOpaque(inRefCon).takeUnretainedValue()
return record.captureCallBack(inRefCon: inRefCon,
ioActionFlags: ioActionFlags,
inTimeStamp: inTimeStamp,
inBusNumber: inBusNumber,
inNumberFrames: inNumberFrames,
ioData: ioData)
}
inputCallback.inputProcRefCon = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
let statusCode = AudioUnitSetProperty(remoteIOUnit!,
kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Global,
KInputBus,
&inputCallback,
UInt32(MemoryLayout.size(ofValue: inputCallback)))
checkError(status: statusCode, msg: "Could not set input callback for I/O node \(statusCode)")
}
private func captureCallBack(inRefCon: UnsafeMutableRawPointer?,
ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>,
inTimeStamp: UnsafePointer<AudioTimeStamp>,
inBusNumber: UInt32,
inNumberFrames: UInt32,
ioData: UnsafeMutablePointer<AudioBufferList>?) -> OSStatus
{
guard let _ = inRefCon else {
return noErr
}
var buffer = AudioBuffer()
buffer.mData = nil
buffer.mDataByteSize = 0
buffer.mNumberChannels = UInt32(channel)
var bufferList = AudioBufferList()
bufferList.mNumberBuffers = 1
bufferList.mBuffers = buffer
let status = AudioUnitRender(remoteIOUnit!, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, &bufferList)
checkError(status: status, msg: "AudioUnitRender error")
let byteSize: UInt32 = bufferList.mBuffers.mDataByteSize
let dst: UnsafeMutableRawPointer = malloc(Int(byteSize))
memcpy(dst, bufferList.mBuffers.mData, Int(byteSize))
guard let audioDelegate = delegate else {
free(dst)
return noErr
}
audioDelegate.output(data: dst, lenght: byteSize, sampleRate: sampleRate, channel: channel,numSamples: inNumberFrames)
free(dst)
return noErr
}
private func checkError(status: OSStatus, msg: String) {
assert(status == noErr, msg)
}
private func configRecorder() {
initAudioComponent()
configAudioFormat()
initInputCallBack()
}
public func startRecord() {
if remoteIOUnit == nil {
configRecorder()
}
if !isRecording {
checkError(status: AudioOutputUnitStart(remoteIOUnit!), msg: "AudioOutputUnitStart(_remoteIOUnit) failure")
isRecording = true
}
}
public func stopRecord() {
if isRecording {
checkError(status: AudioOutputUnitStop(remoteIOUnit!), msg: "AudioOutputUnitStop failure")
isRecording = false
}
}
public func getAudioSamples()->UInt32 {
return self.audioSamples ?? 0
}
}