IOS音視頻(四十六)離線在線語(yǔ)音識(shí)別方案

@TOC

IOS音視頻(四十六)離線在線語(yǔ)音識(shí)別方案

最近做了一個(gè)語(yǔ)音識(shí)別相關(guān)的研究蝇闭,因?yàn)楣拘枰褂秒x線語(yǔ)音識(shí)別功能笼恰,為了兼顧性能和價(jià)格方面的問題其垄,最終選擇的方案是刷钢,在線時(shí)使用siri,離線使用百度語(yǔ)音識(shí)別方案人乓。

封裝了一個(gè)離線在線合成的SDK:語(yǔ)音識(shí)別SDK 這個(gè)Demo里面沒有上傳百度libBaiduSpeechSDK.a 文件勤篮,因?yàn)檫@個(gè)文件太大了超過了100M,無法上傳到Git,需要自己從官方SDK下載替換到Demo中色罚。

這里總結(jié)一下現(xiàn)有的幾種離線語(yǔ)音識(shí)別方案

  • 簡(jiǎn)易使用第三方SDK方案
方案 優(yōu)點(diǎn) 缺點(diǎn) 價(jià)格 成功率
科大訊飛 成功率高95% 價(jià)格貴碰缔,增加ipa包大小 可用買斷或按流量,一臺(tái)設(shè)備成本4元左右 成功率95%左右
百度AI 成功率比較高90% 保屯,價(jià)格便宜手负,離線識(shí)別免費(fèi) ,提供了自定義模型訓(xùn)練姑尺,訓(xùn)練后識(shí)別率提高較多 增加ipa包大小, 識(shí)別率不高竟终,離線只支持命令詞方式,支持的語(yǔ)音只有中文和英文切蟋,在線時(shí)會(huì)強(qiáng)制使用在線識(shí)別方式统捶,超過免費(fèi)流量后就要收費(fèi),如果欠費(fèi),什么都用不了喘鸟;離線引擎使用至少需要連一次外網(wǎng) 離線命令詞免費(fèi)匆绣,在線識(shí)別按流量次數(shù)計(jì)算,1600元套餐包(200萬(wàn)次中文識(shí)別什黑,5萬(wàn)次英文識(shí)別) 在線識(shí)別成功率95%左右崎淳,離線識(shí)別基本上達(dá)不到90%
siri 成功高,免費(fèi)愕把,原始自帶拣凹,蘋果系統(tǒng)自帶,不會(huì)增加包大小 有局限性恨豁,要求IOS10以上系統(tǒng)才能使用siri api, IOS 系統(tǒng)13以上支持離線語(yǔ)音識(shí)別嚣镜,但離線識(shí)別不支持中文識(shí)別,英文離線識(shí)別比百度的準(zhǔn)確率高 完全免費(fèi) 在線識(shí)別率跟科大訊飛差不多95%以上橘蜜,離線英文識(shí)別也有90%左右
  • 開源代碼方案
方案 優(yōu)點(diǎn) 缺點(diǎn) 說明 成功率
KALDI開源框架 KALDI是著名的開源自動(dòng)語(yǔ)音識(shí)別(ASR)工具菊匿,這套工具提供了搭建目前工業(yè)界最常用的ASR模型的訓(xùn)練工具,同時(shí)也提供了其他一些子任務(wù)例如說話人驗(yàn)證(speaker verification)和語(yǔ)種識(shí)別(language recognition)的pipeline计福。KALDI目前由Daniel Povey維護(hù)跌捆,他之前在劍橋做ASR相關(guān)的研究,后來去了JHU開發(fā)KALDI棒搜,目前在北京小米總部作為語(yǔ)音的負(fù)責(zé)人疹蛉。同時(shí)他也是另一個(gè)著名的ASR工具HTK的主要作者之一活箕。
CMU-Sphinx開源框架 功能包括按特定語(yǔ)法進(jìn)行識(shí)別力麸、喚醒詞識(shí)別、n-gram識(shí)別等等育韩,這款語(yǔ)音識(shí)別開源框架相比于Kaldi比較適合做開發(fā)克蚂,各種函數(shù)上的封裝淺顯易懂,解碼部分的代碼非常容易看懂筋讨,且除開PC平臺(tái)埃叭,作者也考慮到了嵌入式平臺(tái),Android開發(fā)也很方便悉罕,已有對(duì)應(yīng)的Demo赤屋,Wiki上有基于PocketSphinx的語(yǔ)音評(píng)測(cè)的例子,且實(shí)時(shí)性相比Kaldi好了很多壁袄。 相比于Kaldi类早,使用的是GMM-HMM框架,準(zhǔn)確率上可能會(huì)差一些嗜逻;其他雜項(xiàng)處理程序(如pitch提取等等)沒有Kaldi多涩僻。
HTK-Cambridage 是C語(yǔ)音編寫,支持win,linux,ios

方案一:Siri語(yǔ)音識(shí)別

Siri語(yǔ)音識(shí)別簡(jiǎn)介

Siri語(yǔ)音識(shí)別用到的Api主要是SFSpeechRecognizer聲音處理器,是IOS 10 才提供的api,所以只有IOS 10以上才能使用逆日,從IOS10 到 IOS13 直接蘋果只提供了在線識(shí)別方式嵌巷,IOS13之后提供了離線識(shí)別方式。不過離線識(shí)別方式不支持中文模式室抽,官方雖然說支持中文搪哪,但是實(shí)際測(cè)試發(fā)現(xiàn)中文離線識(shí)別根本無法識(shí)別。

Siri語(yǔ)音識(shí)別功能類介紹

  • 引入系統(tǒng)庫(kù)Speech
  • SFSpeechRecognizer聲音處理器坪圾,這個(gè)類是語(yǔ)音識(shí)別的操作類噩死,用于語(yǔ)音識(shí)別用戶權(quán)限的申請(qǐng),語(yǔ)言環(huán)境的設(shè)置神年,語(yǔ)音模式的設(shè)置以及向Apple服務(wù)發(fā)送語(yǔ)音識(shí)別的請(qǐng)求已维。
    例如下面代碼會(huì)根據(jù)傳入的語(yǔ)言簡(jiǎn)稱來返回一個(gè)聲音處理器,如果不支持已日,怎會(huì)返回nil垛耳。更多細(xì)節(jié)可以查看官方文檔。
SFSpeechRecognizer(locale: Locale(identifier: langugeSimple))

通過下面的方法來得到語(yǔ)音識(shí)別的結(jié)果:

open func recognitionTask(with request: SFSpeechRecognitionRequest, resultHandler: @escaping (SFSpeechRecognitionResult?, Error?) -> Void) -> SFSpeechRecognitionTask
  • AVAudioEngine專門用來處理聲音的數(shù)據(jù)
 lazy var audioEngine: AVAudioEngine = {
        let audioEngine = AVAudioEngine()
        audioEngine.inputNode.installTap(onBus: 0, bufferSize: 1024, format: audioEngine.inputNode.outputFormat(forBus: 0)) { (buffer, audioTime) in
            // 為語(yǔ)音識(shí)別請(qǐng)求對(duì)象添加一個(gè)AudioPCMBuffer飘千,來獲取聲音數(shù)據(jù)
            self.recognitionRequest.append(buffer)
        }
        return audioEngine
    }()
  • SFSpeechAudioBufferRecognitionRequest語(yǔ)音識(shí)別器堂鲜,通過音頻流來創(chuàng)建語(yǔ)音識(shí)別請(qǐng)求。:
 // 語(yǔ)音識(shí)別器
    lazy var recognitionRequest: SFSpeechAudioBufferRecognitionRequest = {
        let recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
        return recognitionRequest
    }()
  • SFSpeechRecognitionTask語(yǔ)言識(shí)別任務(wù)管理器护奈,啟用和關(guān)閉都要使用這個(gè)管理進(jìn)行缔莲。這個(gè)類是語(yǔ)音識(shí)別服務(wù)請(qǐng)求任務(wù)類,每一個(gè)語(yǔ)音識(shí)別請(qǐng)求都可以抽象為一個(gè)SFSpeechRecognitionTask實(shí)例霉旗,其中SFSpeechRecognitionTaskDelegate協(xié)議中約定了許多請(qǐng)求任務(wù)過程中的監(jiān)聽方法痴奏。
public enum SFSpeechRecognitionTaskState : Int {

    case starting // Speech processing (potentially including recording) has not yet begun

    case running // Speech processing (potentially including recording) is running

    case finishing // No more audio is being recorded, but more recognition results may arrive

    case canceling // No more recognition reuslts will arrive, but recording may not have stopped yet

    case completed // No more results will arrive, and recording is stopped.
}

此外還有一些重要的類:

SFSpeechRecognitionRequest:語(yǔ)音識(shí)別請(qǐng)求類,需要通過其子類來進(jìn)行實(shí)例化厌秒。
SFSpeechURLRecognitionRequest:通過音頻URL來創(chuàng)建語(yǔ)音識(shí)別請(qǐng)求读拆。
SFSpeechRecognitionResult:語(yǔ)音識(shí)別請(qǐng)求結(jié)果類。
SFTranscription:語(yǔ)音轉(zhuǎn)換后的信息類鸵闪。

具體詳情可以參考蘋果官方文檔檐晕,蘋果提供了一個(gè)Swift版本的Demo:點(diǎn)擊這里下載蘋果官方demo

Siri語(yǔ)音識(shí)別功能集成

  • OC 代碼集成:
//
//  KSiriRecognizer.m
//  KSpeechRecognition
//
//  Created by yulu kong on 2020/4/3.
//  Copyright ? 2020 yulu kong. All rights reserved.
//

#import "KSiriRecognizer.h"
#import <Speech/Speech.h>
#import "KHelper.h"
#import "KError.h"

@interface KSiriRecognizer () <SFSpeechRecognizerDelegate>

@property (nonatomic, strong) AVAudioEngine *audioEngine;
@property (nonatomic, strong) SFSpeechRecognizer *recognizer;

@property (nonatomic, assign) BOOL isAvaliable;

@property (nonatomic, strong, nullable) SFSpeechRecognitionTask *currentTask;
@property (nonatomic, strong, nullable) SFSpeechAudioBufferRecognitionRequest *request;

@end

@implementation KSiriRecognizer

+ (void)requestAuthorizationWithResultHandler:(KSiriAuthorizationResultHandler)resultHandler
{
    [SFSpeechRecognizer requestAuthorization:^(SFSpeechRecognizerAuthorizationStatus status) {
        resultHandler([KHelper convertSiriAuthorizationStatus:status]);
    }];
}

- (instancetype)initWithLanguage:(KLanguage)language
{
    if (self = [super initWithLanguage:language]) {
        NSLocale *local = [KHelper localForLanguage:language];
        _recognizer = [[SFSpeechRecognizer alloc] initWithLocale:local];
        _recognizer.delegate = self;
    }
    return self;
}

- (KAuthorizationStatus)authorizationStatus
{
    return [KHelper convertSiriAuthorizationStatus:[SFSpeechRecognizer authorizationStatus]];
}

- (void)startWithResultHandler:(KRecognitionResultHandler)resultHandler errorHandler: (KErrorHandler _Nullable)errorHandler
{
    if (_currentTask != nil) {
        NSLog(@"正在識(shí)別中,請(qǐng)稍候蚌讼。");
        return;
    }
    
    if (self.authorizationStatus != KAuthorizationStatusAuthorized) {
        errorHandler([KError notAuthorizationError]);
        return;
    }
    
    if (!_isAvaliable) {
        NSString *message = [NSString stringWithFormat:@"%@語(yǔ)音識(shí)別器不可用", [KHelper nameForLanguage:self.language]];
        errorHandler([KError errorWithCode:-1 message:message]);
        return;
    }
    
    AVAudioSession *audioSession = AVAudioSession.sharedInstance;
    NSError *error = nil;
    [audioSession setCategory:AVAudioSessionCategoryRecord mode:AVAudioSessionModeMeasurement options: AVAudioSessionCategoryOptionDuckOthers error:&error];
    if (error != nil) {
        errorHandler(error);
        return;
    }
    
    __block typeof(self) weakSelf = self;
    _request = [[SFSpeechAudioBufferRecognitionRequest alloc] init];
    _request.shouldReportPartialResults = YES;
    
//啟用離線識(shí)別的開關(guān)辟灰,這個(gè)屬性只有IOS13以上才支持
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0
     _request.requiresOnDeviceRecognition = self.forceOffline;
#else

#endif
    
    _currentTask = [self.recognizer recognitionTaskWithRequest:_request resultHandler:^(SFSpeechRecognitionResult *result, NSError *error) {
       
        if (error == nil) {
            [weakSelf stop];
            errorHandler(error);
            return;
        }
        
        if (result != nil && !result.isFinal) {
            resultHandler([[KRecognitionResult alloc] initWithText:result.bestTranscription.formattedString isFinal:NO]);
            return;
        }
        
        if (result.isFinal) {
            [weakSelf stop];
            resultHandler([[KRecognitionResult alloc] initWithText:result.bestTranscription.formattedString isFinal:YES]);
        }
    }];
    
    // Configure the microphone input.
    AVAudioFormat *recordingFormat = [_audioEngine.inputNode outputFormatForBus:0];
    [_audioEngine.inputNode installTapOnBus:0 bufferSize:1024 format:recordingFormat block:^(AVAudioPCMBuffer * buffer, AVAudioTime *when) {
        [weakSelf.request appendAudioPCMBuffer:buffer];
    }];
    
    [_audioEngine prepare];
    
    if (![_audioEngine startAndReturnError:&error]) {
        _currentTask = nil;
        errorHandler(error);
    }
}

- (void)stop {
    
    if (_currentTask == nil || !_isAvaliable) {
        return;
    }
    
    [_currentTask cancel];
    [_audioEngine stop];
    [_request endAudio];
    _currentTask = nil;
}



- (void)speechRecognizer:(SFSpeechRecognizer *)speechRecognizer availabilityDidChange:(BOOL)available
{
    _isAvaliable = available;
}

@end

  • Swift5 代碼集成:
//
//  JPSpeechRecognition.swift
//  JimuPro
//
//  Created by 孔雨露 on 2020/3/7.
//  Copyright ? 2020 UBTech. All rights reserved.
//

import Foundation
import UIKit
import Speech

enum JPSpeechType: Int {
    case start
    case stop
    case finished
    case authDenied
}

typealias JPSpeechBlock = (_ speechType: JPSpeechType, _ finalText: String?) -> Void

@available(iOS 10.0, *)

class JPSpeechRecognition: NSObject {

    //private var parentVc: UIViewController!
    private var speechTask: SFSpeechRecognitionTask?
    // 聲音處理器
    private var speechRecognizer: SFSpeechRecognizer?
    
    private var block: JPSpeechBlock?
    
    // 語(yǔ)音識(shí)別器
    var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?
    
 
    
    lazy var audioEngine: AVAudioEngine = {
        let audioEngine = AVAudioEngine()
        audioEngine.inputNode.installTap(onBus: 0, bufferSize: 1024, format: audioEngine.inputNode.outputFormat(forBus: 0)) { (buffer, audioTime) in
            // 為語(yǔ)音識(shí)別請(qǐng)求對(duì)象添加一個(gè)AudioPCMBuffer,來獲取聲音數(shù)據(jù)
            if let recognitionRequest = self.recognitionRequest {
                recognitionRequest.append(buffer)
            }
        }
        return audioEngine
    }()
    
    
    func startSpeech(languge: String, speechBlock: @escaping JPSpeechBlock) {
        //parentVc = speechVc
        block = speechBlock
        setAudioActive()
        checkmicroPhoneAuthorization { (microStatus) in
            if microStatus {
                self.checkRecognizerAuthorization(recongStatus: { (recStatus) in
                    if recStatus {
                        //  初始化語(yǔ)音處理器的輸入模式 語(yǔ)音處理器準(zhǔn)備就緒(會(huì)為一些audioEngine啟動(dòng)時(shí)所必須的資源開辟內(nèi)存)
                        self.audioEngine.prepare()
                        if (self.speechTask?.state == .running) {   // 如果當(dāng)前進(jìn)程狀態(tài)是進(jìn)行中
                            // 停止語(yǔ)音識(shí)別
                           self.stopDictating()
                        } else {   // 進(jìn)程狀態(tài)不在進(jìn)行中
                            self.speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: languge))
                            guard (self.speechRecognizer != nil) else {
                                self.showAlert("抱歉篡石,暫不支持當(dāng)前地區(qū)使用語(yǔ)音輸入")
                                return
                            }
                            self.setCallBack(type: .start, text: nil)
                            // 開啟語(yǔ)音識(shí)別
                            self.startDictating()
                        }
                    } else {
                        self.showAlert("您已取消授權(quán)使用語(yǔ)音識(shí)別芥喇,如果需要使用語(yǔ)音識(shí)別功能,可以到設(shè)置中重新開啟夏志!")
                        self.setCallBack(type: .authDenied, text: nil)
                    }
                })
            } else {
                //麥克風(fēng)沒有授權(quán)
                self.showAlert("您已取消授權(quán)使用麥克風(fēng)乃坤,如果需要使用語(yǔ)音識(shí)別功能苛让,可以到設(shè)置中重新開啟!")
                self.setCallBack(type: .authDenied, text: nil)
            }
        }
    }
}


@available(iOS 10.0, *)
extension JPSpeechRecognition: SFSpeechRecognitionTaskDelegate {
    
    //判斷語(yǔ)音識(shí)別權(quán)限
    private func checkRecognizerAuthorization(recongStatus: @escaping (_ resType: Bool) -> Void) {
        let authorStatus = SFSpeechRecognizer.authorizationStatus()
        if authorStatus == .authorized {
            recongStatus(true)
        } else if authorStatus == .notDetermined {
            SFSpeechRecognizer.requestAuthorization { (status) in
                if status == .authorized {
                    recongStatus(true)
                } else {
                    recongStatus(false )
                }
            }
        } else {
            recongStatus(false)
        }
    }
    
    //檢測(cè)麥克風(fēng)
    private func checkmicroPhoneAuthorization(authoStatus: @escaping (_ resultStatus: Bool) -> Void) {
        let microPhoneStatus = AVCaptureDevice.authorizationStatus(for: .audio)

        if microPhoneStatus == .authorized {
            authoStatus(true)
        } else if microPhoneStatus == .notDetermined {
            AVCaptureDevice.requestAccess(for: .audio, completionHandler: {(res) in
                if res {
                    authoStatus(true)
                } else {
                    authoStatus(false)
                }
            })
        } else {
            authoStatus(false)
        }
    }
    
    //開始進(jìn)行
    private func startDictating() {
        do {
            recognitionRequest = SFSpeechAudioBufferRecognitionRequest()   // recreates recognitionRequest object.
            guard let recognitionRequest = recognitionRequest else {
                fatalError("Unable to created a SFSpeechAudioBufferRecognitionRequest object")
            }
        //啟用離線識(shí)別的開關(guān)湿诊,這個(gè)屬性只有IOS13以上才支持
        // Keep speech recognition data on device
        if #available(iOS 13, *) {
            recognitionRequest.requiresOnDeviceRecognition = true
        }
            try audioEngine.start()
            speechTask = speechRecognizer!.recognitionTask(with: recognitionRequest) { (speechResult, error) in
                // 識(shí)別結(jié)果狱杰,識(shí)別后的操作
                if speechResult == nil {
                    return
                }
                self.setCallBack(type: .finished, text: speechResult!.bestTranscription.formattedString)
            }
        } catch  {
            print(error)
            self.setCallBack(type: .finished, text: nil)
        }
    }
    
    // 停止聲音處理器,停止語(yǔ)音識(shí)別請(qǐng)求進(jìn)程
    func stopDictating() {
        setCallBack(type: .stop, text: nil)
        
        audioEngine.stop()
        recognitionRequest?.endAudio()
        recognitionRequest = nil
        
        if audioEngine.inputNode.numberOfInputs > 0 {
            audioEngine.inputNode.removeTap(onBus: 0)
        }
        speechTask?.cancel()
    }
    
    private func setCallBack(type: JPSpeechType, text: String?) {
        if block != nil {
            block!(type, text)
        }
    }
    
    private func setAudioActive() {
        let audioSession = AVAudioSession.sharedInstance()
                       
       do {
           
           try audioSession.setCategory(AVAudioSession.Category.playAndRecord,mode: .default)
           try audioSession.setMode(AVAudioSession.Mode.spokenAudio)
           
           try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
           try audioSession.overrideOutputAudioPort(AVAudioSession.PortOverride.speaker)
           
       } catch  {
           debugPrint("Audio session initialization error: \(error.localizedDescription)")
       }
    }
    
    private func showAlert(_ message: String) {
//        let alertVC = UIAlertController(title: nil, message: message, preferredStyle: .alert)
//        let firstAction = UIAlertAction(title: "知道了", style: .default, handler: {(action) in
//        })
//        alertVC.addAction(firstAction)
//        parentVc.present(alertVC, animated: true, completion: nil)
        JMProgressHUD.showInfo(message)
    }
}

方案二:百度語(yǔ)音識(shí)別

百度語(yǔ)音識(shí)別簡(jiǎn)介

百度語(yǔ)音識(shí)別提供了很多功能厅须,這里我簡(jiǎn)單介紹一下語(yǔ)音識(shí)別這塊的仿畸。
百度語(yǔ)音識(shí)別有以下特點(diǎn):

  • 在線語(yǔ)音識(shí)別支持識(shí)別任意詞,離線語(yǔ)音識(shí)別僅支持命令詞識(shí)別(語(yǔ)法模式)
  • 首次使用離線朗和,SDK將會(huì)后臺(tái)下載離線授權(quán)文件错沽,成功后,授權(quán)文件有效期(三年)內(nèi)無需聯(lián)網(wǎng)眶拉。有效期即將結(jié)束后SDK將自動(dòng)多次嘗試聯(lián)網(wǎng)更新證書)千埃。
  • 沒有純離線識(shí)別。只能離線識(shí)別固定短語(yǔ)
  • 離線識(shí)別目前不支持任意語(yǔ)句忆植。您可以預(yù)先定義好放可,下載bsg文件 http://yuyin.baidu.com/asr
    bds_easr_gramm.dat 文件件的內(nèi)容替換成 自己定義的bsg 文件的內(nèi)容
    自定義短語(yǔ)越多效果越差,建議不超過100行

百度語(yǔ)音識(shí)別SDK集成步驟

一朝刊。首先在百度語(yǔ)音開放平臺(tái)注冊(cè),創(chuàng)建應(yīng)用耀里,生成API_KEY,SECRET_KEY和APP_ID
創(chuàng)建應(yīng)用時(shí)的包名填工程的 Bundle identifier拾氓。百度語(yǔ)音識(shí)別注冊(cè)地址點(diǎn)擊這里

百度語(yǔ)音識(shí)別控制臺(tái)

二冯挎,下載SDK,先打開官方demo運(yùn)行看看, 替換創(chuàng)建應(yīng)用生成的API_KEY,SECRET_KEY和APP_ID

百度語(yǔ)音識(shí)別IOS sdk

創(chuàng)建自己的appKey

創(chuàng)建自己的appKey

然后可以先測(cè)試一下下載的百度官方demo,運(yùn)行是否OK咙鞍。

配置離線識(shí)別引擎

三房官,然后在自己項(xiàng)目集成,開發(fā)奶陈。

注意集成的資源包導(dǎo)入:


注意集成的資源包導(dǎo)入

例如在上面提供的離線SDK中易阳,我只用一個(gè)簡(jiǎn)單的類對(duì)百度SDK包裝了一層附较,離線配置方法如下:


離線模式配置

百度SDK 集成步驟如下:

  1. 將官方SDK中的如下文件拖入到自己的項(xiàng)目中:


    拖入SDK中必要的文件
  2. 添加必須要的系統(tǒng)框架吃粒,動(dòng)態(tài)庫(kù):


    添加必須要的系統(tǒng)框架,動(dòng)態(tài)庫(kù)
  3. 封裝自己的類拒课,實(shí)現(xiàn)對(duì)百度API調(diào)用:

  • 導(dǎo)入語(yǔ)音識(shí)別需要的頭文件:
#import "BDSASRDefines.h"
#import "BDSASRParameters.h"
#import "BDSEventManager.h"
  • 定義跟AppID綁定的 APP_ID徐勃, API_KEY, SECRET_KEY 相關(guān)信息早像,這個(gè)信息就是你在百度平臺(tái)注冊(cè)得到的
const NSString* APP_ID = @"18569855";
const NSString* API_KEY = @"2qrMX1TgfTGslRMd3TcDuuBq";
const NSString* SECRET_KEY = @"xatUjET5NLNDXYNghNCnejt28MGpRYP2";
  • 初始化SDK僻肖,構(gòu)建一個(gè)BDSEventManager對(duì)象,設(shè)置需要使用短語(yǔ)音服務(wù):百度短語(yǔ)音productId = "1537"
- (instancetype)initWithLanguage:(KLanguage)language offlineGrammarDATFileURL:(NSURL * _Nullable)datFileURL {
    if (self = [super initWithLanguage:language]) {
        _offlineGrammarDATFileURL = [datFileURL copy];
        _asrEventManager = [BDSEventManager createEventManagerWithName:BDS_ASR_NAME];
        NSString *productId = [KHelper identifierForBaiduLanguage:language];
        [_asrEventManager setParameter:productId forKey:BDS_ASR_PRODUCT_ID];
    }
    return self;
}
  • 配置離線引擎和相關(guān)模型資源文件
- (void)configOfflineMode {
    [self.asrEventManager setDelegate:self];
    [self.asrEventManager setParameter:@(EVRDebugLogLevelError) forKey:BDS_ASR_DEBUG_LOG_LEVEL];
    
    
    // 參數(shù)配置:在線身份驗(yàn)證
    [self.asrEventManager setParameter:@[API_KEY, SECRET_KEY] forKey:BDS_ASR_API_SECRET_KEYS];
    
    NSBundle *bundle = [NSBundle bundleForClass:[KBaiduRecognizer class]];
    NSString *basicModelPath = [bundle pathForResource:@"bds_easr_basic_model" ofType:@"dat"];
    
    [self.asrEventManager setParameter:basicModelPath forKey:BDS_ASR_MODEL_VAD_DAT_FILE];
    [self.asrEventManager setParameter:@(YES) forKey:BDS_ASR_ENABLE_MODEL_VAD];
    
    [self.asrEventManager setParameter:APP_ID forKey:BDS_ASR_OFFLINE_APP_CODE];
    [self.asrEventManager setParameter:@(EVR_STRATEGY_BOTH) forKey:BDS_ASR_STRATEGY];
    [self.asrEventManager setParameter:@(EVR_OFFLINE_ENGINE_GRAMMER) forKey:BDS_ASR_OFFLINE_ENGINE_TYPE];
    [self.asrEventManager setParameter:basicModelPath forKey:BDS_ASR_OFFLINE_ENGINE_DAT_FILE_PATH];
    
    // 離線僅可識(shí)別自定義語(yǔ)法規(guī)則下的詞
    NSString *grammarFilePath = [[NSBundle mainBundle] pathForResource:@"baidu_speech_grammar" ofType:@"bsg"];
    if (_offlineGrammarDATFileURL != nil) {
        if (![[NSFileManager defaultManager] fileExistsAtPath:_offlineGrammarDATFileURL.path]) {
            NSLog(@"!!! Error: 你提供的離線語(yǔ)法詞庫(kù)不存在: %@", _offlineGrammarDATFileURL.path);
        } else {
            grammarFilePath = _offlineGrammarDATFileURL.path;
        }
    }
    
    [self.asrEventManager setParameter:grammarFilePath forKey:BDS_ASR_OFFLINE_ENGINE_GRAMMER_FILE_PATH];
}

除了上面離線識(shí)別需要的簡(jiǎn)單設(shè)備外卢鹦,還可以設(shè)置如下信息:

  1. 識(shí)別語(yǔ)言 @0 : @"普通話", @1 : @"粵語(yǔ)", @2 : @"英文", @3 : @"四川話"
//識(shí)別語(yǔ)言 @0 : @"普通話", @1 : @"粵語(yǔ)", @2 : @"英文", @3 : @"四川話"
    [self.asrEventManager setParameter:@(EVoiceRecognitionLanguageChinese) forKey:BDS_ASR_LANGUAGE];
  1. 采樣率 @"自適應(yīng)", @"8K", @"16K"
 //采樣率 @"自適應(yīng)", @"8K", @"16K"
    [self.asrEventManager setParameter:@(EVoiceRecognitionRecordSampleRateAuto) forKey:BDS_ASR_SAMPLE_RATE];
  1. 是否啟用長(zhǎng)語(yǔ)音識(shí)別
 //是否啟用長(zhǎng)語(yǔ)音識(shí)別
    [self.asrEventManager setParameter:@(YES) forKey:BDS_ASR_ENABLE_LONG_SPEECH];
      //開啟提示音 @0 : @"關(guān)閉", @(EVRPlayToneAll) : @"開啟"}
    //使用長(zhǎng)語(yǔ)音必須關(guān)閉提示音
    [self.asrEventManager setParameter:@(0) forKey:BDS_ASR_PLAY_TONE];
  //開啟端點(diǎn)檢測(cè) {@NO : @"關(guān)閉", @YES : @"開啟"}  使用長(zhǎng)語(yǔ)音必須開啟本地VAD
    //端點(diǎn)檢測(cè)臀脏,即自動(dòng)檢測(cè)音頻輸入的起始點(diǎn)和結(jié)束點(diǎn)。SDK默認(rèn)開啟VAD,檢測(cè)到靜音后自動(dòng)停止識(shí)別揉稚。
    //如果需要自行控制識(shí)別結(jié)束需關(guān)閉VAD秒啦,請(qǐng)同時(shí)關(guān)閉服務(wù)端VAD與端上VAD
    //[self.asrEventManager setParameter:@(YES) forKey:BDS_ASR_ENABLE_LOCAL_VAD];
   // 關(guān)閉服務(wù)端VAD
    [self.asrEventManager setParameter:@(NO) forKey:BDS_ASR_ENABLE_EARLY_RETURN];
    // 關(guān)閉本地VAD
    [self.asrEventManager setParameter:@(NO) forKey:BDS_ASR_ENABLE_LOCAL_VAD];    
    //打開的話配置端點(diǎn)檢測(cè)(二選一)
  • 配置 ModelVAD端點(diǎn)檢測(cè)方式 檢測(cè)更加精準(zhǔn),抗噪能力強(qiáng)搀玖,響應(yīng)速度較慢
- (void)configModelVAD {
    NSString *modelVAD_filepath = [[NSBundle mainBundle] pathForResource:@"bds_easr_basic_model" ofType:@"dat"];
    //ModelVAD所需資源文件路徑
    [self.asrEventManager setParameter:modelVAD_filepath forKey:BDS_ASR_MODEL_VAD_DAT_FILE];
}
  • DNNMFE端點(diǎn)檢測(cè)方式 提供基礎(chǔ)檢測(cè)功能余境,性能高,響應(yīng)速度快
//DNNMFE端點(diǎn)檢測(cè)方式 提供基礎(chǔ)檢測(cè)功能灌诅,性能高芳来,響應(yīng)速度快
- (void)configDNNMFE {
    //設(shè)置MFE模型文件
    NSString *mfe_dnn_filepath = [[NSBundle mainBundle] pathForResource:@"bds_easr_mfe_dnn" ofType:@"dat"];
    [self.asrEventManager setParameter:mfe_dnn_filepath forKey:BDS_ASR_MFE_DNN_DAT_FILE];
    //設(shè)置MFE CMVN文件路徑
    NSString *cmvn_dnn_filepath = [[NSBundle mainBundle] pathForResource:@"bds_easr_mfe_cmvn" ofType:@"dat"];
    [self.asrEventManager setParameter:cmvn_dnn_filepath forKey:BDS_ASR_MFE_CMVN_DAT_FILE]    
    //是否使用ModelVAD,打開需配置資源文件參數(shù)
    [self.asrEventManager setParameter:@(NO) forKey:BDS_ASR_ENABLE_MODEL_VAD];
    // MFE支持自定義靜音時(shí)長(zhǎng)
    //    [self.asrEventManager setParameter:@(500.f) forKey:BDS_ASR_MFE_MAX_SPEECH_PAUSE];
    //    [self.asrEventManager setParameter:@(500.f) forKey:BDS_ASR_MFE_MAX_WAIT_DURATION];
}
  • 離在線并行配置
     // 參數(shù)設(shè)置:識(shí)別策略為離在線并行
        [self.asrEventManager setParameter:@(EVR_STRATEGY_BOTH) forKey:BDS_ASR_STRATEGY];
        // 參數(shù)設(shè)置:離線識(shí)別引擎類型 EVR_OFFLINE_ENGINE_INPUT 輸入法模式  EVR_OFFLINE_ENGINE_GRAMMER 離線引    擎語(yǔ)法模式
        //離線語(yǔ)音識(shí)別僅支持命令詞識(shí)別(語(yǔ)法模式)猜拾。
        //[self.asrEventManager setParameter:@(EVR_OFFLINE_ENGINE_INPUT) forKey:BDS_ASR_OFFLINE_ENGINE_TYPE];
        [self.asrEventManager setParameter:@(EVR_OFFLINE_ENGINE_GRAMMER) forKey:BDS_ASR_OFFLINE_ENGINE_TYPE];
        //并生成bsg文件即舌。下載語(yǔ)法文件后,設(shè)置BDS_ASR_OFFLINE_ENGINE_GRAMMER_FILE_PATH參數(shù)
        NSString* gramm_filepath = [[NSBundle mainBundle] pathForResource:@"bds_easr_gramm" ofType:@"dat"];
        // 請(qǐng)?jiān)?(官網(wǎng))[http://speech.baidu.com/asr] 參考模板定義語(yǔ)法挎袜,下載語(yǔ)法文件后侥涵,替換BDS_ASR_OFFLINE_ENGINE_GRAMMER_FILE_PATH參數(shù)
        [self.asrEventManager setParameter:gramm_filepath forKey:BDS_ASR_OFFLINE_ENGINE_GRAMMER_FILE_PATH];
        //離線識(shí)別資源文件路徑
        NSString* lm_filepath = [[NSBundle mainBundle] pathForResource:@"bds_easr_basic_model" ofType:@"dat"];
        [self.asrEventManager setParameter:lm_filepath forKey:BDS_ASR_OFFLINE_ENGINE_DAT_FILE_PATH];
         //加載離線引擎
        [self.asrEventManager sendCommand:BDS_ASR_CMD_LOAD_ENGINE];   
  • 監(jiān)聽回調(diào)代理
#pragma mark -- 語(yǔ)音識(shí)別狀態(tài)、錄音數(shù)據(jù)等回調(diào)均在此代理中發(fā)生
- (void)VoiceRecognitionClientWorkStatus:(int)workStatus obj:(id)aObj{
    switch (workStatus) {
        case EVoiceRecognitionClientWorkStatusNewRecordData: {
            [self.fileHandler writeData:(NSData *)aObj];
            NSLog(@"錄音數(shù)據(jù)回調(diào)");
            break;
        }
            
        case EVoiceRecognitionClientWorkStatusStartWorkIng: {
            NSLog(@"識(shí)別工作開始開始采集及處理數(shù)據(jù)");
            NSDictionary *logDic = [self parseLogToDic:aObj];
            [self printLogTextView:[NSString stringWithFormat:@"開始識(shí)別-log: %@\n", logDic]];
            break;
        }
        case EVoiceRecognitionClientWorkStatusStart: {
            NSLog(@"檢測(cè)到用戶開始說話");
            [self printLogTextView:@"檢測(cè)到用戶開始說話.\n"];
            break;
        }
        case EVoiceRecognitionClientWorkStatusEnd: {
            NSLog(@"用戶說話完成,但服務(wù)器尚未返回結(jié)果");
            [self printLogTextView:@"用戶說話完成,但服務(wù)器尚未返回結(jié)果.\n"];
            self.contentTextView.text = @"無識(shí)別結(jié)果";
            break;
        }
        case EVoiceRecognitionClientWorkStatusFlushData: {
            // 逐句顯示宋雏。配合連續(xù)上屏的中間結(jié)果,可以進(jìn)一步 升語(yǔ)音輸入的體驗(yàn)
            //// 該狀態(tài)值表示服務(wù)器返回了中間結(jié)果,如果想要將中間結(jié)果展示給用戶(形成連續(xù)上屏的效果),
            // 可以利用與該狀態(tài)同時(shí)返回的數(shù)據(jù),每當(dāng)接到新的該類消息應(yīng)當(dāng)清空顯示區(qū)域的文字以免重復(fù)
            NSLog(@"逐句顯示");
            [self printLogTextView:[NSString stringWithFormat:@"服務(wù)器返回了中間結(jié) - %@.\n\n", [self getDescriptionForDic:aObj]]];
            
            self.contentTextView.text = @"";
            NSArray *contentArr = aObj[@"results_recognition"];
            NSString *contentStr = contentArr[0];
            self.contentTextView.text = contentStr;
            
            break;
        }
        case EVoiceRecognitionClientWorkStatusFinish: {
            //// 該狀態(tài)值表示語(yǔ)音識(shí)別服務(wù)器返回了最終結(jié)果,結(jié)果以數(shù)組的形式保存在 aObj 對(duì)象中
            // 接受到該消息時(shí)應(yīng)當(dāng)清空顯示區(qū)域的文字以免重復(fù)
            NSLog(@"返回了最終結(jié)果");
            /*
             "origin_result" =     {
             "corpus_no" = 6643061564690340286;
             "err_no" = 0;
             result =         {
             word =             (
             "\U597d\U7684"
             );
             };
             sn = "5EEAC770-DDD2-4D35-8ABF-F407276A7934";
             "voice_energy" = "29160.45703125";
             };
             "results_recognition" =     (
             "\U597d\U7684"
             );
             
             */
            
            [self printLogTextView:[NSString stringWithFormat:@"最終結(jié)果 - %@.\n", [self getDescriptionForDic:aObj]]];
            if (aObj) {
                
                //                NSArray *contentArr = aObj[@"results_recognition"];
                //                NSString *contentStr = contentArr[0];
                //                NSLog(@"contentStr = %@",contentStr);
                self.contentTextView.text =  [self getDescriptionForDic:aObj];
                
            }
            
            break;
        }
        case EVoiceRecognitionClientWorkStatusMeterLevel: {
            NSLog(@"當(dāng)前音量回調(diào)");
            break;
        }
        case EVoiceRecognitionClientWorkStatusCancel: {
            NSLog(@"用戶主動(dòng)取消");
            [self printLogTextView:@"用戶主動(dòng)取消.\n"];
            
            break;
        }
        case EVoiceRecognitionClientWorkStatusError: {
            // 錯(cuò)誤狀態(tài) 沒有語(yǔ)音輸入
            NSLog(@"錯(cuò)誤狀態(tài)");
            NSError * error = (NSError *)aObj;
            
            if (error.code == 2228236) {
                ////離線引擎錯(cuò)誤狀態(tài):
                //識(shí)別失敗芜飘,無法識(shí)別。(語(yǔ)法模式下磨总,可能為語(yǔ)音不在自定義的語(yǔ)法規(guī)則之下)
                 [self printLogTextView:[NSString stringWithFormat:@"錯(cuò)誤狀態(tài) -語(yǔ)法模式下,可能為語(yǔ)音不在自定義的語(yǔ)法規(guī)則之下\n %@.\n", (NSError *)aObj]];
            }else if (error.code == 2228230){
                 [self printLogTextView:[NSString stringWithFormat:@"錯(cuò)誤狀態(tài) -dat模型文件不可用,請(qǐng)?jiān)O(shè)置 BDS_ASR_OFFLINE_ENGINE_DAT_FILE_PATH\n %@.\n", (NSError *)aObj]];
            }else if (error.code == 2228231){
                 [self printLogTextView:[NSString stringWithFormat:@"錯(cuò)誤狀態(tài) -grammar文件無效,請(qǐng)?jiān)O(shè)置 BDS_ASR_OFFLINE_ENGINE_GRAMMER_FILE_PATH\n %@.\n", (NSError *)aObj]];
            }else if (error.code == 2225219){
                [self printLogTextView:[NSString stringWithFormat:@"錯(cuò)誤狀態(tài) -音頻質(zhì)量過低嗦明,無法識(shí)別\n %@.\n", (NSError *)aObj]];
            }else{
                [self printLogTextView:[NSString stringWithFormat:@"錯(cuò)誤狀態(tài) - %@.\n", (NSError *)aObj]];
            }
           
            break;
        }
        case EVoiceRecognitionClientWorkStatusLoaded: {
            NSLog(@"離線引擎加載完成");
            [self printLogTextView:@"離線引擎加載完成.\n"];
            break;
        }
        case EVoiceRecognitionClientWorkStatusUnLoaded: {
            NSLog(@"離線引擎卸載完成");
            [self printLogTextView:@"離線引擎卸載完成.\n"];
            break;
        }
        case EVoiceRecognitionClientWorkStatusChunkThirdData: {
            NSLog(@"識(shí)別結(jié)果中的第三方數(shù)據(jù)");
            [self printLogTextView:[NSString stringWithFormat:@"識(shí)別結(jié)果中的第三方數(shù)據(jù): %lu\n", (unsigned long)[(NSData *)aObj length]]];
            break;
        }
        case EVoiceRecognitionClientWorkStatusChunkNlu: {
            NSLog(@"別結(jié)果中的語(yǔ)義結(jié)果");
            NSString *nlu = [[NSString alloc] initWithData:(NSData *)aObj encoding:NSUTF8StringEncoding];
            [self printLogTextView:[NSString stringWithFormat:@"識(shí)別結(jié)果中的語(yǔ)義結(jié)果: %@\n", nlu]];
            NSLog(@"%@", nlu);
            break;
        }
        case EVoiceRecognitionClientWorkStatusChunkEnd: {
            NSLog(@"識(shí)別過程結(jié)束");
            [self printLogTextView:[NSString stringWithFormat:@"識(shí)別過程結(jié)束, sn: %@.\n", aObj]];
            
            break;
        }
        case EVoiceRecognitionClientWorkStatusFeedback: {
            NSLog(@"識(shí)別過程反饋的打點(diǎn)數(shù)據(jù)");
            NSDictionary *logDic = [self parseLogToDic:aObj];
            [self printLogTextView:[NSString stringWithFormat:@"識(shí)別過程反饋的打點(diǎn)數(shù)據(jù): %@\n", logDic]];
            break;
        }
        case EVoiceRecognitionClientWorkStatusRecorderEnd: {
            //錄音機(jī)關(guān)閉,頁(yè)面跳轉(zhuǎn)需檢測(cè)此時(shí)間蚪燕,規(guī)避狀態(tài)條 (iOS)
            NSLog(@"錄音機(jī)關(guān)閉");
            [self printLogTextView:@"錄音機(jī)關(guān)閉.\n"];
            break;
        }
        case EVoiceRecognitionClientWorkStatusLongSpeechEnd: {
            NSLog(@"長(zhǎng)語(yǔ)音結(jié)束狀態(tài)");
            [self printLogTextView:@"長(zhǎng)語(yǔ)音結(jié)束狀態(tài).\n"];
            
            break;
        }
        default:
            break;
    }
    
}
  • 提供開始娶牌,停止識(shí)別方法

//開始識(shí)別
- (void) startWithResultHandler:(KRecognitionResultHandler)resultHandler errorHandler:(KErrorHandler)errorHandler {
    self.resultHandler = resultHandler;
    self.errorHandler = errorHandler;
    [self.asrEventManager sendCommand:BDS_ASR_CMD_START];
}

//停止識(shí)別
- (void)stop {
    [self.asrEventManager sendCommand:BDS_ASR_CMD_STOP];
}

  • 完整封裝代碼如下:
//
//  KBaiduRecognizer.m
//  KSpeechRecognition
//
//  Created by yulu kong on 2020/4/3.
//  Copyright ? 2020 yulu kong. All rights reserved.
//

#import "KBaiduRecognizer.h"
#import "BDSASRDefines.h"
#import "BDSASRParameters.h"
#import "BDSEventManager.h"
#import "KHelper.h"
#import "KError.h"

const NSString* APP_ID = @"18569855";
const NSString* API_KEY = @"2qrMX1TgfTGslRMd3TcDuuBq";
const NSString* SECRET_KEY = @"xatUjET5NLNDXYNghNCnejt28MGpRYP2";

@interface KBaiduRecognizer () <BDSClientASRDelegate>
@property (strong, nonatomic) BDSEventManager *asrEventManager;
@property (nonatomic, strong) NSURL *offlineGrammarDATFileURL;

@end

@implementation KBaiduRecognizer


- (KAuthorizationStatus)authorizationStatus {
    return KAuthorizationStatusAuthorized;
}

- (instancetype)initWithLanguage:(KLanguage)language {
    return [self initWithLanguage:language offlineGrammarDATFileURL:nil];
}

- (instancetype)initWithLanguage:(KLanguage)language offlineGrammarDATFileURL:(NSURL * _Nullable)datFileURL {
    if (self = [super initWithLanguage:language]) {
        _offlineGrammarDATFileURL = [datFileURL copy];
        // 創(chuàng)建語(yǔ)音識(shí)別對(duì)象
        _asrEventManager = [BDSEventManager createEventManagerWithName:BDS_ASR_NAME];
        NSString *productId = [KHelper identifierForBaiduLanguage:language];
        [_asrEventManager setParameter:productId forKey:BDS_ASR_PRODUCT_ID];
    }
    return self;
}

- (void) startWithResultHandler:(KRecognitionResultHandler)resultHandler errorHandler:(KErrorHandler)errorHandler {
    self.resultHandler = resultHandler;
    self.errorHandler = errorHandler;
    [self.asrEventManager sendCommand:BDS_ASR_CMD_START];
}

- (void)stop {
    [self.asrEventManager sendCommand:BDS_ASR_CMD_STOP];
}

- (void)configOfflineMode {
    // 設(shè)置語(yǔ)音識(shí)別代理
    [self.asrEventManager setDelegate:self];
    [self.asrEventManager setParameter:@(EVRDebugLogLevelError) forKey:BDS_ASR_DEBUG_LOG_LEVEL];
    
    
    // 參數(shù)配置:在線身份驗(yàn)證
    [self.asrEventManager setParameter:@[API_KEY, SECRET_KEY] forKey:BDS_ASR_API_SECRET_KEYS];
    
    NSBundle *bundle = [NSBundle bundleForClass:[KBaiduRecognizer class]];
    NSString *basicModelPath = [bundle pathForResource:@"bds_easr_basic_model" ofType:@"dat"];
    
    [self.asrEventManager setParameter:basicModelPath forKey:BDS_ASR_MODEL_VAD_DAT_FILE];
    [self.asrEventManager setParameter:@(YES) forKey:BDS_ASR_ENABLE_MODEL_VAD];
    
    //離線引擎身份驗(yàn)證 設(shè)置 APPID 離線授權(quán)所需APPCODE(APPID),如使用該方式進(jìn)行正式授權(quán)馆纳,請(qǐng)移除臨時(shí)授權(quán)文件
    [self.asrEventManager setParameter:APP_ID forKey:BDS_ASR_OFFLINE_APP_CODE];
    
    //識(shí)別策略 @0 : @"在線識(shí)別", @4 : @"離在線并行"
    [self.asrEventManager setParameter:@(EVR_STRATEGY_BOTH) forKey:BDS_ASR_STRATEGY];
    [self.asrEventManager setParameter:@(EVR_OFFLINE_ENGINE_GRAMMER) forKey:BDS_ASR_OFFLINE_ENGINE_TYPE];
    [self.asrEventManager setParameter:basicModelPath forKey:BDS_ASR_OFFLINE_ENGINE_DAT_FILE_PATH];
    
    // 離線僅可識(shí)別自定義語(yǔ)法規(guī)則下的詞
    NSString *grammarFilePath = [[NSBundle mainBundle] pathForResource:@"baidu_speech_grammar" ofType:@"bsg"];
    if (_offlineGrammarDATFileURL != nil) {
        if (![[NSFileManager defaultManager] fileExistsAtPath:_offlineGrammarDATFileURL.path]) {
            NSLog(@"!!! Error: 你提供的離線語(yǔ)法詞庫(kù)不存在: %@", _offlineGrammarDATFileURL.path);
        } else {
            grammarFilePath = _offlineGrammarDATFileURL.path;
        }
    }
    
    [self.asrEventManager setParameter:grammarFilePath forKey:BDS_ASR_OFFLINE_ENGINE_GRAMMER_FILE_PATH];
}


// MARK: - BDSClientASRDelegate

- (void)VoiceRecognitionClientWorkStatus:(int)workStatus obj:(id)aObj {
    switch (workStatus) {
        case EVoiceRecognitionClientWorkStatusStartWorkIng: {
            break;
        }
        case EVoiceRecognitionClientWorkStatusStart:
            break;
            
        case EVoiceRecognitionClientWorkStatusEnd: {
            break;
        }
        case EVoiceRecognitionClientWorkStatusFlushData: {
            [self receiveRecognitionResult:aObj isFinal:NO];
            break;
        }
        case EVoiceRecognitionClientWorkStatusFinish: {
            [self receiveRecognitionResult:aObj isFinal:YES];
            break;
        }
        case EVoiceRecognitionClientWorkStatusError: {
            self.errorHandler([KError errorWithCode:-1 message:@"語(yǔ)音識(shí)別失敗"]);
            break;
        }
        case EVoiceRecognitionClientWorkStatusRecorderEnd: {
            break;
        }
        default:
            break;
    }
}

- (void)receiveRecognitionResult:(id)resultInfo isFinal:(BOOL)isFinal {
    if (resultInfo == nil || ![resultInfo isKindOfClass:[NSDictionary class]]) {
        return;
    }
    
    NSDictionary *info = (NSDictionary *)resultInfo;
    NSString *text = info[@"results_recognition"];
    if (text != nil && [text length] > 0) {
        self.resultHandler([[KRecognitionResult alloc] initWithText:text isFinal:isFinal]);
    }
}

@end


方案三:使用開源框架

1. KALDI

kaldi源碼下載:kaldi源碼下載
安裝git后運(yùn)行

git clone https://github.com/kaldi-asr/kaldi.git kaldi-trunk --origin golden

速度過慢可以參考:github下載提速

通過http://git.oschina.net/離線下載的方式

git clone https://gitee.com/cocoon_zz/kaldi.git kaldi-trunk --origin golden

KALDI簡(jiǎn)介

KALDI 簡(jiǎn)介

KALDI是著名的開源自動(dòng)語(yǔ)音識(shí)別(ASR)工具,這套工具提供了搭建目前工業(yè)界最常用的ASR模型的訓(xùn)練工具脆霎,同時(shí)也提供了其他一些子任務(wù)例如說話人驗(yàn)證(speaker verification)和語(yǔ)種識(shí)別(language recognition)的pipeline总处。KALDI目前由Daniel Povey維護(hù),他之前在劍橋做ASR相關(guān)的研究睛蛛,后來去了JHU開發(fā)KALDI鹦马,目前在北京小米總部作為語(yǔ)音的負(fù)責(zé)人胧谈。同時(shí)他也是另一個(gè)著名的ASR工具HTK的主要作者之一。

KALDI之所以在ASR領(lǐng)域如此流行荸频,是因?yàn)樵摴ぞ咛峁┝似渌鸄SR工具不具備的可以在工業(yè)中使用的神經(jīng)網(wǎng)絡(luò)模型(DNN第岖,TDNN,LSTM)试溯。但是與其他基于Python接口的通用神經(jīng)網(wǎng)絡(luò)庫(kù)(TensorFlow蔑滓,PyTorch等)相比,KALDI提供的接口是一系列的命令行工具遇绞,這就需要學(xué)習(xí)者和使用者需要比較強(qiáng)的shell腳本能力键袱。同時(shí),KALDI為了簡(jiǎn)化搭建語(yǔ)音識(shí)別pipeline的過程摹闽,提供了大量的通用處理腳本蹄咖,這些腳本主要是用shell,perl以及python寫的付鹿,這里主要需要讀懂shell和python腳本澜汤,perl腳本主要是一些文本處理工作,并且可以被python取代舵匾,因此學(xué)習(xí)的性價(jià)比并不高俊抵。整個(gè)KALDI工具箱的結(jié)構(gòu)如下圖所示。
KALDI工具箱的結(jié)構(gòu)

可以看到KALDI的矩陣計(jì)算功能以及構(gòu)圖解碼功能分別是基于BLAS/LAPACK/MKL和OpenFST這兩個(gè)開源工具庫(kù)的坐梯,在這些庫(kù)的基礎(chǔ)上徽诲,KALDI實(shí)現(xiàn)了Matrix,Utils吵血,F(xiàn)eat谎替,GMM,SGMM蹋辅,Transforms钱贯,LM,Tree侦另,F(xiàn)ST ext秩命,HMM,Decoder等工具淋肾,通過編譯之后可以生成命令行工具硫麻,也就是圖中的C++ Executables文件。最后KALDI提供的樣例以及通用腳本展示了如何使用這些工具來搭建ASR Pipeline樊卓,是很好的學(xué)習(xí)材料。

除了KALDI本身提供的腳本杠河,還有其官方網(wǎng)站的Documents也是很好的學(xué)習(xí)資料碌尔。當(dāng)然浇辜,除了KALDI工具本身,ASR的原理也需要去學(xué)習(xí)唾戚,只有雙管齊下柳洋,才能更好的掌握這個(gè)難度較高的領(lǐng)域。

KALDI 源碼編譯 安裝

如何安裝參考下載好的目錄內(nèi)INSTALL文件

This is the official Kaldi INSTALL. Look also at INSTALL.md for the git mirror installation.
[for native Windows install, see windows/INSTALL]

(1)
go to tools/  and follow INSTALL instructions there.

(2)
go to src/ and follow INSTALL instructions there.

出現(xiàn)問題首先查看各個(gè)目錄下面的INSTALL文件叹坦,有些配置的問題(例如gcc的版本以及CUDA等)都可以查看該文檔進(jìn)行解決熊镣。

依賴文件編譯

首先檢查依賴項(xiàng)

cd extras
sudo bash check_dependencies.sh

注意make -j4可以多進(jìn)程進(jìn)行

cd kaldi-trunk/tools
make

配置Kaldi源碼

cd ../src
#如果沒有GPU請(qǐng)查閱configure,設(shè)置不使用CUDA
./configure --shared

編譯Kaldi源碼

make all
#注意在這里有可能關(guān)于CUDA的編譯不成功募书,原因是configure腳本沒有正確的找出CUDA的位置绪囱,需要手動(dòng)編輯configure查找的路徑,修改CUDA庫(kù)的位置

測(cè)試安裝是否成功

cd ../egs/yesno/s5
./run.sh
安裝成功效果圖

解碼的結(jié)果放置在exp目錄(export)下莹捡,例如我們查看一下
~/kaldi-bak/egs/yesno/s5/exp/mono0a/log$ vim align.38.1.log

yesno結(jié)果

這里面0,1就分別代表說的是no還是yes啦鬼吵。

語(yǔ)音識(shí)別原理相關(guān)資料

語(yǔ)音識(shí)別的原理 https://www.zhihu.com/question/20398418

HTK Book http://www.ee.columbia.edu/ln/LabROSA/doc/HTKBook21/HTKBook.html

如何用簡(jiǎn)單易懂的例子解釋隱馬爾可夫模型? https://www.zhihu.com/question/20962240/answer/33438846

kaldi一些文件解讀

1:run.sh  總的運(yùn)行文件篮赢,里面把其他運(yùn)行文件都集成了齿椅。

執(zhí)行順序:run.sh >>> path.sh >>> directory(存放訓(xùn)練數(shù)據(jù)的目錄) >>> mono-phone>>>triphone>>>lda_mllt>>>sat>>>quitck
data preparation:

1:generate text,wav.scp,utt2spk,spk2utt (將數(shù)據(jù)生成這些文件) (由local/data_prep.sh生成)
text:包含每段發(fā)音的標(biāo)注  sw02001-A_002736-002893 AND IS
wav.scp: (extended-filename:實(shí)際的文件名)
sw02001-A /home/dpovey/kaldi-trunk/tools/sph2pipe_v2.5/sph2pipe -f wav -p -c 1 /export/corpora3/LDC/LDC97S62/swb1/sw02001.sph |
utt2spk:  指明某段發(fā)音是哪個(gè)人說的(注意一點(diǎn),說話人編號(hào)并不需要與說話人實(shí)際的名字完全一致——只需要大概能夠猜出來就行启泣。)
sw02001-A_000098-001156 2001-A
spk2utt: ...』两拧(utt2spk和spk2utt文件中包含的信息是一樣的)
2:produce MFCC features
3:prepare language stuff(build a large lexicon that invovles words in both the training and decoding.)
4:monophone單音素訓(xùn)練
5:tri1三音素訓(xùn)練(以單音素模型為輸入訓(xùn)練上下文相關(guān)的三音素模型), trib2進(jìn)行l(wèi)da_mllt聲學(xué)特征變換寥茫,trib3進(jìn)行sat自然語(yǔ)言適應(yīng)(運(yùn)用基于特征空間的最大似然線性回歸(fMLLR)進(jìn)行說話人自適應(yīng)訓(xùn)練)涩澡,trib4做quick
LDA-MLLT(Linear Discriminant Analysis – Maximum Likelihood Linear Transform), LDA根據(jù)降維特征向量建立HMM狀態(tài)坠敷。MLLT根據(jù)LDA降維后的特征空間獲得每一個(gè)說話人的唯一變換妙同。MLLT實(shí)際上是說話人的歸一化。
SAT(Speaker Adaptive Training)膝迎。SAT同樣對(duì)說話人和噪聲的歸一化粥帚。
5:DNN
}

2:cmd.sh  一般需要修改

export train_cmd=run.pl #將原來的queue.pl改為run.pl
export decode_cmd="run.pl"#將原來的queue.pl改為run.pl這里的--mem 4G
export mkgraph_cmd="run.pl"#將原來的queue.pl改為run.pl 這里的--mem 8G
export cuda_cmd="run.pl" #將原來的queue.pl改為run.pl 這里去掉原來的--gpu 1(如果沒有g(shù)pu)

3:path.sh (設(shè)置環(huán)境變量)

export KALDI_ROOT=pwd/../../..
[ -f KALDI_ROOT/tools/env.sh ] && .KALDI_ROOT/tools/env.sh
export PATH=PWD/utils/:KALDI_ROOT/tools/openfst/bin:PWD:PATH
[ ! -f KALDI_ROOT/tools/config/common_path.sh ] && echo >&2 "The standard fileKALDI_ROOT/tools/config/common_path.sh is not present -> Exit!" && exit 1
. KALDI_ROOT/tools/config/common_path.sh export LC_ALL=C 我們看到是在運(yùn)行run.sh是要用到的環(huán)境變量,在這里先設(shè)置一下. 我們看到先是設(shè)置了KALDI_ROOT,它實(shí)際就是kaldi的源碼的根目錄限次。 [ -fKALDI_ROOT/tools/env.sh ] && . $KALDI_ROOT/tools/env.sh
這句話的意思是如果存在這個(gè)環(huán)境變量腳本就執(zhí)行這個(gè)腳本芒涡,但是我沒有在該路徑下發(fā)現(xiàn)這個(gè)腳本。
然后是將本目錄下的utils目錄, kaldi根目錄下的tools/openfst/bin目錄 和 本目錄加入到環(huán)境變量PATH中卖漫。
然后是判斷如果在kaldi根目錄下的tools/config/common_path.sh不存在费尽,就打印提示缺少該文件,并且退出羊始。

Kaldi訓(xùn)練腳本針對(duì)不同的語(yǔ)料庫(kù)旱幼,需要重寫數(shù)據(jù)準(zhǔn)備部分,腳本一般放在conf突委、local文件夾里柏卤;
conf放置一些配置文件冬三,如提取mfcc、filterbank等參數(shù)的配置缘缚,解碼時(shí)的參數(shù)配置 (主要是配置頻率勾笆,將系統(tǒng)采樣頻率與語(yǔ)料庫(kù)的采樣頻率設(shè)置為一致)
local一般用來放置處理語(yǔ)料庫(kù)的數(shù)據(jù)準(zhǔn)備部分腳本 > 中文識(shí)別,應(yīng)該準(zhǔn)備:發(fā)音詞典桥滨、音頻文件對(duì)應(yīng)的文本內(nèi)容和一個(gè)基本可用的語(yǔ)言模型(解碼時(shí)使用)
數(shù)據(jù)訓(xùn)練完后:
exp目錄下:
final.mdl 訓(xùn)練出來的模型
graph_word目錄下:
words.txt HCLG.fst 一個(gè)是字典窝爪,一個(gè)是有限狀態(tài)機(jī)(fst:發(fā)音字典,輸入是音素齐媒,輸出是詞)

kaldi編譯成iOS靜態(tài)庫(kù)

編譯腳本如下:

#!/bin/bash

if [ ! \( -x "./configure" \) ] ; then
    echo "This script must be run in the folder containing the \"configure\" script."
    exit 1
fi

export DEVROOT=`xcode-select --print-path`
export SDKROOT=$DEVROOT/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk

# Set up relevant environment variables
export CPPFLAGS="-I$SDKROOT/usr/include/c++/4.2.1/ -I$SDKROOT/usr/include/ -miphoneos-version-min=10.0 -arch arm64"
export CFLAGS="$CPPFLAGS -arch arm64 -pipe -no-cpp-precomp -isysroot $SDKROOT"
#export CXXFLAGS="$CFLAGS"
export CXXFLAGS="$CFLAGS  -std=c++11 -stdlib=libc++"

MODULES="online2 ivector nnet2 lat decoder feat transform gmm hmm tree matrix util base itf cudamatrix fstext"
INCLUDE_DIR=include/kaldi
mkdir -p $INCLUDE_DIR

echo "Copying include files"
LIBS=""
for m in $MODULES
do
  cd $m
  echo
  echo "BUILDING MODULE $m"
  echo
  if [[ -f Makefile ]]
  then
    make
    lib=$(ls *.a)  # this will fail (gracefully) for ift module since it only contains .h files
    LIBS+=" $m/$lib"
  fi

  echo "創(chuàng)建模塊文件夾:$INCLUDE_DIR/$m"
  cd ..
  mkdir -p $INCLUDE_DIR/$m
  echo "拷貝文件到:$INCLUDE_DIR/$m/"
  cp -v $m/*h $INCLUDE_DIR/$m/
done

echo "LIBS: $LIBS"

LIBNAME="kaldi-ios.a"
libtool -static -o $LIBNAME $LIBS

cat >&2 << EOF

Build succeeded! 

Library is in $LIBNAME
h files are in $INCLUDE_DIR

EOF

上面這個(gè)腳本只編譯了支持arm64架構(gòu)的靜態(tài)庫(kù)蒲每,在真機(jī)環(huán)境下測(cè)試,想支持其他的架構(gòu)的里初,可以直接添加:

export CPPFLAGS="-I$SDKROOT/usr/include/c++/4.2.1/ -I$SDKROOT/usr/include/ -miphoneos-version-min=10.0 -arch arm64"

上面的腳本來自大神:長(zhǎng)風(fēng)浮云 他的簡(jiǎn)書地址:http://www.reibang.com/p/faff2cd489ea 他寫了好多關(guān)于kaldi的相關(guān)博客啃勉,如果需要研究可以參考他的博客。

基于kaldi 源碼編譯的IOS 在線双妨,離線語(yǔ)音識(shí)別Demo

這里引用他提供的IOS 在線和離線識(shí)別的兩個(gè)demo:

2. CMUSphinx

CMUSphinx
官方資源導(dǎo)航:

  1. 這款語(yǔ)音識(shí)別開源框架相比于Kaldi比較適合做開發(fā)淮阐,各種函數(shù)上的封裝淺顯易懂,解碼部分的代碼非常容易看懂刁品,且除開PC平臺(tái)泣特,作者也考慮到了嵌入式平臺(tái),Android開發(fā)也很方便挑随,已有對(duì)應(yīng)的Demo状您,Wiki上有基于PocketSphinx的語(yǔ)音評(píng)測(cè)的例子,且實(shí)時(shí)性相比Kaldi好了很多兜挨。
  2. 由于適合開發(fā)膏孟,有很多基于它的各種開源程序、教育評(píng)測(cè)論文拌汇。
  3. 總的來說柒桑,從PocketSphinx來入門語(yǔ)音識(shí)別是一個(gè)不錯(cuò)的選擇。
  • 缺點(diǎn)

相比于Kaldi噪舀,使用的是GMM-HMM框架魁淳,準(zhǔn)確率上可能會(huì)差一些;其他雜項(xiàng)處理程序(如pitch提取等等)沒有Kaldi多与倡。

Sphinx工具介紹

Pocketsphinx —用C語(yǔ)言編寫的輕量級(jí)識(shí)別庫(kù)界逛,主要是進(jìn)行識(shí)別的。

Sphinxbase — Pocketsphinx所需要的支持庫(kù)纺座,主要完成的是語(yǔ)音信號(hào)的特征提认荨;

Sphinx3 —為語(yǔ)音識(shí)別研究用C語(yǔ)言編寫的解碼器

Sphinx4 —為語(yǔ)音識(shí)別研究用JAVA語(yǔ)言編寫的解碼器

CMUclmtk —語(yǔ)言模型訓(xùn)練工具

Sphinxtrain —聲學(xué)模型訓(xùn)練工具

下載網(wǎng)址:http://sourceforge.net/projects/cmusphinx/files/

Sphinx是由美國(guó)卡內(nèi)基梅隆大學(xué)開發(fā)的大詞匯量、非特定人该溯、連續(xù)英語(yǔ)語(yǔ)音識(shí)別系統(tǒng)岛抄。Sphinx從開發(fā)之初就得到了CMU别惦、DARPA等多個(gè)部門的資助和支持狈茉,后來逐步發(fā)展為開源項(xiàng)目。目前CMU Sphinx小組開發(fā)的下列譯碼器:

Sphinx-2采用半連續(xù)隱含馬爾可夫模型(SCHMM)建模掸掸,采用的技術(shù)相對(duì)落后氯庆,使得識(shí)別精度要低于其它的譯碼器。

PocketSphinx是一個(gè)計(jì)算量和體積都很小的嵌入式語(yǔ)音識(shí)別引擎扰付。在Sphinx-2的基礎(chǔ)上針對(duì)嵌入式系統(tǒng)的需求修改堤撵、優(yōu)化而來,是第一個(gè)開源面向嵌入式的中等詞匯量連續(xù)語(yǔ)音識(shí)別項(xiàng)目羽莺。識(shí)別精度和Sphinx-2差不多实昨。

Sphinx-3是CMU高水平的大詞匯量語(yǔ)音識(shí)別系統(tǒng),采用連續(xù)隱含馬爾可夫模型CHMM建模盐固。支持多種模式操作荒给,高精度模式扁平譯碼器,由Sphinx3的最初版本優(yōu)化而來刁卜;快速搜索模式樹譯碼器志电。目前將這兩種譯碼器融合在一起使用。

Sphinx-4是由JAVA語(yǔ)言編寫的大詞匯量語(yǔ)音識(shí)別系統(tǒng)蛔趴,采用連續(xù)的隱含馬爾可夫模型建模挑辆,和以前的版本相比,它在模塊化孝情、靈活性和算法方面做了改進(jìn)鱼蝉,采用新的搜索策略,支持各種不同的語(yǔ)法和語(yǔ)言模型箫荡、聽覺模型和特征流魁亦,創(chuàng)新的算法允許多種信息源合并成一種更符合實(shí)際語(yǔ)義的優(yōu)雅的知識(shí)規(guī)則。由于完全采用JAVA語(yǔ)言開發(fā)菲茬,具有高度的可移植性吉挣,允許多線程技術(shù)和高度靈活的多線程接口。

3.

參考: http://www.reibang.com/p/0f4a53450209

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末婉弹,一起剝皮案震驚了整個(gè)濱河市睬魂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌镀赌,老刑警劉巖氯哮,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異商佛,居然都是意外死亡喉钢,警方通過查閱死者的電腦和手機(jī)姆打,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肠虽,“玉大人幔戏,你說我怎么就攤上這事∷翱危” “怎么了闲延?”我有些...
    開封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)韩玩。 經(jīng)常有香客問我垒玲,道長(zhǎng),這世上最難降的妖魔是什么找颓? 我笑而不...
    開封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任合愈,我火速辦了婚禮,結(jié)果婚禮上击狮,老公的妹妹穿的比我還像新娘佛析。我一直安慰自己,他們只是感情好帘不,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開白布说莫。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上纺荧,一...
    開封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音辽狈,去河邊找鬼。 笑死呛牲,一個(gè)胖子當(dāng)著我的面吹牛刮萌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播娘扩,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼着茸,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了琐旁?” 一聲冷哼從身側(cè)響起涮阔,我...
    開封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎灰殴,沒想到半個(gè)月后敬特,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年伟阔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了辣之。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡皱炉,死狀恐怖怀估,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情娃承,我是刑警寧澤奏夫,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布怕篷,位于F島的核電站历筝,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏廊谓。R本人自食惡果不足惜梳猪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蒸痹。 院中可真熱鬧春弥,春花似錦、人聲如沸叠荠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)榛鼎。三九已至逃呼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間者娱,已是汗流浹背抡笼。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留黄鳍,地道東北人推姻。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像框沟,于是被迫代替她去往敵國(guó)和親藏古。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容