以8kHz采样率录制的iOS AudioUnit静音

时间:2018-07-19 13:08:09

标签: ios swift audio audiounit downsampling

我正在构建一个voip应用程序,我们需要在其中记录来自麦克风的音频并将其以8kHz的采样率发送到某个地方。 现在,我以默认采样率对其进行记录,在我的情况下,该采样率始终为44,1k。然后使用this algorithm将其手动转换为8k。

这种幼稚的方法会产生“好的”质量,但是我认为使用AudioUnit的本机降采样功能会更好。

但是当我更改录制的AudioUnit上的采样率属性时,它仅输出静默帧(〜0.0),我不知道为什么。

我已经提取了应用程序中负责声音的部分。 它应该从麦克风记录->写到环形缓冲区->播放缓冲区中的数据:

RecordingUnit:

import Foundation
import AudioToolbox
import AVFoundation

class RecordingUnit : NSObject
{
    public static let AudioPacketDataSize = 160
    public static var instance: RecordingUnit!
    public var micBuffers : AudioBufferList?;
    public var OutputBuffer = RingBuffer<Float>(count: 1 * 1000 * AudioPacketDataSize);

    public var currentAudioUnit: AudioUnit?

    override init()
    {
        super.init()
        RecordingUnit.instance = self

        micBuffers = AudioBufferList(
            mNumberBuffers: 1,
            mBuffers: AudioBuffer(
                mNumberChannels: UInt32(1),
                mDataByteSize: UInt32(1024),
                mData: UnsafeMutableRawPointer.allocate(byteCount: 1024, alignment: 1)))
    }

    public func start()
    {
        var acd = AudioComponentDescription(
            componentType: OSType(kAudioUnitType_Output),
            componentSubType: OSType(kAudioUnitSubType_VoiceProcessingIO),
            componentManufacturer: OSType(kAudioUnitManufacturer_Apple),
            componentFlags: 0,
            componentFlagsMask: 0)

        let comp = AudioComponentFindNext(nil, &acd)
        var err : OSStatus

        AudioComponentInstanceNew(comp!, &currentAudioUnit)

        var true_ui32: UInt32 = 1

        guard AudioUnitSetProperty(currentAudioUnit!,
                                kAudioOutputUnitProperty_EnableIO,
                                kAudioUnitScope_Input,
                                1,
                                &true_ui32,
                                UInt32(MemoryLayout<UInt32>.size)) == 0 else {print ("could not enable IO for input "); return}

        err = AudioUnitSetProperty(currentAudioUnit!,
                                kAudioOutputUnitProperty_EnableIO,
                                kAudioUnitScope_Output,
                                0,
                                &true_ui32,
                                UInt32(MemoryLayout<UInt32>.size))
        guard err == 0 else {print ("could not enable IO for output "); return}

        var sampleRate : Float64 = 8000
        err = AudioUnitSetProperty(currentAudioUnit!,
                                kAudioUnitProperty_SampleRate,
                                kAudioUnitScope_Input,
                                0,
                                &sampleRate,
                                UInt32(MemoryLayout<Float64>.size))
        guard err == 0 else {print ("could not set sample rate (error=\(err))"); return}

        var renderCallbackStruct = AURenderCallbackStruct(inputProc: recordingCallback, inputProcRefCon: UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()))
        err = AudioUnitSetProperty(currentAudioUnit!,
                                AudioUnitPropertyID(kAudioOutputUnitProperty_SetInputCallback),
                                AudioUnitScope(kAudioUnitScope_Global),
                                1,
                                &renderCallbackStruct,
                                UInt32(MemoryLayout<AURenderCallbackStruct>.size))
        guard err == 0 else {print("could not set input callback"); return}

        guard AudioUnitInitialize(currentAudioUnit!) == 0 else {print("could not initialize recording unit"); return}
        guard AudioOutputUnitStart(currentAudioUnit!) == 0 else {print("could not start recording unit"); return}

        print("Audio Recording started")
    }

    let recordingCallback: AURenderCallback = { (inRefCon, ioActionFlags, inTimeStamp, inBusNumber, frameCount, ioData ) -> OSStatus in
        let audioObject = RecordingUnit.instance!
        var err: OSStatus = noErr
        guard let au = audioObject.currentAudioUnit else {print("AudioUnit nil (recording)"); return 0}

        err = AudioUnitRender(au, ioActionFlags, inTimeStamp, inBusNumber, frameCount, &audioObject.micBuffers!)

        let bufferPointer = UnsafeMutableRawPointer(audioObject.micBuffers!.mBuffers.mData)
        let dataArray = bufferPointer!.assumingMemoryBound(to: Float.self)

        var frames = 0
        var sum = Float(0)
        for i in 0..<Int(frameCount) {
            if dataArray[i] != Float.nan {
                sum += dataArray[i]
                audioObject.OutputBuffer.write(Float(dataArray[i]))
                frames = frames+1
            }
        }

        let average = sum/Float(frameCount)

        print("recorded -> \(frames)/\(frameCount) -> average=\(average)")
        return 0
    }

    public func stop()
    {
        if currentAudioUnit != nil { AudioUnitUninitialize(currentAudioUnit!) }
    }
}

PlaybackUnit.swift:

import Foundation
import AudioToolbox
import AVFoundation

class PlaybackUnit : NSObject {

    public static var instance : PlaybackUnit!
    public var InputBuffer : RingBuffer<Float>?
    public var currentAudioUnit: AudioUnit?

    override init()
    {
        super.init()
        PlaybackUnit.instance = self
    }

    public func start()
    {
        var acd = AudioComponentDescription(
            componentType: OSType(kAudioUnitType_Output),
            componentSubType: OSType(kAudioUnitSubType_VoiceProcessingIO),
            componentManufacturer: OSType(kAudioUnitManufacturer_Apple),
            componentFlags: 0,
            componentFlagsMask: 0)


        let comp = AudioComponentFindNext(nil, &acd)

        var err = AudioComponentInstanceNew(comp!, &currentAudioUnit)
        guard err == 0 else {print ("could not create a new AudioComponent instance"); return};

        //set sample rate
        var sampleRate : Float64 = 8000
        AudioUnitSetProperty(currentAudioUnit!,
                            kAudioUnitProperty_SampleRate,
                            kAudioUnitScope_Input,
                            0,
                            &sampleRate,
                            UInt32(MemoryLayout<Float64>.size))
        guard err == 0 else {print ("could not set sample rate "); return};

        //register render callback
        var outputCallbackStruct = AURenderCallbackStruct(inputProc: outputCallback, inputProcRefCon: UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()))
        err = AudioUnitSetProperty(currentAudioUnit!,
                                AudioUnitPropertyID(kAudioUnitProperty_SetRenderCallback),
                                AudioUnitScope(kAudioUnitScope_Input),
                                0,
                                &outputCallbackStruct,
                                UInt32(MemoryLayout<AURenderCallbackStruct>.size))
        guard err == 0 else {print("could not set render callback"); return}

        guard AudioUnitInitialize(currentAudioUnit!) == 0 else  {print("could not initialize output unit"); return}
        guard AudioOutputUnitStart(currentAudioUnit!) == 0 else {print("could not start output unit"); return}

        print("Audio Output started")
    }

    let outputCallback: AURenderCallback = { (
        inRefCon,
        ioActionFlags,
        inTimeStamp,
        inBusNumber,
        frameCount,
        ioData ) -> OSStatus in
        let ins = PlaybackUnit.instance
        let audioObject = ins!

        var err: OSStatus = noErr

        var frames = 0
        var average : Float = 0

        if var ringBuffer = audioObject.InputBuffer {
            var dataArray = ioData!.pointee.mBuffers.mData!.assumingMemoryBound(to: Float.self)
            var i = 0

            while i < frameCount {
                if let v = ringBuffer.read() {
                    dataArray[i] = v
                    average += v
                } else {
                    dataArray[i] = 0
                }
                i += 1
                frames += 1
            }
        }

        average = average / Float(frameCount)

        print("played -> \(frames)/\(frameCount) => avarage: \(average)")
        return 0
    }

    public func stop()
    {
        if currentAudioUnit != nil { AudioUnitUninitialize(currentAudioUnit!) }
    }
}

ViewController:

import UIKit
import AVFoundation

class ViewController: UIViewController {

    var micPermission   =  false
    private var micPermissionDispatchToken = 0

    override func viewDidLoad() {
        super.viewDidLoad()

        let audioSession = AVAudioSession.sharedInstance()

        if (micPermission == false) {
            if (micPermissionDispatchToken == 0) {
                micPermissionDispatchToken = 1
                audioSession.requestRecordPermission({(granted: Bool)-> Void in
                    if granted {
                        self.micPermission = true
                        return
                    } else {
                        print("failed to grant microphone access!!")
                    }
                })
            }
        }
        if micPermission == false { return }

        try! audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord)
        try! audioSession.setPreferredSampleRate(8000)
        try! audioSession.overrideOutputAudioPort(.speaker)
        try! audioSession.setPreferredOutputNumberOfChannels(1)
        try! audioSession.setMode(AVAudioSessionModeVoiceChat)
        try! audioSession.setActive(true)

        let microphone = RecordingUnit()
        let speakers = PlaybackUnit()

        speakers.InputBuffer = microphone.OutputBuffer
        microphone.start()
        speakers.start()
    }
}

0 个答案:

没有答案