Compare commits
112 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0269a10833 | |||
| 7af3e9a334 | |||
| d666876be1 | |||
| 564fab7ec1 | |||
| 155c6c2a2a | |||
| d43cbe9344 | |||
| 57cc474c9f | |||
| 586603f8e1 | |||
| d57887d22a | |||
| 6183bcfc5f | |||
| 65f6113b4d | |||
| 8d88b89db1 | |||
| 62885e8963 | |||
| 9fc094a5da | |||
| f97383c17f | |||
| 4b892ec5e7 | |||
| 41035485db | |||
| 9696f4c917 | |||
| 55abf5f5ac | |||
| a1b2e41710 | |||
| dff4ab26e4 | |||
| 83b6e1cdf7 | |||
| 38dbaa15ea | |||
| 0e531b6061 | |||
| de94ef5537 | |||
| 6ef9d13877 | |||
| 1c7b94757d | |||
| 83486e0bef | |||
| 9787e8a53f | |||
| f59d6685ad | |||
| 8a986ef384 | |||
| 78f9f55e14 | |||
| c2e006f664 | |||
| 4b8e9737a5 | |||
| 722b09eaa4 | |||
| f5b4f5a1f2 | |||
| 4407d8da55 | |||
| 73b73527cd | |||
| 4df0e3a741 | |||
| 1a771e0172 | |||
| 4333c3c242 | |||
| b831c9ad57 | |||
| e5c08f7710 | |||
| 51c2968595 | |||
| 5993376322 | |||
| c4281622b9 | |||
| 9d26014de2 | |||
| f6c115d215 | |||
| 6bd102d778 | |||
| d3d6af5712 | |||
| 876093446f | |||
| 336f219f09 | |||
| 584251cbdc | |||
| a34995a788 | |||
| 998e5da227 | |||
| e04c15e367 | |||
| b81f69d407 | |||
| 0ac2064281 | |||
| 1d00bd244e | |||
| 8f5efc58c9 | |||
| a0c5ae1b5e | |||
| 99f48f9de1 | |||
| 1948b23f32 | |||
| c9eb572fc5 | |||
| 7d9895ff81 | |||
| d507210ef8 | |||
| db0a3d23d5 | |||
| f4f920f3cd | |||
| 75993ea276 | |||
| ee9bacb092 | |||
| b1e775c67b | |||
| d631e567aa | |||
| 9edf45be42 | |||
| 8b4b3c646a | |||
| 31bb0557d9 | |||
| 4593183cf9 | |||
| fdc45f1187 | |||
| b3d3c6d12c | |||
| f5f0794def | |||
| 55664fcca0 | |||
| afbf330f16 | |||
| 25aadf61bc | |||
| be47056467 | |||
| 8f623e0aea | |||
| 78025435e4 | |||
| 910455802e | |||
| b12914955c | |||
| dfe11eaf83 | |||
| 37fbe1a52b | |||
| af11bb2361 | |||
| 8da8697c1e | |||
| 944dc87531 | |||
| 5c4dd4644e | |||
| 9bbd172cfd | |||
| dbf9de77c3 | |||
| 8b790cd162 | |||
| 80219066e9 | |||
| 26fa5f098f | |||
| d75bb36131 | |||
| 30c5e8ca79 | |||
| c00e36fab6 | |||
| 80e60b9118 | |||
| e3a95a44bc | |||
| 86caf526f5 | |||
| e09f32b4b4 | |||
| 3803ab345d | |||
| 889b43136f | |||
| b517cf46af | |||
| ffd810fe00 | |||
| a1a0ed70a1 | |||
| 1554d9ede7 | |||
| bca0b86e37 |
+27
-1
@@ -5,6 +5,9 @@
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
@@ -24,14 +27,37 @@ wheelhouse
|
||||
__pycache__
|
||||
*.egg-info
|
||||
python/dist
|
||||
python/vosk/*.cc
|
||||
python/vosk/*.c
|
||||
python/vosk/*.h
|
||||
python/vosk/*.i
|
||||
python/vosk/vosk.py
|
||||
python/vosk/vosk_wrap.cpp
|
||||
python/test/db
|
||||
python/test/hyp
|
||||
python/test/model
|
||||
python/test/ref
|
||||
python/test/result.txt
|
||||
python/test/wav.scp
|
||||
|
||||
# Java
|
||||
*.so
|
||||
java/org
|
||||
java/model-en
|
||||
java/*.cc
|
||||
java/model-spk/
|
||||
java/model/
|
||||
|
||||
# CSharp
|
||||
csharp/gen
|
||||
csharp/*.exe
|
||||
csharp/*.c
|
||||
csharp/model/
|
||||
csharp/test.wav
|
||||
|
||||
# Node
|
||||
nodejs/vosk_wrap.cc
|
||||
nodejs/example/model
|
||||
nodejs/example/test.wav
|
||||
nodejs/node_modules
|
||||
nodejs/package-lock.json
|
||||
nodejs/build
|
||||
|
||||
@@ -1,134 +1,25 @@
|
||||
[](https://travis-ci.com/alphacep/vosk-api)
|
||||
# About
|
||||
|
||||
Language bindings for Vosk and Kaldi to access speech recognition from various languages and on various platforms
|
||||
Vosk is an offline open source speech recognition toolkit. It enables
|
||||
speech recognition models for 16 languages and dialects - English, Indian
|
||||
English, German, French, Spanish, Portuguese, Chinese, Russian, Turkish,
|
||||
Vietnamese, Italian, Dutch, Catalan, Arabic, Greek, Farsi.
|
||||
|
||||
* Python on Linux, Windows and RPi
|
||||
* Node
|
||||
* Android
|
||||
* iOS
|
||||
Vosk models are small (50 Mb) but provide continuous large vocabulary
|
||||
transcription, zero-latency response with streaming API, reconfigurable
|
||||
vocabulary and speaker identification.
|
||||
|
||||
## Android build
|
||||
Speech recognition bindings implemented for various programming languages
|
||||
like Python, Java, Node.JS, C#, C++ and others.
|
||||
|
||||
```
|
||||
cd android
|
||||
gradle build
|
||||
```
|
||||
Vosk supplies speech recognition for chatbots, smart home appliances,
|
||||
virtual assistants. It can also create subtitles for movies,
|
||||
transcription for lectures and interviews.
|
||||
|
||||
Please note that medium blog post about 64-bit is not relevant anymore, the script builds x86, arm64 and armv7 libraries automatically without any modifications.
|
||||
Vosk scales from small devices like Raspberry Pi or Android smartphone to
|
||||
big clusters.
|
||||
|
||||
For example of Android application using Vosk-API check https://github.com/alphacep/kaldi-android-demo project
|
||||
# Documentation
|
||||
|
||||
## iOS build
|
||||
|
||||
Available on request. Drop as a mail at [contact@alphacephei.com](mailto:contact@alphacephei.com).
|
||||
|
||||
## Python installation from Pypi
|
||||
|
||||
The easiest way to install vosk api is with pip. You do not have to compile anything. We currently support only Linux on x86_64 and Raspberry Pi. Other systems (windows, mac) will come soon.
|
||||
|
||||
Make sure you have newer pip and python:
|
||||
|
||||
* Python version >= 3.4
|
||||
* pip version >= 19.0
|
||||
|
||||
Uprade python and pip if needed. Then install vosk on Linux with a simple command
|
||||
|
||||
```
|
||||
pip3 install vosk
|
||||
```
|
||||
|
||||
## Websocket Server and GRPC server
|
||||
|
||||
We also provide a websocket server and grpc server which can be used in telephony and other applications. With bigger models adapted for 8khz audio it provides more accuracy.
|
||||
|
||||
The server is installed with docker and can run with a single command:
|
||||
|
||||
```
|
||||
docker run -d -p 2700:2700 alphacep/kaldi-en:latest
|
||||
```
|
||||
|
||||
For details see https://github.com/alphacep/vosk-server
|
||||
|
||||
|
||||
## Compilation from source
|
||||
|
||||
If you still want to build from scratch, you can compile Kaldi and Vosk yourself. The compilation is straightforward but might be a little confusing for newbie. In case you want to follow this, please watch the errors.
|
||||
|
||||
#### Kaldi compilation for local python, node and java modules
|
||||
|
||||
```
|
||||
git clone -b lookahead --single-branch https://github.com/alphacep/kaldi
|
||||
cd kaldi/tools
|
||||
make
|
||||
```
|
||||
|
||||
install all dependencies and repeat `make` if needed
|
||||
|
||||
```
|
||||
extras/install_openblas.sh
|
||||
cd ../src
|
||||
./configure --mathlib=OPENBLAS --shared --use-cuda=no
|
||||
make -j 10
|
||||
```
|
||||
|
||||
#### Python module build
|
||||
|
||||
Then build the python module
|
||||
|
||||
```
|
||||
export KALDI_ROOT=<KALDI_ROOT>
|
||||
cd python
|
||||
python3 setup.py install
|
||||
```
|
||||
|
||||
#### Java example API build
|
||||
|
||||
Or Java
|
||||
|
||||
```
|
||||
cd java && KALDI_ROOT=<KALDI_ROOT> make
|
||||
wget https://github.com/alphacep/kaldi-android-demo/releases/download/2020-01/alphacep-model-android-en-us-0.3.tar.gz
|
||||
tar xf alphacep-model-android-en-us-0.3.tar.gz
|
||||
mv alphacep-model-android-en-us-0.3 model
|
||||
make run
|
||||
```
|
||||
|
||||
#### C# build
|
||||
|
||||
Or C#
|
||||
|
||||
```
|
||||
cd csharp && KALDI_ROOT=<KALDI_ROOT> make
|
||||
wget https://github.com/alphacep/kaldi-android-demo/releases/download/2020-01/alphacep-model-android-en-us-0.3.tar.gz
|
||||
tar xf alphacep-model-android-en-us-0.3.tar.gz
|
||||
mv alphacep-model-android-en-us-0.3 model
|
||||
mono test.exe
|
||||
```
|
||||
|
||||
## Running the example code with python
|
||||
|
||||
Run like this:
|
||||
|
||||
```
|
||||
cd vosk-api/python/example
|
||||
wget https://github.com/alphacep/kaldi-android-demo/releases/download/2020-01/alphacep-model-android-en-us-0.3.tar.gz
|
||||
tar xf alphacep-model-android-en-us-0.3.tar.gz
|
||||
mv alphacep-model-android-en-us-0.3 model-en
|
||||
python3 ./test_simple.py test.wav
|
||||
```
|
||||
|
||||
To run with your audio file make sure it has proper format - PCM 16khz 16bit mono, otherwise decoding will not work.
|
||||
|
||||
You can find other examples of using a microphone, decoding with a fixed small vocabulary or speaker identification setup in [python/example subfolder](https://github.com/alphacep/vosk-api/tree/master/python/example)
|
||||
|
||||
## Models for different languages
|
||||
|
||||
For information about models see [the documentation on available models](https://github.com/alphacep/vosk-api/blob/master/doc/models.md).
|
||||
|
||||
## Contact Us
|
||||
|
||||
If you have any questions, feel free to
|
||||
|
||||
* Post an issue here on github
|
||||
* Send us an e-mail at [contact@alphacephei.com](mailto:contact@alphacephei.com)
|
||||
* Join our group dedicated to speech recognition on Telegram [@speech_recognition](https://t.me/speech_recognition)
|
||||
For installation instructions, examples and documentation visit [Vosk
|
||||
Website](https://alphacephei.com/vosk).
|
||||
|
||||
@@ -8,6 +8,9 @@ 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")
|
||||
@@ -19,6 +22,8 @@ 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"
|
||||
@@ -29,14 +34,14 @@ set(API_SOURCES
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -DFST_NO_DYNAMIC_LINKING")
|
||||
|
||||
add_library( kaldi_jni SHARED
|
||||
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( kaldi_jni
|
||||
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
|
||||
@@ -45,6 +50,7 @@ target_link_libraries( kaldi_jni
|
||||
${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
|
||||
|
||||
+29
-18
@@ -31,36 +31,38 @@ 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/linux-x86_64
|
||||
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
|
||||
PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin
|
||||
PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/${OS_NAME}-x86_64/bin
|
||||
OPENFST_VERSION=1.6.7
|
||||
|
||||
mkdir -p $WORKDIR_ARM64/local/lib $WORKDIR_ARM32/local/lib $WORKDIR_X86_64/local/lib
|
||||
mkdir -p $WORKDIR_ARM64/local/lib $WORKDIR_ARM32/local/lib $WORKDIR_X86_64/local/lib $WORKDIR_X86/local/lib
|
||||
|
||||
# Build standalone CLAPACK since gfortran is missing
|
||||
cd build
|
||||
git clone https://github.com/simonlynen/android_libs
|
||||
cd android_libs/lapack
|
||||
sed -i 's/APP_STL := gnustl_static/APP_STL := c++_static/g' jni/Application.mk && \
|
||||
sed -i 's/android-10/android-21/g' project.properties && \
|
||||
sed -i 's/APP_ABI := armeabi armeabi-v7a/APP_ABI := armeabi-v7a arm64-v8a x86_64/g' jni/Application.mk && \
|
||||
sed -i 's/LOCAL_MODULE:= testlapack/#LOCAL_MODULE:= testlapack/g' jni/Android.mk && \
|
||||
sed -i 's/LOCAL_SRC_FILES:= testclapack.cpp/#LOCAL_SRC_FILES:= testclapack.cpp/g' jni/Android.mk && \
|
||||
sed -i 's/LOCAL_STATIC_LIBRARIES := lapack/#LOCAL_STATIC_LIBRARIES := lapack/g' jni/Android.mk && \
|
||||
sed -i 's/include $(BUILD_SHARED_LIBRARY)/#include $(BUILD_SHARED_LIBRARY)/g' jni/Android.mk && \
|
||||
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; do
|
||||
for arch in arm32 arm64 x86_64 x86; do
|
||||
#for arch in x86_64; do
|
||||
|
||||
case $arch in
|
||||
@@ -91,6 +93,15 @@ case $arch in
|
||||
CXX=x86_64-linux-android21-clang++
|
||||
ARCHFLAGS=""
|
||||
;;
|
||||
x86)
|
||||
BLAS_ARCH=ATOM
|
||||
WORKDIR=$WORKDIR_X86
|
||||
HOST=i686-linux-android
|
||||
AR=i686-linux-android-ar
|
||||
CC=i686-linux-android21-clang
|
||||
CXX=i686-linux-android21-clang++
|
||||
ARCHFLAGS=""
|
||||
;;
|
||||
esac
|
||||
|
||||
# openblas first
|
||||
@@ -101,12 +112,9 @@ make -C OpenBLAS install PREFIX=$WORKDIR/local
|
||||
|
||||
# tools directory --> we'll only compile OpenFST
|
||||
cd $WORKDIR
|
||||
wget -c -T 10 -t 1 http://www.openfst.org/twiki/pub/FST/FstDownload/openfst-${OPENFST_VERSION}.tar.gz || \
|
||||
wget -c -T 10 -t 3 http://www.openslr.org/resources/2/openfst-${OPENFST_VERSION}.tar.gz
|
||||
|
||||
tar -zxvf openfst-${OPENFST_VERSION}.tar.gz
|
||||
cd openfst-${OPENFST_VERSION}
|
||||
|
||||
git clone https://github.com/alphacep/openfst
|
||||
cd openfst
|
||||
autoreconf -i
|
||||
CXX=$CXX CXXFLAGS="$ARCHFLAGS -O3 -DFST_NO_DYNAMIC_LINKING" ./configure --prefix=${WORKDIR}/local \
|
||||
--enable-shared --enable-static --with-pic --disable-bin \
|
||||
--enable-lookahead-fsts --enable-ngram-fsts --host=$HOST --build=x86-linux-gnu
|
||||
@@ -117,6 +125,9 @@ 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 --shared \
|
||||
|
||||
+46
-4
@@ -5,9 +5,17 @@ buildscript {
|
||||
}
|
||||
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" version "1.8.5"
|
||||
}
|
||||
|
||||
def archiveName = "vosk-android"
|
||||
def libVersion = "0.3.15"
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
@@ -16,22 +24,23 @@ allprojects {
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
versionCode 5
|
||||
versionName "5.2"
|
||||
setProperty("archivesBaseName", "kaldi-android-$versionName")
|
||||
versionCode 6
|
||||
versionName = libVersion
|
||||
archivesBaseName = archiveName
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments "-DCMAKE_VERBOSE_MAKEFILE=ON", "-DANDROID_ARM_NEON=TRUE", "-DCMAKE_CXX_FLAGS_RELEASE=-O3"
|
||||
}
|
||||
}
|
||||
ndk {
|
||||
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86_64'
|
||||
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86_64', 'x86'
|
||||
}
|
||||
}
|
||||
sourceSets {
|
||||
@@ -46,6 +55,39 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task swig {
|
||||
doLast {
|
||||
mkdir 'build/generated-src/java'
|
||||
|
||||
@@ -241,10 +241,6 @@ public class Assets {
|
||||
if (!items.get(path).equals(externalItems.get(path))
|
||||
|| !(new File(externalDir, path).exists()))
|
||||
newItems.add(path);
|
||||
else
|
||||
Log.i(TAG,
|
||||
String.format("Skipping asset %s: checksums are equal", path));
|
||||
|
||||
}
|
||||
|
||||
unusedItems.addAll(externalItems.keySet());
|
||||
@@ -252,13 +248,11 @@ public class Assets {
|
||||
|
||||
for (String path : newItems) {
|
||||
File file = copy(path);
|
||||
Log.i(TAG, String.format("Copying asset %s to %s", path, file));
|
||||
}
|
||||
|
||||
for (String path : unusedItems) {
|
||||
File file = new File(externalDir, path);
|
||||
file.delete();
|
||||
Log.i(TAG, String.format("Removing asset %s", file));
|
||||
}
|
||||
|
||||
updateItemList(items);
|
||||
|
||||
+17
-23
@@ -29,19 +29,18 @@ import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Main class to access recognizer functions. After configuration this class
|
||||
* starts a listener thread which records the data and recognizes it using
|
||||
* VOSK engine. Recognition events are passed to a client using
|
||||
* 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 SpeechRecognizer {
|
||||
public class SpeechService {
|
||||
|
||||
protected static final String TAG = SpeechRecognizer.class.getSimpleName();
|
||||
protected static final String TAG = SpeechService.class.getSimpleName();
|
||||
|
||||
private final KaldiRecognizer recognizer;
|
||||
|
||||
private final int sampleRate;
|
||||
private final int sampleRate;
|
||||
private final static float BUFFER_SIZE_SECONDS = 0.4f;
|
||||
private int bufferSize;
|
||||
private final AudioRecord recorder;
|
||||
@@ -53,17 +52,18 @@ public class SpeechRecognizer {
|
||||
private final Collection<RecognitionListener> listeners = new HashSet<RecognitionListener>();
|
||||
|
||||
/**
|
||||
* Creates speech recognizer. Recognizer holds the AudioRecord object, so you
|
||||
* Creates speech service. Service holds the AudioRecord object, so you
|
||||
* need to call {@link release} in order to properly finalize it.
|
||||
*
|
||||
* @throws IOException thrown if audio recorder can not be created for some reason.
|
||||
*/
|
||||
public SpeechRecognizer(Model model) throws IOException {
|
||||
recognizer = new KaldiRecognizer(model, 16000.0f);
|
||||
sampleRate = 16000;
|
||||
bufferSize = Math.round(sampleRate * BUFFER_SIZE_SECONDS);
|
||||
public SpeechService(KaldiRecognizer recognizer, float sampleRate) throws IOException {
|
||||
this.recognizer = recognizer;
|
||||
this.sampleRate = (int)sampleRate;
|
||||
|
||||
bufferSize = Math.round(this.sampleRate * BUFFER_SIZE_SECONDS);
|
||||
recorder = new AudioRecord(
|
||||
AudioSource.VOICE_RECOGNITION, sampleRate,
|
||||
AudioSource.VOICE_RECOGNITION, this.sampleRate,
|
||||
AudioFormat.CHANNEL_IN_MONO,
|
||||
AudioFormat.ENCODING_PCM_16BIT, bufferSize * 2);
|
||||
|
||||
@@ -148,8 +148,7 @@ public class SpeechRecognizer {
|
||||
public boolean stop() {
|
||||
boolean result = stopRecognizerThread();
|
||||
if (result) {
|
||||
Log.i(TAG, "Stop recognition");
|
||||
mainHandler.post(new ResultEvent(recognizer.FinalResult(), true));
|
||||
mainHandler.post(new ResultEvent(recognizer.Result(), true));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -162,10 +161,7 @@ public class SpeechRecognizer {
|
||||
*/
|
||||
public boolean cancel() {
|
||||
boolean result = stopRecognizerThread();
|
||||
if (result) {
|
||||
Log.i(TAG, "Cancel recognition");
|
||||
}
|
||||
|
||||
recognizer.Result(); // Reset recognizer state
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -175,9 +171,9 @@ public class SpeechRecognizer {
|
||||
public void shutdown() {
|
||||
recorder.release();
|
||||
}
|
||||
|
||||
|
||||
private final class RecognizerThread extends Thread {
|
||||
|
||||
|
||||
private int remainingSamples;
|
||||
private int timeoutSamples;
|
||||
private final static int NO_TIMEOUT = -1;
|
||||
@@ -206,8 +202,6 @@ public class SpeechRecognizer {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(TAG, "Starting decoding");
|
||||
|
||||
short[] buffer = new short[bufferSize];
|
||||
|
||||
while (!interrupted()
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
KALDI_ROOT=$(HOME)/travis/kaldi
|
||||
|
||||
VOSK_SOURCES= \
|
||||
../src/kaldi_recognizer.cc \
|
||||
../src/language_model.cc \
|
||||
../src/model.cc \
|
||||
../src/spk_model.cc \
|
||||
../src/vosk_api.cc
|
||||
|
||||
CFLAGS=-g -O2 -DFST_NO_DYNAMIC_LINKING -I../src -I$(KALDI_ROOT)/src -I$(KALDI_ROOT)/tools/openfst/include
|
||||
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/OpenBLAS/libopenblas.a \
|
||||
$(KALDI_ROOT)/tools/openfst/lib/libfst.a \
|
||||
$(KALDI_ROOT)/tools/openfst/lib/libfstngram.a
|
||||
|
||||
all: test_vosk test_vosk_speaker
|
||||
|
||||
test_vosk: test_vosk.o libvosk.a
|
||||
g++ $^ -o $@ $(LIBS) -lgfortran -lpthread
|
||||
|
||||
test_vosk_speaker: test_vosk_speaker.o libvosk.a
|
||||
g++ $^ -o $@ $(LIBS) -lgfortran -lpthread
|
||||
|
||||
libvosk.a: $(VOSK_SOURCES:.cc=.o)
|
||||
ar rcs $@ $^
|
||||
|
||||
%.o: %.c
|
||||
g++ $(CFLAGS) -c -o $@ $<
|
||||
|
||||
%.o: %.cc
|
||||
g++ -std=c++11 $(CFLAGS) -c -o $@ $<
|
||||
|
||||
clean:
|
||||
rm -f *.o *.a test_vosk
|
||||
@@ -0,0 +1,28 @@
|
||||
#include <vosk_api.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
FILE *wavin;
|
||||
char buf[3200];
|
||||
int nread, final;
|
||||
|
||||
VoskModel *model = vosk_model_new("model");
|
||||
VoskRecognizer *recognizer = vosk_recognizer_new(model, 16000.0);
|
||||
|
||||
wavin = fopen("test.wav", "rb");
|
||||
fseek(wavin, 44, SEEK_SET);
|
||||
while (!feof(wavin)) {
|
||||
nread = fread(buf, 1, sizeof(buf), wavin);
|
||||
final = vosk_recognizer_accept_waveform(recognizer, buf, nread);
|
||||
if (final) {
|
||||
printf("%s\n", vosk_recognizer_result(recognizer));
|
||||
} else {
|
||||
printf("%s\n", vosk_recognizer_partial_result(recognizer));
|
||||
}
|
||||
}
|
||||
printf("%s\n", vosk_recognizer_final_result(recognizer));
|
||||
|
||||
vosk_recognizer_free(recognizer);
|
||||
vosk_model_free(model);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#include <vosk_api.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
FILE *wavin;
|
||||
char buf[3200];
|
||||
int nread, final;
|
||||
|
||||
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);
|
||||
|
||||
wavin = fopen("test.wav", "rb");
|
||||
fseek(wavin, 44, SEEK_SET);
|
||||
while (!feof(wavin)) {
|
||||
nread = fread(buf, 1, sizeof(buf), wavin);
|
||||
final = vosk_recognizer_accept_waveform(recognizer, buf, nread);
|
||||
if (final) {
|
||||
printf("%s\n", vosk_recognizer_result(recognizer));
|
||||
} else {
|
||||
printf("%s\n", vosk_recognizer_partial_result(recognizer));
|
||||
}
|
||||
}
|
||||
printf("%s\n", vosk_recognizer_final_result(recognizer));
|
||||
|
||||
vosk_recognizer_free(recognizer);
|
||||
vosk_spk_model_free(spk_model);
|
||||
vosk_model_free(model);
|
||||
return 0;
|
||||
}
|
||||
+21
-12
@@ -11,6 +11,7 @@ KALDI_LIBS = \
|
||||
${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 \
|
||||
@@ -19,9 +20,12 @@ KALDI_LIBS = \
|
||||
${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/libopenblas.a \
|
||||
-lgfortran -lstdc++
|
||||
${KALDI_ROOT}/tools/openfst/lib/libfstngram.a
|
||||
|
||||
# On Linux
|
||||
MATH_LIBS = ${KALDI_ROOT}/tools/OpenBLAS/libopenblas.a -lgfortran
|
||||
# On OSX
|
||||
# MATH_LIBS = -framework Accelerate
|
||||
|
||||
all: test.exe
|
||||
|
||||
@@ -31,22 +35,27 @@ test.exe: libkaldiwrap.so test.cs
|
||||
VOSK_SOURCES = \
|
||||
vosk_wrap.c \
|
||||
../src/kaldi_recognizer.cc \
|
||||
../src/kaldi_recognizer.h \
|
||||
../src/language_model.cc \
|
||||
../src/model.cc \
|
||||
../src/model.h \
|
||||
../src/spk_model.cc \
|
||||
../src/spk_model.h \
|
||||
../src/vosk_api.cc \
|
||||
../src/vosk_api.h
|
||||
../src/vosk_api.cc
|
||||
|
||||
libkaldiwrap.so: $(VOSK_SOURCES)
|
||||
$(CXX) -fpermissive $(CFLAGS) $(CPPFLAGS) -shared -o $@ $(VOSK_SOURCES) $(KALDI_LIBS)
|
||||
VOSK_HEADERS = \
|
||||
../src/kaldi_recognizer.h \
|
||||
../src/language_model.h \
|
||||
../src/model.h \
|
||||
../src/vosk_api.h \
|
||||
../src/spk_model.h
|
||||
|
||||
libkaldiwrap.so: $(VOSK_SOURCES) $(VOSK_HEADERS)
|
||||
$(CXX) -fpermissive $(CFLAGS) $(CPPFLAGS) -shared -o $@ $(VOSK_SOURCES) $(KALDI_LIBS) $(MATH_LIBS)
|
||||
|
||||
vosk_wrap.c: ../src/vosk.i
|
||||
swig -csharp -DSWIG_CSHARP_NO_EXCEPTION_HELPER -dllimport "libkaldiwrap.so" \
|
||||
mkdir -p gen
|
||||
swig -csharp -DSWIG_CSHARP_NO_EXCEPTION_HELPER -dllimport "libkaldiwrap" \
|
||||
-namespace "Kaldi" -outdir gen -o vosk_wrap.c ../src/vosk.i
|
||||
|
||||
run:
|
||||
run: test.exe
|
||||
mono test.exe
|
||||
|
||||
clean:
|
||||
|
||||
@@ -7,6 +7,7 @@ public class Test
|
||||
public static void Main()
|
||||
{
|
||||
|
||||
Vosk.SetLogLevel(0);
|
||||
Model model = new Model("model");
|
||||
KaldiRecognizer rec = new KaldiRecognizer(model, 16000.0f);
|
||||
|
||||
|
||||
+1
-21
@@ -1,21 +1 @@
|
||||
## Accuracy issues
|
||||
|
||||
Accuracy of modern systems is still unstable, that means sometimes you can have a very good accuracy and sometimes it could be bad.
|
||||
It is hard to make a system that will work good. And there could be many reasons for that:
|
||||
|
||||
* Audio has very bad quality
|
||||
* Vocabulary of the system doesn't match (yes, we still use fixed vocabulary)
|
||||
* Audio conditions like accent were not really the ones that were used in training
|
||||
* Some unpredictable audio issues like frame drop or frame coding bugs
|
||||
* Software bugs
|
||||
|
||||
It is hard to guess what is going on under the hood without getting your hands dirty. For that reason in case of any accuracy
|
||||
issues you must provide the following for analysis:
|
||||
|
||||
* Who are you, where are you from and why are you doing that. We don't like dealing with anonymous
|
||||
* The complete and exact description of the system you want to build - what is it going to do, what do you want to build
|
||||
* The precise description of hardware you are trying to run the system on
|
||||
* The detailed list of software versions you are using
|
||||
* Audio samples to demonstrate the problem together with the reference transcription for those samples
|
||||
|
||||
Remember, the more information you provide the faster you get a solution.
|
||||
See https://alphacephei.com/vosk/accuracy
|
||||
|
||||
+1
-42
@@ -1,42 +1 @@
|
||||
## Updating the language model
|
||||
|
||||
The Kaldi model used in Vosk is compiled from 3 data sources:
|
||||
|
||||
* dictionary
|
||||
* acoustic model
|
||||
* language model
|
||||
|
||||
You can rebuild all three with different level of effort, but sometimes you just
|
||||
need to adjust the probability of the words to improve the recognition. For
|
||||
that it is enough to recompile the language model from the text. To do that
|
||||
|
||||
1) Take a text that reflects the speech you want to recognize
|
||||
2) Remove punctuation, convert everything to the lowercase, you can do it with a python script
|
||||
3) Build openfst and opengrm inside kaldi
|
||||
|
||||
```
|
||||
export KALDI_ROOT=`pwd`/kaldi
|
||||
git clone https://github.com/kaldi-asr/kaldi
|
||||
cd kaldi/tools
|
||||
make
|
||||
# install all required dependencies and repeat `make` if needed
|
||||
extras/install_opengrm.sh
|
||||
```
|
||||
|
||||
4) Now lets build a grammar
|
||||
|
||||
```
|
||||
export PATH=$KALDI_ROOT/tools/openfst/bin:$PATH
|
||||
export LD_LIBRARY_PATH=$KALDI_ROOT/tools/openfst/lib/fst
|
||||
cd model
|
||||
fstsymbols --save_osymbols=words.txt Gr.fst > /dev/null
|
||||
farcompilestrings --fst_type=compact --symbols=words.txt --keep_symbols text.txt | \
|
||||
ngramcount | ngrammake | \
|
||||
fstconvert --fst_type=ngram > Gr.fst
|
||||
```
|
||||
|
||||
Use created Gr.fst instead of standard one in your model.
|
||||
|
||||
For more details see OpenGRM documentation http://www.opengrm.org/twiki/bin/view/GRM/NGramLibrary
|
||||
|
||||
You can not introduce new words this way, that is something we will cover later.
|
||||
See https://alphacephei.com/vosk/adaptation
|
||||
|
||||
+1
-67
@@ -1,67 +1 @@
|
||||
# Models
|
||||
|
||||
This is the list of models compatible with Vosk-API.
|
||||
|
||||
To add a new model here create an issue on Github.
|
||||
|
||||
### English
|
||||
|
||||
| Model | Size | Accuracy | Notes |
|
||||
|-----------------------------------------------------------------------------------------------------------|-------|------------|----------------------------------------------------------------------------------------------|
|
||||
| [kaldi-en-us-aspire-0.1](http://alphacephei.com/kaldi/kaldi-en-us-aspire-0.1.tar.gz) | 363M | TBD | Trained on Fisher + more or less recent LM. Pretty outdated but still ok even even for calls |
|
||||
| [alphacep-model-android-en-us-0.3](http://alphacephei.com/kaldi/alphacep-model-android-en-us-0.3.tar.gz) | 36M | TBD | Lightweight wideband model for Android and RPi |
|
||||
|
||||
### Chinese
|
||||
|
||||
| Model | Size | Accuracy | Notes |
|
||||
|-----------------------------------------------------------------------------------------------------------|-------|------------|----------------------------------------------------------------------------------------------|
|
||||
| [kaldi-cn-0.1.tar.gz](http://alphacephei.com/kaldi/kaldi-cn-0.1.tar.gz) | 195M | TBD | Big narrowband Chinese model for server processing |
|
||||
| [alphacep-model-android-cn-0.3](http://alphacephei.com/kaldi/alphacep-model-android-cn-0.3.tar.gz) | 32M | TBD | Lightweight wideband model for Android and RPi |
|
||||
|
||||
### Russian
|
||||
|
||||
| Model | Size | Accuracy | Notes |
|
||||
|-----------------------------------------------------------------------------------------------------------|-------|------------|----------------------------------------------------------------------------------------------|
|
||||
| [kaldi-ru-0.9.tar.gz](http://alphacephei.com/kaldi/kaldi-ru-0.9.tar.gz) | 2.5G | TBD | Big narrowband Russian model for server processing |
|
||||
| [alphacep-model-android-ru-0.3](http://alphacephei.com/kaldi/alphacep-model-android-ru-0.3.tar.gz) | 39M | TBD | Lightweight wideband model for Android and RPi |
|
||||
|
||||
### French
|
||||
|
||||
| Model | Size | Accuracy | Notes |
|
||||
|-------------------------------------------------------------------------------------------------------------------------|-------|------------|----------------------------------------------------------------------------------------------|
|
||||
| [alphacep-model-android-fr-pguyot-0.3](http://alphacephei.com/kaldi/alphacep-model-android-fr-pguyot-0.3.tar.gz) | 39M | TBD | Lightweight wideband model for Android and RPi trained by [Paul Guyot](https://github.com/pguyot/zamia-speech/releases) |
|
||||
|
||||
### German
|
||||
|
||||
| Model | Size | Accuracy | Notes |
|
||||
|-----------------------------------------------------------------------------------------------------------|-------|------------|------------------------------------------------------------------------------------------------------|
|
||||
| [tuda-de](http://ltdata1.informatik.uni-hamburg.de/kaldi_tuda_de/de_400k_nnet3chain_tdnn1f_2048_sp_bi.tar.bz2) | 566M | TBD | Wideband server model from [tuda-de](https://github.com/uhh-lt/kaldi-tuda-de) |
|
||||
| [alphacep-model-android-de-zamia-0.3](http://alphacephei.com/kaldi/alphacep-model-android-de-zamia-0.3.tar.gz) | 49M | TBD | Lightweight wideband model for Android and RPi |
|
||||
|
||||
### Spanish
|
||||
|
||||
| Model | Size | Accuracy | Notes |
|
||||
|-----------------------------------------------------------------------------------------------------------|-------|------------|----------------------------------------------------------------------------------------------|
|
||||
| [alphacep-model-android-es-0.3](http://alphacephei.com/kaldi/alphacep-model-android-es-0.3.tar.gz) | 33M | TBD | Lightweight wideband model for Android and RPi |
|
||||
|
||||
### Portuguese
|
||||
|
||||
| Model | Size | Accuracy | Notes |
|
||||
|-----------------------------------------------------------------------------------------------------------|-------|------------|----------------------------------------------------------------------------------------------|
|
||||
| [alphacep-model-android-pt-0.3](http://alphacephei.com/kaldi/alphacep-model-android-pt-0.3.tar.gz) | 31M | TBD | Lightweight wideband model for Android and RPi |
|
||||
|
||||
### Dutch
|
||||
|
||||
https://github.com/opensource-spraakherkenning-nl/Kaldi_NL
|
||||
|
||||
### Greek
|
||||
|
||||
| Model | Size | Accuracy | Notes |
|
||||
|-----------------------------------------------------------------------------------------------------------|-------|------------|----------------------------------------------------------------------------------------------|
|
||||
| [kaldi-el-gr-0.6.tar.gz](http://alphacephei.com/kaldi/kaldi-el-gr-0.6.tar.gz) | 1.1G | TBD | Big narrowband Greek model for server processing, not extremely accurate though |
|
||||
|
||||
### Vietnamese
|
||||
|
||||
| Model | Size | Accuracy | Notes |
|
||||
|-----------------------------------------------------------------------------------------------------------|-------|------------|----------------------------------------------------------------------------------------------|
|
||||
| [alphacep-model-android-vn-0.3](http://alphacephei.com/kaldi/alphacep-model-android-vn-0.3.tar.gz) | 32M | TBD | Lightweight wideband model for Android and RPi |
|
||||
See https://alphacephei.com/vosk/models
|
||||
|
||||
@@ -12,37 +12,199 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/* This header contains the C API for Vosk speech recognition system */
|
||||
|
||||
#ifndef _VOSK_API_H_
|
||||
#define _VOSK_API_H_
|
||||
#ifndef VOSK_API_H
|
||||
#define VOSK_API_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Model stores all the data required for recognition
|
||||
* it contains static data and can be shared across processing
|
||||
* threads. */
|
||||
typedef struct VoskModel VoskModel;
|
||||
|
||||
|
||||
/** Speaker model is the same as model but contains the data
|
||||
* for speaker identification. */
|
||||
typedef struct VoskSpkModel VoskSpkModel;
|
||||
|
||||
|
||||
/** Recognizer object is the main object which processes data.
|
||||
* Each recognizer usually runs in own thread and takes audio as input.
|
||||
* Once audio is processed recognizer returns JSON object as a string
|
||||
* which represent decoded information - words, confidences, times, n-best lists,
|
||||
* speaker information and so on */
|
||||
typedef struct VoskRecognizer VoskRecognizer;
|
||||
|
||||
|
||||
/** Loads model data from the file and returns the model object
|
||||
*
|
||||
* @param model_path: the path of the model on the filesystem
|
||||
@ @returns model object */
|
||||
VoskModel *vosk_model_new(const char *model_path);
|
||||
|
||||
|
||||
/** 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. */
|
||||
void vosk_model_free(VoskModel *model);
|
||||
|
||||
|
||||
/** Loads speaker model data from the file and returns the model object
|
||||
*
|
||||
* @param model_path: the path of the model on the filesystem
|
||||
* @returns model object */
|
||||
VoskSpkModel *vosk_spk_model_new(const char *model_path);
|
||||
|
||||
|
||||
/** 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. */
|
||||
void vosk_spk_model_free(VoskSpkModel *model);
|
||||
|
||||
/** Creates the recognizer object
|
||||
*
|
||||
* The recognizers process the speech and return text using shared model data
|
||||
* @param sample_rate The sample rate of the audio you going to feed into the recognizer
|
||||
* @returns recognizer object */
|
||||
VoskRecognizer *vosk_recognizer_new(VoskModel *model, float sample_rate);
|
||||
|
||||
|
||||
/** Creates the recognizer object with speaker recognition
|
||||
*
|
||||
* 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
|
||||
* @returns recognizer object */
|
||||
VoskRecognizer *vosk_recognizer_new_spk(VoskModel *model, VoskSpkModel *spk_model, float sample_rate);
|
||||
|
||||
|
||||
/** Creates the recognizer object with the grammar
|
||||
*
|
||||
* Sometimes when you want to improve recognition accuracy and when you don't need
|
||||
* to recognize large vocabulary you can specify a list of words 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.
|
||||
*
|
||||
* @param sample_rate The sample rate of the audio you going to feed into the recognizer
|
||||
* @param grammar The string with the list of words to recognize, for example "one two three four five [unk]"
|
||||
*
|
||||
* @returns recognizer object */
|
||||
VoskRecognizer *vosk_recognizer_new_grm(VoskModel *model, float sample_rate, const char *grammar);
|
||||
|
||||
|
||||
/** Accept voice data
|
||||
*
|
||||
* accept and process new chunk of voice data
|
||||
*
|
||||
* @param data - audio data in PCM 16-bit mono format
|
||||
* @param length - length of the audio data
|
||||
* @returns true if silence is occured and you can retrieve a new utterance with result method */
|
||||
int vosk_recognizer_accept_waveform(VoskRecognizer *recognizer, const char *data, int length);
|
||||
|
||||
|
||||
/** Same as above but the version with the short data for language bindings where you have
|
||||
* audio as array of shorts */
|
||||
int vosk_recognizer_accept_waveform_s(VoskRecognizer *recognizer, const short *data, int length);
|
||||
|
||||
|
||||
/** Same as above but the version with the float data for language bindings where you have
|
||||
* audio as array of floats */
|
||||
int vosk_recognizer_accept_waveform_f(VoskRecognizer *recognizer, const float *data, int length);
|
||||
|
||||
|
||||
/** Returns speech recognition result
|
||||
*
|
||||
* @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>
|
||||
*/
|
||||
const char *vosk_recognizer_result(VoskRecognizer *recognizer);
|
||||
|
||||
|
||||
/** Returns partial speech recognition
|
||||
*
|
||||
* @returns partial speech recognition text which is not yet finalized.
|
||||
* result may change as recognizer process more data.
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* "partial" : "cyril one eight zero"
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
const char *vosk_recognizer_partial_result(VoskRecognizer *recognizer);
|
||||
|
||||
|
||||
/** 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 speech result in JSON format.
|
||||
*/
|
||||
const char *vosk_recognizer_final_result(VoskRecognizer *recognizer);
|
||||
|
||||
|
||||
/** Releases recognizer object
|
||||
*
|
||||
* Underlying model is also unreferenced and if needed released */
|
||||
void vosk_recognizer_free(VoskRecognizer *recognizer);
|
||||
|
||||
/** Set log level for Kaldi messages
|
||||
*
|
||||
* @param log_level the level
|
||||
* 0 - default value to print info and error messages but no debug
|
||||
* less than 0 - don't print info messages
|
||||
* greather than 0 - more verbose mode
|
||||
*/
|
||||
void vosk_set_log_level(int log_level);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _VOSK_API_H_ */
|
||||
#endif /* VOSK_API_H */
|
||||
|
||||
+11
-8
@@ -11,6 +11,7 @@ KALDI_LIBS = \
|
||||
${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 \
|
||||
@@ -29,6 +30,8 @@ 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 \
|
||||
@@ -49,16 +52,16 @@ clean:
|
||||
$(RM) *.so *_wrap.cc *_wrap.o test/*.class
|
||||
$(RM) -r org model-en
|
||||
|
||||
model-en:
|
||||
wget https://github.com/alphacep/kaldi-android-demo/releases/download/2020-01/alphacep-model-android-en-us-0.3.tar.gz
|
||||
tar xf alphacep-model-android-en-us-0.3.tar.gz && rm alphacep-model-android-en-us-0.3.tar.gz
|
||||
mv alphacep-model-android-en-us-0.3 model-en
|
||||
model:
|
||||
wget https://alphacephei.com/kaldi/models/vosk-model-small-en-us-0.3.zip
|
||||
unzip vosk-model-small-en-us-0.3.zip && rm vosk-model-small-en-us-0.3.zip
|
||||
mv vosk-model-small-en-us-0.3 model
|
||||
|
||||
model-spk:
|
||||
wget https://github.com/alphacep/kaldi-android-demo/releases/download/2020-01/alphacep-spk-model-0.3.tar.gz
|
||||
tar xf alphacep-spk-model-0.3.tar.gz && rm alphacep-spk-model-0.3.tar.gz
|
||||
mv alphacep-spk-model-0.3 model-spk
|
||||
wget https://alphacephei.com/kaldi/models/vosk-model-spk-0.3.zip
|
||||
unzip vosk-model-spk-0.3.zip && rm vosk-model-spk-0.3.zip
|
||||
mv vosk-model-spk-0.3 model-spk
|
||||
|
||||
run: model-en model-spk
|
||||
run: model model-spk
|
||||
javac test/*.java org/kaldi/*.java
|
||||
java -Djava.library.path=. -cp . test.DecoderTest
|
||||
|
||||
@@ -11,15 +11,15 @@ import java.nio.*;
|
||||
import org.kaldi.KaldiRecognizer;
|
||||
import org.kaldi.Model;
|
||||
import org.kaldi.SpkModel;
|
||||
import org.kaldi.Vosk;
|
||||
|
||||
public class DecoderTest {
|
||||
static {
|
||||
System.loadLibrary("vosk_jni");
|
||||
}
|
||||
|
||||
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-en");
|
||||
Model model = new Model("model");
|
||||
SpkModel spkModel = new SpkModel("model-spk");
|
||||
KaldiRecognizer rec = new KaldiRecognizer(model, spkModel, 16000.0f);
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
build
|
||||
node_modules
|
||||
vosk_wrap.cc
|
||||
@@ -0,0 +1,20 @@
|
||||
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.
|
||||
|
||||
The build tested with node-0.10.15, node-0.12 is not yet supported by swig.
|
||||
|
||||
Still, you need swig of newest version 4.0.1
|
||||
|
||||
Build like this
|
||||
|
||||
```
|
||||
npm install --kaldi_root=/home/user/kaldi
|
||||
```
|
||||
|
||||
Then test with
|
||||
|
||||
```
|
||||
cd example
|
||||
node test.js
|
||||
```
|
||||
@@ -0,0 +1,80 @@
|
||||
{
|
||||
'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++11',
|
||||
'-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',
|
||||
],
|
||||
'conditions': [
|
||||
['OS == "mac"', {
|
||||
'xcode_settings': {
|
||||
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
|
||||
'GCC_ENABLE_CPP_RTTI': 'YES',
|
||||
'CLANG_CXX_LANGUAGE_STANDARD': 'c++11'
|
||||
}
|
||||
}]
|
||||
],
|
||||
'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/libopenblas.a',
|
||||
],
|
||||
'library_dirs': [
|
||||
'/usr/lib',
|
||||
],
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
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)
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/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);
|
||||
+3
-3
@@ -1,3 +1,3 @@
|
||||
exports.printMsg = function() {
|
||||
console.log("This is a message from the Vosk package");
|
||||
}
|
||||
const voskNativeModule = require('./build/Release/vosk.node');
|
||||
|
||||
module.exports = voskNativeModule;
|
||||
|
||||
+10
-4
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "vosk",
|
||||
"version": "0.1.0",
|
||||
"description": "Node binding for continuous voice recoginition through pocketsphinx.",
|
||||
"version": "0.3.15",
|
||||
"description": "Node binding for continuous offline voice recoginition with Vosk library.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/alphacep/vosk-api.git"
|
||||
@@ -13,6 +13,12 @@
|
||||
"voice"
|
||||
],
|
||||
"author": "Alpha Cephei Inc.",
|
||||
"license": "Apache 2.0",
|
||||
"engines": { "node" : ">= 12.x.x" }
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">= 10.x.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-gyp": "^5.1.1",
|
||||
"wav": "^1.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.12.0)
|
||||
project(vosk)
|
||||
|
||||
set(TOP_SRCDIR "${CMAKE_SOURCE_DIR}/..")
|
||||
if("x$ENV{WHEEL_FLAGS}" STREQUAL "x")
|
||||
find_package (Python COMPONENTS Interpreter Development)
|
||||
else()
|
||||
# docker case
|
||||
set(Python_INCLUDE_DIRS "")
|
||||
set(TOP_SRCDIR "/io")
|
||||
endif()
|
||||
|
||||
set(KALDI_ROOT "$ENV{KALDI_ROOT}")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -DFST_NO_DYNAMIC_LINKING")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} $ENV{WHEEL_FLAGS}")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_C_FLAGS} -std=c++11")
|
||||
include_directories("${TOP_SRCDIR}/src" "${KALDI_ROOT}/src" "${KALDI_ROOT}/tools/openfst/include" ${Python_INCLUDE_DIRS})
|
||||
|
||||
find_package(SWIG REQUIRED)
|
||||
include(${SWIG_USE_FILE})
|
||||
|
||||
swig_add_library(vosk TYPE SHARED LANGUAGE Python OUTPUT_DIR "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}" OUTFILE_DIR "."
|
||||
SOURCES "${TOP_SRCDIR}/src/kaldi_recognizer.cc"
|
||||
"${TOP_SRCDIR}/src/spk_model.cc"
|
||||
"${TOP_SRCDIR}/src/model.cc"
|
||||
"${TOP_SRCDIR}/src/vosk_api.cc"
|
||||
"${TOP_SRCDIR}/src/vosk.i")
|
||||
|
||||
swig_link_libraries(vosk
|
||||
${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/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/libopenblas.a
|
||||
-lgfortran -lstdc++)
|
||||
|
||||
set_target_properties(_vosk PROPERTIES LINK_FLAGS_RELEASE -s)
|
||||
+22
-2
@@ -1,3 +1,23 @@
|
||||
Python module for vosk-api
|
||||
This is a Python module for Vosk.
|
||||
|
||||
See for details https://github.com/alphacep/vosk-api
|
||||
Vosk is an offline open source speech recognition toolkit. It enables
|
||||
speech recognition models for 16 languages and dialects - English, Indian
|
||||
English, German, French, Spanish, Portuguese, Chinese, Russian, Turkish,
|
||||
Vietnamese, Italian, Dutch, Catalan, Arabic, Greek, Farsi.
|
||||
|
||||
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,76 +0,0 @@
|
||||
# From https://github.com/raydouglass/cmake_setuptools
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import shutil
|
||||
import sys
|
||||
from setuptools import Extension
|
||||
from setuptools.command.build_ext import build_ext
|
||||
from setuptools.command.build_py import build_py
|
||||
|
||||
CMAKE_EXE = os.environ.get('CMAKE_EXE', shutil.which('cmake'))
|
||||
|
||||
|
||||
def check_for_cmake():
|
||||
if not CMAKE_EXE:
|
||||
print('cmake executable not found. '
|
||||
'Set CMAKE_EXE environment or update your path')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class CMakeExtension(Extension):
|
||||
"""
|
||||
setuptools.Extension for cmake
|
||||
"""
|
||||
|
||||
def __init__(self, name, pkg_name, sourcedir=''):
|
||||
check_for_cmake()
|
||||
Extension.__init__(self, name, sources=[])
|
||||
self.sourcedir = os.path.abspath(sourcedir)
|
||||
self.pkg_name = pkg_name
|
||||
|
||||
|
||||
class CMakeBuildExt(build_ext):
|
||||
"""
|
||||
setuptools build_exit which builds using cmake & make
|
||||
You can add cmake args with the CMAKE_COMMON_VARIABLES environment variable
|
||||
"""
|
||||
|
||||
def build_extension(self, ext):
|
||||
check_for_cmake()
|
||||
if isinstance(ext, CMakeExtension):
|
||||
output_dir = os.path.abspath(
|
||||
os.path.dirname(self.get_ext_fullpath(ext.pkg_name + "/" + ext.name)))
|
||||
|
||||
build_type = 'Debug' if self.debug else 'Release'
|
||||
cmake_args = [CMAKE_EXE,
|
||||
ext.sourcedir,
|
||||
'-Wno-dev',
|
||||
'-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + output_dir,
|
||||
'-DCMAKE_BUILD_TYPE=' + build_type]
|
||||
cmake_args.extend(
|
||||
[x for x in
|
||||
os.environ.get('CMAKE_COMMON_VARIABLES', '').split(' ')
|
||||
if x])
|
||||
|
||||
env = os.environ.copy()
|
||||
if not os.path.exists(self.build_temp):
|
||||
os.makedirs(self.build_temp)
|
||||
subprocess.check_call(cmake_args,
|
||||
cwd=self.build_temp,
|
||||
env=env)
|
||||
subprocess.check_call(['make', 'VERBOSE=1', ext.name],
|
||||
cwd=self.build_temp,
|
||||
env=env)
|
||||
print()
|
||||
else:
|
||||
super().build_extension(ext)
|
||||
|
||||
|
||||
|
||||
class CMakeBuildExtFirst(build_py):
|
||||
def run(self):
|
||||
self.run_command("build_ext")
|
||||
return super().run()
|
||||
|
||||
__all__ = ['CMakeBuildExt', 'CMakeExtension', 'CMakeBuildExtFirst']
|
||||
@@ -1,10 +1,10 @@
|
||||
#!/usr/bin/python3
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from vosk import Model, KaldiRecognizer
|
||||
import sys
|
||||
import json
|
||||
|
||||
model = Model("model-en")
|
||||
model = Model("model")
|
||||
rec = KaldiRecognizer(model, 8000)
|
||||
|
||||
res = json.loads(rec.FinalResult())
|
||||
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from vosk import Model, KaldiRecognizer, SetLogLevel
|
||||
import sys
|
||||
import os
|
||||
import wave
|
||||
import subprocess
|
||||
|
||||
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)
|
||||
|
||||
sample_rate=16000
|
||||
model = Model("model")
|
||||
rec = KaldiRecognizer(model, sample_rate)
|
||||
|
||||
process = subprocess.Popen(['ffmpeg', '-loglevel', 'quiet', '-i',
|
||||
sys.argv[1],
|
||||
'-ar', str(sample_rate) , '-ac', '1', '-f', 's16le', '-'],
|
||||
stdout=subprocess.PIPE)
|
||||
|
||||
while True:
|
||||
data = process.stdout.read(4000)
|
||||
if len(data) == 0:
|
||||
break
|
||||
if rec.AcceptWaveform(data):
|
||||
print(rec.Result())
|
||||
else:
|
||||
print(rec.PartialResult())
|
||||
|
||||
print(rec.FinalResult())
|
||||
@@ -1,23 +1,23 @@
|
||||
#!/usr/bin/python3
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from vosk import Model, KaldiRecognizer
|
||||
import os
|
||||
|
||||
if not os.path.exists("model-en"):
|
||||
print ("Please download the model from https://github.com/alphacep/kaldi-android-demo/releases and unpack as 'model-en' in the current folder.")
|
||||
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)
|
||||
|
||||
import pyaudio
|
||||
|
||||
model = Model("model")
|
||||
rec = KaldiRecognizer(model, 16000)
|
||||
|
||||
p = pyaudio.PyAudio()
|
||||
stream = p.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True, frames_per_buffer=8000)
|
||||
stream.start_stream()
|
||||
|
||||
model = Model("model-en")
|
||||
rec = KaldiRecognizer(model, 16000)
|
||||
|
||||
while True:
|
||||
data = stream.read(2000)
|
||||
data = stream.read(4000)
|
||||
if len(data) == 0:
|
||||
break
|
||||
if rec.AcceptWaveform(data):
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
#!/usr/bin/python3
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from vosk import Model, KaldiRecognizer
|
||||
from vosk import Model, KaldiRecognizer, SetLogLevel
|
||||
import sys
|
||||
import os
|
||||
import wave
|
||||
|
||||
if not os.path.exists("model-en"):
|
||||
print ("Please download the model from https://github.com/alphacep/kaldi-android-demo/releases and unpack as 'model-en' in the current folder.")
|
||||
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")
|
||||
@@ -14,11 +16,11 @@ 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-en")
|
||||
model = Model("model")
|
||||
rec = KaldiRecognizer(model, wf.getframerate())
|
||||
|
||||
while True:
|
||||
data = wf.readframes(1000)
|
||||
data = wf.readframes(4000)
|
||||
if len(data) == 0:
|
||||
break
|
||||
if rec.AcceptWaveform(data):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python3
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from vosk import Model, KaldiRecognizer, SpkModel
|
||||
import sys
|
||||
@@ -7,15 +7,15 @@ import json
|
||||
import os
|
||||
import numpy as np
|
||||
|
||||
model_path = "model-en"
|
||||
model_path = "model"
|
||||
spk_model_path = "model-spk"
|
||||
|
||||
if not os.path.exists(model_path):
|
||||
print ("Please download the model from https://github.com/alphacep/kaldi-android-demo/releases and unpack as {} in the current folder.".format(model_path))
|
||||
print ("Please download the model from https://alphacephei.com/vosk/models and unpack as {} in the current folder.".format(model_path))
|
||||
exit (1)
|
||||
|
||||
if not os.path.exists(spk_model_path):
|
||||
print ("Please download the speaker model from https://github.com/alphacep/kaldi-android-demo/releases and unpack as {} in the current folder.".format(spk_model_path))
|
||||
print ("Please download the speaker model from https://alphacephei.com/vosk/models and unpack as {} in the current folder.".format(spk_model_path))
|
||||
exit (1)
|
||||
|
||||
wf = wave.open(sys.argv[1], "rb")
|
||||
@@ -30,7 +30,7 @@ rec = KaldiRecognizer(model, spk_model, wf.getframerate())
|
||||
|
||||
# We compare speakers with cosine distance. We can keep one or several fingerprints for the speaker in a database
|
||||
# to distingusih among users.
|
||||
spk_sig = [5.64308, 4.23898, 1.119433, -0.810904, 2.115443, 2.328436, 6.135152, 1.348195, 2.60771, 1.020717, 4.324225, -0.873012, 6.123375, 4.903791, 0.064803, 4.66212, 3.502724, 2.535861, 5.452417, 7.081769, -0.823969, -5.167974, 8.568919, 4.159035, 5.314441, 3.688272, 5.730379, 4.463213, 7.227232, 3.538961, 3.316218, 1.269628, -1.902378, 3.512679, -1.947611, -1.520158, 3.80928, -2.721601, 5.359588, 2.942463, -7.474174, 3.788054, 0.303426, 4.951366, 1.72281, -1.867125, -3.574615, 3.622509, 4.803109, 2.829714, 1.528521, 6.408293, 0.820131, 5.066522, 2.836125, 2.867029, 3.725267, 0.505927, 1.462984, 5.001863, -3.838309, -2.45902, 3.992581, 4.451616, 2.865211, -1.148313, 4.996399, -3.473454, 2.876967, 3.940124, 7.553079, 0.373356, 1.396561, 2.686691, 2.094895, 0.913796, -0.286909, 3.540179, 4.904687, 0.84554, 7.585956, 1.017081, 0.168355, 6.672327, 4.092033, -4.240158, -2.017081, -0.813043, 6.468298, 4.115041, 2.231936, 2.370055, 4.972295, 5.58382, 6.022872, 2.706988, 5.248096, -1.918003, 8.259204, -0.900911, 1.961962, 2.349709, 3.290093, 3.344172, 3.307027, 4.203372, -0.315103, 5.61919, -3.229496, 3.777309, 4.328595, 1.461014, 2.622894, 0.315525, 5.447259, 5.407609, 5.339016, 1.604555, 5.359932, 0.090242, 0.535306, 4.724705, 4.692502, 0.5783, -5.436688, -4.915511, 1.959807, 2.825248]
|
||||
spk_sig = [-1.110417,0.09703002,1.35658,0.7798632,-0.305457,-0.339204,0.6186931,-0.4521213,0.3982236,-0.004530723,0.7651616,0.6500852,-0.6664245,0.1361499,0.1358056,-0.2887807,-0.1280468,-0.8208137,-1.620276,-0.4628615,0.7870904,-0.105754,0.9739769,-0.3258137,-0.7322628,-0.6212429,-0.5531687,-0.7796484,0.7035915,1.056094,-0.4941756,-0.6521456,-0.2238328,-0.003737517,0.2165709,1.200186,-0.7737719,0.492015,1.16058,0.6135428,-0.7183084,0.3153541,0.3458071,-1.418189,-0.9624157,0.4168292,-1.627305,0.2742135,-0.6166027,0.1962581,-0.6406527,0.4372789,-0.4296024,0.4898657,-0.9531326,-0.2945702,0.7879696,-1.517101,-0.9344181,-0.5049928,-0.005040941,-0.4637912,0.8223695,-1.079849,0.8871287,-0.9732434,-0.5548235,1.879138,-1.452064,-0.1975368,1.55047,0.5941782,-0.52897,1.368219,0.6782904,1.202505,-0.9256122,-0.9718158,-0.9570228,-0.5563112,-1.19049,-1.167985,2.606804,-2.261825,0.01340385,0.2526799,-1.125458,-1.575991,-0.363153,0.3270262,1.485984,-1.769565,1.541829,0.7293826,0.1743717,-0.4759418,1.523451,-2.487134,-1.824067,-0.626367,0.7448186,-1.425648,0.3524166,-0.9903384,3.339342,0.4563958,-0.2876643,1.521635,0.9508078,-0.1398541,0.3867955,-0.7550205,0.6568405,0.09419366,-1.583935,1.306094,-0.3501927,0.1794427,-0.3768163,0.9683866,-0.2442541,-1.696921,-1.8056,-0.6803037,-1.842043,0.3069353,0.9070363,-0.486526]
|
||||
|
||||
def cosine_dist(x, y):
|
||||
nx = np.array(x)
|
||||
@@ -38,15 +38,19 @@ def cosine_dist(x, y):
|
||||
return 1 - np.dot(nx, ny) / np.linalg.norm(nx) / np.linalg.norm(ny)
|
||||
|
||||
while True:
|
||||
data = wf.readframes(1000)
|
||||
data = wf.readframes(4000)
|
||||
if len(data) == 0:
|
||||
break
|
||||
if rec.AcceptWaveform(data):
|
||||
res = json.loads(rec.Result())
|
||||
print ("Text:", res['text'])
|
||||
print ("Speaker distance:", cosine_dist(spk_sig, res['spk']))
|
||||
print ("X-vector:", res['spk'])
|
||||
print ("Speaker distance:", cosine_dist(spk_sig, res['spk']), "based on", res['spk_frames'], "frames")
|
||||
|
||||
print ("Note that second distance is not very reliable because utterance is too short. Utterances longer than 4 seconds give better xvector")
|
||||
|
||||
res = json.loads(rec.FinalResult())
|
||||
print ("Text:", res['text'])
|
||||
print ("Speaker distance:", cosine_dist(spk_sig, res['spk']))
|
||||
print ("X-vector:", res['spk'])
|
||||
print ("Speaker distance:", cosine_dist(spk_sig, res['spk']), "based on", res['spk_frames'], "frames")
|
||||
|
||||
|
||||
Executable
+49
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from vosk import Model, KaldiRecognizer, SetLogLevel
|
||||
import sys
|
||||
import os
|
||||
import wave
|
||||
import subprocess
|
||||
import srt
|
||||
import json
|
||||
import datetime
|
||||
|
||||
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)
|
||||
|
||||
process = subprocess.Popen(['ffmpeg', '-loglevel', 'quiet', '-i',
|
||||
sys.argv[1],
|
||||
'-ar', str(sample_rate) , '-ac', '1', '-f', 's16le', '-'],
|
||||
stdout=subprocess.PIPE)
|
||||
|
||||
|
||||
def transcribe():
|
||||
results = []
|
||||
subs = []
|
||||
while True:
|
||||
data = process.stdout.read(4000)
|
||||
if len(data) == 0:
|
||||
break
|
||||
if rec.AcceptWaveform(data):
|
||||
results.append(rec.Result())
|
||||
results.append(rec.FinalResult())
|
||||
|
||||
for i, res in enumerate(results):
|
||||
jres = json.loads(res)
|
||||
s = srt.Subtitle(index=i,
|
||||
content=jres['text'],
|
||||
start=datetime.timedelta(seconds=jres['result'][0]['start']),
|
||||
end=datetime.timedelta(seconds=jres['result'][-1]['end']))
|
||||
subs.append(s)
|
||||
return subs
|
||||
|
||||
print (srt.compose(transcribe()))
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
#!/usr/bin/python3
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from vosk import Model, KaldiRecognizer
|
||||
import sys
|
||||
import json
|
||||
import os
|
||||
|
||||
if not os.path.exists("model-en"):
|
||||
print ("Please download the model from https://github.com/alphacep/kaldi-android-demo/releases and unpack as 'model-en' in the current folder.")
|
||||
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)
|
||||
|
||||
|
||||
model = Model("model-en")
|
||||
model = Model("model")
|
||||
|
||||
# Large vocabulary free form recognition
|
||||
rec = KaldiRecognizer(model, 16000)
|
||||
@@ -22,7 +22,7 @@ wf = open(sys.argv[1], "rb")
|
||||
wf.read(44) # skip header
|
||||
|
||||
while True:
|
||||
data = wf.read(2000)
|
||||
data = wf.read(4000)
|
||||
if len(data) == 0:
|
||||
break
|
||||
if rec.AcceptWaveform(data):
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#!/usr/bin/python3
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from vosk import Model, KaldiRecognizer
|
||||
import sys
|
||||
import os
|
||||
import wave
|
||||
|
||||
if not os.path.exists("model-en"):
|
||||
print ("Please download the model from https://github.com/alphacep/kaldi-android-demo/releases and unpack as 'model-en' in the current folder.")
|
||||
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")
|
||||
@@ -14,12 +14,13 @@ 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-en")
|
||||
# You can also specify the possible word list
|
||||
rec = KaldiRecognizer(model, wf.getframerate(), "zero oh one two three four five six seven eight nine")
|
||||
model = Model("model")
|
||||
|
||||
# You can also specify the possible word or phrase list as JSON list, the order doesn't have to be strict
|
||||
rec = KaldiRecognizer(model, wf.getframerate(), '["oh one two three four five six seven eight nine zero", "[unk]"]')
|
||||
|
||||
while True:
|
||||
data = wf.readframes(1000)
|
||||
data = wf.readframes(4000)
|
||||
if len(data) == 0:
|
||||
break
|
||||
if rec.AcceptWaveform(data):
|
||||
|
||||
+73
-7
@@ -1,22 +1,88 @@
|
||||
import os
|
||||
import sys
|
||||
import setuptools
|
||||
from cmake import *
|
||||
from setuptools import Extension
|
||||
from setuptools.command.build_py import build_py as _build_py
|
||||
import distutils.dir_util
|
||||
import distutils.log
|
||||
|
||||
class build_py(_build_py):
|
||||
def run(self):
|
||||
self.run_command("build_ext")
|
||||
return super().run()
|
||||
|
||||
kaldi_root = os.getenv('KALDI_ROOT')
|
||||
kaldi_mkl = os.getenv('KALDI_MKL')
|
||||
source_path = os.getenv("VOSK_SOURCE", os.path.abspath(os.path.join(os.path.abspath(os.path.dirname(__file__)), "../src")))
|
||||
|
||||
if kaldi_root == None:
|
||||
print("Define KALDI_ROOT")
|
||||
exit(1)
|
||||
|
||||
distutils.log.set_verbosity(distutils.log.DEBUG)
|
||||
distutils.dir_util.copy_tree(
|
||||
source_path,
|
||||
"vosk",
|
||||
update=1,
|
||||
verbose=1)
|
||||
|
||||
with open("README.md", "r") as fh:
|
||||
long_description = fh.read()
|
||||
|
||||
kaldi_static_libs = ['src/online2/kaldi-online2.a',
|
||||
'src/decoder/kaldi-decoder.a',
|
||||
'src/ivector/kaldi-ivector.a',
|
||||
'src/gmm/kaldi-gmm.a',
|
||||
'src/nnet3/kaldi-nnet3.a',
|
||||
'src/tree/kaldi-tree.a',
|
||||
'src/feat/kaldi-feat.a',
|
||||
'src/lat/kaldi-lat.a',
|
||||
'src/lm/kaldi-lm.a',
|
||||
'src/hmm/kaldi-hmm.a',
|
||||
'src/transform/kaldi-transform.a',
|
||||
'src/cudamatrix/kaldi-cudamatrix.a',
|
||||
'src/matrix/kaldi-matrix.a',
|
||||
'src/fstext/kaldi-fstext.a',
|
||||
'src/util/kaldi-util.a',
|
||||
'src/base/kaldi-base.a',
|
||||
'tools/openfst/lib/libfst.a',
|
||||
'tools/openfst/lib/libfstngram.a']
|
||||
kaldi_link_args = ['-s']
|
||||
kaldi_libraries = []
|
||||
|
||||
if sys.platform.startswith('darwin'):
|
||||
kaldi_link_args.extend(['-Wl,-undefined,dynamic_lookup', '-framework', 'Accelerate'])
|
||||
elif kaldi_mkl == "1":
|
||||
kaldi_link_args.extend(['-L/opt/intel/mkl/lib/intel64', '-Wl,-rpath=/opt/intel/mkl/lib/intel64'])
|
||||
kaldi_libraries.extend(['mkl_rt', 'mkl_intel_lp64', 'mkl_core', 'mkl_sequential'])
|
||||
else:
|
||||
kaldi_static_libs.append('tools/OpenBLAS/libopenblas.a')
|
||||
kaldi_libraries.append('gfortran')
|
||||
|
||||
sources = ['kaldi_recognizer.cc', 'model.cc', 'spk_model.cc', 'vosk_api.cc', 'language_model.cc', 'vosk.i']
|
||||
|
||||
vosk_ext = Extension('vosk._vosk',
|
||||
define_macros = [('FST_NO_DYNAMIC_LINKING', '1')],
|
||||
include_dirs = [kaldi_root + '/src', kaldi_root + '/tools/openfst/include', 'vosk'],
|
||||
swig_opts=['-outdir', 'vosk', '-c++'],
|
||||
libraries = kaldi_libraries,
|
||||
extra_objects = [kaldi_root + '/' + x for x in kaldi_static_libs],
|
||||
sources = ['vosk/' + x for x in sources],
|
||||
extra_link_args = kaldi_link_args,
|
||||
extra_compile_args = ['-std=c++11', '-Wno-sign-compare', '-Wno-unused-variable', '-Wno-unused-local-typedefs'])
|
||||
|
||||
setuptools.setup(
|
||||
name="vosk", # Replace with your own username
|
||||
version="0.3.4",
|
||||
name="vosk",
|
||||
version="0.3.15",
|
||||
author="Alpha Cephei Inc",
|
||||
author_email="contact@alphacephei.com",
|
||||
description="API for Kaldi and Vosk",
|
||||
description="Offline open source speech recognition API based on Kaldi and Vosk",
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
url="https://github.com/alphacep/vosk-api",
|
||||
packages=setuptools.find_packages(),
|
||||
ext_modules=[CMakeExtension('_vosk', 'vosk')],
|
||||
cmdclass={'build_ext': CMakeBuildExt, 'build_py' : CMakeBuildExtFirst},
|
||||
ext_modules=[vosk_ext],
|
||||
cmdclass = {'build_py' : build_py},
|
||||
classifiers=[
|
||||
'Programming Language :: Python :: 3',
|
||||
'License :: OSI Approved :: Apache Software License',
|
||||
@@ -25,5 +91,5 @@ setuptools.setup(
|
||||
'Operating System :: MacOS :: MacOS X',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules'
|
||||
],
|
||||
python_requires='>=3.4',
|
||||
python_requires='>=3.5',
|
||||
)
|
||||
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from multiprocessing.dummy import Pool
|
||||
from vosk import Model, KaldiRecognizer
|
||||
|
||||
import sys
|
||||
import os
|
||||
import wave
|
||||
import json
|
||||
|
||||
model = Model("model")
|
||||
|
||||
def recognize(line):
|
||||
uid, fn = line.split()
|
||||
wf = wave.open(fn, "rb")
|
||||
rec = KaldiRecognizer(model, wf.getframerate())
|
||||
|
||||
text = ""
|
||||
while True:
|
||||
data = wf.readframes(1000)
|
||||
if len(data) == 0:
|
||||
break
|
||||
if rec.AcceptWaveform(data):
|
||||
jres = json.loads(rec.Result())
|
||||
text = text + " " + jres['text']
|
||||
jres = json.loads(rec.FinalResult())
|
||||
text = text + " " + jres['text']
|
||||
return (uid + text)
|
||||
|
||||
def main():
|
||||
p = Pool(8)
|
||||
texts = p.map(recognize, open(sys.argv[1]).readlines())
|
||||
print ("\n".join(texts))
|
||||
|
||||
main()
|
||||
@@ -1 +1,2 @@
|
||||
from .vosk import KaldiRecognizer, Model, SpkModel
|
||||
from .vosk import KaldiRecognizer, Model, SpkModel, SetLogLevel
|
||||
|
||||
|
||||
+299
-111
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019 Alpha Cephei Inc.
|
||||
// Copyright 2019-2020 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.
|
||||
@@ -16,128 +16,214 @@
|
||||
#include "json.h"
|
||||
#include "fstext/fstext-utils.h"
|
||||
#include "lat/sausages.h"
|
||||
#include "language_model.h"
|
||||
|
||||
using namespace fst;
|
||||
using namespace kaldi::nnet3;
|
||||
|
||||
KaldiRecognizer::KaldiRecognizer(Model &model, float sample_frequency) : model_(model), spk_model_(0), sample_frequency_(sample_frequency) {
|
||||
KaldiRecognizer::KaldiRecognizer(Model *model, float sample_frequency) : model_(model), spk_model_(0), sample_frequency_(sample_frequency) {
|
||||
|
||||
feature_pipeline_ = new kaldi::OnlineNnet2FeaturePipeline (model_.feature_info_);
|
||||
silence_weighting_ = new kaldi::OnlineSilenceWeighting(*model_.trans_model_, model_.feature_info_.silence_weighting_config, 3);
|
||||
model_->Ref();
|
||||
|
||||
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_);
|
||||
if (!model_->hclg_fst_) {
|
||||
if (model_->hcl_fst_ && model_->g_fst_) {
|
||||
decode_fst_ = LookaheadComposeFst(*model_->hcl_fst_, *model_->g_fst_, model_->disambig_);
|
||||
} else {
|
||||
KALDI_ERR << "Can't create decoding graph";
|
||||
}
|
||||
}
|
||||
|
||||
decoder_ = new kaldi::SingleUtteranceNnet3Decoder(model_.nnet3_decoding_config_,
|
||||
*model_.trans_model_,
|
||||
*model_.decodable_info_,
|
||||
model_.hclg_fst_ ? *model.hclg_fst_ : *decode_fst_,
|
||||
decoder_ = new kaldi::SingleUtteranceNnet3Decoder(model_->nnet3_decoding_config_,
|
||||
*model_->trans_model_,
|
||||
*model_->decodable_info_,
|
||||
model_->hclg_fst_ ? *model_->hclg_fst_ : *decode_fst_,
|
||||
feature_pipeline_);
|
||||
|
||||
frame_offset_ = 0;
|
||||
input_finalized_ = false;
|
||||
spk_feature_ = NULL;
|
||||
|
||||
InitState();
|
||||
InitRescoring();
|
||||
}
|
||||
|
||||
KaldiRecognizer::KaldiRecognizer(Model &model, float sample_frequency, char const *grammar) : model_(model), spk_model_(0), sample_frequency_(sample_frequency)
|
||||
KaldiRecognizer::KaldiRecognizer(Model *model, float sample_frequency, char const *grammar) : model_(model), spk_model_(0), sample_frequency_(sample_frequency)
|
||||
{
|
||||
feature_pipeline_ = new kaldi::OnlineNnet2FeaturePipeline (model_.feature_info_);
|
||||
silence_weighting_ = new kaldi::OnlineSilenceWeighting(*model_.trans_model_, model_.feature_info_.silence_weighting_config, 3);
|
||||
model_->Ref();
|
||||
|
||||
g_fst_ = new StdVectorFst();
|
||||
if (model_.hcl_fst_) {
|
||||
g_fst_->AddState();
|
||||
g_fst_->SetStart(0);
|
||||
g_fst_->AddState();
|
||||
g_fst_->SetFinal(1, fst::TropicalWeight::One());
|
||||
g_fst_->AddArc(1, StdArc(0, 0, fst::TropicalWeight::One(), 0));
|
||||
feature_pipeline_ = new kaldi::OnlineNnet2FeaturePipeline (model_->feature_info_);
|
||||
silence_weighting_ = new kaldi::OnlineSilenceWeighting(*model_->trans_model_, model_->feature_info_.silence_weighting_config, 3);
|
||||
|
||||
// Create simple word loop FST
|
||||
std::stringstream ss(grammar);
|
||||
std::string token;
|
||||
if (model_->hcl_fst_) {
|
||||
json::JSON obj;
|
||||
obj = json::JSON::Load(grammar);
|
||||
|
||||
while (std::getline(ss, token, ' ')) {
|
||||
int32 id = model_.word_syms_->Find(token);
|
||||
g_fst_->AddArc(0, StdArc(id, id, fst::TropicalWeight::One(), 1));
|
||||
if (obj.length() <= 0) {
|
||||
KALDI_WARN << "Expecting array of strings, got: '" << grammar << "'";
|
||||
} else {
|
||||
KALDI_LOG << obj;
|
||||
|
||||
LanguageModelOptions opts;
|
||||
|
||||
opts.ngram_order = 2;
|
||||
opts.discount = 0.5;
|
||||
|
||||
LanguageModelEstimator estimator(opts);
|
||||
for (int i = 0; i < obj.length(); i++) {
|
||||
bool ok;
|
||||
string line = obj[i].ToString(ok);
|
||||
if (!ok) {
|
||||
KALDI_ERR << "Expecting array of strings, got: '" << obj << "'";
|
||||
}
|
||||
|
||||
std::vector<int32> sentence;
|
||||
stringstream ss(line);
|
||||
string token;
|
||||
while (getline(ss, token, ' ')) {
|
||||
int32 id = model_->word_syms_->Find(token);
|
||||
if (id == kNoSymbol) {
|
||||
KALDI_WARN << "Ignoring word missing in vocabulary: '" << token << "'";
|
||||
} else {
|
||||
sentence.push_back(id);
|
||||
}
|
||||
}
|
||||
estimator.AddCounts(sentence);
|
||||
}
|
||||
g_fst_ = new StdVectorFst();
|
||||
estimator.Estimate(g_fst_);
|
||||
|
||||
decode_fst_ = LookaheadComposeFst(*model_->hcl_fst_, *g_fst_, model_->disambig_);
|
||||
}
|
||||
ArcSort(g_fst_, ILabelCompare<StdArc>());
|
||||
|
||||
decode_fst_ = LookaheadComposeFst(*model_.hcl_fst_, *g_fst_, model_.disambig_);
|
||||
} else {
|
||||
decode_fst_ = NULL;
|
||||
KALDI_ERR << "Can't create decoding graph";
|
||||
KALDI_WARN << "Runtime graphs are not supported by this model";
|
||||
}
|
||||
|
||||
decoder_ = new kaldi::SingleUtteranceNnet3Decoder(model_.nnet3_decoding_config_,
|
||||
*model_.trans_model_,
|
||||
*model_.decodable_info_,
|
||||
model_.hclg_fst_ ? *model.hclg_fst_ : *decode_fst_,
|
||||
decoder_ = new kaldi::SingleUtteranceNnet3Decoder(model_->nnet3_decoding_config_,
|
||||
*model_->trans_model_,
|
||||
*model_->decodable_info_,
|
||||
model_->hclg_fst_ ? *model_->hclg_fst_ : *decode_fst_,
|
||||
feature_pipeline_);
|
||||
|
||||
frame_offset_ = 0;
|
||||
input_finalized_ = false;
|
||||
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, SpkModel *spk_model, float sample_frequency) : model_(model), spk_model_(spk_model), sample_frequency_(sample_frequency) {
|
||||
feature_pipeline_ = new kaldi::OnlineNnet2FeaturePipeline (model_.feature_info_);
|
||||
silence_weighting_ = new kaldi::OnlineSilenceWeighting(*model_.trans_model_, model_.feature_info_.silence_weighting_config, 3);
|
||||
model_->Ref();
|
||||
spk_model->Ref();
|
||||
|
||||
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_);
|
||||
if (!model_->hclg_fst_) {
|
||||
if (model_->hcl_fst_ && model_->g_fst_) {
|
||||
decode_fst_ = LookaheadComposeFst(*model_->hcl_fst_, *model_->g_fst_, model_->disambig_);
|
||||
} else {
|
||||
KALDI_ERR << "Can't create decoding graph";
|
||||
}
|
||||
}
|
||||
|
||||
decoder_ = new kaldi::SingleUtteranceNnet3Decoder(model_.nnet3_decoding_config_,
|
||||
*model_.trans_model_,
|
||||
*model_.decodable_info_,
|
||||
model_.hclg_fst_ ? *model.hclg_fst_ : *decode_fst_,
|
||||
decoder_ = new kaldi::SingleUtteranceNnet3Decoder(model_->nnet3_decoding_config_,
|
||||
*model_->trans_model_,
|
||||
*model_->decodable_info_,
|
||||
model_->hclg_fst_ ? *model_->hclg_fst_ : *decode_fst_,
|
||||
feature_pipeline_);
|
||||
|
||||
frame_offset_ = 0;
|
||||
input_finalized_ = false;
|
||||
|
||||
spk_feature_ = new OnlineMfcc(spk_model_->spkvector_mfcc_opts);
|
||||
|
||||
InitState();
|
||||
InitRescoring();
|
||||
}
|
||||
|
||||
KaldiRecognizer::~KaldiRecognizer() {
|
||||
delete decoder_;
|
||||
delete feature_pipeline_;
|
||||
delete silence_weighting_;
|
||||
delete decoder_;
|
||||
delete g_fst_;
|
||||
delete decode_fst_;
|
||||
delete spk_feature_;
|
||||
delete lm_fst_;
|
||||
|
||||
model_->Unref();
|
||||
if (spk_model_)
|
||||
spk_model_->Unref();
|
||||
}
|
||||
|
||||
void KaldiRecognizer::InitState()
|
||||
{
|
||||
frame_offset_ = 0;
|
||||
samples_processed_ = 0;
|
||||
samples_round_start_ = 0;
|
||||
|
||||
state_ = RECOGNIZER_INITIALIZED;
|
||||
}
|
||||
|
||||
void KaldiRecognizer::InitRescoring()
|
||||
{
|
||||
if (model_->std_lm_fst_) {
|
||||
fst::CacheOptions cache_opts(true, 50000);
|
||||
fst::MapFstOptions mapfst_opts(cache_opts);
|
||||
fst::StdToLatticeMapper<kaldi::BaseFloat> mapper;
|
||||
lm_fst_ = new fst::MapFst<fst::StdArc, kaldi::LatticeArc, fst::StdToLatticeMapper<kaldi::BaseFloat> >(*model_->std_lm_fst_, mapper, mapfst_opts);
|
||||
} else {
|
||||
lm_fst_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void KaldiRecognizer::CleanUp()
|
||||
{
|
||||
delete silence_weighting_;
|
||||
silence_weighting_ = new kaldi::OnlineSilenceWeighting(*model_.trans_model_, model_.feature_info_.silence_weighting_config, 3);
|
||||
silence_weighting_ = new kaldi::OnlineSilenceWeighting(*model_->trans_model_, model_->feature_info_.silence_weighting_config, 3);
|
||||
|
||||
frame_offset_ += decoder_->NumFramesDecoded();
|
||||
decoder_->InitDecoding(frame_offset_);
|
||||
if (decoder_)
|
||||
frame_offset_ += decoder_->NumFramesDecoded();
|
||||
|
||||
// Each 10 minutes we drop the pipeline to save frontend memory in continuous processing
|
||||
// here we drop few frames remaining in the feature pipeline but hope it will not
|
||||
// cause a huge accuracy drop since it happens not very frequently.
|
||||
|
||||
// Also restart if we retrieved final result already
|
||||
|
||||
if (decoder_ == NULL || state_ == RECOGNIZER_FINALIZED || frame_offset_ > 20000) {
|
||||
samples_round_start_ += samples_processed_;
|
||||
samples_processed_ = 0;
|
||||
frame_offset_ = 0;
|
||||
|
||||
delete decoder_;
|
||||
delete feature_pipeline_;
|
||||
|
||||
feature_pipeline_ = new kaldi::OnlineNnet2FeaturePipeline (model_->feature_info_);
|
||||
decoder_ = new kaldi::SingleUtteranceNnet3Decoder(model_->nnet3_decoding_config_,
|
||||
*model_->trans_model_,
|
||||
*model_->decodable_info_,
|
||||
model_->hclg_fst_ ? *model_->hclg_fst_ : *decode_fst_,
|
||||
feature_pipeline_);
|
||||
|
||||
if (spk_model_) {
|
||||
delete spk_feature_;
|
||||
spk_feature_ = new OnlineMfcc(spk_model_->spkvector_mfcc_opts);
|
||||
}
|
||||
} else {
|
||||
decoder_->InitDecoding(frame_offset_);
|
||||
}
|
||||
}
|
||||
|
||||
void KaldiRecognizer::UpdateSilenceWeights()
|
||||
{
|
||||
if (silence_weighting_->Active() && feature_pipeline_->NumFramesReady() > 0 &&
|
||||
feature_pipeline_->IvectorFeature() != NULL) {
|
||||
std::vector<std::pair<int32, BaseFloat> > delta_weights;
|
||||
vector<pair<int32, BaseFloat> > delta_weights;
|
||||
silence_weighting_->ComputeCurrentTraceback(decoder_->Decoder());
|
||||
silence_weighting_->GetDeltaWeights(feature_pipeline_->NumFramesReady(),
|
||||
frame_offset_ * 3,
|
||||
@@ -175,27 +261,32 @@ bool KaldiRecognizer::AcceptWaveform(const float *fdata, int len)
|
||||
|
||||
bool KaldiRecognizer::AcceptWaveform(Vector<BaseFloat> &wdata)
|
||||
{
|
||||
if (input_finalized_) {
|
||||
// Cleanup if we finalized previous utterance or the whole feature pipeline
|
||||
if (!(state_ == RECOGNIZER_RUNNING || state_ == RECOGNIZER_INITIALIZED)) {
|
||||
CleanUp();
|
||||
input_finalized_ = false;
|
||||
}
|
||||
state_ = RECOGNIZER_RUNNING;
|
||||
|
||||
feature_pipeline_->AcceptWaveform(sample_frequency_, wdata);
|
||||
UpdateSilenceWeights();
|
||||
decoder_->AdvanceDecoding();
|
||||
int step = static_cast<int>(sample_frequency_ * 0.2);
|
||||
for (int i = 0; i < wdata.Dim(); i+= step) {
|
||||
SubVector<BaseFloat> r = wdata.Range(i, std::min(step, wdata.Dim() - i));
|
||||
feature_pipeline_->AcceptWaveform(sample_frequency_, r);
|
||||
UpdateSilenceWeights();
|
||||
decoder_->AdvanceDecoding();
|
||||
}
|
||||
samples_processed_ += wdata.Dim();
|
||||
|
||||
if (spk_feature_) {
|
||||
spk_feature_->AcceptWaveform(sample_frequency_, wdata);
|
||||
}
|
||||
|
||||
if (decoder_->EndpointDetected(model_.endpoint_config_)) {
|
||||
if (decoder_->EndpointDetected(model_->endpoint_config_)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Computes an xvector from a chunk of speech features.
|
||||
static void RunNnetComputation(const MatrixBase<BaseFloat> &features,
|
||||
const nnet3::Nnet &nnet, nnet3::CachingOptimizingCompiler *compiler,
|
||||
@@ -212,7 +303,7 @@ static void RunNnetComputation(const MatrixBase<BaseFloat> &features,
|
||||
output_spec.indexes.resize(1);
|
||||
request.outputs.resize(1);
|
||||
request.outputs[0].Swap(&output_spec);
|
||||
std::shared_ptr<const nnet3::NnetComputation> computation = compiler->Compile(request);
|
||||
shared_ptr<const nnet3::NnetComputation> computation = compiler->Compile(request);
|
||||
nnet3::Nnet *nnet_to_update = NULL; // we're not doing any update.
|
||||
nnet3::NnetComputer computer(nnet3::NnetComputeOptions(), *computation,
|
||||
nnet, nnet_to_update);
|
||||
@@ -225,17 +316,48 @@ static void RunNnetComputation(const MatrixBase<BaseFloat> &features,
|
||||
xvector->CopyFromVec(cu_output.Row(0));
|
||||
}
|
||||
|
||||
#define MIN_SPK_FEATS 50
|
||||
|
||||
void KaldiRecognizer::GetSpkVector(Vector<BaseFloat> &xvector)
|
||||
bool KaldiRecognizer::GetSpkVector(Vector<BaseFloat> &out_xvector, int *num_spk_frames)
|
||||
{
|
||||
vector<int32> nonsilence_frames;
|
||||
if (silence_weighting_->Active() && feature_pipeline_->NumFramesReady() > 0) {
|
||||
silence_weighting_->ComputeCurrentTraceback(decoder_->Decoder(), true);
|
||||
silence_weighting_->GetNonsilenceFrames(feature_pipeline_->NumFramesReady(),
|
||||
frame_offset_ * 3,
|
||||
&nonsilence_frames);
|
||||
}
|
||||
|
||||
int num_frames = spk_feature_->NumFramesReady() - frame_offset_ * 3;
|
||||
Matrix<BaseFloat> mfcc(num_frames, spk_feature_->Dim());
|
||||
|
||||
// Not very efficient, would be nice to have faster search
|
||||
int num_nonsilence_frames = 0;
|
||||
Vector<BaseFloat> feat(spk_feature_->Dim());
|
||||
|
||||
for (int i = 0; i < num_frames; ++i) {
|
||||
Vector<BaseFloat> feat(spk_feature_->Dim());
|
||||
if (std::find(nonsilence_frames.begin(),
|
||||
nonsilence_frames.end(), i / 3) == nonsilence_frames.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
spk_feature_->GetFrame(i + frame_offset_ * 3, &feat);
|
||||
mfcc.CopyRowFromVec(feat, i);
|
||||
mfcc.CopyRowFromVec(feat, num_nonsilence_frames);
|
||||
num_nonsilence_frames++;
|
||||
}
|
||||
|
||||
*num_spk_frames = num_nonsilence_frames;
|
||||
|
||||
// Don't extract vector if not enough data
|
||||
if (num_nonsilence_frames < MIN_SPK_FEATS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mfcc.Resize(num_nonsilence_frames, spk_feature_->Dim(), kCopyData);
|
||||
|
||||
SlidingWindowCmnOptions cmvn_opts;
|
||||
cmvn_opts.center = true;
|
||||
cmvn_opts.cmn_window = 300;
|
||||
Matrix<BaseFloat> features(mfcc.NumRows(), mfcc.NumCols(), kUndefined);
|
||||
SlidingWindowCmn(cmvn_opts, mfcc, &features);
|
||||
|
||||
@@ -243,112 +365,178 @@ void KaldiRecognizer::GetSpkVector(Vector<BaseFloat> &xvector)
|
||||
nnet3::CachingOptimizingCompilerOptions compiler_config;
|
||||
nnet3::CachingOptimizingCompiler compiler(spk_model_->speaker_nnet, opts.optimize_config, compiler_config);
|
||||
|
||||
Vector<BaseFloat> xvector;
|
||||
RunNnetComputation(features, spk_model_->speaker_nnet, &compiler, &xvector);
|
||||
|
||||
// Whiten the vector with global mean and transform and normalize mean
|
||||
xvector.AddVec(-1.0, spk_model_->mean);
|
||||
|
||||
out_xvector.Resize(spk_model_->transform.NumRows(), kSetZero);
|
||||
out_xvector.AddMatVec(1.0, spk_model_->transform, kNoTrans, xvector, 0.0);
|
||||
|
||||
BaseFloat norm = out_xvector.Norm(2.0);
|
||||
BaseFloat ratio = norm / sqrt(out_xvector.Dim()); // how much larger it is
|
||||
// than it would be, in
|
||||
// expectation, if normally
|
||||
out_xvector.Scale(1.0 / ratio);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
const char* KaldiRecognizer::Result()
|
||||
const char* KaldiRecognizer::GetResult()
|
||||
{
|
||||
|
||||
if (!input_finalized_) {
|
||||
decoder_->FinalizeDecoding();
|
||||
input_finalized_ = true;
|
||||
}
|
||||
|
||||
if (decoder_->NumFramesDecoded() == 0) {
|
||||
last_result_ = "{\"text\": \"\"}";
|
||||
return last_result_.c_str();
|
||||
return StoreReturn("{\"text\": \"\"}");
|
||||
}
|
||||
|
||||
kaldi::CompactLattice clat;
|
||||
decoder_->GetLattice(true, &clat);
|
||||
fst::ScaleLattice(fst::LatticeScale(8.0, 10.0), &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);
|
||||
if (model_->winfo_) {
|
||||
WordAlignLattice(clat, *model_->trans_model_, *model_->winfo_, 0, &aligned_lat);
|
||||
} else {
|
||||
aligned_lat = clat;
|
||||
}
|
||||
|
||||
MinimumBayesRisk mbr(aligned_lat);
|
||||
const std::vector<BaseFloat> &conf = mbr.GetOneBestConfidences();
|
||||
const std::vector<int32> &words = mbr.GetOneBest();
|
||||
const std::vector<std::pair<BaseFloat, BaseFloat> > × =
|
||||
const vector<BaseFloat> &conf = mbr.GetOneBestConfidences();
|
||||
const vector<int32> &words = mbr.GetOneBest();
|
||||
const vector<pair<BaseFloat, BaseFloat> > × =
|
||||
mbr.GetOneBestTimes();
|
||||
|
||||
int size = words.size();
|
||||
|
||||
json::JSON obj;
|
||||
std::stringstream text;
|
||||
stringstream text;
|
||||
|
||||
// Create JSON object
|
||||
for (int i = 0; i < size; i++) {
|
||||
json::JSON word;
|
||||
word["word"] = model_.word_syms_->Find(words[i]);
|
||||
word["start"] = (frame_offset_ + times[i].first) * 0.03;
|
||||
word["end"] = (frame_offset_ + times[i].second) * 0.03;
|
||||
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 << " ";
|
||||
}
|
||||
text << model_.word_syms_->Find(words[i]);
|
||||
text << model_->word_syms_->Find(words[i]);
|
||||
}
|
||||
obj["text"] = text.str();
|
||||
|
||||
if (spk_model_) {
|
||||
Vector<BaseFloat> xvector;
|
||||
GetSpkVector(xvector);
|
||||
for (int i = 0; i < xvector.Dim(); i++) {
|
||||
obj["spk"].append(xvector(i));
|
||||
int num_spk_frames;
|
||||
if (GetSpkVector(xvector, &num_spk_frames)) {
|
||||
for (int i = 0; i < xvector.Dim(); i++) {
|
||||
obj["spk"].append(xvector(i));
|
||||
}
|
||||
obj["spk_frames"] = num_spk_frames;
|
||||
}
|
||||
}
|
||||
|
||||
last_result_ = obj.dump();
|
||||
return last_result_.c_str();
|
||||
return StoreReturn(obj.dump());
|
||||
}
|
||||
|
||||
|
||||
const char* KaldiRecognizer::PartialResult()
|
||||
{
|
||||
if (state_ != RECOGNIZER_RUNNING) {
|
||||
return StoreReturn("{\"text\": \"\"}");
|
||||
}
|
||||
|
||||
json::JSON res;
|
||||
|
||||
if (decoder_->NumFramesDecoded() == 0) {
|
||||
res["partial"] = "";
|
||||
last_result_ = res.dump();
|
||||
return last_result_.c_str();
|
||||
return StoreReturn(res.dump());
|
||||
}
|
||||
|
||||
kaldi::Lattice lat;
|
||||
decoder_->GetBestPath(false, &lat);
|
||||
std::vector<kaldi::int32> alignment, words;
|
||||
vector<kaldi::int32> alignment, words;
|
||||
LatticeWeight weight;
|
||||
GetLinearSymbolSequence(lat, &alignment, &words, &weight);
|
||||
|
||||
std::ostringstream text;
|
||||
ostringstream text;
|
||||
for (size_t i = 0; i < words.size(); i++) {
|
||||
if (i) {
|
||||
text << " ";
|
||||
}
|
||||
text << model_.word_syms_->Find(words[i]);
|
||||
text << model_->word_syms_->Find(words[i]);
|
||||
}
|
||||
res["partial"] = text.str();
|
||||
|
||||
last_result_ = res.dump();
|
||||
return last_result_.c_str();
|
||||
return StoreReturn(res.dump());
|
||||
}
|
||||
|
||||
const char* KaldiRecognizer::Result()
|
||||
{
|
||||
if (state_ != RECOGNIZER_RUNNING) {
|
||||
return StoreReturn("{\"text\": \"\"}");
|
||||
}
|
||||
decoder_->FinalizeDecoding();
|
||||
state_ = RECOGNIZER_ENDPOINT;
|
||||
return GetResult();
|
||||
}
|
||||
|
||||
const char* KaldiRecognizer::FinalResult()
|
||||
{
|
||||
if (!input_finalized_) {
|
||||
feature_pipeline_->InputFinished();
|
||||
UpdateSilenceWeights();
|
||||
decoder_->AdvanceDecoding();
|
||||
decoder_->FinalizeDecoding();
|
||||
input_finalized_ = true;
|
||||
return Result();
|
||||
} else {
|
||||
last_result_ = "{\"text\": \"\"}";
|
||||
return last_result_.c_str();
|
||||
if (state_ != RECOGNIZER_RUNNING) {
|
||||
return StoreReturn("{\"text\": \"\"}");
|
||||
}
|
||||
|
||||
feature_pipeline_->InputFinished();
|
||||
UpdateSilenceWeights();
|
||||
decoder_->AdvanceDecoding();
|
||||
decoder_->FinalizeDecoding();
|
||||
state_ = RECOGNIZER_FINALIZED;
|
||||
GetResult();
|
||||
|
||||
// Free some memory while we are finalized, next
|
||||
// iteration will reinitialize them anyway
|
||||
delete decoder_;
|
||||
delete feature_pipeline_;
|
||||
delete silence_weighting_;
|
||||
delete spk_feature_;
|
||||
|
||||
feature_pipeline_ = NULL;
|
||||
silence_weighting_ = NULL;
|
||||
decoder_ = NULL;
|
||||
spk_feature_ = NULL;
|
||||
|
||||
return last_result_.c_str();
|
||||
}
|
||||
|
||||
// Store result in recognizer and return as const string
|
||||
const char *KaldiRecognizer::StoreReturn(const string &res)
|
||||
{
|
||||
last_result_ = res;
|
||||
return last_result_.c_str();
|
||||
}
|
||||
|
||||
+29
-7
@@ -12,6 +12,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef VOSK_KALDI_RECOGNIZER_H
|
||||
#define VOSK_KALDI_RECOGNIZER_H
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "util/common-utils.h"
|
||||
#include "fstext/fstext-lib.h"
|
||||
@@ -29,11 +32,18 @@
|
||||
|
||||
using namespace kaldi;
|
||||
|
||||
enum KaldiRecognizerState {
|
||||
RECOGNIZER_INITIALIZED,
|
||||
RECOGNIZER_RUNNING,
|
||||
RECOGNIZER_ENDPOINT,
|
||||
RECOGNIZER_FINALIZED
|
||||
};
|
||||
|
||||
class KaldiRecognizer {
|
||||
public:
|
||||
KaldiRecognizer(Model &model, float sample_frequency);
|
||||
KaldiRecognizer(Model &model, SpkModel *spk_model, float sample_frequency);
|
||||
KaldiRecognizer(Model &model, float sample_frequency, char const *grammar);
|
||||
KaldiRecognizer(Model *model, float sample_frequency);
|
||||
KaldiRecognizer(Model *model, SpkModel *spk_model, float sample_frequency);
|
||||
KaldiRecognizer(Model *model, float sample_frequency, char const *grammar);
|
||||
~KaldiRecognizer();
|
||||
bool AcceptWaveform(const char *data, int len);
|
||||
bool AcceptWaveform(const short *sdata, int len);
|
||||
@@ -43,12 +53,16 @@ class KaldiRecognizer {
|
||||
const char* PartialResult();
|
||||
|
||||
private:
|
||||
void InitState();
|
||||
void InitRescoring();
|
||||
void CleanUp();
|
||||
void UpdateSilenceWeights();
|
||||
bool AcceptWaveform(Vector<BaseFloat> &wdata);
|
||||
void GetSpkVector(Vector<BaseFloat> &xvector);
|
||||
bool GetSpkVector(Vector<BaseFloat> &out_xvector, int *frames);
|
||||
const char *GetResult();
|
||||
const char *StoreReturn(const string &res);
|
||||
|
||||
Model &model_;
|
||||
Model *model_;
|
||||
SingleUtteranceNnet3Decoder *decoder_;
|
||||
fst::LookaheadFst<fst::StdArc, int32> *decode_fst_;
|
||||
fst::StdVectorFst *g_fst_; // dynamically constructed grammar
|
||||
@@ -58,8 +72,16 @@ class KaldiRecognizer {
|
||||
SpkModel *spk_model_;
|
||||
OnlineBaseFeature *spk_feature_;
|
||||
|
||||
fst::MapFst<fst::StdArc, kaldi::LatticeArc, fst::StdToLatticeMapper<kaldi::BaseFloat> > *lm_fst_;
|
||||
|
||||
float sample_frequency_;
|
||||
int32 frame_offset_;
|
||||
bool input_finalized_;
|
||||
std::string last_result_;
|
||||
|
||||
int64 samples_processed_;
|
||||
int64 samples_round_start_;
|
||||
|
||||
KaldiRecognizerState state_;
|
||||
string last_result_;
|
||||
};
|
||||
|
||||
#endif /* VOSK_KALDI_RECOGNIZER_H */
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
// Copyright 2015 Johns Hopkins University (author: Daniel Povey)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// A modified version from chain/language-model.cc for static backoff
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
#include "language_model.h"
|
||||
|
||||
using namespace kaldi;
|
||||
|
||||
void LanguageModelEstimator::AddCounts(const std::vector<int32> &sentence) {
|
||||
KALDI_ASSERT(opts_.ngram_order >= 2 && "--ngram-order must be >= 2");
|
||||
int32 order = opts_.ngram_order;
|
||||
// 0 is used for left-context at the beginning of the file.. treat it as BOS.
|
||||
std::vector<int32> history(0);
|
||||
std::vector<int32>::const_iterator iter = sentence.begin(),
|
||||
end = sentence.end();
|
||||
for (; iter != end; ++iter) {
|
||||
KALDI_ASSERT(*iter != 0);
|
||||
IncrementCount(history, *iter);
|
||||
history.push_back(*iter);
|
||||
if (history.size() >= order)
|
||||
history.erase(history.begin());
|
||||
}
|
||||
// Probability of end of sentence. This will end up getting ignored later, but
|
||||
// it still makes a difference for probability-normalization reasons.
|
||||
IncrementCount(history, 0);
|
||||
}
|
||||
|
||||
void LanguageModelEstimator::IncrementCount(const std::vector<int32> &history,
|
||||
int32 next_phone) {
|
||||
int32 lm_state_index = FindOrCreateLmStateIndexForHistory(history);
|
||||
if (lm_states_[lm_state_index].tot_count == 0) {
|
||||
num_active_lm_states_++;
|
||||
}
|
||||
lm_states_[lm_state_index].AddCount(next_phone, 1);
|
||||
}
|
||||
|
||||
void LanguageModelEstimator::SetParentCounts() {
|
||||
int32 num_lm_states = lm_states_.size();
|
||||
for (int32 l = 0; l < num_lm_states; l++) {
|
||||
int32 l_iter = lm_states_[l].backoff_lmstate_index;
|
||||
while (l_iter != -1) {
|
||||
lm_states_[l_iter].Add(lm_states_[l]);
|
||||
l_iter = lm_states_[l_iter].backoff_lmstate_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32 LanguageModelEstimator::FindLmStateIndexForHistory(
|
||||
const std::vector<int32> &hist) const {
|
||||
MapType::const_iterator iter = hist_to_lmstate_index_.find(hist);
|
||||
if (iter == hist_to_lmstate_index_.end())
|
||||
return -1;
|
||||
else
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
int32 LanguageModelEstimator::FindNonzeroLmStateIndexForHistory(
|
||||
std::vector<int32> hist) const {
|
||||
while (1) {
|
||||
int32 l = FindLmStateIndexForHistory(hist);
|
||||
if (l == -1 || lm_states_[l].tot_count == 0) {
|
||||
// no such state or state has zero count.
|
||||
if (hist.empty())
|
||||
KALDI_ERR << "Error looking up LM state index for history "
|
||||
<< "(likely code bug)";
|
||||
hist.erase(hist.begin()); // back off.
|
||||
} else {
|
||||
return l;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32 LanguageModelEstimator::FindOrCreateLmStateIndexForHistory(
|
||||
const std::vector<int32> &hist) {
|
||||
MapType::const_iterator iter = hist_to_lmstate_index_.find(hist);
|
||||
if (iter != hist_to_lmstate_index_.end())
|
||||
return iter->second;
|
||||
int32 ans = lm_states_.size(); // index of next element
|
||||
// next statement relies on default construct of LmState.
|
||||
lm_states_.resize(lm_states_.size() + 1);
|
||||
lm_states_.back().history = hist;
|
||||
hist_to_lmstate_index_[hist] = ans;
|
||||
|
||||
// make sure backoff_lmstate_index is set
|
||||
if (hist.size() > 0) {
|
||||
std::vector<int32> backoff_hist(hist.begin() + 1,
|
||||
hist.end());
|
||||
int32 backoff_lm_state = FindOrCreateLmStateIndexForHistory(backoff_hist);
|
||||
lm_states_[ans].backoff_lmstate_index = backoff_lm_state;
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
void LanguageModelEstimator::LmState::AddCount(int32 phone, int32 count) {
|
||||
std::map<int32, int32>::iterator iter = phone_to_count.find(phone);
|
||||
if (iter == phone_to_count.end())
|
||||
phone_to_count[phone] = count;
|
||||
else
|
||||
iter->second += count;
|
||||
tot_count += count;
|
||||
}
|
||||
|
||||
void LanguageModelEstimator::LmState::Add(const LmState &other) {
|
||||
KALDI_ASSERT(&other != this);
|
||||
std::map<int32, int32>::const_iterator iter = other.phone_to_count.begin(),
|
||||
end = other.phone_to_count.end();
|
||||
for (; iter != end; ++iter)
|
||||
AddCount(iter->first, iter->second);
|
||||
}
|
||||
|
||||
int32 LanguageModelEstimator::AssignFstStates() {
|
||||
int32 num_lm_states = lm_states_.size();
|
||||
int32 current_fst_state = 0;
|
||||
for (int32 l = 0; l < num_lm_states; l++) {
|
||||
if (lm_states_[l].tot_count != 0) {
|
||||
lm_states_[l].fst_state = current_fst_state++;
|
||||
}
|
||||
}
|
||||
KALDI_ASSERT(current_fst_state == num_active_lm_states_);
|
||||
return current_fst_state;
|
||||
}
|
||||
|
||||
void LanguageModelEstimator::Estimate(fst::StdVectorFst *fst) {
|
||||
KALDI_LOG << "Estimating language model with ngram-order="
|
||||
<< opts_.ngram_order << ", discount="
|
||||
<< opts_.discount;
|
||||
SetParentCounts();
|
||||
int32 num_fst_states = AssignFstStates();
|
||||
OutputToFst(num_fst_states, fst);
|
||||
}
|
||||
|
||||
int32 LanguageModelEstimator::FindInitialFstState() const {
|
||||
std::vector<int32> history(0);
|
||||
int32 l = FindNonzeroLmStateIndexForHistory(history);
|
||||
KALDI_ASSERT(l != -1 && lm_states_[l].fst_state != -1);
|
||||
return lm_states_[l].fst_state;
|
||||
}
|
||||
|
||||
void LanguageModelEstimator::OutputToFst(
|
||||
int32 num_states,
|
||||
fst::StdVectorFst *fst) const {
|
||||
KALDI_ASSERT(num_states == num_active_lm_states_);
|
||||
fst->DeleteStates();
|
||||
for (int32 i = 0; i < num_states; i++)
|
||||
fst->AddState();
|
||||
fst->SetStart(FindInitialFstState());
|
||||
|
||||
int64 tot_count = 0;
|
||||
double tot_logprob = 0.0;
|
||||
|
||||
int32 num_lm_states = lm_states_.size();
|
||||
// note: not all lm-states end up being 'active'.
|
||||
for (int32 l = 0; l < num_lm_states; l++) {
|
||||
const LmState &lm_state = lm_states_[l];
|
||||
if (lm_state.fst_state == -1) {
|
||||
continue;
|
||||
}
|
||||
int32 state_count = lm_state.tot_count;
|
||||
KALDI_ASSERT(state_count != 0);
|
||||
std::map<int32, int32>::const_iterator
|
||||
iter = lm_state.phone_to_count.begin(),
|
||||
end = lm_state.phone_to_count.end();
|
||||
for (; iter != end; ++iter) {
|
||||
int32 phone = iter->first, count = iter->second;
|
||||
BaseFloat logprob = log(count * opts_.discount / state_count);
|
||||
tot_count += count;
|
||||
tot_logprob += logprob * count;
|
||||
if (phone == 0) { // Go to final state
|
||||
fst->SetFinal(lm_state.fst_state, fst::TropicalWeight(-logprob));
|
||||
} else { // It becomes a transition.
|
||||
std::vector<int32> next_history(lm_state.history);
|
||||
next_history.push_back(phone);
|
||||
int32 dest_lm_state = FindNonzeroLmStateIndexForHistory(next_history),
|
||||
dest_fst_state = lm_states_[dest_lm_state].fst_state;
|
||||
KALDI_ASSERT(dest_fst_state != -1);
|
||||
fst->AddArc(lm_state.fst_state,
|
||||
fst::StdArc(phone, phone, fst::TropicalWeight(-logprob),
|
||||
dest_fst_state));
|
||||
}
|
||||
}
|
||||
if (lm_state.backoff_lmstate_index >= 0) {
|
||||
fst->AddArc(lm_state.fst_state, fst::StdArc(0, 0, fst::TropicalWeight(-log(1 - opts_.discount)), lm_states_[lm_state.backoff_lmstate_index].fst_state));
|
||||
}
|
||||
}
|
||||
fst::Connect(fst);
|
||||
// Make sure that Connect does not delete any states.
|
||||
int32 num_states_connected = fst->NumStates();
|
||||
KALDI_ASSERT(num_states_connected == num_states);
|
||||
// arc-sort. ilabel or olabel doesn't matter, it's an acceptor.
|
||||
fst::ArcSort(fst, fst::ILabelCompare<fst::StdArc>());
|
||||
KALDI_LOG << "Created language model with " << num_states
|
||||
<< " states and " << fst::NumArcs(*fst) << " arcs.";
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
// Copyright 2015 Johns Hopkins University (Author: Daniel Povey)
|
||||
|
||||
// See ../../COPYING for clarification regarding multiple authors
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
// MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
#ifndef VOSK_LANGUAGE_MODEL_H
|
||||
#define VOSK_LANGUAGE_MODEL_H
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "util/common-utils.h"
|
||||
#include "fstext/fstext-lib.h"
|
||||
#include "lat/kaldi-lattice.h"
|
||||
|
||||
using namespace kaldi;
|
||||
|
||||
// Very simply lm construction with absolute discounting
|
||||
|
||||
struct LanguageModelOptions {
|
||||
int32 ngram_order; // you might want to tune this
|
||||
BaseFloat discount; // discount for backoff
|
||||
|
||||
LanguageModelOptions():
|
||||
ngram_order(3),
|
||||
discount(0.5)
|
||||
{ }
|
||||
|
||||
void Register(OptionsItf *opts) {
|
||||
opts->Register("ngram-order", &ngram_order, "n-gram order for the phone "
|
||||
"language model used for the 'denominator model'");
|
||||
opts->Register("discount", &discount, "Discount for backoff");
|
||||
}
|
||||
};
|
||||
|
||||
class LanguageModelEstimator {
|
||||
public:
|
||||
LanguageModelEstimator(LanguageModelOptions &opts): opts_(opts),
|
||||
num_active_lm_states_(0) {
|
||||
KALDI_ASSERT(opts.ngram_order >= 1);
|
||||
}
|
||||
|
||||
// Adds counts for this sentence. Basically does: for each n-gram in the
|
||||
// sentence, count[n-gram] += 1. The only constraint on 'sentence' is that it
|
||||
// should contain no zeros.
|
||||
void AddCounts(const std::vector<int32> &sentence);
|
||||
|
||||
// Estimates the LM and outputs it as an FST. Note: there is
|
||||
// no concept here of backoff arcs.
|
||||
void Estimate(fst::StdVectorFst *fst);
|
||||
|
||||
protected:
|
||||
struct LmState {
|
||||
// the phone history associated with this state (length can vary).
|
||||
std::vector<int32> history;
|
||||
|
||||
// maps from
|
||||
std::map<int32, int32> phone_to_count;
|
||||
|
||||
// total count of this state. As we back off states to lower-order states
|
||||
// (and note that this is a hard backoff where we completely remove un-needed
|
||||
// states) this tot_count may become zero.
|
||||
int32 tot_count;
|
||||
|
||||
// LM-state index of the backoff LM state (if it exists, else -1)...
|
||||
// provided for convenience.
|
||||
int32 backoff_lmstate_index;
|
||||
|
||||
// this is only set after we decide on the FST state numbering (at the end).
|
||||
// If not set, it's -1.
|
||||
int32 fst_state;
|
||||
|
||||
void AddCount(int32 phone, int32 count);
|
||||
|
||||
// Add the contents of another LmState.
|
||||
void Add(const LmState &other);
|
||||
|
||||
LmState(): tot_count(0), backoff_lmstate_index(-1),
|
||||
fst_state(-1) { }
|
||||
LmState(const LmState &other):
|
||||
history(other.history), phone_to_count(other.phone_to_count),
|
||||
tot_count(other.tot_count),
|
||||
backoff_lmstate_index(other.backoff_lmstate_index),
|
||||
fst_state(other.fst_state) { }
|
||||
};
|
||||
|
||||
// maps from history to int32
|
||||
typedef unordered_map<std::vector<int32>, int32, VectorHasher<int32> > MapType;
|
||||
|
||||
LanguageModelOptions opts_;
|
||||
|
||||
MapType hist_to_lmstate_index_;
|
||||
std::vector<LmState> lm_states_; // indexed by lmstate_index, the LmStates.
|
||||
|
||||
// Keeps track of the number of lm states that have nonzero counts.
|
||||
int32 num_active_lm_states_;
|
||||
|
||||
|
||||
// adds the counts for this ngram (called from AddCounts()).
|
||||
inline void IncrementCount(const std::vector<int32> &history,
|
||||
int32 next_phone);
|
||||
|
||||
// sets up tot_count_with_parents in all the lm-states
|
||||
void SetParentCounts();
|
||||
|
||||
// Finds and returns an LM-state index for a history -- or -1 if it doesn't
|
||||
// exist. No backoff is done.
|
||||
int32 FindLmStateIndexForHistory(const std::vector<int32> &hist) const;
|
||||
|
||||
// Finds and returns an LM-state index for a history -- and creates one if
|
||||
// it doesn't exist -- and also creates any backoff states needed, down
|
||||
// to history-length no_prune_ngram_order - 1.
|
||||
int32 FindOrCreateLmStateIndexForHistory(const std::vector<int32> &hist);
|
||||
|
||||
// Finds and returns the most specific LM-state index for a history or
|
||||
// backed-off versions of it, that exists and has nonzero count. Will die if
|
||||
// there is no such history. [e.g. if there is no unigram backoff state,
|
||||
// which generally speaking there won't be.]
|
||||
int32 FindNonzeroLmStateIndexForHistory(std::vector<int32> hist) const;
|
||||
|
||||
// after all backoff has been done, assigns FST state indexes to all states
|
||||
// that exist and have nonzero count. Returns the number of states.
|
||||
int32 AssignFstStates();
|
||||
|
||||
// find the FST index of the initial-state, and returns it.
|
||||
int32 FindInitialFstState() const;
|
||||
|
||||
// Write to an FST
|
||||
void OutputToFst(
|
||||
int32 num_fst_states,
|
||||
fst::StdVectorFst *fst) const;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
+198
-65
@@ -14,19 +14,7 @@
|
||||
|
||||
|
||||
//
|
||||
// Possible model layout:
|
||||
//
|
||||
// * Default kaldi model with HCLG.fst
|
||||
//
|
||||
// * Lookahead model with const G.fst
|
||||
//
|
||||
// * Lookahead model with ngram G.fst
|
||||
//
|
||||
// * File disambig_tid.int required only for lookadhead models
|
||||
//
|
||||
// * File word_boundary.int is required if we want to have precise word timing information
|
||||
// otherwise we don't do any word alignment. Optionally lexicon alignment can be done
|
||||
// with corresponding C++ code inside kaldi recognizer.
|
||||
// For details of possible model layout see doc/models.md section model-structure
|
||||
|
||||
#include "model.h"
|
||||
|
||||
@@ -45,24 +33,101 @@ static FstRegisterer<NGramFst<StdArc>> NGramFst_StdArc_registerer;
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <android/log.h>
|
||||
static void AndroidLogHandler(const LogMessageEnvelope &env, const char *message)
|
||||
static void KaldiLogHandler(const LogMessageEnvelope &env, const char *message)
|
||||
{
|
||||
__android_log_print(ANDROID_LOG_VERBOSE, "KaldiDemo", message, 1);
|
||||
int priority;
|
||||
if (env.severity > GetVerboseLevel())
|
||||
return;
|
||||
|
||||
if (env.severity > LogMessageEnvelope::kInfo) {
|
||||
priority = ANDROID_LOG_VERBOSE;
|
||||
} else {
|
||||
switch (env.severity) {
|
||||
case LogMessageEnvelope::kInfo:
|
||||
priority = ANDROID_LOG_INFO;
|
||||
break;
|
||||
case LogMessageEnvelope::kWarning:
|
||||
priority = ANDROID_LOG_WARN;
|
||||
break;
|
||||
case LogMessageEnvelope::kAssertFailed:
|
||||
priority = ANDROID_LOG_FATAL;
|
||||
break;
|
||||
case LogMessageEnvelope::kError:
|
||||
default: // If not the ERROR, it still an error!
|
||||
priority = ANDROID_LOG_ERROR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::stringstream full_message;
|
||||
full_message << env.func << "():" << env.file << ':'
|
||||
<< env.line << ") " << message;
|
||||
|
||||
__android_log_print(priority, "VoskAPI", "%s", full_message.str().c_str());
|
||||
}
|
||||
#else
|
||||
static void KaldiLogHandler(const LogMessageEnvelope &env, const char *message)
|
||||
{
|
||||
if (env.severity > GetVerboseLevel())
|
||||
return;
|
||||
|
||||
// Modified default Kaldi logging so we can disable LOG messages.
|
||||
std::stringstream full_message;
|
||||
if (env.severity > LogMessageEnvelope::kInfo) {
|
||||
full_message << "VLOG[" << env.severity << "] (";
|
||||
} else {
|
||||
switch (env.severity) {
|
||||
case LogMessageEnvelope::kInfo:
|
||||
full_message << "LOG (";
|
||||
break;
|
||||
case LogMessageEnvelope::kWarning:
|
||||
full_message << "WARNING (";
|
||||
break;
|
||||
case LogMessageEnvelope::kAssertFailed:
|
||||
full_message << "ASSERTION_FAILED (";
|
||||
break;
|
||||
case LogMessageEnvelope::kError:
|
||||
default: // If not the ERROR, it still an error!
|
||||
full_message << "ERROR (";
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Add other info from the envelope and the message text.
|
||||
full_message << "VoskAPI" << ':'
|
||||
<< env.func << "():" << env.file << ':'
|
||||
<< env.line << ") " << message;
|
||||
|
||||
// Print the complete message to stderr.
|
||||
full_message << "\n";
|
||||
std::cerr << full_message.str();
|
||||
}
|
||||
#endif
|
||||
|
||||
Model::Model(const char *model_path) {
|
||||
Model::Model(const char *model_path) : model_path_str_(model_path) {
|
||||
|
||||
#ifdef __ANDROID__
|
||||
SetLogHandler(AndroidLogHandler);
|
||||
#endif
|
||||
SetLogHandler(KaldiLogHandler);
|
||||
|
||||
const char *usage = "Read the docs";
|
||||
struct stat buffer;
|
||||
string am_path = model_path_str_ + "/am/final.mdl";
|
||||
if (stat(am_path.c_str(), &buffer) == 0) {
|
||||
ConfigureV2();
|
||||
} else {
|
||||
ConfigureV1();
|
||||
}
|
||||
|
||||
ReadDataFiles();
|
||||
|
||||
ref_cnt_ = 1;
|
||||
}
|
||||
|
||||
// Old model layout without model configuration file
|
||||
|
||||
void Model::ConfigureV1()
|
||||
{
|
||||
const char *extra_args[] = {
|
||||
"--min-active=200",
|
||||
"--max-active=3000",
|
||||
"--beam=10.0",
|
||||
"--lattice-beam=2.0",
|
||||
"--max-active=7000",
|
||||
"--beam=13.0",
|
||||
"--lattice-beam=6.0",
|
||||
"--acoustic-scale=1.0",
|
||||
|
||||
"--frame-subsampling-factor=3",
|
||||
@@ -71,55 +136,76 @@ Model::Model(const char *model_path) {
|
||||
"--endpoint.rule2.min-trailing-silence=0.5",
|
||||
"--endpoint.rule3.min-trailing-silence=1.0",
|
||||
"--endpoint.rule4.min-trailing-silence=2.0",
|
||||
};
|
||||
std::string model_path_str(model_path);
|
||||
|
||||
kaldi::ParseOptions po(usage);
|
||||
"--print-args=false",
|
||||
};
|
||||
|
||||
kaldi::ParseOptions po("");
|
||||
nnet3_decoding_config_.Register(&po);
|
||||
endpoint_config_.Register(&po);
|
||||
decodable_opts_.Register(&po);
|
||||
|
||||
std::vector<const char*> args;
|
||||
vector<const char*> args;
|
||||
args.push_back("vosk");
|
||||
args.insert(args.end(), extra_args, extra_args + sizeof(extra_args) / sizeof(extra_args[0]));
|
||||
po.Read(args.size(), args.data());
|
||||
|
||||
nnet3_rxfilename_ = model_path_str_ + "/final.mdl";
|
||||
hclg_fst_rxfilename_ = model_path_str_ + "/HCLG.fst";
|
||||
hcl_fst_rxfilename_ = model_path_str_ + "/HCLr.fst";
|
||||
g_fst_rxfilename_ = model_path_str_ + "/Gr.fst";
|
||||
disambig_rxfilename_ = model_path_str_ + "/disambig_tid.int";
|
||||
word_syms_rxfilename_ = model_path_str_ + "/words.txt";
|
||||
winfo_rxfilename_ = model_path_str_ + "/word_boundary.int";
|
||||
carpa_rxfilename_ = model_path_str_ + "/rescore/G.carpa";
|
||||
std_fst_rxfilename_ = model_path_str_ + "/rescore/G.fst";
|
||||
final_ie_rxfilename_ = model_path_str_ + "/ivector/final.ie";
|
||||
mfcc_conf_rxfilename_ = model_path_str_ + "/mfcc.conf";
|
||||
}
|
||||
|
||||
void Model::ConfigureV2()
|
||||
{
|
||||
kaldi::ParseOptions po("something");
|
||||
nnet3_decoding_config_.Register(&po);
|
||||
endpoint_config_.Register(&po);
|
||||
decodable_opts_.Register(&po);
|
||||
po.ReadConfigFile(model_path_str_ + "/conf/model.conf");
|
||||
|
||||
|
||||
nnet3_rxfilename_ = model_path_str_ + "/am/final.mdl";
|
||||
hclg_fst_rxfilename_ = model_path_str_ + "/graph/HCLG.fst";
|
||||
hcl_fst_rxfilename_ = model_path_str_ + "/graph/HCLr.fst";
|
||||
g_fst_rxfilename_ = model_path_str_ + "/graph/Gr.fst";
|
||||
disambig_rxfilename_ = model_path_str_ + "/graph/disambig_tid.int";
|
||||
word_syms_rxfilename_ = model_path_str_ + "/graph/words.txt";
|
||||
winfo_rxfilename_ = model_path_str_ + "/graph/phones/word_boundary.int";
|
||||
carpa_rxfilename_ = model_path_str_ + "/rescore/G.carpa";
|
||||
std_fst_rxfilename_ = model_path_str_ + "/rescore/G.fst";
|
||||
final_ie_rxfilename_ = model_path_str_ + "/ivector/final.ie";
|
||||
mfcc_conf_rxfilename_ = model_path_str_ + "/conf/mfcc.conf";
|
||||
}
|
||||
|
||||
void Model::ReadDataFiles()
|
||||
{
|
||||
struct stat buffer;
|
||||
|
||||
KALDI_LOG << "Decoding params beam=" << nnet3_decoding_config_.beam <<
|
||||
" max-active=" << nnet3_decoding_config_.max_active <<
|
||||
" lattice-beam=" << nnet3_decoding_config_.lattice_beam;
|
||||
KALDI_LOG << "Silence phones " << endpoint_config_.silence_phones;
|
||||
|
||||
feature_info_.feature_type = "mfcc";
|
||||
ReadConfigFromFile(model_path_str + "/mfcc.conf", &feature_info_.mfcc_opts);
|
||||
ReadConfigFromFile(mfcc_conf_rxfilename_, &feature_info_.mfcc_opts);
|
||||
feature_info_.mfcc_opts.frame_opts.allow_downsample = true; // It is safe to downsample
|
||||
|
||||
feature_info_.silence_weighting_config.silence_weight = 1e-3;
|
||||
feature_info_.silence_weighting_config.silence_phones_str = "1:2:3:4:5:6:7:8:9:10";
|
||||
|
||||
OnlineIvectorExtractionConfig ivector_extraction_opts;
|
||||
ivector_extraction_opts.splice_config_rxfilename = model_path_str + "/ivector/splice.conf";
|
||||
ivector_extraction_opts.cmvn_config_rxfilename = model_path_str + "/ivector/online_cmvn.conf";
|
||||
ivector_extraction_opts.lda_mat_rxfilename = model_path_str + "/ivector/final.mat";
|
||||
ivector_extraction_opts.global_cmvn_stats_rxfilename = model_path_str + "/ivector/global_cmvn.stats";
|
||||
ivector_extraction_opts.diag_ubm_rxfilename = model_path_str + "/ivector/final.dubm";
|
||||
ivector_extraction_opts.ivector_extractor_rxfilename = model_path_str + "/ivector/final.ie";
|
||||
ivector_extraction_opts.num_gselect = 5;
|
||||
ivector_extraction_opts.min_post = 0.025;
|
||||
ivector_extraction_opts.posterior_scale = 0.1;
|
||||
ivector_extraction_opts.max_remembered_frames = 1000;
|
||||
ivector_extraction_opts.max_count = 100;
|
||||
ivector_extraction_opts.ivector_period = 200;
|
||||
feature_info_.use_ivectors = true;
|
||||
feature_info_.ivector_extractor_info.Init(ivector_extraction_opts);
|
||||
|
||||
std::string nnet3_rxfilename = model_path_str + "/final.mdl";
|
||||
std::string hclg_fst_rxfilename = model_path_str + "/HCLG.fst";
|
||||
std::string hcl_fst_rxfilename = model_path_str + "/HCLr.fst";
|
||||
std::string g_fst_rxfilename = model_path_str + "/Gr.fst";
|
||||
std::string disambig_rxfilename = model_path_str + "/disambig_tid.int";
|
||||
std::string word_syms_rxfilename = model_path_str + "/words.txt";
|
||||
std::string winfo_rxfilename = model_path_str + "/word_boundary.int";
|
||||
feature_info_.silence_weighting_config.silence_phones_str = endpoint_config_.silence_phones;
|
||||
|
||||
trans_model_ = new kaldi::TransitionModel();
|
||||
nnet_ = new kaldi::nnet3::AmNnetSimple();
|
||||
{
|
||||
bool binary;
|
||||
kaldi::Input ki(nnet3_rxfilename, &binary);
|
||||
kaldi::Input ki(nnet3_rxfilename_, &binary);
|
||||
trans_model_->Read(ki.Stream(), binary);
|
||||
nnet_->Read(ki.Stream(), binary);
|
||||
SetBatchnormTestMode(true, &(nnet_->GetNnet()));
|
||||
@@ -129,16 +215,35 @@ Model::Model(const char *model_path) {
|
||||
decodable_info_ = new nnet3::DecodableNnetSimpleLoopedInfo(decodable_opts_,
|
||||
nnet_);
|
||||
|
||||
struct stat buffer;
|
||||
if (stat(hclg_fst_rxfilename.c_str(), &buffer) == 0) {
|
||||
hclg_fst_ = fst::ReadFstKaldiGeneric(hclg_fst_rxfilename);
|
||||
if (stat(final_ie_rxfilename_.c_str(), &buffer) == 0) {
|
||||
KALDI_LOG << "Loading i-vector extractor from " << final_ie_rxfilename_;
|
||||
|
||||
OnlineIvectorExtractionConfig ivector_extraction_opts;
|
||||
ivector_extraction_opts.splice_config_rxfilename = model_path_str_ + "/ivector/splice.conf";
|
||||
ivector_extraction_opts.cmvn_config_rxfilename = model_path_str_ + "/ivector/online_cmvn.conf";
|
||||
ivector_extraction_opts.lda_mat_rxfilename = model_path_str_ + "/ivector/final.mat";
|
||||
ivector_extraction_opts.global_cmvn_stats_rxfilename = model_path_str_ + "/ivector/global_cmvn.stats";
|
||||
ivector_extraction_opts.diag_ubm_rxfilename = model_path_str_ + "/ivector/final.dubm";
|
||||
ivector_extraction_opts.ivector_extractor_rxfilename = model_path_str_ + "/ivector/final.ie";
|
||||
ivector_extraction_opts.max_count = 100;
|
||||
|
||||
feature_info_.use_ivectors = true;
|
||||
feature_info_.ivector_extractor_info.Init(ivector_extraction_opts);
|
||||
} else {
|
||||
feature_info_.use_ivectors = false;
|
||||
}
|
||||
|
||||
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_);
|
||||
hcl_fst_ = fst::StdFst::Read(hcl_fst_rxfilename_);
|
||||
g_fst_ = fst::StdFst::Read(g_fst_rxfilename_);
|
||||
ReadIntegerVectorSimple(disambig_rxfilename_, &disambig_);
|
||||
}
|
||||
|
||||
word_syms_ = NULL;
|
||||
@@ -148,18 +253,46 @@ Model::Model(const char *model_path) {
|
||||
word_syms_ = g_fst_->OutputSymbols();
|
||||
}
|
||||
if (!word_syms_) {
|
||||
if (!(word_syms_ = fst::SymbolTable::ReadText(word_syms_rxfilename)))
|
||||
KALDI_LOG << "Loading words from " << word_syms_rxfilename_;
|
||||
if (!(word_syms_ = fst::SymbolTable::ReadText(word_syms_rxfilename_)))
|
||||
KALDI_ERR << "Could not read symbol table from file "
|
||||
<< word_syms_rxfilename;
|
||||
<< word_syms_rxfilename_;
|
||||
}
|
||||
KALDI_ASSERT(word_syms_);
|
||||
|
||||
if (stat(winfo_rxfilename.c_str(), &buffer) == 0) {
|
||||
if (stat(winfo_rxfilename_.c_str(), &buffer) == 0) {
|
||||
KALDI_LOG << "Loading winfo " << winfo_rxfilename_;
|
||||
kaldi::WordBoundaryInfoNewOpts opts;
|
||||
winfo_ = new kaldi::WordBoundaryInfo(opts, winfo_rxfilename);
|
||||
winfo_ = new kaldi::WordBoundaryInfo(opts, winfo_rxfilename_);
|
||||
} else {
|
||||
winfo_ = NULL;
|
||||
}
|
||||
|
||||
if (stat(carpa_rxfilename_.c_str(), &buffer) == 0) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void Model::Ref()
|
||||
{
|
||||
ref_cnt_++;
|
||||
}
|
||||
|
||||
void Model::Unref()
|
||||
{
|
||||
ref_cnt_--;
|
||||
if (ref_cnt_ == 0) {
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
Model::~Model() {
|
||||
|
||||
+30
-5
@@ -12,8 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MODEL_H_
|
||||
#define MODEL_H_
|
||||
#ifndef VOSK_MODEL_H
|
||||
#define VOSK_MODEL_H
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "fstext/fstext-lib.h"
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "rnnlm/rnnlm-utils.h"
|
||||
|
||||
using namespace kaldi;
|
||||
using namespace std;
|
||||
|
||||
class KaldiRecognizer;
|
||||
|
||||
@@ -39,11 +40,30 @@ class Model {
|
||||
|
||||
public:
|
||||
Model(const char *model_path);
|
||||
~Model();
|
||||
void Ref();
|
||||
void Unref();
|
||||
|
||||
protected:
|
||||
~Model();
|
||||
void ConfigureV1();
|
||||
void ConfigureV2();
|
||||
void ReadDataFiles();
|
||||
|
||||
friend class KaldiRecognizer;
|
||||
|
||||
string model_path_str_;
|
||||
string nnet3_rxfilename_;
|
||||
string hclg_fst_rxfilename_;
|
||||
string hcl_fst_rxfilename_;
|
||||
string g_fst_rxfilename_;
|
||||
string disambig_rxfilename_;
|
||||
string word_syms_rxfilename_;
|
||||
string winfo_rxfilename_;
|
||||
string carpa_rxfilename_;
|
||||
string std_fst_rxfilename_;
|
||||
string final_ie_rxfilename_;
|
||||
string mfcc_conf_rxfilename_;
|
||||
|
||||
kaldi::OnlineEndpointConfig endpoint_config_;
|
||||
kaldi::LatticeFasterDecoderConfig nnet3_decoding_config_;
|
||||
kaldi::nnet3::NnetSimpleLoopedComputationOptions decodable_opts_;
|
||||
@@ -54,11 +74,16 @@ protected:
|
||||
kaldi::nnet3::AmNnetSimple *nnet_;
|
||||
const fst::SymbolTable *word_syms_;
|
||||
kaldi::WordBoundaryInfo *winfo_;
|
||||
std::vector<int32> disambig_;
|
||||
vector<int32> disambig_;
|
||||
|
||||
fst::Fst<fst::StdArc> *hclg_fst_;
|
||||
fst::Fst<fst::StdArc> *hcl_fst_;
|
||||
fst::Fst<fst::StdArc> *g_fst_;
|
||||
|
||||
fst::VectorFst<fst::StdArc> *std_lm_fst_;
|
||||
kaldi::ConstArpaLm const_arpa_;
|
||||
|
||||
int ref_cnt_;
|
||||
};
|
||||
|
||||
#endif /* MODEL_H_ */
|
||||
#endif /* VOSK_MODEL_H */
|
||||
|
||||
@@ -24,4 +24,22 @@ SpkModel::SpkModel(const char *speaker_path) {
|
||||
SetBatchnormTestMode(true, &speaker_nnet);
|
||||
SetDropoutTestMode(true, &speaker_nnet);
|
||||
CollapseModel(nnet3::CollapseModelConfig(), &speaker_nnet);
|
||||
|
||||
ReadKaldiObject(speaker_path_str + "/mean.vec", &mean);
|
||||
ReadKaldiObject(speaker_path_str + "/transform.mat", &transform);
|
||||
|
||||
ref_cnt_ = 1;
|
||||
}
|
||||
|
||||
void SpkModel::Ref()
|
||||
{
|
||||
ref_cnt_++;
|
||||
}
|
||||
|
||||
void SpkModel::Unref()
|
||||
{
|
||||
ref_cnt_--;
|
||||
if (ref_cnt_ == 0) {
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
+11
-3
@@ -12,8 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef SPK_MODEL_H_
|
||||
#define SPK_MODEL_H_
|
||||
#ifndef VOSK_SPK_MODEL_H
|
||||
#define VOSK_SPK_MODEL_H
|
||||
|
||||
#include "base/kaldi-common.h"
|
||||
#include "online2/online-feature-pipeline.h"
|
||||
@@ -27,12 +27,20 @@ class SpkModel {
|
||||
|
||||
public:
|
||||
SpkModel(const char *spk_path);
|
||||
void Ref();
|
||||
void Unref();
|
||||
|
||||
protected:
|
||||
friend class KaldiRecognizer;
|
||||
~SpkModel() {};
|
||||
|
||||
kaldi::nnet3::Nnet speaker_nnet;
|
||||
kaldi::Vector<BaseFloat> mean;
|
||||
kaldi::Matrix<BaseFloat> transform;
|
||||
|
||||
MfccOptions spkvector_mfcc_opts;
|
||||
|
||||
int ref_cnt_;
|
||||
};
|
||||
|
||||
#endif /* SPK_MODEL_H_ */
|
||||
#endif /* VOSK_SPK_MODEL_H */
|
||||
|
||||
+27
-3
@@ -1,4 +1,8 @@
|
||||
%module(package="vosk") vosk
|
||||
#if SWIGPYTHON
|
||||
%module(package="vosk", "threads"=1) vosk
|
||||
#else
|
||||
%module Vosk
|
||||
#endif
|
||||
|
||||
%include <typemaps.i>
|
||||
|
||||
@@ -10,8 +14,6 @@
|
||||
%include <arrays_csharp.i>
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#if SWIGPYTHON
|
||||
%pybuffer_binary(const char *data, int len);
|
||||
#endif
|
||||
@@ -32,6 +34,11 @@ import java.nio.ByteOrder;
|
||||
return AcceptWaveform(bdata, bdata.length);
|
||||
}
|
||||
%}
|
||||
%pragma(java) jniclasscode=%{
|
||||
static {
|
||||
System.loadLibrary("vosk_jni");
|
||||
}
|
||||
%}
|
||||
#endif
|
||||
|
||||
#if SWIGCSHARP
|
||||
@@ -42,6 +49,14 @@ CSHARP_ARRAYS(char, byte)
|
||||
#endif
|
||||
|
||||
|
||||
#if SWIGJAVASCRIPT
|
||||
%begin %{
|
||||
#include <v8.h>
|
||||
#include <node.h>
|
||||
#include <node_buffer.h>
|
||||
%}
|
||||
#endif
|
||||
|
||||
%{
|
||||
#include "vosk_api.h"
|
||||
typedef struct VoskModel Model;
|
||||
@@ -99,6 +114,12 @@ typedef struct {} KaldiRecognizer;
|
||||
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);
|
||||
@@ -115,3 +136,6 @@ typedef struct {} KaldiRecognizer;
|
||||
return vosk_recognizer_final_result($self);
|
||||
}
|
||||
}
|
||||
|
||||
%rename(SetLogLevel) vosk_set_log_level;
|
||||
void vosk_set_log_level(int level);
|
||||
|
||||
+10
-5
@@ -28,7 +28,7 @@ VoskModel *vosk_model_new(const char *model_path)
|
||||
|
||||
void vosk_model_free(VoskModel *model)
|
||||
{
|
||||
delete (Model *)model;
|
||||
((Model *)model)->Unref();
|
||||
}
|
||||
|
||||
VoskSpkModel *vosk_spk_model_new(const char *model_path)
|
||||
@@ -38,22 +38,22 @@ VoskSpkModel *vosk_spk_model_new(const char *model_path)
|
||||
|
||||
void vosk_spk_model_free(VoskSpkModel *model)
|
||||
{
|
||||
delete (SpkModel *)model;
|
||||
((SpkModel *)model)->Unref();
|
||||
}
|
||||
|
||||
VoskRecognizer *vosk_recognizer_new(VoskModel *model, float sample_rate)
|
||||
{
|
||||
return (VoskRecognizer *)new KaldiRecognizer(*(Model *)model, sample_rate);
|
||||
return (VoskRecognizer *)new KaldiRecognizer((Model *)model, sample_rate);
|
||||
}
|
||||
|
||||
VoskRecognizer *vosk_recognizer_new_spk(VoskModel *model, VoskSpkModel *spk_model, float sample_rate)
|
||||
{
|
||||
return (VoskRecognizer *)new KaldiRecognizer(*(Model *)model, (SpkModel *)spk_model, sample_rate);
|
||||
return (VoskRecognizer *)new KaldiRecognizer((Model *)model, (SpkModel *)spk_model, sample_rate);
|
||||
}
|
||||
|
||||
VoskRecognizer *vosk_recognizer_new_grm(VoskModel *model, float sample_rate, const char *grammar)
|
||||
{
|
||||
return (VoskRecognizer *)new KaldiRecognizer(*(Model *)model, sample_rate, grammar);
|
||||
return (VoskRecognizer *)new KaldiRecognizer((Model *)model, sample_rate, grammar);
|
||||
}
|
||||
|
||||
int vosk_recognizer_accept_waveform(VoskRecognizer *recognizer, const char *data, int length)
|
||||
@@ -90,3 +90,8 @@ void vosk_recognizer_free(VoskRecognizer *recognizer)
|
||||
{
|
||||
delete (KaldiRecognizer *)(recognizer);
|
||||
}
|
||||
|
||||
void vosk_set_log_level(int log_level)
|
||||
{
|
||||
SetVerboseLevel(log_level);
|
||||
}
|
||||
|
||||
+166
-3
@@ -12,37 +12,200 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/* This header contains the C API for Vosk speech recognition system */
|
||||
|
||||
#ifndef _VOSK_API_H_
|
||||
#define _VOSK_API_H_
|
||||
#ifndef VOSK_API_H
|
||||
#define VOSK_API_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Model stores all the data required for recognition
|
||||
* it contains static data and can be shared across processing
|
||||
* threads. */
|
||||
typedef struct VoskModel VoskModel;
|
||||
|
||||
|
||||
/** Speaker model is the same as model but contains the data
|
||||
* for speaker identification. */
|
||||
typedef struct VoskSpkModel VoskSpkModel;
|
||||
|
||||
|
||||
/** Recognizer object is the main object which processes data.
|
||||
* Each recognizer usually runs in own thread and takes audio as input.
|
||||
* Once audio is processed recognizer returns JSON object as a string
|
||||
* which represent decoded information - words, confidences, times, n-best lists,
|
||||
* speaker information and so on */
|
||||
typedef struct VoskRecognizer VoskRecognizer;
|
||||
|
||||
|
||||
/** Loads model data from the file and returns the model object
|
||||
*
|
||||
* @param model_path: the path of the model on the filesystem
|
||||
@ @returns model object */
|
||||
VoskModel *vosk_model_new(const char *model_path);
|
||||
|
||||
|
||||
/** 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. */
|
||||
void vosk_model_free(VoskModel *model);
|
||||
|
||||
|
||||
/** Loads speaker model data from the file and returns the model object
|
||||
*
|
||||
* @param model_path: the path of the model on the filesystem
|
||||
* @returns model object */
|
||||
VoskSpkModel *vosk_spk_model_new(const char *model_path);
|
||||
|
||||
|
||||
/** 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. */
|
||||
void vosk_spk_model_free(VoskSpkModel *model);
|
||||
|
||||
/** Creates the recognizer object
|
||||
*
|
||||
* The recognizers process the speech and return text using shared model data
|
||||
* @param sample_rate The sample rate of the audio you going to feed into the recognizer
|
||||
* @returns recognizer object */
|
||||
VoskRecognizer *vosk_recognizer_new(VoskModel *model, float sample_rate);
|
||||
|
||||
|
||||
/** Creates the recognizer object with speaker recognition
|
||||
*
|
||||
* 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
|
||||
* @returns recognizer object */
|
||||
VoskRecognizer *vosk_recognizer_new_spk(VoskModel *model, VoskSpkModel *spk_model, float sample_rate);
|
||||
|
||||
|
||||
/** Creates the recognizer object with the phrase list
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @param sample_rate The sample rate of the audio you going to feed into the recognizer
|
||||
* @param grammar The string with the list of phrases to recognize as JSON array of strings,
|
||||
* for example "["one two three four five", "[unk]"]".
|
||||
*
|
||||
* @returns recognizer object */
|
||||
VoskRecognizer *vosk_recognizer_new_grm(VoskModel *model, float sample_rate, const char *grammar);
|
||||
|
||||
|
||||
/** Accept voice data
|
||||
*
|
||||
* accept and process new chunk of voice data
|
||||
*
|
||||
* @param data - audio data in PCM 16-bit mono format
|
||||
* @param length - length of the audio data
|
||||
* @returns true if silence is occured and you can retrieve a new utterance with result method */
|
||||
int vosk_recognizer_accept_waveform(VoskRecognizer *recognizer, const char *data, int length);
|
||||
|
||||
|
||||
/** Same as above but the version with the short data for language bindings where you have
|
||||
* audio as array of shorts */
|
||||
int vosk_recognizer_accept_waveform_s(VoskRecognizer *recognizer, const short *data, int length);
|
||||
|
||||
|
||||
/** Same as above but the version with the float data for language bindings where you have
|
||||
* audio as array of floats */
|
||||
int vosk_recognizer_accept_waveform_f(VoskRecognizer *recognizer, const float *data, int length);
|
||||
|
||||
|
||||
/** Returns speech recognition result
|
||||
*
|
||||
* @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>
|
||||
*/
|
||||
const char *vosk_recognizer_result(VoskRecognizer *recognizer);
|
||||
|
||||
|
||||
/** Returns partial speech recognition
|
||||
*
|
||||
* @returns partial speech recognition text which is not yet finalized.
|
||||
* result may change as recognizer process more data.
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* "partial" : "cyril one eight zero"
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
const char *vosk_recognizer_partial_result(VoskRecognizer *recognizer);
|
||||
|
||||
|
||||
/** 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 speech result in JSON format.
|
||||
*/
|
||||
const char *vosk_recognizer_final_result(VoskRecognizer *recognizer);
|
||||
|
||||
|
||||
/** Releases recognizer object
|
||||
*
|
||||
* Underlying model is also unreferenced and if needed released */
|
||||
void vosk_recognizer_free(VoskRecognizer *recognizer);
|
||||
|
||||
/** Set log level for Kaldi messages
|
||||
*
|
||||
* @param log_level the level
|
||||
* 0 - default value to print info and error messages but no debug
|
||||
* less than 0 - don't print info messages
|
||||
* greather than 0 - more verbose mode
|
||||
*/
|
||||
void vosk_set_log_level(int log_level);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _VOSK_API_H_ */
|
||||
#endif /* VOSK_API_H */
|
||||
|
||||
+40
-18
@@ -10,6 +10,9 @@ RUN apt-get update && \
|
||||
libffi-dev \
|
||||
libpcre3-dev \
|
||||
zlib1g-dev \
|
||||
automake \
|
||||
autoconf \
|
||||
libtool \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN cd /opt \
|
||||
@@ -20,24 +23,6 @@ RUN cd /opt \
|
||||
&& cd .. \
|
||||
&& rm -rf swig-4.0.1.tar.gz swig-4.0.1
|
||||
|
||||
ARG OPENBLAS_ARCH=ARMV7
|
||||
ARG ARM_HARDWARE_OPTS="-mfloat-abi=hard -mfpu=neon"
|
||||
RUN cd /opt \
|
||||
&& export OPENFST_CONFIGURE="--enable-static --enable-shared --enable-far --enable-ngram-fsts --enable-lookahead-fsts --with-pic --disable-bin --host=${CROSS_TRIPLE} --build=x86-linux-gnu" \
|
||||
&& git clone -b lookahead --single-branch https://github.com/alphacep/kaldi \
|
||||
&& cd kaldi/tools \
|
||||
&& git clone -b v0.3.7 --single-branch https://github.com/xianyi/OpenBLAS \
|
||||
&& make PREFIX=$(pwd)/OpenBLAS/install TARGET="${OPENBLAS_ARCH}" HOSTCC=gcc USE_LOCKING=1 USE_THREAD=0 -C OpenBLAS all install \
|
||||
&& sed -i 's:status=0:exit 0:g' extras/check_dependencies.sh \
|
||||
&& make -j 10 openfst \
|
||||
&& cd /opt/kaldi/src \
|
||||
&& sed -i "s:TARGET_ARCH=\"\`uname -m\`\":TARGET_ARCH=$(echo $CROSS_TRIPLE|cut -d - -f 1):g" configure \
|
||||
&& 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 --shared --use-cuda=no \
|
||||
&& make -j 10 online2 \
|
||||
&& find /opt/kaldi -name "*.o" -exec rm {} \;
|
||||
|
||||
RUN cd /opt \
|
||||
&& wget -q https://github.com/openssl/openssl/archive/OpenSSL_1_0_2u.tar.gz \
|
||||
&& tar xf OpenSSL_1_0_2u.tar.gz \
|
||||
@@ -80,3 +65,40 @@ RUN cd /opt \
|
||||
&& make -j $(nproc) \
|
||||
&& make install \
|
||||
&& rm -rf /opt/cpython-3.6.10 /opt/cpython-3.6.10-cross /opt/v3.6.10.tar.gz
|
||||
|
||||
RUN cd /opt \
|
||||
&& wget -q https://github.com/python/cpython/archive/v3.8.3.tar.gz \
|
||||
&& tar xf v3.8.3.tar.gz \
|
||||
&& cp -r cpython-3.8.3 cpython-3.8.3-cross \
|
||||
&& cd /opt/cpython-3.8.3 \
|
||||
&& AR=/usr/bin/ar RANLIB=/usr/bin/ranlib CPP=/usr/bin/cpp CXX=/usr/bin/g++ CC=/usr/bin/gcc ./configure --prefix="/opt/python/cp3.8-cp3.8m" \
|
||||
&& make -j $(nproc) \
|
||||
&& make install \
|
||||
&& /opt/python/cp3.8-cp3.8m/bin/pip3 install -U pip \
|
||||
&& /opt/python/cp3.8-cp3.8m/bin/pip3 install -U wheel \
|
||||
&& cd /opt/cpython-3.8.3-cross \
|
||||
&& export PATH=/opt/python/cp3.8-cp3.8m/bin:$PATH \
|
||||
&& ./configure --prefix=$CROSS_ROOT --with-openssl=$CROSS_ROOT --host=${CROSS_TRIPLE} --build=x86-linux-gnu --disable-ipv6 ac_cv_file__dev_ptmx=no ac_cv_file__dev_ptc=no ac_cv_have_long_long_format=yes \
|
||||
&& make -j $(nproc) \
|
||||
&& make install \
|
||||
&& rm -rf /opt/cpython-3.8.3 /opt/cpython-3.8.3-cross /opt/v3.8.3.tar.gz
|
||||
|
||||
ARG OPENBLAS_ARCH=ARMV7
|
||||
ARG ARM_HARDWARE_OPTS="-mfloat-abi=hard -mfpu=neon"
|
||||
RUN cd /opt \
|
||||
&& git clone -b lookahead --single-branch https://github.com/alphacep/kaldi \
|
||||
&& cd kaldi/tools \
|
||||
&& git clone -b v0.3.7 --single-branch https://github.com/xianyi/OpenBLAS \
|
||||
&& make PREFIX=$(pwd)/OpenBLAS/install TARGET="${OPENBLAS_ARCH}" HOSTCC=gcc USE_LOCKING=1 USE_THREAD=0 -C OpenBLAS all install \
|
||||
&& git clone -b old-gcc --single-branch https://github.com/alphacep/openfst openfst \
|
||||
&& cd openfst \
|
||||
&& autoreconf -i \
|
||||
&& ./configure --prefix=/opt/kaldi/tools/openfst --enable-static --enable-shared --enable-far --enable-ngram-fsts --enable-lookahead-fsts --with-pic --disable-bin --host=${CROSS_TRIPLE} --build=x86-linux-gnu \
|
||||
&& make -j 10 && make install \
|
||||
&& cd /opt/kaldi/src \
|
||||
&& sed -i "s:TARGET_ARCH=\"\`uname -m\`\":TARGET_ARCH=$(echo $CROSS_TRIPLE|cut -d - -f 1):g" configure \
|
||||
&& 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 --shared --use-cuda=no \
|
||||
&& make -j 10 online2 lm \
|
||||
&& find /opt/kaldi -name "*.o" -exec rm {} \;
|
||||
|
||||
+18
-21
@@ -7,29 +7,11 @@ RUN yum -y update && yum -y install \
|
||||
wget \
|
||||
openssl-devel \
|
||||
pcre-devel \
|
||||
automake \
|
||||
autoconf \
|
||||
libtool \
|
||||
&& yum clean all
|
||||
|
||||
RUN cd /opt \
|
||||
&& wget https://github.com/Kitware/CMake/releases/download/v3.16.2/cmake-3.16.2.tar.gz \
|
||||
&& tar xf cmake-3.16.2.tar.gz \
|
||||
&& cd cmake-3.16.2 \
|
||||
&& ./configure --prefix=/usr && make -j 10 && make install \
|
||||
&& cd .. \
|
||||
&& rm -rf cmake-3.16.2 cmake-3.16.2.tar.gz
|
||||
|
||||
RUN cd /opt \
|
||||
&& export OPENFST_CONFIGURE="--enable-static --enable-shared --enable-far --enable-ngram-fsts --enable-lookahead-fsts --with-pic --disable-bin" \
|
||||
&& git clone -b lookahead --single-branch https://github.com/alphacep/kaldi \
|
||||
&& cd kaldi/tools \
|
||||
&& git clone -b v0.3.7 --single-branch https://github.com/xianyi/OpenBLAS \
|
||||
&& make PREFIX=$(pwd)/OpenBLAS/install TARGET=NEHALEM USE_LOCKING=1 USE_THREAD=0 -C OpenBLAS all install \
|
||||
&& sed -i 's:status=0:exit 0:g' extras/check_dependencies.sh \
|
||||
&& make -j 10 openfst \
|
||||
&& cd ../src \
|
||||
&& ./configure --mathlib=OPENBLAS --shared --use-cuda=no \
|
||||
&& make -j 10 online2 \
|
||||
&& find /opt/kaldi -name "*.o" -exec rm {} \;
|
||||
|
||||
RUN cd /opt \
|
||||
&& wget -O swig-4.0.1.tar.gz https://sourceforge.net/projects/swig/files/swig/swig-4.0.1/swig-4.0.1.tar.gz/download \
|
||||
&& tar xf swig-4.0.1.tar.gz \
|
||||
@@ -37,3 +19,18 @@ RUN cd /opt \
|
||||
&& ./configure --prefix=/usr && make -j 10 && make install \
|
||||
&& cd .. \
|
||||
&& rm -rf swig-4.0.1.tar.gz swig-4.0.1
|
||||
|
||||
RUN cd /opt \
|
||||
&& git clone -b lookahead --single-branch https://github.com/alphacep/kaldi \
|
||||
&& cd kaldi/tools \
|
||||
&& git clone -b v0.3.7 --single-branch https://github.com/xianyi/OpenBLAS \
|
||||
&& make PREFIX=$(pwd)/OpenBLAS/install DYNAMIC_ARCH=1 USE_LOCKING=1 USE_THREAD=0 -C OpenBLAS all install \
|
||||
&& git clone https://github.com/alphacep/openfst openfst \
|
||||
&& cd openfst \
|
||||
&& autoreconf -i \
|
||||
&& ./configure --prefix=/opt/kaldi/tools/openfst --enable-static --enable-shared --enable-far --enable-ngram-fsts --enable-lookahead-fsts --with-pic --disable-bin \
|
||||
&& make -j 10 && make install \
|
||||
&& cd /opt/kaldi/src \
|
||||
&& ./configure --mathlib=OPENBLAS --shared --use-cuda=no \
|
||||
&& make -j 10 online2 lm rnnlm \
|
||||
&& find /opt/kaldi -name "*.o" -exec rm {} \;
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
set -e
|
||||
set -x
|
||||
|
||||
skip() {
|
||||
docker build --build-arg="DOCKCROSS_IMAGE=linux-armv7" --build-arg="OPENBLAS_ARCH=ARMV7" --file Dockerfile.dockcross --tag alphacep/kaldi-dockcross-armv7:latest .
|
||||
docker build --build-arg="DOCKCROSS_IMAGE=linux-armv6" --build-arg="OPENBLAS_ARCH=ARMV6" --build-arg="ARM_HARDWARE_OPTS=" --file Dockerfile.dockcross --tag alphacep/kaldi-dockcross-armv6:latest .
|
||||
docker build --build-arg="DOCKCROSS_IMAGE=linux-arm64" --build-arg="OPENBLAS_ARCH=ARMV8" --file Dockerfile.dockcross --tag alphacep/kaldi-dockcross-arm64:latest .
|
||||
}
|
||||
|
||||
docker run --rm -v /home/shmyrev/travis/vosk-api/:/io alphacep/kaldi-dockcross-armv6 /io/travis/build-wheels-dockcross.sh
|
||||
docker run --rm -v /home/shmyrev/travis/vosk-api/:/io alphacep/kaldi-dockcross-armv7 /io/travis/build-wheels-dockcross.sh
|
||||
|
||||
@@ -2,24 +2,36 @@
|
||||
set -e -x
|
||||
|
||||
ORIG_PATH=$PATH
|
||||
for pyver in 3.6 3.7; do
|
||||
for pyver in 3.6 3.7 3.8; do
|
||||
|
||||
export KALDI_ROOT=/opt/kaldi
|
||||
export WHEEL_FLAGS=`$CROSS_ROOT/bin/python${pyver}-config --cflags`
|
||||
export PATH=/opt/python/cp${pyver}-cp${pyver}m/bin:$ORIG_PATH
|
||||
echo $CROSS_TRIPLE
|
||||
export VOSK_SOURCE=/io/src
|
||||
|
||||
# Python 3.8 somehow changed syconfig file name
|
||||
sysconfig_bit="m"
|
||||
if [ $pyver == "3.8" ]; then
|
||||
sysconfig_bit=""
|
||||
fi
|
||||
|
||||
case $CROSS_TRIPLE in
|
||||
*arm-*)
|
||||
export _PYTHON_HOST_PLATFORM=linux-armv6l
|
||||
export _PYTHON_SYSCONFIGDATA_NAME=_sysconfigdata_${sysconfig_bit}_linux_arm-linux-gnueabihf
|
||||
;;
|
||||
*armv7-*)
|
||||
export _PYTHON_HOST_PLATFORM=linux-armv7l
|
||||
export _PYTHON_SYSCONFIGDATA_NAME=_sysconfigdata_${sysconfig_bit}_linux_arm-linux-gnueabihf
|
||||
;;
|
||||
*aarch64-*)
|
||||
export _PYTHON_HOST_PLATFORM=linux-aarch64
|
||||
export _PYTHON_SYSCONFIGDATA_NAME=_sysconfigdata_${sysconfig_bit}_linux_aarch64-linux-gnu
|
||||
;;
|
||||
esac
|
||||
export PYTHONHOME=$CROSS_ROOT
|
||||
export PYTHONPATH=/opt/python/cp${pyver}-cp${pyver}m/lib/python${pyver}/site-packages:/opt/python/cp${pyver}-cp${pyver}m/lib/python${pyver}/lib-dynload
|
||||
|
||||
pip3 wheel /io/python -w /io/wheelhouse
|
||||
rm -rf /io/python/build
|
||||
pip${pyver} -v wheel /io/python -w /io/wheelhouse
|
||||
|
||||
done
|
||||
|
||||
@@ -5,9 +5,10 @@ export KALDI_ROOT=/opt/kaldi
|
||||
|
||||
# Compile wheels
|
||||
for pypath in /opt/python/cp3[56789]*; do
|
||||
export WHEEL_FLAGS=`${pypath}/bin/python3-config --cflags`
|
||||
export VOSK_SOURCE=/io/src
|
||||
mkdir -p /opt/wheelhouse
|
||||
"${pypath}/bin/pip" wheel /io/python -w /opt/wheelhouse
|
||||
rm -rf /io/python/build
|
||||
"${pypath}/bin/pip" -v wheel /io/python -w /opt/wheelhouse
|
||||
done
|
||||
|
||||
# Bundle external shared libraries into the wheels
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
exports.printMsg = function() {
|
||||
console.log("This is a message from the Vosk package");
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "vosk-js",
|
||||
"version": "0.3.0",
|
||||
"description": "Node binding for continuous voice recoginition through vosk-api.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/alphacep/vosk-api.git"
|
||||
},
|
||||
"main": "index.js",
|
||||
"keywords": [
|
||||
"speech",
|
||||
"speech recognition",
|
||||
"voice"
|
||||
],
|
||||
"author": "Alpha Cephei Inc.",
|
||||
"license": "Apache 2.0",
|
||||
"engines": {
|
||||
"node": ">= 12.x.x"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user