Error 561145187 - Recording audio from keyboard extension

Hi, as other threads have already discussed, I'd like to record audio from a keyboard extension.

The keyboard has been granted both full access and microphone access. Nonetheless whenever I attempt to start a recording from my keyboard, it fails to start with the following error:

Recording failed to start: Error Domain=com.apple.coreaudio.avfaudio Code=561145187 "(null)" UserInfo={failed call=err = PerformCommand(*ioNode, kAUStartIO, NULL, 0)}

This is the code I am using:

import Foundation
import AVFoundation

protocol AudioRecordingServiceDelegate: AnyObject {
    func audioRecordingDidStart()
    func audioRecordingDidStop(withAudioData: Data?)
    func audioRecordingPermissionDenied()
}

class AudioRecordingService {
    weak var delegate: AudioRecordingServiceDelegate?
    private var audioEngine: AVAudioEngine?
    private var audioSession: AVAudioSession?
    private var isRecording = false
    private var audioData = Data()

    private let targetFormat = AVAudioFormat(commonFormat: .pcmFormatInt16,
                                           sampleRate: 16000,
                                           channels: 1,
                                           interleaved: false)!

    private func setupAudioSession() throws {
        let session = AVAudioSession.sharedInstance()
        try session.setCategory(.playAndRecord, mode: .spokenAudio,
                              options: [.mixWithOthers, .allowBluetooth, .defaultToSpeaker])
        try session.setPreferredIOBufferDuration(0.005)
        try session.setActive(true, options: .notifyOthersOnDeactivation)
        audioSession = session
    }

    func checkMicrophonePermission(completion: @escaping (Bool) -> Void) {
        switch AVAudioApplication.shared.recordPermission {
        case .granted:
            completion(true)
        case .denied:
            delegate?.audioRecordingPermissionDenied()
            completion(false)
        case .undetermined:
            AVAudioApplication.requestRecordPermission { [weak self] granted in
                if !granted {
                    self?.delegate?.audioRecordingPermissionDenied()
                }
                completion(granted)
            }
        @unknown default:
            delegate?.audioRecordingPermissionDenied()
            completion(false)
        }
    }

    func toggleRecording() {
        if isRecording {
            stopRecording()
        } else {
            checkMicrophonePermission { [weak self] granted in
                if granted {
                    self?.startRecording()
                }
            }
        }
    }

    private func startRecording() {
        guard !isRecording else { return }

        do {
            try setupAudioSession()

            audioEngine = AVAudioEngine()
            guard let engine = audioEngine else { return }

            let inputNode = engine.inputNode
            let inputFormat = inputNode.inputFormat(forBus: 0)
            audioData.removeAll()

            guard let converter = AVAudioConverter(from: inputFormat, to: targetFormat) else {
                print("Failed to create audio converter")
                return
            }

            inputNode.installTap(onBus: 0, bufferSize: 1024, format: inputFormat) { [weak self] buffer, _ in
                guard let self = self else { return }

                let frameCount = AVAudioFrameCount(Double(buffer.frameLength) * 16000.0 / buffer.format.sampleRate)
                guard let outputBuffer = AVAudioPCMBuffer(pcmFormat: self.targetFormat,
                                                        frameCapacity: frameCount) else { return }

                outputBuffer.frameLength = frameCount

                var error: NSError?
                converter.convert(to: outputBuffer, error: &error) { _, outStatus in
                    outStatus.pointee = .haveData
                    return buffer
                }

                if error == nil, let channelData = outputBuffer.int16ChannelData {
                    let dataLength = Int(outputBuffer.frameLength) * 2
                    let data = Data(bytes: channelData.pointee, count: dataLength)
                    self.audioData.append(data)
                }
            }

            engine.prepare()
            try engine.start()

            isRecording = true
            delegate?.audioRecordingDidStart()
        } catch {
            print("Recording failed to start: \(error)")
            stopRecording()
        }
    }

    private func stopRecording() {
        audioEngine?.inputNode.removeTap(onBus: 0)
        audioEngine?.stop()
        isRecording = false

        let finalData = audioData
        audioData.removeAll()

        delegate?.audioRecordingDidStop(withAudioData: finalData)
        try? audioSession?.setActive(false, options: .notifyOthersOnDeactivation)
    }

    deinit {
        if isRecording {
            stopRecording()
        }
    }
}

Granting the deprecated "Inter-App Audio" capability did not solve the problem either.

Is recording audio from a keyboard extension even possible in general? If so, how do I fix it?

Related threads: https://developer.apple.com/forums/thread/108055 https://developer.apple.com/forums/thread/742601

Hello @wotschofsky, thank you for your post. Are you configuring open access and indicating dictation support? If you are already doing the above and are still getting a !rec error, then please use Feedback Assistant to submit a bug report and please post here the FB number for my reference.

Thanks for the feedback! Can you confirm that this is the correct way of indicating dication support? With this I am still seeing the default iOS dictation button.

class KeyboardViewController: UIInputViewController {
    override var hasDictationKey: Bool {
        get { return true }
        set {}
    }
}
Error 561145187 - Recording audio from keyboard extension
 
 
Q