17

I'm building a voip application where we need to record audio from the microphone and send it somewhere in 8kHz sample rate. Right now I'm recording it in default sample rate, which in my case was always 44,1k. This is then manually converted to 8k using this algorithm.

This naive approach results in an "ok" quality but I think it would be much better using the native downsampling capabilities of the AudioUnit.

But when I change the sample rate property on the recording AudioUnit, it outputs just silent frames (~0.0) and I don't know why.

I've extracted the part of the app responsible for the sound. It should record from microphone -> write to a ring buffer -> playback the data in the buffer:

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()
    }
}
Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Ryupold
  • 191
  • 6

0 Answers0