【iOS10 SpeechRecognition】語音識別 現說現譯的最佳實踐

来源:http://www.cnblogs.com/dsxniubility/archive/2016/12/20/6192457.html
-Advertisement-
Play Games

首先想強調一下“語音識別”四個字字面意義上的需求:用戶說話然後馬上把用戶說的話轉成文字顯示!,這才是開發者真正需要的功能。 做需求之前其實是先谷歌百度一下看有沒有造好的輪子直接用,結果真的很呵呵,都是標著這個庫深入學習的標題,裡面調用一下api從URL里取出一個本地語音文件進行識別,這就沒了? 最基 ...


首先想強調一下“語音識別”四個字字面意義上的需求:用戶說話然後馬上把用戶說的話轉成文字顯示!,這才是開發者真正需要的功能。

做需求之前其實是先谷歌百度一下看有沒有造好的輪子直接用,結果真的很呵呵,都是標著這個庫深入學習的標題,裡面調用一下api從URL里取出一個本地語音文件進行識別,這就沒了? 最基本的需求都沒法實現。

 

今天整理下對於此功能的兩種實現方式:

首先看下識別請求的API有兩種 SFSpeechAudioBufferRecognitionRequest 和 SFSpeechURLRecognitionRequest ,並且實現解析的方式也有兩種 block 和 delegate。 我就相互組合下兩種方法把這些內容都能涵蓋。

在開發之前需要先在info.plist註冊用戶隱私許可權,雖然大家都已經知道了我還是說一嘴為了本文的完整性。

Privacy - Microphone Usage Description
Privacy - Speech Recognition Usage Description  

再使用requestAuthorization來請求使用許可權

    [SFSpeechRecognizer requestAuthorization:^(SFSpeechRecognizerAuthorizationStatus status) {
        // 對結果枚舉的判斷
    }];

關於麥克風的許可權在首次開始錄音時也會提出許可權選擇。

一、 SFSpeechAudioBufferRecognitionRequest 加上 block的方式

用這種方式實現主要分為以下幾個步驟

①多媒體引擎的建立

成員變數需要添加以下幾個屬性,便於開始結束釋放等

@property(nonatomic,strong)SFSpeechRecognizer *bufferRec;
@property(nonatomic,strong)SFSpeechAudioBufferRecognitionRequest *bufferRequest;
@property(nonatomic,strong)SFSpeechRecognitionTask *bufferTask;
@property(nonatomic,strong)AVAudioEngine *bufferEngine;
@property(nonatomic,strong)AVAudioInputNode *buffeInputNode;

初始化建議寫在啟動的方法里,便於啟動和關閉,如果準備使用全局的也可以只初始化一次

    self.bufferRec = [[SFSpeechRecognizer alloc]initWithLocale:[NSLocale localeWithLocaleIdentifier:@"zh_CN"]];
    self.bufferEngine = [[AVAudioEngine alloc]init];
    self.buffeInputNode = [self.bufferEngine inputNode];

②創建語音識別請求

    self.bufferRequest = [[SFSpeechAudioBufferRecognitionRequest alloc]init];
    self.bufferRequest.shouldReportPartialResults = true;

shouldReportPartialResults 其中這個屬性可以自行設置開關,是等你一句話說完再回調一次,還是每一個散碎的語音片段都會回調。

③建立任務,並執行任務

    // block外的代碼也都是準備工作,參數初始設置等
    self.bufferRequest = [[SFSpeechAudioBufferRecognitionRequest alloc]init];
    self.bufferRequest.shouldReportPartialResults = true;
    __weak ViewController *weakSelf = self;
    self.bufferTask = [self.bufferRec recognitionTaskWithRequest:self.bufferRequest resultHandler:^(SFSpeechRecognitionResult * _Nullable result, NSError * _Nullable error) {
            // 接收到結果後的回調
    }];
    
    // 監聽一個標識位並拼接流文件
    AVAudioFormat *format =[self.buffeInputNode outputFormatForBus:0];
    [self.buffeInputNode installTapOnBus:0 bufferSize:1024 format:format block:^(AVAudioPCMBuffer * _Nonnull buffer, AVAudioTime * _Nonnull when) {
        [weakSelf.bufferRequest appendAudioPCMBuffer:buffer];
    }];
    
    // 準備並啟動引擎
    [self.bufferEngine prepare];
    NSError *error = nil;
    if (![self.bufferEngine startAndReturnError:&error]) {
        NSLog(@"%@",error.userInfo);
    };
    self.showBufferText.text = @"等待命令中.....";

對runloop稍微瞭解過的人都知道,block外面的代碼是在前一個運行迴圈先執行的, 正常的啟動流程是 先初始化參數 然後 啟動引擎,然後會不斷地調用拼接buffer的這個回調方法,然後一個單位的buffer攢夠了後會回調一次上面的語音識別結果的回調,有時候沒聲音也會調用buffer的方法,但是不會調用上面的resulthandler回調,這個方法內部應該有個容錯(音量power沒到設定值會自動忽略)。 

④接收到結果的回調

結果的回調就是在上面resultHandler裡面的block里了,執行後返回的參數就是result和error了,可以針對這個結果做一些操作。

        if (result != nil) {
            self.showBufferText.text = result.bestTranscription.formattedString;
        }
        if (error != nil) {
            NSLog(@"%@",error.userInfo);
        }

這個結果類型SFSpeechRecognitionResult可以看看裡面的屬性  ,有最佳結果,還有備選結果的數組。如果想做精確匹配的應該得把備選數組的答案也都過濾一遍。

⑤結束監聽

    [self.bufferEngine stop];
    [self.buffeInputNode removeTapOnBus:0];
    self.showBufferText.text = @"";
    self.bufferRequest = nil;
    self.bufferTask = nil;

這個中間的bus是臨時標識的節點,大概理解和埠的概念差不多。

 

二、SFSpeechURLRecognitionRequest 和 delegate的 方法

block和delegate的主要區別是,block方式使用簡潔, delegate則可以有更多的自定義需求的空間,因為裡面有更多的結果回調生命周期方法。

這五個方法也沒什麼好說的,都是顧名思義。 要註意的一點是第二個方法會調用多次,第三個方法會在一句話說完時調用一次。

// Called when the task first detects speech in the source audio
- (void)speechRecognitionDidDetectSpeech:(SFSpeechRecognitionTask *)task;

// Called for all recognitions, including non-final hypothesis
- (void)speechRecognitionTask:(SFSpeechRecognitionTask *)task didHypothesizeTranscription:(SFTranscription *)transcription;

// Called only for final recognitions of utterances. No more about the utterance will be reported
- (void)speechRecognitionTask:(SFSpeechRecognitionTask *)task didFinishRecognition:(SFSpeechRecognitionResult *)recognitionResult;

// Called when the task is no longer accepting new audio but may be finishing final processing
- (void)speechRecognitionTaskFinishedReadingAudio:(SFSpeechRecognitionTask *)task;

// Called when the task has been cancelled, either by client app, the user, or the system
- (void)speechRecognitionTaskWasCancelled:(SFSpeechRecognitionTask *)task;

// Called when recognition of all requested utterances is finished.
// If successfully is false, the error property of the task will contain error information
- (void)speechRecognitionTask:(SFSpeechRecognitionTask *)task didFinishSuccessfully:(BOOL)successfully;

這種實現的思路是,先實現一個錄音器(可以手動控制開始結束,也可以是根據音調大小自動開始結束的同步錄音器類似於會說話的湯姆貓),然後將錄音文件存到一個本地目錄,然後使用URLRequest的方式讀取出來進行翻譯。步驟分解如下

①建立同步錄音器

需要以下這些屬性

/** 錄音設備 */
@property (nonatomic, strong) AVAudioRecorder *recorder;
/** 監聽設備 */
@property (nonatomic, strong) AVAudioRecorder *monitor;
/** 錄音文件的URL */
@property (nonatomic, strong) NSURL *recordURL;
/** 監聽器 URL */
@property (nonatomic, strong) NSURL *monitorURL;
/** 定時器 */
@property (nonatomic, strong) NSTimer *timer;

屬性的初始化

    // 參數設置
    NSDictionary *recordSettings = [[NSDictionary alloc] initWithObjectsAndKeys:
                                    [NSNumber numberWithFloat: 14400.0], AVSampleRateKey,
                                    [NSNumber numberWithInt: kAudioFormatAppleIMA4], AVFormatIDKey,
                                    [NSNumber numberWithInt: 2], AVNumberOfChannelsKey,
                                    [NSNumber numberWithInt: AVAudioQualityMax], AVEncoderAudioQualityKey,
                                    nil];
    
    NSString *recordPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"record.caf"];
    _recordURL = [NSURL fileURLWithPath:recordPath];
    
    _recorder = [[AVAudioRecorder alloc] initWithURL:_recordURL settings:recordSettings error:NULL];
    
    // 監聽器
    NSString *monitorPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"monitor.caf"];
    _monitorURL = [NSURL fileURLWithPath:monitorPath];
    _monitor = [[AVAudioRecorder alloc] initWithURL:_monitorURL settings:recordSettings error:NULL];
    _monitor.meteringEnabled = YES;

其中參數設置的那個字典里,的那些常量大家不用過於上火,這是之前寫的代碼直接扒來用的,上文中設置的最優語音質量。

②開始與結束

要想通過聲音大小來控制開始結束的話,需要在錄音器外再額外設置個監聽器用來 查看語音的大小 通過peakPowerForChannel 方法查看當前話筒環境的聲音環境音量。並且有個定時器來控制音量檢測的周期。大致代碼如下

- (void)setupTimer {
    [self.monitor record];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(updateTimer) userInfo:nil repeats:YES]; //董鉑然博客園
}

// 監聽開始與結束的方法
- (void)updateTimer {

    // 不更新就沒法用了
    [self.monitor updateMeters];
    
    // 獲得0聲道的音量,完全沒有聲音-160.0,0是最大音量
    float power = [self.monitor peakPowerForChannel:0];
    
    //        NSLog(@"%f", power);
    if (power > -20) {
        if (!self.recorder.isRecording) {
            NSLog(@"開始錄音");
            [self.recorder record];
        }
    } else {
        if (self.recorder.isRecording) {
            NSLog(@"停止錄音");
            [self.recorder stop];
            [self recognition];
        }
    }
}

③語音識別的任務請求

- (void)recognition {
    // 時鐘停止
    [self.timer invalidate];
    // 監聽器也停止
    [self.monitor stop];
    // 刪除監聽器的錄音文件
    [self.monitor deleteRecording];
    
    //創建語音識別操作類對象
    SFSpeechRecognizer *rec = [[SFSpeechRecognizer alloc]initWithLocale:[NSLocale localeWithLocaleIdentifier:@"zh_CN"]];
    //            SFSpeechRecognizer *rec = [[SFSpeechRecognizer alloc]initWithLocale:[NSLocale localeWithLocaleIdentifier:@"en_ww"]];  //董鉑然博客園
    
    //通過一個本地的音頻文件來解析
    SFSpeechRecognitionRequest * request = [[SFSpeechURLRecognitionRequest alloc]initWithURL:_recordURL];
    [rec recognitionTaskWithRequest:request delegate:self];
}

這段通過一個本地文件進行識別轉漢字的代碼,應該是網上傳的最多的,因為不用動腦子都能寫出來。 但是單有這一段代碼基本是沒有什麼卵用的。(除了人家微信現在有個長按把語音轉文字的功能, 其他誰的App需求 我真想不到會直接拿出一個本地音頻文件來解析,自動生成mp3歌詞?周傑倫的歌解析難度比較大,還有語音識別時間要求不能超過1分鐘)

④結果回調的代理方法

- (void)speechRecognitionTask:(SFSpeechRecognitionTask *)task didFinishRecognition:(SFSpeechRecognitionResult *)recognitionResult
{
    NSLog(@"%s",__FUNCTION__);
    NSLog(@"%@",recognitionResult.bestTranscription.formattedString);
    [self setupTimer];
}

用的最多的就這個方法了,另外不同時刻的回調方法可以按需添加,這裡也就是簡單展示,可以看我的demo程式里有更多功能。

https://github.com/dsxNiubility/SXSpeechRecognitionTwoWays

   

iOS10在語音相關識別相關功能上有了一個大的飛躍,主要體現在兩點 一點就是上面的語音識別,另一點是sirikit可以實現將外部的信息透傳到App內進行操作,但是暫時局限性比較明顯,只能夠實現官網所說 叫車,發信息 等消息類型,甚至連“打開美團 搜索烤魚店”這種類型 都還不能識別,所以暫時也無法往下做過多研究,等待蘋果之後的更新吧。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 折騰了大半年的項目,用的angular折騰快瘋了。 總算有個小結了。正好閑下來為新的項目做準備,學點新的玩意玩玩,以往ES6都沒用過,感覺被大部隊甩好遠了,抓緊跟上大部隊的腳步... 1.利用let和const引入塊級作用域 通過ES6中定義的let關鍵字可以形成僅作用於該塊作用域的局部變數 與le ...
  • 最近在用 Xamarin 做一個 Android 應用,打開應用時,如果有新消息,需要在應用內的 Toolbar 或者首頁的圖標上顯示數字提示。在這裡和大家分享一下實現方法,如果你有更新好的實現方法,歡迎分享。 類似推特客戶端的新消息提醒 解決思路:把圖標和數字提示文本放在 RelativeLayo ...
  • 今天快要下班了,都準備收拾電腦下班,突然微信推送了個消息:小程式通過微信審核了,按耐不住心中的喜悅,主動加班給大家分享這次通過審核的一些總結。 審核通過後,處於審核通過,待發佈狀態 小程式介紹 我們做的這個小程式是熱點雲筆記,是一個很簡單的記事本,主要是通過微信登錄,讓用戶免輸入賬號雲端同步文本。 ...
  • 這裡主要介紹一下檢查迴圈定義的結構體、聯合體。是對成員中包含自己本身的結構體、聯合體進行檢查。所謂“成員中包含自己本身”,舉例來說,就是指下麵這樣的定義。 這裡所說的“成員中包含自己本身”是指直接包含自己本身,通過指針來應用自己本身是沒有問題的。例如剛纔的例子,如果是下麵這樣的話就沒有問題了。 剛纔 ...
  • 由OpenDigg 出品的安卓開源項目周報第二期來啦。我們的安卓開源周報集合了OpenDigg一周來新收錄的優質的安卓開發方面的開源項目,方便安卓開發人員便捷的找到自己需要的項目工具等。 ...
  • 今天拉同事最新的代碼,編譯時老是報如下錯誤: Error:Could not find com.android.tools.build:gradle:2.2.0.Searched in the following locations: file:/D:/software/android-studio ...
  • 轉載請標明出處:http://www.cnblogs.com/zhaoyanjun/p/6202369.html 本文出自 "【趙彥軍的博客】" 在Android Studio項目裡面有個local.properties文件,這個文件可以放一些系統配置。比如:sdk路徑、ndk路徑。 當然我們也可以 ...
  • 最近為了滿足蘋果的 https 要求, 經過努力終於寫出了方法 驗證 SSL 證書是否滿足 ATS 要求 nscurl --ats-diagnostics --verbose https://你的功能變數名稱 PASS 符合要求 輸出滿足 ATS 的證書 openssl s_client -connect ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...