🐸

Next.js+WebSpeechAPIで超簡単音声認識をしてみよう

2023/09/07に公開

WebSpeechAPIを使うことで、新しくPythonのコンテナを作ることも、ライブラリを導入しなくても、 APIを叩くこともなく、無料で簡単に音声認識をすることができます。
この記事では、Web上で簡単に自分のしゃべった内容を認識する方法を、Next.js+WebSpeechAPIを用いて解説します。

WebSpeechAPIとは

https://developer.mozilla.org/ja/docs/Web/API/Web_Speech_API

ウェブ音声 API (Web Speech API) で、音声データをウェブアプリに組み入れることができます。 ウェブ音声 API は、SpeechSynthesis (音声合成、Text-to-Speech)と SpeechRecognition (非同期音声認識、Asynchronous Speech Recognition)の 2 つの部分から成り立っています。

WebSpeechAPIは少し前まではChromeでしか対応していませんでしたが、現在はChromeやEdge、Safariなど主要ブラウザの多くでサポートしています。(互換性リスト)

現在、OpenAIなども音声認識機能を提供していますが、多少金がかかってしまいます。もし認識部分をフロントで完結させるのであれば、WebSpeechAPIを使ってみてもいいのではないでしょうか・

完成例

Repository

https://github.com/imaimai17468/webspeechapi-test

実際に確かめられるサイト

これをこの記事では実際に作ることができます。
https://webspeechapi-test.vercel.app/

環境構築

ただの環境構築なので、既知の方は飛ばしてください。

Next.jsの構築

ディレクトリ名は任意の好きな名前にしてください。
本記事はtestとしています

mkdir test
cd test

Next.js環境を作ります

npx create-next-app . --typescript

上記コマンド実行時、色々設定をしますが好きに設定してください。
私の場合は
✔ Would you like to use App Router? (recommended) … No / Yes
Noに、それ以外はそのままエンターキーを押して次に進みます。
これで基本の環境構築は完了です。以下のコマンドでhttp://localhost:3000/を開くと現状を確認できます。

npm run dev

ファイルを整理

デフォルトの画面をまっさらにするために以下のファイルを修正します。

src/pages/index.tsx
export default function Home() {
  return (
    <main>
      <div>
        <p>テスト</p>
      </div>
    </main>
  );
}
src/styles/globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;

必要なpackageのインストール

TypeScirptで進める関係上、WebSpeechAPIの型定義ファイルをもらってきます。

npm i -D @types/webspeechapi

いざ音声認識

コード全体

下のコードをsrc/pages/index.tsに貼り付けるだけで機能します。簡単ですね。

src/pages/index.ts
import { useState, useEffect } from "react";

export default function Home() {
  const [isRecording, setIsRecording] = useState(false);
  const [text, setText] = useState<string>("");
  const [transcript, setTranscript] = useState<string>("");
  const [recognition, setRecognition] = useState<SpeechRecognition | null>(
    null
  );

  useEffect(() => {
    if (typeof window !== "undefined") {
      const recognition = new webkitSpeechRecognition();
      recognition.lang = "ja-JP";
      recognition.continuous = true;
      recognition.interimResults = true;
      setRecognition(recognition);
    }
  }, []);

  useEffect(() => {
    if (!recognition) return;
    if (isRecording) {
      recognition.start();
    } else {
      recognition.stop();
      setText("");
    }
  }, [isRecording]);

  useEffect(() => {
    if (!recognition) return;
    recognition.onresult = (event) => {
      const results = event.results;
      for (let i = event.resultIndex; i < results.length; i++) {
        if (results[i].isFinal) {
          setText((prevText) => prevText + results[i][0].transcript);
          setTranscript("");
        } else {
          setTranscript(results[i][0].transcript);
        }
      }
    };
  }, [recognition]);

  return (
    <main>
      <button
        onClick={() => {
          setIsRecording((prev) => !prev);
        }}
      >
        {isRecording ? "停止" : "録音開始"}
      </button>
      <div>
        <p>途中経過:{transcript}</p>
        <p>解析:{text}</p>
      </div>
    </main>
  );
}

各部分の解説

必要な状態を宣言

  const [isRecording, setIsRecording] = useState(false);
  const [text, setText] = useState<string>("");
  const [transcript, setTranscript] = useState<string>("");
  const [recognition, setRecognition] = useState<SpeechRecognition | null>(
    null
  );
変数名 説明
isRecording 録音を開始しているかどうか
text 録音済みのテキスト
transcript 録音中のテキスト
recognition 生成したSpeechRecognitionを格納する

SpeechRecognitionを生成

  useEffect(() => {
    if (typeof window !== "undefined") {
      const recognition = new webkitSpeechRecognition();
      recognition.lang = "ja-JP";
      recognition.continuous = true;
      recognition.interimResults = true;
      setRecognition(recognition);
    }
  }, []);

ページの初期読み込み時に、SpeechRecognitionを生成してもろもろ設定してます。
日本語以外の言語もいけちゃいます。

ボタン押下によって録音を開始する

  useEffect(() => {
    if (!recognition) return;
    if (isRecording) {
      recognition.start();
    } else {
      recognition.stop();
      setText("");
    }
  }, [isRecording]);

ボタンを押すとisRecordingのbool値が反転するので、その値によって音声認識のオンオフをしています。本記事ではオフにしたときにtextを削除していますが、これを消すことでずっと認識した内容を保持し続けることができます。

解析結果を取得する

  useEffect(() => {
    if (!recognition) return;
    recognition.onresult = (event) => {
      const results = event.results;
      for (let i = event.resultIndex; i < results.length; i++) {
        if (results[i].isFinal) {
          setText((prevText) => prevText + results[i][0].transcript);
          setTranscript("");
        } else {
          setTranscript(results[i][0].transcript);
        }
      }
    };
  }, [recognition]);

recognition.onresultによって結果を取得することができます。
results[i].isFinaltrueなら認識した一文の最終結果が格納されており、そうでないならその時々の途中の認識結果が格納されています。

結果を表示する

  return (
    <main>
      <button
        onClick={() => {
          setIsRecording((prev) => !prev);
        }}
      >
        {isRecording ? "停止" : "録音開始"}
      </button>
      <div>
        <p>途中経過:{transcript}</p>
        <p>解析:{text}</p>
      </div>
    </main>
  );

アルティメットガバガバデザインです。
あとからみなさんの方でCSSバチクソに決めてもらうためにめちゃシンプルにしました。
buttonタグでボタンを生成し、クリックするとisRecordingが切り替わります。
あとは途中結果と解析結果を表示して完成です。

精度

音声認識の精度は非常に高いと思います。と言うのもWebSpeechAPIは音声情報をGoogleのCloud Speech-to-Textに送って音声認識をしてルタめ最高水準の精度で認識できていると思います(参考)。
細かいカスタマイズはできないので、専門的な用語や漢字の誤変換等がたまにみられることがありますが、一般的な利用では最適ではないでしょうか。

注意点

まずfirefoxなど一部のブラウザに対応してないので、ちゃんと使うなら前置きがいりそうです。
また、少し離れた(1.5mくらい)場所での会話、生声ではなく機器越しの音声ではあんまりいい精度は出ないような印象でした。そのため、本格的にアプリケーションに組み込むのは慎重になる必要がありそうです。

まとめと宣伝

とりあえず音声認識機能が欲しかったり、趣味開発で導入したい場合はめっちゃ楽に組み込むことができるのでおすすめです。
宣伝ですが、音を認識して波形を生成するパッケージを作りました。React環境で簡単に導入することができます。ぜひ使ってみてください。
https://github.com/imaimai17468/ts-audio-visualizer
https://www.npmjs.com/package/ts-audio-visualizer?activeTab=readme

Discussion