Compare commits

..

116 Commits

Author SHA1 Message Date
Tom Boccuto e61c01d496 Use unordered_set for nonsilence lookup in GetSpkVector (#2035)
Replaces O(n^2) std::find scan with O(1)-avg hash-set lookup,
addressing the existing "would be nice to have faster search"
comment. Total cost drops from O(num_frames * |nonsilence|) to
O(num_frames + |nonsilence|).
2026-07-02 23:03:34 +03:00
Kevin Brown 3bc886a6c4 Add SpeechService constructor accepting a caller-supplied AudioRecord (#2044)
Allows callers to control the audio input device - for example, to pin
recording to the built-in microphone when an external USB camera without
a microphone causes Android to silently select the wrong input source.

The new overload (Recognizer, float, AudioRecord) mirrors the existing
(Recognizer, float) signature. Ownership semantics differ: the caller
supplies and owns the recorder, so on failure this constructor throws
without releasing it, leaving cleanup to the caller.

Co-authored-by: Kevin Brown <git@whidit.com>
2026-06-04 20:54:55 +03:00
Kevin Brown e81a422289 Add getAudioSessionId() getter to SpeechService (#2042)
Expose the AudioRecord session ID so callers can attach Android audio
effects (e.g. NoiseSuppressor, AcousticEchoCanceler) to the recording
session without subclassing or reflection.

Co-authored-by: Kevin Brown <git@whidit.com>
2026-05-29 08:50:13 +03:00
Vladimir Ulianitsky 9adbd76c77 Add Ruby gem (#2022)
* Init gem

* WIP

* WIP

* WIP: Gemify

* WIP

* Try ffi_gen

* Revert "Try ffi_gen"

This reverts commit a54e56b35a1bdc079dbe122aff47d79038d4e52f.

* Vibecode 1

* Vibecode 2

* rework progressbar

* Some ref + test_captcha

* Return deleted by Claude

* Draft precompiled packaging

* Review and refactor C bindings

* ref OwnedString

* Rename methods and add notes

* rubocop

* Fix progressbar, add examples

* Ref spec

* Ref spec - final

* Final ver

* Add srt, other fixes

* Move to ruby dir

---------

Co-authored-by: Nickolay V. Shmyrev <nshmyrev@gmail.com>
2026-02-22 11:51:10 +03:00
Nickolay Shmyrev 625e44c626 Bump JNA requirements to 5.18.1 and update version 2025-12-08 21:47:46 +01:00
Nickolay Shmyrev 488fc44d71 Add missing file to git, fixes #2001 2025-10-24 13:10:51 +02:00
Adam Jaso a428d65966 Add Golang GPU Batch API and example (#1983)
Co-authored-by: ajaso <github@ajaso.net>
2025-09-10 16:13:51 +03:00
Nickolay Shmyrev 056a2ad548 Updates for recent android 2025-09-03 09:46:07 +02:00
Nickolay Shmyrev 07a0ddb467 Add endpointer mode to java and csharp API 2025-09-03 07:14:40 +02:00
resignedScientist 24f0e0cff8 add 16 KB page size support (#1976)
Co-authored-by: Norman Laudien <kontakt@norman-laudien.de>
2025-08-25 02:08:01 +03:00
Utkarsh Gual 0f364e3a44 Fix typo in encoding in transcribe_scp.py (#1955)
Fix typo in encoding in transcribe_scp.py. uft-8 -> utf-8
2025-07-11 23:02:00 +03:00
Nickolay Shmyrev eabd80a848 Support MUSL variant in packging 2025-05-01 11:20:40 +02:00
Nickolay Shmyrev 780ba2f0b7 Add MUSL build 2025-05-01 11:06:56 +02:00
Nickolay Shmyrev 47509f7f9c Properly rebuilt fst 2025-05-01 09:05:17 +02:00
Md Husain Thekiya 4bf3370826 Fix: Raise exception in Node.js binding when model creation fails (#716) (#1714) 2025-03-05 19:16:56 +03:00
Matt Kenefick cf67ed6cd9 Support ARM 64 in NodeJS (#1655)
Identify and support ARM64 in the NodeJS vosk-api
2024-11-14 00:23:16 +03:00
Nickolay Shmyrev 0979c46766 Add speaker vector to n-best results. Fixes issue #1647 2024-10-24 15:34:05 +02:00
Nickolay V. Shmyrev eeab22ed98 Added postprocessor. Fixes #1641 2024-10-11 00:05:08 +03:00
Paschalis M a9f27eb11d Update README.md with training details (#1637) 2024-09-26 16:29:42 +03:00
Nickolay Shmyrev 1b308a3017 Added Win ARM64 build 2024-08-23 21:36:40 +02:00
Nickolay Shmyrev f5540085b5 Add text processor wrapper for java 2024-08-22 03:13:44 +02:00
Clocks c64c3daa3b Kotlin updates (#1596)
* Add TextProcessor.kt

* Add Recognizer end pointer functions

* Properly close input stream

Mirror of 5d09ee8

* Bump android compile sdk to 34

* Update everything

All dependencies, etc
2024-06-19 22:22:50 +03:00
Nickolay Shmyrev cc48ff9567 Properly count active states 2024-06-17 07:24:51 +02:00
johngebbie 7358c799b1 go: Add SetEndpointerDelays (#1570) 2024-05-06 08:33:26 +03:00
Nickolay Shmyrev a7bf6a51e2 Bump version 2024-04-22 14:39:57 +02:00
Nickolay Shmyrev 72797111db Fix timeouts for endpointer 2024-04-22 14:29:39 +02:00
__Rylex__ 40937b6bcb Kaldi remove lm target because rnnlm has lm (#1543)
* Update Dockerfile.win

* Update Dockerfile.dockcross

* Update Dockerfile.manylinux

* Update Dockerfile.dockcross-manylinux

* Update Dockerfile.manylinux-mkl

* Update Dockerfile.win32

* Update build-vosk.sh
2024-04-01 15:58:09 +03:00
Nickolay Shmyrev 7da70c6107 Add test for ITN 2024-03-29 12:57:01 +01:00
Nickolay Shmyrev 2426225d74 Add postprocessor 2024-03-29 12:44:54 +01:00
Nickolay V. Shmyrev c4d32a2293 Inverse text normalization with FSTs (#1545)
* Add ITN from Wetext
2024-03-29 14:32:05 +03:00
__Rylex__ 6f7fe0e417 fix typp (#1519) 2024-02-21 11:00:19 +03:00
Nickolay Shmyrev aba84973b1 Update to latest gradio 2023-12-15 00:23:37 +01:00
Nickolay Shmyrev 339b1c5d00 Fix t_max endpointer config and introduce t_start_max for silent inputs. 2023-12-13 04:45:30 +01:00
Nickolay Shmyrev a35728fa67 Simplify server argument 2023-11-27 15:40:58 +01:00
Nickolay Shmyrev 322a57b512 Fix json output format 2023-11-27 15:39:49 +01:00
Gonzalo a21d1ad051 feat: enable usage of remote server and not only localhost (#1375) 2023-11-27 17:22:59 +03:00
Nickolay Shmyrev a47fa9147b Add endpointer delays parameter 2023-11-27 15:29:31 +01:00
Wire dfb76f0126 go: do not include libdl if building on Windows (#1464) 2023-11-12 01:26:17 +03:00
Nickolay Shmyrev c3e3fc002f Add online endpointer reconfiguration 2023-10-16 13:40:32 +02:00
Nickolay Shmyrev 62fc308bdf Sometimes python interpreter destroys the handle on exit before
the object. See

https://foss.heptapod.net/pypy/cffi/-/issues/340
2023-09-04 00:33:42 +02:00
Dmitri Soloviev 60c0e33901 simplify go example (#1430) 2023-08-12 23:04:46 +03:00
jsbxyyx 5d09ee8b2b close source input stream (#1) (#1428) 2023-08-11 00:55:33 +03:00
JohnGebbie 12f29a3415 go: Add SetGrm (#1346) 2023-04-29 05:35:01 +03:00
Nickolay Shmyrev d56221bcf2 Add mkl docker build 2023-04-06 02:38:06 +02:00
Nickolay Shmyrev a2eb39d2d7 Set number of MKL threads 2023-04-06 02:10:53 +02:00
Anton Romanov 128c216c61 Fix vosk-transcriber cli json format (#1304)
Fix error UnboundLocalError: local variable 'monologue' referenced before assignment in transcriber.py line 102
2023-03-29 03:12:16 +03:00
mason-acronode da87d0dad3 c# supports GPU inference (#1307)
* Added Batch P/Invoke to be able to use GPU based inference

* Modified Result method

* Modified Result method

* Fixed code to call BatchModel

---------

Co-authored-by: minsik.sim <minsik.sim@cyara.com>
2023-03-25 12:28:14 +03:00
Nickolay Shmyrev 9cee2899bb Update publishing plugin and simplify gradle build 2023-03-15 22:50:42 +01:00
Doomsdayrs a40742bbaf Kotlin: Numerous Improvements (#1274)
* kotlin: bump version for future release

* kotlin: Unify dependency versions

* kotlin: Add AUTHORS

* kotlin: Add MAINTENANCE.md

* kotlin: Add README.md

* kotlin: Update common/Vosk.kt documentation

* kotlin: Fix indentation in build.gradle.kts

* kotlin: Add .idea files to unify how IDEs work

* kotlin: Reformat all code

* kotlin: Update all code headers

* kotlin: Add @JvmStatic to objects

* kotlin: Use Consumer instead of Kotlin lambda in StorageService.kt

* kotlin: Annotate SpeechService.kt as throwing IOException

* kotlin: Add gradle migration settings

* kotlin: Add Deprecated annotation to jvm/LibVosk.setLogLevel

* kotlin: Overhaul exceptions

* Update documentation

* Add function for creating a Recognizer with a proper list

* Update documentation
2023-03-03 19:12:54 +03:00
Doomsdayrs d714dff8d3 Add Kotlin Multiplatform (#1241)
* Init

* Implement common

* Implement JVM target

* Ignore build/ directory

* Implement android

* Disable kotlin/native targets

* Remove jCenter()

* Upgrade android tools to 7.3.0

* Enforce not null for essential windows operations

* Add license headers

* Add maven-publish

* Add dokka for javadoc

* Add disabled kotlin/native for linux

This is disabled currently due to vosk
 not having a proper installation method on linux yet.

* Add empty constructors for JVM classes

* Fix library initialization for JVM

* Add DecoderDemo

* Add serialization support

* Convert no arg functions with returns to val properties

* Properly implement DecoderTest

* Import other android classes

* Properly name json parser as voskJson

* Add license headers to native target

* Unify free functions with Freeable interface

This makes it easy for Kotlin/Native to use a "use" function to
 auto free after use of a Freeable.

* Document JVM default constructor

* Add JVM specific constructors with NIO/IO

* Inline default constructors in Common

* Properly handle invalid paths

* Add android publishing block

* Merge serialization into common

* Update to Kotlin 1.8.10

* Update to JNA 5.13.0

* Put native target behind environment variable or override.

Due to a lack of official packaging,
 this is not possible currently.

* Properly handle Recognizer.acceptWaveform exceptions

* Add flow extension for converting (InputStream->Recognizer) to a flow.

* Use ByteFlow for waveform flow instead.

This allows custom readers, and more advanced backends,
 such as the Dicio example in DecoderTest.kt .
2023-02-17 03:47:20 +03:00
Nickolay Shmyrev 790c564d14 Ignore non-audio files in folder transcription 2023-02-10 00:57:14 +01:00
Nickolay Shmyrev 988ba3f257 Bump JNA dependency everywhere 2023-02-08 22:55:22 +01:00
Nickolay Shmyrev 2796da0a50 Sync android lib to java lib, throw exceptions on errors 2023-02-08 22:07:02 +01:00
Nickolay Shmyrev d9c6fc4958 Check if ivector folder is required 2023-02-08 21:00:15 +01:00
Nickolay Shmyrev fff40ce1a1 Gradle doesn't understand + in version 2022-12-18 22:25:26 +01:00
Nicholas Albion 2deb7de1dd Fixed #883 - use right class loader (#1228) 2022-12-17 14:45:15 +03:00
Nicholas Albion 7d87267c31 copied documentation to javadoc (#1230)
Co-authored-by: Nickolay V. Shmyrev <nshmyrev@gmail.com>
2022-12-17 14:41:33 +03:00
Nicholas Albion 64d84b01b1 added Recognizer.setGrammar(String) for java (#1229) 2022-12-17 11:43:27 +03:00
Nickolay Shmyrev cf2560c9f8 Bump debian requirement 2022-12-14 17:58:40 +01:00
Nickolay Shmyrev 800d3d7089 Bump version 2022-12-14 17:38:53 +01:00
Nickolay Shmyrev b471207f7a Protect against segmentation faults with corrupted model folder 2022-12-14 17:32:55 +01:00
Edwin Hendriks 25c59b52e3 add -m (language model) argument to test_microphone.py (#1219)
* added language model as parameter

* fix fore more help explanation
2022-12-07 22:53:21 +03:00
Nickolay Shmyrev 4c72097478 Allow to configure GPU model path. See https://github.com/alphacep/vosk-server/issues/207 2022-11-19 01:13:42 +01:00
Tomas Mehdi 1053cfa0f8 JSON output_type with texts (#1180) 2022-10-26 22:41:04 +03:00
gmat 21a42cb6cd new output type json for gecko project (#1166)
add a output type `json` to get output file compatible with transcription file format for the gecko project annotation https://github.com/gong-io/gecko
2022-10-06 16:27:29 +03:00
Nickolay Shmyrev 16c4a0d985 Bump version 2022-09-25 21:00:24 +02:00
Nickolay Shmyrev 298253401a Update approach to machine detection on cross-compilation
See for details issue #1155
2022-09-25 21:00:24 +02:00
Nickolay Shmyrev 32aa980069 Allow to change grammar of existing recognizer. Probably can be faster though
if we do not reinit feature pipeline
2022-09-13 23:12:13 +02:00
vadimdddd 7b4d396eb1 fix quotes and import's order (#1143) 2022-09-13 21:51:45 +03:00
vadimdddd 36968fbb30 Fix python style issues (#1140)
* pylint_fix v.2

* init: fix quotes
2022-09-12 23:08:59 +03:00
vadimdddd 7474888801 Fix python style issues (#1131) 2022-09-09 00:13:02 +03:00
vadimdddd d46b7a43eb Split results by newlines (#1124) 2022-08-26 17:22:10 +03:00
vadimdddd 2376b32a8a Add SRTResult to KaldiRecognizer (#1080) 2022-08-24 12:48:18 +03:00
vadimdddd b8a88cc30c Add gradio recognizer (#1114)
Demonstrate speech recognition in gradio
2022-08-19 17:31:47 +03:00
Nickolay V. Shmyrev be0b117711 Add training colab 2022-08-17 19:11:45 +02:00
Nickolay V. Shmyrev 449c8ea5af Introduce stop stage espnet style 2022-08-17 14:30:56 +02:00
Nickolay V. Shmyrev 900da76652 Import script locally 2022-08-17 12:50:59 +02:00
Nickolay V. Shmyrev df0ee24084 Add adaptation colab 2022-08-17 12:38:17 +02:00
Nickolay V. Shmyrev d3d8f53156 Add Kaldi build colab 2022-08-17 10:02:43 +02:00
vadimdddd 6eee303d7e Fix decoding files with spaces (#1084)
Fix decoding files with spaces
2022-08-02 20:56:04 +03:00
Nickolay Shmyrev 053d71f5aa Bug fix in notebook 2022-08-01 22:59:38 +02:00
Nickolay Shmyrev 4fbbf5882c Add colab demo 2022-08-01 22:52:57 +02:00
Nickolay Shmyrev 7012103b3b Simplify the go interface. Move from []byte to strings. Fixes #1095 2022-07-31 09:58:33 +02:00
Nickolay Shmyrev 967024d20a Fix precedence of current path for dll load. Fixes issue #1096 2022-07-30 09:15:28 +02:00
Nicolas Dato b966a8078b Fix comment (#1091) 2022-07-26 23:07:25 +03:00
vadimdddd f63b015284 Add new dependency websockets (#1079)
add websockets to dependencies
2022-07-19 19:58:24 +03:00
Nickolay Shmyrev 0a9672d910 Attempt to build manylinux wheel 2022-07-15 00:40:38 +02:00
Nickolay Shmyrev 4ccccd0cd2 Bump version 2022-07-14 21:38:36 +02:00
Nickolay Shmyrev 354fb672a3 Merge branch 'master' of github.com:alphacep/vosk-api 2022-07-13 14:09:05 +02:00
Nickolay Shmyrev 73abf0740a Add Ruby bindings 2022-07-13 14:08:50 +02:00
Nickolay Shmyrev 81c82935ac Add usage example 2022-07-07 01:09:32 +02:00
Nickolay Shmyrev ac3ec56584 Add notes about Go and Rust 2022-07-05 01:18:08 +02:00
Nickolay Shmyrev 2cbe12d4d0 Add link to rust bindings 2022-07-05 01:17:43 +02:00
Nickolay Shmyrev 1475b0e986 Async ffmpeg for trancriber and code refactoring 2022-07-05 01:13:05 +02:00
vadimdddd 8ceab0b9b1 Add vosk-server to transcriber (#1024)
Allow to use vosk-server for transcription
2022-07-04 13:50:38 +03:00
Nickolay Shmyrev 1496b597d3 Fixes issues with models without alignment 2022-07-03 13:00:05 +02:00
vadimdddd 983519e629 Add compatibility with python 3.5 issue(#1021) (#1041)
Added format for python3.5
2022-06-29 18:00:40 +03:00
Nickolay Shmyrev 58fa98ccd7 Wait for microphone to finalize. Fixes crash in issue #1034 2022-06-28 01:01:53 +03:00
Nickolay Shmyrev a7bc5a22d4 We support Polish now 2022-06-05 23:03:57 +02:00
Rémi Bernon 23bbff0b56 build: Add a CMakeLists.txt. (#992) 2022-06-04 17:34:42 +03:00
Kay Gosho a47b58e2f4 Specify device to get microphone input (#993) 2022-06-03 15:33:50 +03:00
Nickolay Shmyrev 8cf64ee93e Add RISCV support 2022-06-02 01:44:16 +02:00
vadimdddd 630edeb3d6 fixed error if parent directory doesn't exist #988 (#990)
Create missing parent directory
2022-06-01 17:52:16 +03:00
Nickolay Shmyrev b1b216d4c8 Bump versions 2022-05-26 15:22:16 +02:00
Nickolay Shmyrev 55dd29b0ff Fix transcriber bugs while processing folder 2022-05-26 14:05:54 +02:00
Nickolay Shmyrev ea0568a38d Change target binary library name and copy header 2022-05-26 13:29:14 +02:00
Nickolay Shmyrev 298c86d0d4 Bump version and add python requirements 2022-05-26 12:46:06 +02:00
Nickolay Shmyrev 859420809b Add extra comments 2022-05-26 11:45:16 +02:00
Nickolay Shmyrev 0fe3a89768 Update to 0.3.40 2022-05-26 11:41:14 +02:00
Nickolay Shmyrev 5b892fbfc5 Architecture should be universal2 2022-05-25 19:52:05 +03:00
Nickolay Shmyrev fb4ed21a7f Shave transcriber code 2022-05-24 22:53:57 +02:00
vadimdddd 4209f3a9fe Vosk model loader(#871) (#941)
* methods get_model_by_name, get_model_by_lang, get_model were added into the model class

* importing modules changed to using components; introduced constant MODELS_HOME_DIR; simplified code

* added new model folders into init; changed samples and transcriber bin for new mode loader

* changed back in cli.py lang arg to args.lang

* added 3 directories instead of 1 to check for models

* cli.py: added 3 args instead of 1 for model; __init__.py: changed script get_model_path for run get_model_by_name/lang inside current directory

* deleted default env var

* cli.py: changed arg_name; __init__.py: changed const name, changed model loading only for last directory

* deleted unused method

* changed by_name, by_lang methods, added download_model method

* deleted env variable initialization

* deleted print()

* deteled unused modules

* added progress_bar, added folder AppData/Local/vosk for model search

* changed download_model methond; added my_hook method
2022-05-24 21:06:13 +03:00
Nickolay Shmyrev ff2c80d5f1 Add x86 crossbuild 2022-05-24 20:00:03 +02:00
Nickolay Shmyrev 3a07a08121 Universal binary in node 2022-05-24 17:30:49 +02:00
Nickolay Shmyrev 592da81a8c Update nuget package for 0.3.38 2022-05-24 17:05:14 +02:00
Nickolay Shmyrev def8c93711 Remove semicolon 2022-05-24 15:23:03 +03:00
192 changed files with 23016 additions and 783 deletions
+21
View File
@@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.13)
project(vosk-api CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_EXTENSIONS OFF)
add_library(vosk
src/language_model.cc
src/model.cc
src/recognizer.cc
src/spk_model.cc
src/vosk_api.cc
src/postprocessor.cc
)
find_package(kaldi REQUIRED)
target_link_libraries(vosk PUBLIC kaldi-base kaldi-online2 kaldi-rnnlm fstngram)
include(GNUInstallDirs)
install(TARGETS vosk DESTINATION ${CMAKE_INSTALL_LIBDIR})
install(FILES src/vosk_api.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
+3 -2
View File
@@ -4,14 +4,15 @@ Vosk is an offline open source speech recognition toolkit. It enables
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,
Ukrainian, Kazakh, Swedish, Japanese, Esperanto, Hindi, Czech. More to come.
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,
+26 -38
View File
@@ -4,62 +4,50 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.0'
classpath 'com.vanniktech:gradle-maven-publish-plugin:0.18.0'
classpath 'com.android.tools.build:gradle:8.13.0'
classpath 'com.vanniktech:gradle-maven-publish-plugin:0.34.0'
}
}
allprojects {
version = '0.3.38'
version = '0.3.75'
}
subprojects {
apply plugin: 'com.android.library'
apply plugin: 'maven-publish'
apply plugin: 'com.vanniktech.maven.publish'
plugins.withId('com.vanniktech.maven.publish') {
mavenPublish {
group = 'com.alphacephei'
version = version
sonatypeHost = 's01'
androidVariantToPublish = 'release'
}
}
repositories {
google()
mavenCentral()
}
publishing {
publications {
aar(MavenPublication) {
groupId 'com.alphacephei'
version version
pom {
url = 'http://www.alphacephei.com.com/vosk/'
licenses {
license {
name = 'The Apache License, Version 2.0'
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
developers {
developer {
id = 'com.alphacephei'
name = 'Alpha Cephei Inc'
email = 'contact@alphacephei.com'
}
}
scm {
connection = 'scm:git:git://github.com/alphacep/vosk-api.git'
url = 'https://github.com/alphacep/vosk-api/'
}
mavenPublishing {
publishToMavenCentral()
signAllPublications()
}
mavenPublishing {
pom {
url = 'http://www.alphacephei.com.com/vosk/'
licenses {
license {
name = 'The Apache License, Version 2.0'
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
developers {
developer {
id = 'com.alphacephei'
name = 'Alpha Cephei Inc'
email = 'contact@alphacephei.com'
}
}
scm {
connection = 'scm:git:git://github.com/alphacep/vosk-api.git'
url = 'https://github.com/alphacep/vosk-api/'
}
}
}
}
+12 -7
View File
@@ -29,7 +29,7 @@ set -x
OS_NAME=`echo $(uname -s) | tr '[:upper:]' '[:lower:]'`
ANDROID_TOOLCHAIN_PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/${OS_NAME}-x86_64
WORKDIR_BASE=`pwd`/build
PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/${OS_NAME}-x86_64/bin:$PATH
PATH=$ANDROID_TOOLCHAIN_PATH/bin:$PATH
OPENFST_VERSION=1.8.0
for arch in armeabi-v7a arm64-v8a x86_64 x86; do
@@ -45,6 +45,7 @@ case $arch in
CC=armv7a-linux-androideabi21-clang
CXX=armv7a-linux-androideabi21-clang++
ARCHFLAGS="-mfloat-abi=softfp -mfpu=neon"
PAGESIZE_LDFLAGS=""
;;
arm64-v8a)
BLAS_ARCH=ARMV8
@@ -54,6 +55,8 @@ case $arch in
CC=aarch64-linux-android21-clang
CXX=aarch64-linux-android21-clang++
ARCHFLAGS=""
# Ensure compatibility with 16KiB page size devices
PAGESIZE_LDFLAGS="-Wl,-z,common-page-size=4096 -Wl,-z,max-page-size=16384"
;;
x86_64)
BLAS_ARCH=ATOM
@@ -63,6 +66,7 @@ case $arch in
CC=x86_64-linux-android21-clang
CXX=x86_64-linux-android21-clang++
ARCHFLAGS=""
PAGESIZE_LDFLAGS=""
;;
x86)
BLAS_ARCH=ATOM
@@ -72,6 +76,7 @@ case $arch in
CC=i686-linux-android21-clang
CXX=i686-linux-android21-clang++
ARCHFLAGS=""
PAGESIZE_LDFLAGS=""
;;
esac
@@ -79,16 +84,16 @@ mkdir -p $WORKDIR/local/lib
# openblas first
cd $WORKDIR
git clone -b v0.3.13 --single-branch https://github.com/xianyi/OpenBLAS
make -C OpenBLAS TARGET=$BLAS_ARCH ONLY_CBLAS=1 AR=$AR CC=$CC HOSTCC=gcc ARM_SOFTFP_ABI=1 USE_THREAD=0 NUM_THREADS=1 -j4
git clone -b v0.3.20 --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 -j 8
make -C OpenBLAS install PREFIX=$WORKDIR/local
# CLAPACK
cd $WORKDIR
git clone -b v3.2.1 --single-branch https://github.com/alphacep/clapack
mkdir -p clapack/BUILD && cd clapack/BUILD
cmake -DCMAKE_C_FLAGS=$ARCHFLAGS -DCMAKE_C_COMPILER_TARGET=$HOST \
-DCMAKE_C_COMPILER=$CC -DCMAKE_SYSTEM_NAME=Generic -DCMAKE_AR=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/${OS_NAME}-x86_64/bin/$AR \
cmake -DCMAKE_C_FLAGS="$ARCHFLAGS" -DCMAKE_C_COMPILER_TARGET=$HOST \
-DCMAKE_C_COMPILER=$CC -DCMAKE_SYSTEM_NAME=Generic -DCMAKE_AR=$ANDROID_TOOLCHAIN_PATH/bin/$AR \
-DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY \
-DCMAKE_CROSSCOMPILING=True ..
make -j 8 -C F2CLIBS/libf2c
@@ -118,7 +123,7 @@ CXX=$CXX AR=$AR RANLIB=$RANLIB CXXFLAGS="$ARCHFLAGS -O3 -DFST_NO_DYNAMIC_LINKING
--fst-root=${WORKDIR}/local --fst-version=${OPENFST_VERSION}
make -j 8 depend
cd $WORKDIR/kaldi/src
make -j 8 online2 lm rnnlm
make -j 8 online2 rnnlm
# Vosk-api
cd $WORKDIR
@@ -129,7 +134,7 @@ make -j 8 -C ${WORKDIR_BASE}/../../../src \
OPENFST_ROOT=${WORKDIR}/local \
OPENBLAS_ROOT=${WORKDIR}/local \
CXX=$CXX \
EXTRA_LDFLAGS="-llog -static-libstdc++ -Wl,-soname,libvosk.so"
EXTRA_LDFLAGS="-llog -static-libstdc++ -Wl,-soname,libvosk.so ${PAGESIZE_LDFLAGS}"
cp $WORKDIR/vosk/libvosk.so $WORKDIR/../../src/main/jniLibs/$arch/libvosk.so
done
+11 -27
View File
@@ -3,14 +3,15 @@ def pomName = "Vosk Android"
def pomDescription = "Vosk speech recognition library for Android"
android {
compileSdkVersion 29
namespace 'org.vosk'
compileSdkVersion 36
defaultConfig {
minSdkVersion 21
targetSdkVersion 29
versionCode 6
targetSdkVersion 36
versionCode 10
versionName = version
archivesBaseName = archiveName
ndkVersion = "22.1.7171670"
ndkVersion = "28.2.13676358"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
@@ -24,32 +25,15 @@ task buildVosk(type: Exec) {
}
dependencies {
api 'net.java.dev.jna:jna:4.4.0@aar'
api 'net.java.dev.jna:jna:5.18.1@aar'
}
//preBuild.dependsOn buildVosk
publishing {
publications {
aar(MavenPublication) {
artifactId = archiveName
artifact("$buildDir/outputs/aar/$archiveName-release.aar")
pom {
name = pomName
description = pomDescription
}
//generate pom nodes for dependencies
pom.withXml {
def dependenciesNode = asNode().appendNode('dependencies')
configurations.implementation.allDependencies.each { dependency ->
if (dependency.name != 'unspecified') {
def dependencyNode = dependenciesNode.appendNode('dependency')
dependencyNode.appendNode('groupId', dependency.group)
dependencyNode.appendNode('artifactId', dependency.name)
dependencyNode.appendNode('version', dependency.version)
}
}
}
}
mavenPublishing {
coordinates("com.alphacephei", archiveName, version)
pom {
name = pomName
description = pomDescription
}
}
+1 -1
View File
@@ -1,3 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.vosk">
<manifest>
</manifest>
@@ -52,10 +52,30 @@ public class LibVosk {
public static native String vosk_recognizer_partial_result(Pointer recognizer);
public static native void vosk_recognizer_set_grm(Pointer recognizer, String grammar);
public static native void vosk_recognizer_reset(Pointer recognizer);
public static native void vosk_recognizer_set_endpointer_mode(Pointer recognizer, int mode);
public static native void vosk_recognizer_set_endpointer_delays(Pointer recognizer, float t_start_max, float t_end, float t_max);
public static native void vosk_recognizer_free(Pointer recognizer);
public static native Pointer vosk_text_processor_new(String verbalizer, String tagger);
public static native void vosk_text_processor_free(Pointer processor);
public static native String vosk_text_processor_itn(Pointer processor, String input);
/**
* 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());
}
@@ -1,13 +1,18 @@
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) {
public Model(String path) throws IOException {
super(LibVosk.vosk_model_new(path));
if (getPointer() == null) {
throw new IOException("Failed to create a model");
}
}
@Override
@@ -1,36 +1,163 @@
package org.vosk;
import com.sun.jna.PointerType;
import java.io.IOException;
public class Recognizer extends PointerType implements AutoCloseable {
public Recognizer(Model model, float sampleRate) {
/**
* 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");
}
}
public Recognizer(Model model, float sampleRate, SpeakerModel spkModel) {
/**
* 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 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.
* @param spkModel speaker model for speaker identification
* @throws IOException if the recognizer could not be created
*/
public Recognizer(Model model, float sampleRate, SpeakerModel spkModel) throws IOException {
super(LibVosk.vosk_recognizer_new_spk(model.getPointer(), sampleRate, spkModel.getPointer()));
if (getPointer() == null) {
throw new IOException("Failed to create a recognizer");
}
}
public Recognizer(Model model, float sampleRate, String grammar) {
/**
* 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 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.
* @param grammar The string with the list of phrases to recognize as JSON array of strings,
* for example "["one two three four five", "[unk]"]".
* @throws IOException if the recognizer could not be created
*/
public Recognizer(Model model, float sampleRate, String grammar) throws IOException {
super(LibVosk.vosk_recognizer_new_grm(model.getPointer(), sampleRate, grammar));
if (getPointer() == null) {
throw new IOException("Failed to create a recognizer");
}
}
/**
* Configures recognizer to output n-best results.
*
* <pre>
* {
* "alternatives": [
* { "text": "one two three four five", "confidence": 0.97 },
* { "text": "one two three for five", "confidence": 0.03 },
* ]
* }
* </pre>
*
* @param maxAlternatives - maximum alternatives to return from recognition results
*/
public void setMaxAlternatives(int maxAlternatives) {
LibVosk.vosk_recognizer_set_max_alternatives(this.getPointer(), maxAlternatives);
}
/** Enables words with times in the output
*
* <pre>
* "result" : [{
* "conf" : 1.000000,
* "end" : 1.110000,
* "start" : 0.870000,
* "word" : "what"
* }, {
* "conf" : 1.000000,
* "end" : 1.530000,
* "start" : 1.110000,
* "word" : "zero"
* }, {
* "conf" : 1.000000,
* "end" : 1.950000,
* "start" : 1.530000,
* "word" : "zero"
* }, {
* "conf" : 1.000000,
* "end" : 2.340000,
* "start" : 1.950000,
* "word" : "zero"
* }, {
* "conf" : 1.000000,
* "end" : 2.610000,
* "start" : 2.340000,
* "word" : "one"
* }],
* </pre>
*
* @param words - boolean value
*/
public void setWords(boolean words) {
LibVosk.vosk_recognizer_set_words(this.getPointer(), words);
}
/**
* Like above return words and confidences in partial results.
*
* @param partial_words - boolean value
*/
public void setPartialWords(boolean partial_words) {
LibVosk.vosk_recognizer_set_partial_words(this.getPointer(), partial_words);
}
/**
* Adds speaker model to already initialized recognizer.
*
* Can add speaker recognition model to already created recognizer.
* Helps to initialize speaker recognition for grammar-based recognizer.
*
* @param spkModel Speaker recognition model
*/
public void setSpeakerModel(SpeakerModel spkModel) {
LibVosk.vosk_recognizer_set_spk_model(this.getPointer(), spkModel.getPointer());
}
/**
* 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
* @return 1 if silence is occurred and you can retrieve a new utterance with result method
* 0 if decoding continues
* -1 if exception occurred
*/
public boolean acceptWaveForm(byte[] data, int len) {
return LibVosk.vosk_recognizer_accept_waveform(this.getPointer(), data, len);
}
@@ -43,22 +170,104 @@ public class Recognizer extends PointerType implements AutoCloseable {
return LibVosk.vosk_recognizer_accept_waveform_f(this.getPointer(), data, len);
}
/**
* 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(this.getPointer());
}
/**
* Returns partial speech recognition.
*
* @return partial speech recognition text which is not yet finalized.
* result may change as recognizer process more data.
*
* <pre>
* {
* "partial" : "cyril one eight zero"
* }
* </pre>
*/
public String getPartialResult() {
return LibVosk.vosk_recognizer_partial_result(this.getPointer());
}
/**
* 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.
*
* @return speech result in JSON format.
*/
public String getFinalResult() {
return LibVosk.vosk_recognizer_final_result(this.getPointer());
}
/**
* Reconfigures recognizer to use grammar.
*
* @param grammar Set of phrases in JSON array of strings or "[]" to use default model graph.
* @see #Recognizer(Model, float, String)
*/
public void setGrammar(String grammar) {
LibVosk.vosk_recognizer_set_grm(this.getPointer(), grammar);
}
/**
* Resets the recognizer.
* Resets current results so the recognition can continue from scratch.
*/
public void reset() {
LibVosk.vosk_recognizer_reset(this.getPointer());
}
/**
* Endpointer delay mode
*/
public class EndpointerMode {
public static final int DEFAULT = 0;
public static final int SHORT = 1;
public static final int LONG = 2;
public static final int VERY_LONG = 3;
}
/**
* Configures endpointer mode for recognizer
*/
public void setEndpointerMode(int mode) {
LibVosk.vosk_recognizer_set_endpointer_mode(this.getPointer(), mode);
}
/**
* Set endpointer delays
*
* @param t_start_max timeout for stopping recognition in case of initial silence (usually around 5.0)
* @param t_end timeout for stopping recognition in milliseconds after we recognized something (usually around 0.5 - 1.0)
* @param t_max timeout for forcing utterance end in milliseconds (usually around 20-30)
**/
public void setEndpointerDelays(float t_start_max, float t_end, float t_max) {
LibVosk.vosk_recognizer_set_endpointer_delays(this.getPointer(), t_start_max, t_end, t_max);
}
/**
* Releases recognizer object.
* Underlying model is also unreferenced and if needed, released.
*/
@Override
public void close() {
LibVosk.vosk_recognizer_free(this.getPointer());
@@ -1,13 +1,36 @@
package org.vosk;
import com.sun.jna.PointerType;
import java.io.IOException;
/**
* Helps to initialize speaker recognition for grammar-based recognizer.
*/
public class SpeakerModel extends PointerType implements AutoCloseable {
public SpeakerModel() {
}
public SpeakerModel(String path) {
/**
* Loads speaker model data from the file.
*
* The path must contain:
* - a config file: mfcc.conf
* - kaldi nnet: final.ext.raw
* - mean.vec
* - transform.mat
*
* @param path the path of the model on the filesystem
* @throws IOException if the model could not be created
*
* @see <a href="http://kaldi-asr.org/doc/structkaldi_1_1MfccOptions.html">Kaldi MfccOptions</a>
* @see <a href="http://kaldi-asr.org/doc/classkaldi_1_1nnet3_1_1Nnet.html">Kaldi Nnet</a>
*/
public SpeakerModel(String path) throws IOException {
super(LibVosk.vosk_spk_model_new(path));
if (getPointer() == null) {
throw new IOException("Failed to create a speaker model");
}
}
@Override
@@ -0,0 +1,21 @@
package org.vosk;
import com.sun.jna.PointerType;
public class TextProcessor extends PointerType implements AutoCloseable {
public TextProcessor() {
}
public TextProcessor(String verbalizer, String tagger) {
super(LibVosk.vosk_text_processor_new(verbalizer, tagger));
}
@Override
public void close() {
LibVosk.vosk_text_processor_free(this.getPointer());
}
public String itn(String input) {
return LibVosk.vosk_text_processor_itn(this.getPointer(), input);
}
}
@@ -19,9 +19,9 @@ import android.media.AudioRecord;
import android.media.MediaRecorder.AudioSource;
import android.os.Handler;
import android.os.Looper;
import android.annotation.SuppressLint;
import org.vosk.Recognizer;
import java.io.IOException;
/**
@@ -48,6 +48,7 @@ public class SpeechService {
*
* @throws IOException thrown if audio recorder can not be created for some reason.
*/
@SuppressLint("MissingPermission")
public SpeechService(Recognizer recognizer, float sampleRate) throws IOException {
this.recognizer = recognizer;
this.sampleRate = (int) sampleRate;
@@ -65,6 +66,51 @@ public class SpeechService {
}
}
/**
* Creates speech service with a caller-supplied {@link AudioRecord}.
* <p>
* Use this when you need to control the audio input device - for example,
* to pin recording to the built-in microphone when an external USB device
* without a microphone is present:
* <pre>
* AudioRecord recorder = new AudioRecord.Builder()
* .setAudioSource(MediaRecorder.AudioSource.VOICE_RECOGNITION)
* .setAudioFormat(format)
* .build();
* if (Build.VERSION.SDK_INT >= 28) {
* AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
* for (AudioDeviceInfo d : am.getDevices(AudioManager.GET_DEVICES_INPUTS)) {
* if (d.getType() == AudioDeviceInfo.TYPE_BUILTIN_MIC) {
* recorder.setPreferredDevice(d);
* break;
* }
* }
* }
* SpeechService service = new SpeechService(recognizer, 16000f, recorder);
* </pre>
* <p>
* The caller retains ownership of {@code recorder}: if this constructor
* throws, the recorder is <em>not</em> released. Call
* {@link AudioRecord#release()} yourself in that case.
*
* @param recognizer the Vosk recognizer
* @param sampleRate sample rate in Hz; must match {@code recorder}'s configuration
* @param recorder a fully-initialised {@link AudioRecord}
* @throws IOException if {@code recorder} is in STATE_UNINITIALIZED
*/
public SpeechService(Recognizer recognizer, float sampleRate, AudioRecord recorder)
throws IOException {
this.recognizer = recognizer;
this.sampleRate = (int) sampleRate;
this.recorder = recorder;
bufferSize = Math.round(this.sampleRate * BUFFER_SIZE_SECONDS);
if (recorder.getState() == AudioRecord.STATE_UNINITIALIZED) {
throw new IOException(
"Failed to initialize recorder. Microphone might be already in use.");
}
}
/**
* Starts recognition. Does nothing if recognition is active.
@@ -136,6 +182,19 @@ public class SpeechService {
return stopRecognizerThread();
}
/**
* Returns the audio session ID of the underlying {@link AudioRecord}.
* <p>
* The session ID can be used to attach audio effects such as
* {@link android.media.audiofx.NoiseSuppressor} to the recording session.
*
* @return audio session ID, or {@link AudioRecord#ERROR} if unavailable
*/
public int getAudioSessionId() {
return recorder.getAudioSessionId();
}
/**
* Shutdown the recognizer and release the recorder
*/
+9 -13
View File
@@ -3,11 +3,12 @@ def pomName = "Vosk English Model"
def pomDescription = "Small English model for Android"
android {
compileSdkVersion 29
namespace "org.vosk"
compileSdkVersion 36
defaultConfig {
minSdkVersion 21
targetSdkVersion 29
versionCode 6
targetSdkVersion 36
versionCode 10
versionName = version
archivesBaseName = archiveName
}
@@ -33,15 +34,10 @@ tasks.register('genUUID') {
preBuild.dependsOn(genUUID)
publishing {
publications {
aar(MavenPublication) {
artifactId = archiveName
artifact("$buildDir/outputs/aar/$archiveName-release.aar")
pom {
name = pomName
description = pomDescription
}
}
mavenPublishing {
coordinates("com.alphacephei", archiveName, version)
pom {
name = pomName
description = pomDescription
}
}
@@ -1,3 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.vosk.model.en">
<manifest>
</manifest>
+3
View File
@@ -28,6 +28,9 @@ public class VoskDemo
{
// Demo float array
VoskRecognizer rec = new VoskRecognizer(model, 16000.0f);
rec.SetEndpointerMode(EndpointerMode.LONG);
using(Stream source = File.OpenRead("test.wav")) {
byte[] buffer = new byte[4096];
int bytesRead;
+2 -2
View File
@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>VoskDemo</RootNamespace>
</PropertyGroup>
@@ -11,7 +11,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Vosk" Version="0.3.31" />
<PackageReference Include="Vosk" Version="0.3.75" />
</ItemGroup>
</Project>
+17
View File
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageId>Vosk</PackageId>
<Version>0.3.75</Version>
<authors>Alpha Cephei Inc</authors>
<owners>Alpha Cephei Inc</owners>
</PropertyGroup>
<Target Name="CopyFiles" AfterTargets="Build">
<Copy SourceFiles="bin/Release/net8.0/Vosk.dll" DestinationFolder="lib/net8.0" />
</Target>
</Project>
+7 -5
View File
@@ -2,13 +2,14 @@
<package>
<metadata>
<id>Vosk</id>
<version>0.3.38</version>
<version>0.3.75</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.
<title>Vosk Speech Recognition Toolkit</title>
<description>Vosk is an offline open source speech recognition toolkit. It enables speech recognition models for 20+ languages and dialects - English, Indian English, German, French, Spanish, Portuguese, Chinese, Russian, Turkish, Vietnamese, Italian, Dutch, Catalan, Arabic, Greek, Farsi, Filipino, Ukrainian, Kazakh, Swedish, Japanese, Esperanto, Hindi, Czech. 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.
@@ -18,13 +19,14 @@ Vosk supplies speech recognition for chatbots, smart home appliances, virtual as
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>
<repository type="git" url="https://github.com/alphacep/vosk-api.git" branch="master"/>
<copyright>Copyright 2020-2050 Alpha Cephei Inc</copyright>
<tags>speech recognition voice stt asr speech-to-text ai offline privacy</tags>
<dependencies>
<group targetFramework=".NETStandard2.0"/>
<group targetFramework="net8.0"/>
</dependencies>
</metadata>
<files>
<file src="**" exclude="src/*.cs;build.sh;**/.keep-me;*.nupkg" />
<file src="**" exclude="bin/**;obj/**;build.sh;src/*.cs;*.nupkg;**/.keep-me" />
</files>
</package>
+2 -2
View File
@@ -1,2 +1,2 @@
mcs -out:lib/netstandard2.0/Vosk.dll -target:library src/*.cs
nuget pack
rm -rf bin lib obj
/home/shmyrev/local/dotnet/dotnet pack Vosk.csproj -p:NuspecFile=Vosk.nuspec -o .
+1 -1
View File
@@ -2,7 +2,7 @@
<ItemGroup>
<NativeLibs Include="$(MSBuildThisFileDirectory)\lib\linux-x64\*.so" Condition="'$([MSBuild]::IsOsPlatform(Linux))'" />
<NativeLibs Include="$(MSBuildThisFileDirectory)\lib\win-x64\*.dll" Condition="'$([MSBuild]::IsOsPlatform(Windows))'" />
<NativeLibs Include="$(MSBuildThisFileDirectory)\lib\osx-x64\*.dylib" Condition="'$([MSBuild]::IsOsPlatform(OSX))'" />
<NativeLibs Include="$(MSBuildThisFileDirectory)\lib\osx-universal\*.dylib" Condition="'$([MSBuild]::IsOsPlatform(OSX))'" />
<None Include="@(NativeLibs)">
<Link>%(FileName)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+55
View File
@@ -0,0 +1,55 @@
using System;
namespace Vosk
{
public class BatchModel : global::System.IDisposable
{
private global::System.Runtime.InteropServices.HandleRef handle;
internal BatchModel(global::System.IntPtr cPtr)
{
handle = new global::System.Runtime.InteropServices.HandleRef(this, cPtr);
}
internal static global::System.Runtime.InteropServices.HandleRef getCPtr(BatchModel obj)
{
return (obj == null) ? new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero) : obj.handle;
}
~BatchModel()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
global::System.GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
lock (this)
{
if (handle.Handle != global::System.IntPtr.Zero)
{
VoskPINVOKE.delete_BatchModel(handle);
handle = new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
}
}
}
public BatchModel(string model_path) : this(VoskPINVOKE.new_BatchModel(model_path))
{
}
public void WaitForCompletion()
{
if (handle.Handle != global::System.IntPtr.Zero)
{
VoskPINVOKE.wait_BatchModel(handle);
}
}
}
}
+88
View File
@@ -0,0 +1,88 @@
using System;
namespace Vosk
{
public class VoskBatchRecognizer : System.IDisposable
{
private System.Runtime.InteropServices.HandleRef handle;
internal VoskBatchRecognizer(System.IntPtr cPtr)
{
handle = new System.Runtime.InteropServices.HandleRef(this, cPtr);
}
internal static System.Runtime.InteropServices.HandleRef getCPtr(VoskBatchRecognizer obj)
{
return (obj == null) ? new System.Runtime.InteropServices.HandleRef(null, System.IntPtr.Zero) : obj.handle;
}
~VoskBatchRecognizer()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
System.GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
lock (this)
{
if (handle.Handle != System.IntPtr.Zero)
{
VoskPINVOKE.delete_VoskBatchRecognizer(handle);
handle = new System.Runtime.InteropServices.HandleRef(null, System.IntPtr.Zero);
}
}
}
public VoskBatchRecognizer(BatchModel model, float sample_rate) : this(VoskPINVOKE.new_VoskBatchRecognizer(BatchModel.getCPtr(model), sample_rate))
{
}
public bool AcceptWaveform(byte[] data, int len)
{
return VoskPINVOKE.VoskBatchRecognizer_AcceptWaveform(handle, data, len);
}
private static string PtrToStringUTF8(System.IntPtr ptr)
{
int len = 0;
while (System.Runtime.InteropServices.Marshal.ReadByte(ptr, len) != 0)
len++;
byte[] array = new byte[len];
System.Runtime.InteropServices.Marshal.Copy(ptr, array, 0, len);
return System.Text.Encoding.UTF8.GetString(array);
}
public string FrontResult()
{
return PtrToStringUTF8(VoskPINVOKE.VoskBatchRecognizer_FrontResult(handle));
}
public string Result()
{
string result = PtrToStringUTF8(VoskPINVOKE.VoskBatchRecognizer_FrontResult(handle));
VoskPINVOKE.VoskBatchRecognizer_Pop(handle);
return result;
}
public int GetNumPendingChunks()
{
return VoskPINVOKE.VoskBatchRecognizer_GetPendingChunks(handle);
}
public void FinishStream()
{
VoskPINVOKE.VoskBatchRecognizer_FinishStream(handle);
}
public void SetNLSML(bool nlsml)
{
VoskPINVOKE.VoskBatchRecognizer_SetNLSML(handle, Convert.ToInt32(nlsml));
}
}
}
+40
View File
@@ -65,6 +65,12 @@ class VoskPINVOKE {
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint="vosk_recognizer_reset")]
public static extern void VoskRecognizer_Reset(global::System.Runtime.InteropServices.HandleRef jarg1);
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint="vosk_recognizer_set_endpointer_mode")]
public static extern void VoskRecognizer_SetEndpointerMode(global::System.Runtime.InteropServices.HandleRef jarg1, int jarg2);
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint="vosk_recognizer_set_endpointer_delays")]
public static extern void VoskRecognizer_SetEndpointerDelays(global::System.Runtime.InteropServices.HandleRef jarg1, float jarg2, float jarg3, float jarg4);
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint="vosk_set_log_level")]
public static extern void SetLogLevel(int jarg1);
@@ -73,6 +79,40 @@ class VoskPINVOKE {
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint="vosk_gpu_thread_init")]
public static extern void GpuThreadInit();
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint = "vosk_batch_model_new")]
public static extern global::System.IntPtr new_BatchModel(string jarg1);
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint = "vosk_batch_model_free")]
public static extern void delete_BatchModel(global::System.Runtime.InteropServices.HandleRef jarg1);
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint = "vosk_batch_model_wait")]
public static extern void wait_BatchModel(global::System.Runtime.InteropServices.HandleRef jarg1);
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint = "vosk_batch_recognizer_new")]
public static extern global::System.IntPtr new_VoskBatchRecognizer(global::System.Runtime.InteropServices.HandleRef jarg1, float jarg2);
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint = "vosk_batch_recognizer_free")]
public static extern void delete_VoskBatchRecognizer(global::System.Runtime.InteropServices.HandleRef jarg1);
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint = "vosk_batch_recognizer_accept_waveform")]
public static extern bool VoskBatchRecognizer_AcceptWaveform(global::System.Runtime.InteropServices.HandleRef jarg1, [global::System.Runtime.InteropServices.In, global::System.Runtime.InteropServices.MarshalAs(global::System.Runtime.InteropServices.UnmanagedType.LPArray)] byte[] jarg2, int jarg3);
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint = "vosk_batch_recognizer_set_nlsml")]
public static extern void VoskBatchRecognizer_SetNLSML(global::System.Runtime.InteropServices.HandleRef jarg1, int jarg2);
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint = "vosk_batch_recognizer_finish_stream")]
public static extern void VoskBatchRecognizer_FinishStream(global::System.Runtime.InteropServices.HandleRef jarg1);
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint = "vosk_batch_recognizer_front_result")]
public static extern global::System.IntPtr VoskBatchRecognizer_FrontResult(global::System.Runtime.InteropServices.HandleRef jarg1);
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint = "vosk_batch_recognizer_pop")]
public static extern void VoskBatchRecognizer_Pop(global::System.Runtime.InteropServices.HandleRef jarg1);
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint = "vosk_batch_recognizer_get_pending_chunks")]
public static extern int VoskBatchRecognizer_GetPendingChunks(global::System.Runtime.InteropServices.HandleRef jarg1);
}
}
+15
View File
@@ -1,5 +1,12 @@
namespace Vosk {
public enum EndpointerMode {
DEFAULT = 0,
SHORT = 1,
LONG = 2,
VERY_LONG = 3
}
public class VoskRecognizer : System.IDisposable {
private System.Runtime.InteropServices.HandleRef handle;
@@ -91,6 +98,14 @@ public class VoskRecognizer : System.IDisposable {
VoskPINVOKE.VoskRecognizer_Reset(handle);
}
public void SetEndpointerMode(EndpointerMode mode) {
VoskPINVOKE.VoskRecognizer_SetEndpointerMode(handle, (int) mode);
}
public void SetEndpointerDelays(float t_start_max, float t_end, float t_max) {
VoskPINVOKE.VoskRecognizer_SetEndpointerDelays(handle, t_start_max, t_end, t_max);
}
}
}
+1
View File
@@ -0,0 +1 @@
See example subfolder for instructions how to use the module
+99
View File
@@ -0,0 +1,99 @@
package vosk
// #cgo CPPFLAGS: -I ${SRCDIR}/../src
// #cgo !windows LDFLAGS: -L ${SRCDIR}/../src -lvosk -ldl -lpthread
// #cgo windows LDFLAGS: -L ${SRCDIR}/../src -lvosk -lpthread
// #include <stdlib.h>
// #include <vosk_api.h>
import "C"
import "unsafe"
// VoskBatchModel contains a reference to the C VoskBatchModel
type VoskBatchModel struct {
model *C.struct_VoskBatchModel
}
// NewBatchModel creates a new VoskBatchModel instance
func NewBatchModel(modelPath string) (*VoskBatchModel, error) {
cmodelPath := C.CString(modelPath)
defer C.free(unsafe.Pointer(cmodelPath))
internal := C.vosk_batch_model_new(cmodelPath)
model := &VoskBatchModel{model: internal}
return model, nil
}
func (m *VoskBatchModel) Free() {
C.vosk_batch_model_free(m.model)
}
func (m *VoskBatchModel) Wait() {
C.vosk_batch_model_wait(m.model);
}
func freeBatchModel(model *VoskBatchModel) {
C.vosk_batch_model_free(model.model)
}
// VoskBatchRecognizer contains a reference to the C VoskBatchRecognizer
type VoskBatchRecognizer struct {
rec *C.struct_VoskBatchRecognizer
}
func freeBatchRecognizer(recognizer *VoskBatchRecognizer) {
C.vosk_batch_recognizer_free(recognizer.rec)
}
func (r *VoskBatchRecognizer) Free() {
C.vosk_batch_recognizer_free(r.rec)
}
// NewBatchRecognizer creates a new VoskBatchRecognizer instance
func NewBatchRecognizer(model *VoskBatchModel, sampleRate float64) (*VoskBatchRecognizer, error) {
internal := C.vosk_batch_recognizer_new(model.model, C.float(sampleRate))
rec := &VoskBatchRecognizer{rec: internal}
return rec, nil
}
// AcceptWaveform accepts and processes a new chunk of the voice data.
func (r *VoskBatchRecognizer) AcceptWaveform(buffer []byte) {
cbuf := C.CBytes(buffer)
defer C.free(cbuf)
C.vosk_batch_recognizer_accept_waveform(r.rec, (*C.char)(cbuf), C.int(len(buffer)))
}
/** Set NLSML output
* @param nlsml - boolean value
*/
//void vosk_batch_recognizer_set_nlsml(VoskBatchRecognizer *recognizer, int nlsml);
func (r *VoskBatchRecognizer) SetNlsml(nlsml int) {
C.vosk_batch_recognizer_set_nlsml(r.rec, C.int(nlsml))
}
/** Closes the stream */
//void vosk_batch_recognizer_finish_stream(VoskBatchRecognizer *recognizer);
func (r *VoskBatchRecognizer) FinishStream() {
C.vosk_batch_recognizer_finish_stream(r.rec)
}
/** Return results */
//const char *vosk_batch_recognizer_front_result(VoskBatchRecognizer *recognizer);
func (r *VoskBatchRecognizer) FrontResult() string {
return C.GoString(C.vosk_batch_recognizer_front_result(r.rec))
}
/** Release and free first retrieved result */
//void vosk_batch_recognizer_pop(VoskBatchRecognizer *recognizer);
func (r *VoskBatchRecognizer) Pop() {
C.vosk_batch_recognizer_pop(r.rec)
}
/** Get amount of pending chunks for more intelligent waiting */
//int vosk_batch_recognizer_get_pending_chunks(VoskBatchRecognizer *recognizer);
func (r *VoskBatchRecognizer) GetPendingChunks() int {
i := C.vosk_batch_recognizer_get_pending_chunks(r.rec)
return int(i)
}
+5
View File
@@ -0,0 +1,5 @@
This example expects a `s16le` converted audio file and converts it to text in a
manner that imitates the Python example of [test_gpu_batch.py](../python/example/test_gpu_batch.py).
Note that the `libvosk.so` must be in the library path. This was successfully tested on
Ubuntu 24.04 with Go 1.18, gcc-11, NVIDIA driver 570.172.08.
+54
View File
@@ -0,0 +1,54 @@
package main
import (
"flag"
"fmt"
"io"
"log"
"os"
vosk "github.com/alphacep/vosk-api/go"
)
func main() {
var filename string
flag.StringVar(&filename, "f", "", "file to transcribe")
flag.Parse()
vosk.GPUInit()
model, err := vosk.NewBatchModel("model")
if err != nil {
log.Fatal(err)
}
rec, err := vosk.NewBatchRecognizer(model, 16000.0)
if err != nil {
log.Fatal(err)
}
file, err := os.Open(filename)
if err != nil {
panic(err)
}
defer file.Close()
buf := make([]byte, 8000)
for {
if _, err := file.Read(buf); err != nil {
if err != io.EOF {
log.Fatal(err)
}
break
}
rec.AcceptWaveform(buf)
model.Wait()
if rec.FrontResult() != "" {
fmt.Println(rec.FrontResult())
rec.Pop()
}
}
// Is this needed? rec.FinishStream()
}
+31
View File
@@ -0,0 +1,31 @@
To try this package do the following steps:
On Linux (we download library and set LD_LIBRARY_PATH)
```
git clone https://github.com/alphacep/vosk-api
cd vosk-api/go/example
wget https://github.com/alphacep/vosk-api/releases/download/v0.3.45/vosk-linux-x86_64-0.3.45.zip
unzip vosk-linux-x86_64-0.3.45.zip
wget https://alphacephei.com/vosk/models/vosk-model-small-en-us-0.15.zip
unzip vosk-model-small-en-us-0.15.zip
mv vosk-model-small-en-us-0.15 model
cp ../../python/example/test.wav .
VOSK_PATH=`pwd`/vosk-linux-x86_64-0.3.45 LD_LIBRARY_PATH=$VOSK_PATH CGO_CPPFLAGS="-I $VOSK_PATH" CGO_LDFLAGS="-L $VOSK_PATH" go run . -f test.wav
```
for Windows (we place DLLs in current folder where linker finds them):
```
git clone https://github.com/alphacep/vosk-api
cd vosk-api/go/example
wget https://github.com/alphacep/vosk-api/releases/download/v0.3.45/vosk-linux-x86_64-0.3.45.zip
unzip vosk-linux-x86_64-0.3.45.zip
cp vosk-linux-x86_64-0.3.45/*.dll .
cp vosk-linux-x86_64-0.3.45/*.h .
wget https://alphacephei.com/vosk/models/vosk-model-small-en-us-0.15.zip
unzip vosk-model-small-en-us-0.15.zip
mv vosk-model-small-en-us-0.15 model
cp ../../python/example/test.wav .
VOSK_PATH=`pwd` LD_LIBRARY_PATH=$VOSK_PATH CGO_CPPFLAGS="-I $VOSK_PATH" CGO_LDFLAGS="-L $VOSK_PATH -lvosk -lpthread -dl" go run . -f test.wav
```
+1 -1
View File
@@ -1,2 +1,2 @@
// Example package for Vosk Go bindings.
package test_simple
package main
+12 -5
View File
@@ -1,7 +1,7 @@
package main
import (
"bufio"
"encoding/json"
"flag"
"fmt"
"io"
@@ -16,11 +16,16 @@ func main() {
flag.StringVar(&filename, "f", "", "file to transcribe")
flag.Parse()
vosk.GPUInit()
model, err := vosk.NewModel("model")
if err != nil {
log.Fatal(err)
}
// we can check if word is in the vocabulary
// fmt.Println(model.FindWord("air"))
sampleRate := 16000.0
rec, err := vosk.NewRecognizer(model, sampleRate)
if err != nil {
@@ -34,11 +39,10 @@ func main() {
}
defer file.Close()
reader := bufio.NewReader(file)
buf := make([]byte, 4096)
for {
_, err := reader.Read(buf)
_, err := file.Read(buf)
if err != nil {
if err != io.EOF {
log.Fatal(err)
@@ -48,9 +52,12 @@ func main() {
}
if rec.AcceptWaveform(buf) != 0 {
fmt.Println(string(rec.Result()))
fmt.Println(rec.Result())
}
}
fmt.Println(string(rec.FinalResult()))
// Unmarshal example for final result
var jres map[string]interface{}
json.Unmarshal([]byte(rec.FinalResult()), &jres)
fmt.Println(jres["text"])
}
+45 -17
View File
@@ -1,10 +1,12 @@
package vosk
// #cgo CPPFLAGS: -I ${SRCDIR}/../src
// #cgo LDFLAGS: -L ${SRCDIR}/../src -lvosk -ldl -lpthread
// #cgo !windows LDFLAGS: -L ${SRCDIR}/../src -lvosk -ldl -lpthread
// #cgo windows LDFLAGS: -L ${SRCDIR}/../src -lvosk -lpthread
// #include <stdlib.h>
// #include <vosk_api.h>
import "C"
import "unsafe"
// VoskModel contains a reference to the C VoskModel
type VoskModel struct {
@@ -13,7 +15,9 @@ type VoskModel struct {
// NewModel creates a new VoskModel instance
func NewModel(modelPath string) (*VoskModel, error) {
internal := C.vosk_model_new(C.CString(modelPath))
cmodelPath := C.CString(modelPath)
defer C.free(unsafe.Pointer(cmodelPath))
internal := C.vosk_model_new(cmodelPath)
model := &VoskModel{model: internal}
return model, nil
}
@@ -29,10 +33,10 @@ func freeModel(model *VoskModel) {
// FindWord checks if a word can be recognized by the model.
// Returns the word symbol if the word exists inside the model or
// -1 otherwise.
func (m *VoskModel) FindWord(word []byte) int {
cbuf := C.CBytes(word)
defer C.free(cbuf)
i := C.vosk_model_find_word(m.model, (*C.char)(cbuf))
func (m *VoskModel) FindWord(word string) int {
cstr := C.CString(word)
defer C.free(unsafe.Pointer(cstr))
i := C.vosk_model_find_word(m.model, cstr)
return int(i)
}
@@ -43,7 +47,9 @@ type VoskSpkModel struct {
// NewSpkModel creates a new VoskSpkModel instance
func NewSpkModel(spkModelPath string) (*VoskSpkModel, error) {
internal := C.vosk_spk_model_new(C.CString(spkModelPath))
cspkModelPath := C.CString(spkModelPath)
defer C.free(unsafe.Pointer(cspkModelPath))
internal := C.vosk_spk_model_new(cspkModelPath)
spkModel := &VoskSpkModel{spkModel: internal}
return spkModel, nil
}
@@ -84,10 +90,10 @@ func NewRecognizerSpk(model *VoskModel, sampleRate float64, spkModel *VoskSpkMod
}
// NewRecognizerGrm creates a new VoskRecognizer instance with the phrase list.
func NewRecognizerGrm(model *VoskModel, sampleRate float64, grammer []byte) (*VoskRecognizer, error) {
cbuf := C.CBytes(grammer)
defer C.free(cbuf)
internal := C.vosk_recognizer_new_grm(model.model, C.float(sampleRate), (*C.char)(cbuf))
func NewRecognizerGrm(model *VoskModel, sampleRate float64, grammar string) (*VoskRecognizer, error) {
cgrammar := C.CString(grammar)
defer C.free(unsafe.Pointer(cgrammar))
internal := C.vosk_recognizer_new_grm(model.model, C.float(sampleRate), cgrammar)
rec := &VoskRecognizer{rec: internal}
return rec, nil
}
@@ -97,6 +103,13 @@ func (r *VoskRecognizer) SetSpkModel(spkModel *VoskSpkModel) {
C.vosk_recognizer_set_spk_model(r.rec, spkModel.spkModel)
}
// SetGrm sets which phrases to recognize on an already initialized recognizer.
func (r *VoskRecognizer) SetGrm(grammar string) {
cgrammar := C.CString(grammar)
defer C.free(unsafe.Pointer(cgrammar))
C.vosk_recognizer_set_grm(r.rec, cgrammar)
}
// SetMaxAlternatives configures the recognizer to output n-best results.
func (r *VoskRecognizer) SetMaxAlternatives(maxAlternatives int) {
C.vosk_recognizer_set_max_alternatives(r.rec, C.int(maxAlternatives))
@@ -107,6 +120,21 @@ func (r *VoskRecognizer) SetWords(words int) {
C.vosk_recognizer_set_words(r.rec, C.int(words))
}
// SetPartialWords enables words with times in the partial ouput.
func (r *VoskRecognizer) SetPartialWords(words int) {
C.vosk_recognizer_set_partial_words(r.rec, C.int(words))
}
// SetEndpointerDelays sets the recognition timeouts, where startMax
// is the timeout for stopping recognition in case of initial silence
// (usually around 5), end is the timeout for stopping recognition
// in milliseconds after we recognized something (usually around 0.5-1.0),
// and max is the timeout for forcing utterance end in milliseconds
// (usually around 20-30).
func (r *VoskRecognizer) SetEndpointerDelays(startMax, end, max float64) {
C.vosk_recognizer_set_endpointer_delays(r.rec, C.float(startMax), C.float(end), C.float(max))
}
// AcceptWaveform accepts and processes a new chunk of the voice data.
func (r *VoskRecognizer) AcceptWaveform(buffer []byte) int {
cbuf := C.CBytes(buffer)
@@ -116,19 +144,19 @@ func (r *VoskRecognizer) AcceptWaveform(buffer []byte) int {
}
// Result returns a speech recognition result.
func (r *VoskRecognizer) Result() []byte {
return []byte(C.GoString(C.vosk_recognizer_result(r.rec)))
func (r *VoskRecognizer) Result() string {
return C.GoString(C.vosk_recognizer_result(r.rec))
}
// PartialResult returns a partial speech recognition result.
func (r *VoskRecognizer) PartialResult() []byte {
return []byte(C.GoString(C.vosk_recognizer_partial_result(r.rec)))
func (r *VoskRecognizer) PartialResult() string {
return C.GoString(C.vosk_recognizer_partial_result(r.rec))
}
// FinalResult returns a speech recognition result. Same as result, but doesn't wait
// for silence.
func (r *VoskRecognizer) FinalResult() []byte {
return []byte(C.GoString(C.vosk_recognizer_final_result(r.rec)))
func (r *VoskRecognizer) FinalResult() string {
return C.GoString(C.vosk_recognizer_final_result(r.rec))
}
// Reset resets the recognizer.
+1 -2
View File
@@ -11,6 +11,5 @@ repositories {
}
dependencies {
implementation group: 'net.java.dev.jna', name: 'jna', version: '5.7.0'
implementation group: 'com.alphacephei', name: 'vosk', version: '0.3.38+'
implementation group: 'com.alphacephei', name: 'vosk', version: '0.3.75'
}
+2 -2
View File
@@ -16,7 +16,7 @@ repositories {
archivesBaseName = 'vosk'
group = 'com.alphacephei'
version = '0.3.38'
version = '0.3.75'
mavenPublish {
group = 'com.alphacephei'
@@ -25,7 +25,7 @@ mavenPublish {
}
dependencies {
api group: 'net.java.dev.jna', name: 'jna', version: '5.7.0'
api group: 'net.java.dev.jna', name: 'jna', version: '5.18.1'
testImplementation 'junit:junit:4.13'
}
+24 -4
View File
@@ -1,7 +1,6 @@
package org.vosk;
import com.sun.jna.Native;
import com.sun.jna.Library;
import com.sun.jna.Platform;
import com.sun.jna.Pointer;
import java.io.File;
@@ -13,8 +12,9 @@ import java.nio.file.StandardCopyOption;
public class LibVosk {
private static void unpackDll(File targetDir, String lib) throws IOException {
InputStream source = LibVosk.class.getResourceAsStream("/win32-x86-64/" + lib + ".dll");
Files.copy(source, new File(targetDir, lib + ".dll").toPath(), StandardCopyOption.REPLACE_EXISTING);
try (InputStream source = LibVosk.class.getResourceAsStream("/win32-x86-64/" + lib + ".dll")) {
Files.copy(source, new File(targetDir, lib + ".dll").toPath(), StandardCopyOption.REPLACE_EXISTING);
}
}
static {
@@ -23,7 +23,7 @@ public class LibVosk {
// We have to unpack dependencies
try {
// To get a tmp folder we unpack small library and mark it for deletion
File tmpFile = Native.extractFromResourcePath("/win32-x86-64/empty");
File tmpFile = Native.extractFromResourcePath("/win32-x86-64/empty", LibVosk.class.getClassLoader());
File tmpDir = tmpFile.getParentFile();
new File(tmpDir, tmpFile.getName() + ".x").createNewFile();
@@ -78,10 +78,30 @@ public class LibVosk {
public static native String vosk_recognizer_partial_result(Pointer recognizer);
public static native void vosk_recognizer_set_grm(Pointer recognizer, String grammar);
public static native void vosk_recognizer_reset(Pointer recognizer);
public static native void vosk_recognizer_set_endpointer_mode(Pointer recognizer, int mode);
public static native void vosk_recognizer_set_endpointer_delays(Pointer recognizer, float t_start_max, float t_end, float t_max);
public static native void vosk_recognizer_free(Pointer recognizer);
public static native Pointer vosk_text_processor_new(String verbalizer, String tagger);
public static native void vosk_text_processor_free(Pointer processor);
public static native String vosk_text_processor_itn(Pointer processor, String input);
/**
* 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());
}
+206 -2
View File
@@ -4,6 +4,17 @@ 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));
@@ -12,30 +23,141 @@ public class Recognizer extends PointerType implements AutoCloseable {
}
}
public Recognizer(Model model, float sampleRate, SpeakerModel spkModel) {
/**
* 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 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.
* @param spkModel speaker model for speaker identification
* @throws IOException if the recognizer could not be created
*/
public Recognizer(Model model, float sampleRate, SpeakerModel spkModel) throws IOException {
super(LibVosk.vosk_recognizer_new_spk(model.getPointer(), sampleRate, spkModel.getPointer()));
if (getPointer() == null) {
throw new IOException("Failed to create a recognizer");
}
}
public Recognizer(Model model, float sampleRate, String grammar) {
/**
* 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 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.
* @param grammar The string with the list of phrases to recognize as JSON array of strings,
* for example "["one two three four five", "[unk]"]".
* @throws IOException if the recognizer could not be created
*/
public Recognizer(Model model, float sampleRate, String grammar) throws IOException {
super(LibVosk.vosk_recognizer_new_grm(model.getPointer(), sampleRate, grammar));
if (getPointer() == null) {
throw new IOException("Failed to create a recognizer");
}
}
/**
* Configures recognizer to output n-best results.
*
* <pre>
* {
* "alternatives": [
* { "text": "one two three four five", "confidence": 0.97 },
* { "text": "one two three for five", "confidence": 0.03 },
* ]
* }
* </pre>
*
* @param maxAlternatives - maximum alternatives to return from recognition results
*/
public void setMaxAlternatives(int maxAlternatives) {
LibVosk.vosk_recognizer_set_max_alternatives(this.getPointer(), maxAlternatives);
}
/** Enables words with times in the output
*
* <pre>
* "result" : [{
* "conf" : 1.000000,
* "end" : 1.110000,
* "start" : 0.870000,
* "word" : "what"
* }, {
* "conf" : 1.000000,
* "end" : 1.530000,
* "start" : 1.110000,
* "word" : "zero"
* }, {
* "conf" : 1.000000,
* "end" : 1.950000,
* "start" : 1.530000,
* "word" : "zero"
* }, {
* "conf" : 1.000000,
* "end" : 2.340000,
* "start" : 1.950000,
* "word" : "zero"
* }, {
* "conf" : 1.000000,
* "end" : 2.610000,
* "start" : 2.340000,
* "word" : "one"
* }],
* </pre>
*
* @param words - boolean value
*/
public void setWords(boolean words) {
LibVosk.vosk_recognizer_set_words(this.getPointer(), words);
}
/**
* Like above return words and confidences in partial results.
*
* @param partial_words - boolean value
*/
public void setPartialWords(boolean partial_words) {
LibVosk.vosk_recognizer_set_partial_words(this.getPointer(), partial_words);
}
/**
* Adds speaker model to already initialized recognizer.
*
* Can add speaker recognition model to already created recognizer.
* Helps to initialize speaker recognition for grammar-based recognizer.
*
* @param spkModel Speaker recognition model
*/
public void setSpeakerModel(SpeakerModel spkModel) {
LibVosk.vosk_recognizer_set_spk_model(this.getPointer(), spkModel.getPointer());
}
/**
* 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
* @return 1 if silence is occurred and you can retrieve a new utterance with result method
* 0 if decoding continues
* -1 if exception occurred
*/
public boolean acceptWaveForm(byte[] data, int len) {
return LibVosk.vosk_recognizer_accept_waveform(this.getPointer(), data, len);
}
@@ -48,22 +170,104 @@ public class Recognizer extends PointerType implements AutoCloseable {
return LibVosk.vosk_recognizer_accept_waveform_f(this.getPointer(), data, len);
}
/**
* 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(this.getPointer());
}
/**
* Returns partial speech recognition.
*
* @return partial speech recognition text which is not yet finalized.
* result may change as recognizer process more data.
*
* <pre>
* {
* "partial" : "cyril one eight zero"
* }
* </pre>
*/
public String getPartialResult() {
return LibVosk.vosk_recognizer_partial_result(this.getPointer());
}
/**
* 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.
*
* @return speech result in JSON format.
*/
public String getFinalResult() {
return LibVosk.vosk_recognizer_final_result(this.getPointer());
}
/**
* Reconfigures recognizer to use grammar.
*
* @param grammar Set of phrases in JSON array of strings or "[]" to use default model graph.
* @see #Recognizer(Model, float, String)
*/
public void setGrammar(String grammar) {
LibVosk.vosk_recognizer_set_grm(this.getPointer(), grammar);
}
/**
* Resets the recognizer.
* Resets current results so the recognition can continue from scratch.
*/
public void reset() {
LibVosk.vosk_recognizer_reset(this.getPointer());
}
/**
* Endpointer delay mode
*/
public class EndpointerMode {
public static final int DEFAULT = 0;
public static final int SHORT = 1;
public static final int LONG = 2;
public static final int VERY_LONG = 3;
}
/**
* Configures endpointer mode for recognizer
*/
public void setEndpointerMode(int mode) {
LibVosk.vosk_recognizer_set_endpointer_mode(this.getPointer(), mode);
}
/**
* Set endpointer delays
*
* @param t_start_max timeout for stopping recognition in case of initial silence (usually around 5.0)
* @param t_end timeout for stopping recognition in milliseconds after we recognized something (usually around 0.5 - 1.0)
* @param t_max timeout for forcing utterance end in milliseconds (usually around 20-30)
**/
public void setEndpointerDelays(float t_start_max, float t_end, float t_max) {
LibVosk.vosk_recognizer_set_endpointer_delays(this.getPointer(), t_start_max, t_end, t_max);
}
/**
* Releases recognizer object.
* Underlying model is also unreferenced and if needed, released.
*/
@Override
public void close() {
LibVosk.vosk_recognizer_free(this.getPointer());
@@ -3,10 +3,28 @@ package org.vosk;
import com.sun.jna.PointerType;
import java.io.IOException;
/**
* Helps to initialize speaker recognition for grammar-based recognizer.
*/
public class SpeakerModel extends PointerType implements AutoCloseable {
public SpeakerModel() {
}
/**
* Loads speaker model data from the file.
*
* The path must contain:
* - a config file: mfcc.conf
* - kaldi nnet: final.ext.raw
* - mean.vec
* - transform.mat
*
* @param path the path of the model on the filesystem
* @throws IOException if the model could not be created
*
* @see <a href="http://kaldi-asr.org/doc/structkaldi_1_1MfccOptions.html">Kaldi MfccOptions</a>
* @see <a href="http://kaldi-asr.org/doc/classkaldi_1_1nnet3_1_1Nnet.html">Kaldi Nnet</a>
*/
public SpeakerModel(String path) throws IOException {
super(LibVosk.vosk_spk_model_new(path));
@@ -15,8 +15,10 @@ import javax.sound.sampled.UnsupportedAudioFileException;
import org.vosk.LogLevel;
import org.vosk.Recognizer;
import org.vosk.Recognizer.EndpointerMode;
import org.vosk.LibVosk;
import org.vosk.Model;
import org.vosk.TextProcessor;
public class DecoderTest {
@@ -95,9 +97,24 @@ public class DecoderTest {
Assert.assertTrue(true);
}
@Test
public void decoderEndpointerDelays() throws IOException, UnsupportedAudioFileException {
try (Model model = new Model("model");
Recognizer recognizer = new Recognizer(model, 16000)) {
recognizer.setEndpointerMode(EndpointerMode.VERY_LONG);
recognizer.setEndpointerDelays(5.0f, 3.0f, 50.0f);
}
Assert.assertTrue(true);
}
@Test(expected = IOException.class)
public void decoderTestException() throws IOException {
Model model = new Model("model_missing");
}
@Test
public void testItn() throws IOException {
TextProcessor p = new TextProcessor("model/itn/en_itn_tagger.fst", "model/itn/en_itn_verbalizer.fst");
System.out.println(p.itn("as easy as one two three"));
}
}
+3
View File
@@ -0,0 +1,3 @@
.gradle/
.idea/
build/
+3
View File
@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml
+1
View File
@@ -0,0 +1 @@
vosk-api-kotlin
+127
View File
@@ -0,0 +1,127 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>
+5
View File
@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>
+6
View File
@@ -0,0 +1,6 @@
<component name="CopyrightManager">
<copyright>
<option name="notice" value="Copyright &amp;#36;today.year Alpha Cephei Inc.&#10;&#10;Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);&#10;you may not use this file except in compliance with the License.&#10;You may obtain a copy of the License at&#10;&#10; http://www.apache.org/licenses/LICENSE-2.0&#10;&#10;Unless required by applicable law or agreed to in writing, software&#10;distributed under the License is distributed on an &quot;AS IS&quot; BASIS,&#10;WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&#10;See the License for the specific language governing permissions and&#10;limitations under the License." />
<option name="myName" value="Apache-2.0" />
</copyright>
</component>
+7
View File
@@ -0,0 +1,7 @@
<component name="CopyrightManager">
<settings default="Apache-2.0">
<module2copyright>
<element module="All" copyright="Apache-2.0" />
</module2copyright>
</settings>
</component>
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>
+25
View File
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="Google" />
<option name="name" value="Google" />
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
</remote-repository>
</component>
</project>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Embedded JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>
+1
View File
@@ -0,0 +1 @@
Doomsdayrs doomsdayrs@gmail.com
+8
View File
@@ -0,0 +1,8 @@
# Maintenance
To maintain this module, please ensure the following.
1. Kotlin version is kept up to date.
This will be found in the plugins block of the [build.gradle.kts](./build.gradle.kts).
Ensure both multiplatform and serialization have the same value.
2. Ensure dependencies are up to date
3. Ensure that the android min & target sdks are up to date.
+63
View File
@@ -0,0 +1,63 @@
# vosk-api-kotlin
The vosk-api wrapped using Kotlin Multiplatform.
## Usage
The following are ways to use this wrapper.
### JVM
For Java & Android targets.
```kotlin
dependencoes {
val voskVersion = "0.4.0-alpha0"
// Generic
implementation("com.alphacephei:vosk-api-kotlin:$voskVersion")
// Android
implementation("com.alphacephei:vosk-api-kotlin-android:$voskVersion")
}
```
## Building
To build this project, follow the following steps.
1. Install `libvosk`.
This can be done from [source][source install] (parent monorepo)
or [downloaded][download].
2. Download a [Vosk model](https://alphacephei.com/vosk/models) to use.
- It is suggested to use a small model to speed up tests.
3. Once both are downloaded and placed into a proper location (hopefully following UNIX specification).
Set the following environment variables:
- `VOSK_MODEL` to the path of the model.
- `VOSK_PATH` to the path of `libvosk`.
These are used by the various tests to operate.
4. Now that the required steps are complete, one can run `./gradlew build`.
## Kotlin/Native
Currently, the native target is disabled due to a lack of vosk-api packaging on platforms.
Further worsened by the fact that installing the vosk-api on Linux systems is a chore.
First, either install from [source][source install]
or [download][download] and install into the proper unix directories as expected in
[libvosk.def](./src/nativeInterop/cinterop/libvosk.def).
To enable development for Kotlin/Native, do either for the following.
- Add `NATIVE_EXPERIMENT=true` to your environment.
- Go into [build.gradle.kts](./build.gradle.kts) & find `enableNative` & set the right side to true.
Afterwards, when syncing the project, the native source sets will become available to work on.
It is suggested to run `cinteropLibvoskNative` to generate the Kotlin C bindings to work with.
## Future
- Possibly target Kotlin/JS
- Possibly target Kotlin/Objective-C (?)
[source install]: https://alphacephei.com/vosk/install
[download]: https://github.com/alphacep/vosk-api/releases/latest
+204
View File
@@ -0,0 +1,204 @@
import org.jetbrains.dokka.gradle.DokkaTask
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
/*
* Copyright 2020 Alpha Cephei Inc. & Doomsdayrs
*
* 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.
*/
plugins {
kotlin("multiplatform") version "2.0.0"
id("com.android.library")
`maven-publish`
id("org.jetbrains.dokka") version "1.9.20"
kotlin("plugin.serialization") version "2.0.0"
}
group = "com.alphacephei"
version = "0.3.75"
repositories {
google()
mavenCentral()
}
val dokkaOutputDir = "$buildDir/dokka"
tasks.getByName<DokkaTask>("dokkaHtml") {
outputDirectory.set(file(dokkaOutputDir))
}
val deleteDokkaOutputDir by tasks.register<Delete>("deleteDokkaOutputDirectory") {
delete(dokkaOutputDir)
}
val javadocJar = tasks.register<Jar>("javadocJar") {
dependsOn(deleteDokkaOutputDir, tasks.dokkaHtml)
archiveClassifier.set("javadoc")
from(dokkaOutputDir)
}
fun org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension.native(
configure: org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetWithHostTests.() -> Unit = {}
) {
when {
org.jetbrains.kotlin.konan.target.HostManager.hostIsMingw -> mingwX64("native")
org.jetbrains.kotlin.konan.target.HostManager.hostIsLinux -> linuxX64("native")
org.jetbrains.kotlin.konan.target.HostManager.hostIsMac -> if (org.jetbrains.kotlin.konan.target.HostManager.hostArch() == "arm64") {
macosArm64("native")
} else {
macosX64("native")
}
else -> error("Unsupported Host OS: ${org.jetbrains.kotlin.konan.target.HostManager.hostOs()}")
}.apply(configure)
}
kotlin {
jvm {
@OptIn(ExperimentalKotlinGradlePluginApi::class)
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
}
testRuns["test"].executionTask.configure {
useJUnitPlatform()
environment("MODEL", "VOSK_MODEL")
//environment("MODEL", "/home/doomsdayrs/Downloads/vosk-model-small-en-us-0.15/")
environment("LIBRARY", "VOSK_PATH")
//environment("LIBRARY", "/usr/local/lib64/libvosk/libvosk.so")
environment("AUDIO", "$projectDir/../python/example/test.wav")
}
}
androidTarget {
publishAllLibraryVariants()
}
/**
* If native target should be enabled or not.
*
* Currently disabled as there is no proper packaging distribution currently.
*/
@Suppress("SimplifyBooleanWithConstants") // Ignore, the false is for overrides
val enableNative = System.getenv("NATIVE_EXPERIMENT") == "true" || false
if (enableNative)
native {
val main by compilations.getting
val libvosk by main.cinterops.creating
binaries {
sharedLib()
}
}
@OptIn(ExperimentalKotlinGradlePluginApi::class)
applyDefaultHierarchyTemplate {
withJvm()
withAndroidTarget()
if (enableNative)
withNative()
}
publishing {
publications {
withType<MavenPublication> {
artifact(javadocJar)
pom {
url.set("http://www.alphacephei.com.com/vosk/")
licenses {
license {
name.set("The Apache License, Version 2.0")
url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
}
}
developers {
developer {
id.set("com.alphacephei")
name.set("Alpha Cephei Inc")
email.set("contact@alphacephei.com")
}
}
scm {
connection.set("scm:git:git://github.com/alphacep/vosk-api.git")
url.set("https://github.com/alphacep/vosk-api/")
}
}
}
}
}
val jna_version = "5.14.0"
val coroutines_version = "1.7.3"
sourceSets {
val commonMain by getting {
dependencies {
api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.0")
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version")
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version")
}
}
val jvmMain by getting {
dependencies {
api("net.java.dev.jna:jna:$jna_version")
}
}
val jvmTest by getting
if (enableNative) {
val nativeMain by getting
}
val androidMain by getting {
dependsOn(jvmMain)
dependencies {
api("net.java.dev.jna:jna:$jna_version@aar")
}
}
val androidUnitTest by getting {
dependencies {
implementation("junit:junit:4.13.2")
}
}
}
}
android {
namespace = "com.alphacephei.library"
compileSdk = 34
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdk = 24
targetSdk = 34
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
publishing {
multipleVariants {
withSourcesJar()
withJavadocJar()
allVariants()
}
}
}
+31
View File
@@ -0,0 +1,31 @@
/*
* Copyright 2020 Alpha Cephei Inc. & Doomsdayrs
*
* 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.
*/
pluginManagement {
repositories {
google()
gradlePluginPortal()
mavenCentral()
}
resolutionStrategy {
eachPlugin {
if (requested.id.namespace == "com.android") {
useModule("com.android.tools.build:gradle:8.3.0")
}
}
}
}
rootProject.name = "vosk-api-kotlin"
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.alphacephei.library">
<uses-permission android:name="android.permission.RECORD_AUDIO" />
</manifest>
@@ -0,0 +1,46 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk.android
/**
* Interface to receive recognition results
*/
interface RecognitionListener {
/**
* Called when partial recognition result is available.
*/
fun onPartialResult(hypothesis: String)
/**
* Called after silence occured.
*/
fun onResult(hypothesis: String)
/**
* Called after stream end.
*/
fun onFinalResult(hypothesis: String)
/**
* Called when an error occurs.
*/
fun onError(exception: Exception)
/**
* Called after timeout expired
*/
fun onTimeout()
}
@@ -0,0 +1,241 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk.android
import android.annotation.SuppressLint
import android.media.AudioFormat
import android.media.AudioRecord
import android.media.MediaRecorder.AudioSource
import android.os.Handler
import android.os.Looper
import org.vosk.Recognizer
import java.io.IOException
import kotlin.math.roundToInt
/**
* Service that records audio in a thread, passes it to a recognizer and emits
* recognition results. Recognition events are passed to a client using
* [RecognitionListener]
*/
class SpeechService @Throws(IOException::class) constructor(
private val recognizer: Recognizer,
sampleRate: Float
) {
private val sampleRate: Int
private val bufferSize: Int
private val recorder: AudioRecord
private var recognizerThread: RecognizerThread? = null
private val mainHandler = Handler(Looper.getMainLooper())
/**
* Creates speech service. Service holds the AudioRecord object, so you
* need to call [.shutdown] in order to properly finalize it.
*
* @throws IOException thrown if audio recorder can not be created for some reason.
*/
init {
this.sampleRate = sampleRate.toInt()
bufferSize = (this.sampleRate * BUFFER_SIZE_SECONDS).roundToInt()
@SuppressLint("MissingPermission")
recorder = AudioRecord(
AudioSource.VOICE_RECOGNITION, this.sampleRate,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT, bufferSize * 2
)
if (recorder.state == AudioRecord.STATE_UNINITIALIZED) {
recorder.release()
throw IOException(
"Failed to initialize recorder. Microphone might be already in use."
)
}
}
/**
* Starts recognition. Does nothing if recognition is active.
*
* @return true if recognition was actually started
*/
fun startListening(listener: RecognitionListener): Boolean {
if (null != recognizerThread) return false
recognizerThread = RecognizerThread(listener)
recognizerThread!!.start()
return true
}
/**
* Starts recognition. After specified timeout listening stops and the
* endOfSpeech signals about that. Does nothing if recognition is active.
*
*
* timeout - timeout in milliseconds to listen.
*
* @return true if recognition was actually started
*/
fun startListening(listener: RecognitionListener, timeout: Int): Boolean {
if (null != recognizerThread) return false
recognizerThread = RecognizerThread(listener, timeout)
recognizerThread!!.start()
return true
}
private fun stopRecognizerThread(): Boolean {
if (null == recognizerThread) return false
try {
recognizerThread!!.interrupt()
recognizerThread!!.join()
} catch (e: InterruptedException) {
// Restore the interrupted status.
Thread.currentThread().interrupt()
}
recognizerThread = null
return true
}
/**
* Stops recognition. Listener should receive final result if there is
* any. Does nothing if recognition is not active.
*
* @return true if recognition was actually stopped
*/
fun stop(): Boolean {
return stopRecognizerThread()
}
/**
* Cancel recognition. Do not post any new events, simply cancel processing.
* Does nothing if recognition is not active.
*
* @return true if recognition was actually stopped
*/
fun cancel(): Boolean {
if (recognizerThread != null) {
recognizerThread!!.setPause(true)
}
return stopRecognizerThread()
}
/**
* Shutdown the recognizer and release the recorder
*/
fun shutdown() {
recorder.release()
}
fun setPause(paused: Boolean) {
if (recognizerThread != null) {
recognizerThread!!.setPause(paused)
}
}
/**
* Resets recognizer in a thread, starts recognition over again
*/
fun reset() {
if (recognizerThread != null) {
recognizerThread!!.reset()
}
}
private inner class RecognizerThread @JvmOverloads constructor(
var listener: RecognitionListener,
timeout: Int = Companion.NO_TIMEOUT
) : Thread() {
private var remainingSamples: Int
private val timeoutSamples: Int
@Volatile
private var paused = false
@Volatile
private var reset = false
init {
timeoutSamples = if (timeout != Companion.NO_TIMEOUT) {
timeout * sampleRate / 1000
} else {
Companion.NO_TIMEOUT
}
remainingSamples = timeoutSamples
}
/**
* When we are paused, don't process audio by the recognizer and don't emit
* any listener results
*
* @param paused the status of pause
*/
fun setPause(paused: Boolean) {
this.paused = paused
}
/**
* Set reset state to signal reset of the recognizer and start over
*/
fun reset() {
reset = true
}
override fun run() {
recorder.startRecording()
if (recorder.recordingState == AudioRecord.RECORDSTATE_STOPPED) {
recorder.stop()
val ioe = IOException(
"Failed to start recording. Microphone might be already in use."
)
mainHandler.post { listener.onError(ioe) }
}
val buffer = ShortArray(bufferSize)
while (!interrupted()
&& (timeoutSamples == Companion.NO_TIMEOUT || remainingSamples > 0)
) {
val nread = recorder.read(buffer, 0, buffer.size)
if (paused) {
continue
}
if (reset) {
recognizer.reset()
reset = false
}
if (nread < 0) throw RuntimeException("error reading audio buffer")
if (recognizer.acceptWaveform(buffer)) {
val result = recognizer.result
mainHandler.post { listener.onResult(result) }
} else {
val partialResult = recognizer.partialResult
mainHandler.post { listener.onPartialResult(partialResult) }
}
if (timeoutSamples != NO_TIMEOUT) {
remainingSamples -= nread
}
}
recorder.stop()
if (!paused) {
// If we met timeout signal that speech ended
if (timeoutSamples != NO_TIMEOUT && remainingSamples <= 0) {
mainHandler.post { listener.onTimeout() }
} else {
val finalResult = recognizer.finalResult
mainHandler.post { listener.onFinalResult(finalResult) }
}
}
}
}
companion object {
private const val NO_TIMEOUT = -1
private const val BUFFER_SIZE_SECONDS = 0.2f
}
}
@@ -0,0 +1,151 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk.android
import android.os.Handler
import android.os.Looper
import org.vosk.Recognizer
import java.io.IOException
import java.io.InputStream
import kotlin.math.roundToInt
/**
* Service that recognizes stream audio in a thread, passes it to a recognizer and emits
* recognition results. Recognition events are passed to a client using
* [RecognitionListener]
*/
class SpeechStreamService(
private val recognizer: Recognizer,
inputStream: InputStream,
sampleRate: Float
) {
private val inputStream: InputStream
private val sampleRate: Int
private val bufferSize: Int
private var recognizerThread: Thread? = null
private val mainHandler = Handler(Looper.getMainLooper())
/**
* Creates speech service.
*/
init {
this.sampleRate = sampleRate.toInt()
this.inputStream = inputStream
bufferSize = (this.sampleRate * BUFFER_SIZE_SECONDS * 2).roundToInt()
}
/**
* Starts recognition. Does nothing if recognition is active.
*
* @return true if recognition was actually started
*/
fun start(listener: RecognitionListener): Boolean {
if (null != recognizerThread) return false
recognizerThread = RecognizerThread(listener)
recognizerThread!!.start()
return true
}
/**
* Starts recognition. After specified timeout listening stops and the
* endOfSpeech signals about that. Does nothing if recognition is active.
*
*
* timeout - timeout in milliseconds to listen.
*
* @return true if recognition was actually started
*/
fun start(listener: RecognitionListener, timeout: Int): Boolean {
if (null != recognizerThread) return false
recognizerThread = RecognizerThread(listener, timeout)
recognizerThread!!.start()
return true
}
/**
* Stops recognition. All listeners should receive final result if there is
* any. Does nothing if recognition is not active.
*
* @return true if recognition was actually stopped
*/
fun stop(): Boolean {
if (null == recognizerThread) return false
try {
recognizerThread!!.interrupt()
recognizerThread!!.join()
} catch (e: InterruptedException) {
// Restore the interrupted status.
Thread.currentThread().interrupt()
}
recognizerThread = null
return true
}
private inner class RecognizerThread @JvmOverloads constructor(
var listener: RecognitionListener,
timeout: Int = Companion.NO_TIMEOUT
) : Thread() {
private var remainingSamples: Int
private val timeoutSamples: Int
init {
if (timeout != Companion.NO_TIMEOUT) timeoutSamples =
timeout * sampleRate / 1000 else timeoutSamples = Companion.NO_TIMEOUT
remainingSamples = timeoutSamples
}
override fun run() {
val buffer = ByteArray(bufferSize)
while (!interrupted()
&& (timeoutSamples == Companion.NO_TIMEOUT || remainingSamples > 0)
) {
try {
val nread = inputStream.read(buffer, 0, buffer.size)
if (nread < 0) {
break
} else {
val isSilence: Boolean = recognizer.acceptWaveform(buffer)
if (isSilence) {
val result = recognizer.result
mainHandler.post { listener.onResult(result) }
} else {
val partialResult = recognizer.partialResult
mainHandler.post { listener.onPartialResult(partialResult) }
}
}
if (timeoutSamples != NO_TIMEOUT) {
remainingSamples -= nread
}
} catch (e: IOException) {
mainHandler.post { listener.onError(e) }
}
}
// If we met timeout signal that speech ended
if (timeoutSamples != NO_TIMEOUT && remainingSamples <= 0) {
mainHandler.post { listener.onTimeout() }
} else {
val finalResult = recognizer.finalResult
mainHandler.post { listener.onFinalResult(finalResult) }
}
}
}
companion object {
private const val NO_TIMEOUT = -1
private const val BUFFER_SIZE_SECONDS = 0.2f
}
}
@@ -0,0 +1,138 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk.android
import android.content.Context
import android.content.res.AssetManager
import android.os.Environment
import android.os.Handler
import android.os.Looper
import android.util.Log
import org.vosk.Model
import java.io.*
import java.util.concurrent.Executor
import java.util.concurrent.Executors
import java.util.function.Consumer
/**
* Provides utility methods to sync model files to external storage to allow
* C++ code access them. Relies on file named "uuid" to track updates.
*/
object StorageService {
private val TAG = StorageService::class.simpleName
@JvmStatic
fun unpack(
context: Context,
sourcePath: String,
targetPath: String,
completeCallback: Consumer<Model>,
errorCallback: Consumer<IOException>
) {
val executor: Executor =
Executors.newSingleThreadExecutor() // change according to your requirements
val handler = Handler(Looper.getMainLooper())
executor.execute {
try {
val outputPath = sync(context, sourcePath, targetPath)
val model = Model(outputPath)
handler.post { completeCallback.accept(model) }
} catch (e: IOException) {
handler.post { errorCallback.accept(e) }
}
}
}
@JvmStatic
@Throws(IOException::class)
fun sync(context: Context, sourcePath: String, targetPath: String): String {
val assetManager = context.assets
val externalFilesDir = context.getExternalFilesDir(null)
?: throw IOException(
"cannot get external files dir, "
+ "external storage state is " + Environment.getExternalStorageState()
)
val targetDir = File(externalFilesDir, targetPath)
val resultPath = File(targetDir, sourcePath).absolutePath
val sourceUUID = readLine(assetManager.open("$sourcePath/uuid"))
try {
val targetUUID = readLine(FileInputStream(File(targetDir, "$sourcePath/uuid")))
if (targetUUID == sourceUUID) return resultPath
} catch (e: FileNotFoundException) {
// ignore
}
deleteContents(targetDir)
copyAssets(assetManager, sourcePath, targetDir)
// Copy uuid
copyFile(assetManager, "$sourcePath/uuid", targetDir)
return resultPath
}
@Throws(IOException::class)
private fun readLine(inputStream: InputStream): String {
return BufferedReader(InputStreamReader(inputStream)).use { it.readLine() }
}
private fun deleteContents(dir: File): Boolean {
val files = dir.listFiles()
var success = true
if (files != null) {
for (file in files) {
if (file.isDirectory) {
success = success and deleteContents(file)
}
if (!file.delete()) {
success = false
}
}
}
return success
}
@Throws(IOException::class)
private fun copyAssets(assetManager: AssetManager, path: String, outPath: File) {
val assets = assetManager.list(path) ?: return
if (assets.isEmpty()) {
if (!path.endsWith("uuid")) copyFile(assetManager, path, outPath)
} else {
val dir = File(outPath, path)
if (!dir.exists()) {
Log.v(TAG, "Making directory " + dir.absolutePath)
if (!dir.mkdirs()) {
Log.v(TAG, "Failed to create directory " + dir.absolutePath)
}
}
for (asset in assets) {
copyAssets(assetManager, "$path/$asset", outPath)
}
}
}
@Throws(IOException::class)
private fun copyFile(assetManager: AssetManager, fileName: String, outPath: File) {
Log.v(TAG, "Copy $fileName to $outPath")
assetManager.open(fileName).use { inputStream ->
FileOutputStream("$outPath/$fileName").use { out ->
val buffer = ByteArray(4000)
var read: Int
while (inputStream.read(buffer).also { read = it } != -1) {
out.write(buffer, 0, read)
}
}
}
}
}
@@ -0,0 +1,22 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk
/**
* Thrown when a [Recognizer] cannot accept a given waveform
*/
class AcceptWaveformException(data: Any) : Exception("Could not accept waveform: $data")
@@ -0,0 +1,38 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk
import org.vosk.exception.ModelException
/**
* Batch model object
*
* @since 26 / 12 / 2022
* @constructor Creates the batch recognizer object
*/
expect class BatchModel @Throws(ModelException::class) constructor(path: String) : Freeable {
/**
* Releases batch model object
*/
override fun free()
/**
* Wait for the processing
*/
fun await()
}
@@ -0,0 +1,62 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk
/**
* Batch recognizer object
*
* @since 26 / 12 / 2022
* @constructor Creates batch recognizer object
*/
expect class BatchRecognizer constructor(model: BatchModel, sampleRate: Float) : Freeable {
/**
* Releases batch recognizer object
*/
override fun free()
/**
* Accept batch voice data
*/
fun acceptWaveform(data: ByteArray)
/**
* Set NLSML output
* @param nlsml - boolean value
*/
fun setNLSML(nlsml: Boolean)
/**
* Closes the stream
*/
fun finishStream()
/**
* Return results
*/
val frontResult: String
/**
* Release and free first retrieved result
*/
fun pop()
/**
* Get amount of pending chunks for more intelligent waiting
*/
val pendingChunks: Int
}
@@ -0,0 +1,27 @@
/*
* Copyright 2024 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk
/**
* VoskEpMode
*/
enum class EndPointerMode {
ANSWER_DEFAULT,
ANSWER_SHORT,
ANSWER_LONG,
ANSWER_VERY_LONG
}
@@ -0,0 +1,31 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk
/**
* Denotes an object that must be freed afterwards.
*
* On JVM, This is done via AutoClosable.
*/
@Suppress("SpellCheckingInspection")
interface Freeable {
/**
* Dereference the related object
*/
fun free()
}
@@ -0,0 +1,40 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk
/**
* Log level for Kaldi messages.
*
* @since 26 / 12 / 2022
*/
enum class LogLevel(val value: Int) {
/**
* Don't print info messages
*/
WARNINGS(-1),
/**
* Default value to print info and error messages but no debug
*/
INFO(0),
/**
* More verbose mode
*/
DEBUG(1);
}
@@ -0,0 +1,52 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk
import org.vosk.exception.IOException
/**
* Model stores all the data required for recognition
*
* It contains static data and can be shared across processing
* threads.
*
* @since 26 / 12 / 2022
* @constructor Loads model data from the file and returns the model object
* @param path the path of the model on the filesystem
* @throws IOException if the path provided is invalid
*/
expect class Model @Throws(IOException::class) constructor(path: String) : Freeable {
/**
* Check if a word can be recognized by the model
* @param word: the word
* @returns the word symbol if @param word exists inside the model
* or -1 otherwise.
* Reminding that word symbol 0 is for <epsilon>
*/
fun findWord(word: String): Int
/**
* 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.
*/
override fun free()
}
@@ -0,0 +1,273 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk
import org.vosk.exception.RecognizerException
/**
* 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
*
* @since 26 / 12 / 2022
*/
expect class Recognizer : Freeable {
/**
* 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 going to feed into the recognizer.
* Make sure this rate matches the audio content, it is a common
* issue causing accuracy problems.
* @throws RecognizerException if a problem occurred
*/
@Throws(RecognizerException::class)
constructor(model: Model, sampleRate: Float)
/**
* 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 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 going to feed into the recognizer.
* Make sure this rate matches the audio content, it is a common
* issue causing accuracy problems.
* @param speakerModel speaker model for speaker identification
* @throws RecognizerException if a problem occurred
*/
@Throws(RecognizerException::class)
constructor(model: Model, sampleRate: Float, speakerModel: SpeakerModel)
/**
* 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 model VoskModel containing static data for recognizer. Model can be
* shared across recognizers, even running in different threads.
* @param sampleRate The valuesample rate of the audio you going to feed into the recognizer.
* Make sure this rate matches the audio content, it is a common
* issue causing accuracy problems.
* @param grammar The string with the list of phrases to recognize as JSON array of strings,
* for example "["one two three four five", "[unk]"]".
*
* @throws RecognizerException if a problem occurred
*/
@Throws(RecognizerException::class)
constructor(model: Model, sampleRate: Float, grammar: String)
/**
* Adds speaker model to already initialized recognizer
*
* Can add speaker recognition model to already created recognizer. Helps to initialize
* speaker recognition for grammar-based recognizer.
*
* @param speakerModel Speaker recognition model
*/
fun setSpeakerModel(speakerModel: SpeakerModel)
/**
* Reconfigures recognizer to use grammar
*
* @param grammar Set of phrases in JSON array of strings or "[]" to use default model graph.
* See also vosk_recognizer_new_grm
*/
fun setGrammar(grammar: String)
/**
* Configures recognizer to output n-best results
*
* <pre>
* {
* "alternatives": [
* { "text": "one two three four five", "confidence": 0.97 },
* { "text": "one two three for five", "confidence": 0.03 },
* ]
* }
* </pre>
*
* @param maxAlternatives - maximum alternatives to return from recognition results
*/
fun setMaxAlternatives(maxAlternatives: Int)
/**
* Enables words with times in the output
*
* <pre>
* "result" : [{
* "conf" : 1.000000,
* "end" : 1.110000,
* "start" : 0.870000,
* "word" : "what"
* }, {
* "conf" : 1.000000,
* "end" : 1.530000,
* "start" : 1.110000,
* "word" : "zero"
* }, {
* "conf" : 1.000000,
* "end" : 1.950000,
* "start" : 1.530000,
* "word" : "zero"
* }, {
* "conf" : 1.000000,
* "end" : 2.340000,
* "start" : 1.950000,
* "word" : "zero"
* }, {
* "conf" : 1.000000,
* "end" : 2.610000,
* "start" : 2.340000,
* "word" : "one"
* }],
* </pre>
*
* C equivalent = vosk_recognizer_set_words
* @param words - boolean value
*/
fun setOutputWordTimes(words: Boolean)
/**
* Like [setOutputWordTimes] return words and confidences in partial results
*
* @param partialWords - boolean value
*/
fun setPartialWords(partialWords: Boolean)
/**
* Set NLSML output
* @param nlsml - boolean value
*/
fun setNLSML(nlsml: Boolean)
/**
* Set endpointer scaling factor
*
* @param mode Endpointer mode
**/
fun setEndPointerMode(mode: EndPointerMode)
/**
* Set endpointer delays
*
* @param tStartMax timeout for stopping recognition in case of initial silence (usually around 5.0)
* @param tEnd timeout for stopping recognition in milliseconds after we recognized something (usually around 0.5 - 1.0)
* @param tMax timeout for forcing utterance end in milliseconds (usually around 20-30)
**/
fun setEndPointerDelays(tStartMax: Float, tEnd: Float, tMax: Float)
/**
* 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
* 1 - If silence is occurred and you can retrieve a new utterance with result method
* 0 - If decoding continues
* -1 - If exception occurred
*/
@Throws(AcceptWaveformException::class)
fun acceptWaveform(data: ByteArray): Boolean
/**
* Same as [acceptWaveform] but the version with the short data for language bindings where you have
* audio as array of shorts
*/
@Throws(AcceptWaveformException::class)
fun acceptWaveform(data: ShortArray): Boolean
/**
* Same as [acceptWaveform] but the version with the float data for language bindings where you have
* audio as array of floats
*/
@Throws(AcceptWaveformException::class)
fun acceptWaveform(data: FloatArray): Boolean
/**
* 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>
* {
* "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 [setOutputWordTimes].
*/
val result: String
/**
* 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>
*/
val finalResult: String
/**
* 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.
*/
val partialResult: String
/**
* Resets the recognizer
*
* Resets current results so the recognition can continue from scratch
*/
fun reset()
/**
* Releases recognizer object
*
* Underlying model is also unreferenced and if needed released
*/
override fun free()
}
@@ -0,0 +1,40 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk
import org.vosk.exception.ModelException
/**
* Speaker model is the same as model but contains the data
* for speaker identification.
*
* @since 26 / 12 / 2022
* @constructor Loads speaker model data from the file and returns the model object
* @param path the path of the model on the filesystem
* @throws ModelException if the path provided is invalid
*/
expect class SpeakerModel @Throws(ModelException::class) constructor(path: String) : Freeable {
/**
* 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.
*/
override fun free()
}
@@ -0,0 +1,32 @@
/*
* Copyright 2024 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk
/**
* Inverse text normalization
*
* @since 2024/06/19
* @constructor Create text processor
*/
expect class TextProcessor constructor(tagger: Char, verbalizer: Char) : Freeable {
/** Release text processor */
override fun free()
/** Convert string */
fun itn(input: Char): Char
}
@@ -0,0 +1,46 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk
/**
* 26 / 12 / 2022
*
* Control overarching features of libvosk.
*/
expect object Vosk {
/**
* Set log level for Kaldi messages
*
* @param logLevel the level
*/
fun setLogLevel(logLevel: LogLevel)
/**
* Init, automatically select a CUDA device and allow multithreading.
* Must be called once from the main thread.
* Has no effect if HAVE_CUDA flag is not set.
*/
fun gpuInit()
/**
* Init CUDA device in a multi-threaded environment.
* Must be called for each thread.
* Has no effect if HAVE_CUDA flag is not set.
*/
fun gpuThreadInit()
}
@@ -0,0 +1,22 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk.exception
/**
* Internal common IO exception. On JVM this is just a type alias.
*/
expect open class IOException(message: String?) : Exception
@@ -0,0 +1,22 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk.exception
/**
* Thrown when there is an exception creating a model.
*/
class ModelException(path: String): IOException("Failed to find model: $path")
@@ -0,0 +1,22 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk.exception
/**
* Thrown when the recognizer fails to be created
*/
class RecognizerException: IOException("Failed to create recognizer.")
@@ -0,0 +1,31 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk.json
import kotlinx.serialization.Serializable
/**
* Represents an alternative result.
*
* @since 26 / 12 / 2022
*/
@Serializable
data class Alternative(
val confidence: Double,
val result: List<Result> = emptyList(),
val text: String
)
@@ -0,0 +1,61 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk.json
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.encodeToJsonElement
import org.vosk.Recognizer
import org.vosk.Model
import org.vosk.exception.RecognizerException
/*
* 26 / 12 / 2022
*/
/**
* Vosk JSON encoder
*/
val voskJson = Json { encodeDefaults = true }
/**
* Get the result as a JSON object
*/
fun Recognizer.resultAsJson(): ResultOutput =
voskJson.decodeFromString(result)
/**
* Get the final result as a JSON object
*/
fun Recognizer.finalResultAsJson(): ResultOutput =
voskJson.decodeFromString(finalResult)
/**
* Get the partial result as a JSON object
*/
fun Recognizer.partialResultAsJson(): PartialResultOutput =
voskJson.decodeFromString(partialResult)
/**
* Create a [Recognizer], but using a list for grammar instead.
*
* The grammar list is converted into a JSON array
*/
@Throws(RecognizerException::class)
fun Recognizer(model: Model, sampleRate: Float, grammar: List<String>) =
Recognizer(model, sampleRate, voskJson.encodeToJsonElement(grammar).toString())
@@ -0,0 +1,32 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk.json
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* Represents a partial result
*
* @since 26 / 12 / 2022
*/
@Serializable
data class PartialResultOutput(
val partial: String,
@SerialName("partial_result")
val partialResult: List<Result> = emptyList(),
)
@@ -0,0 +1,32 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk.json
import kotlinx.serialization.Serializable
/**
* Represents a result for any given word.
*
* @since 26 / 12 / 2022
*/
@Serializable
data class Result(
val conf: Double? = null,
val end: Double,
val start: Double,
val word: String,
)
@@ -0,0 +1,31 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk.json
import kotlinx.serialization.Serializable
/**
* Result output
*
* @since 26 / 12 / 2022
*/
@Serializable
data class ResultOutput(
val alternatives: List<Alternative> = emptyList(),
val result: List<Result> = emptyList(),
val text: String? = null
)
@@ -0,0 +1,38 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk.json
/**
* For extension functions transforming (input streams->recognizers) into flows.
*/
sealed interface WaveformResult {
/**
* A result made after a period of silence.
*/
data class Result(val result: ResultOutput) : WaveformResult
/**
* A partial result that is being made in progress.
*/
data class PartialResult(val result: PartialResultOutput) : WaveformResult
/**
* A final result made after there is no more content to feed.
*/
data class FinalResult(val result: ResultOutput) : WaveformResult
}
@@ -0,0 +1,83 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk
import com.sun.jna.PointerType
import org.vosk.exception.ModelException
import java.io.File
import java.nio.file.Path
import kotlin.io.path.absolutePathString
/**
* Batch model object
*
* @since 26 / 12 / 2022
*/
actual class BatchModel : Freeable, PointerType, AutoCloseable {
/**
* Empty constructor for JNA
*/
constructor()
/**
* Creates the batch recognizer object
*/
@Throws(ModelException::class)
actual constructor(path: String) : super(
LibVosk.vosk_batch_model_new(path) ?: throw ModelException(path)
)
/**
* Constructor using a Path, will retrieve absolutePath
*
* @param path to batch model
*/
@Throws(ModelException::class)
constructor(path: Path) : this(path.absolutePathString())
/**
* Constructor using a File, will retrieve absolutePath
*
* @param file to batch model
*/
@Throws(ModelException::class)
constructor(file: File) : this(file.absolutePath)
/**
* Releases batch model object
*/
actual override fun free() {
LibVosk.vosk_batch_model_free(this)
}
/**
* Wait for the processing
*/
actual fun await() {
LibVosk.vosk_batch_model_wait(this)
}
/**
* @see free
*/
override fun close() {
free()
}
}
@@ -0,0 +1,95 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk
import com.sun.jna.PointerType
/**
* Batch recognizer object
*
* @since 26 / 12 / 2022
* @constructor Creates batch recognizer object
*/
actual class BatchRecognizer : Freeable, PointerType, AutoCloseable {
/**
* Empty constructor for JNA
*/
constructor()
/**
* Creates batch recognizer object
*/
actual constructor(model: BatchModel, sampleRate: Float) :
super(LibVosk.vosk_batch_recognizer_new(model, sampleRate))
/**
* Releases batch recognizer object
*/
actual override fun free() {
LibVosk.vosk_batch_recognizer_free(this);
}
/**
* Accept batch voice data
*/
actual fun acceptWaveform(data: ByteArray) {
LibVosk.vosk_batch_recognizer_accept_waveform(this, data, data.size)
}
/**
* Set NLSML output
* @param nlsml - boolean value
*/
actual fun setNLSML(nlsml: Boolean) {
LibVosk.vosk_batch_recognizer_set_nlsml(this, nlsml)
}
/**
* Closes the stream
*/
actual fun finishStream() {
LibVosk.vosk_batch_recognizer_finish_stream(this)
}
/**
* Return results
*/
actual val frontResult: String
get() = LibVosk.vosk_batch_recognizer_front_result(this)
/**
* Release and free first retrieved result
*/
actual fun pop() {
LibVosk.vosk_batch_recognizer_pop(this)
}
/**
* Get amount of pending chunks for more intelligent waiting
*/
actual val pendingChunks: Int
get() = LibVosk.vosk_batch_recognizer_get_pending_chunks(this)
/**
* @see free
*/
override fun close() {
free()
}
}
@@ -0,0 +1,213 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk
import com.sun.jna.Native
import com.sun.jna.Platform
import com.sun.jna.Pointer
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.nio.file.Files
import java.nio.file.StandardCopyOption
/**
* 26 / 12 / 2022
*/
@Suppress("FunctionName")
internal object LibVosk {
@JvmStatic
@Deprecated(
"LibVosk is now for internal JNA, use Vosk instead",
ReplaceWith("Vosk.setLogLevel(logLevel)", "org.vosk.Vosk")
)
fun setLogLevel(logLevel: LogLevel) {
Vosk.setLogLevel(logLevel)
}
@Throws(IOException::class)
private fun unpackDll(targetDir: File, lib: String) {
Vosk::class.java.getResourceAsStream("/win32-x86-64/$lib.dll")!!.use {
Files.copy(
it,
File(targetDir, "$lib.dll").toPath(),
StandardCopyOption.REPLACE_EXISTING
)
}
}
init {
when {
Platform.isAndroid() -> {
Native.register(LibVosk::class.java, "vosk")
}
Platform.isWindows() -> {
// We have to unpack dependencies
try {
// To get a tmp folder we unpack small library and mark it for deletion
val tmpFile: File = Native.extractFromResourcePath(
"/win32-x86-64/empty",
LibVosk::class.java.classLoader
)
val tmpDir = tmpFile.parentFile!!
File(tmpDir, tmpFile.name + ".x").createNewFile()
// Now unpack dependencies
unpackDll(tmpDir, "libwinpthread-1");
unpackDll(tmpDir, "libgcc_s_seh-1");
unpackDll(tmpDir, "libstdc++-6");
} catch (e: IOException) {
// Nothing for now, it will fail on next step
} finally {
Native.register(LibVosk::class.java, "libvosk");
}
}
else -> {
Native.register(LibVosk::class.java, "vosk");
}
}
}
external fun vosk_model_new(path: String): Pointer?
external fun vosk_model_free(model: Model)
external fun vosk_model_find_word(model: Model, word: String): Int
external fun vosk_spk_model_new(path: String): Pointer?
external fun vosk_spk_model_free(model: SpeakerModel)
external fun vosk_recognizer_new(model: Model, sampleRate: Float): Pointer?
external fun vosk_recognizer_new_spk(
model: Model,
sampleRate: Float,
spkModel: SpeakerModel
): Pointer?
external fun vosk_recognizer_new_grm(
model: Model,
sampleRate: Float,
grammar: String?
): Pointer?
external fun vosk_recognizer_set_spk_model(recognizer: Recognizer, spk_model: SpeakerModel)
external fun vosk_recognizer_set_grm(recognizer: Recognizer, grammar: String)
external fun vosk_recognizer_set_max_alternatives(recognizer: Recognizer, maxAlternatives: Int)
external fun vosk_recognizer_set_words(recognizer: Recognizer, words: Boolean)
external fun vosk_recognizer_set_partial_words(recognizer: Recognizer, partial_words: Boolean)
external fun vosk_recognizer_set_nlsml(recognizer: Recognizer, nlsml: Boolean)
external fun vosk_recognizer_accept_waveform(
recognizer: Recognizer,
data: ByteArray?,
len: Int
): Int
external fun vosk_recognizer_accept_waveform_s(
recognizer: Recognizer,
data: ShortArray?,
len: Int
): Int
external fun vosk_recognizer_accept_waveform_f(
recognizer: Recognizer,
data: FloatArray?,
len: Int
): Int
external fun vosk_recognizer_result(recognizer: Recognizer): String
external fun vosk_recognizer_final_result(recognizer: Recognizer): String
external fun vosk_recognizer_partial_result(recognizer: Recognizer): String
external fun vosk_recognizer_reset(recognizer: Recognizer)
external fun vosk_recognizer_free(recognizer: Recognizer)
external fun vosk_set_log_level(level: Int)
external fun vosk_gpu_init()
external fun vosk_gpu_thread_init()
external fun vosk_batch_model_new(path: String): Pointer?
external fun vosk_batch_model_free(model: BatchModel)
external fun vosk_batch_model_wait(model: BatchModel)
external fun vosk_batch_recognizer_new(batchModel: BatchModel, sampleRate: Float): Pointer
external fun vosk_batch_recognizer_free(recognizer: BatchRecognizer)
external fun vosk_batch_recognizer_accept_waveform(
recognizer: BatchRecognizer,
data: ByteArray?,
length: Int
)
external fun vosk_batch_recognizer_set_nlsml(
recognizer: BatchRecognizer,
nlsml: Boolean
)
external fun vosk_batch_recognizer_finish_stream(
recognizer: BatchRecognizer
)
external fun vosk_batch_recognizer_front_result(
recognizer: BatchRecognizer
): String
external fun vosk_batch_recognizer_pop(recognizer: BatchRecognizer)
external fun vosk_batch_recognizer_get_pending_chunks(recognizer: BatchRecognizer): Int
external fun vosk_text_processor_new(tagger: Char, verbalizer: Char): Pointer
external fun vosk_text_processor_free(processor: TextProcessor)
external fun vosk_text_processor_itn(processor: TextProcessor, input: Char): Char
external fun vosk_recognizer_set_endpointer_mode(recognizer: Recognizer, ordinal: Int)
external fun vosk_recognizer_set_endpointer_delays(
recognizer: Recognizer,
tStartMax: Float,
tEnd: Float,
tMax: Float
)
}
@@ -0,0 +1,95 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk
import com.sun.jna.PointerType
import org.vosk.exception.IOException
import org.vosk.exception.ModelException
import java.io.File
import java.nio.file.Path
import kotlin.io.path.absolutePathString
/**
* Model stores all the data required for recognition
*
* It contains static data and can be shared across processing
* threads.
*
* @since 26 / 12 / 2022
* @constructor Loads model data from the file and returns the model object
* @throws IOException if the path provided is invalid
*/
actual class Model : Freeable, PointerType, AutoCloseable {
/**
* Empty constructor for JNA
*/
constructor()
/**
* Loads model data from the file and returns the model object
*/
@Throws(IOException::class)
actual constructor(path: String) : super(
LibVosk.vosk_model_new(path) ?: throw ModelException(path)
)
/**
* Constructor using a Path, will retrieve absolutePath
*
* @param path to batch model
*/
@Throws(IOException::class)
constructor(path: Path) : this(path.absolutePathString())
/**
* Constructor using a File, will retrieve absolutePath
*
* @param file to batch model
*/
@Throws(IOException::class)
constructor(file: File) : this(file.absolutePath)
/**
* Check if a word can be recognized by the model
* @param word: the word
* @returns the word symbol if @param word exists inside the model
* or -1 otherwise.
* Reminding that word symbol 0 is for <epsilon>
*/
actual fun findWord(word: String): Int =
LibVosk.vosk_model_find_word(this, word)
/**
* 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.
*/
actual override fun free() {
LibVosk.vosk_model_free(this)
}
/**
* @see free
*/
override fun close() {
free()
}
}
@@ -0,0 +1,353 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk
import com.sun.jna.PointerType
import org.vosk.exception.RecognizerException
/**
* 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
*
* @since 26 / 12 / 2022
*/
actual class Recognizer : Freeable, PointerType, AutoCloseable {
/**
* Empty constructor for JNA
*/
constructor()
/**
* 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 going to feed into the recognizer.
* Make sure this rate matches the audio content, it is a common
* issue causing accuracy problems.
* @throws RecognizerException if a problem occurred
*/
@Throws(RecognizerException::class)
actual constructor(model: Model, sampleRate: Float) :
super(LibVosk.vosk_recognizer_new(model, sampleRate) ?: throw RecognizerException())
/**
* 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 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 going to feed into the recognizer.
* Make sure this rate matches the audio content, it is a common
* issue causing accuracy problems.
* @param speakerModel speaker model for speaker identification
* @throws RecognizerException if a problem occurred
*/
@Throws(RecognizerException::class)
actual constructor(
model: Model,
sampleRate: Float,
speakerModel: SpeakerModel
) : super(
LibVosk.vosk_recognizer_new_spk(model, sampleRate, speakerModel)
?: throw RecognizerException()
)
/**
* 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 model VoskModel containing static data for recognizer. Model can be
* shared across recognizers, even running in different threads.
* @param sampleRate The valuesample rate of the audio you going to feed into the recognizer.
* Make sure this rate matches the audio content, it is a common
* issue causing accuracy problems.
* @param grammar The string with the list of phrases to recognize as JSON array of strings,
* for example "["one two three four five", "[unk]"]".
*
* @throws RecognizerException if a problem occurred
*/
@Throws(RecognizerException::class)
actual constructor(
model: Model,
sampleRate: Float,
grammar: String
) : super(
LibVosk.vosk_recognizer_new_grm(model, sampleRate, grammar) ?: throw RecognizerException()
)
/**
* JVM analog of Kotlin extension
* @see [org.vosk.json.Recognizer]
*/
@Throws(RecognizerException::class)
constructor(
model: Model,
sampleRate: Float,
grammar: List<String>
) : super(
// We need the full qualifer to avoid any build issues
@Suppress("RemoveRedundantQualifierName")
org.vosk.json.Recognizer(model, sampleRate, grammar).pointer
)
/**
* Adds speaker model to already initialized recognizer
*
* Can add speaker recognition model to already created recognizer. Helps to initialize
* speaker recognition for grammar-based recognizer.
*
* @param speakerModel Speaker recognition model
*/
actual fun setSpeakerModel(speakerModel: SpeakerModel) {
LibVosk.vosk_recognizer_set_spk_model(this, speakerModel)
}
/**
* Reconfigures recognizer to use grammar
*
* @param grammar Set of phrases in JSON array of strings or "[]" to use default model graph.
* See also vosk_recognizer_new_grm
*/
actual fun setGrammar(grammar: String) {
LibVosk.vosk_recognizer_set_grm(this, grammar)
}
/**
* Configures recognizer to output n-best results
*
* <pre>
* {
* "alternatives": [
* { "text": "one two three four five", "confidence": 0.97 },
* { "text": "one two three for five", "confidence": 0.03 },
* ]
* }
* </pre>
*
* @param maxAlternatives - maximum alternatives to return from recognition results
*/
actual fun setMaxAlternatives(maxAlternatives: Int) {
LibVosk.vosk_recognizer_set_max_alternatives(this, maxAlternatives)
}
/**
* Enables words with times in the output
*
* <pre>
* "result" : [{
* "conf" : 1.000000,
* "end" : 1.110000,
* "start" : 0.870000,
* "word" : "what"
* }, {
* "conf" : 1.000000,
* "end" : 1.530000,
* "start" : 1.110000,
* "word" : "zero"
* }, {
* "conf" : 1.000000,
* "end" : 1.950000,
* "start" : 1.530000,
* "word" : "zero"
* }, {
* "conf" : 1.000000,
* "end" : 2.340000,
* "start" : 1.950000,
* "word" : "zero"
* }, {
* "conf" : 1.000000,
* "end" : 2.610000,
* "start" : 2.340000,
* "word" : "one"
* }],
* </pre>
*
* @param words - boolean value
*/
actual fun setOutputWordTimes(words: Boolean) {
LibVosk.vosk_recognizer_set_words(this, words)
}
/**
* Like [setOutputWordTimes] return words and confidences in partial results
*
* @param partialWords - boolean value
*/
actual fun setPartialWords(partialWords: Boolean) {
LibVosk.vosk_recognizer_set_partial_words(this, partialWords)
}
/**
* Set NLSML output
* @param nlsml - boolean value
*/
actual fun setNLSML(nlsml: Boolean) {
LibVosk.vosk_recognizer_set_nlsml(this, nlsml)
}
/**
* 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
* 1 - If silence is occurred and you can retrieve a new utterance with result method
* 0 - If decoding continues
* -1 - If exception occurred
*/
@Throws(AcceptWaveformException::class)
actual fun acceptWaveform(data: ByteArray): Boolean {
val result = LibVosk.vosk_recognizer_accept_waveform(this, data, data.size)
if (result == -1) throw AcceptWaveformException(data)
return result == 1
}
/**
* Same as [acceptWaveform] but the version with the short data for language bindings where you have
* audio as array of shorts
*/
@Throws(AcceptWaveformException::class)
actual fun acceptWaveform(data: ShortArray): Boolean {
val result = LibVosk.vosk_recognizer_accept_waveform_s(this, data, data.size)
if (result == -1) throw AcceptWaveformException(data)
return result == 1
}
/**
* Same as [acceptWaveform] but the version with the float data for language bindings where you have
* audio as array of floats
*/
@Throws(AcceptWaveformException::class)
actual fun acceptWaveform(data: FloatArray): Boolean {
val result = LibVosk.vosk_recognizer_accept_waveform_f(this, data, data.size)
if (result == -1) throw AcceptWaveformException(data)
return result == 1
}
/**
* 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>
* {
* "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 [setOutputWordTimes].
*/
actual val result: String
get() = LibVosk.vosk_recognizer_result(this)
/**
* 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>
*/
actual val finalResult: String
get() = LibVosk.vosk_recognizer_final_result(this)
/**
* 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.
*/
actual val partialResult: String
get() = LibVosk.vosk_recognizer_partial_result(this)
/**
* Resets the recognizer
*
* Resets current results so the recognition can continue from scratch
*/
actual fun reset() {
LibVosk.vosk_recognizer_reset(this)
}
/**
* Releases recognizer object
*
* Underlying model is also unreferenced and if needed released
*/
actual override fun free() {
LibVosk.vosk_recognizer_free(this)
}
/**
* @see free
*/
override fun close() {
free()
}
/**
* Set endpointer scaling factor
*
* @param mode Endpointer mode
**/
actual fun setEndPointerMode(mode: EndPointerMode) {
LibVosk.vosk_recognizer_set_endpointer_mode(this, mode.ordinal)
}
/**
* Set endpointer delays
*
* @param tStartMax timeout for stopping recognition in case of initial silence (usually around 5.0)
* @param tEnd timeout for stopping recognition in milliseconds after we recognized something (usually around 0.5 - 1.0)
* @param tMax timeout for forcing utterance end in milliseconds (usually around 20-30)
**/
actual fun setEndPointerDelays(
tStartMax: Float,
tEnd: Float,
tMax: Float
) {
LibVosk.vosk_recognizer_set_endpointer_delays(this, tStartMax, tEnd, tMax)
}
}
@@ -0,0 +1,84 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk
import com.sun.jna.PointerType
import org.vosk.exception.ModelException
import java.io.File
import java.nio.file.Path
import kotlin.io.path.absolutePathString
/**
* Speaker model is the same as model but contains the data
* for speaker identification.
*
* @since 26 / 12 / 2022
* @constructor Loads speaker model data from the file and returns the model object
* @throws ModelException if the path provided is invalid
*/
actual class SpeakerModel : Freeable, PointerType, AutoCloseable {
/**
* Empty constructor for JNA
*/
constructor()
/**
* Loads speaker model data from the file and returns the model object
*
* @param path the path of the model on the filesystem
*/
@Throws(ModelException::class)
actual constructor(path: String) : super(
LibVosk.vosk_spk_model_new(path) ?: throw ModelException(path)
)
/**
* Constructor using a Path, will retrieve absolutePath
*
* @param path to batch model
*/
@Throws(ModelException::class)
constructor(path: Path) : this(path.absolutePathString())
/**
* Constructor using a File, will retrieve absolutePath
*
* @param file to batch model
*/
@Throws(ModelException::class)
constructor(file: File) : this(file.absolutePath)
/**
* 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.
*/
actual override fun free() {
LibVosk.vosk_spk_model_free(this)
}
/**
* @see free
*/
override fun close() {
free()
}
}
@@ -0,0 +1,51 @@
/*
* Copyright 2024 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk
import com.sun.jna.PointerType
/**
* Inverse text normalization
*
* @since 2024/06/19
*/
actual class TextProcessor :
Freeable, PointerType, AutoCloseable {
/**
* Create text processor
*/
actual constructor(tagger: Char, verbalizer: Char) :
super(LibVosk.vosk_text_processor_new(tagger, verbalizer))
/** Release text processor */
actual override fun free() {
LibVosk.vosk_text_processor_free(this)
}
/** Convert string */
actual fun itn(input: Char): Char =
LibVosk.vosk_text_processor_itn(this, input)
/**
* @see free
*/
override fun close() {
free()
}
}
@@ -0,0 +1,55 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk
/**
* 26 / 12 / 2022
*
* Control overarching features of libvosk.
*/
actual object Vosk {
/**
* Set log level for Kaldi messages
*
* @param logLevel the level
*/
@JvmStatic
actual fun setLogLevel(logLevel: LogLevel) {
LibVosk.vosk_set_log_level(logLevel.value)
}
/**
* Init, automatically select a CUDA device and allow multithreading.
* Must be called once from the main thread.
* Has no effect if HAVE_CUDA flag is not set.
*/
@JvmStatic
actual fun gpuInit() {
LibVosk.vosk_gpu_init()
}
/**
* Init CUDA device in a multi-threaded environment.
* Must be called for each thread.
* Has no effect if HAVE_CUDA flag is not set.
*/
@JvmStatic
actual fun gpuThreadInit() {
LibVosk.vosk_gpu_thread_init()
}
}
@@ -0,0 +1,55 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import org.vosk.json.WaveformResult
import org.vosk.json.finalResultAsJson
import org.vosk.json.partialResultAsJson
import org.vosk.json.resultAsJson
/**
* Feed an [Flow] of [ByteArray] into a [Recognizer].
*
* The returned flow will emit an [WaveformResult] for each result parsed.
*
* This expects a null terminator to signify the end of a stream.
*
* This will not close the [recognizer] at the end of reading.
*
* Any exceptions will be fed into the flow,
* and should be collected via [kotlinx.coroutines.flow.catch].
*
* Flows on [Dispatchers.IO] to prevent blocking the main thread.
*
* @see [Recognizer.acceptWaveform]
*/
fun Flow<ByteArray?>.feed(recognizer: Recognizer): Flow<WaveformResult> =
map {
if (it != null) {
if (recognizer.acceptWaveform(it)) {
WaveformResult.Result(recognizer.resultAsJson())
} else {
WaveformResult.PartialResult(recognizer.partialResultAsJson())
}
} else {
WaveformResult.FinalResult(recognizer.finalResultAsJson())
}
}.flowOn(Dispatchers.IO)
@@ -0,0 +1,22 @@
/*
* Copyright 2023 Alpha Cephei Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk.exception
/**
* Analog of [java.io.IOException]
*/
actual typealias IOException = java.io.IOException
+212
View File
@@ -0,0 +1,212 @@
/*
* Copyright 2023 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.
*/
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.test.runTest
import org.vosk.*
import java.io.BufferedInputStream
import java.io.FileInputStream
import java.io.IOException
import java.nio.ByteBuffer
import java.nio.ByteOrder
import javax.sound.sampled.AudioSystem
import javax.sound.sampled.UnsupportedAudioFileException
import kotlin.test.Test
class DecoderTest {
val modelPath = System.getenv("MODEL")
val testFile = System.getenv("AUDIO")
init {
System.load(System.getenv("LIBRARY"))
Vosk.setLogLevel(LogLevel.DEBUG)
}
@Test
fun grammarList() {
Model(modelPath).use { model ->
Recognizer(model, 16000f, listOf("one")).apply {
setMaxAlternatives(10)
setOutputWordTimes(true)
setPartialWords(true)
}
}
}
@Test
@Throws(IOException::class, UnsupportedAudioFileException::class)
fun decoderTest() {
Model(modelPath).use { model ->
AudioSystem.getAudioInputStream(BufferedInputStream(FileInputStream(testFile)))
.use { ais ->
Recognizer(model, 16000f).apply {
setMaxAlternatives(10)
setOutputWordTimes(true)
setPartialWords(true)
}.use { recognizer ->
val b = ByteArray(4096)
while (ais.read(b) >= 0) {
if (recognizer.acceptWaveform(b)) {
println(recognizer.result)
} else {
println(recognizer.partialResult)
}
}
println(recognizer.finalResult)
}
}
}
}
@OptIn(ExperimentalCoroutinesApi::class)
@Test
@Throws(IOException::class, UnsupportedAudioFileException::class)
fun decoderTestFlow() = runTest {
Model(modelPath).use { model ->
Recognizer(model, 16000f).apply {
setMaxAlternatives(10)
setOutputWordTimes(true)
setPartialWords(true)
}.use { recognizer ->
AudioSystem.getAudioInputStream(BufferedInputStream(FileInputStream(testFile)))
.use { ais ->
flow {
val b = ByteArray(4096)
while (ais.read(b) >= 0) {
emit(b)
}
emit(null)
}.flowOn(Dispatchers.IO)
.feed(recognizer)
.collect {
println(it)
}
}
}
}
}
/**
* This test aims to simulate the situation for Dicio,
* In which we can receive the audio input stream before the recognizer is setup.
*
* It is recommended to use a large model on desktop to properly see how long it takes to load up.
*/
@Test
@Throws(IOException::class, UnsupportedAudioFileException::class)
fun decoderTestFlowBuffered() {
// Scope to buffer into
val scope = CoroutineScope(Dispatchers.IO)
AudioSystem.getAudioInputStream(BufferedInputStream(FileInputStream(testFile))).use { ais ->
val byteFlow = flow {
val b = ByteArray(4096)
while (ais.read(b) >= 0) {
emit(b)
}
emit(null)
}.flowOn(Dispatchers.IO)
.shareIn(scope, SharingStarted.Eagerly, 100)
// Tell us the current buffer size
println("Buffered size:" + byteFlow.replayCache.size)
var startTime = System.currentTimeMillis()
Model(modelPath).use { model ->
var resultTime = System.currentTimeMillis() - startTime
println("Model initialized in: $resultTime")
startTime = System.currentTimeMillis()
Recognizer(model, 16000f).apply {
setMaxAlternatives(10)
setOutputWordTimes(true)
setPartialWords(true)
}.use { recognizer ->
resultTime = System.currentTimeMillis() - startTime
println("Recognizer initialized in: $resultTime")
println("Buffered size:" + byteFlow.replayCache.size)
runBlocking {
byteFlow
.feed(recognizer)
.take(byteFlow.replayCache.size)
.collect {
println(it)
}
}
}
}
}
}
@Test
@Throws(IOException::class, UnsupportedAudioFileException::class)
fun decoderTestShort() {
Model(modelPath).use { model ->
AudioSystem.getAudioInputStream(BufferedInputStream(FileInputStream(testFile)))
.use { ais ->
Recognizer(model, 16000f).use { recognizer ->
val b = ByteArray(4096)
val s = ShortArray(2048)
while (ais.read(b) >= 0) {
ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(s)
if (recognizer.acceptWaveform(s)) {
println(recognizer.result)
} else {
println(recognizer.partialResult)
}
}
println(recognizer.finalResult)
}
}
}
}
@Test
@Throws(IOException::class, UnsupportedAudioFileException::class)
fun decoderTestGrammar() {
Model(modelPath).use { model ->
AudioSystem.getAudioInputStream(BufferedInputStream(FileInputStream(testFile)))
.use { ais ->
Recognizer(
model, 16000f, "[\"one two three four five six seven eight nine zero oh\"]"
).use { recognizer ->
val b = ByteArray(4096)
while (ais.read(b) >= 0) {
if (recognizer.acceptWaveform(b)) {
println(recognizer.result)
} else {
println(recognizer.partialResult)
}
}
println(recognizer.finalResult)
}
}
}
}
@Test
fun decoderTestException() {
try {
val model = Model("model_missing")
assert(false)
} catch (e: IOException) {
assert(true)
}
}
}
@@ -0,0 +1,25 @@
headers = libvosk/vosk_api.h
package = libvosk
noStringConversion = \
vosk_batch_recognizer_accept_waveform \
vosk_recognizer_accept_waveform
compilerOpts.linux = \
-I/usr/include/libvosk/ \
-I/usr/local/include/libvosk/ \
-I/usr/include/ \
-I/usr/local/include/
compilerOpts.linux_x64 = \
-I/usr/lib64/libvosk/ \
-I/usr/local/lib64/libvosk/
linkerOpts.linux = \
-L/usr/lib/ \
-L/usr/local/lib/ \
-llibvosk
linkerOpts.linux_x64 = \
-L/usr/lib64/ \
-L/usr/local/lib64/
@@ -0,0 +1,50 @@
/*
* Copyright 2020 Alpha Cephei Inc. & Doomsdayrs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk
import cnames.structs.VoskBatchModel
import kotlinx.cinterop.CPointer
import libvosk.vosk_batch_model_free
import libvosk.vosk_batch_model_new
import libvosk.vosk_batch_model_wait
/**
* Batch model object
*
* 26 / 12 / 2022
*/
actual class BatchModel(val pointer: CPointer<VoskBatchModel>) : Freeable {
/**
* Creates the batch recognizer object
*/
@Throws(IOException::class)
actual constructor(path: String) : this(vosk_batch_model_new(path) ?: throw ioException(path))
/**
* Releases batch model object
*/
actual override fun free() {
vosk_batch_model_free(pointer)
}
/**
* Wait for the processing
*/
actual fun await() {
vosk_batch_model_wait(pointer)
}
}
@@ -0,0 +1,86 @@
/*
* Copyright 2020 Alpha Cephei Inc. & Doomsdayrs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk
import cnames.structs.VoskBatchRecognizer
import kotlinx.cinterop.CPointer
import kotlinx.cinterop.toCValues
import kotlinx.cinterop.toKString
import libvosk.*
/**
* Batch recognizer object
*
* 26 / 12 / 2022
*/
actual class BatchRecognizer(val pointer: CPointer<VoskBatchRecognizer>) : Freeable {
/**
* Creates batch recognizer object
*/
actual constructor(
model: BatchModel,
sampleRate: Float
) : this(vosk_batch_recognizer_new(model.pointer, sampleRate)!!)
/**
* Releases batch recognizer object
*/
actual override fun free() {
vosk_batch_recognizer_free(pointer)
}
/**
* Accept batch voice data
*/
actual fun acceptWaveform(data: ByteArray) {
vosk_batch_recognizer_accept_waveform(pointer, data.toCValues(), data.size)
}
/**
* Set NLSML output
* @param nlsml - boolean value
*/
actual fun setNLSML(nlsml: Boolean) {
vosk_batch_recognizer_set_nlsml(pointer, nlsml.toInt())
}
/**
* Closes the stream
*/
actual fun finishStream() {
vosk_batch_recognizer_finish_stream(pointer)
}
/**
* Return results
*/
actual val frontResult: String
get() = vosk_batch_recognizer_front_result(pointer)!!.toKString()
/**
* Release and free first retrieved result
*/
actual fun pop() {
vosk_batch_recognizer_pop(pointer)
}
/**
* Get amount of pending chunks for more intelligent waiting
*/
actual val pendingChunks: Int
get() = vosk_batch_recognizer_get_pending_chunks(pointer)
}
@@ -0,0 +1,23 @@
/*
* Copyright 2020 Alpha Cephei Inc. & Doomsdayrs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk
/**
* Use a [Freeable], then immediately free it and return any values.
*/
fun <T : Freeable, R> T.use(block: (T) -> R): R =
block(this).also { free() }
@@ -0,0 +1,3 @@
package org.vosk
actual class IOException actual constructor(message: String?) : Exception()
@@ -0,0 +1,63 @@
/*
* Copyright 2020 Alpha Cephei Inc. & Doomsdayrs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk
import cnames.structs.VoskModel
import kotlinx.cinterop.CPointer
import libvosk.vosk_model_find_word
import libvosk.vosk_model_free
import libvosk.vosk_model_new
/**
* Model stores all the data required for recognition
*
* It contains static data and can be shared across processing
* threads.
*
* 26 / 12 / 2022
*/
actual class Model(val pointer: CPointer<VoskModel>) : Freeable {
/**
* Loads model data from the file and returns the model object
*
* @param path: the path of the model on the filesystem
* @returns model object or NULL if problem occured
*/
@Throws(IOException::class)
actual constructor(path: String) : this(vosk_model_new(path) ?: throw ioException(path))
/**
* Check if a word can be recognized by the model
* @param word: the word
* @returns the word symbol if @param word exists inside the model
* or -1 otherwise.
* Reminding that word symbol 0 is for <epsilon>
*/
actual fun findWord(word: String): Int =
vosk_model_find_word(pointer, word)
/**
* 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.
*/
actual override fun free() {
vosk_model_free(pointer)
}
}
@@ -0,0 +1,310 @@
/*
* Copyright 2020 Alpha Cephei Inc. & Doomsdayrs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vosk
import kotlinx.cinterop.CPointer
import kotlinx.cinterop.toCValues
import kotlinx.cinterop.toKString
import libvosk.*
/**
* 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
*
* 26 / 12 / 2022
*/
actual class Recognizer(val pointer: CPointer<VoskRecognizer>) : Freeable {
/**
* 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 going to feed into the recognizer.
* Make sure this rate matches the audio content, it is a common
* issue causing accuracy problems.
* @returns recognizer object or NULL if problem occured
*/
actual constructor(
model: Model,
sampleRate: Float
) : this(
vosk_recognizer_new(
model.pointer,
sampleRate
)!!
)
/**
* 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 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 going to feed into the recognizer.
* Make sure this rate matches the audio content, it is a common
* issue causing accuracy problems.
* @param speakerModel speaker model for speaker identification
* @returns recognizer object or NULL if problem occured
*/
actual constructor(
model: Model,
sampleRate: Float,
speakerModel: SpeakerModel
) : this(
vosk_recognizer_new_spk(
model.pointer,
sampleRate,
speakerModel.pointer
)!!
)
/**
* 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 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 going to feed into the recognizer.
* Make sure this rate matches the audio content, it is a common
* issue causing accuracy problems.
* @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 or NULL if problem occured
*/
actual constructor(
model: Model,
sampleRate: Float,
grammar: String
) : this(
vosk_recognizer_new_grm(
model.pointer,
sampleRate,
grammar
)!!
)
/**
* Adds speaker model to already initialized recognizer
*
* Can add speaker recognition model to already created recognizer. Helps to initialize
* speaker recognition for grammar-based recognizer.
*
* @param speakerModel Speaker recognition model
*/
actual fun setSpeakerModel(speakerModel: SpeakerModel) {
vosk_recognizer_set_spk_model(pointer, speakerModel.pointer)
}
/**
* Reconfigures recognizer to use grammar
*
* @param grammar Set of phrases in JSON array of strings or "[]" to use default model graph.
* See also vosk_recognizer_new_grm
*/
actual fun setGrammar(grammar: String) {
vosk_recognizer_set_grm(pointer, grammar)
}
/**
* Configures recognizer to output n-best results
*
* <pre>
* {
* "alternatives": [
* { "text": "one two three four five", "confidence": 0.97 },
* { "text": "one two three for five", "confidence": 0.03 },
* ]
* }
* </pre>
*
* @param maxAlternatives - maximum alternatives to return from recognition results
*/
actual fun setMaxAlternatives(maxAlternatives: Int) {
vosk_recognizer_set_max_alternatives(pointer, maxAlternatives)
}
/**
* Enables words with times in the output
*
* <pre>
* "result" : [{
* "conf" : 1.000000,
* "end" : 1.110000,
* "start" : 0.870000,
* "word" : "what"
* }, {
* "conf" : 1.000000,
* "end" : 1.530000,
* "start" : 1.110000,
* "word" : "zero"
* }, {
* "conf" : 1.000000,
* "end" : 1.950000,
* "start" : 1.530000,
* "word" : "zero"
* }, {
* "conf" : 1.000000,
* "end" : 2.340000,
* "start" : 1.950000,
* "word" : "zero"
* }, {
* "conf" : 1.000000,
* "end" : 2.610000,
* "start" : 2.340000,
* "word" : "one"
* }],
* </pre>
*
* @param words - boolean value
*/
actual fun setWords(words: Boolean) {
vosk_recognizer_set_words(pointer, words.toInt())
}
/**
* Like above return words and confidences in partial results
*
* @param partialWords - boolean value
*/
actual fun setPartialWords(partialWords: Boolean) {
vosk_recognizer_set_partial_words(pointer, partialWords.toInt())
}
/**
* Set NLSML output
* @param nlsml - boolean value
*/
actual fun setNLSML(nlsml: Boolean) {
vosk_recognizer_set_nlsml(pointer, nlsml.toInt())
}
/**
* 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 1 if silence is occured and you can retrieve a new utterance with result method
* 0 if decoding continues
* -1 if exception occured
*/
@Throws(AcceptWaveformException::class)
actual fun acceptWaveform(data: ByteArray): Boolean {
val result = vosk_recognizer_accept_waveform(pointer, data.toCValues(), data.size)
if (result == -1) throw AcceptWaveformException(data)
return result == 1
}
/**
* Same as above but the version with the short data for language bindings where you have
* audio as array of shorts
*/
@Throws(AcceptWaveformException::class)
actual fun acceptWaveform(data: ShortArray): Boolean {
val result = vosk_recognizer_accept_waveform_s(pointer, data.toCValues(), data.size)
if (result == -1) throw AcceptWaveformException(data)
return result == 1
}
/**
* Same as above but the version with the float data for language bindings where you have
* audio as array of floats
*/
@Throws(AcceptWaveformException::class)
actual fun acceptWaveform(data: FloatArray): Boolean {
val result = vosk_recognizer_accept_waveform_f(pointer, data.toCValues(), data.size)
if (result == -1) throw AcceptWaveformException(data)
return result == 1
}
/**
* 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>
* {
* "text" : "what zero zero zero one"
* }
* </pre>
*
* If alternatives enabled it returns result with alternatives, see also vosk_recognizer_set_max_alternatives().
*
* If word times enabled returns word time, see also vosk_recognizer_set_word_times().
*/
actual val result: String
get() = vosk_recognizer_result(pointer)!!.toKString()
/**
* 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>
*/
actual val finalResult: String
get() = vosk_recognizer_result(pointer)!!.toKString()
/**
* 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.
*/
actual val partialResult: String
get() = vosk_recognizer_partial_result(pointer)!!.toKString()
/**
* Resets the recognizer
*
* Resets current results so the recognition can continue from scratch */
actual fun reset() {
vosk_recognizer_reset(pointer)
}
/**
* Releases recognizer object
*
* Underlying model is also unreferenced and if needed released */
actual override fun free() {
vosk_recognizer_free(pointer)
}
}

Some files were not shown because too many files have changed in this diff Show More