TadaoYamaokaの開発日記

個人開発しているスマホアプリや将棋AIの開発ネタを中心に書いていきます。

PCで再生中の音声をWhisperでリアルタイムに文字起こしする

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つのセンテンスを含むくらいの長さの音声が必要である。
リアルタイムで文字起こしする場合、数秒間隔ごとに音声を区切って処理を行う。

音声を単語の途中で区切ると、誤認識するため、できるだけ無音の区間で区切る方がよい。
そこで、バッファリングした音声の末尾あたりで、無音の区間を探し、その位置で区切るようにする。
区切った残りの音声は、次の処理の先頭に結合するようにする。

無音の区間の探し方は、バッファリングした音声の後半4/5の区間で、音圧の移動平均の最小値の位置とした。

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でサーバに送信して、サーバ側で文字起こしする方法についても記載した。