Xamarin.iOSでゆっくりボイスを喋らせてみた(*'▽')
ごあいさつ
今使っているMacBookProが2010年モデルでメモリが4GBしかありません。
ちなみに来月が誕生日です。
・・・・・・・('ω')・・・・・・・・・
前回の記事に引き続き、今回はXamarin.iOSでゆっくりボイスをしゃべらせてみます。
作ってみる。
開発環境
Mac + XCode8.1 + Xamarin Studio 6.1.2
今回の開発はMacが必須です。
もしMacを持っていない・・・という事であれば
MacinCloudを使うことでプログラムはできますが音が出ないのでゆっくりボイスを聞けません(´・ω・`)
サンプルプログラム
今回作成するプログラムはこちらのリポジトリに保存してあります。
ダウンロード
http://www.a-quest.com/download/index.html
AquesTalk iOS 1.6.2 をダウンロードしてください。
libファイルの中にあるヘッダファイルとライブラリファイルを使用します。
プロジェクト作成
まずはプロジェクトを作成します。
iOSのSingle View Appを使用します。
名前は適当で良いかと思います。
アプリの ソリューションをを作成できましたら次はバインディングライブラリ用のプロジェクトをソリューションに追加します。
iOSのLibraryにあるBindingLibraryを作成してください。
名前は適当で良いです。AquesTaklLibとしました。
そうしたらアプリプロジェクトにてバインディングライブラリを参照追加してください。
これでとりあえずソリューションとプロジェクトの準備が完了です。
Objective Sharpie
これは参考までにということで・・・
iOSライブラリへのP/Invokeを行うC#クラスを作成してくれるツールがObjective Sharpieです。
ヘッダファイル、Frameworkを元に作成することができます。
ただあまり精度が良くありません。複雑でない関数を5,6個という事であればヘッダファイルをみながら自分で関数を書いた方が早いです。
今回もObjectiveSharpieは試してみましたが良い結果は得られなかったため自分でP/Invokeを書きました。
バインディングライブラリ
先ほど作成したバインディングライブラリにaファイルを追加します。
すると
こんな風にLinkWithファイルができていると思います。
このLinkWithファイルを開きLinkWithAtributesにIsCxx=trueとする指定を追加します。
IsCxxフラグはNativeライブラリにC++が使用されている場合に指定します。
次にP/Invokeするクラスを追加します。
ヘッダファイルを参考にこのようなクラスを作成しました。
AquesTalk_Xamarin_Sample/StructsAndEnums.cs at master · yuka1984/AquesTalk_Xamarin_Sample · GitHub
P/Invoke用に関数を書くときのポイントとしては
- 値型は長さに気を付けてそのまんま
- ポインタ型はIntPtrにしておけばOK
って感じです。
アクセスをinternalにしているのは外部からアクセスさせないためです。
なのでラッパークラスを作成します。
AquesTalk_Xamarin_Sample/AquesTalk.cs at master · yuka1984/AquesTalk_Xamarin_Sample · GitHub
ラッパークラスとしてはあまり良いとは言えない手抜きなので参考程度にお願いします。
stringをエンコードしてbyte配列にした上でMarshalでIntPtrにして渡しています。
アプリ
ダウンロードしたzipファイルに付属されているサンプルアプリをベースに作成します。
まずは画面を作ります。
まずはサンプルアプリのmain.storyboardファイルをxamarinプロジェクトの物と入れ替えます。
そのまま入れ替えてしまえばOKです。
そうしたらMain.Storyboardを開きplayボタン、stopボタンをダブルクリックしてViewControllerクラスにクリック時の呼び出し関数を追加します。
サンプルアプリのObjective-Cで書かれたViewController.mファイルを参考にViewControllerクラスを作成します。
AquesTalk_Xamarin_Sample/ViewController.cs at master · yuka1984/AquesTalk_Xamarin_Sample · GitHub
ほぼそのまんまです。
1点ポイントとしては、NsNotificationによるコールバック関数コールはUIスレッドで実行されていないようなのでLabelのTextを変更する際にUIスレッドで処理するようにしないエラーになる。
というくらいです。
それにしてもXamarinがほんとに良いです。
Objective-CであろうとSwiftであろうとJavaであろうとネイティブのコードを参考にほとんど迷いなくC#に移植することができます。
逆にXamarinで作成してあるアプリをNativeに持っていくこともできると思います。あまり意味はないと思いますが・・・
次回はXamarin.Formsでゆっくりボイスを・・・・という事を考えていたのですが実はうまくないのです。
XFってAndroidのAPI15移行に対応しているんですがAquesTalkの評価版はarmeabiしかsoファイルが無いのでエミュレータが用意できないのです。
API15だとarmeabi-v7aになってしまうのです。
なので開発ライセンス買うしかないのです。XFで使えるか評価したかったのですが残念です。
ですので株式会社AQUEST AquestTalkを使用したゆっくりボイスをXamarinで喋らせてみるシリーズは終了です。
AquesTalkの開発ライセンスは
こちらで購入することができます。
ではでは('ω')ノ
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でゆっくりしていってね!!!ってしてみたいと思います。
ASP.NET CoreでDI設定をappsettings.jsonに記述して設定してみよう('◇')ゞ
ごあいさつ
この前クルマ買い換えたんです。
その時にディーラーの営業さん(サーファー)に
ダム行くのに高速とか山道走るから性能のいいクルマが欲しいみたいな話をしたら・・・・
結婚の心配をされました\(^o^)/
そんなのとうに諦めとるわ、こらぁ(# ゚Д゚)
本題
最近のWebのUI周りのトレンドを勉強するためにASP.NET Coreを始めてみたら WebのUIに辿り着く前にASP.NET Coreで遊んでます。
このフレームワーク、ASP.NET MVCより色々とよくできてると思います。
今のところ不満だなぁって思うところが見つからないです。
ASP.NET CoreはDIありきで作られているんですが
Unity(ゲームじゃない方)とかではweb,configとかUnity.configにRegisterの設定を記載してLoadConfigurationを呼ぶと設定どおりにContainerに登録されるような仕組みがあるのですが、ASP.NET CoreのDIにはないっぽいのです。
ですのでappsettings.jsonに設定を記載してconfigurationするプログラムを簡単に書いてみました。
まずはこんな拡張メソッドを作ってみます。
appsetting.jsonから設定をオブジェクトで作成する部分はしばやさんの記事から
ようするに
appsettings.jsonからRegisterSettingクラスを起こしてIServiceCollectionに登録している感じです。
Assemblyが指定されている場合にはAssembly.Loadを、指定されていなければ現在のAssemblyを使ってType型を手に入れています。
appsettings.jsonは
こんな感じで指定してあげれば良いです。
最後に呼び出しは
StartupクラスのConfigureServiceメソッドにて
こんな感じで登録してあげればよい感じです。
ではでは( `ー´)ノ
ASP.NET Coreの学習帳
ASP.NET Core MVCの学習帳なのでちょっとずつ更新していきます。
GitHubでやれ、とのうわさがある。
プロジェクト構成
Staticなファイルはwwwrootフォルダに固まってる。 わかりやすくてよい( *´艸`)
jsとかcssファイルのbundleはbundleconfig.jsonにて管理するっぽい。 あまり調べてない。
project.jsonとかある。
以前に.net coreとかになる前に触ったときにあまり良い印象がなかったけど普通にnugetマネージャーで管理できてたのでそこまで苦労しなさそう。
なんかTool系のnugetパッケージはマネージャから追加したときにうまく動かなかった時があった。 コンソールからインストールしたらうまくいった。
設定関連
アプリケーションの設定はappsettings.jsonにjsonとして追加していくみたい。
StartupクラスのIConfigurationRoot Configurationプロパティを経由して取得するようになってた。
"Logging": { "IncludeScopes": false, "LogLevel": { "Default": "Debug", "System": "Information", "Microsoft": "Information" }
ConnectionStringは専用の取得拡張メソッドが用意されてる。
"ConnectionStrings": { "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=webapp1;Trusted_Connection=True;MultipleActiveResultSets=true", "StorageConnectionString": "" },
こんなのだったら
Configuration.GetConnectionString("DefaultConnection")
みたいな感じで取得できる。
自分で定義して取りたいようなケースには
"Logging": { "IncludeScopes": false, "LogLevel": { "Default": "Debug", "System": "Information", "Microsoft": "Information" }
からIncludeScopeを取得したい場合
Configuration.GetSection("Logging:IncludeScopes").Value;
で取得できる。
appsettings.jsonはStartupクラスのコンストラクタにて
var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) .AddEnvironmentVariables(); Configuration = builder.Build();
こんな感じでビルドが行われている。
べつにappsettings.jsonってファイルじゃなくてもオッケー
AddJsonFileの二つ目が大切。
appsettings.xxxx.jsonというのでappsettings.jsonの中身を上書きしている。
env.EnviromentNameというのは環境変数ASPNETCORE_ENVIRONMENTにて設定される値らしい。
デバッグ環境のIIS ExpressではDeploymentと設定されている。
変えたければプロジェクトのPropertiesの下にあるlaunchSettings.jsonにて変更できる。
なので開発環境用、ステージ用、本番環境用にappsettings.jsonを分けて作成できる。
また、このセッティングのもう一つの特徴はappsettings.json以外でも環境変数設定にて指定可能なこと。
例えばappsettingsとかでは管理したくない本番環境の接続文字列とかは、その環境の環境変数に設定してしまえばよい。
ConnectionStrings:DefaultConnection みたいな感じで変数を作成して値に接続文字列を指定するとConfigurationで読み込まれている。
たぶんStartupのコンストラクタのAddEnvironmentVariablesがそれ。
パスワードとか秘密にしたいのは、シークレットなんちゃら、みたいな別の機構があるらしい。未確認。
DependencyInjection
そもそもFrameworkがDIありきで作られてる。すばらしい( *´艸`)
LifeCycleはSingleton/Scoped/Transientの3種類。Webなら十分。
Scopedはちゃんとリクエスト処理終了時にIDisposableならDisposeが呼ばれてたので安心して使える。
Startup.csのConfigureServices関数で注入するみたい。
ConfigureServicesの引数はIServiceCollectionなのだけど、このクラスに対する拡張メソッドが沢山用意されてる、
Loggingとかantiforgeryとか。
たぶん必要になったら追加したりするのでしょう。
設定ファイルからの注入が可能かは未確認。-> 確認したい。使うクラスを切り替えるのにビルドしなおす必要があるのは嫌だ。
ロギング
まだ調べてない。
O/Rマッパー
一般的にはEntityFramework Core https://docs.efproject.net/en/latest/を使うらしい。
Dapperは.NetStd1.3に対応していたので使える。
PetaPocoは www.nuget.org 対応してくれた方がいた。 ただPocoクラス作成のT4がなかったので(´・ω・`)なかんじ。
基本的には機能制限方向。コンスラクタがIDbConnectionを引数とする一択だった。
EntityFramework Core
Codefirst
DbContextを継承して各パターンはそのまま。
マイグレーションをするには
SQLServerにする場合には
- Microsoft.EntityFrameworkCore.Design
- Microsoft.EntityFrameworkCore.SqlServer.Design
- Microsoft.EntityFrameworkCore.Tools (Prev)
を入れる。Toolsだけはコンソールマネージャから入れた。
Add-Migration hoge でマイグレーションクラスが作成されて
update-database でマイグレーションできる。
マイグレーションするデータベースはDbContextクラスで指定したもの。 DbContextクラスの接続先をDIなどで注入する形をとっている場合、何かのルールで勝手にいい感じに注入が行われて開発環境にマイグレーションできる。
ASP.NET Coreプロジェクトの場合、Startupクラスで注入しているのであれば問題なく開発環境にマイグレーションされた。
ただこのことによってクラウド上の環境にマイグレーションする場合の方法がわからなかった。
もう少し仕組みを調べる必要がある。
DBFirst
調べてない。
クラス中のReactivePropertyに自動でSubscribeを仕掛けてみよう(*'▽')ノ
ご挨拶
全国何人かの皆様、超お久しぶりです。
ずっとさぼってました( `ー´)ノ
仕事でもプライベートでもXamarinはあまり触ってなくて、もっぱらクラウドデザインパターンのお勉強とAzureのお勉強などをしておりました。
Prism.Formsはちょっとしたアイディアを試すために少々触っていましたがMVVMフレームワークの必要性に関して疑問符が付く結果となってしまい記事にはできませんでした。
昨日JXUGのカンファレンス
がありまして、地方在住の私はyoutubeから見ておりました。
最近はあまり面白いプログラミングのアイディアがなかったり仕事の方も人間関係や職場環境によりモチベーションが保てなくてウジウジな生活をしておりましたがJXUGCの登壇を見ていてmoq(もっきゅ(*'▽'))というキーワードからやってみたいことを思いつきまして早速プログラムをしてみたので記事にいたしました。
果てしなく一般向けでない内容となっておりまして私自身もしっかりと理解できていない点もありますので御指摘等ありましたらTwitterの方にお願いいたします。
成長の為にも是非ともお願いいたします。
なお本日は休日出勤しなければならないので早く寝なければなりません(^^)/
本題
MVVMのViewModelにて値の変化をログ出力したりデバッグ出力するような場合、ViewModel自体にINotifyPropertyChangedが実装されている時にはイベントを仕掛けて出力やその他処理を行えばよいです。
しかしプロパティにReactivePropertyを用いている場合にはプロパティ個別にSubscribeする必要があるので状況によってはコードを書くことが面倒だったりします。
なのでクラス中のReactivePropertyにリフレクションを使って汎用的にSubscribeとかする事ができたらいいなぁ というのが今回のアイディアでありました。
がしかし
ReactivePropertyってGenericなんですよね。
Genericの場合、Reflectionでメソッド実行したりする事が難しかったり無理だったりすることが多いんですよね。
しかもSubscribeはIObservable
当初はReflectionでサクッとできたらなぁと思っていたのですがどうやら無理そう・・・・
ReactiveProperty自体はINotifyPropertyChangedを持っているのでイベントを仕掛けてIReactivePropertyのValueプロパティからObject型の値をとるところくらいまではできるのですが、それでは発展性もなくてイマイチ(´・ω・`)
またアイディアを形にできないのかぁ・・・・なんて思っていました。
そんな中、ふと思い出したのがSystem.Expression 所謂式木です。
なんとなくはわかるのです。でもやっぱり難しい式木(´・ω・`)
以前O/Rマッパーのカスタマイズを少しやっていた時に式木に遭遇して多少は実コードを読んだことがあるのですが、やっぱり難しかったです。
でも、きっと、Genericなメソッドも式木を使えば呼び出せるのだろうと思い調べてみましたらこんな情報を見つけました。
なるほど、サンプルコードがわかりやすいのでなんとなく理解できた気がします。
このサイトを参考にして小一時間コネコネしてみましたらうまくできました。
完成コード
では実際にトライしてできたコードがこちらです。
実行結果がこちら
個別にReactivePropertyにSubsucribeを行わずに購読を行いコンソール出力を行うことができています。
解説します。
ReactivePropertyのみ抽出
vm.GetType().GetProperties() .Where( x => x.PropertyType.Name.StartsWith("ReactiveProperty") || x.PropertyType.Name.StartsWith("ReadOnlyReactiveProperty"))
ViewModelのインスタンスからTypeクラスを取得しPropertyInfo配列を取得しプロパティの名前の先頭から文字列比較しています。
もっとちゃんと書けると思いますが、今回の主題でないので割愛です。
ReactivePropertyにSubscribeを行うメソッドを定義
public static IDisposable MySubscribe<T>(IObservable<T> source, string name) => source.Subscribe(x => Console.WriteLine($"{name}:{x}"));
渡られたIObservableとnameを使用してSubscribeを仕掛けてOnNext時にはコンソール出力を行うようにします。
購読を追加する
.Select(x => Expression.Lambda( Expression.Call(typeof(Program), "MySubscribe" , new[] {x.PropertyType.GetGenericArguments().First()} , Expression.Constant(x.GetValue(vm)), Expression.Constant(x.Name))) .Compile().DynamicInvoke() as IDisposable)
PropertyInfoの配列に対してSelectを行ってその中でSubscribeをコールしています、
Expression.Lamda -> ラムダ式を作成するメソッドです。
引数には
メソッドをコールする式木を作成するExpression.Callを使用しています。
第1引数のtypeof(Program)はコンソールプログラムですのでメインプログラムクラスを指定しています。 第2引数にはコールするメソッド名を指定しますので先ほど作成しておいたMySubscribeの名前を指定します。 第3引数はメソッドがGenericの場合にTypeの配列を指定します。
今回はReactivePropertyのGenericの型を指定したいのでx(PropertyInfo)に対してPropertyType.GetGeneticArguments().First()とすることでGenericの型を指定します。
第4引数以降はparamsです。MySubscribeの引数を指定します。
MySubscribeの第1引数はIObservable
これでMySubscribeをコールするLambdaができますので .Compile()でラムダをコンパイルしDelegateを作成します。 DelegateのDynamicInvokeを行い実際に作成されたメソッドを実行します。 戻り値はIDisposableですので as で変換してselectの戻り値とします。
これでクラス中のReactivePropertyに対してSubScribeを仕掛けることができました。
テストしてみる
vm.StringTestProperty.Value = "test1"; vm.IntTestProperty.Value = 10; vm.StringTestProperty.Value = "test2"; vm.IntTestProperty.Value = 11; Console.WriteLine("SubscribeをDisposeします"); disposes.ForEach(x => x.Dispose()); vm.StringTestProperty.Value = "test3"; vm.IntTestProperty.Value = 12; Console.WriteLine("おしまい");
結果を見るとわかる通り
ReactivePropertyの値を変更するとコンソールに出力され式木で作成したメソッドの戻り値であるIDisposableをDisposeすると購読が解除されコンソール出力されなくなります。
今回は単純にコンソール出力を行っただけなのですが、Subscribeを仕掛けるMySubscribe内にて色々することができるので例えばZipとSkipを使って変更前の値との同時に出力したり色々できるので夢が広がります。
本日は休日出勤なのでもう寝ます。
WatsonのSpeech To TextをXamarin.Formsで試してみたよ('◇')ゞ
音声認識の実装に関して興味があったのでWatsonのSpeech To Textを使用してXamarin.Fromsで音声認識を行ってみました。
なぜWatsonにしたか
GooleのCloud Speech APIが50回の回数制限があったから。
です。
Xamarin.Formsで音声入力を文字化する実装を考えたときにAndroidの方はSpeechRecognizerを使用すればできるんですが
iOSの方がなかなか難しいんです。
最初はGoogle Cloud Speech APIを使って音声認識を試してみようかと思ったのですが使用回数が50/dayだったりでほんとにちょっとしたテストしかできませんでした。
ということで別の方法を探してました。
この記事を読んでいたら
Watsonは有料となっていたのですがサイトを見てみたら最初の1000分までは無料とのことだったので試すにはちょうど良いと思いWatsonを試してみました。
CMとかもやってますしねぇ。Watson。
登録とか
Bluemixのアカウントを作成します。
最初の30日は無料ですべてのサービスにアクセスできるみたいです。
Bluemixのアカウントを作成すると組織とスペースを決めます。
組織は日本語で大丈夫でしたがスペースは日本語だめかもしれません。
最初に日本語を入れてみたのですが、登録は成功するけど反映されないみたいなおかしな状態になったので英語にしたほうが良いかと思います。
ホーム画面からWatsonを選択します。
右上の「+」ボタンをクリックします。
Speech To Textを選択します。
特に考えずに作成を行います。
資格情報の作成を行います。
usernameとpasswordは認証情報として使用します。
APIについて
APIReferenceはちゃんと用意されています。
主にHTTPを使用したセッションありとセッションなしのAPI、WebSocketを使用したAPIがあります。
HTTPを使用したセッションは保存済みのデータをテキストに変換するのに適していてWebSocketはリアルタイムに変換する事に向いていると考えてよいかと思います。
Java用とnode.js用のライブラリは用意されています。
またUnity用のC#ライブラリもありますので、お手軽にっていうのであればそちらを利用するのも手です。
今回はAPIの理解を得たい面もあったのでそれらは使用せずに実装します。
リアルタイム変換を行いたいのでWebSocketのAPIをします。
実装
とりあえずソースコードを
Observer/Observableパターンで実装してます。
使用しているライブラリは
WebSocket-Sharp
Json.NET
認証
まずWebSocketで接続を行います。
接続URLはwss://stream.watsonplatform.net/speech-to-text/api/v1/recognize
クエリストリングにてmodelを指定できます。
modelは言語に使用する言語に対応した物を指定します。
今回は日本語ですのでja-JP_BroadbandModel
を使います。
資格情報としてSpeechToTextの資格情報を設定します。
_client.SetCredentials(user, password, true);
そして接続を行います。
接続に成功した場合、クライアントからスタート情報をjsonで送信することで変換ストリーミングを開始できます。
var startjson = JsonConvert.SerializeObject( new Connection { Action = "start", content_Type = $"audio/l16;rate={SampleRate};channels={Channel}", InterimResults = InterimResults, Continuous = Continuous, InactivityTimeout = -1, MaxAlternatives = 1, Timestamps = false, WordConfidence = false }, Formatting.Indented); Debug.WriteLine(startjson); _client.Send(startjson); _isSending = true;
今回はPCM音声データを使用しますのでcontent-typeにはl16を指定しrateとchannelsを追加で指定します。
Interim_resultsは変換が確定しない時点でもストリーミング的に途中結果を受け取ることができます。
continuousを有効にすることで断続した変換を行うことができます。
スタート要求を送信するとstate : "listening"というjsonを返答してくれます。
スタートを送信したらあとは、PCMデータをWebSocketのBinaryFrameにて送信することで結果を返答してくれます。
if (!_isConnected) return; if (value.IsStop == false) { if (!_isSending) { StartRecognize(); } _client.Send(value.PcmStream); } else { _client.Send("{\"Action\": \"stop\"}"); _isSending = false; Debug.WriteLine("Stop"); }
結果に関しては Speech to Text Service Documentation | Watson Developer Cloudを読んでください。
受信処理は
var result = JsonConvert.DeserializeObject<SpeechResult>(args.Data); Debug.WriteLine(args.Data); result.Results.ForEach(res => { if (res.Alternatives.Any()) { var speechtext = new SpeechText { IsFainel = res.Final, Text = res.Alternatives.First().Transcript }; _speechTextSubject.OnNext(speechtext); } });
こんな感じで書くことができます。
今回はXamarin.Forms側のプログラムは説明しませんがソースコードは
こちらで確認することができます。
精度的には他と比較していないので何ともです。
おそらく多と比較検討してくれる記事などを書いてくれる人が出てくると思うので期待です。
このように実装することでオンライン限定ですがクロスプラットフォームでの音声認識を実装できます。
このケースでは音声認識部分はAndroidとiOSでコード共有率100パーセントです。
Xamarin素晴らしい。
以上です。
ToReactiveProperty拡張メソッド
特に書くことがないのでReactiveProperty使うときに追加している拡張メソッドを紹介します。
ToReactivePropertyAsOneWaySync
ModelからReadOnlyReactivePropertyを作成するときに使います。
むっちゃ使います。
INotifyPropertyChangedクラスからViewModelのプロパティ作成するときはまずこれです。
基本はViewModelのプロパティは基本がReadOnlyReactivePropertyでありReadOnlyObservvableCollectionでありReadOnlyReactiveCollectionです。
編集が必要な時のみReactivePropertyを使ってます。
ToReactivePropertyAsOneTime
たまに使います。
ReactiveProperty.FromObjectを使いたい時によくなんだっけ?って忘れるのでToって書けばインテリジェンスで候補が表示されれば幸せになれると思います。