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から使えるので便利です。