Update to latest Vosk version

This commit is contained in:
Ciaran O'Reilly 2021-08-03 21:18:32 +02:00
parent a0fe247c46
commit e90a92e366
9 changed files with 314 additions and 35 deletions

1
.gitignore vendored
View File

@ -11,3 +11,4 @@ build
.classpath
.vscode
local.properties

View File

@ -2,8 +2,9 @@ apply plugin: 'com.android.application'
repositories {
google()
mavenCentral()
maven {
url "https://dl.bintray.com/alphacep/vosk"
url 'https://alphacephei.com/maven/'
}
maven {
url "https://jitpack.io"
@ -31,9 +32,10 @@ android {
}
dependencies {
implementation 'com.alphacep:vosk-android:0.3.15'
implementation 'com.alphacephei:vosk-android:0.3.30'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'net.java.dev.jna:jna:5.8.0@aar'
implementation 'com.google.code.gson:gson:2.8.7'
implementation 'org.mozilla.deepspeech:libdeepspeech:0.8.2'
implementation 'com.github.gkonovalov:android-vad:1.0.0'
}

View File

@ -0,0 +1,261 @@
// Copyright 2019 Alpha Cephei Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cat.oreilly.localstt;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import android.content.Context;
import android.content.res.AssetManager;
import android.os.Environment;
/**
* Provides utility methods to keep asset files to external storage to allow
* further JNI code access assets from a filesystem.
*
* There must be special file {@value #ASSET_LIST_NAME} among the application
* assets containing relative paths of assets to synchronize. If the
* corresponding path does not exist on the external storage it is copied. If
* the path exists checksums are compared and the asset is copied only if there
* is a mismatch. Checksum is stored in a separate asset with the name that
* consists of the original name and a suffix that depends on the checksum
* algorithm (e.g. MD5). Checksum files are copied along with the corresponding
* asset files.
*
* @author Alexander Solovets
*/
public class Assets {
protected static final String TAG = Assets.class.getSimpleName();
public static final String ASSET_LIST_NAME = "assets.lst";
public static final String SYNC_DIR = "sync";
public static final String HASH_EXT = ".md5";
private final AssetManager assetManager;
private final File externalDir;
/**
* Creates new instance for asset synchronization
*
* @param context
* application context
*
* @throws IOException
* if the directory does not exist
*
* @see android.content.Context#getExternalFilesDir
* @see android.os.Environment#getExternalStorageState
*/
public Assets(Context context) throws IOException {
File appDir = context.getExternalFilesDir(null);
if (null == appDir)
throw new IOException("cannot get external files dir, "
+ "external storage state is " + Environment.getExternalStorageState());
externalDir = new File(appDir, SYNC_DIR);
assetManager = context.getAssets();
}
/**
* Creates new instance with specified destination for assets
*
* @param context
* application context to retrieve the assets
* @param path
* path to sync the files
*/
public Assets(Context context, String dest) {
externalDir = new File(dest);
assetManager = context.getAssets();
}
/**
* Returns destination path on external storage where assets are copied.
*
* @return path to application directory or null if it does not exists
*/
public File getExternalDir() {
return externalDir;
}
/**
* Returns the map of asset paths to the files checksums.
*
* @return path to the root of resources directory on external storage
* @throws IOException
* if an I/O error occurs or "assets.lst" is missing
*/
public Map<String, String> getItems() throws IOException {
Map<String, String> items = new HashMap<String, String>();
for (String path : readLines(openAsset(ASSET_LIST_NAME))) {
Reader reader = new InputStreamReader(openAsset(path + HASH_EXT));
items.put(path, new BufferedReader(reader).readLine());
}
return items;
}
/**
* Returns path to hash mappings for the previously copied files. This
* method can be used to find out assets which must be updated.
*/
public Map<String, String> getExternalItems() {
try {
Map<String, String> items = new HashMap<String, String>();
File assetFile = new File(externalDir, ASSET_LIST_NAME);
for (String line : readLines(new FileInputStream(assetFile))) {
String[] fields = line.split(" ");
items.put(fields[0], fields[1]);
}
return items;
} catch (IOException e) {
return Collections.emptyMap();
}
}
/**
* In case you want to create more smart sync implementation, this method
* returns the list of items which must be synchronized.
*/
public Collection<String> getItemsToCopy(String path) throws IOException {
Collection<String> items = new ArrayList<String>();
Queue<String> queue = new ArrayDeque<String>();
queue.offer(path);
while (!queue.isEmpty()) {
path = queue.poll();
String[] list = assetManager.list(path);
for (String nested : list)
queue.offer(nested);
if (list.length == 0)
items.add(path);
}
return items;
}
private List<String> readLines(InputStream source) throws IOException {
List<String> lines = new ArrayList<String>();
BufferedReader br = new BufferedReader(new InputStreamReader(source));
String line;
while (null != (line = br.readLine()))
lines.add(line);
return lines;
}
private InputStream openAsset(String asset) throws IOException {
return assetManager.open(new File(SYNC_DIR, asset).getPath());
}
/**
* Saves the list of synchronized items. The list is stored as a two-column
* space-separated list of items in a text file. The file is located at the
* root of synchronization directory in the external storage.
*
* @param items
* the items
* @throws IOException
* if an I/O error occurs
*/
public void updateItemList(Map<String, String> items) throws IOException {
File assetListFile = new File(externalDir, ASSET_LIST_NAME);
PrintWriter pw = new PrintWriter(new FileOutputStream(assetListFile));
for (Map.Entry<String, String> entry : items.entrySet())
pw.format("%s %s\n", entry.getKey(), entry.getValue());
pw.close();
}
/**
* Copies raw asset resource to external storage of the device.
*
* @param path
* path of the asset to copy
* @throws IOException
* if an I/O error occurs
*/
public File copy(String asset) throws IOException {
InputStream source = openAsset(asset);
File destinationFile = new File(externalDir, asset);
destinationFile.getParentFile().mkdirs();
OutputStream destination = new FileOutputStream(destinationFile);
byte[] buffer = new byte[1024];
int nread;
while ((nread = source.read(buffer)) != -1) {
if (nread == 0) {
nread = source.read();
if (nread < 0)
break;
destination.write(nread);
continue;
}
destination.write(buffer, 0, nread);
}
destination.close();
return destinationFile;
}
/**
* Performs the sync of assets in the application and on the external
* storage
*
* @return The folder on external storage with data
* @throws IOException
*/
public File syncAssets() throws IOException {
Collection<String> newItems = new ArrayList<String>();
Collection<String> unusedItems = new ArrayList<String>();
Map<String, String> items = getItems();
Map<String, String> externalItems = getExternalItems();
for (String path : items.keySet()) {
if (!items.get(path).equals(externalItems.get(path))
|| !(new File(externalDir, path).exists()))
newItems.add(path);
}
unusedItems.addAll(externalItems.keySet());
unusedItems.removeAll(items.keySet());
for (String path : newItems) {
File file = copy(path);
}
for (String path : unusedItems) {
File file = new File(externalDir, path);
file.delete();
}
updateItemList(items);
return externalDir;
}
}

View File

@ -15,23 +15,17 @@
package cat.oreilly.localstt;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.Handler;
import android.os.Looper;
import android.speech.RecognitionService;
import android.util.Log;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import org.kaldi.Assets;
import org.kaldi.RecognitionListener;
import org.vosk.android.RecognitionListener;
import org.mozilla.deepspeech.libdeepspeech.DeepSpeechModel;
import java.io.File;
import java.util.Map;
import java.util.ArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@ -170,6 +164,14 @@ public class DeepSpeechRecognitionService extends RecognitionService implements
}
}
@Override
public void onFinalResult(String hypothesis) {
if (hypothesis != null) {
Log.i(TAG, hypothesis);
results(createResultsBundle(hypothesis), true);
}
}
@Override
public void onPartialResult(String hypothesis) {
if (hypothesis != null) {

View File

@ -16,9 +16,6 @@
package cat.oreilly.localstt;
import static java.lang.String.format;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
@ -30,7 +27,7 @@ import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import org.kaldi.RecognitionListener;
import org.vosk.android.RecognitionListener;
import org.mozilla.deepspeech.libdeepspeech.DeepSpeechModel;
import org.mozilla.deepspeech.libdeepspeech.DeepSpeechStreamingState;

View File

@ -17,7 +17,6 @@
package cat.oreilly.localstt;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@ -26,12 +25,12 @@ import android.speech.RecognitionService;
import android.util.Log;
import com.google.gson.Gson;
import org.kaldi.Assets;
import org.kaldi.KaldiRecognizer;
import org.kaldi.Model;
import org.kaldi.RecognitionListener;
import org.kaldi.SpeechService;
import org.kaldi.Vosk;
import org.vosk.Recognizer;
import org.vosk.Model;
import org.vosk.android.RecognitionListener;
import org.vosk.android.SpeechService;
import org.vosk.LibVosk;
import org.vosk.LogLevel;
import java.io.File;
import java.util.Map;
@ -44,7 +43,7 @@ public class VoskRecognitionService extends RecognitionService implements Recogn
private final static String TAG = VoskRecognitionService.class.getSimpleName();
private final Handler handler = new Handler(Looper.getMainLooper());
private final Executor executor = Executors.newSingleThreadExecutor();
private KaldiRecognizer recognizer;
private Recognizer recognizer;
private SpeechService speechService;
private Model model;
@ -77,7 +76,7 @@ public class VoskRecognitionService extends RecognitionService implements Recogn
if (model == null) {
Assets assets = new Assets(VoskRecognitionService.this);
File assetDir = assets.syncAssets();
Vosk.SetLogLevel(0);
LibVosk.setLogLevel(LogLevel.INFO);
Log.i(TAG, "Loading model");
model = new Model(assetDir.toString() + "/vosk-catala");
@ -111,23 +110,22 @@ public class VoskRecognitionService extends RecognitionService implements Recogn
}
}
private void setupRecognizer() throws IOException {
private void setupRecognizer() {
try {
if (recognizer == null) {
Log.i(TAG, "Creating recognizer");
recognizer = new KaldiRecognizer(model, 16000.0f);
recognizer = new Recognizer(model, 16000.0f);
}
if (speechService == null) {
Log.i(TAG, "Creating speechService");
speechService = new SpeechService(recognizer, 16000.0f);
speechService.addListener(this);
} else {
speechService.cancel();
}
speechService.startListening();
speechService.startListening(this);
} catch (IOException e) {
Log.e(TAG, e.getMessage());
}
@ -171,7 +169,9 @@ public class VoskRecognitionService extends RecognitionService implements Recogn
}
private void error(int errorCode) {
speechService.cancel();
if (speechService != null) {
speechService.cancel();
}
try {
mCallback.error(errorCode);
} catch (RemoteException e) {
@ -190,6 +190,17 @@ public class VoskRecognitionService extends RecognitionService implements Recogn
}
}
@Override
public void onFinalResult(String hypothesis) {
if (hypothesis != null) {
Log.i(TAG, hypothesis);
Gson gson = new Gson();
Map<String, String> map = gson.fromJson(hypothesis, Map.class);
String text = map.get("text");
results(createResultsBundle(text), true);
}
}
@Override
public void onPartialResult(String hypothesis) {
if (hypothesis != null) {
@ -210,6 +221,6 @@ public class VoskRecognitionService extends RecognitionService implements Recogn
@Override
public void onTimeout() {
speechService.cancel();
speechService.startListening();
speechService.startListening(this);
}
}

View File

@ -2,17 +2,17 @@ buildscript {
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.2'
classpath 'com.android.tools.build:gradle:4.2.0'
}
}
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}

View File

@ -1,3 +1,7 @@
org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=4096m -XX:+HeapDumpOnOutOfMemoryError
org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.configureondemand=true
android.enableD8=true
android.enableJetifier=true
android.useAndroidX=true

View File

@ -1,5 +1,6 @@
#Tue Aug 03 00:12:16 CEST 2021
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME