オリジナルのスマートスピーカーを作ってみる 3 音声ファイル合成
この記事は?
この記事は オリジナルのスマートスピーカーを作ってみる シリーズの続き「その3」です。
音声合成 API をコールするプログラムを作成する
VOICEVOX ENGINE の API をコールするプログラムを作ります。
書き慣れた C# で作ろうと思います。
開発環境
開発マシン OS | Windows 11 (x64) |
アプリケーションプラットフォーム | .NET 8 |
言語 | C# 12 |
IDE | Visual Studio Community 2022 |
アプリケーションサーバー
製品名 | Raspberry Pi 5 |
OS | Raspberry Pi OS (ベースは Debian GNU/Linux 12 (bookworm)) |
CPU アーキテクチャ | aarch64/arm64 |
メモリ | 8GB |
アプリケーションのプロジェクトはコンソールアプリを選択しました。
実行ファイルの発行方法
ラズパイ向けの実行ファイルの発行方法は、以下ドキュメントを参考にしました。
今回、実行ファイルの形式は SCD(自己完結型。実行環境に.net のインストールは必要ない)を取りました。
発行するときのコマンドは以下のようになりました。
#ラズパイ向けの実行ファイルを発行する
dotnet publish -c Release -r linux-arm64 --self-contained true
hello world プログラムをラズパイでテスト
実行ファイルをラズパイにコピペして、hello world プログラムを実行しました。
実行方法は以下ドキュメントを参考にし、無事動作することを確認しました。
実行のコマンドは以下のようになりました。
$ chmod +x <実行ファイル名>
$ ./<実行ファイル名>
Hello, World!
ラズパイ(arm64/aarch64)向けリリースビルドが無いことが判明
VOICEVOX ENGINE の VOICEVOX ENGINE をラズパイで動かそうと思ったら、 arm64/aarch64 向けリリースビルドが無いことが判明しました。 自分でビルドするのも大変そうなので、改めてほかの読み上げツールを検討しましたが、無料でここまでの品質は他に無さそうです。
とりあえず開発用PC(Windows 11 NVIDIA GPU 搭載)を VOICEVOX ENGINE サーバーとすることにしました。今後どうしてもラズパイ上でやりたくなったら、VOICEVOX CORE で自分で API を作ろうと思います。
(そもそもラズパイ上で voicevox 動かすのはキツイ説もある)
追記
Mac OS 向けがありましたので、それを選べばOKです。
ラズパイ to VOICEVOX ENGINE サーバーの開通を行う
VOICEVOX ENGINE はデフォルトではローカルホストからのアクセスしか受け付けないセキュリティ設定になっているので、それを変更する
- VOICEVOX ENGINE を起動後、 http://127.0.0.1:50021/setting にアクセスし、ラズパイからアクセスできるように設定の変更を行う。
- VOICEVOX ENGINE を再起動し、コマンドライン引数(--host <hostname or ip> --port <port>)で任意の IP とポートで公開する。
ラズパイから nmap コマンドを使って開通 (STATE: open) を確認できました。
$ nmap -P0 -p <設定したポート番号> <設定した IP>
Starting Nmap 7.93 ( https://nmap.org ) at 2024-03-12 15:34 JST
Nmap scan report for <サーバー名> (<設定した IP>)
Host is up (0.0093s latency).
PORT STATE SERVICE
<設定したポート番号>/tcp open unknown
Nmap done: 1 IP address (1 host up) scanned in 0.06 seconds
ラズパイから音声合成 API をコールするプログラム
音声ファイルを合成するプログラムを作成しました。
成果物としては .wav のオーディオファイルとなります。
以下はそのプログラムです。
using NLog;
internal class Program
{
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
static async Task Main(string[] args)
{
try
{
logger.Info("Application starts.");
var voiceText = "これはテストです。";
await VoiceFile.Synthesize_and_SaveFile(voiceText);
logger.Info("Application closed.");
}
catch (Exception ex)
{
logger.Error(ex);
}
}
}
internal class VoiceFile
{
public async static Task Synthesize_and_SaveFile(string voiceText)
{
var audioQuery = await AudioQueryAPICaller.Call(voiceText);
var byteArray = await SynthesisAPICaller.Call(audioQuery);
File.WriteAllBytes("./this_is_test.wav", byteArray);
}
}
internal class AudioQueryAPICaller : APICallerBase
{
private const string path = "audio_query";
public static async Task<string> Call(string voiceText)
{
var uRL = MakeURL(voiceText);
using var jsonContent = NewJsonContent("");
using var response = await client.PostAsync(uRL, jsonContent);
response.EnsureSuccessStatusCode();
var audioQuery = await response.Content.ReadAsStringAsync();
logger.Info(audioQuery);
return audioQuery;
}
private static string MakeURL(string voiceText)
{
var uRL = $"{voicevoxBaseURL}/{path}?text={voiceText}&speaker=0";
return uRL;
}
}
internal class SynthesisAPICaller : APICallerBase
{
private const string path = "synthesis";
public static async Task<byte[]> Call(string audioQuery)
{
var uRL = MakeURL();
using var jsonContent = NewJsonContent(audioQuery);
using var response = await client.PostAsync(uRL, jsonContent);
response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsByteArrayAsync();
return responseContent;
}
private static string MakeURL()
{
var uRL = $"{voicevoxBaseURL}/{path}?speaker=0";
return uRL;
}
}
using NLog;
using System.Text;
internal abstract class APICallerBase
{
protected static readonly HttpClient client = new HttpClient();
protected static readonly Logger logger = LogManager.GetCurrentClassLogger();
private static readonly AppSettingsModel appSettings = AppSettingsFetcher.Fetch();
protected static string voicevoxBaseURL = $"http://{appSettings.voicevox_engine_ip}:{appSettings.voicevox_engine_port}";
protected static StringContent NewJsonContent(string jsonText) => new(jsonText, Encoding.UTF8, "application/json");
}
Discussion