HY-2012 commited on
Commit
759d0dd
·
verified ·
1 Parent(s): 6db57e6

First commit

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +16 -1
  2. README.md +125 -3
  3. audio/openwakeword/alexa_test.wav +0 -0
  4. audio/openwakeword/hey_jane.wav +0 -0
  5. audio/openwakeword/hey_mycroft_test.wav +0 -0
  6. config.json +9 -0
  7. config/openwakeword_mel_weights.bin +3 -0
  8. config/openwakeword_mel_weights.npz +3 -0
  9. cpp/CMakeLists.txt +57 -0
  10. cpp/README.md +89 -0
  11. cpp/bin/openwakeword_ax630c +0 -0
  12. cpp/bin/openwakeword_ax650 +0 -0
  13. cpp/build_ax630c.sh +32 -0
  14. cpp/build_ax650.sh +32 -0
  15. cpp/download_bsp.sh +45 -0
  16. cpp/openwakeword_ax.cpp +379 -0
  17. cpp/run_batch_openwakeword_ax.sh +122 -0
  18. cpp/run_batch_openwakeword_ax630c.sh +5 -0
  19. cpp/run_batch_openwakeword_ax650.sh +5 -0
  20. cpp/run_openwakeword_ax.sh +46 -0
  21. cpp/run_openwakeword_ax630c.sh +5 -0
  22. cpp/run_openwakeword_ax650.sh +5 -0
  23. cpp/src/engine_wrapper.cpp +197 -0
  24. cpp/src/engine_wrapper.hpp +43 -0
  25. cpp/src/wav_reader.hpp +80 -0
  26. manifest.json +217 -0
  27. models/630C/.gitkeep +1 -0
  28. models/630C/openwakeword__alexa_v0.1.axmodel +3 -0
  29. models/630C/openwakeword__embedding_model.axmodel +3 -0
  30. models/630C/openwakeword__hey_jarvis_v0.1.axmodel +3 -0
  31. models/630C/openwakeword__hey_mycroft_v0.1.axmodel +3 -0
  32. models/630C/openwakeword__hey_rhasspy_v0.1.axmodel +3 -0
  33. models/630C/openwakeword__melspectrogram.axmodel +3 -0
  34. models/630C/openwakeword__timer_v0.1.axmodel +3 -0
  35. models/630C/openwakeword__weather_v0.1.axmodel +3 -0
  36. models/650/openwakeword__alexa_v0.1.axmodel +3 -0
  37. models/650/openwakeword__embedding_model.axmodel +3 -0
  38. models/650/openwakeword__hey_jarvis_v0.1.axmodel +3 -0
  39. models/650/openwakeword__hey_mycroft_v0.1.axmodel +3 -0
  40. models/650/openwakeword__hey_rhasspy_v0.1.axmodel +3 -0
  41. models/650/openwakeword__melspectrogram.axmodel +3 -0
  42. models/650/openwakeword__timer_v0.1.axmodel +3 -0
  43. models/650/openwakeword__weather_v0.1.axmodel +3 -0
  44. outputs/.gitkeep +1 -0
  45. outputs/cpp_batch/logs/alexa_test.log +25 -0
  46. outputs/cpp_batch/logs/hey_jane.log +25 -0
  47. outputs/cpp_batch/logs/hey_mycroft_test.log +25 -0
  48. outputs/cpp_batch/results.tsv +4 -0
  49. outputs/cpp_batch/summary.txt +7 -0
  50. reference/local_inference_results.json +497 -0
.gitattributes CHANGED
@@ -10,7 +10,6 @@
10
  *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
  *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
  *.model filter=lfs diff=lfs merge=lfs -text
13
- *.axmodel filter=lfs diff=lfs merge=lfs -text
14
  *.msgpack filter=lfs diff=lfs merge=lfs -text
15
  *.npy filter=lfs diff=lfs merge=lfs -text
16
  *.npz filter=lfs diff=lfs merge=lfs -text
@@ -34,3 +33,19 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
34
  *.zip filter=lfs diff=lfs merge=lfs -text
35
  *.zst filter=lfs diff=lfs merge=lfs -text
36
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
  *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
  *.model filter=lfs diff=lfs merge=lfs -text
 
13
  *.msgpack filter=lfs diff=lfs merge=lfs -text
14
  *.npy filter=lfs diff=lfs merge=lfs -text
15
  *.npz filter=lfs diff=lfs merge=lfs -text
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ models/630C/openwakeword__alexa_v0.1.axmodel filter=lfs diff=lfs merge=lfs -text
37
+ models/630C/openwakeword__embedding_model.axmodel filter=lfs diff=lfs merge=lfs -text
38
+ models/630C/openwakeword__hey_jarvis_v0.1.axmodel filter=lfs diff=lfs merge=lfs -text
39
+ models/630C/openwakeword__hey_mycroft_v0.1.axmodel filter=lfs diff=lfs merge=lfs -text
40
+ models/630C/openwakeword__hey_rhasspy_v0.1.axmodel filter=lfs diff=lfs merge=lfs -text
41
+ models/630C/openwakeword__melspectrogram.axmodel filter=lfs diff=lfs merge=lfs -text
42
+ models/630C/openwakeword__timer_v0.1.axmodel filter=lfs diff=lfs merge=lfs -text
43
+ models/630C/openwakeword__weather_v0.1.axmodel filter=lfs diff=lfs merge=lfs -text
44
+ models/650/openwakeword__alexa_v0.1.axmodel filter=lfs diff=lfs merge=lfs -text
45
+ models/650/openwakeword__embedding_model.axmodel filter=lfs diff=lfs merge=lfs -text
46
+ models/650/openwakeword__hey_jarvis_v0.1.axmodel filter=lfs diff=lfs merge=lfs -text
47
+ models/650/openwakeword__hey_mycroft_v0.1.axmodel filter=lfs diff=lfs merge=lfs -text
48
+ models/650/openwakeword__hey_rhasspy_v0.1.axmodel filter=lfs diff=lfs merge=lfs -text
49
+ models/650/openwakeword__melspectrogram.axmodel filter=lfs diff=lfs merge=lfs -text
50
+ models/650/openwakeword__timer_v0.1.axmodel filter=lfs diff=lfs merge=lfs -text
51
+ models/650/openwakeword__weather_v0.1.axmodel filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,3 +1,125 @@
1
- ---
2
- license: mit
3
- ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # openWakeWord.AXERA
2
+
3
+ [openWakeWord](https://github.com/dscripka/openWakeWord) 语音唤醒 AXERA 推理 demo。
4
+
5
+ ## 功能
6
+
7
+ - 支持 AX650 和 AX630C;
8
+ - 支持 Python 和 C++ 板端推理;
9
+
10
+ ## 目录结构
11
+
12
+ ```text
13
+ OpenWakeWord.AXERA/
14
+ ├── models/
15
+ │ ├── 650/ # AX650 axmodel
16
+ │ └── 630C/ # AX630C axmodel
17
+ ├── config/ # STFT/mel 权重
18
+ ├── audio/openwakeword/
19
+ ├── scripts/ # Python 推理代码
20
+ ├── cpp/ # C++ 源码、编译和运行脚本
21
+ └── README.md
22
+ ```
23
+
24
+
25
+ ## Python
26
+
27
+ ```bash
28
+ 创建虚拟环境
29
+ conda create -n OpenWakeWord python=3.10
30
+ conda activate OpenWakeWord
31
+
32
+ 安装 [pyaxengine](https://github.com/AXERA-TECH/pyaxengine/releases/latest):
33
+ pip install axengine-x.x.x-py3-none-any.whl
34
+
35
+ pip install -r requirements.txt
36
+ ```
37
+
38
+ AX650:
39
+
40
+ ```bash
41
+ python scripts/openwakeword_ax650.py
42
+ ```
43
+
44
+ AX630C:
45
+
46
+ ```bash
47
+ python scripts/openwakeword_ax630c.py
48
+ ```
49
+
50
+ 自定义音频目录、阈值和输出文件:
51
+
52
+ ```bash
53
+ python scripts/openwakeword_ax630c.py \
54
+ --audio-dir audio/openwakeword \
55
+ --threshold 0.5 \
56
+ --output outputs/openwakeword_ax630c.json
57
+ ```
58
+
59
+ ## C++
60
+
61
+ ### 编译环境
62
+
63
+ AX650 和 AX630C 共用 Arm GNU 9.2 AArch64 交叉编译器:
64
+
65
+ <https://developer.arm.com/-/media/Files/downloads/gnu-a/9.2-2019.12/binrel/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu.tar.xz>
66
+
67
+ ```bash
68
+ mkdir -p cpp/toolchains
69
+ wget -P cpp/toolchains \
70
+ https://developer.arm.com/-/media/Files/downloads/gnu-a/9.2-2019.12/binrel/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu.tar.xz
71
+ tar -xf cpp/toolchains/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu.tar.xz \
72
+ -C cpp/toolchains
73
+ ```
74
+
75
+ BSP SDK 下载地址:
76
+
77
+ | 平台 | BSP SDK |
78
+ | --- | --- |
79
+ | AX650 | <https://github.com/ZHEQIUSHUI/assets/releases/download/ax_3.6.2/msp_50_3.10.2.zip> |
80
+ | AX630C | <https://github.com/ZHEQIUSHUI/assets/releases/download/ax_3.6.2/msp_20e_3.0.0.zip> |
81
+
82
+ 下载并整理到 `cpp/toolchains/`:
83
+
84
+ ```bash
85
+ bash cpp/download_bsp.sh all
86
+ ```
87
+
88
+ ### 交叉编译
89
+
90
+ ```bash
91
+ export TOOLCHAIN_ROOT="cpp/toolchains/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu"
92
+
93
+ bash cpp/build_ax650.sh
94
+ bash cpp/build_ax630c.sh
95
+ ```
96
+
97
+ 编译结果:
98
+
99
+ ```text
100
+ cpp/bin/openwakeword_ax650
101
+ cpp/bin/openwakeword_ax630c
102
+ ```
103
+
104
+ ### 板端批量测试
105
+
106
+ AX650:
107
+
108
+ ```bash
109
+ THRESHOLD=0.5 bash cpp/run_batch_openwakeword_ax650.sh
110
+ ```
111
+
112
+ AX630C:
113
+
114
+ ```bash
115
+ THRESHOLD=0.5 bash cpp/run_batch_openwakeword_ax630c.sh
116
+ ```
117
+
118
+ 结果分别写入 `outputs/cpp_ax650_batch/` 和 `outputs/cpp_ax630c_batch/`。
119
+
120
+ 详细 C++ 说明见 `cpp/README.md`。
121
+
122
+ ## 参考
123
+
124
+ - [openWakeWord](https://github.com/dscripka/openWakeWord)
125
+ - [Pulsar2](https://pulsar2-docs.readthedocs.io/en/latest/pulsar2/introduction.html)
audio/openwakeword/alexa_test.wav ADDED
Binary file (20 kB). View file
 
audio/openwakeword/hey_jane.wav ADDED
Binary file (75.8 kB). View file
 
audio/openwakeword/hey_mycroft_test.wav ADDED
Binary file (30.5 kB). View file
 
config.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_type": "keyword-spotting",
3
+ "source": "openWakeWord",
4
+ "target_hardware": "AX630C/AX650",
5
+ "runtime": "axengine",
6
+ "sample_rate": 16000,
7
+ "default_threshold": 0.5,
8
+ "mel_backend": "numpy"
9
+ }
config/openwakeword_mel_weights.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f5d3a5bafe0def6c1efb4874d724c272d78edadada6652ef885059db2ba147a7
3
+ size 1085608
config/openwakeword_mel_weights.npz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a3de443a7383dc51a4bf8b1d2dc9bbc767f7e33e17af617d993700a5f9947ed6
3
+ size 1086554
cpp/CMakeLists.txt ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ cmake_minimum_required(VERSION 3.13)
2
+ project(openwakeword_axera_cpp LANGUAGES C CXX)
3
+
4
+ set(CMAKE_CXX_STANDARD 17)
5
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
6
+ set(CMAKE_BUILD_TYPE Release CACHE STRING "")
7
+
8
+ set(AXERA_TARGET "AX650" CACHE STRING "AXERA target: AX650 or AX630C")
9
+ set_property(CACHE AXERA_TARGET PROPERTY STRINGS AX650 AX630C)
10
+ string(TOUPPER "${AXERA_TARGET}" AXERA_TARGET)
11
+ if(AXERA_TARGET STREQUAL "AX650")
12
+ set(KWS_EXECUTABLE openwakeword_ax650)
13
+ set(DEFAULT_BSP_MSP_DIR
14
+ "${CMAKE_SOURCE_DIR}/toolchains/ax650n_bsp_sdk/msp/out")
15
+ elseif(AXERA_TARGET STREQUAL "AX630C")
16
+ set(KWS_EXECUTABLE openwakeword_ax630c)
17
+ set(DEFAULT_BSP_MSP_DIR
18
+ "${CMAKE_SOURCE_DIR}/toolchains/ax620e_bsp_sdk/msp/out/arm64_glibc")
19
+ else()
20
+ message(FATAL_ERROR "AXERA_TARGET must be AX650 or AX630C")
21
+ endif()
22
+
23
+ set(BSP_MSP_DIR "${DEFAULT_BSP_MSP_DIR}" CACHE PATH
24
+ "BSP directory containing include/ and lib/")
25
+ if(NOT EXISTS "${BSP_MSP_DIR}/include/ax_engine_api.h")
26
+ message(FATAL_ERROR
27
+ "${AXERA_TARGET} BSP not found: ${BSP_MSP_DIR}. "
28
+ "Set BSP_MSP_DIR to the directory containing include/ and lib/.")
29
+ endif()
30
+
31
+ add_executable(${KWS_EXECUTABLE}
32
+ openwakeword_ax.cpp
33
+ src/engine_wrapper.cpp
34
+ )
35
+ target_include_directories(${KWS_EXECUTABLE} PRIVATE
36
+ ${CMAKE_SOURCE_DIR}
37
+ ${CMAKE_SOURCE_DIR}/src
38
+ ${BSP_MSP_DIR}/include
39
+ )
40
+ target_link_directories(${KWS_EXECUTABLE} PRIVATE ${BSP_MSP_DIR}/lib)
41
+ target_compile_definitions(${KWS_EXECUTABLE} PRIVATE
42
+ AXERA_TARGET_NAME="${AXERA_TARGET}"
43
+ )
44
+ target_compile_options(${KWS_EXECUTABLE} PRIVATE
45
+ -O3 -Wall -Wextra -Wpedantic
46
+ -fvisibility=hidden -fdata-sections -ffunction-sections
47
+ )
48
+ target_link_libraries(${KWS_EXECUTABLE} PRIVATE
49
+ ax_sys ax_engine ax_interpreter pthread dl m
50
+ )
51
+ target_link_options(${KWS_EXECUTABLE} PRIVATE
52
+ -Wl,--enable-new-dtags -Wl,--allow-shlib-undefined
53
+ )
54
+ set_target_properties(${KWS_EXECUTABLE} PROPERTIES
55
+ BUILD_WITH_INSTALL_RPATH TRUE
56
+ INSTALL_RPATH "$ORIGIN"
57
+ )
cpp/README.md ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # openWakeWord C++ 推理
2
+
3
+ 本目录提供 AX650 和 AX630C C++ 推理程序。WAV、STFT、mel 和缓存更新在 CPU
4
+ 端完成,embedding 与 classifier 使用对应平台的 `axmodel`。
5
+
6
+ ## 编译环境
7
+
8
+ AX650 与 AX630C 共用 Arm GNU 9.2 AArch64 交叉编译器:
9
+
10
+ ```bash
11
+ mkdir -p cpp/toolchains
12
+ wget -P cpp/toolchains \
13
+ https://developer.arm.com/-/media/Files/downloads/gnu-a/9.2-2019.12/binrel/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu.tar.xz
14
+ tar -xf cpp/toolchains/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu.tar.xz \
15
+ -C cpp/toolchains
16
+ ```
17
+
18
+ 下载 BSP:
19
+
20
+ ```bash
21
+ bash cpp/download_bsp.sh all
22
+ ```
23
+
24
+ | 平台 | BSP 压缩包 | 编译目录 |
25
+ | --- | --- | --- |
26
+ | AX650 | `msp_50_3.10.2.zip` | `cpp/toolchains/ax650n_bsp_sdk/msp/out` |
27
+ | AX630C | `msp_20e_3.0.0.zip` | `cpp/toolchains/ax620e_bsp_sdk/msp/out/arm64_glibc` |
28
+
29
+ ## 交叉编译
30
+
31
+ ```bash
32
+ export TOOLCHAIN_ROOT="cpp/toolchains/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu"
33
+
34
+ bash cpp/build_ax650.sh
35
+ bash cpp/build_ax630c.sh
36
+ ```
37
+
38
+ 生成:
39
+
40
+ ```text
41
+ cpp/bin/openwakeword_ax650
42
+ cpp/bin/openwakeword_ax630c
43
+ ```
44
+
45
+ 如 BSP 放在其他位置,可设置 `BSP_MSP_DIR`。AX650 应指向包含 `include/` 和
46
+ `lib/` 的 `msp/out`;AX630C 应指向 `msp/out/arm64_glibc`。
47
+
48
+ ## 板端运行
49
+
50
+ AX650 单条推理:
51
+
52
+ ```bash
53
+ AUDIO=audio/openwakeword/alexa_test.wav \
54
+ bash cpp/run_openwakeword_ax650.sh
55
+ ```
56
+
57
+ AX630C 单条推理:
58
+
59
+ ```bash
60
+ AUDIO=audio/openwakeword/alexa_test.wav \
61
+ bash cpp/run_openwakeword_ax630c.sh
62
+ ```
63
+
64
+ 批量测试:
65
+
66
+ ```bash
67
+ THRESHOLD=0.5 bash cpp/run_batch_openwakeword_ax650.sh
68
+ THRESHOLD=0.5 bash cpp/run_batch_openwakeword_ax630c.sh
69
+ ```
70
+
71
+ 模型目录必须与平台对应:
72
+
73
+ ```text
74
+ models/650/
75
+ models/630C/
76
+ ```
77
+
78
+ 运行脚本会检查 embedding 和 6 个 classifier 模型,避免误用其他平台的 axmodel。
79
+ 输入音频必须为 16 kHz、单声道、16-bit PCM WAV。
80
+
81
+ 单条推理会输出 classifier 分数和 `WAKEUP`,并只输出一个 `rtf`。`rtf` 包含
82
+ CPU mel 和 NPU 推理,不包含一次性模型加载与初始化。
83
+
84
+ 阈值可通过环境变量调整:
85
+
86
+ ```bash
87
+ THRESHOLD=0.6 AUDIO=audio/openwakeword/hey_mycroft_test.wav \
88
+ bash cpp/run_openwakeword_ax630c.sh
89
+ ```
cpp/bin/openwakeword_ax630c ADDED
Binary file (39.7 kB). View file
 
cpp/bin/openwakeword_ax650 ADDED
Binary file (39.7 kB). View file
 
cpp/build_ax630c.sh ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ CPP_DIR="${ROOT}/cpp"
6
+ TOOLCHAIN_ROOT="${TOOLCHAIN_ROOT:-${CPP_DIR}/toolchains/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu}"
7
+ BSP_MSP_DIR="${BSP_MSP_DIR:-${CPP_DIR}/toolchains/ax620e_bsp_sdk/msp/out/arm64_glibc}"
8
+ BUILD_DIR="${CPP_DIR}/build/ax630c"
9
+
10
+ if [[ ! -x "${TOOLCHAIN_ROOT}/bin/aarch64-none-linux-gnu-g++" ]]; then
11
+ echo "ERROR: AArch64 toolchain not found: ${TOOLCHAIN_ROOT}" >&2
12
+ exit 2
13
+ fi
14
+ if [[ ! -f "${BSP_MSP_DIR}/include/ax_engine_api.h" ]]; then
15
+ echo "ERROR: AX630C BSP arm64_glibc directory not found: ${BSP_MSP_DIR}" >&2
16
+ exit 2
17
+ fi
18
+
19
+ mkdir -p "${BUILD_DIR}" "${CPP_DIR}/bin"
20
+ cmake -S "${CPP_DIR}" -B "${BUILD_DIR}" \
21
+ -DCMAKE_BUILD_TYPE=Release \
22
+ -DCMAKE_SYSTEM_NAME=Linux \
23
+ -DCMAKE_SYSTEM_PROCESSOR=aarch64 \
24
+ -DCMAKE_C_COMPILER="${TOOLCHAIN_ROOT}/bin/aarch64-none-linux-gnu-gcc" \
25
+ -DCMAKE_CXX_COMPILER="${TOOLCHAIN_ROOT}/bin/aarch64-none-linux-gnu-g++" \
26
+ -DAXERA_TARGET=AX630C \
27
+ -DBSP_MSP_DIR="${BSP_MSP_DIR}"
28
+ cmake --build "${BUILD_DIR}" -j"$(nproc)"
29
+ cp "${BUILD_DIR}/openwakeword_ax630c" "${CPP_DIR}/bin/"
30
+ "${TOOLCHAIN_ROOT}/bin/aarch64-none-linux-gnu-strip" --strip-unneeded \
31
+ "${CPP_DIR}/bin/openwakeword_ax630c"
32
+ echo "Built ${CPP_DIR}/bin/openwakeword_ax630c"
cpp/build_ax650.sh ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ CPP_DIR="${ROOT}/cpp"
6
+ TOOLCHAIN_ROOT="${TOOLCHAIN_ROOT:-${CPP_DIR}/toolchains/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu}"
7
+ BSP_MSP_DIR="${BSP_MSP_DIR:-${CPP_DIR}/toolchains/ax650n_bsp_sdk/msp/out}"
8
+ BUILD_DIR="${CPP_DIR}/build/ax650"
9
+
10
+ if [[ ! -x "${TOOLCHAIN_ROOT}/bin/aarch64-none-linux-gnu-g++" ]]; then
11
+ echo "ERROR: AArch64 toolchain not found: ${TOOLCHAIN_ROOT}" >&2
12
+ exit 2
13
+ fi
14
+ if [[ ! -f "${BSP_MSP_DIR}/include/ax_engine_api.h" ]]; then
15
+ echo "ERROR: AX650 BSP msp/out not found: ${BSP_MSP_DIR}" >&2
16
+ exit 2
17
+ fi
18
+
19
+ mkdir -p "${BUILD_DIR}" "${CPP_DIR}/bin"
20
+ cmake -S "${CPP_DIR}" -B "${BUILD_DIR}" \
21
+ -DCMAKE_BUILD_TYPE=Release \
22
+ -DCMAKE_SYSTEM_NAME=Linux \
23
+ -DCMAKE_SYSTEM_PROCESSOR=aarch64 \
24
+ -DCMAKE_C_COMPILER="${TOOLCHAIN_ROOT}/bin/aarch64-none-linux-gnu-gcc" \
25
+ -DCMAKE_CXX_COMPILER="${TOOLCHAIN_ROOT}/bin/aarch64-none-linux-gnu-g++" \
26
+ -DAXERA_TARGET=AX650 \
27
+ -DBSP_MSP_DIR="${BSP_MSP_DIR}"
28
+ cmake --build "${BUILD_DIR}" -j"$(nproc)"
29
+ cp "${BUILD_DIR}/openwakeword_ax650" "${CPP_DIR}/bin/"
30
+ "${TOOLCHAIN_ROOT}/bin/aarch64-none-linux-gnu-strip" --strip-unneeded \
31
+ "${CPP_DIR}/bin/openwakeword_ax650"
32
+ echo "Built ${CPP_DIR}/bin/openwakeword_ax650"
cpp/download_bsp.sh ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ CPP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ TOOLCHAINS_DIR="${CPP_DIR}/toolchains"
6
+ PLATFORM="${1:-all}"
7
+
8
+ download_sdk() {
9
+ local directory="$1"
10
+ local archive_name="$2"
11
+ local extracted_name="$3"
12
+ local url="$4"
13
+
14
+ if [[ -d "${TOOLCHAINS_DIR}/${directory}" ]]; then
15
+ echo "Found ${TOOLCHAINS_DIR}/${directory}"
16
+ return
17
+ fi
18
+ mkdir -p "${TOOLCHAINS_DIR}"
19
+ wget -c "${url}" -O "${TOOLCHAINS_DIR}/${archive_name}"
20
+ unzip -q "${TOOLCHAINS_DIR}/${archive_name}" -d "${TOOLCHAINS_DIR}"
21
+ mv "${TOOLCHAINS_DIR}/${extracted_name}" "${TOOLCHAINS_DIR}/${directory}"
22
+ rm -f "${TOOLCHAINS_DIR}/${archive_name}"
23
+ echo "Downloaded ${TOOLCHAINS_DIR}/${directory}"
24
+ }
25
+
26
+ case "${PLATFORM}" in
27
+ 650|AX650|ax650)
28
+ download_sdk \
29
+ ax650n_bsp_sdk msp_50_3.10.2.zip msp_50_3.10.2 \
30
+ https://github.com/ZHEQIUSHUI/assets/releases/download/ax_3.6.2/msp_50_3.10.2.zip
31
+ ;;
32
+ 630C|AX630C|ax630c)
33
+ download_sdk \
34
+ ax620e_bsp_sdk msp_20e_3.0.0.zip msp_20e_3.0.0 \
35
+ https://github.com/ZHEQIUSHUI/assets/releases/download/ax_3.6.2/msp_20e_3.0.0.zip
36
+ ;;
37
+ all)
38
+ bash "${BASH_SOURCE[0]}" 650
39
+ bash "${BASH_SOURCE[0]}" 630C
40
+ ;;
41
+ *)
42
+ echo "Usage: bash cpp/download_bsp.sh [650|630C|all]" >&2
43
+ exit 2
44
+ ;;
45
+ esac
cpp/openwakeword_ax.cpp ADDED
@@ -0,0 +1,379 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #include <algorithm>
2
+ #include <array>
3
+ #include <chrono>
4
+ #include <cmath>
5
+ #include <cstdint>
6
+ #include <cstdio>
7
+ #include <cstring>
8
+ #include <fstream>
9
+ #include <limits>
10
+ #include <stdexcept>
11
+ #include <string>
12
+ #include <vector>
13
+
14
+ #include "ax_engine_api.h"
15
+ #include "ax_sys_api.h"
16
+ #include "src/engine_wrapper.hpp"
17
+ #include "src/wav_reader.hpp"
18
+
19
+ #ifndef AXERA_TARGET_NAME
20
+ #define AXERA_TARGET_NAME "AXERA"
21
+ #endif
22
+
23
+ namespace {
24
+ constexpr int kSampleRate = 16000;
25
+ constexpr int kChunkSamples = 1280;
26
+ constexpr int kHistorySamples = 480;
27
+ constexpr int kFftSize = 512;
28
+ constexpr int kSpectrumBins = 257;
29
+ constexpr int kMelBins = 32;
30
+ constexpr int kMelFrames = 8;
31
+ constexpr int kEmbeddingFrames = 76;
32
+ constexpr int kEmbeddingSize = 96;
33
+ constexpr int kFeatureFrames = 34;
34
+ using Clock = std::chrono::steady_clock;
35
+
36
+ double ElapsedSeconds(Clock::time_point begin, Clock::time_point end) {
37
+ return std::chrono::duration<double>(end - begin).count();
38
+ }
39
+
40
+ struct Args {
41
+ std::string models_dir = "models";
42
+ std::string weights = "config/openwakeword_mel_weights.bin";
43
+ std::string audio = "audio/openwakeword/alexa_test.wav";
44
+ float threshold = 0.5f;
45
+ };
46
+
47
+ void Usage(const char *program) {
48
+ std::printf(
49
+ "Usage: %s [--models-dir DIR] [--mel-weights FILE] [--audio WAV] "
50
+ "[--threshold VALUE]\n",
51
+ program);
52
+ }
53
+
54
+ Args ParseArgs(int argc, char **argv) {
55
+ Args args;
56
+ for (int i = 1; i < argc; ++i) {
57
+ const std::string key = argv[i];
58
+ auto value = [&]() -> std::string {
59
+ if (++i >= argc) throw std::runtime_error("Missing value for " + key);
60
+ return argv[i];
61
+ };
62
+ if (key == "--models-dir") {
63
+ args.models_dir = value();
64
+ } else if (key == "--mel-weights") {
65
+ args.weights = value();
66
+ } else if (key == "--audio") {
67
+ args.audio = value();
68
+ } else if (key == "--threshold") {
69
+ args.threshold = std::stof(value());
70
+ } else if (key == "-h" || key == "--help") {
71
+ Usage(argv[0]);
72
+ std::exit(0);
73
+ } else {
74
+ throw std::runtime_error("Unknown argument: " + key);
75
+ }
76
+ }
77
+ return args;
78
+ }
79
+
80
+ std::string Join(const std::string &left, const std::string &right) {
81
+ return left.empty() || left.back() == '/' ? left + right
82
+ : left + "/" + right;
83
+ }
84
+
85
+ class AxRuntime {
86
+ public:
87
+ AxRuntime() {
88
+ if (AX_SYS_Init() != 0) throw std::runtime_error("AX_SYS_Init failed");
89
+ sys_initialized_ = true;
90
+ AX_ENGINE_NPU_ATTR_T attr{};
91
+ if (AX_ENGINE_Init(&attr) != 0) {
92
+ AX_SYS_Deinit();
93
+ sys_initialized_ = false;
94
+ throw std::runtime_error("AX_ENGINE_Init failed");
95
+ }
96
+ engine_initialized_ = true;
97
+ }
98
+ ~AxRuntime() {
99
+ if (engine_initialized_) AX_ENGINE_Deinit();
100
+ if (sys_initialized_) AX_SYS_Deinit();
101
+ }
102
+
103
+ private:
104
+ bool sys_initialized_ = false;
105
+ bool engine_initialized_ = false;
106
+ };
107
+
108
+ template <typename T>
109
+ T ReadScalar(std::istream &input) {
110
+ T value{};
111
+ input.read(reinterpret_cast<char *>(&value), sizeof(value));
112
+ if (!input) throw std::runtime_error("Truncated mel weight file");
113
+ return value;
114
+ }
115
+
116
+ struct MelWeights {
117
+ std::vector<float> real;
118
+ std::vector<float> imag;
119
+ std::vector<float> mel;
120
+ float floor = 0.0f;
121
+
122
+ static MelWeights Load(const std::string &path) {
123
+ std::ifstream input(path, std::ios::binary);
124
+ if (!input) throw std::runtime_error("Cannot open mel weights: " + path);
125
+ char magic[8]{};
126
+ input.read(magic, 8);
127
+ if (std::memcmp(magic, "OWWMEL1", 7) != 0 ||
128
+ ReadScalar<uint32_t>(input) != 1) {
129
+ throw std::runtime_error("Invalid openWakeWord mel weight file");
130
+ }
131
+ const uint32_t real_rows = ReadScalar<uint32_t>(input);
132
+ const uint32_t real_cols = ReadScalar<uint32_t>(input);
133
+ const uint32_t imag_rows = ReadScalar<uint32_t>(input);
134
+ const uint32_t imag_cols = ReadScalar<uint32_t>(input);
135
+ const uint32_t mel_rows = ReadScalar<uint32_t>(input);
136
+ const uint32_t mel_cols = ReadScalar<uint32_t>(input);
137
+ MelWeights result;
138
+ result.floor = ReadScalar<float>(input);
139
+ if (real_rows != kSpectrumBins || real_cols != kFftSize ||
140
+ imag_rows != kSpectrumBins || imag_cols != kFftSize ||
141
+ mel_rows != kSpectrumBins || mel_cols != kMelBins) {
142
+ throw std::runtime_error("Unexpected openWakeWord mel weight shapes");
143
+ }
144
+ result.real.resize(static_cast<std::size_t>(real_rows) * real_cols);
145
+ result.imag.resize(static_cast<std::size_t>(imag_rows) * imag_cols);
146
+ result.mel.resize(static_cast<std::size_t>(mel_rows) * mel_cols);
147
+ input.read(reinterpret_cast<char *>(result.real.data()),
148
+ result.real.size() * sizeof(float));
149
+ input.read(reinterpret_cast<char *>(result.imag.data()),
150
+ result.imag.size() * sizeof(float));
151
+ input.read(reinterpret_cast<char *>(result.mel.data()),
152
+ result.mel.size() * sizeof(float));
153
+ if (!input) throw std::runtime_error("Truncated openWakeWord mel weights");
154
+ return result;
155
+ }
156
+ };
157
+
158
+ std::array<float, kMelFrames * kMelBins> ComputeMel(
159
+ const std::array<float, kHistorySamples + kChunkSamples> &samples,
160
+ const MelWeights &weights) {
161
+ std::array<float, kMelFrames * kMelBins> result{};
162
+ std::array<float, kSpectrumBins> power{};
163
+ float max_db = -std::numeric_limits<float>::infinity();
164
+ for (int frame = 0; frame < kMelFrames; ++frame) {
165
+ const float *frame_samples = samples.data() + frame * 160;
166
+ for (int frequency = 0; frequency < kSpectrumBins; ++frequency) {
167
+ const float *real = weights.real.data() + frequency * kFftSize;
168
+ const float *imag = weights.imag.data() + frequency * kFftSize;
169
+ float real_sum = 0.0f;
170
+ float imag_sum = 0.0f;
171
+ for (int n = 0; n < kFftSize; ++n) {
172
+ real_sum += frame_samples[n] * real[n];
173
+ imag_sum += frame_samples[n] * imag[n];
174
+ }
175
+ power[frequency] = real_sum * real_sum + imag_sum * imag_sum;
176
+ }
177
+ for (int bin = 0; bin < kMelBins; ++bin) {
178
+ float value = 0.0f;
179
+ for (int frequency = 0; frequency < kSpectrumBins; ++frequency) {
180
+ value += power[frequency] *
181
+ weights.mel[frequency * kMelBins + bin];
182
+ }
183
+ value = std::max(value, weights.floor);
184
+ const float db = std::log(value) * 10.0f / 2.3025851249694824f;
185
+ result[frame * kMelBins + bin] = db;
186
+ max_db = std::max(max_db, db);
187
+ }
188
+ }
189
+ const float minimum = max_db - 80.0f;
190
+ for (float &value : result) {
191
+ value = std::max(value, minimum) / 10.0f + 2.0f;
192
+ }
193
+ return result;
194
+ }
195
+
196
+ struct Classifier {
197
+ std::string name;
198
+ int frames;
199
+ EngineWrapper engine;
200
+ std::vector<float> maximum;
201
+ };
202
+
203
+ void Run(const Args &args) {
204
+ const PcmWav wav = ReadPcmWav(args.audio);
205
+ if (wav.sample_rate != kSampleRate) {
206
+ throw std::runtime_error("Input WAV must use 16 kHz sample rate");
207
+ }
208
+ const double audio_seconds =
209
+ static_cast<double>(wav.samples.size()) / kSampleRate;
210
+ if (audio_seconds <= 0.0) {
211
+ throw std::runtime_error("Input WAV contains no samples");
212
+ }
213
+ const MelWeights weights = MelWeights::Load(args.weights);
214
+ const auto model_load_begin = Clock::now();
215
+ AxRuntime runtime;
216
+
217
+ EngineWrapper embedding;
218
+ if (embedding.Init(Join(args.models_dir,
219
+ "openwakeword__embedding_model.axmodel")) != 0) {
220
+ throw std::runtime_error("Failed to load embedding model");
221
+ }
222
+ const std::array<std::pair<const char *, int>, 6> definitions{{
223
+ {"alexa_v0.1", 16},
224
+ {"hey_jarvis_v0.1", 16},
225
+ {"hey_mycroft_v0.1", 16},
226
+ {"hey_rhasspy_v0.1", 16},
227
+ {"timer_v0.1", 34},
228
+ {"weather_v0.1", 22},
229
+ }};
230
+ std::array<Classifier, 6> classifiers;
231
+ for (std::size_t classifier_index = 0;
232
+ classifier_index < definitions.size(); ++classifier_index) {
233
+ const auto &definition = definitions[classifier_index];
234
+ Classifier &classifier = classifiers[classifier_index];
235
+ classifier.name = definition.first;
236
+ classifier.frames = definition.second;
237
+ const std::string model =
238
+ Join(args.models_dir, "openwakeword__" + classifier.name + ".axmodel");
239
+ if (classifier.engine.Init(model) != 0) {
240
+ throw std::runtime_error("Failed to load classifier: " + classifier.name);
241
+ }
242
+ const int output_bytes = classifier.engine.GetOutputSizeByName(
243
+ classifier.engine.OutputName(0));
244
+ if (output_bytes <= 0 || output_bytes % sizeof(float) != 0) {
245
+ throw std::runtime_error("Unexpected classifier output: " +
246
+ classifier.name);
247
+ }
248
+ classifier.maximum.assign(output_bytes / sizeof(float),
249
+ -std::numeric_limits<float>::infinity());
250
+ }
251
+ const double model_load_seconds =
252
+ ElapsedSeconds(model_load_begin, Clock::now());
253
+
254
+ std::vector<int16_t> padded = wav.samples;
255
+ const std::size_t remainder = padded.size() % kChunkSamples;
256
+ if (remainder != 0) padded.resize(padded.size() + kChunkSamples - remainder);
257
+ std::array<int16_t, kHistorySamples> history{};
258
+ std::array<float, kEmbeddingFrames * kMelBins> mel_buffer{};
259
+ mel_buffer.fill(1.0f);
260
+ std::array<float, kFeatureFrames * kEmbeddingSize> feature_buffer{};
261
+ int chunks = 0;
262
+ double feature_seconds = 0.0;
263
+ double npu_seconds = 0.0;
264
+ const auto inference_begin = Clock::now();
265
+
266
+ for (std::size_t start = 0; start < padded.size(); start += kChunkSamples) {
267
+ std::array<float, kHistorySamples + kChunkSamples> mel_input{};
268
+ for (int i = 0; i < kHistorySamples; ++i) mel_input[i] = history[i];
269
+ for (int i = 0; i < kChunkSamples; ++i) {
270
+ mel_input[kHistorySamples + i] = padded[start + i];
271
+ }
272
+ for (int i = 0; i < kHistorySamples; ++i) {
273
+ history[i] = padded[start + kChunkSamples - kHistorySamples + i];
274
+ }
275
+ const auto feature_begin = Clock::now();
276
+ const auto mel = ComputeMel(mel_input, weights);
277
+ feature_seconds += ElapsedSeconds(feature_begin, Clock::now());
278
+ std::memmove(mel_buffer.data(), mel_buffer.data() + kMelFrames * kMelBins,
279
+ (kEmbeddingFrames - kMelFrames) * kMelBins * sizeof(float));
280
+ std::memcpy(mel_buffer.data() +
281
+ (kEmbeddingFrames - kMelFrames) * kMelBins,
282
+ mel.data(), mel.size() * sizeof(float));
283
+
284
+ const std::string &embedding_input = embedding.InputName(0);
285
+ if (embedding.SetInputByName(embedding_input, mel_buffer.data(),
286
+ mel_buffer.size() * sizeof(float)) != 0) {
287
+ throw std::runtime_error("Failed to set embedding input");
288
+ }
289
+ const auto embedding_begin = Clock::now();
290
+ const int embedding_ret = embedding.RunSync();
291
+ npu_seconds += ElapsedSeconds(embedding_begin, Clock::now());
292
+ if (embedding_ret != 0) {
293
+ throw std::runtime_error("Embedding inference failed");
294
+ }
295
+ std::array<float, kEmbeddingSize> feature{};
296
+ if (embedding.GetOutputByName(embedding.OutputName(0), feature.data(),
297
+ feature.size() * sizeof(float)) != 0) {
298
+ throw std::runtime_error("Failed to read embedding output");
299
+ }
300
+ std::memmove(feature_buffer.data(), feature_buffer.data() + kEmbeddingSize,
301
+ (kFeatureFrames - 1) * kEmbeddingSize * sizeof(float));
302
+ std::memcpy(feature_buffer.data() +
303
+ (kFeatureFrames - 1) * kEmbeddingSize,
304
+ feature.data(), feature.size() * sizeof(float));
305
+
306
+ for (Classifier &classifier : classifiers) {
307
+ const float *input =
308
+ feature_buffer.data() +
309
+ (kFeatureFrames - classifier.frames) * kEmbeddingSize;
310
+ const std::string &input_name = classifier.engine.InputName(0);
311
+ if (classifier.engine.SetInputByName(
312
+ input_name, input,
313
+ classifier.frames * kEmbeddingSize * sizeof(float)) != 0) {
314
+ throw std::runtime_error("Failed to set classifier input: " +
315
+ classifier.name);
316
+ }
317
+ const auto classifier_begin = Clock::now();
318
+ const int classifier_ret = classifier.engine.RunSync();
319
+ npu_seconds += ElapsedSeconds(classifier_begin, Clock::now());
320
+ if (classifier_ret != 0) {
321
+ throw std::runtime_error("Classifier inference failed: " +
322
+ classifier.name);
323
+ }
324
+ std::vector<float> output(classifier.maximum.size());
325
+ if (classifier.engine.GetOutputByName(
326
+ classifier.engine.OutputName(0), output.data(),
327
+ output.size() * sizeof(float)) != 0) {
328
+ throw std::runtime_error("Failed to read classifier output");
329
+ }
330
+ for (std::size_t i = 0; i < output.size(); ++i) {
331
+ classifier.maximum[i] = std::max(classifier.maximum[i], output[i]);
332
+ }
333
+ }
334
+ ++chunks;
335
+ }
336
+ const double inference_seconds =
337
+ ElapsedSeconds(inference_begin, Clock::now());
338
+ const double rtf = inference_seconds / audio_seconds;
339
+
340
+ std::printf("\nopenWakeWord C++ inference complete\n");
341
+ std::printf("target: %s\naudio: %s\nchunks: %d\nthreshold: %.3f\n",
342
+ AXERA_TARGET_NAME, args.audio.c_str(), chunks, args.threshold);
343
+ bool detected = false;
344
+ for (const Classifier &classifier : classifiers) {
345
+ std::printf("%-22s", classifier.name.c_str());
346
+ float score = -std::numeric_limits<float>::infinity();
347
+ for (std::size_t i = 0; i < classifier.maximum.size(); ++i) {
348
+ const float value = classifier.maximum[i];
349
+ std::printf(" %.6f", value);
350
+ if (classifier.name != "timer_v0.1" || i != 0) {
351
+ score = std::max(score, value);
352
+ }
353
+ }
354
+ if (score >= args.threshold) {
355
+ std::printf(" WAKEUP");
356
+ detected = true;
357
+ }
358
+ std::printf("\n");
359
+ }
360
+ std::printf("detected: %s\n", detected ? "true" : "false");
361
+ std::printf("audio_seconds: %.6f\n", audio_seconds);
362
+ std::printf("feature_seconds: %.6f\n", feature_seconds);
363
+ std::printf("npu_seconds: %.6f\n", npu_seconds);
364
+ std::printf("model_load_seconds: %.6f\n", model_load_seconds);
365
+ std::printf("inference_seconds: %.6f\n", inference_seconds);
366
+ std::printf("rtf: %.6f\n", rtf);
367
+ }
368
+ } // namespace
369
+
370
+ int main(int argc, char **argv) {
371
+ try {
372
+ const Args args = ParseArgs(argc, argv);
373
+ Run(args);
374
+ return 0;
375
+ } catch (const std::exception &error) {
376
+ std::fprintf(stderr, "ERROR: %s\n", error.what());
377
+ return 1;
378
+ }
379
+ }
cpp/run_batch_openwakeword_ax.sh ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ TARGET="${AXERA_TARGET:-AX650}"
6
+ AUDIO_DIR="${AUDIO_DIR:-${ROOT}/audio/openwakeword}"
7
+ THRESHOLD="${THRESHOLD:-0.5}"
8
+
9
+ case "${TARGET}" in
10
+ AX650)
11
+ TARGET_SLUG="ax650"
12
+ BINARY="${ROOT}/cpp/bin/openwakeword_ax650"
13
+ MODELS_DIR="${MODELS_DIR:-${ROOT}/models/650}"
14
+ BSP_LIB_DIR="${AXERA_BSP_LIB_DIR:-${AX650_BSP_LIB_DIR:-${ROOT}/cpp/toolchains/ax650n_bsp_sdk/msp/out/lib}}"
15
+ BUILD_SCRIPT="cpp/build_ax650.sh"
16
+ ;;
17
+ AX630C)
18
+ TARGET_SLUG="ax630c"
19
+ BINARY="${ROOT}/cpp/bin/openwakeword_ax630c"
20
+ MODELS_DIR="${MODELS_DIR:-${ROOT}/models/630C}"
21
+ BSP_LIB_DIR="${AXERA_BSP_LIB_DIR:-${AX630C_BSP_LIB_DIR:-${ROOT}/cpp/toolchains/ax620e_bsp_sdk/msp/out/arm64_glibc/lib}}"
22
+ BUILD_SCRIPT="cpp/build_ax630c.sh"
23
+ ;;
24
+ *)
25
+ echo "ERROR: AXERA_TARGET must be AX650 or AX630C" >&2
26
+ exit 2
27
+ ;;
28
+ esac
29
+
30
+ OUTPUT_DIR="${OUTPUT_DIR:-${ROOT}/outputs/cpp_${TARGET_SLUG}_batch}"
31
+ if [[ ! -x "${BINARY}" ]]; then
32
+ echo "ERROR: ${BINARY} is missing; run bash ${BUILD_SCRIPT} first" >&2
33
+ exit 2
34
+ fi
35
+ if [[ ! -d "${AUDIO_DIR}" ]]; then
36
+ echo "ERROR: audio directory does not exist: ${AUDIO_DIR}" >&2
37
+ exit 2
38
+ fi
39
+ for name in embedding_model alexa_v0.1 hey_jarvis_v0.1 hey_mycroft_v0.1 \
40
+ hey_rhasspy_v0.1 timer_v0.1 weather_v0.1; do
41
+ model="${MODELS_DIR}/openwakeword__${name}.axmodel"
42
+ if [[ ! -f "${model}" ]]; then
43
+ echo "ERROR: ${TARGET} model is missing: ${model}" >&2
44
+ exit 2
45
+ fi
46
+ done
47
+
48
+ mapfile -d '' AUDIO_FILES < <(
49
+ find "${AUDIO_DIR}" -maxdepth 1 -type f -iname '*.wav' -print0 | sort -z
50
+ )
51
+ if [[ "${#AUDIO_FILES[@]}" -eq 0 ]]; then
52
+ echo "ERROR: no WAV files found under ${AUDIO_DIR}" >&2
53
+ exit 2
54
+ fi
55
+
56
+ mkdir -p "${OUTPUT_DIR}/logs"
57
+ RESULTS="${OUTPUT_DIR}/results.tsv"
58
+ SUMMARY="${OUTPUT_DIR}/summary.txt"
59
+ printf 'file\taudio_seconds\tfeature_seconds\tnpu_seconds\tinference_seconds\trtf\tmodel_load_seconds\tdetections\n' > "${RESULTS}"
60
+
61
+ export LD_LIBRARY_PATH="${BSP_LIB_DIR}:${LD_LIBRARY_PATH:-}"
62
+ for audio in "${AUDIO_FILES[@]}"; do
63
+ name="$(basename "${audio}")"
64
+ stem="${name%.*}"
65
+ log="${OUTPUT_DIR}/logs/${stem}.log"
66
+ echo "[RUN] target=${TARGET} ${name} threshold=${THRESHOLD}"
67
+ "${BINARY}" \
68
+ --models-dir "${MODELS_DIR}" \
69
+ --mel-weights "${ROOT}/config/openwakeword_mel_weights.bin" \
70
+ --audio "${audio}" \
71
+ --threshold "${THRESHOLD}" > "${log}" 2>&1
72
+ cat "${log}"
73
+
74
+ field() {
75
+ awk -F': ' -v key="$1" '$1 == key {print $2; exit}' "${log}"
76
+ }
77
+ audio_seconds="$(field audio_seconds)"
78
+ feature_seconds="$(field feature_seconds)"
79
+ npu_seconds="$(field npu_seconds)"
80
+ inference_seconds="$(field inference_seconds)"
81
+ rtf="$(field rtf)"
82
+ model_load_seconds="$(field model_load_seconds)"
83
+ detections="$(awk '$NF == "WAKEUP" {print $1}' "${log}" | paste -sd ',' -)"
84
+ detections="${detections:-none}"
85
+
86
+ for value in "${audio_seconds}" "${feature_seconds}" "${npu_seconds}" \
87
+ "${inference_seconds}" "${rtf}" "${model_load_seconds}"; do
88
+ if [[ -z "${value}" ]]; then
89
+ echo "ERROR: timing field missing from ${log}" >&2
90
+ exit 1
91
+ fi
92
+ done
93
+ printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
94
+ "${name}" "${audio_seconds}" "${feature_seconds}" "${npu_seconds}" \
95
+ "${inference_seconds}" "${rtf}" "${model_load_seconds}" "${detections}" \
96
+ >> "${RESULTS}"
97
+ done
98
+
99
+ awk -F'\t' '
100
+ NR > 1 {
101
+ files += 1
102
+ audio += $2
103
+ feature += $3
104
+ npu += $4
105
+ inference += $5
106
+ model_load += $7
107
+ }
108
+ END {
109
+ printf "files: %d\n", files
110
+ printf "total_audio_seconds: %.6f\n", audio
111
+ printf "total_feature_seconds: %.6f\n", feature
112
+ printf "total_npu_seconds: %.6f\n", npu
113
+ printf "total_inference_seconds: %.6f\n", inference
114
+ printf "total_model_load_seconds: %.6f\n", model_load
115
+ printf "rtf: %.6f\n", inference / audio
116
+ }
117
+ ' "${RESULTS}" > "${SUMMARY}"
118
+
119
+ echo "[SUMMARY]"
120
+ cat "${SUMMARY}"
121
+ echo "results: ${RESULTS}"
122
+ echo "logs: ${OUTPUT_DIR}/logs"
cpp/run_batch_openwakeword_ax630c.sh ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ AXERA_TARGET=AX630C exec bash "${ROOT}/cpp/run_batch_openwakeword_ax.sh"
cpp/run_batch_openwakeword_ax650.sh ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ AXERA_TARGET=AX650 exec bash "${ROOT}/cpp/run_batch_openwakeword_ax.sh"
cpp/run_openwakeword_ax.sh ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ TARGET="${AXERA_TARGET:-AX650}"
6
+ AUDIO="${AUDIO:-${ROOT}/audio/openwakeword/alexa_test.wav}"
7
+ THRESHOLD="${THRESHOLD:-0.5}"
8
+
9
+ case "${TARGET}" in
10
+ AX650)
11
+ BINARY="${ROOT}/cpp/bin/openwakeword_ax650"
12
+ MODELS_DIR="${MODELS_DIR:-${ROOT}/models/650}"
13
+ BSP_LIB_DIR="${AXERA_BSP_LIB_DIR:-${AX650_BSP_LIB_DIR:-${ROOT}/cpp/toolchains/ax650n_bsp_sdk/msp/out/lib}}"
14
+ BUILD_SCRIPT="cpp/build_ax650.sh"
15
+ ;;
16
+ AX630C)
17
+ BINARY="${ROOT}/cpp/bin/openwakeword_ax630c"
18
+ MODELS_DIR="${MODELS_DIR:-${ROOT}/models/630C}"
19
+ BSP_LIB_DIR="${AXERA_BSP_LIB_DIR:-${AX630C_BSP_LIB_DIR:-${ROOT}/cpp/toolchains/ax620e_bsp_sdk/msp/out/arm64_glibc/lib}}"
20
+ BUILD_SCRIPT="cpp/build_ax630c.sh"
21
+ ;;
22
+ *)
23
+ echo "ERROR: AXERA_TARGET must be AX650 or AX630C" >&2
24
+ exit 2
25
+ ;;
26
+ esac
27
+
28
+ if [[ ! -x "${BINARY}" ]]; then
29
+ echo "ERROR: ${BINARY} is missing; run bash ${BUILD_SCRIPT} first" >&2
30
+ exit 2
31
+ fi
32
+ for name in embedding_model alexa_v0.1 hey_jarvis_v0.1 hey_mycroft_v0.1 \
33
+ hey_rhasspy_v0.1 timer_v0.1 weather_v0.1; do
34
+ model="${MODELS_DIR}/openwakeword__${name}.axmodel"
35
+ if [[ ! -f "${model}" ]]; then
36
+ echo "ERROR: ${TARGET} model is missing: ${model}" >&2
37
+ exit 2
38
+ fi
39
+ done
40
+
41
+ export LD_LIBRARY_PATH="${BSP_LIB_DIR}:${LD_LIBRARY_PATH:-}"
42
+ exec "${BINARY}" \
43
+ --models-dir "${MODELS_DIR}" \
44
+ --mel-weights "${ROOT}/config/openwakeword_mel_weights.bin" \
45
+ --audio "${AUDIO}" \
46
+ --threshold "${THRESHOLD}"
cpp/run_openwakeword_ax630c.sh ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ AXERA_TARGET=AX630C exec bash "${ROOT}/cpp/run_openwakeword_ax.sh"
cpp/run_openwakeword_ax650.sh ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ AXERA_TARGET=AX650 exec bash "${ROOT}/cpp/run_openwakeword_ax.sh"
cpp/src/engine_wrapper.cpp ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #include "engine_wrapper.hpp"
2
+
3
+ #include <cstdio>
4
+ #include <cstring>
5
+ #include <fstream>
6
+ #include <vector>
7
+
8
+ #include "ax_sys_api.h"
9
+
10
+ namespace {
11
+ constexpr int kAlignment = 128;
12
+
13
+ void FreeIo(AX_ENGINE_IO_T *io) {
14
+ for (AX_U32 i = 0; io->pInputs && i < io->nInputSize; ++i) {
15
+ if (io->pInputs[i].pVirAddr) {
16
+ AX_SYS_MemFree(io->pInputs[i].phyAddr, io->pInputs[i].pVirAddr);
17
+ }
18
+ }
19
+ for (AX_U32 i = 0; io->pOutputs && i < io->nOutputSize; ++i) {
20
+ if (io->pOutputs[i].pVirAddr) {
21
+ AX_SYS_MemFree(io->pOutputs[i].phyAddr, io->pOutputs[i].pVirAddr);
22
+ }
23
+ }
24
+ delete[] io->pInputs;
25
+ delete[] io->pOutputs;
26
+ std::memset(io, 0, sizeof(*io));
27
+ }
28
+ } // namespace
29
+
30
+ EngineWrapper::~EngineWrapper() { Release(); }
31
+
32
+ int EngineWrapper::Init(const std::string &path) {
33
+ std::ifstream input(path, std::ios::binary);
34
+ if (!input) {
35
+ std::fprintf(stderr, "Failed to read model: %s\n", path.c_str());
36
+ return -1;
37
+ }
38
+ input.seekg(0, std::ios::end);
39
+ const std::streamoff model_size = input.tellg();
40
+ if (model_size <= 0 || model_size > static_cast<std::streamoff>(UINT32_MAX)) {
41
+ std::fprintf(stderr, "Invalid model size: %s\n", path.c_str());
42
+ return -1;
43
+ }
44
+ input.seekg(0, std::ios::beg);
45
+ std::vector<char> model(static_cast<std::size_t>(model_size));
46
+ input.read(model.data(), model_size);
47
+ if (!input) {
48
+ std::fprintf(stderr, "Failed to read model: %s\n", path.c_str());
49
+ return -1;
50
+ }
51
+ const AX_S32 create_ret = AX_ENGINE_CreateHandle(
52
+ &handle_, model.data(), static_cast<AX_U32>(model.size()));
53
+ if (create_ret != 0 || !handle_) {
54
+ std::fprintf(stderr, "AX_ENGINE_CreateHandle failed: %s ret=0x%x\n",
55
+ path.c_str(), static_cast<unsigned>(create_ret));
56
+ return -1;
57
+ }
58
+ if (AX_ENGINE_CreateContext(handle_) != 0 ||
59
+ AX_ENGINE_GetIOInfo(handle_, &info_) != 0 || !info_) {
60
+ Release();
61
+ return -1;
62
+ }
63
+
64
+ io_.nInputSize = info_->nInputSize;
65
+ io_.nOutputSize = info_->nOutputSize;
66
+ io_.pInputs = new AX_ENGINE_IO_BUFFER_T[io_.nInputSize]{};
67
+ io_.pOutputs = new AX_ENGINE_IO_BUFFER_T[io_.nOutputSize]{};
68
+ AX_S8 input_tag[] = "KWS-INPUT";
69
+ AX_S8 output_tag[] = "KWS-OUTPUT";
70
+ for (AX_U32 i = 0; i < io_.nInputSize; ++i) {
71
+ io_.pInputs[i].nSize = info_->pInputs[i].nSize;
72
+ if (AX_SYS_MemAlloc(&io_.pInputs[i].phyAddr,
73
+ &io_.pInputs[i].pVirAddr, io_.pInputs[i].nSize,
74
+ kAlignment, input_tag) != 0) {
75
+ Release();
76
+ return -1;
77
+ }
78
+ const std::string name = info_->pInputs[i].pName;
79
+ inputs_[name] = static_cast<int>(i);
80
+ input_names_.push_back(name);
81
+ }
82
+ for (AX_U32 i = 0; i < io_.nOutputSize; ++i) {
83
+ io_.pOutputs[i].nSize = info_->pOutputs[i].nSize;
84
+ if (AX_SYS_MemAlloc(&io_.pOutputs[i].phyAddr,
85
+ &io_.pOutputs[i].pVirAddr, io_.pOutputs[i].nSize,
86
+ kAlignment, output_tag) != 0) {
87
+ Release();
88
+ return -1;
89
+ }
90
+ const std::string name = info_->pOutputs[i].pName;
91
+ outputs_[name] = static_cast<int>(i);
92
+ output_names_.push_back(name);
93
+ }
94
+ initialized_ = true;
95
+ std::printf("Loaded %s (inputs=%u outputs=%u)\n", path.c_str(),
96
+ io_.nInputSize, io_.nOutputSize);
97
+ return 0;
98
+ }
99
+
100
+ void EngineWrapper::Release() {
101
+ FreeIo(&io_);
102
+ if (handle_) AX_ENGINE_DestroyHandle(handle_);
103
+ initialized_ = false;
104
+ handle_ = nullptr;
105
+ info_ = nullptr;
106
+ inputs_.clear();
107
+ outputs_.clear();
108
+ input_names_.clear();
109
+ output_names_.clear();
110
+ }
111
+
112
+ int EngineWrapper::InputIndex(const std::string &name) const {
113
+ const auto it = inputs_.find(name);
114
+ return it == inputs_.end() ? -1 : it->second;
115
+ }
116
+
117
+ int EngineWrapper::OutputIndex(const std::string &name) const {
118
+ const auto it = outputs_.find(name);
119
+ return it == outputs_.end() ? -1 : it->second;
120
+ }
121
+
122
+ int EngineWrapper::SetInputByName(const std::string &name, const void *data,
123
+ std::size_t size) {
124
+ const int index = InputIndex(name);
125
+ if (!initialized_ || index < 0 || !data) return -1;
126
+ const std::size_t expected = io_.pInputs[index].nSize;
127
+ if (size != 0 && size != expected) return -1;
128
+ std::memcpy(io_.pInputs[index].pVirAddr, data, expected);
129
+ return 0;
130
+ }
131
+
132
+ int EngineWrapper::ZeroInputByName(const std::string &name) {
133
+ const int index = InputIndex(name);
134
+ if (!initialized_ || index < 0) return -1;
135
+ std::memset(io_.pInputs[index].pVirAddr, 0, io_.pInputs[index].nSize);
136
+ return 0;
137
+ }
138
+
139
+ int EngineWrapper::RunSync() {
140
+ if (!initialized_) return -1;
141
+ for (AX_U32 i = 0; i < io_.nInputSize; ++i) {
142
+ const AX_S32 flush_ret = AX_SYS_MflushCache(
143
+ io_.pInputs[i].phyAddr, io_.pInputs[i].pVirAddr,
144
+ io_.pInputs[i].nSize);
145
+ if (flush_ret != 0) {
146
+ std::fprintf(stderr, "AX_SYS_MflushCache failed input=%u ret=0x%x\n", i,
147
+ static_cast<unsigned>(flush_ret));
148
+ return flush_ret;
149
+ }
150
+ }
151
+ const AX_S32 ret = AX_ENGINE_RunSync(handle_, &io_);
152
+ if (ret != 0) {
153
+ std::fprintf(stderr, "AX_ENGINE_RunSync failed ret=0x%x\n",
154
+ static_cast<unsigned>(ret));
155
+ return ret;
156
+ }
157
+ for (AX_U32 i = 0; i < io_.nOutputSize; ++i) {
158
+ const AX_S32 invalidate_ret = AX_SYS_MinvalidateCache(
159
+ io_.pOutputs[i].phyAddr, io_.pOutputs[i].pVirAddr,
160
+ io_.pOutputs[i].nSize);
161
+ if (invalidate_ret != 0) {
162
+ std::fprintf(stderr,
163
+ "AX_SYS_MinvalidateCache failed output=%u ret=0x%x\n", i,
164
+ static_cast<unsigned>(invalidate_ret));
165
+ return invalidate_ret;
166
+ }
167
+ }
168
+ return 0;
169
+ }
170
+
171
+ int EngineWrapper::GetOutputByName(const std::string &name, void *data,
172
+ std::size_t size) const {
173
+ const int index = OutputIndex(name);
174
+ if (!initialized_ || index < 0 || !data) return -1;
175
+ const std::size_t expected = io_.pOutputs[index].nSize;
176
+ if (size != 0 && size != expected) return -1;
177
+ std::memcpy(data, io_.pOutputs[index].pVirAddr, expected);
178
+ return 0;
179
+ }
180
+
181
+ int EngineWrapper::GetInputSizeByName(const std::string &name) const {
182
+ const int index = InputIndex(name);
183
+ return index < 0 ? -1 : static_cast<int>(io_.pInputs[index].nSize);
184
+ }
185
+
186
+ int EngineWrapper::GetOutputSizeByName(const std::string &name) const {
187
+ const int index = OutputIndex(name);
188
+ return index < 0 ? -1 : static_cast<int>(io_.pOutputs[index].nSize);
189
+ }
190
+
191
+ const std::string &EngineWrapper::InputName(std::size_t index) const {
192
+ return input_names_.at(index);
193
+ }
194
+
195
+ const std::string &EngineWrapper::OutputName(std::size_t index) const {
196
+ return output_names_.at(index);
197
+ }
cpp/src/engine_wrapper.hpp ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #pragma once
2
+
3
+ #include <cstddef>
4
+ #include <string>
5
+ #include <unordered_map>
6
+ #include <vector>
7
+
8
+ #include "ax_engine_api.h"
9
+
10
+ class EngineWrapper {
11
+ public:
12
+ EngineWrapper() = default;
13
+ ~EngineWrapper();
14
+
15
+ EngineWrapper(const EngineWrapper &) = delete;
16
+ EngineWrapper &operator=(const EngineWrapper &) = delete;
17
+
18
+ int Init(const std::string &model_path);
19
+ void Release();
20
+ int SetInputByName(const std::string &name, const void *data,
21
+ std::size_t size = 0);
22
+ int ZeroInputByName(const std::string &name);
23
+ int RunSync();
24
+ int GetOutputByName(const std::string &name, void *data,
25
+ std::size_t size = 0) const;
26
+ int GetInputSizeByName(const std::string &name) const;
27
+ int GetOutputSizeByName(const std::string &name) const;
28
+ const std::string &InputName(std::size_t index) const;
29
+ const std::string &OutputName(std::size_t index) const;
30
+
31
+ private:
32
+ int InputIndex(const std::string &name) const;
33
+ int OutputIndex(const std::string &name) const;
34
+
35
+ bool initialized_ = false;
36
+ AX_ENGINE_HANDLE handle_ = nullptr;
37
+ AX_ENGINE_IO_INFO_T *info_ = nullptr;
38
+ AX_ENGINE_IO_T io_{};
39
+ std::unordered_map<std::string, int> inputs_;
40
+ std::unordered_map<std::string, int> outputs_;
41
+ std::vector<std::string> input_names_;
42
+ std::vector<std::string> output_names_;
43
+ };
cpp/src/wav_reader.hpp ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #pragma once
2
+
3
+ #include <cstdint>
4
+ #include <cstring>
5
+ #include <fstream>
6
+ #include <stdexcept>
7
+ #include <string>
8
+ #include <vector>
9
+
10
+ struct PcmWav {
11
+ int sample_rate = 0;
12
+ std::vector<int16_t> samples;
13
+ };
14
+
15
+ inline uint16_t ReadLe16(std::istream &input) {
16
+ uint8_t bytes[2]{};
17
+ input.read(reinterpret_cast<char *>(bytes), 2);
18
+ return static_cast<uint16_t>(bytes[0]) |
19
+ (static_cast<uint16_t>(bytes[1]) << 8);
20
+ }
21
+
22
+ inline uint32_t ReadLe32(std::istream &input) {
23
+ uint8_t bytes[4]{};
24
+ input.read(reinterpret_cast<char *>(bytes), 4);
25
+ return static_cast<uint32_t>(bytes[0]) |
26
+ (static_cast<uint32_t>(bytes[1]) << 8) |
27
+ (static_cast<uint32_t>(bytes[2]) << 16) |
28
+ (static_cast<uint32_t>(bytes[3]) << 24);
29
+ }
30
+
31
+ inline PcmWav ReadPcmWav(const std::string &path) {
32
+ std::ifstream input(path, std::ios::binary);
33
+ if (!input) throw std::runtime_error("Cannot open WAV: " + path);
34
+ char riff[4]{};
35
+ char wave[4]{};
36
+ input.read(riff, 4);
37
+ ReadLe32(input);
38
+ input.read(wave, 4);
39
+ if (std::memcmp(riff, "RIFF", 4) != 0 ||
40
+ std::memcmp(wave, "WAVE", 4) != 0) {
41
+ throw std::runtime_error("Invalid RIFF/WAVE file: " + path);
42
+ }
43
+
44
+ uint16_t format = 0;
45
+ uint16_t channels = 0;
46
+ uint16_t bits = 0;
47
+ uint32_t sample_rate = 0;
48
+ std::vector<uint8_t> data;
49
+ while (input && (!format || data.empty())) {
50
+ char id[4]{};
51
+ input.read(id, 4);
52
+ if (!input) break;
53
+ const uint32_t size = ReadLe32(input);
54
+ if (std::memcmp(id, "fmt ", 4) == 0) {
55
+ if (size < 16) throw std::runtime_error("Invalid WAV fmt chunk");
56
+ format = ReadLe16(input);
57
+ channels = ReadLe16(input);
58
+ sample_rate = ReadLe32(input);
59
+ ReadLe32(input);
60
+ ReadLe16(input);
61
+ bits = ReadLe16(input);
62
+ input.seekg(size - 16, std::ios::cur);
63
+ } else if (std::memcmp(id, "data", 4) == 0) {
64
+ data.resize(size);
65
+ input.read(reinterpret_cast<char *>(data.data()), size);
66
+ } else {
67
+ input.seekg(size, std::ios::cur);
68
+ }
69
+ if (size & 1U) input.seekg(1, std::ios::cur);
70
+ }
71
+ if (format != 1 || channels != 1 || bits != 16 || sample_rate == 0 ||
72
+ data.empty() || data.size() % sizeof(int16_t) != 0) {
73
+ throw std::runtime_error("Expected mono 16-bit PCM WAV: " + path);
74
+ }
75
+ PcmWav result;
76
+ result.sample_rate = static_cast<int>(sample_rate);
77
+ result.samples.resize(data.size() / sizeof(int16_t));
78
+ std::memcpy(result.samples.data(), data.data(), data.size());
79
+ return result;
80
+ }
manifest.json ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "target_hardware": "AX630C/AX650",
3
+ "runtime": "axengine",
4
+ "axmodel_count": 8,
5
+ "files": [
6
+ {
7
+ "path": ".gitattributes",
8
+ "size_bytes": 179,
9
+ "sha256": "7b33e03d32f81ffc4bf98649921ff61fdbdfcff7477345dffc00bd5472351478"
10
+ },
11
+ {
12
+ "path": ".gitignore",
13
+ "size_bytes": 74,
14
+ "sha256": "496c11fcbfe22bd803d39235e6539b04e2fc8b0e7d747479302176ae84ae181a"
15
+ },
16
+ {
17
+ "path": "README.md",
18
+ "size_bytes": 3689,
19
+ "sha256": "7d609fad6c1e359a68551ce097e64e047056739be92a6311fd701ead738f5cd4"
20
+ },
21
+ {
22
+ "path": "audio/openwakeword/alexa_test.wav",
23
+ "size_bytes": 20044,
24
+ "sha256": "915dba88a2b936f3ba3ee91affcdbe9041ffbe532613d3aca58d8a15447fbd81"
25
+ },
26
+ {
27
+ "path": "audio/openwakeword/hey_jane.wav",
28
+ "size_bytes": 75820,
29
+ "sha256": "cf1b938bd8ca1a7e9bb042665b9cf92a772a6673c6b98e264b27176ea4d3f443"
30
+ },
31
+ {
32
+ "path": "audio/openwakeword/hey_mycroft_test.wav",
33
+ "size_bytes": 30508,
34
+ "sha256": "8d728ce1dbd54a97c6cddf0c80acec3113285407a967368f4abe476119c1a8c6"
35
+ },
36
+ {
37
+ "path": "config.json",
38
+ "size_bytes": 207,
39
+ "sha256": "67d68940a1ef8586cad523779ea86876b3bd2362c593c2d4e74ef6dd0557f892"
40
+ },
41
+ {
42
+ "path": "config/openwakeword_mel_weights.bin",
43
+ "size_bytes": 1085608,
44
+ "sha256": "f5d3a5bafe0def6c1efb4874d724c272d78edadada6652ef885059db2ba147a7"
45
+ },
46
+ {
47
+ "path": "config/openwakeword_mel_weights.npz",
48
+ "size_bytes": 1086554,
49
+ "sha256": "a3de443a7383dc51a4bf8b1d2dc9bbc767f7e33e17af617d993700a5f9947ed6"
50
+ },
51
+ {
52
+ "path": "cpp/CMakeLists.txt",
53
+ "size_bytes": 1948,
54
+ "sha256": "89980c1323031f9b1689562f493325b5ad1e1bd7960f85bd1e932ba097d42360"
55
+ },
56
+ {
57
+ "path": "cpp/README.md",
58
+ "size_bytes": 2197,
59
+ "sha256": "8b1abab04850f18bbd980b6f4de0f42334bfc1086fac97607606c7f6c914422f"
60
+ },
61
+ {
62
+ "path": "cpp/bin/openwakeword_ax630c",
63
+ "size_bytes": 39688,
64
+ "sha256": "309405a685c6928975ec732525ab069316bf43a3495de80c99d6aa566e6c37a3"
65
+ },
66
+ {
67
+ "path": "cpp/bin/openwakeword_ax650",
68
+ "size_bytes": 39688,
69
+ "sha256": "cea03dc9b00f40c4508bb82ee8e1dfa2a8775088f4e193fc413ba781e4607984"
70
+ },
71
+ {
72
+ "path": "cpp/build_ax630c.sh",
73
+ "size_bytes": 1299,
74
+ "sha256": "4d594ea058d6563fa9177698f0af0e3409959672f399d9729a378de2a47ebc09"
75
+ },
76
+ {
77
+ "path": "cpp/build_ax650.sh",
78
+ "size_bytes": 1267,
79
+ "sha256": "fbf962de8ed6433fef6b2aac7332bf84e18720f01b6bf7955e9d32eb409f8e01"
80
+ },
81
+ {
82
+ "path": "cpp/download_bsp.sh",
83
+ "size_bytes": 1290,
84
+ "sha256": "0c5afbc290c6d8341df2e8b356626642935a5c9971b0b6b603d30a9b32e69f7a"
85
+ },
86
+ {
87
+ "path": "cpp/openwakeword_ax.cpp",
88
+ "size_bytes": 14122,
89
+ "sha256": "6c4ef28769f1321624819a6c915e43d2127989cefc58a39e8600631d57f98d8d"
90
+ },
91
+ {
92
+ "path": "cpp/run_batch_openwakeword_ax.sh",
93
+ "size_bytes": 3938,
94
+ "sha256": "5c4a4d3d9e399fb98d0a9b794389fb03ce585cfe9e24d884772052fd1f948c88"
95
+ },
96
+ {
97
+ "path": "cpp/run_batch_openwakeword_ax630c.sh",
98
+ "size_bytes": 168,
99
+ "sha256": "f6545c7db37deb52ea727c8f641f000a2c7c1805fc7b40fbc06ec6841b3a259c"
100
+ },
101
+ {
102
+ "path": "cpp/run_batch_openwakeword_ax650.sh",
103
+ "size_bytes": 167,
104
+ "sha256": "8711184e6284d9de1ac5b11fdfb23de7c02b9667596d3cb060be702b38b6a858"
105
+ },
106
+ {
107
+ "path": "cpp/run_openwakeword_ax.sh",
108
+ "size_bytes": 1510,
109
+ "sha256": "2cf5332a4a6457b1356609fd66e8777d3cc89cc871aa176695cd868d4667f948"
110
+ },
111
+ {
112
+ "path": "cpp/run_openwakeword_ax630c.sh",
113
+ "size_bytes": 162,
114
+ "sha256": "f2bd3486e9e8db53835a6d09f9f6a73769342f2a032939842559268342264cc8"
115
+ },
116
+ {
117
+ "path": "cpp/run_openwakeword_ax650.sh",
118
+ "size_bytes": 161,
119
+ "sha256": "4ac47b1d3535d6351e595ec8c4594d9a696ae22e4224076f654bb2969f9e7dd8"
120
+ },
121
+ {
122
+ "path": "cpp/src/engine_wrapper.cpp",
123
+ "size_bytes": 6489,
124
+ "sha256": "d5ee6d04eaaa6cff492b1b2cd8c60d8519b630b1fc66fd93ee4085cf2121f4ea"
125
+ },
126
+ {
127
+ "path": "cpp/src/engine_wrapper.hpp",
128
+ "size_bytes": 1313,
129
+ "sha256": "6a160d748e4fda7c4a69cd2113c36f36c66bf7292e32ffe3df966d24eac51ede"
130
+ },
131
+ {
132
+ "path": "cpp/src/wav_reader.hpp",
133
+ "size_bytes": 2447,
134
+ "sha256": "c4890925751e81c78a8429e06fd7fc1e35673d4c9b84989d0f9fd64a364b4d34"
135
+ },
136
+ {
137
+ "path": "models/630C/.gitkeep",
138
+ "size_bytes": 1,
139
+ "sha256": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"
140
+ },
141
+ {
142
+ "path": "models/650/openwakeword__alexa_v0.1.axmodel",
143
+ "size_bytes": 241041,
144
+ "sha256": "3595cd242ddf38aca019042d593c45ac1e90107a6d54836ea5593e3dc55f9784"
145
+ },
146
+ {
147
+ "path": "models/650/openwakeword__embedding_model.axmodel",
148
+ "size_bytes": 451068,
149
+ "sha256": "a9c579200c4e9c076d86181343cdbe5cf2c786016cd60b333463784b5c2fa9b4"
150
+ },
151
+ {
152
+ "path": "models/650/openwakeword__hey_jarvis_v0.1.axmodel",
153
+ "size_bytes": 365733,
154
+ "sha256": "c878a73a9f187505a9496c0b12c479c94de9102d7851bc31a7d75de15a7298ef"
155
+ },
156
+ {
157
+ "path": "models/650/openwakeword__hey_mycroft_v0.1.axmodel",
158
+ "size_bytes": 247553,
159
+ "sha256": "1ff391da1aefc04f20df5a24be0d063941459caeef0d5e6d6892c5561049fdf2"
160
+ },
161
+ {
162
+ "path": "models/650/openwakeword__hey_rhasspy_v0.1.axmodel",
163
+ "size_bytes": 64073,
164
+ "sha256": "4e13babcf713a3b62ca871bd6d7df50315a09da17ab35c294fa41034baed4a69"
165
+ },
166
+ {
167
+ "path": "models/650/openwakeword__melspectrogram.axmodel",
168
+ "size_bytes": 340028,
169
+ "sha256": "9ea2c5293ef3c05c3c5681b947f270d3fe77a0464308d67cf43d9090e9e7fb5f"
170
+ },
171
+ {
172
+ "path": "models/650/openwakeword__timer_v0.1.axmodel",
173
+ "size_bytes": 482100,
174
+ "sha256": "4b7c243daadeed2e7ac2b398e5ee13fd7a3c40a5a857ad835809cbfc5d687b5a"
175
+ },
176
+ {
177
+ "path": "models/650/openwakeword__weather_v0.1.axmodel",
178
+ "size_bytes": 320785,
179
+ "sha256": "f94fe025de680f18cfb55b7ca8f7cfabb94624f96b9f51424d73e75a9e0b471c"
180
+ },
181
+ {
182
+ "path": "outputs/.gitkeep",
183
+ "size_bytes": 1,
184
+ "sha256": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"
185
+ },
186
+ {
187
+ "path": "reference/local_inference_results.json",
188
+ "size_bytes": 15035,
189
+ "sha256": "3e4319055ee7b4a7fae93203411acdf71d5596427c63beb2ee07b749ca090a1f"
190
+ },
191
+ {
192
+ "path": "requirements.txt",
193
+ "size_bytes": 12,
194
+ "sha256": "a83a2a4ed7ab2e022b9b8a83cfa2b8cf52cc243006c597e8b96f90a8365590ac"
195
+ },
196
+ {
197
+ "path": "scripts/openwakeword_ax.py",
198
+ "size_bytes": 11767,
199
+ "sha256": "4ad53f63027b7a650cbd95c6a038cde28d7a047b16cc25b5b54aa4b3b1ec865d"
200
+ },
201
+ {
202
+ "path": "scripts/openwakeword_ax630c.py",
203
+ "size_bytes": 327,
204
+ "sha256": "8f00e9bb9edc80dcff413c652eb1878b5174529a6d33a83e1a4f39eab52e6ed2"
205
+ },
206
+ {
207
+ "path": "scripts/openwakeword_ax650.py",
208
+ "size_bytes": 324,
209
+ "sha256": "c9b37219849cca038434abeae54d3a5d2f0fdf585126fb8649e7ecffaa643ee4"
210
+ },
211
+ {
212
+ "path": "scripts/runtime.py",
213
+ "size_bytes": 5296,
214
+ "sha256": "99b9f762b630a186cf3b010f57b11987564cb76e41ab403e78d3ada57729b0fb"
215
+ }
216
+ ]
217
+ }
models/630C/.gitkeep ADDED
@@ -0,0 +1 @@
 
 
1
+
models/630C/openwakeword__alexa_v0.1.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7115c3e39908999edc3af0dd15baddc6c6e3093f25007a3dcebf18320ef81177
3
+ size 226487
models/630C/openwakeword__embedding_model.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1dd2250f109865d94aa81c56f0aebdc90ca7dc36c5a80f2c13cce220b9ddd554
3
+ size 376612
models/630C/openwakeword__hey_jarvis_v0.1.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f2d5ab8698daf6ea715b2189a71b0cae3593191780cd29fc703d896aa67b0789
3
+ size 338337
models/630C/openwakeword__hey_mycroft_v0.1.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:34627c22ef7aaf915b88516a3a17d18631fe92f8945c734b57ef6b1232307b73
3
+ size 229479
models/630C/openwakeword__hey_rhasspy_v0.1.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:721f58b213a2951e52a80e45e3a1bb96fb761f631157dacb8e5f3aa7591aac26
3
+ size 61607
models/630C/openwakeword__melspectrogram.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d3bb40d15f6af7ba3b0aa06702a8bd428ab7a871e28dc6557487fae7cd35e2b7
3
+ size 321576
models/630C/openwakeword__timer_v0.1.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:954b46936f4fcee8694379812e0f89d00cea4b8d4f9ec0cb0632ce41a22d3c5b
3
+ size 450738
models/630C/openwakeword__weather_v0.1.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2e597cc394a4ab34c5b1d9f777d366cb19053b07ec6ff873b163f306fb57a215
3
+ size 300175
models/650/openwakeword__alexa_v0.1.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3595cd242ddf38aca019042d593c45ac1e90107a6d54836ea5593e3dc55f9784
3
+ size 241041
models/650/openwakeword__embedding_model.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a9c579200c4e9c076d86181343cdbe5cf2c786016cd60b333463784b5c2fa9b4
3
+ size 451068
models/650/openwakeword__hey_jarvis_v0.1.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c878a73a9f187505a9496c0b12c479c94de9102d7851bc31a7d75de15a7298ef
3
+ size 365733
models/650/openwakeword__hey_mycroft_v0.1.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1ff391da1aefc04f20df5a24be0d063941459caeef0d5e6d6892c5561049fdf2
3
+ size 247553
models/650/openwakeword__hey_rhasspy_v0.1.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4e13babcf713a3b62ca871bd6d7df50315a09da17ab35c294fa41034baed4a69
3
+ size 64073
models/650/openwakeword__melspectrogram.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9ea2c5293ef3c05c3c5681b947f270d3fe77a0464308d67cf43d9090e9e7fb5f
3
+ size 340028
models/650/openwakeword__timer_v0.1.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4b7c243daadeed2e7ac2b398e5ee13fd7a3c40a5a857ad835809cbfc5d687b5a
3
+ size 482100
models/650/openwakeword__weather_v0.1.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f94fe025de680f18cfb55b7ca8f7cfabb94624f96b9f51424d73e75a9e0b471c
3
+ size 320785
outputs/.gitkeep ADDED
@@ -0,0 +1 @@
 
 
1
+
outputs/cpp_batch/logs/alexa_test.log ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Loaded /mnt/huyuan/workspace/kws_quant/huggingface/openwakeword-axera-inference/models/openwakeword__embedding_model.axmodel (inputs=1 outputs=1)
2
+ Loaded /mnt/huyuan/workspace/kws_quant/huggingface/openwakeword-axera-inference/models/openwakeword__alexa_v0.1.axmodel (inputs=1 outputs=1)
3
+ Loaded /mnt/huyuan/workspace/kws_quant/huggingface/openwakeword-axera-inference/models/openwakeword__hey_jarvis_v0.1.axmodel (inputs=1 outputs=1)
4
+ Loaded /mnt/huyuan/workspace/kws_quant/huggingface/openwakeword-axera-inference/models/openwakeword__hey_mycroft_v0.1.axmodel (inputs=1 outputs=1)
5
+ Loaded /mnt/huyuan/workspace/kws_quant/huggingface/openwakeword-axera-inference/models/openwakeword__hey_rhasspy_v0.1.axmodel (inputs=1 outputs=1)
6
+ Loaded /mnt/huyuan/workspace/kws_quant/huggingface/openwakeword-axera-inference/models/openwakeword__timer_v0.1.axmodel (inputs=1 outputs=1)
7
+ Loaded /mnt/huyuan/workspace/kws_quant/huggingface/openwakeword-axera-inference/models/openwakeword__weather_v0.1.axmodel (inputs=1 outputs=1)
8
+
9
+ openWakeWord C++ inference complete
10
+ audio: audio/openwakeword/alexa_test.wav
11
+ chunks: 8
12
+ threshold: 0.500
13
+ alexa_v0.1 1.000000 WAKEUP
14
+ hey_jarvis_v0.1 0.000005
15
+ hey_mycroft_v0.1 0.000839
16
+ hey_rhasspy_v0.1 0.001408
17
+ timer_v0.1 0.997989 0.011197 0.011197 0.012590 0.011609 0.011197 0.011197
18
+ weather_v0.1 0.010740
19
+ detected: true
20
+ audio_seconds: 0.625000
21
+ feature_seconds: 0.038823
22
+ npu_seconds: 0.005308
23
+ model_load_seconds: 0.310009
24
+ inference_seconds: 0.044567
25
+ rtf: 0.071308
outputs/cpp_batch/logs/hey_jane.log ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Loaded /mnt/huyuan/workspace/kws_quant/huggingface/openwakeword-axera-inference/models/openwakeword__embedding_model.axmodel (inputs=1 outputs=1)
2
+ Loaded /mnt/huyuan/workspace/kws_quant/huggingface/openwakeword-axera-inference/models/openwakeword__alexa_v0.1.axmodel (inputs=1 outputs=1)
3
+ Loaded /mnt/huyuan/workspace/kws_quant/huggingface/openwakeword-axera-inference/models/openwakeword__hey_jarvis_v0.1.axmodel (inputs=1 outputs=1)
4
+ Loaded /mnt/huyuan/workspace/kws_quant/huggingface/openwakeword-axera-inference/models/openwakeword__hey_mycroft_v0.1.axmodel (inputs=1 outputs=1)
5
+ Loaded /mnt/huyuan/workspace/kws_quant/huggingface/openwakeword-axera-inference/models/openwakeword__hey_rhasspy_v0.1.axmodel (inputs=1 outputs=1)
6
+ Loaded /mnt/huyuan/workspace/kws_quant/huggingface/openwakeword-axera-inference/models/openwakeword__timer_v0.1.axmodel (inputs=1 outputs=1)
7
+ Loaded /mnt/huyuan/workspace/kws_quant/huggingface/openwakeword-axera-inference/models/openwakeword__weather_v0.1.axmodel (inputs=1 outputs=1)
8
+
9
+ openWakeWord C++ inference complete
10
+ audio: audio/openwakeword/hey_jane.wav
11
+ chunks: 30
12
+ threshold: 0.500
13
+ alexa_v0.1 0.001312
14
+ hey_jarvis_v0.1 0.017346
15
+ hey_mycroft_v0.1 0.000000
16
+ hey_rhasspy_v0.1 0.004955
17
+ timer_v0.1 1.000000 0.009932 0.008854 0.009936 0.009849 0.008854 0.021674
18
+ weather_v0.1 0.387996
19
+ detected: false
20
+ audio_seconds: 2.368000
21
+ feature_seconds: 0.145612
22
+ npu_seconds: 0.022135
23
+ model_load_seconds: 0.653880
24
+ inference_seconds: 0.169402
25
+ rtf: 0.071538
outputs/cpp_batch/logs/hey_mycroft_test.log ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Loaded /mnt/huyuan/workspace/kws_quant/huggingface/openwakeword-axera-inference/models/openwakeword__embedding_model.axmodel (inputs=1 outputs=1)
2
+ Loaded /mnt/huyuan/workspace/kws_quant/huggingface/openwakeword-axera-inference/models/openwakeword__alexa_v0.1.axmodel (inputs=1 outputs=1)
3
+ Loaded /mnt/huyuan/workspace/kws_quant/huggingface/openwakeword-axera-inference/models/openwakeword__hey_jarvis_v0.1.axmodel (inputs=1 outputs=1)
4
+ Loaded /mnt/huyuan/workspace/kws_quant/huggingface/openwakeword-axera-inference/models/openwakeword__hey_mycroft_v0.1.axmodel (inputs=1 outputs=1)
5
+ Loaded /mnt/huyuan/workspace/kws_quant/huggingface/openwakeword-axera-inference/models/openwakeword__hey_rhasspy_v0.1.axmodel (inputs=1 outputs=1)
6
+ Loaded /mnt/huyuan/workspace/kws_quant/huggingface/openwakeword-axera-inference/models/openwakeword__timer_v0.1.axmodel (inputs=1 outputs=1)
7
+ Loaded /mnt/huyuan/workspace/kws_quant/huggingface/openwakeword-axera-inference/models/openwakeword__weather_v0.1.axmodel (inputs=1 outputs=1)
8
+
9
+ openWakeWord C++ inference complete
10
+ audio: audio/openwakeword/hey_mycroft_test.wav
11
+ chunks: 12
12
+ threshold: 0.500
13
+ alexa_v0.1 0.001801
14
+ hey_jarvis_v0.1 0.000025
15
+ hey_mycroft_v0.1 1.000000 WAKEUP
16
+ hey_rhasspy_v0.1 0.002780
17
+ timer_v0.1 0.998750 0.010560 0.010560 0.011588 0.011225 0.010560 0.010560
18
+ weather_v0.1 0.279959
19
+ detected: true
20
+ audio_seconds: 0.952000
21
+ feature_seconds: 0.058748
22
+ npu_seconds: 0.007945
23
+ model_load_seconds: 0.862838
24
+ inference_seconds: 0.067336
25
+ rtf: 0.070731
outputs/cpp_batch/results.tsv ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ file audio_seconds feature_seconds npu_seconds inference_seconds rtf model_load_seconds detections
2
+ alexa_test.wav 0.625000 0.038823 0.005308 0.044567 0.071308 0.310009 alexa_v0.1
3
+ hey_jane.wav 2.368000 0.145612 0.022135 0.169402 0.071538 0.653880 none
4
+ hey_mycroft_test.wav 0.952000 0.058748 0.007945 0.067336 0.070731 0.862838 hey_mycroft_v0.1
outputs/cpp_batch/summary.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ files: 3
2
+ total_audio_seconds: 3.945000
3
+ total_feature_seconds: 0.243183
4
+ total_npu_seconds: 0.035388
5
+ total_inference_seconds: 0.281305
6
+ total_model_load_seconds: 1.826727
7
+ rtf: 0.071307
reference/local_inference_results.json ADDED
@@ -0,0 +1,497 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "environment": {
3
+ "python": "3.12.0",
4
+ "numpy": "1.26.4",
5
+ "onnxruntime": "1.23.1",
6
+ "platform": "Linux-5.15.0-94-generic-x86_64-with-glibc2.35"
7
+ },
8
+ "openwakeword": {
9
+ "pipeline": "fixed 80 ms streaming chunks with 30 ms PCM history",
10
+ "clips": [
11
+ {
12
+ "audio": "openWakeWord/tests/data/alexa_test.wav",
13
+ "sample_rate": 16000,
14
+ "sample_count": 10240,
15
+ "frame_count": 8,
16
+ "initial_context": "480 zero PCM samples for fixed [1,1760] mel input",
17
+ "stage_metrics": {
18
+ "melspectrogram": {
19
+ "shape_match": true,
20
+ "comparisons": 8,
21
+ "minimum_cosine": 0.9999999999999984,
22
+ "maximum_mse": 2.3590018827235326e-11,
23
+ "maximum_abs": 1.52587890625e-05
24
+ },
25
+ "embedding": {
26
+ "shape_match": true,
27
+ "comparisons": 8,
28
+ "minimum_cosine": 0.9999999999996391,
29
+ "maximum_mse": 2.417571698387633e-10,
30
+ "maximum_abs": 3.814697265625e-05
31
+ },
32
+ "classifiers": {
33
+ "shape_match": true,
34
+ "comparisons": 48,
35
+ "minimum_cosine": 0.9999999999999992,
36
+ "maximum_mse": 7.993605777301127e-15,
37
+ "maximum_abs": 1.1920928955078125e-07
38
+ }
39
+ },
40
+ "source_max_scores": {
41
+ "alexa_v0.1.onnx": [
42
+ 1.0
43
+ ],
44
+ "hey_jarvis_v0.1.onnx": [
45
+ 4.9173831939697266e-06
46
+ ],
47
+ "hey_mycroft_v0.1.onnx": [
48
+ 0.0007033348083496094
49
+ ],
50
+ "hey_rhasspy_v0.1.onnx": [
51
+ 0.0013944804668426514
52
+ ],
53
+ "timer_v0.1.onnx": [
54
+ 0.9979174733161926,
55
+ 0.011717854999005795,
56
+ 0.011717854999005795,
57
+ 0.01298792939633131,
58
+ 0.012006871402263641,
59
+ 0.011717854999005795,
60
+ 0.011717854999005795
61
+ ],
62
+ "weather_v0.1.onnx": [
63
+ 0.011630207300186157
64
+ ]
65
+ },
66
+ "static_max_scores": {
67
+ "alexa_v0.1.onnx": [
68
+ 1.0
69
+ ],
70
+ "hey_jarvis_v0.1.onnx": [
71
+ 4.947185516357422e-06
72
+ ],
73
+ "hey_mycroft_v0.1.onnx": [
74
+ 0.0007033348083496094
75
+ ],
76
+ "hey_rhasspy_v0.1.onnx": [
77
+ 0.0013944804668426514
78
+ ],
79
+ "timer_v0.1.onnx": [
80
+ 0.9979174733161926,
81
+ 0.011717865243554115,
82
+ 0.011717865243554115,
83
+ 0.012987940572202206,
84
+ 0.012006882578134537,
85
+ 0.011717865243554115,
86
+ 0.011717865243554115
87
+ ],
88
+ "weather_v0.1.onnx": [
89
+ 0.011630237102508545
90
+ ]
91
+ },
92
+ "expected_model": "alexa_v0.1.onnx",
93
+ "expected_score": 1.0,
94
+ "expected_detected": true,
95
+ "numerical_match": true,
96
+ "passed": true
97
+ },
98
+ {
99
+ "audio": "openWakeWord/tests/data/hey_mycroft_test.wav",
100
+ "sample_rate": 16000,
101
+ "sample_count": 15360,
102
+ "frame_count": 12,
103
+ "initial_context": "480 zero PCM samples for fixed [1,1760] mel input",
104
+ "stage_metrics": {
105
+ "melspectrogram": {
106
+ "shape_match": true,
107
+ "comparisons": 12,
108
+ "minimum_cosine": 0.9999999999999982,
109
+ "maximum_mse": 2.4101609596982598e-11,
110
+ "maximum_abs": 1.52587890625e-05
111
+ },
112
+ "embedding": {
113
+ "shape_match": true,
114
+ "comparisons": 12,
115
+ "minimum_cosine": 0.9999999999998549,
116
+ "maximum_mse": 1.3607677947409988e-10,
117
+ "maximum_abs": 4.1961669921875e-05
118
+ },
119
+ "classifiers": {
120
+ "shape_match": true,
121
+ "comparisons": 72,
122
+ "minimum_cosine": 0.9999999999999998,
123
+ "maximum_mse": 1.9454660105111543e-11,
124
+ "maximum_abs": 4.410743713378906e-06
125
+ }
126
+ },
127
+ "source_max_scores": {
128
+ "alexa_v0.1.onnx": [
129
+ 0.0016120076179504395
130
+ ],
131
+ "hey_jarvis_v0.1.onnx": [
132
+ 2.256035804748535e-05
133
+ ],
134
+ "hey_mycroft_v0.1.onnx": [
135
+ 1.0
136
+ ],
137
+ "hey_rhasspy_v0.1.onnx": [
138
+ 0.0026790201663970947
139
+ ],
140
+ "timer_v0.1.onnx": [
141
+ 0.9988803267478943,
142
+ 0.011138674803078175,
143
+ 0.011138674803078175,
144
+ 0.012048495002090931,
145
+ 0.011720946989953518,
146
+ 0.011138674803078175,
147
+ 0.011138674803078175
148
+ ],
149
+ "weather_v0.1.onnx": [
150
+ 0.18771854043006897
151
+ ]
152
+ },
153
+ "static_max_scores": {
154
+ "alexa_v0.1.onnx": [
155
+ 0.0016120672225952148
156
+ ],
157
+ "hey_jarvis_v0.1.onnx": [
158
+ 2.2590160369873047e-05
159
+ ],
160
+ "hey_mycroft_v0.1.onnx": [
161
+ 1.0
162
+ ],
163
+ "hey_rhasspy_v0.1.onnx": [
164
+ 0.0026790201663970947
165
+ ],
166
+ "timer_v0.1.onnx": [
167
+ 0.9988803267478943,
168
+ 0.011138674803078175,
169
+ 0.011138674803078175,
170
+ 0.012048495002090931,
171
+ 0.011720946989953518,
172
+ 0.011138674803078175,
173
+ 0.011138674803078175
174
+ ],
175
+ "weather_v0.1.onnx": [
176
+ 0.18771809339523315
177
+ ]
178
+ },
179
+ "expected_model": "hey_mycroft_v0.1.onnx",
180
+ "expected_score": 1.0,
181
+ "expected_detected": true,
182
+ "numerical_match": true,
183
+ "passed": true
184
+ },
185
+ {
186
+ "audio": "openWakeWord/tests/data/hey_jane.wav",
187
+ "sample_rate": 16000,
188
+ "sample_count": 38400,
189
+ "frame_count": 30,
190
+ "initial_context": "480 zero PCM samples for fixed [1,1760] mel input",
191
+ "stage_metrics": {
192
+ "melspectrogram": {
193
+ "shape_match": true,
194
+ "comparisons": 30,
195
+ "minimum_cosine": 0.9999999999999951,
196
+ "maximum_mse": 1.836042429204099e-11,
197
+ "maximum_abs": 1.52587890625e-05
198
+ },
199
+ "embedding": {
200
+ "shape_match": true,
201
+ "comparisons": 30,
202
+ "minimum_cosine": 0.9999999999996753,
203
+ "maximum_mse": 1.5618928372873597e-10,
204
+ "maximum_abs": 3.910064697265625e-05
205
+ },
206
+ "classifiers": {
207
+ "shape_match": true,
208
+ "comparisons": 180,
209
+ "minimum_cosine": 0.0,
210
+ "maximum_mse": 4.3520742565306136e-14,
211
+ "maximum_abs": 2.086162567138672e-07
212
+ }
213
+ },
214
+ "source_max_scores": {
215
+ "alexa_v0.1.onnx": [
216
+ 0.001211315393447876
217
+ ],
218
+ "hey_jarvis_v0.1.onnx": [
219
+ 0.0183732807636261
220
+ ],
221
+ "hey_mycroft_v0.1.onnx": [
222
+ 3.1888484954833984e-06
223
+ ],
224
+ "hey_rhasspy_v0.1.onnx": [
225
+ 0.0046852827072143555
226
+ ],
227
+ "timer_v0.1.onnx": [
228
+ 0.9999986886978149,
229
+ 0.01402811985462904,
230
+ 0.009387511759996414,
231
+ 0.010385164059698582,
232
+ 0.012926612980663776,
233
+ 0.009387511759996414,
234
+ 0.03614084795117378
235
+ ],
236
+ "weather_v0.1.onnx": [
237
+ 0.31700584292411804
238
+ ]
239
+ },
240
+ "static_max_scores": {
241
+ "alexa_v0.1.onnx": [
242
+ 0.001211315393447876
243
+ ],
244
+ "hey_jarvis_v0.1.onnx": [
245
+ 0.018373221158981323
246
+ ],
247
+ "hey_mycroft_v0.1.onnx": [
248
+ 3.159046173095703e-06
249
+ ],
250
+ "hey_rhasspy_v0.1.onnx": [
251
+ 0.0046852827072143555
252
+ ],
253
+ "timer_v0.1.onnx": [
254
+ 0.9999986886978149,
255
+ 0.01402811985462904,
256
+ 0.009387511759996414,
257
+ 0.010385164059698582,
258
+ 0.012926612980663776,
259
+ 0.009387511759996414,
260
+ 0.036140866577625275
261
+ ],
262
+ "weather_v0.1.onnx": [
263
+ 0.31700563430786133
264
+ ]
265
+ },
266
+ "expected_model": null,
267
+ "expected_score": null,
268
+ "expected_detected": null,
269
+ "numerical_match": true,
270
+ "passed": true
271
+ }
272
+ ],
273
+ "passed": true
274
+ },
275
+ "sherpa": {
276
+ "sherpa_onnx_version": "1.13.4",
277
+ "variants": [
278
+ {
279
+ "chunk_size": 8,
280
+ "max_active_paths": 1,
281
+ "audio_count": 9,
282
+ "comparisons": [
283
+ {
284
+ "audio": "sherpa-onnx-kws-zipformer-zh-en-3M-2025-12-20/test_wavs/en_0.wav",
285
+ "source_detections": [
286
+ "LIGHT_UP"
287
+ ],
288
+ "static_detections": [
289
+ "LIGHT_UP"
290
+ ],
291
+ "source_decode_calls": 45,
292
+ "static_decode_calls": 45,
293
+ "match": true
294
+ },
295
+ {
296
+ "audio": "sherpa-onnx-kws-zipformer-zh-en-3M-2025-12-20/test_wavs/en_1.wav",
297
+ "source_detections": [
298
+ "LOVELY_CHILD"
299
+ ],
300
+ "static_detections": [
301
+ "LOVELY_CHILD"
302
+ ],
303
+ "source_decode_calls": 108,
304
+ "static_decode_calls": 108,
305
+ "match": true
306
+ },
307
+ {
308
+ "audio": "sherpa-onnx-kws-zipformer-zh-en-3M-2025-12-20/test_wavs/zh_0.wav",
309
+ "source_detections": [],
310
+ "static_detections": [],
311
+ "source_decode_calls": 39,
312
+ "static_decode_calls": 39,
313
+ "match": true
314
+ },
315
+ {
316
+ "audio": "sherpa-onnx-kws-zipformer-zh-en-3M-2025-12-20/test_wavs/zh_1.wav",
317
+ "source_detections": [],
318
+ "static_detections": [],
319
+ "source_decode_calls": 36,
320
+ "static_decode_calls": 36,
321
+ "match": true
322
+ },
323
+ {
324
+ "audio": "sherpa-onnx-kws-zipformer-zh-en-3M-2025-12-20/test_wavs/zh_2.wav",
325
+ "source_detections": [],
326
+ "static_detections": [],
327
+ "source_decode_calls": 32,
328
+ "static_decode_calls": 32,
329
+ "match": true
330
+ },
331
+ {
332
+ "audio": "sherpa-onnx-kws-zipformer-zh-en-3M-2025-12-20/test_wavs/zh_3.wav",
333
+ "source_detections": [
334
+ "法国"
335
+ ],
336
+ "static_detections": [
337
+ "法国"
338
+ ],
339
+ "source_decode_calls": 54,
340
+ "static_decode_calls": 54,
341
+ "match": true
342
+ },
343
+ {
344
+ "audio": "sherpa-onnx-kws-zipformer-zh-en-3M-2025-12-20/test_wavs/zh_4.wav",
345
+ "source_detections": [
346
+ "蒋友伯",
347
+ "女儿"
348
+ ],
349
+ "static_detections": [
350
+ "蒋友伯",
351
+ "女儿"
352
+ ],
353
+ "source_decode_calls": 32,
354
+ "static_decode_calls": 32,
355
+ "match": true
356
+ },
357
+ {
358
+ "audio": "sherpa-onnx-kws-zipformer-zh-en-3M-2025-12-20/test_wavs/zh_5.wav",
359
+ "source_detections": [
360
+ "周望军",
361
+ "落实"
362
+ ],
363
+ "static_detections": [
364
+ "周望军",
365
+ "落实"
366
+ ],
367
+ "source_decode_calls": 30,
368
+ "static_decode_calls": 30,
369
+ "match": true
370
+ },
371
+ {
372
+ "audio": "sherpa-onnx-kws-zipformer-zh-en-3M-2025-12-20/test_wavs/zh_6.wav",
373
+ "source_detections": [
374
+ "朱丽楠"
375
+ ],
376
+ "static_detections": [
377
+ "朱丽楠"
378
+ ],
379
+ "source_decode_calls": 26,
380
+ "static_decode_calls": 26,
381
+ "match": true
382
+ }
383
+ ],
384
+ "detected_audio_count": 6,
385
+ "passed": true
386
+ },
387
+ {
388
+ "chunk_size": 16,
389
+ "max_active_paths": 1,
390
+ "audio_count": 9,
391
+ "comparisons": [
392
+ {
393
+ "audio": "sherpa-onnx-kws-zipformer-zh-en-3M-2025-12-20/test_wavs/en_0.wav",
394
+ "source_detections": [],
395
+ "static_detections": [],
396
+ "source_decode_calls": 22,
397
+ "static_decode_calls": 22,
398
+ "match": true
399
+ },
400
+ {
401
+ "audio": "sherpa-onnx-kws-zipformer-zh-en-3M-2025-12-20/test_wavs/en_1.wav",
402
+ "source_detections": [
403
+ "LOVELY_CHILD"
404
+ ],
405
+ "static_detections": [
406
+ "LOVELY_CHILD"
407
+ ],
408
+ "source_decode_calls": 54,
409
+ "static_decode_calls": 54,
410
+ "match": true
411
+ },
412
+ {
413
+ "audio": "sherpa-onnx-kws-zipformer-zh-en-3M-2025-12-20/test_wavs/zh_0.wav",
414
+ "source_detections": [],
415
+ "static_detections": [],
416
+ "source_decode_calls": 19,
417
+ "static_decode_calls": 19,
418
+ "match": true
419
+ },
420
+ {
421
+ "audio": "sherpa-onnx-kws-zipformer-zh-en-3M-2025-12-20/test_wavs/zh_1.wav",
422
+ "source_detections": [],
423
+ "static_detections": [],
424
+ "source_decode_calls": 18,
425
+ "static_decode_calls": 18,
426
+ "match": true
427
+ },
428
+ {
429
+ "audio": "sherpa-onnx-kws-zipformer-zh-en-3M-2025-12-20/test_wavs/zh_2.wav",
430
+ "source_detections": [],
431
+ "static_detections": [],
432
+ "source_decode_calls": 16,
433
+ "static_decode_calls": 16,
434
+ "match": true
435
+ },
436
+ {
437
+ "audio": "sherpa-onnx-kws-zipformer-zh-en-3M-2025-12-20/test_wavs/zh_3.wav",
438
+ "source_detections": [
439
+ "法国"
440
+ ],
441
+ "static_detections": [
442
+ "法国"
443
+ ],
444
+ "source_decode_calls": 27,
445
+ "static_decode_calls": 27,
446
+ "match": true
447
+ },
448
+ {
449
+ "audio": "sherpa-onnx-kws-zipformer-zh-en-3M-2025-12-20/test_wavs/zh_4.wav",
450
+ "source_detections": [
451
+ "蒋友伯",
452
+ "女儿"
453
+ ],
454
+ "static_detections": [
455
+ "蒋友伯",
456
+ "女儿"
457
+ ],
458
+ "source_decode_calls": 16,
459
+ "static_decode_calls": 16,
460
+ "match": true
461
+ },
462
+ {
463
+ "audio": "sherpa-onnx-kws-zipformer-zh-en-3M-2025-12-20/test_wavs/zh_5.wav",
464
+ "source_detections": [
465
+ "周望军",
466
+ "落实"
467
+ ],
468
+ "static_detections": [
469
+ "周望军",
470
+ "落实"
471
+ ],
472
+ "source_decode_calls": 15,
473
+ "static_decode_calls": 15,
474
+ "match": true
475
+ },
476
+ {
477
+ "audio": "sherpa-onnx-kws-zipformer-zh-en-3M-2025-12-20/test_wavs/zh_6.wav",
478
+ "source_detections": [
479
+ "朱丽楠"
480
+ ],
481
+ "static_detections": [
482
+ "朱丽楠"
483
+ ],
484
+ "source_decode_calls": 13,
485
+ "static_decode_calls": 13,
486
+ "match": true
487
+ }
488
+ ],
489
+ "detected_audio_count": 5,
490
+ "passed": true
491
+ }
492
+ ],
493
+ "passed": true
494
+ },
495
+ "elapsed_seconds": 9.052458902820945,
496
+ "all_passed": true
497
+ }