概要
この記事では voicevox で生成された音声を python を使い zello に送る方法を記載しています。また、同様の方法を voiceroid や discord.py に応用できます。
個人の趣味の範疇で利用してください
ポイント
- zello の API では opus と呼ばれる Audio Codec を利用します。
- この記事では windows で実験しています。
* この記事のための一部コマンドの実行結果例は macos から貼っています。
事前準備
zello developer の登録
- zello console からアカウントの登録を行い、開発用の API キーを発行します。
- この開発用の API キーは3ヶ月有効です。
opus-tools のインストール
- opus-tools をダウンロードします。
- ダウンロードした zip ファイルを展開, フォルダ内に
opusenc
のバイナリ (*.exe) があることを確認します。 - 環境変数にこのフォルダ (
opusenc
) へのパスを設定します。 -
powershell
を開き、次のコマンドが通ることを確認します。
// TODO 差し替える
> opusenc --version
opusenc opus-tools 0.1.9 (using libopus 1.1)
Copyright (C) 2008-2013 Xiph.Org Foundation
sox のインストール
- sox は Audio を扱う十徳ナイフです。
-
sox.exe
へのパスを環境変数に設定します。 -
powershell
を開き、次のコマンドが通ることを確認します。
// TODO: windows 版で差し替える
sox --version
sox: SoX v14.4.2
voicevox の起動
voicevox をダウンロードします。ダウンロードしたファイルを展開後にフォルダ内の run.exe
(voicevox.exe
) で合成音声エンジンの機能を提供する web API アプリケーションを起動します。このアプリケーションは http://localhost:50021
で web サーバーを起動しています。
合成音声の送信テスト方法
- example コードを github にアップロードしています。
-
demo_on_zello.py で動作確認ができます。
- 環境変数
ZELLO_AUTH_TOKEN
に zello console から発行した API キーをセット - 実行時引数の
username
,password
,channel
には zello で普段利用しているアカウントとチャンネルを設定
- 環境変数
- 別のデバイス、Zello アカウントからテキストメッセージをチャンネルに送ると合成音声が再生されます。
実装上のポイント
- zello の API 仕様はこちらを参照します。
- audio は 16bit 48k の 1ch として 20ms の opus packet を送ります
- codec header でこれ以外を設定しても正しく再生されませんでした。
-
opusenc
の振る舞いも怪しいためsox
でデータを整えています
- voicevox は 16bit 符号あり 24k の 1ch の PCM らしいので、
sox
で 48k に直しています。 - voiceroid の場合は このような変換 でうまくいきます。
Raw PCM と opus エンコーディング後のファイルサイズの違い
マイク入力を扱うための備忘録のついでにマイク音源を保存した raw pcm と opus encoding 後のファイルサイズを残します。raw pcm のファイルサイズ 960k に対して opus encoding 後は 82.9k ほどに圧縮されていることがわかります。
import pyaudio
import subprocess
from subprocess import PIPE
if __name__ == '__main__':
audio = pyaudio.PyAudio()
sampling_rate = 48000
stream = audio.open(format=pyaudio.paInt16, channels=1, rate=sampling_rate,
input=True, input_device_index=1, frames_per_buffer=int(48000/12))
chunk_size_in_seconds = 0.2 # 200ms
number_of_frames_per_chunk = int(sampling_rate * chunk_size_in_seconds)
record_time_in_seconds = 10
number_of_chunks = int((1 / chunk_size_in_seconds) * record_time_in_seconds)
frames = b''
for chunk_id in range(number_of_chunks):
# NOTE: wait for `chunk_size_in_seconds`.
frames += stream.read(num_frames=number_of_frames_per_chunk)
command = 'sox -b 16 -e signed -c 1 -r 48000 -t raw - -b 16 -e signed -c 1 -r 48000 -t wav - | opusenc --quiet - -'
p = subprocess.Popen(command, stdin=PIPE, stdout=PIPE, shell=True)
opus_data = p.communicate(frames)[0]
with open('sample.opus', 'wb') as f:
f.write(opus_data)
sample.opus:
File Size: 82.9k Bit Rate: 66.3k
Encoding: Opus
Channels: 1 @ 16-bit
Samplerate: 48000Hz
Replaygain: off
Duration: 00:00:10.00
In:100% 00:00:10.00 [00:00:00.00] Out:480k [ | ] Clip:0
Done.
[app]$play sample.wav
sample.wav:
File Size: 960k
Encoding: Signed PCM
Channels: 1 @ 16-bit
Samplerate: 48000Hz
Replaygain: off
Duration: unknown
In:0.00% 00:00:10.00 [00:00:00.00] Out:480k [ | ] Clip:0
Done.
余談
- windows <=> linux 間で wav ファイルを直接やりとりせず、numpy.ndarray で変換した方がよい。
- エンディアンの問題で再生できない
- discord に投稿すると音が小さい
-
discord.py
の FFmpegOpusAudio を始め使おうとしたが上手くいかない。- これが外部コマンド
ffmpeg
を使うが、ffmpeg
のコマンド実行結果がエラーになる - このため demo_on_discord.py では
PCMAudio
を使っている。
- これが外部コマンド