AutoCat/AutoCat/Utils/Recorder.swift

160 lines
6.5 KiB
Swift

import Foundation
import Speech
import AVFoundation
import AudioToolbox
import RxSwift
import os.log
import ExceptionCatcher
class Recorder {
let engine = AVAudioEngine()
var fileRef: ExtAudioFileRef? = nil
let recognizer = SFSpeechRecognizer(locale: Locale(identifier: "ru_RU"))
let request = SFSpeechAudioBufferRecognitionRequest()
var recognitionTask: SFSpeechRecognitionTask?
var endRecognitionTimer: Timer?
var result: String = ""
let recordingSettings: [String:Any] = [
AVFormatIDKey:kAudioFormatMPEG4AAC_HE,
AVSampleRateKey:44100.0,
AVNumberOfChannelsKey:2,
//AVEncoderBitRateKey:320*1024,
//AVLinearPCMBitDepthKey:16,
AVEncoderAudioQualityKey:AVAudioQuality.max.rawValue
]
init() {
self.request.contextualStrings = ["61", "161", "761", "123", "750", "777", "799"]
}
func microphoneAvailable() -> Bool {
// FIXME:
return true
}
func requestPermissions() -> Single<Void> {
return Single<Void>.create { observer in
AVAudioSession.sharedInstance().requestRecordPermission { allowed in
if allowed {
SFSpeechRecognizer.requestAuthorization { status in
switch status {
case .authorized:
observer(.success(()))
break
case .denied:
let error = CocoaError.error("Access error", reason: "Access to speech recognition is denied", suggestion: "Please give permission to use speech recognition in system settings")
observer(.failure(error))
break
case .restricted:
let error = CocoaError.error("Access error", reason: "Speech recognition is restricted on this device")
observer(.failure(error))
break
case .notDetermined:
let error = CocoaError.error("Access error", reason: "Speech recognition status is not yet determined")
observer(.failure(error))
break
@unknown default:
let error = CocoaError.error("Access error", reason: "Unknown error accessing speech recognizer")
observer(.failure(error))
break
}
}
} else {
let error = CocoaError.error("Access error", reason: "Access to microphone is denied", suggestion: "Please give permission to use microphone in system settings")
observer(.failure(error))
}
}
return Disposables.create()
}
}
func startRecording(to file: URL) -> Single<String> {
guard self.microphoneAvailable() else {
return Single.error(CocoaError.error("Recording error", reason: "Microphone not found"))
}
return Single<String>.create { observer in
guard let aac = AVAudioFormat(settings: self.recordingSettings) else {
observer(.failure(CocoaError.error("Recording error", reason: "Format not supported")))
return Disposables.create()
}
ExtAudioFileCreateWithURL(file as CFURL, kAudioFileM4AType, aac.streamDescription, nil, AudioFileFlags.eraseFile.rawValue, &self.fileRef)
guard let fileRef = self.fileRef else {
observer(.failure(CocoaError.error(CocoaError.Code.fileWriteUnknown)))
return Disposables.create()
}
do {
try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: [])
try AVAudioSession.sharedInstance().setActive(true)
let inFormat = self.engine.inputNode.outputFormat(forBus: 0)
ExtAudioFileSetProperty(fileRef, kExtAudioFileProperty_ClientDataFormat, UInt32(MemoryLayout<AudioStreamBasicDescription>.size), inFormat.streamDescription)
try ExceptionCatcher.catch {
self.engine.inputNode.installTap(onBus: 0, bufferSize: 1024, format: inFormat) { buffer, time in
self.request.append(buffer)
ExtAudioFileWrite(fileRef, buffer.frameLength, buffer.audioBufferList)
}
}
self.recognitionTask = self.recognizer!.recognitionTask(with: self.request) { result, error in
if let transcription = result?.bestTranscription {
self.result = transcription.formattedString
self.endRecognitionTimer?.invalidate()
self.endRecognitionTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { timer in
self.finishRecording()
observer(.success(self.result))
}
}
}
self.endRecognitionTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { timer in
self.finishRecording()
observer(.success(self.result))
}
self.engine.prepare()
try self.engine.start()
} catch {
observer(.failure(error))
}
return Disposables.create {
self.cancelRecording()
}
}
}
func cancelRecording() {
self.finishRecording()
self.endRecognitionTimer?.invalidate()
self.endRecognitionTimer = nil
}
func finishRecording() {
guard self.engine.isRunning else { return }
self.engine.stop()
self.engine.inputNode.removeTap(onBus: 0)
self.request.endAudio()
self.recognitionTask?.cancel()
try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
try? AVAudioSession.sharedInstance().setCategory(.soloAmbient)
if let fileRef = self.fileRef {
ExtAudioFileDispose(fileRef)
}
}
func stopRecording() {
self.finishRecording()
self.endRecognitionTimer?.fire()
self.endRecognitionTimer = nil
}
}