最近做了一个项目,涉及到语音识别,使用的是iOS的speech Framework框架,在网上搜了很多资料,也看了很多博客,但介绍的不是很详细,正好项目做完,在这里给大家详解一下speech Framework的运用,使用的语言是Swift,文章结尾会给OC语言的网址,可以参照。

      首先要做的准备,将开发的app版本设置为iOS 10,这是苹果在iOS 10 发布出来的时候新增的内容,低于这版本用不了,同时运行的设备系统也得保持在iOS 10 及以上。

      废话不多说,先上代码。

      语音识别需要用户给予权限,在info.plist文件中增加两个key:

  • NSMicrophoneUsageDescription - 这个 key 用于指定录音设备授权信息。注意,只有在用户点击麦克风按钮时,这条信息才会显示。
  • NSSpeechRecognitionUsageDescription -這個 key 用于指定语音识别授权信息。      

    这里就不做UI了,将机制写出来,大家可以根据自己的需要完善 。

import UIKit
import Speech

// 引用的框架是Speech,需要遵循的协议有两个
class ViewController: UIViewController ,SFSpeechRecognizerDelegate, SFSpeechRecognitionTaskDelegate {
    // 语音识别对象,这里直接给出识别语言(中文)
    private let speechRecognizer = SFSpeechRecognizer(locale: Locale.init(identifier: "zh_CN"))!
    // 识别请求
    private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?
    // 识别任务
    private var recognitionTask: SFSpeechRecognitionTask?
    // 设备音频
    private let audioSession = AVAudioSession.sharedInstance()
    // 声音输入引擎
    private let audioEngine = AVAudioEngine()
    // 麦克风按钮是否可点,取决于用户权限
    private var micButtonEnabled = false
    // 语音识别结果
    private var recordResult:String = ""
    // 说话间隔时间
    private var timer:Timer!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        speechRecognizer.delegate = self
        
        // 语音识别权限请求
        SFSpeechRecognizer.requestAuthorization { (authStatus) in
            switch authStatus {
            case .authorized:
                // 通过授权
                self.micButtonEnabled = true
                break
            case .denied:
                // 拒绝授权
                self.micButtonEnabled = false
                break
            case .restricted:
                // 权限受限制
                self.micButtonEnabled = false
                break
            case .notDetermined:
                // 权限不明确
                self.micButtonEnabled = false
                break
            }
        }
    }
    
    // 这里就会出现一个问题,比如说用户同意了语音识别权限,麦克风按钮可点,返回桌面进入设置,找到本应用,关闭语音识别权限,然后再进入app,发现麦克风按钮还是可点的,这就尴尬啦,所以页面出现时还要判断当前权限
    override func viewWillAppear(_ animated: Bool) {
        // 获取当前语音识别权限
        AVAudioSession.sharedInstance().requestRecordPermission { (permiss:Bool) in
            self.micButtonEnabled = permiss
        }
    }
    
    // 开始语音识别(这是我们自己写的方法,在麦克风按钮事件中调用这个函数)
    func startRecording(){
        // 判断音声引擎是否在运行
        if !audioEngine.isRunning {
            recordResult = "" // 接收识别结果的String赋为空
            recording()
        }
    }
    
    // 语音识别终止
    func stopRecording(){
        if (recognitionRequest != nil) {
            recognitionRequest?.endAudio()
        }
    }
    
    // 语音识别详细内容
    func recording() {
        // 判断目前有无识别任务,取消之前所有任务
        if recognitionTask != nil {
            recognitionTask?.cancel()
            recognitionTask = nil
        }
        do {
            // 设置设备音频
            try audioSession.setCategory(AVAudioSessionCategoryRecord) // 将音频设置为录音
            try audioSession.setMode(AVAudioSessionModeMeasurement)
            try audioSession.setActive(true, with: .notifyOthersOnDeactivation)
        } catch {
            print("audioSession properties weren't set because of an error.")
        }
        // 初始化识别请求
        recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
        
        guard let inputNode = audioEngine.inputNode else {
            fatalError("Audio engine has no input node")
        }
        
        guard let recognitionRequest = recognitionRequest else {
            fatalError("Unable to create an SFSpeechAudioBufferRecognitionRequest object")
        }
        recognitionRequest.shouldReportPartialResults = true
        // 登机语音识别任务
        recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest, delegate: self)
        
        // 麦克风获取语音片断
        let recordingFormat = inputNode.outputFormat(forBus: 0)
        
        // 追加后续输入的语音
        inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer, when) in
            self.recognitionRequest?.append(buffer)
        }
        audioEngine.prepare()
        do {
            // 开始录音
            try audioEngine.start()
        } catch {
            print("audioEngine couldn't start because of an error.")
        }
    }
    
    /******* 以下都是代理方法 *******/
    
    // 判断当前是否连接网络
    func speechRecognizer(_ speechRecognizer: SFSpeechRecognizer, availabilityDidChange available: Bool) {
        // 这里有两点不足,1:只有关闭或打开网络的操作时这个函数才执行,如果我一直不改变网络状态,这个函数等于没用。假设我点击语音按钮时判断available是否为true,false时弹出alert提示没网,那么设备没联网的状态下打开app且不改变网络状态,点击语音按钮就不会提示,这就需要程序员自己判断了。2:这个函数判断不了当前连接的网络是否有效,比如连了一个无效的wifi,available还是为true
    }
    
    // 录音过程中获取到声音
    func speechRecognitionTask(_ task: SFSpeechRecognitionTask, didHypothesizeTranscription transcription: SFTranscription) {
        // 这个代理方法非常重要,录音过程中检测到声音就会执行,比如说了话之后让他自动结束语音,就可以在此加上计时器timer。
         if(timer != nil && timer.isValid){
            timer.invalidate()
            timer = nil
         }
         timer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true, block: { (Timer) in
            self.stopRecording()
         })
        //只要在说话,计时器就不会走,停止说话计时器开始走,停止2两秒不说话,则录音就会自动结束开始识别成文本,时间可以自己设置
    }
    
    // 开始识别语音
    func speechRecognitionTaskFinishedReadingAudio(_ task: SFSpeechRecognitionTask) {
        // 将声音转成文字,这个函数里面可以什么都不用写
    }
    
    // 录音结束之后的识别处理
    func speechRecognitionTask(_ task: SFSpeechRecognitionTask, didFinishRecognition recognitionResult: SFSpeechRecognitionResult) {
        print(recognitionResult) // 输出的是一个数组,里面是所有识别出来的结果
        recordResult = recognitionResult.bestTranscription.formattedString // 获取最优的结果,这里看情况,不一定是你需要的那个,也可以做一个tableView,让用户自己选结果
    }
    
    // 语音转文本结束
    func speechRecognitionTask(_ task: SFSpeechRecognitionTask, didFinishSuccessfully successfully: Bool) {
        // 语音识别结束后,在这里释放对象
        audioEngine.stop()
        audioEngine.inputNode?.removeTap(onBus: 0)
        self.recognitionRequest = nil
        self.recognitionTask = nil
        do {
            // 添加这个代码是因为涉及到文本转语音的需求。语音识别会让音频处于录音状态,这个时候要朗读文本的话根本没有声音,所以需要添加这个设置。
            try audioSession.setCategory(AVAudioSessionCategoryAmbient)
        }catch let error as NSError{
            print(error.code)
        }
        if(timer != nil){
            timer.invalidate()
            timer = nil
        }
        // 在这里,大家拿到了recordResult,就可以做想做的事啦
    }
    
}

这是比较完整的代理方法,网上还有另一种方法,我就直接复制粘贴过来了:

func startRecording() {
    if recognitionTask != nil {
        recognitionTask?.cancel()
        recognitionTask = nil
    }
     
    let audioSession = AVAudioSession.sharedInstance()
    do {
        try audioSession.setCategory(AVAudioSessionCategoryRecord)
        try audioSession.setMode(AVAudioSessionModeMeasurement)
        try audioSession.setActive(true, with: .notifyOthersOnDeactivation)
    } catch {
        print("audioSession properties weren't set because of an error.")
    }
     
    recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
     
    guard let inputNode = audioEngine.inputNode else {
        fatalError("Audio engine has no input node")
    }
     
    guard let recognitionRequest = recognitionRequest else {
        fatalError("Unable to create an SFSpeechAudioBufferRecognitionRequest object")
    }
     
    recognitionRequest.shouldReportPartialResults = true
     
    recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest, resultHandler: { (result, error) in
         
        var isFinal = false
         
        if result != nil {
            // 此处获取语音识别结果,处理获取到识别结果之后的事
            self.textView.text = result?.bestTranscription.formattedString
            isFinal = (result?.isFinal)!
        }
         
        if error != nil || isFinal {
            self.audioEngine.stop()
            inputNode.removeTap(onBus: 0)
             
            self.recognitionRequest = nil
            self.recognitionTask = nil
             
            self.microphoneButton.isEnabled = true
        }
    })
     
    let recordingFormat = inputNode.outputFormat(forBus: 0)
    inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer, when) in
        self.recognitionRequest?.append(buffer)
    }
     
    audioEngine.prepare()
     
    do {
        try audioEngine.start()
    } catch {
        print("audioEngine couldn't start because of an error.")
    }
     
    textView.text = "Say something, I'm listening!"
     
}

两种方法之间识别结果都是一样的,但第二种方法比较死板,无法设置自动结束语音,适应不了更多需求。

以上就是我对Speech Framework的个人理解,希望对广大同行有所帮助。