This is a Chinese TTS developed with Android studio. I packaged its modules into aar (because it contains so files, it is too large to upload). I refer to DemoActivity.java and use the RunMethod method to test in B4A. There is no problem in calling the "init" method, but an error occurs when calling the "speak" method. I looked at the java class and since I'm not familiar with java, I don't know how to solve the problem. I uploaded the B4A project and java class, hope to get your tips.Thank you in advance.
Test:
Sub Activity_Create(FirstTime As Boolean)
Activity.LoadLayout("Layout")
GetTTS.RunMethod("init", Array(GetContext))
End Sub
Sub Activity_Resume
End Sub
Sub GetTTS As JavaObject
Dim jo As JavaObject
Return jo.InitializeNewInstance("com.wzq.ntts.tts.TtsManager",Null).RunMethodJO("getInstance", Null)
End Sub
Sub GetContext As JavaObject
Return GetBA.GetField("context")
End Sub
Sub GetBA As JavaObject
Dim jo As JavaObject
Dim cls As String = Me
cls = cls.SubString("class ".Length)
jo.InitializeStatic(cls)
Return jo.GetFieldJO("processBA")
End Sub
Sub Activity_Pause (UserClosed As Boolean)
End Sub
Sub Button1_Click
GetTTS.RunMethodJO("speak", Array("语音合成示例",1.0F,True))
'GetTTS1.RunMethodJO("stopTts", Null)
End Sub
b4x:
Logger connected to: HUAWEI BLN-AL30
--------- beginning of main
--------- beginning of system
Copying updated assets files (1)
*** Service (starter) Create ***
** Service (starter) Start **
** Activity (main) Create, isFirst = true **
** Activity (main) Resume **
Error occurred on line: 39 (Main)
java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invoke(Native Method)
at anywheresoftware.b4j.object.JavaObject.RunMethod(JavaObject.java:132)
at anywheresoftware.b4j.object.JavaObject.RunMethodJO(JavaObject.java:139)
at b4a.example.main._button1_click(main.java:454)
at java.lang.reflect.Method.invoke(Native Method)
at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:732)
at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:348)
at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:255)
at java.lang.reflect.Method.invoke(Native Method)
at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:144)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:197)
at anywheresoftware.b4a.BA.raiseEvent(BA.java:193)
at anywheresoftware.b4a.objects.ViewWrapper$1.onClick(ViewWrapper.java:80)
at android.view.View.performClick(View.java:6291)
at android.view.View$PerformClick.run(View.java:24931)
at android.os.Handler.handleCallback(Handler.java:808)
at android.os.Handler.dispatchMessage(Handler.java:101)
at android.os.Looper.loop(Looper.java:166)
at android.app.ActivityThread.main(ActivityThread.java:7529)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void com.wzq.ntts.tts.InputWorker.interrupt()' on a null object reference
at com.wzq.ntts.tts.TtsManager.stopTts(TtsManager.java:87)
at com.wzq.ntts.tts.TtsManager.speak(TtsManager.java:92)
... 22 more
TtsManager.java:
package com.wzq.ntts.tts;
import android.content.Context;
import android.util.Log;
import com.wzq.ntts.dispatcher.TtsStateDispatcher;
import com.wzq.ntts.utils.ThreadPoolManager;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
/**
* @author {@link "mailto:xuefeng.ding@outlook.com" "Xuefeng Ding"}
* Created 2020-07-28 14:25
*/
public class TtsManager {
private static final String TAG = "TtsManager";
private static final Object INSTANCE_WRITE_LOCK = new Object();
private static volatile TtsManager instance;
public static TtsManager getInstance() {
if (instance == null) {
System.loadLibrary("tensorflowlite_flex_jni");
System.loadLibrary("tensorflowlite_jni");
synchronized (INSTANCE_WRITE_LOCK) {
if (instance == null) {
instance = new TtsManager();
}
}
}
return instance;
}
private InputWorker mWorker;
// private final static String FASTSPEECH2_MODULE = "fastspeech2_quan.tflite";
private final static String FASTSPEECH2_MODULE = "fastspeech2_quan.tflite";
private final static String MELGAN_MODULE = "mb_melgan_new.tflite";
public void init(Context context) {
ThreadPoolManager.getInstance().getSingleExecutor("init").execute(() -> {
try {
String fastspeech = copyFile(context, FASTSPEECH2_MODULE);
String vocoder = copyFile(context, MELGAN_MODULE);
mWorker = new InputWorker(context,fastspeech, vocoder);
} catch (Exception e) {
Log.e(TAG, "mWorker init failed", e);
}
TtsStateDispatcher.getInstance().onTtsReady();
});
}
private String copyFile(Context context, String strOutFileName) {
Log.d(TAG, "start copy file " + strOutFileName);
File file = context.getFilesDir();
String tmpFile = file.getAbsolutePath() + "/" + strOutFileName;
File f = new File(tmpFile);
if (f.exists()) {
Log.d(TAG, "file exists " + strOutFileName);
return f.getAbsolutePath();
}
try (OutputStream myOutput = new FileOutputStream(f);
InputStream myInput = context.getAssets().open(strOutFileName)) {
byte[] buffer = new byte[1024];
int length = myInput.read(buffer);
while (length > 0) {
myOutput.write(buffer, 0, length);
length = myInput.read(buffer);
}
myOutput.flush();
Log.d(TAG, "Copy task successful");
} catch (Exception e) {
Log.e(TAG, "copyFile: Failed to copy", e);
} finally {
Log.d(TAG, "end copy file " + strOutFileName);
}
return f.getAbsolutePath();
}
public void stopTts() {
mWorker.interrupt();
}
public void speak(String inputText, float speed, boolean interrupt) {
if (interrupt) {
stopTts();
}
ThreadPoolManager.getInstance().execute(() ->
mWorker.processInput(inputText, speed));
}
}
InputWorker.java:
package com.wzq.ntts.tts;
import android.content.Context;
import android.util.Log;
import com.wzq.ntts.dispatcher.TtsStateDispatcher;
import com.wzq.ntts.module.FastSpeech2;
import com.wzq.ntts.module.MBMelGan;
import com.wzq.ntts.utils.Processor;
import com.wzq.ntts.utils.ThreadPoolManager;
import com.wzq.ntts.utils.ZhProcessor;
import org.tensorflow.lite.support.tensorbuffer.TensorBuffer;
import java.util.Arrays;
import java.util.concurrent.LinkedBlockingQueue;
/**
* @author {@link "mailto:xuefeng.ding@outlook.com" "Xuefeng Ding"}
* Created 2020-07-28 14:25
*/
class InputWorker {
private static final String TAG = "InputWorker";
private LinkedBlockingQueue<InputText> mInputQueue = new LinkedBlockingQueue<>();
private InputText mCurrentInputText;
private FastSpeech2 mFastSpeech2;
private MBMelGan mMBMelGan;
private Processor mProcessor;
private TtsPlayer mTtsPlayer;
private ZhProcessor zhProcessor;
private Context context;
InputWorker(Context context, String fastspeech, String vocoder) {
this.context = context;
mFastSpeech2 = new FastSpeech2(fastspeech);
mMBMelGan = new MBMelGan(vocoder);
mProcessor = new Processor();
mTtsPlayer = new TtsPlayer();
zhProcessor = new ZhProcessor(context);
ThreadPoolManager.getInstance().getSingleExecutor("worker").execute(() -> {
//noinspection InfiniteLoopStatement
while (true) {
try {
mCurrentInputText = mInputQueue.take();
Log.d(TAG, "processing: " + mCurrentInputText.INPUT_TEXT);
TtsStateDispatcher.getInstance().onTtsStart(mCurrentInputText.INPUT_TEXT);
mCurrentInputText.proceed();
TtsStateDispatcher.getInstance().onTtsStop();
} catch (Exception e) {
Log.e(TAG, "Exception: ", e);
}
}
});
}
void processInput(String inputText, float speed) {
Log.d(TAG, "add to queue: " + inputText);
mInputQueue.offer(new InputText(inputText, speed));
}
void interrupt() {
mInputQueue.clear();
if (mCurrentInputText != null) {
mCurrentInputText.interrupt();
}
mTtsPlayer.interrupt();
}
private class InputText {
private final String INPUT_TEXT;
private final float SPEED;
private boolean isInterrupt;
private InputText(String inputText, float speed) {
this.INPUT_TEXT = inputText;
this.SPEED = speed;
}
private void proceed() {
String[] sentences = INPUT_TEXT.split("[\n,。??!!,;;]");
Log.d(TAG, "speak: " + Arrays.toString(sentences));
for (String sentence : sentences) {
long time = System.currentTimeMillis();
// int[] inputIds = mProcessor.textToIds(sentence);
int[] inputIds = zhProcessor.text2ids(sentence);
TensorBuffer output = mFastSpeech2.getMelSpectrogram(inputIds, SPEED);
if (isInterrupt) {
Log.d(TAG, "proceed: interrupt");
return;
}
long encoderTime = System.currentTimeMillis();
float[] audioData;
try {
audioData = mMBMelGan.getAudio(output);
} catch (Exception e) {
e.printStackTrace();
continue;
}
if (isInterrupt) {
Log.d(TAG, "proceed: interrupt");
return;
}
long vocoderTime = System.currentTimeMillis();
Log.d(TAG, "Time cost: " + (encoderTime - time) + "+" + (vocoderTime - encoderTime) + "=" + (vocoderTime - time));
mTtsPlayer.play(new TtsPlayer.AudioData(sentence, audioData));
}
}
private void interrupt() {
this.isInterrupt = true;
}
}
}
DemoActivity.java:
package com.air4.ttschineseDemo;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.RadioGroup;
import com.air4.chinesetts.dispatcher.OnTtsStateListener;
import com.air4.chinesetts.dispatcher.TtsStateDispatcher;
import com.air4.chinesetts.tts.TtsManager;
import com.air4.chinesetts.utils.ThreadPoolManager;
public class DemoActivity extends AppCompatActivity {
private static final String DEFAULT_INPUT_TEXT = "君不见,黄河之水天上来,奔流到海不复回,君不见,高堂明镜悲白发,朝如青丝暮成雪,人生得意须尽欢,莫使金樽空对月";
private View speakBtn;
private RadioGroup speedGroup;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ttsdemo);
TtsManager.getInstance().init(this);
TtsStateDispatcher.getInstance().addListener(new OnTtsStateListener() {
@Override
public void onTtsReady() {
speakBtn.setEnabled(true);
}
@Override
public void onTtsStart(String text) {
}
@Override
public void onTtsStop() {
}
});
EditText input = findViewById(R.id.input);
input.setHint(DEFAULT_INPUT_TEXT);
speedGroup = findViewById(R.id.speed_chooser);
speedGroup.check(R.id.normal);
speakBtn = findViewById(R.id.start);
speakBtn.setEnabled(false);
speakBtn.setOnClickListener(v ->
ThreadPoolManager.getInstance().execute(() -> {
float speed;
switch (speedGroup.getCheckedRadioButtonId()) {
case R.id.fast:
speed = 0.8F;
break;
case R.id.slow:
speed = 1.2F;
break;
case R.id.normal:
default:
speed = 1.0F;
break;
}
String inputText = input.getText().toString();
if (TextUtils.isEmpty(inputText)) {
inputText = DEFAULT_INPUT_TEXT;
}
TtsManager.getInstance().speak(inputText, speed, true);
}));
findViewById(R.id.stop).setOnClickListener(v ->
TtsManager.getInstance().stopTts());
}
}
Attachments
Last edited: