質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.03%

UnityのネイティブプラグインでIOSのSpeech APIを使用する

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 703

monomac

score 18

UnityネイティブプラグインでIOSの音声認識APIを使ったアプリを考えております。

APIのサンプルコードがあったためなんとか音声認識を使えるところまでは出来たのですが、今のままでは、画面の認識停止(Stop)を押すまでずっと認識し続けてしまうため
「ユーザーの声が無くなって○秒立ったら音声認識を停止!」

みたいな形の仕様にしたいと考えているのですが、どういった方法だと可能でしょうか?

プログラムはSwift3で書いています。
Swiftについては全くの初心者なためどなたかお力をお貸しください。

import Foundation
import Speech

public class SpeechRecognizer : NSObject {
    static let sharedInstance: SpeechRecognizer = SpeechRecognizer()
    private var _unitySendMessageGameObjectName: String = "SpeechRecognizer"
    var unitySendMessageGameObjectName: String {
        get {
            return _unitySendMessageGameObjectName
        }
        set {
            _unitySendMessageGameObjectName = newValue
        }
    }

    private let speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "ja-JP"))!
    private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?
    private var recognitionTask: SFSpeechRecognitionTask?
    private let audioEngine = AVAudioEngine()
    private override init() {
        super.init()
        speechRecognizer.delegate = self
    }

    func requestRecognizerAuthorization() {
        SFSpeechRecognizer.requestAuthorization { authStatus in
            OperationQueue.main.addOperation {
                switch authStatus {
                case .authorized:
                    self.unitySendMessage("OnAuthorized")
                    break
                case .denied:
                    self.unitySendMessage("OnUnauthorized")
                case .restricted:
                    self.unitySendMessage("OnUnauthorized")
                case .notDetermined:
                    self.unitySendMessage("OnUnauthorized")
                }
            }
        }
    }

    func startRecord() -> Bool {
        if audioEngine.isRunning {
            return false
        }
        try! startRecording()
        return true
    }

    func stopRecord() -> Bool {
        if !audioEngine.isRunning {
            return false
        }
        audioEngine.stop()
        recognitionRequest?.endAudio()
        return true
    }

    private func startRecording() throws {
        refreshTask()
        let audioSession = AVAudioSession.sharedInstance()
        try audioSession.setCategory(AVAudioSessionCategoryRecord)
        try audioSession.setMode(AVAudioSessionModeMeasurement)
        try audioSession.setActive(true, with: .notifyOthersOnDeactivation)

        recognitionRequest = SFSpeechAudioBufferRecognitionRequest()

        guard let inputNode = audioEngine.inputNode else { fatalError("Audio engine has no input node") }
        guard let recognitionRequest = recognitionRequest else { fatalError("Unable to created a SFSpeechAudioBufferRecognitionRequest object") }

        recognitionRequest.shouldReportPartialResults = true   
        recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest) { result, error in
            var isFinal = false

            if let result = result {
                self.unitySendMessage("OnRecognized", message: result.bestTranscription.formattedString)
                isFinal = result.isFinal
            }

            if error != nil || isFinal {
                self.audioEngine.stop()
                inputNode.removeTap(onBus: 0)

                self.recognitionRequest = nil
                self.recognitionTask = nil
                self.unitySendMessage("OnError", message: error.debugDescription)
            }
        }

        let recordingFormat = inputNode.outputFormat(forBus: 0)
        inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer: AVAudioPCMBuffer, when: AVAudioTime) in
            self.recognitionRequest?.append(buffer)
        }
        try startAudioEngine()
    }

    private func refreshTask() {
        if let recognitionTask = recognitionTask {
            recognitionTask.cancel()
            self.recognitionTask = nil
        }
    }

    private func startAudioEngine() throws {
        audioEngine.prepare()
        try audioEngine.start()
    }

    func unitySendMessage(_ methodName: String, message: String = "") {
        UnitySendMessage(self.unitySendMessageGameObjectName, methodName, message)
    }
}

extension SpeechRecognizer: SFSpeechRecognizerDelegate {
    public func speechRecognizer(_ speechRecognizer: SFSpeechRecognizer, availabilityDidChange available: Bool) {
        if (available) {
            unitySendMessage("OnAvailable")
        } else {
            unitySendMessage("OnUnavailable")
        }
    }
}

追記

ネット上で検索していたところ、
https://stackoverflow.com/questions/45111072/swift-stop-speech-recognition-after-x-seconds-of-silence#
上記のサイトで同じ内容の記事を見つけたのですが、コードの内容が理解できず困っています。

recognitionTask = speechRecognizer?.recognitionTask(with: recognitionRequest, resultHandler: { (result, error) in

        var isFinal = false

        if result != nil {
            self.inputTextView.text = result?.bestTranscription.formattedString
            isFinal = (result?.isFinal)!
        }
        if let timer = self.detectionTimer, timer.isValid {
            if isFinal {
                self.inputTextView.text = ""
                self.textViewDidChange(self.inputTextView)
                self.detectionTimer?.invalidate()
            }
        } else {
            self.detectionTimer = Timer.scheduledTimer(withTimeInterval: 1.5, repeats: false, block: { (timer) in
                self.handleSend()
                isFinal = true
                timer.invalidate()
            })
        }
    })


上記のコードの中の

self.textViewDidChange(self.inputTextView)


このtextViewDidChangeについて、公式HPの説明を読んでもどういう意味なのか理解できず困っています。

https://developer.apple.com/documentation/uikit/uitextviewdelegate/1618599-textviewdidchange

どなたか教えていただけると助かります。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

0

Unity側で「最後に認識結果が返って来た時刻」と「現在時刻」を比較して、
規定時間以上だったらSwift側の停止メソッドを呼び出せばいいのでは?

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/10/20 14:24

    回答有り難うございます。質問内容に記載していませんでしたが、アプリの仕様上、Unity側からではなくSwiftにコードを追加して解決する方法を探しています。
    説明不足で大変申し訳ありません。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.03%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる