diff --git a/.github/workflows/har.yaml b/.github/workflows/har.yaml new file mode 100644 index 000000000..afe99a1be --- /dev/null +++ b/.github/workflows/har.yaml @@ -0,0 +1,181 @@ +name: har + +on: + push: + branches: + - master + # - ohos-har + tags: + - 'v[0-9]+.[0-9]+.[0-9]+*' + + workflow_dispatch: + +concurrency: + group: har-${{ github.ref }} + cancel-in-progress: true + +jobs: + har: + name: Har + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: ccache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: har-linux + + - name: cache-toolchain + id: cache-toolchain-ohos + uses: actions/cache@v4 + with: + path: command-line-tools + key: commandline-tools-linux-x64-5.0.5.200.zip + + - name: Download toolchain + if: steps.cache-toolchain-ohos.outputs.cache-hit != 'true' + shell: bash + run: | + curl -SL -O https://huggingface.co/csukuangfj/harmonyos-commandline-tools/resolve/main/commandline-tools-linux-x64-5.0.5.200.zip + unzip commandline-tools-linux-x64-5.0.5.200.zip + rm commandline-tools-linux-x64-5.0.5.200.zip + + - name: Set environment variable + shell: bash + run: | + echo "$GITHUB_WORKSPACE/command-line-tools/sdk/default/openharmony/native/build-tools/cmake/bin" >> "$GITHUB_PATH" + which cmake + + cmake --version + + ls -lh $GITHUB_WORKSPACE/command-line-tools/sdk/default/openharmony/native/build/cmake/ohos.toolchain.cmake + + echo "====" + cat $GITHUB_WORKSPACE/command-line-tools/sdk/default/openharmony/native/build/cmake/ohos.toolchain.cmake + echo "====" + + # echo "$GITHUB_WORKSPACE/command-line-tools/sdk/default/openharmony/native/llvm/bin" >> "$GITHUB_PATH" + + ls -lh $GITHUB_WORKSPACE/command-line-tools/sdk/default/openharmony/native/llvm/bin/ + echo "--" + ls -lh $GITHUB_WORKSPACE/command-line-tools/sdk/default/openharmony/native/llvm/bin/*unknown* + + cat $GITHUB_PATH + + # /home/runner/work/onnxruntime-libs/onnxruntime-libs/command-line-tools/sdk/default/openharmony/native/llvm/bin/aarch64-unknown-linux-ohos-clang -v || true + export PATH=$PWD/command-line-tools/sdk/default/openharmony/native/llvm/bin:$PATH + echo "path: $PATH" + + which aarch64-unknown-linux-ohos-clang++ || true + which aarch64-unknown-linux-ohos-clang || true + + aarch64-unknown-linux-ohos-clang++ --version || true + aarch64-unknown-linux-ohos-clang --version || true + + which armv7-unknown-linux-ohos-clang++ + which armv7-unknown-linux-ohos-clang + + armv7-unknown-linux-ohos-clang++ --version + armv7-unknown-linux-ohos-clang --version + + which x86_64-unknown-linux-ohos-clang++ + which x86_64-unknown-linux-ohos-clang + + x86_64-unknown-linux-ohos-clang++ --version + x86_64-unknown-linux-ohos-clang --version + + - name: Build libraries + shell: bash + run: | + export CMAKE_CXX_COMPILER_LAUNCHER=ccache + export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" + cmake --version + + export OHOS_SDK_NATIVE_DIR="$GITHUB_WORKSPACE/command-line-tools/sdk/default/openharmony/native" + + ./build-ohos-arm64-v8a.sh + ./build-ohos-x86-64.sh + + - name: Build Har + shell: bash + run: | + export PATH="$GITHUB_WORKSPACE/command-line-tools/bin:$PATH" + + which hvigorw + + pushd harmony-os/SherpaOnnxHar + + hvigorw --mode module -p product=default -p module=sherpa_onnx@default assembleHar --analyze=normal --parallel --incremental --no-daemon + ls -lh ./sherpa_onnx/build/default/outputs/default/sherpa_onnx.har + cp -v ./sherpa_onnx/build/default/outputs/default/sherpa_onnx.har ../../ + + popd + + ls -lh *.har + + - name: Collect result + shell: bash + run: | + SHERPA_ONNX_VERSION=v$(grep "SHERPA_ONNX_VERSION" ./CMakeLists.txt | cut -d " " -f 2 | cut -d '"' -f 2) + echo "SHERPA_ONNX_VERSION=$SHERPA_ONNX_VERSION" >> "$GITHUB_ENV" + + mv sherpa_onnx.har sherpa_onnx-$SHERPA_ONNX_VERSION.har + + - uses: actions/upload-artifact@v4 + with: + name: sherpa-onnx-har + path: ./sherpa_onnx*.har + + - name: Release jar + if: (github.repository_owner == 'csukuangfj' || github.repository_owner == 'k2-fsa') && github.event_name == 'push' && contains(github.ref, 'refs/tags/') + uses: svenstaro/upload-release-action@v2 + with: + file_glob: true + overwrite: true + file: ./*.har + # repo_name: k2-fsa/sherpa-onnx + # repo_token: ${{ secrets.UPLOAD_GH_SHERPA_ONNX_TOKEN }} + # tag: v1.10.32 + + - name: Publish to huggingface + if: (github.repository_owner == 'csukuangfj' || github.repository_owner == 'k2-fsa') && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') + env: + HF_TOKEN: ${{ secrets.HF_TOKEN }} + uses: nick-fields/retry@v3 + with: + max_attempts: 20 + timeout_seconds: 200 + shell: bash + command: | + git config --global user.email "csukuangfj@gmail.com" + git config --global user.name "Fangjun Kuang" + + rm -rf huggingface + export GIT_LFS_SKIP_SMUDGE=1 + export GIT_CLONE_PROTECTION_ACTIVE=false + + SHERPA_ONNX_VERSION=$(grep "SHERPA_ONNX_VERSION" ./CMakeLists.txt | cut -d " " -f 2 | cut -d '"' -f 2) + echo "SHERPA_ONNX_VERSION $SHERPA_ONNX_VERSION" + + git clone https://csukuangfj:$HF_TOKEN@huggingface.co/csukuangfj/sherpa-onnx-harmony-os huggingface + cd huggingface + git fetch + git pull + git merge -m "merge remote" --ff origin main + + d=har + mkdir -p $d + cp -v ../*.har $d/ + git status + git lfs track "*.har" + git add . + git commit -m "add more hars" + git push https://csukuangfj:$HF_TOKEN@huggingface.co/csukuangfj/sherpa-onnx-harmony-os main diff --git a/README.md b/README.md index 20882d636..96c7885c6 100644 --- a/README.md +++ b/README.md @@ -18,14 +18,13 @@ ### Supported platforms -|Architecture| Android | iOS | Windows | macOS | linux | -|------------|---------|---------|------------|-------|-------| -| x64 | ✔️ | | ✔️ | ✔️ | ✔️ | -| x86 | ✔️ | | ✔️ | | | -| arm64 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | -| arm32 | ✔️ | | | | ✔️ | -| riscv64 | | | | | ✔️ | - +|Architecture| Android | iOS | Windows | macOS | linux | HarmonyOS | +|------------|---------|---------|------------|-------|-------|-----------| +| x64 | ✔️ | | ✔️ | ✔️ | ✔️ | ✔️ | +| x86 | ✔️ | | ✔️ | | | | +| arm64 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | +| arm32 | ✔️ | | | | ✔️ | ✔️ | +| riscv64 | | | | | ✔️ | | ### Supported programming languages @@ -65,6 +64,7 @@ on the following platforms and operating systems: - Linux, macOS, Windows, openKylin - Android, WearOS - iOS + - HarmonyOS - NodeJS - WebAssembly - [Raspberry Pi][Raspberry Pi] diff --git a/build-ohos-arm64-v8a.sh b/build-ohos-arm64-v8a.sh index 182575bc1..4e0ecbb29 100755 --- a/build-ohos-arm64-v8a.sh +++ b/build-ohos-arm64-v8a.sh @@ -134,3 +134,9 @@ cp -fv $onnxruntime_dir/lib/libonnxruntime.so install/lib rm -rf install/share rm -rf install/lib/pkgconfig + +d=../harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/libs/arm64-v8a +if [ -d $d ]; then + cp -v install/lib/libsherpa-onnx-c-api.so $d/ + cp -v install/lib/libonnxruntime.so $d/ +fi diff --git a/build-ohos-x86-64.sh b/build-ohos-x86-64.sh index 58ff5da5a..9584edafc 100755 --- a/build-ohos-x86-64.sh +++ b/build-ohos-x86-64.sh @@ -134,3 +134,9 @@ cp -fv $onnxruntime_dir/lib/libonnxruntime.so install/lib rm -rf install/share rm -rf install/lib/pkgconfig + +d=../harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/libs/x86_64 +if [ -d $d ]; then + cp -v install/lib/libsherpa-onnx-c-api.so $d/ + cp -v install/lib/libonnxruntime.so $d/ +fi diff --git a/harmony-os/.gitignore b/harmony-os/.gitignore new file mode 100644 index 000000000..ddb010f66 --- /dev/null +++ b/harmony-os/.gitignore @@ -0,0 +1 @@ +!build-profile.json5 diff --git a/harmony-os/SherpaOnnxHar/.gitignore b/harmony-os/SherpaOnnxHar/.gitignore new file mode 100644 index 000000000..d2ff20141 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/.gitignore @@ -0,0 +1,12 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +/.appanalyzer \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/AppScope/app.json5 b/harmony-os/SherpaOnnxHar/AppScope/app.json5 new file mode 100644 index 000000000..8f5c08b90 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "com.k2fsa.sherpa.onnx", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} diff --git a/harmony-os/SherpaOnnxHar/AppScope/resources/base/element/string.json b/harmony-os/SherpaOnnxHar/AppScope/resources/base/element/string.json new file mode 100644 index 000000000..a0fa21ba7 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "SherpaOnnxHar" + } + ] +} diff --git a/harmony-os/SherpaOnnxHar/AppScope/resources/base/media/app_icon.png b/harmony-os/SherpaOnnxHar/AppScope/resources/base/media/app_icon.png new file mode 100644 index 000000000..a39445dc8 Binary files /dev/null and b/harmony-os/SherpaOnnxHar/AppScope/resources/base/media/app_icon.png differ diff --git a/harmony-os/SherpaOnnxHar/README.md b/harmony-os/SherpaOnnxHar/README.md new file mode 100644 index 000000000..936315564 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/README.md @@ -0,0 +1,18 @@ +# Introduction + +How to build `sherpa_onnx.har` from the command line: + +```bash +git clone https://github.com/k2-fsa/sherpa-onnx +cd sherpa-onnx +./build-ohos-arm64-v8a.sh +./build-ohos-x86-64.sh + +cd harmony-os/SherpaOnnxHar + +hvigorw clean --no-daemon + +hvigorw --mode module -p product=default -p module=sherpa_onnx@default assembleHar --analyze=normal --parallel --incremental --no-daemon + +ls -lh ./sherpa_onnx/build/default/outputs/default/sherpa_onnx.har +``` diff --git a/harmony-os/SherpaOnnxHar/build-profile.json5 b/harmony-os/SherpaOnnxHar/build-profile.json5 new file mode 100644 index 000000000..2b12adad0 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/build-profile.json5 @@ -0,0 +1,44 @@ +{ + "app": { + "signingConfigs": [], + "products": [ + { + "name": "default", + "signingConfig": "default", + "compatibleSdkVersion": "4.0.0(10)", + "runtimeOS": "HarmonyOS", + "buildOption": { + "strictMode": { + "caseSensitiveCheck": true, + } + } + } + ], + "buildModeSet": [ + { + "name": "debug", + }, + { + "name": "release" + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + }, + { + "name": "sherpa_onnx", + "srcPath": "./sherpa_onnx", + } + ] +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/code-linter.json5 b/harmony-os/SherpaOnnxHar/code-linter.json5 new file mode 100644 index 000000000..77b31b517 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/code-linter.json5 @@ -0,0 +1,20 @@ +{ + "files": [ + "**/*.ets" + ], + "ignore": [ + "**/src/ohosTest/**/*", + "**/src/test/**/*", + "**/src/mock/**/*", + "**/node_modules/**/*", + "**/oh_modules/**/*", + "**/build/**/*", + "**/.preview/**/*" + ], + "ruleSet": [ + "plugin:@performance/recommended", + "plugin:@typescript-eslint/recommended" + ], + "rules": { + } +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/entry/.gitignore b/harmony-os/SherpaOnnxHar/entry/.gitignore new file mode 100644 index 000000000..e2713a277 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/entry/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/entry/build-profile.json5 b/harmony-os/SherpaOnnxHar/entry/build-profile.json5 new file mode 100644 index 000000000..4d611879c --- /dev/null +++ b/harmony-os/SherpaOnnxHar/entry/build-profile.json5 @@ -0,0 +1,28 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + } + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/entry/hvigorfile.ts b/harmony-os/SherpaOnnxHar/entry/hvigorfile.ts new file mode 100644 index 000000000..c6edcd904 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/entry/hvigorfile.ts @@ -0,0 +1,6 @@ +import { hapTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/harmony-os/SherpaOnnxHar/entry/obfuscation-rules.txt b/harmony-os/SherpaOnnxHar/entry/obfuscation-rules.txt new file mode 100644 index 000000000..272efb6ca --- /dev/null +++ b/harmony-os/SherpaOnnxHar/entry/obfuscation-rules.txt @@ -0,0 +1,23 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope + +-enable-property-obfuscation +-enable-toplevel-obfuscation +-enable-filename-obfuscation +-enable-export-obfuscation \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/entry/oh-package.json5 b/harmony-os/SherpaOnnxHar/entry/oh-package.json5 new file mode 100644 index 000000000..248c3b754 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/entry/oh-package.json5 @@ -0,0 +1,10 @@ +{ + "name": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": {} +} + diff --git a/harmony-os/SherpaOnnxHar/entry/src/main/ets/entryability/EntryAbility.ets b/harmony-os/SherpaOnnxHar/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 000000000..679d91453 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,43 @@ +import AbilityConstant from '@ohos.app.ability.AbilityConstant'; +import hilog from '@ohos.hilog'; +import UIAbility from '@ohos.app.ability.UIAbility'; +import Want from '@ohos.app.ability.Want'; +import window from '@ohos.window'; + +export default class EntryAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); + } + + onDestroy(): void { + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); + + windowStage.loadContent('pages/Index', (err) => { + if (err.code) { + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.'); + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground'); + } + + onBackground(): void { + // Ability has back to background + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground'); + } +} diff --git a/harmony-os/SherpaOnnxHar/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets b/harmony-os/SherpaOnnxHar/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets new file mode 100644 index 000000000..d2c48b421 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets @@ -0,0 +1,12 @@ +import hilog from '@ohos.hilog'; +import BackupExtensionAbility, { BundleVersion } from '@ohos.application.BackupExtensionAbility'; + +export default class EntryBackupAbility extends BackupExtensionAbility { + async onBackup() { + hilog.info(0x0000, 'testTag', 'onBackup ok'); + } + + async onRestore(bundleVersion: BundleVersion) { + hilog.info(0x0000, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion)); + } +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/entry/src/main/ets/pages/Index.ets b/harmony-os/SherpaOnnxHar/entry/src/main/ets/pages/Index.ets new file mode 100644 index 000000000..423b4276e --- /dev/null +++ b/harmony-os/SherpaOnnxHar/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,17 @@ +@Entry +@Component +struct Index { + @State message: string = 'Hello World'; + + build() { + Row() { + Column() { + Text(this.message) + .fontSize(50) + .fontWeight(FontWeight.Bold) + } + .width('100%') + } + .height('100%') + } +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/entry/src/main/module.json5 b/harmony-os/SherpaOnnxHar/entry/src/main/module.json5 new file mode 100644 index 000000000..a1cea8b6a --- /dev/null +++ b/harmony-os/SherpaOnnxHar/entry/src/main/module.json5 @@ -0,0 +1,52 @@ +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "phone", + "tablet", + "2in1" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "icon": "$media:layered_image", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:startIcon", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ] + } + ], + "extensionAbilities": [ + { + "name": "EntryBackupAbility", + "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets", + "type": "backup", + "exported": false, + "metadata": [ + { + "name": "ohos.extension.backup", + "resource": "$profile:backup_config" + } + ], + } + ] + } +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/entry/src/main/resources/base/element/color.json b/harmony-os/SherpaOnnxHar/entry/src/main/resources/base/element/color.json new file mode 100644 index 000000000..3c712962d --- /dev/null +++ b/harmony-os/SherpaOnnxHar/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/entry/src/main/resources/base/element/string.json b/harmony-os/SherpaOnnxHar/entry/src/main/resources/base/element/string.json new file mode 100644 index 000000000..f94595515 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/entry/src/main/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/entry/src/main/resources/base/media/background.png b/harmony-os/SherpaOnnxHar/entry/src/main/resources/base/media/background.png new file mode 100644 index 000000000..f939c9fa8 Binary files /dev/null and b/harmony-os/SherpaOnnxHar/entry/src/main/resources/base/media/background.png differ diff --git a/harmony-os/SherpaOnnxHar/entry/src/main/resources/base/media/foreground.png b/harmony-os/SherpaOnnxHar/entry/src/main/resources/base/media/foreground.png new file mode 100644 index 000000000..4483ddad1 Binary files /dev/null and b/harmony-os/SherpaOnnxHar/entry/src/main/resources/base/media/foreground.png differ diff --git a/harmony-os/SherpaOnnxHar/entry/src/main/resources/base/media/layered_image.json b/harmony-os/SherpaOnnxHar/entry/src/main/resources/base/media/layered_image.json new file mode 100644 index 000000000..fb4992044 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/entry/src/main/resources/base/media/layered_image.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background" : "$media:background", + "foreground" : "$media:foreground" + } +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/entry/src/main/resources/base/media/startIcon.png b/harmony-os/SherpaOnnxHar/entry/src/main/resources/base/media/startIcon.png new file mode 100644 index 000000000..205ad8b5a Binary files /dev/null and b/harmony-os/SherpaOnnxHar/entry/src/main/resources/base/media/startIcon.png differ diff --git a/harmony-os/SherpaOnnxHar/entry/src/main/resources/base/profile/backup_config.json b/harmony-os/SherpaOnnxHar/entry/src/main/resources/base/profile/backup_config.json new file mode 100644 index 000000000..78f40ae7c --- /dev/null +++ b/harmony-os/SherpaOnnxHar/entry/src/main/resources/base/profile/backup_config.json @@ -0,0 +1,3 @@ +{ + "allowToBackupRestore": true +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/entry/src/main/resources/base/profile/main_pages.json b/harmony-os/SherpaOnnxHar/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 000000000..1898d94f5 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/Index" + ] +} diff --git a/harmony-os/SherpaOnnxHar/entry/src/main/resources/en_US/element/string.json b/harmony-os/SherpaOnnxHar/entry/src/main/resources/en_US/element/string.json new file mode 100644 index 000000000..f94595515 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/entry/src/main/resources/en_US/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/entry/src/main/resources/zh_CN/element/string.json b/harmony-os/SherpaOnnxHar/entry/src/main/resources/zh_CN/element/string.json new file mode 100644 index 000000000..597ecf95e --- /dev/null +++ b/harmony-os/SherpaOnnxHar/entry/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "模块描述" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/entry/src/ohosTest/ets/test/Ability.test.ets b/harmony-os/SherpaOnnxHar/entry/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 000000000..8aa374977 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/entry/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,35 @@ +import hilog from '@ohos.hilog'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function abilityTest() { + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }) + }) +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/entry/src/ohosTest/ets/test/List.test.ets b/harmony-os/SherpaOnnxHar/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 000000000..794c7dc4e --- /dev/null +++ b/harmony-os/SherpaOnnxHar/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,5 @@ +import abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/entry/src/ohosTest/module.json5 b/harmony-os/SherpaOnnxHar/entry/src/ohosTest/module.json5 new file mode 100644 index 000000000..55725a929 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/entry/src/ohosTest/module.json5 @@ -0,0 +1,13 @@ +{ + "module": { + "name": "entry_test", + "type": "feature", + "deviceTypes": [ + "phone", + "tablet", + "2in1" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} diff --git a/harmony-os/SherpaOnnxHar/entry/src/test/List.test.ets b/harmony-os/SherpaOnnxHar/entry/src/test/List.test.ets new file mode 100644 index 000000000..bb5b5c373 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/entry/src/test/List.test.ets @@ -0,0 +1,5 @@ +import localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/entry/src/test/LocalUnit.test.ets b/harmony-os/SherpaOnnxHar/entry/src/test/LocalUnit.test.ets new file mode 100644 index 000000000..165fc1615 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/entry/src/test/LocalUnit.test.ets @@ -0,0 +1,33 @@ +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function localUnitTest() { + describe('localUnitTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }); + }); +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/hvigor/hvigor-config.json5 b/harmony-os/SherpaOnnxHar/hvigor/hvigor-config.json5 new file mode 100644 index 000000000..06b278367 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/hvigor/hvigor-config.json5 @@ -0,0 +1,22 @@ +{ + "modelVersion": "5.0.0", + "dependencies": { + }, + "execution": { + // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */ + // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */ + // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */ + // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */ + // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */ + }, + "logging": { + // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */ + }, + "debugging": { + // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */ + }, + "nodeOptions": { + // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/ + // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/ + } +} diff --git a/harmony-os/SherpaOnnxHar/hvigorfile.ts b/harmony-os/SherpaOnnxHar/hvigorfile.ts new file mode 100644 index 000000000..f3cb9f1a8 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/hvigorfile.ts @@ -0,0 +1,6 @@ +import { appTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/harmony-os/SherpaOnnxHar/notes.md b/harmony-os/SherpaOnnxHar/notes.md new file mode 100644 index 000000000..6926a7bb6 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/notes.md @@ -0,0 +1,13 @@ +# Notes + +## How to publish a package + +Please see + - + - + - + +## How to sign the HAP file from commandline + +Please see + diff --git a/harmony-os/SherpaOnnxHar/oh-package-lock.json5 b/harmony-os/SherpaOnnxHar/oh-package-lock.json5 new file mode 100644 index 000000000..f538ae290 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/oh-package-lock.json5 @@ -0,0 +1,19 @@ +{ + "meta": { + "stableOrder": true + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "@ohos/hypium@1.0.19": "@ohos/hypium@1.0.19" + }, + "packages": { + "@ohos/hypium@1.0.19": { + "name": "@ohos/hypium", + "version": "1.0.19", + "integrity": "sha512-cEjDgLFCm3cWZDeRXk7agBUkPqjWxUo6AQeiu0gEkb3J8ESqlduQLSIXeo3cCsm8U/asL7iKjF85ZyOuufAGSQ==", + "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hypium/-/hypium-1.0.19.har", + "registryType": "ohpm" + } + } +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/oh-package.json5 b/harmony-os/SherpaOnnxHar/oh-package.json5 new file mode 100644 index 000000000..a79d5300e --- /dev/null +++ b/harmony-os/SherpaOnnxHar/oh-package.json5 @@ -0,0 +1,9 @@ +{ + "modelVersion": "5.0.0", + "description": "Please describe the basic information.", + "dependencies": { + }, + "devDependencies": { + "@ohos/hypium": "1.0.19" + } +} diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/.gitignore b/harmony-os/SherpaOnnxHar/sherpa_onnx/.gitignore new file mode 100644 index 000000000..e2713a277 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/BuildProfile.ets b/harmony-os/SherpaOnnxHar/sherpa_onnx/BuildProfile.ets new file mode 100644 index 000000000..3a501e5dd --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/BuildProfile.ets @@ -0,0 +1,17 @@ +/** + * Use these variables when you tailor your ArkTS code. They must be of the const type. + */ +export const HAR_VERSION = '1.0.0'; +export const BUILD_MODE_NAME = 'debug'; +export const DEBUG = true; +export const TARGET_NAME = 'default'; + +/** + * BuildProfile Class is used only for compatibility purposes. + */ +export default class BuildProfile { + static readonly HAR_VERSION = HAR_VERSION; + static readonly BUILD_MODE_NAME = BUILD_MODE_NAME; + static readonly DEBUG = DEBUG; + static readonly TARGET_NAME = TARGET_NAME; +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/Index.ets b/harmony-os/SherpaOnnxHar/sherpa_onnx/Index.ets new file mode 100644 index 000000000..185aa51fd --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/Index.ets @@ -0,0 +1,40 @@ +export { readWave, readWaveFromBinary } from "libsherpa_onnx.so"; + +export { + CircularBuffer, + SileroVadConfig, + SpeechSegment, + Vad, + VadConfig, +} from './src/main/ets/components/Vad'; + + +export { + Samples, + OfflineStream, + FeatureConfig, + OfflineTransducerModelConfig, + OfflineParaformerModelConfig, + OfflineNemoEncDecCtcModelConfig, + OfflineWhisperModelConfig, + OfflineTdnnModelConfig, + OfflineSenseVoiceModelConfig, + OfflineMoonshineModelConfig, + OfflineModelConfig, + OfflineLMConfig, + OfflineRecognizerConfig, + OfflineRecognizerResult, + OfflineRecognizer, +} from './src/main/ets/components/NonStreamingAsr'; + +export { + OnlineStream, + OnlineTransducerModelConfig, + OnlineParaformerModelConfig, + OnlineZipformer2CtcModelConfig, + OnlineModelConfig, + OnlineCtcFstDecoderConfig, + OnlineRecognizerConfig, + OnlineRecognizerResult, + OnlineRecognizer, +} from './src/main/ets/components/StreamingAsr'; diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/README.md b/harmony-os/SherpaOnnxHar/sherpa_onnx/README.md new file mode 100644 index 000000000..2690bf624 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/README.md @@ -0,0 +1,12 @@ +# Introduction + +[sherpa-onnx](https://github.com/k2-fsa/sherpa-onnx) is one of the deployment +frameworks of [Next-gen Kaldi](https://github.com/k2-fsa). + +It supports speech-to-text, text-to-speech, speaker diarization, and VAD using +onnxruntime without Internet connection. + +It also supports embedded systems, Android, iOS, HarmonyOS, +Raspberry Pi, RISC-V, x86_64 servers, websocket server/client, +C/C++, Python, Kotlin, C#, Go, NodeJS, Java, Swift, Dart, JavaScript, +Flutter, Object Pascal, Lazarus, Rust, etc. diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/build-profile.json5 b/harmony-os/SherpaOnnxHar/sherpa_onnx/build-profile.json5 new file mode 100644 index 000000000..8f789fb2a --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/build-profile.json5 @@ -0,0 +1,46 @@ +{ + "apiType": "stageMode", + "buildOption": { + "externalNativeOptions": { + "path": "./src/main/cpp/CMakeLists.txt", + "arguments": "", + "cppFlags": "", + "abiFilters": [ + "arm64-v8a", + "x86_64", + ], + }, + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + }, + "consumerFiles": [ + "./consumer-rules.txt" + ] + } + }, + "nativeLib": { + "debugSymbol": { + "strip": true, + "exclude": [] + } + } + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest" + } + ] +} diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/consumer-rules.txt b/harmony-os/SherpaOnnxHar/sherpa_onnx/consumer-rules.txt new file mode 100644 index 000000000..e69de29bb diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/hvigorfile.ts b/harmony-os/SherpaOnnxHar/sherpa_onnx/hvigorfile.ts new file mode 100644 index 000000000..421870714 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/hvigorfile.ts @@ -0,0 +1,6 @@ +import { harTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/obfuscation-rules.txt b/harmony-os/SherpaOnnxHar/sherpa_onnx/obfuscation-rules.txt new file mode 100644 index 000000000..272efb6ca --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/obfuscation-rules.txt @@ -0,0 +1,23 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope + +-enable-property-obfuscation +-enable-toplevel-obfuscation +-enable-filename-obfuscation +-enable-export-obfuscation \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/oh-package-lock.json5 b/harmony-os/SherpaOnnxHar/sherpa_onnx/oh-package-lock.json5 new file mode 100644 index 000000000..2585b2e83 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/oh-package-lock.json5 @@ -0,0 +1,18 @@ +{ + "meta": { + "stableOrder": true + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "libsherpa_onnx.so@src/main/cpp/types/libsherpa_onnx": "libsherpa_onnx.so@src/main/cpp/types/libsherpa_onnx" + }, + "packages": { + "libsherpa_onnx.so@src/main/cpp/types/libsherpa_onnx": { + "name": "libsherpa_onnx.so", + "version": "1.0.0", + "resolved": "src/main/cpp/types/libsherpa_onnx", + "registryType": "local" + } + } +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/oh-package.json5 b/harmony-os/SherpaOnnxHar/sherpa_onnx/oh-package.json5 new file mode 100644 index 000000000..cc2260a23 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/oh-package.json5 @@ -0,0 +1,25 @@ +{ + "name": "sherpa_onnx", + "version": "1.0.0", + "description": "Speech-to-text, text-to-speech, and speaker diarization using Next-gen Kaldi without internet connection", + "main": "Index.ets", + "author": "The next-gen Kaldi team", + "license": "Apache-2.0", + "homepage": "https://github.com/k2-fsa/sherpa-onnx", + "repository": "https://github.com/k2-fsa/sherpa-onnx/tree/master/harmonyos-SherpaOnnxHar", + "dependencies": { + "libsherpa_onnx.so": "file:./src/main/cpp/types/libsherpa_onnx" + }, + "keywords": [ + "tts", + "asr", + "locally", + "diarization", + "privacy", + "open-source", + "speaker", + ], + "bugs": { + "url": "https://github.com/k2-fsa/sherpa-onnx/issues" + }, +} diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/CMakeLists.txt b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/CMakeLists.txt new file mode 100644 index 000000000..e131b21da --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/CMakeLists.txt @@ -0,0 +1,69 @@ +# the minimum version of CMake. +cmake_minimum_required(VERSION 3.13.0) +project(myNpmLib) + +# Disable warning about +# +# "The DOWNLOAD_EXTRACT_TIMESTAMP option was not given and policy CMP0135 is +# not set. +if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0") + cmake_policy(SET CMP0135 NEW) +endif() + +set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}) + +if(DEFINED PACKAGE_FIND_FILE) + include(${PACKAGE_FIND_FILE}) +endif() + +include_directories(${NATIVERENDER_ROOT_PATH} + ${NATIVERENDER_ROOT_PATH}/include) + +include(FetchContent) +FetchContent_Declare(node_addon_api + GIT_REPOSITORY "https://github.com/nodejs/node-addon-api.git" + GIT_TAG c679f6f4c9dc6bf9fc0d99cbe5982bd24a5e2c7b + PATCH_COMMAND git checkout . && git apply --ignore-whitespace "${CMAKE_CURRENT_LIST_DIR}/my-patch.diff" +) +FetchContent_MakeAvailable(node_addon_api) +FetchContent_GetProperties(node_addon_api) +if(NOT node_addon_api_POPULATED) + message(STATUS "Downloading node-addon-api from") + FetchContent_Populate(node_addon_api) +endif() + +message(STATUS "node-addon-api is downloaded to ${node_addon_api_SOURCE_DIR}") +include_directories(${node_addon_api_SOURCE_DIR}) + +add_library(sherpa_onnx SHARED + audio-tagging.cc + keyword-spotting.cc + non-streaming-asr.cc + non-streaming-speaker-diarization.cc + non-streaming-tts.cc + punctuation.cc + sherpa-onnx-node-addon-api.cc + speaker-identification.cc + spoken-language-identification.cc + streaming-asr.cc + vad.cc + wave-reader.cc + wave-writer.cc +) + +add_library(sherpa_onnx_c_api SHARED IMPORTED) +set_target_properties(sherpa_onnx_c_api + PROPERTIES + IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/libs/${OHOS_ARCH}/libsherpa-onnx-c-api.so) + +add_library(onnxruntime SHARED IMPORTED) +set_target_properties(onnxruntime + PROPERTIES + IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/libs/${OHOS_ARCH}/libonnxruntime.so) + + +target_link_libraries(sherpa_onnx PUBLIC libace_napi.z.so + libhilog_ndk.z.so # for hilog + librawfile.z.so + sherpa_onnx_c_api onnxruntime +) diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/audio-tagging.cc b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/audio-tagging.cc new file mode 100644 index 000000000..bed4e48a2 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/audio-tagging.cc @@ -0,0 +1,227 @@ +// scripts/node-addon-api/src/audio-tagging.cc +// +// Copyright (c) 2024 Xiaomi Corporation +#include + +#include "macros.h" // NOLINT +#include "napi.h" // NOLINT +#include "sherpa-onnx/c-api/c-api.h" + +static SherpaOnnxOfflineZipformerAudioTaggingModelConfig +GetAudioTaggingZipformerModelConfig(Napi::Object obj) { + SherpaOnnxOfflineZipformerAudioTaggingModelConfig c; + memset(&c, 0, sizeof(c)); + + if (!obj.Has("zipformer") || !obj.Get("zipformer").IsObject()) { + return c; + } + + Napi::Object o = obj.Get("zipformer").As(); + + SHERPA_ONNX_ASSIGN_ATTR_STR(model, model); + + return c; +} + +static SherpaOnnxAudioTaggingModelConfig GetAudioTaggingModelConfig( + Napi::Object obj) { + SherpaOnnxAudioTaggingModelConfig c; + memset(&c, 0, sizeof(c)); + + if (!obj.Has("model") || !obj.Get("model").IsObject()) { + return c; + } + + Napi::Object o = obj.Get("model").As(); + c.zipformer = GetAudioTaggingZipformerModelConfig(o); + + SHERPA_ONNX_ASSIGN_ATTR_STR(ced, ced); + + SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads); + + if (o.Has("debug") && + (o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) { + if (o.Get("debug").IsBoolean()) { + c.debug = o.Get("debug").As().Value(); + } else { + c.debug = o.Get("debug").As().Int32Value(); + } + } + SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider); + + return c; +} + +static Napi::External CreateAudioTaggingWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsObject()) { + Napi::TypeError::New(env, "You should pass an object as the only argument.") + .ThrowAsJavaScriptException(); + + return {}; + } + + Napi::Object o = info[0].As(); + + SherpaOnnxAudioTaggingConfig c; + memset(&c, 0, sizeof(c)); + c.model = GetAudioTaggingModelConfig(o); + + SHERPA_ONNX_ASSIGN_ATTR_STR(labels, labels); + SHERPA_ONNX_ASSIGN_ATTR_INT32(top_k, topK); + + const SherpaOnnxAudioTagging *at = SherpaOnnxCreateAudioTagging(&c); + + if (c.model.zipformer.model) { + delete[] c.model.zipformer.model; + } + + if (c.model.ced) { + delete[] c.model.ced; + } + + if (c.model.provider) { + delete[] c.model.provider; + } + + if (c.labels) { + delete[] c.labels; + } + + if (!at) { + Napi::TypeError::New(env, "Please check your config!") + .ThrowAsJavaScriptException(); + + return {}; + } + + return Napi::External::New( + env, const_cast(at), + [](Napi::Env env, SherpaOnnxAudioTagging *at) { + SherpaOnnxDestroyAudioTagging(at); + }); +} + +static Napi::External +AudioTaggingCreateOfflineStreamWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New( + env, "You should pass an audio tagging pointer as the only argument") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxAudioTagging *at = + info[0].As>().Data(); + + const SherpaOnnxOfflineStream *stream = + SherpaOnnxAudioTaggingCreateOfflineStream(at); + + return Napi::External::New( + env, const_cast(stream), + [](Napi::Env env, SherpaOnnxOfflineStream *stream) { + SherpaOnnxDestroyOfflineStream(stream); + }); +} + +static Napi::Object AudioTaggingComputeWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 3) { + std::ostringstream os; + os << "Expect only 3 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New( + env, "You should pass an audio tagging pointer as the first argument") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[1].IsExternal()) { + Napi::TypeError::New( + env, "You should pass an offline stream pointer as the second argument") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[2].IsNumber()) { + Napi::TypeError::New(env, + "You should pass an integer as the third argument") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxAudioTagging *at = + info[0].As>().Data(); + + SherpaOnnxOfflineStream *stream = + info[1].As>().Data(); + + int32_t top_k = info[2].As().Int32Value(); + + const SherpaOnnxAudioEvent *const *events = + SherpaOnnxAudioTaggingCompute(at, stream, top_k); + + auto p = events; + int32_t k = 0; + while (p && *p) { + ++k; + ++p; + } + + Napi::Array ans = Napi::Array::New(env, k); + for (uint32_t i = 0; i != k; ++i) { + Napi::Object obj = Napi::Object::New(env); + obj.Set(Napi::String::New(env, "name"), + Napi::String::New(env, events[i]->name)); + obj.Set(Napi::String::New(env, "index"), + Napi::Number::New(env, events[i]->index)); + obj.Set(Napi::String::New(env, "prob"), + Napi::Number::New(env, events[i]->prob)); + ans[i] = obj; + } + + SherpaOnnxAudioTaggingFreeResults(events); + + return ans; +} + +void InitAudioTagging(Napi::Env env, Napi::Object exports) { + exports.Set(Napi::String::New(env, "createAudioTagging"), + Napi::Function::New(env, CreateAudioTaggingWrapper)); + + exports.Set(Napi::String::New(env, "audioTaggingCreateOfflineStream"), + Napi::Function::New(env, AudioTaggingCreateOfflineStreamWrapper)); + + exports.Set(Napi::String::New(env, "audioTaggingCompute"), + Napi::Function::New(env, AudioTaggingComputeWrapper)); +} diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/include/sherpa-onnx/c-api/README.md b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/include/sherpa-onnx/c-api/README.md new file mode 100644 index 000000000..95744c221 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/include/sherpa-onnx/c-api/README.md @@ -0,0 +1,8 @@ +# Node + +[./c-api.h](./c-api.h) is a symbolic link to +https://github.com/k2-fsa/sherpa-onnx/blob/master/sherpa-onnx/c-api/c-api.h + +If you are using Windows, then you need to manually replace this file with +https://github.com/k2-fsa/sherpa-onnx/blob/master/sherpa-onnx/c-api/c-api.h +since Windows does not support symbolic links. diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/include/sherpa-onnx/c-api/c-api.h b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/include/sherpa-onnx/c-api/c-api.h new file mode 120000 index 000000000..d9c1b82e1 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/include/sherpa-onnx/c-api/c-api.h @@ -0,0 +1 @@ +../../../../../../../../../sherpa-onnx/c-api/c-api.h \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/keyword-spotting.cc b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/keyword-spotting.cc new file mode 100644 index 000000000..2b5a24100 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/keyword-spotting.cc @@ -0,0 +1,266 @@ +// scripts/node-addon-api/src/keyword-spotting.cc +// +// Copyright (c) 2024 Xiaomi Corporation +#include + +#include "macros.h" // NOLINT +#include "napi.h" // NOLINT +#include "sherpa-onnx/c-api/c-api.h" + +// defined ./streaming-asr.cc +SherpaOnnxFeatureConfig GetFeatureConfig(Napi::Object obj); + +// defined ./streaming-asr.cc +SherpaOnnxOnlineModelConfig GetOnlineModelConfig(Napi::Object obj); + +static Napi::External CreateKeywordSpotterWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsObject()) { + Napi::TypeError::New(env, "Expect an object as the argument") + .ThrowAsJavaScriptException(); + + return {}; + } + + Napi::Object o = info[0].As(); + SherpaOnnxKeywordSpotterConfig c; + memset(&c, 0, sizeof(c)); + c.feat_config = GetFeatureConfig(o); + c.model_config = GetOnlineModelConfig(o); + + SHERPA_ONNX_ASSIGN_ATTR_INT32(max_active_paths, maxActivePaths); + SHERPA_ONNX_ASSIGN_ATTR_INT32(num_trailing_blanks, numTrailingBlanks); + SHERPA_ONNX_ASSIGN_ATTR_FLOAT(keywords_score, keywordsScore); + SHERPA_ONNX_ASSIGN_ATTR_FLOAT(keywords_threshold, keywordsThreshold); + SHERPA_ONNX_ASSIGN_ATTR_STR(keywords_file, keywordsFile); + SHERPA_ONNX_ASSIGN_ATTR_STR(keywords_buf, keywordsBuf); + SHERPA_ONNX_ASSIGN_ATTR_INT32(keywords_buf_size, keywordsBufSize); + + SherpaOnnxKeywordSpotter *kws = SherpaOnnxCreateKeywordSpotter(&c); + + if (c.model_config.transducer.encoder) { + delete[] c.model_config.transducer.encoder; + } + + if (c.model_config.transducer.decoder) { + delete[] c.model_config.transducer.decoder; + } + + if (c.model_config.transducer.joiner) { + delete[] c.model_config.transducer.joiner; + } + + if (c.model_config.paraformer.encoder) { + delete[] c.model_config.paraformer.encoder; + } + + if (c.model_config.paraformer.decoder) { + delete[] c.model_config.paraformer.decoder; + } + + if (c.model_config.zipformer2_ctc.model) { + delete[] c.model_config.zipformer2_ctc.model; + } + + if (c.model_config.tokens) { + delete[] c.model_config.tokens; + } + + if (c.model_config.provider) { + delete[] c.model_config.provider; + } + + if (c.model_config.model_type) { + delete[] c.model_config.model_type; + } + + if (c.keywords_file) { + delete[] c.keywords_file; + } + + if (c.keywords_buf) { + delete[] c.keywords_buf; + } + + if (!kws) { + Napi::TypeError::New(env, "Please check your config!") + .ThrowAsJavaScriptException(); + + return {}; + } + + return Napi::External::New( + env, kws, [](Napi::Env env, SherpaOnnxKeywordSpotter *kws) { + SherpaOnnxDestroyKeywordSpotter(kws); + }); +} + +static Napi::External CreateKeywordStreamWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New( + env, "You should pass a keyword spotter pointer as the only argument") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxKeywordSpotter *kws = + info[0].As>().Data(); + + SherpaOnnxOnlineStream *stream = SherpaOnnxCreateKeywordStream(kws); + + return Napi::External::New( + env, stream, [](Napi::Env env, SherpaOnnxOnlineStream *stream) { + SherpaOnnxDestroyOnlineStream(stream); + }); +} + +static Napi::Boolean IsKeywordStreamReadyWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, "Argument 0 should be a keyword spotter pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[1].IsExternal()) { + Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxKeywordSpotter *kws = + info[0].As>().Data(); + + SherpaOnnxOnlineStream *stream = + info[1].As>().Data(); + + int32_t is_ready = SherpaOnnxIsKeywordStreamReady(kws, stream); + + return Napi::Boolean::New(env, is_ready); +} + +static void DecodeKeywordStreamWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, "Argument 0 should be a keyword spotter pointer.") + .ThrowAsJavaScriptException(); + + return; + } + + if (!info[1].IsExternal()) { + Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.") + .ThrowAsJavaScriptException(); + + return; + } + + SherpaOnnxKeywordSpotter *kws = + info[0].As>().Data(); + + SherpaOnnxOnlineStream *stream = + info[1].As>().Data(); + + SherpaOnnxDecodeKeywordStream(kws, stream); +} + +static Napi::String GetKeywordResultAsJsonWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, "Argument 0 should be a keyword spotter pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[1].IsExternal()) { + Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxKeywordSpotter *kws = + info[0].As>().Data(); + + SherpaOnnxOnlineStream *stream = + info[1].As>().Data(); + + const char *json = SherpaOnnxGetKeywordResultAsJson(kws, stream); + + Napi::String s = Napi::String::New(env, json); + + SherpaOnnxFreeKeywordResultJson(json); + + return s; +} + +void InitKeywordSpotting(Napi::Env env, Napi::Object exports) { + exports.Set(Napi::String::New(env, "createKeywordSpotter"), + Napi::Function::New(env, CreateKeywordSpotterWrapper)); + + exports.Set(Napi::String::New(env, "createKeywordStream"), + Napi::Function::New(env, CreateKeywordStreamWrapper)); + + exports.Set(Napi::String::New(env, "isKeywordStreamReady"), + Napi::Function::New(env, IsKeywordStreamReadyWrapper)); + + exports.Set(Napi::String::New(env, "decodeKeywordStream"), + Napi::Function::New(env, DecodeKeywordStreamWrapper)); + + exports.Set(Napi::String::New(env, "getKeywordResultAsJson"), + Napi::Function::New(env, GetKeywordResultAsJsonWrapper)); +} diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/libs/.gitignore b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/libs/.gitignore new file mode 100644 index 000000000..140f8cf80 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/libs/.gitignore @@ -0,0 +1 @@ +*.so diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/libs/README.md b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/libs/README.md new file mode 100644 index 000000000..0094a8def --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/libs/README.md @@ -0,0 +1,17 @@ +# Introduction + +You need to get the following four `.so` files using + + - [build-ohos-arm64-v8a.sh](https://github.com/k2-fsa/sherpa-onnx/blob/master/build-ohos-arm64-v8a.sh) + - [build-ohos-x86-64.sh](https://github.com/k2-fsa/sherpa-onnx/blob/master/build-ohos-x86-64.sh) + +``` +. +├── README.md +├── arm64-v8a +│   ├── libonnxruntime.so +│   └── libsherpa-onnx-c-api.so +└── x86_64 + ├── libonnxruntime.so + └── libsherpa-onnx-c-api.so +``` diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/libs/arm64-v8a/.gitkeep b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/libs/arm64-v8a/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/libs/x86_64/.gitkeep b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/libs/x86_64/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/macros.h b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/macros.h new file mode 100644 index 000000000..7a6cc93e6 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/macros.h @@ -0,0 +1,63 @@ +// scripts/node-addon-api/src/macros.h +// +// Copyright (c) 2024 Xiaomi Corporation +#ifndef SCRIPTS_NODE_ADDON_API_SRC_MACROS_H_ +#define SCRIPTS_NODE_ADDON_API_SRC_MACROS_H_ + +#include +#include + +#if __OHOS__ +#include "rawfile/raw_file_manager.h" +#include "hilog/log.h" + +#undef LOG_DOMAIN +#undef LOG_TAG + +// https://gitee.com/openharmony/docs/blob/145a084f0b742e4325915e32f8184817927d1251/en/contribute/OpenHarmony-Log-guide.md#hilog-api-usage-specifications +#define LOG_DOMAIN 0x6666 +#define LOG_TAG "sherpa_onnx" +#endif + +#define SHERPA_ONNX_ASSIGN_ATTR_STR(c_name, js_name) \ + do { \ + if (o.Has(#js_name) && o.Get(#js_name).IsString()) { \ + Napi::String _str = o.Get(#js_name).As(); \ + std::string s = _str.Utf8Value(); \ + char *p = new char[s.size() + 1]; \ + std::copy(s.begin(), s.end(), p); \ + p[s.size()] = 0; \ + \ + c.c_name = p; \ + } else if (o.Has(#js_name) && o.Get(#js_name).IsTypedArray()) { \ + Napi::Uint8Array _array = o.Get(#js_name).As(); \ + char *p = new char[_array.ElementLength() + 1]; \ + std::copy(_array.Data(), _array.Data() + _array.ElementLength(), p); \ + p[_array.ElementLength()] = '\0'; \ + \ + c.c_name = p; \ + } \ + } while (0) + +#define SHERPA_ONNX_ASSIGN_ATTR_INT32(c_name, js_name) \ + do { \ + if (o.Has(#js_name) && o.Get(#js_name).IsNumber()) { \ + c.c_name = o.Get(#js_name).As().Int32Value(); \ + } \ + } while (0) + +#define SHERPA_ONNX_ASSIGN_ATTR_FLOAT(c_name, js_name) \ + do { \ + if (o.Has(#js_name) && o.Get(#js_name).IsNumber()) { \ + c.c_name = o.Get(#js_name).As().FloatValue(); \ + } \ + } while (0) + +#define SHERPA_ONNX_DELETE_C_STR(p) \ + do { \ + if (p) { \ + delete[] p; \ + } \ + } while (0) + +#endif // SCRIPTS_NODE_ADDON_API_SRC_MACROS_H_ diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/my-patch.diff b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/my-patch.diff new file mode 100644 index 000000000..535dc38d1 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/my-patch.diff @@ -0,0 +1,14 @@ +diff --git a/napi-inl.h b/napi-inl.h +index e7141c0..0fd90d8 100644 +--- a/napi-inl.h ++++ b/napi-inl.h +@@ -2156,7 +2156,8 @@ inline ArrayBuffer::ArrayBuffer(napi_env env, napi_value value) + + inline void* ArrayBuffer::Data() { + void* data; +- napi_status status = napi_get_arraybuffer_info(_env, _value, &data, nullptr); ++ size_t byte_length; ++ napi_status status = napi_get_arraybuffer_info(_env, _value, &data, &byte_length); + NAPI_THROW_IF_FAILED(_env, status, nullptr); + return data; + } diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/non-streaming-asr.cc b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/non-streaming-asr.cc new file mode 100644 index 000000000..c7d9560a2 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/non-streaming-asr.cc @@ -0,0 +1,487 @@ +// scripts/node-addon-api/src/non-streaming-asr.cc +// +// Copyright (c) 2024 Xiaomi Corporation +#include + +#include "macros.h" // NOLINT +#include "napi.h" // NOLINT +#include "sherpa-onnx/c-api/c-api.h" + +// defined in ./streaming-asr.cc +SherpaOnnxFeatureConfig GetFeatureConfig(Napi::Object obj); + +static SherpaOnnxOfflineTransducerModelConfig GetOfflineTransducerModelConfig( + Napi::Object obj) { + SherpaOnnxOfflineTransducerModelConfig c; + memset(&c, 0, sizeof(c)); + + if (!obj.Has("transducer") || !obj.Get("transducer").IsObject()) { + return c; + } + + Napi::Object o = obj.Get("transducer").As(); + + SHERPA_ONNX_ASSIGN_ATTR_STR(encoder, encoder); + SHERPA_ONNX_ASSIGN_ATTR_STR(decoder, decoder); + SHERPA_ONNX_ASSIGN_ATTR_STR(joiner, joiner); + + return c; +} + +static SherpaOnnxOfflineParaformerModelConfig GetOfflineParaformerModelConfig( + Napi::Object obj) { + SherpaOnnxOfflineParaformerModelConfig c; + memset(&c, 0, sizeof(c)); + + if (!obj.Has("paraformer") || !obj.Get("paraformer").IsObject()) { + return c; + } + + Napi::Object o = obj.Get("paraformer").As(); + + SHERPA_ONNX_ASSIGN_ATTR_STR(model, model); + + return c; +} + +static SherpaOnnxOfflineNemoEncDecCtcModelConfig GetOfflineNeMoCtcModelConfig( + Napi::Object obj) { + SherpaOnnxOfflineNemoEncDecCtcModelConfig c; + memset(&c, 0, sizeof(c)); + + if (!obj.Has("nemoCtc") || !obj.Get("nemoCtc").IsObject()) { + return c; + } + + Napi::Object o = obj.Get("nemoCtc").As(); + + SHERPA_ONNX_ASSIGN_ATTR_STR(model, model); + + return c; +} + +static SherpaOnnxOfflineWhisperModelConfig GetOfflineWhisperModelConfig( + Napi::Object obj) { + SherpaOnnxOfflineWhisperModelConfig c; + memset(&c, 0, sizeof(c)); + + if (!obj.Has("whisper") || !obj.Get("whisper").IsObject()) { + return c; + } + + Napi::Object o = obj.Get("whisper").As(); + + SHERPA_ONNX_ASSIGN_ATTR_STR(encoder, encoder); + SHERPA_ONNX_ASSIGN_ATTR_STR(decoder, decoder); + SHERPA_ONNX_ASSIGN_ATTR_STR(language, language); + SHERPA_ONNX_ASSIGN_ATTR_STR(task, task); + SHERPA_ONNX_ASSIGN_ATTR_INT32(tail_paddings, tailPaddings); + + return c; +} + +static SherpaOnnxOfflineMoonshineModelConfig GetOfflineMoonshineModelConfig( + Napi::Object obj) { + SherpaOnnxOfflineMoonshineModelConfig c; + memset(&c, 0, sizeof(c)); + + if (!obj.Has("moonshine") || !obj.Get("moonshine").IsObject()) { + return c; + } + + Napi::Object o = obj.Get("moonshine").As(); + + SHERPA_ONNX_ASSIGN_ATTR_STR(preprocessor, preprocessor); + SHERPA_ONNX_ASSIGN_ATTR_STR(encoder, encoder); + SHERPA_ONNX_ASSIGN_ATTR_STR(uncached_decoder, uncachedDecoder); + SHERPA_ONNX_ASSIGN_ATTR_STR(cached_decoder, cachedDecoder); + + return c; +} + +static SherpaOnnxOfflineTdnnModelConfig GetOfflineTdnnModelConfig( + Napi::Object obj) { + SherpaOnnxOfflineTdnnModelConfig c; + memset(&c, 0, sizeof(c)); + + if (!obj.Has("tdnn") || !obj.Get("tdnn").IsObject()) { + return c; + } + + Napi::Object o = obj.Get("tdnn").As(); + + SHERPA_ONNX_ASSIGN_ATTR_STR(model, model); + + return c; +} + +static SherpaOnnxOfflineSenseVoiceModelConfig GetOfflineSenseVoiceModelConfig( + Napi::Object obj) { + SherpaOnnxOfflineSenseVoiceModelConfig c; + memset(&c, 0, sizeof(c)); + + if (!obj.Has("senseVoice") || !obj.Get("senseVoice").IsObject()) { + return c; + } + + Napi::Object o = obj.Get("senseVoice").As(); + + SHERPA_ONNX_ASSIGN_ATTR_STR(model, model); + SHERPA_ONNX_ASSIGN_ATTR_STR(language, language); + SHERPA_ONNX_ASSIGN_ATTR_INT32(use_itn, useInverseTextNormalization); + + return c; +} + +static SherpaOnnxOfflineModelConfig GetOfflineModelConfig(Napi::Object obj) { + SherpaOnnxOfflineModelConfig c; + memset(&c, 0, sizeof(c)); + + if (!obj.Has("modelConfig") || !obj.Get("modelConfig").IsObject()) { + return c; + } + + Napi::Object o = obj.Get("modelConfig").As(); + + c.transducer = GetOfflineTransducerModelConfig(o); + c.paraformer = GetOfflineParaformerModelConfig(o); + c.nemo_ctc = GetOfflineNeMoCtcModelConfig(o); + c.whisper = GetOfflineWhisperModelConfig(o); + c.tdnn = GetOfflineTdnnModelConfig(o); + c.sense_voice = GetOfflineSenseVoiceModelConfig(o); + c.moonshine = GetOfflineMoonshineModelConfig(o); + + SHERPA_ONNX_ASSIGN_ATTR_STR(tokens, tokens); + SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads); + + if (o.Has("debug") && + (o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) { + if (o.Get("debug").IsBoolean()) { + c.debug = o.Get("debug").As().Value(); + } else { + c.debug = o.Get("debug").As().Int32Value(); + } + } + + SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider); + SHERPA_ONNX_ASSIGN_ATTR_STR(model_type, modelType); + SHERPA_ONNX_ASSIGN_ATTR_STR(modeling_unit, modelingUnit); + SHERPA_ONNX_ASSIGN_ATTR_STR(bpe_vocab, bpeVocab); + SHERPA_ONNX_ASSIGN_ATTR_STR(telespeech_ctc, teleSpeechCtc); + + return c; +} + +static SherpaOnnxOfflineLMConfig GetOfflineLMConfig(Napi::Object obj) { + SherpaOnnxOfflineLMConfig c; + memset(&c, 0, sizeof(c)); + + if (!obj.Has("lmConfig") || !obj.Get("lmConfig").IsObject()) { + return c; + } + + Napi::Object o = obj.Get("lmConfig").As(); + + SHERPA_ONNX_ASSIGN_ATTR_STR(model, model); + SHERPA_ONNX_ASSIGN_ATTR_FLOAT(scale, scale); + + return c; +} + +static Napi::External +CreateOfflineRecognizerWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); +#if __OHOS__ + // the last argument is the NativeResourceManager + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } +#else + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } +#endif + + if (!info[0].IsObject()) { + Napi::TypeError::New(env, "Expect an object as the argument") + .ThrowAsJavaScriptException(); + + return {}; + } + + Napi::Object o = info[0].As(); + + SherpaOnnxOfflineRecognizerConfig c; + memset(&c, 0, sizeof(c)); + c.feat_config = GetFeatureConfig(o); + c.model_config = GetOfflineModelConfig(o); + c.lm_config = GetOfflineLMConfig(o); + + SHERPA_ONNX_ASSIGN_ATTR_STR(decoding_method, decodingMethod); + SHERPA_ONNX_ASSIGN_ATTR_INT32(max_active_paths, maxActivePaths); + SHERPA_ONNX_ASSIGN_ATTR_STR(hotwords_file, hotwordsFile); + SHERPA_ONNX_ASSIGN_ATTR_FLOAT(hotwords_score, hotwordsScore); + SHERPA_ONNX_ASSIGN_ATTR_STR(rule_fsts, ruleFsts); + SHERPA_ONNX_ASSIGN_ATTR_STR(rule_fars, ruleFars); + SHERPA_ONNX_ASSIGN_ATTR_FLOAT(blank_penalty, blankPenalty); + +#if __OHOS__ + std::unique_ptr mgr (OH_ResourceManager_InitNativeResourceManager(env, info[1]), &OH_ResourceManager_ReleaseNativeResourceManager); + + const SherpaOnnxOfflineRecognizer *recognizer = + SherpaOnnxCreateOfflineRecognizerOHOS(&c, mgr.get()); +#else + const SherpaOnnxOfflineRecognizer *recognizer = + SherpaOnnxCreateOfflineRecognizer(&c); +#endif + + SHERPA_ONNX_DELETE_C_STR(c.model_config.transducer.encoder); + SHERPA_ONNX_DELETE_C_STR(c.model_config.transducer.decoder); + SHERPA_ONNX_DELETE_C_STR(c.model_config.transducer.joiner); + + SHERPA_ONNX_DELETE_C_STR(c.model_config.paraformer.model); + + SHERPA_ONNX_DELETE_C_STR(c.model_config.nemo_ctc.model); + + SHERPA_ONNX_DELETE_C_STR(c.model_config.whisper.encoder); + SHERPA_ONNX_DELETE_C_STR(c.model_config.whisper.decoder); + SHERPA_ONNX_DELETE_C_STR(c.model_config.whisper.language); + SHERPA_ONNX_DELETE_C_STR(c.model_config.whisper.task); + + SHERPA_ONNX_DELETE_C_STR(c.model_config.tdnn.model); + + SHERPA_ONNX_DELETE_C_STR(c.model_config.sense_voice.model); + SHERPA_ONNX_DELETE_C_STR(c.model_config.sense_voice.language); + + SHERPA_ONNX_DELETE_C_STR(c.model_config.moonshine.preprocessor); + SHERPA_ONNX_DELETE_C_STR(c.model_config.moonshine.encoder); + SHERPA_ONNX_DELETE_C_STR(c.model_config.moonshine.uncached_decoder); + SHERPA_ONNX_DELETE_C_STR(c.model_config.moonshine.cached_decoder); + + SHERPA_ONNX_DELETE_C_STR(c.model_config.tokens); + SHERPA_ONNX_DELETE_C_STR(c.model_config.provider); + SHERPA_ONNX_DELETE_C_STR(c.model_config.model_type); + SHERPA_ONNX_DELETE_C_STR(c.model_config.modeling_unit); + SHERPA_ONNX_DELETE_C_STR(c.model_config.bpe_vocab); + SHERPA_ONNX_DELETE_C_STR(c.model_config.telespeech_ctc); + + SHERPA_ONNX_DELETE_C_STR(c.lm_config.model); + + SHERPA_ONNX_DELETE_C_STR(c.decoding_method); + SHERPA_ONNX_DELETE_C_STR(c.hotwords_file); + SHERPA_ONNX_DELETE_C_STR(c.rule_fsts); + SHERPA_ONNX_DELETE_C_STR(c.rule_fars); + + if (!recognizer) { + Napi::TypeError::New(env, "Please check your config!") + .ThrowAsJavaScriptException(); + + return {}; + } + + return Napi::External::New( + env, const_cast(recognizer), + [](Napi::Env env, SherpaOnnxOfflineRecognizer *recognizer) { + SherpaOnnxDestroyOfflineRecognizer(recognizer); + }); +} + +static Napi::External CreateOfflineStreamWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New( + env, + "You should pass an offline recognizer pointer as the only argument") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxOfflineRecognizer *recognizer = + info[0].As>().Data(); + + const SherpaOnnxOfflineStream *stream = + SherpaOnnxCreateOfflineStream(recognizer); + + return Napi::External::New( + env, const_cast(stream), + [](Napi::Env env, SherpaOnnxOfflineStream *stream) { + SherpaOnnxDestroyOfflineStream(stream); + }); +} + +static void AcceptWaveformOfflineWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, "Argument 0 should be an online stream pointer.") + .ThrowAsJavaScriptException(); + + return; + } + + SherpaOnnxOfflineStream *stream = + info[0].As>().Data(); + + if (!info[1].IsObject()) { + Napi::TypeError::New(env, "Argument 1 should be an object") + .ThrowAsJavaScriptException(); + + return; + } + + Napi::Object obj = info[1].As(); + + if (!obj.Has("samples")) { + Napi::TypeError::New(env, "The argument object should have a field samples") + .ThrowAsJavaScriptException(); + + return; + } + + if (!obj.Get("samples").IsTypedArray()) { + Napi::TypeError::New(env, "The object['samples'] should be a typed array") + .ThrowAsJavaScriptException(); + + return; + } + + if (!obj.Has("sampleRate")) { + Napi::TypeError::New(env, + "The argument object should have a field sampleRate") + .ThrowAsJavaScriptException(); + + return; + } + + if (!obj.Get("sampleRate").IsNumber()) { + Napi::TypeError::New(env, "The object['samples'] should be a number") + .ThrowAsJavaScriptException(); + + return; + } + + Napi::Float32Array samples = obj.Get("samples").As(); + int32_t sample_rate = obj.Get("sampleRate").As().Int32Value(); + +#if __OHOS__ + // Note(fangjun): For unknown reasons on HarmonyOS, we need to divide it by + // sizeof(float) here + SherpaOnnxAcceptWaveformOffline(stream, sample_rate, samples.Data(), + samples.ElementLength() / sizeof(float)); +#else + SherpaOnnxAcceptWaveformOffline(stream, sample_rate, samples.Data(), + samples.ElementLength()); +#endif +} + +static void DecodeOfflineStreamWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, + "Argument 0 should be an offline recognizer pointer.") + .ThrowAsJavaScriptException(); + + return; + } + + if (!info[1].IsExternal()) { + Napi::TypeError::New(env, "Argument 1 should be an offline stream pointer.") + .ThrowAsJavaScriptException(); + + return; + } + + SherpaOnnxOfflineRecognizer *recognizer = + info[0].As>().Data(); + + SherpaOnnxOfflineStream *stream = + info[1].As>().Data(); + + SherpaOnnxDecodeOfflineStream(recognizer, stream); +} + +static Napi::String GetOfflineStreamResultAsJsonWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, "Argument 0 should be an online stream pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxOfflineStream *stream = + info[0].As>().Data(); + + const char *json = SherpaOnnxGetOfflineStreamResultAsJson(stream); + Napi::String s = Napi::String::New(env, json); + + SherpaOnnxDestroyOfflineStreamResultJson(json); + + return s; +} + +void InitNonStreamingAsr(Napi::Env env, Napi::Object exports) { + exports.Set(Napi::String::New(env, "createOfflineRecognizer"), + Napi::Function::New(env, CreateOfflineRecognizerWrapper)); + + exports.Set(Napi::String::New(env, "createOfflineStream"), + Napi::Function::New(env, CreateOfflineStreamWrapper)); + + exports.Set(Napi::String::New(env, "acceptWaveformOffline"), + Napi::Function::New(env, AcceptWaveformOfflineWrapper)); + + exports.Set(Napi::String::New(env, "decodeOfflineStream"), + Napi::Function::New(env, DecodeOfflineStreamWrapper)); + + exports.Set(Napi::String::New(env, "getOfflineStreamResultAsJson"), + Napi::Function::New(env, GetOfflineStreamResultAsJsonWrapper)); +} diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/non-streaming-speaker-diarization.cc b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/non-streaming-speaker-diarization.cc new file mode 100644 index 000000000..a35f7924a --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/non-streaming-speaker-diarization.cc @@ -0,0 +1,310 @@ +// scripts/node-addon-api/src/non-streaming-speaker-diarization.cc +// +// Copyright (c) 2024 Xiaomi Corporation + +#include +#include + +#include "macros.h" // NOLINT +#include "napi.h" // NOLINT +#include "sherpa-onnx/c-api/c-api.h" + +static SherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig +GetOfflineSpeakerSegmentationPyannoteModelConfig(Napi::Object obj) { + SherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig c; + memset(&c, 0, sizeof(c)); + + if (!obj.Has("pyannote") || !obj.Get("pyannote").IsObject()) { + return c; + } + + Napi::Object o = obj.Get("pyannote").As(); + SHERPA_ONNX_ASSIGN_ATTR_STR(model, model); + + return c; +} + +static SherpaOnnxOfflineSpeakerSegmentationModelConfig +GetOfflineSpeakerSegmentationModelConfig(Napi::Object obj) { + SherpaOnnxOfflineSpeakerSegmentationModelConfig c; + memset(&c, 0, sizeof(c)); + + if (!obj.Has("segmentation") || !obj.Get("segmentation").IsObject()) { + return c; + } + + Napi::Object o = obj.Get("segmentation").As(); + + c.pyannote = GetOfflineSpeakerSegmentationPyannoteModelConfig(o); + + SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads); + + if (o.Has("debug") && + (o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) { + if (o.Get("debug").IsBoolean()) { + c.debug = o.Get("debug").As().Value(); + } else { + c.debug = o.Get("debug").As().Int32Value(); + } + } + + SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider); + + return c; +} + +static SherpaOnnxSpeakerEmbeddingExtractorConfig +GetSpeakerEmbeddingExtractorConfig(Napi::Object obj) { + SherpaOnnxSpeakerEmbeddingExtractorConfig c; + memset(&c, 0, sizeof(c)); + + if (!obj.Has("embedding") || !obj.Get("embedding").IsObject()) { + return c; + } + + Napi::Object o = obj.Get("embedding").As(); + + SHERPA_ONNX_ASSIGN_ATTR_STR(model, model); + SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads); + + if (o.Has("debug") && + (o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) { + if (o.Get("debug").IsBoolean()) { + c.debug = o.Get("debug").As().Value(); + } else { + c.debug = o.Get("debug").As().Int32Value(); + } + } + + SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider); + + return c; +} + +static SherpaOnnxFastClusteringConfig GetFastClusteringConfig( + Napi::Object obj) { + SherpaOnnxFastClusteringConfig c; + memset(&c, 0, sizeof(c)); + + if (!obj.Has("clustering") || !obj.Get("clustering").IsObject()) { + return c; + } + + Napi::Object o = obj.Get("clustering").As(); + + SHERPA_ONNX_ASSIGN_ATTR_INT32(num_clusters, numClusters); + SHERPA_ONNX_ASSIGN_ATTR_FLOAT(threshold, threshold); + + return c; +} + +static Napi::External +CreateOfflineSpeakerDiarizationWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsObject()) { + Napi::TypeError::New(env, "Expect an object as the argument") + .ThrowAsJavaScriptException(); + + return {}; + } + + Napi::Object o = info[0].As(); + + SherpaOnnxOfflineSpeakerDiarizationConfig c; + memset(&c, 0, sizeof(c)); + + c.segmentation = GetOfflineSpeakerSegmentationModelConfig(o); + c.embedding = GetSpeakerEmbeddingExtractorConfig(o); + c.clustering = GetFastClusteringConfig(o); + + SHERPA_ONNX_ASSIGN_ATTR_FLOAT(min_duration_on, minDurationOn); + SHERPA_ONNX_ASSIGN_ATTR_FLOAT(min_duration_off, minDurationOff); + + const SherpaOnnxOfflineSpeakerDiarization *sd = + SherpaOnnxCreateOfflineSpeakerDiarization(&c); + + if (c.segmentation.pyannote.model) { + delete[] c.segmentation.pyannote.model; + } + + if (c.segmentation.provider) { + delete[] c.segmentation.provider; + } + + if (c.embedding.model) { + delete[] c.embedding.model; + } + + if (c.embedding.provider) { + delete[] c.embedding.provider; + } + + if (!sd) { + Napi::TypeError::New(env, "Please check your config!") + .ThrowAsJavaScriptException(); + + return {}; + } + + return Napi::External::New( + env, const_cast(sd), + [](Napi::Env env, SherpaOnnxOfflineSpeakerDiarization *sd) { + SherpaOnnxDestroyOfflineSpeakerDiarization(sd); + }); +} + +static Napi::Number OfflineSpeakerDiarizationGetSampleRateWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New( + env, "Argument 0 should be an offline speaker diarization pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + const SherpaOnnxOfflineSpeakerDiarization *sd = + info[0].As>().Data(); + + int32_t sample_rate = SherpaOnnxOfflineSpeakerDiarizationGetSampleRate(sd); + + return Napi::Number::New(env, sample_rate); +} + +static Napi::Array OfflineSpeakerDiarizationProcessWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New( + env, "Argument 0 should be an offline speaker diarization pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + const SherpaOnnxOfflineSpeakerDiarization *sd = + info[0].As>().Data(); + + if (!info[1].IsTypedArray()) { + Napi::TypeError::New(env, "Argument 1 should be a typed array") + .ThrowAsJavaScriptException(); + + return {}; + } + + Napi::Float32Array samples = info[1].As(); + + const SherpaOnnxOfflineSpeakerDiarizationResult *r = + SherpaOnnxOfflineSpeakerDiarizationProcess(sd, samples.Data(), + samples.ElementLength()); + + int32_t num_segments = + SherpaOnnxOfflineSpeakerDiarizationResultGetNumSegments(r); + + const SherpaOnnxOfflineSpeakerDiarizationSegment *segments = + SherpaOnnxOfflineSpeakerDiarizationResultSortByStartTime(r); + + Napi::Array ans = Napi::Array::New(env, num_segments); + + for (int32_t i = 0; i != num_segments; ++i) { + Napi::Object obj = Napi::Object::New(env); + + obj.Set(Napi::String::New(env, "start"), segments[i].start); + obj.Set(Napi::String::New(env, "end"), segments[i].end); + obj.Set(Napi::String::New(env, "speaker"), segments[i].speaker); + + ans.Set(i, obj); + } + + SherpaOnnxOfflineSpeakerDiarizationDestroySegment(segments); + SherpaOnnxOfflineSpeakerDiarizationDestroyResult(r); + + return ans; +} + +static void OfflineSpeakerDiarizationSetConfigWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New( + env, "Argument 0 should be an offline speaker diarization pointer.") + .ThrowAsJavaScriptException(); + + return; + } + + const SherpaOnnxOfflineSpeakerDiarization *sd = + info[0].As>().Data(); + + if (!info[1].IsObject()) { + Napi::TypeError::New(env, "Expect an object as the argument") + .ThrowAsJavaScriptException(); + + return; + } + + Napi::Object o = info[0].As(); + + SherpaOnnxOfflineSpeakerDiarizationConfig c; + memset(&c, 0, sizeof(c)); + + c.clustering = GetFastClusteringConfig(o); + SherpaOnnxOfflineSpeakerDiarizationSetConfig(sd, &c); +} + +void InitNonStreamingSpeakerDiarization(Napi::Env env, Napi::Object exports) { + exports.Set(Napi::String::New(env, "createOfflineSpeakerDiarization"), + Napi::Function::New(env, CreateOfflineSpeakerDiarizationWrapper)); + + exports.Set( + Napi::String::New(env, "getOfflineSpeakerDiarizationSampleRate"), + Napi::Function::New(env, OfflineSpeakerDiarizationGetSampleRateWrapper)); + + exports.Set( + Napi::String::New(env, "offlineSpeakerDiarizationProcess"), + Napi::Function::New(env, OfflineSpeakerDiarizationProcessWrapper)); + + exports.Set( + Napi::String::New(env, "offlineSpeakerDiarizationSetConfig"), + Napi::Function::New(env, OfflineSpeakerDiarizationSetConfigWrapper)); +} diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/non-streaming-tts.cc b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/non-streaming-tts.cc new file mode 100644 index 000000000..70d97cddb --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/non-streaming-tts.cc @@ -0,0 +1,329 @@ +// scripts/node-addon-api/src/non-streaming-tts.cc +// +// Copyright (c) 2024 Xiaomi Corporation + +#include +#include + +#include "macros.h" // NOLINT +#include "napi.h" // NOLINT +#include "sherpa-onnx/c-api/c-api.h" + +static SherpaOnnxOfflineTtsVitsModelConfig GetOfflineTtsVitsModelConfig( + Napi::Object obj) { + SherpaOnnxOfflineTtsVitsModelConfig c; + memset(&c, 0, sizeof(c)); + + if (!obj.Has("vits") || !obj.Get("vits").IsObject()) { + return c; + } + + Napi::Object o = obj.Get("vits").As(); + SHERPA_ONNX_ASSIGN_ATTR_STR(model, model); + SHERPA_ONNX_ASSIGN_ATTR_STR(lexicon, lexicon); + SHERPA_ONNX_ASSIGN_ATTR_STR(tokens, tokens); + SHERPA_ONNX_ASSIGN_ATTR_STR(data_dir, dataDir); + SHERPA_ONNX_ASSIGN_ATTR_FLOAT(noise_scale, noiseScale); + SHERPA_ONNX_ASSIGN_ATTR_FLOAT(noise_scale_w, noiseScaleW); + SHERPA_ONNX_ASSIGN_ATTR_FLOAT(length_scale, lengthScale); + SHERPA_ONNX_ASSIGN_ATTR_STR(dict_dir, dictDir); + + return c; +} + +static SherpaOnnxOfflineTtsModelConfig GetOfflineTtsModelConfig( + Napi::Object obj) { + SherpaOnnxOfflineTtsModelConfig c; + memset(&c, 0, sizeof(c)); + + if (!obj.Has("model") || !obj.Get("model").IsObject()) { + return c; + } + + Napi::Object o = obj.Get("model").As(); + + c.vits = GetOfflineTtsVitsModelConfig(o); + + SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads); + + if (o.Has("debug") && + (o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) { + if (o.Get("debug").IsBoolean()) { + c.debug = o.Get("debug").As().Value(); + } else { + c.debug = o.Get("debug").As().Int32Value(); + } + } + + SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider); + + return c; +} + +static Napi::External CreateOfflineTtsWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsObject()) { + Napi::TypeError::New(env, "Expect an object as the argument") + .ThrowAsJavaScriptException(); + + return {}; + } + + Napi::Object o = info[0].As(); + + SherpaOnnxOfflineTtsConfig c; + memset(&c, 0, sizeof(c)); + + c.model = GetOfflineTtsModelConfig(o); + + SHERPA_ONNX_ASSIGN_ATTR_STR(rule_fsts, ruleFsts); + SHERPA_ONNX_ASSIGN_ATTR_INT32(max_num_sentences, maxNumSentences); + SHERPA_ONNX_ASSIGN_ATTR_STR(rule_fars, ruleFars); + + SherpaOnnxOfflineTts *tts = SherpaOnnxCreateOfflineTts(&c); + + if (c.model.vits.model) { + delete[] c.model.vits.model; + } + + if (c.model.vits.lexicon) { + delete[] c.model.vits.lexicon; + } + + if (c.model.vits.tokens) { + delete[] c.model.vits.tokens; + } + + if (c.model.vits.data_dir) { + delete[] c.model.vits.data_dir; + } + + if (c.model.vits.dict_dir) { + delete[] c.model.vits.dict_dir; + } + + if (c.model.provider) { + delete[] c.model.provider; + } + + if (c.rule_fsts) { + delete[] c.rule_fsts; + } + + if (c.rule_fars) { + delete[] c.rule_fars; + } + + if (!tts) { + Napi::TypeError::New(env, "Please check your config!") + .ThrowAsJavaScriptException(); + + return {}; + } + + return Napi::External::New( + env, tts, [](Napi::Env env, SherpaOnnxOfflineTts *tts) { + SherpaOnnxDestroyOfflineTts(tts); + }); +} + +static Napi::Number OfflineTtsSampleRateWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, "Argument 0 should be an offline tts pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxOfflineTts *tts = + info[0].As>().Data(); + + int32_t sample_rate = SherpaOnnxOfflineTtsSampleRate(tts); + + return Napi::Number::New(env, sample_rate); +} + +static Napi::Number OfflineTtsNumSpeakersWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, "Argument 0 should be an offline tts pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxOfflineTts *tts = + info[0].As>().Data(); + + int32_t num_speakers = SherpaOnnxOfflineTtsNumSpeakers(tts); + + return Napi::Number::New(env, num_speakers); +} + +static Napi::Object OfflineTtsGenerateWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, "Argument 0 should be an offline tts pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxOfflineTts *tts = + info[0].As>().Data(); + + if (!info[1].IsObject()) { + Napi::TypeError::New(env, "Argument 1 should be an object") + .ThrowAsJavaScriptException(); + + return {}; + } + + Napi::Object obj = info[1].As(); + + if (!obj.Has("text")) { + Napi::TypeError::New(env, "The argument object should have a field text") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!obj.Get("text").IsString()) { + Napi::TypeError::New(env, "The object['text'] should be a string") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!obj.Has("sid")) { + Napi::TypeError::New(env, "The argument object should have a field sid") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!obj.Get("sid").IsNumber()) { + Napi::TypeError::New(env, "The object['sid'] should be a number") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!obj.Has("speed")) { + Napi::TypeError::New(env, "The argument object should have a field speed") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!obj.Get("speed").IsNumber()) { + Napi::TypeError::New(env, "The object['speed'] should be a number") + .ThrowAsJavaScriptException(); + + return {}; + } + + bool enable_external_buffer = true; + if (obj.Has("enableExternalBuffer") && + obj.Get("enableExternalBuffer").IsBoolean()) { + enable_external_buffer = + obj.Get("enableExternalBuffer").As().Value(); + } + + Napi::String _text = obj.Get("text").As(); + std::string text = _text.Utf8Value(); + int32_t sid = obj.Get("sid").As().Int32Value(); + float speed = obj.Get("speed").As().FloatValue(); + + const SherpaOnnxGeneratedAudio *audio = + SherpaOnnxOfflineTtsGenerate(tts, text.c_str(), sid, speed); + + if (enable_external_buffer) { + Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New( + env, const_cast(audio->samples), sizeof(float) * audio->n, + [](Napi::Env /*env*/, void * /*data*/, + const SherpaOnnxGeneratedAudio *hint) { + SherpaOnnxDestroyOfflineTtsGeneratedAudio(hint); + }, + audio); + Napi::Float32Array float32Array = + Napi::Float32Array::New(env, audio->n, arrayBuffer, 0); + + Napi::Object ans = Napi::Object::New(env); + ans.Set(Napi::String::New(env, "samples"), float32Array); + ans.Set(Napi::String::New(env, "sampleRate"), audio->sample_rate); + return ans; + } else { + // don't use external buffer + Napi::ArrayBuffer arrayBuffer = + Napi::ArrayBuffer::New(env, sizeof(float) * audio->n); + + Napi::Float32Array float32Array = + Napi::Float32Array::New(env, audio->n, arrayBuffer, 0); + + std::copy(audio->samples, audio->samples + audio->n, float32Array.Data()); + + Napi::Object ans = Napi::Object::New(env); + ans.Set(Napi::String::New(env, "samples"), float32Array); + ans.Set(Napi::String::New(env, "sampleRate"), audio->sample_rate); + SherpaOnnxDestroyOfflineTtsGeneratedAudio(audio); + return ans; + } +} + +void InitNonStreamingTts(Napi::Env env, Napi::Object exports) { + exports.Set(Napi::String::New(env, "createOfflineTts"), + Napi::Function::New(env, CreateOfflineTtsWrapper)); + + exports.Set(Napi::String::New(env, "getOfflineTtsSampleRate"), + Napi::Function::New(env, OfflineTtsSampleRateWrapper)); + + exports.Set(Napi::String::New(env, "getOfflineTtsNumSpeakers"), + Napi::Function::New(env, OfflineTtsNumSpeakersWrapper)); + + exports.Set(Napi::String::New(env, "offlineTtsGenerate"), + Napi::Function::New(env, OfflineTtsGenerateWrapper)); +} diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/punctuation.cc b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/punctuation.cc new file mode 100644 index 000000000..df079b96d --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/punctuation.cc @@ -0,0 +1,135 @@ +// scripts/node-addon-api/src/punctuation.cc +// +// Copyright (c) 2024 Xiaomi Corporation +#include + +#include "macros.h" // NOLINT +#include "napi.h" // NOLINT +#include "sherpa-onnx/c-api/c-api.h" + +static SherpaOnnxOfflinePunctuationModelConfig GetOfflinePunctuationModelConfig( + Napi::Object obj) { + SherpaOnnxOfflinePunctuationModelConfig c; + memset(&c, 0, sizeof(c)); + + if (!obj.Has("model") || !obj.Get("model").IsObject()) { + return c; + } + + Napi::Object o = obj.Get("model").As(); + + SHERPA_ONNX_ASSIGN_ATTR_STR(ct_transformer, ctTransformer); + + SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads); + + if (o.Has("debug") && + (o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) { + if (o.Get("debug").IsBoolean()) { + c.debug = o.Get("debug").As().Value(); + } else { + c.debug = o.Get("debug").As().Int32Value(); + } + } + SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider); + + return c; +} + +static Napi::External +CreateOfflinePunctuationWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsObject()) { + Napi::TypeError::New(env, "You should pass an object as the only argument.") + .ThrowAsJavaScriptException(); + + return {}; + } + + Napi::Object o = info[0].As(); + + SherpaOnnxOfflinePunctuationConfig c; + memset(&c, 0, sizeof(c)); + c.model = GetOfflinePunctuationModelConfig(o); + + const SherpaOnnxOfflinePunctuation *punct = + SherpaOnnxCreateOfflinePunctuation(&c); + + if (c.model.ct_transformer) { + delete[] c.model.ct_transformer; + } + + if (c.model.provider) { + delete[] c.model.provider; + } + + if (!punct) { + Napi::TypeError::New(env, "Please check your config!") + .ThrowAsJavaScriptException(); + + return {}; + } + + return Napi::External::New( + env, const_cast(punct), + [](Napi::Env env, SherpaOnnxOfflinePunctuation *punct) { + SherpaOnnxDestroyOfflinePunctuation(punct); + }); +} + +static Napi::String OfflinePunctuationAddPunctWraper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New( + env, + "You should pass an offline punctuation pointer as the first argument") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[1].IsString()) { + Napi::TypeError::New(env, "You should pass a string as the second argument") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxOfflinePunctuation *punct = + info[0].As>().Data(); + Napi::String js_text = info[1].As(); + std::string text = js_text.Utf8Value(); + + const char *punct_text = + SherpaOfflinePunctuationAddPunct(punct, text.c_str()); + + Napi::String ans = Napi::String::New(env, punct_text); + SherpaOfflinePunctuationFreeText(punct_text); + return ans; +} + +void InitPunctuation(Napi::Env env, Napi::Object exports) { + exports.Set(Napi::String::New(env, "createOfflinePunctuation"), + Napi::Function::New(env, CreateOfflinePunctuationWrapper)); + + exports.Set(Napi::String::New(env, "offlinePunctuationAddPunct"), + Napi::Function::New(env, OfflinePunctuationAddPunctWraper)); +} diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/sherpa-onnx-node-addon-api.cc b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/sherpa-onnx-node-addon-api.cc new file mode 100644 index 000000000..3f0affd79 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/sherpa-onnx-node-addon-api.cc @@ -0,0 +1,47 @@ +// scripts/node-addon-api/src/sherpa-onnx-node-addon-api.cc +// +// Copyright (c) 2024 Xiaomi Corporation +#include "napi.h" // NOLINT + +void InitStreamingAsr(Napi::Env env, Napi::Object exports); + +void InitNonStreamingAsr(Napi::Env env, Napi::Object exports); + +void InitNonStreamingTts(Napi::Env env, Napi::Object exports); + +void InitVad(Napi::Env env, Napi::Object exports); + +void InitWaveReader(Napi::Env env, Napi::Object exports); + +void InitWaveWriter(Napi::Env env, Napi::Object exports); + +void InitSpokenLanguageID(Napi::Env env, Napi::Object exports); + +void InitSpeakerID(Napi::Env env, Napi::Object exports); + +void InitAudioTagging(Napi::Env env, Napi::Object exports); + +void InitPunctuation(Napi::Env env, Napi::Object exports); + +void InitKeywordSpotting(Napi::Env env, Napi::Object exports); + +void InitNonStreamingSpeakerDiarization(Napi::Env env, Napi::Object exports); + +Napi::Object Init(Napi::Env env, Napi::Object exports) { + InitStreamingAsr(env, exports); + InitNonStreamingAsr(env, exports); + InitNonStreamingTts(env, exports); + InitVad(env, exports); + InitWaveReader(env, exports); + InitWaveWriter(env, exports); + InitSpokenLanguageID(env, exports); + InitSpeakerID(env, exports); + InitAudioTagging(env, exports); + InitPunctuation(env, exports); + InitKeywordSpotting(env, exports); + InitNonStreamingSpeakerDiarization(env, exports); + + return exports; +} + +NODE_API_MODULE(addon, Init) diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/speaker-identification.cc b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/speaker-identification.cc new file mode 100644 index 000000000..a08a6ed66 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/speaker-identification.cc @@ -0,0 +1,808 @@ +// scripts/node-addon-api/src/speaker-identification.cc +// +// Copyright (c) 2024 Xiaomi Corporation +#include +#include + +#include "macros.h" // NOLINT +#include "napi.h" // NOLINT +#include "sherpa-onnx/c-api/c-api.h" + +static Napi::External +CreateSpeakerEmbeddingExtractorWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsObject()) { + Napi::TypeError::New(env, "You should pass an object as the only argument.") + .ThrowAsJavaScriptException(); + + return {}; + } + + Napi::Object o = info[0].As(); + + SherpaOnnxSpeakerEmbeddingExtractorConfig c; + memset(&c, 0, sizeof(c)); + + SHERPA_ONNX_ASSIGN_ATTR_STR(model, model); + SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads); + + if (o.Has("debug") && + (o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) { + if (o.Get("debug").IsBoolean()) { + c.debug = o.Get("debug").As().Value(); + } else { + c.debug = o.Get("debug").As().Int32Value(); + } + } + + SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider); + + const SherpaOnnxSpeakerEmbeddingExtractor *extractor = + SherpaOnnxCreateSpeakerEmbeddingExtractor(&c); + + if (c.model) { + delete[] c.model; + } + + if (c.provider) { + delete[] c.provider; + } + + if (!extractor) { + Napi::TypeError::New(env, "Please check your config!") + .ThrowAsJavaScriptException(); + + return {}; + } + + return Napi::External::New( + env, const_cast(extractor), + [](Napi::Env env, SherpaOnnxSpeakerEmbeddingExtractor *extractor) { + SherpaOnnxDestroySpeakerEmbeddingExtractor(extractor); + }); +} + +static Napi::Number SpeakerEmbeddingExtractorDimWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New( + env, "Argument 0 should be a speaker embedding extractor pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxSpeakerEmbeddingExtractor *extractor = + info[0].As>().Data(); + + int32_t dim = SherpaOnnxSpeakerEmbeddingExtractorDim(extractor); + + return Napi::Number::New(env, dim); +} + +static Napi::External +SpeakerEmbeddingExtractorCreateStreamWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, + "You should pass a speaker embedding extractor " + "pointer as the only argument") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxSpeakerEmbeddingExtractor *extractor = + info[0].As>().Data(); + + const SherpaOnnxOnlineStream *stream = + SherpaOnnxSpeakerEmbeddingExtractorCreateStream(extractor); + + return Napi::External::New( + env, const_cast(stream), + [](Napi::Env env, SherpaOnnxOnlineStream *stream) { + SherpaOnnxDestroyOnlineStream(stream); + }); +} + +static Napi::Boolean SpeakerEmbeddingExtractorIsReadyWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New( + env, "Argument 0 should be a speaker embedding extractor pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[1].IsExternal()) { + Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxSpeakerEmbeddingExtractor *extractor = + info[0].As>().Data(); + + SherpaOnnxOnlineStream *stream = + info[1].As>().Data(); + + int32_t is_ready = + SherpaOnnxSpeakerEmbeddingExtractorIsReady(extractor, stream); + + return Napi::Boolean::New(env, is_ready); +} + +static Napi::Float32Array SpeakerEmbeddingExtractorComputeEmbeddingWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 2 && info.Length() != 3) { + std::ostringstream os; + os << "Expect only 2 or 3 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New( + env, "Argument 0 should be a speaker embedding extractor pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[1].IsExternal()) { + Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + bool enable_external_buffer = true; + if (info.Length() == 3) { + if (info[2].IsBoolean()) { + enable_external_buffer = info[2].As().Value(); + } else { + Napi::TypeError::New(env, "Argument 2 should be a boolean.") + .ThrowAsJavaScriptException(); + } + } + + SherpaOnnxSpeakerEmbeddingExtractor *extractor = + info[0].As>().Data(); + + SherpaOnnxOnlineStream *stream = + info[1].As>().Data(); + + const float *v = + SherpaOnnxSpeakerEmbeddingExtractorComputeEmbedding(extractor, stream); + + int32_t dim = SherpaOnnxSpeakerEmbeddingExtractorDim(extractor); + + if (enable_external_buffer) { + Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New( + env, const_cast(v), sizeof(float) * dim, + [](Napi::Env /*env*/, void *data) { + SherpaOnnxSpeakerEmbeddingExtractorDestroyEmbedding( + reinterpret_cast(data)); + }); + + return Napi::Float32Array::New(env, dim, arrayBuffer, 0); + } else { + // don't use external buffer + Napi::ArrayBuffer arrayBuffer = + Napi::ArrayBuffer::New(env, sizeof(float) * dim); + + Napi::Float32Array float32Array = + Napi::Float32Array::New(env, dim, arrayBuffer, 0); + + std::copy(v, v + dim, float32Array.Data()); + + SherpaOnnxSpeakerEmbeddingExtractorDestroyEmbedding(v); + + return float32Array; + } +} + +static Napi::External +CreateSpeakerEmbeddingManagerWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsNumber()) { + Napi::TypeError::New(env, + "You should pass an integer as the only argument.") + .ThrowAsJavaScriptException(); + + return {}; + } + + int32_t dim = info[0].As().Int32Value(); + + const SherpaOnnxSpeakerEmbeddingManager *manager = + SherpaOnnxCreateSpeakerEmbeddingManager(dim); + + if (!manager) { + Napi::TypeError::New(env, "Please check your input dim!") + .ThrowAsJavaScriptException(); + + return {}; + } + + return Napi::External::New( + env, const_cast(manager), + [](Napi::Env env, SherpaOnnxSpeakerEmbeddingManager *manager) { + SherpaOnnxDestroySpeakerEmbeddingManager(manager); + }); +} + +static Napi::Boolean SpeakerEmbeddingManagerAddWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, + "You should pass a speaker embedding manager pointer " + "as the first argument.") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[1].IsObject()) { + Napi::TypeError::New(env, "Argument 1 should be an object") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxSpeakerEmbeddingManager *manager = + info[0].As>().Data(); + + Napi::Object obj = info[1].As(); + + if (!obj.Has("v")) { + Napi::TypeError::New(env, "The argument object should have a field v") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!obj.Get("v").IsTypedArray()) { + Napi::TypeError::New(env, "The object['v'] should be a typed array") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!obj.Has("name")) { + Napi::TypeError::New(env, "The argument object should have a field name") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!obj.Get("name").IsString()) { + Napi::TypeError::New(env, "The object['name'] should be a string") + .ThrowAsJavaScriptException(); + + return {}; + } + + Napi::Float32Array v = obj.Get("v").As(); + Napi::String js_name = obj.Get("name").As(); + std::string name = js_name.Utf8Value(); + + int32_t ok = + SherpaOnnxSpeakerEmbeddingManagerAdd(manager, name.c_str(), v.Data()); + return Napi::Boolean::New(env, ok); +} + +static Napi::Boolean SpeakerEmbeddingManagerAddListFlattenedWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, + "You should pass a speaker embedding manager pointer " + "as the first argument.") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[1].IsObject()) { + Napi::TypeError::New(env, "Argument 1 should be an object") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxSpeakerEmbeddingManager *manager = + info[0].As>().Data(); + + Napi::Object obj = info[1].As(); + + if (!obj.Has("vv")) { + Napi::TypeError::New(env, "The argument object should have a field vv") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!obj.Get("vv").IsTypedArray()) { + Napi::TypeError::New(env, "The object['vv'] should be a typed array") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!obj.Has("name")) { + Napi::TypeError::New(env, "The argument object should have a field name") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!obj.Get("name").IsString()) { + Napi::TypeError::New(env, "The object['name'] should be a string") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!obj.Has("n")) { + Napi::TypeError::New(env, "The argument object should have a field n") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!obj.Get("n").IsNumber()) { + Napi::TypeError::New(env, "The object['n'] should be an integer") + .ThrowAsJavaScriptException(); + + return {}; + } + + Napi::Float32Array v = obj.Get("vv").As(); + Napi::String js_name = obj.Get("name").As(); + int32_t n = obj.Get("n").As().Int32Value(); + + std::string name = js_name.Utf8Value(); + + int32_t ok = SherpaOnnxSpeakerEmbeddingManagerAddListFlattened( + manager, name.c_str(), v.Data(), n); + + return Napi::Boolean::New(env, ok); +} + +static Napi::Boolean SpeakerEmbeddingManagerRemoveWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, + "You should pass a speaker embedding manager pointer " + "as the first argument.") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[1].IsString()) { + Napi::TypeError::New(env, "Argument 1 should be string") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxSpeakerEmbeddingManager *manager = + info[0].As>().Data(); + + Napi::String js_name = info[1].As(); + std::string name = js_name.Utf8Value(); + + int32_t ok = SherpaOnnxSpeakerEmbeddingManagerRemove(manager, name.c_str()); + + return Napi::Boolean::New(env, ok); +} + +static Napi::String SpeakerEmbeddingManagerSearchWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, + "You should pass a speaker embedding manager pointer " + "as the first argument.") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[1].IsObject()) { + Napi::TypeError::New(env, "Argument 1 should be an object") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxSpeakerEmbeddingManager *manager = + info[0].As>().Data(); + + Napi::Object obj = info[1].As(); + + if (!obj.Has("v")) { + Napi::TypeError::New(env, "The argument object should have a field v") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!obj.Get("v").IsTypedArray()) { + Napi::TypeError::New(env, "The object['v'] should be a typed array") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!obj.Has("threshold")) { + Napi::TypeError::New(env, + "The argument object should have a field threshold") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!obj.Get("threshold").IsNumber()) { + Napi::TypeError::New(env, "The object['threshold'] should be a float") + .ThrowAsJavaScriptException(); + + return {}; + } + + Napi::Float32Array v = obj.Get("v").As(); + float threshold = obj.Get("threshold").As().FloatValue(); + + const char *name = + SherpaOnnxSpeakerEmbeddingManagerSearch(manager, v.Data(), threshold); + const char *p = name; + if (!p) { + p = ""; + } + + Napi::String js_name = Napi::String::New(env, p); + SherpaOnnxSpeakerEmbeddingManagerFreeSearch(name); + + return js_name; +} + +static Napi::Boolean SpeakerEmbeddingManagerVerifyWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, + "You should pass a speaker embedding manager pointer " + "as the first argument.") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[1].IsObject()) { + Napi::TypeError::New(env, "Argument 1 should be an object") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxSpeakerEmbeddingManager *manager = + info[0].As>().Data(); + + Napi::Object obj = info[1].As(); + + if (!obj.Has("v")) { + Napi::TypeError::New(env, "The argument object should have a field v") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!obj.Get("v").IsTypedArray()) { + Napi::TypeError::New(env, "The object['v'] should be a typed array") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!obj.Has("threshold")) { + Napi::TypeError::New(env, + "The argument object should have a field threshold") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!obj.Get("threshold").IsNumber()) { + Napi::TypeError::New(env, "The object['threshold'] should be a float") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!obj.Has("name")) { + Napi::TypeError::New(env, "The argument object should have a field name") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!obj.Get("name").IsString()) { + Napi::TypeError::New(env, "The object['name'] should be a string") + .ThrowAsJavaScriptException(); + + return {}; + } + + Napi::Float32Array v = obj.Get("v").As(); + float threshold = obj.Get("threshold").As().FloatValue(); + + Napi::String js_name = obj.Get("name").As(); + std::string name = js_name.Utf8Value(); + + int32_t found = SherpaOnnxSpeakerEmbeddingManagerVerify(manager, name.c_str(), + v.Data(), threshold); + + return Napi::Boolean::New(env, found); +} + +static Napi::Boolean SpeakerEmbeddingManagerContainsWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, + "You should pass a speaker embedding manager pointer " + "as the first argument.") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[1].IsString()) { + Napi::TypeError::New(env, "Argument 1 should be a string") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxSpeakerEmbeddingManager *manager = + info[0].As>().Data(); + + Napi::String js_name = info[1].As(); + std::string name = js_name.Utf8Value(); + + int32_t exists = + SherpaOnnxSpeakerEmbeddingManagerContains(manager, name.c_str()); + + return Napi::Boolean::New(env, exists); +} + +static Napi::Number SpeakerEmbeddingManagerNumSpeakersWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, + "You should pass a speaker embedding manager pointer " + "as the first argument.") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxSpeakerEmbeddingManager *manager = + info[0].As>().Data(); + + int32_t num_speakers = SherpaOnnxSpeakerEmbeddingManagerNumSpeakers(manager); + + return Napi::Number::New(env, num_speakers); +} + +static Napi::Array SpeakerEmbeddingManagerGetAllSpeakersWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, + "You should pass a speaker embedding manager pointer " + "as the first argument.") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxSpeakerEmbeddingManager *manager = + info[0].As>().Data(); + + int32_t num_speakers = SherpaOnnxSpeakerEmbeddingManagerNumSpeakers(manager); + if (num_speakers == 0) { + return {}; + } + + const char *const *all_speaker_names = + SherpaOnnxSpeakerEmbeddingManagerGetAllSpeakers(manager); + + Napi::Array ans = Napi::Array::New(env, num_speakers); + for (uint32_t i = 0; i != num_speakers; ++i) { + ans[i] = Napi::String::New(env, all_speaker_names[i]); + } + SherpaOnnxSpeakerEmbeddingManagerFreeAllSpeakers(all_speaker_names); + return ans; +} + +void InitSpeakerID(Napi::Env env, Napi::Object exports) { + exports.Set(Napi::String::New(env, "createSpeakerEmbeddingExtractor"), + Napi::Function::New(env, CreateSpeakerEmbeddingExtractorWrapper)); + + exports.Set(Napi::String::New(env, "speakerEmbeddingExtractorDim"), + Napi::Function::New(env, SpeakerEmbeddingExtractorDimWrapper)); + + exports.Set( + Napi::String::New(env, "speakerEmbeddingExtractorCreateStream"), + Napi::Function::New(env, SpeakerEmbeddingExtractorCreateStreamWrapper)); + + exports.Set( + Napi::String::New(env, "speakerEmbeddingExtractorIsReady"), + Napi::Function::New(env, SpeakerEmbeddingExtractorIsReadyWrapper)); + + exports.Set( + Napi::String::New(env, "speakerEmbeddingExtractorComputeEmbedding"), + Napi::Function::New(env, + SpeakerEmbeddingExtractorComputeEmbeddingWrapper)); + + exports.Set(Napi::String::New(env, "createSpeakerEmbeddingManager"), + Napi::Function::New(env, CreateSpeakerEmbeddingManagerWrapper)); + + exports.Set(Napi::String::New(env, "speakerEmbeddingManagerAdd"), + Napi::Function::New(env, SpeakerEmbeddingManagerAddWrapper)); + + exports.Set( + Napi::String::New(env, "speakerEmbeddingManagerAddListFlattened"), + Napi::Function::New(env, SpeakerEmbeddingManagerAddListFlattenedWrapper)); + + exports.Set(Napi::String::New(env, "speakerEmbeddingManagerRemove"), + Napi::Function::New(env, SpeakerEmbeddingManagerRemoveWrapper)); + + exports.Set(Napi::String::New(env, "speakerEmbeddingManagerSearch"), + Napi::Function::New(env, SpeakerEmbeddingManagerSearchWrapper)); + + exports.Set(Napi::String::New(env, "speakerEmbeddingManagerVerify"), + Napi::Function::New(env, SpeakerEmbeddingManagerVerifyWrapper)); + + exports.Set(Napi::String::New(env, "speakerEmbeddingManagerContains"), + Napi::Function::New(env, SpeakerEmbeddingManagerContainsWrapper)); + + exports.Set( + Napi::String::New(env, "speakerEmbeddingManagerNumSpeakers"), + Napi::Function::New(env, SpeakerEmbeddingManagerNumSpeakersWrapper)); + + exports.Set( + Napi::String::New(env, "speakerEmbeddingManagerGetAllSpeakers"), + Napi::Function::New(env, SpeakerEmbeddingManagerGetAllSpeakersWrapper)); +} diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/spoken-language-identification.cc b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/spoken-language-identification.cc new file mode 100644 index 000000000..35ade6541 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/spoken-language-identification.cc @@ -0,0 +1,188 @@ +// scripts/node-addon-api/src/spoken-language-identification.cc +// +// Copyright (c) 2024 Xiaomi Corporation + +#include + +#include "macros.h" // NOLINT +#include "napi.h" // NOLINT +#include "sherpa-onnx/c-api/c-api.h" + +static SherpaOnnxSpokenLanguageIdentificationWhisperConfig +GetSpokenLanguageIdentificationWhisperConfig(Napi::Object obj) { + SherpaOnnxSpokenLanguageIdentificationWhisperConfig c; + memset(&c, 0, sizeof(c)); + + if (!obj.Has("whisper") || !obj.Get("whisper").IsObject()) { + return c; + } + + Napi::Object o = obj.Get("whisper").As(); + + SHERPA_ONNX_ASSIGN_ATTR_STR(encoder, encoder); + SHERPA_ONNX_ASSIGN_ATTR_STR(decoder, decoder); + SHERPA_ONNX_ASSIGN_ATTR_INT32(tail_paddings, tailPaddings); + + return c; +} + +static Napi::External +CreateSpokenLanguageIdentificationWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsObject()) { + Napi::TypeError::New(env, "You should pass an object as the only argument.") + .ThrowAsJavaScriptException(); + + return {}; + } + + Napi::Object o = info[0].As(); + + SherpaOnnxSpokenLanguageIdentificationConfig c; + memset(&c, 0, sizeof(c)); + c.whisper = GetSpokenLanguageIdentificationWhisperConfig(o); + + SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads); + + if (o.Has("debug") && + (o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) { + if (o.Get("debug").IsBoolean()) { + c.debug = o.Get("debug").As().Value(); + } else { + c.debug = o.Get("debug").As().Int32Value(); + } + } + SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider); + + const SherpaOnnxSpokenLanguageIdentification *slid = + SherpaOnnxCreateSpokenLanguageIdentification(&c); + + if (c.whisper.encoder) { + delete[] c.whisper.encoder; + } + + if (c.whisper.decoder) { + delete[] c.whisper.decoder; + } + + if (c.provider) { + delete[] c.provider; + } + + if (!slid) { + Napi::TypeError::New(env, "Please check your config!") + .ThrowAsJavaScriptException(); + + return {}; + } + + return Napi::External::New( + env, const_cast(slid), + [](Napi::Env env, SherpaOnnxSpokenLanguageIdentification *slid) { + SherpaOnnxDestroySpokenLanguageIdentification(slid); + }); +} + +static Napi::External +SpokenLanguageIdentificationCreateOfflineStreamWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New( + env, + "You should pass an offline language ID pointer as the only argument") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxSpokenLanguageIdentification *slid = + info[0] + .As>() + .Data(); + + SherpaOnnxOfflineStream *stream = + SherpaOnnxSpokenLanguageIdentificationCreateOfflineStream(slid); + + return Napi::External::New( + env, stream, [](Napi::Env env, SherpaOnnxOfflineStream *stream) { + SherpaOnnxDestroyOfflineStream(stream); + }); +} + +static Napi::String SpokenLanguageIdentificationComputeWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New( + env, "Argument 0 should be an offline spoken language ID pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[1].IsExternal()) { + Napi::TypeError::New(env, "Argument 1 should be an offline stream pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxSpokenLanguageIdentification *slid = + info[0] + .As>() + .Data(); + + SherpaOnnxOfflineStream *stream = + info[1].As>().Data(); + + const SherpaOnnxSpokenLanguageIdentificationResult *r = + SherpaOnnxSpokenLanguageIdentificationCompute(slid, stream); + + std::string lang = r->lang; + SherpaOnnxDestroySpokenLanguageIdentificationResult(r); + + return Napi::String::New(env, lang); +} + +void InitSpokenLanguageID(Napi::Env env, Napi::Object exports) { + exports.Set( + Napi::String::New(env, "createSpokenLanguageIdentification"), + Napi::Function::New(env, CreateSpokenLanguageIdentificationWrapper)); + + exports.Set( + Napi::String::New(env, "createSpokenLanguageIdentificationOfflineStream"), + Napi::Function::New( + env, SpokenLanguageIdentificationCreateOfflineStreamWrapper)); + + exports.Set( + Napi::String::New(env, "spokenLanguageIdentificationCompute"), + Napi::Function::New(env, SpokenLanguageIdentificationComputeWrapper)); +} diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/streaming-asr.cc b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/streaming-asr.cc new file mode 100644 index 000000000..ffe562d79 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/streaming-asr.cc @@ -0,0 +1,731 @@ +// scripts/node-addon-api/src/streaming-asr.cc +// +// Copyright (c) 2024 Xiaomi Corporation +#include + +#include "macros.h" // NOLINT +#include "napi.h" // NOLINT +#include "sherpa-onnx/c-api/c-api.h" +/* +{ + 'featConfig': { + 'sampleRate': 16000, + 'featureDim': 80, + } +}; + */ +SherpaOnnxFeatureConfig GetFeatureConfig(Napi::Object obj) { + SherpaOnnxFeatureConfig c; + memset(&c, 0, sizeof(c)); + + if (!obj.Has("featConfig") || !obj.Get("featConfig").IsObject()) { + return c; + } + + Napi::Object o = obj.Get("featConfig").As(); + + SHERPA_ONNX_ASSIGN_ATTR_INT32(sample_rate, sampleRate); + SHERPA_ONNX_ASSIGN_ATTR_INT32(feature_dim, featureDim); + + return c; +} +/* +{ + 'transducer': { + 'encoder': './encoder.onnx', + 'decoder': './decoder.onnx', + 'joiner': './joiner.onnx', + } +} + */ + +static SherpaOnnxOnlineTransducerModelConfig GetOnlineTransducerModelConfig( + Napi::Object obj) { + SherpaOnnxOnlineTransducerModelConfig c; + memset(&c, 0, sizeof(c)); + + if (!obj.Has("transducer") || !obj.Get("transducer").IsObject()) { + return c; + } + + Napi::Object o = obj.Get("transducer").As(); + + SHERPA_ONNX_ASSIGN_ATTR_STR(encoder, encoder); + SHERPA_ONNX_ASSIGN_ATTR_STR(decoder, decoder); + SHERPA_ONNX_ASSIGN_ATTR_STR(joiner, joiner); + + return c; +} + +static SherpaOnnxOnlineZipformer2CtcModelConfig +GetOnlineZipformer2CtcModelConfig(Napi::Object obj) { + SherpaOnnxOnlineZipformer2CtcModelConfig c; + memset(&c, 0, sizeof(c)); + + if (!obj.Has("zipformer2Ctc") || !obj.Get("zipformer2Ctc").IsObject()) { + return c; + } + + Napi::Object o = obj.Get("zipformer2Ctc").As(); + + SHERPA_ONNX_ASSIGN_ATTR_STR(model, model); + + return c; +} + +static SherpaOnnxOnlineParaformerModelConfig GetOnlineParaformerModelConfig( + Napi::Object obj) { + SherpaOnnxOnlineParaformerModelConfig c; + memset(&c, 0, sizeof(c)); + + if (!obj.Has("paraformer") || !obj.Get("paraformer").IsObject()) { + return c; + } + + Napi::Object o = obj.Get("paraformer").As(); + + SHERPA_ONNX_ASSIGN_ATTR_STR(encoder, encoder); + SHERPA_ONNX_ASSIGN_ATTR_STR(decoder, decoder); + + return c; +} + +SherpaOnnxOnlineModelConfig GetOnlineModelConfig(Napi::Object obj) { + SherpaOnnxOnlineModelConfig c; + memset(&c, 0, sizeof(c)); + + if (!obj.Has("modelConfig") || !obj.Get("modelConfig").IsObject()) { + return c; + } + + Napi::Object o = obj.Get("modelConfig").As(); + + c.transducer = GetOnlineTransducerModelConfig(o); + c.paraformer = GetOnlineParaformerModelConfig(o); + c.zipformer2_ctc = GetOnlineZipformer2CtcModelConfig(o); + + SHERPA_ONNX_ASSIGN_ATTR_STR(tokens, tokens); + SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads); + SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider); + + if (o.Has("debug") && + (o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) { + if (o.Get("debug").IsBoolean()) { + c.debug = o.Get("debug").As().Value(); + } else { + c.debug = o.Get("debug").As().Int32Value(); + } + } + + SHERPA_ONNX_ASSIGN_ATTR_STR(model_type, modelType); + SHERPA_ONNX_ASSIGN_ATTR_STR(modeling_unit, modelingUnit); + SHERPA_ONNX_ASSIGN_ATTR_STR(bpe_vocab, bpeVocab); + SHERPA_ONNX_ASSIGN_ATTR_STR(tokens_buf, tokensBuf); + SHERPA_ONNX_ASSIGN_ATTR_INT32(tokens_buf_size, tokensBufSize); + + return c; +} + +static SherpaOnnxOnlineCtcFstDecoderConfig GetCtcFstDecoderConfig( + Napi::Object obj) { + SherpaOnnxOnlineCtcFstDecoderConfig c; + memset(&c, 0, sizeof(c)); + + if (!obj.Has("ctcFstDecoderConfig") || + !obj.Get("ctcFstDecoderConfig").IsObject()) { + return c; + } + + Napi::Object o = obj.Get("ctcFstDecoderConfig").As(); + + SHERPA_ONNX_ASSIGN_ATTR_STR(graph, graph); + SHERPA_ONNX_ASSIGN_ATTR_INT32(max_active, maxActive); + + return c; +} + +static Napi::External CreateOnlineRecognizerWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); +#if __OHOS__ + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } +#else + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } +#endif + + if (!info[0].IsObject()) { + Napi::TypeError::New(env, "Expect an object as the argument") + .ThrowAsJavaScriptException(); + + return {}; + } + + Napi::Object o = info[0].As(); + SherpaOnnxOnlineRecognizerConfig c; + memset(&c, 0, sizeof(c)); + c.feat_config = GetFeatureConfig(o); + c.model_config = GetOnlineModelConfig(o); + + SHERPA_ONNX_ASSIGN_ATTR_STR(decoding_method, decodingMethod); + SHERPA_ONNX_ASSIGN_ATTR_INT32(max_active_paths, maxActivePaths); + + // enableEndpoint can be either a boolean or an integer + if (o.Has("enableEndpoint") && (o.Get("enableEndpoint").IsNumber() || + o.Get("enableEndpoint").IsBoolean())) { + if (o.Get("enableEndpoint").IsNumber()) { + c.enable_endpoint = + o.Get("enableEndpoint").As().Int32Value(); + } else { + c.enable_endpoint = o.Get("enableEndpoint").As().Value(); + } + } + + SHERPA_ONNX_ASSIGN_ATTR_FLOAT(rule1_min_trailing_silence, + rule1MinTrailingSilence); + SHERPA_ONNX_ASSIGN_ATTR_FLOAT(rule2_min_trailing_silence, + rule2MinTrailingSilence); + SHERPA_ONNX_ASSIGN_ATTR_FLOAT(rule3_min_utterance_length, + rule3MinUtteranceLength); + SHERPA_ONNX_ASSIGN_ATTR_STR(hotwords_file, hotwordsFile); + SHERPA_ONNX_ASSIGN_ATTR_FLOAT(hotwords_score, hotwordsScore); + SHERPA_ONNX_ASSIGN_ATTR_STR(rule_fsts, ruleFsts); + SHERPA_ONNX_ASSIGN_ATTR_STR(rule_fars, ruleFars); + SHERPA_ONNX_ASSIGN_ATTR_FLOAT(blank_penalty, blankPenalty); + SHERPA_ONNX_ASSIGN_ATTR_STR(hotwords_buf, hotwordsBuf); + SHERPA_ONNX_ASSIGN_ATTR_INT32(hotwords_buf_size, hotwordsBufSize); + + c.ctc_fst_decoder_config = GetCtcFstDecoderConfig(o); + +#if __OHOS__ + std::unique_ptr mgr (OH_ResourceManager_InitNativeResourceManager(env, info[1]), &OH_ResourceManager_ReleaseNativeResourceManager); + + const SherpaOnnxOnlineRecognizer *recognizer = + SherpaOnnxCreateOnlineRecognizerOHOS(&c, mgr.get()); +#else + const SherpaOnnxOnlineRecognizer *recognizer = + SherpaOnnxCreateOnlineRecognizer(&c); +#endif + + if (c.model_config.transducer.encoder) { + delete[] c.model_config.transducer.encoder; + } + + if (c.model_config.transducer.decoder) { + delete[] c.model_config.transducer.decoder; + } + + if (c.model_config.transducer.joiner) { + delete[] c.model_config.transducer.joiner; + } + + if (c.model_config.paraformer.encoder) { + delete[] c.model_config.paraformer.encoder; + } + + if (c.model_config.paraformer.decoder) { + delete[] c.model_config.paraformer.decoder; + } + + if (c.model_config.zipformer2_ctc.model) { + delete[] c.model_config.zipformer2_ctc.model; + } + + if (c.model_config.tokens) { + delete[] c.model_config.tokens; + } + + if (c.model_config.provider) { + delete[] c.model_config.provider; + } + + if (c.model_config.model_type) { + delete[] c.model_config.model_type; + } + + if (c.model_config.modeling_unit) { + delete[] c.model_config.modeling_unit; + } + + if (c.model_config.bpe_vocab) { + delete[] c.model_config.bpe_vocab; + } + + if (c.model_config.tokens_buf) { + delete[] c.model_config.tokens_buf; + } + + if (c.decoding_method) { + delete[] c.decoding_method; + } + + if (c.hotwords_file) { + delete[] c.hotwords_file; + } + + if (c.rule_fsts) { + delete[] c.rule_fsts; + } + + if (c.rule_fars) { + delete[] c.rule_fars; + } + + if (c.hotwords_buf) { + delete[] c.hotwords_buf; + } + + if (c.ctc_fst_decoder_config.graph) { + delete[] c.ctc_fst_decoder_config.graph; + } + + if (!recognizer) { + Napi::TypeError::New(env, "Please check your config!") + .ThrowAsJavaScriptException(); + + return {}; + } + + return Napi::External::New( + env, const_cast(recognizer), + [](Napi::Env env, SherpaOnnxOnlineRecognizer *recognizer) { + SherpaOnnxDestroyOnlineRecognizer(recognizer); + }); +} + +static Napi::External CreateOnlineStreamWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New( + env, + "You should pass an online recognizer pointer as the only argument") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxOnlineRecognizer *recognizer = + info[0].As>().Data(); + + const SherpaOnnxOnlineStream *stream = + SherpaOnnxCreateOnlineStream(recognizer); + + return Napi::External::New( + env, const_cast(stream), + [](Napi::Env env, SherpaOnnxOnlineStream *stream) { + SherpaOnnxDestroyOnlineStream(stream); + }); +} + +static void AcceptWaveformWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, "Argument 0 should be an online stream pointer.") + .ThrowAsJavaScriptException(); + + return; + } + + SherpaOnnxOnlineStream *stream = + info[0].As>().Data(); + + if (!info[1].IsObject()) { + Napi::TypeError::New(env, "Argument 1 should be an object") + .ThrowAsJavaScriptException(); + + return; + } + + Napi::Object obj = info[1].As(); + + if (!obj.Has("samples")) { + Napi::TypeError::New(env, "The argument object should have a field samples") + .ThrowAsJavaScriptException(); + + return; + } + + if (!obj.Get("samples").IsTypedArray()) { + Napi::TypeError::New(env, "The object['samples'] should be a typed array") + .ThrowAsJavaScriptException(); + + return; + } + + if (!obj.Has("sampleRate")) { + Napi::TypeError::New(env, + "The argument object should have a field sampleRate") + .ThrowAsJavaScriptException(); + + return; + } + + if (!obj.Get("sampleRate").IsNumber()) { + Napi::TypeError::New(env, "The object['samples'] should be a number") + .ThrowAsJavaScriptException(); + + return; + } + + Napi::Float32Array samples = obj.Get("samples").As(); + int32_t sample_rate = obj.Get("sampleRate").As().Int32Value(); + +#if __OHOS__ + SherpaOnnxOnlineStreamAcceptWaveform(stream, sample_rate, samples.Data(), + samples.ElementLength() / sizeof(float)); +#else + SherpaOnnxOnlineStreamAcceptWaveform(stream, sample_rate, samples.Data(), + samples.ElementLength()); +#endif +} + +static Napi::Boolean IsOnlineStreamReadyWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, + "Argument 0 should be an online recognizer pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[1].IsExternal()) { + Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxOnlineRecognizer *recognizer = + info[0].As>().Data(); + + SherpaOnnxOnlineStream *stream = + info[1].As>().Data(); + + int32_t is_ready = SherpaOnnxIsOnlineStreamReady(recognizer, stream); + + return Napi::Boolean::New(env, is_ready); +} + +static void DecodeOnlineStreamWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, + "Argument 0 should be an online recognizer pointer.") + .ThrowAsJavaScriptException(); + + return; + } + + if (!info[1].IsExternal()) { + Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.") + .ThrowAsJavaScriptException(); + + return; + } + + SherpaOnnxOnlineRecognizer *recognizer = + info[0].As>().Data(); + + SherpaOnnxOnlineStream *stream = + info[1].As>().Data(); + + SherpaOnnxDecodeOnlineStream(recognizer, stream); +} + +static Napi::String GetOnlineStreamResultAsJsonWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, + "Argument 0 should be an online recognizer pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[1].IsExternal()) { + Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxOnlineRecognizer *recognizer = + info[0].As>().Data(); + + SherpaOnnxOnlineStream *stream = + info[1].As>().Data(); + + const char *json = SherpaOnnxGetOnlineStreamResultAsJson(recognizer, stream); + Napi::String s = Napi::String::New(env, json); + + SherpaOnnxDestroyOnlineStreamResultJson(json); + + return s; +} + +static void InputFinishedWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, "Argument 0 should be an online stream pointer.") + .ThrowAsJavaScriptException(); + + return; + } + + SherpaOnnxOnlineStream *stream = + info[0].As>().Data(); + + SherpaOnnxOnlineStreamInputFinished(stream); +} + +static void ResetOnlineStreamWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, + "Argument 0 should be an online recognizer pointer.") + .ThrowAsJavaScriptException(); + + return; + } + + if (!info[1].IsExternal()) { + Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.") + .ThrowAsJavaScriptException(); + + return; + } + + SherpaOnnxOnlineRecognizer *recognizer = + info[0].As>().Data(); + + SherpaOnnxOnlineStream *stream = + info[1].As>().Data(); + + SherpaOnnxOnlineStreamReset(recognizer, stream); +} + +static Napi::Boolean IsEndpointWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, + "Argument 0 should be an online recognizer pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[1].IsExternal()) { + Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxOnlineRecognizer *recognizer = + info[0].As>().Data(); + + SherpaOnnxOnlineStream *stream = + info[1].As>().Data(); + + int32_t is_endpoint = SherpaOnnxOnlineStreamIsEndpoint(recognizer, stream); + + return Napi::Boolean::New(env, is_endpoint); +} + +static Napi::External CreateDisplayWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsNumber()) { + Napi::TypeError::New(env, "Expect a number as the argument") + .ThrowAsJavaScriptException(); + + return {}; + } + int32_t max_word_per_line = info[0].As().Int32Value(); + + const SherpaOnnxDisplay *display = SherpaOnnxCreateDisplay(max_word_per_line); + + return Napi::External::New( + env, const_cast(display), + [](Napi::Env env, SherpaOnnxDisplay *display) { + SherpaOnnxDestroyDisplay(display); + }); +} + +static void PrintWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() != 3) { + std::ostringstream os; + os << "Expect only 3 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, "Argument 0 should be an online stream pointer.") + .ThrowAsJavaScriptException(); + + return; + } + + if (!info[1].IsNumber()) { + Napi::TypeError::New(env, "Argument 1 should be a number.") + .ThrowAsJavaScriptException(); + + return; + } + + if (!info[2].IsString()) { + Napi::TypeError::New(env, "Argument 2 should be a string.") + .ThrowAsJavaScriptException(); + + return; + } + + SherpaOnnxDisplay *display = + info[0].As>().Data(); + + int32_t idx = info[1].As().Int32Value(); + + Napi::String text = info[2].As(); + std::string s = text.Utf8Value(); + SherpaOnnxPrint(display, idx, s.c_str()); +} + +void InitStreamingAsr(Napi::Env env, Napi::Object exports) { + exports.Set(Napi::String::New(env, "createOnlineRecognizer"), + Napi::Function::New(env, CreateOnlineRecognizerWrapper)); + + exports.Set(Napi::String::New(env, "createOnlineStream"), + Napi::Function::New(env, CreateOnlineStreamWrapper)); + + exports.Set(Napi::String::New(env, "acceptWaveformOnline"), + Napi::Function::New(env, AcceptWaveformWrapper)); + + exports.Set(Napi::String::New(env, "isOnlineStreamReady"), + Napi::Function::New(env, IsOnlineStreamReadyWrapper)); + + exports.Set(Napi::String::New(env, "decodeOnlineStream"), + Napi::Function::New(env, DecodeOnlineStreamWrapper)); + + exports.Set(Napi::String::New(env, "getOnlineStreamResultAsJson"), + Napi::Function::New(env, GetOnlineStreamResultAsJsonWrapper)); + + exports.Set(Napi::String::New(env, "inputFinished"), + Napi::Function::New(env, InputFinishedWrapper)); + + exports.Set(Napi::String::New(env, "reset"), + Napi::Function::New(env, ResetOnlineStreamWrapper)); + + exports.Set(Napi::String::New(env, "isEndpoint"), + Napi::Function::New(env, IsEndpointWrapper)); + + exports.Set(Napi::String::New(env, "createDisplay"), + Napi::Function::New(env, CreateDisplayWrapper)); + + exports.Set(Napi::String::New(env, "print"), + Napi::Function::New(env, PrintWrapper)); +} diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/types/libsherpa_onnx/Index.d.ts b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/types/libsherpa_onnx/Index.d.ts new file mode 100644 index 000000000..10ff7745c --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/types/libsherpa_onnx/Index.d.ts @@ -0,0 +1,35 @@ +export const readWave: (filename: string, enableExternalBuffer: boolean = true) => {samples: Float32Array, sampleRate: number}; +export const readWaveFromBinary: (data: Uint8Array, enableExternalBuffer: boolean = true) => {samples: Float32Array, sampleRate: number}; +export const createCircularBuffer: (capacity: number) => object; +export const circularBufferPush: (handle: object, samples: Float32Array) => void; +export const circularBufferGet: (handle: object, index: number, n: number, enableExternalBuffer: boolean = true) => Float32Array; +export const circularBufferPop: (handle: object, n: number) => void; +export const circularBufferSize: (handle: object) => number; +export const circularBufferHead: (handle: object) => number; +export const circularBufferReset: (handle: object) => void; + +export const createVoiceActivityDetector: (config: object, bufferSizeInSeconds: number, mgr?: object) => object; +export const voiceActivityDetectorAcceptWaveform: (handle: object, samples: Float32Array) => void; +export const voiceActivityDetectorIsEmpty: (handle: object) => boolean; +export const voiceActivityDetectorIsDetected: (handle: object) => boolean; +export const voiceActivityDetectorPop: (handle: object) => void; +export const voiceActivityDetectorClear: (handle: object) => void; +export const voiceActivityDetectorFront: (handle: object, enableExternalBuffer: boolean = true) => {samples: Float32Array, start: number}; +export const voiceActivityDetectorReset: (handle: object) => void; +export const voiceActivityDetectorFlush: (handle: object) => void; + +export const createOfflineRecognizer: (config: object, mgr?: object) => object; +export const createOfflineStream: (handle: object) => object; +export const acceptWaveformOffline: (handle: object, audio: object) => void; +export const decodeOfflineStream: (handle: object, streamHandle: object) => void; +export const getOfflineStreamResultAsJson: (streamHandle: object) => string; + +export const createOnlineRecognizer: (config: object, mgr?: object) => object; +export const createOnlineStream: (handle: object) => object; +export const acceptWaveformOnline: (handle: object, audio: object) => void; +export const inputFinished: (streamHandle: object) => void; +export const isOnlineStreamReady: (handle: object, streamHandle: object) => boolean; +export const decodeOnlineStream: (handle: object, streamHandle: object) => void; +export const isEndpoint: (handle: object, streamHandle: object) => boolean; +export const reset: (handle: object, streamHandle: object) => void; +export const getOnlineStreamResultAsJson: (handle: object, streamHandle: object) => string; diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/types/libsherpa_onnx/oh-package.json5 b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/types/libsherpa_onnx/oh-package.json5 new file mode 100644 index 000000000..09065d8d3 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/types/libsherpa_onnx/oh-package.json5 @@ -0,0 +1,6 @@ +{ + "name": "libsherpa_onnx.so", + "types": "./Index.d.ts", + "version": "1.0.0", + "description": "Please describe the basic information." +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/vad.cc b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/vad.cc new file mode 100644 index 000000000..b505c53d2 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/vad.cc @@ -0,0 +1,700 @@ +// scripts/node-addon-api/src/vad.cc +// +// Copyright (c) 2024 Xiaomi Corporation + +#include +#include + +#include "macros.h" // NOLINT +#include "napi.h" // NOLINT +#include "sherpa-onnx/c-api/c-api.h" + +static Napi::External CreateCircularBufferWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsNumber()) { + Napi::TypeError::New(env, "You should pass an integer as the argument.") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxCircularBuffer *buf = + SherpaOnnxCreateCircularBuffer(info[0].As().Int32Value()); + + return Napi::External::New( + env, buf, [](Napi::Env env, SherpaOnnxCircularBuffer *p) { + SherpaOnnxDestroyCircularBuffer(p); + }); +} + +static void CircularBufferPushWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, "Argument 0 should be an CircularBuffer pointer.") + .ThrowAsJavaScriptException(); + + return; + } + + SherpaOnnxCircularBuffer *buf = + info[0].As>().Data(); + + if (!info[1].IsTypedArray()) { + Napi::TypeError::New(env, "Argument 1 should be a Float32Array.") + .ThrowAsJavaScriptException(); + + return; + } + + Napi::Float32Array data = info[1].As(); + +#if __OHOS__ + // Note(fangjun): Normally, we don't need to divied it by sizeof(float). + // However, data.ElementLength() here returns number of bytes, not number of elements. + SherpaOnnxCircularBufferPush(buf, data.Data(), data.ElementLength() / sizeof(float)); +#else + SherpaOnnxCircularBufferPush(buf, data.Data(), data.ElementLength()); +#endif +} + +// see https://github.com/nodejs/node-addon-api/blob/main/doc/typed_array.md +// https://github.com/nodejs/node-addon-examples/blob/main/src/2-js-to-native-conversion/typed_array_to_native/node-addon-api/typed_array_to_native.cc +static Napi::Float32Array CircularBufferGetWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() != 3 && info.Length() != 4) { + std::ostringstream os; + os << "Expect only 3 or 4 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, "Argument 0 should be an CircularBuffer pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxCircularBuffer *buf = + info[0].As>().Data(); + + if (!info[1].IsNumber()) { + Napi::TypeError::New(env, "Argument 1 should be an integer (startIndex).") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[2].IsNumber()) { + Napi::TypeError::New(env, "Argument 2 should be an integer (n).") + .ThrowAsJavaScriptException(); + + return {}; + } + + bool enable_external_buffer = true; + if (info.Length() == 4) { + if (info[3].IsBoolean()) { + enable_external_buffer = info[3].As().Value(); + } else { + Napi::TypeError::New(env, "Argument 3 should be a boolean.") + .ThrowAsJavaScriptException(); + } + } + + int32_t start_index = info[1].As().Int32Value(); + int32_t n = info[2].As().Int32Value(); + + const float *data = SherpaOnnxCircularBufferGet(buf, start_index, n); + + if (enable_external_buffer) { + Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New( + env, const_cast(data), sizeof(float) * n, + [](Napi::Env /*env*/, void *p) { + SherpaOnnxCircularBufferFree(reinterpret_cast(p)); + }); + + Napi::Float32Array float32Array = + Napi::Float32Array::New(env, n, arrayBuffer, 0); + + return float32Array; + } else { + // don't use external buffer + Napi::ArrayBuffer arrayBuffer = + Napi::ArrayBuffer::New(env, sizeof(float) * n); + + Napi::Float32Array float32Array = + Napi::Float32Array::New(env, n, arrayBuffer, 0); + + std::copy(data, data + n, float32Array.Data()); + + SherpaOnnxCircularBufferFree(data); + + return float32Array; + } +} + +static void CircularBufferPopWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, "Argument 0 should be an CircularBuffer pointer.") + .ThrowAsJavaScriptException(); + + return; + } + + SherpaOnnxCircularBuffer *buf = + info[0].As>().Data(); + + if (!info[1].IsNumber()) { + Napi::TypeError::New(env, "Argument 1 should be an integer (n).") + .ThrowAsJavaScriptException(); + + return; + } + + int32_t n = info[1].As().Int32Value(); + + SherpaOnnxCircularBufferPop(buf, n); +} + +static Napi::Number CircularBufferSizeWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, "Argument 0 should be an CircularBuffer pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxCircularBuffer *buf = + info[0].As>().Data(); + + int32_t size = SherpaOnnxCircularBufferSize(buf); + + return Napi::Number::New(env, size); +} + +static Napi::Number CircularBufferHeadWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, "Argument 0 should be an CircularBuffer pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxCircularBuffer *buf = + info[0].As>().Data(); + + int32_t size = SherpaOnnxCircularBufferHead(buf); + + return Napi::Number::New(env, size); +} + +static void CircularBufferResetWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, "Argument 0 should be an CircularBuffer pointer.") + .ThrowAsJavaScriptException(); + + return; + } + + SherpaOnnxCircularBuffer *buf = + info[0].As>().Data(); + + SherpaOnnxCircularBufferReset(buf); +} + +static SherpaOnnxSileroVadModelConfig GetSileroVadConfig( + const Napi::Object &obj) { + SherpaOnnxSileroVadModelConfig c; + memset(&c, 0, sizeof(c)); + + if (!obj.Has("sileroVad") || !obj.Get("sileroVad").IsObject()) { + return c; + } + + Napi::Object o = obj.Get("sileroVad").As(); + SHERPA_ONNX_ASSIGN_ATTR_STR(model, model); + SHERPA_ONNX_ASSIGN_ATTR_FLOAT(threshold, threshold); + SHERPA_ONNX_ASSIGN_ATTR_FLOAT(min_silence_duration, minSilenceDuration); + SHERPA_ONNX_ASSIGN_ATTR_FLOAT(min_speech_duration, minSpeechDuration); + SHERPA_ONNX_ASSIGN_ATTR_INT32(window_size, windowSize); + SHERPA_ONNX_ASSIGN_ATTR_FLOAT(max_speech_duration, maxSpeechDuration); + + return c; +} + +static Napi::External +CreateVoiceActivityDetectorWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); +#if __OHOS__ + // the last argument is a NativeResourceManager + if (info.Length() != 3) { + std::ostringstream os; + os << "Expect only 3 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } +#else + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } +#endif + + if (!info[0].IsObject()) { + Napi::TypeError::New(env, + "You should pass an object as the first argument.") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[1].IsNumber()) { + Napi::TypeError::New(env, + "You should pass an integer as the second argument.") + .ThrowAsJavaScriptException(); + + return {}; + } + + Napi::Object o = info[0].As(); + + SherpaOnnxVadModelConfig c; + memset(&c, 0, sizeof(c)); + c.silero_vad = GetSileroVadConfig(o); + + SHERPA_ONNX_ASSIGN_ATTR_INT32(sample_rate, sampleRate); + SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads); + SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider); + + if (o.Has("debug") && + (o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) { + if (o.Get("debug").IsBoolean()) { + c.debug = o.Get("debug").As().Value(); + } else { + c.debug = o.Get("debug").As().Int32Value(); + } + } + + float buffer_size_in_seconds = info[1].As().FloatValue(); + +#if __OHOS__ + std::unique_ptr mgr(OH_ResourceManager_InitNativeResourceManager(env, info[2]), &OH_ResourceManager_ReleaseNativeResourceManager); + + SherpaOnnxVoiceActivityDetector *vad = + SherpaOnnxCreateVoiceActivityDetectorOHOS(&c, buffer_size_in_seconds, mgr.get()); +#else + SherpaOnnxVoiceActivityDetector *vad = + SherpaOnnxCreateVoiceActivityDetector(&c, buffer_size_in_seconds); +#endif + + if (c.silero_vad.model) { + delete[] c.silero_vad.model; + } + + if (c.provider) { + delete[] c.provider; + } + + return Napi::External::New( + env, vad, [](Napi::Env env, SherpaOnnxVoiceActivityDetector *p) { + SherpaOnnxDestroyVoiceActivityDetector(p); + }); +} + +static void VoiceActivityDetectorAcceptWaveformWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, "Argument 0 should be a VAD pointer.") + .ThrowAsJavaScriptException(); + + return; + } + + SherpaOnnxVoiceActivityDetector *vad = + info[0].As>().Data(); + + if (!info[1].IsTypedArray()) { + Napi::TypeError::New( + env, "Argument 1 should be a Float32Array containing samples") + .ThrowAsJavaScriptException(); + + return; + } + + Napi::Float32Array samples = info[1].As(); + +#if __OHOS__ + // Note(fangjun): For unknown reasons, we need to use `/sizeof(float)` here for Huawei + SherpaOnnxVoiceActivityDetectorAcceptWaveform(vad, samples.Data(), + samples.ElementLength() / sizeof(float)); +#else + SherpaOnnxVoiceActivityDetectorAcceptWaveform(vad, samples.Data(), + samples.ElementLength()); +#endif +} + +static Napi::Boolean VoiceActivityDetectorEmptyWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, "Argument 0 should be a VAD pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxVoiceActivityDetector *vad = + info[0].As>().Data(); + + int32_t is_empty = SherpaOnnxVoiceActivityDetectorEmpty(vad); + + return Napi::Boolean::New(env, is_empty); +} + +static Napi::Boolean VoiceActivityDetectorDetectedWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, "Argument 0 should be a VAD pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + SherpaOnnxVoiceActivityDetector *vad = + info[0].As>().Data(); + + int32_t is_detected = SherpaOnnxVoiceActivityDetectorDetected(vad); + + return Napi::Boolean::New(env, is_detected); +} + +static void VoiceActivityDetectorPopWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, "Argument 0 should be a VAD pointer.") + .ThrowAsJavaScriptException(); + + return; + } + + SherpaOnnxVoiceActivityDetector *vad = + info[0].As>().Data(); + + SherpaOnnxVoiceActivityDetectorPop(vad); +} + +static void VoiceActivityDetectorClearWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, "Argument 0 should be a VAD pointer.") + .ThrowAsJavaScriptException(); + + return; + } + + SherpaOnnxVoiceActivityDetector *vad = + info[0].As>().Data(); + + SherpaOnnxVoiceActivityDetectorClear(vad); +} + +static Napi::Object VoiceActivityDetectorFrontWrapper( + const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() != 1 && info.Length() != 2) { + std::ostringstream os; + os << "Expect only 1 or 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, "Argument 0 should be a VAD pointer.") + .ThrowAsJavaScriptException(); + + return {}; + } + + bool enable_external_buffer = true; + if (info.Length() == 2) { + if (info[1].IsBoolean()) { + enable_external_buffer = info[1].As().Value(); + } else { + Napi::TypeError::New(env, "Argument 1 should be a boolean.") + .ThrowAsJavaScriptException(); + } + } + + SherpaOnnxVoiceActivityDetector *vad = + info[0].As>().Data(); + + const SherpaOnnxSpeechSegment *segment = + SherpaOnnxVoiceActivityDetectorFront(vad); + + if (enable_external_buffer) { + Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New( + env, const_cast(segment->samples), sizeof(float) * segment->n, + [](Napi::Env /*env*/, void * /*data*/, + const SherpaOnnxSpeechSegment *hint) { + SherpaOnnxDestroySpeechSegment(hint); + }, + segment); + + Napi::Float32Array float32Array = + Napi::Float32Array::New(env, segment->n, arrayBuffer, 0); + + Napi::Object obj = Napi::Object::New(env); + obj.Set(Napi::String::New(env, "start"), segment->start); + obj.Set(Napi::String::New(env, "samples"), float32Array); + + return obj; + } else { + Napi::ArrayBuffer arrayBuffer = + Napi::ArrayBuffer::New(env, sizeof(float) * segment->n); + + Napi::Float32Array float32Array = + Napi::Float32Array::New(env, segment->n, arrayBuffer, 0); + + std::copy(segment->samples, segment->samples + segment->n, + float32Array.Data()); + + Napi::Object obj = Napi::Object::New(env); + obj.Set(Napi::String::New(env, "start"), segment->start); + obj.Set(Napi::String::New(env, "samples"), float32Array); + + SherpaOnnxDestroySpeechSegment(segment); + + return obj; + } +} + +static void VoiceActivityDetectorResetWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, "Argument 0 should be a VAD pointer.") + .ThrowAsJavaScriptException(); + + return; + } + + SherpaOnnxVoiceActivityDetector *vad = + info[0].As>().Data(); + + SherpaOnnxVoiceActivityDetectorReset(vad); +} + +static void VoiceActivityDetectorFlushWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() != 1) { + std::ostringstream os; + os << "Expect only 1 argument. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return; + } + + if (!info[0].IsExternal()) { + Napi::TypeError::New(env, "Argument 0 should be a VAD pointer.") + .ThrowAsJavaScriptException(); + + return; + } + + SherpaOnnxVoiceActivityDetector *vad = + info[0].As>().Data(); + + SherpaOnnxVoiceActivityDetectorFlush(vad); +} + +void InitVad(Napi::Env env, Napi::Object exports) { + exports.Set(Napi::String::New(env, "createCircularBuffer"), + Napi::Function::New(env, CreateCircularBufferWrapper)); + + exports.Set(Napi::String::New(env, "circularBufferPush"), + Napi::Function::New(env, CircularBufferPushWrapper)); + + exports.Set(Napi::String::New(env, "circularBufferGet"), + Napi::Function::New(env, CircularBufferGetWrapper)); + + exports.Set(Napi::String::New(env, "circularBufferPop"), + Napi::Function::New(env, CircularBufferPopWrapper)); + + exports.Set(Napi::String::New(env, "circularBufferSize"), + Napi::Function::New(env, CircularBufferSizeWrapper)); + + exports.Set(Napi::String::New(env, "circularBufferHead"), + Napi::Function::New(env, CircularBufferHeadWrapper)); + + exports.Set(Napi::String::New(env, "circularBufferReset"), + Napi::Function::New(env, CircularBufferResetWrapper)); + + exports.Set(Napi::String::New(env, "createVoiceActivityDetector"), + Napi::Function::New(env, CreateVoiceActivityDetectorWrapper)); + + exports.Set( + Napi::String::New(env, "voiceActivityDetectorAcceptWaveform"), + Napi::Function::New(env, VoiceActivityDetectorAcceptWaveformWrapper)); + + exports.Set(Napi::String::New(env, "voiceActivityDetectorIsEmpty"), + Napi::Function::New(env, VoiceActivityDetectorEmptyWrapper)); + + exports.Set(Napi::String::New(env, "voiceActivityDetectorIsDetected"), + Napi::Function::New(env, VoiceActivityDetectorDetectedWrapper)); + + exports.Set(Napi::String::New(env, "voiceActivityDetectorPop"), + Napi::Function::New(env, VoiceActivityDetectorPopWrapper)); + + exports.Set(Napi::String::New(env, "voiceActivityDetectorClear"), + Napi::Function::New(env, VoiceActivityDetectorClearWrapper)); + + exports.Set(Napi::String::New(env, "voiceActivityDetectorFront"), + Napi::Function::New(env, VoiceActivityDetectorFrontWrapper)); + + exports.Set(Napi::String::New(env, "voiceActivityDetectorReset"), + Napi::Function::New(env, VoiceActivityDetectorResetWrapper)); + + exports.Set(Napi::String::New(env, "voiceActivityDetectorFlush"), + Napi::Function::New(env, VoiceActivityDetectorFlushWrapper)); +} diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/wave-reader.cc b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/wave-reader.cc new file mode 100644 index 000000000..2973c6169 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/wave-reader.cc @@ -0,0 +1,171 @@ +// scripts/node-addon-api/src/wave-reader.cc +// +// Copyright (c) 2024 Xiaomi Corporation + +#include +#include + +#include "napi.h" // NOLINT +#include "sherpa-onnx/c-api/c-api.h" + +static Napi::Object ReadWaveWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() > 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsString()) { + Napi::TypeError::New(env, "Argument 0 should be a string") + .ThrowAsJavaScriptException(); + + return {}; + } + + std::string filename = info[0].As().Utf8Value(); + + bool enable_external_buffer = true; + if (info.Length() == 2) { + if (info[1].IsBoolean()) { + enable_external_buffer = info[1].As().Value(); + } else { + Napi::TypeError::New(env, "Argument 1 should be a boolean") + .ThrowAsJavaScriptException(); + + return {}; + } + } + + const SherpaOnnxWave *wave = SherpaOnnxReadWave(filename.c_str()); + if (!wave) { + std::ostringstream os; + os << "Failed to read '" << filename << "'"; + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (enable_external_buffer) { + Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New( + env, const_cast(wave->samples), + sizeof(float) * wave->num_samples, + [](Napi::Env /*env*/, void * /*data*/, const SherpaOnnxWave *hint) { + SherpaOnnxFreeWave(hint); + }, + wave); + Napi::Float32Array float32Array = + Napi::Float32Array::New(env, wave->num_samples, arrayBuffer, 0); + + Napi::Object obj = Napi::Object::New(env); + obj.Set(Napi::String::New(env, "samples"), float32Array); + obj.Set(Napi::String::New(env, "sampleRate"), wave->sample_rate); + return obj; + } else { + // don't use external buffer + Napi::ArrayBuffer arrayBuffer = + Napi::ArrayBuffer::New(env, sizeof(float) * wave->num_samples); + + Napi::Float32Array float32Array = + Napi::Float32Array::New(env, wave->num_samples, arrayBuffer, 0); + + std::copy(wave->samples, wave->samples + wave->num_samples, + float32Array.Data()); + + Napi::Object obj = Napi::Object::New(env); + obj.Set(Napi::String::New(env, "samples"), float32Array); + obj.Set(Napi::String::New(env, "sampleRate"), wave->sample_rate); + + SherpaOnnxFreeWave(wave); + + return obj; + } +} + +static Napi::Object ReadWaveFromBinaryWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (info.Length() > 2) { + std::ostringstream os; + os << "Expect only 1 or 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsTypedArray()) { + Napi::TypeError::New(env, "Argument 0 should be a float32 array") + .ThrowAsJavaScriptException(); + + return {}; + } + + Napi::Uint8Array data = info[0].As(); + int32_t n = data.ElementLength(); + const SherpaOnnxWave *wave = SherpaOnnxReadWaveFromBinaryData(reinterpret_cast(data.Data()), n); + if (!wave) { + std::ostringstream os; + os << "Failed to read wave"; + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + bool enable_external_buffer = true; + if (info.Length() == 2) { + if (info[1].IsBoolean()) { + enable_external_buffer = info[1].As().Value(); + } else { + Napi::TypeError::New(env, "Argument 1 should be a boolean") + .ThrowAsJavaScriptException(); + + return {}; + } + } + + if (enable_external_buffer) { + Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New( + env, const_cast(wave->samples), + sizeof(float) * wave->num_samples, + [](Napi::Env /*env*/, void * /*data*/, const SherpaOnnxWave *hint) { + SherpaOnnxFreeWave(hint); + }, + wave); + Napi::Float32Array float32Array = + Napi::Float32Array::New(env, wave->num_samples, arrayBuffer, 0); + + Napi::Object obj = Napi::Object::New(env); + obj.Set(Napi::String::New(env, "samples"), float32Array); + obj.Set(Napi::String::New(env, "sampleRate"), wave->sample_rate); + return obj; + } else { + // don't use external buffer + Napi::ArrayBuffer arrayBuffer = + Napi::ArrayBuffer::New(env, sizeof(float) * wave->num_samples); + + Napi::Float32Array float32Array = + Napi::Float32Array::New(env, wave->num_samples, arrayBuffer, 0); + + std::copy(wave->samples, wave->samples + wave->num_samples, + float32Array.Data()); + + Napi::Object obj = Napi::Object::New(env); + obj.Set(Napi::String::New(env, "samples"), float32Array); + obj.Set(Napi::String::New(env, "sampleRate"), wave->sample_rate); + + SherpaOnnxFreeWave(wave); + + return obj; + } +} + +void InitWaveReader(Napi::Env env, Napi::Object exports) { + exports.Set(Napi::String::New(env, "readWave"), + Napi::Function::New(env, ReadWaveWrapper)); + + exports.Set(Napi::String::New(env, "readWaveFromBinary"), + Napi::Function::New(env, ReadWaveFromBinaryWrapper)); +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/wave-writer.cc b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/wave-writer.cc new file mode 100644 index 000000000..3ade695a0 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/wave-writer.cc @@ -0,0 +1,81 @@ +// scripts/node-addon-api/src/wave-writer.cc +// +// Copyright (c) 2024 Xiaomi Corporation + +#include + +#include "napi.h" // NOLINT +#include "sherpa-onnx/c-api/c-api.h" + +// (filename, {samples: samples, sampleRate: sampleRate} +static Napi::Boolean WriteWaveWrapper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (info.Length() != 2) { + std::ostringstream os; + os << "Expect only 2 arguments. Given: " << info.Length(); + + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[0].IsString()) { + Napi::TypeError::New(env, "Argument 0 should be a string") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!info[1].IsObject()) { + Napi::TypeError::New(env, "Argument 1 should be an object") + .ThrowAsJavaScriptException(); + + return {}; + } + + Napi::Object obj = info[1].As(); + + if (!obj.Has("samples")) { + Napi::TypeError::New(env, "The argument object should have a field samples") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!obj.Get("samples").IsTypedArray()) { + Napi::TypeError::New(env, "The object['samples'] should be a typed array") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!obj.Has("sampleRate")) { + Napi::TypeError::New(env, + "The argument object should have a field sampleRate") + .ThrowAsJavaScriptException(); + + return {}; + } + + if (!obj.Get("sampleRate").IsNumber()) { + Napi::TypeError::New(env, "The object['samples'] should be a number") + .ThrowAsJavaScriptException(); + + return {}; + } + + Napi::Float32Array samples = obj.Get("samples").As(); + int32_t sample_rate = obj.Get("sampleRate").As().Int32Value(); + + int32_t ok = + SherpaOnnxWriteWave(samples.Data(), samples.ElementLength(), sample_rate, + info[0].As().Utf8Value().c_str()); + + return Napi::Boolean::New(env, ok); +} + +void InitWaveWriter(Napi::Env env, Napi::Object exports) { + exports.Set(Napi::String::New(env, "writeWave"), + Napi::Function::New(env, WriteWaveWrapper)); +} diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/ets/components/MainPage.ets b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/ets/components/MainPage.ets new file mode 100644 index 000000000..34b4b2c88 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/ets/components/MainPage.ets @@ -0,0 +1,21 @@ +import hilog from '@ohos.hilog'; +import testNapi from 'libsherpa_onnx.so'; + +@Component +export struct MainPage { + @State message: string = 'Hello World'; + + build() { + Row() { + Column() { + Text(this.message) + .fontSize(50) + .fontWeight(FontWeight.Bold) + .onClick(() => { + }) + } + .width('100%') + } + .height('100%') + } +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/ets/components/NonStreamingAsr.ets b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/ets/components/NonStreamingAsr.ets new file mode 100644 index 000000000..0cc8466a9 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/ets/components/NonStreamingAsr.ets @@ -0,0 +1,162 @@ +import { + acceptWaveformOffline, + createOfflineRecognizer, + createOfflineStream, + decodeOfflineStream, + getOfflineStreamResultAsJson, +} from 'libsherpa_onnx.so'; + +export interface Samples { + samples: Float32Array; + sampleRate: number; +} + +export class OfflineStream { + public handle: object; + + constructor(handle: object) { + this.handle = handle; + } + + // obj is {samples: samples, sampleRate: sampleRate} + // samples is a float32 array containing samples in the range [-1, 1] + // sampleRate is a number + acceptWaveform(obj: Samples) { + acceptWaveformOffline(this.handle, obj) + } +} + +export class FeatureConfig { + public sampleRate: number = 16000; + public featureDim: number = 80; +} + +export class OfflineTransducerModelConfig { + public encoder: string = ''; + public decoder: string = ''; + public joiner: string = ''; +} + +export class OfflineParaformerModelConfig { + public model: string = ''; +} + +export class OfflineNemoEncDecCtcModelConfig { + public model: string = ''; +} + +export class OfflineWhisperModelConfig { + public encoder: string = ''; + public decoder: string = ''; + public language: string = ''; + public task: string = 'transcribe'; + public tailPaddings: number = -1; +} + +export class OfflineTdnnModelConfig { + public model: string = ''; +} + +export class OfflineSenseVoiceModelConfig { + public model: string = ''; + public language: string = ''; + public useItn: boolean = false; +} + +export class OfflineMoonshineModelConfig { + public preprocessor: string = ''; + public encoder: string = ''; + public uncachedDecoder: string = ''; + public cachedDecoder: string = ''; +} + +export class OfflineModelConfig { + public transducer: OfflineTransducerModelConfig = new OfflineTransducerModelConfig(); + public paraformer: OfflineParaformerModelConfig = new OfflineParaformerModelConfig(); + public nemoCtc: OfflineNemoEncDecCtcModelConfig = new OfflineNemoEncDecCtcModelConfig(); + public whisper: OfflineWhisperModelConfig = new OfflineWhisperModelConfig(); + public tdnn: OfflineTdnnModelConfig = new OfflineTdnnModelConfig(); + public tokens: string = ''; + public numThreads: number = 1; + public debug: boolean = false; + public provider: string = "cpu"; + public modelType: string = ''; + public modelingUnit: string = "cjkchar"; + public bpeVocab: string = ''; + public telespeechCtc: string = ''; + public senseVoice: OfflineSenseVoiceModelConfig = new OfflineSenseVoiceModelConfig(); + public moonshine: OfflineMoonshineModelConfig = new OfflineMoonshineModelConfig(); +} + +export class OfflineLMConfig { + public model: string = ''; + public scale: number = 1.0; +} + +export class OfflineRecognizerConfig { + public featConfig: FeatureConfig = new FeatureConfig(); + public modelConfig: OfflineModelConfig = new OfflineModelConfig(); + public lmConfig: OfflineLMConfig = new OfflineLMConfig(); + public decodingMethod: string = "greedy_search"; + public maxActivePaths: number = 4; + public hotwordsFfile: string = ''; + public hotwordsScore: number = 1.5; + public ruleFsts: string = ''; + public ruleFars: string = ''; + public blankPenalty: number = 0; +} + +export class OfflineRecognizerResult { + public text: string = ''; + public timestamps: number[] = []; + public tokens: string[] = []; + public json = ''; + public lang: string = ''; + public emotion: string = ''; + public event: string = ''; +} + +interface OfflineRecognizerResultJson { + text: string; + timestamps: number[]; + tokens: string[]; + lang: string; + emotion: string; + event: string; +} + +export class OfflineRecognizer { + public handle: object; + public config: OfflineRecognizerConfig; + + constructor(config: OfflineRecognizerConfig, mgr?: object) { + this.handle = createOfflineRecognizer(config, mgr); + this.config = config + } + + createStream(): OfflineStream { + const handle: object = createOfflineStream(this.handle); + return new OfflineStream(handle); + } + + decode(stream: OfflineStream) { + decodeOfflineStream(this.handle, stream.handle); + } + + getResult(stream: OfflineStream): OfflineRecognizerResult { + const jsonStr: string = getOfflineStreamResultAsJson(stream.handle); + + let o = JSON.parse(jsonStr) as OfflineRecognizerResultJson; + + const r = new OfflineRecognizerResult() + r.text = o.text + r.timestamps = o.timestamps; + r.tokens = o.tokens; + r.json = jsonStr; + r.lang = o.lang; + r.emotion = o.emotion; + r.event = o.event; + + return r; + } +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/ets/components/StreamingAsr.ets b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/ets/components/StreamingAsr.ets new file mode 100644 index 000000000..7ecc552ca --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/ets/components/StreamingAsr.ets @@ -0,0 +1,141 @@ +import { + acceptWaveformOnline, + createOnlineRecognizer, + createOnlineStream, + decodeOnlineStream, + getOnlineStreamResultAsJson, + inputFinished, + isEndpoint, + isOnlineStreamReady, + reset, +} from 'libsherpa_onnx.so'; + +import { FeatureConfig, Samples } from './NonStreamingAsr'; + +export class OnlineStream { + public handle: object; + + constructor(handle: object) { + this.handle = handle; + } + + // obj is {samples: samples, sampleRate: sampleRate} + // samples is a float32 array containing samples in the range [-1, 1] + // sampleRate is a number + acceptWaveform(obj: Samples) { + acceptWaveformOnline(this.handle, obj) + } + + inputFinished() { + inputFinished(this.handle) + } +} + +export class OnlineTransducerModelConfig { + public encoder: string = ''; + public decoder: string = ''; + public joiner: string = ''; +} + +export class OnlineParaformerModelConfig { + public encoder: string = ''; + public decoder: string = ''; +} + +export class OnlineZipformer2CtcModelConfig { + public model: string = ''; +} + +export class OnlineModelConfig { + public transducer: OnlineTransducerModelConfig = new OnlineTransducerModelConfig(); + public paraformer: OnlineParaformerModelConfig = new OnlineParaformerModelConfig(); + public zipformer2_ctc: OnlineZipformer2CtcModelConfig = new OnlineZipformer2CtcModelConfig(); + public tokens: string = ''; + public numThreads: number = 1; + public provider: string = "cpu"; + public debug: boolean = false; + public modelType: string = ''; + public modelingUnit: string = "cjkchar"; + public bpeVocab: string = ''; +} + +export class OnlineCtcFstDecoderConfig { + public graph: string = ''; + public maxActive: number = 3000; +} + +export class OnlineRecognizerConfig { + public featConfig: FeatureConfig = new FeatureConfig(); + public modelConfig: OnlineModelConfig = new OnlineModelConfig(); + public decodingMethod: string = "greedy_search"; + public maxActivePaths: number = 4; + public enableEndpoint: boolean = false; + public rule1MinTrailingSilence: number = 2.4; + public rule2MinTrailingSilence: number = 1.2; + public rule3MinUtteranceLength: number = 20; + public hotwordsFile: string = ''; + public hotwordsScore: number = 1.5; + public ctcFstDecoderConfig: OnlineCtcFstDecoderConfig = new OnlineCtcFstDecoderConfig(); + public ruleFsts: string = ''; + public ruleFars: string = ''; + public blankPenalty: number = 0; +} + +interface OnlineRecognizerResultJson { + text: string; + timestamps: number[]; + tokens: string[]; +} + +export class OnlineRecognizerResult { + public text: string = ''; + public tokens: string[] = []; + public timestamps: number[] = []; + public json: string = ''; +} + +export class OnlineRecognizer { + public handle: object; + public config: OnlineRecognizerConfig + + constructor(config: OnlineRecognizerConfig, mgr?: object) { + this.handle = createOnlineRecognizer(config, mgr); + this.config = config + } + + createStream(): OnlineStream { + const handle: object = createOnlineStream(this.handle); + return new OnlineStream(handle); + } + + isReady(stream: OnlineStream): boolean { + return isOnlineStreamReady(this.handle, stream.handle); + } + + decode(stream: OnlineStream) { + decodeOnlineStream(this.handle, stream.handle); + } + + isEndpoint(stream: OnlineStream): boolean { + return isEndpoint(this.handle, stream.handle); + } + + reset(stream: OnlineStream) { + reset(this.handle, stream.handle); + } + + getResult(stream: OnlineStream): OnlineRecognizerResult { + const jsonStr: string = + getOnlineStreamResultAsJson(this.handle, stream.handle); + + let o = JSON.parse(jsonStr) as OnlineRecognizerResultJson; + + const r = new OnlineRecognizerResult() + r.text = o.text + r.timestamps = o.timestamps; + r.tokens = o.tokens; + r.json = jsonStr; + + return r; + } +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/ets/components/Vad.ets b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/ets/components/Vad.ets new file mode 100644 index 000000000..155eac680 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/ets/components/Vad.ets @@ -0,0 +1,133 @@ +import { + circularBufferGet, + circularBufferHead, + circularBufferPop, + circularBufferPush, + circularBufferReset, + circularBufferSize, + createCircularBuffer, + createVoiceActivityDetector, + voiceActivityDetectorAcceptWaveform, + voiceActivityDetectorClear, + voiceActivityDetectorFlush, + voiceActivityDetectorFront, + voiceActivityDetectorIsDetected, + voiceActivityDetectorIsEmpty, + voiceActivityDetectorPop, + voiceActivityDetectorReset, +} from 'libsherpa_onnx.so'; + +export class SileroVadConfig { + public model: string; + public threshold: number; + public minSpeechDuration: number; + public minSilenceDuration: number; + public windowSize: number; + + public constructor(model: string, threshold: number, minSpeechDuration: number, minSilenceDuration: number, + windowSize: number) { + this.model = model; + this.threshold = threshold; + this.minSpeechDuration = minSpeechDuration; + this.minSilenceDuration = minSilenceDuration; + this.windowSize = windowSize; + } +} + +export class VadConfig { + public sileroVad: SileroVadConfig; + public sampleRate: number; + public debug: boolean; + public numThreads: number; + + public constructor(sileroVad: SileroVadConfig, sampleRate: number, debug: boolean, numThreads: number) { + this.sileroVad = sileroVad; + this.sampleRate = sampleRate; + this.debug = debug; + this.numThreads = numThreads; + } +} + +export class CircularBuffer { + private handle: object; + + constructor(capacity: number) { + this.handle = createCircularBuffer(capacity); + } + + // samples is a float32 array + push(samples: Float32Array) { + console.log(`here samples: ${samples}`); + circularBufferPush(this.handle, samples); + } + + // return a float32 array + get(startIndex: number, n: number, enableExternalBuffer: boolean = true): Float32Array { + return circularBufferGet( + this.handle, startIndex, n, enableExternalBuffer); + } + + pop(n: number) { + circularBufferPop(this.handle, n); + } + + size(): number { + return circularBufferSize(this.handle); + } + + head(): number { + return circularBufferHead(this.handle); + } + + reset() { + circularBufferReset(this.handle); + } +} + +export interface SpeechSegment { + samples: Float32Array; + start: number; +} + +export class Vad { + public config: VadConfig; + private handle: object; + + constructor(config: VadConfig, bufferSizeInSeconds?: number, mgr?: object) { + this.handle = + createVoiceActivityDetector(config, bufferSizeInSeconds, mgr); + this.config = config; + } + + acceptWaveform(samples: Float32Array): void { + voiceActivityDetectorAcceptWaveform(this.handle, samples); + } + + isEmpty(): boolean { + return voiceActivityDetectorIsEmpty(this.handle); + } + + isDetected(): boolean { + return voiceActivityDetectorIsDetected(this.handle); + } + + pop(): void { + voiceActivityDetectorPop(this.handle); + } + + clear(): void { + voiceActivityDetectorClear(this.handle); + } + + front(enableExternalBuffer = true): SpeechSegment { + return voiceActivityDetectorFront(this.handle, enableExternalBuffer); + } + + reset(): void { + voiceActivityDetectorReset(this.handle); + } + + flush(): void { + voiceActivityDetectorFlush(this.handle); + } +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/module.json5 b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/module.json5 new file mode 100644 index 000000000..1db0eb692 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/module.json5 @@ -0,0 +1,11 @@ +{ + "module": { + "name": "sherpa_onnx", + "type": "har", + "deviceTypes": [ + "default", + "tablet", + "2in1" + ] + } +} diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/resources/base/element/string.json b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/resources/base/element/string.json new file mode 100644 index 000000000..f51a9c846 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from package" + } + ] +} diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/resources/en_US/element/string.json b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/resources/en_US/element/string.json new file mode 100644 index 000000000..f51a9c846 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/resources/en_US/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from package" + } + ] +} diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/resources/zh_CN/element/string.json b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/resources/zh_CN/element/string.json new file mode 100644 index 000000000..f51a9c846 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from package" + } + ] +} diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/ohosTest/ets/test/Ability.test.ets b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 000000000..8aa374977 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,35 @@ +import hilog from '@ohos.hilog'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function abilityTest() { + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }) + }) +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/ohosTest/ets/test/List.test.ets b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 000000000..794c7dc4e --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,5 @@ +import abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/ohosTest/module.json5 b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/ohosTest/module.json5 new file mode 100644 index 000000000..b7a1665a7 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/ohosTest/module.json5 @@ -0,0 +1,13 @@ +{ + "module": { + "name": "sherpa_onnx_test", + "type": "feature", + "deviceTypes": [ + "default", + "tablet", + "2in1" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/test/List.test.ets b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/test/List.test.ets new file mode 100644 index 000000000..bb5b5c373 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/test/List.test.ets @@ -0,0 +1,5 @@ +import localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/test/LocalUnit.test.ets b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/test/LocalUnit.test.ets new file mode 100644 index 000000000..165fc1615 --- /dev/null +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/test/LocalUnit.test.ets @@ -0,0 +1,33 @@ +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function localUnitTest() { + describe('localUnitTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }); + }); +} \ No newline at end of file diff --git a/scripts/node-addon-api/src/audio-tagging.cc b/scripts/node-addon-api/src/audio-tagging.cc deleted file mode 100644 index bed4e48a2..000000000 --- a/scripts/node-addon-api/src/audio-tagging.cc +++ /dev/null @@ -1,227 +0,0 @@ -// scripts/node-addon-api/src/audio-tagging.cc -// -// Copyright (c) 2024 Xiaomi Corporation -#include - -#include "macros.h" // NOLINT -#include "napi.h" // NOLINT -#include "sherpa-onnx/c-api/c-api.h" - -static SherpaOnnxOfflineZipformerAudioTaggingModelConfig -GetAudioTaggingZipformerModelConfig(Napi::Object obj) { - SherpaOnnxOfflineZipformerAudioTaggingModelConfig c; - memset(&c, 0, sizeof(c)); - - if (!obj.Has("zipformer") || !obj.Get("zipformer").IsObject()) { - return c; - } - - Napi::Object o = obj.Get("zipformer").As(); - - SHERPA_ONNX_ASSIGN_ATTR_STR(model, model); - - return c; -} - -static SherpaOnnxAudioTaggingModelConfig GetAudioTaggingModelConfig( - Napi::Object obj) { - SherpaOnnxAudioTaggingModelConfig c; - memset(&c, 0, sizeof(c)); - - if (!obj.Has("model") || !obj.Get("model").IsObject()) { - return c; - } - - Napi::Object o = obj.Get("model").As(); - c.zipformer = GetAudioTaggingZipformerModelConfig(o); - - SHERPA_ONNX_ASSIGN_ATTR_STR(ced, ced); - - SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads); - - if (o.Has("debug") && - (o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) { - if (o.Get("debug").IsBoolean()) { - c.debug = o.Get("debug").As().Value(); - } else { - c.debug = o.Get("debug").As().Int32Value(); - } - } - SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider); - - return c; -} - -static Napi::External CreateAudioTaggingWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsObject()) { - Napi::TypeError::New(env, "You should pass an object as the only argument.") - .ThrowAsJavaScriptException(); - - return {}; - } - - Napi::Object o = info[0].As(); - - SherpaOnnxAudioTaggingConfig c; - memset(&c, 0, sizeof(c)); - c.model = GetAudioTaggingModelConfig(o); - - SHERPA_ONNX_ASSIGN_ATTR_STR(labels, labels); - SHERPA_ONNX_ASSIGN_ATTR_INT32(top_k, topK); - - const SherpaOnnxAudioTagging *at = SherpaOnnxCreateAudioTagging(&c); - - if (c.model.zipformer.model) { - delete[] c.model.zipformer.model; - } - - if (c.model.ced) { - delete[] c.model.ced; - } - - if (c.model.provider) { - delete[] c.model.provider; - } - - if (c.labels) { - delete[] c.labels; - } - - if (!at) { - Napi::TypeError::New(env, "Please check your config!") - .ThrowAsJavaScriptException(); - - return {}; - } - - return Napi::External::New( - env, const_cast(at), - [](Napi::Env env, SherpaOnnxAudioTagging *at) { - SherpaOnnxDestroyAudioTagging(at); - }); -} - -static Napi::External -AudioTaggingCreateOfflineStreamWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New( - env, "You should pass an audio tagging pointer as the only argument") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxAudioTagging *at = - info[0].As>().Data(); - - const SherpaOnnxOfflineStream *stream = - SherpaOnnxAudioTaggingCreateOfflineStream(at); - - return Napi::External::New( - env, const_cast(stream), - [](Napi::Env env, SherpaOnnxOfflineStream *stream) { - SherpaOnnxDestroyOfflineStream(stream); - }); -} - -static Napi::Object AudioTaggingComputeWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 3) { - std::ostringstream os; - os << "Expect only 3 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New( - env, "You should pass an audio tagging pointer as the first argument") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[1].IsExternal()) { - Napi::TypeError::New( - env, "You should pass an offline stream pointer as the second argument") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[2].IsNumber()) { - Napi::TypeError::New(env, - "You should pass an integer as the third argument") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxAudioTagging *at = - info[0].As>().Data(); - - SherpaOnnxOfflineStream *stream = - info[1].As>().Data(); - - int32_t top_k = info[2].As().Int32Value(); - - const SherpaOnnxAudioEvent *const *events = - SherpaOnnxAudioTaggingCompute(at, stream, top_k); - - auto p = events; - int32_t k = 0; - while (p && *p) { - ++k; - ++p; - } - - Napi::Array ans = Napi::Array::New(env, k); - for (uint32_t i = 0; i != k; ++i) { - Napi::Object obj = Napi::Object::New(env); - obj.Set(Napi::String::New(env, "name"), - Napi::String::New(env, events[i]->name)); - obj.Set(Napi::String::New(env, "index"), - Napi::Number::New(env, events[i]->index)); - obj.Set(Napi::String::New(env, "prob"), - Napi::Number::New(env, events[i]->prob)); - ans[i] = obj; - } - - SherpaOnnxAudioTaggingFreeResults(events); - - return ans; -} - -void InitAudioTagging(Napi::Env env, Napi::Object exports) { - exports.Set(Napi::String::New(env, "createAudioTagging"), - Napi::Function::New(env, CreateAudioTaggingWrapper)); - - exports.Set(Napi::String::New(env, "audioTaggingCreateOfflineStream"), - Napi::Function::New(env, AudioTaggingCreateOfflineStreamWrapper)); - - exports.Set(Napi::String::New(env, "audioTaggingCompute"), - Napi::Function::New(env, AudioTaggingComputeWrapper)); -} diff --git a/scripts/node-addon-api/src/audio-tagging.cc b/scripts/node-addon-api/src/audio-tagging.cc new file mode 120000 index 000000000..42b052870 --- /dev/null +++ b/scripts/node-addon-api/src/audio-tagging.cc @@ -0,0 +1 @@ +../../../harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/audio-tagging.cc \ No newline at end of file diff --git a/scripts/node-addon-api/src/keyword-spotting.cc b/scripts/node-addon-api/src/keyword-spotting.cc deleted file mode 100644 index 2b5a24100..000000000 --- a/scripts/node-addon-api/src/keyword-spotting.cc +++ /dev/null @@ -1,266 +0,0 @@ -// scripts/node-addon-api/src/keyword-spotting.cc -// -// Copyright (c) 2024 Xiaomi Corporation -#include - -#include "macros.h" // NOLINT -#include "napi.h" // NOLINT -#include "sherpa-onnx/c-api/c-api.h" - -// defined ./streaming-asr.cc -SherpaOnnxFeatureConfig GetFeatureConfig(Napi::Object obj); - -// defined ./streaming-asr.cc -SherpaOnnxOnlineModelConfig GetOnlineModelConfig(Napi::Object obj); - -static Napi::External CreateKeywordSpotterWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsObject()) { - Napi::TypeError::New(env, "Expect an object as the argument") - .ThrowAsJavaScriptException(); - - return {}; - } - - Napi::Object o = info[0].As(); - SherpaOnnxKeywordSpotterConfig c; - memset(&c, 0, sizeof(c)); - c.feat_config = GetFeatureConfig(o); - c.model_config = GetOnlineModelConfig(o); - - SHERPA_ONNX_ASSIGN_ATTR_INT32(max_active_paths, maxActivePaths); - SHERPA_ONNX_ASSIGN_ATTR_INT32(num_trailing_blanks, numTrailingBlanks); - SHERPA_ONNX_ASSIGN_ATTR_FLOAT(keywords_score, keywordsScore); - SHERPA_ONNX_ASSIGN_ATTR_FLOAT(keywords_threshold, keywordsThreshold); - SHERPA_ONNX_ASSIGN_ATTR_STR(keywords_file, keywordsFile); - SHERPA_ONNX_ASSIGN_ATTR_STR(keywords_buf, keywordsBuf); - SHERPA_ONNX_ASSIGN_ATTR_INT32(keywords_buf_size, keywordsBufSize); - - SherpaOnnxKeywordSpotter *kws = SherpaOnnxCreateKeywordSpotter(&c); - - if (c.model_config.transducer.encoder) { - delete[] c.model_config.transducer.encoder; - } - - if (c.model_config.transducer.decoder) { - delete[] c.model_config.transducer.decoder; - } - - if (c.model_config.transducer.joiner) { - delete[] c.model_config.transducer.joiner; - } - - if (c.model_config.paraformer.encoder) { - delete[] c.model_config.paraformer.encoder; - } - - if (c.model_config.paraformer.decoder) { - delete[] c.model_config.paraformer.decoder; - } - - if (c.model_config.zipformer2_ctc.model) { - delete[] c.model_config.zipformer2_ctc.model; - } - - if (c.model_config.tokens) { - delete[] c.model_config.tokens; - } - - if (c.model_config.provider) { - delete[] c.model_config.provider; - } - - if (c.model_config.model_type) { - delete[] c.model_config.model_type; - } - - if (c.keywords_file) { - delete[] c.keywords_file; - } - - if (c.keywords_buf) { - delete[] c.keywords_buf; - } - - if (!kws) { - Napi::TypeError::New(env, "Please check your config!") - .ThrowAsJavaScriptException(); - - return {}; - } - - return Napi::External::New( - env, kws, [](Napi::Env env, SherpaOnnxKeywordSpotter *kws) { - SherpaOnnxDestroyKeywordSpotter(kws); - }); -} - -static Napi::External CreateKeywordStreamWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New( - env, "You should pass a keyword spotter pointer as the only argument") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxKeywordSpotter *kws = - info[0].As>().Data(); - - SherpaOnnxOnlineStream *stream = SherpaOnnxCreateKeywordStream(kws); - - return Napi::External::New( - env, stream, [](Napi::Env env, SherpaOnnxOnlineStream *stream) { - SherpaOnnxDestroyOnlineStream(stream); - }); -} - -static Napi::Boolean IsKeywordStreamReadyWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, "Argument 0 should be a keyword spotter pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[1].IsExternal()) { - Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxKeywordSpotter *kws = - info[0].As>().Data(); - - SherpaOnnxOnlineStream *stream = - info[1].As>().Data(); - - int32_t is_ready = SherpaOnnxIsKeywordStreamReady(kws, stream); - - return Napi::Boolean::New(env, is_ready); -} - -static void DecodeKeywordStreamWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, "Argument 0 should be a keyword spotter pointer.") - .ThrowAsJavaScriptException(); - - return; - } - - if (!info[1].IsExternal()) { - Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.") - .ThrowAsJavaScriptException(); - - return; - } - - SherpaOnnxKeywordSpotter *kws = - info[0].As>().Data(); - - SherpaOnnxOnlineStream *stream = - info[1].As>().Data(); - - SherpaOnnxDecodeKeywordStream(kws, stream); -} - -static Napi::String GetKeywordResultAsJsonWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, "Argument 0 should be a keyword spotter pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[1].IsExternal()) { - Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxKeywordSpotter *kws = - info[0].As>().Data(); - - SherpaOnnxOnlineStream *stream = - info[1].As>().Data(); - - const char *json = SherpaOnnxGetKeywordResultAsJson(kws, stream); - - Napi::String s = Napi::String::New(env, json); - - SherpaOnnxFreeKeywordResultJson(json); - - return s; -} - -void InitKeywordSpotting(Napi::Env env, Napi::Object exports) { - exports.Set(Napi::String::New(env, "createKeywordSpotter"), - Napi::Function::New(env, CreateKeywordSpotterWrapper)); - - exports.Set(Napi::String::New(env, "createKeywordStream"), - Napi::Function::New(env, CreateKeywordStreamWrapper)); - - exports.Set(Napi::String::New(env, "isKeywordStreamReady"), - Napi::Function::New(env, IsKeywordStreamReadyWrapper)); - - exports.Set(Napi::String::New(env, "decodeKeywordStream"), - Napi::Function::New(env, DecodeKeywordStreamWrapper)); - - exports.Set(Napi::String::New(env, "getKeywordResultAsJson"), - Napi::Function::New(env, GetKeywordResultAsJsonWrapper)); -} diff --git a/scripts/node-addon-api/src/keyword-spotting.cc b/scripts/node-addon-api/src/keyword-spotting.cc new file mode 120000 index 000000000..66230e586 --- /dev/null +++ b/scripts/node-addon-api/src/keyword-spotting.cc @@ -0,0 +1 @@ +../../../harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/keyword-spotting.cc \ No newline at end of file diff --git a/scripts/node-addon-api/src/macros.h b/scripts/node-addon-api/src/macros.h deleted file mode 100644 index 1f4f401e3..000000000 --- a/scripts/node-addon-api/src/macros.h +++ /dev/null @@ -1,51 +0,0 @@ -// scripts/node-addon-api/src/macros.h -// -// Copyright (c) 2024 Xiaomi Corporation -#ifndef SCRIPTS_NODE_ADDON_API_SRC_MACROS_H_ -#define SCRIPTS_NODE_ADDON_API_SRC_MACROS_H_ - -#include -#include - -#define SHERPA_ONNX_ASSIGN_ATTR_STR(c_name, js_name) \ - do { \ - if (o.Has(#js_name) && o.Get(#js_name).IsString()) { \ - Napi::String _str = o.Get(#js_name).As(); \ - std::string s = _str.Utf8Value(); \ - char *p = new char[s.size() + 1]; \ - std::copy(s.begin(), s.end(), p); \ - p[s.size()] = 0; \ - \ - c.c_name = p; \ - } else if (o.Has(#js_name) && o.Get(#js_name).IsTypedArray()) { \ - Napi::Uint8Array _array = o.Get(#js_name).As(); \ - char *p = new char[_array.ElementLength() + 1]; \ - std::copy(_array.Data(), _array.Data() + _array.ElementLength(), p); \ - p[_array.ElementLength()] = '\0'; \ - \ - c.c_name = p; \ - } \ - } while (0) - -#define SHERPA_ONNX_ASSIGN_ATTR_INT32(c_name, js_name) \ - do { \ - if (o.Has(#js_name) && o.Get(#js_name).IsNumber()) { \ - c.c_name = o.Get(#js_name).As().Int32Value(); \ - } \ - } while (0) - -#define SHERPA_ONNX_ASSIGN_ATTR_FLOAT(c_name, js_name) \ - do { \ - if (o.Has(#js_name) && o.Get(#js_name).IsNumber()) { \ - c.c_name = o.Get(#js_name).As().FloatValue(); \ - } \ - } while (0) - -#define SHERPA_ONNX_DELETE_C_STR(p) \ - do { \ - if (p) { \ - delete[] p; \ - } \ - } while (0) - -#endif // SCRIPTS_NODE_ADDON_API_SRC_MACROS_H_ diff --git a/scripts/node-addon-api/src/macros.h b/scripts/node-addon-api/src/macros.h new file mode 120000 index 000000000..3d541e3da --- /dev/null +++ b/scripts/node-addon-api/src/macros.h @@ -0,0 +1 @@ +../../../harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/macros.h \ No newline at end of file diff --git a/scripts/node-addon-api/src/non-streaming-asr.cc b/scripts/node-addon-api/src/non-streaming-asr.cc deleted file mode 100644 index a95c892a8..000000000 --- a/scripts/node-addon-api/src/non-streaming-asr.cc +++ /dev/null @@ -1,461 +0,0 @@ -// scripts/node-addon-api/src/non-streaming-asr.cc -// -// Copyright (c) 2024 Xiaomi Corporation -#include - -#include "macros.h" // NOLINT -#include "napi.h" // NOLINT -#include "sherpa-onnx/c-api/c-api.h" - -// defined in ./streaming-asr.cc -SherpaOnnxFeatureConfig GetFeatureConfig(Napi::Object obj); - -static SherpaOnnxOfflineTransducerModelConfig GetOfflineTransducerModelConfig( - Napi::Object obj) { - SherpaOnnxOfflineTransducerModelConfig c; - memset(&c, 0, sizeof(c)); - - if (!obj.Has("transducer") || !obj.Get("transducer").IsObject()) { - return c; - } - - Napi::Object o = obj.Get("transducer").As(); - - SHERPA_ONNX_ASSIGN_ATTR_STR(encoder, encoder); - SHERPA_ONNX_ASSIGN_ATTR_STR(decoder, decoder); - SHERPA_ONNX_ASSIGN_ATTR_STR(joiner, joiner); - - return c; -} - -static SherpaOnnxOfflineParaformerModelConfig GetOfflineParaformerModelConfig( - Napi::Object obj) { - SherpaOnnxOfflineParaformerModelConfig c; - memset(&c, 0, sizeof(c)); - - if (!obj.Has("paraformer") || !obj.Get("paraformer").IsObject()) { - return c; - } - - Napi::Object o = obj.Get("paraformer").As(); - - SHERPA_ONNX_ASSIGN_ATTR_STR(model, model); - - return c; -} - -static SherpaOnnxOfflineNemoEncDecCtcModelConfig GetOfflineNeMoCtcModelConfig( - Napi::Object obj) { - SherpaOnnxOfflineNemoEncDecCtcModelConfig c; - memset(&c, 0, sizeof(c)); - - if (!obj.Has("nemoCtc") || !obj.Get("nemoCtc").IsObject()) { - return c; - } - - Napi::Object o = obj.Get("nemoCtc").As(); - - SHERPA_ONNX_ASSIGN_ATTR_STR(model, model); - - return c; -} - -static SherpaOnnxOfflineWhisperModelConfig GetOfflineWhisperModelConfig( - Napi::Object obj) { - SherpaOnnxOfflineWhisperModelConfig c; - memset(&c, 0, sizeof(c)); - - if (!obj.Has("whisper") || !obj.Get("whisper").IsObject()) { - return c; - } - - Napi::Object o = obj.Get("whisper").As(); - - SHERPA_ONNX_ASSIGN_ATTR_STR(encoder, encoder); - SHERPA_ONNX_ASSIGN_ATTR_STR(decoder, decoder); - SHERPA_ONNX_ASSIGN_ATTR_STR(language, language); - SHERPA_ONNX_ASSIGN_ATTR_STR(task, task); - SHERPA_ONNX_ASSIGN_ATTR_INT32(tail_paddings, tailPaddings); - - return c; -} - -static SherpaOnnxOfflineMoonshineModelConfig GetOfflineMoonshineModelConfig( - Napi::Object obj) { - SherpaOnnxOfflineMoonshineModelConfig c; - memset(&c, 0, sizeof(c)); - - if (!obj.Has("moonshine") || !obj.Get("moonshine").IsObject()) { - return c; - } - - Napi::Object o = obj.Get("moonshine").As(); - - SHERPA_ONNX_ASSIGN_ATTR_STR(preprocessor, preprocessor); - SHERPA_ONNX_ASSIGN_ATTR_STR(encoder, encoder); - SHERPA_ONNX_ASSIGN_ATTR_STR(uncached_decoder, uncachedDecoder); - SHERPA_ONNX_ASSIGN_ATTR_STR(cached_decoder, cachedDecoder); - - return c; -} - -static SherpaOnnxOfflineTdnnModelConfig GetOfflineTdnnModelConfig( - Napi::Object obj) { - SherpaOnnxOfflineTdnnModelConfig c; - memset(&c, 0, sizeof(c)); - - if (!obj.Has("tdnn") || !obj.Get("tdnn").IsObject()) { - return c; - } - - Napi::Object o = obj.Get("tdnn").As(); - - SHERPA_ONNX_ASSIGN_ATTR_STR(model, model); - - return c; -} - -static SherpaOnnxOfflineSenseVoiceModelConfig GetOfflineSenseVoiceModelConfig( - Napi::Object obj) { - SherpaOnnxOfflineSenseVoiceModelConfig c; - memset(&c, 0, sizeof(c)); - - if (!obj.Has("senseVoice") || !obj.Get("senseVoice").IsObject()) { - return c; - } - - Napi::Object o = obj.Get("senseVoice").As(); - - SHERPA_ONNX_ASSIGN_ATTR_STR(model, model); - SHERPA_ONNX_ASSIGN_ATTR_STR(language, language); - SHERPA_ONNX_ASSIGN_ATTR_INT32(use_itn, useInverseTextNormalization); - - return c; -} - -static SherpaOnnxOfflineModelConfig GetOfflineModelConfig(Napi::Object obj) { - SherpaOnnxOfflineModelConfig c; - memset(&c, 0, sizeof(c)); - - if (!obj.Has("modelConfig") || !obj.Get("modelConfig").IsObject()) { - return c; - } - - Napi::Object o = obj.Get("modelConfig").As(); - - c.transducer = GetOfflineTransducerModelConfig(o); - c.paraformer = GetOfflineParaformerModelConfig(o); - c.nemo_ctc = GetOfflineNeMoCtcModelConfig(o); - c.whisper = GetOfflineWhisperModelConfig(o); - c.tdnn = GetOfflineTdnnModelConfig(o); - c.sense_voice = GetOfflineSenseVoiceModelConfig(o); - c.moonshine = GetOfflineMoonshineModelConfig(o); - - SHERPA_ONNX_ASSIGN_ATTR_STR(tokens, tokens); - SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads); - - if (o.Has("debug") && - (o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) { - if (o.Get("debug").IsBoolean()) { - c.debug = o.Get("debug").As().Value(); - } else { - c.debug = o.Get("debug").As().Int32Value(); - } - } - - SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider); - SHERPA_ONNX_ASSIGN_ATTR_STR(model_type, modelType); - SHERPA_ONNX_ASSIGN_ATTR_STR(modeling_unit, modelingUnit); - SHERPA_ONNX_ASSIGN_ATTR_STR(bpe_vocab, bpeVocab); - SHERPA_ONNX_ASSIGN_ATTR_STR(telespeech_ctc, teleSpeechCtc); - - return c; -} - -static SherpaOnnxOfflineLMConfig GetOfflineLMConfig(Napi::Object obj) { - SherpaOnnxOfflineLMConfig c; - memset(&c, 0, sizeof(c)); - - if (!obj.Has("lmConfig") || !obj.Get("lmConfig").IsObject()) { - return c; - } - - Napi::Object o = obj.Get("lmConfig").As(); - - SHERPA_ONNX_ASSIGN_ATTR_STR(model, model); - SHERPA_ONNX_ASSIGN_ATTR_FLOAT(scale, scale); - - return c; -} - -static Napi::External -CreateOfflineRecognizerWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsObject()) { - Napi::TypeError::New(env, "Expect an object as the argument") - .ThrowAsJavaScriptException(); - - return {}; - } - - Napi::Object o = info[0].As(); - - SherpaOnnxOfflineRecognizerConfig c; - memset(&c, 0, sizeof(c)); - c.feat_config = GetFeatureConfig(o); - c.model_config = GetOfflineModelConfig(o); - c.lm_config = GetOfflineLMConfig(o); - - SHERPA_ONNX_ASSIGN_ATTR_STR(decoding_method, decodingMethod); - SHERPA_ONNX_ASSIGN_ATTR_INT32(max_active_paths, maxActivePaths); - SHERPA_ONNX_ASSIGN_ATTR_STR(hotwords_file, hotwordsFile); - SHERPA_ONNX_ASSIGN_ATTR_FLOAT(hotwords_score, hotwordsScore); - SHERPA_ONNX_ASSIGN_ATTR_STR(rule_fsts, ruleFsts); - SHERPA_ONNX_ASSIGN_ATTR_STR(rule_fars, ruleFars); - SHERPA_ONNX_ASSIGN_ATTR_FLOAT(blank_penalty, blankPenalty); - - const SherpaOnnxOfflineRecognizer *recognizer = - SherpaOnnxCreateOfflineRecognizer(&c); - - SHERPA_ONNX_DELETE_C_STR(c.model_config.transducer.encoder); - SHERPA_ONNX_DELETE_C_STR(c.model_config.transducer.decoder); - SHERPA_ONNX_DELETE_C_STR(c.model_config.transducer.joiner); - - SHERPA_ONNX_DELETE_C_STR(c.model_config.paraformer.model); - - SHERPA_ONNX_DELETE_C_STR(c.model_config.nemo_ctc.model); - - SHERPA_ONNX_DELETE_C_STR(c.model_config.whisper.encoder); - SHERPA_ONNX_DELETE_C_STR(c.model_config.whisper.decoder); - SHERPA_ONNX_DELETE_C_STR(c.model_config.whisper.language); - SHERPA_ONNX_DELETE_C_STR(c.model_config.whisper.task); - - SHERPA_ONNX_DELETE_C_STR(c.model_config.tdnn.model); - - SHERPA_ONNX_DELETE_C_STR(c.model_config.sense_voice.model); - SHERPA_ONNX_DELETE_C_STR(c.model_config.sense_voice.language); - - SHERPA_ONNX_DELETE_C_STR(c.model_config.moonshine.preprocessor); - SHERPA_ONNX_DELETE_C_STR(c.model_config.moonshine.encoder); - SHERPA_ONNX_DELETE_C_STR(c.model_config.moonshine.uncached_decoder); - SHERPA_ONNX_DELETE_C_STR(c.model_config.moonshine.cached_decoder); - - SHERPA_ONNX_DELETE_C_STR(c.model_config.tokens); - SHERPA_ONNX_DELETE_C_STR(c.model_config.provider); - SHERPA_ONNX_DELETE_C_STR(c.model_config.model_type); - SHERPA_ONNX_DELETE_C_STR(c.model_config.modeling_unit); - SHERPA_ONNX_DELETE_C_STR(c.model_config.bpe_vocab); - SHERPA_ONNX_DELETE_C_STR(c.model_config.telespeech_ctc); - - SHERPA_ONNX_DELETE_C_STR(c.lm_config.model); - - SHERPA_ONNX_DELETE_C_STR(c.decoding_method); - SHERPA_ONNX_DELETE_C_STR(c.hotwords_file); - SHERPA_ONNX_DELETE_C_STR(c.rule_fsts); - SHERPA_ONNX_DELETE_C_STR(c.rule_fars); - - if (!recognizer) { - Napi::TypeError::New(env, "Please check your config!") - .ThrowAsJavaScriptException(); - - return {}; - } - - return Napi::External::New( - env, const_cast(recognizer), - [](Napi::Env env, SherpaOnnxOfflineRecognizer *recognizer) { - SherpaOnnxDestroyOfflineRecognizer(recognizer); - }); -} - -static Napi::External CreateOfflineStreamWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New( - env, - "You should pass an offline recognizer pointer as the only argument") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxOfflineRecognizer *recognizer = - info[0].As>().Data(); - - const SherpaOnnxOfflineStream *stream = - SherpaOnnxCreateOfflineStream(recognizer); - - return Napi::External::New( - env, const_cast(stream), - [](Napi::Env env, SherpaOnnxOfflineStream *stream) { - SherpaOnnxDestroyOfflineStream(stream); - }); -} - -static void AcceptWaveformOfflineWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, "Argument 0 should be an online stream pointer.") - .ThrowAsJavaScriptException(); - - return; - } - - SherpaOnnxOfflineStream *stream = - info[0].As>().Data(); - - if (!info[1].IsObject()) { - Napi::TypeError::New(env, "Argument 1 should be an object") - .ThrowAsJavaScriptException(); - - return; - } - - Napi::Object obj = info[1].As(); - - if (!obj.Has("samples")) { - Napi::TypeError::New(env, "The argument object should have a field samples") - .ThrowAsJavaScriptException(); - - return; - } - - if (!obj.Get("samples").IsTypedArray()) { - Napi::TypeError::New(env, "The object['samples'] should be a typed array") - .ThrowAsJavaScriptException(); - - return; - } - - if (!obj.Has("sampleRate")) { - Napi::TypeError::New(env, - "The argument object should have a field sampleRate") - .ThrowAsJavaScriptException(); - - return; - } - - if (!obj.Get("sampleRate").IsNumber()) { - Napi::TypeError::New(env, "The object['samples'] should be a number") - .ThrowAsJavaScriptException(); - - return; - } - - Napi::Float32Array samples = obj.Get("samples").As(); - int32_t sample_rate = obj.Get("sampleRate").As().Int32Value(); - - SherpaOnnxAcceptWaveformOffline(stream, sample_rate, samples.Data(), - samples.ElementLength()); -} - -static void DecodeOfflineStreamWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, - "Argument 0 should be an offline recognizer pointer.") - .ThrowAsJavaScriptException(); - - return; - } - - if (!info[1].IsExternal()) { - Napi::TypeError::New(env, "Argument 1 should be an offline stream pointer.") - .ThrowAsJavaScriptException(); - - return; - } - - SherpaOnnxOfflineRecognizer *recognizer = - info[0].As>().Data(); - - SherpaOnnxOfflineStream *stream = - info[1].As>().Data(); - - SherpaOnnxDecodeOfflineStream(recognizer, stream); -} - -static Napi::String GetOfflineStreamResultAsJsonWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, "Argument 0 should be an online stream pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxOfflineStream *stream = - info[0].As>().Data(); - - const char *json = SherpaOnnxGetOfflineStreamResultAsJson(stream); - Napi::String s = Napi::String::New(env, json); - - SherpaOnnxDestroyOfflineStreamResultJson(json); - - return s; -} - -void InitNonStreamingAsr(Napi::Env env, Napi::Object exports) { - exports.Set(Napi::String::New(env, "createOfflineRecognizer"), - Napi::Function::New(env, CreateOfflineRecognizerWrapper)); - - exports.Set(Napi::String::New(env, "createOfflineStream"), - Napi::Function::New(env, CreateOfflineStreamWrapper)); - - exports.Set(Napi::String::New(env, "acceptWaveformOffline"), - Napi::Function::New(env, AcceptWaveformOfflineWrapper)); - - exports.Set(Napi::String::New(env, "decodeOfflineStream"), - Napi::Function::New(env, DecodeOfflineStreamWrapper)); - - exports.Set(Napi::String::New(env, "getOfflineStreamResultAsJson"), - Napi::Function::New(env, GetOfflineStreamResultAsJsonWrapper)); -} diff --git a/scripts/node-addon-api/src/non-streaming-asr.cc b/scripts/node-addon-api/src/non-streaming-asr.cc new file mode 120000 index 000000000..12f89f891 --- /dev/null +++ b/scripts/node-addon-api/src/non-streaming-asr.cc @@ -0,0 +1 @@ +../../../harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/non-streaming-asr.cc \ No newline at end of file diff --git a/scripts/node-addon-api/src/non-streaming-speaker-diarization.cc b/scripts/node-addon-api/src/non-streaming-speaker-diarization.cc deleted file mode 100644 index a35f7924a..000000000 --- a/scripts/node-addon-api/src/non-streaming-speaker-diarization.cc +++ /dev/null @@ -1,310 +0,0 @@ -// scripts/node-addon-api/src/non-streaming-speaker-diarization.cc -// -// Copyright (c) 2024 Xiaomi Corporation - -#include -#include - -#include "macros.h" // NOLINT -#include "napi.h" // NOLINT -#include "sherpa-onnx/c-api/c-api.h" - -static SherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig -GetOfflineSpeakerSegmentationPyannoteModelConfig(Napi::Object obj) { - SherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig c; - memset(&c, 0, sizeof(c)); - - if (!obj.Has("pyannote") || !obj.Get("pyannote").IsObject()) { - return c; - } - - Napi::Object o = obj.Get("pyannote").As(); - SHERPA_ONNX_ASSIGN_ATTR_STR(model, model); - - return c; -} - -static SherpaOnnxOfflineSpeakerSegmentationModelConfig -GetOfflineSpeakerSegmentationModelConfig(Napi::Object obj) { - SherpaOnnxOfflineSpeakerSegmentationModelConfig c; - memset(&c, 0, sizeof(c)); - - if (!obj.Has("segmentation") || !obj.Get("segmentation").IsObject()) { - return c; - } - - Napi::Object o = obj.Get("segmentation").As(); - - c.pyannote = GetOfflineSpeakerSegmentationPyannoteModelConfig(o); - - SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads); - - if (o.Has("debug") && - (o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) { - if (o.Get("debug").IsBoolean()) { - c.debug = o.Get("debug").As().Value(); - } else { - c.debug = o.Get("debug").As().Int32Value(); - } - } - - SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider); - - return c; -} - -static SherpaOnnxSpeakerEmbeddingExtractorConfig -GetSpeakerEmbeddingExtractorConfig(Napi::Object obj) { - SherpaOnnxSpeakerEmbeddingExtractorConfig c; - memset(&c, 0, sizeof(c)); - - if (!obj.Has("embedding") || !obj.Get("embedding").IsObject()) { - return c; - } - - Napi::Object o = obj.Get("embedding").As(); - - SHERPA_ONNX_ASSIGN_ATTR_STR(model, model); - SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads); - - if (o.Has("debug") && - (o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) { - if (o.Get("debug").IsBoolean()) { - c.debug = o.Get("debug").As().Value(); - } else { - c.debug = o.Get("debug").As().Int32Value(); - } - } - - SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider); - - return c; -} - -static SherpaOnnxFastClusteringConfig GetFastClusteringConfig( - Napi::Object obj) { - SherpaOnnxFastClusteringConfig c; - memset(&c, 0, sizeof(c)); - - if (!obj.Has("clustering") || !obj.Get("clustering").IsObject()) { - return c; - } - - Napi::Object o = obj.Get("clustering").As(); - - SHERPA_ONNX_ASSIGN_ATTR_INT32(num_clusters, numClusters); - SHERPA_ONNX_ASSIGN_ATTR_FLOAT(threshold, threshold); - - return c; -} - -static Napi::External -CreateOfflineSpeakerDiarizationWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsObject()) { - Napi::TypeError::New(env, "Expect an object as the argument") - .ThrowAsJavaScriptException(); - - return {}; - } - - Napi::Object o = info[0].As(); - - SherpaOnnxOfflineSpeakerDiarizationConfig c; - memset(&c, 0, sizeof(c)); - - c.segmentation = GetOfflineSpeakerSegmentationModelConfig(o); - c.embedding = GetSpeakerEmbeddingExtractorConfig(o); - c.clustering = GetFastClusteringConfig(o); - - SHERPA_ONNX_ASSIGN_ATTR_FLOAT(min_duration_on, minDurationOn); - SHERPA_ONNX_ASSIGN_ATTR_FLOAT(min_duration_off, minDurationOff); - - const SherpaOnnxOfflineSpeakerDiarization *sd = - SherpaOnnxCreateOfflineSpeakerDiarization(&c); - - if (c.segmentation.pyannote.model) { - delete[] c.segmentation.pyannote.model; - } - - if (c.segmentation.provider) { - delete[] c.segmentation.provider; - } - - if (c.embedding.model) { - delete[] c.embedding.model; - } - - if (c.embedding.provider) { - delete[] c.embedding.provider; - } - - if (!sd) { - Napi::TypeError::New(env, "Please check your config!") - .ThrowAsJavaScriptException(); - - return {}; - } - - return Napi::External::New( - env, const_cast(sd), - [](Napi::Env env, SherpaOnnxOfflineSpeakerDiarization *sd) { - SherpaOnnxDestroyOfflineSpeakerDiarization(sd); - }); -} - -static Napi::Number OfflineSpeakerDiarizationGetSampleRateWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New( - env, "Argument 0 should be an offline speaker diarization pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - const SherpaOnnxOfflineSpeakerDiarization *sd = - info[0].As>().Data(); - - int32_t sample_rate = SherpaOnnxOfflineSpeakerDiarizationGetSampleRate(sd); - - return Napi::Number::New(env, sample_rate); -} - -static Napi::Array OfflineSpeakerDiarizationProcessWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New( - env, "Argument 0 should be an offline speaker diarization pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - const SherpaOnnxOfflineSpeakerDiarization *sd = - info[0].As>().Data(); - - if (!info[1].IsTypedArray()) { - Napi::TypeError::New(env, "Argument 1 should be a typed array") - .ThrowAsJavaScriptException(); - - return {}; - } - - Napi::Float32Array samples = info[1].As(); - - const SherpaOnnxOfflineSpeakerDiarizationResult *r = - SherpaOnnxOfflineSpeakerDiarizationProcess(sd, samples.Data(), - samples.ElementLength()); - - int32_t num_segments = - SherpaOnnxOfflineSpeakerDiarizationResultGetNumSegments(r); - - const SherpaOnnxOfflineSpeakerDiarizationSegment *segments = - SherpaOnnxOfflineSpeakerDiarizationResultSortByStartTime(r); - - Napi::Array ans = Napi::Array::New(env, num_segments); - - for (int32_t i = 0; i != num_segments; ++i) { - Napi::Object obj = Napi::Object::New(env); - - obj.Set(Napi::String::New(env, "start"), segments[i].start); - obj.Set(Napi::String::New(env, "end"), segments[i].end); - obj.Set(Napi::String::New(env, "speaker"), segments[i].speaker); - - ans.Set(i, obj); - } - - SherpaOnnxOfflineSpeakerDiarizationDestroySegment(segments); - SherpaOnnxOfflineSpeakerDiarizationDestroyResult(r); - - return ans; -} - -static void OfflineSpeakerDiarizationSetConfigWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New( - env, "Argument 0 should be an offline speaker diarization pointer.") - .ThrowAsJavaScriptException(); - - return; - } - - const SherpaOnnxOfflineSpeakerDiarization *sd = - info[0].As>().Data(); - - if (!info[1].IsObject()) { - Napi::TypeError::New(env, "Expect an object as the argument") - .ThrowAsJavaScriptException(); - - return; - } - - Napi::Object o = info[0].As(); - - SherpaOnnxOfflineSpeakerDiarizationConfig c; - memset(&c, 0, sizeof(c)); - - c.clustering = GetFastClusteringConfig(o); - SherpaOnnxOfflineSpeakerDiarizationSetConfig(sd, &c); -} - -void InitNonStreamingSpeakerDiarization(Napi::Env env, Napi::Object exports) { - exports.Set(Napi::String::New(env, "createOfflineSpeakerDiarization"), - Napi::Function::New(env, CreateOfflineSpeakerDiarizationWrapper)); - - exports.Set( - Napi::String::New(env, "getOfflineSpeakerDiarizationSampleRate"), - Napi::Function::New(env, OfflineSpeakerDiarizationGetSampleRateWrapper)); - - exports.Set( - Napi::String::New(env, "offlineSpeakerDiarizationProcess"), - Napi::Function::New(env, OfflineSpeakerDiarizationProcessWrapper)); - - exports.Set( - Napi::String::New(env, "offlineSpeakerDiarizationSetConfig"), - Napi::Function::New(env, OfflineSpeakerDiarizationSetConfigWrapper)); -} diff --git a/scripts/node-addon-api/src/non-streaming-speaker-diarization.cc b/scripts/node-addon-api/src/non-streaming-speaker-diarization.cc new file mode 120000 index 000000000..5c325a885 --- /dev/null +++ b/scripts/node-addon-api/src/non-streaming-speaker-diarization.cc @@ -0,0 +1 @@ +../../../harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/non-streaming-speaker-diarization.cc \ No newline at end of file diff --git a/scripts/node-addon-api/src/non-streaming-tts.cc b/scripts/node-addon-api/src/non-streaming-tts.cc deleted file mode 100644 index 70d97cddb..000000000 --- a/scripts/node-addon-api/src/non-streaming-tts.cc +++ /dev/null @@ -1,329 +0,0 @@ -// scripts/node-addon-api/src/non-streaming-tts.cc -// -// Copyright (c) 2024 Xiaomi Corporation - -#include -#include - -#include "macros.h" // NOLINT -#include "napi.h" // NOLINT -#include "sherpa-onnx/c-api/c-api.h" - -static SherpaOnnxOfflineTtsVitsModelConfig GetOfflineTtsVitsModelConfig( - Napi::Object obj) { - SherpaOnnxOfflineTtsVitsModelConfig c; - memset(&c, 0, sizeof(c)); - - if (!obj.Has("vits") || !obj.Get("vits").IsObject()) { - return c; - } - - Napi::Object o = obj.Get("vits").As(); - SHERPA_ONNX_ASSIGN_ATTR_STR(model, model); - SHERPA_ONNX_ASSIGN_ATTR_STR(lexicon, lexicon); - SHERPA_ONNX_ASSIGN_ATTR_STR(tokens, tokens); - SHERPA_ONNX_ASSIGN_ATTR_STR(data_dir, dataDir); - SHERPA_ONNX_ASSIGN_ATTR_FLOAT(noise_scale, noiseScale); - SHERPA_ONNX_ASSIGN_ATTR_FLOAT(noise_scale_w, noiseScaleW); - SHERPA_ONNX_ASSIGN_ATTR_FLOAT(length_scale, lengthScale); - SHERPA_ONNX_ASSIGN_ATTR_STR(dict_dir, dictDir); - - return c; -} - -static SherpaOnnxOfflineTtsModelConfig GetOfflineTtsModelConfig( - Napi::Object obj) { - SherpaOnnxOfflineTtsModelConfig c; - memset(&c, 0, sizeof(c)); - - if (!obj.Has("model") || !obj.Get("model").IsObject()) { - return c; - } - - Napi::Object o = obj.Get("model").As(); - - c.vits = GetOfflineTtsVitsModelConfig(o); - - SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads); - - if (o.Has("debug") && - (o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) { - if (o.Get("debug").IsBoolean()) { - c.debug = o.Get("debug").As().Value(); - } else { - c.debug = o.Get("debug").As().Int32Value(); - } - } - - SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider); - - return c; -} - -static Napi::External CreateOfflineTtsWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsObject()) { - Napi::TypeError::New(env, "Expect an object as the argument") - .ThrowAsJavaScriptException(); - - return {}; - } - - Napi::Object o = info[0].As(); - - SherpaOnnxOfflineTtsConfig c; - memset(&c, 0, sizeof(c)); - - c.model = GetOfflineTtsModelConfig(o); - - SHERPA_ONNX_ASSIGN_ATTR_STR(rule_fsts, ruleFsts); - SHERPA_ONNX_ASSIGN_ATTR_INT32(max_num_sentences, maxNumSentences); - SHERPA_ONNX_ASSIGN_ATTR_STR(rule_fars, ruleFars); - - SherpaOnnxOfflineTts *tts = SherpaOnnxCreateOfflineTts(&c); - - if (c.model.vits.model) { - delete[] c.model.vits.model; - } - - if (c.model.vits.lexicon) { - delete[] c.model.vits.lexicon; - } - - if (c.model.vits.tokens) { - delete[] c.model.vits.tokens; - } - - if (c.model.vits.data_dir) { - delete[] c.model.vits.data_dir; - } - - if (c.model.vits.dict_dir) { - delete[] c.model.vits.dict_dir; - } - - if (c.model.provider) { - delete[] c.model.provider; - } - - if (c.rule_fsts) { - delete[] c.rule_fsts; - } - - if (c.rule_fars) { - delete[] c.rule_fars; - } - - if (!tts) { - Napi::TypeError::New(env, "Please check your config!") - .ThrowAsJavaScriptException(); - - return {}; - } - - return Napi::External::New( - env, tts, [](Napi::Env env, SherpaOnnxOfflineTts *tts) { - SherpaOnnxDestroyOfflineTts(tts); - }); -} - -static Napi::Number OfflineTtsSampleRateWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, "Argument 0 should be an offline tts pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxOfflineTts *tts = - info[0].As>().Data(); - - int32_t sample_rate = SherpaOnnxOfflineTtsSampleRate(tts); - - return Napi::Number::New(env, sample_rate); -} - -static Napi::Number OfflineTtsNumSpeakersWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, "Argument 0 should be an offline tts pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxOfflineTts *tts = - info[0].As>().Data(); - - int32_t num_speakers = SherpaOnnxOfflineTtsNumSpeakers(tts); - - return Napi::Number::New(env, num_speakers); -} - -static Napi::Object OfflineTtsGenerateWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, "Argument 0 should be an offline tts pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxOfflineTts *tts = - info[0].As>().Data(); - - if (!info[1].IsObject()) { - Napi::TypeError::New(env, "Argument 1 should be an object") - .ThrowAsJavaScriptException(); - - return {}; - } - - Napi::Object obj = info[1].As(); - - if (!obj.Has("text")) { - Napi::TypeError::New(env, "The argument object should have a field text") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!obj.Get("text").IsString()) { - Napi::TypeError::New(env, "The object['text'] should be a string") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!obj.Has("sid")) { - Napi::TypeError::New(env, "The argument object should have a field sid") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!obj.Get("sid").IsNumber()) { - Napi::TypeError::New(env, "The object['sid'] should be a number") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!obj.Has("speed")) { - Napi::TypeError::New(env, "The argument object should have a field speed") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!obj.Get("speed").IsNumber()) { - Napi::TypeError::New(env, "The object['speed'] should be a number") - .ThrowAsJavaScriptException(); - - return {}; - } - - bool enable_external_buffer = true; - if (obj.Has("enableExternalBuffer") && - obj.Get("enableExternalBuffer").IsBoolean()) { - enable_external_buffer = - obj.Get("enableExternalBuffer").As().Value(); - } - - Napi::String _text = obj.Get("text").As(); - std::string text = _text.Utf8Value(); - int32_t sid = obj.Get("sid").As().Int32Value(); - float speed = obj.Get("speed").As().FloatValue(); - - const SherpaOnnxGeneratedAudio *audio = - SherpaOnnxOfflineTtsGenerate(tts, text.c_str(), sid, speed); - - if (enable_external_buffer) { - Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New( - env, const_cast(audio->samples), sizeof(float) * audio->n, - [](Napi::Env /*env*/, void * /*data*/, - const SherpaOnnxGeneratedAudio *hint) { - SherpaOnnxDestroyOfflineTtsGeneratedAudio(hint); - }, - audio); - Napi::Float32Array float32Array = - Napi::Float32Array::New(env, audio->n, arrayBuffer, 0); - - Napi::Object ans = Napi::Object::New(env); - ans.Set(Napi::String::New(env, "samples"), float32Array); - ans.Set(Napi::String::New(env, "sampleRate"), audio->sample_rate); - return ans; - } else { - // don't use external buffer - Napi::ArrayBuffer arrayBuffer = - Napi::ArrayBuffer::New(env, sizeof(float) * audio->n); - - Napi::Float32Array float32Array = - Napi::Float32Array::New(env, audio->n, arrayBuffer, 0); - - std::copy(audio->samples, audio->samples + audio->n, float32Array.Data()); - - Napi::Object ans = Napi::Object::New(env); - ans.Set(Napi::String::New(env, "samples"), float32Array); - ans.Set(Napi::String::New(env, "sampleRate"), audio->sample_rate); - SherpaOnnxDestroyOfflineTtsGeneratedAudio(audio); - return ans; - } -} - -void InitNonStreamingTts(Napi::Env env, Napi::Object exports) { - exports.Set(Napi::String::New(env, "createOfflineTts"), - Napi::Function::New(env, CreateOfflineTtsWrapper)); - - exports.Set(Napi::String::New(env, "getOfflineTtsSampleRate"), - Napi::Function::New(env, OfflineTtsSampleRateWrapper)); - - exports.Set(Napi::String::New(env, "getOfflineTtsNumSpeakers"), - Napi::Function::New(env, OfflineTtsNumSpeakersWrapper)); - - exports.Set(Napi::String::New(env, "offlineTtsGenerate"), - Napi::Function::New(env, OfflineTtsGenerateWrapper)); -} diff --git a/scripts/node-addon-api/src/non-streaming-tts.cc b/scripts/node-addon-api/src/non-streaming-tts.cc new file mode 120000 index 000000000..fd4eb93f2 --- /dev/null +++ b/scripts/node-addon-api/src/non-streaming-tts.cc @@ -0,0 +1 @@ +../../../harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/non-streaming-tts.cc \ No newline at end of file diff --git a/scripts/node-addon-api/src/punctuation.cc b/scripts/node-addon-api/src/punctuation.cc deleted file mode 100644 index df079b96d..000000000 --- a/scripts/node-addon-api/src/punctuation.cc +++ /dev/null @@ -1,135 +0,0 @@ -// scripts/node-addon-api/src/punctuation.cc -// -// Copyright (c) 2024 Xiaomi Corporation -#include - -#include "macros.h" // NOLINT -#include "napi.h" // NOLINT -#include "sherpa-onnx/c-api/c-api.h" - -static SherpaOnnxOfflinePunctuationModelConfig GetOfflinePunctuationModelConfig( - Napi::Object obj) { - SherpaOnnxOfflinePunctuationModelConfig c; - memset(&c, 0, sizeof(c)); - - if (!obj.Has("model") || !obj.Get("model").IsObject()) { - return c; - } - - Napi::Object o = obj.Get("model").As(); - - SHERPA_ONNX_ASSIGN_ATTR_STR(ct_transformer, ctTransformer); - - SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads); - - if (o.Has("debug") && - (o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) { - if (o.Get("debug").IsBoolean()) { - c.debug = o.Get("debug").As().Value(); - } else { - c.debug = o.Get("debug").As().Int32Value(); - } - } - SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider); - - return c; -} - -static Napi::External -CreateOfflinePunctuationWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsObject()) { - Napi::TypeError::New(env, "You should pass an object as the only argument.") - .ThrowAsJavaScriptException(); - - return {}; - } - - Napi::Object o = info[0].As(); - - SherpaOnnxOfflinePunctuationConfig c; - memset(&c, 0, sizeof(c)); - c.model = GetOfflinePunctuationModelConfig(o); - - const SherpaOnnxOfflinePunctuation *punct = - SherpaOnnxCreateOfflinePunctuation(&c); - - if (c.model.ct_transformer) { - delete[] c.model.ct_transformer; - } - - if (c.model.provider) { - delete[] c.model.provider; - } - - if (!punct) { - Napi::TypeError::New(env, "Please check your config!") - .ThrowAsJavaScriptException(); - - return {}; - } - - return Napi::External::New( - env, const_cast(punct), - [](Napi::Env env, SherpaOnnxOfflinePunctuation *punct) { - SherpaOnnxDestroyOfflinePunctuation(punct); - }); -} - -static Napi::String OfflinePunctuationAddPunctWraper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New( - env, - "You should pass an offline punctuation pointer as the first argument") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[1].IsString()) { - Napi::TypeError::New(env, "You should pass a string as the second argument") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxOfflinePunctuation *punct = - info[0].As>().Data(); - Napi::String js_text = info[1].As(); - std::string text = js_text.Utf8Value(); - - const char *punct_text = - SherpaOfflinePunctuationAddPunct(punct, text.c_str()); - - Napi::String ans = Napi::String::New(env, punct_text); - SherpaOfflinePunctuationFreeText(punct_text); - return ans; -} - -void InitPunctuation(Napi::Env env, Napi::Object exports) { - exports.Set(Napi::String::New(env, "createOfflinePunctuation"), - Napi::Function::New(env, CreateOfflinePunctuationWrapper)); - - exports.Set(Napi::String::New(env, "offlinePunctuationAddPunct"), - Napi::Function::New(env, OfflinePunctuationAddPunctWraper)); -} diff --git a/scripts/node-addon-api/src/punctuation.cc b/scripts/node-addon-api/src/punctuation.cc new file mode 120000 index 000000000..a0d6b08e1 --- /dev/null +++ b/scripts/node-addon-api/src/punctuation.cc @@ -0,0 +1 @@ +../../../harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/punctuation.cc \ No newline at end of file diff --git a/scripts/node-addon-api/src/sherpa-onnx-node-addon-api.cc b/scripts/node-addon-api/src/sherpa-onnx-node-addon-api.cc deleted file mode 100644 index 3f0affd79..000000000 --- a/scripts/node-addon-api/src/sherpa-onnx-node-addon-api.cc +++ /dev/null @@ -1,47 +0,0 @@ -// scripts/node-addon-api/src/sherpa-onnx-node-addon-api.cc -// -// Copyright (c) 2024 Xiaomi Corporation -#include "napi.h" // NOLINT - -void InitStreamingAsr(Napi::Env env, Napi::Object exports); - -void InitNonStreamingAsr(Napi::Env env, Napi::Object exports); - -void InitNonStreamingTts(Napi::Env env, Napi::Object exports); - -void InitVad(Napi::Env env, Napi::Object exports); - -void InitWaveReader(Napi::Env env, Napi::Object exports); - -void InitWaveWriter(Napi::Env env, Napi::Object exports); - -void InitSpokenLanguageID(Napi::Env env, Napi::Object exports); - -void InitSpeakerID(Napi::Env env, Napi::Object exports); - -void InitAudioTagging(Napi::Env env, Napi::Object exports); - -void InitPunctuation(Napi::Env env, Napi::Object exports); - -void InitKeywordSpotting(Napi::Env env, Napi::Object exports); - -void InitNonStreamingSpeakerDiarization(Napi::Env env, Napi::Object exports); - -Napi::Object Init(Napi::Env env, Napi::Object exports) { - InitStreamingAsr(env, exports); - InitNonStreamingAsr(env, exports); - InitNonStreamingTts(env, exports); - InitVad(env, exports); - InitWaveReader(env, exports); - InitWaveWriter(env, exports); - InitSpokenLanguageID(env, exports); - InitSpeakerID(env, exports); - InitAudioTagging(env, exports); - InitPunctuation(env, exports); - InitKeywordSpotting(env, exports); - InitNonStreamingSpeakerDiarization(env, exports); - - return exports; -} - -NODE_API_MODULE(addon, Init) diff --git a/scripts/node-addon-api/src/sherpa-onnx-node-addon-api.cc b/scripts/node-addon-api/src/sherpa-onnx-node-addon-api.cc new file mode 120000 index 000000000..9394c068a --- /dev/null +++ b/scripts/node-addon-api/src/sherpa-onnx-node-addon-api.cc @@ -0,0 +1 @@ +../../../harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/sherpa-onnx-node-addon-api.cc \ No newline at end of file diff --git a/scripts/node-addon-api/src/speaker-identification.cc b/scripts/node-addon-api/src/speaker-identification.cc deleted file mode 100644 index a08a6ed66..000000000 --- a/scripts/node-addon-api/src/speaker-identification.cc +++ /dev/null @@ -1,808 +0,0 @@ -// scripts/node-addon-api/src/speaker-identification.cc -// -// Copyright (c) 2024 Xiaomi Corporation -#include -#include - -#include "macros.h" // NOLINT -#include "napi.h" // NOLINT -#include "sherpa-onnx/c-api/c-api.h" - -static Napi::External -CreateSpeakerEmbeddingExtractorWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsObject()) { - Napi::TypeError::New(env, "You should pass an object as the only argument.") - .ThrowAsJavaScriptException(); - - return {}; - } - - Napi::Object o = info[0].As(); - - SherpaOnnxSpeakerEmbeddingExtractorConfig c; - memset(&c, 0, sizeof(c)); - - SHERPA_ONNX_ASSIGN_ATTR_STR(model, model); - SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads); - - if (o.Has("debug") && - (o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) { - if (o.Get("debug").IsBoolean()) { - c.debug = o.Get("debug").As().Value(); - } else { - c.debug = o.Get("debug").As().Int32Value(); - } - } - - SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider); - - const SherpaOnnxSpeakerEmbeddingExtractor *extractor = - SherpaOnnxCreateSpeakerEmbeddingExtractor(&c); - - if (c.model) { - delete[] c.model; - } - - if (c.provider) { - delete[] c.provider; - } - - if (!extractor) { - Napi::TypeError::New(env, "Please check your config!") - .ThrowAsJavaScriptException(); - - return {}; - } - - return Napi::External::New( - env, const_cast(extractor), - [](Napi::Env env, SherpaOnnxSpeakerEmbeddingExtractor *extractor) { - SherpaOnnxDestroySpeakerEmbeddingExtractor(extractor); - }); -} - -static Napi::Number SpeakerEmbeddingExtractorDimWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New( - env, "Argument 0 should be a speaker embedding extractor pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxSpeakerEmbeddingExtractor *extractor = - info[0].As>().Data(); - - int32_t dim = SherpaOnnxSpeakerEmbeddingExtractorDim(extractor); - - return Napi::Number::New(env, dim); -} - -static Napi::External -SpeakerEmbeddingExtractorCreateStreamWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, - "You should pass a speaker embedding extractor " - "pointer as the only argument") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxSpeakerEmbeddingExtractor *extractor = - info[0].As>().Data(); - - const SherpaOnnxOnlineStream *stream = - SherpaOnnxSpeakerEmbeddingExtractorCreateStream(extractor); - - return Napi::External::New( - env, const_cast(stream), - [](Napi::Env env, SherpaOnnxOnlineStream *stream) { - SherpaOnnxDestroyOnlineStream(stream); - }); -} - -static Napi::Boolean SpeakerEmbeddingExtractorIsReadyWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New( - env, "Argument 0 should be a speaker embedding extractor pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[1].IsExternal()) { - Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxSpeakerEmbeddingExtractor *extractor = - info[0].As>().Data(); - - SherpaOnnxOnlineStream *stream = - info[1].As>().Data(); - - int32_t is_ready = - SherpaOnnxSpeakerEmbeddingExtractorIsReady(extractor, stream); - - return Napi::Boolean::New(env, is_ready); -} - -static Napi::Float32Array SpeakerEmbeddingExtractorComputeEmbeddingWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 2 && info.Length() != 3) { - std::ostringstream os; - os << "Expect only 2 or 3 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New( - env, "Argument 0 should be a speaker embedding extractor pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[1].IsExternal()) { - Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - bool enable_external_buffer = true; - if (info.Length() == 3) { - if (info[2].IsBoolean()) { - enable_external_buffer = info[2].As().Value(); - } else { - Napi::TypeError::New(env, "Argument 2 should be a boolean.") - .ThrowAsJavaScriptException(); - } - } - - SherpaOnnxSpeakerEmbeddingExtractor *extractor = - info[0].As>().Data(); - - SherpaOnnxOnlineStream *stream = - info[1].As>().Data(); - - const float *v = - SherpaOnnxSpeakerEmbeddingExtractorComputeEmbedding(extractor, stream); - - int32_t dim = SherpaOnnxSpeakerEmbeddingExtractorDim(extractor); - - if (enable_external_buffer) { - Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New( - env, const_cast(v), sizeof(float) * dim, - [](Napi::Env /*env*/, void *data) { - SherpaOnnxSpeakerEmbeddingExtractorDestroyEmbedding( - reinterpret_cast(data)); - }); - - return Napi::Float32Array::New(env, dim, arrayBuffer, 0); - } else { - // don't use external buffer - Napi::ArrayBuffer arrayBuffer = - Napi::ArrayBuffer::New(env, sizeof(float) * dim); - - Napi::Float32Array float32Array = - Napi::Float32Array::New(env, dim, arrayBuffer, 0); - - std::copy(v, v + dim, float32Array.Data()); - - SherpaOnnxSpeakerEmbeddingExtractorDestroyEmbedding(v); - - return float32Array; - } -} - -static Napi::External -CreateSpeakerEmbeddingManagerWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsNumber()) { - Napi::TypeError::New(env, - "You should pass an integer as the only argument.") - .ThrowAsJavaScriptException(); - - return {}; - } - - int32_t dim = info[0].As().Int32Value(); - - const SherpaOnnxSpeakerEmbeddingManager *manager = - SherpaOnnxCreateSpeakerEmbeddingManager(dim); - - if (!manager) { - Napi::TypeError::New(env, "Please check your input dim!") - .ThrowAsJavaScriptException(); - - return {}; - } - - return Napi::External::New( - env, const_cast(manager), - [](Napi::Env env, SherpaOnnxSpeakerEmbeddingManager *manager) { - SherpaOnnxDestroySpeakerEmbeddingManager(manager); - }); -} - -static Napi::Boolean SpeakerEmbeddingManagerAddWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, - "You should pass a speaker embedding manager pointer " - "as the first argument.") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[1].IsObject()) { - Napi::TypeError::New(env, "Argument 1 should be an object") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxSpeakerEmbeddingManager *manager = - info[0].As>().Data(); - - Napi::Object obj = info[1].As(); - - if (!obj.Has("v")) { - Napi::TypeError::New(env, "The argument object should have a field v") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!obj.Get("v").IsTypedArray()) { - Napi::TypeError::New(env, "The object['v'] should be a typed array") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!obj.Has("name")) { - Napi::TypeError::New(env, "The argument object should have a field name") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!obj.Get("name").IsString()) { - Napi::TypeError::New(env, "The object['name'] should be a string") - .ThrowAsJavaScriptException(); - - return {}; - } - - Napi::Float32Array v = obj.Get("v").As(); - Napi::String js_name = obj.Get("name").As(); - std::string name = js_name.Utf8Value(); - - int32_t ok = - SherpaOnnxSpeakerEmbeddingManagerAdd(manager, name.c_str(), v.Data()); - return Napi::Boolean::New(env, ok); -} - -static Napi::Boolean SpeakerEmbeddingManagerAddListFlattenedWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, - "You should pass a speaker embedding manager pointer " - "as the first argument.") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[1].IsObject()) { - Napi::TypeError::New(env, "Argument 1 should be an object") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxSpeakerEmbeddingManager *manager = - info[0].As>().Data(); - - Napi::Object obj = info[1].As(); - - if (!obj.Has("vv")) { - Napi::TypeError::New(env, "The argument object should have a field vv") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!obj.Get("vv").IsTypedArray()) { - Napi::TypeError::New(env, "The object['vv'] should be a typed array") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!obj.Has("name")) { - Napi::TypeError::New(env, "The argument object should have a field name") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!obj.Get("name").IsString()) { - Napi::TypeError::New(env, "The object['name'] should be a string") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!obj.Has("n")) { - Napi::TypeError::New(env, "The argument object should have a field n") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!obj.Get("n").IsNumber()) { - Napi::TypeError::New(env, "The object['n'] should be an integer") - .ThrowAsJavaScriptException(); - - return {}; - } - - Napi::Float32Array v = obj.Get("vv").As(); - Napi::String js_name = obj.Get("name").As(); - int32_t n = obj.Get("n").As().Int32Value(); - - std::string name = js_name.Utf8Value(); - - int32_t ok = SherpaOnnxSpeakerEmbeddingManagerAddListFlattened( - manager, name.c_str(), v.Data(), n); - - return Napi::Boolean::New(env, ok); -} - -static Napi::Boolean SpeakerEmbeddingManagerRemoveWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, - "You should pass a speaker embedding manager pointer " - "as the first argument.") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[1].IsString()) { - Napi::TypeError::New(env, "Argument 1 should be string") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxSpeakerEmbeddingManager *manager = - info[0].As>().Data(); - - Napi::String js_name = info[1].As(); - std::string name = js_name.Utf8Value(); - - int32_t ok = SherpaOnnxSpeakerEmbeddingManagerRemove(manager, name.c_str()); - - return Napi::Boolean::New(env, ok); -} - -static Napi::String SpeakerEmbeddingManagerSearchWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, - "You should pass a speaker embedding manager pointer " - "as the first argument.") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[1].IsObject()) { - Napi::TypeError::New(env, "Argument 1 should be an object") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxSpeakerEmbeddingManager *manager = - info[0].As>().Data(); - - Napi::Object obj = info[1].As(); - - if (!obj.Has("v")) { - Napi::TypeError::New(env, "The argument object should have a field v") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!obj.Get("v").IsTypedArray()) { - Napi::TypeError::New(env, "The object['v'] should be a typed array") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!obj.Has("threshold")) { - Napi::TypeError::New(env, - "The argument object should have a field threshold") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!obj.Get("threshold").IsNumber()) { - Napi::TypeError::New(env, "The object['threshold'] should be a float") - .ThrowAsJavaScriptException(); - - return {}; - } - - Napi::Float32Array v = obj.Get("v").As(); - float threshold = obj.Get("threshold").As().FloatValue(); - - const char *name = - SherpaOnnxSpeakerEmbeddingManagerSearch(manager, v.Data(), threshold); - const char *p = name; - if (!p) { - p = ""; - } - - Napi::String js_name = Napi::String::New(env, p); - SherpaOnnxSpeakerEmbeddingManagerFreeSearch(name); - - return js_name; -} - -static Napi::Boolean SpeakerEmbeddingManagerVerifyWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, - "You should pass a speaker embedding manager pointer " - "as the first argument.") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[1].IsObject()) { - Napi::TypeError::New(env, "Argument 1 should be an object") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxSpeakerEmbeddingManager *manager = - info[0].As>().Data(); - - Napi::Object obj = info[1].As(); - - if (!obj.Has("v")) { - Napi::TypeError::New(env, "The argument object should have a field v") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!obj.Get("v").IsTypedArray()) { - Napi::TypeError::New(env, "The object['v'] should be a typed array") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!obj.Has("threshold")) { - Napi::TypeError::New(env, - "The argument object should have a field threshold") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!obj.Get("threshold").IsNumber()) { - Napi::TypeError::New(env, "The object['threshold'] should be a float") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!obj.Has("name")) { - Napi::TypeError::New(env, "The argument object should have a field name") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!obj.Get("name").IsString()) { - Napi::TypeError::New(env, "The object['name'] should be a string") - .ThrowAsJavaScriptException(); - - return {}; - } - - Napi::Float32Array v = obj.Get("v").As(); - float threshold = obj.Get("threshold").As().FloatValue(); - - Napi::String js_name = obj.Get("name").As(); - std::string name = js_name.Utf8Value(); - - int32_t found = SherpaOnnxSpeakerEmbeddingManagerVerify(manager, name.c_str(), - v.Data(), threshold); - - return Napi::Boolean::New(env, found); -} - -static Napi::Boolean SpeakerEmbeddingManagerContainsWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, - "You should pass a speaker embedding manager pointer " - "as the first argument.") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[1].IsString()) { - Napi::TypeError::New(env, "Argument 1 should be a string") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxSpeakerEmbeddingManager *manager = - info[0].As>().Data(); - - Napi::String js_name = info[1].As(); - std::string name = js_name.Utf8Value(); - - int32_t exists = - SherpaOnnxSpeakerEmbeddingManagerContains(manager, name.c_str()); - - return Napi::Boolean::New(env, exists); -} - -static Napi::Number SpeakerEmbeddingManagerNumSpeakersWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, - "You should pass a speaker embedding manager pointer " - "as the first argument.") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxSpeakerEmbeddingManager *manager = - info[0].As>().Data(); - - int32_t num_speakers = SherpaOnnxSpeakerEmbeddingManagerNumSpeakers(manager); - - return Napi::Number::New(env, num_speakers); -} - -static Napi::Array SpeakerEmbeddingManagerGetAllSpeakersWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, - "You should pass a speaker embedding manager pointer " - "as the first argument.") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxSpeakerEmbeddingManager *manager = - info[0].As>().Data(); - - int32_t num_speakers = SherpaOnnxSpeakerEmbeddingManagerNumSpeakers(manager); - if (num_speakers == 0) { - return {}; - } - - const char *const *all_speaker_names = - SherpaOnnxSpeakerEmbeddingManagerGetAllSpeakers(manager); - - Napi::Array ans = Napi::Array::New(env, num_speakers); - for (uint32_t i = 0; i != num_speakers; ++i) { - ans[i] = Napi::String::New(env, all_speaker_names[i]); - } - SherpaOnnxSpeakerEmbeddingManagerFreeAllSpeakers(all_speaker_names); - return ans; -} - -void InitSpeakerID(Napi::Env env, Napi::Object exports) { - exports.Set(Napi::String::New(env, "createSpeakerEmbeddingExtractor"), - Napi::Function::New(env, CreateSpeakerEmbeddingExtractorWrapper)); - - exports.Set(Napi::String::New(env, "speakerEmbeddingExtractorDim"), - Napi::Function::New(env, SpeakerEmbeddingExtractorDimWrapper)); - - exports.Set( - Napi::String::New(env, "speakerEmbeddingExtractorCreateStream"), - Napi::Function::New(env, SpeakerEmbeddingExtractorCreateStreamWrapper)); - - exports.Set( - Napi::String::New(env, "speakerEmbeddingExtractorIsReady"), - Napi::Function::New(env, SpeakerEmbeddingExtractorIsReadyWrapper)); - - exports.Set( - Napi::String::New(env, "speakerEmbeddingExtractorComputeEmbedding"), - Napi::Function::New(env, - SpeakerEmbeddingExtractorComputeEmbeddingWrapper)); - - exports.Set(Napi::String::New(env, "createSpeakerEmbeddingManager"), - Napi::Function::New(env, CreateSpeakerEmbeddingManagerWrapper)); - - exports.Set(Napi::String::New(env, "speakerEmbeddingManagerAdd"), - Napi::Function::New(env, SpeakerEmbeddingManagerAddWrapper)); - - exports.Set( - Napi::String::New(env, "speakerEmbeddingManagerAddListFlattened"), - Napi::Function::New(env, SpeakerEmbeddingManagerAddListFlattenedWrapper)); - - exports.Set(Napi::String::New(env, "speakerEmbeddingManagerRemove"), - Napi::Function::New(env, SpeakerEmbeddingManagerRemoveWrapper)); - - exports.Set(Napi::String::New(env, "speakerEmbeddingManagerSearch"), - Napi::Function::New(env, SpeakerEmbeddingManagerSearchWrapper)); - - exports.Set(Napi::String::New(env, "speakerEmbeddingManagerVerify"), - Napi::Function::New(env, SpeakerEmbeddingManagerVerifyWrapper)); - - exports.Set(Napi::String::New(env, "speakerEmbeddingManagerContains"), - Napi::Function::New(env, SpeakerEmbeddingManagerContainsWrapper)); - - exports.Set( - Napi::String::New(env, "speakerEmbeddingManagerNumSpeakers"), - Napi::Function::New(env, SpeakerEmbeddingManagerNumSpeakersWrapper)); - - exports.Set( - Napi::String::New(env, "speakerEmbeddingManagerGetAllSpeakers"), - Napi::Function::New(env, SpeakerEmbeddingManagerGetAllSpeakersWrapper)); -} diff --git a/scripts/node-addon-api/src/speaker-identification.cc b/scripts/node-addon-api/src/speaker-identification.cc new file mode 120000 index 000000000..f83455f6b --- /dev/null +++ b/scripts/node-addon-api/src/speaker-identification.cc @@ -0,0 +1 @@ +../../../harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/speaker-identification.cc \ No newline at end of file diff --git a/scripts/node-addon-api/src/spoken-language-identification.cc b/scripts/node-addon-api/src/spoken-language-identification.cc deleted file mode 100644 index 35ade6541..000000000 --- a/scripts/node-addon-api/src/spoken-language-identification.cc +++ /dev/null @@ -1,188 +0,0 @@ -// scripts/node-addon-api/src/spoken-language-identification.cc -// -// Copyright (c) 2024 Xiaomi Corporation - -#include - -#include "macros.h" // NOLINT -#include "napi.h" // NOLINT -#include "sherpa-onnx/c-api/c-api.h" - -static SherpaOnnxSpokenLanguageIdentificationWhisperConfig -GetSpokenLanguageIdentificationWhisperConfig(Napi::Object obj) { - SherpaOnnxSpokenLanguageIdentificationWhisperConfig c; - memset(&c, 0, sizeof(c)); - - if (!obj.Has("whisper") || !obj.Get("whisper").IsObject()) { - return c; - } - - Napi::Object o = obj.Get("whisper").As(); - - SHERPA_ONNX_ASSIGN_ATTR_STR(encoder, encoder); - SHERPA_ONNX_ASSIGN_ATTR_STR(decoder, decoder); - SHERPA_ONNX_ASSIGN_ATTR_INT32(tail_paddings, tailPaddings); - - return c; -} - -static Napi::External -CreateSpokenLanguageIdentificationWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsObject()) { - Napi::TypeError::New(env, "You should pass an object as the only argument.") - .ThrowAsJavaScriptException(); - - return {}; - } - - Napi::Object o = info[0].As(); - - SherpaOnnxSpokenLanguageIdentificationConfig c; - memset(&c, 0, sizeof(c)); - c.whisper = GetSpokenLanguageIdentificationWhisperConfig(o); - - SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads); - - if (o.Has("debug") && - (o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) { - if (o.Get("debug").IsBoolean()) { - c.debug = o.Get("debug").As().Value(); - } else { - c.debug = o.Get("debug").As().Int32Value(); - } - } - SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider); - - const SherpaOnnxSpokenLanguageIdentification *slid = - SherpaOnnxCreateSpokenLanguageIdentification(&c); - - if (c.whisper.encoder) { - delete[] c.whisper.encoder; - } - - if (c.whisper.decoder) { - delete[] c.whisper.decoder; - } - - if (c.provider) { - delete[] c.provider; - } - - if (!slid) { - Napi::TypeError::New(env, "Please check your config!") - .ThrowAsJavaScriptException(); - - return {}; - } - - return Napi::External::New( - env, const_cast(slid), - [](Napi::Env env, SherpaOnnxSpokenLanguageIdentification *slid) { - SherpaOnnxDestroySpokenLanguageIdentification(slid); - }); -} - -static Napi::External -SpokenLanguageIdentificationCreateOfflineStreamWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New( - env, - "You should pass an offline language ID pointer as the only argument") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxSpokenLanguageIdentification *slid = - info[0] - .As>() - .Data(); - - SherpaOnnxOfflineStream *stream = - SherpaOnnxSpokenLanguageIdentificationCreateOfflineStream(slid); - - return Napi::External::New( - env, stream, [](Napi::Env env, SherpaOnnxOfflineStream *stream) { - SherpaOnnxDestroyOfflineStream(stream); - }); -} - -static Napi::String SpokenLanguageIdentificationComputeWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New( - env, "Argument 0 should be an offline spoken language ID pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[1].IsExternal()) { - Napi::TypeError::New(env, "Argument 1 should be an offline stream pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxSpokenLanguageIdentification *slid = - info[0] - .As>() - .Data(); - - SherpaOnnxOfflineStream *stream = - info[1].As>().Data(); - - const SherpaOnnxSpokenLanguageIdentificationResult *r = - SherpaOnnxSpokenLanguageIdentificationCompute(slid, stream); - - std::string lang = r->lang; - SherpaOnnxDestroySpokenLanguageIdentificationResult(r); - - return Napi::String::New(env, lang); -} - -void InitSpokenLanguageID(Napi::Env env, Napi::Object exports) { - exports.Set( - Napi::String::New(env, "createSpokenLanguageIdentification"), - Napi::Function::New(env, CreateSpokenLanguageIdentificationWrapper)); - - exports.Set( - Napi::String::New(env, "createSpokenLanguageIdentificationOfflineStream"), - Napi::Function::New( - env, SpokenLanguageIdentificationCreateOfflineStreamWrapper)); - - exports.Set( - Napi::String::New(env, "spokenLanguageIdentificationCompute"), - Napi::Function::New(env, SpokenLanguageIdentificationComputeWrapper)); -} diff --git a/scripts/node-addon-api/src/spoken-language-identification.cc b/scripts/node-addon-api/src/spoken-language-identification.cc new file mode 120000 index 000000000..39c77df75 --- /dev/null +++ b/scripts/node-addon-api/src/spoken-language-identification.cc @@ -0,0 +1 @@ +../../../harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/spoken-language-identification.cc \ No newline at end of file diff --git a/scripts/node-addon-api/src/streaming-asr.cc b/scripts/node-addon-api/src/streaming-asr.cc deleted file mode 100644 index 4652be5f5..000000000 --- a/scripts/node-addon-api/src/streaming-asr.cc +++ /dev/null @@ -1,708 +0,0 @@ -// scripts/node-addon-api/src/streaming-asr.cc -// -// Copyright (c) 2024 Xiaomi Corporation -#include - -#include "macros.h" // NOLINT -#include "napi.h" // NOLINT -#include "sherpa-onnx/c-api/c-api.h" -/* -{ - 'featConfig': { - 'sampleRate': 16000, - 'featureDim': 80, - } -}; - */ -SherpaOnnxFeatureConfig GetFeatureConfig(Napi::Object obj) { - SherpaOnnxFeatureConfig c; - memset(&c, 0, sizeof(c)); - - if (!obj.Has("featConfig") || !obj.Get("featConfig").IsObject()) { - return c; - } - - Napi::Object o = obj.Get("featConfig").As(); - - SHERPA_ONNX_ASSIGN_ATTR_INT32(sample_rate, sampleRate); - SHERPA_ONNX_ASSIGN_ATTR_INT32(feature_dim, featureDim); - - return c; -} -/* -{ - 'transducer': { - 'encoder': './encoder.onnx', - 'decoder': './decoder.onnx', - 'joiner': './joiner.onnx', - } -} - */ - -static SherpaOnnxOnlineTransducerModelConfig GetOnlineTransducerModelConfig( - Napi::Object obj) { - SherpaOnnxOnlineTransducerModelConfig c; - memset(&c, 0, sizeof(c)); - - if (!obj.Has("transducer") || !obj.Get("transducer").IsObject()) { - return c; - } - - Napi::Object o = obj.Get("transducer").As(); - - SHERPA_ONNX_ASSIGN_ATTR_STR(encoder, encoder); - SHERPA_ONNX_ASSIGN_ATTR_STR(decoder, decoder); - SHERPA_ONNX_ASSIGN_ATTR_STR(joiner, joiner); - - return c; -} - -static SherpaOnnxOnlineZipformer2CtcModelConfig -GetOnlineZipformer2CtcModelConfig(Napi::Object obj) { - SherpaOnnxOnlineZipformer2CtcModelConfig c; - memset(&c, 0, sizeof(c)); - - if (!obj.Has("zipformer2Ctc") || !obj.Get("zipformer2Ctc").IsObject()) { - return c; - } - - Napi::Object o = obj.Get("zipformer2Ctc").As(); - - SHERPA_ONNX_ASSIGN_ATTR_STR(model, model); - - return c; -} - -static SherpaOnnxOnlineParaformerModelConfig GetOnlineParaformerModelConfig( - Napi::Object obj) { - SherpaOnnxOnlineParaformerModelConfig c; - memset(&c, 0, sizeof(c)); - - if (!obj.Has("paraformer") || !obj.Get("paraformer").IsObject()) { - return c; - } - - Napi::Object o = obj.Get("paraformer").As(); - - SHERPA_ONNX_ASSIGN_ATTR_STR(encoder, encoder); - SHERPA_ONNX_ASSIGN_ATTR_STR(decoder, decoder); - - return c; -} - -SherpaOnnxOnlineModelConfig GetOnlineModelConfig(Napi::Object obj) { - SherpaOnnxOnlineModelConfig c; - memset(&c, 0, sizeof(c)); - - if (!obj.Has("modelConfig") || !obj.Get("modelConfig").IsObject()) { - return c; - } - - Napi::Object o = obj.Get("modelConfig").As(); - - c.transducer = GetOnlineTransducerModelConfig(o); - c.paraformer = GetOnlineParaformerModelConfig(o); - c.zipformer2_ctc = GetOnlineZipformer2CtcModelConfig(o); - - SHERPA_ONNX_ASSIGN_ATTR_STR(tokens, tokens); - SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads); - SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider); - - if (o.Has("debug") && - (o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) { - if (o.Get("debug").IsBoolean()) { - c.debug = o.Get("debug").As().Value(); - } else { - c.debug = o.Get("debug").As().Int32Value(); - } - } - - SHERPA_ONNX_ASSIGN_ATTR_STR(model_type, modelType); - SHERPA_ONNX_ASSIGN_ATTR_STR(modeling_unit, modelingUnit); - SHERPA_ONNX_ASSIGN_ATTR_STR(bpe_vocab, bpeVocab); - SHERPA_ONNX_ASSIGN_ATTR_STR(tokens_buf, tokensBuf); - SHERPA_ONNX_ASSIGN_ATTR_INT32(tokens_buf_size, tokensBufSize); - - return c; -} - -static SherpaOnnxOnlineCtcFstDecoderConfig GetCtcFstDecoderConfig( - Napi::Object obj) { - SherpaOnnxOnlineCtcFstDecoderConfig c; - memset(&c, 0, sizeof(c)); - - if (!obj.Has("ctcFstDecoderConfig") || - !obj.Get("ctcFstDecoderConfig").IsObject()) { - return c; - } - - Napi::Object o = obj.Get("ctcFstDecoderConfig").As(); - - SHERPA_ONNX_ASSIGN_ATTR_STR(graph, graph); - SHERPA_ONNX_ASSIGN_ATTR_INT32(max_active, maxActive); - - return c; -} - -static Napi::External CreateOnlineRecognizerWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsObject()) { - Napi::TypeError::New(env, "Expect an object as the argument") - .ThrowAsJavaScriptException(); - - return {}; - } - - Napi::Object o = info[0].As(); - SherpaOnnxOnlineRecognizerConfig c; - memset(&c, 0, sizeof(c)); - c.feat_config = GetFeatureConfig(o); - c.model_config = GetOnlineModelConfig(o); - - SHERPA_ONNX_ASSIGN_ATTR_STR(decoding_method, decodingMethod); - SHERPA_ONNX_ASSIGN_ATTR_INT32(max_active_paths, maxActivePaths); - - // enableEndpoint can be either a boolean or an integer - if (o.Has("enableEndpoint") && (o.Get("enableEndpoint").IsNumber() || - o.Get("enableEndpoint").IsBoolean())) { - if (o.Get("enableEndpoint").IsNumber()) { - c.enable_endpoint = - o.Get("enableEndpoint").As().Int32Value(); - } else { - c.enable_endpoint = o.Get("enableEndpoint").As().Value(); - } - } - - SHERPA_ONNX_ASSIGN_ATTR_FLOAT(rule1_min_trailing_silence, - rule1MinTrailingSilence); - SHERPA_ONNX_ASSIGN_ATTR_FLOAT(rule2_min_trailing_silence, - rule2MinTrailingSilence); - SHERPA_ONNX_ASSIGN_ATTR_FLOAT(rule3_min_utterance_length, - rule3MinUtteranceLength); - SHERPA_ONNX_ASSIGN_ATTR_STR(hotwords_file, hotwordsFile); - SHERPA_ONNX_ASSIGN_ATTR_FLOAT(hotwords_score, hotwordsScore); - SHERPA_ONNX_ASSIGN_ATTR_STR(rule_fsts, ruleFsts); - SHERPA_ONNX_ASSIGN_ATTR_STR(rule_fars, ruleFars); - SHERPA_ONNX_ASSIGN_ATTR_FLOAT(blank_penalty, blankPenalty); - SHERPA_ONNX_ASSIGN_ATTR_STR(hotwords_buf, hotwordsBuf); - SHERPA_ONNX_ASSIGN_ATTR_INT32(hotwords_buf_size, hotwordsBufSize); - - c.ctc_fst_decoder_config = GetCtcFstDecoderConfig(o); - - const SherpaOnnxOnlineRecognizer *recognizer = - SherpaOnnxCreateOnlineRecognizer(&c); - - if (c.model_config.transducer.encoder) { - delete[] c.model_config.transducer.encoder; - } - - if (c.model_config.transducer.decoder) { - delete[] c.model_config.transducer.decoder; - } - - if (c.model_config.transducer.joiner) { - delete[] c.model_config.transducer.joiner; - } - - if (c.model_config.paraformer.encoder) { - delete[] c.model_config.paraformer.encoder; - } - - if (c.model_config.paraformer.decoder) { - delete[] c.model_config.paraformer.decoder; - } - - if (c.model_config.zipformer2_ctc.model) { - delete[] c.model_config.zipformer2_ctc.model; - } - - if (c.model_config.tokens) { - delete[] c.model_config.tokens; - } - - if (c.model_config.provider) { - delete[] c.model_config.provider; - } - - if (c.model_config.model_type) { - delete[] c.model_config.model_type; - } - - if (c.model_config.modeling_unit) { - delete[] c.model_config.modeling_unit; - } - - if (c.model_config.bpe_vocab) { - delete[] c.model_config.bpe_vocab; - } - - if (c.model_config.tokens_buf) { - delete[] c.model_config.tokens_buf; - } - - if (c.decoding_method) { - delete[] c.decoding_method; - } - - if (c.hotwords_file) { - delete[] c.hotwords_file; - } - - if (c.rule_fsts) { - delete[] c.rule_fsts; - } - - if (c.rule_fars) { - delete[] c.rule_fars; - } - - if (c.hotwords_buf) { - delete[] c.hotwords_buf; - } - - if (c.ctc_fst_decoder_config.graph) { - delete[] c.ctc_fst_decoder_config.graph; - } - - if (!recognizer) { - Napi::TypeError::New(env, "Please check your config!") - .ThrowAsJavaScriptException(); - - return {}; - } - - return Napi::External::New( - env, const_cast(recognizer), - [](Napi::Env env, SherpaOnnxOnlineRecognizer *recognizer) { - SherpaOnnxDestroyOnlineRecognizer(recognizer); - }); -} - -static Napi::External CreateOnlineStreamWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New( - env, - "You should pass an online recognizer pointer as the only argument") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxOnlineRecognizer *recognizer = - info[0].As>().Data(); - - const SherpaOnnxOnlineStream *stream = - SherpaOnnxCreateOnlineStream(recognizer); - - return Napi::External::New( - env, const_cast(stream), - [](Napi::Env env, SherpaOnnxOnlineStream *stream) { - SherpaOnnxDestroyOnlineStream(stream); - }); -} - -static void AcceptWaveformWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, "Argument 0 should be an online stream pointer.") - .ThrowAsJavaScriptException(); - - return; - } - - SherpaOnnxOnlineStream *stream = - info[0].As>().Data(); - - if (!info[1].IsObject()) { - Napi::TypeError::New(env, "Argument 1 should be an object") - .ThrowAsJavaScriptException(); - - return; - } - - Napi::Object obj = info[1].As(); - - if (!obj.Has("samples")) { - Napi::TypeError::New(env, "The argument object should have a field samples") - .ThrowAsJavaScriptException(); - - return; - } - - if (!obj.Get("samples").IsTypedArray()) { - Napi::TypeError::New(env, "The object['samples'] should be a typed array") - .ThrowAsJavaScriptException(); - - return; - } - - if (!obj.Has("sampleRate")) { - Napi::TypeError::New(env, - "The argument object should have a field sampleRate") - .ThrowAsJavaScriptException(); - - return; - } - - if (!obj.Get("sampleRate").IsNumber()) { - Napi::TypeError::New(env, "The object['samples'] should be a number") - .ThrowAsJavaScriptException(); - - return; - } - - Napi::Float32Array samples = obj.Get("samples").As(); - int32_t sample_rate = obj.Get("sampleRate").As().Int32Value(); - - SherpaOnnxOnlineStreamAcceptWaveform(stream, sample_rate, samples.Data(), - samples.ElementLength()); -} - -static Napi::Boolean IsOnlineStreamReadyWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, - "Argument 0 should be an online recognizer pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[1].IsExternal()) { - Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxOnlineRecognizer *recognizer = - info[0].As>().Data(); - - SherpaOnnxOnlineStream *stream = - info[1].As>().Data(); - - int32_t is_ready = SherpaOnnxIsOnlineStreamReady(recognizer, stream); - - return Napi::Boolean::New(env, is_ready); -} - -static void DecodeOnlineStreamWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, - "Argument 0 should be an online recognizer pointer.") - .ThrowAsJavaScriptException(); - - return; - } - - if (!info[1].IsExternal()) { - Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.") - .ThrowAsJavaScriptException(); - - return; - } - - SherpaOnnxOnlineRecognizer *recognizer = - info[0].As>().Data(); - - SherpaOnnxOnlineStream *stream = - info[1].As>().Data(); - - SherpaOnnxDecodeOnlineStream(recognizer, stream); -} - -static Napi::String GetOnlineStreamResultAsJsonWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, - "Argument 0 should be an online recognizer pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[1].IsExternal()) { - Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxOnlineRecognizer *recognizer = - info[0].As>().Data(); - - SherpaOnnxOnlineStream *stream = - info[1].As>().Data(); - - const char *json = SherpaOnnxGetOnlineStreamResultAsJson(recognizer, stream); - Napi::String s = Napi::String::New(env, json); - - SherpaOnnxDestroyOnlineStreamResultJson(json); - - return s; -} - -static void InputFinishedWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, "Argument 0 should be an online stream pointer.") - .ThrowAsJavaScriptException(); - - return; - } - - SherpaOnnxOnlineStream *stream = - info[0].As>().Data(); - - SherpaOnnxOnlineStreamInputFinished(stream); -} - -static void ResetOnlineStreamWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, - "Argument 0 should be an online recognizer pointer.") - .ThrowAsJavaScriptException(); - - return; - } - - if (!info[1].IsExternal()) { - Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.") - .ThrowAsJavaScriptException(); - - return; - } - - SherpaOnnxOnlineRecognizer *recognizer = - info[0].As>().Data(); - - SherpaOnnxOnlineStream *stream = - info[1].As>().Data(); - - SherpaOnnxOnlineStreamReset(recognizer, stream); -} - -static Napi::Boolean IsEndpointWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, - "Argument 0 should be an online recognizer pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[1].IsExternal()) { - Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxOnlineRecognizer *recognizer = - info[0].As>().Data(); - - SherpaOnnxOnlineStream *stream = - info[1].As>().Data(); - - int32_t is_endpoint = SherpaOnnxOnlineStreamIsEndpoint(recognizer, stream); - - return Napi::Boolean::New(env, is_endpoint); -} - -static Napi::External CreateDisplayWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsNumber()) { - Napi::TypeError::New(env, "Expect a number as the argument") - .ThrowAsJavaScriptException(); - - return {}; - } - int32_t max_word_per_line = info[0].As().Int32Value(); - - const SherpaOnnxDisplay *display = SherpaOnnxCreateDisplay(max_word_per_line); - - return Napi::External::New( - env, const_cast(display), - [](Napi::Env env, SherpaOnnxDisplay *display) { - SherpaOnnxDestroyDisplay(display); - }); -} - -static void PrintWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (info.Length() != 3) { - std::ostringstream os; - os << "Expect only 3 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, "Argument 0 should be an online stream pointer.") - .ThrowAsJavaScriptException(); - - return; - } - - if (!info[1].IsNumber()) { - Napi::TypeError::New(env, "Argument 1 should be a number.") - .ThrowAsJavaScriptException(); - - return; - } - - if (!info[2].IsString()) { - Napi::TypeError::New(env, "Argument 2 should be a string.") - .ThrowAsJavaScriptException(); - - return; - } - - SherpaOnnxDisplay *display = - info[0].As>().Data(); - - int32_t idx = info[1].As().Int32Value(); - - Napi::String text = info[2].As(); - std::string s = text.Utf8Value(); - SherpaOnnxPrint(display, idx, s.c_str()); -} - -void InitStreamingAsr(Napi::Env env, Napi::Object exports) { - exports.Set(Napi::String::New(env, "createOnlineRecognizer"), - Napi::Function::New(env, CreateOnlineRecognizerWrapper)); - - exports.Set(Napi::String::New(env, "createOnlineStream"), - Napi::Function::New(env, CreateOnlineStreamWrapper)); - - exports.Set(Napi::String::New(env, "acceptWaveformOnline"), - Napi::Function::New(env, AcceptWaveformWrapper)); - - exports.Set(Napi::String::New(env, "isOnlineStreamReady"), - Napi::Function::New(env, IsOnlineStreamReadyWrapper)); - - exports.Set(Napi::String::New(env, "decodeOnlineStream"), - Napi::Function::New(env, DecodeOnlineStreamWrapper)); - - exports.Set(Napi::String::New(env, "getOnlineStreamResultAsJson"), - Napi::Function::New(env, GetOnlineStreamResultAsJsonWrapper)); - - exports.Set(Napi::String::New(env, "inputFinished"), - Napi::Function::New(env, InputFinishedWrapper)); - - exports.Set(Napi::String::New(env, "reset"), - Napi::Function::New(env, ResetOnlineStreamWrapper)); - - exports.Set(Napi::String::New(env, "isEndpoint"), - Napi::Function::New(env, IsEndpointWrapper)); - - exports.Set(Napi::String::New(env, "createDisplay"), - Napi::Function::New(env, CreateDisplayWrapper)); - - exports.Set(Napi::String::New(env, "print"), - Napi::Function::New(env, PrintWrapper)); -} diff --git a/scripts/node-addon-api/src/streaming-asr.cc b/scripts/node-addon-api/src/streaming-asr.cc new file mode 120000 index 000000000..a3d3201fb --- /dev/null +++ b/scripts/node-addon-api/src/streaming-asr.cc @@ -0,0 +1 @@ +../../../harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/streaming-asr.cc \ No newline at end of file diff --git a/scripts/node-addon-api/src/vad.cc b/scripts/node-addon-api/src/vad.cc deleted file mode 100644 index eaed2aeea..000000000 --- a/scripts/node-addon-api/src/vad.cc +++ /dev/null @@ -1,668 +0,0 @@ -// scripts/node-addon-api/src/vad.cc -// -// Copyright (c) 2024 Xiaomi Corporation - -#include -#include - -#include "macros.h" // NOLINT -#include "napi.h" // NOLINT -#include "sherpa-onnx/c-api/c-api.h" - -static Napi::External CreateCircularBufferWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsNumber()) { - Napi::TypeError::New(env, "You should pass an integer as the argument.") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxCircularBuffer *buf = - SherpaOnnxCreateCircularBuffer(info[0].As().Int32Value()); - - return Napi::External::New( - env, buf, [](Napi::Env env, SherpaOnnxCircularBuffer *p) { - SherpaOnnxDestroyCircularBuffer(p); - }); -} - -static void CircularBufferPushWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, "Argument 0 should be an CircularBuffer pointer.") - .ThrowAsJavaScriptException(); - - return; - } - - SherpaOnnxCircularBuffer *buf = - info[0].As>().Data(); - - if (!info[1].IsTypedArray()) { - Napi::TypeError::New(env, "Argument 1 should be a Float32Array.") - .ThrowAsJavaScriptException(); - - return; - } - - Napi::Float32Array data = info[1].As(); - SherpaOnnxCircularBufferPush(buf, data.Data(), data.ElementLength()); -} - -// see https://github.com/nodejs/node-addon-api/blob/main/doc/typed_array.md -// https://github.com/nodejs/node-addon-examples/blob/main/src/2-js-to-native-conversion/typed_array_to_native/node-addon-api/typed_array_to_native.cc -static Napi::Float32Array CircularBufferGetWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (info.Length() != 3 && info.Length() != 4) { - std::ostringstream os; - os << "Expect only 3 or 4 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, "Argument 0 should be an CircularBuffer pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxCircularBuffer *buf = - info[0].As>().Data(); - - if (!info[1].IsNumber()) { - Napi::TypeError::New(env, "Argument 1 should be an integer (startIndex).") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[2].IsNumber()) { - Napi::TypeError::New(env, "Argument 2 should be an integer (n).") - .ThrowAsJavaScriptException(); - - return {}; - } - - bool enable_external_buffer = true; - if (info.Length() == 4) { - if (info[3].IsBoolean()) { - enable_external_buffer = info[3].As().Value(); - } else { - Napi::TypeError::New(env, "Argument 3 should be a boolean.") - .ThrowAsJavaScriptException(); - } - } - - int32_t start_index = info[1].As().Int32Value(); - int32_t n = info[2].As().Int32Value(); - - const float *data = SherpaOnnxCircularBufferGet(buf, start_index, n); - - if (enable_external_buffer) { - Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New( - env, const_cast(data), sizeof(float) * n, - [](Napi::Env /*env*/, void *p) { - SherpaOnnxCircularBufferFree(reinterpret_cast(p)); - }); - - Napi::Float32Array float32Array = - Napi::Float32Array::New(env, n, arrayBuffer, 0); - - return float32Array; - } else { - // don't use external buffer - Napi::ArrayBuffer arrayBuffer = - Napi::ArrayBuffer::New(env, sizeof(float) * n); - - Napi::Float32Array float32Array = - Napi::Float32Array::New(env, n, arrayBuffer, 0); - - std::copy(data, data + n, float32Array.Data()); - - SherpaOnnxCircularBufferFree(data); - - return float32Array; - } -} - -static void CircularBufferPopWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, "Argument 0 should be an CircularBuffer pointer.") - .ThrowAsJavaScriptException(); - - return; - } - - SherpaOnnxCircularBuffer *buf = - info[0].As>().Data(); - - if (!info[1].IsNumber()) { - Napi::TypeError::New(env, "Argument 1 should be an integer (n).") - .ThrowAsJavaScriptException(); - - return; - } - - int32_t n = info[1].As().Int32Value(); - - SherpaOnnxCircularBufferPop(buf, n); -} - -static Napi::Number CircularBufferSizeWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, "Argument 0 should be an CircularBuffer pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxCircularBuffer *buf = - info[0].As>().Data(); - - int32_t size = SherpaOnnxCircularBufferSize(buf); - - return Napi::Number::New(env, size); -} - -static Napi::Number CircularBufferHeadWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, "Argument 0 should be an CircularBuffer pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxCircularBuffer *buf = - info[0].As>().Data(); - - int32_t size = SherpaOnnxCircularBufferHead(buf); - - return Napi::Number::New(env, size); -} - -static void CircularBufferResetWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, "Argument 0 should be an CircularBuffer pointer.") - .ThrowAsJavaScriptException(); - - return; - } - - SherpaOnnxCircularBuffer *buf = - info[0].As>().Data(); - - SherpaOnnxCircularBufferReset(buf); -} - -static SherpaOnnxSileroVadModelConfig GetSileroVadConfig( - const Napi::Object &obj) { - SherpaOnnxSileroVadModelConfig c; - memset(&c, 0, sizeof(c)); - - if (!obj.Has("sileroVad") || !obj.Get("sileroVad").IsObject()) { - return c; - } - - Napi::Object o = obj.Get("sileroVad").As(); - SHERPA_ONNX_ASSIGN_ATTR_STR(model, model); - SHERPA_ONNX_ASSIGN_ATTR_FLOAT(threshold, threshold); - SHERPA_ONNX_ASSIGN_ATTR_FLOAT(min_silence_duration, minSilenceDuration); - SHERPA_ONNX_ASSIGN_ATTR_FLOAT(min_speech_duration, minSpeechDuration); - SHERPA_ONNX_ASSIGN_ATTR_INT32(window_size, windowSize); - SHERPA_ONNX_ASSIGN_ATTR_FLOAT(max_speech_duration, maxSpeechDuration); - - return c; -} - -static Napi::External -CreateVoiceActivityDetectorWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsObject()) { - Napi::TypeError::New(env, - "You should pass an object as the first argument.") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[1].IsNumber()) { - Napi::TypeError::New(env, - "You should pass an integer as the second argument.") - .ThrowAsJavaScriptException(); - - return {}; - } - - Napi::Object o = info[0].As(); - - SherpaOnnxVadModelConfig c; - memset(&c, 0, sizeof(c)); - c.silero_vad = GetSileroVadConfig(o); - - SHERPA_ONNX_ASSIGN_ATTR_INT32(sample_rate, sampleRate); - SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads); - SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider); - - if (o.Has("debug") && - (o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) { - if (o.Get("debug").IsBoolean()) { - c.debug = o.Get("debug").As().Value(); - } else { - c.debug = o.Get("debug").As().Int32Value(); - } - } - - float buffer_size_in_seconds = info[1].As().FloatValue(); - - SherpaOnnxVoiceActivityDetector *vad = - SherpaOnnxCreateVoiceActivityDetector(&c, buffer_size_in_seconds); - - if (c.silero_vad.model) { - delete[] c.silero_vad.model; - } - - if (c.provider) { - delete[] c.provider; - } - - return Napi::External::New( - env, vad, [](Napi::Env env, SherpaOnnxVoiceActivityDetector *p) { - SherpaOnnxDestroyVoiceActivityDetector(p); - }); -} - -static void VoiceActivityDetectorAcceptWaveformWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, "Argument 0 should be a VAD pointer.") - .ThrowAsJavaScriptException(); - - return; - } - - SherpaOnnxVoiceActivityDetector *vad = - info[0].As>().Data(); - - if (!info[1].IsTypedArray()) { - Napi::TypeError::New( - env, "Argument 1 should be a Float32Array containing samples") - .ThrowAsJavaScriptException(); - - return; - } - - Napi::Float32Array samples = info[1].As(); - - SherpaOnnxVoiceActivityDetectorAcceptWaveform(vad, samples.Data(), - samples.ElementLength()); -} - -static Napi::Boolean VoiceActivityDetectorEmptyWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, "Argument 0 should be a VAD pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxVoiceActivityDetector *vad = - info[0].As>().Data(); - - int32_t is_empty = SherpaOnnxVoiceActivityDetectorEmpty(vad); - - return Napi::Boolean::New(env, is_empty); -} - -static Napi::Boolean VoiceActivityDetectorDetectedWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, "Argument 0 should be a VAD pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - SherpaOnnxVoiceActivityDetector *vad = - info[0].As>().Data(); - - int32_t is_detected = SherpaOnnxVoiceActivityDetectorDetected(vad); - - return Napi::Boolean::New(env, is_detected); -} - -static void VoiceActivityDetectorPopWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, "Argument 0 should be a VAD pointer.") - .ThrowAsJavaScriptException(); - - return; - } - - SherpaOnnxVoiceActivityDetector *vad = - info[0].As>().Data(); - - SherpaOnnxVoiceActivityDetectorPop(vad); -} - -static void VoiceActivityDetectorClearWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, "Argument 0 should be a VAD pointer.") - .ThrowAsJavaScriptException(); - - return; - } - - SherpaOnnxVoiceActivityDetector *vad = - info[0].As>().Data(); - - SherpaOnnxVoiceActivityDetectorClear(vad); -} - -static Napi::Object VoiceActivityDetectorFrontWrapper( - const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (info.Length() != 1 && info.Length() != 2) { - std::ostringstream os; - os << "Expect only 1 or 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, "Argument 0 should be a VAD pointer.") - .ThrowAsJavaScriptException(); - - return {}; - } - - bool enable_external_buffer = true; - if (info.Length() == 2) { - if (info[1].IsBoolean()) { - enable_external_buffer = info[1].As().Value(); - } else { - Napi::TypeError::New(env, "Argument 1 should be a boolean.") - .ThrowAsJavaScriptException(); - } - } - - SherpaOnnxVoiceActivityDetector *vad = - info[0].As>().Data(); - - const SherpaOnnxSpeechSegment *segment = - SherpaOnnxVoiceActivityDetectorFront(vad); - - if (enable_external_buffer) { - Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New( - env, const_cast(segment->samples), sizeof(float) * segment->n, - [](Napi::Env /*env*/, void * /*data*/, - const SherpaOnnxSpeechSegment *hint) { - SherpaOnnxDestroySpeechSegment(hint); - }, - segment); - - Napi::Float32Array float32Array = - Napi::Float32Array::New(env, segment->n, arrayBuffer, 0); - - Napi::Object obj = Napi::Object::New(env); - obj.Set(Napi::String::New(env, "start"), segment->start); - obj.Set(Napi::String::New(env, "samples"), float32Array); - - return obj; - } else { - Napi::ArrayBuffer arrayBuffer = - Napi::ArrayBuffer::New(env, sizeof(float) * segment->n); - - Napi::Float32Array float32Array = - Napi::Float32Array::New(env, segment->n, arrayBuffer, 0); - - std::copy(segment->samples, segment->samples + segment->n, - float32Array.Data()); - - Napi::Object obj = Napi::Object::New(env); - obj.Set(Napi::String::New(env, "start"), segment->start); - obj.Set(Napi::String::New(env, "samples"), float32Array); - - SherpaOnnxDestroySpeechSegment(segment); - - return obj; - } -} - -static void VoiceActivityDetectorResetWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, "Argument 0 should be a VAD pointer.") - .ThrowAsJavaScriptException(); - - return; - } - - SherpaOnnxVoiceActivityDetector *vad = - info[0].As>().Data(); - - SherpaOnnxVoiceActivityDetectorReset(vad); -} - -static void VoiceActivityDetectorFlushWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (info.Length() != 1) { - std::ostringstream os; - os << "Expect only 1 argument. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return; - } - - if (!info[0].IsExternal()) { - Napi::TypeError::New(env, "Argument 0 should be a VAD pointer.") - .ThrowAsJavaScriptException(); - - return; - } - - SherpaOnnxVoiceActivityDetector *vad = - info[0].As>().Data(); - - SherpaOnnxVoiceActivityDetectorFlush(vad); -} - -void InitVad(Napi::Env env, Napi::Object exports) { - exports.Set(Napi::String::New(env, "createCircularBuffer"), - Napi::Function::New(env, CreateCircularBufferWrapper)); - - exports.Set(Napi::String::New(env, "circularBufferPush"), - Napi::Function::New(env, CircularBufferPushWrapper)); - - exports.Set(Napi::String::New(env, "circularBufferGet"), - Napi::Function::New(env, CircularBufferGetWrapper)); - - exports.Set(Napi::String::New(env, "circularBufferPop"), - Napi::Function::New(env, CircularBufferPopWrapper)); - - exports.Set(Napi::String::New(env, "circularBufferSize"), - Napi::Function::New(env, CircularBufferSizeWrapper)); - - exports.Set(Napi::String::New(env, "circularBufferHead"), - Napi::Function::New(env, CircularBufferHeadWrapper)); - - exports.Set(Napi::String::New(env, "circularBufferReset"), - Napi::Function::New(env, CircularBufferResetWrapper)); - - exports.Set(Napi::String::New(env, "createVoiceActivityDetector"), - Napi::Function::New(env, CreateVoiceActivityDetectorWrapper)); - - exports.Set( - Napi::String::New(env, "voiceActivityDetectorAcceptWaveform"), - Napi::Function::New(env, VoiceActivityDetectorAcceptWaveformWrapper)); - - exports.Set(Napi::String::New(env, "voiceActivityDetectorIsEmpty"), - Napi::Function::New(env, VoiceActivityDetectorEmptyWrapper)); - - exports.Set(Napi::String::New(env, "voiceActivityDetectorIsDetected"), - Napi::Function::New(env, VoiceActivityDetectorDetectedWrapper)); - - exports.Set(Napi::String::New(env, "voiceActivityDetectorPop"), - Napi::Function::New(env, VoiceActivityDetectorPopWrapper)); - - exports.Set(Napi::String::New(env, "voiceActivityDetectorClear"), - Napi::Function::New(env, VoiceActivityDetectorClearWrapper)); - - exports.Set(Napi::String::New(env, "voiceActivityDetectorFront"), - Napi::Function::New(env, VoiceActivityDetectorFrontWrapper)); - - exports.Set(Napi::String::New(env, "voiceActivityDetectorReset"), - Napi::Function::New(env, VoiceActivityDetectorResetWrapper)); - - exports.Set(Napi::String::New(env, "voiceActivityDetectorFlush"), - Napi::Function::New(env, VoiceActivityDetectorFlushWrapper)); -} diff --git a/scripts/node-addon-api/src/vad.cc b/scripts/node-addon-api/src/vad.cc new file mode 120000 index 000000000..5650274b8 --- /dev/null +++ b/scripts/node-addon-api/src/vad.cc @@ -0,0 +1 @@ +../../../harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/vad.cc \ No newline at end of file diff --git a/scripts/node-addon-api/src/wave-reader.cc b/scripts/node-addon-api/src/wave-reader.cc deleted file mode 100644 index 874f61bab..000000000 --- a/scripts/node-addon-api/src/wave-reader.cc +++ /dev/null @@ -1,91 +0,0 @@ -// scripts/node-addon-api/src/wave-reader.cc -// -// Copyright (c) 2024 Xiaomi Corporation - -#include -#include - -#include "napi.h" // NOLINT -#include "sherpa-onnx/c-api/c-api.h" - -static Napi::Object ReadWaveWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() > 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsString()) { - Napi::TypeError::New(env, "Argument 0 should be a string") - .ThrowAsJavaScriptException(); - - return {}; - } - - std::string filename = info[0].As().Utf8Value(); - - bool enable_external_buffer = true; - if (info.Length() == 2) { - if (info[1].IsBoolean()) { - enable_external_buffer = info[1].As().Value(); - } else { - Napi::TypeError::New(env, "Argument 1 should be a boolean") - .ThrowAsJavaScriptException(); - - return {}; - } - } - - const SherpaOnnxWave *wave = SherpaOnnxReadWave(filename.c_str()); - if (!wave) { - std::ostringstream os; - os << "Failed to read '" << filename << "'"; - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (enable_external_buffer) { - Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New( - env, const_cast(wave->samples), - sizeof(float) * wave->num_samples, - [](Napi::Env /*env*/, void * /*data*/, const SherpaOnnxWave *hint) { - SherpaOnnxFreeWave(hint); - }, - wave); - Napi::Float32Array float32Array = - Napi::Float32Array::New(env, wave->num_samples, arrayBuffer, 0); - - Napi::Object obj = Napi::Object::New(env); - obj.Set(Napi::String::New(env, "samples"), float32Array); - obj.Set(Napi::String::New(env, "sampleRate"), wave->sample_rate); - return obj; - } else { - // don't use external buffer - Napi::ArrayBuffer arrayBuffer = - Napi::ArrayBuffer::New(env, sizeof(float) * wave->num_samples); - - Napi::Float32Array float32Array = - Napi::Float32Array::New(env, wave->num_samples, arrayBuffer, 0); - - std::copy(wave->samples, wave->samples + wave->num_samples, - float32Array.Data()); - - Napi::Object obj = Napi::Object::New(env); - obj.Set(Napi::String::New(env, "samples"), float32Array); - obj.Set(Napi::String::New(env, "sampleRate"), wave->sample_rate); - - SherpaOnnxFreeWave(wave); - - return obj; - } -} - -void InitWaveReader(Napi::Env env, Napi::Object exports) { - exports.Set(Napi::String::New(env, "readWave"), - Napi::Function::New(env, ReadWaveWrapper)); -} diff --git a/scripts/node-addon-api/src/wave-reader.cc b/scripts/node-addon-api/src/wave-reader.cc new file mode 120000 index 000000000..839b562d0 --- /dev/null +++ b/scripts/node-addon-api/src/wave-reader.cc @@ -0,0 +1 @@ +../../../harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/wave-reader.cc \ No newline at end of file diff --git a/scripts/node-addon-api/src/wave-writer.cc b/scripts/node-addon-api/src/wave-writer.cc deleted file mode 100644 index 3ade695a0..000000000 --- a/scripts/node-addon-api/src/wave-writer.cc +++ /dev/null @@ -1,81 +0,0 @@ -// scripts/node-addon-api/src/wave-writer.cc -// -// Copyright (c) 2024 Xiaomi Corporation - -#include - -#include "napi.h" // NOLINT -#include "sherpa-onnx/c-api/c-api.h" - -// (filename, {samples: samples, sampleRate: sampleRate} -static Napi::Boolean WriteWaveWrapper(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (info.Length() != 2) { - std::ostringstream os; - os << "Expect only 2 arguments. Given: " << info.Length(); - - Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[0].IsString()) { - Napi::TypeError::New(env, "Argument 0 should be a string") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!info[1].IsObject()) { - Napi::TypeError::New(env, "Argument 1 should be an object") - .ThrowAsJavaScriptException(); - - return {}; - } - - Napi::Object obj = info[1].As(); - - if (!obj.Has("samples")) { - Napi::TypeError::New(env, "The argument object should have a field samples") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!obj.Get("samples").IsTypedArray()) { - Napi::TypeError::New(env, "The object['samples'] should be a typed array") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!obj.Has("sampleRate")) { - Napi::TypeError::New(env, - "The argument object should have a field sampleRate") - .ThrowAsJavaScriptException(); - - return {}; - } - - if (!obj.Get("sampleRate").IsNumber()) { - Napi::TypeError::New(env, "The object['samples'] should be a number") - .ThrowAsJavaScriptException(); - - return {}; - } - - Napi::Float32Array samples = obj.Get("samples").As(); - int32_t sample_rate = obj.Get("sampleRate").As().Int32Value(); - - int32_t ok = - SherpaOnnxWriteWave(samples.Data(), samples.ElementLength(), sample_rate, - info[0].As().Utf8Value().c_str()); - - return Napi::Boolean::New(env, ok); -} - -void InitWaveWriter(Napi::Env env, Napi::Object exports) { - exports.Set(Napi::String::New(env, "writeWave"), - Napi::Function::New(env, WriteWaveWrapper)); -} diff --git a/scripts/node-addon-api/src/wave-writer.cc b/scripts/node-addon-api/src/wave-writer.cc new file mode 120000 index 000000000..03c3818c3 --- /dev/null +++ b/scripts/node-addon-api/src/wave-writer.cc @@ -0,0 +1 @@ +../../../harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/wave-writer.cc \ No newline at end of file diff --git a/sherpa-onnx/c-api/c-api.cc b/sherpa-onnx/c-api/c-api.cc index fbc3a010a..a6ad0772e 100644 --- a/sherpa-onnx/c-api/c-api.cc +++ b/sherpa-onnx/c-api/c-api.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -1201,6 +1202,28 @@ const SherpaOnnxWave *SherpaOnnxReadWave(const char *filename) { return wave; } +const SherpaOnnxWave *SherpaOnnxReadWaveFromBinaryData(const char *data, + int32_t n) { + int32_t sample_rate = -1; + bool is_ok = false; + + std::istrstream is(data, n); + + std::vector samples = sherpa_onnx::ReadWave(is, &sample_rate, &is_ok); + if (!is_ok) { + return nullptr; + } + + float *c_samples = new float[samples.size()]; + std::copy(samples.begin(), samples.end(), c_samples); + + SherpaOnnxWave *wave = new SherpaOnnxWave; + wave->samples = c_samples; + wave->sample_rate = sample_rate; + wave->num_samples = samples.size(); + return wave; +} + void SherpaOnnxFreeWave(const SherpaOnnxWave *wave) { if (wave) { delete[] wave->samples; diff --git a/sherpa-onnx/c-api/c-api.h b/sherpa-onnx/c-api/c-api.h index 1feaab306..5251413a8 100644 --- a/sherpa-onnx/c-api/c-api.h +++ b/sherpa-onnx/c-api/c-api.h @@ -1001,6 +1001,14 @@ SHERPA_ONNX_API typedef struct SherpaOnnxWave { // SherpaOnnxFreeWave() to free the returned pointer to avoid memory leak. SHERPA_ONNX_API const SherpaOnnxWave *SherpaOnnxReadWave(const char *filename); +// Similar to SherpaOnnxReadWave(), it has read the content of `filename` +// into the array `data`. +// +// If the returned pointer is not NULL, the user has to invoke +// SherpaOnnxFreeWave() to free the returned pointer to avoid memory leak. +SHERPA_ONNX_API const SherpaOnnxWave *SherpaOnnxReadWaveFromBinaryData( + const char *data, int32_t n); + SHERPA_ONNX_API void SherpaOnnxFreeWave(const SherpaOnnxWave *wave); // ============================================================