FC2ブログ

スマフォのアプリを作りたい(35):単語音声認識制御のデバッグ

   プログラミング [2021/01/06]
前回まで、JuliusのAndroidスマフォ上へのポーティングの話を延々書いてきて、一旦「完了?」にしたのですが・・・
どーも認識率が悪過ぎるし、何回か試すと動きが固まるし。

なので、もうちょっとデバッグしました。


◆単語認識率0%の件
JuliusをPC上で評価して、さらに.wavファイルを入力として味見したときには見なかった状態:認識率0%。
これは、Juliusのせいではなく、使い方の問題と予想して調べ始めました。


これまで、Windows版、AndroidStudio環境でも確認していなかったのは、
・入力デバイスを標準入力とした場合の動作(-input stdin)
・rawデータを入力とした場合の動作
です。(動作環境を作る手間がめんどうでサボった。)

まずは、サンプリイングレートとかビット深度とかを改めて、VoiceRecorder側とJulius側への指定が一致してるか確認しました。
大丈夫そうです。

Juliusのマニュアル(3.4.3 標準入力)では、以下のように書かれてました。
『標準入力から RAW 形式あるいは WAV 形式の音声入力を受け取ることができる.これには-input stdinを指定する.RAW ファイルの場合、サンプリングレートがチェックされないので注意すること.』
これしか書いてません。
・RAWとWAVは勝手に判断してくれるんだろうか?
・RAW形式って、PCMデータが頭からべた書きされているファイルだよね?
とか不安になります。

なので、まずはWindows環境で、標準入力にリダイレクトでrawファイルを入力して見ます。
まずは、raw形式の音声データを作るところから。
<Audacity手順1>:rawファイルとして出力
-インストール済みのAudacityを起動して、変換元の.wavファイルを開きます。
-メニュー[ファイル]-[音声の書き出し]を選択
-「ファイルの種類」に「その他の非圧縮ファイル」を選択
-ヘッダ:「RAW(header-less)」、
 エンコーディング:「Signed 16-bit PCM」を指定
-「ファイル名」欄の名前を確認した後、[保存]ボタンをクリック


Windows版のJuliusで「-input stdin」を試してみます。
コマンドラインでリダイレクトしたら、rawファイルを入力できるのではないかと・・・
あてずっぽうです。
----コマンドプロンプト-----------------------------
C:\WINDOWS\system32>cd /d d:\appmake\julius_base\for_teburarec

d:\AppMake\julius_base\for_teburarec>chcp 65001
Active code page: 65001

d:\AppMake\julius_base\for_teburarec>..\dictation-kit-4.5\bin\windows\julius.exe -w teburarecword.dict -h ..\dictation-kit-4.5\model\phone_m\jnas-mono-16mix-gid.binhmm -input stdin < w_kiroku.raw
STAT: jconf successfully finalized
STAT: *** loading AM00 _default



------
### read waveform input
Stat: adin_stdin: reading wavedata from stdin...
STAT: 925 samples (0.06 sec.)
STAT: ### speech analysis (waveform -> MFCC)
### Recognition: 1st pass (LR beam)
....WARNING: 00 _default: input processed, but no survived word found
<search failed>

------
### read waveform input
Error: adin_stdin: stdin reached EOF
reached end of input on stdin

d:\AppMake\julius_base\for_teburarec>
---------------------------------------------------

結果は、ファイルから読み込めてないみたいです。

Juliusには「-input rawfile」という指定も可能なので、そっちも試してみました。
----コマンドプロンプト-----------------------------
C:\WINDOWS\system32>cd /d d:\appmake\julius_base\for_teburarec

d:\AppMake\julius_base\for_teburarec>chcp 65001
Active code page: 65001

d:\AppMake\julius_base\for_teburarec>..\dictation-kit-4.5\bin\windows\julius.exe -w teburarecword.dict -h ..\dictation-kit-4.5\model\phone_m\jnas-mono-16mix-gid.binhmm -input rawfile



------
### read waveform input
enter filename->w_kiroku.raw
Stat: adin_file: input speechfile: w_kiroku.raw
STAT: 25078 samples (1.57 sec.)
STAT: ### speech analysis (waveform -> MFCC)
### Recognition: 1st pass (LR beam)
.........................................................................................
pass1_best: チェック
pass1_best_wordseq: チェック
pass1_best_phonemeseq: silB ch e q k u silE
pass1_best_score: -3824.508789
sentence1: チェック
wseq1: チェック
phseq1: silB ch e q k u silE
cmscore1: 0.353
score1: -3824.508789

------
### read waveform input
enter filename->
---------------------------------------------------

正しく認識しませんでした。
誤認識ぶりもReactNative版のときと同じです。
なので、続けて元のwavファイルを指定してみました。
----コマンドプロンプト-----------------------------
enter filename->w_kiroku.wav
Stat: adin_file: input speechfile: w_kiroku.wav
STAT: 25078 samples (1.57 sec.)
STAT: ### speech analysis (waveform -> MFCC)
### Recognition: 1st pass (LR beam)
.........................................................................................
pass1_best: 記録
pass1_best_wordseq: 記録
pass1_best_phonemeseq: silB k i r o k u silE
pass1_best_score: -4061.207031
sentence1: 記録
wseq1: 記録
phseq1: silB k i r o k u silE
cmscore1: 0.972
score1: -4061.207031

------
### read waveform input
enter filename->
---------------------------------------------------

wavファイルは正常認識されました。
・・・
改めてraw形式の音声ファイルについてぐぐってみました。
16bitデータの場合は、通常ビッグエンディアンになってなければダメらしいことが分かった。
一方、
・wav形式のPCMデータは・・・リトルエンディアンなのか?
・それを、Audacityで単純にraw出力した場合はリトルのまま?

試した方が早いかと思って以下をやってみました。
-Audcityを再度起動して、rawファイルを読み込んでみたら、読み込み時にビッグかリトルを指定するようになってます。
<Audacity手順2>:rawファイルの読み込み
-メニュー[ファイル]-[取り込み]-[ロー(raw)データの取り込み]を選択
-ファイル選択ダイアログで、rawファイルを指定して[開く]
-ダイアログ上のエンコーディング/バイト順序/チャンネル/サンプリング周波数等を指定し[取り込み]

-ビッグを指定してオープンすると波形が化けて、再生してもまともな言葉としては認識不能になりました。
-リトルを指定してオープンすると元の言葉が聞き取れます。

つまり、最初に.wavを.rawとして出力したデータはリトルエンディアンになっているということだと思います。
そこで、
-最初に作成したrawファイルを敢えてビッグということにしてオープンします。
-そのデータを再度、前の<手順1>でセーブし直します。
-新たに作成したrawファイルをAudacityで再生してみると、2倍速再生のようになりました。
-でも、さるの耳でも言葉として聞き取れるので、Juliusに入力してみました。
----コマンドプロンプト-----------------------------
enter filename->w_kirokuB.raw
Stat: adin_file: input speechfile: w_kirokuB.raw
STAT: 25078 samples (1.57 sec.)
STAT: ### speech analysis (waveform -> MFCC)
### Recognition: 1st pass (LR beam)
.........................................................................................
pass1_best: 記録
pass1_best_wordseq: 記録
pass1_best_phonemeseq: silB k i r o k u silE
pass1_best_score: -3679.109375
sentence1: 記録
wseq1: 記録
phseq1: silB k i r o k u silE
cmscore1: 0.933
score1: -3679.109375
---------------------------------------------------

正常認識されました。

ReactNative版もこれが原因ではないかと思われます。
つまり、VoiceRecorder(com.reactlibrary.voice_recorder)から渡されるPCMデータはリトルエンディアンであって、それをそのまま、Julius(-input atdin)に渡したらダメということ。

・対策方法は、ヘッダデータを付してwavデータとして渡すか
 →予めデータ長を把握してからでないとJuliusに渡せない。
・Julius側バッファにコピーする際に、ビッグエンディアンにする。
 →性能負荷が増える。

でも、後者ですよね。

続きます。


コードは、以下のように変更しました。
<Julius-Lib>:<プロジェクト・フォルダ>\node_modules\react-native-google-speech-api\calljulius\android\src\main\cpp

<Julius-Lib>\julius-entry.cpp
----julius-entry.cpp-------------------------------


//******************************************************************************
// function :音声データ(16bit)をエンディアン変換してコピー
// parameter :dst_p -コピー先バッファアドレス
// src_p -コピー元バッファアドレス
// lng -データサイズ
// return :音声データ長
//******************************************************************************
int convlowhig(
char* dst_p,
char* src_p,
int lng
)
{
int conv_size = lng;
while(lng > 1) {
*dst_p = *(src_p+1);
dst_p++;
*dst_p = *src_p;
dst_p++;
src_p += 2;
lng -= 2;
}
if (lng > 0) {
*dst_p = *src_p;
lng--;
}
return (conv_size - lng);
}



//******************************************************************************
// function :コールバック内で使用する、音声データのコピー処理
// parameter :env -JNIインタフェース構造体へのポインタ
// jObj -呼出し元JAVAクラスのインスタンス
// jvoicebuf -Java音声データバッファ
// return :コピー先バッファの残りのサイズ
//******************************************************************************
extern "C" JNIEXPORT jint JNICALL
Java_com_teburarec_calljulius_JlibGate_jlibCopy(
JNIEnv* env,
jobject jObj,
//jclass jcls,
jbyteArray jvoicebuf //データ格納先Javaバッファ
)
{
int dst_lng = g_dst_lng;
char* dst_curp = g_dst_curp;
int cpy_lng;
char* src_buf;
int src_lng;
jboolean isCopy;

fprintf(stderr, "*** jlibCopy. >>>");

src_lng = (int) env->GetArrayLength(jvoicebuf);
fprintf(stderr, "data size=%d", src_lng);
if (src_lng <= 0) return -1;
//jbyteArrayバッファポインタの取得
src_buf = (char *) env->GetByteArrayElements(jvoicebuf, &isCopy);
cpy_lng = min(dst_lng, src_lng);
//memcpy(dst_curp, src_buf, cpy_lng);
convlowhig(dst_curp, src_buf, cpy_lng); //PCMデータをエンディアン変換コピー

dst_curp += cpy_lng;
dst_lng -= cpy_lng;
//バッファに入りきらなかった分を一時保存
src_lng -= cpy_lng;
if (src_lng > 0) {
//memcpy(g_tmpbuf, src_buf+cpy_lng, src_lng);
convlowhig(g_tmpbuf, src_buf+cpy_lng, src_lng); //PCMデータをエンディアン変換コピー

g_tmpsize = src_lng;
}
if (isCopy) {
//jbyteArrayバッファポインタの解放
env->ReleaseByteArrayElements(jvoicebuf, (jbyte *)src_buf, JNI_ABORT);
}

return (jint) dst_lng;
}


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

エミュレータ上では効果が分からない程の認識率でした。
※エミュレータ上では、PCに接続したヘッドセットの音声は品質がだいぶ劣化するようです。

実機ではそれなりに単語認識できるようになりました。


◆繰り返す内に動作が固まる件
ログの出力から推測するに、
前の単語認識が完了しない内に次の発話を返したときの同期制御が不完全なためでした。
原因はすぐに分かったのですが、どう直すかで悩みました。
3パターン程試して、結果こんな感じに。

<RN-Google-speech>:<プロジェクト・フォルダ>\node_modules\react-native-google-speech-api\android\src\main\java\com\reactlibrary

<RN-Google-speech>\GoogleSpeechApiModule.java
----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 com.teburarec.calljulius.JlibGate; //## JuliusLib I/F

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 EncFilePath;
private String speechText;
private String tempText;
//## standalone regognization engine
private JlibGate jlibGate = null; //## JuliusLib I/F object

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)
);

//## create a instance of JuliusLib and start it.
if (jlibGate == null) {
jlibGate = new JlibGate(getCurrentActivity());
if (!jlibGate.jlib_call_start(getSingleWord)) {
Log.i(TAG, "Faile to start the JuliusLib.");
jlibGate.recogStep = JlibGate.STEP_STARTWORD;
}
}
voiceRecorder.start(maxVoiceBlank);
//# Start the encoder.
if (!Encoding) {
if (voiceEnc.start(voiceRecorder.getSampleRate(), 128000, EncFilePath) != VoiceEncoder.RETURN_ABEND) {
Encoding = true;
}
}
}

@Override
public void onServiceDisconnected(ComponentName name) {
speechService = null;
//## stop JuliusLib.
jlibGate.jlib_call_stop();
jlibGate = 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");
EncFilePath = encFilePath;
if (apiKey == null) {
sendJSErrorEvent("call setApiKey() with valid access token before calling start()", 0);
}
if (compositeDisposable != null) {
compositeDisposable.dispose();
}
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);
}
if (jlibGate != null) {
jlibGate.recogStep = JlibGate.STEP_STARTWORD;
}

}

@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");
boolean fstarted = false;
//## start recognizing
if (jlibGate.recogStep != JlibGate.STEP_FREESPEECH) {
fstarted = jlibGate.jlib_call_recognize();
}
if (!fstarted && speechService != null) {
speechService.startRecognizing(voiceRecorder.getSampleRate(), apiKey, langCode);
}
Log.v(TAG, "onVoiceStart end.");
}

private void onVoice(byte[] data, int size) {
Log.i(TAG, "onVoice");
//## send voice data
if (jlibGate.recogStep != JlibGate.STEP_FREESPEECH) {
jlibGate.jlib_call_send(data);
Log.i(TAG, "send size=("+size+")");
}
else {
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 (jlibGate.recogStep != JlibGate.STEP_FREESPEECH) {
jlibGate.jlib_call_send(null);
Log.i(TAG, "send blank");
}
else {
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);
}

//## define callback for calljulius
private JlibGate.JlibCallback getSingleWord = new JlibGate.JlibCallback() {
public int NotifyRecognized(String word) {
Log.i(TAG, "recognized string:"+word);
WritableMap params = Arguments.createMap();
String currenttext = word;
//## check a word and progress the step.
//case STEP_STARTWORD:
//case STEP_OBJECTWORD:
//case STEP_ITEMWORD:
//case STEP_FREESPEECH:
if (!TextUtils.isEmpty(currenttext)) {
GoogleSpeechApiModule.this.speechText += (currenttext + "/");
params.putString(KEY_TEXT, GoogleSpeechApiModule.this.speechText);
params.putBoolean(GoogleSpeechApiModule.this.KEY_IS_FINAL, false);
GoogleSpeechApiModule.this.sendJSEvent(getReactApplicationContext(),
GoogleSpeechApiModule.this.ON_SPEECH_RECOGNIZED, params);
}
//## free speech start.
jlibGate.recogStep = JlibGate.STEP_FREESPEECH;
return jlibGate.recogStep;
}
};

//# 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");
}
}
---------------------------------------------------


<Call-Julius>:<プロジェクト・フォルダ>\node_modules\react-native-google-speech-api\calljulius\android\src\main\java\com\teburarec\calljulius

<Call-Julius>\JlibGate.java
----JlibGate.java----------------------------------
package com.teburarec.calljulius;

import android.util.Log;
import java.lang.Thread;
import java.lang.Exception;
import android.content.Context;
import android.app.Activity;
import android.content.res.AssetManager;
import java.io.File; //"File" Object
import java.io.InputStream;
import java.io.FileOutputStream;
import java.io.IOException;


public class JlibGate {
private static final String TAG = "CallJulius";
private static final String ASSETSUBDIR = "jdata";
private static final int RECOG_RECOGNIZING = 1;
private static final int RECOG_STANDBY = 0;
private static final int RECOG_COMPLETED = -1;
private static final int INPUT_NOREQ = 0;
private static final int INPUT_REQ = 1;
public static final int STEP_STARTWORD = 1;
public static final int STEP_OBJECTWORD = 2;
public static final int STEP_ITEMWORD = 3;
public static final int STEP_FREESPEECH = 0;


private static Activity myAct;
private static JlibGate myObj;
private static int recogState = RECOG_COMPLETED;
private static int inputState = INPUT_REQ;
private static boolean fthread_live = false;
private static int ret_lng; //destination space length
private static boolean fblank = false; //previouse input is blank.
public int recogStep; //0:speech, not 0:word

// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("juliusword");
}

//### callback interface
public static interface JlibCallback {
int NotifyRecognized(String word);
}
private static JlibCallback myCallback = null;

//### constractor
public JlibGate(Activity act) {
Log.d(TAG, "activity context="+act);
myAct = act;
myObj = this;
initDataFiles();

//### start Jlib thread
Thread thread = new JlibCallThread();
thread.start();
}

//### setup the asset files
private void initDataFiles() {
AssetManager assetMgr = myAct.getResources().getAssets();
try {
byte[] buffer = new byte[4096];

String files[] = assetMgr.list(ASSETSUBDIR);
for(int i = 0; i < files.length; i++) {
Log.d(TAG, "assets file: " + files[i]);
String assetPath = ASSETSUBDIR + "/" + files[i];
InputStream infile = assetMgr.open(assetPath);
FileOutputStream outfile = myAct.openFileOutput(files[i], myAct.MODE_PRIVATE);
int n = 0;
while (-1 != (n = infile.read(buffer))) {
outfile.write(buffer, 0, n);
}
outfile.close();
}
}
catch (IOException e) {
e.printStackTrace();
}
}

//### Jlib call thread
class JlibCallThread extends Thread {
public String myDataPath;

@Override
public void run() {
String rtmsg;
Log.i(TAG, "Start Jlib thread.");

synchronized (myObj) {
fthread_live = true;
File myDirObj = myAct.getFilesDir();
myDataPath = myDirObj.getPath();
Log.d(TAG, "My dir path: " + myDataPath);

rtmsg = jlibStart(myDataPath);
if (rtmsg.indexOf("ERROR:") == 0) {
Log.i(TAG, rtmsg);
//return;
}
recogState = RECOG_STANDBY;

while (recogState != RECOG_COMPLETED) {
while (recogState == RECOG_STANDBY) {
try {
Log.v(TAG, ">>> wait for recognize");
myObj.wait();
}
catch (Exception e) {
Log.e(TAG, "# Jlib thread: exception!", e);
return;
}
}
if (recogState == RECOG_RECOGNIZING) {
Log.v(TAG, "start recognizing");
rtmsg = jlibRecognize();
if (rtmsg.indexOf("ERROR:") != 0) {
myCallback.NotifyRecognized(rtmsg);
Log.v(TAG, "notified. result-step="+recogStep);

}
else {
Log.d(TAG, rtmsg);
}
recogState = RECOG_STANDBY;
}
myObj.notifyAll();
}

jlibStop();
fthread_live = false;
} //end synchronized block
Log.i(TAG, "End Jlib thread.");
} //end run()


} //end inner class JlibCallThread

//### JNI C functions
public native String jlibStart(String curpath);
public native String jlibRecognize();
public native String jlibStop();

public native int jlibCopy(byte[] srcbuf);

//### callback gate from JuliusLib.
public int inputVoice() {
Log.v(TAG, ">>> input voice");
if (fblank) {
Log.v(TAG, "input: escape/no data.");
fblank = false;
return -1;
}
synchronized (myObj) {
if (recogState != RECOG_COMPLETED) {
inputState = INPUT_REQ;
}
myObj.notifyAll();
while (recogState == RECOG_RECOGNIZING && inputState == INPUT_REQ) {
try {
myObj.wait();
}
catch (Exception e) {
Log.e(TAG, "input voice: exception!", e);
recogState = RECOG_COMPLETED;
myObj.notifyAll();
return -1;
}
}
} //end synchronized block
if (ret_lng == -1) fblank = true;
return ret_lng;
}


//### start the JuliusLib
public boolean jlib_call_start(JlibCallback callback) {
Log.v(TAG, ">>> start");
myCallback = callback;
return true;
}

//### execute a recognizing
public boolean jlib_call_recognize() {
Log.v(TAG, ">>> recognize req");
boolean fstart_recog = true;
if (myCallback == null) {
Log.e(TAG, "recognize: not started!");
return false;
}
synchronized (myObj) {
while (recogState == RECOG_RECOGNIZING) {
try {
myObj.wait();
}
catch (Exception e) {
Log.e(TAG, "recognize: exception!", e);
recogState = RECOG_COMPLETED;
myObj.notifyAll();
return false;
}
}
//if RECOGNIZING-state occur at the same time, an error returns after the hold.
Log.v(TAG, "recognize req. wake. deeds-step="+recogStep);
if (recogStep == STEP_FREESPEECH) {
fstart_recog = false;
}
else
if (recogState == RECOG_STANDBY) {
recogState = RECOG_RECOGNIZING;
}
myObj.notifyAll();
} //end synchronized block
Log.v(TAG, "<<< recognize req");
return fstart_recog;

}


//### send the voice data. call JlibCopy
public void jlib_call_send(byte[] sendbuf) {
Log.v(TAG, ">>> send");
synchronized (myObj) {
while (recogState == RECOG_RECOGNIZING && inputState == INPUT_NOREQ) {
try {
myObj.wait();
}
catch (Exception e) {
Log.e(TAG, "send: exception!", e);
recogState = RECOG_COMPLETED;
ret_lng = -1;
myObj.notifyAll();
return;
}
}
if (recogState == RECOG_RECOGNIZING && sendbuf != null) {
ret_lng = jlibCopy(sendbuf);
}
else {
Log.v(TAG, "send: escape!");
ret_lng = -1;
}
if (recogState != RECOG_COMPLETED) {
inputState = INPUT_NOREQ;
}
myObj.notifyAll();
} //end synchronized block
return;
}

//### end the JuliusLib
public void jlib_call_stop() {
Log.v(TAG, ">>> stop");
try {
Thread.sleep(2);

synchronized (myObj) {
recogState = RECOG_COMPLETED;
myObj.notifyAll();
}
}
catch (Exception e) {
Log.e(TAG, "stop: exception!", e);
}
}

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



◆ちょっとテンポが遅い件
もともとGoogleSpeechオンリーで音声認識していたときからの仕様で、
・無音状態が1秒続くと
 :センテンスの区切りとしてそこまでの音声をテキストに変換します。
・無音状態が5秒続くと
 :発話終了としてそこまでのセンテンスをテキストに変換して、認識を終了します。
となっていました。

今回、1)単語:Julius認識、2)文章:GoogleSpeech認識、3)終了とするために、
1)の後に1秒、2)の後に5秒間、声を出さないことを意識する必要があります。

正式な仕様とする際は、1)と2)、それぞれの後に何らかの音を出して認識したことを知らせるつもりですが、無音時間後に認識処理の時間と音を発する時間が必要になってしまうわけで、何だか音声入力のテンポが悪くなりそう。
なので、センテンス区切りを無音750mSec、発話終了を無音4秒に変更しました。
(修正箇所については、省略します。)


また、rawデータについてあれこれ調べている最中に以下のサイトを発見しました。
(今頃になって、同じようなことをやってる書き込みを見つけるなんて・・・)
参考:https://www.geekfeed.co.jp/geekblog/julius_speechtotext/
Juliusに指定するパラメタについてなんかよさげなことが書いてありました。
-cutsilence、-realtime、-nostrip
この3つも指定してみました。

効果は・・・・ちょっと感覚的に違いが分かる程の変化はない感じ。

ついでに、Juliusの動作パラメタの認識結果の出力数を2から1に変更しました。
(-n 4 -output 2 → -n 2 -output 1)
なんとなくですが、2つの候補をもらっても、どっちを正解とするかの判断基準を思いつかなかったから。


以上で、まあ、直す前に比べたらダイブまともになりました。
20210105_1.jpg

これで、今度こそ次のステップに進めそうです。
基礎的なコンポーネントの準備は終わりで、欲しかったアプリの仕様検討に入ります。

ここに来るまでに思い立ってから1年以上経っちゃった。とほほ。

では、この話はこの辺で。御機嫌よう。
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