【ChatGPT】TypeScript + Nuxt3 + Voice Activity Detection(VAD) + FastAPI + GPTで音声認識システムを作ってみる 1
2023-12-21
azblob://2023/12/11/eyecatch/2023-12-21-talk-recognization-system-to-gpt-using-nuxt-and-fastapi-on-azure-000.png

AIの進化は止まらず、特に自然言語処理の領域ではその進化が顕著です。そんな中、OpenAIのChatGPTがそのパフォーマンスで注目を集めています。本記事では、Nuxtと組み合わせて音声認識を行い、ChatGPTとの会話を可能にする方法を解説します。

音声システムを作るための準備

システムは以下の構成を考えています。

構成図

ユーザから音声の入力をWebAPIやVADを用いてフロント側で受け取り、Web socketをもちいて逐次音声をバックエンドに転送します。

バックエンドでは受け取った音声をAzureのSpeech to Textを用いてテキストに書き起こし、AzureのLanguage Under Standingを用いて「~を教えてください」という意図理解を行います。その後それまでのテキストをAzureのGPTに転送し、生成されたテキストをText to Speechで音声に変換します。

前四部構成で、今回のブログではNuxt3のアプリの構築を行っていきます。

Nuxt3のアプリを作成

Nuxt3の公式サイトを参考にして以下のコマンドをたたいていきます。

npx nuxi@latest init <project-name>
cd <project-name>
yarn install
yarn dev

http://localhost:3000 にアクセスし、アプリが立ち上がっているのを確認します。

Voice Activity Detection(VAD)の導入

次に作成したフロントエンドアプリにVoice Activity Detection通称VADを導入します。

VADとは、日本語で「音声区間検出」といい、音声と雑音が含まれる信号から音声が存在する区間とそれ以外の区間を判別する技術です(※1)。

今回では、スムーズな音声での返答を求めるため、ユーザの言葉の区切れを検知した瞬間に、それまでの音声データをバックエンドへ転送します。

では、さっそくVADを導入していきましょう。

試してみた感じ、VAD for Reactは存在していたのですが、Nuxt用のものがなかったためVAD for webを使用していきます。 

npmで公開されている、@ricky0123/vad-webでは、全くうまくいかなかったので(ライブラリの中身を見たりして、格闘しましたが10時間ほどたっても解決できなかったです;;)、Scriptタグにbundleを読み込む形で導入します。

 
nuxt.config.jsのscriptに以下を追加します。
TypeScriptscript:[
                {
                    src: 'https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.js',
                    body: true,
                },
                {
                    src: 'https://cdn.jsdelivr.net/npm/@ricky0123/vad-web@0.0.11/dist/bundle.min.js',
                    body: true,
                }
            ]

ただ、TypeScriptだと型周りのエラーが出るため、@ricky0123/vad-webもinstallしておきます。

yarn add @ricky0123/vad-web

では、アプリを再起動しScriptが読み込まれているか確認してみましょう

console1

しっかりとVADがWindowオブジェクトに読み込まれていますね。

これで、VADの導入は完了です。

次の章では実際にVADを動かして遊んでいきます。

VADを使用して遊んでみる

では、コードを記述していきます。

pageディレクトリを作成し、その中にindex.vueを作成します。

index.vueのscriptタグに以下を記述します。

TypeScript<script setup lang="ts">
import type { MicVAD } from '@ricky0123/vad-web';
import type { SpeechProbabilities } from "@ricky0123/vad-web/dist/_common/models";
import { ref } from 'vue';
const mymicVad = ref<null | MicVAD>(null);

const startVadFunction = async () => {
    if (navigator.mediaDevices && window.vad) {
        mymicVad.value = await window.vad.MicVAD.new({
            positiveSpeechThreshold: 0.8,
            minSpeechFrames: 5,
            preSpeechPadFrames: 10,
            onSpeechStart: () => {
            },
            onFrameProcessed: (probs:SpeechProbabilities) => {
                if (probs.isSpeech > 0.8) {
                    // 音が0.8以上の確率で音声である場合の処理
                }
            },
            onSpeechEnd (arr: Float32Array | undefined) {
                if (!arr) return
                // 音声が終了した場合の処理
            },
            onVADMisfire () {
                // 音声が取得できなかった時の処理
            }
        })
        if (!mymicVad.value) return
        mymicVad.value.start()
}
}
</script>

上のコードの解説をしていきます。

1. 型呼び出しと変数定義

TypeScriptimport type { MicVAD } from '@ricky0123/vad-web';
import type { SpeechProbabilities } from "@ricky0123/vad-web/dist/_common/models";
import { ref } from 'vue';
const mymicVad = ref<null | MicVAD>(null);

TypeScriptでうまく型を扱うために、Vadで使用する型を使用していない@ricky0123/vad-webのライブラリから型をimportします。

importしたMicVADの方を持った変数を定義します。

2. VADを使用するための関数を作成

TypeScriptconst startVadFunction = async () => {


......


}

VADの読み込みが遅いタイミングがあるので、非同期関数にします。

3. VADを初期化

TypeScriptif (navigator.mediaDevices && window.vad) {
        mymicVad.value = await window.vad.MicVAD.new({
            positiveSpeechThreshold: 0.8,
            minSpeechFrames: 5,
            preSpeechPadFrames: 10,
            onSpeechStart: () => {
            },
            onFrameProcessed: (probs:SpeechProbabilities) => {
                if (probs.isSpeech > 0.8) {
                    // 音が0.8以上の確率で音声である場合の処理
                    console.log("聞こえてる!!")
                }
            },
            onSpeechEnd (arr: Float32Array | undefined) {
                if (!arr) return
                // 音声が終了した場合の処理
            },
            onVADMisfire () {
                // 音声が取得できなかった時の処理
            }
        })
        if (!mymicVad.value) return
        mymicVad.value.start()
}

positiveSpeechThreshold: 音声の判定の閾値

minSpeechFrames: 最小音声フレーム

preSpeedPadFrames: 覚えてないですが必要だったはず

onSpeedStart: VADが開始したときのEvent

onFrameProcessed: VADが音声を認識中のEvent

onSpeedEnd: VADが音声を認識しなくなったときのEvent

onVADMisfire: VADが音声をうまく認識できなかった時のEvent

mymicVad.value.start()でVADを起動します。

ここでは記述していないですが、mymicVad.value.pause()でストップします。

声を出してみると、、、聞こえてる!!

声に反応して、Logが出力されましたね!!

以上で、フロントエンド側のVADの導入が完了しました。次回のブログでは音声を取得し、取得した音声データをバックエンドに転送していきます。

次回をお楽しみに!!

1. 雑音環境下で頑健な 音声区間検出技術の開発 https://www.oki.com/jp/otr/2012/n219/pdf/219_r08.pdf 2023/12/08 access