PCで再生中の音声をリアルタイムで文字起こしできると、字幕機能がない動画の再生や、外国とのオンライン会議で便利である。
先日、OpenAIが公開したWhisperは、音声ファイルから文字起こしするするツールが提供されているが、リアルタイムで処理するツールは提供されていない。
そこで、Pythonスクリプトで、リアルタイムで文字起こしするツールを作成した。
ループバック録音
SoundCardを使うと、PCで再生されている音声を録音することができる。
pip install SoundCard
でインストールする。
import soundcard as sc with sc.get_microphone(id=str(sc.default_speaker().name), include_loopback=True).recorder(samplerate=SAMPLE_RATE, channels=1) as mic: while True: data = mic.record(BUFFER_SIZE)
のようにして処理を行う。
メインスレッド以外では動作しないので、注意が必要である。
リアルタイム文字起こし
Whisperで音声を認識するには、1つのセンテンスを含むくらいの長さの音声が必要である。
リアルタイムで文字起こしする場合、数秒間隔ごとに音声を区切って処理を行う。
音声を単語の途中で区切ると、誤認識するため、できるだけ無音の区間で区切る方がよい。
そこで、バッファリングした音声の末尾あたりで、無音の区間を探し、その位置で区切るようにする。
区切った残りの音声は、次の処理の先頭に結合するようにする。
Whisperの処理
Whisperで音声を文字列に変換する処理は、READMEにサンプルコードがあるので、ほぼそのまま流用できる。
float32の音声をwhisper.pad_or_trimでパディングして、whisper.log_mel_spectrogramでログメルスペクトログラムに変換し、whisper.decodeでテキストに変換する。
言語の認識は、model.detect_languageで行える。
録音と文字起こしの非同期処理
文字起こしが間に合わない可能性があるため、録音処理と文字起こしの処理は非同期に行う。
録音処理では音声を処理単位に区切って、queueに追加し、文字起こしの処理は別スレッドで、queueから取り出して処理を行う。
以上の処理を実装したコードは、以下の通りである。
LoopbackWhisper.py
import whisper import soundcard as sc import threading import queue import numpy as np import argparse SAMPLE_RATE = 16000 INTERVAL = 3 BUFFER_SIZE = 4096 parser = argparse.ArgumentParser() parser.add_argument('--model', default='base') args = parser.parse_args() print('Loading model...') model = whisper.load_model(args.model) print('Done') q = queue.Queue() b = np.ones(100) / 100 options = whisper.DecodingOptions() def recognize(): while True: audio = q.get() if (audio ** 2).max() > 0.001: audio = whisper.pad_or_trim(audio) # make log-Mel spectrogram and move to the same device as the model mel = whisper.log_mel_spectrogram(audio).to(model.device) # detect the spoken language _, probs = model.detect_language(mel) # decode the audio result = whisper.decode(model, mel, options) # print the recognized text print(f'{max(probs, key=probs.get)}: {result.text}') th_recognize = threading.Thread(target=recognize, daemon=True) th_recognize.start() # start recording with sc.get_microphone(id=str(sc.default_speaker().name), include_loopback=True).recorder(samplerate=SAMPLE_RATE, channels=1) as mic: audio = np.empty(SAMPLE_RATE * INTERVAL + BUFFER_SIZE, dtype=np.float32) n = 0 while True: while n < SAMPLE_RATE * INTERVAL: data = mic.record(BUFFER_SIZE) audio[n:n+len(data)] = data.reshape(-1) n += len(data) # find silent periods m = n * 4 // 5 vol = np.convolve(audio[m:n] ** 2, b, 'same') m += vol.argmin() q.put(audio[:m]) audio_prev = audio audio = np.empty(SAMPLE_RATE * INTERVAL + BUFFER_SIZE, dtype=np.float32) audio[:n-m] = audio_prev[m:n] n = n-m
※2022/10/16追記:Ctrl+Cで終了させるため、daemon=Trueを追加した
GitHub: GitHub - TadaoYamaoka/LoopbackWhisper
実行例
コマンドラインからスクリプトを実行し、PCで音声を再生すると、認識されたテキストが表示される。
D:\src\LoopbackWhisper>python LoopbackWhisper.py Loading model... Done en: And so my fellow Americans en: Ask not. en: What your country can do for you? en: Ask what you can do for your country.
使用するモデルを変更する場合は、--modelオプションで指定する。
>python LoopbackWhisper.py --model large
サーバで文字起こしする
PCにGPUがない場合は、処理負荷が大きくなるため、オンライン会議などでは支障がでる可能性がある。
その場合は、PCでは録音のみ行い、音声をサーバにリアルタイムに送信して、サーバで文字起こしするとよい。
PCにはPythonの実行環境がない場合もあるので、C#で録音処理を行い、Socket通信でサーバで文字起こしするツールも作成した。
GitHub - TadaoYamaoka/StreamingWhisper
まとめ
PCで再生中の音声をWhisperでリアルタイムに文字起こしする方法について記述した。
リアルタイム処理するために、音声を単語の途中で区切らないようにすることと、録音と文字起こしを非同期で処理することを考慮して実装した。
また、PCの処理負荷をかけないために、音声をSocketでサーバに送信して、サーバ側で文字起こしする方法についても記載した。