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);
*/
}