Compare commits
305 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ea5177e22d | |||
| cdb9a183e7 | |||
| ee0eac64f3 | |||
| 7303e38495 | |||
| 241123ccd2 | |||
| 692717c500 | |||
| 742c112ba4 | |||
| 7a9a8cd6a1 | |||
| cf76d35985 | |||
| b72fdf9248 | |||
| 34ebc48539 | |||
| 8409db4c15 | |||
| dad4ba398b | |||
| 762bb60a73 | |||
| 32b099bf85 | |||
| 030ad8d7a0 | |||
| a82c11bb11 | |||
| 57bfc1a96a | |||
| 1e71ad78f6 | |||
| ba19a2bd37 | |||
| 6db8bfa0f5 | |||
| f4832ca301 | |||
| aabd069cff | |||
| 02690ac087 | |||
| aba84973b1 | |||
| 339b1c5d00 | |||
| a35728fa67 | |||
| 322a57b512 | |||
| a21d1ad051 | |||
| a47fa9147b | |||
| dfb76f0126 | |||
| c3e3fc002f | |||
| 62fc308bdf | |||
| 60c0e33901 | |||
| 5d09ee8b2b | |||
| 12f29a3415 | |||
| d56221bcf2 | |||
| a2eb39d2d7 | |||
| 128c216c61 | |||
| da87d0dad3 | |||
| 9cee2899bb | |||
| a40742bbaf | |||
| d714dff8d3 | |||
| 790c564d14 | |||
| 988ba3f257 | |||
| 2796da0a50 | |||
| d9c6fc4958 | |||
| fff40ce1a1 | |||
| 2deb7de1dd | |||
| 7d87267c31 | |||
| 64d84b01b1 | |||
| cf2560c9f8 | |||
| 800d3d7089 | |||
| b471207f7a | |||
| 25c59b52e3 | |||
| 4c72097478 | |||
| 1053cfa0f8 | |||
| 21a42cb6cd | |||
| 16c4a0d985 | |||
| 298253401a | |||
| 32aa980069 | |||
| 7b4d396eb1 | |||
| 36968fbb30 | |||
| 7474888801 | |||
| d46b7a43eb | |||
| 2376b32a8a | |||
| b8a88cc30c | |||
| be0b117711 | |||
| 449c8ea5af | |||
| 900da76652 | |||
| df0ee24084 | |||
| d3d8f53156 | |||
| 6eee303d7e | |||
| 053d71f5aa | |||
| 4fbbf5882c | |||
| 7012103b3b | |||
| 967024d20a | |||
| b966a8078b | |||
| f63b015284 | |||
| 0a9672d910 | |||
| 4ccccd0cd2 | |||
| 354fb672a3 | |||
| 73abf0740a | |||
| 81c82935ac | |||
| ac3ec56584 | |||
| 2cbe12d4d0 | |||
| 1475b0e986 | |||
| 8ceab0b9b1 | |||
| 1496b597d3 | |||
| 983519e629 | |||
| 58fa98ccd7 | |||
| a7bc5a22d4 | |||
| 23bbff0b56 | |||
| a47b58e2f4 | |||
| 8cf64ee93e | |||
| 630edeb3d6 | |||
| b1b216d4c8 | |||
| 55dd29b0ff | |||
| ea0568a38d | |||
| 298c86d0d4 | |||
| 859420809b | |||
| 0fe3a89768 | |||
| 5b892fbfc5 | |||
| fb4ed21a7f | |||
| 4209f3a9fe | |||
| ff2c80d5f1 | |||
| 3a07a08121 | |||
| 592da81a8c | |||
| def8c93711 | |||
| f73088da58 | |||
| 06a761ecbd | |||
| 97d737a30a | |||
| c7bffbf603 | |||
| 02c40ea612 | |||
| ce5ffb980a | |||
| d5ca98a982 | |||
| b0146782d6 | |||
| 5aaea8fc90 | |||
| 5d7752b657 | |||
| 9d94746479 | |||
| a87f2e1e07 | |||
| 7b7d814484 | |||
| 2daf67f31c | |||
| 62dd631379 | |||
| 2511192ecb | |||
| 22cb90de4a | |||
| 3951834df2 | |||
| 1ea0de106c | |||
| a9bf929ebd | |||
| 3336cd704b | |||
| a57a84f90e | |||
| ad546a8f1a | |||
| 1f447a8dfc | |||
| f574d896e9 | |||
| a561c2d6d4 | |||
| 79b8395be0 | |||
| d2c11a611f | |||
| b0903413b1 | |||
| 2135223490 | |||
| 6f86944a06 | |||
| 9861be2787 | |||
| c6fab363e6 | |||
| c32099705f | |||
| a1eac015dc | |||
| 64dfc65d51 | |||
| 70d5cbd0e0 | |||
| 5428d36d16 | |||
| ed4c15b7aa | |||
| 525b722c44 | |||
| 72bf210164 | |||
| 93e81c3bc8 | |||
| cb0f8e6411 | |||
| 848b2dc753 | |||
| 60f0396fe0 | |||
| 344e137a61 | |||
| 6977be7fb7 | |||
| a4721de8aa | |||
| 378ba122c8 | |||
| 287160622f | |||
| a5d788a2e9 | |||
| 7f651e1e45 | |||
| ad5bec114d | |||
| f71c62ad0f | |||
| 44f7dd2d8b | |||
| 15a9508a78 | |||
| bdea9a53e8 | |||
| 81f58667ff | |||
| 680a2e4c31 | |||
| 13993db542 | |||
| 59d595a4f0 | |||
| 4c562e15a4 | |||
| 5bcdf454ec | |||
| 4ccdda44ac | |||
| 5e46825474 | |||
| fcab5a9581 | |||
| e7f5e0ac23 | |||
| 9a3906831b | |||
| 195db43b9e | |||
| 332553ec1e | |||
| 646af3f652 | |||
| f24ac65fcb | |||
| 14312c93f9 | |||
| 4346d4155d | |||
| 7790ac5040 | |||
| 7b3ea0b59d | |||
| e2af710369 | |||
| 6b1b620f39 | |||
| 966524da16 | |||
| 83c999f298 | |||
| abff8a4f56 | |||
| 188575f3a2 | |||
| 9f4d8ca187 | |||
| 72cc8f3a5e | |||
| 915dcab597 | |||
| d82052b168 | |||
| dbb65bc71c | |||
| 2498bc595b | |||
| fee63d712f | |||
| 5965ea30e6 | |||
| 5c75495c03 | |||
| 98ae3c66cb | |||
| 942e15484a | |||
| 2349e66a97 | |||
| 5750e3224f | |||
| 558b4dd69e | |||
| 02ef49f67e | |||
| 7cdf8f1d03 | |||
| 7f894f784a | |||
| a770251151 | |||
| 7ccf743bb6 | |||
| 75bedfe06d | |||
| 4ea9885d44 | |||
| 0930def9ec | |||
| 6aa5af7640 | |||
| e74fe5edf7 | |||
| cbb5d0fcdf | |||
| d489bc40e4 | |||
| 499b2f183a | |||
| f57b926f66 | |||
| 733dca5aea | |||
| 1ab7e8ca87 | |||
| 53426f794c | |||
| f8189685e5 | |||
| e6bd200c85 | |||
| 4a4c33fd9d | |||
| dc44922b92 | |||
| 67896cfba7 | |||
| ee8663e6d9 | |||
| 652c05f052 | |||
| 193d129d9e | |||
| e995df4bc3 | |||
| f6a30c3626 | |||
| ee95420f8f | |||
| 2221cd0209 | |||
| 8368d831aa | |||
| 91a128b3ed | |||
| 83d8b63351 | |||
| 0881aa856f | |||
| 77abb02ada | |||
| 119c6cd0dc | |||
| cc649aa78e | |||
| 0b0c9fce0e | |||
| d845e2107f | |||
| 69992a1b39 | |||
| f351280f49 | |||
| c15065b044 | |||
| db41f16051 | |||
| e276de61be | |||
| 43c786ab18 | |||
| 1199f39d55 | |||
| 70aab86257 | |||
| e4b0af7a8e | |||
| a120d06f6b | |||
| e01ca37e45 | |||
| f5c3d898e2 | |||
| a5a3697b7c | |||
| 4be9874ba2 | |||
| 5647a2dc6a | |||
| a067a5b5f3 | |||
| 475b0ecfc1 | |||
| 2a9a605144 | |||
| eea7ca571b | |||
| 11c16610ee | |||
| 2f4124eb66 | |||
| db3e31d7ce | |||
| d917af21ab | |||
| 948c8c7cfd | |||
| dec8e1acf3 | |||
| 3c020526ce | |||
| b0da19b07f | |||
| 70bcd9b018 | |||
| d2604b609d | |||
| e960e0cacf | |||
| 77b114d434 | |||
| 2860092cef | |||
| c6119c4835 | |||
| 415d1927e0 | |||
| 3d1e21d242 | |||
| 7a75e1c3f4 | |||
| c3430e448a | |||
| ae49ea60d2 | |||
| 02dc0ce0c8 | |||
| 15697a18e8 | |||
| 481881e59d | |||
| 11a25b26a7 | |||
| bf87358f6c | |||
| 04a8242230 | |||
| ceb96c301c | |||
| 307df8fdc0 | |||
| c44875a2dd | |||
| 31f11990ca | |||
| b79b85856d | |||
| 6e861c6a19 | |||
| cf62296a51 | |||
| 387b132814 | |||
| de83de8624 | |||
| fe91b5a717 | |||
| a052506a5d | |||
| 6f2d6d0d69 | |||
| a8ae6025bd | |||
| 1b8332a609 | |||
| fe16ec7e57 | |||
| c5ce5e46bd | |||
| 99d66670bc | |||
| 072b42cac6 |
+39
-32
@@ -1,22 +1,25 @@
|
||||
# Built application files
|
||||
*.apk
|
||||
*.ap_
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
|
||||
# Temp files
|
||||
nohup.out
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
gradlew
|
||||
gradlew.bat
|
||||
gradle
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
gradle.properties
|
||||
|
||||
# Android
|
||||
android/build
|
||||
android/lib/build
|
||||
android/model-en/build
|
||||
android/model-en/src/main/assets/model-en-us
|
||||
android/repo
|
||||
*.apk
|
||||
*.ap_
|
||||
|
||||
# Cmake
|
||||
.cxx
|
||||
@@ -26,13 +29,9 @@ cmake_install.cmake
|
||||
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
|
||||
bindings/python/dist
|
||||
bindings/python/build
|
||||
bindings/python/vosk/*.so
|
||||
python/test/db
|
||||
python/test/hyp
|
||||
python/test/model
|
||||
@@ -41,24 +40,32 @@ python/test/result.txt
|
||||
python/test/wav.scp
|
||||
|
||||
# Java
|
||||
*.so
|
||||
java/org
|
||||
java/*.cc
|
||||
java/model-spk/
|
||||
java/model/
|
||||
*.class
|
||||
bindings/java/lib/model
|
||||
bindings/java/demo/model
|
||||
bindings/java/lib/build
|
||||
bindings/java/demo/build
|
||||
|
||||
# CSharp
|
||||
csharp/gen
|
||||
csharp/*.exe
|
||||
csharp/*.c
|
||||
csharp/model/
|
||||
csharp/test.wav
|
||||
Vosk.dll
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
*.nupkg
|
||||
csharp/demo/model
|
||||
csharp/demo/test.wav
|
||||
csharp/demo/bin
|
||||
csharp/demo/obj
|
||||
|
||||
# Node
|
||||
nodejs/vosk_wrap.cc
|
||||
nodejs/example/model
|
||||
nodejs/example/test.wav
|
||||
nodejs/demo/model
|
||||
nodejs/demo/model-spk
|
||||
nodejs/demo/test.wav
|
||||
nodejs/node_modules
|
||||
nodejs/package-lock.json
|
||||
nodejs/build
|
||||
|
||||
# C
|
||||
c/test_vosk
|
||||
c/test_vosk_speaker
|
||||
c/oprofile_data
|
||||
c/model
|
||||
c/test.wav
|
||||
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- sudo: required
|
||||
services:
|
||||
- docker
|
||||
env: DOCKER_IMAGE=alphacep/kaldi-manylinux:latest
|
||||
PLAT=manylinux2010_x86_64
|
||||
|
||||
install:
|
||||
- docker pull $DOCKER_IMAGE
|
||||
|
||||
script:
|
||||
- docker run --rm -e PLAT=$PLAT -v `pwd`:/io $DOCKER_IMAGE $PRE_CMD /io/travis/build-wheels.sh
|
||||
- ls wheelhouse/
|
||||
@@ -1,16 +1,18 @@
|
||||
# About
|
||||
# Vosk Speech Recognition Toolkit
|
||||
|
||||
Vosk is an offline open source speech recognition toolkit. It enables
|
||||
speech recognition models for 17 languages and dialects - English, Indian
|
||||
speech recognition for 20+ languages and dialects - English, Indian
|
||||
English, German, French, Spanish, Portuguese, Chinese, Russian, Turkish,
|
||||
Vietnamese, Italian, Dutch, Catalan, Arabic, Greek, Farsi, Filipino.
|
||||
Vietnamese, Italian, Dutch, Catalan, Arabic, Greek, Farsi, Filipino,
|
||||
Ukrainian, Kazakh, Swedish, Japanese, Esperanto, Hindi, Czech, Polish.
|
||||
More to come.
|
||||
|
||||
Vosk models are small (50 Mb) but provide continuous large vocabulary
|
||||
transcription, zero-latency response with streaming API, reconfigurable
|
||||
vocabulary and speaker identification.
|
||||
|
||||
Speech recognition bindings implemented for various programming languages
|
||||
like Python, Java, Node.JS, C#, C++ and others.
|
||||
like Python, Java, Node.JS, C#, C++, Rust, Go and others.
|
||||
|
||||
Vosk supplies speech recognition for chatbots, smart home appliances,
|
||||
virtual assistants. It can also create subtitles for movies,
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
# Vosk CMake File
|
||||
|
||||
cmake_minimum_required(VERSION 3.4.1)
|
||||
|
||||
if ("x${ANDROID_ABI}" STREQUAL "xarmeabi-v7a")
|
||||
set(OPENBLAS_ARCH "armv7")
|
||||
set(KALDI_SUFFIX "arm_32")
|
||||
elseif ("x${ANDROID_ABI}" STREQUAL "xarm64-v8a")
|
||||
set(OPENBLAS_ARCH "armv8")
|
||||
set(KALDI_SUFFIX "arm_64")
|
||||
elseif ("x${ANDROID_ABI}" STREQUAL "xx86")
|
||||
set(OPENBLAS_ARCH "atom")
|
||||
set(KALDI_SUFFIX "x86")
|
||||
else ("x${ANDROID_ABI}" STREQUAL "xarmeabi-v7a")
|
||||
set(OPENBLAS_ARCH "atom")
|
||||
set(KALDI_SUFFIX "x86_64")
|
||||
endif ("x${ANDROID_ABI}" STREQUAL "xarmeabi-v7a")
|
||||
|
||||
set(KALDI_ROOT "${PROJECT_SOURCE_DIR}/build/kaldi_${KALDI_SUFFIX}/kaldi")
|
||||
set(LIB_ROOT "${PROJECT_SOURCE_DIR}/build/kaldi_${KALDI_SUFFIX}/local")
|
||||
|
||||
set(API_SOURCES
|
||||
"${PROJECT_SOURCE_DIR}/../src/kaldi_recognizer.cc"
|
||||
"${PROJECT_SOURCE_DIR}/../src/kaldi_recognizer.h"
|
||||
"${PROJECT_SOURCE_DIR}/../src/language_model.cc"
|
||||
"${PROJECT_SOURCE_DIR}/../src/language_model.h"
|
||||
"${PROJECT_SOURCE_DIR}/../src/model.cc"
|
||||
"${PROJECT_SOURCE_DIR}/../src/model.h"
|
||||
"${PROJECT_SOURCE_DIR}/../src/spk_model.cc"
|
||||
"${PROJECT_SOURCE_DIR}/../src/spk_model.h"
|
||||
"${PROJECT_SOURCE_DIR}/../src/vosk_api.cc"
|
||||
"${PROJECT_SOURCE_DIR}/../src/vosk_api.h"
|
||||
)
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -DFST_NO_DYNAMIC_LINKING")
|
||||
|
||||
add_library( vosk_jni SHARED
|
||||
build/generated-src/cpp/vosk_wrap.cc
|
||||
${API_SOURCES}
|
||||
)
|
||||
|
||||
include_directories("${PROJECT_SOURCE_DIR}/../src" "build/kaldi_${KALDI_SUFFIX}/kaldi/src" "build/kaldi_${KALDI_SUFFIX}/local/include")
|
||||
|
||||
target_link_libraries( vosk_jni
|
||||
${KALDI_ROOT}/src/online2/kaldi-online2.a
|
||||
${KALDI_ROOT}/src/decoder/kaldi-decoder.a
|
||||
${KALDI_ROOT}/src/ivector/kaldi-ivector.a
|
||||
${KALDI_ROOT}/src/gmm/kaldi-gmm.a
|
||||
${KALDI_ROOT}/src/nnet3/kaldi-nnet3.a
|
||||
${KALDI_ROOT}/src/tree/kaldi-tree.a
|
||||
${KALDI_ROOT}/src/feat/kaldi-feat.a
|
||||
${KALDI_ROOT}/src/lat/kaldi-lat.a
|
||||
${KALDI_ROOT}/src/lm/kaldi-lm.a
|
||||
${KALDI_ROOT}/src/hmm/kaldi-hmm.a
|
||||
${KALDI_ROOT}/src/transform/kaldi-transform.a
|
||||
${KALDI_ROOT}/src/cudamatrix/kaldi-cudamatrix.a
|
||||
${KALDI_ROOT}/src/matrix/kaldi-matrix.a
|
||||
${KALDI_ROOT}/src/fstext/kaldi-fstext.a
|
||||
${KALDI_ROOT}/src/util/kaldi-util.a
|
||||
${KALDI_ROOT}/src/base/kaldi-base.a
|
||||
${LIB_ROOT}/lib/libfst.a
|
||||
${LIB_ROOT}/lib/libfstngram.a
|
||||
${LIB_ROOT}/lib/libopenblas_${OPENBLAS_ARCH}-r0.3.7.a
|
||||
${LIB_ROOT}/lib/libclapack.a
|
||||
${LIB_ROOT}/lib/liblapack.a
|
||||
${LIB_ROOT}/lib/libblas.a
|
||||
${LIB_ROOT}/lib/libf2c.a
|
||||
log
|
||||
)
|
||||
@@ -1,3 +0,0 @@
|
||||
Vosk library for Android
|
||||
|
||||
See for details https://alphacephei.com/vosk/android
|
||||
@@ -1,141 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright 2019 Alpha Cephei Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
if [ "x$ANDROID_SDK_HOME" == "x" ]; then
|
||||
echo "ANDROID_SDK_HOME environment variable is undefined, define it with local.properties or with export"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$ANDROID_SDK_HOME" ]; then
|
||||
echo "ANDROID_SDK_HOME ($ANDROID_SDK_HOME) is missing. Make sure you have sdk installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$ANDROID_SDK_HOME/ndk-bundle" ]; then
|
||||
echo "$ANDROID_SDK_HOME/ndk-bundle is missing. Make sure you have ndk installed within sdk"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -x
|
||||
|
||||
OS_NAME=`echo $(uname -s) | tr '[:upper:]' '[:lower:]'`
|
||||
ANDROID_NDK_HOME=$ANDROID_SDK_HOME/ndk-bundle
|
||||
ANDROID_TOOLCHAIN_PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/${OS_NAME}-x86_64
|
||||
WORKDIR_X86=`pwd`/build/kaldi_x86
|
||||
WORKDIR_X86_64=`pwd`/build/kaldi_x86_64
|
||||
WORKDIR_ARM32=`pwd`/build/kaldi_arm_32
|
||||
WORKDIR_ARM64=`pwd`/build/kaldi_arm_64
|
||||
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 $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.bak -e 's/APP_STL := gnustl_static/APP_STL := c++_static/g' jni/Application.mk && \
|
||||
sed -i.bak -e 's/android-10/android-21/g' project.properties && \
|
||||
sed -i.bak -e 's/APP_ABI := armeabi armeabi-v7a/APP_ABI := armeabi-v7a arm64-v8a x86_64 x86/g' jni/Application.mk && \
|
||||
sed -i.bak -e 's/LOCAL_MODULE:= testlapack/#LOCAL_MODULE:= testlapack/g' jni/Android.mk && \
|
||||
sed -i.bak -e 's/LOCAL_SRC_FILES:= testclapack.cpp/#LOCAL_SRC_FILES:= testclapack.cpp/g' jni/Android.mk && \
|
||||
sed -i.bak -e 's/LOCAL_STATIC_LIBRARIES := lapack/#LOCAL_STATIC_LIBRARIES := lapack/g' jni/Android.mk && \
|
||||
sed -i.bak -e 's/include $(BUILD_SHARED_LIBRARY)/#include $(BUILD_SHARED_LIBRARY)/g' jni/Android.mk && \
|
||||
${ANDROID_NDK_HOME}/ndk-build && \
|
||||
cp obj/local/armeabi-v7a/*.a ${WORKDIR_ARM32}/local/lib && \
|
||||
cp obj/local/arm64-v8a/*.a ${WORKDIR_ARM64}/local/lib
|
||||
cp obj/local/x86_64/*.a ${WORKDIR_X86_64}/local/lib
|
||||
cp obj/local/x86/*.a ${WORKDIR_X86}/local/lib
|
||||
|
||||
# Architecture-specific part
|
||||
|
||||
for arch in arm32 arm64 x86_64 x86; do
|
||||
#for arch in x86_64; do
|
||||
|
||||
case $arch in
|
||||
arm32)
|
||||
BLAS_ARCH=ARMV7
|
||||
WORKDIR=$WORKDIR_ARM32
|
||||
HOST=arm-linux-androideabi
|
||||
AR=arm-linux-androideabi-ar
|
||||
CC=armv7a-linux-androideabi21-clang
|
||||
CXX=armv7a-linux-androideabi21-clang++
|
||||
ARCHFLAGS="-mfloat-abi=softfp -mfpu=neon"
|
||||
;;
|
||||
arm64)
|
||||
BLAS_ARCH=ARMV8
|
||||
WORKDIR=$WORKDIR_ARM64
|
||||
HOST=aarch64-linux-android
|
||||
AR=aarch64-linux-android-ar
|
||||
CC=aarch64-linux-android21-clang
|
||||
CXX=aarch64-linux-android21-clang++
|
||||
ARCHFLAGS=""
|
||||
;;
|
||||
x86_64)
|
||||
BLAS_ARCH=ATOM
|
||||
WORKDIR=$WORKDIR_X86_64
|
||||
HOST=x86_64-linux-android
|
||||
AR=x86_64-linux-android-ar
|
||||
CC=x86_64-linux-android21-clang
|
||||
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
|
||||
cd $WORKDIR
|
||||
git clone -b v0.3.7 --single-branch https://github.com/xianyi/OpenBLAS
|
||||
make -C OpenBLAS TARGET=$BLAS_ARCH ONLY_CBLAS=1 AR=$AR CC=$CC HOSTCC=gcc ARM_SOFTFP_ABI=1 USE_THREAD=0 NUM_THREADS=1 -j4
|
||||
make -C OpenBLAS install PREFIX=$WORKDIR/local
|
||||
|
||||
# tools directory --> we'll only compile OpenFST
|
||||
cd $WORKDIR
|
||||
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
|
||||
make -j 8
|
||||
make install
|
||||
|
||||
# Kaldi itself
|
||||
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 \
|
||||
--android-incdir=${ANDROID_TOOLCHAIN_PATH}/sysroot/usr/include \
|
||||
--host=$HOST --openblas-root=${WORKDIR}/local \
|
||||
--fst-root=${WORKDIR}/local --fst-version=${OPENFST_VERSION}
|
||||
|
||||
make -j 8 depend
|
||||
make -j 8 online2 lm
|
||||
|
||||
done
|
||||
@@ -1,110 +0,0 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.5.3'
|
||||
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.5'
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "com.jfrog.bintray" version "1.8.5"
|
||||
}
|
||||
|
||||
def archiveName = "vosk-android"
|
||||
def libVersion = "0.3.16"
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
versionCode 6
|
||||
versionName = libVersion
|
||||
archivesBaseName = archiveName
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments "-DCMAKE_VERBOSE_MAKEFILE=ON", "-DANDROID_ARM_NEON=TRUE", "-DCMAKE_CXX_FLAGS_RELEASE=-O3"
|
||||
}
|
||||
}
|
||||
ndk {
|
||||
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86_64', 'x86'
|
||||
}
|
||||
}
|
||||
sourceSets {
|
||||
main {
|
||||
java.srcDirs = ['src/main/java', 'build/generated-src']
|
||||
}
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path "CMakeLists.txt"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Properties properties = new Properties()
|
||||
properties.load(project.rootProject.file('local.properties').newDataInputStream())
|
||||
|
||||
bintray {
|
||||
user = properties.getProperty("bintray.user")
|
||||
key = properties.getProperty("bintray.apikey")
|
||||
pkg {
|
||||
repo = 'vosk'
|
||||
name = 'vosk-android'
|
||||
userOrg = "alphacep"
|
||||
licenses = ['Apache2.0']
|
||||
websiteUrl = 'https://github.com/alphacep/vosk-api'
|
||||
issueTrackerUrl = 'https://github.com/alphacep/vosk-api/issues'
|
||||
vcsUrl = 'https://github.com/alphacep/vosk-api'
|
||||
version {
|
||||
name = libVersion
|
||||
vcsTag = libVersion
|
||||
}
|
||||
}
|
||||
publications = ['aar']
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
aar(MavenPublication) {
|
||||
groupId 'com.alphacep'
|
||||
artifactId archiveName
|
||||
version libVersion
|
||||
artifact("$buildDir/outputs/aar/$archiveName-release.aar")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task swig {
|
||||
doLast {
|
||||
mkdir 'build/generated-src/java'
|
||||
mkdir 'build/generated-src/cpp'
|
||||
exec {
|
||||
commandLine 'swig',
|
||||
"-c++",
|
||||
"-java", "-package", "org.kaldi",
|
||||
"-outdir", "build/generated-src/java", "-o", "build/generated-src/cpp/vosk_wrap.cc",
|
||||
"../src/vosk.i"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task kaldi(type: Exec) {
|
||||
commandLine './build-kaldi.sh'
|
||||
environment ANDROID_SDK_HOME: android.getSdkDirectory()
|
||||
}
|
||||
|
||||
preBuild.dependsOn kaldi, swig
|
||||
@@ -1,71 +0,0 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.5.3'
|
||||
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.5'
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "com.jfrog.bintray"
|
||||
}
|
||||
|
||||
def archiveName = "vosk-model-en"
|
||||
def libVersion = "0.3.16"
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
versionCode 6
|
||||
versionName = libVersion
|
||||
archivesBaseName = archiveName
|
||||
}
|
||||
}
|
||||
|
||||
Properties properties = new Properties()
|
||||
properties.load(project.rootProject.file('local.properties').newDataInputStream())
|
||||
|
||||
bintray {
|
||||
user = properties.getProperty("bintray.user")
|
||||
key = properties.getProperty("bintray.apikey")
|
||||
pkg {
|
||||
repo = 'vosk'
|
||||
name = 'vosk-model-en'
|
||||
userOrg = "alphacep"
|
||||
licenses = ['Apache2.0']
|
||||
websiteUrl = 'https://github.com/alphacep/vosk-api'
|
||||
issueTrackerUrl = 'https://github.com/alphacep/vosk-api/issues'
|
||||
vcsUrl = 'https://github.com/alphacep/vosk-api'
|
||||
version {
|
||||
name = libVersion
|
||||
vcsTag = libVersion
|
||||
}
|
||||
}
|
||||
publications = ['aar']
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
aar(MavenPublication) {
|
||||
groupId 'com.alphacep'
|
||||
artifactId archiveName
|
||||
version libVersion
|
||||
artifact("$buildDir/outputs/aar/$archiveName-release.aar")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.kaldi.model.en">
|
||||
</manifest>
|
||||
@@ -1,2 +0,0 @@
|
||||
US English model for android
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
0.3.16
|
||||
@@ -1 +0,0 @@
|
||||
include 'model-en'
|
||||
@@ -1,3 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.kaldi">
|
||||
</manifest>
|
||||
@@ -1,262 +0,0 @@
|
||||
// Copyright 2019 Alpha Cephei Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package org.kaldi;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.Reader;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Provides utility methods to keep asset files to external storage to allow
|
||||
* further JNI code access assets from a filesystem.
|
||||
*
|
||||
* There must be special file {@value #ASSET_LIST_NAME} among the application
|
||||
* assets containing relative paths of assets to synchronize. If the
|
||||
* corresponding path does not exist on the external storage it is copied. If
|
||||
* the path exists checksums are compared and the asset is copied only if there
|
||||
* is a mismatch. Checksum is stored in a separate asset with the name that
|
||||
* consists of the original name and a suffix that depends on the checksum
|
||||
* algorithm (e.g. MD5). Checksum files are copied along with the corresponding
|
||||
* asset files.
|
||||
*
|
||||
* @author Alexander Solovets
|
||||
*/
|
||||
public class Assets {
|
||||
|
||||
protected static final String TAG = Assets.class.getSimpleName();
|
||||
|
||||
public static final String ASSET_LIST_NAME = "assets.lst";
|
||||
public static final String SYNC_DIR = "sync";
|
||||
public static final String HASH_EXT = ".md5";
|
||||
|
||||
private final AssetManager assetManager;
|
||||
private final File externalDir;
|
||||
|
||||
/**
|
||||
* Creates new instance for asset synchronization
|
||||
*
|
||||
* @param context
|
||||
* application context
|
||||
*
|
||||
* @throws IOException
|
||||
* if the directory does not exist
|
||||
*
|
||||
* @see android.content.Context#getExternalFilesDir
|
||||
* @see android.os.Environment#getExternalStorageState
|
||||
*/
|
||||
public Assets(Context context) throws IOException {
|
||||
File appDir = context.getExternalFilesDir(null);
|
||||
if (null == appDir)
|
||||
throw new IOException("cannot get external files dir, "
|
||||
+ "external storage state is " + Environment.getExternalStorageState());
|
||||
externalDir = new File(appDir, SYNC_DIR);
|
||||
assetManager = context.getAssets();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new instance with specified destination for assets
|
||||
*
|
||||
* @param context
|
||||
* application context to retrieve the assets
|
||||
* @param path
|
||||
* path to sync the files
|
||||
*/
|
||||
public Assets(Context context, String dest) {
|
||||
externalDir = new File(dest);
|
||||
assetManager = context.getAssets();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns destination path on external storage where assets are copied.
|
||||
*
|
||||
* @return path to application directory or null if it does not exists
|
||||
*/
|
||||
public File getExternalDir() {
|
||||
return externalDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the map of asset paths to the files checksums.
|
||||
*
|
||||
* @return path to the root of resources directory on external storage
|
||||
* @throws IOException
|
||||
* if an I/O error occurs or "assets.lst" is missing
|
||||
*/
|
||||
public Map<String, String> getItems() throws IOException {
|
||||
Map<String, String> items = new HashMap<String, String>();
|
||||
for (String path : readLines(openAsset(ASSET_LIST_NAME))) {
|
||||
Reader reader = new InputStreamReader(openAsset(path + HASH_EXT));
|
||||
items.put(path, new BufferedReader(reader).readLine());
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns path to hash mappings for the previously copied files. This
|
||||
* method can be used to find out assets which must be updated.
|
||||
*/
|
||||
public Map<String, String> getExternalItems() {
|
||||
try {
|
||||
Map<String, String> items = new HashMap<String, String>();
|
||||
File assetFile = new File(externalDir, ASSET_LIST_NAME);
|
||||
for (String line : readLines(new FileInputStream(assetFile))) {
|
||||
String[] fields = line.split(" ");
|
||||
items.put(fields[0], fields[1]);
|
||||
}
|
||||
return items;
|
||||
} catch (IOException e) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* In case you want to create more smart sync implementation, this method
|
||||
* returns the list of items which must be synchronized.
|
||||
*/
|
||||
public Collection<String> getItemsToCopy(String path) throws IOException {
|
||||
Collection<String> items = new ArrayList<String>();
|
||||
Queue<String> queue = new ArrayDeque<String>();
|
||||
queue.offer(path);
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
path = queue.poll();
|
||||
String[] list = assetManager.list(path);
|
||||
for (String nested : list)
|
||||
queue.offer(nested);
|
||||
|
||||
if (list.length == 0)
|
||||
items.add(path);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private List<String> readLines(InputStream source) throws IOException {
|
||||
List<String> lines = new ArrayList<String>();
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(source));
|
||||
String line;
|
||||
while (null != (line = br.readLine()))
|
||||
lines.add(line);
|
||||
return lines;
|
||||
}
|
||||
|
||||
private InputStream openAsset(String asset) throws IOException {
|
||||
return assetManager.open(new File(SYNC_DIR, asset).getPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the list of synchronized items. The list is stored as a two-column
|
||||
* space-separated list of items in a text file. The file is located at the
|
||||
* root of synchronization directory in the external storage.
|
||||
*
|
||||
* @param items
|
||||
* the items
|
||||
* @throws IOException
|
||||
* if an I/O error occurs
|
||||
*/
|
||||
public void updateItemList(Map<String, String> items) throws IOException {
|
||||
File assetListFile = new File(externalDir, ASSET_LIST_NAME);
|
||||
PrintWriter pw = new PrintWriter(new FileOutputStream(assetListFile));
|
||||
for (Map.Entry<String, String> entry : items.entrySet())
|
||||
pw.format("%s %s\n", entry.getKey(), entry.getValue());
|
||||
pw.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies raw asset resource to external storage of the device.
|
||||
*
|
||||
* @param path
|
||||
* path of the asset to copy
|
||||
* @throws IOException
|
||||
* if an I/O error occurs
|
||||
*/
|
||||
public File copy(String asset) throws IOException {
|
||||
InputStream source = openAsset(asset);
|
||||
File destinationFile = new File(externalDir, asset);
|
||||
destinationFile.getParentFile().mkdirs();
|
||||
OutputStream destination = new FileOutputStream(destinationFile);
|
||||
byte[] buffer = new byte[1024];
|
||||
int nread;
|
||||
|
||||
while ((nread = source.read(buffer)) != -1) {
|
||||
if (nread == 0) {
|
||||
nread = source.read();
|
||||
if (nread < 0)
|
||||
break;
|
||||
destination.write(nread);
|
||||
continue;
|
||||
}
|
||||
destination.write(buffer, 0, nread);
|
||||
}
|
||||
destination.close();
|
||||
return destinationFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the sync of assets in the application and on the external
|
||||
* storage
|
||||
*
|
||||
* @return The folder on external storage with data
|
||||
* @throws IOException
|
||||
*/
|
||||
public File syncAssets() throws IOException {
|
||||
Collection<String> newItems = new ArrayList<String>();
|
||||
Collection<String> unusedItems = new ArrayList<String>();
|
||||
Map<String, String> items = getItems();
|
||||
Map<String, String> externalItems = getExternalItems();
|
||||
|
||||
for (String path : items.keySet()) {
|
||||
if (!items.get(path).equals(externalItems.get(path))
|
||||
|| !(new File(externalDir, path).exists()))
|
||||
newItems.add(path);
|
||||
}
|
||||
|
||||
unusedItems.addAll(externalItems.keySet());
|
||||
unusedItems.removeAll(items.keySet());
|
||||
|
||||
for (String path : newItems) {
|
||||
File file = copy(path);
|
||||
}
|
||||
|
||||
for (String path : unusedItems) {
|
||||
File file = new File(externalDir, path);
|
||||
file.delete();
|
||||
}
|
||||
|
||||
updateItemList(items);
|
||||
return externalDir;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
// Copyright 2019 Alpha Cephei Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package org.kaldi;
|
||||
|
||||
/** Interface to receive recognition results */
|
||||
public interface RecognitionListener {
|
||||
|
||||
/**
|
||||
* Called when partial recognition result is available.
|
||||
*/
|
||||
public void onPartialResult(String hypothesis);
|
||||
|
||||
/**
|
||||
* Called after the recognition is ended.
|
||||
*/
|
||||
public void onResult(String hypothesis);
|
||||
|
||||
/**
|
||||
* Called when an error occurs.
|
||||
*/
|
||||
public void onError(Exception exception);
|
||||
|
||||
/**
|
||||
* Called after timeout expired
|
||||
*/
|
||||
public void onTimeout();
|
||||
}
|
||||
@@ -1,286 +0,0 @@
|
||||
// Copyright 2019 Alpha Cephei Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package org.kaldi;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioRecord;
|
||||
import android.media.MediaRecorder.AudioSource;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Service that records audio in a thread, passes it to a recognizer and emits
|
||||
* recognition results. Recognition events are passed to a client using
|
||||
* {@link RecognitionListener}
|
||||
*
|
||||
*/
|
||||
public class SpeechService {
|
||||
|
||||
protected static final String TAG = SpeechService.class.getSimpleName();
|
||||
|
||||
private final KaldiRecognizer recognizer;
|
||||
|
||||
private final int sampleRate;
|
||||
private final static float BUFFER_SIZE_SECONDS = 0.4f;
|
||||
private int bufferSize;
|
||||
private final AudioRecord recorder;
|
||||
|
||||
private Thread recognizerThread;
|
||||
|
||||
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
private final Collection<RecognitionListener> listeners = new HashSet<RecognitionListener>();
|
||||
|
||||
/**
|
||||
* Creates speech service. Service holds the AudioRecord object, so you
|
||||
* need to call {@link release} in order to properly finalize it.
|
||||
*
|
||||
* @throws IOException thrown if audio recorder can not be created for some reason.
|
||||
*/
|
||||
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, this.sampleRate,
|
||||
AudioFormat.CHANNEL_IN_MONO,
|
||||
AudioFormat.ENCODING_PCM_16BIT, bufferSize * 2);
|
||||
|
||||
if (recorder.getState() == AudioRecord.STATE_UNINITIALIZED) {
|
||||
recorder.release();
|
||||
throw new IOException(
|
||||
"Failed to initialize recorder. Microphone might be already in use.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds listener.
|
||||
*/
|
||||
public void addListener(RecognitionListener listener) {
|
||||
synchronized (listeners) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes listener.
|
||||
*/
|
||||
public void removeListener(RecognitionListener listener) {
|
||||
synchronized (listeners) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts recognition. Does nothing if recognition is active.
|
||||
*
|
||||
* @return true if recognition was actually started
|
||||
*/
|
||||
public boolean startListening() {
|
||||
if (null != recognizerThread)
|
||||
return false;
|
||||
|
||||
recognizerThread = new RecognizerThread();
|
||||
recognizerThread.start();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts recognition. After specified timeout listening stops and the
|
||||
* endOfSpeech signals about that. Does nothing if recognition is active.
|
||||
*
|
||||
* @timeout - timeout in milliseconds to listen.
|
||||
*
|
||||
* @return true if recognition was actually started
|
||||
*/
|
||||
public boolean startListening(int timeout) {
|
||||
if (null != recognizerThread)
|
||||
return false;
|
||||
|
||||
recognizerThread = new RecognizerThread(timeout);
|
||||
recognizerThread.start();
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean stopRecognizerThread() {
|
||||
if (null == recognizerThread)
|
||||
return false;
|
||||
|
||||
try {
|
||||
recognizerThread.interrupt();
|
||||
recognizerThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
// Restore the interrupted status.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
recognizerThread = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops recognition. All listeners should receive final result if there is
|
||||
* any. Does nothing if recognition is not active.
|
||||
*
|
||||
* @return true if recognition was actually stopped
|
||||
*/
|
||||
public boolean stop() {
|
||||
boolean result = stopRecognizerThread();
|
||||
if (result) {
|
||||
mainHandler.post(new ResultEvent(recognizer.Result(), true));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels recognition. Listeners do not receive final result. Does nothing
|
||||
* if recognition is not active.
|
||||
*
|
||||
* @return true if recognition was actually canceled
|
||||
*/
|
||||
public boolean cancel() {
|
||||
boolean result = stopRecognizerThread();
|
||||
recognizer.Result(); // Reset recognizer state
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown the recognizer and release the recorder
|
||||
*/
|
||||
public void shutdown() {
|
||||
recorder.release();
|
||||
}
|
||||
|
||||
private final class RecognizerThread extends Thread {
|
||||
|
||||
private int remainingSamples;
|
||||
private int timeoutSamples;
|
||||
private final static int NO_TIMEOUT = -1;
|
||||
|
||||
public RecognizerThread(int timeout) {
|
||||
if (timeout != NO_TIMEOUT)
|
||||
this.timeoutSamples = timeout * sampleRate / 1000;
|
||||
else
|
||||
this.timeoutSamples = NO_TIMEOUT;
|
||||
this.remainingSamples = this.timeoutSamples;
|
||||
}
|
||||
|
||||
public RecognizerThread() {
|
||||
this(NO_TIMEOUT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
recorder.startRecording();
|
||||
if (recorder.getRecordingState() == AudioRecord.RECORDSTATE_STOPPED) {
|
||||
recorder.stop();
|
||||
IOException ioe = new IOException(
|
||||
"Failed to start recording. Microphone might be already in use.");
|
||||
mainHandler.post(new OnErrorEvent(ioe));
|
||||
return;
|
||||
}
|
||||
|
||||
short[] buffer = new short[bufferSize];
|
||||
|
||||
while (!interrupted()
|
||||
&& ((timeoutSamples == NO_TIMEOUT) || (remainingSamples > 0))) {
|
||||
int nread = recorder.read(buffer, 0, buffer.length);
|
||||
|
||||
if (nread < 0) {
|
||||
throw new RuntimeException("error reading audio buffer");
|
||||
} else {
|
||||
boolean isFinal = recognizer.AcceptWaveform(buffer, nread);
|
||||
if (isFinal) {
|
||||
mainHandler.post(new ResultEvent(recognizer.Result(), true));
|
||||
} else {
|
||||
mainHandler.post(new ResultEvent(recognizer.PartialResult(), false));
|
||||
}
|
||||
}
|
||||
|
||||
if (timeoutSamples != NO_TIMEOUT) {
|
||||
remainingSamples = remainingSamples - nread;
|
||||
}
|
||||
}
|
||||
|
||||
recorder.stop();
|
||||
|
||||
// Remove all pending notifications.
|
||||
mainHandler.removeCallbacksAndMessages(null);
|
||||
|
||||
// If we met timeout signal that speech ended
|
||||
if (timeoutSamples != NO_TIMEOUT && remainingSamples <= 0) {
|
||||
mainHandler.post(new TimeoutEvent());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class RecognitionEvent implements Runnable {
|
||||
public void run() {
|
||||
RecognitionListener[] emptyArray = new RecognitionListener[0];
|
||||
for (RecognitionListener listener : listeners.toArray(emptyArray))
|
||||
execute(listener);
|
||||
}
|
||||
|
||||
protected abstract void execute(RecognitionListener listener);
|
||||
}
|
||||
|
||||
private class ResultEvent extends RecognitionEvent {
|
||||
protected final String hypothesis;
|
||||
private final boolean finalResult;
|
||||
|
||||
ResultEvent(String hypothesis, boolean finalResult) {
|
||||
this.hypothesis = hypothesis;
|
||||
this.finalResult = finalResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(RecognitionListener listener) {
|
||||
if (finalResult)
|
||||
listener.onResult(hypothesis);
|
||||
else
|
||||
listener.onPartialResult(hypothesis);
|
||||
}
|
||||
}
|
||||
|
||||
private class OnErrorEvent extends RecognitionEvent {
|
||||
private final Exception exception;
|
||||
|
||||
OnErrorEvent(Exception exception) {
|
||||
this.exception = exception;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(RecognitionListener listener) {
|
||||
listener.onError(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private class TimeoutEvent extends RecognitionEvent {
|
||||
@Override
|
||||
protected void execute(RecognitionListener listener) {
|
||||
listener.onTimeout();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
CFLAGS=-I../../src
|
||||
LDFLAGS=-L../../src -lvosk -ldl -lpthread -Wl,-rpath,../../src
|
||||
|
||||
all: test_vosk test_vosk_multi
|
||||
|
||||
test_vosk: test_vosk.o
|
||||
gcc $^ -o $@ $(LDFLAGS)
|
||||
|
||||
test_vosk_multi: test_vosk_multi.o
|
||||
gcc $^ -o $@ $(LDFLAGS) -lpthread
|
||||
|
||||
%.o: %.c
|
||||
gcc $(CFLAGS) -c -o $@ $<
|
||||
|
||||
clean:
|
||||
rm -f *.o *.a test_vosk test_vosk_multi
|
||||
@@ -0,0 +1,37 @@
|
||||
#include <vosk_api.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int main() {
|
||||
FILE *wavin;
|
||||
char buf[3200];
|
||||
int nread, final;
|
||||
|
||||
VoskModel *model = vosk_model_new("vosk-model-small-ru");
|
||||
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);
|
||||
vosk_recognizer_accept_waveform(recognizer, buf, nread);
|
||||
while (vosk_recognizer_get_num_pending_results(recognizer) > 0)
|
||||
sleep(0.05);
|
||||
while (!vosk_recognizer_results_empty(recognizer)) {
|
||||
printf("%s\n", vosk_recognizer_result_front(recognizer));
|
||||
vosk_recognizer_result_pop(recognizer);
|
||||
}
|
||||
}
|
||||
vosk_recognizer_flush(recognizer);
|
||||
while (vosk_recognizer_get_num_pending_results(recognizer) > 0)
|
||||
sleep(0.05);
|
||||
while (!vosk_recognizer_results_empty(recognizer)) {
|
||||
printf("%s\n", vosk_recognizer_result_front(recognizer));
|
||||
vosk_recognizer_result_pop(recognizer);
|
||||
}
|
||||
|
||||
vosk_recognizer_free(recognizer);
|
||||
vosk_model_free(model);
|
||||
fclose(wavin);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
#include <vosk_api.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <time.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define NUM_THREADS 10
|
||||
#define NUM_FILES_PER_THREAD 3
|
||||
#define NUM_FILES 400
|
||||
#define MAX_FILE 100
|
||||
|
||||
VoskModel *model;
|
||||
char infiles[NUM_FILES][MAX_FILE];
|
||||
pthread_t thread_ids[NUM_THREADS];
|
||||
|
||||
static void *worker(void *data) {
|
||||
int j;
|
||||
|
||||
for (j = 0; j < NUM_FILES_PER_THREAD; j++) {
|
||||
FILE *wavin;
|
||||
char buf[4000];
|
||||
int nread;
|
||||
VoskRecognizer *recognizer = vosk_recognizer_new(model, 16000.0);
|
||||
int foffset = rand() % NUM_FILES;
|
||||
|
||||
wavin = fopen(infiles[foffset], "rb");
|
||||
fseek(wavin, 44, SEEK_SET);
|
||||
while (!feof(wavin)) {
|
||||
nread = fread(buf, 1, sizeof(buf), wavin);
|
||||
vosk_recognizer_accept_waveform(recognizer, buf, nread);
|
||||
while (vosk_recognizer_get_num_pending_results(recognizer) > 0)
|
||||
sleep(0.05);
|
||||
while (!vosk_recognizer_results_empty(recognizer)) {
|
||||
printf("%s\n", vosk_recognizer_result_front(recognizer));
|
||||
vosk_recognizer_result_pop(recognizer);
|
||||
}
|
||||
}
|
||||
vosk_recognizer_flush(recognizer);
|
||||
while (vosk_recognizer_get_num_pending_results(recognizer) > 0)
|
||||
sleep(0.05);
|
||||
while (!vosk_recognizer_results_empty(recognizer)) {
|
||||
printf("%s\n", vosk_recognizer_result_front(recognizer));
|
||||
vosk_recognizer_result_pop(recognizer);
|
||||
}
|
||||
|
||||
vosk_recognizer_free(recognizer);
|
||||
fclose(wavin);
|
||||
}
|
||||
}
|
||||
|
||||
void read_file_list() {
|
||||
FILE* infile;
|
||||
infile = fopen("file.list", "r");
|
||||
int i = 0;
|
||||
|
||||
while(i < NUM_FILES, fgets(infiles[i], MAX_FILE, infile)) {
|
||||
infiles[i][strlen(infiles[i]) - 1] = 0;
|
||||
printf("%s\n", infiles[i]);
|
||||
i++;
|
||||
}
|
||||
|
||||
fclose(infile);
|
||||
}
|
||||
|
||||
int main() {
|
||||
int i;
|
||||
srand(0);
|
||||
|
||||
read_file_list();
|
||||
|
||||
model = vosk_model_new("vosk-model-small-ru");
|
||||
for (i = 0; i < NUM_THREADS; i++) {
|
||||
pthread_create(&thread_ids[i], NULL, worker, NULL);
|
||||
}
|
||||
for (i = 0; i < NUM_THREADS; i++) {
|
||||
pthread_join(thread_ids[i], NULL);
|
||||
}
|
||||
vosk_model_free(model);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
Java bindings for Vosk API using jnr-ffi
|
||||
|
||||
See demo project for details, build it with Gradle.
|
||||
|
||||
Download model and unpack as "model" folder in the demo project.
|
||||
|
||||
Make sure you are using recent JDK and Gradle.
|
||||
@@ -0,0 +1,71 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'maven-publish'
|
||||
id 'com.vanniktech.maven.publish' version '0.18.0'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
archivesBaseName = 'vosk'
|
||||
group = 'com.alphacephei'
|
||||
version = '0.4.0'
|
||||
|
||||
mavenPublish {
|
||||
group = 'com.alphacephei'
|
||||
version = version
|
||||
sonatypeHost = 's01'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api group: 'net.java.dev.jna', name: 'jna', version: '5.13.0'
|
||||
testImplementation 'junit:junit:4.13'
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
artifactId = 'vosk'
|
||||
from components.java
|
||||
pom {
|
||||
name = 'Vosk'
|
||||
description = 'Speech recognition library'
|
||||
url = 'http://www.alphacephei.com.com/vosk/'
|
||||
licenses {
|
||||
license {
|
||||
name = 'The Apache License, Version 2.0'
|
||||
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||
}
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id = 'alphacephei'
|
||||
name = 'Alpha Cephei Inc'
|
||||
email = 'contact@alphacephei.com'
|
||||
}
|
||||
}
|
||||
scm {
|
||||
connection = 'scm:git:git://github.com/alphacep/vosk-api.git'
|
||||
url = 'https://github.com/alphacep/vosk-api/'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
dependsOn cleanTest
|
||||
testLogging.showStandardStreams = true
|
||||
}
|
||||
|
||||
java {
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package org.vosk;
|
||||
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Platform;
|
||||
import com.sun.jna.Pointer;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
|
||||
public class LibVosk {
|
||||
|
||||
private static void unpackDll(File targetDir, String lib) throws IOException {
|
||||
try (InputStream source = LibVosk.class.getResourceAsStream("/win32-x86-64/" + lib + ".dll")) {
|
||||
Files.copy(source, new File(targetDir, lib + ".dll").toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
|
||||
if (Platform.isWindows()) {
|
||||
// We have to unpack dependencies
|
||||
try {
|
||||
// To get a tmp folder we unpack small library and mark it for deletion
|
||||
File tmpFile = Native.extractFromResourcePath("/win32-x86-64/empty", LibVosk.class.getClassLoader());
|
||||
File tmpDir = tmpFile.getParentFile();
|
||||
new File(tmpDir, tmpFile.getName() + ".x").createNewFile();
|
||||
|
||||
// Now unpack dependencies
|
||||
unpackDll(tmpDir, "libwinpthread-1");
|
||||
unpackDll(tmpDir, "libgcc_s_seh-1");
|
||||
unpackDll(tmpDir, "libstdc++-6");
|
||||
|
||||
} catch (IOException e) {
|
||||
// Nothing for now, it will fail on next step
|
||||
} finally {
|
||||
Native.register(LibVosk.class, "libvosk");
|
||||
}
|
||||
} else {
|
||||
Native.register(LibVosk.class, "vosk");
|
||||
}
|
||||
}
|
||||
|
||||
public static native void vosk_set_log_level(int level);
|
||||
|
||||
public static native Pointer vosk_model_new(String path);
|
||||
|
||||
public static native void vosk_model_free(Pointer model);
|
||||
|
||||
public static native Pointer vosk_recognizer_new(Model model, float sample_rate);
|
||||
|
||||
public static native void vosk_recognizer_accept_waveform(Pointer recognizer, byte[] data, int len);
|
||||
|
||||
public static native void vosk_recognizer_accept_waveform_s(Pointer recognizer, short[] data, int len);
|
||||
|
||||
public static native void vosk_recognizer_accept_waveform_f(Pointer recognizer, float[] data, int len);
|
||||
|
||||
public static native void vosk_recognizer_flush(Pointer recognizer);
|
||||
|
||||
public static native String vosk_recognizer_result_front(Pointer recognizer);
|
||||
|
||||
public static native void vosk_recognizer_result_pop(Pointer recognizer);
|
||||
|
||||
public static native int vosk_recognizer_get_num_pending_results(Pointer recognizer);
|
||||
|
||||
public static native int vosk_recognizer_get_num_results(Pointer recognizer);
|
||||
|
||||
public static native int vosk_recognizer_results_empty(Pointer recognizer);
|
||||
|
||||
public static native void vosk_recognizer_reset(Pointer recognizer);
|
||||
|
||||
public static native void vosk_recognizer_free(Pointer recognizer);
|
||||
|
||||
/**
|
||||
* Set log level for Kaldi messages.
|
||||
*
|
||||
* @param loglevel the level
|
||||
* 0 - default value to print info and error messages but no debug
|
||||
* less than 0 - don't print info messages
|
||||
* greater than 0 - more verbose mode
|
||||
*/
|
||||
public static void setLogLevel(LogLevel loglevel) {
|
||||
vosk_set_log_level(loglevel.getValue());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.vosk;
|
||||
|
||||
public enum LogLevel {
|
||||
WARNINGS(-1), // Print warning and errors
|
||||
INFO(0), // Print info, along with warning and error messages, but no debug
|
||||
DEBUG(1); // Print debug info
|
||||
|
||||
private final int value;
|
||||
|
||||
LogLevel(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.vosk;
|
||||
|
||||
import java.io.IOException;
|
||||
import com.sun.jna.PointerType;
|
||||
|
||||
public class Model extends PointerType implements AutoCloseable {
|
||||
public Model() {
|
||||
}
|
||||
|
||||
public Model(String path) throws IOException {
|
||||
super(LibVosk.vosk_model_new(path));
|
||||
|
||||
if (getPointer() == null) {
|
||||
throw new IOException("Failed to create a model");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
LibVosk.vosk_model_free(this.getPointer());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package org.vosk;
|
||||
|
||||
import com.sun.jna.PointerType;
|
||||
import java.io.IOException;
|
||||
|
||||
public class Recognizer extends PointerType implements AutoCloseable {
|
||||
/**
|
||||
* Creates the recognizer object.
|
||||
*
|
||||
* The recognizers process the speech and return text using shared model data
|
||||
* @param model VoskModel containing static data for recognizer. Model can be
|
||||
* shared across recognizers, even running in different threads.
|
||||
* @param sampleRate The sample rate of the audio you are going to feed into the recognizer.
|
||||
* Make sure this rate matches the audio content, it is a common
|
||||
* issue causing accuracy problems.
|
||||
* @throws IOException if the recognizer could not be created
|
||||
*/
|
||||
public Recognizer(Model model, float sampleRate) throws IOException {
|
||||
super(LibVosk.vosk_recognizer_new(model, sampleRate));
|
||||
|
||||
if (getPointer() == null) {
|
||||
throw new IOException("Failed to create a recognizer");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept and process new chunk of voice data.
|
||||
*
|
||||
* @param data - audio data in PCM 16-bit mono format
|
||||
* @param len - length of the audio data
|
||||
*/
|
||||
public void acceptWaveForm(byte[] data, int len) {
|
||||
LibVosk.vosk_recognizer_accept_waveform(this.getPointer(), data, len);
|
||||
}
|
||||
|
||||
public void acceptWaveForm(short[] data, int len) {
|
||||
LibVosk.vosk_recognizer_accept_waveform_s(this.getPointer(), data, len);
|
||||
}
|
||||
|
||||
public void acceptWaveForm(float[] data, int len) {
|
||||
LibVosk.vosk_recognizer_accept_waveform_f(this.getPointer(), data, len);
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
LibVosk.vosk_recognizer_flush(this.getPointer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns speech recognition result
|
||||
*
|
||||
* @return 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>
|
||||
* {
|
||||
* "text" : "what zero zero zero one"
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* If alternatives enabled it returns result with alternatives, see also #setMaxAlternatives().
|
||||
*
|
||||
* If word times enabled returns word time, see also #setWordTimes().
|
||||
*/
|
||||
public String getResult() {
|
||||
return LibVosk.vosk_recognizer_result_front(this.getPointer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the latest result from the result queue
|
||||
*/
|
||||
public void popResult() {
|
||||
LibVosk.vosk_recognizer_result_pop(this.getPointer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts pending results
|
||||
*/
|
||||
public int getNumPendingResults() {
|
||||
return LibVosk.vosk_recognizer_get_num_pending_results(this.getPointer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts pending results
|
||||
*/
|
||||
public int getNumResults() {
|
||||
return LibVosk.vosk_recognizer_get_num_results(this.getPointer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if output queue is empty
|
||||
*/
|
||||
public boolean getResultsEmpty() {
|
||||
return (LibVosk.vosk_recognizer_results_empty(this.getPointer()) == 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the recognizer.
|
||||
* Resets current results so the recognition can continue from scratch.
|
||||
*/
|
||||
public void reset() {
|
||||
LibVosk.vosk_recognizer_reset(this.getPointer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases recognizer object.
|
||||
* Underlying model is also unreferenced and if needed, released.
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
LibVosk.vosk_recognizer_free(this.getPointer());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package org.vosk.test;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.Assert;
|
||||
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.UnsupportedAudioFileException;
|
||||
|
||||
import org.vosk.LogLevel;
|
||||
import org.vosk.Recognizer;
|
||||
import org.vosk.LibVosk;
|
||||
import org.vosk.Model;
|
||||
|
||||
public class DecoderTest {
|
||||
|
||||
@Test
|
||||
public void decoderTest() throws IOException, UnsupportedAudioFileException, InterruptedException {
|
||||
LibVosk.setLogLevel(LogLevel.DEBUG);
|
||||
|
||||
try (Model model = new Model("../../python/example/vosk-model-small-ru");
|
||||
InputStream ais = AudioSystem.getAudioInputStream(new BufferedInputStream(new FileInputStream("../../python/example/test-ru.wav")));
|
||||
Recognizer recognizer = new Recognizer(model, 16000)) {
|
||||
|
||||
int nbytes;
|
||||
byte[] b = new byte[4000];
|
||||
while ((nbytes = ais.read(b)) >= 0) {
|
||||
recognizer.acceptWaveForm(b, nbytes);
|
||||
while (recognizer.getNumPendingResults() > 0) {
|
||||
Thread.sleep(50);
|
||||
}
|
||||
while (!recognizer.getResultsEmpty()) {
|
||||
System.out.println(recognizer.getResult());
|
||||
recognizer.popResult();
|
||||
}
|
||||
}
|
||||
recognizer.flush();
|
||||
while (recognizer.getNumPendingResults() > 0) {
|
||||
Thread.sleep(50);
|
||||
}
|
||||
while (!recognizer.getResultsEmpty()) {
|
||||
System.out.println(recognizer.getResult());
|
||||
recognizer.popResult();
|
||||
}
|
||||
}
|
||||
Assert.assertTrue(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decoderTestBig() throws IOException, UnsupportedAudioFileException, InterruptedException {
|
||||
LibVosk.setLogLevel(LogLevel.DEBUG);
|
||||
|
||||
try (Model model = new Model("../../python/example/vosk-model-small-ru");
|
||||
InputStream ais = AudioSystem.getAudioInputStream(new BufferedInputStream(new FileInputStream("../../python/example/test-ru.wav")));
|
||||
Recognizer recognizer = new Recognizer(model, 16000)) {
|
||||
|
||||
int nbytes;
|
||||
byte[] b = new byte[400000];
|
||||
while ((nbytes = ais.read(b)) >= 0) {
|
||||
recognizer.acceptWaveForm(b, nbytes);
|
||||
while (recognizer.getNumPendingResults() > 0) {
|
||||
Thread.sleep(50);
|
||||
}
|
||||
while (!recognizer.getResultsEmpty()) {
|
||||
System.out.println(recognizer.getResult());
|
||||
recognizer.popResult();
|
||||
}
|
||||
}
|
||||
recognizer.flush();
|
||||
while (recognizer.getNumPendingResults() > 0) {
|
||||
Thread.sleep(50);
|
||||
}
|
||||
while (!recognizer.getResultsEmpty()) {
|
||||
System.out.println(recognizer.getResult());
|
||||
recognizer.popResult();
|
||||
}
|
||||
}
|
||||
Assert.assertTrue(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,639 @@
|
||||
[MAIN]
|
||||
|
||||
# Analyse import fallback blocks. This can be used to support both Python 2 and
|
||||
# 3 compatible code, which means that the block might have code that exists
|
||||
# only in one or another interpreter, leading to false positives when analysed.
|
||||
analyse-fallback-blocks=no
|
||||
|
||||
# Load and enable all available extensions. Use --list-extensions to see a list
|
||||
# all available extensions.
|
||||
#enable-all-extensions=
|
||||
|
||||
# In error mode, messages with a category besides ERROR or FATAL are
|
||||
# suppressed, and no reports are done by default. Error mode is compatible with
|
||||
# disabling specific errors.
|
||||
#errors-only=
|
||||
|
||||
# Always return a 0 (non-error) status code, even if lint errors are found.
|
||||
# This is primarily useful in continuous integration scripts.
|
||||
#exit-zero=
|
||||
|
||||
# A comma-separated list of package or module names from where C extensions may
|
||||
# be loaded. Extensions are loading into the active Python interpreter and may
|
||||
# run arbitrary code.
|
||||
extension-pkg-allow-list=
|
||||
|
||||
# A comma-separated list of package or module names from where C extensions may
|
||||
# be loaded. Extensions are loading into the active Python interpreter and may
|
||||
# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
|
||||
# for backward compatibility.)
|
||||
extension-pkg-whitelist=
|
||||
|
||||
# Return non-zero exit code if any of these messages/categories are detected,
|
||||
# even if score is above --fail-under value. Syntax same as enable. Messages
|
||||
# specified are enabled, while categories only check already-enabled messages.
|
||||
fail-on=
|
||||
|
||||
# Specify a score threshold under which the program will exit with error.
|
||||
fail-under=10
|
||||
|
||||
# Interpret the stdin as a python script, whose filename needs to be passed as
|
||||
# the module_or_package argument.
|
||||
#from-stdin=
|
||||
|
||||
# Files or directories to be skipped. They should be base names, not paths.
|
||||
ignore=CVS
|
||||
|
||||
# Add files or directories matching the regular expressions patterns to the
|
||||
# ignore-list. The regex matches against paths and can be in Posix or Windows
|
||||
# format. Because '\' represents the directory delimiter on Windows systems, it
|
||||
# can't be used as an escape character.
|
||||
ignore-paths=
|
||||
|
||||
# Files or directories matching the regular expression patterns are skipped.
|
||||
# The regex matches against base names, not paths. The default value ignores
|
||||
# Emacs file locks
|
||||
ignore-patterns=^\.#
|
||||
|
||||
# List of module names for which member attributes should not be checked
|
||||
# (useful for modules/projects where namespaces are manipulated during runtime
|
||||
# and thus existing member attributes cannot be deduced by static analysis). It
|
||||
# supports qualified module names, as well as Unix pattern matching.
|
||||
ignored-modules=
|
||||
|
||||
# Python code to execute, usually for sys.path manipulation such as
|
||||
# pygtk.require().
|
||||
#init-hook=
|
||||
|
||||
# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
|
||||
# number of processors available to use, and will cap the count on Windows to
|
||||
# avoid hangs.
|
||||
jobs=1
|
||||
|
||||
# Control the amount of potential inferred values when inferring a single
|
||||
# object. This can help the performance when dealing with large functions or
|
||||
# complex, nested conditions.
|
||||
limit-inference-results=100
|
||||
|
||||
# List of plugins (as comma separated values of python module names) to load,
|
||||
# usually to register additional checkers.
|
||||
load-plugins=
|
||||
|
||||
# Pickle collected data for later comparisons.
|
||||
persistent=yes
|
||||
|
||||
# Minimum Python version to use for version dependent checks. Will default to
|
||||
# the version used to run pylint.
|
||||
py-version=3.7
|
||||
|
||||
# Discover python modules and packages in the file system subtree.
|
||||
recursive=no
|
||||
|
||||
# When enabled, pylint would attempt to guess common misconfiguration and emit
|
||||
# user-friendly hints instead of false-positive error messages.
|
||||
suggestion-mode=yes
|
||||
|
||||
# Allow loading of arbitrary C extensions. Extensions are imported into the
|
||||
# active Python interpreter and may run arbitrary code.
|
||||
unsafe-load-any-extension=no
|
||||
|
||||
# In verbose mode, extra non-checker-related info will be displayed.
|
||||
#verbose=
|
||||
|
||||
|
||||
[REPORTS]
|
||||
|
||||
# Python expression which should return a score less than or equal to 10. You
|
||||
# have access to the variables 'fatal', 'error', 'warning', 'refactor',
|
||||
# 'convention', and 'info' which contain the number of messages in each
|
||||
# category, as well as 'statement' which is the total number of statements
|
||||
# analyzed. This score is used by the global evaluation report (RP0004).
|
||||
evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10))
|
||||
|
||||
# Template used to display messages. This is a python new-style format string
|
||||
# used to format the message information. See doc for all details.
|
||||
msg-template=
|
||||
|
||||
# Set the output format. Available formats are text, parseable, colorized, json
|
||||
# and msvs (visual studio). You can also give a reporter class, e.g.
|
||||
# mypackage.mymodule.MyReporterClass.
|
||||
#output-format=
|
||||
|
||||
# Tells whether to display a full report or only the messages.
|
||||
reports=no
|
||||
|
||||
# Activate the evaluation score.
|
||||
score=yes
|
||||
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
|
||||
# Only show warnings with the listed confidence levels. Leave empty to show
|
||||
# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE,
|
||||
# UNDEFINED.
|
||||
confidence=HIGH,
|
||||
CONTROL_FLOW,
|
||||
INFERENCE,
|
||||
INFERENCE_FAILURE,
|
||||
UNDEFINED
|
||||
|
||||
# Disable the message, report, category or checker with the given id(s). You
|
||||
# can either give multiple identifiers separated by comma (,) or put this
|
||||
# option multiple times (only on the command line, not in the configuration
|
||||
# file where it should appear only once). You can also use "--disable=all" to
|
||||
# disable everything first and then re-enable specific checks. For example, if
|
||||
# you want to run only the similarities checker, you can use "--disable=all
|
||||
# --enable=similarities". If you want to run only the classes checker, but have
|
||||
# no Warning level messages displayed, use "--disable=all --enable=classes
|
||||
# --disable=W".
|
||||
disable=raw-checker-failed,
|
||||
bad-inline-option,
|
||||
locally-disabled,
|
||||
file-ignored,
|
||||
suppressed-message,
|
||||
useless-suppression,
|
||||
deprecated-pragma,
|
||||
use-symbolic-message-instead,
|
||||
missing-module-docstring, # added
|
||||
missing-class-docstring, # added
|
||||
missing-function-docstring, # added
|
||||
invalid-name, # added
|
||||
broad-except, # added
|
||||
wrong-import-order, # added
|
||||
consider-using-f-string, # added
|
||||
logging-format-interpolation, # added
|
||||
no-member, # added
|
||||
consider-using-with, # added
|
||||
unused-argument,
|
||||
no-else-return, # added
|
||||
too-few-public-methods, # added
|
||||
import-error, # added
|
||||
protected-access, # added
|
||||
unused-variable, # added
|
||||
R0801 # added (similarity of two scripts)
|
||||
|
||||
# Enable the message, report, category or checker with the given id(s). You can
|
||||
# either give multiple identifier separated by comma (,) or put this option
|
||||
# multiple time (only on the command line, not in the configuration file where
|
||||
# it should appear only once). See also the "--disable" option for examples.
|
||||
enable=c-extension-no-member
|
||||
|
||||
|
||||
[BASIC]
|
||||
|
||||
# Naming style matching correct argument names.
|
||||
argument-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct argument names. Overrides argument-
|
||||
# naming-style. If left empty, argument names will be checked with the set
|
||||
# naming style.
|
||||
#argument-rgx=
|
||||
|
||||
# Naming style matching correct attribute names.
|
||||
attr-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct attribute names. Overrides attr-naming-
|
||||
# style. If left empty, attribute names will be checked with the set naming
|
||||
# style.
|
||||
#attr-rgx=
|
||||
|
||||
# Bad variable names which should always be refused, separated by a comma.
|
||||
bad-names=foo,
|
||||
bar,
|
||||
baz,
|
||||
toto,
|
||||
tutu,
|
||||
tata
|
||||
|
||||
# Bad variable names regexes, separated by a comma. If names match any regex,
|
||||
# they will always be refused
|
||||
bad-names-rgxs=
|
||||
|
||||
# Naming style matching correct class attribute names.
|
||||
class-attribute-naming-style=any
|
||||
|
||||
# Regular expression matching correct class attribute names. Overrides class-
|
||||
# attribute-naming-style. If left empty, class attribute names will be checked
|
||||
# with the set naming style.
|
||||
#class-attribute-rgx=
|
||||
|
||||
# Naming style matching correct class constant names.
|
||||
class-const-naming-style=UPPER_CASE
|
||||
|
||||
# Regular expression matching correct class constant names. Overrides class-
|
||||
# const-naming-style. If left empty, class constant names will be checked with
|
||||
# the set naming style.
|
||||
#class-const-rgx=
|
||||
|
||||
# Naming style matching correct class names.
|
||||
class-naming-style=PascalCase
|
||||
|
||||
# Regular expression matching correct class names. Overrides class-naming-
|
||||
# style. If left empty, class names will be checked with the set naming style.
|
||||
#class-rgx=
|
||||
|
||||
# Naming style matching correct constant names.
|
||||
const-naming-style=UPPER_CASE
|
||||
|
||||
# Regular expression matching correct constant names. Overrides const-naming-
|
||||
# style. If left empty, constant names will be checked with the set naming
|
||||
# style.
|
||||
#const-rgx=
|
||||
|
||||
# Minimum line length for functions/classes that require docstrings, shorter
|
||||
# ones are exempt.
|
||||
docstring-min-length=-1
|
||||
|
||||
# Naming style matching correct function names.
|
||||
function-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct function names. Overrides function-
|
||||
# naming-style. If left empty, function names will be checked with the set
|
||||
# naming style.
|
||||
#function-rgx=
|
||||
|
||||
# Good variable names which should always be accepted, separated by a comma.
|
||||
good-names=i,
|
||||
j,
|
||||
k,
|
||||
ex,
|
||||
Run,
|
||||
_,
|
||||
x,
|
||||
y,
|
||||
nx,
|
||||
ny
|
||||
|
||||
# Good variable names regexes, separated by a comma. If names match any regex,
|
||||
# they will always be accepted
|
||||
good-names-rgxs=
|
||||
|
||||
# Include a hint for the correct naming format with invalid-name.
|
||||
include-naming-hint=no
|
||||
|
||||
# Naming style matching correct inline iteration names.
|
||||
inlinevar-naming-style=any
|
||||
|
||||
# Regular expression matching correct inline iteration names. Overrides
|
||||
# inlinevar-naming-style. If left empty, inline iteration names will be checked
|
||||
# with the set naming style.
|
||||
#inlinevar-rgx=
|
||||
|
||||
# Naming style matching correct method names.
|
||||
method-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct method names. Overrides method-naming-
|
||||
# style. If left empty, method names will be checked with the set naming style.
|
||||
#method-rgx=
|
||||
|
||||
# Naming style matching correct module names.
|
||||
module-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct module names. Overrides module-naming-
|
||||
# style. If left empty, module names will be checked with the set naming style.
|
||||
#module-rgx=
|
||||
|
||||
# Colon-delimited sets of names that determine each other's naming style when
|
||||
# the name regexes allow several styles.
|
||||
name-group=
|
||||
|
||||
# Regular expression which should only match function or class names that do
|
||||
# not require a docstring.
|
||||
no-docstring-rgx=^_
|
||||
|
||||
# List of decorators that produce properties, such as abc.abstractproperty. Add
|
||||
# to this list to register other decorators that produce valid properties.
|
||||
# These decorators are taken in consideration only for invalid-name.
|
||||
property-classes=abc.abstractproperty
|
||||
|
||||
# Regular expression matching correct type variable names. If left empty, type
|
||||
# variable names will be checked with the set naming style.
|
||||
#typevar-rgx=
|
||||
|
||||
# Naming style matching correct variable names.
|
||||
variable-naming-style=snake_case
|
||||
|
||||
# Regular expression matching correct variable names. Overrides variable-
|
||||
# naming-style. If left empty, variable names will be checked with the set
|
||||
# naming style.
|
||||
#variable-rgx=
|
||||
|
||||
|
||||
[VARIABLES]
|
||||
|
||||
# List of additional names supposed to be defined in builtins. Remember that
|
||||
# you should avoid defining new builtins when possible.
|
||||
additional-builtins=
|
||||
|
||||
# Tells whether unused global variables should be treated as a violation.
|
||||
allow-global-unused-variables=yes
|
||||
|
||||
# List of names allowed to shadow builtins
|
||||
allowed-redefined-builtins=
|
||||
|
||||
# List of strings which can identify a callback function by name. A callback
|
||||
# name must start or end with one of those strings.
|
||||
callbacks=cb_,
|
||||
_cb
|
||||
|
||||
# A regular expression matching the name of dummy variables (i.e. expected to
|
||||
# not be used).
|
||||
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
|
||||
|
||||
# Argument names that match this expression will be ignored.
|
||||
ignored-argument-names=_.*|^ignored_|^unused_
|
||||
|
||||
# Tells whether we should check for unused import in __init__ files.
|
||||
init-import=no
|
||||
|
||||
# List of qualified module names which can have objects that can redefine
|
||||
# builtins.
|
||||
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
|
||||
|
||||
|
||||
[SIMILARITIES]
|
||||
|
||||
# Comments are removed from the similarity computation
|
||||
ignore-comments=yes
|
||||
|
||||
# Docstrings are removed from the similarity computation
|
||||
ignore-docstrings=yes
|
||||
|
||||
# Imports are removed from the similarity computation
|
||||
ignore-imports=yes
|
||||
|
||||
# Signatures are removed from the similarity computation
|
||||
ignore-signatures=yes
|
||||
|
||||
# Minimum lines number of a similarity.
|
||||
min-similarity-lines=4
|
||||
|
||||
|
||||
[MISCELLANEOUS]
|
||||
|
||||
# List of note tags to take in consideration, separated by a comma.
|
||||
notes=FIXME,
|
||||
XXX,
|
||||
TODO
|
||||
|
||||
# Regular expression of note tags to take in consideration.
|
||||
notes-rgx=
|
||||
|
||||
|
||||
[STRING]
|
||||
|
||||
# This flag controls whether inconsistent-quotes generates a warning when the
|
||||
# character used as a quote delimiter is used inconsistently within a module.
|
||||
check-quote-consistency=no
|
||||
|
||||
# This flag controls whether the implicit-str-concat should generate a warning
|
||||
# on implicit string concatenation in sequences defined over several lines.
|
||||
check-str-concat-over-line-jumps=no
|
||||
|
||||
|
||||
[IMPORTS]
|
||||
|
||||
# List of modules that can be imported at any level, not just the top level
|
||||
# one.
|
||||
allow-any-import-level=
|
||||
|
||||
# Allow wildcard imports from modules that define __all__.
|
||||
allow-wildcard-with-all=no
|
||||
|
||||
# Deprecated modules which should not be used, separated by a comma.
|
||||
deprecated-modules=
|
||||
|
||||
# Output a graph (.gv or any supported image format) of external dependencies
|
||||
# to the given file (report RP0402 must not be disabled).
|
||||
ext-import-graph=
|
||||
|
||||
# Output a graph (.gv or any supported image format) of all (i.e. internal and
|
||||
# external) dependencies to the given file (report RP0402 must not be
|
||||
# disabled).
|
||||
import-graph=
|
||||
|
||||
# Output a graph (.gv or any supported image format) of internal dependencies
|
||||
# to the given file (report RP0402 must not be disabled).
|
||||
int-import-graph=
|
||||
|
||||
# Force import order to recognize a module as part of the standard
|
||||
# compatibility libraries.
|
||||
known-standard-library=
|
||||
|
||||
# Force import order to recognize a module as part of a third party library.
|
||||
known-third-party=enchant
|
||||
|
||||
# Couples of modules and preferred modules, separated by a comma.
|
||||
preferred-modules=
|
||||
|
||||
|
||||
[EXCEPTIONS]
|
||||
|
||||
# Exceptions that will emit a warning when caught.
|
||||
overgeneral-exceptions=BaseException,
|
||||
Exception
|
||||
|
||||
|
||||
[DESIGN]
|
||||
|
||||
# List of regular expressions of class ancestor names to ignore when counting
|
||||
# public methods (see R0903)
|
||||
exclude-too-few-public-methods=
|
||||
|
||||
# List of qualified class names to ignore when counting class parents (see
|
||||
# R0901)
|
||||
ignored-parents=
|
||||
|
||||
# Maximum number of arguments for function / method.
|
||||
max-args=5
|
||||
|
||||
# Maximum number of attributes for a class (see R0902).
|
||||
max-attributes=7
|
||||
|
||||
# Maximum number of boolean expressions in an if statement (see R0916).
|
||||
max-bool-expr=5
|
||||
|
||||
# Maximum number of branch for function / method body.
|
||||
max-branches=12
|
||||
|
||||
# Maximum number of locals for function / method body.
|
||||
max-locals=15
|
||||
|
||||
# Maximum number of parents for a class (see R0901).
|
||||
max-parents=7
|
||||
|
||||
# Maximum number of public methods for a class (see R0904).
|
||||
max-public-methods=20
|
||||
|
||||
# Maximum number of return / yield for function / method body.
|
||||
max-returns=6
|
||||
|
||||
# Maximum number of statements in function / method body.
|
||||
max-statements=50
|
||||
|
||||
# Minimum number of public methods for a class (see R0903).
|
||||
min-public-methods=2
|
||||
|
||||
|
||||
[METHOD_ARGS]
|
||||
|
||||
# List of qualified names (i.e., library.method) which require a timeout
|
||||
# parameter e.g. 'requests.api.get,requests.api.post'
|
||||
timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request
|
||||
|
||||
|
||||
[LOGGING]
|
||||
|
||||
# The type of string formatting that logging methods do. `old` means using %
|
||||
# formatting, `new` is for `{}` formatting.
|
||||
logging-format-style=old
|
||||
|
||||
# Logging modules to check that the string format arguments are in logging
|
||||
# function parameter format.
|
||||
logging-modules=logging
|
||||
|
||||
|
||||
[FORMAT]
|
||||
|
||||
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
|
||||
expected-line-ending-format=
|
||||
|
||||
# Regexp for a line that is allowed to be longer than the limit.
|
||||
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
|
||||
|
||||
# Number of spaces of indent required inside a hanging or continued line.
|
||||
indent-after-paren=4
|
||||
|
||||
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
|
||||
# tab).
|
||||
indent-string=' '
|
||||
|
||||
# Maximum number of characters on a single line.
|
||||
max-line-length=100
|
||||
|
||||
# Maximum number of lines in a module.
|
||||
max-module-lines=1000
|
||||
|
||||
# Allow the body of a class to be on the same line as the declaration if body
|
||||
# contains single statement.
|
||||
single-line-class-stmt=no
|
||||
|
||||
# Allow the body of an if to be on the same line as the test if there is no
|
||||
# else.
|
||||
single-line-if-stmt=no
|
||||
|
||||
|
||||
[REFACTORING]
|
||||
|
||||
# Maximum number of nested blocks for function / method body
|
||||
max-nested-blocks=5
|
||||
|
||||
# Complete name of functions that never returns. When checking for
|
||||
# inconsistent-return-statements if a never returning function is called then
|
||||
# it will be considered as an explicit return statement and no message will be
|
||||
# printed.
|
||||
never-returning-functions=sys.exit,argparse.parse_error
|
||||
|
||||
|
||||
[TYPECHECK]
|
||||
|
||||
# List of decorators that produce context managers, such as
|
||||
# contextlib.contextmanager. Add to this list to register other decorators that
|
||||
# produce valid context managers.
|
||||
contextmanager-decorators=contextlib.contextmanager
|
||||
|
||||
# List of members which are set dynamically and missed by pylint inference
|
||||
# system, and so shouldn't trigger E1101 when accessed. Python regular
|
||||
# expressions are accepted.
|
||||
generated-members=
|
||||
|
||||
# Tells whether to warn about missing members when the owner of the attribute
|
||||
# is inferred to be None.
|
||||
ignore-none=yes
|
||||
|
||||
# This flag controls whether pylint should warn about no-member and similar
|
||||
# checks whenever an opaque object is returned when inferring. The inference
|
||||
# can return multiple potential results while evaluating a Python object, but
|
||||
# some branches might not be evaluated, which results in partial inference. In
|
||||
# that case, it might be useful to still emit no-member and other checks for
|
||||
# the rest of the inferred objects.
|
||||
ignore-on-opaque-inference=yes
|
||||
|
||||
# List of symbolic message names to ignore for Mixin members.
|
||||
ignored-checks-for-mixins=no-member,
|
||||
not-async-context-manager,
|
||||
not-context-manager,
|
||||
attribute-defined-outside-init
|
||||
|
||||
# List of class names for which member attributes should not be checked (useful
|
||||
# for classes with dynamically set attributes). This supports the use of
|
||||
# qualified names.
|
||||
ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace
|
||||
|
||||
# Show a hint with possible names when a member name was not found. The aspect
|
||||
# of finding the hint is based on edit distance.
|
||||
missing-member-hint=yes
|
||||
|
||||
# The minimum edit distance a name should have in order to be considered a
|
||||
# similar match for a missing member name.
|
||||
missing-member-hint-distance=1
|
||||
|
||||
# The total number of similar names that should be taken in consideration when
|
||||
# showing a hint for a missing member.
|
||||
missing-member-max-choices=1
|
||||
|
||||
# Regex pattern to define which classes are considered mixins.
|
||||
mixin-class-rgx=.*[Mm]ixin
|
||||
|
||||
# List of decorators that change the signature of a decorated function.
|
||||
signature-mutators=
|
||||
|
||||
|
||||
[CLASSES]
|
||||
|
||||
# Warn about protected attribute access inside special methods
|
||||
check-protected-access-in-special-methods=no
|
||||
|
||||
# List of method names used to declare (i.e. assign) instance attributes.
|
||||
defining-attr-methods=__init__,
|
||||
__new__,
|
||||
setUp,
|
||||
__post_init__
|
||||
|
||||
# List of member names, which should be excluded from the protected access
|
||||
# warning.
|
||||
exclude-protected=_asdict,
|
||||
_fields,
|
||||
_replace,
|
||||
_source,
|
||||
_make
|
||||
|
||||
# List of valid names for the first argument in a class method.
|
||||
valid-classmethod-first-arg=cls
|
||||
|
||||
# List of valid names for the first argument in a metaclass class method.
|
||||
valid-metaclass-classmethod-first-arg=cls
|
||||
|
||||
|
||||
[SPELLING]
|
||||
|
||||
# Limits count of emitted suggestions for spelling mistakes.
|
||||
max-spelling-suggestions=4
|
||||
|
||||
# Spelling dictionary name. Available dictionaries: none. To make it work,
|
||||
# install the 'python-enchant' package.
|
||||
spelling-dict=
|
||||
|
||||
# List of comma separated words that should be considered directives if they
|
||||
# appear at the beginning of a comment and should not be checked.
|
||||
spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:
|
||||
|
||||
# List of comma separated words that should not be checked.
|
||||
spelling-ignore-words=
|
||||
|
||||
# A path to a file that contains the private dictionary; one word per line.
|
||||
spelling-private-dict-file=
|
||||
|
||||
# Tells whether to store unknown words to the private dictionary (see the
|
||||
# --spelling-private-dict-file option) instead of raising a message.
|
||||
spelling-store-unknown-words=no
|
||||
@@ -1,9 +1,11 @@
|
||||
This is a Python module for Vosk.
|
||||
|
||||
Vosk is an offline open source speech recognition toolkit. It enables
|
||||
speech recognition models for 17 languages and dialects - English, Indian
|
||||
speech recognition for 20+ languages and dialects - English, Indian
|
||||
English, German, French, Spanish, Portuguese, Chinese, Russian, Turkish,
|
||||
Vietnamese, Italian, Dutch, Catalan, Arabic, Greek, Farsi, Filipino.
|
||||
Vietnamese, Italian, Dutch, Catalan, Arabic, Greek, Farsi, Filipino,
|
||||
Ukrainian, Kazakh, Swedish, Japanese, Esperanto, Hindi, Czech, Polish.
|
||||
More to come.
|
||||
|
||||
Vosk models are small (50 Mb) but provide continuous large vocabulary
|
||||
transcription, zero-latency response with streaming API, reconfigurable
|
||||
Executable
+48
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import wave
|
||||
import sys
|
||||
import time
|
||||
|
||||
from vosk import Model, KaldiRecognizer, SetLogLevel
|
||||
|
||||
# You can set log level to -1 to disable debug messages
|
||||
SetLogLevel(0)
|
||||
|
||||
wf = wave.open(sys.argv[1], "rb")
|
||||
if wf.getnchannels() != 1 or wf.getsampwidth() != 2 or wf.getcomptype() != "NONE":
|
||||
print("Audio file must be WAV format mono PCM.")
|
||||
sys.exit(1)
|
||||
|
||||
#model = Model("vosk-model-ru-0.53-private-0.1")
|
||||
model = Model("vosk-model-small-ru")
|
||||
|
||||
rec = KaldiRecognizer(model, wf.getframerate())
|
||||
|
||||
while True:
|
||||
data = wf.readframes(4000)
|
||||
if len(data) == 0:
|
||||
break
|
||||
|
||||
# Feed into waveform
|
||||
rec.AcceptWaveform(data)
|
||||
|
||||
# Wait for processing
|
||||
while rec.GetNumPendingResults() > 0:
|
||||
time.sleep(0.05)
|
||||
|
||||
# Retrieve the results
|
||||
while not rec.ResultsEmpty():
|
||||
print (rec.Result())
|
||||
rec.Pop()
|
||||
|
||||
rec.Flush()
|
||||
|
||||
# Wait for processing
|
||||
while rec.GetNumPendingResults() > 0:
|
||||
time.sleep(0.05)
|
||||
|
||||
# Retrieve the results
|
||||
while not rec.ResultsEmpty():
|
||||
print (rec.Result())
|
||||
rec.Pop()
|
||||
Executable
+62
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import wave
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
from timeit import default_timer as timer
|
||||
|
||||
from multiprocessing.dummy import Pool
|
||||
from vosk import Model, KaldiRecognizer
|
||||
|
||||
#model = Model("vosk-model-ru-0.54-private-0.1")
|
||||
model = Model("vosk-model-ru-0.62")
|
||||
#model = Model("vosk-model-small-ru")
|
||||
|
||||
def recognize(line):
|
||||
fn = line.strip()
|
||||
wf = wave.open(fn, "rb")
|
||||
rec = KaldiRecognizer(model, wf.getframerate())
|
||||
|
||||
results = []
|
||||
while True:
|
||||
data = wf.readframes(4000)
|
||||
if len(data) == 0:
|
||||
break
|
||||
|
||||
rec.AcceptWaveform(data)
|
||||
while rec.GetNumPendingResults() > 0:
|
||||
time.sleep(0.05)
|
||||
|
||||
while not rec.ResultsEmpty():
|
||||
jres = json.loads(rec.Result())
|
||||
if 'text' in jres:
|
||||
print (jres)
|
||||
results.append(jres['text'])
|
||||
rec.Pop()
|
||||
|
||||
rec.Flush()
|
||||
|
||||
while rec.GetNumPendingResults() > 0:
|
||||
time.sleep(0.05)
|
||||
|
||||
while not rec.ResultsEmpty():
|
||||
jres = json.loads(rec.Result())
|
||||
if 'text' in jres:
|
||||
print (jres)
|
||||
results.append(jres['text'])
|
||||
rec.Pop()
|
||||
|
||||
owf = open(fn.replace(".wav", ".hyp"), "w")
|
||||
owf.write("\n".join(results))
|
||||
|
||||
def main():
|
||||
start = timer()
|
||||
p = Pool(20)
|
||||
p.map(recognize, open(sys.argv[1]).readlines())
|
||||
allsize = sum([os.path.getsize(f.strip()) for f in open(sys.argv[1]).readlines()]) / 8000.0 / 2
|
||||
dur = timer() - start
|
||||
print (f"Recognized {allsize:.2f} seconds in {dur} seconds speed {dur / allsize:.3f} x RT")
|
||||
|
||||
main()
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import wave
|
||||
import sys
|
||||
import time
|
||||
|
||||
from vosk import Model, KaldiRecognizer, SetLogLevel
|
||||
|
||||
# You can set log level to -1 to disable debug messages
|
||||
SetLogLevel(0)
|
||||
|
||||
wf = wave.open(sys.argv[1], "rb")
|
||||
if wf.getnchannels() != 1 or wf.getsampwidth() != 2 or wf.getcomptype() != "NONE":
|
||||
print("Audio file must be WAV format mono PCM.")
|
||||
sys.exit(1)
|
||||
|
||||
#model = Model("vosk-model-ru-0.53-private-0.1")
|
||||
model = Model("vosk-model-small-ru")
|
||||
|
||||
rec = KaldiRecognizer(model, wf.getframerate())
|
||||
|
||||
data = wf.readframes(min(wf.getnframes(), 320000))
|
||||
rec.AcceptWaveform(data)
|
||||
rec.Flush()
|
||||
|
||||
while rec.GetNumPendingResults() > 0:
|
||||
time.sleep(0.05)
|
||||
while not rec.ResultsEmpty():
|
||||
print (rec.Result())
|
||||
rec.Pop()
|
||||
@@ -0,0 +1,75 @@
|
||||
import os
|
||||
import setuptools
|
||||
import shutil
|
||||
import glob
|
||||
import platform
|
||||
|
||||
# Figure out environment for cross-compile
|
||||
vosk_source = os.getenv("VOSK_SOURCE", os.path.abspath(os.path.join(os.path.dirname(__file__),
|
||||
"../..")))
|
||||
system = os.environ.get('VOSK_SYSTEM', platform.system())
|
||||
architecture = os.environ.get('VOSK_ARCHITECTURE', platform.architecture()[0])
|
||||
machine = os.environ.get('VOSK_MACHINE', platform.machine())
|
||||
|
||||
# Copy precompmilled libraries
|
||||
for lib in glob.glob(os.path.join(vosk_source, "src/lib*.*")):
|
||||
print ("Adding library", lib)
|
||||
shutil.copy(lib, "vosk")
|
||||
|
||||
# Create OS-dependent, but Python-independent wheels.
|
||||
try:
|
||||
from wheel.bdist_wheel import bdist_wheel
|
||||
except ImportError:
|
||||
cmdclass = {}
|
||||
else:
|
||||
class bdist_wheel_tag_name(bdist_wheel):
|
||||
def get_tag(self):
|
||||
abi = 'none'
|
||||
if system == 'Darwin':
|
||||
oses = 'macosx_10_6_universal2'
|
||||
elif system == 'Windows' and architecture == '32bit':
|
||||
oses = 'win32'
|
||||
elif system == 'Windows' and architecture == '64bit':
|
||||
oses = 'win_amd64'
|
||||
elif system == 'Linux' and machine == 'aarch64' and architecture == '64bit':
|
||||
oses = 'manylinux2014_aarch64'
|
||||
elif system == 'Linux':
|
||||
oses = 'linux_' + machine
|
||||
else:
|
||||
raise TypeError("Unknown build environment")
|
||||
return 'py3', abi, oses
|
||||
cmdclass = {'bdist_wheel': bdist_wheel_tag_name}
|
||||
|
||||
with open("README.md", "rb") as fh:
|
||||
long_description = fh.read().decode("utf-8")
|
||||
|
||||
setuptools.setup(
|
||||
name="vosk",
|
||||
version="0.4.0",
|
||||
author="Alpha Cephei Inc",
|
||||
author_email="contact@alphacephei.com",
|
||||
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(),
|
||||
package_data = {'vosk': ['*.so*', '*.dll', '*.dyld']},
|
||||
entry_points = {
|
||||
'console_scripts': ['vosk-transcriber=vosk.transcriber.cli:main'],
|
||||
},
|
||||
include_package_data=True,
|
||||
classifiers=[
|
||||
'Programming Language :: Python :: 3',
|
||||
'License :: OSI Approved :: Apache Software License',
|
||||
'Operating System :: Microsoft :: Windows',
|
||||
'Operating System :: POSIX :: Linux',
|
||||
'Operating System :: MacOS :: MacOS X',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules'
|
||||
],
|
||||
cmdclass=cmdclass,
|
||||
python_requires='>=3',
|
||||
zip_safe=False, # Since we load so file from the filesystem, we can not run from zip file
|
||||
setup_requires=['cffi>=1.0', 'requests', 'tqdm', 'srt', 'websockets'],
|
||||
install_requires=['cffi>=1.0', 'requests', 'tqdm', 'srt', 'websockets'],
|
||||
cffi_modules=['vosk_builder.py:ffibuilder'],
|
||||
)
|
||||
@@ -0,0 +1,247 @@
|
||||
import os
|
||||
import sys
|
||||
import srt
|
||||
import datetime
|
||||
import json
|
||||
import enum
|
||||
|
||||
import requests
|
||||
from urllib.request import urlretrieve
|
||||
from zipfile import ZipFile
|
||||
from re import match
|
||||
from pathlib import Path
|
||||
from .vosk_cffi import ffi as _ffi
|
||||
from tqdm import tqdm
|
||||
|
||||
# Remote location of the models and local folders
|
||||
MODEL_PRE_URL = "https://alphacephei.com/vosk/models/"
|
||||
MODEL_LIST_URL = MODEL_PRE_URL + "model-list.json"
|
||||
MODEL_DIRS = [os.getenv("VOSK_MODEL_PATH"), Path("/usr/share/vosk"),
|
||||
Path.home() / "AppData/Local/vosk", Path.home() / ".cache/vosk"]
|
||||
|
||||
def open_dll():
|
||||
dlldir = os.path.abspath(os.path.dirname(__file__))
|
||||
if sys.platform == "win32":
|
||||
# We want to load dependencies too
|
||||
os.environ["PATH"] = dlldir + os.pathsep + os.environ["PATH"]
|
||||
if hasattr(os, "add_dll_directory"):
|
||||
os.add_dll_directory(dlldir)
|
||||
return _ffi.dlopen(os.path.join(dlldir, "libvosk.dll"))
|
||||
elif sys.platform == "linux":
|
||||
return _ffi.dlopen(os.path.join(dlldir, "libvosk.so"))
|
||||
elif sys.platform == "darwin":
|
||||
return _ffi.dlopen(os.path.join(dlldir, "libvosk.dyld"))
|
||||
else:
|
||||
raise TypeError("Unsupported platform")
|
||||
|
||||
_c = open_dll()
|
||||
|
||||
def list_models():
|
||||
response = requests.get(MODEL_LIST_URL, timeout=10)
|
||||
for model in response.json():
|
||||
print(model["name"])
|
||||
|
||||
def list_languages():
|
||||
response = requests.get(MODEL_LIST_URL, timeout=10)
|
||||
languages = {m["lang"] for m in response.json()}
|
||||
for lang in languages:
|
||||
print (lang)
|
||||
|
||||
class Model:
|
||||
def __init__(self, model_path=None, model_name=None, lang=None):
|
||||
if model_path is not None:
|
||||
self._handle = _c.vosk_model_new(model_path.encode("utf-8"))
|
||||
else:
|
||||
model_path = self.get_model_path(model_name, lang)
|
||||
self._handle = _c.vosk_model_new(model_path.encode("utf-8"))
|
||||
if self._handle == _ffi.NULL:
|
||||
raise Exception("Failed to create a model")
|
||||
|
||||
def __del__(self):
|
||||
if _c is not None:
|
||||
_c.vosk_model_free(self._handle)
|
||||
|
||||
def vosk_model_find_word(self, word):
|
||||
return _c.vosk_model_find_word(self._handle, word.encode("utf-8"))
|
||||
|
||||
def get_model_path(self, model_name, lang):
|
||||
if model_name is None:
|
||||
model_path = self.get_model_by_lang(lang)
|
||||
else:
|
||||
model_path = self.get_model_by_name(model_name)
|
||||
return str(model_path)
|
||||
|
||||
def get_model_by_name(self, model_name):
|
||||
for directory in MODEL_DIRS:
|
||||
if directory is None or not Path(directory).exists():
|
||||
continue
|
||||
model_file_list = os.listdir(directory)
|
||||
model_file = [model for model in model_file_list if model == model_name]
|
||||
if model_file != []:
|
||||
return Path(directory, model_file[0])
|
||||
response = requests.get(MODEL_LIST_URL, timeout=10)
|
||||
result_model = [model["name"] for model in response.json() if model["name"] == model_name]
|
||||
if result_model == []:
|
||||
print("model name %s does not exist" % (model_name))
|
||||
sys.exit(1)
|
||||
else:
|
||||
self.download_model(Path(directory, result_model[0]))
|
||||
return Path(directory, result_model[0])
|
||||
|
||||
def get_model_by_lang(self, lang):
|
||||
for directory in MODEL_DIRS:
|
||||
if directory is None or not Path(directory).exists():
|
||||
continue
|
||||
model_file_list = os.listdir(directory)
|
||||
model_file = [model for model in model_file_list if
|
||||
match(r"vosk-model(-small)?-{}".format(lang), model)]
|
||||
if model_file != []:
|
||||
return Path(directory, model_file[0])
|
||||
response = requests.get(MODEL_LIST_URL, timeout=10)
|
||||
result_model = [model["name"] for model in response.json() if
|
||||
model["lang"] == lang and model["type"] == "small" and model["obsolete"] == "false"]
|
||||
if result_model == []:
|
||||
print("lang %s does not exist" % (lang))
|
||||
sys.exit(1)
|
||||
else:
|
||||
self.download_model(Path(directory, result_model[0]))
|
||||
return Path(directory, result_model[0])
|
||||
|
||||
def download_model(self, model_name):
|
||||
if not (model_name.parent).exists():
|
||||
(model_name.parent).mkdir(parents=True)
|
||||
with tqdm(unit="B", unit_scale=True, unit_divisor=1024, miniters=1,
|
||||
desc=(MODEL_PRE_URL + str(model_name.name) + ".zip").rsplit("/",
|
||||
maxsplit=1)[-1]) as t:
|
||||
reporthook = self.download_progress_hook(t)
|
||||
urlretrieve(MODEL_PRE_URL + str(model_name.name) + ".zip",
|
||||
str(model_name) + ".zip", reporthook=reporthook, data=None)
|
||||
t.total = t.n
|
||||
with ZipFile(str(model_name) + ".zip", "r") as model_ref:
|
||||
model_ref.extractall(model_name.parent)
|
||||
Path(str(model_name) + ".zip").unlink()
|
||||
|
||||
def download_progress_hook(self, t):
|
||||
last_b = [0]
|
||||
def update_to(b=1, bsize=1, tsize=None):
|
||||
if tsize not in (None, -1):
|
||||
t.total = tsize
|
||||
displayed = t.update((b - last_b[0]) * bsize)
|
||||
last_b[0] = b
|
||||
return displayed
|
||||
return update_to
|
||||
|
||||
class SpkModel:
|
||||
|
||||
def __init__(self, model_path):
|
||||
self._handle = _c.vosk_spk_model_new(model_path.encode("utf-8"))
|
||||
|
||||
if self._handle == _ffi.NULL:
|
||||
raise Exception("Failed to create a speaker model")
|
||||
|
||||
def __del__(self):
|
||||
_c.vosk_spk_model_free(self._handle)
|
||||
|
||||
class EndpointerMode(enum.Enum):
|
||||
DEFAULT = 0
|
||||
SHORT = 1
|
||||
LONG = 2
|
||||
VERY_LONG = 3
|
||||
|
||||
class KaldiRecognizer:
|
||||
|
||||
def __init__(self, *args):
|
||||
if len(args) == 2:
|
||||
self._handle = _c.vosk_recognizer_new(args[0]._handle, args[1])
|
||||
elif len(args) == 3 and isinstance(args[2], SpkModel):
|
||||
self._handle = _c.vosk_recognizer_new_spk(args[0]._handle,
|
||||
args[1], args[2]._handle)
|
||||
elif len(args) == 3 and isinstance(args[2], str):
|
||||
self._handle = _c.vosk_recognizer_new_grm(args[0]._handle,
|
||||
args[1], args[2].encode("utf-8"))
|
||||
else:
|
||||
raise TypeError("Unknown arguments")
|
||||
|
||||
if self._handle == _ffi.NULL:
|
||||
raise Exception("Failed to create a recognizer")
|
||||
|
||||
def __del__(self):
|
||||
_c.vosk_recognizer_free(self._handle)
|
||||
|
||||
def SetMaxAlternatives(self, max_alternatives):
|
||||
_c.vosk_recognizer_set_max_alternatives(self._handle, max_alternatives)
|
||||
|
||||
def SetWords(self, enable_words):
|
||||
_c.vosk_recognizer_set_words(self._handle, 1 if enable_words else 0)
|
||||
|
||||
def SetPartialWords(self, enable_partial_words):
|
||||
_c.vosk_recognizer_set_partial_words(self._handle, 1 if enable_partial_words else 0)
|
||||
|
||||
def SetNLSML(self, enable_nlsml):
|
||||
_c.vosk_recognizer_set_nlsml(self._handle, 1 if enable_nlsml else 0)
|
||||
|
||||
def SetEndpointerMode(self, mode):
|
||||
_c.vosk_recognizer_set_endpointer_mode(self._handle, mode.value)
|
||||
|
||||
def SetEndpointerDelays(self, t_start_max, t_end, t_max):
|
||||
_c.vosk_recognizer_set_endpointer_delays(self._handle, t_start_max, t_end, t_max)
|
||||
|
||||
def SetSpkModel(self, spk_model):
|
||||
_c.vosk_recognizer_set_spk_model(self._handle, spk_model._handle)
|
||||
|
||||
def SetGrammar(self, grammar):
|
||||
_c.vosk_recognizer_set_grm(self._handle, grammar.encode("utf-8"))
|
||||
|
||||
def AcceptWaveform(self, data):
|
||||
_c.vosk_recognizer_accept_waveform(self._handle, data, len(data))
|
||||
|
||||
def Flush(self):
|
||||
return _c.vosk_recognizer_flush(self._handle)
|
||||
|
||||
def Result(self):
|
||||
return _ffi.string(_c.vosk_recognizer_result_front(self._handle)).decode("utf-8")
|
||||
|
||||
def Pop(self):
|
||||
return _c.vosk_recognizer_result_pop(self._handle)
|
||||
|
||||
def GetNumPendingResults(self):
|
||||
return _c.vosk_recognizer_get_num_pending_results(self._handle)
|
||||
|
||||
def GetNumResults(self):
|
||||
return _c.vosk_recognizer_get_num_results(self._handle)
|
||||
|
||||
def ResultsEmpty(self):
|
||||
return _c.vosk_recognizer_results_empty(self._handle)
|
||||
|
||||
def Reset(self):
|
||||
return _c.vosk_recognizer_reset(self._handle)
|
||||
|
||||
def SrtResult(self, stream, words_per_line = 7):
|
||||
results = []
|
||||
|
||||
while True:
|
||||
data = stream.read(4000)
|
||||
if len(data) == 0:
|
||||
break
|
||||
if self.AcceptWaveform(data):
|
||||
results.append(self.Result())
|
||||
results.append(self.FinalResult())
|
||||
|
||||
subs = []
|
||||
for res in results:
|
||||
jres = json.loads(res)
|
||||
if not "result" in jres:
|
||||
continue
|
||||
words = jres["result"]
|
||||
for j in range(0, len(words), words_per_line):
|
||||
line = words[j : j + words_per_line]
|
||||
s = srt.Subtitle(index=len(subs),
|
||||
content=" ".join([l["word"] for l in line]),
|
||||
start=datetime.timedelta(seconds=line[0]["start"]),
|
||||
end=datetime.timedelta(seconds=line[-1]["end"]))
|
||||
subs.append(s)
|
||||
|
||||
return srt.compose(subs)
|
||||
|
||||
def SetLogLevel(level):
|
||||
return _c.vosk_set_log_level(level)
|
||||
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import sys
|
||||
import os
|
||||
|
||||
from pathlib import Path
|
||||
from vosk import list_models, list_languages
|
||||
from vosk.transcriber.transcriber import Transcriber
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description = "Transcribe audio file and save result in selected format")
|
||||
parser.add_argument(
|
||||
"--model", "-m", type=str,
|
||||
help="model path")
|
||||
parser.add_argument(
|
||||
"--server", "-s", type=str,
|
||||
help="use server for recognition. For example ws://localhost:2700")
|
||||
parser.add_argument(
|
||||
"--list-models", default=False, action="store_true",
|
||||
help="list available models")
|
||||
parser.add_argument(
|
||||
"--list-languages", default=False, action="store_true",
|
||||
help="list available languages")
|
||||
parser.add_argument(
|
||||
"--model-name", "-n", type=str,
|
||||
help="select model by name")
|
||||
parser.add_argument(
|
||||
"--lang", "-l", default="en-us", type=str,
|
||||
help="select model by language")
|
||||
parser.add_argument(
|
||||
"--input", "-i", type=str,
|
||||
help="audiofile")
|
||||
parser.add_argument(
|
||||
"--output", "-o", default="", type=str,
|
||||
help="optional output filename path")
|
||||
parser.add_argument(
|
||||
"--output-type", "-t", default="txt", type=str,
|
||||
help="optional arg output data type")
|
||||
parser.add_argument(
|
||||
"--tasks", "-ts", default=10, type=int,
|
||||
help="number of parallel recognition tasks")
|
||||
parser.add_argument(
|
||||
"--log-level", default="INFO",
|
||||
help="logging level")
|
||||
|
||||
def main():
|
||||
|
||||
args = parser.parse_args()
|
||||
log_level = args.log_level.upper()
|
||||
logging.getLogger().setLevel(log_level)
|
||||
|
||||
if args.list_models is True:
|
||||
list_models()
|
||||
return
|
||||
|
||||
if args.list_languages is True:
|
||||
list_languages()
|
||||
return
|
||||
|
||||
if not args.input:
|
||||
logging.info("Please specify input file or directory")
|
||||
sys.exit(1)
|
||||
|
||||
if not Path(args.input).exists():
|
||||
logging.info("File/folder {args.input} does not exist, "\
|
||||
"please specify an existing file/directory")
|
||||
sys.exit(1)
|
||||
|
||||
transcriber = Transcriber(args)
|
||||
|
||||
if Path(args.input).is_dir():
|
||||
task_list = [(Path(args.input, fn),
|
||||
Path(args.output,
|
||||
Path(fn).stem).with_suffix("." + args.output_type)) for fn in os.listdir(args.input)]
|
||||
elif Path(args.input).is_file():
|
||||
if args.output == "":
|
||||
task_list = [(Path(args.input), args.output)]
|
||||
else:
|
||||
task_list = [(Path(args.input), Path(args.output))]
|
||||
else:
|
||||
logging.info("Wrong arguments")
|
||||
sys.exit(1)
|
||||
|
||||
transcriber.process_task_list(task_list)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,201 @@
|
||||
import json
|
||||
import logging
|
||||
import asyncio
|
||||
import websockets
|
||||
import srt
|
||||
import datetime
|
||||
import shlex
|
||||
import subprocess
|
||||
|
||||
from vosk import KaldiRecognizer, Model
|
||||
from queue import Queue
|
||||
from timeit import default_timer as timer
|
||||
from multiprocessing.dummy import Pool
|
||||
|
||||
CHUNK_SIZE = 4000
|
||||
SAMPLE_RATE = 16000.0
|
||||
|
||||
class Transcriber:
|
||||
|
||||
def __init__(self, args):
|
||||
self.model = Model(model_path=args.model, model_name=args.model_name, lang=args.lang)
|
||||
self.args = args
|
||||
self.queue = Queue()
|
||||
|
||||
def recognize_stream(self, rec, stream):
|
||||
tot_samples = 0
|
||||
result = []
|
||||
|
||||
while True:
|
||||
data = stream.stdout.read(CHUNK_SIZE)
|
||||
|
||||
if len(data) == 0:
|
||||
break
|
||||
|
||||
tot_samples += len(data)
|
||||
if rec.AcceptWaveform(data):
|
||||
jres = json.loads(rec.Result())
|
||||
logging.info(jres)
|
||||
result.append(jres)
|
||||
else:
|
||||
jres = json.loads(rec.PartialResult())
|
||||
if jres["partial"] != "":
|
||||
logging.info(jres)
|
||||
|
||||
jres = json.loads(rec.FinalResult())
|
||||
result.append(jres)
|
||||
|
||||
return result, tot_samples
|
||||
|
||||
async def recognize_stream_server(self, proc):
|
||||
async with websockets.connect(self.args.server) as websocket:
|
||||
tot_samples = 0
|
||||
result = []
|
||||
|
||||
await websocket.send('{ "config" : { "sample_rate" : %f } }' % (SAMPLE_RATE))
|
||||
while True:
|
||||
data = await proc.stdout.read(CHUNK_SIZE)
|
||||
tot_samples += len(data)
|
||||
if len(data) == 0:
|
||||
break
|
||||
await websocket.send(data)
|
||||
jres = json.loads(await websocket.recv())
|
||||
logging.info(jres)
|
||||
if not "partial" in jres:
|
||||
result.append(jres)
|
||||
await websocket.send('{"eof" : 1}')
|
||||
jres = json.loads(await websocket.recv())
|
||||
logging.info(jres)
|
||||
result.append(jres)
|
||||
|
||||
return result, tot_samples
|
||||
|
||||
|
||||
def format_result(self, result, words_per_line=7):
|
||||
processed_result = ""
|
||||
if self.args.output_type == "srt":
|
||||
subs = []
|
||||
|
||||
for _, res in enumerate(result):
|
||||
if not "result" in res:
|
||||
continue
|
||||
words = res["result"]
|
||||
|
||||
for j in range(0, len(words), words_per_line):
|
||||
line = words[j : j + words_per_line]
|
||||
s = srt.Subtitle(index=len(subs),
|
||||
content = " ".join([l["word"] for l in line]),
|
||||
start=datetime.timedelta(seconds=line[0]["start"]),
|
||||
end=datetime.timedelta(seconds=line[-1]["end"]))
|
||||
subs.append(s)
|
||||
processed_result = srt.compose(subs)
|
||||
|
||||
elif self.args.output_type == "txt":
|
||||
for part in result:
|
||||
if part["text"] != "":
|
||||
processed_result += part["text"] + "\n"
|
||||
|
||||
elif self.args.output_type == "json":
|
||||
monologues = {"schemaVersion":"2.0", "monologues":[], "text":[]}
|
||||
for part in result:
|
||||
if part["text"] != "":
|
||||
monologues["text"] += [part["text"]]
|
||||
for _, res in enumerate(result):
|
||||
if not "result" in res:
|
||||
continue
|
||||
monologue = { "speaker": {"id": "unknown", "name": None}, "start": 0, "end": 0, "terms": []}
|
||||
monologue["start"] = res["result"][0]["start"]
|
||||
monologue["end"] = res["result"][-1]["end"]
|
||||
monologue["terms"] = [{"confidence": t["conf"], "start": t["start"], "end": t["end"], "text": t["word"], "type": "WORD" } for t in res["result"]]
|
||||
monologues["monologues"].append(monologue)
|
||||
processed_result = json.dumps(monologues)
|
||||
return processed_result
|
||||
|
||||
def resample_ffmpeg(self, infile):
|
||||
cmd = shlex.split("ffmpeg -nostdin -loglevel quiet "
|
||||
"-i \'{}\' -ar {} -ac 1 -f s16le -".format(str(infile), SAMPLE_RATE))
|
||||
stream = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
||||
return stream
|
||||
|
||||
async def resample_ffmpeg_async(self, infile):
|
||||
cmd = "ffmpeg -nostdin -loglevel quiet "\
|
||||
"-i \'{}\' -ar {} -ac 1 -f s16le -".format(str(infile), SAMPLE_RATE)
|
||||
return await asyncio.create_subprocess_shell(cmd, stdout=subprocess.PIPE)
|
||||
|
||||
async def server_worker(self):
|
||||
while True:
|
||||
try:
|
||||
input_file, output_file = self.queue.get_nowait()
|
||||
except Exception:
|
||||
break
|
||||
|
||||
logging.info("Recognizing {}".format(input_file))
|
||||
start_time = timer()
|
||||
proc = await self.resample_ffmpeg_async(input_file)
|
||||
result, tot_samples = await self.recognize_stream_server(proc)
|
||||
await proc.wait()
|
||||
|
||||
# Bad input, continue
|
||||
if tot_samples == 0:
|
||||
self.queue.task_done()
|
||||
continue
|
||||
|
||||
processed_result = self.format_result(result)
|
||||
if output_file != "":
|
||||
logging.info("File {} processing complete".format(output_file))
|
||||
with open(output_file, "w", encoding="utf-8") as fh:
|
||||
fh.write(processed_result)
|
||||
else:
|
||||
print(processed_result)
|
||||
|
||||
elapsed = timer() - start_time
|
||||
logging.info("Execution time: {:.3f} sec; "\
|
||||
"xRT {:.3f}".format(elapsed, float(elapsed) * (2 * SAMPLE_RATE) / tot_samples))
|
||||
self.queue.task_done()
|
||||
|
||||
def pool_worker(self, inputdata):
|
||||
logging.info("Recognizing {}".format(inputdata[0]))
|
||||
start_time = timer()
|
||||
|
||||
try:
|
||||
stream = self.resample_ffmpeg(inputdata[0])
|
||||
except FileNotFoundError as e:
|
||||
print(e, "Missing FFMPEG, please install and try again")
|
||||
return
|
||||
except Exception as e:
|
||||
logging.info(e)
|
||||
return
|
||||
|
||||
rec = KaldiRecognizer(self.model, SAMPLE_RATE)
|
||||
rec.SetWords(True)
|
||||
result, tot_samples = self.recognize_stream(rec, stream)
|
||||
if tot_samples == 0:
|
||||
return
|
||||
|
||||
processed_result = self.format_result(result)
|
||||
if inputdata[1] != "":
|
||||
logging.info("File {} processing complete".format(inputdata[1]))
|
||||
with open(inputdata[1], "w", encoding="utf-8") as fh:
|
||||
fh.write(processed_result)
|
||||
else:
|
||||
print(processed_result)
|
||||
|
||||
elapsed = timer() - start_time
|
||||
logging.info("Execution time: {:.3f} sec; "\
|
||||
"xRT {:.3f}".format(elapsed, float(elapsed) * (2 * SAMPLE_RATE) / tot_samples))
|
||||
|
||||
async def process_task_list_server(self, task_list):
|
||||
for x in task_list:
|
||||
self.queue.put(x)
|
||||
workers = [asyncio.create_task(self.server_worker()) for i in range(self.args.tasks)]
|
||||
await asyncio.gather(*workers)
|
||||
|
||||
def process_task_list_pool(self, task_list):
|
||||
with Pool() as pool:
|
||||
pool.map(self.pool_worker, task_list)
|
||||
|
||||
def process_task_list(self, task_list):
|
||||
if self.args.server is None:
|
||||
self.process_task_list_pool(task_list)
|
||||
else:
|
||||
asyncio.run(self.process_task_list_server(task_list))
|
||||
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
from cffi import FFI
|
||||
|
||||
vosk_root=os.environ.get("VOSK_SOURCE", "../..")
|
||||
cpp_command = "cpp " + vosk_root + "/src/vosk_api.h"
|
||||
|
||||
ffibuilder = FFI()
|
||||
ffibuilder.set_source("vosk.vosk_cffi", None)
|
||||
ffibuilder.cdef(os.popen(cpp_command).read())
|
||||
|
||||
if __name__ == '__main__':
|
||||
ffibuilder.compile(verbose=True)
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
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/install/lib/libopenblas.a \
|
||||
$(KALDI_ROOT)/tools/OpenBLAS/install/lib/liblapack.a \
|
||||
$(KALDI_ROOT)/tools/OpenBLAS/install/lib/libblas.a \
|
||||
$(KALDI_ROOT)/tools/OpenBLAS/install/lib/libf2c.a \
|
||||
$(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) -lpthread
|
||||
|
||||
test_vosk_speaker: test_vosk_speaker.o libvosk.a
|
||||
g++ $^ -o $@ $(LIBS) -lpthread
|
||||
|
||||
libvosk.a: $(VOSK_SOURCES:.cc=.o)
|
||||
ar rcs $@ $^
|
||||
|
||||
%.o: %.c
|
||||
g++ $(CFLAGS) -c -o $@ $<
|
||||
|
||||
%.o: %.cc
|
||||
g++ -std=c++17 $(CFLAGS) -c -o $@ $<
|
||||
|
||||
clean:
|
||||
rm -f *.o *.a test_vosk test_vosk_speaker
|
||||
@@ -1,29 +0,0 @@
|
||||
#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);
|
||||
fclose(wavin);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
KALDI_ROOT ?= $(HOME)/kaldi
|
||||
CFLAGS := -std=c++17 -g -O2 -DPIC -fPIC -Wno-unused-function
|
||||
CPPFLAGS := -I$(KALDI_ROOT)/src -I$(KALDI_ROOT)/tools/openfst/include -I../src -DFST_NO_DYNAMIC_LINKING
|
||||
|
||||
KALDI_LIBS = \
|
||||
${KALDI_ROOT}/src/online2/kaldi-online2.a \
|
||||
${KALDI_ROOT}/src/decoder/kaldi-decoder.a \
|
||||
${KALDI_ROOT}/src/ivector/kaldi-ivector.a \
|
||||
${KALDI_ROOT}/src/gmm/kaldi-gmm.a \
|
||||
${KALDI_ROOT}/src/nnet3/kaldi-nnet3.a \
|
||||
${KALDI_ROOT}/src/tree/kaldi-tree.a \
|
||||
${KALDI_ROOT}/src/feat/kaldi-feat.a \
|
||||
${KALDI_ROOT}/src/lat/kaldi-lat.a \
|
||||
${KALDI_ROOT}/src/lm/kaldi-lm.a \
|
||||
${KALDI_ROOT}/src/hmm/kaldi-hmm.a \
|
||||
${KALDI_ROOT}/src/transform/kaldi-transform.a \
|
||||
${KALDI_ROOT}/src/cudamatrix/kaldi-cudamatrix.a \
|
||||
${KALDI_ROOT}/src/matrix/kaldi-matrix.a \
|
||||
${KALDI_ROOT}/src/fstext/kaldi-fstext.a \
|
||||
${KALDI_ROOT}/src/util/kaldi-util.a \
|
||||
${KALDI_ROOT}/src/base/kaldi-base.a \
|
||||
${KALDI_ROOT}/tools/openfst/lib/libfst.a \
|
||||
${KALDI_ROOT}/tools/openfst/lib/libfstngram.a
|
||||
|
||||
# On Linux
|
||||
MATH_LIBS = \
|
||||
${KALDI_ROOT}/tools/OpenBLAS/install/lib/libopenblas.a \
|
||||
${KALDI_ROOT}/tools/OpenBLAS/install/lib/liblapack.a \
|
||||
${KALDI_ROOT}/tools/OpenBLAS/install/lib/libblas.a \
|
||||
${KALDI_ROOT}/tools/OpenBLAS/install/lib/libf2c.a
|
||||
|
||||
# On OSX
|
||||
# MATH_LIBS = -framework Accelerate
|
||||
|
||||
all: test.exe
|
||||
|
||||
test.exe: libkaldiwrap.so test.cs
|
||||
mcs -out:Vosk.dll -target:library gen/*.cs
|
||||
mcs test.cs -r:Vosk.dll -out:test.exe
|
||||
|
||||
VOSK_SOURCES = \
|
||||
vosk_wrap.c \
|
||||
../src/kaldi_recognizer.cc \
|
||||
../src/language_model.cc \
|
||||
../src/model.cc \
|
||||
../src/spk_model.cc \
|
||||
../src/vosk_api.cc
|
||||
|
||||
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
|
||||
mkdir -p gen
|
||||
swig -csharp -DSWIG_CSHARP_NO_EXCEPTION_HELPER -dllimport "libkaldiwrap" \
|
||||
-namespace "Kaldi" -outdir gen -o vosk_wrap.c ../src/vosk.i
|
||||
|
||||
run: test.exe
|
||||
mono test.exe
|
||||
|
||||
clean:
|
||||
$(RM) *.so vosk_wrap.c *.o gen/*.cs test.exe *.dll
|
||||
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<package>
|
||||
<metadata>
|
||||
<id>Vosk</id>
|
||||
<version>0.3.16</version>
|
||||
<authors>Alpha Cephei Inc</authors>
|
||||
<owners>Alpha Cephei Inc</owners>
|
||||
<license type="expression">Apache-2.0</license>
|
||||
<projectUrl>https://alphacephei.com/vosk/</projectUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<description>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.
|
||||
|
||||
Speech recognition bindings implemented for various programming languages like Python, Java, Node.JS, C#, C++ and others.
|
||||
|
||||
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.</description>
|
||||
<releaseNotes>See for details https://github.com/alphacep/vosk-api/releases</releaseNotes>
|
||||
<copyright>Copyright 2020 Alpha Cephei Inc</copyright>
|
||||
<tags>speech recognition voice stt asr speech-to-text ai offline privacy</tags>
|
||||
<dependencies>
|
||||
<group targetFramework=".NETStandard2.0"/>
|
||||
</dependencies>
|
||||
</metadata>
|
||||
</package>
|
||||
@@ -1,9 +0,0 @@
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Content Include="$(MSBuildThisFileDirectory)lib/**/**">
|
||||
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<Visible>false</Visible>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,47 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Kaldi;
|
||||
|
||||
public class Test
|
||||
{
|
||||
public static void Main()
|
||||
{
|
||||
|
||||
Vosk.SetLogLevel(0);
|
||||
Model model = new Model("model");
|
||||
KaldiRecognizer rec = new KaldiRecognizer(model, 16000.0f);
|
||||
|
||||
using(Stream source = File.OpenRead("test.wav")) {
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
while((bytesRead = source.Read(buffer, 0, buffer.Length)) > 0) {
|
||||
if (rec.AcceptWaveform(buffer, bytesRead)) {
|
||||
Console.WriteLine(rec.Result());
|
||||
} else {
|
||||
Console.WriteLine(rec.PartialResult());
|
||||
}
|
||||
}
|
||||
}
|
||||
Console.WriteLine(rec.FinalResult());
|
||||
|
||||
rec = new KaldiRecognizer(model, 16000.0f);
|
||||
|
||||
using(Stream source = File.OpenRead("test.wav")) {
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
while((bytesRead = source.Read(buffer, 0, buffer.Length)) > 0) {
|
||||
float[] fbuffer = new float[bytesRead / 2];
|
||||
for (int i = 0, n = 0; i < fbuffer.Length; i++, n+=2) {
|
||||
fbuffer[i] = (short)(buffer[n] | buffer[n+1] << 8);
|
||||
}
|
||||
if (rec.AcceptWaveform(fbuffer, fbuffer.Length)) {
|
||||
Console.WriteLine(rec.Result());
|
||||
GC.Collect();
|
||||
} else {
|
||||
Console.WriteLine(rec.PartialResult());
|
||||
}
|
||||
}
|
||||
}
|
||||
Console.WriteLine(rec.FinalResult());
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
See https://alphacephei.com/vosk/accuracy
|
||||
@@ -1 +0,0 @@
|
||||
See https://alphacephei.com/vosk/adaptation
|
||||
@@ -1 +0,0 @@
|
||||
See https://alphacephei.com/vosk/models
|
||||
@@ -1,3 +0,0 @@
|
||||
This is a baseline for the vosk-api iOS demo. It requires a build of a
|
||||
Vosk-API library, mail contact@alphacephei.com for the details.
|
||||
|
||||
@@ -1,394 +0,0 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
92375222240C550B00DD6076 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92375221240C550B00DD6076 /* AppDelegate.swift */; };
|
||||
92375224240C550B00DD6076 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92375223240C550B00DD6076 /* ViewController.swift */; };
|
||||
92375227240C550B00DD6076 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 92375225240C550B00DD6076 /* Main.storyboard */; };
|
||||
92375229240C550B00DD6076 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 92375228240C550B00DD6076 /* Assets.xcassets */; };
|
||||
9237522C240C550B00DD6076 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9237522A240C550B00DD6076 /* LaunchScreen.storyboard */; };
|
||||
92375234240C558900DD6076 /* Vosk.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92375233240C558900DD6076 /* Vosk.swift */; };
|
||||
9237523C240C642000DD6076 /* libkaldiwrap.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9237523A240C642000DD6076 /* libkaldiwrap.a */; };
|
||||
92375244240C6DAF00DD6076 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92375243240C6DAF00DD6076 /* Accelerate.framework */; };
|
||||
92375246240C6DC900DD6076 /* libstdc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 92375245240C6DC900DD6076 /* libstdc++.tbd */; };
|
||||
92375274240C6F1E00DD6076 /* 10001-90210-01803.wav in Resources */ = {isa = PBXBuildFile; fileRef = 92375256240C6E3D00DD6076 /* 10001-90210-01803.wav */; };
|
||||
92D86BD5253F823F0040D53F /* vosk-model-small-en-us-0.4 in Resources */ = {isa = PBXBuildFile; fileRef = 92D86BD3253F823E0040D53F /* vosk-model-small-en-us-0.4 */; };
|
||||
92D86BD6253F823F0040D53F /* vosk-model-spk-0.4 in Resources */ = {isa = PBXBuildFile; fileRef = 92D86BD4253F823F0040D53F /* vosk-model-spk-0.4 */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
9237521E240C550B00DD6076 /* VoskApiTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VoskApiTest.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
92375221240C550B00DD6076 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
92375223240C550B00DD6076 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
||||
92375226240C550B00DD6076 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
92375228240C550B00DD6076 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
9237522B240C550B00DD6076 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
9237522D240C550B00DD6076 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
92375233240C558900DD6076 /* Vosk.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vosk.swift; sourceTree = "<group>"; };
|
||||
9237523A240C642000DD6076 /* libkaldiwrap.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libkaldiwrap.a; sourceTree = "<group>"; };
|
||||
92375243240C6DAF00DD6076 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; };
|
||||
92375245240C6DC900DD6076 /* libstdc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libstdc++.tbd"; path = "usr/lib/libstdc++.tbd"; sourceTree = SDKROOT; };
|
||||
92375256240C6E3D00DD6076 /* 10001-90210-01803.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = "10001-90210-01803.wav"; sourceTree = "<group>"; };
|
||||
92AA22AD244CDD1200DA464B /* vosk_api.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vosk_api.h; sourceTree = "<group>"; };
|
||||
92AA22AE244CDD5200DA464B /* bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bridging.h; sourceTree = "<group>"; };
|
||||
92D86BD3253F823E0040D53F /* vosk-model-small-en-us-0.4 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "vosk-model-small-en-us-0.4"; sourceTree = "<group>"; };
|
||||
92D86BD4253F823F0040D53F /* vosk-model-spk-0.4 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "vosk-model-spk-0.4"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
9237521B240C550B00DD6076 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
92375246240C6DC900DD6076 /* libstdc++.tbd in Frameworks */,
|
||||
92375244240C6DAF00DD6076 /* Accelerate.framework in Frameworks */,
|
||||
9237523C240C642000DD6076 /* libkaldiwrap.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
92375215240C550A00DD6076 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
92375239240C642000DD6076 /* Vosk */,
|
||||
92375220240C550B00DD6076 /* VoskApiTest */,
|
||||
9237521F240C550B00DD6076 /* Products */,
|
||||
92375242240C6DAF00DD6076 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9237521F240C550B00DD6076 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9237521E240C550B00DD6076 /* VoskApiTest.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
92375220240C550B00DD6076 /* VoskApiTest */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
92AA22AE244CDD5200DA464B /* bridging.h */,
|
||||
92375221240C550B00DD6076 /* AppDelegate.swift */,
|
||||
92375223240C550B00DD6076 /* ViewController.swift */,
|
||||
92375225240C550B00DD6076 /* Main.storyboard */,
|
||||
92375228240C550B00DD6076 /* Assets.xcassets */,
|
||||
9237522A240C550B00DD6076 /* LaunchScreen.storyboard */,
|
||||
9237522D240C550B00DD6076 /* Info.plist */,
|
||||
92375233240C558900DD6076 /* Vosk.swift */,
|
||||
);
|
||||
path = VoskApiTest;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
92375239240C642000DD6076 /* Vosk */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
92D86BD3253F823E0040D53F /* vosk-model-small-en-us-0.4 */,
|
||||
92D86BD4253F823F0040D53F /* vosk-model-spk-0.4 */,
|
||||
92375256240C6E3D00DD6076 /* 10001-90210-01803.wav */,
|
||||
92AA22AD244CDD1200DA464B /* vosk_api.h */,
|
||||
9237523A240C642000DD6076 /* libkaldiwrap.a */,
|
||||
);
|
||||
name = Vosk;
|
||||
path = VoskApiTest/Vosk;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
92375242240C6DAF00DD6076 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
92375245240C6DC900DD6076 /* libstdc++.tbd */,
|
||||
92375243240C6DAF00DD6076 /* Accelerate.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
9237521D240C550B00DD6076 /* VoskApiTest */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 92375230240C550B00DD6076 /* Build configuration list for PBXNativeTarget "VoskApiTest" */;
|
||||
buildPhases = (
|
||||
9237521A240C550B00DD6076 /* Sources */,
|
||||
9237521B240C550B00DD6076 /* Frameworks */,
|
||||
9237521C240C550B00DD6076 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = VoskApiTest;
|
||||
productName = VoskApiTest;
|
||||
productReference = 9237521E240C550B00DD6076 /* VoskApiTest.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
92375216240C550A00DD6076 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0830;
|
||||
LastUpgradeCheck = 0920;
|
||||
ORGANIZATIONNAME = "Alpha Cephei";
|
||||
TargetAttributes = {
|
||||
9237521D240C550B00DD6076 = {
|
||||
CreatedOnToolsVersion = 8.3.2;
|
||||
LastSwiftMigration = 0920;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 92375219240C550A00DD6076 /* Build configuration list for PBXProject "VoskApiTest" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 92375215240C550A00DD6076;
|
||||
productRefGroup = 9237521F240C550B00DD6076 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
9237521D240C550B00DD6076 /* VoskApiTest */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
9237521C240C550B00DD6076 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
92375274240C6F1E00DD6076 /* 10001-90210-01803.wav in Resources */,
|
||||
9237522C240C550B00DD6076 /* LaunchScreen.storyboard in Resources */,
|
||||
92375229240C550B00DD6076 /* Assets.xcassets in Resources */,
|
||||
92D86BD6253F823F0040D53F /* vosk-model-spk-0.4 in Resources */,
|
||||
92375227240C550B00DD6076 /* Main.storyboard in Resources */,
|
||||
92D86BD5253F823F0040D53F /* vosk-model-small-en-us-0.4 in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
9237521A240C550B00DD6076 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
92375224240C550B00DD6076 /* ViewController.swift in Sources */,
|
||||
92375222240C550B00DD6076 /* AppDelegate.swift in Sources */,
|
||||
92375234240C558900DD6076 /* Vosk.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
92375225240C550B00DD6076 /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
92375226240C550B00DD6076 /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9237522A240C550B00DD6076 /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
9237522B240C550B00DD6076 /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
9237522E240C550B00DD6076 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = bridging.h;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
9237522F240C550B00DD6076 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = bridging.h;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
92375231240C550B00DD6076 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
ENABLE_BITCODE = YES;
|
||||
INFOPLIST_FILE = VoskApiTest/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/VoskApiTest/Vosk",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.alphacephei.VoskApiTest;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_INSTALL_OBJC_HEADER = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = VoskApiTest/bridging.h;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
SWIFT_VERSION = 4.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
92375232240C550B00DD6076 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
ENABLE_BITCODE = YES;
|
||||
INFOPLIST_FILE = VoskApiTest/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/VoskApiTest/Vosk",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.alphacephei.VoskApiTest;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_INSTALL_OBJC_HEADER = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = VoskApiTest/bridging.h;
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
SWIFT_VERSION = 4.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
92375219240C550A00DD6076 /* Build configuration list for PBXProject "VoskApiTest" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
9237522E240C550B00DD6076 /* Debug */,
|
||||
9237522F240C550B00DD6076 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
92375230240C550B00DD6076 /* Build configuration list for PBXNativeTarget "VoskApiTest" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
92375231240C550B00DD6076 /* Debug */,
|
||||
92375232240C550B00DD6076 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 92375216240C550A00DD6076 /* Project object */;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:../VoskApiTest/Vosk/vosk-model-small-en-us-0.4">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:../VoskApiTest/Vosk/vosk-model-spk-0.4">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -1,46 +0,0 @@
|
||||
//
|
||||
// AppDelegate.swift
|
||||
// VoskApiTest
|
||||
//
|
||||
// Created by Niсkolay Shmyrev on 01.03.20.
|
||||
// Copyright © 2020 Alpha Cephei. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
|
||||
// Override point for customization after application launch.
|
||||
return true
|
||||
}
|
||||
|
||||
func applicationWillResignActive(_ application: UIApplication) {
|
||||
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
||||
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
|
||||
}
|
||||
|
||||
func applicationDidEnterBackground(_ application: UIApplication) {
|
||||
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
|
||||
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
|
||||
}
|
||||
|
||||
func applicationWillEnterForeground(_ application: UIApplication) {
|
||||
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
|
||||
}
|
||||
|
||||
func applicationDidBecomeActive(_ application: UIApplication) {
|
||||
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ application: UIApplication) {
|
||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "20x20",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "20x20",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "40x40",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "76x76",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "76x76",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "83.5x83.5",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"size" : "1024x1024",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11134" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11106"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -1,32 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="VoskApiTest" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<textView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" editable="NO" textAlignment="natural" id="CtX-mx-X98">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
<connections>
|
||||
<outlet property="mainText" destination="CtX-mx-X98" id="oJy-5J-NKp"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="32.799999999999997" y="32.833583208395808"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -1,45 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,34 +0,0 @@
|
||||
//
|
||||
// ViewController.swift
|
||||
// VoskApiTest
|
||||
//
|
||||
// Created by Niсkolay Shmyrev on 01.03.20.
|
||||
// Copyright © 2020 Alpha Cephei. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class ViewController: UIViewController {
|
||||
|
||||
@IBOutlet var mainText: UITextView!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
DispatchQueue.main.async {
|
||||
self.mainText.text = "Processing file..."
|
||||
}
|
||||
let vosk = Vosk()
|
||||
let res = vosk.recognizeFile()
|
||||
DispatchQueue.main.async {
|
||||
self.mainText.text = res
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func didReceiveMemoryWarning() {
|
||||
super.didReceiveMemoryWarning()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
//
|
||||
// Vosk.swift
|
||||
// VoskApiTest
|
||||
//
|
||||
// Created by Niсkolay Shmyrev on 01.03.20.
|
||||
// Copyright © 2020 Alpha Cephei. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public final class Vosk {
|
||||
|
||||
func recognizeFile() -> String {
|
||||
var sres = ""
|
||||
if let resourcePath = Bundle.main.resourcePath {
|
||||
|
||||
// Set to -1 to disable logs
|
||||
vosk_set_log_level(0);
|
||||
|
||||
let modelPath = resourcePath + "/vosk-model-small-en-us-0.4"
|
||||
let spkModelPath = resourcePath + "/vosk-model-spk-0.4"
|
||||
|
||||
let model = vosk_model_new(modelPath)
|
||||
let spkModel = vosk_spk_model_new(spkModelPath)
|
||||
let recognizer = vosk_recognizer_new_spk(model, spkModel, 16000.0)
|
||||
|
||||
let audioFile = URL(fileURLWithPath: resourcePath + "/10001-90210-01803.wav")
|
||||
if let data = try? Data(contentsOf: audioFile) {
|
||||
let _ = data.withUnsafeBytes {
|
||||
vosk_recognizer_accept_waveform(recognizer, $0, Int32(data.count))
|
||||
}
|
||||
let res = vosk_recognizer_final_result(recognizer);
|
||||
sres = String(validatingUTF8: res!)!;
|
||||
print(sres);
|
||||
}
|
||||
|
||||
vosk_recognizer_free(recognizer)
|
||||
vosk_model_free(model)
|
||||
}
|
||||
return sres
|
||||
}
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
// Copyright 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.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/* This header contains the C API for Vosk speech recognition system */
|
||||
|
||||
#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 */
|
||||
@@ -1 +0,0 @@
|
||||
#import "Vosk/vosk_api.h"
|
||||
@@ -1,72 +0,0 @@
|
||||
KALDI_ROOT ?= $(HOME)/kaldi
|
||||
CFLAGS := -g -O2 -DPIC -fPIC -Wno-unused-function -std=c++17
|
||||
CPPFLAGS := -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/linux -I$(KALDI_ROOT)/src -I$(KALDI_ROOT)/tools/openfst/include -I../src
|
||||
|
||||
KALDI_LIBS = \
|
||||
${KALDI_ROOT}/src/online2/kaldi-online2.a \
|
||||
${KALDI_ROOT}/src/decoder/kaldi-decoder.a \
|
||||
${KALDI_ROOT}/src/ivector/kaldi-ivector.a \
|
||||
${KALDI_ROOT}/src/gmm/kaldi-gmm.a \
|
||||
${KALDI_ROOT}/src/nnet3/kaldi-nnet3.a \
|
||||
${KALDI_ROOT}/src/tree/kaldi-tree.a \
|
||||
${KALDI_ROOT}/src/feat/kaldi-feat.a \
|
||||
${KALDI_ROOT}/src/lat/kaldi-lat.a \
|
||||
${KALDI_ROOT}/src/lm/kaldi-lm.a \
|
||||
${KALDI_ROOT}/src/hmm/kaldi-hmm.a \
|
||||
${KALDI_ROOT}/src/transform/kaldi-transform.a \
|
||||
${KALDI_ROOT}/src/cudamatrix/kaldi-cudamatrix.a \
|
||||
${KALDI_ROOT}/src/matrix/kaldi-matrix.a \
|
||||
${KALDI_ROOT}/src/fstext/kaldi-fstext.a \
|
||||
${KALDI_ROOT}/src/util/kaldi-util.a \
|
||||
${KALDI_ROOT}/src/base/kaldi-base.a \
|
||||
${KALDI_ROOT}/tools/openfst/lib/libfst.a \
|
||||
${KALDI_ROOT}/tools/openfst/lib/libfstngram.a \
|
||||
|
||||
# On Linux
|
||||
MATH_LIBS = \
|
||||
${KALDI_ROOT}/tools/OpenBLAS/install/lib/libopenblas.a \
|
||||
${KALDI_ROOT}/tools/OpenBLAS/install/lib/liblapack.a \
|
||||
${KALDI_ROOT}/tools/OpenBLAS/install/lib/libblas.a \
|
||||
${KALDI_ROOT}/tools/OpenBLAS/install/lib/libf2c.a
|
||||
|
||||
all: libvosk_jni.so
|
||||
|
||||
VOSK_SOURCES = \
|
||||
vosk_wrap.cc \
|
||||
../src/kaldi_recognizer.cc \
|
||||
../src/kaldi_recognizer.h \
|
||||
../src/language_model.cc \
|
||||
../src/language_model.h \
|
||||
../src/model.cc \
|
||||
../src/model.h \
|
||||
../src/spk_model.cc \
|
||||
../src/spk_model.h \
|
||||
../src/vosk_api.cc \
|
||||
../src/vosk_api.h
|
||||
|
||||
libvosk_jni.so: $(VOSK_SOURCES)
|
||||
$(CXX) -shared -o $@ $(CPPFLAGS) $(CFLAGS) $(VOSK_SOURCES) $(KALDI_LIBS) $(MATH_LIBS)
|
||||
|
||||
vosk_wrap.cc: ../src/vosk.i
|
||||
mkdir -p org/kaldi
|
||||
swig -c++ -I../src \
|
||||
-java -package org.kaldi \
|
||||
-outdir org/kaldi -o $@ $<
|
||||
|
||||
clean:
|
||||
$(RM) *.so *_wrap.cc *_wrap.o test/*.class
|
||||
$(RM) -r org model-en
|
||||
|
||||
model:
|
||||
wget https://alphacephei.com/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://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 model-spk
|
||||
javac test/*.java org/kaldi/*.java
|
||||
java -Djava.library.path=. -cp . test.DecoderTest
|
||||
@@ -1,19 +0,0 @@
|
||||
Java API sample
|
||||
|
||||
Doesn't work on Windows or Mac yet, help to prepare the packaged jars is welcome.
|
||||
|
||||
For now to try it:
|
||||
|
||||
On Linux you can do
|
||||
|
||||
1. Build recent kaldi
|
||||
1. `git clone https://github.com/alphacep/vosk-api`
|
||||
1. `cd vosk-api/java`
|
||||
1. `export KALDI_ROOT=<KALDI_ROOT>`
|
||||
1. `export JAVA_HOME=<JAVA_HOME>`
|
||||
1. `make`
|
||||
1. `make run`
|
||||
|
||||
For details of the code you can check:
|
||||
|
||||
https://github.com/alphacep/vosk-api/blob/master/java/test/DecoderTest.java
|
||||
@@ -1,37 +0,0 @@
|
||||
package test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.*;
|
||||
|
||||
import org.kaldi.KaldiRecognizer;
|
||||
import org.kaldi.Model;
|
||||
import org.kaldi.SpkModel;
|
||||
import org.kaldi.Vosk;
|
||||
|
||||
public class DecoderTest {
|
||||
|
||||
public static void main(String args[]) throws IOException {
|
||||
Vosk.SetLogLevel(-10);
|
||||
|
||||
FileInputStream ais = new FileInputStream(new File("../python/example/test.wav"));
|
||||
Model model = new Model("model");
|
||||
SpkModel spkModel = new SpkModel("model-spk");
|
||||
KaldiRecognizer rec = new KaldiRecognizer(model, spkModel, 16000.0f);
|
||||
|
||||
int nbytes;
|
||||
byte[] b = new byte[4096];
|
||||
while ((nbytes = ais.read(b)) >= 0) {
|
||||
if (rec.AcceptWaveform(b)) {
|
||||
System.out.println(rec.Result());
|
||||
} else {
|
||||
System.out.println(rec.PartialResult());
|
||||
}
|
||||
}
|
||||
System.out.println(rec.FinalResult());
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
build
|
||||
node_modules
|
||||
vosk_wrap.cc
|
||||
@@ -1,20 +0,0 @@
|
||||
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
|
||||
```
|
||||
@@ -1,84 +0,0 @@
|
||||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'vosk',
|
||||
'sources': [
|
||||
'../src/kaldi_recognizer.cc',
|
||||
'../src/model.cc',
|
||||
'../src/language_model.cc',
|
||||
'../src/spk_model.cc',
|
||||
'../src/vosk_api.cc',
|
||||
'vosk_wrap.cc',
|
||||
],
|
||||
'cflags': [
|
||||
'-std=c++17',
|
||||
'-DFST_NO_DYNAMIC_LINKING',
|
||||
'-Wno-deprecated-declarations',
|
||||
'-Wno-sign-compare',
|
||||
'-Wno-unused-local-typedefs',
|
||||
'-Wno-ignored-quaifiers',
|
||||
'-Wno-extra',
|
||||
],
|
||||
'cflags_cc!' : [
|
||||
'-fno-rtti',
|
||||
'-fno-exceptions',
|
||||
'-std=gnu++1y',
|
||||
],
|
||||
'conditions': [
|
||||
['OS == "mac"', {
|
||||
'xcode_settings': {
|
||||
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
|
||||
'GCC_ENABLE_CPP_RTTI': 'YES',
|
||||
'CLANG_CXX_LANGUAGE_STANDARD': 'c++17'
|
||||
}
|
||||
}]
|
||||
],
|
||||
'actions': [
|
||||
{
|
||||
'action_name': 'swig',
|
||||
'inputs': [
|
||||
'../src/vosk.i',
|
||||
],
|
||||
'outputs': [
|
||||
'vosk_wrap.cc',
|
||||
],
|
||||
'action': ['swig', '-c++', '-javascript', '-o', 'vosk_wrap.cc', '-v8', '-DV8_MAJOR_VERSION=10', '../src/vosk.i']
|
||||
},
|
||||
],
|
||||
'include_dirs': [
|
||||
'<@(kaldi_root)/src',
|
||||
'<@(kaldi_root)/tools/openfst/include',
|
||||
'../src',
|
||||
],
|
||||
'link_settings': {
|
||||
'libraries': [
|
||||
'<@(kaldi_root)/src/online2/kaldi-online2.a',
|
||||
'<@(kaldi_root)/src/decoder/kaldi-decoder.a',
|
||||
'<@(kaldi_root)/src/ivector/kaldi-ivector.a',
|
||||
'<@(kaldi_root)/src/gmm/kaldi-gmm.a',
|
||||
'<@(kaldi_root)/src/nnet3/kaldi-nnet3.a',
|
||||
'<@(kaldi_root)/src/tree/kaldi-tree.a',
|
||||
'<@(kaldi_root)/src/feat/kaldi-feat.a',
|
||||
'<@(kaldi_root)/src/lat/kaldi-lat.a',
|
||||
'<@(kaldi_root)/src/lm/kaldi-lm.a',
|
||||
'<@(kaldi_root)/src/hmm/kaldi-hmm.a',
|
||||
'<@(kaldi_root)/src/transform/kaldi-transform.a',
|
||||
'<@(kaldi_root)/src/cudamatrix/kaldi-cudamatrix.a',
|
||||
'<@(kaldi_root)/src/matrix/kaldi-matrix.a',
|
||||
'<@(kaldi_root)/src/fstext/kaldi-fstext.a',
|
||||
'<@(kaldi_root)/src/util/kaldi-util.a',
|
||||
'<@(kaldi_root)/src/base/kaldi-base.a',
|
||||
'<@(kaldi_root)/tools/openfst/lib/libfst.a',
|
||||
'<@(kaldi_root)/tools/openfst/lib/libfstngram.a',
|
||||
'<@(kaldi_root)/tools/OpenBLAS/install/lib/libopenblas.a',
|
||||
'<@(kaldi_root)/tools/OpenBLAS/install/lib/liblapack.a',
|
||||
'<@(kaldi_root)/tools/OpenBLAS/install/lib/libblas.a',
|
||||
'<@(kaldi_root)/tools/OpenBLAS/install/lib/libf2c.a',
|
||||
],
|
||||
'library_dirs': [
|
||||
'/usr/lib',
|
||||
],
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
const wav = require('wav')
|
||||
const fs = require('fs')
|
||||
|
||||
const {Readable} = require('stream')
|
||||
const {Model, KaldiRecognizer, SpkModel} = require('..')
|
||||
|
||||
try {
|
||||
fs.accessSync('model', fs.constants.R_OK)
|
||||
} catch (err) {
|
||||
console.error("Please download the model from https://github.com/alphacep/kaldi-android-demo/releases and unpack as 'model' in the current folder.")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
try {
|
||||
fs.accessSync('model-spk', fs.constants.R_OK)
|
||||
} catch (err) {
|
||||
console.error("Please download the speaker model from https://github.com/alphacep/kaldi-android-demo/releases and unpack as 'model-spk' in the current folder.")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const wfStream = fs.createReadStream('test.wav', { highWaterMark: 4096 })
|
||||
const wfReader = new wav.Reader()
|
||||
|
||||
const model = new Model('model')
|
||||
const spkModel = new SpkModel('model-spk')
|
||||
|
||||
const spk_sig = [-0.56648, 0.030579, 1.730239, 0.239899, -1.194183,
|
||||
0.251954, 0.540388, -0.971872, -0.020963, 0.085036, 0.563973, -0.019682,
|
||||
-0.597381, 1.094719, -0.566738, 0.29819, 0.171165, 0.370341, -0.033539,
|
||||
-0.09757, 1.228286, 0.485949, 0.427826, 0.147762, -0.015112, 0.599513,
|
||||
-2.040655, -0.490882, 0.440161, -0.072991, 0.835955, -0.496124, 0.952978,
|
||||
0.85356, -1.096116, 0.107764, -0.385486, 1.410305, 0.609147, -0.457014,
|
||||
-1.542864, 0.343669, 0.171913, -0.627281, -1.281781, -1.134276,
|
||||
-0.639895, 1.190183, -0.700537, 1.063457, 0.206946, 0.342198, -1.165625,
|
||||
1.475955, -0.089007, -2.555155, 0.551438, -0.212736, 1.025625, -1.631965,
|
||||
-0.716256, -1.295995, 1.554956, -1.866009, -1.010782, -1.43231, 0.109027,
|
||||
2.123925, 1.703283, -0.784997, 2.730568, 0.755113, 0.0617, 0.128955,
|
||||
-0.054047, 1.359119, -0.611666, -1.105754, -0.631353, 0.052109, 0.729386,
|
||||
-0.769876, 1.250235, -1.463298, 0.648176, -0.73239, -0.385239,
|
||||
-1.661856, 0.602106, -0.45567, -1.438431, -0.836673, 0.033557, 0.373597,
|
||||
-1.343341, -0.181095, 0.237287, -0.522005, -1.722836, 0.932333,
|
||||
-0.092861, -0.219254, 0.476182, 1.033803, -1.633563, -0.874341, 1.039064,
|
||||
-1.758573, -0.838422, -0.324336, -0.924634, 1.962594, 2.152814, 1.2521,
|
||||
-0.46172, -1.50271, 1.685691, 0.403097, -0.819042, 0.866403, -0.591716,
|
||||
-0.578645, -0.553839, 0.381861, -1.051647, -1.477578, 0.524005, 0.925245]
|
||||
|
||||
function dotp(x, y) {
|
||||
function dotp_sum(a, b) {
|
||||
return a + b
|
||||
}
|
||||
function dotp_times(a, i) {
|
||||
return x[i] * y[i]
|
||||
}
|
||||
return x.map(dotp_times).reduce(dotp_sum, 0)
|
||||
}
|
||||
|
||||
function cosineSimilarity(A, B) {
|
||||
var similarity =
|
||||
dotp(A, B) / (Math.sqrt(dotp(A, A)) * Math.sqrt(dotp(B, B)))
|
||||
return similarity
|
||||
}
|
||||
|
||||
function cosine_dist(x, y) {
|
||||
return 1 - cosineSimilarity(x, y)
|
||||
}
|
||||
|
||||
wfReader.on('format', async ({ audioFormat, sampleRate, channels }) => {
|
||||
if (audioFormat != 1 || channels != 1) {
|
||||
console.error('Audio file must be WAV format mono PCM.')
|
||||
process.exit(1)
|
||||
}
|
||||
const rec = new KaldiRecognizer(model, spkModel, sampleRate)
|
||||
for await (const data of new Readable().wrap(wfReader)) {
|
||||
const endOfSpeech = await rec.AcceptWaveform(data)
|
||||
if (endOfSpeech) {
|
||||
res = await JSON.parse(rec.Result());
|
||||
console.log(res)
|
||||
console.log('X-vector:', JSON.stringify(res['spk']))
|
||||
console.log('Speaker distance:', cosine_dist(spk_sig, res['spk']))
|
||||
} else {
|
||||
console.log(await rec.PartialResult())
|
||||
}
|
||||
}
|
||||
res = await JSON.parse(rec.FinalResult());
|
||||
console.log(res)
|
||||
console.log('X-vector:', JSON.stringify(res['spk']))
|
||||
console.log('Speaker distance:', cosine_dist(spk_sig, res['spk']))
|
||||
})
|
||||
|
||||
wfStream.pipe(wfReader)
|
||||
@@ -1,38 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require("fs");
|
||||
const { Readable } = require("stream");
|
||||
const wav = require("wav");
|
||||
|
||||
const { Model, KaldiRecognizer } = require("..");
|
||||
|
||||
try {
|
||||
fs.accessSync("model", fs.constants.R_OK);
|
||||
} catch(err) {
|
||||
console.error("Please download the model from https://github.com/alphacep/kaldi-android-demo/releases and unpack as 'model' in the current folder.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const wfStream = fs.createReadStream("test.wav", {'highWaterMark': 4096});
|
||||
const wfReader = new wav.Reader();
|
||||
|
||||
const model = new Model("model");
|
||||
|
||||
wfReader.on('format', async ({ audioFormat, sampleRate, channels }) => {
|
||||
if (audioFormat != 1 || channels != 1) {
|
||||
console.error("Audio file must be WAV format mono PCM.");
|
||||
process.exit(1);
|
||||
}
|
||||
const rec = new KaldiRecognizer(model, sampleRate);
|
||||
for await (const data of new Readable().wrap(wfReader)) {
|
||||
const result = await rec.AcceptWaveform(data);
|
||||
if (result != 0) {
|
||||
console.log(await rec.Result());
|
||||
} else {
|
||||
console.log(await rec.PartialResult());
|
||||
}
|
||||
}
|
||||
console.log(await rec.FinalResult());
|
||||
});
|
||||
|
||||
wfStream.pipe(wfReader);
|
||||
@@ -1,3 +0,0 @@
|
||||
const voskNativeModule = require('./build/Release/vosk.node');
|
||||
|
||||
module.exports = voskNativeModule;
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"name": "vosk",
|
||||
"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"
|
||||
},
|
||||
"main": "index.js",
|
||||
"keywords": [
|
||||
"speech",
|
||||
"speech recognition",
|
||||
"voice"
|
||||
],
|
||||
"author": "Alpha Cephei Inc.",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">= 10.x.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-gyp": "^5.1.1",
|
||||
"wav": "^1.0.2"
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from vosk import Model, KaldiRecognizer
|
||||
import sys
|
||||
import json
|
||||
|
||||
model = Model("model")
|
||||
rec = KaldiRecognizer(model, 8000)
|
||||
|
||||
res = json.loads(rec.FinalResult())
|
||||
print (res)
|
||||
@@ -1,33 +0,0 @@
|
||||
#!/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,28 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from vosk import Model, KaldiRecognizer
|
||||
import os
|
||||
|
||||
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()
|
||||
|
||||
while True:
|
||||
data = stream.read(4000)
|
||||
if len(data) == 0:
|
||||
break
|
||||
if rec.AcceptWaveform(data):
|
||||
print(rec.Result())
|
||||
else:
|
||||
print(rec.PartialResult())
|
||||
|
||||
print(rec.FinalResult())
|
||||
@@ -1,31 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from vosk import Model, KaldiRecognizer, SetLogLevel
|
||||
import sys
|
||||
import os
|
||||
import wave
|
||||
|
||||
SetLogLevel(0)
|
||||
|
||||
if not os.path.exists("model"):
|
||||
print ("Please download the model from https://alphacephei.com/vosk/models and unpack as 'model' in the current folder.")
|
||||
exit (1)
|
||||
|
||||
wf = wave.open(sys.argv[1], "rb")
|
||||
if wf.getnchannels() != 1 or wf.getsampwidth() != 2 or wf.getcomptype() != "NONE":
|
||||
print ("Audio file must be WAV format mono PCM.")
|
||||
exit (1)
|
||||
|
||||
model = Model("model")
|
||||
rec = KaldiRecognizer(model, wf.getframerate())
|
||||
|
||||
while True:
|
||||
data = wf.readframes(4000)
|
||||
if len(data) == 0:
|
||||
break
|
||||
if rec.AcceptWaveform(data):
|
||||
print(rec.Result())
|
||||
else:
|
||||
print(rec.PartialResult())
|
||||
|
||||
print(rec.FinalResult())
|
||||
@@ -1,58 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from vosk import Model, KaldiRecognizer, SpkModel
|
||||
import sys
|
||||
import wave
|
||||
import json
|
||||
import os
|
||||
import numpy as np
|
||||
|
||||
model_path = "model"
|
||||
spk_model_path = "model-spk"
|
||||
|
||||
if not os.path.exists(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://alphacephei.com/vosk/models and unpack as {} in the current folder.".format(spk_model_path))
|
||||
exit (1)
|
||||
|
||||
wf = wave.open(sys.argv[1], "rb")
|
||||
if wf.getnchannels() != 1 or wf.getsampwidth() != 2 or wf.getcomptype() != "NONE":
|
||||
print ("Audio file must be WAV format mono PCM.")
|
||||
exit (1)
|
||||
|
||||
# Large vocabulary free form recognition
|
||||
model = Model(model_path)
|
||||
spk_model = SpkModel(spk_model_path)
|
||||
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 = [-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)
|
||||
ny = np.array(y)
|
||||
return 1 - np.dot(nx, ny) / np.linalg.norm(nx) / np.linalg.norm(ny)
|
||||
|
||||
while True:
|
||||
data = wf.readframes(4000)
|
||||
if len(data) == 0:
|
||||
break
|
||||
if rec.AcceptWaveform(data):
|
||||
res = json.loads(rec.Result())
|
||||
print ("Text:", res['text'])
|
||||
if 'spk' in res:
|
||||
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'])
|
||||
if 'spk' in res:
|
||||
print ("X-vector:", res['spk'])
|
||||
print ("Speaker distance:", cosine_dist(spk_sig, res['spk']), "based on", res['spk_frames'], "frames")
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
#!/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)
|
||||
|
||||
|
||||
WORDS_PER_LINE = 7
|
||||
|
||||
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)
|
||||
if not 'result' in jres:
|
||||
continue
|
||||
words = jres['result']
|
||||
for j in range(0, len(words), WORDS_PER_LINE):
|
||||
line = words[j : j + WORDS_PER_LINE]
|
||||
s = srt.Subtitle(index=len(subs),
|
||||
content=" ".join([l['word'] for l in line]),
|
||||
start=datetime.timedelta(seconds=line[0]['start']),
|
||||
end=datetime.timedelta(seconds=line[-1]['end']))
|
||||
subs.append(s)
|
||||
return subs
|
||||
|
||||
print (srt.compose(transcribe()))
|
||||
@@ -1,33 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from vosk import Model, KaldiRecognizer
|
||||
import sys
|
||||
import json
|
||||
import os
|
||||
|
||||
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")
|
||||
|
||||
# Large vocabulary free form recognition
|
||||
rec = KaldiRecognizer(model, 16000)
|
||||
|
||||
# You can also specify the possible word list
|
||||
#rec = KaldiRecognizer(model, 16000, "zero oh one two three four five six seven eight nine")
|
||||
|
||||
wf = open(sys.argv[1], "rb")
|
||||
wf.read(44) # skip header
|
||||
|
||||
while True:
|
||||
data = wf.read(4000)
|
||||
if len(data) == 0:
|
||||
break
|
||||
if rec.AcceptWaveform(data):
|
||||
res = json.loads(rec.Result())
|
||||
print (res['text'])
|
||||
|
||||
res = json.loads(rec.FinalResult())
|
||||
print (res['text'])
|
||||
@@ -1,31 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from vosk import Model, KaldiRecognizer
|
||||
import sys
|
||||
import os
|
||||
import wave
|
||||
|
||||
if not os.path.exists("model"):
|
||||
print ("Please download the model from https://alphacephei.com/vosk/models and unpack as 'model' in the current folder.")
|
||||
exit (1)
|
||||
|
||||
wf = wave.open(sys.argv[1], "rb")
|
||||
if wf.getnchannels() != 1 or wf.getsampwidth() != 2 or wf.getcomptype() != "NONE":
|
||||
print ("Audio file must be WAV format mono PCM.")
|
||||
exit (1)
|
||||
|
||||
model = Model("model")
|
||||
|
||||
# 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(4000)
|
||||
if len(data) == 0:
|
||||
break
|
||||
if rec.AcceptWaveform(data):
|
||||
print(rec.Result())
|
||||
else:
|
||||
print(rec.PartialResult())
|
||||
|
||||
print(rec.FinalResult())
|
||||
@@ -1,97 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
import setuptools
|
||||
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', '-latomic', '-lpthread', '-lm']
|
||||
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.extend(['tools/OpenBLAS/install/lib/libopenblas.a',
|
||||
'tools/OpenBLAS/install/lib/liblapack.a',
|
||||
'tools/OpenBLAS/install/lib/libblas.a',
|
||||
'tools/OpenBLAS/install/lib/libf2c.a'])
|
||||
|
||||
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 = ['-O3', '-std=c++17', '-Wno-sign-compare', '-Wno-unused-variable', '-Wno-unused-local-typedefs'])
|
||||
|
||||
setuptools.setup(
|
||||
name="vosk",
|
||||
version="0.3.16",
|
||||
author="Alpha Cephei Inc",
|
||||
author_email="contact@alphacephei.com",
|
||||
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=[vosk_ext],
|
||||
cmdclass = {'build_py' : build_py},
|
||||
classifiers=[
|
||||
'Programming Language :: Python :: 3',
|
||||
'License :: OSI Approved :: Apache Software License',
|
||||
'Operating System :: Microsoft :: Windows',
|
||||
'Operating System :: POSIX :: Linux',
|
||||
'Operating System :: MacOS :: MacOS X',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules'
|
||||
],
|
||||
python_requires='>=3.5',
|
||||
)
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/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,2 +0,0 @@
|
||||
from .vosk import KaldiRecognizer, Model, SpkModel, SetLogLevel
|
||||
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
# ONNX
|
||||
ONNX_ROOT=$(abspath ../../onnxruntime-linux-x64-gpu-1.22.0)
|
||||
# Compiler
|
||||
CXX?=g++
|
||||
EXT?=so
|
||||
# Extra
|
||||
EXTRA_CFLAGS?=
|
||||
EXTRA_LDFLAGS?=
|
||||
OUTDIR?=.
|
||||
|
||||
VOSK_SOURCES= \
|
||||
circular-buffer.cc \
|
||||
context-graph.cc \
|
||||
feature-fbank.cc \
|
||||
feature-functions.cc \
|
||||
feature-extractor.cc \
|
||||
feature-window.cc \
|
||||
fftsg.cc \
|
||||
file-utils.cc \
|
||||
hypothesis.cc \
|
||||
log.cc \
|
||||
mel-computations.cc \
|
||||
offline-lm.cc \
|
||||
offline-lm-config.cc \
|
||||
offline-model-config.cc \
|
||||
offline-recognizer.cc \
|
||||
offline-recognizer-impl.cc \
|
||||
offline-rnn-lm.cc \
|
||||
offline-stream.cc \
|
||||
offline-transducer-greedy-search-decoder.cc \
|
||||
offline-transducer-model.cc \
|
||||
offline-transducer-model-config.cc \
|
||||
offline-transducer-modified-beam-search-decoder.cc \
|
||||
online-feature.cc \
|
||||
onnx-utils.cc \
|
||||
packed-sequence.cc \
|
||||
pad-sequence.cc \
|
||||
parse-options.cc \
|
||||
provider.cc \
|
||||
resample.cc \
|
||||
rfft.cc \
|
||||
session.cc \
|
||||
silero-vad-model.cc \
|
||||
silero-vad-model-config.cc \
|
||||
slice.cc \
|
||||
symbol-table.cc \
|
||||
text-utils.cc \
|
||||
transpose.cc \
|
||||
utils.cc \
|
||||
vad-model.cc \
|
||||
vad-model-config.cc \
|
||||
voice-activity-detector.cc \
|
||||
vosk_api.cc
|
||||
|
||||
VOSK_HEADERS= \
|
||||
circular-buffer.h \
|
||||
context-graph.h \
|
||||
feature-fbank.h \
|
||||
feature-functions.h \
|
||||
feature-extractor.h \
|
||||
feature-window.h \
|
||||
file-utils.h \
|
||||
hypothesis.h \
|
||||
log.h \
|
||||
macros.h \
|
||||
math.h \
|
||||
mel-computations.h \
|
||||
offline-lm-config.h \
|
||||
offline-lm.h \
|
||||
offline-model-config.h \
|
||||
offline-recognizer.h \
|
||||
offline-recognizer-impl.h \
|
||||
offline-recognizer-transducer-impl.h \
|
||||
offline-rnn-lm.h \
|
||||
offline-stream.h \
|
||||
offline-transducer-decoder.h \
|
||||
offline-transducer-greedy-search-decoder.h \
|
||||
offline-transducer-model-config.h \
|
||||
offline-transducer-model.h \
|
||||
offline-transducer-modified-beam-search-decoder.h \
|
||||
online-feature.h \
|
||||
onnx-utils.h \
|
||||
packed-sequence.h \
|
||||
pad-sequence.h \
|
||||
parse-options.h \
|
||||
provider.h \
|
||||
resample.h \
|
||||
rfft.h \
|
||||
session.h \
|
||||
silero-vad-model-config.h \
|
||||
silero-vad-model.h \
|
||||
slice.h \
|
||||
symbol-table.h \
|
||||
text-utils.h \
|
||||
transpose.h \
|
||||
utils.h \
|
||||
vad-model-config.h \
|
||||
vad-model.h \
|
||||
voice-activity-detector.h \
|
||||
vosk_api.h
|
||||
|
||||
CFLAGS=-g -O3 -mavx2 -fPIC -std=c++17 \
|
||||
-I$(ONNX_ROOT)/include $(EXTRA_CFLAGS)
|
||||
|
||||
LDFLAGS=-Wl,-rpath=$(ONNX_ROOT)/lib -L$(ONNX_ROOT)/lib -lonnxruntime
|
||||
|
||||
all: $(OUTDIR)/libvosk.$(EXT)
|
||||
|
||||
$(OUTDIR)/libvosk.$(EXT): $(VOSK_SOURCES:%.cc=$(OUTDIR)/%.o)
|
||||
$(CXX) --shared -o $@ $^ $(LDFLAGS) $(EXTRA_LDFLAGS)
|
||||
|
||||
$(OUTDIR)/%.o: %.cc $(VOSK_HEADERS)
|
||||
$(CXX) -c -o $@ $(CFLAGS) $<
|
||||
|
||||
clean:
|
||||
rm -f *.o *.so *.dll
|
||||
@@ -0,0 +1,166 @@
|
||||
// circular-buffer.cc
|
||||
//
|
||||
// Copyright (c) 2023 Xiaomi Corporation
|
||||
|
||||
#include "circular-buffer.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "macros.h"
|
||||
|
||||
namespace sherpa_onnx {
|
||||
|
||||
CircularBuffer::CircularBuffer(int32_t capacity) {
|
||||
if (capacity <= 0) {
|
||||
SHERPA_ONNX_LOGE("Please specify a positive capacity. Given: %d\n",
|
||||
capacity);
|
||||
exit(-1);
|
||||
}
|
||||
buffer_.resize(capacity);
|
||||
}
|
||||
|
||||
void CircularBuffer::Resize(int32_t new_capacity) {
|
||||
int32_t capacity = buffer_.size();
|
||||
if (new_capacity <= capacity) {
|
||||
SHERPA_ONNX_LOGE("new_capacity (%d) <= original capacity (%d). Skip it.",
|
||||
new_capacity, capacity);
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t size = Size();
|
||||
if (size == 0) {
|
||||
buffer_.resize(new_capacity);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<float> new_buffer(new_capacity);
|
||||
int32_t start = head_ % capacity;
|
||||
int32_t dest = head_ % new_capacity;
|
||||
|
||||
if (start + size <= capacity) {
|
||||
if (dest + size <= new_capacity) {
|
||||
std::copy(buffer_.begin() + start, buffer_.begin() + start + size,
|
||||
new_buffer.begin() + dest);
|
||||
} else {
|
||||
int32_t part1_size = new_capacity - dest;
|
||||
|
||||
// copy [start, start+part1_size] to new_buffer
|
||||
std::copy(buffer_.begin() + start, buffer_.begin() + start + part1_size,
|
||||
new_buffer.begin() + dest);
|
||||
|
||||
// copy [start+part1_size, start+size] to new_buffer
|
||||
std::copy(buffer_.begin() + start + part1_size,
|
||||
buffer_.begin() + start + size, new_buffer.begin());
|
||||
}
|
||||
} else {
|
||||
int32_t part1_size = capacity - start;
|
||||
int32_t part2_size = size - part1_size;
|
||||
|
||||
// copy [start, start+part1_size] to new_buffer
|
||||
if (dest + part1_size <= new_capacity) {
|
||||
std::copy(buffer_.begin() + start, buffer_.begin() + start + part1_size,
|
||||
new_buffer.begin() + dest);
|
||||
} else {
|
||||
int32_t first_part = new_capacity - dest;
|
||||
int32_t second_part = part1_size - first_part;
|
||||
std::copy(buffer_.begin() + start, buffer_.begin() + start + first_part,
|
||||
new_buffer.begin() + dest);
|
||||
|
||||
std::copy(buffer_.begin() + start + first_part,
|
||||
buffer_.begin() + start + part1_size, new_buffer.begin());
|
||||
}
|
||||
|
||||
int32_t new_dest = (dest + part1_size) % new_capacity;
|
||||
|
||||
if (new_dest + part2_size <= new_capacity) {
|
||||
std::copy(buffer_.begin(), buffer_.begin() + part2_size,
|
||||
new_buffer.begin() + new_dest);
|
||||
} else {
|
||||
int32_t first_part = new_capacity - new_dest;
|
||||
std::copy(buffer_.begin(), buffer_.begin() + first_part,
|
||||
new_buffer.begin() + new_dest);
|
||||
std::copy(buffer_.begin() + first_part, buffer_.begin() + part2_size,
|
||||
new_buffer.begin());
|
||||
}
|
||||
}
|
||||
buffer_.swap(new_buffer);
|
||||
}
|
||||
|
||||
void CircularBuffer::Push(const float *p, int32_t n) {
|
||||
int32_t capacity = buffer_.size();
|
||||
int32_t size = Size();
|
||||
if (n + size > capacity) {
|
||||
int32_t new_capacity = std::max(capacity * 2, n + size);
|
||||
SHERPA_ONNX_LOGE(
|
||||
"Overflow! n: %d, size: %d, n+size: %d, capacity: %d. Increase "
|
||||
"capacity to: %d",
|
||||
n, size, n + size, capacity, new_capacity);
|
||||
Resize(new_capacity);
|
||||
}
|
||||
|
||||
int32_t start = tail_ % capacity;
|
||||
|
||||
tail_ += n;
|
||||
|
||||
if (start + n < capacity) {
|
||||
std::copy(p, p + n, buffer_.begin() + start);
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t part1_size = capacity - start;
|
||||
|
||||
std::copy(p, p + part1_size, buffer_.begin() + start);
|
||||
|
||||
std::copy(p + part1_size, p + n, buffer_.begin());
|
||||
}
|
||||
|
||||
std::vector<float> CircularBuffer::Get(int32_t start_index, int32_t n) const {
|
||||
if (start_index < head_ || start_index >= tail_) {
|
||||
SHERPA_ONNX_LOGE("Invalid start_index: %d. head_: %d, tail_: %d",
|
||||
start_index, head_, tail_);
|
||||
return {};
|
||||
}
|
||||
|
||||
int32_t size = Size();
|
||||
if (n < 0 || n > size) {
|
||||
SHERPA_ONNX_LOGE("Invalid n: %d. size: %d", n, size);
|
||||
return {};
|
||||
}
|
||||
|
||||
int32_t capacity = buffer_.size();
|
||||
|
||||
if (start_index - head_ + n > size) {
|
||||
SHERPA_ONNX_LOGE("Invalid start_index: %d and n: %d. head_: %d, size: %d",
|
||||
start_index, n, head_, size);
|
||||
return {};
|
||||
}
|
||||
|
||||
int32_t start = start_index % capacity;
|
||||
|
||||
if (start + n < capacity) {
|
||||
return {buffer_.begin() + start, buffer_.begin() + start + n};
|
||||
}
|
||||
|
||||
std::vector<float> ans(n);
|
||||
|
||||
std::copy(buffer_.begin() + start, buffer_.end(), ans.begin());
|
||||
|
||||
int32_t part1_size = capacity - start;
|
||||
int32_t part2_size = n - part1_size;
|
||||
std::copy(buffer_.begin(), buffer_.begin() + part2_size,
|
||||
ans.begin() + part1_size);
|
||||
|
||||
return ans;
|
||||
}
|
||||
|
||||
void CircularBuffer::Pop(int32_t n) {
|
||||
int32_t size = Size();
|
||||
if (n < 0 || n > size) {
|
||||
SHERPA_ONNX_LOGE("Invalid n: %d. size: %d", n, size);
|
||||
return;
|
||||
}
|
||||
|
||||
head_ += n;
|
||||
}
|
||||
|
||||
} // namespace sherpa_onnx
|
||||
@@ -0,0 +1,61 @@
|
||||
// circular-buffer.h
|
||||
//
|
||||
// Copyright (c) 2023 Xiaomi Corporation
|
||||
#ifndef SHERPA_ONNX_CSRC_CIRCULAR_BUFFER_H_
|
||||
#define SHERPA_ONNX_CSRC_CIRCULAR_BUFFER_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace sherpa_onnx {
|
||||
|
||||
class CircularBuffer {
|
||||
public:
|
||||
// Capacity of this buffer. Should be large enough.
|
||||
// If it is full, we just print a message and exit the program.
|
||||
explicit CircularBuffer(int32_t capacity);
|
||||
|
||||
// Push an array
|
||||
//
|
||||
// @param p Pointer to the start address of the array
|
||||
// @param n Number of elements in the array
|
||||
//
|
||||
// Note: If n + Size() > capacity, we print an error message and exit.
|
||||
void Push(const float *p, int32_t n);
|
||||
|
||||
// @param start_index Should in the range [head_, tail_)
|
||||
// @param n Number of elements to get
|
||||
// @return Return a vector of size n containing the requested elements
|
||||
std::vector<float> Get(int32_t start_index, int32_t n) const;
|
||||
|
||||
// Remove n elements from the buffer
|
||||
//
|
||||
// @param n Should be in the range [0, size_]
|
||||
void Pop(int32_t n);
|
||||
|
||||
// Number of elements in the buffer.
|
||||
int32_t Size() const { return tail_ - head_; }
|
||||
|
||||
// Current position of the head
|
||||
int32_t Head() const { return head_; }
|
||||
|
||||
// Current position of the tail
|
||||
int32_t Tail() const { return tail_; }
|
||||
|
||||
void Reset() {
|
||||
head_ = 0;
|
||||
tail_ = 0;
|
||||
}
|
||||
|
||||
void Resize(int32_t new_capacity);
|
||||
|
||||
private:
|
||||
std::vector<float> buffer_;
|
||||
|
||||
int32_t head_ = 0; // linear index; always increasing; never wraps around
|
||||
int32_t tail_ = 0; // linear index, always increasing; never wraps around.
|
||||
};
|
||||
|
||||
} // namespace sherpa_onnx
|
||||
|
||||
#endif // SHERPA_ONNX_CSRC_CIRCULAR_BUFFER_H_
|
||||
@@ -0,0 +1,96 @@
|
||||
// sherpa-onnx/csrc/context-graph.cc
|
||||
//
|
||||
// Copyright (c) 2023 Xiaomi Corporation
|
||||
|
||||
#include "context-graph.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <queue>
|
||||
#include <utility>
|
||||
|
||||
namespace sherpa_onnx {
|
||||
void ContextGraph::Build(
|
||||
const std::vector<std::vector<int32_t>> &token_ids) const {
|
||||
for (int32_t i = 0; i < token_ids.size(); ++i) {
|
||||
auto node = root_.get();
|
||||
for (int32_t j = 0; j < token_ids[i].size(); ++j) {
|
||||
int32_t token = token_ids[i][j];
|
||||
if (0 == node->next.count(token)) {
|
||||
bool is_end = j == token_ids[i].size() - 1;
|
||||
node->next[token] = std::make_unique<ContextState>(
|
||||
token, context_score_, node->node_score + context_score_,
|
||||
is_end ? node->node_score + context_score_ : 0, is_end);
|
||||
}
|
||||
node = node->next[token].get();
|
||||
}
|
||||
}
|
||||
FillFailOutput();
|
||||
}
|
||||
|
||||
std::pair<float, const ContextState *> ContextGraph::ForwardOneStep(
|
||||
const ContextState *state, int32_t token) const {
|
||||
const ContextState *node;
|
||||
float score;
|
||||
if (1 == state->next.count(token)) {
|
||||
node = state->next.at(token).get();
|
||||
score = node->token_score;
|
||||
} else {
|
||||
node = state->fail;
|
||||
while (0 == node->next.count(token)) {
|
||||
node = node->fail;
|
||||
if (-1 == node->token) break; // root
|
||||
}
|
||||
if (1 == node->next.count(token)) {
|
||||
node = node->next.at(token).get();
|
||||
}
|
||||
score = node->node_score - state->node_score;
|
||||
}
|
||||
SHERPA_ONNX_CHECK(nullptr != node);
|
||||
return std::make_pair(score + node->output_score, node);
|
||||
}
|
||||
|
||||
std::pair<float, const ContextState *> ContextGraph::Finalize(
|
||||
const ContextState *state) const {
|
||||
float score = -state->node_score;
|
||||
return std::make_pair(score, root_.get());
|
||||
}
|
||||
|
||||
void ContextGraph::FillFailOutput() const {
|
||||
std::queue<const ContextState *> node_queue;
|
||||
for (auto &kv : root_->next) {
|
||||
kv.second->fail = root_.get();
|
||||
node_queue.push(kv.second.get());
|
||||
}
|
||||
while (!node_queue.empty()) {
|
||||
auto current_node = node_queue.front();
|
||||
node_queue.pop();
|
||||
for (auto &kv : current_node->next) {
|
||||
auto fail = current_node->fail;
|
||||
if (1 == fail->next.count(kv.first)) {
|
||||
fail = fail->next.at(kv.first).get();
|
||||
} else {
|
||||
fail = fail->fail;
|
||||
while (0 == fail->next.count(kv.first)) {
|
||||
fail = fail->fail;
|
||||
if (-1 == fail->token) break;
|
||||
}
|
||||
if (1 == fail->next.count(kv.first))
|
||||
fail = fail->next.at(kv.first).get();
|
||||
}
|
||||
kv.second->fail = fail;
|
||||
// fill the output arc
|
||||
auto output = fail;
|
||||
while (!output->is_end) {
|
||||
output = output->fail;
|
||||
if (-1 == output->token) {
|
||||
output = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
kv.second->output = output;
|
||||
kv.second->output_score += output == nullptr ? 0 : output->output_score;
|
||||
node_queue.push(kv.second.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace sherpa_onnx
|
||||
@@ -0,0 +1,66 @@
|
||||
// sherpa-onnx/csrc/context-graph.h
|
||||
//
|
||||
// Copyright (c) 2023 Xiaomi Corporation
|
||||
|
||||
#ifndef SHERPA_ONNX_CSRC_CONTEXT_GRAPH_H_
|
||||
#define SHERPA_ONNX_CSRC_CONTEXT_GRAPH_H_
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
namespace sherpa_onnx {
|
||||
|
||||
class ContextGraph;
|
||||
using ContextGraphPtr = std::shared_ptr<ContextGraph>;
|
||||
|
||||
struct ContextState {
|
||||
int32_t token;
|
||||
float token_score;
|
||||
float node_score;
|
||||
float output_score;
|
||||
bool is_end;
|
||||
std::unordered_map<int32_t, std::unique_ptr<ContextState>> next;
|
||||
const ContextState *fail = nullptr;
|
||||
const ContextState *output = nullptr;
|
||||
|
||||
ContextState() = default;
|
||||
ContextState(int32_t token, float token_score, float node_score,
|
||||
float output_score, bool is_end)
|
||||
: token(token),
|
||||
token_score(token_score),
|
||||
node_score(node_score),
|
||||
output_score(output_score),
|
||||
is_end(is_end) {}
|
||||
};
|
||||
|
||||
class ContextGraph {
|
||||
public:
|
||||
ContextGraph() = default;
|
||||
ContextGraph(const std::vector<std::vector<int32_t>> &token_ids,
|
||||
float context_score)
|
||||
: context_score_(context_score) {
|
||||
root_ = std::make_unique<ContextState>(-1, 0, 0, 0, false);
|
||||
root_->fail = root_.get();
|
||||
Build(token_ids);
|
||||
}
|
||||
|
||||
std::pair<float, const ContextState *> ForwardOneStep(
|
||||
const ContextState *state, int32_t token_id) const;
|
||||
std::pair<float, const ContextState *> Finalize(
|
||||
const ContextState *state) const;
|
||||
|
||||
const ContextState *Root() const { return root_.get(); }
|
||||
|
||||
private:
|
||||
float context_score_;
|
||||
std::unique_ptr<ContextState> root_;
|
||||
void Build(const std::vector<std::vector<int32_t>> &token_ids) const;
|
||||
void FillFailOutput() const;
|
||||
};
|
||||
|
||||
} // namespace sherpa_onnx
|
||||
#endif // SHERPA_ONNX_CSRC_CONTEXT_GRAPH_H_
|
||||
@@ -0,0 +1,204 @@
|
||||
// features.cc
|
||||
//
|
||||
// Copyright (c) 2023 Xiaomi Corporation
|
||||
|
||||
#include "feature-extractor.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <mutex> // NOLINT
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
#include "online-feature.h"
|
||||
#include "macros.h"
|
||||
#include "resample.h"
|
||||
|
||||
namespace sherpa_onnx {
|
||||
|
||||
void FeatureExtractorConfig::Register(ParseOptions *po) {
|
||||
po->Register("sample-rate", &sampling_rate,
|
||||
"Sampling rate of the input waveform. "
|
||||
"Note: You can have a different "
|
||||
"sample rate for the input waveform. We will do resampling "
|
||||
"inside the feature extractor");
|
||||
|
||||
po->Register("feat-dim", &feature_dim,
|
||||
"Feature dimension. Must match the one expected by the model.");
|
||||
}
|
||||
|
||||
std::string FeatureExtractorConfig::ToString() const {
|
||||
std::ostringstream os;
|
||||
|
||||
os << "FeatureExtractorConfig(";
|
||||
os << "sampling_rate=" << sampling_rate << ", ";
|
||||
os << "feature_dim=" << feature_dim << ")";
|
||||
|
||||
return os.str();
|
||||
}
|
||||
|
||||
class FeatureExtractor::Impl {
|
||||
public:
|
||||
explicit Impl(const FeatureExtractorConfig &config) : config_(config) {
|
||||
opts_.frame_opts.dither = 1;
|
||||
opts_.frame_opts.snip_edges = false;
|
||||
opts_.frame_opts.samp_freq = config.sampling_rate;
|
||||
|
||||
opts_.mel_opts.num_bins = config.feature_dim;
|
||||
opts_.mel_opts.high_freq = -400;
|
||||
|
||||
fbank_ = std::make_unique<knf::OnlineFbank>(opts_);
|
||||
}
|
||||
|
||||
void AcceptWaveform(int32_t sampling_rate, const float *waveform, int32_t n) {
|
||||
if (config_.normalize_samples) {
|
||||
AcceptWaveformImpl(sampling_rate, waveform, n);
|
||||
} else {
|
||||
std::vector<float> buf(n);
|
||||
for (int32_t i = 0; i != n; ++i) {
|
||||
buf[i] = waveform[i] * 32768;
|
||||
}
|
||||
AcceptWaveformImpl(sampling_rate, buf.data(), n);
|
||||
}
|
||||
}
|
||||
|
||||
void AcceptWaveformImpl(int32_t sampling_rate, const float *waveform,
|
||||
int32_t n) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (resampler_) {
|
||||
if (sampling_rate != resampler_->GetInputSamplingRate()) {
|
||||
SHERPA_ONNX_LOGE(
|
||||
"You changed the input sampling rate!! Expected: %d, given: "
|
||||
"%d",
|
||||
resampler_->GetInputSamplingRate(), sampling_rate);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
std::vector<float> samples;
|
||||
resampler_->Resample(waveform, n, false, &samples);
|
||||
fbank_->AcceptWaveform(opts_.frame_opts.samp_freq, samples.data(),
|
||||
samples.size());
|
||||
return;
|
||||
}
|
||||
|
||||
if (sampling_rate != opts_.frame_opts.samp_freq) {
|
||||
SHERPA_ONNX_LOGE(
|
||||
"Creating a resampler:\n"
|
||||
" in_sample_rate: %d\n"
|
||||
" output_sample_rate: %d\n",
|
||||
sampling_rate, static_cast<int32_t>(opts_.frame_opts.samp_freq));
|
||||
|
||||
float min_freq =
|
||||
std::min<int32_t>(sampling_rate, opts_.frame_opts.samp_freq);
|
||||
float lowpass_cutoff = 0.99 * 0.5 * min_freq;
|
||||
|
||||
int32_t lowpass_filter_width = 6;
|
||||
resampler_ = std::make_unique<LinearResample>(
|
||||
sampling_rate, opts_.frame_opts.samp_freq, lowpass_cutoff,
|
||||
lowpass_filter_width);
|
||||
|
||||
std::vector<float> samples;
|
||||
resampler_->Resample(waveform, n, false, &samples);
|
||||
fbank_->AcceptWaveform(opts_.frame_opts.samp_freq, samples.data(),
|
||||
samples.size());
|
||||
return;
|
||||
}
|
||||
|
||||
fbank_->AcceptWaveform(sampling_rate, waveform, n);
|
||||
}
|
||||
|
||||
void InputFinished() const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
fbank_->InputFinished();
|
||||
}
|
||||
|
||||
int32_t NumFramesReady() const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return fbank_->NumFramesReady();
|
||||
}
|
||||
|
||||
bool IsLastFrame(int32_t frame) const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return fbank_->IsLastFrame(frame);
|
||||
}
|
||||
|
||||
std::vector<float> GetFrames(int32_t frame_index, int32_t n) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (frame_index + n > fbank_->NumFramesReady()) {
|
||||
SHERPA_ONNX_LOGE("%d + %d > %d\n", frame_index, n,
|
||||
fbank_->NumFramesReady());
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
int32_t discard_num = frame_index - last_frame_index_;
|
||||
if (discard_num < 0) {
|
||||
SHERPA_ONNX_LOGE("last_frame_index_: %d, frame_index_: %d",
|
||||
last_frame_index_, frame_index);
|
||||
exit(-1);
|
||||
}
|
||||
fbank_->Pop(discard_num);
|
||||
|
||||
int32_t feature_dim = fbank_->Dim();
|
||||
std::vector<float> features(feature_dim * n);
|
||||
|
||||
float *p = features.data();
|
||||
|
||||
for (int32_t i = 0; i != n; ++i) {
|
||||
const float *f = fbank_->GetFrame(i + frame_index);
|
||||
|
||||
// std::cout << "Frame " << i;
|
||||
// for (int j = 0; j < feature_dim; j++) {
|
||||
// std::cout << " " << f[j];
|
||||
// }
|
||||
// std::cout << std::endl;
|
||||
|
||||
std::copy(f, f + feature_dim, p);
|
||||
p += feature_dim;
|
||||
}
|
||||
|
||||
last_frame_index_ = frame_index;
|
||||
|
||||
return features;
|
||||
}
|
||||
|
||||
int32_t FeatureDim() const { return opts_.mel_opts.num_bins; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<knf::OnlineFbank> fbank_;
|
||||
knf::FbankOptions opts_;
|
||||
FeatureExtractorConfig config_;
|
||||
mutable std::mutex mutex_;
|
||||
std::unique_ptr<LinearResample> resampler_;
|
||||
int32_t last_frame_index_ = 0;
|
||||
};
|
||||
|
||||
FeatureExtractor::FeatureExtractor(const FeatureExtractorConfig &config /*={}*/)
|
||||
: impl_(std::make_unique<Impl>(config)) {}
|
||||
|
||||
FeatureExtractor::~FeatureExtractor() = default;
|
||||
|
||||
void FeatureExtractor::AcceptWaveform(int32_t sampling_rate,
|
||||
const float *waveform, int32_t n) const {
|
||||
impl_->AcceptWaveform(sampling_rate, waveform, n);
|
||||
}
|
||||
|
||||
void FeatureExtractor::InputFinished() const { impl_->InputFinished(); }
|
||||
|
||||
int32_t FeatureExtractor::NumFramesReady() const {
|
||||
return impl_->NumFramesReady();
|
||||
}
|
||||
|
||||
bool FeatureExtractor::IsLastFrame(int32_t frame) const {
|
||||
return impl_->IsLastFrame(frame);
|
||||
}
|
||||
|
||||
std::vector<float> FeatureExtractor::GetFrames(int32_t frame_index,
|
||||
int32_t n) const {
|
||||
return impl_->GetFrames(frame_index, n);
|
||||
}
|
||||
|
||||
int32_t FeatureExtractor::FeatureDim() const { return impl_->FeatureDim(); }
|
||||
|
||||
} // namespace sherpa_onnx
|
||||
@@ -0,0 +1,86 @@
|
||||
// features.h
|
||||
//
|
||||
// Copyright (c) 2023 Xiaomi Corporation
|
||||
|
||||
#ifndef SHERPA_ONNX_CSRC_FEATURES_H_
|
||||
#define SHERPA_ONNX_CSRC_FEATURES_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "parse-options.h"
|
||||
|
||||
namespace sherpa_onnx {
|
||||
|
||||
struct FeatureExtractorConfig {
|
||||
// Sampling rate used by the feature extractor. If it is different from
|
||||
// the sampling rate of the input waveform, we will do resampling inside.
|
||||
int32_t sampling_rate = 16000;
|
||||
|
||||
// Feature dimension
|
||||
int32_t feature_dim = 80;
|
||||
|
||||
// Set internally by some models, e.g., paraformer sets it to false.
|
||||
// This parameter is not exposed to users from the commandline
|
||||
// If true, the feature extractor expects inputs to be normalized to
|
||||
// the range [-1, 1].
|
||||
// If false, we will multiply the inputs by 32768
|
||||
bool normalize_samples = true;
|
||||
|
||||
std::string ToString() const;
|
||||
|
||||
void Register(ParseOptions *po);
|
||||
};
|
||||
|
||||
class FeatureExtractor {
|
||||
public:
|
||||
explicit FeatureExtractor(const FeatureExtractorConfig &config = {});
|
||||
~FeatureExtractor();
|
||||
|
||||
/**
|
||||
@param sampling_rate The sampling_rate of the input waveform. If it does
|
||||
not equal to config.sampling_rate, we will do
|
||||
resampling inside.
|
||||
@param waveform Pointer to a 1-D array of size n. It must be normalized to
|
||||
the range [-1, 1].
|
||||
@param n Number of entries in waveform
|
||||
*/
|
||||
void AcceptWaveform(int32_t sampling_rate, const float *waveform,
|
||||
int32_t n) const;
|
||||
|
||||
/**
|
||||
* InputFinished() tells the class you won't be providing any
|
||||
* more waveform. This will help flush out the last frame or two
|
||||
* of features, in the case where snip-edges == false; it also
|
||||
* affects the return value of IsLastFrame().
|
||||
*/
|
||||
void InputFinished() const;
|
||||
|
||||
int32_t NumFramesReady() const;
|
||||
|
||||
/** Note: IsLastFrame() will only ever return true if you have called
|
||||
* InputFinished() (and this frame is the last frame).
|
||||
*/
|
||||
bool IsLastFrame(int32_t frame) const;
|
||||
|
||||
/** Get n frames starting from the given frame index.
|
||||
*
|
||||
* @param frame_index The starting frame index
|
||||
* @param n Number of frames to get.
|
||||
* @return Return a 2-D tensor of shape (n, feature_dim).
|
||||
* which is flattened into a 1-D vector (flattened in in row major)
|
||||
*/
|
||||
std::vector<float> GetFrames(int32_t frame_index, int32_t n) const;
|
||||
|
||||
/// Return feature dim of this extractor
|
||||
int32_t FeatureDim() const;
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
} // namespace sherpa_onnx
|
||||
|
||||
#endif // SHERPA_ONNX_CSRC_FEATURES_H_
|
||||
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Copyright (c) 2022 Xiaomi Corporation (authors: Fangjun Kuang)
|
||||
*
|
||||
* See LICENSE 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
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// This file is copied/modified from kaldi/src/feat/feature-fbank.cc
|
||||
//
|
||||
#include "feature-fbank.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#include "feature-functions.h"
|
||||
|
||||
namespace knf {
|
||||
|
||||
static void Sqrt(float *in_out, int32_t n) {
|
||||
for (int32_t i = 0; i != n; ++i) {
|
||||
in_out[i] = std::sqrt(in_out[i]);
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &os, const FbankOptions &opts) {
|
||||
os << opts.ToString();
|
||||
return os;
|
||||
}
|
||||
|
||||
FbankComputer::FbankComputer(const FbankOptions &opts)
|
||||
: opts_(opts), rfft_(opts.frame_opts.PaddedWindowSize()) {
|
||||
if (opts.energy_floor > 0.0f) {
|
||||
log_energy_floor_ = logf(opts.energy_floor);
|
||||
}
|
||||
|
||||
// We'll definitely need the filterbanks info for VTLN warping factor 1.0.
|
||||
// [note: this call caches it.]
|
||||
GetMelBanks(1.0f);
|
||||
}
|
||||
|
||||
FbankComputer::~FbankComputer() {
|
||||
for (auto iter = mel_banks_.begin(); iter != mel_banks_.end(); ++iter)
|
||||
delete iter->second;
|
||||
}
|
||||
|
||||
const MelBanks *FbankComputer::GetMelBanks(float vtln_warp) {
|
||||
MelBanks *this_mel_banks = nullptr;
|
||||
|
||||
// std::map<float, MelBanks *>::iterator iter = mel_banks_.find(vtln_warp);
|
||||
auto iter = mel_banks_.find(vtln_warp);
|
||||
if (iter == mel_banks_.end()) {
|
||||
this_mel_banks = new MelBanks(opts_.mel_opts, opts_.frame_opts, vtln_warp);
|
||||
mel_banks_[vtln_warp] = this_mel_banks;
|
||||
} else {
|
||||
this_mel_banks = iter->second;
|
||||
}
|
||||
return this_mel_banks;
|
||||
}
|
||||
|
||||
void FbankComputer::Compute(float signal_raw_log_energy, float vtln_warp,
|
||||
std::vector<float> *signal_frame, float *feature) {
|
||||
const MelBanks &mel_banks = *(GetMelBanks(vtln_warp));
|
||||
|
||||
SHERPA_ONNX_CHECK_EQ(signal_frame->size(), opts_.frame_opts.PaddedWindowSize());
|
||||
|
||||
// Compute energy after window function (not the raw one).
|
||||
if (opts_.use_energy && !opts_.raw_energy) {
|
||||
signal_raw_log_energy = std::log(
|
||||
std::max<float>(InnerProduct(signal_frame->data(), signal_frame->data(),
|
||||
signal_frame->size()),
|
||||
std::numeric_limits<float>::epsilon()));
|
||||
}
|
||||
rfft_.Compute(signal_frame->data()); // signal_frame is modified in-place
|
||||
ComputePowerSpectrum(signal_frame);
|
||||
|
||||
// Use magnitude instead of power if requested.
|
||||
if (!opts_.use_power) {
|
||||
Sqrt(signal_frame->data(), signal_frame->size() / 2 + 1);
|
||||
}
|
||||
|
||||
int32_t mel_offset = ((opts_.use_energy && !opts_.htk_compat) ? 1 : 0);
|
||||
|
||||
// Its length is opts_.mel_opts.num_bins
|
||||
float *mel_energies = feature + mel_offset;
|
||||
|
||||
// Sum with mel filter banks over the power spectrum
|
||||
mel_banks.Compute(signal_frame->data(), mel_energies);
|
||||
|
||||
if (opts_.use_log_fbank) {
|
||||
// Avoid log of zero (which should be prevented anyway by dithering).
|
||||
for (int32_t i = 0; i != opts_.mel_opts.num_bins; ++i) {
|
||||
auto t = std::max(mel_energies[i], std::numeric_limits<float>::epsilon());
|
||||
mel_energies[i] = std::log(t);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy energy as first value (or the last, if htk_compat == true).
|
||||
if (opts_.use_energy) {
|
||||
if (opts_.energy_floor > 0.0 && signal_raw_log_energy < log_energy_floor_) {
|
||||
signal_raw_log_energy = log_energy_floor_;
|
||||
}
|
||||
int32_t energy_index = opts_.htk_compat ? opts_.mel_opts.num_bins : 0;
|
||||
feature[energy_index] = signal_raw_log_energy;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace knf
|
||||
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* Copyright (c) 2022 Xiaomi Corporation (authors: Fangjun Kuang)
|
||||
*
|
||||
* See LICENSE 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
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// This file is copied/modified from kaldi/src/feat/feature-fbank.h
|
||||
|
||||
#ifndef KALDI_NATIVE_FBANK_CSRC_FEATURE_FBANK_H_
|
||||
#define KALDI_NATIVE_FBANK_CSRC_FEATURE_FBANK_H_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "feature-window.h"
|
||||
#include "mel-computations.h"
|
||||
#include "rfft.h"
|
||||
|
||||
namespace knf {
|
||||
|
||||
struct FbankOptions {
|
||||
FrameExtractionOptions frame_opts;
|
||||
MelBanksOptions mel_opts;
|
||||
// append an extra dimension with energy to the filter banks
|
||||
bool use_energy = false;
|
||||
float energy_floor = 0.0f; // active iff use_energy==true
|
||||
|
||||
// If true, compute log_energy before preemphasis and windowing
|
||||
// If false, compute log_energy after preemphasis ans windowing
|
||||
bool raw_energy = true; // active iff use_energy==true
|
||||
|
||||
// If true, put energy last (if using energy)
|
||||
// If false, put energy first
|
||||
bool htk_compat = false; // active iff use_energy==true
|
||||
|
||||
// if true (default), produce log-filterbank, else linear
|
||||
bool use_log_fbank = true;
|
||||
|
||||
// if true (default), use power in filterbank
|
||||
// analysis, else magnitude.
|
||||
bool use_power = true;
|
||||
|
||||
FbankOptions() { mel_opts.num_bins = 23; }
|
||||
|
||||
std::string ToString() const {
|
||||
std::ostringstream os;
|
||||
os << "frame_opts: \n";
|
||||
os << frame_opts << "\n";
|
||||
os << "\n";
|
||||
|
||||
os << "mel_opts: \n";
|
||||
os << mel_opts << "\n";
|
||||
|
||||
os << "use_energy: " << use_energy << "\n";
|
||||
os << "energy_floor: " << energy_floor << "\n";
|
||||
os << "raw_energy: " << raw_energy << "\n";
|
||||
os << "htk_compat: " << htk_compat << "\n";
|
||||
os << "use_log_fbank: " << use_log_fbank << "\n";
|
||||
os << "use_power: " << use_power << "\n";
|
||||
return os.str();
|
||||
}
|
||||
};
|
||||
|
||||
std::ostream &operator<<(std::ostream &os, const FbankOptions &opts);
|
||||
|
||||
class FbankComputer {
|
||||
public:
|
||||
using Options = FbankOptions;
|
||||
|
||||
explicit FbankComputer(const FbankOptions &opts);
|
||||
~FbankComputer();
|
||||
|
||||
int32_t Dim() const {
|
||||
return opts_.mel_opts.num_bins + (opts_.use_energy ? 1 : 0);
|
||||
}
|
||||
|
||||
// if true, compute log_energy_pre_window but after dithering and dc removal
|
||||
bool NeedRawLogEnergy() const { return opts_.use_energy && opts_.raw_energy; }
|
||||
|
||||
const FrameExtractionOptions &GetFrameOptions() const {
|
||||
return opts_.frame_opts;
|
||||
}
|
||||
|
||||
const FbankOptions &GetOptions() const { return opts_; }
|
||||
|
||||
/**
|
||||
Function that computes one frame of features from
|
||||
one frame of signal.
|
||||
|
||||
@param [in] signal_raw_log_energy The log-energy of the frame of the signal
|
||||
prior to windowing and pre-emphasis, or
|
||||
log(numeric_limits<float>::min()), whichever is greater. Must be
|
||||
ignored by this function if this class returns false from
|
||||
this->NeedsRawLogEnergy().
|
||||
@param [in] vtln_warp The VTLN warping factor that the user wants
|
||||
to be applied when computing features for this utterance. Will
|
||||
normally be 1.0, meaning no warping is to be done. The value will
|
||||
be ignored for feature types that don't support VLTN, such as
|
||||
spectrogram features.
|
||||
@param [in] signal_frame One frame of the signal,
|
||||
as extracted using the function ExtractWindow() using the options
|
||||
returned by this->GetFrameOptions(). The function will use the
|
||||
vector as a workspace, which is why it's a non-const pointer.
|
||||
@param [out] feature Pointer to a vector of size this->Dim(), to which
|
||||
the computed feature will be written. It should be pre-allocated.
|
||||
*/
|
||||
void Compute(float signal_raw_log_energy, float vtln_warp,
|
||||
std::vector<float> *signal_frame, float *feature);
|
||||
|
||||
private:
|
||||
const MelBanks *GetMelBanks(float vtln_warp);
|
||||
|
||||
FbankOptions opts_;
|
||||
float log_energy_floor_;
|
||||
std::map<float, MelBanks *> mel_banks_; // float is VTLN coefficient.
|
||||
Rfft rfft_;
|
||||
};
|
||||
|
||||
} // namespace knf
|
||||
|
||||
#endif // KALDI_NATIVE_FBANK_CSRC_FEATURE_FBANK_H_
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2022 Xiaomi Corporation (authors: Fangjun Kuang)
|
||||
*
|
||||
* See LICENSE 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
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// This file is copied/modified from kaldi/src/feat/feature-functions.cc
|
||||
|
||||
#include "feature-functions.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace knf {
|
||||
|
||||
void ComputePowerSpectrum(std::vector<float> *complex_fft) {
|
||||
int32_t dim = complex_fft->size();
|
||||
|
||||
// now we have in complex_fft, first half of complex spectrum
|
||||
// it's stored as [real0, realN/2, real1, im1, real2, im2, ...]
|
||||
|
||||
float *p = complex_fft->data();
|
||||
int32_t half_dim = dim / 2;
|
||||
float first_energy = p[0] * p[0];
|
||||
float last_energy = p[1] * p[1]; // handle this special case
|
||||
|
||||
for (int32_t i = 1; i < half_dim; ++i) {
|
||||
float real = p[i * 2];
|
||||
float im = p[i * 2 + 1];
|
||||
p[i] = real * real + im * im;
|
||||
}
|
||||
p[0] = first_energy;
|
||||
p[half_dim] = last_energy; // Will actually never be used, and anyway
|
||||
// if the signal has been bandlimited sensibly this should be zero.
|
||||
}
|
||||
|
||||
} // namespace knf
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2022 Xiaomi Corporation (authors: Fangjun Kuang)
|
||||
*
|
||||
* See LICENSE 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
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// This file is copied/modified from kaldi/src/feat/feature-functions.h
|
||||
#ifndef KALDI_NATIVE_FBANK_CSRC_FEATURE_FUNCTIONS_H_
|
||||
#define KALDI_NATIVE_FBANK_CSRC_FEATURE_FUNCTIONS_H_
|
||||
|
||||
#include <vector>
|
||||
namespace knf {
|
||||
|
||||
// ComputePowerSpectrum converts a complex FFT (as produced by the FFT
|
||||
// functions in csrc/rfft.h), and converts it into
|
||||
// a power spectrum. If the complex FFT is a vector of size n (representing
|
||||
// half of the complex FFT of a real signal of size n, as described there),
|
||||
// this function computes in the first (n/2) + 1 elements of it, the
|
||||
// energies of the fft bins from zero to the Nyquist frequency. Contents of the
|
||||
// remaining (n/2) - 1 elements are undefined at output.
|
||||
|
||||
void ComputePowerSpectrum(std::vector<float> *complex_fft);
|
||||
|
||||
} // namespace knf
|
||||
|
||||
#endif // KALDI_NATIVE_FBANK_CSRC_FEATURE_FUNCTIONS_H_
|
||||
@@ -0,0 +1,258 @@
|
||||
// feature-window.cc
|
||||
//
|
||||
// Copyright (c) 2022 Xiaomi Corporation (authors: Fangjun Kuang)
|
||||
|
||||
// This file is copied/modified from kaldi/src/feat/feature-window.cc
|
||||
|
||||
#include "feature-window.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
#include <random>
|
||||
|
||||
#ifndef M_2PI
|
||||
#define M_2PI 6.283185307179586476925286766559005
|
||||
#endif
|
||||
|
||||
namespace knf {
|
||||
|
||||
std::ostream &operator<<(std::ostream &os, const FrameExtractionOptions &opts) {
|
||||
os << opts.ToString();
|
||||
return os;
|
||||
}
|
||||
|
||||
FeatureWindowFunction::FeatureWindowFunction(const FrameExtractionOptions &opts)
|
||||
: window_(opts.WindowSize()) {
|
||||
int32_t frame_length = opts.WindowSize();
|
||||
SHERPA_ONNX_CHECK_GT(frame_length, 0);
|
||||
|
||||
float *window_data = window_.data();
|
||||
|
||||
double a = M_2PI / (frame_length - 1);
|
||||
if (opts.window_type == "hann") {
|
||||
// see https://pytorch.org/docs/stable/generated/torch.hann_window.html
|
||||
// We assume periodic is true
|
||||
a = M_2PI / frame_length;
|
||||
}
|
||||
|
||||
for (int32_t i = 0; i < frame_length; i++) {
|
||||
double i_fl = static_cast<double>(i);
|
||||
if (opts.window_type == "hanning") {
|
||||
window_data[i] = 0.5 - 0.5 * cos(a * i_fl);
|
||||
} else if (opts.window_type == "sine") {
|
||||
// when you are checking ws wikipedia, please
|
||||
// note that 0.5 * a = M_PI/(frame_length-1)
|
||||
window_data[i] = sin(0.5 * a * i_fl);
|
||||
} else if (opts.window_type == "hamming") {
|
||||
window_data[i] = 0.54 - 0.46 * cos(a * i_fl);
|
||||
} else if (opts.window_type == "hann") {
|
||||
window_data[i] = 0.50 - 0.50 * cos(a * i_fl);
|
||||
} else if (opts.window_type == "povey") {
|
||||
// like hamming but goes to zero at edges.
|
||||
window_data[i] = pow(0.5 - 0.5 * cos(a * i_fl), 0.85);
|
||||
} else if (opts.window_type == "rectangular") {
|
||||
window_data[i] = 1.0;
|
||||
} else if (opts.window_type == "blackman") {
|
||||
window_data[i] = opts.blackman_coeff - 0.5 * cos(a * i_fl) +
|
||||
(0.5 - opts.blackman_coeff) * cos(2 * a * i_fl);
|
||||
} else {
|
||||
SHERPA_ONNX_LOG(FATAL) << "Invalid window type " << opts.window_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FeatureWindowFunction::Apply(float *wave) const {
|
||||
int32_t window_size = window_.size();
|
||||
const float *p = window_.data();
|
||||
for (int32_t k = 0; k != window_size; ++k) {
|
||||
wave[k] *= p[k];
|
||||
}
|
||||
}
|
||||
|
||||
int64_t FirstSampleOfFrame(int32_t frame, const FrameExtractionOptions &opts) {
|
||||
int64_t frame_shift = opts.WindowShift();
|
||||
if (opts.snip_edges) {
|
||||
return frame * frame_shift;
|
||||
} else {
|
||||
int64_t midpoint_of_frame = frame_shift * frame + frame_shift / 2,
|
||||
beginning_of_frame = midpoint_of_frame - opts.WindowSize() / 2;
|
||||
return beginning_of_frame;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t NumFrames(int64_t num_samples, const FrameExtractionOptions &opts,
|
||||
bool flush /*= true*/) {
|
||||
int64_t frame_shift = opts.WindowShift();
|
||||
int64_t frame_length = opts.WindowSize();
|
||||
if (opts.snip_edges) {
|
||||
// with --snip-edges=true (the default), we use a HTK-like approach to
|
||||
// determining the number of frames-- all frames have to fit completely into
|
||||
// the waveform, and the first frame begins at sample zero.
|
||||
if (num_samples < frame_length)
|
||||
return 0;
|
||||
else
|
||||
return (1 + ((num_samples - frame_length) / frame_shift));
|
||||
// You can understand the expression above as follows: 'num_samples -
|
||||
// frame_length' is how much room we have to shift the frame within the
|
||||
// waveform; 'frame_shift' is how much we shift it each time; and the ratio
|
||||
// is how many times we can shift it (integer arithmetic rounds down).
|
||||
} else {
|
||||
// if --snip-edges=false, the number of frames is determined by rounding the
|
||||
// (file-length / frame-shift) to the nearest integer. The point of this
|
||||
// formula is to make the number of frames an obvious and predictable
|
||||
// function of the frame shift and signal length, which makes many
|
||||
// segmentation-related questions simpler.
|
||||
//
|
||||
// Because integer division in C++ rounds toward zero, we add (half the
|
||||
// frame-shift minus epsilon) before dividing, to have the effect of
|
||||
// rounding towards the closest integer.
|
||||
int32_t num_frames = (num_samples + (frame_shift / 2)) / frame_shift;
|
||||
|
||||
if (flush) return num_frames;
|
||||
|
||||
// note: 'end' always means the last plus one, i.e. one past the last.
|
||||
int64_t end_sample_of_last_frame =
|
||||
FirstSampleOfFrame(num_frames - 1, opts) + frame_length;
|
||||
|
||||
// the following code is optimized more for clarity than efficiency.
|
||||
// If flush == false, we can't output frames that extend past the end
|
||||
// of the signal.
|
||||
while (num_frames > 0 && end_sample_of_last_frame > num_samples) {
|
||||
num_frames--;
|
||||
end_sample_of_last_frame -= frame_shift;
|
||||
}
|
||||
return num_frames;
|
||||
}
|
||||
}
|
||||
|
||||
void ExtractWindow(int64_t sample_offset, const std::vector<float> &wave,
|
||||
int32_t f, const FrameExtractionOptions &opts,
|
||||
const FeatureWindowFunction &window_function,
|
||||
std::vector<float> *window,
|
||||
float *log_energy_pre_window /*= nullptr*/) {
|
||||
SHERPA_ONNX_CHECK(sample_offset >= 0 && wave.size() != 0);
|
||||
|
||||
int32_t frame_length = opts.WindowSize();
|
||||
int32_t frame_length_padded = opts.PaddedWindowSize();
|
||||
|
||||
int64_t num_samples = sample_offset + wave.size();
|
||||
int64_t start_sample = FirstSampleOfFrame(f, opts);
|
||||
int64_t end_sample = start_sample + frame_length;
|
||||
|
||||
if (opts.snip_edges) {
|
||||
SHERPA_ONNX_CHECK(start_sample >= sample_offset && end_sample <= num_samples);
|
||||
} else {
|
||||
SHERPA_ONNX_CHECK(sample_offset == 0 || start_sample >= sample_offset);
|
||||
}
|
||||
|
||||
if (window->size() != frame_length_padded) {
|
||||
window->resize(frame_length_padded);
|
||||
}
|
||||
|
||||
// wave_start and wave_end are start and end indexes into 'wave', for the
|
||||
// piece of wave that we're trying to extract.
|
||||
int32_t wave_start = int32_t(start_sample - sample_offset);
|
||||
int32_t wave_end = wave_start + frame_length;
|
||||
|
||||
if (wave_start >= 0 && wave_end <= wave.size()) {
|
||||
// the normal case-- no edge effects to consider.
|
||||
std::copy(wave.begin() + wave_start,
|
||||
wave.begin() + wave_start + frame_length, window->data());
|
||||
} else {
|
||||
// Deal with any end effects by reflection, if needed. This code will only
|
||||
// be reached for about two frames per utterance, so we don't concern
|
||||
// ourselves excessively with efficiency.
|
||||
int32_t wave_dim = wave.size();
|
||||
for (int32_t s = 0; s < frame_length; ++s) {
|
||||
int32_t s_in_wave = s + wave_start;
|
||||
while (s_in_wave < 0 || s_in_wave >= wave_dim) {
|
||||
// reflect around the beginning or end of the wave.
|
||||
// e.g. -1 -> 0, -2 -> 1.
|
||||
// dim -> dim - 1, dim + 1 -> dim - 2.
|
||||
// the code supports repeated reflections, although this
|
||||
// would only be needed in pathological cases.
|
||||
if (s_in_wave < 0)
|
||||
s_in_wave = -s_in_wave - 1;
|
||||
else
|
||||
s_in_wave = 2 * wave_dim - 1 - s_in_wave;
|
||||
}
|
||||
(*window)[s] = wave[s_in_wave];
|
||||
}
|
||||
}
|
||||
|
||||
ProcessWindow(opts, window_function, window->data(), log_energy_pre_window);
|
||||
}
|
||||
|
||||
static void RemoveDcOffset(float *d, int32_t n) {
|
||||
float sum = 0;
|
||||
for (int32_t i = 0; i != n; ++i) {
|
||||
sum += d[i];
|
||||
}
|
||||
|
||||
float mean = sum / n;
|
||||
|
||||
for (int32_t i = 0; i != n; ++i) {
|
||||
d[i] -= mean;
|
||||
}
|
||||
}
|
||||
|
||||
float InnerProduct(const float *a, const float *b, int32_t n) {
|
||||
float sum = 0;
|
||||
for (int32_t i = 0; i != n; ++i) {
|
||||
sum += a[i] * b[i];
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
static std::default_random_engine de(1234); //seed
|
||||
|
||||
void Dither(float *waveform, int32_t n, float dither_value) {
|
||||
if (dither_value == 0.0)
|
||||
return;
|
||||
std::normal_distribution<float> nd(0, 1.0 / 32768.0); //mean followed by stdiv
|
||||
for (int i = 0; i < n; i++)
|
||||
waveform[i] += nd(de) * dither_value;
|
||||
}
|
||||
|
||||
static void Preemphasize(float *d, int32_t n, float preemph_coeff) {
|
||||
if (preemph_coeff == 0.0) {
|
||||
return;
|
||||
}
|
||||
|
||||
SHERPA_ONNX_CHECK(preemph_coeff >= 0.0 && preemph_coeff <= 1.0);
|
||||
|
||||
for (int32_t i = n - 1; i > 0; --i) {
|
||||
d[i] -= preemph_coeff * d[i - 1];
|
||||
}
|
||||
d[0] -= preemph_coeff * d[0];
|
||||
}
|
||||
|
||||
void ProcessWindow(const FrameExtractionOptions &opts,
|
||||
const FeatureWindowFunction &window_function, float *window,
|
||||
float *log_energy_pre_window /*= nullptr*/) {
|
||||
int32_t frame_length = opts.WindowSize();
|
||||
|
||||
if (opts.dither != 0.0) {
|
||||
Dither(window, frame_length, opts.dither);
|
||||
}
|
||||
|
||||
if (opts.remove_dc_offset) {
|
||||
RemoveDcOffset(window, frame_length);
|
||||
}
|
||||
|
||||
if (log_energy_pre_window != NULL) {
|
||||
float energy = std::max<float>(InnerProduct(window, window, frame_length),
|
||||
std::numeric_limits<float>::epsilon());
|
||||
*log_energy_pre_window = std::log(energy);
|
||||
}
|
||||
|
||||
if (opts.preemph_coeff != 0.0) {
|
||||
Preemphasize(window, frame_length, opts.preemph_coeff);
|
||||
}
|
||||
|
||||
window_function.Apply(window);
|
||||
}
|
||||
|
||||
} // namespace knf
|
||||
@@ -0,0 +1,174 @@
|
||||
// feature-window.h
|
||||
//
|
||||
// Copyright (c) 2022 Xiaomi Corporation (authors: Fangjun Kuang)
|
||||
|
||||
// This file is copied/modified from kaldi/src/feat/feature-window.h
|
||||
|
||||
#ifndef KALDI_NATIVE_FBANK_CSRC_FEATURE_WINDOW_H_
|
||||
#define KALDI_NATIVE_FBANK_CSRC_FEATURE_WINDOW_H_
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
namespace knf {
|
||||
|
||||
inline int32_t RoundUpToNearestPowerOfTwo(int32_t n) {
|
||||
// copied from kaldi/src/base/kaldi-math.cc
|
||||
SHERPA_ONNX_CHECK_GT(n, 0);
|
||||
n--;
|
||||
n |= n >> 1;
|
||||
n |= n >> 2;
|
||||
n |= n >> 4;
|
||||
n |= n >> 8;
|
||||
n |= n >> 16;
|
||||
return n + 1;
|
||||
}
|
||||
|
||||
struct FrameExtractionOptions {
|
||||
float samp_freq = 16000;
|
||||
float frame_shift_ms = 10.0f; // in milliseconds.
|
||||
float frame_length_ms = 25.0f; // in milliseconds.
|
||||
float dither = 1.0f; // Amount of dithering, 0.0 means no dither.
|
||||
float preemph_coeff = 0.97f; // Preemphasis coefficient.
|
||||
bool remove_dc_offset = true; // Subtract mean of wave before FFT.
|
||||
std::string window_type = "povey"; // e.g. Hamming window
|
||||
// May be "hamming", "rectangular", "povey", "hanning", "hann", "sine",
|
||||
// "blackman".
|
||||
// "povey" is a window I made to be similar to Hamming but to go to zero at
|
||||
// the edges, it's pow((0.5 - 0.5*cos(n/N*2*pi)), 0.85) I just don't think the
|
||||
// Hamming window makes sense as a windowing function.
|
||||
bool round_to_power_of_two = true;
|
||||
float blackman_coeff = 0.42f;
|
||||
bool snip_edges = true;
|
||||
// bool allow_downsample = false;
|
||||
// bool allow_upsample = false;
|
||||
|
||||
int32_t WindowShift() const {
|
||||
return static_cast<int32_t>(samp_freq * 0.001f * frame_shift_ms);
|
||||
}
|
||||
int32_t WindowSize() const {
|
||||
return static_cast<int32_t>(samp_freq * 0.001f * frame_length_ms);
|
||||
}
|
||||
int32_t PaddedWindowSize() const {
|
||||
return (round_to_power_of_two ? RoundUpToNearestPowerOfTwo(WindowSize())
|
||||
: WindowSize());
|
||||
}
|
||||
std::string ToString() const {
|
||||
std::ostringstream os;
|
||||
#define KNF_PRINT(x) os << #x << ": " << x << "\n"
|
||||
KNF_PRINT(samp_freq);
|
||||
KNF_PRINT(frame_shift_ms);
|
||||
KNF_PRINT(frame_length_ms);
|
||||
KNF_PRINT(dither);
|
||||
KNF_PRINT(preemph_coeff);
|
||||
KNF_PRINT(remove_dc_offset);
|
||||
KNF_PRINT(window_type);
|
||||
KNF_PRINT(round_to_power_of_two);
|
||||
KNF_PRINT(blackman_coeff);
|
||||
KNF_PRINT(snip_edges);
|
||||
// KNF_PRINT(allow_downsample);
|
||||
// KNF_PRINT(allow_upsample);
|
||||
#undef KNF_PRINT
|
||||
return os.str();
|
||||
}
|
||||
};
|
||||
|
||||
std::ostream &operator<<(std::ostream &os, const FrameExtractionOptions &opts);
|
||||
|
||||
class FeatureWindowFunction {
|
||||
public:
|
||||
FeatureWindowFunction() = default;
|
||||
explicit FeatureWindowFunction(const FrameExtractionOptions &opts);
|
||||
/**
|
||||
* @param wave Pointer to a 1-D array of shape [window_size].
|
||||
* It is modified in-place: wave[i] = wave[i] * window_[i].
|
||||
* @param
|
||||
*/
|
||||
void Apply(float *wave) const;
|
||||
const std::vector<float> &GetWindow() const { return window_; }
|
||||
|
||||
private:
|
||||
std::vector<float> window_; // of size opts.WindowSize()
|
||||
};
|
||||
|
||||
int64_t FirstSampleOfFrame(int32_t frame, const FrameExtractionOptions &opts);
|
||||
|
||||
/**
|
||||
This function returns the number of frames that we can extract from a wave
|
||||
file with the given number of samples in it (assumed to have the same
|
||||
sampling rate as specified in 'opts').
|
||||
|
||||
@param [in] num_samples The number of samples in the wave file.
|
||||
@param [in] opts The frame-extraction options class
|
||||
|
||||
@param [in] flush True if we are asserting that this number of samples
|
||||
is 'all there is', false if we expecting more data to possibly come in. This
|
||||
only makes a difference to the answer
|
||||
if opts.snip_edges== false. For offline feature extraction you always want
|
||||
flush == true. In an online-decoding context, once you know (or decide) that
|
||||
no more data is coming in, you'd call it with flush == true at the end to
|
||||
flush out any remaining data.
|
||||
*/
|
||||
int32_t NumFrames(int64_t num_samples, const FrameExtractionOptions &opts,
|
||||
bool flush = true);
|
||||
|
||||
/*
|
||||
ExtractWindow() extracts a windowed frame of waveform (possibly with a
|
||||
power-of-two, padded size, depending on the config), including all the
|
||||
processing done by ProcessWindow().
|
||||
|
||||
@param [in] sample_offset If 'wave' is not the entire waveform, but
|
||||
part of it to the left has been discarded, then the
|
||||
number of samples prior to 'wave' that we have
|
||||
already discarded. Set this to zero if you are
|
||||
processing the entire waveform in one piece, or
|
||||
if you get 'no matching function' compilation
|
||||
errors when updating the code.
|
||||
@param [in] wave The waveform
|
||||
@param [in] f The frame index to be extracted, with
|
||||
0 <= f < NumFrames(sample_offset + wave.Dim(), opts, true)
|
||||
@param [in] opts The options class to be used
|
||||
@param [in] window_function The windowing function, as derived from the
|
||||
options class.
|
||||
@param [out] window The windowed, possibly-padded waveform to be
|
||||
extracted. Will be resized as needed.
|
||||
@param [out] log_energy_pre_window If non-NULL, the log-energy of
|
||||
the signal prior to pre-emphasis and multiplying by
|
||||
the windowing function will be written to here.
|
||||
*/
|
||||
void ExtractWindow(int64_t sample_offset, const std::vector<float> &wave,
|
||||
int32_t f, const FrameExtractionOptions &opts,
|
||||
const FeatureWindowFunction &window_function,
|
||||
std::vector<float> *window,
|
||||
float *log_energy_pre_window = nullptr);
|
||||
|
||||
/**
|
||||
This function does all the windowing steps after actually
|
||||
extracting the windowed signal: depending on the
|
||||
configuration, it does dithering, dc offset removal,
|
||||
preemphasis, and multiplication by the windowing function.
|
||||
@param [in] opts The options class to be used
|
||||
@param [in] window_function The windowing function-- should have
|
||||
been initialized using 'opts'.
|
||||
@param [in,out] window A vector of size opts.WindowSize(). Note:
|
||||
it will typically be a sub-vector of a larger vector of size
|
||||
opts.PaddedWindowSize(), with the remaining samples zero,
|
||||
as the FFT code is more efficient if it operates on data with
|
||||
power-of-two size.
|
||||
@param [out] log_energy_pre_window If non-NULL, then after dithering and
|
||||
DC offset removal, this function will write to this pointer the log of
|
||||
the total energy (i.e. sum-squared) of the frame.
|
||||
*/
|
||||
void ProcessWindow(const FrameExtractionOptions &opts,
|
||||
const FeatureWindowFunction &window_function, float *window,
|
||||
float *log_energy_pre_window = nullptr);
|
||||
|
||||
// Compute the inner product of two vectors
|
||||
float InnerProduct(const float *a, const float *b, int32_t n);
|
||||
|
||||
} // namespace knf
|
||||
|
||||
#endif // KALDI_NATIVE_FBANK_CSRC_FEATURE_WINDOW_H_
|
||||
+2883
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,24 @@
|
||||
// file-utils.cc
|
||||
//
|
||||
// Copyright (c) 2022-2023 Xiaomi Corporation
|
||||
|
||||
#include "file-utils.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
namespace sherpa_onnx {
|
||||
|
||||
bool FileExists(const std::string &filename) {
|
||||
return std::ifstream(filename).good();
|
||||
}
|
||||
|
||||
void AssertFileExists(const std::string &filename) {
|
||||
if (!FileExists(filename)) {
|
||||
SHERPA_ONNX_LOG(FATAL) << filename << " does not exist!";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sherpa_onnx
|
||||
@@ -0,0 +1,28 @@
|
||||
// file-utils.h
|
||||
//
|
||||
// Copyright (c) 2022-2023 Xiaomi Corporation
|
||||
|
||||
#ifndef SHERPA_ONNX_CSRC_FILE_UTILS_H_
|
||||
#define SHERPA_ONNX_CSRC_FILE_UTILS_H_
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
namespace sherpa_onnx {
|
||||
|
||||
/** Check whether a given path is a file or not
|
||||
*
|
||||
* @param filename Path to check.
|
||||
* @return Return true if the given path is a file; return false otherwise.
|
||||
*/
|
||||
bool FileExists(const std::string &filename);
|
||||
|
||||
/** Abort if the file does not exist.
|
||||
*
|
||||
* @param filename The file to check.
|
||||
*/
|
||||
void AssertFileExists(const std::string &filename);
|
||||
|
||||
} // namespace sherpa_onnx
|
||||
|
||||
#endif // SHERPA_ONNX_CSRC_FILE_UTILS_H_
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) 2023 Xiaomi Corporation
|
||||
* Copyright (c) 2023 Pingfeng Luo
|
||||
*/
|
||||
|
||||
#include "hypothesis.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
namespace sherpa_onnx {
|
||||
|
||||
void Hypotheses::Add(Hypothesis hyp) {
|
||||
auto key = hyp.Key();
|
||||
auto it = hyps_dict_.find(key);
|
||||
if (it == hyps_dict_.end()) {
|
||||
hyps_dict_[key] = std::move(hyp);
|
||||
} else {
|
||||
it->second.log_prob = LogAdd<double>()(it->second.log_prob, hyp.log_prob);
|
||||
}
|
||||
}
|
||||
|
||||
Hypothesis Hypotheses::GetMostProbable(bool length_norm) const {
|
||||
if (length_norm == false) {
|
||||
return std::max_element(hyps_dict_.begin(), hyps_dict_.end(),
|
||||
[](const auto &left, auto &right) -> bool {
|
||||
return left.second.TotalLogProb() <
|
||||
right.second.TotalLogProb();
|
||||
})
|
||||
->second;
|
||||
} else {
|
||||
// for length_norm is true
|
||||
return std::max_element(
|
||||
hyps_dict_.begin(), hyps_dict_.end(),
|
||||
[](const auto &left, const auto &right) -> bool {
|
||||
return left.second.TotalLogProb() / left.second.ys.size() <
|
||||
right.second.TotalLogProb() / right.second.ys.size();
|
||||
})
|
||||
->second;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Hypothesis> Hypotheses::GetTopK(int32_t k, bool length_norm) const {
|
||||
k = std::max(k, 1);
|
||||
k = std::min(k, Size());
|
||||
|
||||
std::vector<Hypothesis> all_hyps = Vec();
|
||||
|
||||
if (length_norm == false) {
|
||||
std::partial_sort(all_hyps.begin(), all_hyps.begin() + k, all_hyps.end(),
|
||||
[](const auto &a, const auto &b) {
|
||||
return a.TotalLogProb() > b.TotalLogProb();
|
||||
});
|
||||
} else {
|
||||
// for length_norm is true
|
||||
std::partial_sort(all_hyps.begin(), all_hyps.begin() + k, all_hyps.end(),
|
||||
[](const auto &a, const auto &b) {
|
||||
return a.TotalLogProb() / a.ys.size() >
|
||||
b.TotalLogProb() / b.ys.size();
|
||||
});
|
||||
}
|
||||
|
||||
return {all_hyps.begin(), all_hyps.begin() + k};
|
||||
}
|
||||
|
||||
const std::vector<int32_t> GetHypsRowSplits(
|
||||
const std::vector<Hypotheses> &hyps) {
|
||||
std::vector<int32_t> row_splits;
|
||||
row_splits.reserve(hyps.size() + 1);
|
||||
|
||||
row_splits.push_back(0);
|
||||
int32_t s = 0;
|
||||
for (const auto &h : hyps) {
|
||||
s += h.Size();
|
||||
row_splits.push_back(s);
|
||||
}
|
||||
|
||||
return row_splits;
|
||||
}
|
||||
|
||||
} // namespace sherpa_onnx
|
||||
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* Copyright (c) 2023 Xiaomi Corporation
|
||||
* Copyright (c) 2023 Pingfeng Luo
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SHERPA_ONNX_CSRC_HYPOTHESIS_H_
|
||||
#define SHERPA_ONNX_CSRC_HYPOTHESIS_H_
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "onnxruntime_cxx_api.h" // NOLINT
|
||||
#include "context-graph.h"
|
||||
#include "math.h"
|
||||
#include "onnx-utils.h"
|
||||
|
||||
namespace sherpa_onnx {
|
||||
|
||||
struct Hypothesis {
|
||||
// The predicted tokens so far. Newly predicated tokens are appended.
|
||||
std::vector<int64_t> ys;
|
||||
|
||||
// timestamps[i] contains the frame number after subsampling
|
||||
// on which ys[i] is decoded.
|
||||
std::vector<int32_t> timestamps;
|
||||
|
||||
// The total score of ys in log space.
|
||||
// It contains only acoustic scores
|
||||
double log_prob = 0;
|
||||
|
||||
// LM log prob if any.
|
||||
double lm_log_prob = 0;
|
||||
|
||||
// the nn lm score for next token given the current ys
|
||||
CopyableOrtValue nn_lm_scores;
|
||||
// the nn lm states
|
||||
std::vector<CopyableOrtValue> nn_lm_states;
|
||||
|
||||
const ContextState *context_state;
|
||||
|
||||
// TODO(fangjun): Make it configurable
|
||||
// the minimum of tokens in a chunk for streaming RNN LM
|
||||
int32_t lm_rescore_min_chunk = 2; // a const
|
||||
|
||||
int32_t num_trailing_blanks = 0;
|
||||
|
||||
Hypothesis() = default;
|
||||
Hypothesis(const std::vector<int64_t> &ys, double log_prob,
|
||||
const ContextState *context_state = nullptr)
|
||||
: ys(ys), log_prob(log_prob), context_state(context_state) {}
|
||||
|
||||
double TotalLogProb() const { return log_prob + lm_log_prob; }
|
||||
|
||||
// If two Hypotheses have the same `Key`, then they contain
|
||||
// the same token sequence.
|
||||
std::string Key() const {
|
||||
// TODO(fangjun): Use a hash function?
|
||||
std::ostringstream os;
|
||||
std::string sep;
|
||||
for (auto i : ys) {
|
||||
os << sep << i;
|
||||
sep = "-";
|
||||
}
|
||||
return os.str();
|
||||
}
|
||||
|
||||
// For debugging
|
||||
std::string ToString() const {
|
||||
std::ostringstream os;
|
||||
os << "(" << Key() << ", " << log_prob << ")";
|
||||
return os.str();
|
||||
}
|
||||
};
|
||||
|
||||
class Hypotheses {
|
||||
public:
|
||||
Hypotheses() = default;
|
||||
|
||||
explicit Hypotheses(std::vector<Hypothesis> hyps) {
|
||||
for (auto &h : hyps) {
|
||||
hyps_dict_[h.Key()] = std::move(h);
|
||||
}
|
||||
}
|
||||
|
||||
explicit Hypotheses(std::unordered_map<std::string, Hypothesis> hyps_dict)
|
||||
: hyps_dict_(std::move(hyps_dict)) {}
|
||||
|
||||
// Add hyp to this object. If it already exists, its log_prob
|
||||
// is updated with the given hyp using log-sum-exp.
|
||||
void Add(Hypothesis hyp);
|
||||
|
||||
// Get the hyp that has the largest log_prob.
|
||||
// If length_norm is true, hyp's log_prob is divided by
|
||||
// len(hyp.ys) before comparison.
|
||||
Hypothesis GetMostProbable(bool length_norm) const;
|
||||
|
||||
// Get the k hyps that have the largest log_prob.
|
||||
// If length_norm is true, hyp's log_prob is divided by
|
||||
// len(hyp.ys) before comparison.
|
||||
std::vector<Hypothesis> GetTopK(int32_t k, bool length_norm) const;
|
||||
|
||||
int32_t Size() const { return hyps_dict_.size(); }
|
||||
|
||||
std::string ToString() const {
|
||||
std::ostringstream os;
|
||||
for (const auto &p : hyps_dict_) {
|
||||
os << p.second.ToString() << "\n";
|
||||
}
|
||||
return os.str();
|
||||
}
|
||||
|
||||
const auto begin() const { return hyps_dict_.begin(); }
|
||||
const auto end() const { return hyps_dict_.end(); }
|
||||
|
||||
auto begin() { return hyps_dict_.begin(); }
|
||||
auto end() { return hyps_dict_.end(); }
|
||||
|
||||
void Clear() { hyps_dict_.clear(); }
|
||||
|
||||
private:
|
||||
// Return a list of hyps contained in this object.
|
||||
std::vector<Hypothesis> Vec() const {
|
||||
std::vector<Hypothesis> ans;
|
||||
ans.reserve(hyps_dict_.size());
|
||||
for (const auto &p : hyps_dict_) {
|
||||
ans.push_back(p.second);
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
private:
|
||||
using Map = std ::unordered_map<std::string, Hypothesis>;
|
||||
Map hyps_dict_;
|
||||
};
|
||||
|
||||
const std::vector<int32_t> GetHypsRowSplits(
|
||||
const std::vector<Hypotheses> &hyps);
|
||||
|
||||
} // namespace sherpa_onnx
|
||||
|
||||
#endif // SHERPA_ONNX_CSRC_HYPOTHESIS_H_
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user