[ゆっくり] AquesTalk2をRustから使う

2024.02.19

Introduction

このあいだ、たまたま自分が昔(10年前)書いた記事をみたらAquesTalk2をつかってなんかやってました。

このときはEC2でCのプログラムコードを使っていたのですが、
今回はローカル(OrbStack)環境で、AquestTalk2のライブラリを
Rustからつかってみます。

※今回もAquestTalk2の評価版を使っています。
評価版では「ナ行、マ行」が「ぬ」になります。
製品版を使用するにはライセンスが必要なので、詳しくはこちらをご確認ください。

Environments

  • MacBook Pro (13-inch, M1, 2020)
  • OS : MacOS 14.3.1
  • OrbStack : 1.4.3

Setup

まずはOrbStackでLinux仮想環境を作成します。
Ubuntuを選択し、CPU TypeはIntel(Appleだと動かなかった)を選択して
起動しましょう。
SSHでログインして必要なパッケージをインストールします。

% sudo apt update
% sudo apt install unzip
% sudo apt install -y build-essential
% sudo apt install libclang-dev

AquesTalk2をダウンロードして、lib64ディレクトリにあるlibAquesTalk2Evaの
リンクを作成しておきます。

% curl -o aqtk2-lnx-eva_230.zip https://www.a-quest.com/archive/package/aqtk2-lnx-eva_230.zip
% unzip aqtk2-lnx-eva_230.zip
% cd aqtk2-lnx-eva_230/lib64
% ln -s libAquesTalk2Eva.so.2.3 libAquesTalk2.so.2
% ln -s libAquesTalk2Eva.so.2.3 libAquesTalk2.so

$ ls -l /path/your/aqtk2-lnx-eva/lib64/
AquesTalk2.h
libAquesTalk2.so -> libAquesTalk2Eva.so.2.3
libAquesTalk2.so.2 -> libAquesTalk2Eva.so.2.3
libAquesTalk2Eva.so.2.3

Rust環境をインストールしてCargoでプロジェクトを作成しましょう。

% curl https://sh.rustup.rs -sSf | sh
・・・
% cargo new yukkuri

Try Yukkuri

プロジェクトを作成したので、
Aquestalk2ライブラリをRustから使ってみます。
まずはCargo.tomlに依存ライブラリを記述します。

[dependencies]
libc = "0.2.153"

[build-dependencies]
bindgen = "0.59.1"
cc = "1.0"

次に、プロジェクトのルートにbuild.rsを作成します。
build.rsではビルド実行時にbindgenでAquesTalk2の
Rustバインディングを生成します。
このファイルはbindings.rsという名前で指定した場所へ出力されます。

extern crate bindgen;

use std::env;
use std::path::PathBuf;

fn main() {
    // Aquestal2のライブラリパス
    let lib_path = "/path/your/aqtk2-lnx-eva/lib64";
    // AquesTalk2のheader file
    let header_path = format!("{}/AquesTalk2.h", lib_path);

    // generate bindings.rs
    let bindings = bindgen::Builder::default()
        .header(&header_path)
        .generate()
        .expect("Unable to generate bindings");

    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings!");

    // AquesTalk2のライブラリのパスを指定
    println!("cargo:rustc-link-search=native={}", lib_path);
    // ライブラリ名を指定
    println!("cargo:rustc-link-lib=AquesTalk2");
}

cargo buildすると、debugディレクトリの下にbindings.rsが生成されます。
ではsrc/main.rsで生成されたファイルをつかってみましょう。
main.rsでは指定した文字列をCスタイルの文字列に変換し、音声合成を行います。
wavデータを取得したらファイルに出力して処理は終了します。

use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::ffi::CString;
use std::os::raw::c_char;


include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

fn main() {
    // 生成する音声
    let input_text = "こんにちわ。ひさしぶりのゆっくりボイスネタです";
    let c_input_text = CString::new(input_text).expect("CString::new failed");
    let c_input_ptr = c_input_text.as_ptr() as *const c_char;

    // 音声合成の速度
    let speed = 100;
    // 音声データのサイズ
    let mut size = 0;

    // WAVデータ取得
    let wav_ptr = unsafe {
        AquesTalk2_Synthe_Utf8(c_input_ptr, speed, &mut size, std::ptr::null_mut())
    };

    if !wav_ptr.is_null() {
        println!("output wav file.");
        let wav_slice = unsafe { std::slice::from_raw_parts(wav_ptr, size as usize) };
        //wavファイルとして出力
        let output_path = Path::new("output.wav");
        let mut file = File::create(&output_path).expect("Failed to create WAV file");
        file.write_all(wav_slice).expect("Failed to write WAV data to file");
        file.flush().expect("Failed to flush WAV file");

        unsafe {
            AquesTalk2_FreeWave(wav_ptr);
        }
    } else {
        eprintln!("ERR:{}", size);
    }
}

cargo runでプログラムを実行すると、wavファイルが生成されています。
再生してみるとおなじみのあの声で音声が再生されます。

Summary

今回はbindgenを使ってAquesTalk2をRustから使ってみました。
自動でインターフェイスを生成してそのままRustから使えるので便利です。

References