Xamarin.Androidでゆっくりボイスを喋らせてみた( *´艸`)
ごあいさつ
先週は頭に霧がかかったみたいにずっとモヤモヤしていたのと吐き気で物が食べられなくて大変でしたが、金曜日にとあるエンジニアの方々とお話しする機会がありまして色々と意見をもらえた事と、私自身が現状の何が嫌なのかがハッキリしてとてもスッキリしたことが相まって調子が回復いたしました。
ですのでおうちプログラムをしてみました。
きっかけ
この間Xamarin Dev Daysの課題アプリで音声読み上げがあったのですが、日本で音声読み上げといえば・・・
ゆっくりしていってね!!!
ゆっくりボイス
ゆっくりしていってね!!!
でおなじみゆっくりボイスは
softalkという音声読み上げソフトが読み上げる独特な音声です。
SoftalkはAquesTalkというライブラリを使用して作られています。
このAquesTalk Android用ライブラリ、iOS用ライブラリがあるんです。
ということは~~
なんとかすればXamarinでも使えるのではないでしょうか?
ということで まずはAndroidでゆっくりボイスをしゃべらせてみましょう
ダウンロード
まずは評価版のライブラリをダウンロードしてきます。
http://www.a-quest.com/download/index.html
このAndroidの評価版は二つの制限があるそうです。
とくに二つ目の制限が結構きついです。
armeabi-v7aではありません。 armeabi用のsoファイルしか用意されていないのです。
v7aでは無いということはAndroid 2.3辺りということです。
AQUEST様、さすがにv7aじゃないarmeabiはつらいです。
今どきのエミュレーター事情を考えるとx86用を評価用に用意してもらえると嬉しいです。
zipファイルを展開すると
が入っています。
今回はXamarin Androidで Samplesの中にあるAqTkAppというアプリを作ってみたいと思います。
サンプルプロジェクト
こちらのリポジトリに今回作成したアプリを載せました。
詳しくはこちらをご覧ください。
- なお、こちらのリポジトリにはsoファイルは入っていません。*
作成
プロジェクト作成
Xamarin.Androidプロジェクトを普通に作ってください。
プロジェクトへSOファイルを追加する
NativeLibraryを使用するため
このようなフォルダ構成でsoファイルをプロジェクトに追加します。
追加したsoファイルのビルドアクションをAndroidNativeLibraryに変更します。
Layoutとstringsを作成する
参考元のプロジェクトのmain.xmlファイルをそのままコピーしてきます。
strings.xmlもそのまんま丸写しで大丈夫です。
こんな感じになるかと思います。
AquresTalkクラスを作成する
今回の核心部分です。これで3日も悩みました。
当初soファイルを追加したのでP/Invokeで直接soファイルの関数を呼び出せばよいのだろうと考えていたのですが、どうしてもうまくいきませんでした。
2日頑張ってNativeメソッドのコールまでは出来ていそうな感じになったのですがエラーでアプリが落ちてしまってどうしても原因がわかりませんでした・・・
ですので今回はNativeLibraryのメソッドをコールするjavaクラスを追加して、そのクラスへのバインディングクラスを作成してコールする形式をとってみました。
まずはjavaファイルをプロジェクトに追加してビルドアクションをAndroidJavaSourceに変更します。
package aquestalk; public class AquesTalk { static { System.loadLibrary("AquesTalk"); } /** * 音声記号列から音声データを生成します。JNI実装(native修飾子) * <p>発話速度は通常の速度を100として、50 - 300 の間で指定します(単位は%)。</p> * @param kanaText 音声記号列(UTF-8) * @param speed 発話速度(%) * @return wavフォーマットのデータ */ public synchronized native byte[] syntheWav(String kanaText, int speed); }
中身はこんな感じです。
次にバインディングクラスを作成します。
作成したクラスはこちらです。
AquesTalk_Xamarin_Sample/AquesTalk.cs at master · yuka1984/AquesTalk_Xamarin_Sample · GitHub
このあたりのサンプルコードをベースに作成しました。
ちょっと悩んだのはこの関数です。
[Register("syntheWav", "(Ljava/lang/String;I)[B", "GetsyntheHandler")] public virtual unsafe byte[] synthe(string kana, int speed) { if (id_syntheWav == IntPtr.Zero) id_syntheWav = JNIEnv.GetMethodID(ClassRef, "syntheWav", "(Ljava/lang/String;I)[B"); try { var ptr = IntPtr.Zero; if (GetType() != ThresholdType) ptr = JNIEnv.CallNonvirtualObjectMethod(Handle, ThresholdClass, JNIEnv.GetMethodID(ThresholdClass, "syntheWav", "(Ljava/lang/String;I)[B"), new JValue(new Java.Lang.String(kana)), new JValue(speed)); else ptr = JNIEnv.CallObjectMethod(Handle, id_syntheWav, new JValue(new Java.Lang.String(kana)), new JValue(speed)); return JNIEnv.GetArray<byte>(ptr); } finally { } }
RegisterAttributeの第2引数、JNIEnv.GetMethodIDなどでメソッドのシグネチャを指定する必要があります。
要するにメソッドの形ですね。
Determine the signature of a method - Real's Java How-to
このページが例がついててわかりやすいです。
今回のメソッドは byte[] syntheWav(String kanaText, int speed)なので (Ljava/lang/String;I)[Bとなるわけです。
そして関数の実行と戻り値の受け取りはJNIEnv.CallxxxMethidを使用します。
JNIEnvクラスにはintとか単純な方はCallintMethodみたいな感じでコール用の関数が用意されているのですが戻り値が配列の場合には
CallObjectMethiodを使用してIntPrtを受け取りJNIEnv.GetArrayで配列に変換します。
MatinActivityを作成する
Javaで書かれているMainActivityクラスをC#に移植します。
単純作業でよいと思います。
AquesTalk_Xamarin_Sample/MainActivity.cs at master · yuka1984/AquesTalk_Xamarin_Sample · GitHub
実行
評価版はsoファイルがarmeabi用だけしかないのでAVDでarmeabiのバーチャルデバイスを用意します。
Android2.3にて作成する必要がありAPIレベルが10になってしまいますのでプロジェクトのMinimum Android to Targetを変更します。
以上で実行すれば ゆっくりしていってね!!!ってしゃべると思います。
ゆっくりしていってね pic.twitter.com/S8uUXnam8r
— ゆうか@撤退中😇 (@yu_ka1984) 2016年11月21日
以上、次回はXamarin.iOSでゆっくりしていってね!!!ってしてみたいと思います。