使用 iOS 10 的 Speech 框架構建語音轉文本應用

做者:SAHAND EDRISIAN,原文連接,原文日期:2016-08-09
譯者:ckitakishi;校對:mmoaay;定稿:CMBnode

在 2016 年的 WWDC 上,Apple 介紹了一個十分有用的語音識別 API,那就是 Speech 框架。事實上,Siri 的語音識別正是由 Speech Kit 提供支持。就目前來講,可用的語音識別框架並不是沒有,可是它們要麼太貴,要麼不夠好。在本教程中,我將會向你演示如何使用 Speech Kit 來建立一個像 Siri 同樣的應用來進行語音到文本的轉換。git

應用界面設計

事前準備: Xcode 8 beta 版和一臺運行 iOS 10 beta 版的設備.github

首先,讓咱們來建立一個 iOS Single View Application 工程,並將其命名爲 SpeechToTextDemo。而後在 Main.storyboard 上添加 UILabelUITextViewUIButton 各一個。swift

此時 storyboard 應該看起來像這樣:服務器

下一步,在 ViewController.swift 文件中爲 UITextViewUIButton 定義 outlet 變量,將 UITextView 命名爲 「textView」,UIButton 命名爲 「microphoneButton」 以後,再建立一個空 action 方法來監聽麥克風按鈕 (microphoneButton) 的點擊事件:session

@IBAction func microphoneTapped(_ sender: AnyObject) {
 
}

若是你不想從頭開始,也能夠下載初始工程,而後跟隨教程繼續對它進行完善。app

使用 Speech 框架

要使用 Speech 框架,第一件要作的事天然是引入這個框架,並遵循 SFSpeechRecognizerDelegate 協議。因此,咱們先引入該框架,而後將它的協議添加到 ViewController.swift 類中。此時 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 框架進行語音識別以前,你必須先請求用戶許可,緣由是識別不只發生在 iOS 設備本地,還須要依賴 Apple 的服務器。具體來講,全部音頻數據都會被傳輸到蘋果後臺進行處理。所以須要獲取用戶的權限。ide

咱們將在 ViewDidLoad 方法中處理受權。其中包括用戶必須容許應用使用的音頻輸入和語音識別權限。首先,聲明一個名爲 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
        }
    }
}
  1. 第一步,建立一個區域標誌符 (locale identifier) 爲 en-USSFSpeechRecognizer 實例,這時候語音識別就會知道用戶錄入的語種。簡單說,這就是語音識別的處理對象。

  2. 在語音識別被激活以前,默認設置麥克風按鈕爲禁用狀態。

  3. 而後,將語音識別的 delegate 設置爲 ViewController 中的 self

  4. 以後,就到了請求語音識別權限的階段了,這時咱們經過調用 SFSpeechRecognizer.requestAuthorization 來達到目的。

  5. 最後,檢查驗證狀態,若是獲得了受權,則啓用麥克風按鈕。不然,打印錯誤信息,繼續禁用麥克風按鈕。

你可能會認爲,如今咱們啓動應用將會看到一個受權提示框,很遺憾你錯了。運行應用帶來的是崩潰。你可能會想問,這是爲何?

提供受權信息

Apple 要求應用爲全部請求的權限提供自定義消息,對於語音權限的狀況,咱們必須爲兩個行爲請求受權:

  1. 麥克風的使用

  2. 語音的識別

要自定義消息,你須要在 info.plist 文件中定義這些消息。

讓咱們打開 info.plist 文件的源代碼。方法是在 info.plist 上點擊右鍵。而後選擇 Open As > Source Code。最後,複製下面的 XML 代碼並將它們插入到 </dict> 標籤前。

xml
<key>NSMicrophoneUsageDescription</key>  <string>Your microphone will be used to record your speech when you press the &quot;Start Recording&quot; button.</string>
 
<key>NSSpeechRecognitionUsageDescription</key>  <string>Speech recognition will be used to determine which words you speak into this device&apos;s microphone.</string>

好了,如今你已經將兩個 key 添加到 info.plist 中了:

  • NSMicrophoneUsageDescription – 音頻輸入受權請求的自定義信息。注意,音頻輸入受權請求只發生在用戶點擊麥克風按鈕的時候。

  • NSSpeechRecognitionUsageDescription – 語音識別受權請求的自定義信息。

你能夠隨意修改這些記錄的值。一切就緒,如今能夠運行程序了,不出意外的話,編譯並運行應用不會報任何錯。

[](https://www.appcoda.com/wp-co...

注意:若是工程完成以後你沒有看到音頻輸入受權請求的話,首先務必確認你是否正在模擬器上運行應用。iOS 模擬器並不會鏈接 Mac 的麥克風。

處理語音識別

如今用戶受權已經完成了,讓咱們一氣呵成,接着來實現語音識別。首先,在 ViewController 中定義下述對象:

private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?
private var recognitionTask: SFSpeechRecognitionTask?
private let audioEngine = AVAudioEngine()
  1. recognitionRequest 對象用於處理語音識別請求,爲語音識別提供音頻輸入。

  2. recognitionTask 能夠將識別請求的結果返回給你,它帶來了極大的便利,必要時,能夠取消或中止任務。

  3. 最後的 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!"
    
}

上述函數被調用的時機是開始錄音按鈕被按下的瞬間。它的主要功能是啓動語音識別並開始監聽麥克風。讓咱們來逐行剖析一下這段代碼:

  1. 3-6 行 – 檢查 recognitionTask 的運行狀態,若是正在運行,取消任務。

  2. 8-15 行 – 建立一個 AVAudioSession 對象爲音頻錄製作準備。這裏咱們將錄音分類設置爲 Record,模式設爲 Measurement,而後啓動。注意,設置這些屬性有可能會拋出異常,所以你必須將其置於 try catch 語句中。

  3. 17 行 – 實例化 recognitionResquest。建立 SFSpeechAudioBufferRecognitionRequest 對象,而後咱們就能夠利用它將音頻數據傳輸到 Apple 的服務器。

  4. 19-21 行 – 檢查 audioEngine (你的設備)是否支持音頻輸入以錄音。若是不支持,報一個 fatal error。

  5. 23-25 行 – 檢查 recognitionRequest 對象是否已被實例化,而且值不爲 nil

  6. 27 行 – 告訴 recognitionRequest 不要等到錄音完成才發送請求,而是在用戶說話時一部分一部分發送語音識別數據。

  7. 29 行 – 在調用 speechRecognizerrecognitionTask 函數時開始識別。該函數有一個完成回調函數,每次識別引擎收到輸入時都會調用它,在修改當前識別結果,亦或是取消或中止時,返回一個最終記錄。

  8. 31 行 – 定義一個 boolean 變量來表示識別是否已結束。

  9. 35 行 – 假若結果非空,則設置 textView.text 屬性爲結果中的最佳記錄。同時若爲最終結果,將 isFinal 置爲 true。

  10. 39-47 行 – 若是請求沒有錯誤或已經收到最終結果,中止 audioEngine (音頻輸入),recognitionRequestrecognitionTask。同時,將開始錄音按鈕的狀態切換爲可用。

  11. 50-53 行 – 向 recognitionRequest 添加一個音頻輸入。值得留意的是,在 recognitionTask 啓動後再添加音頻輸入徹底沒有問題。Speech 框架會在添加了音頻輸入以後當即開始識別任務。

  12. 55 行 – 將 audioEngine 設爲準備就緒狀態,並啓動引擎。

觸發語音識別

在建立語音識別任務時,咱們首先得確保語音識別的可用性,所以,須要向 ViewController 添加一個 delegate 方法。若是語音識別不可用,或是改變了狀態,應隨之設置 microphoneButton.enable 屬性。針對這個方案,咱們實現了 SFSpeechRecognizerDelegate 協議的 availabilityDidChange 方法。詳細實現以下所示:

func speechRecognizer(_ speechRecognizer: SFSpeechRecognizer, availabilityDidChange available: Bool) {
    if available {
        microphoneButton.isEnabled = true
    } else {
        microphoneButton.isEnabled = false
    }
}

這個方法會在按鈕的可用性改變時被調用。若是語音識別可用,錄音按鈕也將被啓用。

最後,咱們還須要更新一下 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 是否在運行。若是正在運行,中止 audioEngine,終止 recognitionRequest 的音頻輸入,禁用 microphoneButton,並將按鈕的文字改成「開始錄音」。

audioEngine 正在工做,則應用應該調用 startRecording(),以及設置按鈕的文字爲「中止錄音」。

太棒了!一切就緒,能夠準備測試一下應用了。首先將該應用佈署到一臺 iOS 10 的設備上,而後點擊「開始錄製」按鈕。來吧,說點什麼試試!

[](http://www.appcoda.com/wp-con...

注意:

  1. Apple 對每臺設備的識別有限制。詳情未知,不過你能夠嘗試聯繫 Apple 得到更多信息。

  2. Apple 對每一個應用的識別也有限制。

  3. 若是你老是遭遇限制,務必聯繫 Apple,他們或許能夠解決這個問題。

  4. 語音識別會消耗很多電量和流量。

  5. 語音識別每次只能持續大概一分鐘。

總結

在本教程中,咱們講解了如何利用 Apple 向開發者開放的新語音 API 來識別語音並將其轉換爲文本,你應該對這些 API 的優勢深有體會並可以合理使用了吧。Speech 框架使用的語音識別框架與 Siri 相同。該 API 雖小,但功能十分強大,爲開發者創造像是獲取音頻文件記錄同樣震撼的東西提供了極大的便利。

我強烈推薦看一看 WWDC 2016 的 session 509,你能夠從中獲取更多信息。我但願你會喜歡這篇文章,更重要的是,能在探索這個全新 API 的過程當中感覺到樂趣。

做爲參考,你能夠訪問 Github 來查看完整項目

本文由 SwiftGG 翻譯組翻譯,已經得到做者翻譯受權,最新文章請訪問 http://swift.gg

相關文章
相關標籤/搜索