llm_exttts - nyasu3w/StackFlow GitHub Wiki
llm_extttsとは
llm_melottsのソースからmelottsの実装を取っ払って、代わりにopen_jtalkの起動(shell経由)を詰めたもの。 日本語でしゃべることが出来る。
インストール
ビルドと導入
本家では無く、llm_extttsの入ったここのプロジェクトのdev_nブランチをビルドしてllm_extttsを作成する。これをModuleLLMの、/opt/m5stack/binに配置する。 ビルドは、Linux(WSLでOK) ここに書いてあるとおり。 できあがりは、distディレクトリにある。つまり、dist/llm_extttsをModuleLLMの/opt/m5stack/binにコピーすることになる。
当方で作った物が、dev_n_binブランチのdistrフォルダに置いてあったりもするが、めんどくさいとここは更新されないので注意
試しにdebを作ってみました。llm-exttts0.0.1 apt upgradeが動く環境でdpkg -i llm-exttts-0.0.1-arm64.deb
。
なお、本家StackFlowに何らかの形で取り込まれる可能性があります(2025/2/1現在)
open_jtalk 等の導入
open_jtalkを呼び出しているのでそれをインストールする必要がある。また、出力のフォーマット変更にsoxを使うのでそれもインストールする必要がある。 ModuleLLMがデバッグボードなど経由でネットワークに接続しているのなら、
apt install open-jtalk open-jtalk-mecab-naist-jdic hts-voice-nitech-jp-atr503-m001 sox
ネットワーク接続していない状態では、依存パッケージの多いsoxのインストールが面倒だと思われる。 soxの役割は、モノラルの音声をステレオに増倍しているだけなので、同じことがもっと楽に出来るならソースの コマンドライン 書き換えで何とかなるでしょう。
ModuleLLMで自動起動
自動起動するためには、systemdのサービスを作成する。
- /lib/systemd/system などに、llm-exttts.service というファイルを作成し、中身を以下のようにする。
[Unit]
Description=llm-exttts Service
After=llm-sys.service
Requires=llm-sys.service
[Service]
Environment = LD_LIBRARY_PATH=/usr/local/lib:/usr/lib:/opt/lib:/opt/usr/lib:/soc/lib
ExecStart=/opt/m5stack/bin/llm_exttts
WorkingDirectory=/opt/m5stack
Restart=always
RestartSec=1
StartLimitInterval=0
[Install]
WantedBy=multi-user.target
- サービスの有効化 作成したサービスを有効化するため、
systemctl enable llm_exttts
を実施する。これで、次回起動時からllm_extttsが自動起動する。 すぐ起動する場合は
systemctl start llm_exttts
利用法
Arduinoからの利用
Arduino側のライブラリの対応は無い。M5Module-LLMライブラリを導入した状態で、以下のようにしてllm-llmと繋ぐと、qwenやllamaなどのLLMの出力をllm-llm経由でTTSでしゃべるようになる。与えるllm_work_idは、 llm_work_id = module_llm.llm.setup(config);
で得られるものである。
void tts_setup(String llm_work_id){
String cmd;
{
JsonDocument doc;
doc["request_id"] = "exttts_setup";
doc["work_id"] = "exttts";
doc["action"] = "setup";
doc["object"] = "exttts.setup";
doc["data"]["model"] = "n/a";
doc["data"]["response_format"] = "tts.wav";
doc["data"]["enoutput"] = false;
doc["data"]["cmdtype"] = "open_jtalk";
// doc["data"]["cmdparam"] = "voice2";
doc["data"]["speed"] = 1.3; // default: 1.0
doc["data"]["volume"] = -30.0; // default 0.0 (db)
doc["data"]["input"] = llm_work_id;
serializeJson(doc, cmd);
}
String work_id;
module_llm.msg.sendCmdAndWaitToTakeMsg(
cmd.c_str(), "exttts_setup",
[&work_id](m5_module_llm::ResponseMsg_t& msg) {
// Copy work id
work_id = msg.work_id;
},
5000);
tts_work_id = work_id;
M5.Display.printf(">>link llm:%s, tts: %s\n", llm_work_id, tts_work_id.c_str());
}
KWS-VAD-Whisper-LLM-exttts と繋いで、ウエイクワードから日本語の会話を一往復するサンプルコードとしては、これなど。
声の追加・変更
open_jtalkのデフォルトの音声ではない他の音声を利用することが出来る。今のところソースの書き換えを伴うが、
ここのように追加音声のパスを定義した後、このようなif文を追加する。Arduino側からは、if文で定義した名前を上記サンプルtts_setup()のコメントアウトされた行のように、doc["data"]["cmdparam"] = "voice2";
と指定することで、その音声を使うことになる。
音声合成OpenJTalkをインストールする(ボイス70種)](https://ytyaru.hatenablog.com/entry/2022/05/22/000000) が参考になるかも
2025/2/1時点のソースは、tohoku-f01-neutralが導入された状態になっているので、ソースの通りそれが/usr/share/hts-voice/にあるなら、cmdparamにvoice2を指定するとこれでしゃべる。
速度、音量の変更
上記サンプルでも設定している、speedとvolumeパラメータを変更する。
doc["data"]["speed"] = 1.3; // default: 1.0
doc["data"]["volume"] = -30.0; // default 0.0 (db)
speedはデフォルト速度が1.0で、数字を大きくすると口調が早くなる。volumeは単位がdBなので、数字が大きいほど音が大きくなり、0でデフォルト、負数で音が小さくなる。
今後やりたいこと
やるかどうかは分かりません。出来るかどうかも分かりません
- 再生の途中停止 →できた
- llmと繋ぐのではなくて別ルート(Arduino側からシリアルやport10001など)でしゃべる (melottsから持ってきてるかも?) →stop/resumeと同様に出来そう
再生の途中停止
extttsにrpcの口を定義する。コンストラクタ
llm_tts() : StackFlow("exttts")
{
task_count_ = 1;
rpc_ctx_->register_rpc_action("stop",
std::bind(&llm_tts::stop, this, std::placeholders::_1, std::placeholders::_2));
rpc_ctx_->register_rpc_action("resume",
std::bind(&llm_tts::resume, this, std::placeholders::_1, std::placeholders::_2));
}
コンストラクタで登録したメンバ関数
std::string stop(pzmq *_pzmq, const std::string &rawdata)
{
SLOGI("llm_tts::stop");
stopping=true;
return LLM_NONE;
}
std::string resume(pzmq *_pzmq, const std::string &rawdata)
{
SLOGI("llm_tts::resume");
stopping=false;
return LLM_NONE;
}
Arduino側からキックするコード。cmdがstopまたはresume
void send_command_to_tts(const char* cmd)
{
String jsonstr;
M5.Display.setTextColor(TFT_WHITE);
M5.Display.printf("\n>>> %s\n",cmd);
{
JsonDocument doc;
doc["request_id"] = "tts_command";
doc["work_id"] = tts_work_id;
doc["action"] = cmd;
serializeJson(doc, jsonstr);
}
module_llm.msg.sendCmd(jsonstr.c_str());
/* rpc先がjsonを返す(send()をreturnの前に呼ぶ)ならこっちかも
module_llm.msg.sendCmdAndWaitToTakeMsg(
jsonstr.c_str(), "tts_command", [](m5_module_llm::ResponseMsg_t& msg) {}, 5000);
*/
}