美文网首页
Swift-AudioUnit音频采集与播放

Swift-AudioUnit音频采集与播放

作者: T92 | 来源:发表于2022-02-16 11:34 被阅读0次

    AudioConst

    import Foundation
    import AudioUnit
    
    //var WSBufferDuration: Int {
    //    get {
    //        var value = UserDefaults.standard.integer(forKey: "key_WSBufferDuration")
    //        if value == 0{
    //            value = 16
    //        }
    //        return value
    //    }
    //    set { UserDefaults.standard.set(newValue, forKey: "WSBufferDuration") }
    //}
    let sampleMinValue = 64
    var WSBufferDuration = 16
    var audioPlayCacheBufferLen = 5
    var WSmDataByteSize: Int = 4096
    
    struct AudioConst {
        static let SampleRate: Int = 48000//44100
        static let Channels: UInt32 = 1
        static let InputBus: AudioUnitElement = 1
        static let OutputBus: AudioUnitElement = 0
        static let mBitsPerChannel: UInt32 = 16
    }
    

    PointerConvert

    import Foundation
    
    
    func bridge<T : AnyObject>(ptr : UnsafeRawPointer) -> T {
        return Unmanaged<T>.fromOpaque(ptr).takeUnretainedValue()}
    
    func bridge<T : AnyObject>(obj : T) -> UnsafeRawPointer {
        return UnsafeRawPointer(Unmanaged.passUnretained(obj).toOpaque())}
    
    

    setupAudioSession

    func setupAudioSession() {
            let session: AVAudioSession = AVAudioSession.sharedInstance()
            do {
                try session.setCategory(.playAndRecord, options: [.allowBluetooth, .allowBluetoothA2DP])
                try session.setPreferredSampleRate(Double(AudioConst.SampleRate))
                try session.setPreferredIOBufferDuration(Double(WSBufferDuration) / 1000.0)
                try session.setActive(true)
            } catch  {
                print(error.localizedDescription)
            }
        }
    

    设置WSBufferDuration可以调整每次回调采样点的个数,即音频包大小,具体设置需要根据采样率、位深、声道数计算

    录制-QAAudioRecorder

    import Foundation
    import AudioUnit
    import AVKit
    
    protocol QAAudioRecordDelegate: AnyObject {
        func audioRecorder(recorder: QAAudioRecorder, didUpdate volume: Double)
        func audioRecorder(recorder: QAAudioRecorder, didRecieve buffer: AudioBufferList)
    }
    
    extension QAAudioRecordDelegate {
        func audioRecorder(recorder: QAAudioRecorder, didUpdate volume: Double){}
        func audioRecorder(recorder: QAAudioRecorder, didRecieve buffer: AudioBufferList){}
    }
    
    class QAAudioRecorder: NSObject {
        
        private var ioUnit: AudioComponentInstance? = nil
        
        weak var delegate: QAAudioRecordDelegate? = nil
        
        private var bufferList: AudioBufferList = AudioBufferList.init(mNumberBuffers: 1, mBuffers: AudioBuffer.init(mNumberChannels: UInt32(AudioConst.Channels), mDataByteSize: UInt32(WSmDataByteSize), mData: UnsafeMutableRawPointer.allocate(byteCount: WSmDataByteSize, alignment: 1)))
        
        override init() {
            super.init()
            WSAppManager.shared.setupAudioSession()
            let _ = self.setupIoUnit()
        }
    
        private func setupIoUnit() -> Bool {
            var ioDes: AudioComponentDescription = AudioComponentDescription.init(
                componentType: kAudioUnitType_Output,
                componentSubType: kAudioUnitSubType_RemoteIO,
                componentManufacturer: kAudioUnitManufacturer_Apple,
                componentFlags: 0,
                componentFlagsMask: 0)
            guard let inputComp: AudioComponent = AudioComponentFindNext(nil, &ioDes) else {
                print("outputComp init error")
                return false
            }
            if AudioComponentInstanceNew(inputComp, &ioUnit) != noErr {
                print("io AudioComponentInstanceNew error")
                return false
            }
            
            var ioFormat: AudioStreamBasicDescription = AudioStreamBasicDescription.init(
                mSampleRate: Float64(AudioConst.SampleRate),
                mFormatID: kAudioFormatLinearPCM,
                mFormatFlags: kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked,
                mBytesPerPacket:  UInt32(2 * AudioConst.Channels),
                mFramesPerPacket: 1,
                mBytesPerFrame: UInt32(2 * AudioConst.Channels),
                mChannelsPerFrame: UInt32(AudioConst.Channels),
                mBitsPerChannel: AudioConst.mBitsPerChannel,
                mReserved: 0)
            if AudioUnitSetProperty(self.ioUnit!, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, AudioConst.InputBus, &ioFormat, UInt32(MemoryLayout.size(ofValue: ioFormat))) != noErr {
                print("set StreamFormat error")
                return false
            }
            
            var value: UInt32 = 1
            if AudioUnitSetProperty(self.ioUnit!, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, AudioConst.InputBus, &value, UInt32(MemoryLayout.size(ofValue: value))) != noErr {
                print("can't enable input io")
                return false
            }
            
            
    
            //方式1
    //        if AudioUnitSetProperty(self.ioUnit!, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, AudioConst.OutputBus, &ioFormat, UInt32(MemoryLayout.size(ofValue: ioFormat))) != noErr {
    //            print("set StreamFormat error")
    //            return false
    //        }
            
    //        var recordCallback: AURenderCallbackStruct = AURenderCallbackStruct.init(inputProc:  { (inRefCon, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData) -> OSStatus in
    //            let bridgeSelf: QAAudioRecorder = bridge(ptr: UnsafeRawPointer.init(inRefCon))
    //            let error: OSStatus = AudioUnitRender(bridgeSelf.ioUnit!, ioActionFlags, inTimeStamp, AudioConst.InputBus, inNumberFrames, ioData!)
    //            if error == noErr {
    //                bridgeSelf.updateVolumeValue(buffer: ioData!.pointee.mBuffers)
    //                bridgeSelf.delegate?.audioRecorder(recorder: bridgeSelf, didRecieve: ioData!.pointee)
    //            }
    //            //实际上是有播放,这里设置静音
    //            let mdata = ioData!.pointee.mBuffers.mData
    //            memset(mdata, 0, Int(ioData!.pointee.mBuffers.mDataByteSize))
    //            ioData?.pointee.mBuffers.mData = mdata
    //            return noErr
    //        }, inputProcRefCon: UnsafeMutableRawPointer(mutating: bridge(obj: self)))
    //
    //
    //        if AudioUnitSetProperty(self.ioUnit!, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, AudioConst.OutputBus, &recordCallback, UInt32(MemoryLayout.size(ofValue: recordCallback))) != noErr {
    //            print("SetRenderCallback error")
    //            return false
    //        }
            
            
            
            
            //方式二
            var recordCallback1: AURenderCallbackStruct = AURenderCallbackStruct.init(inputProc:  { (inRefCon, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData) -> OSStatus in
    
                let bridgeSelf: QAAudioRecorder = bridge(ptr: UnsafeRawPointer.init(inRefCon))
                let error: OSStatus = AudioUnitRender(bridgeSelf.ioUnit!, ioActionFlags, inTimeStamp, AudioConst.InputBus, inNumberFrames, &bridgeSelf.bufferList)
                if error == noErr {
    
                    let bufferData: AudioBuffer = bridgeSelf.bufferList.mBuffers
                    let rawPointer = UnsafeMutableRawPointer.allocate(byteCount: Int(bufferData.mDataByteSize), alignment: 1)
    
                    if let mData = bufferData.mData {
                        rawPointer.copyMemory(from: mData, byteCount: Int(bufferData.mDataByteSize))
                        let tempBuf = AudioBuffer.init(mNumberChannels: bufferData.mNumberChannels, mDataByteSize: bufferData.mDataByteSize, mData: rawPointer)
                        bridgeSelf.updateVolumeValue(buffer: tempBuf)
                    }
    
    
                    bridgeSelf.delegate?.audioRecorder(recorder: bridgeSelf, didRecieve: bridgeSelf.bufferList)
                    rawPointer.deallocate()
                }
                return noErr
            }, inputProcRefCon: UnsafeMutableRawPointer(mutating: bridge(obj: self)))
    
    
            if AudioUnitSetProperty(self.ioUnit!, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, AudioConst.InputBus, &recordCallback1, UInt32(MemoryLayout.size(ofValue: recordCallback1))) != noErr {
                print("SetRenderCallback error")
                return false
            }
            
            
            
            
            
    
            return true
            
        }
        
        private func updateVolumeValue(buffer: AudioBuffer) {
            var pcmAll: Int = 0
            let bufferPoint = UnsafeMutableBufferPointer<Int16>.init(buffer)
            let bufferArray = Array(bufferPoint)
            //取5个采样点计算,节省CPU资源
            let len = min(5, bufferArray.count)
            for index in 0..<len {
                let value = bufferArray[index]
                pcmAll += Int(value) * Int(value)
            }
            let mean: Double = Double(pcmAll) / Double(len)
            let volume: Double = 10 * log10(mean)
            guard "\(volume)" != "nan" else { return }
    //            print(volume)
            // 0-42 42-97
            delegate?.audioRecorder(recorder: self, didUpdate: volume)
    
            
    //        var pcmAll: Double = 0
    //        let bufferPoint = UnsafeMutableBufferPointer<Int16>.init(buffer)
    //        let bufferArray = Array(bufferPoint)
    //        //取5个采样点计算,节省CPU资源
    //        let len = min(5, bufferArray.count)
    //        for index in 0..<len {
    //            let value = bufferArray[index]
    //            pcmAll += Double(value)/65536
    //        }
    //        let mean: Double = pcmAll / Double(len)
    //        let volume: Double = 20 * log10(mean)
    //        guard "\(volume)" != "nan" else { return }
    ////        print(volume)
    //        // 0-42 42-97
    //        delegate?.audioRecorder(recorder: self, didUpdate: volume)
        }
        
        public func startRecord() {
            
            var error = AudioUnitInitialize(self.ioUnit!)
            if error != noErr  {
                print("AudioUnitInitialize error: \(error)")
            }
            error = AudioOutputUnitStart(self.ioUnit!)
            if  error != noErr {
                print("AudioOutputUnitStart error")
            }
    
        }
        
        public func stopRecord() {
            AudioUnitUninitialize(self.ioUnit!)
            AudioOutputUnitStop(self.ioUnit!)
        }
        
    }
    

    播放-QAAudioUnitPlayer

    import Foundation
    import AudioUnit
    import AVKit
    
    class QAAudioUnitPlayer: NSObject {
        
        var ioUnit: AudioComponentInstance? = nil
        
        private(set) var isPlaying = false
        private var cacheBufferData = Data()
        
        private let semaphore = DispatchSemaphore(value: 1)
        
    //    private var bufferList: AudioBufferList = AudioBufferList.init(mNumberBuffers: 1, mBuffers: AudioBuffer.init(mNumberChannels: UInt32(AudioConst.Channels), mDataByteSize: UInt32(AudioConst.mDataByteSize), mData: UnsafeMutableRawPointer.allocate(byteCount: AudioConst.mDataByteSize, alignment: 1)))
        
        override init() {
            super.init()
            WSAppManager.shared.setupAudioSession()
            let _ = self.setupIoUnit()
        }
        
        func addAudioData(data: Data){
            guard isPlaying else { return }
    //        print("包大小:\(data.count)")
            if cacheBufferData.count > WSmDataByteSize * audioPlayCacheBufferLen {
                removeCacheData(count: cacheBufferData.count - WSmDataByteSize*audioPlayCacheBufferLen)
            }
            semaphore.wait()
            cacheBufferData.append(data)
            semaphore.signal()
        }
        
    
        private func setupIoUnit() -> Bool {
            var ioDes: AudioComponentDescription = AudioComponentDescription.init(
                componentType: kAudioUnitType_Output,
                componentSubType: kAudioUnitSubType_RemoteIO,
                componentManufacturer: kAudioUnitManufacturer_Apple,
                componentFlags: 0,
                componentFlagsMask: 0)
            guard let inputComp: AudioComponent = AudioComponentFindNext(nil, &ioDes) else {
                print("outputComp init error")
                return false
            }
            if AudioComponentInstanceNew(inputComp, &ioUnit) != noErr {
                print("io AudioComponentInstanceNew error")
                return false
            }
            
            var value = 1
            if AudioUnitSetProperty(self.ioUnit!, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, AudioConst.OutputBus, &value, UInt32(MemoryLayout.size(ofValue: value))) != noErr {
                print("can't enable output io")
                return false
            }
            
            var ioFormat: AudioStreamBasicDescription = AudioStreamBasicDescription.init(
                mSampleRate: Float64(AudioConst.SampleRate),
                mFormatID: kAudioFormatLinearPCM,
                mFormatFlags: kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked,
                mBytesPerPacket:  UInt32(2 * AudioConst.Channels),
                mFramesPerPacket: 1,
                mBytesPerFrame: UInt32(2 * AudioConst.Channels),
                mChannelsPerFrame: UInt32(AudioConst.Channels),
                mBitsPerChannel: AudioConst.mBitsPerChannel,
                mReserved: 0)
    
            if AudioUnitSetProperty(self.ioUnit!, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, AudioConst.OutputBus, &ioFormat, UInt32(MemoryLayout.size(ofValue: ioFormat))) != noErr {
                print("set StreamFormat error")
                return false
            }
            
            var playCallback: AURenderCallbackStruct = AURenderCallbackStruct.init(inputProc:  { (inRefCon, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData) -> OSStatus in
                let bridgeSelf: QAAudioUnitPlayer = bridge(ptr: UnsafeRawPointer.init(inRefCon))
                let cacheData = bridgeSelf.cacheBufferData as NSData
                let bufferData: AudioBuffer = ioData!.pointee.mBuffers
                let len = Int(bufferData.mDataByteSize)
                if len <= cacheData.count{
                    cacheData.getBytes(bufferData.mData!, range: NSMakeRange(0, len))
                    bridgeSelf.removeCacheData(count: len)
                }else{
                    let mdata = ioData!.pointee.mBuffers.mData
                    memset(mdata, 0, Int(ioData!.pointee.mBuffers.mDataByteSize))
                }
                return noErr
            }, inputProcRefCon: UnsafeMutableRawPointer(mutating: bridge(obj: self)))
            
            
            if AudioUnitSetProperty(self.ioUnit!, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, AudioConst.OutputBus, &playCallback, UInt32(MemoryLayout.size(ofValue: playCallback))) != noErr {
                print("SetRenderCallback error")
                return false
            }
            return true
            
        }
        private func removeCacheData(count: Int){
            guard count >= 0 else { return }
            semaphore.wait()
            guard cacheBufferData.count >= count else {
                semaphore.signal()
                return
            }
            let startIndex = cacheBufferData.index(cacheBufferData.startIndex, offsetBy: 0)
            let endIndex = cacheBufferData.index(cacheBufferData.startIndex, offsetBy: count)
            let range = startIndex..<endIndex
            cacheBufferData.removeSubrange(range)
            semaphore.signal()
        }
        
        public func startPlay() {
            isPlaying = true
            var error = AudioUnitInitialize(self.ioUnit!)
            if error != noErr  {
                print("AudioUnitInitialize error: \(error)")
            }
            error = AudioOutputUnitStart(self.ioUnit!)
            if  error != noErr {
                print("AudioOutputUnitStart error")
            }
    
        }
        
        public func stopPlay() {
            isPlaying = false
            AudioUnitUninitialize(self.ioUnit!)
            AudioOutputUnitStop(self.ioUnit!)
            cacheBufferData.removeAll()
        }
        
        
    }
    

    相关文章

      网友评论

          本文标题:Swift-AudioUnit音频采集与播放

          本文链接:https://www.haomeiwen.com/subject/caorlrtx.html