2
votes

I'm attempting to access real-time microphone data with the following code:

import AVFoundation // for AVAudioEngine

class Mic
{
    public let audioEngine = AVAudioEngine()

    func startRecording() throws
    {
        // https://forums.developer.apple.com/thread/44833
        //audioEngine.mainMixerNode  // causes DIFFERENT crash!

        audioEngine.prepare()  // CRASHES


        let inputNode = audioEngine.inputNode
        if inputNode.inputFormat(forBus: 0).sampleRate == 0 {
            exit(0);
        }

        let recordingFormat = inputNode.outputFormat(forBus: 0)
        inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer: AVAudioPCMBuffer, when: AVAudioTime) in
            print( "YES! Got some samples!")
        }

        audioEngine.prepare()

        try audioEngine.start()
    }

    //public
    func stopRecording()
    {
        audioEngine.stop()
    }
}

However it crashes on:

        audioEngine.prepare()  // CRASHES

2019-07-16 17:51:34.448107+0300 realtime_mic[8992:386743] [avae]

AVAEInternal.h:76 required condition is false:

[AVAudioEngineGraph.mm:1318:Initialize: (inputNode != nullptr || outputNode != nullptr)]

realtime_mic[8992:386743] required condition is false: inputNode != nullptr || outputNode != nullptr2019-07-16 17:51:34.449214+0300

As can be seen, I've tried to apply a hack/patch:

        // https://forums.developer.apple.com/thread/44833
        audioEngine.mainMixerNode

but this causes a different crash:

2019-07-16 17:50:34.315005+0300 realtime_mic[8901:385699] [plugin] AddInstanceForFactory:

No factory registered for id F8BB1C28-BAE8-11D6-9C31-00039315CD46 2019-07-16

17:50:34.349337+0300 realtime_mic[8901:385699]

HALC_ShellDriverPlugIn::Open: Can't get a pointer to the Open routine

2019-07-16 17:50:34.354277+0300 realtime_mic[8901:385699] [ddagg]

AggregateDevice.mm:776 couldn't get default input device, ID = 0, err = 0!

I've sent entitlements just in case: macOS Entitlements audio-input vs. microphone -- but to no avail.

What is the correct way to do this?

Test case at: https://github.com/p-i-/macOS_rt_mic

1
By the way, AVAudioEngine is not best for real-time. There's a visible delay. For (milliSecond) low latency audio, you need to use Audio Units.hotpaw2
The problem here was that I was executing audioEngine.prepare() before setting up either an input or an output node. So the error makes perfect sense.P i

1 Answers

1
votes

Entered the following code into testRecord.swift :

import Foundation
import AVFoundation 

print("starting")

public let audioEngine = AVAudioEngine()

var flag = 0

func startRecording() throws {

    let inputNode = audioEngine.inputNode
    let srate = inputNode.inputFormat(forBus: 0).sampleRate
    print("sample rate = \(srate)")
    if srate == 0 {
        exit(0);
    }

    let recordingFormat = inputNode.outputFormat(forBus: 0)
        inputNode.installTap(onBus: 0, 
        bufferSize: 1024, 
        format: recordingFormat) { 
            (buffer: AVAudioPCMBuffer, when: AVAudioTime) in
            let n = buffer.frameLength
            let c = buffer.stride
            if flag == 0 { 
                print( "num samples  = \(n)") ; 
                print( "num channels = \(c)") ; 
                flag = 1 
            }
        }

    try audioEngine.start()
}

func stopRecording() {
    audioEngine.stop()
}

do {
    try startRecording()
} catch {
    print("error?")
}

usleep(UInt32(1000*1000))         // sleep 1 second before quitting
stopRecording()
print("done")
exit(0)

Compiled testRecord.swift using swiftc on macOS 10.14.5 / Xcode 10.2.1 ; then tried to run the result from Terminal. The first time it ran, macOS asked if Terminal could have microphone permissions. Replied yes, but no output.

But then on subsequent runs it output:

starting

sample rate = 44100.0

num samples = 4410

num channels = 1

done

So it might be you need to allow your app some permissions in System Preferences : Privacy : Microphone