FC2ブログ

スマフォのアプリを作りたい(19):音声認識と同時に録音②

   プログラミング [2020/04/04]
前回の続きです。
「音声認識(react-google-speech-api)と同時にその音を録音させるには、ひとさまの作ったフレームワークを弄るしかない。」という結論に達しました。
しかし、ちょっとした修正ならまだしも、結構な作り込みになるのは予想つくので些か躊躇・・・というか、面倒臭くなってちょっとの間、現実逃避してました。
その後、シブシブ再開したが、Androidのことも、Javaについても全く無知なさるなので、やっぱりエライ時間が掛かりました。

録音処理の実装でトラブった話を纏まりなく以下に書きます。
初心者の方の・・・何かになるんでしょうか。


◆ああ、Javaだったのね
react-native-google-speech-api内にエンコード用の処理(ソースファイル/クラス)を追加する方針です。
ここで、上記のパッケージ内のコードが「.java」であることがジワジワ響いてきました。
JavaScriptすらド素人なのに、Javaは全く未経験です。
予備知識 0
なのに弄っちゃってるなんて無謀でしたよね。

JSとJAVAの違いも知らないのに、「違う言語だ」という認識も薄いため、コードをどう書けばいいのか行き詰ることもしばしばでした。
元々のコードも理解できてないのに、それらの書き方を適当に真似してみるというやり方です。

まずは初期のころ、
・追加したエンコード・クラス内のメソッドに、意識せずに「static」指定してしまったり
・呼び出し元でクラスのインスタンス生成(new)の要/不要やタイミングとか分かってなかったり
イミフなビルドエラーが出まくりました。

ちょっと分かった気分になれたのは以下の解説のおかげ。
参考:https://java.keicode.com/lang/initialize-initialization-block.php
(他サイトは細かい説明も含まれていて耳からミソが垂れてきそうでした。)
さる並みの方は上記のサイトを頭から読んでお勉強されることをお勧めします。
 さる自身は・・・まあイズレね。

ちょっと知恵がついたことと言えば、
  • メンバ変数の初期化は、コンストラクタ(クラス名のメソッド)を書かなくてもできるらしい。
  • 単にクラス内に変数を定義して、「・・・= xxx;」とかやっとけば、そのクラスのオブジェクト(インスタンス)が生成(new)されたときに初期化されるらしい。
  • 固定値で初期化するのであれば、「static」と付ける。これを「クラス変数」とか呼ぶ。これに対して先の「new」時に初期化されるのを「インスタンス変数」と呼ぶ。
  • 「static」を付けたメソッド/メンバは、「new」しなくても呼び出し/参照できる。クラス名.メソッド名()/クラス名.変数名 と記述して使う。
  • 以上から察するに、コンストラクタ(クラス名と同じ名前のメソッド)を明示的に定義する意義は、
     -基底クラスの初期化を引き継げる(super()する)ことや、
     -メンバ変数をコンストラクタ「new クラス名(引数)」時に初期化内容を引数で制御できること
     かと思う。
  • でもイミフな現象がありました:
     -例えば、「AruClass」という名前でクラスを定義して、
     -参照側クラスのメンバ変数を「AruClass aruClass = new AruClass();」とやって、
     -参照側クラス内のメソッドから「aruClass.xxxxx();」とやったら、
     -エミュレータで実行しかけた時に「xxxxx()はstaticじゃねーよ!」的なエラーになった。
     →クラス名とは頭1文字違う「aruClass」としてるんだけど・・・
      「AruClass aruCla = new AruClass();・・・aruCla.xxxxx();」的な修正をしたら治まった。


◆Context(ApplicationContext)の参照の仕方が分かりません
Androidの本家の説明では、「Context」とか「Application」とかのクラスが準備されていて、これらを使うと、アプリケーション毎に固有に割り当てられたリソースに関する情報を参照できるように書かれてありました。

アプリ毎にワークのディレクトリ(ストレージ上のフォルダ)も準備されているらしい。
それをエンコードしたファイルの出力先にしようとして、追加したエンコード処理クラスの中で「Application myapp = new Application();」とかやったのですが、どうも上手くいきません。
「myapp.getFilesDir()」でExceptionになります。「Contextがnull」だとメッセージが出ます。Applicationクラスの基底クラスがContextなのは分かるけど、myappはnullではないのは確認しました。
正体が掴めず、この件は挫折。
(呼び出しの元(JavaScript側)でファイル名パスを指定する方向に変更しました。


まだ、序の口です。ぼやきは続きます・・・


◆MediaCodecのコールバックの使い方が分からん
参考にしたサイトのサンプルは、単一スレッドで動作するように書かれています。
ファイルから入力してファイルに吐き出すサンプルなので、それはそれでありだと思います。

一方、本家のMediaCodecの説明の中で紹介されている使い方の例には、非同期で処理する例も書かれています。

今回の場合、マイク入力された音声は、その流れでGoogleクラウドに渡して、その足でMediaCodecのInputのデータとして渡します。
ただし、エンコードしてファイルに書き込むのを待っていられません。(マイク入力の次が来るからそこで足踏みしたくない。)
なので、エンコード&ファイル書き込みは非同期で動作してもらって、エンコード完了通知を受けるコールバックでファイルへの書き込みを処理することを考えました。

部分的なコールバックの例は、見つけられませんでした。
でもやってみた。

起動後「Exception in native call from JS」と出る現象が繰り返し起こります。

コールスタック内の表現は「MediaCodec.setCallback()」から「native_setCallback()」がコールされたとなってました。
自分が作った流れでもないところでそんなこと言われても・・・「そんなこと、わしゃ知らん!」と言いたいところ。

原因は、MediaCodecのメソッドの呼び出し順序、サンプルで使ってたメソッドがある時点から非サポートになってたり、非同期制御との組み合わせNGだったりしたためです。
早い話、何も理解しないでサンプルをコピペしたことが原因です。

-setCallback()は、configure()/start()前に実施しないといけない。
-setCallback()をやって、configure()すると、「非同期モード」を使用するという意味。
-setCallback()で@Overrideするのは、4つ全部じゃないとエラー
 [onInbutBufferAvailable()、onOutputBufferAvailable()、onOutputFormatChanged()、onError()]
-getOutputBuffers()は、API21で廃止。非同期モードでは利用不可。
-dequeueInputBuffer()は、非同期モードでは使えない。IllegalSteteExceptionになる。

メソッドには同期処理専用のがあってコールバック処理内では使えません。
つまるところ、MediaCodecを使う場合、部分的に非同期にすることはできない。
Android本家のMediaCodecのところをよーっく読むと分かります。
そうはっきり書いてくれよ!と言いたいです。

さらに、
-onInputBufferAvailable()内で何もせずにreturnしたら・・・何もなく、次の空バッファのindexで呼ばれる。
 何もしなかった入力バッファは使われないままになる。
-onInputBufferAvailable()内で「入力終わり」の意味でBUFFER_FLAG_END_OF_STREAMを送らないといけない。
 ex) codec.queueInputBuffer(index, 0, 0, presentationTime, MediaCodec.BUFFER_FLAG_END_OF_STREAM)
 かつ、そのあとでqueueInputBuffer()をやってはいけないと、本家サイトには書いてあった。
-BUFFER_FLAG_END_OF_STREAMで、onInputBufferAvailable()が呼ばれなくなるかというと・・・
 止まらない。次のindexでコールされる。


◆Exception発生!!どうすれば?
上記の「同期/非同期混在不可」が判明した前後のコード修正でExceptionが発生しまくりました。
アプリを起動して実行させたら、エミュレータ上のアプリの表示が消えて、
「問題が発生したため、(アプリ名)を終了します。」とポップアップが出てしまう。

デバッガ(Chromeのデベロッパーツール)上で何かメッセージが出るのかというと出ません。
logcatを表示させても、try~catchしてない範囲では、何も出ません。
デバッガの使い方をチャント調べれば、何か手立てがあるのかもしれないけど。

ここに来るまで、こういうパターンの異常終了は初めてでした。
んーどうすれば?・・・とりあえず以下の手順で調べました。

-異常終了した辺りで何をしていたのか、logcatの表示で見当を付けます。
 絞り込みたい場合、どうせ自分で作り込んだ内容が問題なのだから、その辺にLog.x()を追加するといいでしょう。
-当たりを付けた周辺がtry~catchされていない(たぶんそう)のであれば、
 「try{(怪しい処理群)} catch (Exception e) { Log.e(TAG, "exceptio!", e);}」を加えます。
 TAGは「"クラス名"」を指定しました。「import java.lang.Exception;」も頭に入れます。
-ビルドし直して、現象を再現させます。
-logcatのメッセージの中で、「exception!」と表示されている行を探します。
 その直後に詳しいExeceptionの種別が「java.xxx.Exceptionサブクラス名」として表示されているかと思います。
 さらに、その下にコールスタックの内容がダダダーと出てるかと。
-それで、凡その問題個所とExceptionの種別は判別できますよね。
-理由が分かって、完全にExceptionすることなどないと思ったら、追加したtry~catchは元に戻してもいいでしょう。

※もうちょっと、いい方法がありました。後で出てきます。


◆スレッド(コールバック)間の同期が必要
音声を入力してGoogle Cloud側に渡す処理は、音声入力クラスのコールバック関数の中で行われている。
そのコールバック中の処理から、エンコードクラスの入力処理(こちらもコールバック)に元データを渡してあげないといけない。
エンコードクラスの入力処理は、入力バッファのキューに空きが出るとコールバックされるし、音声入力クラスも音の入力とともにバンバン入ってくる。

MediaCodecのonInputBufferAvailable()は、「何もせずにreturnしたらどうなるか」とかの説明はなく、漏れなくデータを入れてqueueInputBuffer()することしか書かれてない。やって見た感じでは、何もせずにreturnしたら、それはそれで「終り」なようで、全部のindexで何もしないでreturnしたら、onInputAvailableコールバックは多分来なくなっちゃうと思います。(リスタート:flush()&start()とかしたら、再動作するのかな。)
ともかく、「データが未だ来てなかったら、何もしないでreturn」はNGなようです。

つまり、音声入力とエンコードの両コールバックは、同期せずには居られない感じです。
・・・どうやって?

同期したい処理どうしをsynchronizedでブロックして、wait()とnotify()/notifyAll()を使って同期する例はたくさん出てきます。
ですが、どーにも親切な前置き(「こんな場合」の実例の説明)が長くて却って分かりにくい。
以下に一番シンプルな同期の例が載ってました。
参考:https://www.javaroad.jp/java_thread4.htm

素朴な疑問-
synchronized{}で囲んだブロック(or メソッド)どうしは排他されるとあります。
同じオブジェクトでsynchronized指定しておくと、同時には動かない。
後で動こうとしても片方がブロックから抜けるまで待たされるということです。
一方、wait()もnotify()/notifyAll()も、synchronized{}内でしか使えないとある。
・・・あれ?
synchronizedブロック内でwait()してたら、別の場所のsynchronizedブロック内でnotify()しようとしてもロックされていて起こせないんじゃないの?
とか思ったら、wait()を呼んでる間は一時的にロックは解除されているんですって。
なるほどね。

先のサイトのコード例を参考に、wait()とnotifyAll()を使ってコードを書いてみました。
ですが、先にwait状態になってるonInputBufferAvailable()を音声入力側スレッドのnotifyAll()で起き上がらせることができません。
・・・なぜ?

synchronizedブロックの指定個所は、同一クラス内にあります。
念のため、wait()とnotifyAll()には、「this.」を付けてみます。でもだめでした。
どうにも原因が掴めません。
まさかとは思いましたが、双方のsynchronizedブロック前で「Log.d(TAG, "this="+this);」とやってメッセージトレース(logcat)を見てみた。
すると、thisのアドレスっていうのか何かは分かりませんが、内容が違ってました。
他のメンバ変数なんかは、普通に使えています。
なぜ?
まあ、そういうこともあるってことで。
同じクラス内のコードでも、コールバック時には「this」が別物になることがあると心の隅に置いておきましょう。

幸いにメンバ変数は共有できてるので、自オブジェクト(this)をメンバ変数に代入して、それでsynchronized/wait()とnotifyAll()に使うことでまともに交互動作ができるようになりました。


◆音声認識と録音の終わり方
元々のreact-native-google-speech-apiは、音声認識クラウド側から「はい!一文 認識完了」と通知されると、そこで「その回のテキスト化終了」ということになってました。
さるは、「言い淀み」対策として、「はい!一文 認識完了」で終わりにしないで、
1)無音が数秒間続いた場合
2)長話(定常的な雑音)で30秒経ったら
一回の認識終了とするように、処理を変更してました。

録音の開始/終了も「一回の認識」毎にしたい。

前回は「終わらせずに続けて認識」処理変更をアプリ側をメインに実装したのですが、
録音と認識の区切りを同じにするために「一回の認識」仕様変更をreact-native-google-speech-api側に改めて実装しなおしました。

ここ困ったのが、音声入力(VoiceRecrder)、音声認識(SpeechService)、録音(VoiceEncoder)の終わらせ方。

これまでは、SpeechServiceからのコールバック(handleSpeechEvent)内で、GoogleSpeechApiModuleの終了処理(stop)をコールしてました。
それを音声入力のコールバック(onVoiceEnd)で終了処理(stop)をコールするように、何も考えずに変更したら、Exceptionが発生します。
そりゃそうだ、VoiceRecrderからのコールバック中のにVoiceRecrderのインスタンスを解放しちゃう感じに見えるし。

似たようなもんだと思ってたのですが、VoiceRecrderとSpeechServiceのコールバックの仕方に違いがあるに違いない。
でも、その違いを理解できるほどの頭がないので、そこはあきらめました。

既存の仕掛け(以下)を利用して終わるようにしてみます。
GoogleSpeechApiModuleクラス内で、以下の設定がされています。
----GoogleSpeechApiModule.java---------------------

private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {

compositeDisposable.add(
voiceRecorder.getVoiceErrorEventObservable()
.doAfterNext(event -> stop())
.subscribe(GoogleSpeechApiModule.this::handleErrorEvent)
);

}

};

---------------------------------------------------

VoiceRecrderからエラーを通知する仕掛けで、GoogleSpeechApiModule内のhandleErrorEventをコールバックした後にstop()が実行されるようになってる見たいです。
元からある仕掛けなので、これならきっと大丈夫でしょう。
これを利用することにしました。


◆それでもExceptionがでちゃう
コールバックだらけで、複雑に非同期処理を順次動作させてるので、タイミング的に起きたり起きなかったりするException:「問題が発生したため、(アプリ名)を終了します。」がありました。
ちょっと、どこで出てるか見当が付かない。
なので、try~catchを引っかけようもない。

そういう場合にどうするか。
デバッガによっては、そういうときもExceptionをフックして、発生場所を特定できるものもあるようです。
Chromeのデベロッパーツールには・・・無いのかな?

ソース自体にコードを入れて、try~catchできなかったExceptionを引っかける方法がありました。
以下のようなコードをルートのクラスのonCreateとかでやればOKです。
----GoogleSpeechApiModule.java---------------------
import java.lang.Thread.UncaughtExceptionHandler;

//# FOR DEBUG
//# When an exception occurred at unknown point, enable the following statements.
final UncaughtExceptionHandler savedUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable ex) {
Log.d(TAG, "UncaughtException", ex);
savedUncaughtExceptionHandler.uncaughtException(thread, ex);
}
});
---------------------------------------------------



◆react-native-google-speech-apiに録音機能追加
できたコードを載せます。それなりに動いてます。
オリジナルからの変更部分、追加部分は朱書きにしてます。


----GoogleSpeechApiModule.java---------------------
package com.reactlibrary;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;
import java.lang.Thread.UncaughtExceptionHandler;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.reactlibrary.speech_service.SpeechEvent;
import com.reactlibrary.speech_service.SpeechService;
import com.reactlibrary.voice_recorder.VoiceEvent;
import com.reactlibrary.voice_recorder.VoiceRecorder;
import com.reactlibrary.voice_encoder.VoiceEncoder;

import io.reactivex.disposables.CompositeDisposable;

public class GoogleSpeechApiModule extends ReactContextBaseJavaModule {

private static final String TAG = "GoogleSpeechApi";
private static final String KEY_TEXT = "text";
private static final String KEY_IS_FINAL = "isFinal";
private static final String KEY_REASON_CODE = "reasonCode";
private static final String KEY_MESSAGE = "message";
private static final String ON_SPEECH_RECOGNIZED = "onSpeechRecognized";
private static final String ON_SPEECH_RECOGNIZED_ERROR = "onSpeechRecognizedError";

private VoiceRecorder voiceRecorder = new VoiceRecorder();
private VoiceEncoder voiceEnc = new VoiceEncoder();
private boolean Encoding = false;

private SpeechService speechService;
private CompositeDisposable compositeDisposable;
private String apiKey;
private String langCode;
private int maxVoiceBlank;
private String speechText;
private String tempText;


private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
speechService = SpeechService.from(service);

compositeDisposable.add(
speechService.getSpeechEventObservable()
.subscribe(GoogleSpeechApiModule.this::handleSpeechEvent)
);
compositeDisposable.add(
speechService.getSpeechErrorEventObservable()
.doAfterNext(event -> stop())
.subscribe(GoogleSpeechApiModule.this::handleErrorEvent)
);
compositeDisposable.add(
voiceRecorder.getVoiceEventObservable()
.subscribe(GoogleSpeechApiModule.this::handleVoiceEvent)
);
compositeDisposable.add(
voiceRecorder.getVoiceErrorEventObservable()
.doAfterNext(event -> stop())
.subscribe(GoogleSpeechApiModule.this::handleErrorEvent)
);

voiceRecorder.start(maxVoiceBlank);
}

@Override
public void onServiceDisconnected(ComponentName name) {
speechService = null;
}
};

public GoogleSpeechApiModule(ReactApplicationContext reactContext) {
super(reactContext);

//# FOR DEBUG
//# When an exception occurred at unknown point, enable the following statements.
final UncaughtExceptionHandler savedUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable ex) {
Log.d(TAG, "UncaughtException", ex);
savedUncaughtExceptionHandler.uncaughtException(thread, ex);
}
});

}

@ReactMethod
public void start(String encFilePath) {
Log.i(TAG, "start");
if (apiKey == null) {
sendJSErrorEvent("call setApiKey() with valid access token before calling start()", 0);
}
if (compositeDisposable != null) {
compositeDisposable.dispose();
}
//# Start the encoder.
if (!Encoding) {
if (voiceEnc.start(voiceRecorder.getSampleRate(), 128000, encFilePath) != VoiceEncoder.RETURN_ABEND) {
Encoding = true;
}
}

compositeDisposable = new CompositeDisposable();
if (speechService == null) {
Intent serviceIntent = new Intent(getReactApplicationContext(), SpeechService.class);
getReactApplicationContext().bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
speechText = "";
tempText = "";

} else {
sendJSErrorEvent("Another instance of SpeechService is already running", 0);
}
}

@ReactMethod
public void stop() {
Log.i(TAG, "stop");
voiceRecorder.stop();
compositeDisposable.dispose();
getReactApplicationContext().unbindService(serviceConnection);
speechService = null;
Log.d(TAG, "stoped.");
}

@ReactMethod
public void setApiKey(String apiKey) {
Log.i(TAG, "setApiKey");
this.apiKey = apiKey;
}

@ReactMethod
public void setLangCode(String langCode) {
Log.i(TAG, "setLangCode");
this.langCode = langCode;
}
@ReactMethod
public void setMaxVoiceBlank(int maxVoiceBlank) {
Log.i(TAG, "setMaxVoiceBlank");
this.maxVoiceBlank = maxVoiceBlank;
}


@Override
public String getName() {
return TAG;
}

private void handleVoiceEvent(VoiceEvent event) {
switch (event.getState()) {
case START:
onVoiceStart();
break;
case VOICE:
onVoice(event.getData(), event.getSize());
break;
case END:
onVoiceEnd(event.getReason());
break;
}
}

private void onVoiceStart() {
Log.i(TAG, "onVoiceStart");
if (speechService != null) {
speechService.startRecognizing(voiceRecorder.getSampleRate(), apiKey, langCode);
}
}

private void onVoice(byte[] data, int size) {
Log.i(TAG, "onVoice");
if (speechService != null) {
speechService.recognize(data, size);
}
//# Send a voice data buffer to the encoder.
if (Encoding && voiceEnc.send(data, size) == VoiceEncoder.RETURN_ABEND) {
Encoding = false;
}

}

private void onVoiceEnd(int reason) {
Log.i(TAG, "onVoiceEnd("+reason+")");
if (speechService != null) {
speechService.finishRecognizing();
}
if (reason == VoiceRecorder.STOP_REASON_BLOCK) {
//# Stop the encoder.
if (Encoding) {
if (voiceEnc.send(null, VoiceEncoder.STOP_STREAM) != VoiceEncoder.RETURN_ABEND) {
Log.d(TAG, "onVoiceEnd sended end of stream");
voiceEnc.stop();
}
Encoding = false;
}
//# The calling of stop() on here has risk that produce an exception.
//# Therefore, another error event is issued by VoiceRecorder after this event.
//stop();
Log.d(TAG, "onVoiceEnd push text="+speechText+"temp="+tempText);
speechText = speechText + tempText + ".";
WritableMap params = Arguments.createMap();
params.putString(KEY_TEXT, speechText);
params.putBoolean(KEY_IS_FINAL, true);
sendJSEvent(getReactApplicationContext(), ON_SPEECH_RECOGNIZED, params);
}
Log.d(TAG, "onVoiceEnd exit");

}

private void handleSpeechEvent(SpeechEvent speechEvent) {
Log.i(TAG, speechEvent.getText() + " " + speechEvent.isFinal());
WritableMap params = Arguments.createMap();

//# I changed the specification here.
//# Even if the "isFinal" was returned from the SpeechService, the voices inputting and the speech
//# regonition is continued. The text of "isFinal" is merged with previouse texts.

//if (!TextUtils.isEmpty(speechEvent.getText())) {
// params.putString(KEY_TEXT, speechEvent.getText());
//} else {
// params.putString(KEY_TEXT, "");
//}
//params.putBoolean(KEY_IS_FINAL, speechEvent.isFinal());
//sendJSEvent(getReactApplicationContext(), ON_SPEECH_RECOGNIZED, params);
//if (speechEvent.isFinal()) {
// stop();
//}

boolean isfinal = speechEvent.isFinal();
String currenttext = speechEvent.getText();
if (TextUtils.isEmpty(currenttext)) {
currenttext = "";
}
if (isfinal) {
speechText = speechText + currenttext + ".";
tempText = "";
params.putString(KEY_TEXT, speechText);
}
else {
tempText = currenttext;
params.putString(KEY_TEXT, (speechText + tempText + "."));
}
params.putBoolean(KEY_IS_FINAL, false);
sendJSEvent(getReactApplicationContext(), ON_SPEECH_RECOGNIZED, params);

}

//# In serviceConnection, the calling this method be set to execute stop() after here.
private void handleErrorEvent(Throwable throwable) {
sendJSErrorEvent(throwable.getMessage(), 0);
}

private void sendJSErrorEvent(String message, int reasonCode){
Log.d(TAG, "ssendJSErrorEvent message="+message+", reasonCode="+reasonCode);
//# If the event from VoiceRecoder was "VR_STOP_MSG", this event is not notified to App.
//# In this case, the event is issued only to call stop() safely.
//# Its event is issued when voice blank reached the timeout.
if (!message.equals(VoiceRecorder.VR_STOP_MSG)) {
WritableMap params = Arguments.createMap();
params.putString(KEY_MESSAGE, message);
params.putInt(KEY_REASON_CODE, reasonCode);
sendJSEvent(getReactApplicationContext(), ON_SPEECH_RECOGNIZED_ERROR, params);
}

}

private void sendJSEvent(ReactContext reactContext,
String eventName,
WritableMap params) {
Log.d(TAG, "sendJSEvent params="+params);
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
Log.d(TAG, "sendJSEvent exit");
}
}
---------------------------------------------------


----VoiceRecorder.java-----------------------------
package com.reactlibrary.voice_recorder;

import android.util.Log;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;

import io.reactivex.Observable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.PublishSubject;

import static com.reactlibrary.voice_recorder.VoiceUtils.isHearingVoice;

public class VoiceRecorder {

private static final String TAG = "VoiceRecorder";
private static final int[] SAMPLE_RATE_CANDIDATES = new int[]{16000, 11025, 22050, 44100};

private static final int SPEECH_TIMEOUT_MILLIS = 1000;
private static final int MAX_SPEECH_LENGTH_MILLIS = 30 * 1000;
private static final int DEFAULT_SAMPLE_RATE = 16000;

public static final int STOP_REASON_PAUSE = 0;
public static final int STOP_REASON_BLOCK = 1;
public static final String VR_STOP_MSG = "Stopped inputting the voice.";

private int maxVoiceBlank = SPEECH_TIMEOUT_MILLIS;
private boolean fVoiceless = true;


private PublishSubject voiceEventPublishSubject = PublishSubject.create();

private PublishSubject voiceErrorEventPublishSubject = PublishSubject.create();

private Disposable voiceEventDisposable;

private AudioRecord audioRecord;

private byte[] buffer;

/**
* The timestamp of the last time that voice is heard.
*/
private long lastVoiceHeardMillis = Long.MAX_VALUE;

/**
* The timestamp when the current voice is started.
*/
private long voiceStartedMillis = Long.MAX_VALUE;

/**
* Starts recording audio.
*
*

The caller is responsible for calling {@link #stop()} later.


*/
public void start(int maxVoiceBlank) {
// Stop recording if it is currently ongoing.
stop();
if (maxVoiceBlank > SPEECH_TIMEOUT_MILLIS) {
this.maxVoiceBlank = maxVoiceBlank;
}

// Try to create a new recording session.
audioRecord = createAudioRecord();
if (audioRecord == null) {
voiceErrorEventPublishSubject.onNext(new RuntimeException("Cannot instantiate VoiceRecorder. Probably the android.permission.RECORD_AUDIO was not granted."));
return;
}
// Start recording.
audioRecord.startRecording();
// Start processing the captured audio.
voiceEventDisposable = createVoiceEventObservable()
.subscribeOn(Schedulers.io())
.subscribe(voiceEventPublishSubject::onNext);
}

/**
* Stops recording audio.
*/
public void stop() {
if (voiceEventDisposable != null) {
voiceEventDisposable.dispose();
}
dismiss();
if (audioRecord != null) {
audioRecord.stop();
audioRecord.release();
audioRecord = null;
}
buffer = null;
}

/**
* Dismisses the currently ongoing utterance.
*/
public void dismiss() {
if (lastVoiceHeardMillis != Long.MAX_VALUE) {
voiceStartedMillis = Long.MAX_VALUE;
lastVoiceHeardMillis = Long.MAX_VALUE;
voiceEventPublishSubject.onNext(VoiceEvent.end(STOP_REASON_PAUSE));
}
}

/**
* Retrieves the sample rate currently used to record audio.
*
* @return The sample rate of recorded audio.
*/
public int getSampleRate() {
if (audioRecord != null) {
return audioRecord.getSampleRate();
}
return DEFAULT_SAMPLE_RATE;
}

public Observable getVoiceEventObservable() {
return voiceEventPublishSubject;
}

public Observable getVoiceErrorEventObservable() {
return voiceErrorEventPublishSubject;
}

/**
* Creates a new {@link AudioRecord}.
*
* @return A newly created {@link AudioRecord}, or null if it cannot be created (missing
* permissions?).
*/
private AudioRecord createAudioRecord() {
for (int sampleRate : SAMPLE_RATE_CANDIDATES) {
final int sizeInBytes = AudioRecord.getMinBufferSize(
sampleRate,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT
);
if (sizeInBytes == AudioRecord.ERROR_BAD_VALUE) {
continue;
}
final AudioRecord audioRecord = new AudioRecord(
MediaRecorder.AudioSource.MIC,
sampleRate,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT,
sizeInBytes
);
if (audioRecord.getState() == AudioRecord.STATE_INITIALIZED) {
buffer = new byte[sizeInBytes];
return audioRecord;
} else {
audioRecord.release();
}
}
return null;
}

private Observable createVoiceEventObservable() {
return Observable.create(emitter -> {
while (!emitter.isDisposed()) {
final int size = audioRecord.read(buffer, 0, buffer.length);
final long now = System.currentTimeMillis();
//# There are the sounds.
if (isHearingVoice(buffer, size)) {
if (voiceStartedMillis == Long.MAX_VALUE) {
voiceStartedMillis = now;
}
if (fVoiceless) {
emitter.onNext(VoiceEvent.start());
fVoiceless = false;
}
Log.i(TAG, "input voice("+size+").");

emitter.onNext(VoiceEvent.voice(buffer, size));
lastVoiceHeardMillis = now;
//# break off long speech with the max time.
if (now - voiceStartedMillis > MAX_SPEECH_LENGTH_MILLIS) {
Log.i(TAG, "reached the limit of long speech("+MAX_SPEECH_LENGTH_MILLIS+")!!!");
voiceStartedMillis = Long.MAX_VALUE;

lastVoiceHeardMillis = Long.MAX_VALUE;
emitter.onNext(VoiceEvent.end(STOP_REASON_BLOCK));
//# If the callee's stop() is executed in above event handlling, a exection occur.
//# Therefore, after the event handlling of the following callback, the stop() of callee is executed.
voiceErrorEventPublishSubject.onNext(new RuntimeException(VR_STOP_MSG));

}
//# silence 0: recode to start a speech.
} else if (lastVoiceHeardMillis == Long.MAX_VALUE) {
lastVoiceHeardMillis = now;
} else {
//# silence 1: is not silence-state yet.
if (voiceStartedMillis != Long.MAX_VALUE && !fVoiceless) {
emitter.onNext(VoiceEvent.voice(buffer, size));
}
//# silence 3: reached the longest silence-state.
if (now - lastVoiceHeardMillis > maxVoiceBlank) {
Log.i(TAG, "max voice blank("+maxVoiceBlank+")!!!");
voiceStartedMillis = Long.MAX_VALUE;
lastVoiceHeardMillis = Long.MAX_VALUE;
emitter.onNext(VoiceEvent.end(STOP_REASON_BLOCK));
//# If the callee's stop() is executed in above event handlling, a exection occur.
//# Therefore, after the event handlling of the following callback, the stop() of callee is executed.
voiceErrorEventPublishSubject.onNext(new RuntimeException(VR_STOP_MSG));
}
//# silence 2: stop of sound data
else if (now - lastVoiceHeardMillis > SPEECH_TIMEOUT_MILLIS && !fVoiceless) {
Log.d(TAG, "found silence("+SPEECH_TIMEOUT_MILLIS+")!!!");
fVoiceless = true;
emitter.onNext(VoiceEvent.end(STOP_REASON_PAUSE));
}
}

}
});
}
}
---------------------------------------------------


----VoiceEvent.java--------------------------------
package com.reactlibrary.voice_recorder;

public class VoiceEvent {

private State state;
private byte[] data;
private int size;

public static VoiceEvent start() {
return new VoiceEvent(State.START, null, 0);
}

public static VoiceEvent voice(byte[] data, int size) {
return new VoiceEvent(State.VOICE, data, size);
}

public static VoiceEvent end(int reason) {
//reason=STOP_REASON_PAUSE(0):requested stop, STOP_REASON_BLOCK(1):for error handling
return new VoiceEvent(State.END, null, reason);
}

private VoiceEvent(State state, byte[] data, int size) {
this.state = state;
this.data = data;
this.size = size;
}

public State getState() {
return state;
}

public byte[] getData() {
return data;
}

public int getSize() {
return size;
}

public int getReason() {
//When event type is 'end', the 'size' is the reason code.
return size;
}


@Override
public String toString() {
return state.toString();
}

public enum State {
START, VOICE, END
}
}
---------------------------------------------------


----VoiceEncoder.java------------------------------
package com.reactlibrary.voice_encoder;


import android.util.Log;
import android.media.MediaCodec;
import android.media.MediaCodec.BufferInfo;
import android.media.MediaCodec.CodecException;
import android.media.MediaCodec.Callback;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.AudioFormat;
import android.media.MediaMuxer;

import java.lang.Thread;
import java.lang.Exception;
import java.lang.InterruptedException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;


public class VoiceEncoder {
private static final String TAG = "VoiceEncoder";
private static final String AUDIO_FILE_MIME_TYPE = "audio/mp4a-latm";
private static final int CODEC_TIMEOUT_IN_MS = 5000;

public static final int RETURN_NORMAL = 0;
public static final int RETURN_ABEND = -1;
public static final int STOP_STREAM = -1;

private static final int ENC_READY = 1;
private static final int ENC_IDLE = 0;
private static final int ENC_COMPLETED = -1;

private VoiceEncoder myObj;
private MediaCodec codec = null;
private MediaMuxer mux = null;
private int encState = ENC_COMPLETED;
private double presentationTimeUs;
private int audioTrackIdx;
private int totalBytesRead;
private int sampleRate;
private byte[] rawData;
private int rawSize;

public int start(int samplerate, int bitrate, String encfilepath) {
try {
myObj = this;
sampleRate = samplerate;
Log.i(TAG, "start: samplerate="+sampleRate+", bitrate="+bitrate);
Log.i(TAG, "start: path="+encfilepath);
File outputFile = new File(encfilepath);
if (outputFile.exists()) outputFile.delete();
//outputFile.createNewFile();
//fos = new FileOutputStream(outputFile);
mux = new MediaMuxer(outputFile.getAbsolutePath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);

MediaFormat outputFormat = MediaFormat.createAudioFormat(AUDIO_FILE_MIME_TYPE, samplerate, 1);
outputFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
outputFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
outputFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
//outputFormat.setInteger(MediaFormat.KEY_CHANNEL_MASK, AudioFormat.CHANNEL_CONFIGURATION_MONO);
outputFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 16384);
//double durationInMs = (inputFile.length()/16000)*1000;
//outputFormat.setLong(MediaFormat.KEY_DURATION, (long)durationInMs );

codec = MediaCodec.createEncoderByType(AUDIO_FILE_MIME_TYPE);
codec.setCallback(new MediaCodec.Callback() {
@Override
public void onInputBufferAvailable(MediaCodec myCodec, int index) {
Log.i(TAG, "# onInputBufferAvailable: start. index="+index);
synchronized (myObj) {
while (encState == ENC_IDLE) {
try {
myObj.wait();
}
//catch (InterruptedException e) {
// Log.e(TAG, "# onInputBufferAvailable: timeout!");
catch (Exception e) {
Log.e(TAG, "# onInputBufferAvailable: exception!", e);
return;
}
}

if (encState != ENC_COMPLETED) {
ByteBuffer dstBuf = myCodec.getInputBuffer(index);
dstBuf.clear();
if (rawSize == STOP_STREAM) { // -1 implies EOS
if (totalBytesRead > 0) {
Log.d(TAG, "# onInputBufferAvailable: end stream.");
myCodec.queueInputBuffer(index, 0, 0, (long) presentationTimeUs, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
}
else {
Log.d(TAG, "# onInputBufferAvailable: stop codec.");
myCodec.stop();
}
} else {
Log.d(TAG, "# onInputBufferAvailable: encode data. size="+rawSize);
if (rawSize > 0) {
totalBytesRead += rawSize;
dstBuf.put(rawData, 0, rawSize);
}
myCodec.queueInputBuffer(index, 0, rawSize, (long) presentationTimeUs, 0);
presentationTimeUs = 1000000L * (totalBytesRead/2) / sampleRate;
}
rawSize = 0;
encState = ENC_IDLE;
}
myObj.notifyAll();
} //end synchronized block
Log.d(TAG, "# onInputBufferAvailable: end.");
} //end onInputBufferAvailable()

@Override
public void onOutputBufferAvailable(MediaCodec myCodec, int outBufIndex, BufferInfo bufInfo) {
Log.i(TAG, "# onOutputBufferAvailable: start. bufInfo.offset/size="+bufInfo.offset+"/"+bufInfo.size);
synchronized (myObj) {
if (encState != ENC_COMPLETED) {
ByteBuffer encodedData = myCodec.getOutputBuffer(outBufIndex);
encodedData.position(bufInfo.offset);
encodedData.limit(bufInfo.offset + bufInfo.size);
if ((bufInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0 && bufInfo.size != 0) {
myCodec.releaseOutputBuffer(outBufIndex, false);
}else{
Log.d(TAG, "# onOutputBufferAvailable: to Muxer.");
mux.writeSampleData(audioTrackIdx, encodedData, bufInfo);
myCodec.releaseOutputBuffer(outBufIndex, false);
}
}
}
Log.d(TAG, "# onOutputBufferAvailable: exit.");
}

@Override
public void onOutputFormatChanged(MediaCodec myCodec, MediaFormat format) {
Log.i(TAG, "# onOutputFormatChanged: start. formt="+format);
audioTrackIdx = mux.addTrack(format);
mux.start();
}

@Override
public void onError(MediaCodec myCode, CodecException e) {
Log.e(TAG, "# onError", e);
}
});

codec.configure(outputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE );
rawSize = 0;
encState = ENC_IDLE;
presentationTimeUs = 0;
audioTrackIdx = 0;
totalBytesRead = 0;
codec.start();
Log.d(TAG, "start: end.");

return RETURN_NORMAL;
}
//catch (FileNotFoundException e) {
// Log.e(TAG, "File not found!", e);
catch (IOException e) {
Log.e(TAG, "start : exception!", e);
this.stop();
return RETURN_ABEND;
}
}

public int send(byte[] rawdata, int rawsize) {
Log.i(TAG, "send : start. data size="+rawsize);
synchronized (myObj) {
while (encState == ENC_READY) {
try {
myObj.wait();
}
//catch (InterruptedException e) {
// Log.e(TAG, "send: timeout!");
catch (Exception e) {
Log.e(TAG, "send: exception!", e);
this.stop();
return RETURN_ABEND;
}
}

if (encState != ENC_COMPLETED) {
rawData = rawdata;
rawSize = rawsize;
encState = ENC_READY;
}

myObj.notifyAll();
} //end synchronized block
Log.d(TAG, "send: end.");
return RETURN_NORMAL;
}

public void stop() {
Log.i(TAG, "stop");
try {
Thread.sleep(2);

synchronized (myObj) {
encState = ENC_COMPLETED;
myObj.notifyAll();

if (codec != null) {
codec.stop();
codec.release();
codec = null;
}
if (mux != null) {
if (audioTrackIdx != 0) {
mux.stop();
}
mux.release();
mux = null;
}
}
}
//catch (InterruptedException e) {
catch (Exception e) {
Log.e(TAG, "stop: exception!", e);
}
Log.d(TAG, "stoped");
}

}

---------------------------------------------------


----SpeechService.java----------------------------
package com.reactlibrary.speech_service;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

import com.google.cloud.speech.v1.RecognitionConfig;
import com.google.cloud.speech.v1.SpeechGrpc;
import com.google.cloud.speech.v1.SpeechRecognitionAlternative;
import com.google.cloud.speech.v1.StreamingRecognitionConfig;
import com.google.cloud.speech.v1.StreamingRecognitionResult;
import com.google.cloud.speech.v1.StreamingRecognizeRequest;
import com.google.cloud.speech.v1.StreamingRecognizeResponse;
import com.google.protobuf.ByteString;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

import io.grpc.ManagedChannel;
import io.grpc.internal.DnsNameResolverProvider;
import io.grpc.okhttp.OkHttpChannelProvider;
import io.grpc.stub.StreamObserver;
import io.reactivex.Observable;
import io.reactivex.subjects.PublishSubject;

public class SpeechService extends Service {

private static final String TAG = "SpeechService";
private static final List SCOPE =
Collections.singletonList("https://www.googleapis.com/auth/cloud-platform");
private static final String HOSTNAME = "speech.googleapis.com";
private static final int PORT = 443;
private static final String LANGUAGE_CODE = "en-US";
private static final int TERMINATION_TIMEOUT_SECONDS = 5;
private String LanguageCode = LANGUAGE_CODE;

private final SpeechBinder binder = new SpeechBinder();
private SpeechGrpc.SpeechStub speechStub;
private StreamObserver requestObserver;
private PublishSubject speechEventPublishSubject = PublishSubject.create();
private PublishSubject speechErrorEventPublishSubject = PublishSubject.create();

private final StreamObserver responseObserver
= new StreamObserver() {
@Override
public void onNext(StreamingRecognizeResponse response) {
String text = null;
boolean isFinal = false;
if (response.getResultsCount() > 0) {
final StreamingRecognitionResult result = response.getResults(0);
isFinal = result.getIsFinal();
if (result.getAlternativesCount() > 0) {
final SpeechRecognitionAlternative alternative = result.getAlternatives(0);
text = alternative.getTranscript();
}
}
if (text != null) {
speechEventPublishSubject.onNext(new SpeechEvent(text, isFinal));
}
}

@Override
public void onError(Throwable t) {
speechErrorEventPublishSubject.onNext(t);
}

@Override
public void onCompleted() {
Log.i(TAG, "API completed.");
}

};

public static SpeechService from(IBinder binder) {
return ((SpeechBinder) binder).getService();
}

@Override
public void onDestroy() {
super.onDestroy();
// Release the gRPC channel.
if (speechStub != null) {
final ManagedChannel channel = (ManagedChannel) speechStub.getChannel();
if (channel != null && !channel.isShutdown()) {
try {
channel.shutdown()
.awaitTermination(TERMINATION_TIMEOUT_SECONDS, TimeUnit.SECONDS);
} catch (InterruptedException e) {
speechErrorEventPublishSubject.onNext(e);
}
}
speechStub = null;
}
}

@Override
public IBinder onBind(Intent intent) {
return binder;
}

public Observable getSpeechEventObservable() {
return speechEventPublishSubject;
}

public Observable getSpeechErrorEventObservable() {
return speechErrorEventPublishSubject;
}

/**
* Starts recognizing speech audio.
*
* @param sampleRate The sample rate of the audio.
*/
public void startRecognizing(int sampleRate, String apiKey, String langCode) {
Log.i(TAG, "start Recognizing.");
final ManagedChannel channel = new OkHttpChannelProvider()
.builderForAddress(HOSTNAME, PORT)
.nameResolverFactory(new DnsNameResolverProvider())
.intercept(new GoogleCredentialsInterceptor(apiKey))
.build();

if (langCode != null) {
LanguageCode = langCode;
}


speechStub = SpeechGrpc.newStub(channel);
requestObserver = speechStub.streamingRecognize(responseObserver);
requestObserver.onNext(StreamingRecognizeRequest.newBuilder()
.setStreamingConfig(StreamingRecognitionConfig.newBuilder()
.setConfig(RecognitionConfig.newBuilder()
.setLanguageCode(LanguageCode)
.setEncoding(RecognitionConfig.AudioEncoding.LINEAR16)
.setSampleRateHertz(sampleRate)
.build())
.setInterimResults(true)
.setSingleUtterance(true)
.build())
.build());
}

/**
* Recognizes the speech audio. This method should be called every time a chunk of byte buffer
* is ready.
*
* @param data The audio data.
* @param size The number of elements that are actually relevant in the {@code data}.
*/
public void recognize(byte[] data, int size) {
Log.d(TAG, "recognize. size="+size);
if (requestObserver == null) {
return;
}
// Call the streaming recognition API
requestObserver.onNext(StreamingRecognizeRequest.newBuilder()
.setAudioContent(ByteString.copyFrom(data, 0, size))
.build());
}

/**
* Finishes recognizing speech audio.
*/
public void finishRecognizing() {
Log.i(TAG, "finish recognizing");
if (requestObserver == null) {
return;
}
requestObserver.onCompleted();
requestObserver = null;
}

private class SpeechBinder extends Binder {

SpeechService getService() {
return SpeechService.this;
}

}
}
---------------------------------------------------


react-native-google-speech-apiのExample/App.jsをベースとしたコードです。
もう、余り原型なくなっちゃてるので、変更箇所の朱書きは省略します。
----Speech.js--------------------------------------
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
* @flow
*/

import {
NativeModules,
Platform,
NativeEventEmitter,
DeviceEventEmitter,
SafeAreaView,
ScrollView,
Text,
View,
Button,
Alert,
PermissionsAndroid,
} from 'react-native';

import React, { Component } from 'react';
import { AudioUtils } from 'react-native-audio'

const { GoogleSpeechApi } = NativeModules;

const EventEmitter = Platform.select({
android: DeviceEventEmitter,
ios: new NativeEventEmitter(GoogleSpeechApi),
});

const Btn_cap = ["認識開始", "認識中"];

export default class Speech extends Component {
constructor(props) {
super(props);
this.state = {
currentText: "",
previousTexts: "",
button: "",
btn_state: 0,
};
this.state.button = Btn_cap[this.state.btn_state];
this.speechcnt = 0;
this.filepath = AudioUtils.DocumentDirectoryPath;
}

componentDidMount(){
GoogleSpeechApi.setMaxVoiceBlank(5000);
GoogleSpeechApi.setLangCode("ja_JP");
GoogleSpeechApi.setApiKey("AIzaSyCU0HAWLwS-nK_3UKOrqm6nzwdAhT5t6Zo");
EventEmitter.addListener('onSpeechRecognized', (event) => {
var previousTexts = this.state.previousTexts;
var currentText = event['text'];
if (event['isFinal']){
previousTexts = currentText + "\n" + previousTexts;
currentText = "";
this.state.btn_state = 0;
}

this.setState({
currentText: currentText,
previousTexts: previousTexts,
button: Btn_cap[this.state.btn_state]
});
});

EventEmitter.addListener('onSpeechRecognizedError', (error) => {
var previousTexts = this.state.previousTexts;
var currentText = this.state.currentText;
if (error['reasonCode'] != 1) {
Alert.alert("Error occured", error['message']);
}
else {
previousTexts = currentText + "\n" + previousTexts;
currenttext = "";
}
this.state.btn_state = 0;
this.setState({
currenttext: currentText,
previousTexts: previousTexts,
button: Btn_cap[this.state.btn_state]
});
});

}


startListening = () => {
this.setState({
button: Btn_cap[this.state.btn_state]
});
var encfilepath = this.filepath + "/speech" + this.speechcnt + ".m4a";
GoogleSpeechApi.start(encfilepath);
}

requestAudioPermission = async () => {
if (this.state.btn_state) {
console.log('double clicked button');
return;
}
this.state.btn_state = 1;
this.speechcnt++;

try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
{
title: 'Record Audio Permission',
message:
'App needs access to your microphone ' +
'so you can convert speech to text.',
buttonNeutral: 'Ask Me Later',
buttonNegative: 'Cancel',
buttonPositive: 'OK',
},
);
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
console.log('permission granted');
this.startListening();
} else {
console.log('permission denied');
}
} catch (err) {
console.warn(err);
this.state.btn_state = 0;
}
}

render() {
return (
<SafeAreaView>
<ScrollView
contentInsetAdjustmentBehavior="automatic">
<View style={{ margin: 20 }}>
<Button
title={this.state.button}
onPress={Platform.OS === 'ios' ? this.startListening : this.requestAudioPermission}/>
<Text>{this.state.currentText}</Text>
<Text>{this.state.previousTexts}</Text>
</View>
</ScrollView>
</SafeAreaView>
);
}
}
---------------------------------------------------



◆蛇足
どっか(MediaRecoder?)で見かけたJavaの記述で、意味が分からなかったもの。
せっかくメモしてあったので・・・

今回のreact-native-google-speech-api改造には、関連ありません。

-ちょっと脱線:関数名.bind(・・・)の意味-
関数内で使用するthis、および引数を予め決めておくことができるメソッド(なんだと思う)。
----例えば-----------------------------------------
function abc(num1, num2) {
return this.basenum + num1 + num2;
}

var obj1 = {basenum:10};
var test_abc = abc.bind(obj1, 1, 6);

console.log(test_abc());
---------------------------------------------------

とやったら、コンソールには「17」が出力される。
.bind()の第1引数には、その関数内でthisとして使いたいものを指定するようです。
何がうれしいのかいまいち理解できてません。




はい。ぼやきが長かったですね。
ただし、エミュレータ上で動かしてるせいか、認識率がよくない。
マイク入力のせいなのか、Google Cloud(Speech to Text)との通信のタイミングが録音のせいで変わったせいなのか。
もう少し、調べます。

次回、認識率の改善か、あるいはチャットアプリに音声認識を組合せる作業の話になろうかと思います。

では、今回はこの辺で。ごきげんよう。
m(__m)


スポンサーサイト





コメントの投稿

非公開コメント

カレンダー
01 | 2024/02 | 03
- - - - 1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 - -
プロフィール

さるもすなる

Author:さるもすなる
さるです。別HPサイト「さるもすなる」から侵食してきました。 山菜/きのこ、それとタイトルにしたPPバンド籠のことをメインに徒然に・・・・暇を持て余したさるの手仕事:男手芸のブログってことで。

最新記事
最新コメント
月別アーカイブ
カテゴリ
天気予報

-天気予報コム- -FC2-
本家のHPのトップ
山菜や茸の話です
PPバンドの籠作品と作り方です
投稿をお待ちしております



PVアクセスランキング にほんブログ村 にほんブログ村 ハンドメイドブログへ



マニュアルのお申し込み



検索フォーム
リンク
RSSリンクの表示
ブロとも申請フォーム

この人とブロともになる

QRコード
QR