Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7b3ea0b59d | |||
| e2af710369 | |||
| 6b1b620f39 | |||
| 966524da16 | |||
| 83c999f298 | |||
| abff8a4f56 | |||
| 188575f3a2 | |||
| 9f4d8ca187 | |||
| 72cc8f3a5e | |||
| 915dcab597 | |||
| d82052b168 | |||
| dbb65bc71c | |||
| 2498bc595b | |||
| fee63d712f | |||
| 5965ea30e6 | |||
| 5c75495c03 | |||
| 98ae3c66cb | |||
| 942e15484a | |||
| 2349e66a97 | |||
| 5750e3224f | |||
| 558b4dd69e | |||
| 02ef49f67e | |||
| 7cdf8f1d03 | |||
| 7f894f784a | |||
| a770251151 | |||
| 7ccf743bb6 | |||
| 75bedfe06d | |||
| 4ea9885d44 | |||
| 0930def9ec | |||
| 6aa5af7640 | |||
| e74fe5edf7 | |||
| cbb5d0fcdf | |||
| d489bc40e4 | |||
| 499b2f183a | |||
| f57b926f66 | |||
| 733dca5aea | |||
| 1ab7e8ca87 | |||
| 53426f794c | |||
| f8189685e5 | |||
| e6bd200c85 | |||
| 4a4c33fd9d | |||
| dc44922b92 | |||
| 67896cfba7 | |||
| ee8663e6d9 | |||
| 652c05f052 | |||
| 193d129d9e | |||
| e995df4bc3 | |||
| f6a30c3626 | |||
| ee95420f8f | |||
| 2221cd0209 | |||
| 8368d831aa | |||
| 91a128b3ed | |||
| 83d8b63351 | |||
| 0881aa856f | |||
| 77abb02ada | |||
| 119c6cd0dc | |||
| cc649aa78e | |||
| 0b0c9fce0e | |||
| d845e2107f | |||
| 69992a1b39 | |||
| f351280f49 | |||
| c15065b044 | |||
| db41f16051 | |||
| e276de61be | |||
| 43c786ab18 | |||
| 1199f39d55 | |||
| 70aab86257 | |||
| e4b0af7a8e | |||
| a120d06f6b | |||
| e01ca37e45 | |||
| f5c3d898e2 | |||
| a5a3697b7c | |||
| 4be9874ba2 | |||
| 5647a2dc6a | |||
| a067a5b5f3 | |||
| 475b0ecfc1 | |||
| 2a9a605144 | |||
| eea7ca571b | |||
| 11c16610ee | |||
| 2f4124eb66 | |||
| db3e31d7ce | |||
| d917af21ab | |||
| 948c8c7cfd | |||
| dec8e1acf3 | |||
| 3c020526ce | |||
| b0da19b07f | |||
| 70bcd9b018 | |||
| d2604b609d | |||
| e960e0cacf | |||
| 77b114d434 | |||
| 2860092cef | |||
| c6119c4835 |
+21
-19
@@ -1,23 +1,25 @@
|
||||
# Built application files
|
||||
*.apk
|
||||
*.ap_
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
|
||||
# Temp files
|
||||
nohup.out
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
android/build/
|
||||
gradlew
|
||||
gradlew.bat
|
||||
gradle
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Android
|
||||
android/build
|
||||
android/lib/build
|
||||
android/model-en/build
|
||||
android/model-en/src/main/assets/model-en-us
|
||||
android/repo
|
||||
*.apk
|
||||
*.ap_
|
||||
|
||||
# Cmake
|
||||
.cxx
|
||||
cmake_install.cmake
|
||||
@@ -37,15 +39,16 @@ python/test/result.txt
|
||||
python/test/wav.scp
|
||||
|
||||
# Java
|
||||
*.so
|
||||
java/org
|
||||
java/*.cc
|
||||
java/model-spk/
|
||||
java/model/
|
||||
*.class
|
||||
java/lib/model
|
||||
java/demo/model
|
||||
java/lib/build
|
||||
java/demo/build
|
||||
|
||||
# CSharp
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
*.nupkg
|
||||
csharp/demo/model
|
||||
csharp/demo/test.wav
|
||||
@@ -53,12 +56,11 @@ csharp/demo/bin
|
||||
csharp/demo/obj
|
||||
|
||||
# Node
|
||||
nodejs/vosk_wrap.cc
|
||||
nodejs/example/model
|
||||
nodejs/example/test.wav
|
||||
nodejs/demo/model
|
||||
nodejs/demo/model-spk
|
||||
nodejs/demo/test.wav
|
||||
nodejs/node_modules
|
||||
nodejs/package-lock.json
|
||||
nodejs/build
|
||||
|
||||
# C
|
||||
c/test_vosk
|
||||
|
||||
+1
-2
@@ -7,11 +7,10 @@ matrix:
|
||||
services:
|
||||
- docker
|
||||
env: DOCKER_IMAGE=alphacep/kaldi-manylinux:latest
|
||||
PLAT=manylinux2010_x86_64
|
||||
|
||||
install:
|
||||
- docker pull $DOCKER_IMAGE
|
||||
|
||||
script:
|
||||
- docker run --rm -e PLAT=$PLAT -v `pwd`:/io $DOCKER_IMAGE $PRE_CMD /io/travis/build-wheels.sh
|
||||
- docker run --rm -v `pwd`:/io $DOCKER_IMAGE $PRE_CMD /io/travis/build-wheels.sh
|
||||
- ls wheelhouse/
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# About
|
||||
|
||||
Vosk is an offline open source speech recognition toolkit. It enables
|
||||
speech recognition models for 17 languages and dialects - English, Indian
|
||||
speech recognition models for 18 languages and dialects - English, Indian
|
||||
English, German, French, Spanish, Portuguese, Chinese, Russian, Turkish,
|
||||
Vietnamese, Italian, Dutch, Catalan, Arabic, Greek, Farsi, Filipino.
|
||||
Vietnamese, Italian, Dutch, Catalan, Arabic, Greek, Farsi, Filipino,
|
||||
Ukrainian.
|
||||
|
||||
Vosk models are small (50 Mb) but provide continuous large vocabulary
|
||||
transcription, zero-latency response with streaming API, reconfigurable
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
# Vosk CMake File
|
||||
|
||||
cmake_minimum_required(VERSION 3.4.1)
|
||||
|
||||
if ("x${ANDROID_ABI}" STREQUAL "xarmeabi-v7a")
|
||||
set(OPENBLAS_ARCH "armv7")
|
||||
set(KALDI_SUFFIX "arm_32")
|
||||
elseif ("x${ANDROID_ABI}" STREQUAL "xarm64-v8a")
|
||||
set(OPENBLAS_ARCH "armv8")
|
||||
set(KALDI_SUFFIX "arm_64")
|
||||
elseif ("x${ANDROID_ABI}" STREQUAL "xx86")
|
||||
set(OPENBLAS_ARCH "atom")
|
||||
set(KALDI_SUFFIX "x86")
|
||||
else ("x${ANDROID_ABI}" STREQUAL "xarmeabi-v7a")
|
||||
set(OPENBLAS_ARCH "atom")
|
||||
set(KALDI_SUFFIX "x86_64")
|
||||
endif ("x${ANDROID_ABI}" STREQUAL "xarmeabi-v7a")
|
||||
|
||||
set(KALDI_ROOT "${PROJECT_SOURCE_DIR}/build/kaldi_${KALDI_SUFFIX}/kaldi")
|
||||
set(LIB_ROOT "${PROJECT_SOURCE_DIR}/build/kaldi_${KALDI_SUFFIX}/local")
|
||||
|
||||
set(API_SOURCES
|
||||
"${PROJECT_SOURCE_DIR}/../src/kaldi_recognizer.cc"
|
||||
"${PROJECT_SOURCE_DIR}/../src/kaldi_recognizer.h"
|
||||
"${PROJECT_SOURCE_DIR}/../src/language_model.cc"
|
||||
"${PROJECT_SOURCE_DIR}/../src/language_model.h"
|
||||
"${PROJECT_SOURCE_DIR}/../src/model.cc"
|
||||
"${PROJECT_SOURCE_DIR}/../src/model.h"
|
||||
"${PROJECT_SOURCE_DIR}/../src/spk_model.cc"
|
||||
"${PROJECT_SOURCE_DIR}/../src/spk_model.h"
|
||||
"${PROJECT_SOURCE_DIR}/../src/vosk_api.cc"
|
||||
"${PROJECT_SOURCE_DIR}/../src/vosk_api.h"
|
||||
)
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -O3 -DFST_NO_DYNAMIC_LINKING")
|
||||
|
||||
add_library( vosk_jni SHARED
|
||||
build/generated-src/cpp/vosk_wrap.cc
|
||||
${API_SOURCES}
|
||||
)
|
||||
|
||||
include_directories("${PROJECT_SOURCE_DIR}/../src" "build/kaldi_${KALDI_SUFFIX}/kaldi/src" "build/kaldi_${KALDI_SUFFIX}/local/include")
|
||||
|
||||
target_link_libraries( vosk_jni
|
||||
${KALDI_ROOT}/src/online2/kaldi-online2.a
|
||||
${KALDI_ROOT}/src/decoder/kaldi-decoder.a
|
||||
${KALDI_ROOT}/src/ivector/kaldi-ivector.a
|
||||
${KALDI_ROOT}/src/gmm/kaldi-gmm.a
|
||||
${KALDI_ROOT}/src/nnet3/kaldi-nnet3.a
|
||||
${KALDI_ROOT}/src/tree/kaldi-tree.a
|
||||
${KALDI_ROOT}/src/feat/kaldi-feat.a
|
||||
${KALDI_ROOT}/src/lat/kaldi-lat.a
|
||||
${KALDI_ROOT}/src/lm/kaldi-lm.a
|
||||
${KALDI_ROOT}/src/hmm/kaldi-hmm.a
|
||||
${KALDI_ROOT}/src/transform/kaldi-transform.a
|
||||
${KALDI_ROOT}/src/cudamatrix/kaldi-cudamatrix.a
|
||||
${KALDI_ROOT}/src/matrix/kaldi-matrix.a
|
||||
${KALDI_ROOT}/src/fstext/kaldi-fstext.a
|
||||
${KALDI_ROOT}/src/util/kaldi-util.a
|
||||
${KALDI_ROOT}/src/base/kaldi-base.a
|
||||
${LIB_ROOT}/lib/libfst.a
|
||||
${LIB_ROOT}/lib/libfstngram.a
|
||||
${LIB_ROOT}/lib/libopenblas.a
|
||||
${LIB_ROOT}/lib/libclapack.a
|
||||
${LIB_ROOT}/lib/liblapack.a
|
||||
${LIB_ROOT}/lib/libblas.a
|
||||
${LIB_ROOT}/lib/libf2c.a
|
||||
log
|
||||
)
|
||||
+40
-92
@@ -4,107 +4,55 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.5.3'
|
||||
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.5'
|
||||
classpath 'com.android.tools.build:gradle:4.1.3'
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "com.jfrog.bintray" version "1.8.5"
|
||||
}
|
||||
|
||||
def archiveName = "vosk-android"
|
||||
def libVersion = "0.3.17"
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
version = '0.3.30'
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'maven-publish'
|
||||
subprojects {
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
versionCode 6
|
||||
versionName = libVersion
|
||||
archivesBaseName = archiveName
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments "-DCMAKE_VERBOSE_MAKEFILE=ON", "-DANDROID_ARM_NEON=TRUE", "-DCMAKE_CXX_FLAGS_RELEASE=-O3"
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
aar(MavenPublication) {
|
||||
groupId 'com.alphacephei'
|
||||
version version
|
||||
pom {
|
||||
url = 'http://www.alphacephei.com.com/vosk/'
|
||||
licenses {
|
||||
license {
|
||||
name = 'The Apache License, Version 2.0'
|
||||
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||
}
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id = 'com.alphacephei'
|
||||
name = 'Alpha Cephei Inc'
|
||||
email = 'contact@alphacephei.com'
|
||||
}
|
||||
}
|
||||
scm {
|
||||
connection = 'scm:git:git://github.com/alphacep/vosk-api.git'
|
||||
url = 'https://github.com/alphacep/vosk-api/'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ndk {
|
||||
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86_64', 'x86'
|
||||
}
|
||||
}
|
||||
sourceSets {
|
||||
main {
|
||||
java.srcDirs = ['src/main/java', 'build/generated-src']
|
||||
}
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path "CMakeLists.txt"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Properties properties = new Properties()
|
||||
properties.load(project.rootProject.file('local.properties').newDataInputStream())
|
||||
|
||||
bintray {
|
||||
user = properties.getProperty("bintray.user")
|
||||
key = properties.getProperty("bintray.apikey")
|
||||
pkg {
|
||||
repo = 'vosk'
|
||||
name = 'vosk-android'
|
||||
userOrg = "alphacep"
|
||||
licenses = ['Apache2.0']
|
||||
websiteUrl = 'https://github.com/alphacep/vosk-api'
|
||||
issueTrackerUrl = 'https://github.com/alphacep/vosk-api/issues'
|
||||
vcsUrl = 'https://github.com/alphacep/vosk-api'
|
||||
version {
|
||||
name = libVersion
|
||||
vcsTag = libVersion
|
||||
}
|
||||
}
|
||||
publications = ['aar']
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
aar(MavenPublication) {
|
||||
groupId 'com.alphacep'
|
||||
artifactId archiveName
|
||||
version libVersion
|
||||
artifact("$buildDir/outputs/aar/$archiveName-release.aar")
|
||||
repositories {
|
||||
maven {
|
||||
url = "$rootDir/repo"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task swig {
|
||||
doLast {
|
||||
mkdir 'build/generated-src/java'
|
||||
mkdir 'build/generated-src/cpp'
|
||||
exec {
|
||||
commandLine 'swig',
|
||||
"-c++",
|
||||
"-java", "-package", "org.kaldi",
|
||||
"-outdir", "build/generated-src/java", "-o", "build/generated-src/cpp/vosk_wrap.cc",
|
||||
"../src/vosk.i"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task kaldi(type: Exec) {
|
||||
commandLine './build-kaldi.sh'
|
||||
environment ANDROID_SDK_HOME: android.getSdkDirectory()
|
||||
}
|
||||
|
||||
preBuild.dependsOn kaldi, swig
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright 2019 Alpha Cephei Inc.
|
||||
# Copyright 2019-2021 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.
|
||||
@@ -14,70 +14,39 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
if [ "x$ANDROID_SDK_HOME" == "x" ]; then
|
||||
echo "ANDROID_SDK_HOME environment variable is undefined, define it with local.properties or with export"
|
||||
if [ "x$ANDROID_NDK_HOME" == "x" ]; then
|
||||
echo "ANDROID_NDK_HOME environment variable is undefined, define it with local.properties or with export"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$ANDROID_SDK_HOME" ]; then
|
||||
echo "ANDROID_SDK_HOME ($ANDROID_SDK_HOME) is missing. Make sure you have sdk installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$ANDROID_SDK_HOME/ndk-bundle" ]; then
|
||||
echo "$ANDROID_SDK_HOME/ndk-bundle is missing. Make sure you have ndk installed within sdk"
|
||||
if [ ! -d "$ANDROID_NDK_HOME" ]; then
|
||||
echo "ANDROID_NDK_HOME ($ANDROID_NDK_HOME) is missing. Make sure you have ndk installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -x
|
||||
|
||||
OS_NAME=`echo $(uname -s) | tr '[:upper:]' '[:lower:]'`
|
||||
ANDROID_NDK_HOME=$ANDROID_SDK_HOME/ndk-bundle
|
||||
ANDROID_TOOLCHAIN_PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/${OS_NAME}-x86_64
|
||||
WORKDIR_X86=`pwd`/build/kaldi_x86
|
||||
WORKDIR_X86_64=`pwd`/build/kaldi_x86_64
|
||||
WORKDIR_ARM32=`pwd`/build/kaldi_arm_32
|
||||
WORKDIR_ARM64=`pwd`/build/kaldi_arm_64
|
||||
WORKDIR_BASE=`pwd`/build
|
||||
PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/${OS_NAME}-x86_64/bin
|
||||
OPENFST_VERSION=1.8.0
|
||||
|
||||
mkdir -p $WORKDIR_ARM64/local/lib $WORKDIR_ARM32/local/lib $WORKDIR_X86_64/local/lib $WORKDIR_X86/local/lib
|
||||
for arch in armeabi-v7a arm64-v8a x86_64 x86; do
|
||||
|
||||
# Build standalone CLAPACK since gfortran is missing
|
||||
cd build
|
||||
git clone https://github.com/simonlynen/android_libs
|
||||
cd android_libs/lapack
|
||||
sed -i.bak -e 's/APP_STL := gnustl_static/APP_STL := c++_static/g' jni/Application.mk && \
|
||||
sed -i.bak -e 's/android-10/android-21/g' project.properties && \
|
||||
sed -i.bak -e 's/APP_ABI := armeabi armeabi-v7a/APP_ABI := armeabi-v7a arm64-v8a x86_64 x86/g' jni/Application.mk && \
|
||||
sed -i.bak -e 's/LOCAL_MODULE:= testlapack/#LOCAL_MODULE:= testlapack/g' jni/Android.mk && \
|
||||
sed -i.bak -e 's/LOCAL_SRC_FILES:= testclapack.cpp/#LOCAL_SRC_FILES:= testclapack.cpp/g' jni/Android.mk && \
|
||||
sed -i.bak -e 's/LOCAL_STATIC_LIBRARIES := lapack/#LOCAL_STATIC_LIBRARIES := lapack/g' jni/Android.mk && \
|
||||
sed -i.bak -e 's/include $(BUILD_SHARED_LIBRARY)/#include $(BUILD_SHARED_LIBRARY)/g' jni/Android.mk && \
|
||||
${ANDROID_NDK_HOME}/ndk-build && \
|
||||
cp obj/local/armeabi-v7a/*.a ${WORKDIR_ARM32}/local/lib && \
|
||||
cp obj/local/arm64-v8a/*.a ${WORKDIR_ARM64}/local/lib
|
||||
cp obj/local/x86_64/*.a ${WORKDIR_X86_64}/local/lib
|
||||
cp obj/local/x86/*.a ${WORKDIR_X86}/local/lib
|
||||
|
||||
# Architecture-specific part
|
||||
|
||||
for arch in arm32 arm64 x86_64 x86; do
|
||||
#for arch in x86_64; do
|
||||
WORKDIR=${WORKDIR_BASE}/kaldi_${arch}
|
||||
|
||||
case $arch in
|
||||
arm32)
|
||||
armeabi-v7a)
|
||||
BLAS_ARCH=ARMV7
|
||||
WORKDIR=$WORKDIR_ARM32
|
||||
HOST=arm-linux-androideabi
|
||||
AR=arm-linux-androideabi-ar
|
||||
CC=armv7a-linux-androideabi21-clang
|
||||
CXX=armv7a-linux-androideabi21-clang++
|
||||
ARCHFLAGS="-mfloat-abi=softfp -mfpu=neon"
|
||||
;;
|
||||
arm64)
|
||||
arm64-v8a)
|
||||
BLAS_ARCH=ARMV8
|
||||
WORKDIR=$WORKDIR_ARM64
|
||||
HOST=aarch64-linux-android
|
||||
AR=aarch64-linux-android-ar
|
||||
CC=aarch64-linux-android21-clang
|
||||
@@ -86,7 +55,6 @@ case $arch in
|
||||
;;
|
||||
x86_64)
|
||||
BLAS_ARCH=ATOM
|
||||
WORKDIR=$WORKDIR_X86_64
|
||||
HOST=x86_64-linux-android
|
||||
AR=x86_64-linux-android-ar
|
||||
CC=x86_64-linux-android21-clang
|
||||
@@ -95,7 +63,6 @@ case $arch in
|
||||
;;
|
||||
x86)
|
||||
BLAS_ARCH=ATOM
|
||||
WORKDIR=$WORKDIR_X86
|
||||
HOST=i686-linux-android
|
||||
AR=i686-linux-android-ar
|
||||
CC=i686-linux-android21-clang
|
||||
@@ -104,12 +71,27 @@ case $arch in
|
||||
;;
|
||||
esac
|
||||
|
||||
mkdir -p $WORKDIR/local/lib
|
||||
|
||||
# openblas first
|
||||
cd $WORKDIR
|
||||
git clone -b v0.3.13 --single-branch https://github.com/xianyi/OpenBLAS
|
||||
make -C OpenBLAS TARGET=$BLAS_ARCH ONLY_CBLAS=1 AR=$AR CC=$CC HOSTCC=gcc ARM_SOFTFP_ABI=1 USE_THREAD=0 NUM_THREADS=1 -j4
|
||||
make -C OpenBLAS install PREFIX=$WORKDIR/local
|
||||
|
||||
# CLAPACK
|
||||
cd $WORKDIR
|
||||
git clone -b v3.2.1 --single-branch https://github.com/alphacep/clapack
|
||||
mkdir -p clapack/BUILD && cd clapack/BUILD
|
||||
cmake -DCMAKE_C_FLAGS=$ARCHFLAGS -DCMAKE_C_COMPILER_TARGET=$HOST \
|
||||
-DCMAKE_C_COMPILER=$CC -DCMAKE_SYSTEM_NAME=Generic -DCMAKE_AR=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/${OS_NAME}-x86_64/bin/$AR \
|
||||
-DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY \
|
||||
-DCMAKE_CROSSCOMPILING=True ..
|
||||
make -j 8 -C F2CLIBS/libf2c
|
||||
make -j 8 -C BLAS/SRC
|
||||
make -j 8 -C SRC
|
||||
find . -name "*.a" | xargs cp -t $WORKDIR/local/lib
|
||||
|
||||
# tools directory --> we'll only compile OpenFST
|
||||
cd $WORKDIR
|
||||
git clone https://github.com/alphacep/openfst
|
||||
@@ -125,17 +107,23 @@ make install
|
||||
cd $WORKDIR
|
||||
git clone -b android-mix --single-branch https://github.com/alphacep/kaldi
|
||||
cd $WORKDIR/kaldi/src
|
||||
if [ "`uname`" == "Darwin" ]; then
|
||||
sed -i.bak -e 's/libfst.dylib/libfst.a/' configure
|
||||
fi
|
||||
|
||||
CXX=$CXX CXXFLAGS="$ARCHFLAGS -O3 -DFST_NO_DYNAMIC_LINKING" ./configure --use-cuda=no \
|
||||
--mathlib=OPENBLAS_CLAPACK --shared \
|
||||
--android-incdir=${ANDROID_TOOLCHAIN_PATH}/sysroot/usr/include \
|
||||
--host=$HOST --openblas-root=${WORKDIR}/local \
|
||||
--fst-root=${WORKDIR}/local --fst-version=${OPENFST_VERSION}
|
||||
|
||||
make -j 8 depend
|
||||
make -j 8 online2 lm
|
||||
cd $WORKDIR/kaldi/src
|
||||
make -j 8 online2 lm rnnlm
|
||||
|
||||
# Vosk-api
|
||||
cd $WORKDIR
|
||||
#rm -rf vosk-api
|
||||
git clone -b master --single-branch https://github.com/alphacep/vosk-api
|
||||
cd vosk-api/src
|
||||
make -j 8 KALDI_ROOT=${WORKDIR}/kaldi OPENFST_ROOT=${WORKDIR}/local OPENBLAS_ROOT=${WORKDIR}/local CXX=$CXX EXTRA_LDFLAGS="-llog -static-libstdc++"
|
||||
|
||||
# Copy JNI library to sources
|
||||
cp $WORKDIR/vosk-api/src/libvosk.so $WORKDIR/../../src/main/jniLibs/$arch/libvosk.so
|
||||
|
||||
done
|
||||
@@ -0,0 +1,42 @@
|
||||
def archiveName = "vosk-android"
|
||||
def pomName = "Vosk Android"
|
||||
def pomDescription = "Vosk speech recognition library for Android"
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
versionCode 6
|
||||
versionName = version
|
||||
archivesBaseName = archiveName
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
task buildVosk(type: Exec) {
|
||||
commandLine './build-vosk.sh'
|
||||
environment ANDROID_NDK_HOME: android.getSdkDirectory()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'net.java.dev.jna:jna:4.4.0@aar'
|
||||
}
|
||||
|
||||
//preBuild.dependsOn buildVosk
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
aar(MavenPublication) {
|
||||
artifactId = archiveName
|
||||
artifact("$buildDir/outputs/aar/$archiveName-release.aar")
|
||||
pom {
|
||||
name = pomName
|
||||
description = pomDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
include 'model-en'
|
||||
@@ -1,3 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.kaldi">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.vosk">
|
||||
</manifest>
|
||||
@@ -0,0 +1,60 @@
|
||||
package org.vosk;
|
||||
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Library;
|
||||
import com.sun.jna.Platform;
|
||||
import com.sun.jna.Pointer;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
|
||||
public class LibVosk {
|
||||
|
||||
static {
|
||||
Native.register(LibVosk.class, "vosk");
|
||||
}
|
||||
|
||||
public static native void vosk_set_log_level(int level);
|
||||
|
||||
public static native Pointer vosk_model_new(String path);
|
||||
|
||||
public static native void vosk_model_free(Pointer model);
|
||||
|
||||
public static native Pointer vosk_spk_model_new(String path);
|
||||
|
||||
public static native void vosk_spk_model_free(Pointer model);
|
||||
|
||||
public static native Pointer vosk_recognizer_new(Model model, float sample_rate);
|
||||
|
||||
public static native Pointer vosk_recognizer_new_spk(Pointer model, float sample_rate, Pointer spk_model);
|
||||
|
||||
public static native Pointer vosk_recognizer_new_grm(Pointer model, float sample_rate, String grammar);
|
||||
|
||||
public static native void vosk_recognizer_set_max_alternatives(Pointer recognizer, int max_alternatives);
|
||||
|
||||
public static native void vosk_recognizer_set_words(Pointer recognizer, boolean words);
|
||||
|
||||
public static native void vosk_recognizer_set_spk_model(Pointer recognizer, Pointer spk_model);
|
||||
|
||||
public static native boolean vosk_recognizer_accept_waveform(Pointer recognizer, byte[] data, int len);
|
||||
|
||||
public static native boolean vosk_recognizer_accept_waveform_s(Pointer recognizer, short[] data, int len);
|
||||
|
||||
public static native boolean vosk_recognizer_accept_waveform_f(Pointer recognizer, float[] data, int len);
|
||||
|
||||
public static native String vosk_recognizer_result(Pointer recognizer);
|
||||
|
||||
public static native String vosk_recognizer_final_result(Pointer recognizer);
|
||||
|
||||
public static native String vosk_recognizer_partial_result(Pointer recognizer);
|
||||
|
||||
public static native void vosk_recognizer_reset(Pointer recognizer);
|
||||
|
||||
public static native void vosk_recognizer_free(Pointer recognizer);
|
||||
|
||||
public static void setLogLevel(LogLevel loglevel) {
|
||||
vosk_set_log_level(loglevel.getValue());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.vosk;
|
||||
|
||||
public enum LogLevel {
|
||||
WARNINGS(-1), // Print warning and errors
|
||||
INFO(0), // Print info, along with warning and error messages, but no debug
|
||||
DEBUG(1); // Print debug info
|
||||
|
||||
private final int value;
|
||||
|
||||
LogLevel(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.vosk;
|
||||
|
||||
import com.sun.jna.PointerType;
|
||||
|
||||
public class Model extends PointerType implements AutoCloseable {
|
||||
public Model() {
|
||||
}
|
||||
|
||||
public Model(String path) {
|
||||
super(LibVosk.vosk_model_new(path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
LibVosk.vosk_model_free(this.getPointer());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.vosk;
|
||||
|
||||
import com.sun.jna.PointerType;
|
||||
|
||||
public class Recognizer extends PointerType implements AutoCloseable {
|
||||
public Recognizer(Model model, float sampleRate) {
|
||||
super(LibVosk.vosk_recognizer_new(model, sampleRate));
|
||||
}
|
||||
|
||||
public Recognizer(Model model, float sampleRate, SpeakerModel spkModel) {
|
||||
super(LibVosk.vosk_recognizer_new_spk(model.getPointer(), sampleRate, spkModel.getPointer()));
|
||||
}
|
||||
|
||||
public Recognizer(Model model, float sampleRate, String grammar) {
|
||||
super(LibVosk.vosk_recognizer_new_grm(model.getPointer(), sampleRate, grammar));
|
||||
}
|
||||
|
||||
public void setMaxAlternatives(int maxAlternatives) {
|
||||
LibVosk.vosk_recognizer_set_max_alternatives(this.getPointer(), maxAlternatives);
|
||||
}
|
||||
|
||||
public void setWords(boolean words) {
|
||||
LibVosk.vosk_recognizer_set_words(this.getPointer(), words);
|
||||
}
|
||||
|
||||
public void setSpeakerModel(SpeakerModel spkModel) {
|
||||
LibVosk.vosk_recognizer_set_spk_model(this.getPointer(), spkModel.getPointer());
|
||||
}
|
||||
|
||||
public boolean acceptWaveForm(byte[] data, int len) {
|
||||
return LibVosk.vosk_recognizer_accept_waveform(this.getPointer(), data, len);
|
||||
}
|
||||
|
||||
public boolean acceptWaveForm(short[] data, int len) {
|
||||
return LibVosk.vosk_recognizer_accept_waveform_s(this.getPointer(), data, len);
|
||||
}
|
||||
|
||||
public boolean acceptWaveForm(float[] data, int len) {
|
||||
return LibVosk.vosk_recognizer_accept_waveform_f(this.getPointer(), data, len);
|
||||
}
|
||||
|
||||
public String getResult() {
|
||||
return LibVosk.vosk_recognizer_result(this.getPointer());
|
||||
}
|
||||
|
||||
public String getPartialResult() {
|
||||
return LibVosk.vosk_recognizer_partial_result(this.getPointer());
|
||||
}
|
||||
|
||||
public String getFinalResult() {
|
||||
return LibVosk.vosk_recognizer_final_result(this.getPointer());
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
LibVosk.vosk_recognizer_reset(this.getPointer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
LibVosk.vosk_recognizer_free(this.getPointer());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.vosk;
|
||||
|
||||
import com.sun.jna.PointerType;
|
||||
|
||||
public class SpeakerModel extends PointerType implements AutoCloseable {
|
||||
public SpeakerModel() {
|
||||
}
|
||||
|
||||
public SpeakerModel(String path) {
|
||||
super(LibVosk.vosk_spk_model_new(path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
LibVosk.vosk_spk_model_free(this.getPointer());
|
||||
}
|
||||
}
|
||||
+14
-7
@@ -12,28 +12,35 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package org.kaldi;
|
||||
package org.vosk.android;
|
||||
|
||||
/** Interface to receive recognition results */
|
||||
/**
|
||||
* Interface to receive recognition results
|
||||
*/
|
||||
public interface RecognitionListener {
|
||||
|
||||
/**
|
||||
* Called when partial recognition result is available.
|
||||
*/
|
||||
public void onPartialResult(String hypothesis);
|
||||
void onPartialResult(String hypothesis);
|
||||
|
||||
/**
|
||||
* Called after the recognition is ended.
|
||||
* Called after silence occured.
|
||||
*/
|
||||
public void onResult(String hypothesis);
|
||||
void onResult(String hypothesis);
|
||||
|
||||
/**
|
||||
* Called after stream end.
|
||||
*/
|
||||
void onFinalResult(String hypothesis);
|
||||
|
||||
/**
|
||||
* Called when an error occurs.
|
||||
*/
|
||||
public void onError(Exception exception);
|
||||
void onError(Exception exception);
|
||||
|
||||
/**
|
||||
* Called after timeout expired
|
||||
*/
|
||||
public void onTimeout();
|
||||
void onTimeout();
|
||||
}
|
||||
+103
-132
@@ -12,54 +12,45 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package org.kaldi;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
package org.vosk.android;
|
||||
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioRecord;
|
||||
import android.media.MediaRecorder.AudioSource;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import org.vosk.Recognizer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Service that records audio in a thread, passes it to a recognizer and emits
|
||||
* Service that records audio in a thread, passes it to a recognizer and emits
|
||||
* recognition results. Recognition events are passed to a client using
|
||||
* {@link RecognitionListener}
|
||||
*
|
||||
*/
|
||||
public class SpeechService {
|
||||
|
||||
protected static final String TAG = SpeechService.class.getSimpleName();
|
||||
|
||||
private final KaldiRecognizer recognizer;
|
||||
private final Recognizer recognizer;
|
||||
|
||||
private final int sampleRate;
|
||||
private final static float BUFFER_SIZE_SECONDS = 0.4f;
|
||||
private int bufferSize;
|
||||
private final static float BUFFER_SIZE_SECONDS = 0.2f;
|
||||
private final int bufferSize;
|
||||
private final AudioRecord recorder;
|
||||
|
||||
private Thread recognizerThread;
|
||||
|
||||
private RecognizerThread recognizerThread;
|
||||
|
||||
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
private final Collection<RecognitionListener> listeners = new HashSet<RecognitionListener>();
|
||||
|
||||
|
||||
/**
|
||||
* Creates speech service. Service holds the AudioRecord object, so you
|
||||
* need to call {@link release} in order to properly finalize it.
|
||||
*
|
||||
* Creates speech service. Service holds the AudioRecord object, so you
|
||||
* need to call {@link #shutdown()} in order to properly finalize it.
|
||||
*
|
||||
* @throws IOException thrown if audio recorder can not be created for some reason.
|
||||
*/
|
||||
public SpeechService(KaldiRecognizer recognizer, float sampleRate) throws IOException {
|
||||
public SpeechService(Recognizer recognizer, float sampleRate) throws IOException {
|
||||
this.recognizer = recognizer;
|
||||
this.sampleRate = (int)sampleRate;
|
||||
this.sampleRate = (int) sampleRate;
|
||||
|
||||
bufferSize = Math.round(this.sampleRate * BUFFER_SIZE_SECONDS);
|
||||
recorder = new AudioRecord(
|
||||
@@ -74,34 +65,17 @@ public class SpeechService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds listener.
|
||||
*/
|
||||
public void addListener(RecognitionListener listener) {
|
||||
synchronized (listeners) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes listener.
|
||||
*/
|
||||
public void removeListener(RecognitionListener listener) {
|
||||
synchronized (listeners) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts recognition. Does nothing if recognition is active.
|
||||
*
|
||||
*
|
||||
* @return true if recognition was actually started
|
||||
*/
|
||||
public boolean startListening() {
|
||||
public boolean startListening(RecognitionListener listener) {
|
||||
if (null != recognizerThread)
|
||||
return false;
|
||||
|
||||
recognizerThread = new RecognizerThread();
|
||||
recognizerThread = new RecognizerThread(listener);
|
||||
recognizerThread.start();
|
||||
return true;
|
||||
}
|
||||
@@ -109,16 +83,16 @@ public class SpeechService {
|
||||
/**
|
||||
* Starts recognition. After specified timeout listening stops and the
|
||||
* endOfSpeech signals about that. Does nothing if recognition is active.
|
||||
*
|
||||
* @timeout - timeout in milliseconds to listen.
|
||||
*
|
||||
* <p>
|
||||
* timeout - timeout in milliseconds to listen.
|
||||
*
|
||||
* @return true if recognition was actually started
|
||||
*/
|
||||
public boolean startListening(int timeout) {
|
||||
public boolean startListening(RecognitionListener listener, int timeout) {
|
||||
if (null != recognizerThread)
|
||||
return false;
|
||||
|
||||
recognizerThread = new RecognizerThread(timeout);
|
||||
recognizerThread = new RecognizerThread(listener, timeout);
|
||||
recognizerThread.start();
|
||||
return true;
|
||||
}
|
||||
@@ -140,31 +114,28 @@ public class SpeechService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops recognition. All listeners should receive final result if there is
|
||||
* Stops recognition. Listener should receive final result if there is
|
||||
* any. Does nothing if recognition is not active.
|
||||
*
|
||||
*
|
||||
* @return true if recognition was actually stopped
|
||||
*/
|
||||
public boolean stop() {
|
||||
boolean result = stopRecognizerThread();
|
||||
if (result) {
|
||||
mainHandler.post(new ResultEvent(recognizer.Result(), true));
|
||||
}
|
||||
return result;
|
||||
return stopRecognizerThread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels recognition. Listeners do not receive final result. Does nothing
|
||||
* if recognition is not active.
|
||||
*
|
||||
* @return true if recognition was actually canceled
|
||||
* Cancel recognition. Do not post any new events, simply cancel processing.
|
||||
* Does nothing if recognition is not active.
|
||||
*
|
||||
* @return true if recognition was actually stopped
|
||||
*/
|
||||
public boolean cancel() {
|
||||
boolean result = stopRecognizerThread();
|
||||
recognizer.Result(); // Reset recognizer state
|
||||
return result;
|
||||
if (recognizerThread != null) {
|
||||
recognizerThread.setPause(true);
|
||||
}
|
||||
return stopRecognizerThread();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shutdown the recognizer and release the recorder
|
||||
*/
|
||||
@@ -172,13 +143,33 @@ public class SpeechService {
|
||||
recorder.release();
|
||||
}
|
||||
|
||||
public void setPause(boolean paused) {
|
||||
if (recognizerThread != null) {
|
||||
recognizerThread.setPause(paused);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets recognizer in a thread, starts recognition over again
|
||||
*/
|
||||
public void reset() {
|
||||
if (recognizerThread != null) {
|
||||
recognizerThread.reset();
|
||||
}
|
||||
}
|
||||
|
||||
private final class RecognizerThread extends Thread {
|
||||
|
||||
private int remainingSamples;
|
||||
private int timeoutSamples;
|
||||
private final int timeoutSamples;
|
||||
private final static int NO_TIMEOUT = -1;
|
||||
private volatile boolean paused = false;
|
||||
private volatile boolean reset = false;
|
||||
|
||||
public RecognizerThread(int timeout) {
|
||||
RecognitionListener listener;
|
||||
|
||||
public RecognizerThread(RecognitionListener listener, int timeout) {
|
||||
this.listener = listener;
|
||||
if (timeout != NO_TIMEOUT)
|
||||
this.timeoutSamples = timeout * sampleRate / 1000;
|
||||
else
|
||||
@@ -186,8 +177,25 @@ public class SpeechService {
|
||||
this.remainingSamples = this.timeoutSamples;
|
||||
}
|
||||
|
||||
public RecognizerThread() {
|
||||
this(NO_TIMEOUT);
|
||||
public RecognizerThread(RecognitionListener listener) {
|
||||
this(listener, NO_TIMEOUT);
|
||||
}
|
||||
|
||||
/**
|
||||
* When we are paused, don't process audio by the recognizer and don't emit
|
||||
* any listener results
|
||||
*
|
||||
* @param paused the status of pause
|
||||
*/
|
||||
public void setPause(boolean paused) {
|
||||
this.paused = paused;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set reset state to signal reset of the recognizer and start over
|
||||
*/
|
||||
public void reset() {
|
||||
this.reset = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -198,8 +206,7 @@ public class SpeechService {
|
||||
recorder.stop();
|
||||
IOException ioe = new IOException(
|
||||
"Failed to start recording. Microphone might be already in use.");
|
||||
mainHandler.post(new OnErrorEvent(ioe));
|
||||
return;
|
||||
mainHandler.post(() -> listener.onError(ioe));
|
||||
}
|
||||
|
||||
short[] buffer = new short[bufferSize];
|
||||
@@ -208,15 +215,24 @@ public class SpeechService {
|
||||
&& ((timeoutSamples == NO_TIMEOUT) || (remainingSamples > 0))) {
|
||||
int nread = recorder.read(buffer, 0, buffer.length);
|
||||
|
||||
if (nread < 0) {
|
||||
if (paused) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (reset) {
|
||||
recognizer.reset();
|
||||
reset = false;
|
||||
}
|
||||
|
||||
if (nread < 0)
|
||||
throw new RuntimeException("error reading audio buffer");
|
||||
|
||||
if (recognizer.acceptWaveForm(buffer, nread)) {
|
||||
final String result = recognizer.getResult();
|
||||
mainHandler.post(() -> listener.onResult(result));
|
||||
} else {
|
||||
boolean isFinal = recognizer.AcceptWaveform(buffer, nread);
|
||||
if (isFinal) {
|
||||
mainHandler.post(new ResultEvent(recognizer.Result(), true));
|
||||
} else {
|
||||
mainHandler.post(new ResultEvent(recognizer.PartialResult(), false));
|
||||
}
|
||||
final String partialResult = recognizer.getPartialResult();
|
||||
mainHandler.post(() -> listener.onPartialResult(partialResult));
|
||||
}
|
||||
|
||||
if (timeoutSamples != NO_TIMEOUT) {
|
||||
@@ -226,61 +242,16 @@ public class SpeechService {
|
||||
|
||||
recorder.stop();
|
||||
|
||||
// Remove all pending notifications.
|
||||
mainHandler.removeCallbacksAndMessages(null);
|
||||
|
||||
// If we met timeout signal that speech ended
|
||||
if (timeoutSamples != NO_TIMEOUT && remainingSamples <= 0) {
|
||||
mainHandler.post(new TimeoutEvent());
|
||||
if (!paused) {
|
||||
// If we met timeout signal that speech ended
|
||||
if (timeoutSamples != NO_TIMEOUT && remainingSamples <= 0) {
|
||||
mainHandler.post(() -> listener.onTimeout());
|
||||
} else {
|
||||
final String finalResult = recognizer.getFinalResult();
|
||||
mainHandler.post(() -> listener.onFinalResult(finalResult));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class RecognitionEvent implements Runnable {
|
||||
public void run() {
|
||||
RecognitionListener[] emptyArray = new RecognitionListener[0];
|
||||
for (RecognitionListener listener : listeners.toArray(emptyArray))
|
||||
execute(listener);
|
||||
}
|
||||
|
||||
protected abstract void execute(RecognitionListener listener);
|
||||
}
|
||||
|
||||
private class ResultEvent extends RecognitionEvent {
|
||||
protected final String hypothesis;
|
||||
private final boolean finalResult;
|
||||
|
||||
ResultEvent(String hypothesis, boolean finalResult) {
|
||||
this.hypothesis = hypothesis;
|
||||
this.finalResult = finalResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(RecognitionListener listener) {
|
||||
if (finalResult)
|
||||
listener.onResult(hypothesis);
|
||||
else
|
||||
listener.onPartialResult(hypothesis);
|
||||
}
|
||||
}
|
||||
|
||||
private class OnErrorEvent extends RecognitionEvent {
|
||||
private final Exception exception;
|
||||
|
||||
OnErrorEvent(Exception exception) {
|
||||
this.exception = exception;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(RecognitionListener listener) {
|
||||
listener.onError(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private class TimeoutEvent extends RecognitionEvent {
|
||||
@Override
|
||||
protected void execute(RecognitionListener listener) {
|
||||
listener.onTimeout();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
// 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 org.vosk.android;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import org.vosk.Recognizer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Service that recognizes stream audio in a thread, passes it to a recognizer and emits
|
||||
* recognition results. Recognition events are passed to a client using
|
||||
* {@link RecognitionListener}
|
||||
*/
|
||||
public class SpeechStreamService {
|
||||
|
||||
private final Recognizer recognizer;
|
||||
private final InputStream inputStream;
|
||||
private final int sampleRate;
|
||||
private final static float BUFFER_SIZE_SECONDS = 0.2f;
|
||||
private final int bufferSize;
|
||||
|
||||
private Thread recognizerThread;
|
||||
|
||||
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
/**
|
||||
* Creates speech service.
|
||||
**/
|
||||
public SpeechStreamService(Recognizer recognizer, InputStream inputStream, float sampleRate) {
|
||||
this.recognizer = recognizer;
|
||||
this.sampleRate = (int) sampleRate;
|
||||
this.inputStream = inputStream;
|
||||
bufferSize = Math.round(this.sampleRate * BUFFER_SIZE_SECONDS * 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts recognition. Does nothing if recognition is active.
|
||||
*
|
||||
* @return true if recognition was actually started
|
||||
*/
|
||||
public boolean start(RecognitionListener listener) {
|
||||
if (null != recognizerThread)
|
||||
return false;
|
||||
|
||||
recognizerThread = new RecognizerThread(listener);
|
||||
recognizerThread.start();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts recognition. After specified timeout listening stops and the
|
||||
* endOfSpeech signals about that. Does nothing if recognition is active.
|
||||
* <p>
|
||||
* timeout - timeout in milliseconds to listen.
|
||||
*
|
||||
* @return true if recognition was actually started
|
||||
*/
|
||||
public boolean start(RecognitionListener listener, int timeout) {
|
||||
if (null != recognizerThread)
|
||||
return false;
|
||||
|
||||
recognizerThread = new RecognizerThread(listener, timeout);
|
||||
recognizerThread.start();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops recognition. All listeners should receive final result if there is
|
||||
* any. Does nothing if recognition is not active.
|
||||
*
|
||||
* @return true if recognition was actually stopped
|
||||
*/
|
||||
public boolean stop() {
|
||||
if (null == recognizerThread)
|
||||
return false;
|
||||
|
||||
try {
|
||||
recognizerThread.interrupt();
|
||||
recognizerThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
// Restore the interrupted status.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
recognizerThread = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
private final class RecognizerThread extends Thread {
|
||||
|
||||
private int remainingSamples;
|
||||
private final int timeoutSamples;
|
||||
private final static int NO_TIMEOUT = -1;
|
||||
RecognitionListener listener;
|
||||
|
||||
public RecognizerThread(RecognitionListener listener, int timeout) {
|
||||
this.listener = listener;
|
||||
if (timeout != NO_TIMEOUT)
|
||||
this.timeoutSamples = timeout * sampleRate / 1000;
|
||||
else
|
||||
this.timeoutSamples = NO_TIMEOUT;
|
||||
this.remainingSamples = this.timeoutSamples;
|
||||
}
|
||||
|
||||
public RecognizerThread(RecognitionListener listener) {
|
||||
this(listener, NO_TIMEOUT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
|
||||
while (!interrupted()
|
||||
&& ((timeoutSamples == NO_TIMEOUT) || (remainingSamples > 0))) {
|
||||
try {
|
||||
int nread = inputStream.read(buffer, 0, buffer.length);
|
||||
if (nread < 0) {
|
||||
break;
|
||||
} else {
|
||||
boolean isSilence = recognizer.acceptWaveForm(buffer, nread);
|
||||
if (isSilence) {
|
||||
final String result = recognizer.getResult();
|
||||
mainHandler.post(() -> listener.onResult(result));
|
||||
} else {
|
||||
final String partialResult = recognizer.getPartialResult();
|
||||
mainHandler.post(() -> listener.onPartialResult(partialResult));
|
||||
}
|
||||
}
|
||||
|
||||
if (timeoutSamples != NO_TIMEOUT) {
|
||||
remainingSamples = remainingSamples - nread;
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
mainHandler.post(() -> listener.onError(e));
|
||||
}
|
||||
}
|
||||
|
||||
// If we met timeout signal that speech ended
|
||||
if (timeoutSamples != NO_TIMEOUT && remainingSamples <= 0) {
|
||||
mainHandler.post(() -> listener.onTimeout());
|
||||
} else {
|
||||
final String finalResult = recognizer.getFinalResult();
|
||||
mainHandler.post(() -> listener.onFinalResult(finalResult));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
// 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 org.vosk.android;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import org.vosk.Model;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Provides utility methods to sync model files to external storage to allow
|
||||
* C++ code access them. Relies on file named "uuid" to track updates.
|
||||
*/
|
||||
public class StorageService {
|
||||
|
||||
protected static final String TAG = StorageService.class.getSimpleName();
|
||||
|
||||
public interface Callback<R> {
|
||||
void onComplete(R result);
|
||||
}
|
||||
|
||||
public static void unpack(Context context, String sourcePath, final String targetPath, final Callback<Model> completeCallback, final Callback<IOException> errorCallback) {
|
||||
Executor executor = Executors.newSingleThreadExecutor(); // change according to your requirements
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
final String outputPath = sync(context, sourcePath, targetPath);
|
||||
Model model = new Model(outputPath);
|
||||
handler.post(() -> completeCallback.onComplete(model));
|
||||
} catch (final IOException e) {
|
||||
handler.post(() -> errorCallback.onComplete(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static String sync(Context context, String sourcePath, String targetPath) throws IOException {
|
||||
|
||||
AssetManager assetManager = context.getAssets();
|
||||
|
||||
File externalFilesDir = context.getExternalFilesDir(null);
|
||||
if (externalFilesDir == null) {
|
||||
throw new IOException("cannot get external files dir, "
|
||||
+ "external storage state is " + Environment.getExternalStorageState());
|
||||
}
|
||||
|
||||
File targetDir = new File(externalFilesDir, targetPath);
|
||||
String resultPath = new File(targetDir, sourcePath).getAbsolutePath();
|
||||
String sourceUUID = readLine(assetManager.open(sourcePath + "/uuid"));
|
||||
try {
|
||||
String targetUUID = readLine(new FileInputStream(new File(targetDir, sourcePath + "/uuid")));
|
||||
if (targetUUID.equals(sourceUUID)) return resultPath;
|
||||
} catch (FileNotFoundException e) {
|
||||
// ignore
|
||||
}
|
||||
deleteContents(targetDir);
|
||||
|
||||
copyAssets(assetManager, sourcePath, targetDir);
|
||||
|
||||
// Copy uuid
|
||||
copyFile(assetManager, sourcePath + "/uuid", targetDir);
|
||||
|
||||
return resultPath;
|
||||
}
|
||||
|
||||
private static String readLine(InputStream is) throws IOException {
|
||||
return new BufferedReader(new InputStreamReader(is)).readLine();
|
||||
}
|
||||
|
||||
private static boolean deleteContents(File dir) {
|
||||
File[] files = dir.listFiles();
|
||||
boolean success = true;
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
success &= deleteContents(file);
|
||||
}
|
||||
if (!file.delete()) {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
private static void copyAssets(AssetManager assetManager, String path, File outPath) throws IOException {
|
||||
String[] assets = assetManager.list(path);
|
||||
if (assets == null) {
|
||||
return;
|
||||
}
|
||||
if (assets.length == 0) {
|
||||
if (!path.endsWith("uuid"))
|
||||
copyFile(assetManager, path, outPath);
|
||||
} else {
|
||||
File dir = new File(outPath, path);
|
||||
if (!dir.exists()) {
|
||||
Log.v(TAG, "Making directory " + dir.getAbsolutePath());
|
||||
if (!dir.mkdirs()) {
|
||||
Log.v(TAG, "Failed to create directory " + dir.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
for (String asset : assets) {
|
||||
copyAssets(assetManager, path + "/" + asset, outPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void copyFile(AssetManager assetManager, String fileName, File outPath) throws IOException {
|
||||
InputStream in;
|
||||
|
||||
Log.v(TAG, "Copy " + fileName + " to " + outPath);
|
||||
in = assetManager.open(fileName);
|
||||
OutputStream out = new FileOutputStream(outPath + "/" + fileName);
|
||||
|
||||
byte[] buffer = new byte[4000];
|
||||
int read;
|
||||
while ((read = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, read);
|
||||
}
|
||||
in.close();
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,6 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.5.3'
|
||||
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.5'
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "com.jfrog.bintray"
|
||||
}
|
||||
|
||||
def archiveName = "vosk-model-en"
|
||||
def libVersion = "0.3.17"
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'maven-publish'
|
||||
def pomName = "Vosk English Model"
|
||||
def pomDescription = "Small English model for Android"
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
@@ -32,40 +8,40 @@ android {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
versionCode 6
|
||||
versionName = libVersion
|
||||
versionName = version
|
||||
archivesBaseName = archiveName
|
||||
}
|
||||
}
|
||||
|
||||
Properties properties = new Properties()
|
||||
properties.load(project.rootProject.file('local.properties').newDataInputStream())
|
||||
|
||||
bintray {
|
||||
user = properties.getProperty("bintray.user")
|
||||
key = properties.getProperty("bintray.apikey")
|
||||
pkg {
|
||||
repo = 'vosk'
|
||||
name = 'vosk-model-en'
|
||||
userOrg = "alphacep"
|
||||
licenses = ['Apache2.0']
|
||||
websiteUrl = 'https://github.com/alphacep/vosk-api'
|
||||
issueTrackerUrl = 'https://github.com/alphacep/vosk-api/issues'
|
||||
vcsUrl = 'https://github.com/alphacep/vosk-api'
|
||||
version {
|
||||
name = libVersion
|
||||
vcsTag = libVersion
|
||||
buildFeatures {
|
||||
buildConfig = false
|
||||
}
|
||||
sourceSets {
|
||||
main {
|
||||
assets.srcDirs += "$buildDir/generated/assets"
|
||||
}
|
||||
}
|
||||
publications = ['aar']
|
||||
}
|
||||
|
||||
tasks.register('genUUID') {
|
||||
def uuid = UUID.randomUUID().toString()
|
||||
def odir = file("$buildDir/generated/assets/model-en-us")
|
||||
def ofile = file("$odir/uuid")
|
||||
doLast {
|
||||
mkdir odir
|
||||
ofile.text = uuid
|
||||
}
|
||||
}
|
||||
|
||||
preBuild.dependsOn(genUUID)
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
aar(MavenPublication) {
|
||||
groupId 'com.alphacep'
|
||||
artifactId archiveName
|
||||
version libVersion
|
||||
artifactId = archiveName
|
||||
artifact("$buildDir/outputs/aar/$archiveName-release.aar")
|
||||
pom {
|
||||
name = pomName
|
||||
description = pomDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.kaldi.model.en">
|
||||
package="org.vosk.model.en">
|
||||
</manifest>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
0.3.16
|
||||
@@ -1 +1 @@
|
||||
include 'model-en'
|
||||
include ':lib', ':model-en'
|
||||
|
||||
@@ -1,262 +0,0 @@
|
||||
// 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 org.kaldi;
|
||||
|
||||
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;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,7 +8,7 @@ int main() {
|
||||
|
||||
VoskModel *model = vosk_model_new("model");
|
||||
VoskSpkModel *spk_model = vosk_spk_model_new("spk-model");
|
||||
VoskRecognizer *recognizer = vosk_recognizer_new_spk(model, spk_model, 16000.0);
|
||||
VoskRecognizer *recognizer = vosk_recognizer_new_spk(model, 16000.0, spk_model);
|
||||
|
||||
wavin = fopen("test.wav", "rb");
|
||||
fseek(wavin, 44, SEEK_SET);
|
||||
|
||||
+56
-23
@@ -2,14 +2,56 @@ using System;
|
||||
using System.IO;
|
||||
using Vosk;
|
||||
|
||||
public class Test
|
||||
public class VoskDemo
|
||||
{
|
||||
public static void Main()
|
||||
public static void DemoBytes(Model model)
|
||||
{
|
||||
|
||||
Vosk.Vosk.SetLogLevel(0);
|
||||
Model model = new Model("model");
|
||||
// Demo byte buffer
|
||||
VoskRecognizer rec = new VoskRecognizer(model, 16000.0f);
|
||||
rec.SetMaxAlternatives(0);
|
||||
rec.SetWords(true);
|
||||
using(Stream source = File.OpenRead("test.wav")) {
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
while((bytesRead = source.Read(buffer, 0, buffer.Length)) > 0) {
|
||||
if (rec.AcceptWaveform(buffer, bytesRead)) {
|
||||
Console.WriteLine(rec.Result());
|
||||
} else {
|
||||
Console.WriteLine(rec.PartialResult());
|
||||
}
|
||||
}
|
||||
}
|
||||
Console.WriteLine(rec.FinalResult());
|
||||
}
|
||||
|
||||
public static void DemoFloats(Model model)
|
||||
{
|
||||
// Demo float array
|
||||
VoskRecognizer rec = new VoskRecognizer(model, 16000.0f);
|
||||
using(Stream source = File.OpenRead("test.wav")) {
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
while((bytesRead = source.Read(buffer, 0, buffer.Length)) > 0) {
|
||||
float[] fbuffer = new float[bytesRead / 2];
|
||||
for (int i = 0, n = 0; i < fbuffer.Length; i++, n+=2) {
|
||||
fbuffer[i] = (short)(buffer[n] | buffer[n+1] << 8);
|
||||
}
|
||||
if (rec.AcceptWaveform(fbuffer, fbuffer.Length)) {
|
||||
Console.WriteLine(rec.Result());
|
||||
} else {
|
||||
Console.WriteLine(rec.PartialResult());
|
||||
}
|
||||
}
|
||||
}
|
||||
Console.WriteLine(rec.FinalResult());
|
||||
}
|
||||
|
||||
public static void DemoSpeaker(Model model)
|
||||
{
|
||||
// Output speakers
|
||||
SpkModel spkModel = new SpkModel("model-spk");
|
||||
VoskRecognizer rec = new VoskRecognizer(model, 16000.0f);
|
||||
rec.SetSpkModel(spkModel);
|
||||
|
||||
using(Stream source = File.OpenRead("test.wav")) {
|
||||
byte[] buffer = new byte[4096];
|
||||
@@ -23,25 +65,16 @@ public class Test
|
||||
}
|
||||
}
|
||||
Console.WriteLine(rec.FinalResult());
|
||||
}
|
||||
|
||||
rec = new VoskRecognizer(model, 16000.0f);
|
||||
public static void Main()
|
||||
{
|
||||
// You can set to -1 to disable logging messages
|
||||
Vosk.Vosk.SetLogLevel(0);
|
||||
|
||||
using(Stream source = File.OpenRead("test.wav")) {
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
while((bytesRead = source.Read(buffer, 0, buffer.Length)) > 0) {
|
||||
float[] fbuffer = new float[bytesRead / 2];
|
||||
for (int i = 0, n = 0; i < fbuffer.Length; i++, n+=2) {
|
||||
fbuffer[i] = (short)(buffer[n] | buffer[n+1] << 8);
|
||||
}
|
||||
if (rec.AcceptWaveform(fbuffer, fbuffer.Length)) {
|
||||
Console.WriteLine(rec.Result());
|
||||
GC.Collect();
|
||||
} else {
|
||||
Console.WriteLine(rec.PartialResult());
|
||||
}
|
||||
}
|
||||
}
|
||||
Console.WriteLine(rec.FinalResult());
|
||||
Model model = new Model("model");
|
||||
DemoBytes(model);
|
||||
DemoFloats(model);
|
||||
DemoSpeaker(model);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,12 @@
|
||||
<RootNamespace>VoskDemo</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<RestoreSources>$(RestoreSources);../nuget</RestoreSources>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Vosk" Version="0.3.18" />
|
||||
<PackageReference Include="Vosk" Version="0.3.30" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<package>
|
||||
<metadata>
|
||||
<id>Vosk</id>
|
||||
<version>0.3.18</version>
|
||||
<version>0.3.30</version>
|
||||
<authors>Alpha Cephei Inc</authors>
|
||||
<owners>Alpha Cephei Inc</owners>
|
||||
<license type="expression">Apache-2.0</license>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<ItemGroup>
|
||||
<NativeLibs Include="$(MSBuildThisFileDirectory)\lib\linux-x64\*.so" Condition="'$([MSBuild]::IsOsPlatform(Linux))'" />
|
||||
<NativeLibs Include="$(MSBuildThisFileDirectory)\lib\win-x64\*.dll" Condition="'$([MSBuild]::IsOsPlatform(Windows))'" />
|
||||
<NativeLibs Include="$(MSBuildThisFileDirectory)\lib\osx-x64\*.dylib" Condition="'$([MSBuild]::IsOsPlatform(OSX))'" />
|
||||
<None Include="@(NativeLibs)">
|
||||
<Link>%(FileName)%(Extension)</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
|
||||
@@ -5,6 +5,13 @@ public class Vosk {
|
||||
VoskPINVOKE.SetLogLevel(level);
|
||||
}
|
||||
|
||||
public static void GpuInit() {
|
||||
VoskPINVOKE.GpuInit();
|
||||
}
|
||||
|
||||
public static void GpuThreadInit() {
|
||||
VoskPINVOKE.GpuThreadInit();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ class VoskPINVOKE {
|
||||
public static extern global::System.IntPtr new_VoskRecognizer(global::System.Runtime.InteropServices.HandleRef jarg1, float jarg2);
|
||||
|
||||
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint="vosk_recognizer_new_spk")]
|
||||
public static extern global::System.IntPtr new_VoskRecognizerSpk(global::System.Runtime.InteropServices.HandleRef jarg1, global::System.Runtime.InteropServices.HandleRef jarg2, float jarg3);
|
||||
public static extern global::System.IntPtr new_VoskRecognizerSpk(global::System.Runtime.InteropServices.HandleRef jarg1, float jarg2, global::System.Runtime.InteropServices.HandleRef jarg3);
|
||||
|
||||
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint="vosk_recognizer_new_grm")]
|
||||
public static extern global::System.IntPtr new_VoskRecognizerGrm(global::System.Runtime.InteropServices.HandleRef jarg1, float jarg2, string jarg3);
|
||||
@@ -32,6 +32,15 @@ class VoskPINVOKE {
|
||||
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint="vosk_recognizer_free")]
|
||||
public static extern void delete_VoskRecognizer(global::System.Runtime.InteropServices.HandleRef jarg1);
|
||||
|
||||
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint="vosk_recognizer_set_max_alternatives")]
|
||||
public static extern void VoskRecognizer_SetMaxAlternatives(global::System.Runtime.InteropServices.HandleRef jarg1, int jarg2);
|
||||
|
||||
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint="vosk_recognizer_set_words")]
|
||||
public static extern void VoskRecognizer_SetWords(global::System.Runtime.InteropServices.HandleRef jarg1, int jarg2);
|
||||
|
||||
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint="vosk_recognizer_set_spk_model")]
|
||||
public static extern void VoskRecognizer_SetSpkModel(global::System.Runtime.InteropServices.HandleRef jarg1, global::System.Runtime.InteropServices.HandleRef jarg2);
|
||||
|
||||
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint="vosk_recognizer_accept_waveform")]
|
||||
public static extern bool VoskRecognizer_AcceptWaveform(global::System.Runtime.InteropServices.HandleRef jarg1, [global::System.Runtime.InteropServices.In, global::System.Runtime.InteropServices.MarshalAs(global::System.Runtime.InteropServices.UnmanagedType.LPArray)]byte[] jarg2, int jarg3);
|
||||
|
||||
@@ -50,8 +59,17 @@ class VoskPINVOKE {
|
||||
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint="vosk_recognizer_final_result")]
|
||||
public static extern global::System.IntPtr VoskRecognizer_FinalResult(global::System.Runtime.InteropServices.HandleRef jarg1);
|
||||
|
||||
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint="vosk_recognizer_reset")]
|
||||
public static extern void VoskRecognizer_Reset(global::System.Runtime.InteropServices.HandleRef jarg1);
|
||||
|
||||
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint="vosk_set_log_level")]
|
||||
public static extern void SetLogLevel(int jarg1);
|
||||
|
||||
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint="vosk_gpu_init")]
|
||||
public static extern void GpuInit();
|
||||
|
||||
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint="vosk_gpu_thread_init")]
|
||||
public static extern void GpuThreadInit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
namespace Vosk {
|
||||
|
||||
public class VoskRecognizer : global::System.IDisposable {
|
||||
private global::System.Runtime.InteropServices.HandleRef handle;
|
||||
public class VoskRecognizer : System.IDisposable {
|
||||
private System.Runtime.InteropServices.HandleRef handle;
|
||||
|
||||
internal VoskRecognizer(global::System.IntPtr cPtr) {
|
||||
handle = new global::System.Runtime.InteropServices.HandleRef(this, cPtr);
|
||||
internal VoskRecognizer(System.IntPtr cPtr) {
|
||||
handle = new System.Runtime.InteropServices.HandleRef(this, cPtr);
|
||||
}
|
||||
|
||||
internal static global::System.Runtime.InteropServices.HandleRef getCPtr(VoskRecognizer obj) {
|
||||
return (obj == null) ? new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero) : obj.handle;
|
||||
internal static System.Runtime.InteropServices.HandleRef getCPtr(VoskRecognizer obj) {
|
||||
return (obj == null) ? new System.Runtime.InteropServices.HandleRef(null, System.IntPtr.Zero) : obj.handle;
|
||||
}
|
||||
|
||||
~VoskRecognizer() {
|
||||
@@ -17,14 +17,14 @@ public class VoskRecognizer : global::System.IDisposable {
|
||||
|
||||
public void Dispose() {
|
||||
Dispose(true);
|
||||
global::System.GC.SuppressFinalize(this);
|
||||
System.GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing) {
|
||||
lock(this) {
|
||||
if (handle.Handle != global::System.IntPtr.Zero) {
|
||||
if (handle.Handle != System.IntPtr.Zero) {
|
||||
VoskPINVOKE.delete_VoskRecognizer(handle);
|
||||
handle = new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
|
||||
handle = new System.Runtime.InteropServices.HandleRef(null, System.IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,12 +32,24 @@ public class VoskRecognizer : global::System.IDisposable {
|
||||
public VoskRecognizer(Model model, float sample_rate) : this(VoskPINVOKE.new_VoskRecognizer(Model.getCPtr(model), sample_rate)) {
|
||||
}
|
||||
|
||||
public VoskRecognizer(Model model, SpkModel spk_model, float sample_rate) : this(VoskPINVOKE.new_VoskRecognizerSpk(Model.getCPtr(model), SpkModel.getCPtr(spk_model), sample_rate)) {
|
||||
public VoskRecognizer(Model model, float sample_rate, SpkModel spk_model) : this(VoskPINVOKE.new_VoskRecognizerSpk(Model.getCPtr(model), sample_rate, SpkModel.getCPtr(spk_model))) {
|
||||
}
|
||||
|
||||
public VoskRecognizer(Model model, float sample_rate, string grammar) : this(VoskPINVOKE.new_VoskRecognizerGrm(Model.getCPtr(model), sample_rate, grammar)) {
|
||||
}
|
||||
|
||||
public void SetMaxAlternatives(int max_alternatives) {
|
||||
VoskPINVOKE.VoskRecognizer_SetMaxAlternatives(handle, max_alternatives);
|
||||
}
|
||||
|
||||
public void SetWords(bool words) {
|
||||
VoskPINVOKE.VoskRecognizer_SetWords(handle, words ? 1 : 0);
|
||||
}
|
||||
|
||||
public void SetSpkModel(SpkModel spk_model) {
|
||||
VoskPINVOKE.VoskRecognizer_SetSpkModel(handle, SpkModel.getCPtr(spk_model));
|
||||
}
|
||||
|
||||
public bool AcceptWaveform(byte[] data, int len) {
|
||||
return VoskPINVOKE.VoskRecognizer_AcceptWaveform(handle, data, len);
|
||||
}
|
||||
@@ -50,16 +62,29 @@ public class VoskRecognizer : global::System.IDisposable {
|
||||
return VoskPINVOKE.VoskRecognizer_AcceptWaveformFloat(handle, fdata, len);
|
||||
}
|
||||
|
||||
private static string PtrToStringUTF8(System.IntPtr ptr) {
|
||||
int len = 0;
|
||||
while (System.Runtime.InteropServices.Marshal.ReadByte(ptr, len) != 0)
|
||||
len++;
|
||||
byte[] array = new byte[len];
|
||||
System.Runtime.InteropServices.Marshal.Copy(ptr, array, 0, len);
|
||||
return System.Text.Encoding.UTF8.GetString(array);
|
||||
}
|
||||
|
||||
public string Result() {
|
||||
return global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(VoskPINVOKE.VoskRecognizer_Result(handle));
|
||||
return PtrToStringUTF8(VoskPINVOKE.VoskRecognizer_Result(handle));
|
||||
}
|
||||
|
||||
public string PartialResult() {
|
||||
return global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(VoskPINVOKE.VoskRecognizer_PartialResult(handle));
|
||||
return PtrToStringUTF8(VoskPINVOKE.VoskRecognizer_PartialResult(handle));
|
||||
}
|
||||
|
||||
public string FinalResult() {
|
||||
return global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(VoskPINVOKE.VoskRecognizer_FinalResult(handle));
|
||||
return PtrToStringUTF8(VoskPINVOKE.VoskRecognizer_FinalResult(handle));
|
||||
}
|
||||
|
||||
public void Reset() {
|
||||
VoskPINVOKE.VoskRecognizer_Reset(handle);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
See https://alphacephei.com/vosk/accuracy
|
||||
@@ -1 +0,0 @@
|
||||
See https://alphacephei.com/vosk/adaptation
|
||||
@@ -1 +0,0 @@
|
||||
See https://alphacephei.com/vosk/models
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. this License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
@@ -0,0 +1,7 @@
|
||||
// Go bindings for Vosk speech recognition toolkit. Vosk is an offline
|
||||
// open source speech to text API for Android, iOS, Raspberry Pi and
|
||||
// servers. It enables speech recognition models for 18 languages and
|
||||
// dialects - English, Indian English, German, French, Spanish, Portuguese,
|
||||
// Chinese, Russian, Turkish, Vietnamese, Italian, Dutch, Catalan, Arabic,
|
||||
// Greek, Farsi, Filipino, Ukrainian.
|
||||
package vosk
|
||||
@@ -0,0 +1,2 @@
|
||||
// Example package for Vosk Go bindings.
|
||||
package test_simple
|
||||
@@ -0,0 +1,67 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
vosk "github.com/alphacep/vosk-api/go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var filename string
|
||||
flag.StringVar(&filename, "f", "", "file to transcribe")
|
||||
flag.Parse()
|
||||
|
||||
model, err := vosk.NewModel("model")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
spkModel, err := vosk.NewSpkModel("model-spk")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
sampleRate := 16000.0
|
||||
rec, err := vosk.NewRecognizer(model, sampleRate, spkModel)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
rec.SetWords(1)
|
||||
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
reader := bufio.NewReader(file)
|
||||
buf := make([]byte, 4096)
|
||||
|
||||
for {
|
||||
_, err := reader.Read(buf)
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if rec.AcceptWaveform(buf) != 0 {
|
||||
fmt.Println(string(rec.Result()))
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println(string(rec.FinalResult()))
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module github.com/alphacep/vosk-api/go
|
||||
|
||||
go 1.16
|
||||
|
||||
replace (
|
||||
github.com/alphacep/vosk-api/go => ./
|
||||
)
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
package vosk
|
||||
|
||||
// #cgo CPPFLAGS: -I ${SRCDIR}/../src
|
||||
// #cgo LDFLAGS: -L ${SRCDIR}/../src -lvosk -ldl -lpthread
|
||||
// #include <stdlib.h>
|
||||
// #include <vosk_api.h>
|
||||
import "C"
|
||||
|
||||
// VoskModel contains a reference to the C VoskModel
|
||||
type VoskModel struct {
|
||||
model *C.struct_VoskModel
|
||||
}
|
||||
|
||||
// NewModel creates a new VoskModel instance
|
||||
func NewModel(modelPath string) (*VoskModel, error) {
|
||||
internal := C.vosk_model_new(C.CString(modelPath))
|
||||
model := &VoskModel{model: internal}
|
||||
return model, nil
|
||||
}
|
||||
|
||||
func freeModel(model *VoskModel) {
|
||||
C.vosk_model_free(model.model)
|
||||
}
|
||||
|
||||
// FindWord checks if a word can be recognized by the model.
|
||||
// Returns the word symbol if the word exists inside the model or
|
||||
// -1 otherwise.
|
||||
func (m *VoskModel) FindWord(word []byte) int {
|
||||
cbuf := C.CBytes(word)
|
||||
defer C.free(cbuf)
|
||||
i := C.vosk_model_find_word(m.model, (*C.char)(cbuf))
|
||||
return int(i)
|
||||
}
|
||||
|
||||
// VoskSpkModel contains a reference to the C VoskSpkModel
|
||||
type VoskSpkModel struct {
|
||||
spkModel *C.struct_VoskSpkModel
|
||||
}
|
||||
|
||||
// NewSpkModel creates a new VoskSpkModel instance
|
||||
func NewSpkModel(spkModelPath string) (*VoskSpkModel, error) {
|
||||
internal := C.vosk_spk_model_new(C.CString(spkModelPath))
|
||||
spkModel := &VoskSpkModel{spkModel: internal}
|
||||
return spkModel, nil
|
||||
}
|
||||
|
||||
func freeSpkModel(model *VoskSpkModel) {
|
||||
C.vosk_spk_model_free(model.spkModel)
|
||||
}
|
||||
|
||||
// VoskRecognizer contains a reference to the C VoskRecognizer
|
||||
type VoskRecognizer struct {
|
||||
rec *C.struct_VoskRecognizer
|
||||
}
|
||||
|
||||
func freeRecognizer(recognizer *VoskRecognizer) {
|
||||
C.vosk_recognizer_free(recognizer.rec)
|
||||
}
|
||||
|
||||
// NewRecognizer creates a new VoskRecognizer instance
|
||||
func NewRecognizer(model *VoskModel, sampleRate float64) (*VoskRecognizer, error) {
|
||||
internal := C.vosk_recognizer_new(model.model, C.float(sampleRate))
|
||||
rec := &VoskRecognizer{rec: internal}
|
||||
return rec, nil
|
||||
}
|
||||
|
||||
// NewRecognizerSpk creates a new VoskRecognizer instance with a speaker model.
|
||||
func NewRecognizerSpk(model *VoskModel, sampleRate float64, spkModel *VoskSpkModel) (*VoskRecognizer, error) {
|
||||
internal := C.vosk_recognizer_new_spk(model.model, C.float(sampleRate), spkModel.spkModel)
|
||||
rec := &VoskRecognizer{rec: internal}
|
||||
return rec, nil
|
||||
}
|
||||
|
||||
// NewRecognizerGrm creates a new VoskRecognizer instance with the phrase list.
|
||||
func NewRecognizerGrm(model *VoskModel, sampleRate float64, grammer []byte) (*VoskRecognizer, error) {
|
||||
cbuf := C.CBytes(grammer)
|
||||
defer C.free(cbuf)
|
||||
internal := C.vosk_recognizer_new_grm(model.model, C.float(sampleRate), (*C.char)(cbuf))
|
||||
rec := &VoskRecognizer{rec: internal}
|
||||
return rec, nil
|
||||
}
|
||||
|
||||
// SetSpkModel adds a speaker model to an already initialized recognizer.
|
||||
func (r *VoskRecognizer) SetSpkModel(spkModel *VoskSpkModel) {
|
||||
C.vosk_recognizer_set_spk_model(r.rec, spkModel.spkModel)
|
||||
}
|
||||
|
||||
// SetMaxAlternatives configures the recognizer to output n-best results.
|
||||
func (r *VoskRecognizer) SetMaxAlternatives(maxAlternatives int) {
|
||||
C.vosk_recognizer_set_max_alternatives(r.rec, C.int(maxAlternatives))
|
||||
}
|
||||
|
||||
// SetWords enables words with times in the ouput.
|
||||
func (r *VoskRecognizer) SetWords(words int) {
|
||||
C.vosk_recognizer_set_words(r.rec, C.int(words))
|
||||
}
|
||||
|
||||
// AcceptWaveform accepts and processes a new chunk of the voice data.
|
||||
func (r *VoskRecognizer) AcceptWaveform(buffer []byte) int {
|
||||
cbuf := C.CBytes(buffer)
|
||||
defer C.free(cbuf)
|
||||
i := C.vosk_recognizer_accept_waveform(r.rec, (*C.char)(cbuf), C.int(len(buffer)))
|
||||
return int(i)
|
||||
}
|
||||
|
||||
// Result returns a speech recognition result.
|
||||
func (r *VoskRecognizer) Result() []byte {
|
||||
return []byte(C.GoString(C.vosk_recognizer_result(r.rec)))
|
||||
}
|
||||
|
||||
// PartialResult returns a partial speech recognition result.
|
||||
func (r *VoskRecognizer) PartialResult() []byte {
|
||||
return []byte(C.GoString(C.vosk_recognizer_result(r.rec)))
|
||||
}
|
||||
|
||||
// FinalResult returns a speech recognition result. Same as result, but doesn't wait
|
||||
// for silence.
|
||||
func (r *VoskRecognizer) FinalResult() []byte {
|
||||
return []byte(C.GoString(C.vosk_recognizer_final_result(r.rec)))
|
||||
}
|
||||
|
||||
// Reset resets the recognizer.
|
||||
func (r *VoskRecognizer) Reset() {
|
||||
C.vosk_recognizer_reset(r.rec)
|
||||
}
|
||||
|
||||
// SetLogLevel sets the log level for Kaldi messages.
|
||||
func SetLogLevel(logLevel int) {
|
||||
C.vosk_set_log_level(C.int(logLevel))
|
||||
}
|
||||
|
||||
// GPUInit automatically selects a CUDA device and allows multithreading.
|
||||
func GPUInit() {
|
||||
C.vosk_gpu_init()
|
||||
}
|
||||
|
||||
// GPUThreadInit inits CUDA device in a multi-threaded environment.
|
||||
func GPUThreadInit() {
|
||||
C.vosk_gpu_thread_init()
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
KALDI_ROOT ?= $(HOME)/kaldi
|
||||
CFLAGS := -g -O2 -DPIC -fPIC -Wno-unused-function -std=c++17
|
||||
CPPFLAGS := -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/linux -I$(KALDI_ROOT)/src -I$(KALDI_ROOT)/tools/openfst/include -I../src
|
||||
|
||||
KALDI_LIBS = \
|
||||
${KALDI_ROOT}/src/online2/kaldi-online2.a \
|
||||
${KALDI_ROOT}/src/decoder/kaldi-decoder.a \
|
||||
${KALDI_ROOT}/src/ivector/kaldi-ivector.a \
|
||||
${KALDI_ROOT}/src/gmm/kaldi-gmm.a \
|
||||
${KALDI_ROOT}/src/nnet3/kaldi-nnet3.a \
|
||||
${KALDI_ROOT}/src/tree/kaldi-tree.a \
|
||||
${KALDI_ROOT}/src/feat/kaldi-feat.a \
|
||||
${KALDI_ROOT}/src/lat/kaldi-lat.a \
|
||||
${KALDI_ROOT}/src/lm/kaldi-lm.a \
|
||||
${KALDI_ROOT}/src/hmm/kaldi-hmm.a \
|
||||
${KALDI_ROOT}/src/transform/kaldi-transform.a \
|
||||
${KALDI_ROOT}/src/cudamatrix/kaldi-cudamatrix.a \
|
||||
${KALDI_ROOT}/src/matrix/kaldi-matrix.a \
|
||||
${KALDI_ROOT}/src/fstext/kaldi-fstext.a \
|
||||
${KALDI_ROOT}/src/util/kaldi-util.a \
|
||||
${KALDI_ROOT}/src/base/kaldi-base.a \
|
||||
${KALDI_ROOT}/tools/openfst/lib/libfst.a \
|
||||
${KALDI_ROOT}/tools/openfst/lib/libfstngram.a \
|
||||
|
||||
# On Linux
|
||||
MATH_LIBS = \
|
||||
${KALDI_ROOT}/tools/OpenBLAS/install/lib/libopenblas.a \
|
||||
${KALDI_ROOT}/tools/OpenBLAS/install/lib/liblapack.a \
|
||||
${KALDI_ROOT}/tools/OpenBLAS/install/lib/libblas.a \
|
||||
${KALDI_ROOT}/tools/OpenBLAS/install/lib/libf2c.a
|
||||
|
||||
all: libvosk_jni.so
|
||||
|
||||
VOSK_SOURCES = \
|
||||
vosk_wrap.cc \
|
||||
../src/kaldi_recognizer.cc \
|
||||
../src/kaldi_recognizer.h \
|
||||
../src/language_model.cc \
|
||||
../src/language_model.h \
|
||||
../src/model.cc \
|
||||
../src/model.h \
|
||||
../src/spk_model.cc \
|
||||
../src/spk_model.h \
|
||||
../src/vosk_api.cc \
|
||||
../src/vosk_api.h
|
||||
|
||||
libvosk_jni.so: $(VOSK_SOURCES)
|
||||
$(CXX) -shared -o $@ $(CPPFLAGS) $(CFLAGS) $(VOSK_SOURCES) $(KALDI_LIBS) $(MATH_LIBS)
|
||||
|
||||
vosk_wrap.cc: ../src/vosk.i
|
||||
mkdir -p org/kaldi
|
||||
swig -c++ -I../src \
|
||||
-java -package org.kaldi \
|
||||
-outdir org/kaldi -o $@ $<
|
||||
|
||||
clean:
|
||||
$(RM) *.so *_wrap.cc *_wrap.o test/*.class
|
||||
$(RM) -r org model-en
|
||||
|
||||
model:
|
||||
wget https://alphacephei.com/vosk/models/vosk-model-small-en-us-0.15.zip
|
||||
unzip vosk-model-small-en-us-0.15.zip && rm vosk-model-small-en-us-0.15.zip
|
||||
mv vosk-model-small-en-us-0.15 model
|
||||
|
||||
model-spk:
|
||||
wget https://alphacephei.com/vosk/models/vosk-model-spk-0.4.zip
|
||||
unzip vosk-model-spk-0.4.zip && rm vosk-model-spk-0.4.zip
|
||||
mv vosk-model-spk-0.4 model-spk
|
||||
|
||||
run: model model-spk
|
||||
javac test/*.java org/kaldi/*.java
|
||||
java -Djava.library.path=. -cp . test.DecoderTest
|
||||
+4
-16
@@ -1,19 +1,7 @@
|
||||
Java API sample
|
||||
Java bindings for Vosk API using jnr-ffi
|
||||
|
||||
Doesn't work on Windows or Mac yet, help to prepare the packaged jars is welcome.
|
||||
See demo project for details, build it with Gradle.
|
||||
|
||||
For now to try it:
|
||||
Download model and unpack as "model" folder in the demo project.
|
||||
|
||||
On Linux you can do
|
||||
|
||||
1. Build recent kaldi
|
||||
1. `git clone https://github.com/alphacep/vosk-api`
|
||||
1. `cd vosk-api/java`
|
||||
1. `export KALDI_ROOT=<KALDI_ROOT>`
|
||||
1. `export JAVA_HOME=<JAVA_HOME>`
|
||||
1. `make`
|
||||
1. `make run`
|
||||
|
||||
For details of the code you can check:
|
||||
|
||||
https://github.com/alphacep/vosk-api/blob/master/java/test/DecoderTest.java
|
||||
Make sure you are using recent JDK and Gradle.
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
plugins {
|
||||
id 'application'
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass = 'org.vosk.demo.DecoderDemo'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url 'https://alphacephei.com/maven/'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation group: 'net.java.dev.jna', name: 'jna', version: '5.7.0'
|
||||
implementation group: 'com.alphacephei', name: 'vosk', version: '0.3.30+'
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.vosk.demo;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.UnsupportedAudioFileException;
|
||||
|
||||
import org.vosk.LogLevel;
|
||||
import org.vosk.Recognizer;
|
||||
import org.vosk.LibVosk;
|
||||
import org.vosk.Model;
|
||||
|
||||
public class DecoderDemo {
|
||||
|
||||
public static void main(String[] argv) throws IOException, UnsupportedAudioFileException {
|
||||
LibVosk.setLogLevel(LogLevel.DEBUG);
|
||||
|
||||
try (Model model = new Model("model");
|
||||
InputStream ais = AudioSystem.getAudioInputStream(new BufferedInputStream(new FileInputStream("../../python/example/test.wav")));
|
||||
Recognizer recognizer = new Recognizer(model, 16000)) {
|
||||
|
||||
int nbytes;
|
||||
byte[] b = new byte[4096];
|
||||
while ((nbytes = ais.read(b)) >= 0) {
|
||||
if (recognizer.acceptWaveForm(b, nbytes)) {
|
||||
System.out.println(recognizer.getResult());
|
||||
} else {
|
||||
System.out.println(recognizer.getPartialResult());
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println(recognizer.getFinalResult());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
archivesBaseName = 'vosk'
|
||||
group = 'com.alphacephei'
|
||||
version = '0.3.30'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation group: 'net.java.dev.jna', name: 'jna', version: '5.7.0'
|
||||
testImplementation 'junit:junit:4.13'
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
artifactId = 'vosk'
|
||||
from components.java
|
||||
pom {
|
||||
name = 'Vosk'
|
||||
description = 'Speech recognition library'
|
||||
url = 'http://www.alphacephei.com.com/vosk/'
|
||||
licenses {
|
||||
license {
|
||||
name = 'The Apache License, Version 2.0'
|
||||
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||
}
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id = 'alphacephei'
|
||||
name = 'Alpha Cephei Inc'
|
||||
email = 'contact@alphacephei.com'
|
||||
}
|
||||
}
|
||||
scm {
|
||||
connection = 'scm:git:git://github.com/alphacep/vosk-api.git'
|
||||
url = 'https://github.com/alphacep/vosk-api/'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
repositories {
|
||||
maven {
|
||||
url = "repo"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
dependsOn cleanTest
|
||||
testLogging.showStandardStreams = true
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package org.vosk;
|
||||
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Library;
|
||||
import com.sun.jna.Platform;
|
||||
import com.sun.jna.Pointer;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
|
||||
public class LibVosk {
|
||||
|
||||
private static void unpackDll(File targetDir, String lib) throws IOException {
|
||||
InputStream source = LibVosk.class.getResourceAsStream("/win32-x86-64/" + lib + ".dll");
|
||||
Files.copy(source, new File(targetDir, lib + ".dll").toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
static {
|
||||
|
||||
if (Platform.isWindows()) {
|
||||
// We have to unpack dependencies
|
||||
try {
|
||||
// To get a tmp folder we unpack small library and mark it for deletion
|
||||
File tmpFile = Native.extractFromResourcePath("/win32-x86-64/empty");
|
||||
File tmpDir = tmpFile.getParentFile();
|
||||
new File(tmpDir, tmpFile.getName() + ".x").createNewFile();
|
||||
|
||||
// Now unpack dependencies
|
||||
unpackDll(tmpDir, "libwinpthread-1");
|
||||
unpackDll(tmpDir, "libgcc_s_seh-1");
|
||||
unpackDll(tmpDir, "libstdc++-6");
|
||||
|
||||
} catch (IOException e) {
|
||||
// Nothing for now, it will fail on next step
|
||||
} finally {
|
||||
Native.register(LibVosk.class, "libvosk");
|
||||
}
|
||||
} else {
|
||||
Native.register(LibVosk.class, "vosk");
|
||||
}
|
||||
}
|
||||
|
||||
public static native void vosk_set_log_level(int level);
|
||||
|
||||
public static native Pointer vosk_model_new(String path);
|
||||
|
||||
public static native void vosk_model_free(Pointer model);
|
||||
|
||||
public static native Pointer vosk_spk_model_new(String path);
|
||||
|
||||
public static native void vosk_spk_model_free(Pointer model);
|
||||
|
||||
public static native Pointer vosk_recognizer_new(Model model, float sample_rate);
|
||||
|
||||
public static native Pointer vosk_recognizer_new_spk(Pointer model, float sample_rate, Pointer spk_model);
|
||||
|
||||
public static native Pointer vosk_recognizer_new_grm(Pointer model, float sample_rate, String grammar);
|
||||
|
||||
public static native void vosk_recognizer_set_max_alternatives(Pointer recognizer, int max_alternatives);
|
||||
|
||||
public static native void vosk_recognizer_set_words(Pointer recognizer, boolean words);
|
||||
|
||||
public static native void vosk_recognizer_set_spk_model(Pointer recognizer, Pointer spk_model);
|
||||
|
||||
public static native boolean vosk_recognizer_accept_waveform(Pointer recognizer, byte[] data, int len);
|
||||
|
||||
public static native boolean vosk_recognizer_accept_waveform_s(Pointer recognizer, short[] data, int len);
|
||||
|
||||
public static native boolean vosk_recognizer_accept_waveform_f(Pointer recognizer, float[] data, int len);
|
||||
|
||||
public static native String vosk_recognizer_result(Pointer recognizer);
|
||||
|
||||
public static native String vosk_recognizer_final_result(Pointer recognizer);
|
||||
|
||||
public static native String vosk_recognizer_partial_result(Pointer recognizer);
|
||||
|
||||
public static native void vosk_recognizer_reset(Pointer recognizer);
|
||||
|
||||
public static native void vosk_recognizer_free(Pointer recognizer);
|
||||
|
||||
public static void setLogLevel(LogLevel loglevel) {
|
||||
vosk_set_log_level(loglevel.getValue());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.vosk;
|
||||
|
||||
public enum LogLevel {
|
||||
WARNINGS(-1), // Print warning and errors
|
||||
INFO(0), // Print info, along with warning and error messages, but no debug
|
||||
DEBUG(1); // Print debug info
|
||||
|
||||
private final int value;
|
||||
|
||||
LogLevel(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.vosk;
|
||||
|
||||
import com.sun.jna.PointerType;
|
||||
|
||||
public class Model extends PointerType implements AutoCloseable {
|
||||
public Model() {
|
||||
}
|
||||
|
||||
public Model(String path) {
|
||||
super(LibVosk.vosk_model_new(path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
LibVosk.vosk_model_free(this.getPointer());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.vosk;
|
||||
|
||||
import com.sun.jna.PointerType;
|
||||
|
||||
public class Recognizer extends PointerType implements AutoCloseable {
|
||||
public Recognizer(Model model, float sampleRate) {
|
||||
super(LibVosk.vosk_recognizer_new(model, sampleRate));
|
||||
}
|
||||
|
||||
public Recognizer(Model model, float sampleRate, SpeakerModel spkModel) {
|
||||
super(LibVosk.vosk_recognizer_new_spk(model.getPointer(), sampleRate, spkModel.getPointer()));
|
||||
}
|
||||
|
||||
public Recognizer(Model model, float sampleRate, String grammar) {
|
||||
super(LibVosk.vosk_recognizer_new_grm(model.getPointer(), sampleRate, grammar));
|
||||
}
|
||||
|
||||
public void setMaxAlternatives(int maxAlternatives) {
|
||||
LibVosk.vosk_recognizer_set_max_alternatives(this.getPointer(), maxAlternatives);
|
||||
}
|
||||
|
||||
public void setWords(boolean words) {
|
||||
LibVosk.vosk_recognizer_set_words(this.getPointer(), words);
|
||||
}
|
||||
|
||||
public void setSpeakerModel(SpeakerModel spkModel) {
|
||||
LibVosk.vosk_recognizer_set_spk_model(this.getPointer(), spkModel.getPointer());
|
||||
}
|
||||
|
||||
public boolean acceptWaveForm(byte[] data, int len) {
|
||||
return LibVosk.vosk_recognizer_accept_waveform(this.getPointer(), data, len);
|
||||
}
|
||||
|
||||
public boolean acceptWaveForm(short[] data, int len) {
|
||||
return LibVosk.vosk_recognizer_accept_waveform_s(this.getPointer(), data, len);
|
||||
}
|
||||
|
||||
public boolean acceptWaveForm(float[] data, int len) {
|
||||
return LibVosk.vosk_recognizer_accept_waveform_f(this.getPointer(), data, len);
|
||||
}
|
||||
|
||||
public String getResult() {
|
||||
return LibVosk.vosk_recognizer_result(this.getPointer());
|
||||
}
|
||||
|
||||
public String getPartialResult() {
|
||||
return LibVosk.vosk_recognizer_partial_result(this.getPointer());
|
||||
}
|
||||
|
||||
public String getFinalResult() {
|
||||
return LibVosk.vosk_recognizer_final_result(this.getPointer());
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
LibVosk.vosk_recognizer_reset(this.getPointer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
LibVosk.vosk_recognizer_free(this.getPointer());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.vosk;
|
||||
|
||||
import com.sun.jna.PointerType;
|
||||
|
||||
public class SpeakerModel extends PointerType implements AutoCloseable {
|
||||
public SpeakerModel() {
|
||||
}
|
||||
|
||||
public SpeakerModel(String path) {
|
||||
super(LibVosk.vosk_spk_model_new(path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
LibVosk.vosk_spk_model_free(this.getPointer());
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
Mainly for work around JNA API
|
||||
@@ -0,0 +1,96 @@
|
||||
package org.vosk.test;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.Assert;
|
||||
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.UnsupportedAudioFileException;
|
||||
|
||||
import org.vosk.LogLevel;
|
||||
import org.vosk.Recognizer;
|
||||
import org.vosk.LibVosk;
|
||||
import org.vosk.Model;
|
||||
|
||||
public class DecoderTest {
|
||||
|
||||
@Test
|
||||
public void decoderTest() throws IOException, UnsupportedAudioFileException {
|
||||
LibVosk.setLogLevel(LogLevel.DEBUG);
|
||||
|
||||
try (Model model = new Model("model");
|
||||
InputStream ais = AudioSystem.getAudioInputStream(new BufferedInputStream(new FileInputStream("../../python/example/test.wav")));
|
||||
Recognizer recognizer = new Recognizer(model, 16000)) {
|
||||
|
||||
recognizer.setMaxAlternatives(10);
|
||||
recognizer.setWords(true);
|
||||
|
||||
int nbytes;
|
||||
byte[] b = new byte[4096];
|
||||
while ((nbytes = ais.read(b)) >= 0) {
|
||||
if (recognizer.acceptWaveForm(b, nbytes)) {
|
||||
System.out.println(recognizer.getResult());
|
||||
} else {
|
||||
System.out.println(recognizer.getPartialResult());
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println(recognizer.getFinalResult());
|
||||
}
|
||||
Assert.assertTrue(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decoderTestShort() throws IOException, UnsupportedAudioFileException {
|
||||
LibVosk.setLogLevel(LogLevel.DEBUG);
|
||||
|
||||
try (Model model = new Model("model");
|
||||
InputStream ais = AudioSystem.getAudioInputStream(new BufferedInputStream(new FileInputStream("../../python/example/test.wav")));
|
||||
Recognizer recognizer = new Recognizer(model, 16000)) {
|
||||
|
||||
int nbytes;
|
||||
byte[] b = new byte[4096];
|
||||
short[] s = new short[2048];
|
||||
while ((nbytes = ais.read(b)) >= 0) {
|
||||
ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(s);
|
||||
if (recognizer.acceptWaveForm(s, nbytes / 2)) {
|
||||
System.out.println(recognizer.getResult());
|
||||
} else {
|
||||
System.out.println(recognizer.getPartialResult());
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println(recognizer.getFinalResult());
|
||||
}
|
||||
Assert.assertTrue(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decoderTestGrammar() throws IOException, UnsupportedAudioFileException {
|
||||
LibVosk.setLogLevel(LogLevel.DEBUG);
|
||||
|
||||
try (Model model = new Model("model");
|
||||
InputStream ais = AudioSystem.getAudioInputStream(new BufferedInputStream(new FileInputStream("../../python/example/test.wav")));
|
||||
Recognizer recognizer = new Recognizer(model, 16000, "[\"one two three four five six seven eight nine zero oh\"]")) {
|
||||
|
||||
int nbytes;
|
||||
byte[] b = new byte[4096];
|
||||
while ((nbytes = ais.read(b)) >= 0) {
|
||||
if (recognizer.acceptWaveForm(b, nbytes)) {
|
||||
System.out.println(recognizer.getResult());
|
||||
} else {
|
||||
System.out.println(recognizer.getPartialResult());
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println(recognizer.getFinalResult());
|
||||
}
|
||||
Assert.assertTrue(true);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.*;
|
||||
|
||||
import org.kaldi.KaldiRecognizer;
|
||||
import org.kaldi.Model;
|
||||
import org.kaldi.SpkModel;
|
||||
import org.kaldi.Vosk;
|
||||
|
||||
public class DecoderTest {
|
||||
|
||||
public static void main(String args[]) throws IOException {
|
||||
Vosk.SetLogLevel(-10);
|
||||
|
||||
FileInputStream ais = new FileInputStream(new File("../python/example/test.wav"));
|
||||
Model model = new Model("model");
|
||||
SpkModel spkModel = new SpkModel("model-spk");
|
||||
KaldiRecognizer rec = new KaldiRecognizer(model, spkModel, 16000.0f);
|
||||
|
||||
int nbytes;
|
||||
byte[] b = new byte[4096];
|
||||
while ((nbytes = ais.read(b)) >= 0) {
|
||||
if (rec.AcceptWaveform(b)) {
|
||||
System.out.println(rec.Result());
|
||||
} else {
|
||||
System.out.println(rec.PartialResult());
|
||||
}
|
||||
}
|
||||
System.out.println(rec.FinalResult());
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -1,3 +1,3 @@
|
||||
build
|
||||
node_modules
|
||||
vosk_wrap.cc
|
||||
demo/model
|
||||
demo/model-spk
|
||||
demo/test.wav
|
||||
|
||||
+27
-14
@@ -1,20 +1,33 @@
|
||||
Installation requires vosk-api checkout, it doesn't yet work with `npm
|
||||
install vosk`. We have to figure out how to properly distribute native
|
||||
modules for Vosk.
|
||||
This is an FFI-NAPI wrapper for the Vosk library.
|
||||
|
||||
The build tested with node-0.10.15, node-0.12 is not yet supported by swig.
|
||||
## Usage
|
||||
|
||||
Still, you need swig of newest version 4.0.1
|
||||
It mostly follows Vosk interface, some methods are not yet fully implemented.
|
||||
|
||||
Build like this
|
||||
To use it you need to compile libvosk library, see Python module build
|
||||
instructions for details. You can find prebuilt library inside python
|
||||
wheel.
|
||||
|
||||
```
|
||||
npm install --kaldi_root=/home/user/kaldi
|
||||
```
|
||||
## About
|
||||
|
||||
Then test with
|
||||
Vosk is an offline open source speech recognition toolkit. It enables
|
||||
speech recognition models for 17 languages and dialects - English, Indian
|
||||
English, German, French, Spanish, Portuguese, Chinese, Russian, Turkish,
|
||||
Vietnamese, Italian, Dutch, Catalan, Arabic, Greek, Farsi, Filipino.
|
||||
|
||||
```
|
||||
cd example
|
||||
node test.js
|
||||
```
|
||||
Vosk models are small (50 Mb) but provide continuous large vocabulary
|
||||
transcription, zero-latency response with streaming API, reconfigurable
|
||||
vocabulary and speaker identification.
|
||||
|
||||
Vosk supplies speech recognition for chatbots, smart home appliances,
|
||||
virtual assistants. It can also create subtitles for movies,
|
||||
transcription for lectures and interviews.
|
||||
|
||||
Vosk scales from small devices like Raspberry Pi or Android smartphone to
|
||||
big clusters.
|
||||
|
||||
# Documentation
|
||||
|
||||
For installation instructions, examples and documentation visit [Vosk
|
||||
Website](https://alphacephei.com/vosk). See also our project on
|
||||
[Github](https://github.com/alphacep/vosk-api).
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'vosk',
|
||||
'sources': [
|
||||
'../src/kaldi_recognizer.cc',
|
||||
'../src/model.cc',
|
||||
'../src/language_model.cc',
|
||||
'../src/spk_model.cc',
|
||||
'../src/vosk_api.cc',
|
||||
'vosk_wrap.cc',
|
||||
],
|
||||
'cflags': [
|
||||
'-std=c++17',
|
||||
'-DFST_NO_DYNAMIC_LINKING',
|
||||
'-Wno-deprecated-declarations',
|
||||
'-Wno-sign-compare',
|
||||
'-Wno-unused-local-typedefs',
|
||||
'-Wno-ignored-quaifiers',
|
||||
'-Wno-extra',
|
||||
],
|
||||
'cflags_cc!' : [
|
||||
'-fno-rtti',
|
||||
'-fno-exceptions',
|
||||
'-std=gnu++1y',
|
||||
],
|
||||
'conditions': [
|
||||
['OS == "mac"', {
|
||||
'xcode_settings': {
|
||||
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
|
||||
'GCC_ENABLE_CPP_RTTI': 'YES',
|
||||
'CLANG_CXX_LANGUAGE_STANDARD': 'c++17'
|
||||
}
|
||||
}]
|
||||
],
|
||||
'actions': [
|
||||
{
|
||||
'action_name': 'swig',
|
||||
'inputs': [
|
||||
'../src/vosk.i',
|
||||
],
|
||||
'outputs': [
|
||||
'vosk_wrap.cc',
|
||||
],
|
||||
'action': ['swig', '-c++', '-javascript', '-o', 'vosk_wrap.cc', '-v8', '-DV8_MAJOR_VERSION=10', '../src/vosk.i']
|
||||
},
|
||||
],
|
||||
'include_dirs': [
|
||||
'<@(kaldi_root)/src',
|
||||
'<@(kaldi_root)/tools/openfst/include',
|
||||
'../src',
|
||||
],
|
||||
'link_settings': {
|
||||
'libraries': [
|
||||
'<@(kaldi_root)/src/online2/kaldi-online2.a',
|
||||
'<@(kaldi_root)/src/decoder/kaldi-decoder.a',
|
||||
'<@(kaldi_root)/src/ivector/kaldi-ivector.a',
|
||||
'<@(kaldi_root)/src/gmm/kaldi-gmm.a',
|
||||
'<@(kaldi_root)/src/nnet3/kaldi-nnet3.a',
|
||||
'<@(kaldi_root)/src/tree/kaldi-tree.a',
|
||||
'<@(kaldi_root)/src/feat/kaldi-feat.a',
|
||||
'<@(kaldi_root)/src/lat/kaldi-lat.a',
|
||||
'<@(kaldi_root)/src/lm/kaldi-lm.a',
|
||||
'<@(kaldi_root)/src/hmm/kaldi-hmm.a',
|
||||
'<@(kaldi_root)/src/transform/kaldi-transform.a',
|
||||
'<@(kaldi_root)/src/cudamatrix/kaldi-cudamatrix.a',
|
||||
'<@(kaldi_root)/src/matrix/kaldi-matrix.a',
|
||||
'<@(kaldi_root)/src/fstext/kaldi-fstext.a',
|
||||
'<@(kaldi_root)/src/util/kaldi-util.a',
|
||||
'<@(kaldi_root)/src/base/kaldi-base.a',
|
||||
'<@(kaldi_root)/tools/openfst/lib/libfst.a',
|
||||
'<@(kaldi_root)/tools/openfst/lib/libfstngram.a',
|
||||
'<@(kaldi_root)/tools/OpenBLAS/install/lib/libopenblas.a',
|
||||
'<@(kaldi_root)/tools/OpenBLAS/install/lib/liblapack.a',
|
||||
'<@(kaldi_root)/tools/OpenBLAS/install/lib/libblas.a',
|
||||
'<@(kaldi_root)/tools/OpenBLAS/install/lib/libf2c.a',
|
||||
],
|
||||
'library_dirs': [
|
||||
'/usr/lib',
|
||||
],
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
var vosk = require('..')
|
||||
|
||||
const fs = require("fs");
|
||||
const { spawn } = require("child_process");
|
||||
|
||||
MODEL_PATH = "model"
|
||||
FILE_NAME = "test.wav"
|
||||
SAMPLE_RATE = 16000
|
||||
BUFFER_SIZE = 4000
|
||||
|
||||
if (!fs.existsSync(MODEL_PATH)) {
|
||||
console.log("Please download the model from https://alphacephei.com/vosk/models and unpack as " + MODEL_PATH + " in the current folder.")
|
||||
process.exit()
|
||||
}
|
||||
|
||||
if (process.argv.length > 2)
|
||||
FILE_NAME = process.argv[2]
|
||||
|
||||
vosk.setLogLevel(0);
|
||||
const model = new vosk.Model(MODEL_PATH);
|
||||
const rec = new vosk.Recognizer({model: model, sampleRate: SAMPLE_RATE});
|
||||
|
||||
const ffmpeg_run = spawn('ffmpeg', ['-loglevel', 'quiet', '-i', FILE_NAME,
|
||||
'-ar', String(SAMPLE_RATE) , '-ac', '1',
|
||||
'-f', 's16le', '-bufsize', String(BUFFER_SIZE) , '-']);
|
||||
|
||||
ffmpeg_run.stdout.on('data', (stdout) => {
|
||||
if (rec.acceptWaveform(stdout))
|
||||
console.log(rec.result());
|
||||
else
|
||||
console.log(rec.partialResult());
|
||||
console.log(rec.finalResult());
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
var vosk = require('..')
|
||||
|
||||
const fs = require("fs");
|
||||
var mic = require("mic");
|
||||
|
||||
MODEL_PATH = "model"
|
||||
SAMPLE_RATE = 16000
|
||||
|
||||
if (!fs.existsSync(MODEL_PATH)) {
|
||||
console.log("Please download the model from https://alphacephei.com/vosk/models and unpack as " + MODEL_PATH + " in the current folder.")
|
||||
process.exit()
|
||||
}
|
||||
|
||||
vosk.setLogLevel(0);
|
||||
const model = new vosk.Model(MODEL_PATH);
|
||||
const rec = new vosk.Recognizer({model: model, sampleRate: SAMPLE_RATE});
|
||||
|
||||
var micInstance = mic({
|
||||
rate: String(SAMPLE_RATE),
|
||||
channels: '1',
|
||||
debug: false
|
||||
});
|
||||
|
||||
var micInputStream = micInstance.getAudioStream();
|
||||
micInstance.start();
|
||||
|
||||
micInputStream.on('data', data => {
|
||||
if (rec.acceptWaveform(data))
|
||||
console.log(rec.result());
|
||||
else
|
||||
console.log(rec.partialResult());
|
||||
});
|
||||
|
||||
process.on('SIGINT', function() {
|
||||
console.log(rec.finalResult());
|
||||
console.log("\nDone");
|
||||
rec.free();
|
||||
model.free();
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
var vosk = require('..')
|
||||
|
||||
const fs = require("fs");
|
||||
const { Readable } = require("stream");
|
||||
const wav = require("wav");
|
||||
|
||||
MODEL_PATH = "model"
|
||||
FILE_NAME = "test.wav"
|
||||
|
||||
if (!fs.existsSync(MODEL_PATH)) {
|
||||
console.log("Please download the model from https://alphacephei.com/vosk/models and unpack as " + MODEL_PATH + " in the current folder.")
|
||||
process.exit()
|
||||
}
|
||||
|
||||
if (process.argv.length > 2)
|
||||
FILE_NAME = process.argv[2]
|
||||
|
||||
vosk.setLogLevel(0);
|
||||
const model = new vosk.Model(MODEL_PATH);
|
||||
|
||||
const wfReader = new wav.Reader();
|
||||
const wfReadable = new Readable().wrap(wfReader);
|
||||
|
||||
wfReader.on('format', async ({ audioFormat, sampleRate, channels }) => {
|
||||
if (audioFormat != 1 || channels != 1) {
|
||||
console.error("Audio file must be WAV format mono PCM.");
|
||||
process.exit(1);
|
||||
}
|
||||
const rec = new vosk.Recognizer({model: model, sampleRate: sampleRate});
|
||||
rec.setMaxAlternatives(10);
|
||||
rec.setWords(true);
|
||||
for await (const data of wfReadable) {
|
||||
const end_of_speech = rec.acceptWaveform(data);
|
||||
if (end_of_speech) {
|
||||
console.log(JSON.stringify(rec.result(), null, 4));
|
||||
}
|
||||
}
|
||||
console.log(JSON.stringify(rec.finalResult(rec), null, 4));
|
||||
rec.free();
|
||||
});
|
||||
|
||||
fs.createReadStream(FILE_NAME, {'highWaterMark': 4096}).pipe(wfReader).on('finish',
|
||||
function (err) {
|
||||
model.free();
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
var vosk = require('..')
|
||||
|
||||
const async = require("async");
|
||||
const fs = require("fs");
|
||||
const { Readable } = require("stream");
|
||||
const wav = require("wav");
|
||||
|
||||
MODEL_PATH = "model"
|
||||
|
||||
if (!fs.existsSync(MODEL_PATH)) {
|
||||
console.log("Please download the model from https://alphacephei.com/vosk/models and unpack as " + MODEL_PATH + " in the current folder.")
|
||||
process.exit()
|
||||
}
|
||||
|
||||
// Process file 4 times in parallel with a single model
|
||||
files = Array(10).fill("test.wav")
|
||||
const model = new vosk.Model(MODEL_PATH)
|
||||
|
||||
async.filter(files, function(filePath, callback) {
|
||||
const wfReader = new wav.Reader();
|
||||
const wfReadable = new Readable().wrap(wfReader);
|
||||
|
||||
wfReader.on('format', async ({ audioFormat, sampleRate, channels }) => {
|
||||
const rec = new vosk.Recognizer({model: model, sampleRate: sampleRate});
|
||||
if (audioFormat != 1 || channels != 1) {
|
||||
console.error("Audio file must be WAV format mono PCM.");
|
||||
process.exit(1);
|
||||
}
|
||||
for await (const data of wfReadable) {
|
||||
const end_of_speech = await rec.acceptWaveformAsync(data);
|
||||
if (end_of_speech) {
|
||||
console.log(rec.result());
|
||||
}
|
||||
}
|
||||
console.log(rec.finalResult(rec));
|
||||
rec.free();
|
||||
// Signal we are done without errors
|
||||
callback(null, true);
|
||||
});
|
||||
|
||||
fs.createReadStream(filePath, {'highWaterMark': 4096}).pipe(wfReader);
|
||||
|
||||
}, function(err, results) {
|
||||
model.free();
|
||||
console.log("Done!!!!!");
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
const vosk = require('..');
|
||||
|
||||
const fs = require("fs");
|
||||
const { Readable } = require("stream");
|
||||
const wav = require("wav");
|
||||
|
||||
MODEL_PATH = "model"
|
||||
SPEAKER_MODEL_PATH = "model-spk"
|
||||
FILE_NAME = "test.wav"
|
||||
|
||||
if (!fs.existsSync(MODEL_PATH)) {
|
||||
console.log("Please download the model from https://alphacephei.com/vosk/models and unpack as " + MODEL_PATH + " in the current folder.")
|
||||
process.exit()
|
||||
}
|
||||
|
||||
if (!fs.existsSync(SPEAKER_MODEL_PATH)) {
|
||||
console.log("Please download the speaker model from https://alphacephei.com/vosk/models and unpack as " + SPEAKER_MODEL_PATH + " in the current folder.")
|
||||
process.exit()
|
||||
}
|
||||
|
||||
if (process.argv.length > 2)
|
||||
FILE_NAME = process.argv[2]
|
||||
|
||||
const model = new vosk.Model(MODEL_PATH);
|
||||
const speakerModel = new vosk.SpeakerModel(SPEAKER_MODEL_PATH);
|
||||
|
||||
const wfReader = new wav.Reader();
|
||||
const wfReadable = new Readable().wrap(wfReader);
|
||||
|
||||
wfReader.on('format', async ({ audioFormat, sampleRate, channels }) => {
|
||||
if (audioFormat != 1 || channels != 1) {
|
||||
console.error('Audio file must be WAV format mono PCM.');
|
||||
process.exit(1);
|
||||
}
|
||||
// const rec = new vosk.Recognizer({ model: model,
|
||||
// speakerModel: speakerModel,
|
||||
// sampleRate: sampleRate });
|
||||
const rec = new vosk.Recognizer({model: model, sampleRate: sampleRate});
|
||||
rec.setSpkModel(speakerModel);
|
||||
for await (const data of wfReadable) {
|
||||
const end_of_speech = rec.acceptWaveform(data);
|
||||
if (end_of_speech) {
|
||||
console.log(rec.finalResult());
|
||||
}
|
||||
}
|
||||
console.log(rec.finalResult());
|
||||
rec.free();
|
||||
});
|
||||
|
||||
fs.createReadStream(FILE_NAME, { highWaterMark: 4096 }).pipe(wfReader).on('finish', function (err) {
|
||||
model.free();
|
||||
speakerModel.free();
|
||||
});
|
||||
@@ -0,0 +1,84 @@
|
||||
var vosk = require('..')
|
||||
|
||||
const fs = require("fs");
|
||||
const { spawn } = require("child_process");
|
||||
const { stringifySync } = require('subtitle')
|
||||
|
||||
MODEL_PATH = "model"
|
||||
FILE_NAME = "test.wav"
|
||||
SAMPLE_RATE = 16000
|
||||
BUFFER_SIZE = 4000
|
||||
|
||||
if (!fs.existsSync(MODEL_PATH)) {
|
||||
console.log("Please download the model from https://alphacephei.com/vosk/models and unpack as " + MODEL_PATH + " in the current folder.")
|
||||
process.exit()
|
||||
}
|
||||
|
||||
if (process.argv.length > 2)
|
||||
FILE_NAME = process.argv[2]
|
||||
|
||||
vosk.setLogLevel(-1);
|
||||
const model = new vosk.Model(MODEL_PATH);
|
||||
const rec = new vosk.Recognizer({model: model, sampleRate: SAMPLE_RATE});
|
||||
rec.setWords(true);
|
||||
|
||||
const ffmpeg_run = spawn('ffmpeg', ['-loglevel', 'quiet', '-i', FILE_NAME,
|
||||
'-ar', String(SAMPLE_RATE) , '-ac', '1',
|
||||
'-f', 's16le', '-bufsize', String(BUFFER_SIZE), '-']);
|
||||
|
||||
WORDS_PER_LINE = 7
|
||||
const subs = []
|
||||
const results = []
|
||||
ffmpeg_run.stdout.on('data', (stdout) => {
|
||||
if (rec.acceptWaveform(stdout))
|
||||
results.push(rec.result());
|
||||
results.push(rec.finalResult());
|
||||
});
|
||||
|
||||
ffmpeg_run.on('exit', code => {
|
||||
rec.free();
|
||||
model.free();
|
||||
results.forEach(element =>{
|
||||
if (!element.hasOwnProperty('result'))
|
||||
return;
|
||||
const words = element.result;
|
||||
if (words.length == 1) {
|
||||
subs.push({
|
||||
type: 'cue',
|
||||
data: {
|
||||
start: words[0].start,
|
||||
end: words[0].end,
|
||||
text: words[0].word
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
var start_index = 0;
|
||||
var text = words[0].word + " ";
|
||||
for (let i = 1; i < words.length; i++) {
|
||||
text += words[i].word + " ";
|
||||
if (i % WORDS_PER_LINE == 0) {
|
||||
subs.push({
|
||||
type: 'cue',
|
||||
data: {
|
||||
start: words[start_index].start,
|
||||
end: words[i].end,
|
||||
text: text.slice(0, text.length-1)
|
||||
}
|
||||
});
|
||||
start_index = i;
|
||||
text = "";
|
||||
}
|
||||
}
|
||||
if (start_index != words.length - 1)
|
||||
subs.push({
|
||||
type: 'cue',
|
||||
data: {
|
||||
start: words[start_index].start,
|
||||
end: words[words.length-1].end,
|
||||
text: text
|
||||
}
|
||||
});
|
||||
});
|
||||
console.log(stringifySync(subs, {format: "SRT"}));
|
||||
});
|
||||
@@ -1,90 +0,0 @@
|
||||
const wav = require('wav')
|
||||
const fs = require('fs')
|
||||
|
||||
const {Readable} = require('stream')
|
||||
const {Model, KaldiRecognizer, SpkModel} = require('..')
|
||||
|
||||
try {
|
||||
fs.accessSync('model', fs.constants.R_OK)
|
||||
} catch (err) {
|
||||
console.error("Please download the model from https://github.com/alphacep/kaldi-android-demo/releases and unpack as 'model' in the current folder.")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
try {
|
||||
fs.accessSync('model-spk', fs.constants.R_OK)
|
||||
} catch (err) {
|
||||
console.error("Please download the speaker model from https://github.com/alphacep/kaldi-android-demo/releases and unpack as 'model-spk' in the current folder.")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const wfStream = fs.createReadStream('test.wav', { highWaterMark: 4096 })
|
||||
const wfReader = new wav.Reader()
|
||||
|
||||
const model = new Model('model')
|
||||
const spkModel = new SpkModel('model-spk')
|
||||
|
||||
const spk_sig = [-0.56648, 0.030579, 1.730239, 0.239899, -1.194183,
|
||||
0.251954, 0.540388, -0.971872, -0.020963, 0.085036, 0.563973, -0.019682,
|
||||
-0.597381, 1.094719, -0.566738, 0.29819, 0.171165, 0.370341, -0.033539,
|
||||
-0.09757, 1.228286, 0.485949, 0.427826, 0.147762, -0.015112, 0.599513,
|
||||
-2.040655, -0.490882, 0.440161, -0.072991, 0.835955, -0.496124, 0.952978,
|
||||
0.85356, -1.096116, 0.107764, -0.385486, 1.410305, 0.609147, -0.457014,
|
||||
-1.542864, 0.343669, 0.171913, -0.627281, -1.281781, -1.134276,
|
||||
-0.639895, 1.190183, -0.700537, 1.063457, 0.206946, 0.342198, -1.165625,
|
||||
1.475955, -0.089007, -2.555155, 0.551438, -0.212736, 1.025625, -1.631965,
|
||||
-0.716256, -1.295995, 1.554956, -1.866009, -1.010782, -1.43231, 0.109027,
|
||||
2.123925, 1.703283, -0.784997, 2.730568, 0.755113, 0.0617, 0.128955,
|
||||
-0.054047, 1.359119, -0.611666, -1.105754, -0.631353, 0.052109, 0.729386,
|
||||
-0.769876, 1.250235, -1.463298, 0.648176, -0.73239, -0.385239,
|
||||
-1.661856, 0.602106, -0.45567, -1.438431, -0.836673, 0.033557, 0.373597,
|
||||
-1.343341, -0.181095, 0.237287, -0.522005, -1.722836, 0.932333,
|
||||
-0.092861, -0.219254, 0.476182, 1.033803, -1.633563, -0.874341, 1.039064,
|
||||
-1.758573, -0.838422, -0.324336, -0.924634, 1.962594, 2.152814, 1.2521,
|
||||
-0.46172, -1.50271, 1.685691, 0.403097, -0.819042, 0.866403, -0.591716,
|
||||
-0.578645, -0.553839, 0.381861, -1.051647, -1.477578, 0.524005, 0.925245]
|
||||
|
||||
function dotp(x, y) {
|
||||
function dotp_sum(a, b) {
|
||||
return a + b
|
||||
}
|
||||
function dotp_times(a, i) {
|
||||
return x[i] * y[i]
|
||||
}
|
||||
return x.map(dotp_times).reduce(dotp_sum, 0)
|
||||
}
|
||||
|
||||
function cosineSimilarity(A, B) {
|
||||
var similarity =
|
||||
dotp(A, B) / (Math.sqrt(dotp(A, A)) * Math.sqrt(dotp(B, B)))
|
||||
return similarity
|
||||
}
|
||||
|
||||
function cosine_dist(x, y) {
|
||||
return 1 - cosineSimilarity(x, y)
|
||||
}
|
||||
|
||||
wfReader.on('format', async ({ audioFormat, sampleRate, channels }) => {
|
||||
if (audioFormat != 1 || channels != 1) {
|
||||
console.error('Audio file must be WAV format mono PCM.')
|
||||
process.exit(1)
|
||||
}
|
||||
const rec = new KaldiRecognizer(model, spkModel, sampleRate)
|
||||
for await (const data of new Readable().wrap(wfReader)) {
|
||||
const endOfSpeech = await rec.AcceptWaveform(data)
|
||||
if (endOfSpeech) {
|
||||
res = await JSON.parse(rec.Result());
|
||||
console.log(res)
|
||||
console.log('X-vector:', JSON.stringify(res['spk']))
|
||||
console.log('Speaker distance:', cosine_dist(spk_sig, res['spk']))
|
||||
} else {
|
||||
console.log(await rec.PartialResult())
|
||||
}
|
||||
}
|
||||
res = await JSON.parse(rec.FinalResult());
|
||||
console.log(res)
|
||||
console.log('X-vector:', JSON.stringify(res['spk']))
|
||||
console.log('Speaker distance:', cosine_dist(spk_sig, res['spk']))
|
||||
})
|
||||
|
||||
wfStream.pipe(wfReader)
|
||||
@@ -1,38 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require("fs");
|
||||
const { Readable } = require("stream");
|
||||
const wav = require("wav");
|
||||
|
||||
const { Model, KaldiRecognizer } = require("..");
|
||||
|
||||
try {
|
||||
fs.accessSync("model", fs.constants.R_OK);
|
||||
} catch(err) {
|
||||
console.error("Please download the model from https://github.com/alphacep/kaldi-android-demo/releases and unpack as 'model' in the current folder.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const wfStream = fs.createReadStream("test.wav", {'highWaterMark': 4096});
|
||||
const wfReader = new wav.Reader();
|
||||
|
||||
const model = new Model("model");
|
||||
|
||||
wfReader.on('format', async ({ audioFormat, sampleRate, channels }) => {
|
||||
if (audioFormat != 1 || channels != 1) {
|
||||
console.error("Audio file must be WAV format mono PCM.");
|
||||
process.exit(1);
|
||||
}
|
||||
const rec = new KaldiRecognizer(model, sampleRate);
|
||||
for await (const data of new Readable().wrap(wfReader)) {
|
||||
const result = await rec.AcceptWaveform(data);
|
||||
if (result != 0) {
|
||||
console.log(await rec.Result());
|
||||
} else {
|
||||
console.log(await rec.PartialResult());
|
||||
}
|
||||
}
|
||||
console.log(await rec.FinalResult());
|
||||
});
|
||||
|
||||
wfStream.pipe(wfReader);
|
||||
+426
-2
@@ -1,3 +1,427 @@
|
||||
const voskNativeModule = require('./build/Release/vosk.node');
|
||||
// @ts-check
|
||||
'use strict'
|
||||
|
||||
module.exports = voskNativeModule;
|
||||
/**
|
||||
* @module vosk
|
||||
*/
|
||||
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
/** @type {import('ffi-napi')} */
|
||||
const ffi = require('ffi-napi');
|
||||
/** @type {import('ref-napi')} */
|
||||
const ref = require('ref-napi');
|
||||
|
||||
const vosk_model = ref.types.void;
|
||||
const vosk_model_ptr = ref.refType(vosk_model);
|
||||
const vosk_spk_model = ref.types.void;
|
||||
const vosk_spk_model_ptr = ref.refType(vosk_spk_model);
|
||||
const vosk_recognizer = ref.types.void;
|
||||
const vosk_recognizer_ptr = ref.refType(vosk_recognizer);
|
||||
|
||||
/**
|
||||
* @typedef {Object} WordResult
|
||||
* @property {number} conf The confidence rate in the detection. 0 For unlikely, and 1 for totally accurate.
|
||||
* @property {number} start The start of the timeframe when the word is pronounced in seconds
|
||||
* @property {number} end The end of the timeframe when the word is pronounced in seconds
|
||||
* @property {string} word The word detected
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} RecognitionResults
|
||||
* @property {WordResult[]} result Details about the words that have been detected
|
||||
* @property {string} text The complete sentence that have been detected
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} SpeakerResults
|
||||
* @property {number[]} spk A floating vector representing speaker identity. It is usually about 128 numbers which uniquely represent speaker voice.
|
||||
* @property {number} spk_frames The number of frames used to extract speaker vector. The more frames you have the more reliable is speaker vector.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} BaseRecognizerParam
|
||||
* @property {Model} model The language model to be used
|
||||
* @property {number} sampleRate The sample rate. Most models are trained at 16kHz
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} GrammarRecognizerParam
|
||||
* @property {string[]} grammar The list of sentences to be recognized.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} SpeakerRecognizerParam
|
||||
* @property {SpeakerModel} speakerModel The SpeakerModel that will enable speaker identification
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {SpeakerRecognizerParam | GrammarRecognizerParam} T
|
||||
* @typedef {T extends SpeakerRecognizerParam ? SpeakerResults & RecognitionResults : RecognitionResults} Result
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} PartialResults
|
||||
* @property {string} partial The partial sentence that have been detected until now
|
||||
*/
|
||||
|
||||
/** @typedef {string[]} Grammar The list of strings to be recognized */
|
||||
|
||||
let soname;
|
||||
if (os.platform() == 'win32') {
|
||||
// Update path to load dependent dlls
|
||||
let currentPath = process.env.Path;
|
||||
let dllDirectory = path.resolve(path.join(__dirname, "lib", "win-x86_64"));
|
||||
process.env.Path = currentPath + path.delimiter + dllDirectory;
|
||||
|
||||
soname = path.join(__dirname, "lib", "win-x86_64", "libvosk.dll")
|
||||
} else if (os.platform() == 'darwin') {
|
||||
soname = path.join(__dirname, "lib", "osx-x86_64", "libvosk.dylib")
|
||||
} else {
|
||||
soname = path.join(__dirname, "lib", "linux-x86_64", "libvosk.so")
|
||||
}
|
||||
|
||||
const libvosk = ffi.Library(soname, {
|
||||
'vosk_set_log_level': ['void', ['int']],
|
||||
'vosk_model_new': [vosk_model_ptr, ['string']],
|
||||
'vosk_model_free': ['void', [vosk_model_ptr]],
|
||||
'vosk_spk_model_new': [vosk_spk_model_ptr, ['string']],
|
||||
'vosk_spk_model_free': ['void', [vosk_spk_model_ptr]],
|
||||
'vosk_recognizer_new': [vosk_recognizer_ptr, [vosk_model_ptr, 'float']],
|
||||
'vosk_recognizer_new_spk': [vosk_recognizer_ptr, [vosk_model_ptr, 'float', vosk_spk_model_ptr]],
|
||||
'vosk_recognizer_new_grm': [vosk_recognizer_ptr, [vosk_model_ptr, 'float', 'string']],
|
||||
'vosk_recognizer_free': ['void', [vosk_recognizer_ptr]],
|
||||
'vosk_recognizer_set_max_alternatives': ['void', [vosk_recognizer_ptr, 'int']],
|
||||
'vosk_recognizer_set_words': ['void', [vosk_recognizer_ptr, 'bool']],
|
||||
'vosk_recognizer_set_spk_model': ['void', [vosk_recognizer_ptr, vosk_spk_model_ptr]],
|
||||
'vosk_recognizer_accept_waveform': ['bool', [vosk_recognizer_ptr, 'pointer', 'int']],
|
||||
'vosk_recognizer_result': ['string', [vosk_recognizer_ptr]],
|
||||
'vosk_recognizer_final_result': ['string', [vosk_recognizer_ptr]],
|
||||
'vosk_recognizer_partial_result': ['string', [vosk_recognizer_ptr]],
|
||||
'vosk_recognizer_reset': ['void', [vosk_recognizer_ptr]],
|
||||
});
|
||||
|
||||
/**
|
||||
* Set log level for Kaldi messages
|
||||
* @param {number} level The higher, the more verbose. 0 for infos and errors. Less than 0 for silence.
|
||||
*/
|
||||
function setLogLevel(level) {
|
||||
libvosk.vosk_set_log_level(level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a Model from a model file.
|
||||
* @see models [models](https://alphacephei.com/vosk/models)
|
||||
*/
|
||||
class Model {
|
||||
/**
|
||||
* Build a Model to be used with the voice recognition. Each language should have it's own Model
|
||||
* for the speech recognition to work.
|
||||
* @param {string} modelPath The abstract pathname to the model
|
||||
* @see models [models](https://alphacephei.com/vosk/models)
|
||||
*/
|
||||
constructor(modelPath) {
|
||||
/**
|
||||
* Store the handle.
|
||||
* For internal use only
|
||||
* @type {unknown}
|
||||
*/
|
||||
this.handle = libvosk.vosk_model_new(modelPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the model memory
|
||||
*
|
||||
* The model object is reference-counted so if some recognizer
|
||||
* depends on this model, model might still stay alive. When
|
||||
* last recognizer is released, model will be released too.
|
||||
*/
|
||||
free() {
|
||||
libvosk.vosk_model_free(this.handle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a Speaker Model from a speaker model file.
|
||||
* The Speaker Model enables speaker identification.
|
||||
* @see models [models](https://alphacephei.com/vosk/models)
|
||||
*/
|
||||
class SpeakerModel {
|
||||
/**
|
||||
* Loads speaker model data from the file and returns the model object
|
||||
*
|
||||
* @param {string} modelPath the path of the model on the filesystem
|
||||
* @see models [models](https://alphacephei.com/vosk/models)
|
||||
*/
|
||||
constructor(modelPath) {
|
||||
/**
|
||||
* Store the handle.
|
||||
* For internal use only
|
||||
* @type {unknown}
|
||||
*/
|
||||
this.handle = libvosk.vosk_spk_model_new(modelPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the model memory
|
||||
*
|
||||
* The model object is reference-counted so if some recognizer
|
||||
* depends on this model, model might still stay alive. When
|
||||
* last recognizer is released, model will be released too.
|
||||
*/
|
||||
free() {
|
||||
libvosk.vosk_spk_model_free(this.handle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to narrow down type while using `hasOwnProperty`.
|
||||
* @see hasOwnProperty [typescript issue](https://fettblog.eu/typescript-hasownproperty/)
|
||||
* @template {Object} Obj
|
||||
* @template {PropertyKey} Key
|
||||
* @param {Obj} obj
|
||||
* @param {Key} prop
|
||||
* @returns {obj is Obj & Record<Key, unknown>}
|
||||
*/
|
||||
function hasOwnProperty(obj, prop) {
|
||||
return obj.hasOwnProperty(prop)
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @template U
|
||||
* @typedef {{ [P in Exclude<keyof T, keyof U>]?: never }} Without
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @template U
|
||||
* @typedef {(T | U) extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U} XOR
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a Recognizer that will be able to transform audio streams into text using a Model.
|
||||
* @template {XOR<SpeakerRecognizerParam, Partial<GrammarRecognizerParam>>} T extra parameter
|
||||
* @see Model
|
||||
*/
|
||||
class Recognizer {
|
||||
/**
|
||||
* Create a Recognizer that will handle speech to text recognition.
|
||||
* @constructor
|
||||
* @param {T & BaseRecognizerParam} param The Recognizer parameters
|
||||
*
|
||||
* Sometimes when you want to improve recognition accuracy and when you don't need
|
||||
* to recognize large vocabulary you can specify a list of phrases to recognize. This
|
||||
* will improve recognizer speed and accuracy but might return [unk] if user said
|
||||
* something different.
|
||||
*
|
||||
* Only recognizers with lookahead models support this type of quick configuration.
|
||||
* Precompiled HCLG graph models are not supported.
|
||||
*/
|
||||
constructor(param) {
|
||||
const { model, sampleRate } = param
|
||||
// Prevent the user to receive unpredictable results
|
||||
if (hasOwnProperty(param, 'speakerModel') && hasOwnProperty(param, 'grammar')) {
|
||||
throw new Error('grammar and speakerModel cannot be used together for now.')
|
||||
}
|
||||
/**
|
||||
* Store the handle.
|
||||
* For internal use only
|
||||
* @type {unknown}
|
||||
*/
|
||||
this.handle = hasOwnProperty(param, 'speakerModel')
|
||||
? libvosk.vosk_recognizer_new_spk(model.handle, sampleRate, param.speakerModel.handle)
|
||||
: hasOwnProperty(param, 'grammar')
|
||||
? libvosk.vosk_recognizer_new_grm(model.handle, sampleRate, JSON.stringify(param.grammar))
|
||||
: libvosk.vosk_recognizer_new(model.handle, sampleRate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the model memory
|
||||
*
|
||||
* The model object is reference-counted so if some recognizer
|
||||
* depends on this model, model might still stay alive. When
|
||||
* last recognizer is released, model will be released too.
|
||||
*/
|
||||
free() {
|
||||
libvosk.vosk_recognizer_free(this.handle);
|
||||
}
|
||||
|
||||
/** Configures recognizer to output n-best results
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* "alternatives": [
|
||||
* { "text": "one two three four five", "confidence": 0.97 },
|
||||
* { "text": "one two three for five", "confidence": 0.03 },
|
||||
* ]
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param max_alternatives - maximum alternatives to return from recognition results
|
||||
*/
|
||||
setMaxAlternatives(max_alternatives) {
|
||||
libvosk.vosk_recognizer_set_max_alternatives(this.handle, max_alternatives);
|
||||
}
|
||||
|
||||
/** Configures recognizer to output words with times
|
||||
*
|
||||
* <pre>
|
||||
* "result" : [{
|
||||
* "conf" : 1.000000,
|
||||
* "end" : 1.110000,
|
||||
* "start" : 0.870000,
|
||||
* "word" : "what"
|
||||
* }, {
|
||||
* "conf" : 1.000000,
|
||||
* "end" : 1.530000,
|
||||
* "start" : 1.110000,
|
||||
* "word" : "zero"
|
||||
* }, {
|
||||
* "conf" : 1.000000,
|
||||
* "end" : 1.950000,
|
||||
* "start" : 1.530000,
|
||||
* "word" : "zero"
|
||||
* }, {
|
||||
* "conf" : 1.000000,
|
||||
* "end" : 2.340000,
|
||||
* "start" : 1.950000,
|
||||
* "word" : "zero"
|
||||
* }, {
|
||||
* "conf" : 1.000000,
|
||||
* "end" : 2.610000,
|
||||
* "start" : 2.340000,
|
||||
* "word" : "one"
|
||||
* }],
|
||||
* </pre>
|
||||
*
|
||||
* @param words - boolean value
|
||||
*/
|
||||
setWords(words) {
|
||||
libvosk.vosk_recognizer_set_words(this.handle, words);
|
||||
}
|
||||
|
||||
/** Adds speaker recognition model to already created recognizer. Helps to initialize
|
||||
* speaker recognition for grammar-based recognizer.
|
||||
*
|
||||
* @param spk_model Speaker recognition model
|
||||
*/
|
||||
setSpkModel(spk_model) {
|
||||
libvosk.vosk_recognizer_set_spk_model(this.handle, spk_model.handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept voice data
|
||||
*
|
||||
* accept and process new chunk of voice data
|
||||
*
|
||||
* @param {Buffer} data audio data in PCM 16-bit mono format
|
||||
* @returns true if silence is occured and you can retrieve a new utterance with result method
|
||||
*/
|
||||
acceptWaveform(data) {
|
||||
return libvosk.vosk_recognizer_accept_waveform(this.handle, data, data.length);
|
||||
};
|
||||
|
||||
/**
|
||||
* Accept voice data
|
||||
*
|
||||
* accept and process new chunk of voice data
|
||||
*
|
||||
* @param {Buffer} data audio data in PCM 16-bit mono format
|
||||
* @returns true if silence is occured and you can retrieve a new utterance with result method
|
||||
*/
|
||||
acceptWaveformAsync(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
libvosk.vosk_recognizer_accept_waveform.async(this.handle, data, data.length, function(err, result) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/** Returns speech recognition result in a string
|
||||
*
|
||||
* @returns the result in JSON format which contains decoded line, decoded
|
||||
* words, times in seconds and confidences. You can parse this result
|
||||
* with any json parser
|
||||
* <pre>
|
||||
* {
|
||||
* "result" : [{
|
||||
* "conf" : 1.000000,
|
||||
* "end" : 1.110000,
|
||||
* "start" : 0.870000,
|
||||
* "word" : "what"
|
||||
* }, {
|
||||
* "conf" : 1.000000,
|
||||
* "end" : 1.530000,
|
||||
* "start" : 1.110000,
|
||||
* "word" : "zero"
|
||||
* }, {
|
||||
* "conf" : 1.000000,
|
||||
* "end" : 1.950000,
|
||||
* "start" : 1.530000,
|
||||
* "word" : "zero"
|
||||
* }, {
|
||||
* "conf" : 1.000000,
|
||||
* "end" : 2.340000,
|
||||
* "start" : 1.950000,
|
||||
* "word" : "zero"
|
||||
* }, {
|
||||
* "conf" : 1.000000,
|
||||
* "end" : 2.610000,
|
||||
* "start" : 2.340000,
|
||||
* "word" : "one"
|
||||
* }],
|
||||
* "text" : "what zero zero zero one"
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
resultString() {
|
||||
return libvosk.vosk_recognizer_result(this.handle);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns speech recognition results
|
||||
* @returns {Result<T>} The results
|
||||
*/
|
||||
result() {
|
||||
return JSON.parse(libvosk.vosk_recognizer_result(this.handle));
|
||||
};
|
||||
|
||||
/**
|
||||
* speech recognition text which is not yet finalized.
|
||||
* result may change as recognizer process more data.
|
||||
*
|
||||
* @returns {PartialResults} The partial results
|
||||
*/
|
||||
partialResult() {
|
||||
return JSON.parse(libvosk.vosk_recognizer_partial_result(this.handle));
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns speech recognition result. Same as result, but doesn't wait for silence
|
||||
* You usually call it in the end of the stream to get final bits of audio. It
|
||||
* flushes the feature pipeline, so all remaining audio chunks got processed.
|
||||
*
|
||||
* @returns {Result<T>} speech result.
|
||||
*/
|
||||
finalResult() {
|
||||
return JSON.parse(libvosk.vosk_recognizer_final_result(this.handle));
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Resets current results so the recognition can continue from scratch
|
||||
*/
|
||||
reset() {
|
||||
libvosk.vosk_recognizer_reset(this.handle);
|
||||
}
|
||||
}
|
||||
|
||||
exports.setLogLevel = setLogLevel
|
||||
exports.Model = Model
|
||||
exports.SpeakerModel = SpeakerModel
|
||||
exports.Recognizer = Recognizer
|
||||
|
||||
+6
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vosk",
|
||||
"version": "0.3.17",
|
||||
"version": "0.3.30",
|
||||
"description": "Node binding for continuous offline voice recoginition with Vosk library.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -15,10 +15,13 @@
|
||||
"author": "Alpha Cephei Inc.",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">= 10.x.x"
|
||||
"node": ">= 12.x.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-gyp": "^5.1.1",
|
||||
"async": "^3.2.0",
|
||||
"ffi-napi": "^4.0.3",
|
||||
"mic": "^2.1.2",
|
||||
"ref-napi": ">=2.0.0",
|
||||
"wav": "^1.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from vosk import Model, KaldiRecognizer, SetLogLevel
|
||||
import sys
|
||||
import os
|
||||
import wave
|
||||
import json
|
||||
|
||||
SetLogLevel(0)
|
||||
|
||||
if not os.path.exists("model"):
|
||||
print ("Please download the model from https://alphacephei.com/vosk/models and unpack as 'model' in the current folder.")
|
||||
exit (1)
|
||||
|
||||
wf = wave.open(sys.argv[1], "rb")
|
||||
if wf.getnchannels() != 1 or wf.getsampwidth() != 2 or wf.getcomptype() != "NONE":
|
||||
print ("Audio file must be WAV format mono PCM.")
|
||||
exit (1)
|
||||
|
||||
model = Model("model")
|
||||
rec = KaldiRecognizer(model, wf.getframerate())
|
||||
rec.SetMaxAlternatives(10)
|
||||
rec.SetWords(True)
|
||||
|
||||
while True:
|
||||
data = wf.readframes(4000)
|
||||
if len(data) == 0:
|
||||
break
|
||||
if rec.AcceptWaveform(data):
|
||||
print(json.loads(rec.Result()))
|
||||
else:
|
||||
print(json.loads(rec.PartialResult()))
|
||||
|
||||
print(json.loads(rec.FinalResult()))
|
||||
@@ -20,7 +20,7 @@ def callback(indata, frames, time, status):
|
||||
"""This is called (from a separate thread) for each audio block."""
|
||||
if status:
|
||||
print(status, file=sys.stderr)
|
||||
q.put(indata)
|
||||
q.put(bytes(indata))
|
||||
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument(
|
||||
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from vosk import Model, KaldiRecognizer, SetLogLevel
|
||||
import sys
|
||||
import os
|
||||
import wave
|
||||
import json
|
||||
|
||||
SetLogLevel(0)
|
||||
|
||||
if not os.path.exists("model"):
|
||||
print ("Please download the model from https://alphacephei.com/vosk/models and unpack as 'model' in the current folder.")
|
||||
exit (1)
|
||||
|
||||
wf = wave.open(sys.argv[1], "rb")
|
||||
if wf.getnchannels() != 1 or wf.getsampwidth() != 2 or wf.getcomptype() != "NONE":
|
||||
print ("Audio file must be WAV format mono PCM.")
|
||||
exit (1)
|
||||
|
||||
model = Model("model")
|
||||
rec = KaldiRecognizer(model, wf.getframerate())
|
||||
|
||||
while True:
|
||||
data = wf.readframes(4000)
|
||||
if len(data) == 0:
|
||||
break
|
||||
if rec.AcceptWaveform(data):
|
||||
print(rec.Result())
|
||||
break
|
||||
|
||||
else:
|
||||
jres = json.loads(rec.PartialResult())
|
||||
print(jres)
|
||||
|
||||
if jres['partial'] == "one zero zero zero":
|
||||
print("We can reset recognizer here and start over")
|
||||
rec.Reset();
|
||||
@@ -18,6 +18,7 @@ if wf.getnchannels() != 1 or wf.getsampwidth() != 2 or wf.getcomptype() != "NONE
|
||||
|
||||
model = Model("model")
|
||||
rec = KaldiRecognizer(model, wf.getframerate())
|
||||
rec.SetWords(True)
|
||||
|
||||
while True:
|
||||
data = wf.readframes(4000)
|
||||
|
||||
@@ -26,7 +26,9 @@ if wf.getnchannels() != 1 or wf.getsampwidth() != 2 or wf.getcomptype() != "NONE
|
||||
# Large vocabulary free form recognition
|
||||
model = Model(model_path)
|
||||
spk_model = SpkModel(spk_model_path)
|
||||
rec = KaldiRecognizer(model, spk_model, wf.getframerate())
|
||||
#rec = KaldiRecognizer(model, wf.getframerate(), spk_model)
|
||||
rec = KaldiRecognizer(model, wf.getframerate())
|
||||
rec.SetSpkModel(spk_model)
|
||||
|
||||
# We compare speakers with cosine distance. We can keep one or several fingerprints for the speaker in a database
|
||||
# to distingusih among users.
|
||||
|
||||
@@ -18,6 +18,7 @@ if not os.path.exists("model"):
|
||||
sample_rate=16000
|
||||
model = Model("model")
|
||||
rec = KaldiRecognizer(model, sample_rate)
|
||||
rec.SetWords(True)
|
||||
|
||||
process = subprocess.Popen(['ffmpeg', '-loglevel', 'quiet', '-i',
|
||||
sys.argv[1],
|
||||
|
||||
Executable
+72
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from vosk import Model, KaldiRecognizer, SetLogLevel
|
||||
from webvtt import WebVTT, Caption
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import json
|
||||
import textwrap
|
||||
|
||||
SetLogLevel(-1)
|
||||
|
||||
if not os.path.exists('model'):
|
||||
print('Please download the model from https://alphacephei.com/vosk/models'
|
||||
' and unpack as `model` in the current folder.')
|
||||
exit(1)
|
||||
|
||||
sample_rate = 16000
|
||||
model = Model('model')
|
||||
rec = KaldiRecognizer(model, sample_rate)
|
||||
rec.SetWords(True)
|
||||
|
||||
WORDS_PER_LINE = 7
|
||||
|
||||
|
||||
def timeString(seconds):
|
||||
minutes = seconds / 60
|
||||
seconds = seconds % 60
|
||||
hours = int(minutes / 60)
|
||||
minutes = int(minutes % 60)
|
||||
return '%i:%02i:%06.3f' % (hours, minutes, seconds)
|
||||
|
||||
|
||||
def transcribe():
|
||||
command = ['ffmpeg', '-nostdin', '-loglevel', 'quiet', '-i', sys.argv[1],
|
||||
'-ar', str(sample_rate), '-ac', '1', '-f', 's16le', '-']
|
||||
process = subprocess.Popen(command, stdout=subprocess.PIPE)
|
||||
|
||||
results = []
|
||||
while True:
|
||||
data = process.stdout.read(4000)
|
||||
if len(data) == 0:
|
||||
break
|
||||
if rec.AcceptWaveform(data):
|
||||
results.append(rec.Result())
|
||||
results.append(rec.FinalResult())
|
||||
|
||||
vtt = WebVTT()
|
||||
for i, res in enumerate(results):
|
||||
words = json.loads(res).get('result')
|
||||
if not words:
|
||||
continue
|
||||
|
||||
start = timeString(words[0]['start'])
|
||||
end = timeString(words[-1]['end'])
|
||||
content = ' '.join([w['word'] for w in words])
|
||||
|
||||
caption = Caption(start, end, textwrap.fill(content))
|
||||
vtt.captions.append(caption)
|
||||
|
||||
# save or return webvtt
|
||||
if len(sys.argv) > 2:
|
||||
vtt.save(sys.argv[2])
|
||||
else:
|
||||
print(vtt.content)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if not (1 < len(sys.argv) < 4):
|
||||
print(f'Usage: {sys.argv[0]} audiofile [output file]')
|
||||
exit(1)
|
||||
transcribe()
|
||||
+2
-2
@@ -44,7 +44,7 @@ with open("README.md", "r") as fh:
|
||||
|
||||
setuptools.setup(
|
||||
name="vosk",
|
||||
version="0.3.18",
|
||||
version="0.3.31",
|
||||
author="Alpha Cephei Inc",
|
||||
author_email="contact@alphacephei.com",
|
||||
description="Offline open source speech recognition API based on Kaldi and Vosk",
|
||||
@@ -52,7 +52,7 @@ setuptools.setup(
|
||||
long_description_content_type="text/markdown",
|
||||
url="https://github.com/alphacep/vosk-api",
|
||||
packages=setuptools.find_packages(),
|
||||
package_data = {'vosk': ['*.so', '*.dll']},
|
||||
package_data = {'vosk': ['*.so', '*.dll', '*.dyld']},
|
||||
include_package_data=True,
|
||||
classifiers=[
|
||||
'Programming Language :: Python :: 3',
|
||||
|
||||
+30
-6
@@ -4,13 +4,17 @@ import sys
|
||||
from .vosk_cffi import ffi as _ffi
|
||||
|
||||
def open_dll():
|
||||
dlldir = os.path.abspath(os.path.dirname(__file__))
|
||||
if sys.platform == 'win32':
|
||||
os.environ["PATH"] += os.pathsep + os.path.dirname(__file__)
|
||||
return _ffi.dlopen("libvosk.dll")
|
||||
# We want to load dependencies too
|
||||
os.environ["PATH"] = dlldir + os.pathsep + os.environ['PATH']
|
||||
if hasattr(os, 'add_dll_directory'):
|
||||
os.add_dll_directory(dlldir)
|
||||
return _ffi.dlopen(os.path.join(dlldir, "libvosk.dll"))
|
||||
elif sys.platform == 'linux':
|
||||
return _ffi.dlopen(os.path.dirname(__file__) + "/libvosk.so")
|
||||
return _ffi.dlopen(os.path.join(dlldir, "libvosk.so"))
|
||||
elif sys.platform == 'darwin':
|
||||
return _ffi.dlopen(os.path.dirname(__file__) + "/libvosk.dyld")
|
||||
return _ffi.dlopen(os.path.join(dlldir, "libvosk.dyld"))
|
||||
else:
|
||||
raise TypeError("Unsupported platform")
|
||||
|
||||
@@ -40,8 +44,8 @@ class KaldiRecognizer(object):
|
||||
def __init__(self, *args):
|
||||
if len(args) == 2:
|
||||
self._handle = _c.vosk_recognizer_new(args[0]._handle, args[1])
|
||||
elif len(args) == 3 and type(args[1]) is SpkModel:
|
||||
self._handle = _c.vosk_recognizer_new_spk(args[0]._handle, args[1]._handle, args[2])
|
||||
elif len(args) == 3 and type(args[2]) is SpkModel:
|
||||
self._handle = _c.vosk_recognizer_new_spk(args[0]._handle, args[1], args[2]._handle)
|
||||
elif len(args) == 3 and type(args[2]) is str:
|
||||
self._handle = _c.vosk_recognizer_new_grm(args[0]._handle, args[1], args[2].encode('utf-8'))
|
||||
else:
|
||||
@@ -50,6 +54,15 @@ class KaldiRecognizer(object):
|
||||
def __del__(self):
|
||||
_c.vosk_recognizer_free(self._handle)
|
||||
|
||||
def SetMaxAlternatives(self, max_alternatives):
|
||||
_c.vosk_recognizer_set_max_alternatives(self._handle, max_alternatives)
|
||||
|
||||
def SetWords(self, enable_words):
|
||||
_c.vosk_recognizer_set_words(self._handle, 1 if enable_words else 0)
|
||||
|
||||
def SetSpkModel(self, spk_model):
|
||||
_c.vosk_recognizer_set_spk_model(self._handle, spk_model._handle)
|
||||
|
||||
def AcceptWaveform(self, data):
|
||||
return _c.vosk_recognizer_accept_waveform(self._handle, data, len(data))
|
||||
|
||||
@@ -62,6 +75,17 @@ class KaldiRecognizer(object):
|
||||
def FinalResult(self):
|
||||
return _ffi.string(_c.vosk_recognizer_final_result(self._handle)).decode('utf-8')
|
||||
|
||||
def Reset(self):
|
||||
return _c.vosk_recognizer_reset(self._handle)
|
||||
|
||||
|
||||
def SetLogLevel(level):
|
||||
return _c.vosk_set_log_level(level)
|
||||
|
||||
|
||||
def GpuInit():
|
||||
_c.vosk_gpu_init()
|
||||
|
||||
|
||||
def GpuThreadInit():
|
||||
_c.vosk_gpu_thread_init()
|
||||
|
||||
+49
-13
@@ -1,16 +1,31 @@
|
||||
# Locations of the dependencies
|
||||
KALDI_ROOT?=$(HOME)/travis/kaldi
|
||||
OPENFST_ROOT?=$(KALDI_ROOT)/tools/openfst
|
||||
OPENBLAS_ROOT?=$(KALDI_ROOT)/tools/OpenBLAS/install
|
||||
EXT?=so
|
||||
MKL_ROOT?=/opt/intel/mkl
|
||||
CUDA_ROOT?=/usr/local/cuda
|
||||
# Math libraries
|
||||
HAVE_OPENBLAS_CLAPACK?=1
|
||||
HAVE_MKL?=0
|
||||
HAVE_ACCELERATE=0
|
||||
HAVE_CUDA?=0
|
||||
# Compiler
|
||||
CXX?=g++
|
||||
VOSK_SOURCES= \
|
||||
kaldi_recognizer.cc \
|
||||
language_model.cc \
|
||||
model.cc \
|
||||
spk_model.cc \
|
||||
vosk_api.cc
|
||||
EXT?=so
|
||||
# Extra
|
||||
EXTRA_CFLAGS?=
|
||||
EXTRA_LDFLAGS?=
|
||||
|
||||
VOSK_SOURCES= \
|
||||
kaldi_recognizer.cc \
|
||||
language_model.cc \
|
||||
model.cc \
|
||||
spk_model.cc \
|
||||
vosk_api.cc
|
||||
|
||||
CFLAGS=-g -O3 -std=c++17 -fPIC -DFST_NO_DYNAMIC_LINKING \
|
||||
-I. -I$(KALDI_ROOT)/src -I$(OPENFST_ROOT)/include $(EXTRA_CFLAGS)
|
||||
|
||||
CFLAGS=-g -O2 -std=c++17 -fPIC -DFST_NO_DYNAMIC_LINKING -I. -I$(KALDI_ROOT)/src -I$(OPENFST_ROOT)/include -I$(OPENBLAS_ROOT)/include
|
||||
LIBS= \
|
||||
$(KALDI_ROOT)/src/online2/kaldi-online2.a \
|
||||
$(KALDI_ROOT)/src/decoder/kaldi-decoder.a \
|
||||
@@ -21,6 +36,7 @@ LIBS= \
|
||||
$(KALDI_ROOT)/src/feat/kaldi-feat.a \
|
||||
$(KALDI_ROOT)/src/lat/kaldi-lat.a \
|
||||
$(KALDI_ROOT)/src/lm/kaldi-lm.a \
|
||||
$(KALDI_ROOT)/src/rnnlm/kaldi-rnnlm.a \
|
||||
$(KALDI_ROOT)/src/hmm/kaldi-hmm.a \
|
||||
$(KALDI_ROOT)/src/transform/kaldi-transform.a \
|
||||
$(KALDI_ROOT)/src/cudamatrix/kaldi-cudamatrix.a \
|
||||
@@ -28,17 +44,37 @@ LIBS= \
|
||||
$(KALDI_ROOT)/src/fstext/kaldi-fstext.a \
|
||||
$(KALDI_ROOT)/src/util/kaldi-util.a \
|
||||
$(KALDI_ROOT)/src/base/kaldi-base.a \
|
||||
$(OPENBLAS_ROOT)/lib/libopenblas.a \
|
||||
$(OPENBLAS_ROOT)/lib/liblapack.a \
|
||||
$(OPENBLAS_ROOT)/lib/libblas.a \
|
||||
$(OPENBLAS_ROOT)/lib/libf2c.a \
|
||||
$(OPENFST_ROOT)/lib/libfst.a \
|
||||
$(OPENFST_ROOT)/lib/libfstngram.a
|
||||
|
||||
|
||||
ifeq ($(HAVE_OPENBLAS_CLAPACK), 1)
|
||||
CFLAGS += -I$(OPENBLAS_ROOT)/include
|
||||
LIBS += \
|
||||
$(OPENBLAS_ROOT)/lib/libopenblas.a \
|
||||
$(OPENBLAS_ROOT)/lib/liblapack.a \
|
||||
$(OPENBLAS_ROOT)/lib/libblas.a \
|
||||
$(OPENBLAS_ROOT)/lib/libf2c.a
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_MKL), 1)
|
||||
CFLAGS += -I$(MKL_ROOT)/include
|
||||
LIBS += -L$(MKL_ROOT)/lib/intel64 -Wl,-rpath=$(MKL_ROOT)/lib/intel64 -lmkl_rt -lmkl_intel_lp64 -lmkl_core -lmkl_sequential
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_ACCELERATE), 1)
|
||||
LIBS += -framework Accelerate
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_CUDA), 1)
|
||||
CFLAGS+=-DHAVE_CUDA=1 -I$(CUDA_ROOT)/include
|
||||
LIBS+=-L$(CUDA_ROOT)/lib64 -lcublas -lcusparse -lcudart -lcurand -lcufft -lcusolver -lnvToolsExt
|
||||
endif
|
||||
|
||||
all: libvosk.$(EXT)
|
||||
|
||||
libvosk.$(EXT): $(VOSK_SOURCES:.cc=.o)
|
||||
$(CXX) --shared -s -o $@ $^ $(LIBS) -lm -latomic
|
||||
$(CXX) --shared -s -o $@ $^ $(LIBS) -lm -latomic $(EXTRA_LDFLAGS)
|
||||
|
||||
%.o: %.cc
|
||||
$(CXX) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
+251
-69
@@ -28,9 +28,6 @@ KaldiRecognizer::KaldiRecognizer(Model *model, float sample_frequency) : model_(
|
||||
feature_pipeline_ = new kaldi::OnlineNnet2FeaturePipeline (model_->feature_info_);
|
||||
silence_weighting_ = new kaldi::OnlineSilenceWeighting(*model_->trans_model_, model_->feature_info_.silence_weighting_config, 3);
|
||||
|
||||
g_fst_ = NULL;
|
||||
decode_fst_ = NULL;
|
||||
|
||||
if (!model_->hclg_fst_) {
|
||||
if (model_->hcl_fst_ && model_->g_fst_) {
|
||||
decode_fst_ = LookaheadComposeFst(*model_->hcl_fst_, *model_->g_fst_, model_->disambig_);
|
||||
@@ -45,8 +42,6 @@ KaldiRecognizer::KaldiRecognizer(Model *model, float sample_frequency) : model_(
|
||||
model_->hclg_fst_ ? *model_->hclg_fst_ : *decode_fst_,
|
||||
feature_pipeline_);
|
||||
|
||||
spk_feature_ = NULL;
|
||||
|
||||
InitState();
|
||||
InitRescoring();
|
||||
}
|
||||
@@ -99,7 +94,6 @@ KaldiRecognizer::KaldiRecognizer(Model *model, float sample_frequency, char cons
|
||||
decode_fst_ = LookaheadComposeFst(*model_->hcl_fst_, *g_fst_, model_->disambig_);
|
||||
}
|
||||
} else {
|
||||
decode_fst_ = NULL;
|
||||
KALDI_WARN << "Runtime graphs are not supported by this model";
|
||||
}
|
||||
|
||||
@@ -109,13 +103,11 @@ KaldiRecognizer::KaldiRecognizer(Model *model, float sample_frequency, char cons
|
||||
model_->hclg_fst_ ? *model_->hclg_fst_ : *decode_fst_,
|
||||
feature_pipeline_);
|
||||
|
||||
spk_feature_ = NULL;
|
||||
|
||||
InitState();
|
||||
InitRescoring();
|
||||
}
|
||||
|
||||
KaldiRecognizer::KaldiRecognizer(Model *model, SpkModel *spk_model, float sample_frequency) : model_(model), spk_model_(spk_model), sample_frequency_(sample_frequency) {
|
||||
KaldiRecognizer::KaldiRecognizer(Model *model, float sample_frequency, SpkModel *spk_model) : model_(model), spk_model_(spk_model), sample_frequency_(sample_frequency) {
|
||||
|
||||
model_->Ref();
|
||||
spk_model->Ref();
|
||||
@@ -123,9 +115,6 @@ KaldiRecognizer::KaldiRecognizer(Model *model, SpkModel *spk_model, float sample
|
||||
feature_pipeline_ = new kaldi::OnlineNnet2FeaturePipeline (model_->feature_info_);
|
||||
silence_weighting_ = new kaldi::OnlineSilenceWeighting(*model_->trans_model_, model_->feature_info_.silence_weighting_config, 3);
|
||||
|
||||
decode_fst_ = NULL;
|
||||
g_fst_ = NULL;
|
||||
|
||||
if (!model_->hclg_fst_) {
|
||||
if (model_->hcl_fst_ && model_->g_fst_) {
|
||||
decode_fst_ = LookaheadComposeFst(*model_->hcl_fst_, *model_->g_fst_, model_->disambig_);
|
||||
@@ -153,7 +142,14 @@ KaldiRecognizer::~KaldiRecognizer() {
|
||||
delete g_fst_;
|
||||
delete decode_fst_;
|
||||
delete spk_feature_;
|
||||
delete lm_fst_;
|
||||
|
||||
delete rnnlm_info_;
|
||||
delete lm_to_subtract_;
|
||||
delete lm_to_subtract_scale_;
|
||||
delete carpa_to_add_;
|
||||
delete carpa_to_add_scale_;
|
||||
delete rnnlm_to_add_;
|
||||
delete rnnlm_to_add_scale_;
|
||||
|
||||
model_->Unref();
|
||||
if (spk_model_)
|
||||
@@ -171,13 +167,18 @@ void KaldiRecognizer::InitState()
|
||||
|
||||
void KaldiRecognizer::InitRescoring()
|
||||
{
|
||||
if (model_->std_lm_fst_) {
|
||||
fst::CacheOptions cache_opts(true, 50000);
|
||||
fst::ArcMapFstOptions mapfst_opts(cache_opts);
|
||||
fst::StdToLatticeMapper<kaldi::BaseFloat> mapper;
|
||||
lm_fst_ = new fst::ArcMapFst<fst::StdArc, kaldi::LatticeArc, fst::StdToLatticeMapper<kaldi::BaseFloat> >(*model_->std_lm_fst_, mapper, mapfst_opts);
|
||||
} else {
|
||||
lm_fst_ = NULL;
|
||||
if (model_->graph_lm_fst_) {
|
||||
lm_to_subtract_ = new fst::BackoffDeterministicOnDemandFst<StdArc>(*model_->graph_lm_fst_);
|
||||
lm_to_subtract_scale_ = new fst::ScaleDeterministicOnDemandFst(-1.0, lm_to_subtract_);
|
||||
carpa_to_add_ = new ConstArpaLmDeterministicFst(model_->const_arpa_);
|
||||
|
||||
if (model_->rnnlm_enabled_) {
|
||||
int lm_order = 4;
|
||||
rnnlm_info_ = new kaldi::rnnlm::RnnlmComputeStateInfo(model_->rnnlm_compute_opts, model_->rnnlm, model_->word_embedding_mat);
|
||||
rnnlm_to_add_ = new kaldi::rnnlm::KaldiRnnlmDeterministicFst(lm_order, *rnnlm_info_);
|
||||
rnnlm_to_add_scale_ = new fst::ScaleDeterministicOnDemandFst(0.5, rnnlm_to_add_);
|
||||
carpa_to_add_scale_ = new fst::ScaleDeterministicOnDemandFst(-0.5, carpa_to_add_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +196,7 @@ void KaldiRecognizer::CleanUp()
|
||||
|
||||
// Also restart if we retrieved final result already
|
||||
|
||||
if (decoder_ == NULL || state_ == RECOGNIZER_FINALIZED || frame_offset_ > 20000) {
|
||||
if (decoder_ == nullptr || state_ == RECOGNIZER_FINALIZED || frame_offset_ > 20000) {
|
||||
samples_round_start_ += samples_processed_;
|
||||
samples_processed_ = 0;
|
||||
frame_offset_ = 0;
|
||||
@@ -222,7 +223,7 @@ void KaldiRecognizer::CleanUp()
|
||||
void KaldiRecognizer::UpdateSilenceWeights()
|
||||
{
|
||||
if (silence_weighting_->Active() && feature_pipeline_->NumFramesReady() > 0 &&
|
||||
feature_pipeline_->IvectorFeature() != NULL) {
|
||||
feature_pipeline_->IvectorFeature() != nullptr) {
|
||||
vector<pair<int32, BaseFloat> > delta_weights;
|
||||
silence_weighting_->ComputeCurrentTraceback(decoder_->Decoder());
|
||||
silence_weighting_->GetDeltaWeights(feature_pipeline_->NumFramesReady(),
|
||||
@@ -232,6 +233,27 @@ void KaldiRecognizer::UpdateSilenceWeights()
|
||||
}
|
||||
}
|
||||
|
||||
void KaldiRecognizer::SetMaxAlternatives(int max_alternatives)
|
||||
{
|
||||
max_alternatives_ = max_alternatives;
|
||||
}
|
||||
|
||||
void KaldiRecognizer::SetWords(bool words)
|
||||
{
|
||||
words_ = words;
|
||||
}
|
||||
|
||||
void KaldiRecognizer::SetSpkModel(SpkModel *spk_model)
|
||||
{
|
||||
if (state_ == RECOGNIZER_RUNNING) {
|
||||
KALDI_ERR << "Can't add speaker model to already running recognizer";
|
||||
return;
|
||||
}
|
||||
spk_model_ = spk_model;
|
||||
spk_model_->Ref();
|
||||
spk_feature_ = new OnlineMfcc(spk_model_->spkvector_mfcc_opts);
|
||||
}
|
||||
|
||||
bool KaldiRecognizer::AcceptWaveform(const char *data, int len)
|
||||
{
|
||||
Vector<BaseFloat> wave;
|
||||
@@ -304,7 +326,7 @@ static void RunNnetComputation(const MatrixBase<BaseFloat> &features,
|
||||
request.outputs.resize(1);
|
||||
request.outputs[0].Swap(&output_spec);
|
||||
shared_ptr<const nnet3::NnetComputation> computation = compiler->Compile(request);
|
||||
nnet3::Nnet *nnet_to_update = NULL; // we're not doing any update.
|
||||
nnet3::Nnet *nnet_to_update = nullptr; // we're not doing any update.
|
||||
nnet3::NnetComputer computer(nnet3::NnetComputeOptions(), *computation,
|
||||
nnet, nnet_to_update);
|
||||
CuMatrix<BaseFloat> input_feats_cu(features);
|
||||
@@ -383,44 +405,14 @@ bool KaldiRecognizer::GetSpkVector(Vector<BaseFloat> &out_xvector, int *num_spk_
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* KaldiRecognizer::GetResult()
|
||||
|
||||
const char *KaldiRecognizer::MbrResult(CompactLattice &rlat)
|
||||
{
|
||||
if (decoder_->NumFramesDecoded() == 0) {
|
||||
return StoreReturn("{\"text\": \"\"}");
|
||||
}
|
||||
|
||||
kaldi::CompactLattice clat;
|
||||
decoder_->GetLattice(true, &clat);
|
||||
|
||||
if (model_->std_lm_fst_) {
|
||||
Lattice lat1;
|
||||
|
||||
ConvertLattice(clat, &lat1);
|
||||
fst::ScaleLattice(fst::GraphLatticeScale(-1.0), &lat1);
|
||||
fst::ArcSort(&lat1, fst::OLabelCompare<kaldi::LatticeArc>());
|
||||
kaldi::Lattice composed_lat;
|
||||
fst::Compose(lat1, *lm_fst_, &composed_lat);
|
||||
fst::Invert(&composed_lat);
|
||||
kaldi::CompactLattice determinized_lat;
|
||||
DeterminizeLattice(composed_lat, &determinized_lat);
|
||||
fst::ScaleLattice(fst::GraphLatticeScale(-1), &determinized_lat);
|
||||
fst::ArcSort(&determinized_lat, fst::OLabelCompare<kaldi::CompactLatticeArc>());
|
||||
|
||||
kaldi::ConstArpaLmDeterministicFst const_arpa_fst(model_->const_arpa_);
|
||||
kaldi::CompactLattice composed_clat;
|
||||
kaldi::ComposeCompactLatticeDeterministic(determinized_lat, &const_arpa_fst, &composed_clat);
|
||||
kaldi::Lattice composed_lat1;
|
||||
ConvertLattice(composed_clat, &composed_lat1);
|
||||
fst::Invert(&composed_lat1);
|
||||
DeterminizeLattice(composed_lat1, &clat);
|
||||
}
|
||||
|
||||
fst::ScaleLattice(fst::GraphLatticeScale(0.9), &clat); // Apply rescoring weight
|
||||
CompactLattice aligned_lat;
|
||||
if (model_->winfo_) {
|
||||
WordAlignLattice(clat, *model_->trans_model_, *model_->winfo_, 0, &aligned_lat);
|
||||
WordAlignLattice(rlat, *model_->trans_model_, *model_->winfo_, 0, &aligned_lat);
|
||||
} else {
|
||||
aligned_lat = clat;
|
||||
aligned_lat = rlat;
|
||||
}
|
||||
|
||||
MinimumBayesRisk mbr(aligned_lat);
|
||||
@@ -437,11 +429,14 @@ const char* KaldiRecognizer::GetResult()
|
||||
// Create JSON object
|
||||
for (int i = 0; i < size; i++) {
|
||||
json::JSON word;
|
||||
word["word"] = model_->word_syms_->Find(words[i]);
|
||||
word["start"] = samples_round_start_ / sample_frequency_ + (frame_offset_ + times[i].first) * 0.03;
|
||||
word["end"] = samples_round_start_ / sample_frequency_ + (frame_offset_ + times[i].second) * 0.03;
|
||||
word["conf"] = conf[i];
|
||||
obj["result"].append(word);
|
||||
|
||||
if (words_) {
|
||||
word["word"] = model_->word_syms_->Find(words[i]);
|
||||
word["start"] = samples_round_start_ / sample_frequency_ + (frame_offset_ + times[i].first) * 0.03;
|
||||
word["end"] = samples_round_start_ / sample_frequency_ + (frame_offset_ + times[i].second) * 0.03;
|
||||
word["conf"] = conf[i];
|
||||
obj["result"].append(word);
|
||||
}
|
||||
|
||||
if (i) {
|
||||
text << " ";
|
||||
@@ -464,11 +459,180 @@ const char* KaldiRecognizer::GetResult()
|
||||
return StoreReturn(obj.dump());
|
||||
}
|
||||
|
||||
static bool CompactLatticeToWordAlignmentWeight(const CompactLattice &clat,
|
||||
std::vector<int32> *words,
|
||||
std::vector<int32> *begin_times,
|
||||
std::vector<int32> *lengths,
|
||||
CompactLattice::Weight *tot_weight_out)
|
||||
{
|
||||
typedef CompactLattice::Arc Arc;
|
||||
typedef Arc::Label Label;
|
||||
typedef CompactLattice::StateId StateId;
|
||||
typedef CompactLattice::Weight Weight;
|
||||
using namespace fst;
|
||||
|
||||
words->clear();
|
||||
begin_times->clear();
|
||||
lengths->clear();
|
||||
*tot_weight_out = Weight::Zero();
|
||||
|
||||
StateId state = clat.Start();
|
||||
Weight tot_weight = Weight::One();
|
||||
|
||||
int32 cur_time = 0;
|
||||
if (state == kNoStateId) {
|
||||
KALDI_WARN << "Empty lattice.";
|
||||
return false;
|
||||
}
|
||||
while (1) {
|
||||
Weight final = clat.Final(state);
|
||||
size_t num_arcs = clat.NumArcs(state);
|
||||
if (final != Weight::Zero()) {
|
||||
if (num_arcs != 0) {
|
||||
KALDI_WARN << "Lattice is not linear.";
|
||||
return false;
|
||||
}
|
||||
if (!final.String().empty()) {
|
||||
KALDI_WARN << "Lattice has alignments on final-weight: probably "
|
||||
"was not word-aligned (alignments will be approximate)";
|
||||
}
|
||||
tot_weight = Times(final, tot_weight);
|
||||
*tot_weight_out = tot_weight;
|
||||
return true;
|
||||
} else {
|
||||
if (num_arcs != 1) {
|
||||
KALDI_WARN << "Lattice is not linear: num-arcs = " << num_arcs;
|
||||
return false;
|
||||
}
|
||||
fst::ArcIterator<CompactLattice> aiter(clat, state);
|
||||
const Arc &arc = aiter.Value();
|
||||
Label word_id = arc.ilabel; // Note: ilabel==olabel, since acceptor.
|
||||
// Also note: word_id may be zero; we output it anyway.
|
||||
int32 length = arc.weight.String().size();
|
||||
words->push_back(word_id);
|
||||
begin_times->push_back(cur_time);
|
||||
lengths->push_back(length);
|
||||
tot_weight = Times(arc.weight, tot_weight);
|
||||
cur_time += length;
|
||||
state = arc.nextstate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const char *KaldiRecognizer::NbestResult(CompactLattice &clat)
|
||||
{
|
||||
Lattice lat;
|
||||
Lattice nbest_lat;
|
||||
std::vector<Lattice> nbest_lats;
|
||||
|
||||
ConvertLattice (clat, &lat);
|
||||
fst::ShortestPath(lat, &nbest_lat, max_alternatives_);
|
||||
fst::ConvertNbestToVector(nbest_lat, &nbest_lats);
|
||||
|
||||
json::JSON obj;
|
||||
std::stringstream ss;
|
||||
for (int k = 0; k < nbest_lats.size(); k++) {
|
||||
|
||||
Lattice nlat = nbest_lats[k];
|
||||
RmEpsilon(&nlat);
|
||||
CompactLattice nclat;
|
||||
CompactLattice aligned_nclat;
|
||||
ConvertLattice(nlat, &nclat);
|
||||
|
||||
if (model_->winfo_) {
|
||||
WordAlignLattice(nclat, *model_->trans_model_, *model_->winfo_, 0, &aligned_nclat);
|
||||
} else {
|
||||
aligned_nclat = nclat;
|
||||
}
|
||||
|
||||
std::vector<int32> words;
|
||||
std::vector<int32> begin_times;
|
||||
std::vector<int32> lengths;
|
||||
CompactLattice::Weight weight;
|
||||
|
||||
CompactLatticeToWordAlignmentWeight(aligned_nclat, &words, &begin_times, &lengths, &weight);
|
||||
float likelihood = -(weight.Weight().Value1() + weight.Weight().Value2());
|
||||
|
||||
stringstream text;
|
||||
json::JSON entry;
|
||||
|
||||
for (int i = 0; i < words.size(); i++) {
|
||||
json::JSON word;
|
||||
if (words[i] == 0)
|
||||
continue;
|
||||
if (words_) {
|
||||
word["word"] = model_->word_syms_->Find(words[i]);
|
||||
word["start"] = samples_round_start_ / sample_frequency_ + (frame_offset_ + begin_times[i]) * 0.03;
|
||||
word["end"] = samples_round_start_ / sample_frequency_ + (frame_offset_ + begin_times[i] + lengths[i]) * 0.03;
|
||||
entry["result"].append(word);
|
||||
}
|
||||
if (i)
|
||||
text << " ";
|
||||
text << model_->word_syms_->Find(words[i]);
|
||||
}
|
||||
|
||||
entry["text"] = text.str();
|
||||
entry["confidence"]= likelihood;
|
||||
obj["alternatives"].append(entry);
|
||||
}
|
||||
|
||||
return StoreReturn(obj.dump());
|
||||
}
|
||||
|
||||
const char* KaldiRecognizer::GetResult()
|
||||
{
|
||||
if (decoder_->NumFramesDecoded() == 0) {
|
||||
return StoreEmptyReturn();
|
||||
}
|
||||
|
||||
kaldi::CompactLattice clat;
|
||||
kaldi::CompactLattice rlat;
|
||||
decoder_->GetLattice(true, &clat);
|
||||
|
||||
if (lm_to_subtract_scale_ && carpa_to_add_) {
|
||||
TopSortCompactLatticeIfNeeded(&clat);
|
||||
CompactLattice tlat;
|
||||
fst::ComposeDeterministicOnDemandFst<StdArc> combined_lm(lm_to_subtract_scale_, carpa_to_add_);
|
||||
ComposeCompactLatticeDeterministic(clat, &combined_lm, &tlat);
|
||||
|
||||
if (rnnlm_to_add_scale_) {
|
||||
ComposeLatticePrunedOptions compose_opts;
|
||||
compose_opts.lattice_compose_beam = 3.0;
|
||||
compose_opts.max_arcs = 3000;
|
||||
TopSortCompactLatticeIfNeeded(&tlat);
|
||||
fst::ComposeDeterministicOnDemandFst<StdArc> combined_rnnlm(carpa_to_add_scale_, rnnlm_to_add_scale_);
|
||||
ComposeCompactLatticePruned(compose_opts, tlat,
|
||||
&combined_rnnlm, &rlat);
|
||||
rnnlm_to_add_->Clear();
|
||||
} else {
|
||||
rlat = tlat;
|
||||
}
|
||||
|
||||
kaldi::Lattice slat;
|
||||
ConvertLattice(rlat, &slat);
|
||||
fst::Invert(&slat);
|
||||
DeterminizeLattice(slat, &rlat);
|
||||
|
||||
} else {
|
||||
rlat = clat;
|
||||
}
|
||||
|
||||
fst::ScaleLattice(fst::GraphLatticeScale(0.9), &rlat); // Apply rescoring weight
|
||||
|
||||
if (max_alternatives_ == 0) {
|
||||
return MbrResult(rlat);
|
||||
} else {
|
||||
return NbestResult(rlat);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
const char* KaldiRecognizer::PartialResult()
|
||||
{
|
||||
if (state_ != RECOGNIZER_RUNNING) {
|
||||
return StoreReturn("{\"text\": \"\"}");
|
||||
return StoreEmptyReturn();
|
||||
}
|
||||
|
||||
json::JSON res;
|
||||
@@ -499,7 +663,7 @@ const char* KaldiRecognizer::PartialResult()
|
||||
const char* KaldiRecognizer::Result()
|
||||
{
|
||||
if (state_ != RECOGNIZER_RUNNING) {
|
||||
return StoreReturn("{\"text\": \"\"}");
|
||||
return StoreEmptyReturn();
|
||||
}
|
||||
decoder_->FinalizeDecoding();
|
||||
state_ = RECOGNIZER_ENDPOINT;
|
||||
@@ -509,7 +673,7 @@ const char* KaldiRecognizer::Result()
|
||||
const char* KaldiRecognizer::FinalResult()
|
||||
{
|
||||
if (state_ != RECOGNIZER_RUNNING) {
|
||||
return StoreReturn("{\"text\": \"\"}");
|
||||
return StoreEmptyReturn();
|
||||
}
|
||||
|
||||
feature_pipeline_->InputFinished();
|
||||
@@ -526,14 +690,32 @@ const char* KaldiRecognizer::FinalResult()
|
||||
delete silence_weighting_;
|
||||
delete spk_feature_;
|
||||
|
||||
feature_pipeline_ = NULL;
|
||||
silence_weighting_ = NULL;
|
||||
decoder_ = NULL;
|
||||
spk_feature_ = NULL;
|
||||
feature_pipeline_ = nullptr;
|
||||
silence_weighting_ = nullptr;
|
||||
decoder_ = nullptr;
|
||||
spk_feature_ = nullptr;
|
||||
|
||||
return last_result_.c_str();
|
||||
}
|
||||
|
||||
void KaldiRecognizer::Reset()
|
||||
{
|
||||
if (state_ == RECOGNIZER_RUNNING) {
|
||||
decoder_->FinalizeDecoding();
|
||||
}
|
||||
StoreEmptyReturn();
|
||||
state_ = RECOGNIZER_ENDPOINT;
|
||||
}
|
||||
|
||||
const char *KaldiRecognizer::StoreEmptyReturn()
|
||||
{
|
||||
if (!max_alternatives_) {
|
||||
return StoreReturn("{\"text\": \"\"}");
|
||||
} else {
|
||||
return StoreReturn("{\"alternatives\" : [{\"text\": \"\", \"confidence\" : 1.0}] }");
|
||||
}
|
||||
}
|
||||
|
||||
// Store result in recognizer and return as const string
|
||||
const char *KaldiRecognizer::StoreReturn(const string &res)
|
||||
{
|
||||
|
||||
+33
-10
@@ -23,6 +23,7 @@
|
||||
#include "feat/feature-mfcc.h"
|
||||
#include "lat/kaldi-lattice.h"
|
||||
#include "lat/word-align-lattice.h"
|
||||
#include "lat/compose-lattice-pruned.h"
|
||||
#include "nnet3/am-nnet-simple.h"
|
||||
#include "nnet3/nnet-am-decodable-simple.h"
|
||||
#include "nnet3/nnet-utils.h"
|
||||
@@ -42,15 +43,19 @@ enum KaldiRecognizerState {
|
||||
class KaldiRecognizer {
|
||||
public:
|
||||
KaldiRecognizer(Model *model, float sample_frequency);
|
||||
KaldiRecognizer(Model *model, SpkModel *spk_model, float sample_frequency);
|
||||
KaldiRecognizer(Model *model, float sample_frequency, SpkModel *spk_model);
|
||||
KaldiRecognizer(Model *model, float sample_frequency, char const *grammar);
|
||||
~KaldiRecognizer();
|
||||
void SetMaxAlternatives(int max_alternatives);
|
||||
void SetSpkModel(SpkModel *spk_model);
|
||||
void SetWords(bool words);
|
||||
bool AcceptWaveform(const char *data, int len);
|
||||
bool AcceptWaveform(const short *sdata, int len);
|
||||
bool AcceptWaveform(const float *fdata, int len);
|
||||
const char* Result();
|
||||
const char* FinalResult();
|
||||
const char* PartialResult();
|
||||
void Reset();
|
||||
|
||||
private:
|
||||
void InitState();
|
||||
@@ -60,19 +65,37 @@ class KaldiRecognizer {
|
||||
bool AcceptWaveform(Vector<BaseFloat> &wdata);
|
||||
bool GetSpkVector(Vector<BaseFloat> &out_xvector, int *frames);
|
||||
const char *GetResult();
|
||||
const char *StoreEmptyReturn();
|
||||
const char *StoreReturn(const string &res);
|
||||
const char *MbrResult(CompactLattice &clat);
|
||||
const char *NbestResult(CompactLattice &clat);
|
||||
|
||||
Model *model_;
|
||||
SingleUtteranceNnet3Decoder *decoder_;
|
||||
fst::LookaheadFst<fst::StdArc, int32> *decode_fst_;
|
||||
fst::StdVectorFst *g_fst_; // dynamically constructed grammar
|
||||
OnlineNnet2FeaturePipeline *feature_pipeline_;
|
||||
OnlineSilenceWeighting *silence_weighting_;
|
||||
Model *model_ = nullptr;
|
||||
SingleUtteranceNnet3Decoder *decoder_ = nullptr;
|
||||
fst::LookaheadFst<fst::StdArc, int32> *decode_fst_ = nullptr;
|
||||
fst::StdVectorFst *g_fst_ = nullptr; // dynamically constructed grammar
|
||||
OnlineNnet2FeaturePipeline *feature_pipeline_ = nullptr;
|
||||
OnlineSilenceWeighting *silence_weighting_ = nullptr;
|
||||
|
||||
SpkModel *spk_model_;
|
||||
OnlineBaseFeature *spk_feature_;
|
||||
// Speaker identification
|
||||
SpkModel *spk_model_ = nullptr;
|
||||
OnlineBaseFeature *spk_feature_ = nullptr;
|
||||
|
||||
fst::ArcMapFst<fst::StdArc, kaldi::LatticeArc, fst::StdToLatticeMapper<kaldi::BaseFloat> > *lm_fst_;
|
||||
// Rescoring
|
||||
fst::BackoffDeterministicOnDemandFst<fst::StdArc> *lm_to_subtract_ = nullptr;
|
||||
fst::ScaleDeterministicOnDemandFst *lm_to_subtract_scale_ = nullptr;
|
||||
kaldi::ConstArpaLmDeterministicFst *carpa_to_add_ = nullptr;
|
||||
fst::ScaleDeterministicOnDemandFst *carpa_to_add_scale_ = nullptr;
|
||||
|
||||
// RNNLM rescoring
|
||||
kaldi::rnnlm::KaldiRnnlmDeterministicFst* rnnlm_to_add_ = nullptr;
|
||||
fst::DeterministicOnDemandFst<fst::StdArc> *rnnlm_to_add_scale_ = nullptr;
|
||||
kaldi::rnnlm::RnnlmComputeStateInfo *rnnlm_info_ = nullptr;
|
||||
|
||||
|
||||
// Other
|
||||
int max_alternatives_ = 0; // Disable alternatives by default
|
||||
bool words_ = false;
|
||||
|
||||
float sample_frequency_;
|
||||
int32 frame_offset_;
|
||||
|
||||
+66
-25
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019 Alpha Cephei Inc.
|
||||
// Copyright 2019-2021 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.
|
||||
@@ -108,15 +108,22 @@ Model::Model(const char *model_path) : model_path_str_(model_path) {
|
||||
SetLogHandler(KaldiLogHandler);
|
||||
|
||||
struct stat buffer;
|
||||
string am_path = model_path_str_ + "/am/final.mdl";
|
||||
if (stat(am_path.c_str(), &buffer) == 0) {
|
||||
ConfigureV2();
|
||||
string am_v2_path = model_path_str_ + "/am/final.mdl";
|
||||
string mfcc_v2_path = model_path_str_ + "/conf/mfcc.conf";
|
||||
string am_v1_path = model_path_str_ + "/final.mdl";
|
||||
string mfcc_v1_path = model_path_str_ + "/mfcc.conf";
|
||||
if (stat(am_v2_path.c_str(), &buffer) == 0 && stat(mfcc_v2_path.c_str(), &buffer) == 0) {
|
||||
ConfigureV2();
|
||||
ReadDataFiles();
|
||||
} else if (stat(am_v1_path.c_str(), &buffer) == 0 && stat(mfcc_v1_path.c_str(), &buffer) == 0) {
|
||||
ConfigureV1();
|
||||
ReadDataFiles();
|
||||
} else {
|
||||
ConfigureV1();
|
||||
KALDI_ERR << "Folder '" << model_path_str_ << "' does not contain model files. " <<
|
||||
"Make sure you specified the model path properly in Model constructor. " <<
|
||||
"If you are not sure about relative path, use absolute path specification.";
|
||||
}
|
||||
|
||||
ReadDataFiles();
|
||||
|
||||
ref_cnt_ = 1;
|
||||
}
|
||||
|
||||
@@ -162,6 +169,11 @@ void Model::ConfigureV1()
|
||||
final_ie_rxfilename_ = model_path_str_ + "/ivector/final.ie";
|
||||
mfcc_conf_rxfilename_ = model_path_str_ + "/mfcc.conf";
|
||||
global_cmvn_stats_rxfilename_ = model_path_str_ + "/global_cmvn.stats";
|
||||
pitch_conf_rxfilename_ = model_path_str_ + "/pitch.conf";
|
||||
rnnlm_word_feats_rxfilename_ = model_path_str_ + "/rnnlm/word_feats.txt";
|
||||
rnnlm_feat_embedding_rxfilename_ = model_path_str_ + "/rnnlm/feat_embedding.final.mat";
|
||||
rnnlm_config_rxfilename_ = model_path_str_ + "/rnnlm/special_symbol_opts.conf";
|
||||
rnnlm_lm_rxfilename_ = model_path_str_ + "/rnnlm/final.raw";
|
||||
}
|
||||
|
||||
void Model::ConfigureV2()
|
||||
@@ -185,6 +197,11 @@ void Model::ConfigureV2()
|
||||
final_ie_rxfilename_ = model_path_str_ + "/ivector/final.ie";
|
||||
mfcc_conf_rxfilename_ = model_path_str_ + "/conf/mfcc.conf";
|
||||
global_cmvn_stats_rxfilename_ = model_path_str_ + "/am/global_cmvn.stats";
|
||||
pitch_conf_rxfilename_ = model_path_str_ + "/conf/pitch.conf";
|
||||
rnnlm_word_feats_rxfilename_ = model_path_str_ + "/rnnlm/word_feats.txt";
|
||||
rnnlm_feat_embedding_rxfilename_ = model_path_str_ + "/rnnlm/feat_embedding.final.mat";
|
||||
rnnlm_config_rxfilename_ = model_path_str_ + "/rnnlm/special_symbol_opts.conf";
|
||||
rnnlm_lm_rxfilename_ = model_path_str_ + "/rnnlm/final.raw";
|
||||
}
|
||||
|
||||
void Model::ReadDataFiles()
|
||||
@@ -241,21 +258,22 @@ void Model::ReadDataFiles()
|
||||
ReadKaldiObject(global_cmvn_stats_rxfilename_, &feature_info_.global_cmvn_stats);
|
||||
}
|
||||
|
||||
if (stat(pitch_conf_rxfilename_.c_str(), &buffer) == 0) {
|
||||
KALDI_LOG << "Using pitch in feature pipeline";
|
||||
feature_info_.add_pitch = true;
|
||||
ReadConfigFromFile(pitch_conf_rxfilename_, &feature_info_.pitch_opts);
|
||||
}
|
||||
|
||||
if (stat(hclg_fst_rxfilename_.c_str(), &buffer) == 0) {
|
||||
KALDI_LOG << "Loading HCLG from " << hclg_fst_rxfilename_;
|
||||
hclg_fst_ = fst::ReadFstKaldiGeneric(hclg_fst_rxfilename_);
|
||||
hcl_fst_ = NULL;
|
||||
g_fst_ = NULL;
|
||||
} else {
|
||||
KALDI_LOG << "Loading HCL and G from " << hcl_fst_rxfilename_ << " " << g_fst_rxfilename_;
|
||||
hclg_fst_ = NULL;
|
||||
hcl_fst_ = fst::StdFst::Read(hcl_fst_rxfilename_);
|
||||
g_fst_ = fst::StdFst::Read(g_fst_rxfilename_);
|
||||
ReadIntegerVectorSimple(disambig_rxfilename_, &disambig_);
|
||||
}
|
||||
|
||||
word_syms_ = NULL;
|
||||
if (hclg_fst_ && hclg_fst_->OutputSymbols()) {
|
||||
word_syms_ = hclg_fst_->OutputSymbols();
|
||||
} else if (g_fst_ && g_fst_->OutputSymbols()) {
|
||||
@@ -266,6 +284,7 @@ void Model::ReadDataFiles()
|
||||
if (!(word_syms_ = fst::SymbolTable::ReadText(word_syms_rxfilename_)))
|
||||
KALDI_ERR << "Could not read symbol table from file "
|
||||
<< word_syms_rxfilename_;
|
||||
word_syms_loaded_ = word_syms_;
|
||||
}
|
||||
KALDI_ASSERT(word_syms_);
|
||||
|
||||
@@ -273,34 +292,53 @@ void Model::ReadDataFiles()
|
||||
KALDI_LOG << "Loading winfo " << winfo_rxfilename_;
|
||||
kaldi::WordBoundaryInfoNewOpts opts;
|
||||
winfo_ = new kaldi::WordBoundaryInfo(opts, winfo_rxfilename_);
|
||||
} else {
|
||||
winfo_ = NULL;
|
||||
}
|
||||
|
||||
if (stat(carpa_rxfilename_.c_str(), &buffer) == 0) {
|
||||
|
||||
KALDI_LOG << "Loading subtract G.fst model from " << std_fst_rxfilename_;
|
||||
graph_lm_fst_ = fst::ReadAndPrepareLmFst(std_fst_rxfilename_);
|
||||
KALDI_LOG << "Loading CARPA model from " << carpa_rxfilename_;
|
||||
std_lm_fst_ = fst::ReadFstKaldi(std_fst_rxfilename_);
|
||||
fst::Project(std_lm_fst_, fst::PROJECT_OUTPUT);
|
||||
if (std_lm_fst_->Properties(fst::kILabelSorted, true) == 0) {
|
||||
fst::ILabelCompare<fst::StdArc> ilabel_comp;
|
||||
fst::ArcSort(std_lm_fst_, ilabel_comp);
|
||||
}
|
||||
ReadKaldiObject(carpa_rxfilename_, &const_arpa_);
|
||||
} else {
|
||||
std_lm_fst_ = NULL;
|
||||
}
|
||||
|
||||
// RNNLM Rescoring
|
||||
if (stat(rnnlm_lm_rxfilename_.c_str(), &buffer) == 0) {
|
||||
KALDI_LOG << "Loading RNNLM model from " << rnnlm_lm_rxfilename_;
|
||||
|
||||
ReadKaldiObject(rnnlm_lm_rxfilename_, &rnnlm);
|
||||
Matrix<BaseFloat> feature_embedding_mat;
|
||||
ReadKaldiObject(rnnlm_feat_embedding_rxfilename_, &feature_embedding_mat);
|
||||
SparseMatrix<BaseFloat> word_feature_mat;
|
||||
{
|
||||
Input input(rnnlm_word_feats_rxfilename_);
|
||||
int32 feature_dim = feature_embedding_mat.NumRows();
|
||||
rnnlm::ReadSparseWordFeatures(input.Stream(), feature_dim,
|
||||
&word_feature_mat);
|
||||
}
|
||||
Matrix<BaseFloat> wm(word_feature_mat.NumRows(), feature_embedding_mat.NumCols());
|
||||
wm.AddSmatMat(1.0, word_feature_mat, kNoTrans,
|
||||
feature_embedding_mat, 0.0);
|
||||
word_embedding_mat.Resize(wm.NumRows(), wm.NumCols(), kUndefined);
|
||||
word_embedding_mat.CopyFromMat(wm);
|
||||
|
||||
ReadConfigFromFile(rnnlm_config_rxfilename_, &rnnlm_compute_opts);
|
||||
|
||||
rnnlm_enabled_ = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Model::Ref()
|
||||
{
|
||||
ref_cnt_++;
|
||||
std::atomic_fetch_add_explicit(&ref_cnt_, 1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void Model::Unref()
|
||||
{
|
||||
ref_cnt_--;
|
||||
if (ref_cnt_ == 0) {
|
||||
delete this;
|
||||
if (std::atomic_fetch_sub_explicit(&ref_cnt_, 1, std::memory_order_release) == 1) {
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,8 +354,11 @@ Model::~Model() {
|
||||
delete decodable_info_;
|
||||
delete trans_model_;
|
||||
delete nnet_;
|
||||
if (word_syms_loaded_)
|
||||
delete word_syms_;
|
||||
delete winfo_;
|
||||
delete hclg_fst_;
|
||||
delete hcl_fst_;
|
||||
delete g_fst_;
|
||||
delete graph_lm_fst_;
|
||||
}
|
||||
|
||||
+25
-11
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019 Alpha Cephei Inc.
|
||||
// Copyright 2019-2021 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.
|
||||
@@ -30,6 +30,8 @@
|
||||
#include "util/parse-options.h"
|
||||
#include "nnet3/nnet-utils.h"
|
||||
#include "rnnlm/rnnlm-utils.h"
|
||||
#include "rnnlm/rnnlm-lattice-rescoring.h"
|
||||
#include <atomic>
|
||||
|
||||
using namespace kaldi;
|
||||
using namespace std;
|
||||
@@ -65,27 +67,39 @@ protected:
|
||||
string final_ie_rxfilename_;
|
||||
string mfcc_conf_rxfilename_;
|
||||
string global_cmvn_stats_rxfilename_;
|
||||
string pitch_conf_rxfilename_;
|
||||
|
||||
string rnnlm_word_feats_rxfilename_;
|
||||
string rnnlm_feat_embedding_rxfilename_;
|
||||
string rnnlm_config_rxfilename_;
|
||||
string rnnlm_lm_rxfilename_;
|
||||
|
||||
kaldi::OnlineEndpointConfig endpoint_config_;
|
||||
kaldi::LatticeFasterDecoderConfig nnet3_decoding_config_;
|
||||
kaldi::nnet3::NnetSimpleLoopedComputationOptions decodable_opts_;
|
||||
kaldi::OnlineNnet2FeaturePipelineInfo feature_info_;
|
||||
|
||||
kaldi::nnet3::DecodableNnetSimpleLoopedInfo *decodable_info_;
|
||||
kaldi::TransitionModel *trans_model_;
|
||||
kaldi::nnet3::AmNnetSimple *nnet_;
|
||||
const fst::SymbolTable *word_syms_;
|
||||
kaldi::WordBoundaryInfo *winfo_;
|
||||
kaldi::nnet3::DecodableNnetSimpleLoopedInfo *decodable_info_ = nullptr;
|
||||
kaldi::TransitionModel *trans_model_ = nullptr;
|
||||
kaldi::nnet3::AmNnetSimple *nnet_ = nullptr;
|
||||
const fst::SymbolTable *word_syms_ = nullptr;
|
||||
bool word_syms_loaded_ = false;
|
||||
kaldi::WordBoundaryInfo *winfo_ = nullptr;
|
||||
vector<int32> disambig_;
|
||||
|
||||
fst::Fst<fst::StdArc> *hclg_fst_;
|
||||
fst::Fst<fst::StdArc> *hcl_fst_;
|
||||
fst::Fst<fst::StdArc> *g_fst_;
|
||||
fst::Fst<fst::StdArc> *hclg_fst_ = nullptr;
|
||||
fst::Fst<fst::StdArc> *hcl_fst_ = nullptr;
|
||||
fst::Fst<fst::StdArc> *g_fst_ = nullptr;
|
||||
|
||||
fst::VectorFst<fst::StdArc> *std_lm_fst_;
|
||||
fst::VectorFst<fst::StdArc> *graph_lm_fst_ = nullptr;
|
||||
kaldi::ConstArpaLm const_arpa_;
|
||||
|
||||
int ref_cnt_;
|
||||
kaldi::rnnlm::RnnlmComputeStateComputationOptions rnnlm_compute_opts;
|
||||
CuMatrix<BaseFloat> word_embedding_mat;
|
||||
kaldi::nnet3::Nnet rnnlm;
|
||||
bool rnnlm_enabled_ = false;
|
||||
|
||||
std::atomic<int> ref_cnt_;
|
||||
};
|
||||
|
||||
#endif /* VOSK_MODEL_H */
|
||||
|
||||
+6
-6
@@ -31,15 +31,15 @@ SpkModel::SpkModel(const char *speaker_path) {
|
||||
ref_cnt_ = 1;
|
||||
}
|
||||
|
||||
void SpkModel::Ref()
|
||||
void SpkModel::Ref()
|
||||
{
|
||||
ref_cnt_++;
|
||||
std::atomic_fetch_add_explicit(&ref_cnt_, 1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void SpkModel::Unref()
|
||||
void SpkModel::Unref()
|
||||
{
|
||||
ref_cnt_--;
|
||||
if (ref_cnt_ == 0) {
|
||||
delete this;
|
||||
if (std::atomic_fetch_sub_explicit(&ref_cnt_, 1, std::memory_order_release) == 1) {
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -18,6 +18,7 @@
|
||||
#include "base/kaldi-common.h"
|
||||
#include "online2/online-feature-pipeline.h"
|
||||
#include "nnet3/nnet-utils.h"
|
||||
#include <atomic>
|
||||
|
||||
using namespace kaldi;
|
||||
|
||||
@@ -40,7 +41,7 @@ protected:
|
||||
|
||||
MfccOptions spkvector_mfcc_opts;
|
||||
|
||||
int ref_cnt_;
|
||||
std::atomic<int> ref_cnt_;
|
||||
};
|
||||
|
||||
#endif /* VOSK_SPK_MODEL_H */
|
||||
|
||||
-144
@@ -1,144 +0,0 @@
|
||||
#if SWIGPYTHON
|
||||
%module(package="vosk", "threads"=1) vosk
|
||||
#else
|
||||
%module Vosk
|
||||
#endif
|
||||
|
||||
%include <typemaps.i>
|
||||
|
||||
#if SWIGPYTHON
|
||||
%include <pybuffer.i>
|
||||
#elif SWIGJAVA
|
||||
%include <various.i>
|
||||
#elif SWIGCSHARP
|
||||
%include <arrays_csharp.i>
|
||||
#endif
|
||||
|
||||
#if SWIGPYTHON
|
||||
%pybuffer_binary(const char *data, int len);
|
||||
#endif
|
||||
|
||||
#if SWIGJAVA
|
||||
%apply char *BYTE {const char *data};
|
||||
%typemap(javaimports) KaldiRecognizer %{
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
%}
|
||||
%typemap(javacode) KaldiRecognizer %{
|
||||
public boolean AcceptWaveform(byte[] data) {
|
||||
return AcceptWaveform(data, data.length);
|
||||
}
|
||||
public boolean AcceptWaveform(short[] data, int len) {
|
||||
byte[] bdata = new byte[len * 2];
|
||||
ByteBuffer.wrap(bdata).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().put(data, 0, len);
|
||||
return AcceptWaveform(bdata, bdata.length);
|
||||
}
|
||||
%}
|
||||
%pragma(java) jniclasscode=%{
|
||||
static {
|
||||
System.loadLibrary("vosk_jni");
|
||||
}
|
||||
%}
|
||||
#endif
|
||||
|
||||
#if SWIGCSHARP
|
||||
CSHARP_ARRAYS(char, byte)
|
||||
%apply char INPUT[] {const char *data};
|
||||
%apply float INPUT[] {const float *fdata};
|
||||
%apply short INPUT[] {const short *sdata};
|
||||
#endif
|
||||
|
||||
|
||||
#if SWIGJAVASCRIPT
|
||||
%begin %{
|
||||
#include <v8.h>
|
||||
#include <node.h>
|
||||
#include <node_buffer.h>
|
||||
%}
|
||||
#endif
|
||||
|
||||
%{
|
||||
#include "vosk_api.h"
|
||||
typedef struct VoskModel Model;
|
||||
typedef struct VoskSpkModel SpkModel;
|
||||
typedef struct VoskRecognizer KaldiRecognizer;
|
||||
%}
|
||||
|
||||
typedef struct {} Model;
|
||||
typedef struct {} SpkModel;
|
||||
typedef struct {} KaldiRecognizer;
|
||||
|
||||
%extend Model {
|
||||
Model(const char *model_path) {
|
||||
return vosk_model_new(model_path);
|
||||
}
|
||||
~Model() {
|
||||
vosk_model_free($self);
|
||||
}
|
||||
int vosk_model_find_word(const char* word) {
|
||||
return vosk_model_find_word($self, word);
|
||||
}
|
||||
}
|
||||
|
||||
%extend SpkModel {
|
||||
SpkModel(const char *model_path) {
|
||||
return vosk_spk_model_new(model_path);
|
||||
}
|
||||
~SpkModel() {
|
||||
vosk_spk_model_free($self);
|
||||
}
|
||||
}
|
||||
|
||||
%extend KaldiRecognizer {
|
||||
KaldiRecognizer(Model *model, float sample_rate) {
|
||||
return vosk_recognizer_new(model, sample_rate);
|
||||
}
|
||||
KaldiRecognizer(Model *model, SpkModel *spk_model, float sample_rate) {
|
||||
return vosk_recognizer_new_spk(model, spk_model, sample_rate);
|
||||
}
|
||||
KaldiRecognizer(Model *model, float sample_rate, const char* grammar) {
|
||||
return vosk_recognizer_new_grm(model, sample_rate, grammar);
|
||||
}
|
||||
~KaldiRecognizer() {
|
||||
vosk_recognizer_free($self);
|
||||
}
|
||||
|
||||
#if SWIGCSHARP
|
||||
bool AcceptWaveform(const char *data, int len) {
|
||||
return vosk_recognizer_accept_waveform($self, data, len);
|
||||
}
|
||||
bool AcceptWaveform(const short *sdata, int len) {
|
||||
return vosk_recognizer_accept_waveform_s($self, sdata, len);
|
||||
}
|
||||
bool AcceptWaveform(const float *fdata, int len) {
|
||||
return vosk_recognizer_accept_waveform_f($self, fdata, len);
|
||||
}
|
||||
#elif SWIGJAVA
|
||||
bool AcceptWaveform(const char *data, int len) {
|
||||
return vosk_recognizer_accept_waveform($self, data, len);
|
||||
}
|
||||
#elif SWIGJAVASCRIPT
|
||||
bool AcceptWaveform(SWIG_Object ptr) {
|
||||
char* data = (char*) node::Buffer::Data(ptr);
|
||||
size_t length = node::Buffer::Length(ptr);
|
||||
return vosk_recognizer_accept_waveform($self, data, length);
|
||||
}
|
||||
#else
|
||||
int AcceptWaveform(const char *data, int len) {
|
||||
return vosk_recognizer_accept_waveform($self, data, len);
|
||||
}
|
||||
#endif
|
||||
|
||||
const char* Result() {
|
||||
return vosk_recognizer_result($self);
|
||||
}
|
||||
const char* PartialResult() {
|
||||
return vosk_recognizer_partial_result($self);
|
||||
}
|
||||
const char* FinalResult() {
|
||||
return vosk_recognizer_final_result($self);
|
||||
}
|
||||
}
|
||||
|
||||
%rename(SetLogLevel) vosk_set_log_level;
|
||||
void vosk_set_log_level(int level);
|
||||
+41
-2
@@ -17,6 +17,10 @@
|
||||
#include "model.h"
|
||||
#include "spk_model.h"
|
||||
|
||||
#if HAVE_CUDA
|
||||
#include "cudamatrix/cu-device.h"
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
|
||||
using namespace kaldi;
|
||||
@@ -51,9 +55,9 @@ VoskRecognizer *vosk_recognizer_new(VoskModel *model, float sample_rate)
|
||||
return (VoskRecognizer *)new KaldiRecognizer((Model *)model, sample_rate);
|
||||
}
|
||||
|
||||
VoskRecognizer *vosk_recognizer_new_spk(VoskModel *model, VoskSpkModel *spk_model, float sample_rate)
|
||||
VoskRecognizer *vosk_recognizer_new_spk(VoskModel *model, float sample_rate, VoskSpkModel *spk_model)
|
||||
{
|
||||
return (VoskRecognizer *)new KaldiRecognizer((Model *)model, (SpkModel *)spk_model, sample_rate);
|
||||
return (VoskRecognizer *)new KaldiRecognizer((Model *)model, sample_rate, (SpkModel *)spk_model);
|
||||
}
|
||||
|
||||
VoskRecognizer *vosk_recognizer_new_grm(VoskModel *model, float sample_rate, const char *grammar)
|
||||
@@ -61,6 +65,21 @@ VoskRecognizer *vosk_recognizer_new_grm(VoskModel *model, float sample_rate, con
|
||||
return (VoskRecognizer *)new KaldiRecognizer((Model *)model, sample_rate, grammar);
|
||||
}
|
||||
|
||||
void vosk_recognizer_set_max_alternatives(VoskRecognizer *recognizer, int max_alternatives)
|
||||
{
|
||||
((KaldiRecognizer *)recognizer)->SetMaxAlternatives(max_alternatives);
|
||||
}
|
||||
|
||||
void vosk_recognizer_set_words(VoskRecognizer *recognizer, int words)
|
||||
{
|
||||
((KaldiRecognizer *)recognizer)->SetWords((bool)words);
|
||||
}
|
||||
|
||||
void vosk_recognizer_set_spk_model(VoskRecognizer *recognizer, VoskSpkModel *spk_model)
|
||||
{
|
||||
((KaldiRecognizer *)recognizer)->SetSpkModel((SpkModel *)spk_model);
|
||||
}
|
||||
|
||||
int vosk_recognizer_accept_waveform(VoskRecognizer *recognizer, const char *data, int length)
|
||||
{
|
||||
return ((KaldiRecognizer *)(recognizer))->AcceptWaveform(data, length);
|
||||
@@ -91,6 +110,11 @@ const char *vosk_recognizer_final_result(VoskRecognizer *recognizer)
|
||||
return ((KaldiRecognizer *)recognizer)->FinalResult();
|
||||
}
|
||||
|
||||
void vosk_recognizer_reset(VoskRecognizer *recognizer)
|
||||
{
|
||||
((KaldiRecognizer *)recognizer)->Reset();
|
||||
}
|
||||
|
||||
void vosk_recognizer_free(VoskRecognizer *recognizer)
|
||||
{
|
||||
delete (KaldiRecognizer *)(recognizer);
|
||||
@@ -100,3 +124,18 @@ void vosk_set_log_level(int log_level)
|
||||
{
|
||||
SetVerboseLevel(log_level);
|
||||
}
|
||||
|
||||
void vosk_gpu_init()
|
||||
{
|
||||
#if HAVE_CUDA
|
||||
kaldi::CuDevice::Instantiate().SelectGpuId("yes");
|
||||
kaldi::CuDevice::Instantiate().AllowMultithreading();
|
||||
#endif
|
||||
}
|
||||
|
||||
void vosk_gpu_thread_init()
|
||||
{
|
||||
#if HAVE_CUDA
|
||||
kaldi::CuDevice::Instantiate();
|
||||
#endif
|
||||
}
|
||||
|
||||
+91
-32
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 Alpha Cephei Inc.
|
||||
// Copyright 2020-2021 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.
|
||||
@@ -90,10 +90,10 @@ VoskRecognizer *vosk_recognizer_new(VoskModel *model, float sample_rate);
|
||||
* With the speaker recognition mode the recognizer not just recognize
|
||||
* text but also return speaker vectors one can use for speaker identification
|
||||
*
|
||||
* @param spk_model speaker model for speaker identification
|
||||
* @param sample_rate The sample rate of the audio you going to feed into the recognizer
|
||||
* @param spk_model speaker model for speaker identification
|
||||
* @returns recognizer object */
|
||||
VoskRecognizer *vosk_recognizer_new_spk(VoskModel *model, VoskSpkModel *spk_model, float sample_rate);
|
||||
VoskRecognizer *vosk_recognizer_new_spk(VoskModel *model, float sample_rate, VoskSpkModel *spk_model);
|
||||
|
||||
|
||||
/** Creates the recognizer object with the phrase list
|
||||
@@ -114,6 +114,67 @@ VoskRecognizer *vosk_recognizer_new_spk(VoskModel *model, VoskSpkModel *spk_mode
|
||||
VoskRecognizer *vosk_recognizer_new_grm(VoskModel *model, float sample_rate, const char *grammar);
|
||||
|
||||
|
||||
/** Adds speaker model to already initialized recognizer
|
||||
*
|
||||
* Can add speaker recognition model to already created recognizer. Helps to initialize
|
||||
* speaker recognition for grammar-based recognizer.
|
||||
*
|
||||
* @param spk_model Speaker recognition model */
|
||||
void vosk_recognizer_set_spk_model(VoskRecognizer *recognizer, VoskSpkModel *spk_model);
|
||||
|
||||
|
||||
/** Configures recognizer to output n-best results
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* "alternatives": [
|
||||
* { "text": "one two three four five", "confidence": 0.97 },
|
||||
* { "text": "one two three for five", "confidence": 0.03 },
|
||||
* ]
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param max_alternatives - maximum alternatives to return from recognition results
|
||||
*/
|
||||
void vosk_recognizer_set_max_alternatives(VoskRecognizer *recognizer, int max_alternatives);
|
||||
|
||||
|
||||
/** Enables words with times in the output
|
||||
*
|
||||
* <pre>
|
||||
* "result" : [{
|
||||
* "conf" : 1.000000,
|
||||
* "end" : 1.110000,
|
||||
* "start" : 0.870000,
|
||||
* "word" : "what"
|
||||
* }, {
|
||||
* "conf" : 1.000000,
|
||||
* "end" : 1.530000,
|
||||
* "start" : 1.110000,
|
||||
* "word" : "zero"
|
||||
* }, {
|
||||
* "conf" : 1.000000,
|
||||
* "end" : 1.950000,
|
||||
* "start" : 1.530000,
|
||||
* "word" : "zero"
|
||||
* }, {
|
||||
* "conf" : 1.000000,
|
||||
* "end" : 2.340000,
|
||||
* "start" : 1.950000,
|
||||
* "word" : "zero"
|
||||
* }, {
|
||||
* "conf" : 1.000000,
|
||||
* "end" : 2.610000,
|
||||
* "start" : 2.340000,
|
||||
* "word" : "one"
|
||||
* }],
|
||||
* </pre>
|
||||
*
|
||||
* @param words - boolean value
|
||||
*/
|
||||
void vosk_recognizer_set_words(VoskRecognizer *recognizer, int words);
|
||||
|
||||
|
||||
/** Accept voice data
|
||||
*
|
||||
* accept and process new chunk of voice data
|
||||
@@ -141,36 +202,14 @@ int vosk_recognizer_accept_waveform_f(VoskRecognizer *recognizer, const float *d
|
||||
* with any json parser
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* "result" : [{
|
||||
* "conf" : 1.000000,
|
||||
* "end" : 1.110000,
|
||||
* "start" : 0.870000,
|
||||
* "word" : "what"
|
||||
* }, {
|
||||
* "conf" : 1.000000,
|
||||
* "end" : 1.530000,
|
||||
* "start" : 1.110000,
|
||||
* "word" : "zero"
|
||||
* }, {
|
||||
* "conf" : 1.000000,
|
||||
* "end" : 1.950000,
|
||||
* "start" : 1.530000,
|
||||
* "word" : "zero"
|
||||
* }, {
|
||||
* "conf" : 1.000000,
|
||||
* "end" : 2.340000,
|
||||
* "start" : 1.950000,
|
||||
* "word" : "zero"
|
||||
* }, {
|
||||
* "conf" : 1.000000,
|
||||
* "end" : 2.610000,
|
||||
* "start" : 2.340000,
|
||||
* "word" : "one"
|
||||
* }],
|
||||
* "text" : "what zero zero zero one"
|
||||
* {
|
||||
* "text" : "what zero zero zero one"
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* If alternatives enabled it returns result with alternatives, see also vosk_recognizer_set_alternatives().
|
||||
*
|
||||
* If word times enabled returns word time, see also vosk_recognizer_set_word_times().
|
||||
*/
|
||||
const char *vosk_recognizer_result(VoskRecognizer *recognizer);
|
||||
|
||||
@@ -182,7 +221,7 @@ const char *vosk_recognizer_result(VoskRecognizer *recognizer);
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* "partial" : "cyril one eight zero"
|
||||
* "partial" : "cyril one eight zero"
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
@@ -198,6 +237,12 @@ const char *vosk_recognizer_partial_result(VoskRecognizer *recognizer);
|
||||
const char *vosk_recognizer_final_result(VoskRecognizer *recognizer);
|
||||
|
||||
|
||||
/** Resets the recognizer
|
||||
*
|
||||
* Resets current results so the recognition can continue from scratch */
|
||||
void vosk_recognizer_reset(VoskRecognizer *recognizer);
|
||||
|
||||
|
||||
/** Releases recognizer object
|
||||
*
|
||||
* Underlying model is also unreferenced and if needed released */
|
||||
@@ -212,6 +257,20 @@ void vosk_recognizer_free(VoskRecognizer *recognizer);
|
||||
*/
|
||||
void vosk_set_log_level(int log_level);
|
||||
|
||||
/**
|
||||
* Init, automatically select a CUDA device and allow multithreading.
|
||||
* Must be called once from the main thread.
|
||||
* Has no effect if HAVE_CUDA flag is not set.
|
||||
*/
|
||||
void vosk_gpu_init();
|
||||
|
||||
/**
|
||||
* Init CUDA device in a multi-threaded environment.
|
||||
* Must be called for each thread.
|
||||
* Has no effect if HAVE_CUDA flag is not set.
|
||||
*/
|
||||
void vosk_gpu_thread_init();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -42,5 +42,5 @@ RUN cd /opt \
|
||||
&& sed -i "s:-mfloat-abi=hard -mfpu=neon:${ARM_HARDWARE_OPTS}:g" makefiles/linux_openblas_arm.mk \
|
||||
&& sed -i "s: -O1 : -O3 :g" makefiles/linux_openblas_arm.mk \
|
||||
&& ./configure --mathlib=OPENBLAS_CLAPACK --shared --use-cuda=no \
|
||||
&& make -j 10 online2 lm \
|
||||
&& make -j 10 online2 lm rnnlm \
|
||||
&& find /opt/kaldi -name "*.o" -exec rm {} \;
|
||||
|
||||
@@ -62,4 +62,4 @@ RUN cd /opt/kaldi \
|
||||
--host=x86_64-w64-mingw32 --openblas-clapack-root=/opt/kaldi/local \
|
||||
--fst-root=/opt/kaldi/local --fst-version=1.8.0 \
|
||||
&& make depend -j \
|
||||
&& make -j $(nproc) online2 lm
|
||||
&& make -j $(nproc) online2 lm rnnlm
|
||||
|
||||
@@ -61,4 +61,4 @@ RUN cd /opt/kaldi \
|
||||
--host=i686-w64-mingw32 --openblas-clapack-root=/opt/kaldi/local \
|
||||
--fst-root=/opt/kaldi/local --fst-version=1.8.0 \
|
||||
&& make depend -j \
|
||||
&& make -j $(nproc) online2 lm
|
||||
&& make -j $(nproc) online2 lm rnnlm
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#!/bin/bash
|
||||
set -e -x
|
||||
|
||||
# Build so file
|
||||
cd /opt
|
||||
git clone https://github.com/alphacep/vosk-api
|
||||
cd /opt/vosk-api/src
|
||||
KALDI_ROOT=/opt/kaldi make -j $(nproc)
|
||||
|
||||
# Decide architecture name
|
||||
export VOSK_SOURCE=/opt/vosk-api
|
||||
case $CROSS_TRIPLE in
|
||||
*armv7-*)
|
||||
@@ -15,4 +17,10 @@ case $CROSS_TRIPLE in
|
||||
export VOSK_ARCHITECTURE=aarch64
|
||||
;;
|
||||
esac
|
||||
|
||||
# Copy library to output folder
|
||||
mkdir -p /io/wheelhouse/linux-$VOSK_ARCHITECTURE
|
||||
cp /opt/vosk-api/src/*.so /io/wheelhouse/linux-$VOSK_ARCHITECTURE
|
||||
|
||||
# Build wheel
|
||||
pip3 wheel /opt/vosk-api/python --no-deps -w /io/wheelhouse
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
#!/bin/bash
|
||||
set -e -x
|
||||
|
||||
# Build libvosk
|
||||
cd /opt
|
||||
git clone https://github.com/alphacep/vosk-api
|
||||
cd vosk-api/src
|
||||
CXX=x86_64-w64-mingw32-g++-posix EXT=dll KALDI_ROOT=/opt/kaldi/kaldi OPENFST_ROOT=/opt/kaldi/local OPENBLAS_ROOT=/opt/kaldi/local make -j $(nproc)
|
||||
|
||||
# Collect dependencies
|
||||
cp /usr/lib/gcc/x86_64-w64-mingw32/*-posix/libstdc++-6.dll /opt/vosk-api/src
|
||||
cp /usr/lib/gcc/x86_64-w64-mingw32/*-posix/libgcc_s_seh-1.dll /opt/vosk-api/src
|
||||
cp /usr/x86_64-w64-mingw32/lib/libwinpthread-1.dll /opt/vosk-api/src
|
||||
|
||||
# Copy dlls to output folder
|
||||
mkdir -p /io/wheelhouse/win64
|
||||
cp /opt/vosk-api/src/*.dll /io/wheelhouse/win64
|
||||
|
||||
# Build wheel and put to the output folder
|
||||
export VOSK_SOURCE=/opt/vosk-api
|
||||
export VOSK_PLATFORM=Windows
|
||||
export VOSK_ARCHITECTURE=64bit
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
#!/bin/bash
|
||||
set -e -x
|
||||
|
||||
# Build libvosk
|
||||
cd /opt
|
||||
git clone https://github.com/alphacep/vosk-api
|
||||
cd vosk-api/src
|
||||
CXX=i686-w64-mingw32-g++-posix EXT=dll KALDI_ROOT=/opt/kaldi/kaldi OPENFST_ROOT=/opt/kaldi/local OPENBLAS_ROOT=/opt/kaldi/local make -j $(nproc)
|
||||
|
||||
# Copy dependencies
|
||||
cp /usr/lib/gcc/i686-w64-mingw32/*-posix/libstdc++-6.dll /opt/vosk-api/src
|
||||
cp /usr/lib/gcc/i686-w64-mingw32/*-posix/libgcc_s_sjlj-1.dll /opt/vosk-api/src
|
||||
cp /usr/i686-w64-mingw32/lib/libwinpthread-1.dll /opt/vosk-api/src
|
||||
|
||||
# Copy dlls to output folder
|
||||
mkdir -p /io/wheelhouse/win32
|
||||
cp /opt/vosk-api/src/*.dll /io/wheelhouse/win32
|
||||
|
||||
# Build wheel and put to the output folder
|
||||
export VOSK_SOURCE=/opt/vosk-api
|
||||
export VOSK_PLATFORM=Windows
|
||||
export VOSK_ARCHITECTURE=32bit
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user