筆者在前文《Azure AI 服務之文本翻譯》中簡單介紹了 Azure 認知服務中的文本翻譯 API,通過這些簡單的 REST API 調用就可以輕松地進行機器翻譯。如果能在程序中簡單的集成語音轉文本的功能會不會非常贊!本文我們就介紹如何使用必應的語音識別 API(Bing Speech API) 把語音轉換成文本:
使用 Bing Speech API 可以輕松地開發出下面的應用:
你點擊 "開始錄音" 按鈕,然后對着麥克風說話,就能夠識別輸出你說的內容並輸出成文本。上面的截圖是 Azure 官方提供的 demo,為了演示語音識別 API 的用法,我們寫一個丑點的,但是可以輸出詳細信息的程序:
該程序會以不同的模式識別我們 hardcode 的兩段音頻數據,然后輸出識別的結果。其中上面的文本框會輸出大量的中間識別結果,而下面的文本框則輸出最終的識別結果。
創建 Azure 服務
要使用 Azure 的翻譯服務需要先在 Azure 上創建對應的實例,比如我們需要先創建一個 "Bing Speech API" 服務實例:
說明:對於學習和練習來說,你可以創建免費的 Azure 賬號並創建免費版的上述實例,詳細信息請參考 Azure 官網。
創建 WPF 程序
Bing Speech API 服務同時提供了 REST API 和客戶端類庫,因為 REST API 提供的服務會有一些限制,所以我們在演示程序中使用客戶端類庫。客戶端類庫分為 x86 和 x64 兩個版本,筆者引用的是 x64 的版本 Microsoft.ProjectOxford.SpeechRecognition-x64:
因而需要把工程的 platform target 也設置為 x64。
需要注意的是,Azure 提供的認知服務 API 都是需要認證信息的。具體的方式就是把我們創建的服務的 key 隨 API 發送的服務器端進行認證。你可以在創建的服務實例的詳情界面獲得對應的 key,我們在程序中通過定義的常量來保存它們:
const string SUBSCRIPTIONKEY = "your bing speech API key";
由於 demo 的代碼比較長,為了能集中精力介紹 Azure AI 相關的內容,本文中只貼出相關的代碼。完整的 demo 代碼在這里。
識別模式
語音識別區分不同的識別模式來應對不同的使用場景,如對話模式、聽寫模式和交互式模式。
- 對話模式(conversation) 在對話模式中,使用者參與的是人與人之間的對話。
- 聽寫模式(dictation) 在聽寫模式中,使用者說出一段較長的語音然后等待語音識別的結果。
- 交互式模式(interactive) 在交互模式中, 使用者發出簡短的請求, 並期望應用程序執行響應操作。
遺憾的是在我們使用的客戶端類庫中,相關的模式類型並不是與上面的三種模式一一對應,類庫中提供一個叫 SpeechRecognitionMode 的枚舉:
public enum SpeechRecognitionMode { ShortPhrase = 0, LongDictation = 1 }
它定義了 ShortPhrase 和 LongDictation 兩種識別模式。ShortPhrase 模式最長支持 15 秒的語音。語音數據被分塊發送到服務端,服務端會及時的返回部分的識別結果,所以客戶端會收到多個部分結果和一個包含多個 n-best 選項的最終結果。LongDictation 模式支持最長兩分鍾的語音。語音數據被分塊發送到服務器,根據服務端分辨出的語句間的停頓,客戶端會受到多個部分結果和多個最終結果。
代碼中我們要通過它們來告訴語音識別 API 執行識別的類型。比如要識別比 15s 短的語音,可以使用 ShortPhrase 模式構建 CreateDataClient 類型的實例:
// 使用工廠類型的 CreateDataClient 方法創建 DataRecognitionClient 類型的實例。 this.dataClient = SpeechRecognitionServiceFactory.CreateDataClient( SpeechRecognitionMode.ShortPhrase , // 指定語音識別的模式。 "en-US", // 我們把語音中語言的類型 hardcode 為英語,因為我們的兩個 demo 文件都是英語語音。 SUBSCRIPTIONKEY); // Bing Speech API 服務實例的 key。
如果要識別長於 15s 的語音,就需要使用 SpeechRecognitionMode.LongDictation 模式。
分塊傳輸音頻
為了能得到近乎實時的識別效果,我們必須把音頻數據以適當大小的塊連續發送給服務端,下面代碼中使用的塊大小為 1024:
/// <summary> /// 向服務端發送語音數據。 /// </summary> /// <param name="wavFileName">wav 格式文件的名稱。</param> private void SendAudioHelper(string wavFileName) { using (FileStream fileStream = new FileStream(wavFileName, FileMode.Open, FileAccess.Read)) { // Note for wave files, we can just send data from the file right to the server. // In the case you are not an audio file in wave format, and instead you have just // raw data (for example audio coming over bluetooth), then before sending up any // audio data, you must first send up an SpeechAudioFormat descriptor to describe // the layout and format of your raw audio data via DataRecognitionClient's sendAudioFormat() method. int bytesRead = 0; // 創建大小為 1024 的 buffer。 byte[] buffer = new byte[1024]; try { do { // 把文件數據讀取到 buffer 中。 bytesRead = fileStream.Read(buffer, 0, buffer.Length); // 通過 DataRecognitionClient 類型的實例把語音數據發送到服務端。 this.dataClient.SendAudio(buffer, bytesRead); } while (bytesRead > 0); } finally { // 告訴服務端語音數據已經傳送完了。 this.dataClient.EndAudio(); } } }
注意,在數據傳送結束后需要通過 EndAudio() 方法顯式的告訴服務端數據傳送結束。
部分結果與最終結果
部分結果
把數據分塊發送給語音識別服務端,我們就能得到近乎實時的識別效果。服務器端通過 OnPartialResponseReceived 事件不斷把識別的結果發送到客戶端。比如 demo 中演示的 ShortPhrase 模式實例,我們會得到下面的中間結果(在上面的輸出框中):
--- Partial result received by OnPartialResponseReceivedHandler() --- why --- Partial result received by OnPartialResponseReceivedHandler() --- what's --- Partial result received by OnPartialResponseReceivedHandler() --- what's the weather --- Partial result received by OnPartialResponseReceivedHandler() --- what's the weather like
在識別的過程中 OnPartialResponseReceived 事件被觸發了 4 次,識別的結果也越來越完整。如果應用程序能夠根據這些中間結果不斷地向使用者做出反饋,則應用程序就具備了實時性。
最終結果
當使用者結束語音的輸入后,demo 中就是調用了 EndAudio() 函數。語音識別服務在完成識別后會觸發 OnResponseReceived 事件,我們通過下面的函數把結果輸出到 UI 中:
/// <summary> /// 把服務端返回的語音識別結果輸出到 UI。 /// </summary> /// <param name="e"><see cref="SpeechResponseEventArgs"/>該類型的實例包含語音識別的結果。</param> private void WriteResponseResult(SpeechResponseEventArgs e) { if (e.PhraseResponse.Results.Length == 0) { this.WriteLine("No phrase response is available."); } else { this.WriteLine("********* Final n-BEST Results *********"); for (int i = 0; i < e.PhraseResponse.Results.Length; i++) { this.WriteLine( "[{0}] Confidence={1}, Text=\"{2}\"", i, e.PhraseResponse.Results[i].Confidence, e.PhraseResponse.Results[i].DisplayText); } this.WriteLine(); } }
數據的結果大體如下:
--- OnDataShortPhraseResponseReceivedHandler ---
********* Final n-BEST Results *********
[0] Confidence=High, Text="What's the weather like?"
上面是 ShortPhrase 模式的一個識別結果,它的特點是只有一個最終的返回結果,其中會包含多個識別結果並被稱為 n-best。n-best 中的每個結果都包含 Confidence,DisplayText,InverseTextNormalizationResult,LexicalForm,MaskedInverseTextNormalizationResult 等屬性,比如我們可以根據 Confidence 屬性判斷識別的結果是否可靠:
上圖是實際的返回結果,因為太簡單了,所以 n-best 列表中只有一條(Azure 上的語言材料,發音還是很標准的)。
對於 LongDictation 模式的識別,客戶端事件 OnResponseReceived 會被觸發多次,並返回分階段的識別結果,結果中的內容和 ShortPhrase 模式類似。更詳細的內容請大家直接看代碼吧,很簡單的。
支持語言
筆者圖省事直接使用了 Azure 文檔中提供的英語語音作為 demo 數據,其實 Bing Speech API 對中文支持還是比較全面的,現在支持的所有模式都支持中文。如果你還有其它需求,可以從這里查看詳細的語言支持列表。
總結
筆者最早接觸語音識別是在 2000 年左右,當時感覺太神奇了。只是識別的效果不太好,並且要求反復的讀一個基准文檔…
這么多年過去了,其實語言相關的技術發展並不算很快。 AI 的興起讓我們看到了一線希望,在介紹了 Azure AI 的語音識別服務后,讓我們接着探索如何通過 AI 讓程序理解文本的內容。