@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)擊這里
二冯挎,下載SDK,先打開官方demo運(yùn)行看看, 替換創(chuàng)建應(yīng)用生成的API_KEY,SECRET_KEY和APP_ID
創(chuàng)建自己的appKey
然后可以先測(cè)試一下下載的百度官方demo,運(yùn)行是否OK咙鞍。
三房官,然后在自己項(xiàng)目集成,開發(fā)奶陈。
注意集成的資源包導(dǎo)入:
例如在上面提供的離線SDK中易阳,我只用一個(gè)簡(jiǎn)單的類對(duì)百度SDK包裝了一層附较,離線配置方法如下:
百度SDK 集成步驟如下:
-
將官方SDK中的如下文件拖入到自己的項(xiàng)目中:
-
添加必須要的系統(tǒng)框架吃粒,動(dòng)態(tài)庫(kù):
封裝自己的類拒课,實(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è)置如下信息:
- 識(shí)別語(yǔ)言 @0 : @"普通話", @1 : @"粵語(yǔ)", @2 : @"英文", @3 : @"四川話"
//識(shí)別語(yǔ)言 @0 : @"普通話", @1 : @"粵語(yǔ)", @2 : @"英文", @3 : @"四川話"
[self.asrEventManager setParameter:@(EVoiceRecognitionLanguageChinese) forKey:BDS_ASR_LANGUAGE];
- 采樣率 @"自適應(yīng)", @"8K", @"16K"
//采樣率 @"自適應(yīng)", @"8K", @"16K"
[self.asrEventManager setParameter:@(EVoiceRecognitionRecordSampleRateAuto) forKey:BDS_ASR_SAMPLE_RATE];
- 是否啟用長(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官網(wǎng) http://kaldi-asr.org/doc/index.html 包括一大堆原理和工具的使用說明诗良,有什么問題請(qǐng)首先看這個(gè)。
- Kaldi Lecture http://www.danielpovey.com/kaldi-lectures.html 相比于上一個(gè)會(huì)給一個(gè)更簡(jiǎn)略的原理鲁驶、流程介紹鉴裹。
- Kaldi中文翻譯1 如果感覺英語(yǔ)讀起來比較頭疼的話建議搜一下這個(gè)來看看,是對(duì)官網(wǎng)上文件的翻譯钥弯。這個(gè)文檔來源于一個(gè)學(xué)習(xí)交流Kaldi的QQ群径荔。
- Kaldi中文翻譯2 https://shiweipku.gitbooks.io/chinese-doc-of-kaldi/content/index.html
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的矩陣計(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
這里面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
export PATH=KALDI_ROOT/tools/openfst/bin:PATH
[ ! -f KALDI_ROOT/tools/config/common_path.sh is not present -> Exit!" && exit 1
. KALDI_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)航:
優(yōu)點(diǎn)
- 這款語(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好了很多兜挨。
- 由于適合開發(fā)膏孟,有很多基于它的各種開源程序、教育評(píng)測(cè)論文拌汇。
- 總的來說柒桑,從PocketSphinx來入門語(yǔ)音識(shí)別是一個(gè)不錯(cuò)的選擇。
- 缺點(diǎn)
相比于Kaldi噪舀,使用的是GMM-HMM框架魁淳,準(zhǔn)確率上可能會(huì)差一些;其他雜項(xiàng)處理程序(如pitch提取等等)沒有Kaldi多与倡。
- 語(yǔ)音識(shí)別CMUSphinx(1)Windows下安裝:參考這篇文章:http://cmusphinx.github.io/wiki/tutorialpocketsphinx/
- android demo :https://cmusphinx.github.io/wiki/tutorialandroid/
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ù)和高度靈活的多線程接口。