原文:Building a Speech-to-Text App Using Speech Framework in iOS 10
作者:Sahand Edrisian
譯者:YueWang
在2016 WWDC大會上,Apple公司介紹了一個很好的語音識別的API,那就是Speech framework。事實上,這個Speech Kit就是Siri用來做語音識別的框架。如今已經有一些可用的語音識別框架,但是它們要麼太貴要麼不好。在今天的教程裡面,我會教你怎樣創建一個使用Speech Kit來進行語音轉文字的類似Siri的app。
設計App UI
前提:你需要Xcode 8 beta版本和一個運行iOS 10 beta系統版本的iOS 設備
先從創建一個新的命名為SpeechToTextDemo的單視圖工程開始。接下來,到 Main.storyboard 中添加一個 UILabel,一個 UITextView, 和一個 UIButton,
你的storyboard應該看起來如下圖:
接下來在 ViewController.swift文件中為UITextView 和UIButton 定義outlet變量。在這個demo當中,我設置UITextView 的名稱為“textView”,UIButton的名稱為“microphoneButton”。然後創建一個當microphone按鈕被點擊時會觸發的空的按鈕執行方法。
@IBAction func microphoneTapped(_ sender: AnyObject) { }
如果你不想從創建最原始工程開始,你可以在 在這裡下載原始工程 然後繼續下面的教學指導。
使用Speech Framework
為了能使用Speech framework, 你必須首先導入它然後遵循 SFSpeechRecognizerDelegate 協議。因此讓我們導入這個框架,然後在 ViewController 文件中加上它的協議。現在你的 ViewController.swift 文件應該如下圖所示:
import UIKit import Speech class ViewController: UIViewController, SFSpeechRecognizerDelegate { @IBOutlet weak var textView: UITextView! @IBOutlet weak var microphoneButton: UIButton! override func viewDidLoad() { super.viewDidLoad() } @IBAction func microphoneTapped(_ sender: AnyObject) { } }
用戶授權
在使用speech framework做語音識別之前,你必須首先得到用戶的允許,因為不僅僅只有本地的ios設備會進行識別,蘋果的服務器也會識別。所有的語音數據都會被傳遞到蘋果的後台進行處理。因此,獲取用戶授權是強制必須的。
讓我們在 viewDidLoad 方法裡授權語音識別。用戶必須允許app使用話筒和語音識別。首先,聲明一個speechRecognizer變量:
private let speechRecognizer = SFSpeechRecognizer(locale: Locale.init(identifier: "en-US")) //1
然後如下圖更新 viewDidLoad 方法:
override func viewDidLoad() { super.viewDidLoad() microphoneButton.isEnabled = false //2 speechRecognizer.delegate = self //3 SFSpeechRecognizer.requestAuthorization { (authStatus) in //4 var isButtonEnabled = false switch authStatus { //5 case .authorized: isButtonEnabled = true case .denied: isButtonEnabled = false print("User denied access to speech recognition") case .restricted: isButtonEnabled = false print("Speech recognition restricted on this device") case .notDetermined: isButtonEnabled = false print("Speech recognition not yet authorized") } OperationQueue.main.addOperation() { self.microphoneButton.isEnabled = isButtonEnabled } } }
首先,我們創建一個帶有標識符en-US 的 SFSpeechRecognizer實例,這樣語音識別API就能知道用戶說的是哪一種語言。這個實例就是處理語音識別的對象。
我們默認讓microphone按鈕失效直到語音識別功能被激活。
接下來,把語音識別的代理設置為 self 也就是我們的ViewController.
之後,我們必須通過調用SFSpeechRecognizer.requestAuthorization方法來請求語音識別的授權。
最後,檢查驗證的狀態。如果被授權了,讓microphone按鈕有效。如果沒有,打印錯誤信息然後讓microphone按鈕失效。
現在如果你認為app跑起來之後你會看到一個授權彈出窗口,那你就錯了。如果運行,app會崩潰。好吧,既然知道結果為什麼還要問呢?(別打我),看看下面解決方法。
提供授權消息
蘋果要求app裡所有的授權都要一個自定義的信息。例如語音授權,我們必須請求2個授權:
麥克風使用權
語音識別
為了自定義信息,你必須在info.plist 配置文件裡提供這些自定義消息。
讓我們打開 info.plist配置文件的源代碼。首先,右鍵點擊 info.plist。然後選擇Open As > Source Code。最後,拷貝下面的XML代碼然後在標記前插入這段代碼。
現在你已經在info.plist文件裡添加了兩個鍵值:
NSMicrophoneUsageDescription -為獲取麥克風語音輸入授權的自定義消息。注意這個語音輸入授權僅僅只會在用戶點擊microphone按鈕時發生。
NSSpeechRecognitionUsageDescription – 語音識別授權的自定義信息
可以自行更改這些消息的內容。現在點擊Run按鈕,你應該可以編譯和成功運行app了,不會報任何錯誤。
注意:如果稍後在工程運行完成時還沒有看到語音輸入授權框,那是因為你是在模擬器上運行的程序。iOS模擬器沒有權限進入你Mac電腦的麥克風。
處理語音識別
現在我們已經實現了用戶授權,我們現在去實現語音識別功能。先從在 ViewController裡定義下面的對象開始:
private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest? private var recognitionTask: SFSpeechRecognitionTask? private let audioEngine = AVAudioEngine()
recognitionRequest對象處理了語音識別請求。它給語音識別提供了語音輸入。
reconition task對象告訴你語音識別對象的結果。擁有這個對象很方便因為你可以用它刪除或者中斷任務。
audioEngine是你的語音引擎。它負責提供你的語音輸入。
接下來,創建一個新的方法名叫 startRecording()。
func startRecording() { if recognitionTask != nil { recognitionTask?.cancel() recognitionTask = nil } let audioSession = AVAudioSession.sharedInstance() do { try audioSession.setCategory(AVAudioSessionCategoryRecord) try audioSession.setMode(AVAudioSessionModeMeasurement) try audioSession.setActive(true, with: .notifyOthersOnDeactivation) } catch { print("audioSession properties weren't set because of an error.") } recognitionRequest = SFSpeechAudioBufferRecognitionRequest() guard let inputNode = audioEngine.inputNode else { fatalError("Audio engine has no input node") } guard let recognitionRequest = recognitionRequest else { fatalError("Unable to create an SFSpeechAudioBufferRecognitionRequest object") } recognitionRequest.shouldReportPartialResults = true recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest, resultHandler: { (result, error) in var isFinal = false if result != nil { self.textView.text = result?.bestTranscription.formattedString isFinal = (result?.isFinal)! } if error != nil || isFinal { self.audioEngine.stop() inputNode.removeTap(onBus: 0) self.recognitionRequest = nil self.recognitionTask = nil self.microphoneButton.isEnabled = true } }) let recordingFormat = inputNode.outputFormat(forBus: 0) inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer, when) in self.recognitionRequest?.append(buffer) } audioEngine.prepare() do { try audioEngine.start() } catch { print("audioEngine couldn't start because of an error.") } textView.text = "Say something, I'm listening!" }
這個方法會在Start Recording按鈕被點擊時調用。它主要功能是開啟語音識別然後聆聽你的麥克風。我們一行行分析上面的代碼:
3-6行 – 檢查 recognitionTask 是否在運行。如果在就取消任務和識別。
8-15行 – 創建一個 AVAudioSession來為記錄語音做准備。在這裡我們設置session的類別為recording,模式為measurement,然後激活它。注意設置這些屬性有可能會拋出異常,因此你必須把他們放入try catch語句裡面。
17行 – 實例化recognitionRequest。在這裡我們創建了SFSpeechAudioBufferRecognitionRequest對象。稍後我們利用它把語音數據傳到蘋果後台。
19-21行 – 檢查 audioEngine(你的設備)是否有做錄音功能作為語音輸入。如果沒有,我們就報告一個錯誤。
23-25行 – 檢查recognitionRequest對象是否被實例化和不是nil。
27行– 當用戶說話的時候讓recognitionRequest報告語音識別的部分結果 。
29行 – 調用 speechRecognizer的recognitionTask 方法來開啟語音識別。這個方法有一個completion handler回調。這個回調每次都會在識別引擎收到輸入的時候,完善了當前識別的信息時候,或者被刪除或者停止的時候被調用,最後會返回一個最終的文本。
31行 – 定義一個布爾值決定識別是否已經結束。
35行 – 如果結果 result 不是nil, 把 textView.text 的值設置為我們的最優文本。如果結果是最終結果,設置 isFinal為true。
39-47行 – 如果沒有錯誤或者結果是最終結果,停止 audioEngine(語音輸入)並且停止 recognitionRequest 和 recognitionTask.同時,使Start Recording按鈕有效。
50-53行 – 向 recognitionRequest增加一個語音輸入。注意在開始了recognitionTask之後增加語音輸入是OK的。Speech Framework 會在語音輸入被加入的同時就開始進行解析識別。
55行 – 准備並且開始audioEngine。
觸發語音識別
我們需要保證當創建一個語音識別任務的時候語音識別功能是可用的,因此我們必須給ViewController添加一個代理方法。如果語音輸入不可用或者改變了它的狀態,那麼 microphoneButton.enable屬性就要被設置。針對這種情況,我們實現了SFSpeechRecognizerDelegate 協議的 availabilityDidChange 方法。實現內容看下面:
func speechRecognizer(_ speechRecognizer: SFSpeechRecognizer, availabilityDidChange available: Bool) { if available { microphoneButton.isEnabled = true } else { microphoneButton.isEnabled = false } }
這個方法會在可用性狀態改變時被調用。如果語音識別可用,那麼記錄按鈕record會被設為可用狀態。
最後一件事就是我們必須更新響應方法microphoneTapped(sender:):
@IBAction func microphoneTapped(_ sender: AnyObject) { if audioEngine.isRunning { audioEngine.stop() recognitionRequest?.endAudio() microphoneButton.isEnabled = false microphoneButton.setTitle("Start Recording", for: .normal) } else { startRecording() microphoneButton.setTitle("Stop Recording", for: .normal) } }
在這個方法中,我們必須檢查 audioEngine是否正在工作。如果是,app應該停止 audioEngine, 中止向recognitionRequest輸入音頻,讓microphoneButton按鈕不可用,並且設置按鈕的標題為 “Start Recording”
如果 audioEngine 正在工作,app應該調用 startRecording() 並且設置按鈕的標題為 “Stop Recording”。
非常好!現在可以准備測試app了。把app部署到一個iOS10的設備,然後點擊“Start Recording”按鈕。去說些什麼吧!
注意:
蘋果公司對每個設備的識別功能都有限制。具體的限制並不知道,但是你可以聯系蘋果公司了解更多信息。
蘋果公司對每個app也有識別功能限制。
如果你經常遇到限制,請一定聯系蘋果公司,他們應該可以解決問題。
語音識別會很耗電以及會使用很多數據。
語音識別一次只持續大概一分鐘時間。
總結
在這個教程中,你學習到了怎樣好好的利用蘋果公司開放給開發者的驚人的新語言API,用於語音識別並且轉換到文本。Speech framework 使用了跟Siri相同的語音識別框架。這是一個相對小的API。但是,它非常強大可以讓開發者們開發非凡的應用比如轉換一個語音文件到文本文字。
我推薦你看WWDC 2016 session 509去獲取更多有用信息。希望你喜歡這篇文章並且在探索這個全新API中獲得樂趣。
作為參考,你可以在這裡查看Github完整工程