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

@TOC

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

最近做了一個語音識別相關的研究,因為公司需要使用離線語音識別功能,為了兼顧性能和價格方面的問題,最終選擇的方案是,在線時使用siri,離線使用百度語音識別方案。

封裝了一個離線在線合成的SDK:語音識別SDK 這個Demo里面沒有上傳百度libBaiduSpeechSDK.a 文件,因為這個文件太大了超過了100M,無法上傳到Git,需要自己從官方SDK下載替換到Demo中。

這里總結一下現有的幾種離線語音識別方案

  • 簡易使用第三方SDK方案
方案 優點 缺點 價格 成功率
科大訊飛 成功率高95% 價格貴,增加ipa包大小 可用買斷或按流量,一臺設備成本4元左右 成功率95%左右
百度AI 成功率比較高90% ,價格便宜,離線識別免費 ,提供了自定義模型訓練,訓練后識別率提高較多 增加ipa包大小, 識別率不高,離線只支持命令詞方式,支持的語音只有中文和英文,在線時會強制使用在線識別方式,超過免費流量后就要收費,如果欠費,什么都用不了;離線引擎使用至少需要連一次外網 離線命令詞免費,在線識別按流量次數計算,1600元套餐包(200萬次中文識別,5萬次英文識別) 在線識別成功率95%左右,離線識別基本上達不到90%
siri 成功高,免費,原始自帶,蘋果系統自帶,不會增加包大小 有局限性,要求IOS10以上系統才能使用siri api, IOS 系統13以上支持離線語音識別,但離線識別不支持中文識別,英文離線識別比百度的準確率高 完全免費 在線識別率跟科大訊飛差不多95%以上,離線英文識別也有90%左右
  • 開源代碼方案
方案 優點 缺點 說明 成功率
KALDI開源框架 KALDI是著名的開源自動語音識別(ASR)工具,這套工具提供了搭建目前工業界最常用的ASR模型的訓練工具,同時也提供了其他一些子任務例如說話人驗證(speaker verification)和語種識別(language recognition)的pipeline。KALDI目前由Daniel Povey維護,他之前在劍橋做ASR相關的研究,后來去了JHU開發KALDI,目前在北京小米總部作為語音的負責人。同時他也是另一個著名的ASR工具HTK的主要作者之一。
CMU-Sphinx開源框架 功能包括按特定語法進行識別、喚醒詞識別、n-gram識別等等,這款語音識別開源框架相比于Kaldi比較適合做開發,各種函數上的封裝淺顯易懂,解碼部分的代碼非常容易看懂,且除開PC平臺,作者也考慮到了嵌入式平臺,Android開發也很方便,已有對應的Demo,Wiki上有基于PocketSphinx的語音評測的例子,且實時性相比Kaldi好了很多。 相比于Kaldi,使用的是GMM-HMM框架,準確率上可能會差一些;其他雜項處理程序(如pitch提取等等)沒有Kaldi多。
HTK-Cambridage 是C語音編寫,支持win,linux,ios

方案一:Siri語音識別

Siri語音識別簡介

Siri語音識別用到的Api主要是SFSpeechRecognizer聲音處理器,是IOS 10 才提供的api,所以只有IOS 10以上才能使用,從IOS10 到 IOS13 直接蘋果只提供了在線識別方式,IOS13之后提供了離線識別方式。不過離線識別方式不支持中文模式,官方雖然說支持中文,但是實際測試發現中文離線識別根本無法識別。

Siri語音識別功能類介紹

  • 引入系統庫Speech
  • SFSpeechRecognizer聲音處理器,這個類是語音識別的操作類,用于語音識別用戶權限的申請,語言環境的設置,語音模式的設置以及向Apple服務發送語音識別的請求。
    例如下面代碼會根據傳入的語言簡稱來返回一個聲音處理器,如果不支持,怎會返回nil。更多細節可以查看官方文檔。
SFSpeechRecognizer(locale: Locale(identifier: langugeSimple))

通過下面的方法來得到語音識別的結果:

open func recognitionTask(with request: SFSpeechRecognitionRequest, resultHandler: @escaping (SFSpeechRecognitionResult?, Error?) -> Void) -> SFSpeechRecognitionTask
  • AVAudioEngine專門用來處理聲音的數據
 lazy var audioEngine: AVAudioEngine = {
        let audioEngine = AVAudioEngine()
        audioEngine.inputNode.installTap(onBus: 0, bufferSize: 1024, format: audioEngine.inputNode.outputFormat(forBus: 0)) { (buffer, audioTime) in
            // 為語音識別請求對象添加一個AudioPCMBuffer,來獲取聲音數據
            self.recognitionRequest.append(buffer)
        }
        return audioEngine
    }()
  • SFSpeechAudioBufferRecognitionRequest語音識別器,通過音頻流來創建語音識別請求。:
 // 語音識別器
    lazy var recognitionRequest: SFSpeechAudioBufferRecognitionRequest = {
        let recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
        return recognitionRequest
    }()
  • SFSpeechRecognitionTask語言識別任務管理器,啟用和關閉都要使用這個管理進行。這個類是語音識別服務請求任務類,每一個語音識別請求都可以抽象為一個SFSpeechRecognitionTask實例,其中SFSpeechRecognitionTaskDelegate協議中約定了許多請求任務過程中的監聽方法。
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:語音識別請求類,需要通過其子類來進行實例化。
SFSpeechURLRecognitionRequest:通過音頻URL來創建語音識別請求。
SFSpeechRecognitionResult:語音識別請求結果類。
SFTranscription:語音轉換后的信息類。

具體詳情可以參考蘋果官方文檔,蘋果提供了一個Swift版本的Demo:點擊這里下載蘋果官方demo

Siri語音識別功能集成

  • 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(@"正在識別中,請稍候。");
        return;
    }
    
    if (self.authorizationStatus != KAuthorizationStatusAuthorized) {
        errorHandler([KError notAuthorizationError]);
        return;
    }
    
    if (!_isAvaliable) {
        NSString *message = [NSString stringWithFormat:@"%@語音識別器不可用", [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;
    
//啟用離線識別的開關,這個屬性只有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?
    
    // 語音識別器
    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
            // 為語音識別請求對象添加一個AudioPCMBuffer,來獲取聲音數據
            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 {
                        //  初始化語音處理器的輸入模式 語音處理器準備就緒(會為一些audioEngine啟動時所必須的資源開辟內存)
                        self.audioEngine.prepare()
                        if (self.speechTask?.state == .running) {   // 如果當前進程狀態是進行中
                            // 停止語音識別
                           self.stopDictating()
                        } else {   // 進程狀態不在進行中
                            self.speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: languge))
                            guard (self.speechRecognizer != nil) else {
                                self.showAlert("抱歉,暫不支持當前地區使用語音輸入")
                                return
                            }
                            self.setCallBack(type: .start, text: nil)
                            // 開啟語音識別
                            self.startDictating()
                        }
                    } else {
                        self.showAlert("您已取消授權使用語音識別,如果需要使用語音識別功能,可以到設置中重新開啟!")
                        self.setCallBack(type: .authDenied, text: nil)
                    }
                })
            } else {
                //麥克風沒有授權
                self.showAlert("您已取消授權使用麥克風,如果需要使用語音識別功能,可以到設置中重新開啟!")
                self.setCallBack(type: .authDenied, text: nil)
            }
        }
    }
}


@available(iOS 10.0, *)
extension JPSpeechRecognition: SFSpeechRecognitionTaskDelegate {
    
    //判斷語音識別權限
    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)
        }
    }
    
    //檢測麥克風
    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)
        }
    }
    
    //開始進行
    private func startDictating() {
        do {
            recognitionRequest = SFSpeechAudioBufferRecognitionRequest()   // recreates recognitionRequest object.
            guard let recognitionRequest = recognitionRequest else {
                fatalError("Unable to created a SFSpeechAudioBufferRecognitionRequest object")
            }
        //啟用離線識別的開關,這個屬性只有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
                // 識別結果,識別后的操作
                if speechResult == nil {
                    return
                }
                self.setCallBack(type: .finished, text: speechResult!.bestTranscription.formattedString)
            }
        } catch  {
            print(error)
            self.setCallBack(type: .finished, text: nil)
        }
    }
    
    // 停止聲音處理器,停止語音識別請求進程
    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)
    }
}

方案二:百度語音識別

百度語音識別簡介

百度語音識別提供了很多功能,這里我簡單介紹一下語音識別這塊的。
百度語音識別有以下特點:

  • 在線語音識別支持識別任意詞,離線語音識別僅支持命令詞識別(語法模式)
  • 首次使用離線,SDK將會后臺下載離線授權文件,成功后,授權文件有效期(三年)內無需聯網。有效期即將結束后SDK將自動多次嘗試聯網更新證書)。
  • 沒有純離線識別。只能離線識別固定短語
  • 離線識別目前不支持任意語句。您可以預先定義好,下載bsg文件 http://yuyin.baidu.com/asr
    bds_easr_gramm.dat 文件件的內容替換成 自己定義的bsg 文件的內容
    自定義短語越多效果越差,建議不超過100行

百度語音識別SDK集成步驟

一。首先在百度語音開放平臺注冊,創建應用,生成API_KEY,SECRET_KEY和APP_ID
創建應用時的包名填工程的 Bundle identifier。百度語音識別注冊地址點擊這里

百度語音識別控制臺

二,下載SDK,先打開官方demo運行看看, 替換創建應用生成的API_KEY,SECRET_KEY和APP_ID

百度語音識別IOS sdk

創建自己的appKey

創建自己的appKey

然后可以先測試一下下載的百度官方demo,運行是否OK。

配置離線識別引擎

三,然后在自己項目集成,開發。

注意集成的資源包導入:


注意集成的資源包導入

例如在上面提供的離線SDK中,我只用一個簡單的類對百度SDK包裝了一層,離線配置方法如下:


離線模式配置

百度SDK 集成步驟如下:

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


    拖入SDK中必要的文件
  2. 添加必須要的系統框架,動態庫:


    添加必須要的系統框架,動態庫
  3. 封裝自己的類,實現對百度API調用:

  • 導入語音識別需要的頭文件:
#import "BDSASRDefines.h"
#import "BDSASRParameters.h"
#import "BDSEventManager.h"
  • 定義跟AppID綁定的 APP_ID, API_KEY, SECRET_KEY 相關信息,這個信息就是你在百度平臺注冊得到的
const NSString* APP_ID = @"18569855";
const NSString* API_KEY = @"2qrMX1TgfTGslRMd3TcDuuBq";
const NSString* SECRET_KEY = @"xatUjET5NLNDXYNghNCnejt28MGpRYP2";
  • 初始化SDK,構建一個BDSEventManager對象,設置需要使用短語音服務:百度短語音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;
}
  • 配置離線引擎和相關模型資源文件
- (void)configOfflineMode {
    [self.asrEventManager setDelegate:self];
    [self.asrEventManager setParameter:@(EVRDebugLogLevelError) forKey:BDS_ASR_DEBUG_LOG_LEVEL];
    
    
    // 參數配置:在線身份驗證
    [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];
    
    // 離線僅可識別自定義語法規則下的詞
    NSString *grammarFilePath = [[NSBundle mainBundle] pathForResource:@"baidu_speech_grammar" ofType:@"bsg"];
    if (_offlineGrammarDATFileURL != nil) {
        if (![[NSFileManager defaultManager] fileExistsAtPath:_offlineGrammarDATFileURL.path]) {
            NSLog(@"!!! Error: 你提供的離線語法詞庫不存在: %@", _offlineGrammarDATFileURL.path);
        } else {
            grammarFilePath = _offlineGrammarDATFileURL.path;
        }
    }
    
    [self.asrEventManager setParameter:grammarFilePath forKey:BDS_ASR_OFFLINE_ENGINE_GRAMMER_FILE_PATH];
}

除了上面離線識別需要的簡單設備外,還可以設置如下信息:

  1. 識別語言 @0 : @"普通話", @1 : @"粵語", @2 : @"英文", @3 : @"四川話"
//識別語言 @0 : @"普通話", @1 : @"粵語", @2 : @"英文", @3 : @"四川話"
    [self.asrEventManager setParameter:@(EVoiceRecognitionLanguageChinese) forKey:BDS_ASR_LANGUAGE];
  1. 采樣率 @"自適應", @"8K", @"16K"
 //采樣率 @"自適應", @"8K", @"16K"
    [self.asrEventManager setParameter:@(EVoiceRecognitionRecordSampleRateAuto) forKey:BDS_ASR_SAMPLE_RATE];
  1. 是否啟用長語音識別
 //是否啟用長語音識別
    [self.asrEventManager setParameter:@(YES) forKey:BDS_ASR_ENABLE_LONG_SPEECH];
      //開啟提示音 @0 : @"關閉", @(EVRPlayToneAll) : @"開啟"}
    //使用長語音必須關閉提示音
    [self.asrEventManager setParameter:@(0) forKey:BDS_ASR_PLAY_TONE];
  //開啟端點檢測 {@NO : @"關閉", @YES : @"開啟"}  使用長語音必須開啟本地VAD
    //端點檢測,即自動檢測音頻輸入的起始點和結束點。SDK默認開啟VAD,檢測到靜音后自動停止識別。
    //如果需要自行控制識別結束需關閉VAD,請同時關閉服務端VAD與端上VAD
    //[self.asrEventManager setParameter:@(YES) forKey:BDS_ASR_ENABLE_LOCAL_VAD];
   // 關閉服務端VAD
    [self.asrEventManager setParameter:@(NO) forKey:BDS_ASR_ENABLE_EARLY_RETURN];
    // 關閉本地VAD
    [self.asrEventManager setParameter:@(NO) forKey:BDS_ASR_ENABLE_LOCAL_VAD];    
    //打開的話配置端點檢測(二選一)
  • 配置 ModelVAD端點檢測方式 檢測更加精準,抗噪能力強,響應速度較慢
- (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端點檢測方式 提供基礎檢測功能,性能高,響應速度快
//DNNMFE端點檢測方式 提供基礎檢測功能,性能高,響應速度快
- (void)configDNNMFE {
    //設置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];
    //設置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,打開需配置資源文件參數
    [self.asrEventManager setParameter:@(NO) forKey:BDS_ASR_ENABLE_MODEL_VAD];
    // MFE支持自定義靜音時長
    //    [self.asrEventManager setParameter:@(500.f) forKey:BDS_ASR_MFE_MAX_SPEECH_PAUSE];
    //    [self.asrEventManager setParameter:@(500.f) forKey:BDS_ASR_MFE_MAX_WAIT_DURATION];
}
  • 離在線并行配置
     // 參數設置:識別策略為離在線并行
        [self.asrEventManager setParameter:@(EVR_STRATEGY_BOTH) forKey:BDS_ASR_STRATEGY];
        // 參數設置:離線識別引擎類型 EVR_OFFLINE_ENGINE_INPUT 輸入法模式  EVR_OFFLINE_ENGINE_GRAMMER 離線引    擎語法模式
        //離線語音識別僅支持命令詞識別(語法模式)。
        //[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文件。下載語法文件后,設置BDS_ASR_OFFLINE_ENGINE_GRAMMER_FILE_PATH參數
        NSString* gramm_filepath = [[NSBundle mainBundle] pathForResource:@"bds_easr_gramm" ofType:@"dat"];
        // 請在 (官網)[http://speech.baidu.com/asr] 參考模板定義語法,下載語法文件后,替換BDS_ASR_OFFLINE_ENGINE_GRAMMER_FILE_PATH參數
        [self.asrEventManager setParameter:gramm_filepath forKey:BDS_ASR_OFFLINE_ENGINE_GRAMMER_FILE_PATH];
        //離線識別資源文件路徑
        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];   
  • 監聽回調代理
#pragma mark -- 語音識別狀態、錄音數據等回調均在此代理中發生
- (void)VoiceRecognitionClientWorkStatus:(int)workStatus obj:(id)aObj{
    switch (workStatus) {
        case EVoiceRecognitionClientWorkStatusNewRecordData: {
            [self.fileHandler writeData:(NSData *)aObj];
            NSLog(@"錄音數據回調");
            break;
        }
            
        case EVoiceRecognitionClientWorkStatusStartWorkIng: {
            NSLog(@"識別工作開始開始采集及處理數據");
            NSDictionary *logDic = [self parseLogToDic:aObj];
            [self printLogTextView:[NSString stringWithFormat:@"開始識別-log: %@\n", logDic]];
            break;
        }
        case EVoiceRecognitionClientWorkStatusStart: {
            NSLog(@"檢測到用戶開始說話");
            [self printLogTextView:@"檢測到用戶開始說話.\n"];
            break;
        }
        case EVoiceRecognitionClientWorkStatusEnd: {
            NSLog(@"用戶說話完成,但服務器尚未返回結果");
            [self printLogTextView:@"用戶說話完成,但服務器尚未返回結果.\n"];
            self.contentTextView.text = @"無識別結果";
            break;
        }
        case EVoiceRecognitionClientWorkStatusFlushData: {
            // 逐句顯示。配合連續上屏的中間結果,可以進一步 升語音輸入的體驗
            //// 該狀態值表示服務器返回了中間結果,如果想要將中間結果展示給用戶(形成連續上屏的效果),
            // 可以利用與該狀態同時返回的數據,每當接到新的該類消息應當清空顯示區域的文字以免重復
            NSLog(@"逐句顯示");
            [self printLogTextView:[NSString stringWithFormat:@"服務器返回了中間結 - %@.\n\n", [self getDescriptionForDic:aObj]]];
            
            self.contentTextView.text = @"";
            NSArray *contentArr = aObj[@"results_recognition"];
            NSString *contentStr = contentArr[0];
            self.contentTextView.text = contentStr;
            
            break;
        }
        case EVoiceRecognitionClientWorkStatusFinish: {
            //// 該狀態值表示語音識別服務器返回了最終結果,結果以數組的形式保存在 aObj 對象中
            // 接受到該消息時應當清空顯示區域的文字以免重復
            NSLog(@"返回了最終結果");
            /*
             "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:@"最終結果 - %@.\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(@"當前音量回調");
            break;
        }
        case EVoiceRecognitionClientWorkStatusCancel: {
            NSLog(@"用戶主動取消");
            [self printLogTextView:@"用戶主動取消.\n"];
            
            break;
        }
        case EVoiceRecognitionClientWorkStatusError: {
            // 錯誤狀態 沒有語音輸入
            NSLog(@"錯誤狀態");
            NSError * error = (NSError *)aObj;
            
            if (error.code == 2228236) {
                ////離線引擎錯誤狀態:
                //識別失敗,無法識別。(語法模式下,可能為語音不在自定義的語法規則之下)
                 [self printLogTextView:[NSString stringWithFormat:@"錯誤狀態 -語法模式下,可能為語音不在自定義的語法規則之下\n %@.\n", (NSError *)aObj]];
            }else if (error.code == 2228230){
                 [self printLogTextView:[NSString stringWithFormat:@"錯誤狀態 -dat模型文件不可用,請設置 BDS_ASR_OFFLINE_ENGINE_DAT_FILE_PATH\n %@.\n", (NSError *)aObj]];
            }else if (error.code == 2228231){
                 [self printLogTextView:[NSString stringWithFormat:@"錯誤狀態 -grammar文件無效,請設置 BDS_ASR_OFFLINE_ENGINE_GRAMMER_FILE_PATH\n %@.\n", (NSError *)aObj]];
            }else if (error.code == 2225219){
                [self printLogTextView:[NSString stringWithFormat:@"錯誤狀態 -音頻質量過低,無法識別\n %@.\n", (NSError *)aObj]];
            }else{
                [self printLogTextView:[NSString stringWithFormat:@"錯誤狀態 - %@.\n", (NSError *)aObj]];
            }
           
            break;
        }
        case EVoiceRecognitionClientWorkStatusLoaded: {
            NSLog(@"離線引擎加載完成");
            [self printLogTextView:@"離線引擎加載完成.\n"];
            break;
        }
        case EVoiceRecognitionClientWorkStatusUnLoaded: {
            NSLog(@"離線引擎卸載完成");
            [self printLogTextView:@"離線引擎卸載完成.\n"];
            break;
        }
        case EVoiceRecognitionClientWorkStatusChunkThirdData: {
            NSLog(@"識別結果中的第三方數據");
            [self printLogTextView:[NSString stringWithFormat:@"識別結果中的第三方數據: %lu\n", (unsigned long)[(NSData *)aObj length]]];
            break;
        }
        case EVoiceRecognitionClientWorkStatusChunkNlu: {
            NSLog(@"別結果中的語義結果");
            NSString *nlu = [[NSString alloc] initWithData:(NSData *)aObj encoding:NSUTF8StringEncoding];
            [self printLogTextView:[NSString stringWithFormat:@"識別結果中的語義結果: %@\n", nlu]];
            NSLog(@"%@", nlu);
            break;
        }
        case EVoiceRecognitionClientWorkStatusChunkEnd: {
            NSLog(@"識別過程結束");
            [self printLogTextView:[NSString stringWithFormat:@"識別過程結束, sn: %@.\n", aObj]];
            
            break;
        }
        case EVoiceRecognitionClientWorkStatusFeedback: {
            NSLog(@"識別過程反饋的打點數據");
            NSDictionary *logDic = [self parseLogToDic:aObj];
            [self printLogTextView:[NSString stringWithFormat:@"識別過程反饋的打點數據: %@\n", logDic]];
            break;
        }
        case EVoiceRecognitionClientWorkStatusRecorderEnd: {
            //錄音機關閉,頁面跳轉需檢測此時間,規避狀態條 (iOS)
            NSLog(@"錄音機關閉");
            [self printLogTextView:@"錄音機關閉.\n"];
            break;
        }
        case EVoiceRecognitionClientWorkStatusLongSpeechEnd: {
            NSLog(@"長語音結束狀態");
            [self printLogTextView:@"長語音結束狀態.\n"];
            
            break;
        }
        default:
            break;
    }
    
}
  • 提供開始,停止識別方法

//開始識別
- (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];
}

  • 完整封裝代碼如下:
//
//  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];
        // 創建語音識別對象
        _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 {
    // 設置語音識別代理
    [self.asrEventManager setDelegate:self];
    [self.asrEventManager setParameter:@(EVRDebugLogLevelError) forKey:BDS_ASR_DEBUG_LOG_LEVEL];
    
    
    // 參數配置:在線身份驗證
    [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];
    
    //離線引擎身份驗證 設置 APPID 離線授權所需APPCODE(APPID),如使用該方式進行正式授權,請移除臨時授權文件
    [self.asrEventManager setParameter:APP_ID forKey:BDS_ASR_OFFLINE_APP_CODE];
    
    //識別策略 @0 : @"在線識別", @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];
    
    // 離線僅可識別自定義語法規則下的詞
    NSString *grammarFilePath = [[NSBundle mainBundle] pathForResource:@"baidu_speech_grammar" ofType:@"bsg"];
    if (_offlineGrammarDATFileURL != nil) {
        if (![[NSFileManager defaultManager] fileExistsAtPath:_offlineGrammarDATFileURL.path]) {
            NSLog(@"!!! Error: 你提供的離線語法詞庫不存在: %@", _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:@"語音識別失敗"]);
            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后運行

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簡介

KALDI 簡介

KALDI是著名的開源自動語音識別(ASR)工具,這套工具提供了搭建目前工業界最常用的ASR模型的訓練工具,同時也提供了其他一些子任務例如說話人驗證(speaker verification)和語種識別(language recognition)的pipeline。KALDI目前由Daniel Povey維護,他之前在劍橋做ASR相關的研究,后來去了JHU開發KALDI,目前在北京小米總部作為語音的負責人。同時他也是另一個著名的ASR工具HTK的主要作者之一。

KALDI之所以在ASR領域如此流行,是因為該工具提供了其他ASR工具不具備的可以在工業中使用的神經網絡模型(DNN,TDNN,LSTM)。但是與其他基于Python接口的通用神經網絡庫(TensorFlow,PyTorch等)相比,KALDI提供的接口是一系列的命令行工具,這就需要學習者和使用者需要比較強的shell腳本能力。同時,KALDI為了簡化搭建語音識別pipeline的過程,提供了大量的通用處理腳本,這些腳本主要是用shell,perl以及python寫的,這里主要需要讀懂shell和python腳本,perl腳本主要是一些文本處理工作,并且可以被python取代,因此學習的性價比并不高。整個KALDI工具箱的結構如下圖所示。
KALDI工具箱的結構

可以看到KALDI的矩陣計算功能以及構圖解碼功能分別是基于BLAS/LAPACK/MKL和OpenFST這兩個開源工具庫的,在這些庫的基礎上,KALDI實現了Matrix,Utils,Feat,GMM,SGMM,Transforms,LM,Tree,FST ext,HMM,Decoder等工具,通過編譯之后可以生成命令行工具,也就是圖中的C++ Executables文件。最后KALDI提供的樣例以及通用腳本展示了如何使用這些工具來搭建ASR Pipeline,是很好的學習材料。

除了KALDI本身提供的腳本,還有其官方網站的Documents也是很好的學習資料。當然,除了KALDI工具本身,ASR的原理也需要去學習,只有雙管齊下,才能更好的掌握這個難度較高的領域。

KALDI 源碼編譯 安裝

如何安裝參考下載好的目錄內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.

出現問題首先查看各個目錄下面的INSTALL文件,有些配置的問題(例如gcc的版本以及CUDA等)都可以查看該文檔進行解決。

依賴文件編譯

首先檢查依賴項

cd extras
sudo bash check_dependencies.sh

注意make -j4可以多進程進行

cd kaldi-trunk/tools
make

配置Kaldi源碼

cd ../src
#如果沒有GPU請查閱configure,設置不使用CUDA
./configure --shared

編譯Kaldi源碼

make all
#注意在這里有可能關于CUDA的編譯不成功,原因是configure腳本沒有正確的找出CUDA的位置,需要手動編輯configure查找的路徑,修改CUDA庫的位置

測試安裝是否成功

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

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

yesno結果

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

語音識別原理相關資料

語音識別的原理 https://www.zhihu.com/question/20398418

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

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

kaldi一些文件解讀

1:run.sh  總的運行文件,里面把其他運行文件都集成了。

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

1:generate text,wav.scp,utt2spk,spk2utt (將數據生成這些文件) (由local/data_prep.sh生成)
text:包含每段發音的標注  sw02001-A_002736-002893 AND IS
wav.scp: (extended-filename:實際的文件名)
sw02001-A /home/dpovey/kaldi-trunk/tools/sph2pipe_v2.5/sph2pipe -f wav -p -c 1 /export/corpora3/LDC/LDC97S62/swb1/sw02001.sph |
utt2spk:  指明某段發音是哪個人說的(注意一點,說話人編號并不需要與說話人實際的名字完全一致——只需要大概能夠猜出來就行。)
sw02001-A_000098-001156 2001-A
spk2utt: ...?。╱tt2spk和spk2utt文件中包含的信息是一樣的)
2:produce MFCC features
3:prepare language stuff(build a large lexicon that invovles words in both the training and decoding.)
4:monophone單音素訓練
5:tri1三音素訓練(以單音素模型為輸入訓練上下文相關的三音素模型), trib2進行lda_mllt聲學特征變換,trib3進行sat自然語言適應(運用基于特征空間的最大似然線性回歸(fMLLR)進行說話人自適應訓練),trib4做quick
LDA-MLLT(Linear Discriminant Analysis – Maximum Likelihood Linear Transform), LDA根據降維特征向量建立HMM狀態。MLLT根據LDA降維后的特征空間獲得每一個說話人的唯一變換。MLLT實際上是說話人的歸一化。
SAT(Speaker Adaptive Training)。SAT同樣對說話人和噪聲的歸一化。
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(如果沒有gpu)

3:path.sh (設置環境變量)

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 我們看到是在運行run.sh是要用到的環境變量,在這里先設置一下. 我們看到先是設置了KALDI_ROOT,它實際就是kaldi的源碼的根目錄。 [ -fKALDI_ROOT/tools/env.sh ] && . $KALDI_ROOT/tools/env.sh
這句話的意思是如果存在這個環境變量腳本就執行這個腳本,但是我沒有在該路徑下發現這個腳本。
然后是將本目錄下的utils目錄, kaldi根目錄下的tools/openfst/bin目錄 和 本目錄加入到環境變量PATH中。
然后是判斷如果在kaldi根目錄下的tools/config/common_path.sh不存在,就打印提示缺少該文件,并且退出。

Kaldi訓練腳本針對不同的語料庫,需要重寫數據準備部分,腳本一般放在conf、local文件夾里;
conf放置一些配置文件,如提取mfcc、filterbank等參數的配置,解碼時的參數配置 (主要是配置頻率,將系統采樣頻率與語料庫的采樣頻率設置為一致)
local一般用來放置處理語料庫的數據準備部分腳本 > 中文識別,應該準備:發音詞典、音頻文件對應的文本內容和一個基本可用的語言模型(解碼時使用)
數據訓練完后:
exp目錄下:
final.mdl 訓練出來的模型
graph_word目錄下:
words.txt HCLG.fst 一個是字典,一個是有限狀態機(fst:發音字典,輸入是音素,輸出是詞)

kaldi編譯成iOS靜態庫

編譯腳本如下:

#!/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 "創建模塊文件夾:$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

上面這個腳本只編譯了支持arm64架構的靜態庫,在真機環境下測試,想支持其他的架構的,可以直接添加:

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

上面的腳本來自大神:長風浮云 他的簡書地址:http://www.lxweimin.com/p/faff2cd489ea 他寫了好多關于kaldi的相關博客,如果需要研究可以參考他的博客。

基于kaldi 源碼編譯的IOS 在線,離線語音識別Demo

這里引用他提供的IOS 在線和離線識別的兩個demo:

2. CMUSphinx

CMUSphinx
官方資源導航:

  1. 這款語音識別開源框架相比于Kaldi比較適合做開發,各種函數上的封裝淺顯易懂,解碼部分的代碼非常容易看懂,且除開PC平臺,作者也考慮到了嵌入式平臺,Android開發也很方便,已有對應的Demo,Wiki上有基于PocketSphinx的語音評測的例子,且實時性相比Kaldi好了很多。
  2. 由于適合開發,有很多基于它的各種開源程序、教育評測論文。
  3. 總的來說,從PocketSphinx來入門語音識別是一個不錯的選擇。
  • 缺點

相比于Kaldi,使用的是GMM-HMM框架,準確率上可能會差一些;其他雜項處理程序(如pitch提取等等)沒有Kaldi多。

Sphinx工具介紹

Pocketsphinx —用C語言編寫的輕量級識別庫,主要是進行識別的。

Sphinxbase — Pocketsphinx所需要的支持庫,主要完成的是語音信號的特征提取;

Sphinx3 —為語音識別研究用C語言編寫的解碼器

Sphinx4 —為語音識別研究用JAVA語言編寫的解碼器

CMUclmtk —語言模型訓練工具

Sphinxtrain —聲學模型訓練工具

下載網址:http://sourceforge.net/projects/cmusphinx/files/

Sphinx是由美國卡內基梅隆大學開發的大詞匯量、非特定人、連續英語語音識別系統。Sphinx從開發之初就得到了CMU、DARPA等多個部門的資助和支持,后來逐步發展為開源項目。目前CMU Sphinx小組開發的下列譯碼器:

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

PocketSphinx是一個計算量和體積都很小的嵌入式語音識別引擎。在Sphinx-2的基礎上針對嵌入式系統的需求修改、優化而來,是第一個開源面向嵌入式的中等詞匯量連續語音識別項目。識別精度和Sphinx-2差不多。

Sphinx-3是CMU高水平的大詞匯量語音識別系統,采用連續隱含馬爾可夫模型CHMM建模。支持多種模式操作,高精度模式扁平譯碼器,由Sphinx3的最初版本優化而來;快速搜索模式樹譯碼器。目前將這兩種譯碼器融合在一起使用。

Sphinx-4是由JAVA語言編寫的大詞匯量語音識別系統,采用連續的隱含馬爾可夫模型建模,和以前的版本相比,它在模塊化、靈活性和算法方面做了改進,采用新的搜索策略,支持各種不同的語法和語言模型、聽覺模型和特征流,創新的算法允許多種信息源合并成一種更符合實際語義的優雅的知識規則。由于完全采用JAVA語言開發,具有高度的可移植性,允許多線程技術和高度靈活的多線程接口。

3.

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,963評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,348評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,083評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,706評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,442評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,802評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,795評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,983評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,542評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,287評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,486評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,030評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,710評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,116評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,412評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,224評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,462評論 2 378

推薦閱讀更多精彩內容